refactor: fix latent bugs, invert bubble→app deps, unify color path

Whole-project review pass over the entire crate. No new features.
All function definitions preserved; layout and visibility reorganised.

Bug fixes:
- bubble: ExtractIconExW HICON pair was leaked per bubble toggle.
  Extract once into a process-wide OnceLock, reuse forever (bounded).
- usage/anthropic: parse_iso8601 was stripping the UTC offset without
  applying it — negative-offset users saw countdowns up to 14h wrong.
  Now parses signed minutes and computes utc_secs = local - off*60.
  Also rejects y<1970, mo∉[1,12], d∉[1,max_day(mo,y)] up front so
  malformed API responses can't index DAYS_IN_MONTH out of bounds.
- usage: clamp utilization to [0,100] at all four Window construction
  sites so a misbehaving server can't render "121%".
- bubble: GetDC and CreateCompatibleDC results weren't checked. Guard
  both; release the screen DC on the CreateCompatibleDC failure path.

Refactor:
- Drop type TrayIconKind = ProviderId aliases (5 sites); use ProviderId
  directly everywhere. Inline the identity-function kind_to_provider.
- Delete panel::bar_color_for shim (was just argument-reorder glue).
- Replace local scale_to_dpi fns in bubble.rs and panel.rs with
  use crate::os::dpi::scale as scale_to_dpi (brings os::dpi into the
  live import graph; was unused before).
- Delete dead PCWSTR import + #[allow(dead_code)] sentinel in
  tray/badge.rs; fold the trailing `use BOOL` into the top imports.
- Inline app::primary_dpi() to crate::os::dpi::for_system().

app.rs:
- Add update_settings(|s: &mut AppState|) helper that locks state,
  runs the closure, snapshots Settings, drops the lock, then saves
  to disk. Convert four pure-mutate-then-save callsites.

Layering: bubble.rs no longer reaches upward into crate::app::.
Introduce bubble::Callbacks (fn-pointers), OnceLock<Callbacks>, and
bubble::install_callbacks(). The wnd_proc dispatches the six prior
upward calls via a private dispatch() helper. app::run installs
callbacks once at startup; the six on_bubble_* / recheck_theme fns
are demoted from pub fn to fn.

Resource-warning logs added: dispatch() warns on uninstalled
callbacks; app_icons() warns when ExtractIconExW returns nulls.

Build: cargo build --release clean; cargo clippy reports zero new
warnings (11 pre-existing, all in untouched code).
This commit is contained in:
tiennm99 committed 2026-05-23 11:22:40 +07:00
1 parent a5dec52aaa
commit e243c589a8
8 files changed
+192 -146

No files matched your search

+7 -13
View File
@@ -16,9 +16,9 @@ use windows::Win32::UI::HiDpi::GetDpiForWindow;
use windows::Win32::UI::WindowsAndMessaging::*;
use crate::i18n::LocaleStrings;
use crate::os::dpi::scale as scale_to_dpi;
use crate::os::{to_utf16_nul as wide_str, Rgb as Color};
use crate::usage::ProviderId;
type TrayIconKind = ProviderId;
const CLASS_NAME: &str = "ClaudeCodeUsageBubblePanel";
const PANEL_W_LOGICAL: i32 = 280;
@@ -30,7 +30,7 @@ const RIGHT_TEXT_W_LOGICAL: i32 = 96;
const BAR_HEIGHT_LOGICAL: i32 = 14;
pub struct PanelData {
pub model: TrayIconKind,
pub model: ProviderId,
pub session_pct: f64,
pub session_text: String,
pub weekly_pct: f64,
@@ -86,7 +86,7 @@ pub fn is_visible() -> bool {
.unwrap_or(false)
}
pub fn current_model() -> Option<TrayIconKind> {
pub fn current_model() -> Option<ProviderId> {
lock_state().as_ref().map(|p| p.data.model)
}
@@ -291,8 +291,10 @@ fn paint(hwnd: HWND, hdc: HDC) {
} else {
Color::from_hex("#D6D6D6")
};
let session_accent = bar_color_for(data.model, data.session_pct, data.is_dark);
let weekly_accent = bar_color_for(data.model, data.weekly_pct, data.is_dark);
let session_accent =
crate::usage_color::bar_fill_color(data.model, data.is_dark, data.session_pct);
let weekly_accent =
crate::usage_color::bar_fill_color(data.model, data.is_dark, data.weekly_pct);
unsafe {
let bg_brush = CreateSolidBrush(COLORREF(bg.into_colorref()));
@@ -498,10 +500,6 @@ fn draw_text(
}
}
fn bar_color_for(model: ProviderId, percent: f64, is_dark: bool) -> Color {
crate::usage_color::bar_fill_color(model, is_dark, percent)
}
fn clone_data() -> Option<PanelData> {
let guard = lock_state();
let p = guard.as_ref()?;
@@ -544,7 +542,3 @@ fn place_near(anchor: RECT, panel_w: i32, panel_h: i32) -> (i32, i32) {
}
(x, y)
}
fn scale_to_dpi(logical: i32, dpi: u32) -> i32 {
((logical as i64) * (dpi as i64) / 96) as i32
}