Skip to content
Open
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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Once running, it will appear in your taskbar and as one or more tray icons in th

- Drag the left divider to move the taskbar widget
- On multi-monitor setups, drag the widget onto another Windows taskbar to move it to that screen
- Right-click the taskbar widget or tray icon for refresh, displayed models, update frequency, Start with Windows, reset position, language, updates, and exit
- Right-click the taskbar widget or tray icon for refresh, displayed models, usage display mode, update frequency, Start with Windows, reset position, language, updates, and exit
- Left-click the tray icon to toggle the taskbar widget on or off
- Enable `Start with Windows` from the right-click menu if you want it to launch automatically when you sign in

Expand All @@ -77,9 +77,15 @@ Use the right-click **Models** menu to choose what the widget displays:

When multiple models are shown, each model has its own usage bar and matching usage text color. Antigravity prefers Google's Gemini quota summary when available and falls back to model quota data when needed.

### Usage Display

Use the top-level **Usage Display** menu below **Models** to choose whether percentages show usage as **Used** or **Remaining**. **Used** remains the default for existing and new installations.

The selected mode applies to the widget text and bars, tray icon badges, and tray tooltips for every enabled provider. Tray warning colors always reflect used quota so that low remaining quota still keeps the high-usage warning style.

### System Tray Icon

The tray icon shows your current 5-hour usage as a percentage badge.
The tray icon shows your current 5-hour usage as a percentage badge using the selected usage display mode.

If multiple providers are enabled, the app shows one tray icon per provider. If only one model is enabled, it shows one tray icon.

Expand Down Expand Up @@ -146,6 +152,7 @@ What the app stores locally:
- Language preference
- Last update check time
- Displayed model preferences
- Usage display preference

What it does **not** do:

Expand Down
3 changes: 3 additions & 0 deletions src/localization/dutch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Instellingen",
usage_display: "Gebruiksweergave",
used_usage: "Gebruikt",
remaining_usage: "Resterend",
start_with_windows: "Opstarten met Windows",
reset_position: "Positie herstellen",
language: "Taal",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/english.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Settings",
usage_display: "Usage Display",
used_usage: "Used",
remaining_usage: "Remaining",
start_with_windows: "Start with Windows",
reset_position: "Reset Position",
language: "Language",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/french.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Paramètres",
usage_display: "Affichage de l’utilisation",
used_usage: "Utilisé",
remaining_usage: "Restant",
start_with_windows: "Démarrer avec Windows",
reset_position: "Réinitialiser la position",
language: "Langue",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/german.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Einstellungen",
usage_display: "Nutzungsanzeige",
used_usage: "Verwendet",
remaining_usage: "Verbleibend",
start_with_windows: "Mit Windows starten",
reset_position: "Position zurücksetzen",
language: "Sprache",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/japanese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "設定",
usage_display: "使用量表示",
used_usage: "使用済み",
remaining_usage: "残り",
start_with_windows: "Windows と同時に開始",
reset_position: "位置をリセット",
language: "言語",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/korean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "설정",
usage_display: "사용량 표시",
used_usage: "사용됨",
remaining_usage: "남음",
start_with_windows: "Windows 시작 시 자동 실행",
reset_position: "위치 초기화",
language: "언어",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ pub struct Strings {
pub codex_model: &'static str,
pub antigravity_model: &'static str,
pub settings: &'static str,
pub usage_display: &'static str,
pub used_usage: &'static str,
pub remaining_usage: &'static str,
pub start_with_windows: &'static str,
pub reset_position: &'static str,
pub language: &'static str,
Expand Down
3 changes: 3 additions & 0 deletions src/localization/portuguese_brazil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Configurações",
usage_display: "Exibição de Uso",
used_usage: "Usado",
remaining_usage: "Restante",
start_with_windows: "Iniciar com o Windows",
reset_position: "Redefinir Posição",
language: "Idioma",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/russian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Настройки",
usage_display: "Отображение использования",
used_usage: "Использовано",
remaining_usage: "Осталось",
start_with_windows: "Запускать вместе с Windows",
reset_position: "Сбросить позицию",
language: "Язык",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/spanish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "Configuración",
usage_display: "Visualización de uso",
used_usage: "Usado",
remaining_usage: "Restante",
start_with_windows: "Iniciar con Windows",
reset_position: "Restablecer posición",
language: "Idioma",
Expand Down
3 changes: 3 additions & 0 deletions src/localization/traditional_chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(super) const STRINGS: Strings = Strings {
codex_model: "Codex",
antigravity_model: "Antigravity",
settings: "設定",
usage_display: "使用量顯示",
used_usage: "已使用",
remaining_usage: "剩餘",
start_with_windows: "開機時啟動",
reset_position: "重置位置",
language: "語言",
Expand Down
45 changes: 45 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
use std::time::SystemTime;

use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UsageDisplayMode {
#[default]
Used,
Remaining,
}

impl UsageDisplayMode {
pub fn display_percentage(self, used_percentage: f64) -> f64 {
let used_percentage = used_percentage.clamp(0.0, 100.0);
match self {
Self::Used => used_percentage,
Self::Remaining => 100.0 - used_percentage,
}
}
}

#[derive(Clone, Debug, Default)]
pub struct UsageSection {
pub percentage: f64,
Expand All @@ -18,3 +38,28 @@ pub struct AppUsageData {
pub codex: Option<UsageData>,
pub antigravity: Option<UsageData>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn usage_display_mode_converts_and_clamps_percentages() {
for (used, expected_used, expected_remaining) in [
(-5.0, 0.0, 100.0),
(0.0, 0.0, 100.0),
(42.0, 42.0, 58.0),
(100.0, 100.0, 0.0),
(105.0, 100.0, 0.0),
] {
assert_eq!(
UsageDisplayMode::Used.display_percentage(used),
expected_used
);
assert_eq!(
UsageDisplayMode::Remaining.display_percentage(used),
expected_remaining
);
}
}
}
31 changes: 28 additions & 3 deletions src/poller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::os::windows::process::CommandExt;

use crate::diagnose;
use crate::localization::Strings;
use crate::models::{AppUsageData, UsageData, UsageSection};
use crate::models::{AppUsageData, UsageData, UsageDisplayMode, UsageSection};

const USAGE_URL: &str = "https://api.anthropic.com/api/oauth/usage";
const MESSAGES_URL: &str = "https://api.anthropic.com/v1/messages";
Expand Down Expand Up @@ -1522,8 +1522,15 @@ fn is_leap(y: u64) -> bool {
}

/// Format a usage section as "X% · Yh" style text
pub fn format_line(section: &UsageSection, strings: Strings) -> String {
let pct = format!("{:.0}%", section.percentage);
pub fn format_line(
section: &UsageSection,
strings: Strings,
usage_display: UsageDisplayMode,
) -> String {
let pct = format!(
"{:.0}%",
usage_display.display_percentage(section.percentage)
);
let cd = format_countdown(section.resets_at, strings);
if cd.is_empty() {
pct
Expand Down Expand Up @@ -1603,6 +1610,7 @@ pub fn app_is_past_reset(data: &AppUsageData) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::localization::LanguageId;

fn usage_with_session_percent(percentage: f64) -> UsageData {
UsageData {
Expand All @@ -1614,6 +1622,23 @@ mod tests {
}
}

#[test]
fn format_line_respects_usage_display_mode_and_preserves_countdown() {
let section = UsageSection {
percentage: 42.0,
resets_at: Some(SystemTime::now() + Duration::from_secs(7_201)),
};
let strings = LanguageId::English.strings();

let used = format_line(&section, strings, UsageDisplayMode::Used);
let remaining = format_line(&section, strings, UsageDisplayMode::Remaining);

assert!(used.starts_with("42% · "));
assert!(remaining.starts_with("58% · "));
assert_eq!(used.split_once(" · ").unwrap().1, "2h");
assert_eq!(remaining.split_once(" · ").unwrap().1, "2h");
}

#[test]
fn claude_failure_does_not_block_codex_when_both_are_enabled() {
let data = poll_with(
Expand Down
Loading