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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ features = [
"Win32_Security",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_HiDpi",
"Win32_UI_Controls_Dialogs",
]

[build-dependencies]
Expand Down
7 changes: 7 additions & 0 deletions src/localization/dutch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Pictogramkleuren",
claude_code_color: "Kleur voor Claude Code...",
codex_color: "Kleur voor Codex...",
antigravity_color: "Kleur voor Antigravity...",
reset_icon_colors: "Standaardkleuren herstellen",
background_color: "Achtergrondkleur",
text_color: "Tekstkleur",
settings: "Instellingen",
start_with_windows: "Opstarten met Windows",
reset_position: "Positie herstellen",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/english.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Icon Colors",
claude_code_color: "Claude Code Color...",
codex_color: "Codex Color...",
antigravity_color: "Antigravity Color...",
reset_icon_colors: "Reset to Default",
background_color: "Background Color",
text_color: "Text Color",
settings: "Settings",
start_with_windows: "Start with Windows",
reset_position: "Reset Position",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/french.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Couleurs des icônes",
claude_code_color: "Couleur de Claude Code...",
codex_color: "Couleur de Codex...",
antigravity_color: "Couleur d'Antigravity...",
reset_icon_colors: "Réinitialiser les couleurs par défaut",
background_color: "Couleur d'arrière-plan",
text_color: "Couleur du texte",
settings: "Paramètres",
start_with_windows: "Démarrer avec Windows",
reset_position: "Réinitialiser la position",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/german.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Symbolfarben",
claude_code_color: "Farbe für Claude Code...",
codex_color: "Farbe für Codex...",
antigravity_color: "Farbe für Antigravity...",
reset_icon_colors: "Standardfarben zurücksetzen",
background_color: "Hintergrundfarbe",
text_color: "Textfarbe",
settings: "Einstellungen",
start_with_windows: "Mit Windows starten",
reset_position: "Position zurücksetzen",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/japanese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "アイコンの色",
claude_code_color: "Claude Code の色...",
codex_color: "Codex の色...",
antigravity_color: "Antigravity の色...",
reset_icon_colors: "既定の色に戻す",
background_color: "背景色",
text_color: "文字色",
settings: "設定",
start_with_windows: "Windows と同時に開始",
reset_position: "位置をリセット",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/korean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "아이콘 색상",
claude_code_color: "Claude Code 색상...",
codex_color: "Codex 색상...",
antigravity_color: "Antigravity 색상...",
reset_icon_colors: "기본 색상으로 재설정",
background_color: "배경 색상",
text_color: "텍스트 색상",
settings: "설정",
start_with_windows: "Windows 시작 시 자동 실행",
reset_position: "위치 초기화",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ pub struct Strings {
pub claude_code_model: &'static str,
pub codex_model: &'static str,
pub antigravity_model: &'static str,
pub icon_colors: &'static str,
pub claude_code_color: &'static str,
pub codex_color: &'static str,
pub antigravity_color: &'static str,
pub reset_icon_colors: &'static str,
pub background_color: &'static str,
pub text_color: &'static str,
pub settings: &'static str,
pub start_with_windows: &'static str,
pub reset_position: &'static str,
Expand Down
7 changes: 7 additions & 0 deletions src/localization/portuguese_brazil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Cores do ícone",
claude_code_color: "Cor do Claude Code...",
codex_color: "Cor do Codex...",
antigravity_color: "Cor do Antigravity...",
reset_icon_colors: "Restaurar cores padrão",
background_color: "Cor de fundo",
text_color: "Cor do texto",
settings: "Configurações",
start_with_windows: "Iniciar com o Windows",
reset_position: "Redefinir Posição",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/russian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Цвета значков",
claude_code_color: "Цвет Claude Code...",
codex_color: "Цвет Codex...",
antigravity_color: "Цвет Antigravity...",
reset_icon_colors: "Сбросить цвета по умолчанию",
background_color: "Цвет фона",
text_color: "Цвет текста",
settings: "Настройки",
start_with_windows: "Запускать вместе с Windows",
reset_position: "Сбросить позицию",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/spanish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "Colores del icono",
claude_code_color: "Color de Claude Code...",
codex_color: "Color de Codex...",
antigravity_color: "Color de Antigravity...",
reset_icon_colors: "Restablecer colores predeterminados",
background_color: "Color de fondo",
text_color: "Color de texto",
settings: "Configuración",
start_with_windows: "Iniciar con Windows",
reset_position: "Restablecer posición",
Expand Down
7 changes: 7 additions & 0 deletions src/localization/traditional_chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub(super) const STRINGS: Strings = Strings {
claude_code_model: "Claude Code",
codex_model: "Codex",
antigravity_model: "Antigravity",
icon_colors: "圖示顏色",
claude_code_color: "Claude Code 顏色...",
codex_color: "Codex 顏色...",
antigravity_color: "Antigravity 顏色...",
reset_icon_colors: "重設為預設顏色",
background_color: "背景顏色",
text_color: "文字顏色",
settings: "設定",
start_with_windows: "開機時啟動",
reset_position: "重置位置",
Expand Down
12 changes: 12 additions & 0 deletions src/native_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,19 @@ impl Color {
Self { r, g, b }
}

pub fn to_hex(self) -> String {
format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}

pub fn to_colorref(self) -> u32 {
colorref(self.r, self.g, self.b)
}

pub fn from_colorref(cr: u32) -> Self {
Self {
r: (cr & 0xFF) as u8,
g: ((cr >> 8) & 0xFF) as u8,
b: ((cr >> 16) & 0xFF) as u8,
}
}
}
122 changes: 81 additions & 41 deletions src/tray_icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ pub struct TrayIconData {
pub kind: TrayIconKind,
pub percent: Option<f64>,
pub tooltip: String,
pub custom_color: Option<IconColorOverride>,
}

/// User-chosen background/text color that replaces the default usage-based
/// badge coloring for a given tray icon kind.
#[derive(Clone, Copy)]
pub struct IconColorOverride {
pub background: Color,
pub text: Color,
}

impl TrayIconKind {
Expand Down Expand Up @@ -104,8 +113,12 @@ fn antigravity_fill(percent: f64) -> Color {
/// Create a rounded-rectangle tray icon badge showing the usage percentage.
/// For Claude, `percent` = None uses the embedded app icon as the loading state.
/// For Codex and Antigravity, `percent` = None uses a provider placeholder badge.
pub fn create_icon(kind: TrayIconKind, percent: Option<f64>) -> HICON {
if matches!(kind, TrayIconKind::Claude) && percent.is_none() {
pub fn create_icon(
kind: TrayIconKind,
percent: Option<f64>,
custom_color: Option<IconColorOverride>,
) -> HICON {
if matches!(kind, TrayIconKind::Claude) && percent.is_none() && custom_color.is_none() {
let app_icon = load_embedded_app_icon();
if !app_icon.is_invalid() {
return app_icon;
Expand All @@ -121,24 +134,37 @@ pub fn create_icon(kind: TrayIconKind, percent: Option<f64>) -> HICON {
0_i32
};

let fill = match kind {
TrayIconKind::Claude => interpolated_fill(percent.unwrap_or(0.0)),
TrayIconKind::Codex => codex_fill(percent.unwrap_or(0.0)),
TrayIconKind::Antigravity => antigravity_fill(percent.unwrap_or(0.0)),
let fill = match custom_color {
Some(over) => over.background,
None => match kind {
TrayIconKind::Claude => interpolated_fill(percent.unwrap_or(0.0)),
TrayIconKind::Codex => codex_fill(percent.unwrap_or(0.0)),
TrayIconKind::Antigravity => antigravity_fill(percent.unwrap_or(0.0)),
},
};
let text_col = match kind {
TrayIconKind::Claude => Color::from_hex("#FFFFFF"),
TrayIconKind::Codex if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#111111"),
TrayIconKind::Codex => Color::from_hex("#FFFFFF"),
TrayIconKind::Antigravity if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#1967D2"),
TrayIconKind::Antigravity => Color::from_hex("#FFFFFF"),
let text_col = match custom_color {
Some(over) => over.text,
None => match kind {
TrayIconKind::Claude => Color::from_hex("#FFFFFF"),
TrayIconKind::Codex if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#111111"),
TrayIconKind::Codex => Color::from_hex("#FFFFFF"),
TrayIconKind::Antigravity if percent.unwrap_or(0.0) >= 90.0 => {
Color::from_hex("#1967D2")
}
TrayIconKind::Antigravity => Color::from_hex("#FFFFFF"),
},
};
let outline_col = match kind {
TrayIconKind::Claude => fill,
TrayIconKind::Codex if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#111111"),
TrayIconKind::Codex => Color::from_hex("#FFFFFF"),
TrayIconKind::Antigravity if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#1967D2"),
TrayIconKind::Antigravity => Color::from_hex("#FFFFFF"),
let outline_col = match custom_color {
Some(over) => over.background,
None => match kind {
TrayIconKind::Claude => fill,
TrayIconKind::Codex if percent.unwrap_or(0.0) >= 90.0 => Color::from_hex("#111111"),
TrayIconKind::Codex => Color::from_hex("#FFFFFF"),
TrayIconKind::Antigravity if percent.unwrap_or(0.0) >= 90.0 => {
Color::from_hex("#1967D2")
}
TrayIconKind::Antigravity => Color::from_hex("#FFFFFF"),
},
};

let display_text = match percent {
Expand Down Expand Up @@ -249,19 +275,21 @@ pub fn create_icon(kind: TrayIconKind, percent: Option<f64>) -> HICON {
let _ = SetBkMode(mem_dc, TRANSPARENT);
let _ = SetTextColor(mem_dc, COLORREF(text_col.to_colorref()));

let mut text_rect = RECT {
left: margin,
top: margin,
right: size - margin,
bottom: size - margin,
};
let mut text_wide: Vec<u16> = display_text.encode_utf16().collect();
let _ = DrawTextW(
mem_dc,
&mut text_wide,
&mut text_rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE,
);
if !display_text.is_empty() {
let mut text_rect = RECT {
left: margin,
top: margin,
right: size - margin,
bottom: size - margin,
};
let mut text_wide: Vec<u16> = display_text.encode_utf16().collect();
let _ = DrawTextW(
mem_dc,
&mut text_wide,
&mut text_rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE,
);
}

SelectObject(mem_dc, old_font);
let _ = DeleteObject(font);
Expand Down Expand Up @@ -360,8 +388,14 @@ fn copy_wide_256(s: &str, buf: &mut [u16; 256]) {
}

/// Register the tray icon with the shell.
pub fn add(hwnd: HWND, kind: TrayIconKind, percent: Option<f64>, tooltip: &str) {
let hicon = create_icon(kind, percent);
pub fn add(
hwnd: HWND,
kind: TrayIconKind,
percent: Option<f64>,
tooltip: &str,
custom_color: Option<IconColorOverride>,
) {
let hicon = create_icon(kind, percent, custom_color);
unsafe {
let mut nid: NOTIFYICONDATAW = std::mem::zeroed();
nid.cbSize = std::mem::size_of::<NOTIFYICONDATAW>() as u32;
Expand All @@ -379,8 +413,14 @@ pub fn add(hwnd: HWND, kind: TrayIconKind, percent: Option<f64>, tooltip: &str)
}

/// Update the tray icon colour and tooltip to reflect current usage.
pub fn update(hwnd: HWND, kind: TrayIconKind, percent: Option<f64>, tooltip: &str) {
let hicon = create_icon(kind, percent);
pub fn update(
hwnd: HWND,
kind: TrayIconKind,
percent: Option<f64>,
tooltip: &str,
custom_color: Option<IconColorOverride>,
) {
let hicon = create_icon(kind, percent, custom_color);
unsafe {
let mut nid: NOTIFYICONDATAW = std::mem::zeroed();
nid.cbSize = std::mem::size_of::<NOTIFYICONDATAW>() as u32;
Expand Down Expand Up @@ -419,22 +459,22 @@ pub fn sync(hwnd: HWND, icons: &[TrayIconData]) {
.find(|icon| matches!(icon.kind, TrayIconKind::Antigravity));

if let Some(icon) = show_claude {
add(hwnd, icon.kind, icon.percent, &icon.tooltip);
update(hwnd, icon.kind, icon.percent, &icon.tooltip);
add(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
update(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
} else {
remove(hwnd, TrayIconKind::Claude);
}

if let Some(icon) = show_codex {
add(hwnd, icon.kind, icon.percent, &icon.tooltip);
update(hwnd, icon.kind, icon.percent, &icon.tooltip);
add(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
update(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
} else {
remove(hwnd, TrayIconKind::Codex);
}

if let Some(icon) = show_antigravity {
add(hwnd, icon.kind, icon.percent, &icon.tooltip);
update(hwnd, icon.kind, icon.percent, &icon.tooltip);
add(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
update(hwnd, icon.kind, icon.percent, &icon.tooltip, icon.custom_color);
} else {
remove(hwnd, TrayIconKind::Antigravity);
}
Expand Down
Loading