diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7545987 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Kindle-side shell scripts are parsed by BusyBox/dash on-device, which chokes on +# embedded \r characters. Force LF regardless of a contributor's local +# core.autocrlf setting (Windows commonly defaults to true), otherwise scripts can +# silently pick up CRLF on checkout and fail on the Kindle with no error output at +# all — see WINDOWS_FIXES.md. +*.sh text eol=lf + +# SQL migrations get passed as raw file content to the InsForge CLI in some code +# paths; keep them LF for the same reason. +*.sql text eol=lf diff --git a/WINDOWS_FIXES.md b/WINDOWS_FIXES.md new file mode 100644 index 0000000..3fb7269 --- /dev/null +++ b/WINDOWS_FIXES.md @@ -0,0 +1,226 @@ + +# Windows Setup + Native Build Fixes + +Notes from getting this project fully working end-to-end on a Windows host, for a +Kindle Paperwhite 3 (armel/soft-float, firmware 5.13.7), intended as background for +a PR back to the upstream repo. Every problem found, its root cause, and the fix +applied — grouped by area. File paths are relative to the repo root. + +This file covers **kdashboard repo code/script bugs only** — things that belong in +a PR against this repo. The full end-to-end journey (jailbreaking the Kindle itself +with LanguageBreak/WatchThis, KUAL/MRPI install issues, InsForge account setup) is +documented separately in `../README.md` at the root of this checkout, since none of +that is upstream-repo-relevant except where it exposed an actual bug here. Two +things from that broader journey are worth cross-referencing because they're really +about *this* project, not the jailbreak itself: + +- **InsForge API key confusion** (see `../README.md`, Phase 3 progress log): the + `INSFORGE_API_KEY` the `telegram-webhook`/`health-sync` functions need via + `createAdminClient()` is a project-scoped **admin/service key** (`ik_`-prefixed, + found in the CLI's own `.insforge/project.json` after `create`/`link`) — not the + personal `uak_`-prefixed key from the InsForge web dashboard, nor the + `anon_`-prefixed public key written to `.env.local`. All three look superficially + similar and are easy to mix up; using the wrong one produces a generic + `{"error":"Function execution failed","message":"Invalid token"}` 500 with no + hint about *which* key is wrong. Worth a callout in `docs/INSTALL_FOR_USERS.md`. +- **`kit:backend`'s `db import` reliability** — see item #2 below, found while + running the documented `npm run kit:backend` setup step from + `docs/INSTALL_FOR_USERS.md`. + +## 1. Setup scripts assume a POSIX host + +`scripts/bootstrap-insforge-kit.mjs` and `scripts/configure-telegram.mjs` both shell +out to `npx @insforge/cli ...` via `execFileSync("npx", [...])`. This doesn't work on +Windows for several stacked reasons: + +- **`ENOENT`**: `npx` on Windows is a `.cmd` shim, not a directly-executable file. + `execFileSync` without `shell: true` can't find/run it. +- **`EINVAL`**: switching to `execFileSync("npx.cmd", ...)` directly (still without + `shell: true`) fails differently — recent Node versions (the CVE-2024-27980 + hardening) refuse to spawn `.cmd`/`.bat` files without going through a shell. +- **Argument mangling with `shell: true`**: once you add `shell: true`, Node just + concatenates `command + args.join(' ')` into a single string handed to `cmd.exe`. + Any argument containing spaces or newlines — a `--name "Kindle Dashboard Data"` + flag, or worse, an entire multi-line SQL migration file passed as a single CLI + arg — gets silently split into multiple broken arguments by cmd.exe's naive + parsing. This produced very confusing downstream errors ("too many arguments for + 'query'. Expected 1 argument but got 7", "too many arguments for 'deploy'. + Expected 1 argument but got 3"). + +**Fix**: install `@insforge/cli` as a real local dependency +(`npm install --no-save @insforge/cli`) and invoke its JS entry point directly via +`execFileSync(process.execPath, [cliEntryPath, ...args])`. This is `node.exe` calling +a `.js` file with a plain argument array — no shell, no `.cmd` resolution, no +argument-string reassembly, and it's fully cross-platform. See the `run()` helper in +both scripts. + +## 2. `db import` crashes natively on Windows + +`applyMigration()` originally used `db import `. On the recipes migration +specifically (`migrations/20260629052000_create-recipes.sql`), the CLI printed +`Error: INVALID_INPUT` immediately followed by a native crash: + +``` +Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 94 +``` + +That's a libuv-level assertion failure inside the CLI's own bundled Node runtime — +a real bug in `@insforge/cli db import` on Windows, not something fixable from the +calling script. The crash occurs after the migration is at least partially applied +(the recipe/ingredient rows had already been inserted), so retrying `db import` +after the crash immediately fails again with a duplicate-key conflict, requiring a +manual cleanup query. + +**Fix**: switched `applyMigration()` back to `db query ` instead of `db import `. This works reliably once the +shell-quoting problem above is fixed (a multi-line SQL string as one array element +passed straight to `execFileSync` is completely safe without a shell in the loop). +`db import` may well be fine on Mac/Linux; this is a Windows-specific workaround, +not a claim that `db import` is broadly broken. + +## 3. `M_PI` undefined (native C++ build) + +`kindle/native/src/kindle_dashboard.cpp` uses `M_PI` (for the circular progress-ring +drawing) without defining it. `M_PI` is a POSIX/glibc extension to ``, not +part of the C++ standard — it happens to be defined on most Linux/Mac toolchains by +default, but isn't guaranteed, and isn't defined when compiling with Zig's bundled +libc++ (used for cross-compiling to the Kindle's `arm-linux-musleabi` target from a +non-Linux host). Build fails with `use of undeclared identifier 'M_PI'`. + +**Fix**: added a portable guard right after `#include `: + +```cpp +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +``` + +## 4. `msync(..., MS_SYNC)` hangs forever on this Kindle's fbdev driver + +This was the big one — found via bisection logging, not static reading. Symptom: +after a factory-fresh jailbreak + KUAL install, running the dashboard ("Refresh +Once" or "Start Dashboard") would fetch data and start rendering successfully, but +the screen would go fully unresponsive and never actually show the new content — +touch input got grabbed (`EVIOCGRAB`) and never released, so even the Kindle's own +UI stopped responding until a hard power-cycle. + +The native log showed execution reaching `render=save-pgm ...` and then stopping +cold — no `render=framebuffer ok` line ever appeared, across multiple clean +reproductions. + +**Debugging approach**: added `fprintf(stderr, ...); fflush(stderr);` calls around +every remaining step in `renderToFramebuffer()` after the pixel-writing loop +(pixel-loop start/done, msync start/done, eips start/done), rebuilt, redeployed, +reproduced. Output pinpointed it exactly: + +``` +debug=pixel-loop-done +debug=msync-start +``` + +`debug=msync-done` never printed. `msync(fb, screensize, MS_SYNC)` — called on the +`mmap`'d `/dev/fb0` region — blocks indefinitely on this device's framebuffer +driver. + +**Root cause**: `msync` is designed to flush *file-backed* mmap pages to disk. It's +unnecessary for a *device-backed* mapping like a framebuffer — writes to a mmap'd +`/dev/fb0` take effect immediately (the "backing store" is the hardware itself, not +a file that needs syncing), and this particular fbdev driver evidently doesn't +implement the sync callback correctly, so the call just blocks forever instead of +either working or returning an error. + +**Fix**: removed the `msync` call entirely from `renderToFramebuffer()`. Kept +`munmap`/`close`/the `eips ''` refresh trigger immediately after. Retested clean: +`render=framebuffer ok ... ms=750`, and the device stayed fully responsive +afterward (KUAL menu navigable again, "Stop Dashboard" reachable). + +This is very likely device/driver-specific (older armel Kindles vs. newer armhf +ones may behave differently here), so worth keeping an eye on whether this +regresses anything on other hardware — but `msync` on a framebuffer mmap is +unusual/unnecessary in general, so removing it should be safe everywhere. + +## 5. Dark mode only inverted embedded images, not the dashboard itself + +`--invert-images` (wired up via `DASHBOARD_FORCE_INVERT_IMAGES` in +`start-dark.sh`/`once-dark.sh`) only affected the flip-decision for specific loaded +bitmap assets (`g_invert_images` gates a check inside the image-loading path). The +actual canvas — background, text, card borders — was never inverted, so "dark mode" +had no visible effect on the overall UI; it just looked identical to light mode. + +**Fix**: added a full-canvas inversion pass, gated on the same `g_invert_images` +flag, applied right after `drawCurrentDashboard()` completes and before the canvas +gets written out (both in `renderToFramebuffer()` and the `dumpBitmapPreview()` +fallback path used for local/fixture rendering): + +```cpp +void invertCanvasIfDark(Canvas* canvas) { + if (!g_invert_images || !canvas->pixels) return; + const size_t total = static_cast(canvas->width) * static_cast(canvas->height); + for (size_t i = 0; i < total; i++) canvas->pixels[i] = static_cast(255 - canvas->pixels[i]); +} +``` + +## 6. No `.gitattributes` — Windows checkouts silently get CRLF shell scripts + +The repo has no `.gitattributes` file. The stored blobs for +`kindle/kual/kindle-dashboard/bin/*.sh` are LF (verified: `git diff` against origin +shows zero content difference after normalizing a local checkout back to LF), but +on a Windows machine with the common `core.autocrlf=true` git setting, `git +checkout` silently converts them to CRLF in the working tree — with no warning, +since that's exactly what `autocrlf=true` is documented to do. Whatever gets copied +from that working tree to the Kindle then has CRLF. + +KUAL invokes these scripts via an explicit `/bin/sh ` (per `menu.json`), so +the scripts' own missing execute bit isn't the issue — `/bin/sh` (BusyBox/dash on +the Kindle) just chokes on the embedded `\r` characters. The failure is completely +silent: no error dialog, no log output at all (not even the first line of the +script, a plain `echo >> logfile`), the KUAL action just does nothing. Easy to +misdiagnose as a FAT/exFAT permissions problem (a very reasonable first guess, +since the Kindle's USB-exposed filesystem genuinely has no Unix permission bits) +but it isn't — it's purely the line-ending content, and it's entirely dependent on +the *contributor's own local git config*, not anything in the repo itself. + +**Fix**: added `.gitattributes` at the repo root, pinning `*.sh` (and `*.sql`, for +the same reason — see #2) to `eol=lf` regardless of a contributor's `core.autocrlf` +setting. This is the actual fix — it means no Windows contributor hits this again, +rather than relying on everyone individually knowing to run +`sed -i 's/\r$//' kindle/kual/kindle-dashboard/bin/*.sh` after every checkout. + +## 7. A second, duplicated `msync` hang — this time on every tap + +After fixing #4, "Refresh Once" worked perfectly, but interactive taps in "Start +Dashboard" mode still did nothing after the first one. `pollExitTouch()` in the +touch-watcher thread kept logging `input=action touch action=N x=.. y=..` +(confirming `applyTouchWithDebounce()` was matching regions and setting +`g_pending_action` correctly), but `handlePendingTouch()`'s own per-action log +lines (`touch=open-list`, `touch=exit`, etc.) never appeared after the very first +tap — meaning the function wasn't being re-entered, not just failing silently. + +Added temporary trace logging at the top of the main loop and at the entry of +`waitForWakeEvent()` (`debug=main-loop-top pending_action=.. event_refresh=..` / +`debug=wait-enter seconds=..`) to confirm `g_pending_action` really was being set +correctly by the touch thread. It was — and `handlePendingTouch()` visibly started +running each time (`showTouchVisualFeedback()`'s `visual-feedback=tap ...` line +printed every time), but execution stopped dead immediately after that, before +reaching any of the action-specific branches. + +**Root cause**: the exact same bug as #4, duplicated in a second location. +`showTouchVisualFeedback()` calls `flashTouchRectOnFramebuffer()` (when a tap +region has a highlight rect configured) to draw a brief invert-flash at the tapped +location — and that function has its own independent pair of +`msync(fb, screensize, MS_SYNC)` calls on the same device-backed `/dev/fb0` mmap, +which hang indefinitely for the same reason as #4. The very first tap in a session +happened to hit a region with no flash rect configured, so it worked; every tap +after that (all the real interactive regions — list cards, exit, nav) went through +`flashTouchRectOnFramebuffer()` and hung there, permanently blocking the thread +that owns `g_running`/rendering from ever getting back to the main loop. + +**Fix**: removed both `msync` calls from `flashTouchRectOnFramebuffer()`, same +reasoning as #4. Grepped the whole file afterward to confirm no other `msync` calls +remain. Removed the temporary trace logging once confirmed. + +**Lesson for upstream**: `msync` isn't needed anywhere in this codebase for the +`/dev/fb0` mmap — it appears twice, both times paired with `munmap`/`close`/an +`eips` refresh call directly afterward, and neither call site needs the explicit +sync. Worth a quick search for the string `msync` if this pattern gets copied +elsewhere in the future. diff --git a/journey.md b/journey.md new file mode 100644 index 0000000..f31bcd0 --- /dev/null +++ b/journey.md @@ -0,0 +1,226 @@ +# Kindle Paperwhite 7th Gen — Jailbreak + Custom Dashboard Journey + +A writeup of jailbreaking a Kindle Paperwhite 7th generation (2015, PW3, codename +"wario") and installing [kdashboard](https://github.com/thecodedose/kdashboard), a +native C++ e-ink planner dashboard that polls a self-hosted backend and updates via +a Telegram bot, via KUAL. Documented as a reference for anyone doing the same thing +on similar hardware — especially the parts that went wrong and why, since most +existing guides only cover the happy path. + +## Device Specs + +- **Model**: Kindle Paperwhite 7th Generation (Amazon's own generation numbering) — + this is the 2015 "Paperwhite 3", 300ppi, codename **PW3 / "wario"** in the + jailbreak community's device naming. +- **Firmware at start**: 5.13.7 +- **Architecture**: armel (soft-float ARM) — this matters a lot; see below. + +## Picking a Jailbreak Method + +The jailbreak landscape for older Kindles is a patchwork of firmware-version-gated +exploits, each covering a narrow range: + +| Method | Firmware range | Notes | +|---|---|---| +| Popcorn | any (hardware) | Requires opening the case and soldering a wire between a component pad and a test point to force the SoC into USB recovery mode. Works regardless of firmware but is a real teardown-and-solder job. | +| KindleBreak | 5.10.3–5.13.3 | Software-only | +| WatchThis | 5.12.2.2, 5.13.4–5.14.2 | Software-only, ships exact firmware-matched payloads | +| LanguageBreak | 5.14.3–5.16.2.1.1 | Software-only, most commonly referenced online | + +**First attempt: LanguageBreak.** Most current writeups point at this one, since +it's actively maintained and covers a wide firmware ceiling. Went through the full +process — demo mode entry, sideloading the exploit payload, the Chinese-language-pack +trigger trick, hotfix application — and it consistently failed with **"Update error: +007"** on the hotfix step, no matter how carefully each step was redone (verified via +SHA256 hashes that files weren't corrupted, verified firmware hadn't drifted, redid +the whole sequence twice with extra care). + +Read the actual exploit script (`jb`) to see what it does on execution: the very +first things it does are `touch /mnt/us/LanguageBreakRan` and write a log file, both +on the USB-visible partition. **Neither file ever existed on the device, across every +attempt.** That's conclusive: the exploit script itself was never executing — the +underlying vulnerability trigger just wasn't firing on this specific device/firmware, +so every hotfix attempt afterward was always doomed regardless of how carefully it +was retried. + +**Root cause, found afterward**: firmware 5.13.7 is actually *below* +LanguageBreak's supported floor (5.14.3+). The correct method for this exact +firmware is **WatchThis**, which — bonus — ships an *exact* firmware-matched +payload (a `PW3-5.13.7.zip` specifically, not a generic exploit spanning a firmware +range), a much better sign of fit than LanguageBreak's broader-but-wrong-floor +coverage. + +## WatchThis: What Actually Worked + +The WatchThis process differs from LanguageBreak in a few structural ways: + +- Starts with an actual **factory reset**, with locale explicitly set to + **en_GB / English (United Kingdom)** — not a from-normal-English demo-mode entry. + (The setup wizard doesn't show "en_GB" as a language option directly; the region + step appears *after* language selection, and picking English + United Kingdom + region there is what produces the en_GB locale.) +- Exploit payload files go into a `.demo` folder created at the Kindle root + (`.demo/PW3-5.13.7.zip` + `.demo/demo.json` + an empty `.demo/goodreads/`), not + directly at the root like LanguageBreak. +- Trigger is **Settings → Help & User Guides → Get started**, not a + language-selection screen trick. +- The same two-finger-tap-and-swipe gesture and `;demo`/`;uzb`/`;dsts` search-bar + diagnostic commands carry over from the general Kindle jailbreak toolkit. + +### Notable snags along the way + +- **Frozen "demonstration device missing content" screen with an unresponsive + "Configure Device" button.** This shows up whenever demo mode can't reach the + network to sync content, and the standard "just tap the button" recovery doesn't + always work. The actual fix: a precise gesture — **two-finger tap on the right + edge of the white message box** (not just anywhere on screen), release both + fingers, then a **single-finger swipe from right to left**. This is + under-documented and easy to miss; the more commonly cited "bottom-right corner" + gesture location is imprecise. +- **"Application error, application could not be started" exiting the demo menu.** + Documented in WatchThis's own troubleshooting notes: hard-reboot, re-enter the + demo menu, select Sideload Content → Done again *without* USB connected this + time, then retry exiting. +- **The hotfix install actually succeeded** on the first real attempt — this was + the exact step that failed three times running under LanguageBreak, which in + hindsight was the clearest confirmation that WatchThis was the right method for + this firmware all along. +- **Jailbreak confirmed**: after the hotfix installed and the device rebooted back + to a normal home screen, checked the filesystem for the standard community + jailbreak markers (`mkk`, `libkh`, `rp` folders in the root) — all present. + +## KUAL + MRPI Install + +Getting the app launcher (KUAL) working took longer than the jailbreak itself, for +three independent reasons stacked on top of each other: + +1. **Skipped a required prerequisite.** The documented post-jailbreak sequence is + actually *(1) install a general "Universal Hotfix", (2) then install KUAL/MRPI* — + easy to miss and jump straight to step 2. KUAL's installer explicitly requires a + specific hotfix to have been applied first; the jailbreak's own hotfix (which + just gets the device out of demo mode) is a separate thing and doesn't provide + this. + +2. **Hotfix version mismatch.** After installing the Universal Hotfix, KUAL + appeared in the library but crashed immediately on launch + ("Application error — the selected application could not be started"). The + installer's own documentation specifies a *maximum* supported hotfix version — + installing the *latest* release instead of that pinned version broke launch + compatibility specifically for armel devices (older Kindles, pre the + hard-float architecture cutover introduced partway through the Kindle product + line). The hotfix project's own release history around that version boundary + has commit/release messages that are basically a live diary of this exact bug + getting fixed — a strong signal in hindsight. Reinstalling the specific pinned + version instead of "latest" fixed it immediately. + +3. **Stale library registration.** There's a known class of bug where a factory + reset wipes the app registry but leaves a previously-installed app's binary + sitting in the rootfs — the launcher then sees the leftover binary, assumes the + app is already installed, and tries to launch a registry entry that doesn't + exist. Worth knowing about even though it turned out not to be the actual cause + here (the version mismatch above was the real fix). + +After KUAL was working, installed the OTA-blocking extension via KUAL's menu +(rename the update binaries) so the firmware stays locked at a jailbreak-compatible +version and won't silently update itself out from under the setup. Once that's +done, it's safe to turn Wi-Fi back on — the device connects normally but won't +download/install OTA updates. + +## Installing the Dashboard Itself + +kdashboard isn't just a KUAL app — it's a bring-your-own-backend kit: you run your +own instance of a self-hosted Postgres-backed platform for the database and edge +functions, connect your own Telegram bot, and cross-compile a native C++ binary for +the Kindle's ARM architecture. + +High-level flow: +1. Clone the repo, install dependencies, log in and create/link your own backend + project. +2. Bootstrap the schema, secrets, and edge functions. +3. Add backend secrets (base URL + an admin-level API key — see gotcha below). +4. Connect a Telegram bot (create one via BotFather, discover your chat ID, + register a webhook). +5. Cross-compile the native KUAL package for the Kindle's ARM target. +6. Copy the built package onto the device and configure it. +7. Launch via the KUAL menu. + +### Setting this up on Windows surfaced several real bugs + +None of this was tested on Windows upstream, so getting a bring-your-own-backend +project like this fully working from a Windows machine turned up a handful of +genuine, fixable bugs — in the setup tooling, in the native C++ source, and in one +case in a third-party CLI dependency. All of these got written up in detail and +contributed back upstream as a pull request against the dashboard project, so +they're documented there rather than duplicated here. Summary of what was found: + +- The setup scripts' calls out to the backend CLI didn't work at all on Windows — + three different, stacked failure modes depending on how the child-process call + was structured (missing-shell resolution failures, a Node.js security hardening + change that blocks a naive fix, and silent argument corruption once you work + around that). Fixed by invoking the CLI's own JS entry point directly instead of + going through a shell at all. +- The backend CLI itself has a native crash bug on Windows when importing one + particular SQL migration file — worked around by using a different (but + equivalent) CLI subcommand. +- The native C++ dashboard source used a POSIX-only math constant that isn't + guaranteed by the C++ standard, breaking the cross-compile. +- **The big one**: after getting a clean build onto the device, the dashboard would + render once and then the screen would go completely unresponsive — touch input + got grabbed and never released. Root-caused via targeted logging (not just + reading the code) to a specific system call used to flush a memory-mapped + framebuffer to the display, which hangs indefinitely on this particular Kindle's + display driver. It's meant for syncing *file*-backed memory mappings to disk and + isn't actually needed for a *device*-backed framebuffer mapping at all — removing + it fixed rendering entirely. The exact same bug turned out to be duplicated in a + second function (the tap visual-feedback effect), which is why the very first tap + in any session would work but every tap after that would silently do nothing. +- Dark mode had no visible effect — it only inverted specific embedded images, never + the actual screen background/text. Added a proper full-canvas inversion pass. +- The repo had no `.gitattributes`, so any Windows contributor with the common + `core.autocrlf=true` git setting would silently get Windows-style line endings + checked out into shell scripts meant to run on the Kindle — which then fail + completely silently on-device (no error, no log output at all), very easy to + misdiagnose as a filesystem permissions problem instead of what it actually is. + +### A gotcha worth flagging explicitly: API key confusion + +The dashboard's backend functions need an **admin/service-level** API key to write +to the database directly, server-side. It's easy to accidentally grab the wrong +kind of key — a personal account-level key from the web dashboard, or the public +anonymous key auto-written to a local env file — either of which produces a generic +"invalid token" error with no indication of *which* key is wrong. The correct key +is the project-scoped admin key that the CLI tool itself uses internally (found in +its own local project config file after linking a project), not anything you'd +naturally reach for from the web dashboard UI. + +## General Gotchas / Lessons + +- **USB Drive Mode shows any time the Kindle is plugged in** — it does *not* mean + the device is in some special file-staging mode. Don't assume anything about + device state just because a drive letter appeared. +- **The Kindle's search bar accepts special diagnostic commands** even though the + UI presents it as a book/store search box — this is normal, long-standing Kindle + jailbreak-community behavior, not a bug or a coincidence. +- **Airplane Mode needs to stay on throughout the jailbreak process** to prevent an + OTA update from silently patching out the vulnerability you're relying on. Only + turn it back on after OTA updates have been explicitly disabled. +- When a documented process specifies a hotfix/tool *maximum* version, actually use + that version — grabbing "latest" by default is a reasonable instinct that broke + things twice in this project alone. +- If something fails identically no matter how carefully you retry the same steps, + stop retrying and go read the actual script/binary being invoked. In both major + blockers here (the jailbreak exploit never firing, and the dashboard render hang), + the fix only came from checking what the code *actually* does at the point of + failure rather than continuing to redo the documented process more carefully. + +## Credits / Tools Used + +- [LanguageBreak](https://github.com/notmarek/LanguageBreak) +- [WatchThis](https://kindlemodding.org/jailbreaking/Legacy/WatchThis/) (via + [kindlemodding.org](https://kindlemodding.org)) +- [PEKI](https://github.com/KindleTweaks/PEKI) (KUAL installer) +- [MRInstaller / MRPI](https://kindlemodding.org) +- [Universal Hotfix](https://github.com/KindleModding/Hotfix) +- [kdashboard](https://github.com/thecodedose/kdashboard) +- [Zig](https://ziglang.org) (used for cross-compiling the native ARM binary from + Windows, no separate toolchain needed) diff --git a/kindle/native/src/kindle_dashboard.cpp b/kindle/native/src/kindle_dashboard.cpp index 9bf5d64..e14c6e9 100644 --- a/kindle/native/src/kindle_dashboard.cpp +++ b/kindle/native/src/kindle_dashboard.cpp @@ -6,6 +6,9 @@ #include #include #include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif #include #include #include @@ -164,6 +167,12 @@ struct Canvas { unsigned char* pixels; }; +void invertCanvasIfDark(Canvas* canvas) { + if (!g_invert_images || !canvas->pixels) return; + const size_t total = static_cast(canvas->width) * static_cast(canvas->height); + for (size_t i = 0; i < total; i++) canvas->pixels[i] = static_cast(255 - canvas->pixels[i]); +} + struct Rect { int x; int y; @@ -2306,11 +2315,9 @@ void flashTouchRectOnFramebuffer(Rect rect) { const int right = rect.x + rect.w > static_cast(vinfo.xres) ? static_cast(vinfo.xres) : rect.x + rect.w; const int bottom = rect.y + rect.h > static_cast(vinfo.yres) ? static_cast(vinfo.yres) : rect.y + rect.h; invertFramebufferArea(fb, &vinfo, &finfo, left, top, right, bottom, 0); - msync(fb, screensize, MS_SYNC); system("eips '' >/dev/null 2>&1 || true"); usleep(120000); invertFramebufferArea(fb, &vinfo, &finfo, left, top, right, bottom, 0); - msync(fb, screensize, MS_SYNC); munmap(fb, screensize); close(fd); system("eips '' >/dev/null 2>&1 || true"); @@ -2356,6 +2363,7 @@ int renderToFramebuffer(const Dashboard* dashboard, const char* status, const ch return 0; } drawCurrentDashboard(&canvas, dashboard, status); + invertCanvasIfDark(&canvas); if (save_pgm && save_pgm[0]) { writePgm(save_pgm, &canvas); fprintf(stderr, "render=save-pgm %s width=%d height=%d\n", save_pgm, canvas.width, canvas.height); @@ -2364,7 +2372,6 @@ int renderToFramebuffer(const Dashboard* dashboard, const char* status, const ch for (int x = 0; x < canvas.width; x++) putFramebufferPixel(fb, &vinfo, &finfo, x, y, canvas.pixels[y * canvas.width + x]); } free(canvas.pixels); - msync(fb, screensize, MS_SYNC); munmap(fb, screensize); close(fd); system("eips '' >/dev/null 2>&1 || true"); @@ -3000,6 +3007,7 @@ int dumpBitmapPreview(const Dashboard* dashboard, const char* status, const char canvas.pixels = static_cast(calloc(static_cast(canvas.width) * static_cast(canvas.height), 1)); if (!canvas.pixels) return 0; drawCurrentDashboard(&canvas, dashboard, status); + invertCanvasIfDark(&canvas); const int ok = writePgm(path, &canvas); free(canvas.pixels); return ok; @@ -3079,7 +3087,7 @@ int waitForWakeEvent(const Options* options, int seconds, int allow_repaint) { continue; } if (g_event_refresh) { - fprintf(stderr, "events=refresh_now\n"); + fprintf(stderr, "events=refresh_now elapsed=%d\n", elapsed); return 1; } if (allow_repaint && shouldRepaintCachedTick(elapsed)) { diff --git a/scripts/bootstrap-insforge-kit.mjs b/scripts/bootstrap-insforge-kit.mjs index 992cffd..bf82aba 100644 --- a/scripts/bootstrap-insforge-kit.mjs +++ b/scripts/bootstrap-insforge-kit.mjs @@ -1,6 +1,9 @@ import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { randomBytes } from "node:crypto"; +import { fileURLToPath } from "node:url"; + +const cliEntry = fileURLToPath(new URL("../node_modules/@insforge/cli/dist/index.js", import.meta.url)); const schemaMigrations = [ "migrations/001_planner_lists.sql", @@ -98,9 +101,7 @@ function randomSecret() { } function run(args, options = {}) { - const output = execFileSync("npx", ["@insforge/cli", ...args], { - encoding: "utf8", - stdio: options.silent ? ["ignore", "pipe", "pipe"] : "inherit" - }); + const stdio = options.silent ? ["ignore", "pipe", "pipe"] : "inherit"; + const output = execFileSync(process.execPath, [cliEntry, ...args], { encoding: "utf8", stdio }); return output || ""; } diff --git a/scripts/configure-telegram.mjs b/scripts/configure-telegram.mjs index 9d6cf3a..01eca4f 100644 --- a/scripts/configure-telegram.mjs +++ b/scripts/configure-telegram.mjs @@ -1,4 +1,7 @@ import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const cliEntry = fileURLToPath(new URL("../node_modules/@insforge/cli/dist/index.js", import.meta.url)); const args = new Map(); for (let index = 2; index < process.argv.length; index += 2) { @@ -69,8 +72,5 @@ function fetchSync(url, body) { } function run(args) { - return execFileSync("npx", ["@insforge/cli", ...args], { - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"] - }); + return execFileSync(process.execPath, [cliEntry, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); }