This commit is contained in:
2026-04-01 04:07:48 +09:00
parent 3e7e68a486
commit 5e06b9e157
5 changed files with 85 additions and 6 deletions

87
org.batteryd/battery-viewer Normal file → Executable file
View File

@@ -167,7 +167,7 @@ class BatteryChart(Gtk.DrawingArea):
t_span = t_max - t_min
energies = [s[2] for s in self.samples]
e_min = min(energies)
e_min = 0
e_max = max(energies)
if e_max <= e_min:
e_max = e_min + 1
@@ -395,6 +395,7 @@ class SessionRow(Adw.ActionRow):
t_start = datetime.fromtimestamp(start_ts).strftime('%H:%M')
t_end = datetime.fromtimestamp(end_ts).strftime('%H:%M')
duration = end_ts - start_ts
hours = duration / 3600
if status == 'Discharging':
icon = 'battery-level-50-symbolic'
@@ -406,20 +407,71 @@ class SessionRow(Adw.ActionRow):
icon = 'battery-level-100-symbolic'
arrow = '→'
subtitle = f'{t_start} {t_end} · {format_duration(duration)}'
if hours > 0 and status in ('Discharging', 'Charging'):
rate = abs(end_level - start_level) / hours
subtitle += f' · {rate:.1f}%/hr'
self.set_title(status)
self.set_subtitle(f'{t_start} {t_end} · {format_duration(duration)}')
self.set_subtitle(subtitle)
img = Gtk.Image.new_from_icon_name(icon)
img.set_pixel_size(24)
self.add_prefix(img)
label = Gtk.Label()
label.set_markup(f'{start_level:.0f}% {arrow} {end_level:.0f}%')
delta = end_level - start_level
if status == 'Discharging':
label.set_markup(f'{abs(delta):.0f}%')
elif status == 'Charging':
label.set_markup(f'+{abs(delta):.0f}%')
else:
label.set_markup(f'{abs(delta):.0f}%')
label.add_css_class('caption')
label.set_valign(Gtk.Align.CENTER)
self.add_suffix(label)
class GapRow(Adw.ActionRow):
"""Inferred gap between sessions (suspend/shutdown)."""
def __init__(self, prev_end_ts, prev_end_level, next_start_ts, next_start_level):
super().__init__()
t_start = datetime.fromtimestamp(prev_end_ts).strftime('%H:%M')
t_end = datetime.fromtimestamp(next_start_ts).strftime('%H:%M')
duration = next_start_ts - prev_end_ts
delta = next_start_level - prev_end_level
if abs(delta) < 0.5:
kind = 'Shutdown / Hibernate'
icon = 'system-shutdown-symbolic'
elif delta > 0:
kind = 'Suspended (charged)'
icon = 'battery-level-50-charging-symbolic'
else:
kind = 'Suspended'
icon = 'media-playback-pause-symbolic'
self.set_title(kind)
self.set_subtitle(f'{t_start} {t_end} · {format_duration(duration)}')
self.add_css_class('dim-label')
img = Gtk.Image.new_from_icon_name(icon)
img.set_pixel_size(24)
self.add_prefix(img)
if abs(delta) >= 0.5:
label = Gtk.Label()
if delta > 0:
label.set_markup(f'+{abs(delta):.0f}%')
else:
label.set_markup(f'{abs(delta):.0f}%')
label.add_css_class('caption')
label.set_valign(Gtk.Align.CENTER)
self.add_suffix(label)
# ── Main window ───────────────────────────────────────────
class BatteryWindow(Adw.ApplicationWindow):
@@ -572,14 +624,41 @@ class BatteryWindow(Adw.ApplicationWindow):
self.session_group.set_margin_top(16)
self.content.append(self.session_group)
# merge adjacent sessions with same status and small gaps
MERGE_GAP = 180 # 3 minutes — merge if same status and gap < this
GAP_THRESHOLD = 180 # show gap row if gap >= this between different statuses
merged = []
for s in sessions:
start_ts, end_ts, start_lvl, end_lvl, status = s
if merged:
p_start, p_end, p_slvl, p_elvl, p_status = merged[-1]
gap = start_ts - p_end
if p_status == status and gap < MERGE_GAP:
# extend previous session
merged[-1] = (p_start, end_ts, p_slvl, end_lvl, status)
continue
merged.append(s)
prev = None
for s in merged:
start_ts, end_ts, start_lvl, end_lvl, status = s
if end_ts - start_ts < 30:
prev = s
continue
if prev is not None:
_, prev_end, _, prev_end_lvl, _ = prev
gap = start_ts - prev_end
if gap > GAP_THRESHOLD:
gap_row = GapRow(prev_end, prev_end_lvl, start_ts, start_lvl)
self.session_group.add(gap_row)
row = SessionRow(start_ts, end_ts, start_lvl, end_lvl, status)
self.session_group.add(row)
prev = s
if not sessions:
if not merged:
empty = Adw.ActionRow(title='No sessions recorded')
empty.add_css_class('dim-label')
self.session_group.add(empty)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/python3 -sP
"""
batteryd — battery tracking daemon