Skip to content

Commit ba49eb5

Browse files
committed
feat(tray): add ADR-012 for multi-icon architecture + update battery plugin
ADR-012: Multi-Icon Tray Architecture for Linux - Documents process-based architecture using crossbar_tray_daemon - Explains SNI/AppIndicator limitation workaround - Lists known limitations (menu updates require click) Battery plugin updated to 2s refresh rate with argos features: - Dynamic Freedesktop icons (battery-level-X-symbolic) - Power consumption in Watts via upower/sysfs - Time remaining to full/empty - Color coding by battery state Closes #38
1 parent f2fa286 commit ba49eb5

File tree

3 files changed

+154
-52
lines changed

3 files changed

+154
-52
lines changed

AGENTS.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,15 +518,46 @@ Plugin Lua → CrossbarBridge.batterySync()
518518
MainActivity.kt → BatteryManager (API oficial)
519519
↓ (Desktop)
520520
SystemApi → /sys/class/power_supply (acesso direto)
521+
---
522+
523+
### ADR-012: Multi-Icon Tray Architecture for Linux (2025-12-23)
524+
525+
**Status**: ✅ Accepted
526+
**Context**: Linux limita 1 ícone de tray por aplicação via libappindicator/SNI. O package `xdg_status_notifier_item` usa sufixo D-Bus fixo (`-1`), causando sobrescrita quando múltiplos ícones são criados.
527+
528+
**Decision**: Implementar arquitetura de **processos separados** onde cada plugin spawna um daemon (`crossbar_tray_daemon`) independente que cria seu próprio ícone SNI.
529+
530+
**Architecture**:
521531
```
522532
533+
Crossbar Main ──stdin/JSON──> Daemon (CPU) ──> SNI Icon 1
534+
──stdin/JSON──> Daemon (Battery) ──> SNI Icon 2
535+
──stdin/JSON──> Daemon (Memory) ──> SNI Icon 3
536+
537+
````
538+
539+
**Components**:
540+
- `bin/crossbar_tray_daemon.dart`: Daemon standalone que cria 1 ícone SNI
541+
- `lib/services/tray/backends/process_spawn_tray_backend.dart`: Gerencia spawn de daemons
542+
- `PluginOutput.trayIcon`: Campo para ícones Freedesktop dinâmicos (ex: `battery-level-50-symbolic`)
543+
544+
**Known Limitations**:
545+
- Menus só atualizam visualmente ao clicar no ícone (limitação do protocolo SNI)
546+
- Ícones não mudam dinamicamente sem recriar (falta `NewIcon` signal no package)
547+
548+
**Consequences**:
549+
- ✅ Múltiplos ícones funcionam no Linux GNOME/KDE
550+
- ✅ Cada plugin pode ter seu próprio ícone de tema Freedesktop
551+
- ⚠️ Processos adicionais (limitado a 10 ícones)
552+
- ⚠️ Atualização visual requer interação do usuário
553+
523554
### Template para Novas ADRs
524555
525556
```markdown
526557
### ADR-XXX: Título (YYYY-MM-DD)
527558
528-
**Status**: 🟡 Proposed | ✅ Accepted | ❌ Rejected | ⚠️ Deprecated
529-
**Context**: Qual problema estamos resolvendo?
530-
**Decision**: O que decidimos fazer?
559+
**Status**: 🟡 Proposed | ✅ Accepted | ❌ Rejected | ⚠️ Deprecated
560+
**Context**: Qual problema estamos resolvendo?
561+
**Decision**: O que decidimos fazer?
531562
**Consequences**: Quais são os trade-offs e impactos?
532-
```
563+
````

plugins/battery/battery.2s.sh

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/bin/bash
2+
# battery.2s.sh - Battery monitor with dynamic icons (argos-compatible)
3+
# Updates every 2 seconds for real-time monitoring
4+
#
5+
# Features:
6+
# - Dynamic Freedesktop icons (battery-level-X-symbolic)
7+
# - Power consumption in Watts
8+
# - Time remaining to full/empty
9+
# - Color coding by state
10+
#
11+
# Based on: https://github.com/p-e-w/argos
12+
13+
set -u
14+
15+
# ─── Data Sources ───────────────────────────────────────────────────────────
16+
17+
get_bat_upower() {
18+
upower -e 2>/dev/null | grep -E '/battery_' | head -n1
19+
}
20+
21+
get_bat_sysfs() {
22+
local up dev
23+
up=$(get_bat_upower)
24+
if [ -n "$up" ]; then
25+
dev=$(upower -i "$up" 2>/dev/null | awk -F': *' '/native-path:/{print $2; exit}')
26+
[ -n "$dev" ] && [ -d "/sys/class/power_supply/$dev" ] && { echo "/sys/class/power_supply/$dev"; return; }
27+
fi
28+
for d in /sys/class/power_supply/BAT*; do [ -d "$d" ] && { echo "$d"; return; }; done
29+
echo ""
30+
}
31+
32+
UP_BAT="$(get_bat_upower)"
33+
SYS_BAT="$(get_bat_sysfs)"
34+
35+
# ─── UPower Data ────────────────────────────────────────────────────────────
36+
37+
state="$(upower -i "$UP_BAT" 2>/dev/null | awk -F': *' '/state:/{print $2; exit}')"
38+
percent="$(upower -i "$UP_BAT" 2>/dev/null | awk -F': *' '/percentage:/{gsub("%","",$2); print $2; exit}')"
39+
erate="$(LC_ALL=C upower -i "$UP_BAT" 2>/dev/null | awk -F': *' '/energy-rate:/{gsub(" W","",$2); print $2; exit}')"
40+
ttfull="$(upower -i "$UP_BAT" 2>/dev/null | awk -F': *' '/time to full:/{print $2; exit}')"
41+
ttempty="$(upower -i "$UP_BAT" 2>/dev/null | awk -F': *' '/time to empty:/{print $2; exit}')"
42+
43+
# ─── Sysfs Power Calculation ────────────────────────────────────────────────
44+
45+
p_sysfs=""
46+
if [ -n "$SYS_BAT" ] && [ -f "$SYS_BAT/voltage_now" ] && [ -f "$SYS_BAT/current_now" ]; then
47+
v=$(cat "$SYS_BAT/voltage_now" 2>/dev/null)
48+
i=$(cat "$SYS_BAT/current_now" 2>/dev/null)
49+
p_sysfs=$(awk -v v="$v" -v i="$i" 'BEGIN{p=v*i/1e12; if(p<0)p=-p; printf "%.1f", p}')
50+
fi
51+
52+
# ─── Dynamic Icon ───────────────────────────────────────────────────────────
53+
54+
get_battery_icon() {
55+
local pct="$1"
56+
local st="$2"
57+
local level=$((pct / 10 * 10))
58+
[ "$level" -gt 100 ] && level=100
59+
[ "$level" -lt 0 ] && level=0
60+
61+
local suffix=""
62+
if [ "$st" = "charging" ]; then
63+
suffix="-charging"
64+
elif [ "$st" = "fully-charged" ] || [ "$st" = "charged" ]; then
65+
suffix="-charged"
66+
fi
67+
68+
if [ "$pct" -eq 100 ] && [ "$st" = "fully-charged" ]; then
69+
echo "battery-level-100-charged-symbolic"
70+
elif [ "$pct" -le 5 ]; then
71+
echo "battery-level-0${suffix}-symbolic"
72+
else
73+
echo "battery-level-${level}${suffix}-symbolic"
74+
fi
75+
}
76+
77+
# ─── Output ─────────────────────────────────────────────────────────────────
78+
79+
if [ -z "$percent" ]; then
80+
echo "🔋 --"
81+
echo "---"
82+
echo "No battery detected"
83+
exit 0
84+
fi
85+
86+
icon_name=$(get_battery_icon "${percent:-50}" "$state")
87+
88+
# Color by state
89+
if [ "$state" = "discharging" ]; then
90+
if [ "$percent" -lt 20 ]; then
91+
color="color=#ff5555"
92+
else
93+
color="color=#f8f8f2"
94+
fi
95+
else
96+
color="color=#8be9fd"
97+
fi
98+
99+
# Panel line with power if available
100+
if [ "$state" = "fully-charged" ] || ([ "$percent" -eq 100 ] && [ "$state" = "charging" ]); then
101+
echo "${percent}% | iconName=$icon_name $color"
102+
elif [ -n "$p_sysfs" ] && [ "$p_sysfs" != "0.0" ]; then
103+
echo "${p_sysfs}W ${percent}% | iconName=$icon_name $color"
104+
elif [ -n "$erate" ] && [ "$erate" != "0" ]; then
105+
echo "${erate}W ${percent}% | iconName=$icon_name $color"
106+
else
107+
echo "🔋 ${percent}% | iconName=$icon_name $color"
108+
fi
109+
110+
# Menu
111+
echo "---"
112+
echo "State: $state"
113+
[ -n "$erate" ] && echo "Power: ${erate} W"
114+
[ -n "$p_sysfs" ] && [ "$p_sysfs" != "0.0" ] && echo "Power (sysfs): ${p_sysfs} W"
115+
[ -n "$ttfull" ] && echo "Time to 100%: $ttfull"
116+
[ -n "$ttempty" ] && echo "Time to 0%: $ttempty"
117+
echo "---"
118+
echo "Open Power Settings | bash='gnome-control-center power' terminal=false"
119+
echo "Refresh | refresh=true"

plugins/battery/battery.30s.sh

Lines changed: 0 additions & 48 deletions
This file was deleted.

0 commit comments

Comments
 (0)