Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ concurrency:

jobs:
tools:
name: fetch + games + lazybox + science
name: fetch + games + lazybox + science + screensaver
runs-on: ubuntu-latest
timeout-minutes: 15

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ path = "src/bin/lazybox.rs"
name = "science"
path = "src/bin/science.rs"

[[bin]]
name = "screensaver"
path = "src/bin/screensaver.rs"

[dependencies]
crossterm = { version = "=0.29.0", default-features = false, features = ["events", "windows"] }

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Small native terminal tools with one job each:
- `games` is a native clean-room terminal arcade with eleven complete game modes.
- `lazybox` is one direct dashboard for Docker containers, Apple containers, and Slurm jobs.
- `science` explores planetary orbits, VCD waveforms, and PLY or `.splat` point clouds offline.
- `screensaver` animates six owned terminal scenes, including local Git history and GIF files.

The binaries never download or install software. `games` and `lazybox` use
Crossterm for raw keyboard input and terminal restoration; no upstream TUI,
Expand Down Expand Up @@ -34,6 +35,7 @@ cargo build --release --locked
./target/release/games
./target/release/lazybox
./target/release/science
./target/release/screensaver
```

Useful non-interactive forms:
Expand All @@ -49,6 +51,9 @@ lazybox open --backend docker
science snapshot orbit --json
science open wave capture.vcd
science open cloud scan.ply
screensaver snapshot rain --seed 1 --json
screensaver open git .
screensaver open gif animation.gif
```

`NO_COLOR=1` disables decoration. Interactive play needs a real terminal and
Expand Down Expand Up @@ -85,6 +90,22 @@ large clouds use a deterministic sample of at most 60,000 points for drawing
without weakening full-file bounds and centroid statistics. No source asset,
network service, serial driver, subprocess, or upstream executable is bundled.

## Native screensaver

`screensaver` owns four generated scenes—digital rain, wrapping pipes, a frog
pond, and offline weather—plus a bounded Git-history view and local GIF87a/89a
playback. Generated snapshots are deterministic by seed. Git mode reads one
64-commit batch through the installed `git` command with the same timeout and
output limits as `lazybox`; GIF mode contains a bounded block parser and
fixed-table LZW decoder capped at 64 MiB input, 512 frames, and 16 million
retained terminal-render pixels, with at most 128 million decoded source
pixels. It does not use Pillow, ffmpeg, a browser, a weather API, a
media cache, or copied upstream art.

Interactive output updates only changed terminal rows, supports resize and
`NO_COLOR`, and can be slowed to one frame per second with `--fps 1`. Plain and
JSON snapshots provide a non-animated alternative.

## Scope

`fetch` has detailed native collection on macOS and Linux and a smaller
Expand All @@ -97,6 +118,8 @@ measures native CLI startup and package size without pretending backend latency
belongs to the dashboard.
The [science benchmark](benches/science.md) measures an offline orbit snapshot
and binary size; file parsing latency remains input-dependent.
The [screensaver benchmark](benches/screensaver.md) measures one deterministic
generated snapshot and binary size; Git and GIF latency remains input-dependent.

## Project

Expand Down
8 changes: 6 additions & 2 deletions benches/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ fetch="$project/target/release/fetch"
games="$project/target/release/games"
lazybox="$project/target/release/lazybox"
science="$project/target/release/science"
screensaver="$project/target/release/screensaver"

command -v hyperfine >/dev/null 2>&1 || {
echo "benchmarks: install hyperfine 1.20.0" >&2
exit 127
}
for binary in "$fetch" "$games" "$lazybox" "$science"; do
for binary in "$fetch" "$games" "$lazybox" "$science" "$screensaver"; do
[[ -x "$binary" ]] || {
echo "benchmarks: run cargo build --release --locked first" >&2
exit 2
Expand All @@ -29,12 +30,14 @@ hyperfine --shell=none --warmup "$warmup" --runs "$runs" \
--command-name fetch "$fetch --plain" \
--command-name games "$games list --plain" \
--command-name lazybox "$lazybox --version" \
--command-name science "$science snapshot orbit --plain"
--command-name science "$science snapshot orbit --plain" \
--command-name screensaver "$screensaver snapshot rain --seed 1 --frame 24 --plain"

fetch_size=$(wc -c < "$fetch" | tr -d ' ')
games_size=$(wc -c < "$games" | tr -d ' ')
lazybox_size=$(wc -c < "$lazybox" | tr -d ' ')
science_size=$(wc -c < "$science" | tr -d ' ')
screensaver_size=$(wc -c < "$screensaver" | tr -d ' ')
{
echo "## Keys Tools benchmarks"
echo
Expand All @@ -50,6 +53,7 @@ science_size=$(wc -c < "$science" | tr -d ' ')
echo "| games | $games_size |"
echo "| lazybox | $lazybox_size |"
echo "| science | $science_size |"
echo "| screensaver | $screensaver_size |"
echo
echo "Hyperfine $runs runs after $warmup warmups; hosted-runner results are informational."
} > "$output/summary.md"
30 changes: 30 additions & 0 deletions benches/screensaver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Screensaver benchmark

The benchmark measures `screensaver snapshot rain --seed 1 --frame 24
--plain`, a deterministic owned render with no terminal, file, network, cache,
or subprocess dependency. Git and GIF latency depends on input size and is not
used as a general startup claim.

Measured 2026-08-17 on Darwin 25.5.0 ARM64 with Rust 1.97.1 and Hyperfine
1.20.0, using 100 runs after 20 warmups:

| Command | Mean ± standard deviation | Range | Stripped binary |
| --- | ---: | ---: | ---: |
| `screensaver snapshot rain --seed 1 --frame 24 --plain` | 1.9 ± 0.2 ms | 1.6–2.5 ms | 439,264 bytes |

This is an owned startup measurement, not a claim that one static rain frame
is equivalent to an upstream interactive workload.

The macOS ARM64 wheel is 1,101,605 bytes for all five binaries, package
metadata, project and dependency licenses, and the CycloneDX SBOM.

Run the same benchmark used by CI:

```sh
cargo build --release --locked
RUNS=100 WARMUP=20 bash benches/run.sh
```

Hyperfine JSON, Markdown, and stripped binary sizes are written below ignored
`target/benchmarks/`. Record a same-host fixture, dimensions, frame count,
median, and spread before making a Git- or GIF-performance claim.
12 changes: 10 additions & 2 deletions docs/research.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Terminal tool research

Reviewed 2026-08-12 through 2026-08-14 from the requested repositories. The
Reviewed 2026-08-12 through 2026-08-17 from the requested repositories. The
local `research/` directory contains shallow clones at the revisions below and
is intentionally ignored by Git. Category and license describe the upstream
project; no source or assets are copied into Keys Tools.
Expand All @@ -15,7 +15,7 @@ project; no source or assets are copied into Keys Tools.
| [cloudflare-speed-cli](https://github.com/kavehtehrani/cloudflare-speed-cli) | `89c2b2afa064` | speed test | Rust | GPL-3.0 |
| [astroterm](https://github.com/da-luce/astroterm) | `5c571959dbd7` | planetarium | C | MIT |
| [gitlogue](https://github.com/unhappychoice/gitlogue) | `1e59c4feafc3` | Git animation | Rust | ISC |
| [neo](https://github.com/st3w/neo) | `6ba93ac630b0` | screensaver | C++ | no license detected |
| [neo](https://github.com/st3w/neo) | `6ba93ac630b0` | screensaver | C++ | GPL-3.0 |
| [rxpipes](https://github.com/inunix3/rxpipes) | `d99c35f2d1d5` | screensaver | Rust | MIT |
| [weathr](https://github.com/veirt/weathr) | `7d403a0c4039` | weather TUI | Rust | GPL-3.0 |
| [pond](https://gitlab.com/alice-lefebvre/pond) | `6f7620940d89` | simulation | C | GPL-3.0-or-later |
Expand Down Expand Up @@ -66,6 +66,14 @@ project; no source or assets are copied into Keys Tools.
PLY/`.splat` point clouds. `tuihub`'s todo list duplicates no science workload,
while live DSN, plugins, and serial-port control would add network or driver
stacks, so they are excluded from this offline binary.
- `screensaver` owns four generated scenes plus bounded local Git-history and
GIF87a/89a views. Its Git path uses the installed `git` command's
[documented bounded pretty format](https://git-scm.com/docs/pretty-formats.html);
its GIF parser follows the
[GIF89a block and LZW specification](https://www.w3.org/Graphics/GIF/spec-gif89a.txt).
It does not copy upstream art or code, and it excludes live weather,
geolocation, Tenor, URLs, videos, exports, plugins, and caches because those
require unrelated privacy, TLS, codec, persistence, or subprocess surfaces.
- Crossterm is the only runtime dependency and owns cross-platform raw input,
resize events, alternate-screen entry, and terminal restoration. Ratatui is
excluded because these fixed layouts do not need a widget or layout engine.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
authors = [{ name = "Keys" }]
keywords = ["astronomy", "cli", "containers", "games", "point-cloud", "slurm", "system-information", "terminal", "vcd", "waveform"]
keywords = ["astronomy", "cli", "containers", "games", "gif", "point-cloud", "screensaver", "slurm", "system-information", "terminal", "vcd", "waveform"]
classifiers = [
"Environment :: Console",
"Programming Language :: Rust",
Expand Down
1 change: 1 addition & 0 deletions scripts/smoke-wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ fi
"$bin_dir/games$suffix" list --json
"$bin_dir/lazybox$suffix" --version
"$bin_dir/science$suffix" snapshot orbit --json
"$bin_dir/screensaver$suffix" snapshot rain --seed 1 --json
9 changes: 9 additions & 0 deletions src/bin/screensaver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn main() {
match keys_tools::screensaver::run(std::env::args().skip(1).collect()) {
Ok(code) => std::process::exit(code),
Err(error) => {
eprintln!("screensaver: {error}");
std::process::exit(2);
}
}
}
4 changes: 2 additions & 2 deletions src/lazybox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ fn load(backend: Backend) -> Result<Snapshot, String> {
Ok(Snapshot { backend, rows })
}

fn command_output(
pub(crate) fn command_output(
program: &str,
arguments: &[&str],
include_stderr: bool,
Expand Down Expand Up @@ -356,7 +356,7 @@ fn parse_slurm(output: &str) -> Vec<Row> {
.collect()
}

fn clean(value: &str) -> String {
pub(crate) fn clean(value: &str) -> String {
value
.chars()
.filter(|character| !character.is_control())
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod fetch;
pub mod games;
pub mod lazybox;
pub mod science;
pub mod screensaver;
mod terminal;

use std::fmt::Write as _;
Expand Down Expand Up @@ -33,6 +34,18 @@ pub fn json_string(value: &str) -> String {
escaped
}

pub fn human_text(value: &str) -> String {
let mut output = String::with_capacity(value.len());
for character in value.chars() {
if character.is_control() {
output.extend(character.escape_default());
} else {
output.push(character);
}
}
output
}

pub fn human_bytes(bytes: u64) -> String {
const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
let mut value = bytes as f64;
Expand All @@ -55,6 +68,7 @@ mod tests {
#[test]
fn escapes_json_and_formats_bytes() {
assert_eq!(json_string("a\n\"b"), "\"a\\n\\\"b\"");
assert_eq!(human_text("a\x1b\x07b"), "a\\u{1b}\\u{7}b");
assert_eq!(human_bytes(512), "512 B");
assert_eq!(human_bytes(3 * 1024 * 1024 * 1024), "3.0 GiB");
}
Expand Down
Loading
Loading