diff --git a/Cargo.lock b/Cargo.lock index c537015..e172eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "claude-code-usage-monitor" -version = "1.2.11" +version = "1.3.0" dependencies = [ "dirs", "native-tls", diff --git a/Cargo.toml b/Cargo.toml index 71ac04f..5dd53e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 5a28eda..d1a64bf 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ +![Windows](https://img.shields.io/badge/platform-Windows-blue) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + # Claude Code Usage Monitor +![Screenshot](.github/animation.gif) + 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. -![Windows](https://img.shields.io/badge/platform-Windows-blue) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - -![Screenshot](.github/animation.gif) - ## 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. diff --git a/src/poller.rs b/src/poller.rs index 68fcca9..938075c 100644 --- a/src/poller.rs +++ b/src/poller.rs @@ -663,55 +663,48 @@ fn format_countdown(resets_at: Option, 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) -> Option { 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). diff --git a/src/window.rs b/src/window.rs index c094127..2d1b284 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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();