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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "venu"
version = "0.4.1"
version = "0.4.2"
edition = "2021"
description = "A sleek, hardware-accelerated Dynamic Notch for Windows with live background blur, media controls, multi-glass finishes, and desktop notifications."
license = "MIT"
Expand Down Expand Up @@ -41,6 +41,8 @@ features = [
"Win32_System_Console",
"Win32_System_Diagnostics_Debug",
"Win32_System_LibraryLoader",
"Win32_System_Performance",
"Win32_System_Power",
"Win32_System_SystemInformation",
"Win32_System_Registry",
"Win32_System_Threading",
Expand Down
2 changes: 1 addition & 1 deletion packaging/venu.iss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
; it after the install

#ifndef MyAppVersion
#define MyAppVersion "0.4.1"
#define MyAppVersion "0.4.2"
#endif

#define MyAppName "Venu"
Expand Down
20 changes: 16 additions & 4 deletions src/autostart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use std::path::{Path, PathBuf};
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_SUCCESS};
use windows::Win32::System::Registry::{
RegCloseKey, RegDeleteValueW, RegOpenKeyExW, RegQueryValueExW, RegSetValueExW, HKEY,
HKEY_CURRENT_USER, KEY_QUERY_VALUE, KEY_SET_VALUE, REG_SAM_FLAGS, REG_SZ,
RegCloseKey, RegCreateKeyW, RegDeleteValueW, RegOpenKeyExW, RegQueryValueExW, RegSetValueExW,
HKEY, HKEY_CURRENT_USER, KEY_QUERY_VALUE, KEY_SET_VALUE, REG_SAM_FLAGS, REG_SZ,
};

/// The standard per-user autostart key. Explorer starts every value here at
Expand Down Expand Up @@ -69,7 +69,18 @@ fn with_key<T>(
}

fn set_value(value_name: &str, data: &str) -> RegResult<()> {
with_key(RUN_KEY_PATH, KEY_SET_VALUE, |key| unsafe {
// A clean Windows profile (including GitHub Actions runners) may not have
// the standard Run key yet. Create/open it for writes instead of assuming
// Explorer has already created it. RegCreateKeyW is idempotent for an
// existing key and gives us the write access needed for RegSetValueExW.
unsafe {
let mut key = HKEY::default();
let sub = HSTRING::from(RUN_KEY_PATH);
let err = RegCreateKeyW(HKEY_CURRENT_USER, PCWSTR(sub.as_ptr()), &mut key);
if err != ERROR_SUCCESS {
return Err(err.0);
}

let name = HSTRING::from(value_name);
let mut wide: Vec<u16> = data.encode_utf16().collect();
wide.push(0);
Expand All @@ -78,12 +89,13 @@ fn set_value(value_name: &str, data: &str) -> RegResult<()> {
bytes.extend_from_slice(&unit.to_le_bytes());
}
let err = RegSetValueExW(key, PCWSTR(name.as_ptr()), 0, REG_SZ, Some(&bytes));
let _ = RegCloseKey(key);
if err == ERROR_SUCCESS {
Ok(())
} else {
Err(err.0)
}
})
}
}

fn value_exists(value_name: &str) -> RegResult<bool> {
Expand Down
12 changes: 10 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,20 @@ pub enum SlideKind {
Notifications,
/// Claude Code usage: context window, session cost, and rate limits.
Usage,
/// Live Windows system resource usage and power state.
Stats,
}

impl SlideKind {
pub const ALL: [SlideKind; 7] = [
pub const ALL: [SlideKind; 8] = [
SlideKind::Status,
SlideKind::Clock,
SlideKind::Marquee,
SlideKind::Wallpaper,
SlideKind::Media,
SlideKind::Notifications,
SlideKind::Usage,
SlideKind::Stats,
];

pub fn label(self) -> &'static str {
Expand All @@ -210,6 +213,7 @@ impl SlideKind {
SlideKind::Media => "Now Playing",
SlideKind::Notifications => "Notifications",
SlideKind::Usage => "Claude Usage",
SlideKind::Stats => "Stats",
}
}
}
Expand All @@ -234,12 +238,14 @@ pub enum CollapsedMode {
Notifications,
/// Resets to Claude Code usage slide.
Usage,
/// Resets to system stats slide.
Stats,
/// Smart / Auto mode: shows active alerts if unread, or Now Playing if music is on, otherwise Clock.
Auto,
}

impl CollapsedMode {
pub const ALL: [CollapsedMode; 9] = [
pub const ALL: [CollapsedMode; 10] = [
CollapsedMode::LastActive,
CollapsedMode::Status,
CollapsedMode::Clock,
Expand All @@ -248,6 +254,7 @@ impl CollapsedMode {
CollapsedMode::Media,
CollapsedMode::Notifications,
CollapsedMode::Usage,
CollapsedMode::Stats,
CollapsedMode::Auto,
];

Expand All @@ -261,6 +268,7 @@ impl CollapsedMode {
CollapsedMode::Media => "Now Playing",
CollapsedMode::Notifications => "Notifications",
CollapsedMode::Usage => "Claude Usage",
CollapsedMode::Stats => "Stats",
CollapsedMode::Auto => "Dynamic / Auto",
}
}
Expand Down
168 changes: 168 additions & 0 deletions src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ const PAGES: &[Page] = &[
forget.",
draw: SettingsApp::page_flashscreen,
},
Page {
group: Group::App,
title: "Stats",
blurb: "Live system health at a glance: processor, memory, GPU activity and power state.",
draw: SettingsApp::page_app_stats,
},
Page {
group: Group::App,
title: "Preferences",
Expand Down Expand Up @@ -599,6 +605,130 @@ impl SettingsApp {
Self::sec_flash_behavior(ui, cx.cfg, cx.changed);
}

fn page_app_stats(ui: &mut egui::Ui, _cx: &mut PageCtx<'_>) {
ui.ctx()
.request_repaint_after(std::time::Duration::from_secs(1));

let stats = crate::stats::snapshot();
Self::section_title(ui, "SYSTEM HEALTH");

let cpu = stats
.cpu_pct
.map(|v| format!("{v:.0}%"))
.unwrap_or_else(|| "—".to_string());
let ram = stats
.ram_pct
.map(|v| format!("{v:.0}%"))
.unwrap_or_else(|| "—".to_string());
let gpu = stats
.gpu_pct
.map(|v| format!("{v:.0}%"))
.unwrap_or_else(|| "—".to_string());
let power = match (stats.battery_pct, stats.ac_online) {
(Some(pct), _) if stats.charging => format!("{pct}%"),
(Some(pct), _) => format!("{pct}%"),
(None, Some(true)) => "AC".to_string(),
(None, Some(false)) => "BAT".to_string(),
_ => "—".to_string(),
};

let power_hint = match (stats.battery_pct, stats.ac_online, stats.charging) {
(Some(_), _, true) => "Charging",
(Some(_), Some(true), false) => "Plugged in",
(Some(_), Some(false), false) => "On battery",
(None, Some(true), _) => "AC power",
(None, Some(false), _) => "Battery power",
_ => "Unavailable",
};

let ram_hint = if stats.ram_total > 0 {
format!(
"{} used of {}",
crate::stats::format_bytes(stats.ram_used),
crate::stats::format_bytes(stats.ram_total)
)
} else {
"Physical memory".to_string()
};

ui.columns(4, |cols| {
let cards = [
("CPU", cpu.as_str(), "Processor"),
("RAM", ram.as_str(), ram_hint.as_str()),
("GPU", gpu.as_str(), "Busiest engine"),
("POWER", power.as_str(), power_hint),
];

for (ui, (label, value, hint)) in cols.iter_mut().zip(cards) {
Frame::none()
.fill(theme::surface())
.stroke(Stroke::new(1.0, theme::divider()))
.rounding(Rounding::same(10.0))
.inner_margin(Margin::same(14.0))
.show(ui, |ui| {
ui.set_min_height(86.0);
ui.label(
RichText::new(label)
.size(10.0)
.strong()
.color(theme::text_tertiary()),
);
ui.add_space(6.0);
ui.label(
RichText::new(value)
.size(25.0)
.strong()
.color(theme::text_primary()),
);
ui.add_space(3.0);
ui.label(
RichText::new(hint)
.size(10.5)
.color(theme::text_secondary()),
);
});
}
});

ui.add_space(18.0);
Self::section_title(ui, "LIVE MONITOR");

let rows = [
("CPU utilization", stats.cpu_pct, "Total processor load"),
("Memory pressure", stats.ram_pct, "Physical RAM in use"),
("GPU activity", stats.gpu_pct, "Highest active GPU engine"),
];

for (label, value, hint) in rows {
Self::row_stacked(ui, label, Some(hint), |ui| {
let pct = value.unwrap_or(0.0).clamp(0.0, 100.0);
ui.horizontal(|ui| {
ui.add(
egui::ProgressBar::new(pct / 100.0)
.desired_width(190.0)
.show_percentage(),
);
if value.is_none() {
ui.label(
RichText::new("Unavailable")
.size(10.5)
.color(theme::text_tertiary()),
);
}
});
});
}

ui.add_space(4.0);
ui.label(
RichText::new(
"Updated about once per second. GPU uses Windows performance counters and may be unavailable on some drivers or remote sessions.",
)
.size(10.5)
.color(theme::text_tertiary()),
);
}

fn page_app_prefs(ui: &mut egui::Ui, cx: &mut PageCtx<'_>) {
Self::section_title(ui, "APPEARANCE");

Expand Down Expand Up @@ -3196,6 +3326,44 @@ fn paint_sidebar_hugeicon(
painter.line_segment([pts[i], pts[(i + 1) % pts.len()]], stroke);
}
}
(Group::App, "Stats") => {
// Hugeicons: Analytics bars
painter.line_segment(
[
egui::pos2(cx - 5.2, cy + 4.8),
egui::pos2(cx - 5.2, cy + 0.5),
],
stroke,
);
painter.line_segment(
[
egui::pos2(cx - 1.7, cy + 4.8),
egui::pos2(cx - 1.7, cy - 3.0),
],
stroke,
);
painter.line_segment(
[
egui::pos2(cx + 1.8, cy + 4.8),
egui::pos2(cx + 1.8, cy - 0.8),
],
stroke,
);
painter.line_segment(
[
egui::pos2(cx + 5.2, cy + 4.8),
egui::pos2(cx + 5.2, cy - 5.0),
],
stroke,
);
painter.line_segment(
[
egui::pos2(cx - 6.2, cy + 4.8),
egui::pos2(cx + 6.2, cy + 4.8),
],
stroke,
);
}
(Group::App, "Preferences") => {
// Hugeicons: Settings Gear
painter.circle_stroke(center, 3.8, stroke);
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod flash;
mod gui;
mod notch;
mod overlay;
mod stats;
mod tray;

use parking_lot::RwLock;
Expand Down
Loading
Loading