Skip to content

Add Battery & Power Management plugin - #174

Draft
Piero-93 wants to merge 9 commits into
noctalia-dev:mainfrom
Piero-93:add-battery-power-management
Draft

Add Battery & Power Management plugin#174
Piero-93 wants to merge 9 commits into
noctalia-dev:mainfrom
Piero-93:add-battery-power-management

Conversation

@Piero-93

Copy link
Copy Markdown

Plugin

  • Id: piero-93/battery-power-management
  • New plugin
  • Update to an existing plugin (version bumped in plugin.toml)

What it does

A battery status widget and control panel. The bar widget shows charge percentage, live power draw (watts), and the active power profile; clicking it opens a panel to switch the system power profile and set the battery charge-stop threshold — no root at runtime.

Runs entirely locally: reads /sys/class/power_supply/<device>/uevent and /sys/class/power_supply/<device>/charge_control_end_threshold, runs powerprofilesctl and upower, and writes the charge threshold only when the slider is moved. No network access.

Port of my Noctalia v4 plugin of the same name (noctalia-dev/legacy-v4-plugins#864).

External dependencies

  • powerprofilesctl (power-profiles-daemon) — read/set the power profile. Optional: the profile controls are hidden if it is absent (guarded by commandExists).
  • upower — time-to-empty / time-to-full. Optional: the time-remaining line is hidden if absent.

Both are listed in dependencies in plugin.toml.

Testing

Added the bar widget and opened the panel; switched between the three power profiles; moved the charge-limit slider (writes the threshold via a udev-permitted sysfs write); verified the multi-row tooltip (level / status / power / time / profile) and the status coloring. Settings toggled from the plugin settings.

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Noctalia version tested against: 5.0.0
  • Plugin API level: 3

Screenshots / Videos

Battery & Power Management widget and panel

Checklist

  • The directory name matches the part of id after the / in plugin.toml exactly.
  • It ships plugin.toml, README.md, thumbnail.webp, and translations/en.json.
  • README.md follows the README template, documents every entry id and dependency, and includes exact panel IPC commands and launcher prefixes where applicable.
  • I created thumbnail.webp with the thumbnail generator.
  • version follows semver and is bumped in this PR; plugin_api is the oldest API level this plugin requires.
  • Every non-English translation in this PR uses a locale supported by Noctalia core, and I can read, write, and understand that language well enough to review and maintain it (no unreviewed machine/LLM translations).
  • I did not edit catalog.toml; CI generates it.
  • This PR touches exactly one plugin directory.

Code review attestation

Plugins run as trusted, unsandboxed Luau in the user's session. Confirm:

  • The code is readable and not obfuscated, minified, or generated.
  • It does not download and execute remote code.
  • Every network call, filesystem write, and spawned process is something the description above accounts for.
  • I have the right to publish this code under the license declared in plugin.toml.

@ItsLemmy

ItsLemmy commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

`1. non-blocking - battery-power-management/service.luau:164

probeProfileAndEta() interpolates deviceName() into the upower command without
shellQuote, unlike readSysfs at battery-power-management/service.luau:141 which
quotes the same value. deviceName() derives from the user-editable "battery_device"
setting and only strips path separators, so metacharacters survive. noctalia.runAsync
executes through /bin/sh -c (verified in noctalia-shell src/scripting/luau_host.cpp:1866),
so a setting value such as BAT0; <command> runs that command every probe tick.

Impact is limited to a value the user typed into their own plugin settings, so this
is not an external attack path, but the inconsistency is a real defect. Wrapping the
value in shellQuote matches the rest of the file.

  1. non-blocking - battery-power-management/scripts/setup-threshold-permissions.sh:17

    The script writes /etc/udev/rules.d/99-battery-threshold.rules and uses the group
    battery_ctl. The already-published battery-threshold plugin uses the identical rule
    path and group (battery-threshold/setup_rules.sh:82). The write here is a truncating
    redirect, so a user who has both plugins set up will silently lose the other rule.

    The replacement also narrows KERNEL=="BAT*" to a single KERNEL=="" match, so a
    second battery loses write access. A plugin-specific filename, for example
    99-battery-power-management.rules, avoids the collision.

  2. non-blocking - battery-power-management/README.md:95

    The "What it does to your system" section states the sysfs poll is roughly 2 seconds,
    but battery-power-management/service.luau:6 sets POLL_MS = 5000 with the
    profile/upower probe every third tick.

    The same section is presented as the transparency summary yet omits the setup script's
    system changes: it creates the battery_ctl group, runs usermod -aG on the invoking
    user, and installs a udev rule. Those are documented in the script header, and the
    script is never invoked by the plugin at runtime, but the README section that reviewers
    and users read should list them.
    `

@ItsLemmy
ItsLemmy marked this pull request as draft August 1, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants