Add charging indicator and pacman package, bump to 1.1.0

- Show charge/discharge power in watts, a battery graphic with charge
  bolt, a power history graph and the estimated time until full or
  remaining (respecting the 80 % charge limit) in the app, the panel
  widget, its tooltip and `predatorctl status`
- Add packaging/arch/PKGBUILD that builds a pacman package; the kernel
  module ships as DKMS source and is rebuilt for every kernel
- Bump version to 1.1.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 17:37:03 +02:00
co-authored by Claude Opus 5.5
parent ea7ce49452
commit 2ef62e85cd
17 changed files with 315 additions and 25 deletions
+20 -14
View File
@@ -17,7 +17,7 @@ from PySide6.QtWidgets import (QApplication, QButtonGroup, QCheckBox, QComboBox,
from .. import APP_NAME, __version__, layout
from ..client import Client, DaemonError, read_state
from . import theme
from .widgets import Bar, Card, ColorRow, CurveEditor, Gauge, KeyboardPainter
from .widgets import Bar, Card, ChargeIndicator, ColorRow, CurveEditor, Gauge, KeyboardPainter, format_minutes
PAGES = [
("home", "Dashboard"),
@@ -146,8 +146,9 @@ class HomePage(Page):
quick.body.addWidget(self.l_profile)
self.l_fans = _muted("")
quick.body.addWidget(self.l_fans)
self.l_bat = _muted("")
quick.body.addWidget(self.l_bat)
self.charge = ChargeIndicator(compact=True)
self.charge.setFixedHeight(54)
quick.body.addWidget(self.charge)
b = QPushButton("Next profile")
b.setObjectName("primary")
b.clicked.connect(self._cycle)
@@ -182,7 +183,7 @@ class HomePage(Page):
self.l_profile.setText(PROFILE_INFO.get(st["profile"], (st["profile"],))[0])
self.l_fans.setText("Fans: %s" % st["config"]["fans"]["mode"].capitalize())
b = st["battery"]
self.l_bat.setText("Battery %s %% · %s" % (b.get("capacity"), "AC" if b.get("ac_online") else "on battery"))
self.charge.set_battery(b)
class PerformancePage(Page):
@@ -595,14 +596,13 @@ class BatteryPage(Page):
self.lay.addWidget(_title("Battery", "Battery status and health features"))
row = QHBoxLayout()
row.setSpacing(16)
info = Card("Status")
self.cap = QLabel("–")
self.cap.setObjectName("big")
info.body.addWidget(self.cap)
info = Card("Charging")
self.indicator = ChargeIndicator()
info.body.addWidget(self.indicator)
self.details = _muted("")
info.body.addWidget(self.details)
info.body.addStretch(1)
row.addWidget(info, 1)
row.addWidget(info, 3)
health = Card("Battery health")
self.limit = QCheckBox("Limit charging to 80 %")
self.limit.clicked.connect(lambda v: self._set("set_battery_limit", v))
@@ -648,14 +648,17 @@ class BatteryPage(Page):
def update_state(self, st):
b = st["battery"]
if not b.get("present"):
self.cap.setText("No battery")
self.details.setText("No battery found")
return
self.cap.setText("%s %%" % b.get("capacity"))
lines = ["Status: %s" % b.get("status"),
"Power source: %s" % ("AC adapter" if b.get("ac_online") else "battery"),
"Power draw: %s W" % b.get("power_w"),
self.indicator.set_battery(b)
rate = "Charge rate" if b.get("charging") else "Power draw"
lines = ["Power source: %s" % ("AC adapter" if b.get("ac_online") else "battery"),
"%s: %s W" % (rate, b.get("power_w") if b.get("power_w") is not None else "–"),
"Health: %s %% of design capacity" % b.get("health"),
"Charge cycles: %s" % b.get("cycles")]
if b.get("minutes_left") is not None:
lines.insert(2, "%s: %s" % ("Time until full" + (" (80 % limit)" if st.get("battery_limit") else "")
if b.get("charging") else "Time remaining", format_minutes(b["minutes_left"])))
self.details.setText("\n".join(lines))
@@ -826,6 +829,9 @@ class MainWindow(QMainWindow):
self.banner.hide()
self.state = st
self.stack.currentWidget().update_state(st)
bat_page = self.pages["battery"][0]
if self.stack.currentWidget() is not bat_page:
bat_page.indicator.set_battery(st["battery"]) # keep the power history going
s = st["sensors"]
self.footer.setText("%s\nCPU %s °C · GPU %s" % (
PROFILE_INFO.get(st["profile"], (st["profile"],))[0], s.get("cpu_temp"),