Skip to content

UPower & Power Profiles ​

The Problem ​

A shell needs to display battery percentage, charge state, remaining time, and allow switching power profiles (performance / balanced / power-saver). Reading /sys/class/power_supply directly works but requires polling and parsing sysfs files, and power-profiles-daemon uses D-Bus.

The Naive Approach ​

Reading cat /sys/class/power_supply/BAT0/capacity in a Timer every 5 seconds. This ignores power-supply hotplug, doesn't give charge/discharge rate, and offers no power profile control.

💡Mental Model

UPower is a system-level D-Bus service (org.freedesktop.UPower) that aggregates all power supplies. It emits PropertiesChanged when capacity, state, or time-to-empty changes. Power profiles are managed by net.hadess.PowerProfiles on D-Bus. Both are push-based — subscribe and forget.

The Idea ​

Use Quickshell's Battery widget (Part IV) for the basics, then extend with the Upower D-Bus service directly for advanced metrics. Add a power profile toggle using powerprofilesctl or direct D-Bus calls via Process.

Let's Build It ​

qml
import Quickshell
import Quickshell.Widgets

Row {
  spacing: 6

  Battery {
    id: battery
    visible: battery.available
  }

  // Power profile switcher
  Button {
    text: "Power Saver"
    onClicked: {
      Process.exec("powerprofilesctl set power-saver")
    }
  }

  Button {
    text: "Balanced"
    onClicked: {
      Process.exec("powerprofilesctl set balanced")
    }
  }

  Button {
    text: "Performance"
    onClicked: {
      Process.exec("powerprofilesctl set performance")
    }
  }
}
🛠️Let's Build It

The Battery widget handles UPower D-Bus integration internally — it binds to Battery.percentage, Battery.charging, and Battery.timeRemaining. For power profiles, we shell out to powerprofilesctl. A more robust version would call D-Bus directly via Process with gdbus or busctl.

Let's Improve It ​

Read the current active profile on startup and highlight the active button. Poll powerprofilesctl get at startup and cache it. For a fully reactive approach, subscribe to net.hadess.PowerProfiles PropertiesChanged over D-Bus.

qml
property string activeProfile: "balanced"

Component.onCompleted: {
  Process.run("powerprofilesctl get").stdout.then(out => {
    activeProfile = out.trim()
  })
}
⚠️Common Mistake

Assuming all devices have a battery. Always check Battery.available before showing battery UI. Some desktops (desktops, servers) lack batteries entirely. Also check that powerprofilesctl exists before showing the profile switcher.

Under the Hood ​

UPower exposes org.freedesktop.UPower.Device on each battery. Properties include Percentage, State (charging/discharging/full), TimeToEmpty, EnergyRate. Quickshell's Battery widget subscribes to PropertiesChanged on all matching devices. Power profiles use net.hadess.PowerProfiles which has ActiveProfile and Profiles properties.

Exercises ​

Beginner: Display battery icon that changes based on charge level (0-25%, 25-50%, etc.) and charging state.

Intermediate: Build a power profile popup that shows performance mode descriptions and shows which profile is active.

Advanced: Create a power-saving mode that dims the screen, reduces animation framerate, and pauses non-essential timers when on battery below 20%.

✅What You've Learned
  • UPower and power-profiles-daemon give your shell battery state and performance control
  • Quickshell Battery widget wraps UPower for charge and status
  • Power profiles (performance/balanced/power-saver) require direct D-Bus or powerprofilesctl

Built with VitePress. Licensed under MIT.