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