Skip to content

Tags: wdes/wattaouille

Tags

Verified

This tag was signed with the committer’s verified signature.
williamdes William Desportes

v1.3.5

Toggle v1.3.5's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
fix: detect display off via DRM connector DPMS too (v1.3.5)

`bl_power` (the legacy framebuffer interface) doesn't always flip when
the modern desktop blanks the screen. XFCE Power Manager's "blank
screen", `xset dpms force off`, and GNOME's idle-blank all toggle the
DRM connector's DPMS state at /sys/class/drm/card<N>-eDP-<M>/dpms but
leave bl_power at 0. v1.3.1 only checked bl_power, so the 🖥 line kept
reporting `~1.4 W (30% bl)` even though the panel was actually dark.

BacklightSensor::detect now also enumerates
/sys/class/drm/*-eDP-*/ and /sys/class/drm/*-LVDS-*/ connectors that
are `enabled` + `connected`, captures their `dpms` files, and
is_powered_off() returns true if EITHER bl_power says non-zero OR any
captured dpms file reports anything other than "On".

The detection scan only happens once at startup (the connector list is
stable for the lifetime of a session). Per-frame cost is one extra
read syscall per active panel.

Tests: +2 (DRM-DPMS-off path with bl_power still On; both-on baseline
to ensure the readout works through the new code path). 51 total.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.3.4

Toggle v1.3.4's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
feat: show version in header + --version flag (v1.3.4)

The first line of the live display now starts with `wattaouille vX.Y.Z`
so you can tell at a glance which build you're looking at — handy when
multiple binaries are floating around or when reproducing an issue
from a screenshot.

Also added a `-V` / `--version` flag that prints the version and
exits, matching the convention of every other CLI.

Version is pulled from CARGO_PKG_VERSION at compile time, so there's
nothing to keep in sync — bumping Cargo.toml is the single source.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.3.3

Toggle v1.3.3's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
fix: stdin raw mode prevents echo'd scroll keys in alt screen (v1.3.3)

Scrolling with the mouse wheel (or hitting arrow keys) inside the
running wattaouille produced visible garbage on the alt screen. Cause:
those events arrive on stdin as `\x1B[A` / `\x1B[B` etc., and with
canonical+echo mode (the default for any tty) the kernel tty driver
echoed them back to fd 1 — which is the alt screen we're drawing on.

Fix: capture the original termios at startup, then flip stdin to
non-canonical / non-echo right after entering the alt screen. The
ctrlc handler restores it before exiting (via OnceLock<termios> for
safe lock-free read from the handler thread).

`libc = "0.2"` added as an explicit dep — it was already pulled in by
`ctrlc`'s nix dependency, so this is a zero-cost-on-disk change.
CHANGELOG updated.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.3.2

Toggle v1.3.2's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
feat: Element + Claude shell labels, CHANGELOG.md (v1.3.2)

Element (Matrix) desktop
  comm "element-desktop" (and the un-truncated "Element") now render as
  "Element (Matrix)".

Claude shell
  Every Claude Code tool call spawns a fresh
    bash -c source /home/.../.claude/shell-snapshots/snapshot-bash-X.sh \
        2>/dev/null || true && shopt -u extglob && eval '<cmd>'
  Those used to surface as identical "bash -c source …" lines (one per
  call, indistinguishable). Now collapsed to "Claude shell (<cwd>)" so
  parallel projects are obvious.

CHANGELOG.md
  Initial changelog backfilling every release from v1.0.0 to v1.3.2.
  Format follows Keep a Changelog. README links to it.

Tests
  +3 (Element label, Claude shell with cwd, Claude shell without cwd).
  49 total.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.3.1

Toggle v1.3.1's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
fix: backlight + Wi-Fi power-off detection, width-stable wattage (v1.…

…3.1)

Backlight off
  v1.3.0 always reported a display watts estimate as long as a
  /sys/class/backlight/* device existed. Turning the panel off (Fn-Fx
  or DPMS blank) sets that device's bl_power to a non-zero state
  (1=NORMAL/2=VSYNC/3=HSYNC/4=POWERDOWN), but brightness can stay at
  its last value, so we kept estimating ~1.4 W of phantom panel draw.

  BacklightSensor now also captures the bl_power path. is_powered_off()
  returns true on any non-zero value, in which case estimated_watts()
  returns None and the 🖥 line disappears entirely.

Wi-Fi off (rfkill)
  v1.3.0 used `operstate` to decide whether the Wi-Fi was associated.
  rfkill keeps operstate at "down" but the sysfs `carrier` file is the
  authoritative signal: 1 when there's an active link, 0 otherwise.
  Switched NetIface to read `carrier`, and the 📡 Wi-Fi line is now
  hidden (returns None instead of Some(0.0)) when nothing's linked.

Width-stable wattage
  Per "ensure wattage has always space for 3 numbers": all wattage
  values now format as {:>5.1} (5 chars wide → "  0.5", " 12.5",
  "123.5") so the bits to the right don't shift between frames as
  values cross 10 W or 100 W. Drift uses {:>+6.1} for the sign.

  Affects:
    ⚡ RAPL X.X W avg (core X.X W · uncore X.X W · dram X.X W)
    🔋 BAT X.X W avg
    🖥 ~X.X W
    📡 Wi-Fi ~X.X W
    Δ non-CPU ±X.X W avg

Tests
  +1: backlight_powered_off_returns_none uses a tmp file with
  bl_power=4 to verify the off-state path. Renamed
  backlight_watts_at_zero_is_baseline → ..._no_device_returns_none for
  clarity (the original name was misleading; it actually tests the
  no-path branch). 46 total, all passing.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.3.0

Toggle v1.3.0's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
feat: non-CPU energy attribution — RAPL subdomains, I/O, display, Wi-…

…Fi (v1.3.0)

The big v1.2 → v1.3 bump: wattaouille now reports the things that
weren't visible before, on top of the CPU package draw.

RAPL subdomain breakout
  PowerSensor::detect now also enumerates intel-rapl:0:* (core, uncore,
  dram). Each gets its own RaplDomain with independent wraparound. The
  header shows a per-frame "now" breakdown:

    ⚡ RAPL 12.5 W avg (core 8.1W · uncore 1.4W · dram 0.6W) · 0.146 Wh (525 J)

  Same chmod gates the subdomains as the package counter.

Per-process I/O column
  /proc/[pid]/io read_bytes + write_bytes is parsed each frame, deltas
  attributed per PID, and the leaderboard gains an "I/O" column with
  human-friendly rate (e.g. "74.7 KB/s"). Browser collapse roots sum
  their subtree's I/O. /proc/[pid]/io is mode 0400 + ptrace-restricted,
  so other users' processes show "—" — degrades silently.

Display panel watt estimate
  BacklightSensor reads /sys/class/backlight/*/brightness and models
  panel draw with a linear envelope: 0.5 W floor (panel electronics) to
  3.5 W full (LED backlight). Shown with `~` prefix to flag the
  estimate, plus session Wh:

    🖥 ~1.4 W (30% bl · ~0.002 Wh)

Wi-Fi radio watt estimate
  NetSensor classifies each interface as wireless (presence of
  /sys/class/net/<iface>/wireless or /phy80211 symlink) and tracks
  rx+tx bytes separately. estimate_wifi_radio_watts() models radio
  draw: 0 W when not associated, 0.7 W idle, ramping linearly to
  2.5 W at 5 MB/s aggregated rx+tx. `~` prefix.

    📡 Wi-Fi ~0.7 W (350 KB/s · ~0.001 Wh)

Total network throughput
  Sum of rx+tx across all non-loopback interfaces, formatted with
  fmt_byte_rate (B/s → KB/s → MB/s → GB/s):

    📶 net 130.1 KB/s

Estimate-prefix convention
  Per user request: any value that's modeled rather than read directly
  from a sensor gets a `~` prefix. Currently applies to display watts
  and Wi-Fi radio watts.

More pretty labels
  Slack desktop, Discord, VS Code, Spotify, Thunderbird.

Tests
  9 new tests: parse_proc_io for sums and missing fields, BacklightSensor
  with no backlight path, estimate_wifi_radio_watts at three points,
  fmt_byte_rate units, RaplDomain wraparound, slack label. 45 total.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.2.2

Toggle v1.2.2's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
fix: Ctrl+C handler bypassed stdout lock; add MPL-2.0 license (v1.2.2)

Ctrl+C deadlock
  v1.2.1's ctrlc handler called io::stdout().lock().write_all(...) to
  emit the terminal-restore sequence. The render loop in main holds a
  StdoutLock for its entire run, so the handler thread blocked forever
  acquiring the same lock and the program never exited on Ctrl+C.

  Fix: write the restore sequence directly via libc::write to fd 1.
  That's one syscall, bypasses the Rust mutex, and lets std::process::
  exit(130) actually run.

License: MPL-2.0
  No license file existed. Added Mozilla Public License v2.0 (file-level
  copyleft, friendly to embedding inside proprietary tools as long as
  modifications to wattaouille files themselves are shared back).
  Cargo.toml `license = "MIT"` placeholder corrected to `"MPL-2.0"`.
  README license section updated.

History was kept linear — added as a new commit rather than rewriting
v1.0.0..v1.2.1 because main is already pushed to origin and the rewrite
benefit (LICENSE present from the start) doesn't justify a force-push.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.2.1

Toggle v1.2.1's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
feat: terminal restore on Ctrl+C, two-line header, ~30 more labels (v…

…1.2.1)

Terminal cleanup
  Ctrl+C used to kill the program before it could send the alt-screen
  exit / cursor-show sequences, leaving the terminal in a state where
  the cursor was hidden and (depending on the emulator) typed text was
  invisible. Install a SIGINT/SIGTERM handler via the `ctrlc` crate
  (added as the first dep) that writes the restore sequence and exits
  cleanly with code 130.

Two-line header
  The summary line was getting too long once RAPL + BAT + drift were
  all present. Split into:
    line 1 — runtime stats (interval, CPU count, proc count, elapsed,
             Ctrl+C hint)
    line 2 — energy bits (RAPL avg/Wh/J, BAT avg/Wh/J/SoC/time-to-empty,
             drift)
  When power is disabled line 2 just doesn't render.

`pretty_known()` — friendly labels for ~30 common system processes
  XFCE: xfdesktop, xfce4-panel, xfce4-session, xfwm4, xfsettingsd,
        xfconfd, xfce4-power-mana, xfce4-clipman, xfce4-screensav,
        Thunar, lightdm, Xorg
  Input methods: ibus-daemon, ibus-ui-gtk3, ibus-engine-sim,
        ibus-extension-, ibus-x11
  Audio: pipewire, pipewire-pulse, wireplumber, pulseaudio
  System bus / journal: dbus-daemon, systemd-journal, systemd-logind,
        systemd-udevd
  Daemons: dockerd, containerd, redis-server, teamviewerd, scdaemon,
        ntp-daemon, warp-taskbar, NetworkManager, ModemManager,
        bluetoothd, tailscaled, wpa_supplicant, avahi-daemon,
        accounts-daemon, polkitd, udisksd, upowerd, colord,
        rtkit-daemon, snapd, cupsd, cups-browsed, smartd, boltd,
        xiccd, yubikey-touch-d
  Truncated comm names (kernel limits to 15 chars): power-profiles-,
        xdg-desktop-por, xdg-document-po, xdg-permission-,
        switcheroo-cont, at-spi-bus-laun, at-spi2-registr
  Containers/sandboxing: rootlesskit, slirp4netns
  Servers: caddy, apache2, crowdsec, anydesk
  Misc: agetty, solaar, blueman-tray/applet, smartgit.sh

  Argv-aware shortcuts:
    containerd-shim-runc-v2 → "containerd-shim (sha[:8])" using -id
    wrapper-2.0 …/lib<plugin>.so → "Xfce panel plugin (<plugin>)"
    python3 …/blueman-X → "Blueman (X)"
    node …/ng serve --port=N → "ng serve (:N)"

Tests
  9 new tests covering the new labels (xfce, ibus, pipewire, dockerd,
  containerd-shim with/without id, xfce panel plugin, blueman, ng
  serve). 36 total, all passing.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

v1.2.0

Toggle v1.2.0's commit message

Verified

This commit was signed with the committer’s verified signature.
williamdes William Desportes
release: rename to wattaouille, energy/UX upgrades, unit tests (v1.2.0)

Project rename
  monitor → wattaouille (watt + ratatouille + ouille!).
  Cargo crate, binary name, header banner, help text, README and the
  `Or just run … with sudo` instruction line all updated. Project
  directory moved from @williamdes/monitor → @wdes/wattaouille.

New labels
  python3 /usr/bin/guake          → "Guake terminal"
  mysqld / mariadbd               → "mysqld (cwd-basename)" so multiple
                                    instances are easy to tell apart
                                    (cwd usually points at the data dir)

Battery: state of charge + time-to-empty
  BatterySensor now reads energy_now and energy_full alongside
  power_now. The header gains, while discharging:
    🔋 BAT 18.0 W avg · 0.210 Wh (756 J) · 87% · 4h 23m left
  And while plugged in:
    🔌 on AC · 87%
  fmt_hours() prints "Xh YYm".

Drift display
  Δ now shown as a rate AND totals, leading with W avg so you can read
  it as "the rest of the system is currently pulling X watts":
    Δ non-CPU +5.5 W avg · 0.064 Wh (231 J · +31%)

Unit tests
  27 tests in `mod tests`, no /proc dependence:
   • cwd_basename edge cases (trailing slash, root, missing)
   • is_claude_code (claude.exe path AND @anthropic-ai/claude-code in
     any argv slot — broadened so `node …/cli.js` is recognised)
   • happy_subcommand for claude / daemon / unrelated
   • pretty_cmdline for Claude+Happy+cwd, Claude bare, Happy daemon vs
     session, SmartGit, Guake, mysqld with/without cwd
   • is_collapse_root for librewolf main, opera --type=, librewolf
     -contentproc, plain rustc
   • PowerSensor::joules_between simple diff and counter wraparound
   • fmt_hours basic + invalid (NaN, negative)
   • flatten_visible: promotes busy grandchildren past idle wrappers,
     keeps collapse roots at zero own CPU, drops fully-dead subtrees

`cargo test --release` runs them all and ties them to CI.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>