diff --git a/.gitignore b/.gitignore index 059760a..2a03ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /result /result-* +.desktop-share .direnv diff --git a/README.md b/README.md index 2fc1fc1..ddc3768 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,15 @@ From this checkout (with sibling `../vidya`): nix run # apps.default → cargo build --release && run nix run .#usage # same nix run .#build # cargo build --release only +nix run .#desktop # cargo build + gtk-launch .desktop (icon) +nix run .#package # pure store binary +nix run .#package-desktop # pure package via its .desktop ``` `apps.usage` / `apps.default` use the **devshell toolchain** (`rustc`/`cargo` + egui libs) in the working tree — no pure sandbox, no remote builders. Incremental builds go into `./target/`. +`apps.desktop` cargo-builds, stages `usage.desktop` + hicolor icons under `.desktop-share/`, then runs `gtk-launch usage` so the **app icon** applies. + Optional interactive shell: ```bash @@ -28,6 +33,8 @@ Packaged install (pure Nix derivation — may use remote builders): ```bash nix build .#usage ./result/bin/usage +# also: result/share/applications/usage.desktop +# result/share/icons/hicolor/.../apps/usage.{svg,png} ``` ## What you see @@ -54,8 +61,12 @@ Below the gauges: cards listing individual disks and network interfaces with min |--------|------| | `apps.default` / `apps.usage` | Devshell: `cargo build --release` then run | | `apps.build` | Devshell: `cargo build --release` only | -| `packages.default` / `packages.usage` | Pure packaged binary (`nix build`) | +| `apps.desktop` | `gtk-launch` the packaged `.desktop` (icon) | +| `apps.package` | Pure store binary | +| `packages.default` / `packages.usage` | Binary + `.desktop` + hicolor icons | +| `packages.desktop` | Wrapper that launches the `.desktop` entry | | `devShells.default` | rustc + cargo + egui runtime libs | +| `assets/usage.svg` | App icon source | Apps need a live checkout next to `../vidya`. The packaged `packages.usage` stages flake input `vidya` for pure builds. diff --git a/assets/usage.svg b/assets/usage.svg new file mode 100644 index 0000000..58502d4 --- /dev/null +++ b/assets/usage.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flake.lock b/flake.lock index 8193f51..cc757d7 100644 --- a/flake.lock +++ b/flake.lock @@ -25,18 +25,14 @@ "vidya": { "flake": false, "locked": { - "lastModified": 1785474411, - "narHash": "sha256-gY9B9iBn/DXlQD5ce0HsWwSheFiFUFlFBZ79QjM1cPQ=", - "ref": "refs/heads/main", - "rev": "6583438f1f9e5a985c71aa5ccd1a73614140daa7", - "revCount": 11, - "type": "git", - "url": "https://tangled.org/nandi.uk/vidya" + "path": "../vidya", + "type": "path" }, "original": { - "type": "git", - "url": "https://tangled.org/nandi.uk/vidya" - } + "path": "../vidya", + "type": "path" + }, + "parent": [] } }, "root": "root", diff --git a/flake.nix b/flake.nix index 931adeb..5d78067 100644 --- a/flake.nix +++ b/flake.nix @@ -3,8 +3,9 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # Sibling checkout (same as Cargo path dep). Keeps layout DSL in sync. vidya = { - url = "git+https://tangled.org/nandi.uk/vidya"; + url = "path:../vidya"; flake = false; }; }; @@ -34,7 +35,7 @@ libxrandr ]; - # Shared by apps.build / apps.usage: enter checkout + set link path. + # Shared by cargo apps: enter checkout + set link path. cargoPreamble = libPath: '' set -euo pipefail export LD_LIBRARY_PATH="${libPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" @@ -52,6 +53,25 @@ echo " clone vidya next to usage, or: nix build .#usage" >&2 exit 1 fi + if [ ! -f assets/usage.svg ] || [ ! -f usage.desktop ]; then + echo "usage: missing assets/usage.svg or usage.desktop" >&2 + exit 1 + fi + ''; + + # Stage FreeDesktop share tree (icon + .desktop) for gtk-launch (shell, not nix-build). + installDesktopShare = shareDir: execPath: '' + mkdir -p "${shareDir}/applications" + mkdir -p "${shareDir}/icons/hicolor/scalable/apps" + cp assets/usage.svg "${shareDir}/icons/hicolor/scalable/apps/usage.svg" + for sz in 16 24 32 48 64 128 256; do + mkdir -p "${shareDir}/icons/hicolor/''${sz}x''${sz}/apps" + rsvg-convert -w "$sz" -h "$sz" assets/usage.svg \ + -o "${shareDir}/icons/hicolor/''${sz}x''${sz}/apps/usage.png" + done + # @EXEC@ is a placeholder in usage.desktop (substitute is nix-build only). + sed "s|@EXEC@|${execPath}|g" usage.desktop \ + > "${shareDir}/applications/usage.desktop" ''; in { @@ -63,14 +83,13 @@ libs = eguiLibs pkgs; # Packaged binary for `nix build` / install — pure, remote-builder capable. - # Day-to-day: prefer `nix run .#usage` (devshell cargo path below). srcTree = pkgs.runCommand "usage-src" { } '' mkdir -p $out/usage $out/vidya cp -a ${lib.cleanSource ./.}/. $out/usage/ cp -a ${vidya}/. $out/vidya/ chmod -R u+w $out - rm -rf $out/usage/{target,result,result-*,.git} 2>/dev/null || true - rm -rf $out/vidya/{target,android-demo,host,examples,docs,.git} 2>/dev/null || true + rm -rf $out/usage/{target,result,result-*,.git,.jj,.desktop-share} 2>/dev/null || true + rm -rf $out/vidya/{target,android-demo,host,examples,docs,.git,.jj} 2>/dev/null || true ''; usage = pkgs.rustPlatform.buildRustPackage { @@ -80,12 +99,27 @@ sourceRoot = "usage-src/usage"; cargoLock.lockFile = ./Cargo.lock; - nativeBuildInputs = [ pkgs.makeWrapper ]; + nativeBuildInputs = [ + pkgs.makeWrapper + pkgs.librsvg + ]; buildInputs = libs; postInstall = '' wrapProgram $out/bin/usage \ --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath libs} + + install -Dm644 ${./assets/usage.svg} \ + $out/share/icons/hicolor/scalable/apps/usage.svg + for sz in 16 24 32 48 64 128 256; do + mkdir -p $out/share/icons/hicolor/''${sz}x''${sz}/apps + rsvg-convert -w "$sz" -h "$sz" ${./assets/usage.svg} \ + -o $out/share/icons/hicolor/''${sz}x''${sz}/apps/usage.png + done + + install -d $out/share/applications + substitute ${./usage.desktop} $out/share/applications/usage.desktop \ + --replace-fail '@EXEC@' "$out/bin/usage" ''; meta = { @@ -96,10 +130,25 @@ platforms = lib.platforms.linux; }; }; + + # gtk-launch the packaged .desktop (store binary + icon). + desktop = pkgs.writeShellApplication { + name = "usage-desktop"; + runtimeInputs = [ pkgs.gtk3 ]; + text = '' + set -euo pipefail + export XDG_DATA_DIRS="${usage}/share''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" + if command -v gtk-launch >/dev/null 2>&1; then + exec gtk-launch usage "$@" + fi + exec ${usage}/bin/usage "$@" + ''; + }; in { default = usage; usage = usage; + desktop = desktop; } ); @@ -114,6 +163,8 @@ rustc cargo pkg-config + librsvg + gtk3 ]; # Local cargo build (devshell tools; no pure sandbox / remote upload lock). @@ -140,6 +191,31 @@ exec ./target/release/usage "$@" ''; }; + + # Cargo build + stage .desktop/icons into .desktop-share, then gtk-launch. + # Primary way to "run the .desktop" from a flake app without a pure rebuild. + desktopApp = pkgs.writeShellApplication { + name = "usage-desktop"; + runtimeInputs = cargoTools; + text = '' + ${cargoPreamble libPath} + echo "→ cargo build --release" + cargo build --release + + SHARE="''${USAGE_DESKTOP_SHARE:-$PWD/.desktop-share}" + EXEC="$PWD/target/release/usage" + rm -rf "$SHARE" + mkdir -p "$SHARE" + ${installDesktopShare "$SHARE" "$EXEC"} + + export XDG_DATA_DIRS="$SHARE''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" + echo "→ gtk-launch usage (Icon=usage, Exec=$EXEC)" + if command -v gtk-launch >/dev/null 2>&1; then + exec gtk-launch usage "$@" + fi + exec "$EXEC" "$@" + ''; + }; in { default = { @@ -154,6 +230,19 @@ type = "app"; program = "${build}/bin/usage-build"; }; + desktop = { + type = "app"; + program = "${desktopApp}/bin/usage-desktop"; + }; + # Pure store package launched via its .desktop (needs nix build). + package-desktop = { + type = "app"; + program = "${self.packages.${system}.desktop}/bin/usage-desktop"; + }; + package = { + type = "app"; + program = "${self.packages.${system}.usage}/bin/usage"; + }; } ); @@ -171,11 +260,12 @@ rustfmt clippy pkg-config + librsvg ]; buildInputs = libs; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath libs; shellHook = '' - echo "usage — nix run | nix run .#build | nix develop" + echo "usage — nix run | nix run .#desktop | nix run .#build | nix develop" ''; }; } diff --git a/src/app.rs b/src/app.rs index 827ebb4..0182d15 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,10 +2,10 @@ use std::time::{Duration, Instant}; -use eframe::egui::{self, Align, Layout, RichText}; +use eframe::egui::{self, RichText}; use vidya::{ - apply, body, card, central_page, compact_card, dim_label, grid_cols, lead_trail, status_dot, - title, title_2, ColSpec, Mode, Theme, + apply, body, card, central_page, compact_card, dim_label, grid_cols, lead_trail, title, + title_2, ColSpec, Theme, }; use crate::gauge::{arc_gauge, heat_color, mini_bar, PeakScale}; @@ -29,7 +29,6 @@ struct AnomalyHit { } pub struct UsageApp { - mode: Mode, sampler: IoSampler, snap: IoSnapshot, last_sample: Instant, @@ -54,7 +53,6 @@ impl UsageApp { let snap = sampler.sample(); Self { - mode: Mode::Dark, sampler, snap, last_sample: Instant::now(), @@ -68,13 +66,6 @@ impl UsageApp { } } - fn theme(&self) -> Theme { - match self.mode { - Mode::Dark => Theme::dark(), - Mode::Light => Theme::light(), - } - } - fn tick(&mut self) { if self.last_sample.elapsed() >= SAMPLE_EVERY { self.snap = self.sampler.sample(); @@ -151,7 +142,7 @@ fn push_anomaly(list: &mut Vec, hit: AnomalyHit) { impl eframe::App for UsageApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.tick(); - let th = self.theme(); + let th = Theme::dark(); apply(ctx, &th); // Keep sampling while idle. @@ -160,22 +151,9 @@ impl eframe::App for UsageApp { egui::TopBottomPanel::top("header") .frame(th.header_frame()) .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.vertical(|ui| { - title(ui, &th, "I/O Usage"); - dim_label(ui, &th, "Disk & network throughput"); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - let dark = self.mode == Mode::Dark; - if vidya::button(ui, &th, if dark { "Light" } else { "Dark" }).clicked() { - self.mode = if dark { Mode::Light } else { Mode::Dark }; - } - ui.add_space(th.spacing.sm); - ui.horizontal(|ui| { - status_dot(ui, &th, true); - dim_label(ui, &th, "live"); - }); - }); + ui.vertical(|ui| { + title(ui, &th, "I/O Usage"); + dim_label(ui, &th, "Disk & network throughput"); }); }); diff --git a/usage.desktop b/usage.desktop new file mode 100644 index 0000000..51c7fe1 --- /dev/null +++ b/usage.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=I/O Usage +GenericName=I/O Gauges +Comment=Live disk and network throughput gauges +Exec=@EXEC@ +Icon=usage +Categories=System;Monitor; +Keywords=io;disk;network;gauge;throughput;vidya;usage; +Terminal=false +StartupNotify=true +StartupWMClass=usage