Krolyxon
Commit 1978b2f

Branches

Refs →

budctl1978b2f

README: screenshots of the desktop app, and the custom-preset commands

- Screenshots of the Overview, Equalizer, Gestures and Earbud settings
  pages and the keys overlay, in docs/screenshots/. They show --demo's
  made-up buds. `cargo test -p budctl-gui readme_screenshots -- --ignored`
  renders them again; it is ignored by default because it writes into
  docs/. pages_render and it share one render helper.
- The command list gains `eq new`, `eq rename` and `eq delete`. The page
  list says where the bass level now is, and what App settings shows.
- CLAUDE.md says how to refresh the screenshots.

No new dependency.
Author
krolyxon
Date
2026-10-28 02:23 +0530
Parent
b835c9b
Commit
1978b2f049ad65018daf5030ba5074487468d9df

+66 −3

8 files changed

  1. M CLAUDE.md +1 −0
  2. M README.md +23 −3
  3. M crates/budctl-gui/src/main.rs +42 −0
  4. A docs/screenshots/earbuds.png
  5. A docs/screenshots/equalizer.png
  6. A docs/screenshots/gestures.png
  7. A docs/screenshots/keys.png
  8. A docs/screenshots/overview.png
M CLAUDE.md View
@@ -127,6 +127,7 @@ Exit codes:
127127 - The cursor shows only after a key press, and the first key only shows it, so nothing changes where it cannot be seen. A click hides it.
128128 - **Buds:** `worker.rs` owns the session, on a thread of its own, and answers each command with one event, in order. It opens the session when a command needs it, answers the buds' own requests while it waits (`Session::serve`), and closes it 10 s after the last command. A write shows "Saving…" until the buds' read-back arrives, and takes no second write meanwhile. A failure says why, with "Try again". `--demo` shows made-up buds; `--debug` hex-dumps frames like the CLI.
129129 - **Logic:** keys and clicks both become messages for one `update` function, which stays testable without a display. `iced_test` renders pages to PNG, so the look can be checked without the owner.
130+- **Screenshots:** the README's come from the demo buds: `cargo test -p budctl-gui readme_screenshots -- --ignored` renders them again into `docs/screenshots/`.
130131
131132 ## Working with Claude
132133
M README.md View
@@ -9,6 +9,10 @@ touch controls, battery and more. There is a command-line tool, `budctl`, and a
99
1010 **Supported buds:** realme Buds T310. More are added as the author gets them.
1111
12+<p align="center">
13+ <img src="docs/screenshots/overview.png" width="560" alt="budctl-gui's Overview page: battery of each bud, noise control, and switches such as game mode and 3D sound">
14+</p>
15+
1216 ## Install
1317
1418 You need Rust 1.88 or newer, BlueZ, and for the desktop app a Wayland desktop.
@@ -31,6 +35,9 @@ budctl anc light|medium|deep # noise cancelling, at this strength
3135 budctl eq # the EQ presets, and the one in use
3236 budctl eq deep-bass # select a preset
3337 budctl eq custom-4 50=+3 16000=-2 # change bands of a custom preset (dB)
38+budctl eq new Gym # create a custom preset: flat, and put to use
39+budctl eq rename custom-4 "Gym mix" # rename a custom preset
40+budctl eq delete custom-4 # delete a custom preset
3441 budctl game on|off # game mode (low latency)
3542 budctl set bass-boost|spatial|wind|dual on|off
3643 budctl dual # the phones and computers dual connection keeps
@@ -60,11 +67,24 @@ Exit codes:
6067
6168 `budctl-gui` has five pages:
6269
63−- **Overview:** battery, noise control, the switches.
64−- **Equalizer:** presets, the bands of your custom presets, the bass level.
70+- **Overview:** battery, noise control and its strength, the switches, the bass level.
71+- **Equalizer:** the presets; create, rename and delete your own, and change their bands.
6572 - **Gestures:** what each tap and hold does on each bud, and what a hold steps through.
6673 - **Earbud settings:** dual connection and the devices it keeps, find my earbuds, firmware, audio codec.
67−- **App settings.**
74+- **App settings:** where the colors come from, the keys, the version.
75+
76+<table>
77+ <tr>
78+ <td valign="top"><img src="docs/screenshots/equalizer.png" alt="Equalizer: the built-in presets, a custom preset with its pencil and bin, and its six bands open below"></td>
79+ <td valign="top"><img src="docs/screenshots/gestures.png" alt="Gestures: double tap, triple tap and hold for each bud, and the modes a hold steps through"></td>
80+ </tr>
81+ <tr>
82+ <td valign="top"><img src="docs/screenshots/earbuds.png" alt="Earbud settings: the dual connection switch, the devices the buds keep, find my earbuds, firmware and codec"></td>
83+ <td valign="top"><img src="docs/screenshots/keys.png" alt="The keys overlay, shown with ?"></td>
84+ </tr>
85+</table>
86+
87+The screenshots show `--demo`'s made-up buds.
6888
6989 The app connects when it needs to, and frees the buds' control channel 10 s after your last
7090 change, so other apps can use it. A change shows "Saving…" until the buds confirm it.
M crates/budctl-gui/src/main.rs View
@@ -2347,6 +2347,48 @@ mod tests {
23472347 let _ = saving.update(Message::Switch("spatial", false));
23482348 let _ = saving.update(Message::Noise(NoiseMode::Off));
23492349 shots.push(("saving".to_owned(), 1240.0, saving));
2350+ render(&dir, font, shots);
2351+ }
2352+
2353+ /// The README's screenshots: the demo buds in the built-in palette, written to
2354+ /// `docs/screenshots/` by `cargo test -p budctl-gui readme_screenshots -- --ignored`.
2355+ #[test]
2356+ #[ignore = "writes into docs/; run it when the look changes"]
2357+ fn readme_screenshots() {
2358+ let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
2359+ let scratch = root.join("target/readme-screenshots");
2360+ let _ = fs::remove_dir_all(&scratch);
2361+ let font = theme::system_font();
2362+ let on = |page: Page, keys: &str| {
2363+ let mut app = App::new(None, font, None, true);
2364+ app.page = page;
2365+ press(&mut app, keys);
2366+ app
2367+ };
2368+ let mut equalizer = on(Page::Equalizer, "");
2369+ let _ = equalizer.update(Message::EditPreset(4));
2370+ let shots = vec![
2371+ ("overview".to_owned(), 1140.0, on(Page::Overview, "")),
2372+ ("equalizer".to_owned(), 900.0, equalizer),
2373+ ("gestures".to_owned(), 840.0, on(Page::Gestures, "")),
2374+ ("earbuds".to_owned(), 800.0, on(Page::Earbuds, "")),
2375+ ("keys".to_owned(), 800.0, on(Page::Overview, "?")),
2376+ ];
2377+ let names: Vec<String> = shots.iter().map(|(name, _, _)| name.clone()).collect();
2378+ render(&scratch, font, shots);
2379+ let out = root.join("docs/screenshots");
2380+ fs::create_dir_all(&out).unwrap();
2381+ for name in names {
2382+ fs::rename(
2383+ scratch.join(format!("{name}-tiny-skia.png")),
2384+ out.join(format!("{name}.png")),
2385+ )
2386+ .unwrap();
2387+ }
2388+ }
2389+
2390+ /// Renders each (name, window height, app) 1040 px wide, to `<dir>/<name>-tiny-skia.png`.
2391+ fn render(dir: &Path, font: Font, shots: Vec<(String, f32, App)>) {
23502392 for (name, height, app) in shots {
23512393 let settings = iced::Settings {
23522394 default_font: font,
A docs/screenshots/earbuds.png View

[Binary file]

A docs/screenshots/equalizer.png View

[Binary file]

A docs/screenshots/gestures.png View

[Binary file]

A docs/screenshots/keys.png View

[Binary file]

A docs/screenshots/overview.png View

[Binary file]