mirror of
https://github.com/CodeZeno/Claude-Code-Usage-Monitor.git
synced 2026-06-20 10:19:27 +08:00
v1.3.0
- Added a native system tray icon that shows current 5-hour usage as a percentage badge. - Added tray icon tooltip support showing both 5h and 7d countdown/usage values. - Added tray icon interactions: left-click toggles the taskbar widget, right-click opens the context menu. - Anchored widget to the bottom of the taskbar - Added embedded app icon loading so tray fallback uses the app's own icon. - Updated countdown timer logic to stop cases where 61m is shown - Updated README.md
This commit is contained in:
parent
1d8ec6cad0
commit
643cc0a8b6
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -38,7 +38,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "claude-code-usage-monitor"
|
||||
version = "1.2.11"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"native-tls",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "claude-code-usage-monitor"
|
||||
version = "1.2.11"
|
||||
version = "1.3.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Claude Code Usage Monitor"
|
||||
|
||||
20
README.md
20
README.md
@ -1,14 +1,14 @@
|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
# Claude Code Usage Monitor
|
||||
|
||||

|
||||
|
||||
A lightweight Windows taskbar widget for people already using Claude Code.
|
||||
|
||||
It sits in your taskbar and shows how much of your Claude Code usage window you have left, without needing to open the terminal or the Claude site.
|
||||
|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||

|
||||
|
||||
## What You Get
|
||||
|
||||
- A **5h** bar for your current 5-hour Claude usage window
|
||||
@ -59,15 +59,7 @@ Once running, it will appear in your taskbar and as a tray icon in the notificat
|
||||
|
||||
### System Tray Icon
|
||||
|
||||
The tray icon shows your current 5-hour usage as a color-coded percentage badge:
|
||||
|
||||
| Color | Meaning |
|
||||
|--------|-------------------|
|
||||
| Green | Under 50% used |
|
||||
| Yellow | 50–75% used |
|
||||
| Orange | 75–90% used |
|
||||
| Red | 90% or more used |
|
||||
| Gray | No data available |
|
||||
The tray icon shows your current 5-hour usage as a color-coded percentage badge.
|
||||
|
||||
Hovering over the tray icon shows a tooltip with both your 5h and 7d usage.
|
||||
|
||||
|
||||
@ -663,55 +663,48 @@ fn format_countdown(resets_at: Option<SystemTime>, strings: Strings) -> String {
|
||||
Err(_) => return strings.now.to_string(),
|
||||
};
|
||||
|
||||
let total_secs = remaining.as_secs();
|
||||
let total_mins = total_secs / 60;
|
||||
let total_hours = total_secs / 3600;
|
||||
let total_days = total_secs / 86400;
|
||||
|
||||
if total_days >= 1 {
|
||||
format!("{total_days}{}", strings.day_suffix)
|
||||
} else if total_mins > 61 {
|
||||
format!("{total_hours}{}", strings.hour_suffix)
|
||||
} else if total_secs > 60 {
|
||||
format!("{total_mins}{}", strings.minute_suffix)
|
||||
} else {
|
||||
format!("{total_secs}{}", strings.second_suffix)
|
||||
}
|
||||
format_countdown_from_secs(remaining.as_secs(), strings)
|
||||
}
|
||||
|
||||
/// Calculate how long until the display text would change
|
||||
pub fn time_until_display_change(resets_at: Option<SystemTime>) -> Option<Duration> {
|
||||
let reset = resets_at?;
|
||||
let remaining = reset.duration_since(SystemTime::now()).ok()?;
|
||||
Some(time_until_display_change_from_secs(remaining.as_secs()))
|
||||
}
|
||||
|
||||
let total_secs = remaining.as_secs();
|
||||
fn format_countdown_from_secs(total_secs: u64, strings: Strings) -> String {
|
||||
let total_mins = total_secs / 60;
|
||||
let total_hours = total_secs / 3600;
|
||||
let total_days = total_secs / 86400;
|
||||
|
||||
if total_secs <= 60 {
|
||||
// Update every second during final countdown
|
||||
return Some(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
let next_boundary = if total_days >= 1 {
|
||||
Duration::from_secs(total_days * 86400)
|
||||
} else if total_mins > 61 {
|
||||
if total_hours > 1 {
|
||||
Duration::from_secs(total_hours * 3600)
|
||||
} else {
|
||||
Duration::from_secs(61 * 60)
|
||||
}
|
||||
if total_days >= 1 {
|
||||
format!("{total_days}{}", strings.day_suffix)
|
||||
} else if total_hours >= 1 {
|
||||
format!("{total_hours}{}", strings.hour_suffix)
|
||||
} else if total_mins >= 1 {
|
||||
format!("{total_mins}{}", strings.minute_suffix)
|
||||
} else {
|
||||
Duration::from_secs(total_mins * 60)
|
||||
format!("{total_secs}{}", strings.second_suffix)
|
||||
}
|
||||
}
|
||||
|
||||
fn time_until_display_change_from_secs(total_secs: u64) -> Duration {
|
||||
let total_mins = total_secs / 60;
|
||||
let total_hours = total_secs / 3600;
|
||||
let total_days = total_secs / 86400;
|
||||
|
||||
let current_bucket_start = if total_days >= 1 {
|
||||
total_days * 86400
|
||||
} else if total_hours >= 1 {
|
||||
total_hours * 3600
|
||||
} else if total_mins >= 1 {
|
||||
total_mins * 60
|
||||
} else {
|
||||
total_secs
|
||||
};
|
||||
|
||||
let delay = remaining.saturating_sub(next_boundary);
|
||||
if delay > Duration::ZERO {
|
||||
Some(delay + Duration::from_secs(1))
|
||||
} else {
|
||||
Some(Duration::from_secs(1))
|
||||
}
|
||||
Duration::from_secs(total_secs.saturating_sub(current_bucket_start) + 1)
|
||||
}
|
||||
|
||||
/// Returns true if either section has reached "now" (reset time has passed).
|
||||
|
||||
@ -1179,8 +1179,8 @@ fn paint_content(
|
||||
let _ = DeleteObject(right_brush);
|
||||
|
||||
let content_x = sc(LEFT_DIVIDER_W) + sc(DIVIDER_RIGHT_MARGIN);
|
||||
let row1_y = sc(5);
|
||||
let row2_y = sc(5) + sc(SEGMENT_H) + sc(10);
|
||||
let row2_y = height - sc(5) - sc(SEGMENT_H);
|
||||
let row1_y = row2_y - sc(10) - sc(SEGMENT_H);
|
||||
|
||||
let _ = SetBkMode(hdc, TRANSPARENT);
|
||||
let _ = SetTextColor(hdc, COLORREF(text_color.to_colorref()));
|
||||
@ -1408,14 +1408,12 @@ fn position_at_taskbar() {
|
||||
|
||||
let taskbar_height = taskbar_rect.bottom - taskbar_rect.top;
|
||||
let mut tray_left = taskbar_rect.right;
|
||||
let mut anchor_top = taskbar_rect.top;
|
||||
let mut anchor_height = taskbar_height;
|
||||
let anchor_top = taskbar_rect.top;
|
||||
let anchor_height = taskbar_height;
|
||||
|
||||
if let Some(tray_hwnd) = native_interop::find_child_window(taskbar_hwnd, "TrayNotifyWnd") {
|
||||
if let Some(tray_rect) = native_interop::get_window_rect_safe(tray_hwnd) {
|
||||
tray_left = tray_rect.left;
|
||||
anchor_top = tray_rect.top;
|
||||
anchor_height = tray_rect.bottom - tray_rect.top;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1443,8 +1441,7 @@ fn position_at_taskbar() {
|
||||
|
||||
fn compute_anchor_y(anchor_top: i32, anchor_height: i32, widget_height: i32) -> i32 {
|
||||
let anchor_bottom = anchor_top + anchor_height;
|
||||
let bottom_padding = (anchor_height - widget_height).clamp(0, sc(6));
|
||||
(anchor_bottom - widget_height - bottom_padding).max(anchor_top)
|
||||
(anchor_bottom - widget_height).max(anchor_top)
|
||||
}
|
||||
|
||||
/// WinEvent callback for tray icon location changes
|
||||
@ -1667,16 +1664,14 @@ unsafe extern "system" fn wnd_proc(
|
||||
if let Some(taskbar_rect) = native_interop::get_taskbar_rect(taskbar_hwnd) {
|
||||
let taskbar_height = taskbar_rect.bottom - taskbar_rect.top;
|
||||
let mut tray_left = taskbar_rect.right;
|
||||
let mut anchor_top = taskbar_rect.top;
|
||||
let mut anchor_height = taskbar_height;
|
||||
let anchor_top = taskbar_rect.top;
|
||||
let anchor_height = taskbar_height;
|
||||
if let Some(tray_hwnd) =
|
||||
native_interop::find_child_window(taskbar_hwnd, "TrayNotifyWnd")
|
||||
{
|
||||
if let Some(tray_rect) = native_interop::get_window_rect_safe(tray_hwnd)
|
||||
{
|
||||
tray_left = tray_rect.left;
|
||||
anchor_top = tray_rect.top;
|
||||
anchor_height = tray_rect.bottom - tray_rect.top;
|
||||
}
|
||||
}
|
||||
let widget_width = total_widget_width();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user