Skip to content

Add ThinkPad Fan & Thermal Control plugin - #175

Draft
Piero-93 wants to merge 3 commits into
noctalia-dev:mainfrom
Piero-93:add-thinkpad-fan
Draft

Add ThinkPad Fan & Thermal Control plugin#175
Piero-93 wants to merge 3 commits into
noctalia-dev:mainfrom
Piero-93:add-thinkpad-fan

Conversation

@Piero-93

Copy link
Copy Markdown

Plugin

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

What it does

A fan monitor and manual speed control for ThinkPads using the thinkpad_acpi kernel module. The bar widget shows the current fan RPM and turns a warning color when the fans are forced off or set to a manual level; clicking it opens a panel to pick a fan level (Auto, Full, 0–7). No root at runtime.

Runs entirely locally: reads /proc/acpi/ibm/fan and /sys/class/thermal/<zone>/temp, and writes level <value> to /proc/acpi/ibm/fan only when a level is picked in the panel. No external commands, no network access.

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

External dependencies

None — self-contained (reads/writes sysfs and procfs directly, using only coreutils).

Requires the thinkpad_acpi kernel module loaded with fan_control=1 and write access to /proc/acpi/ibm/fan; scripts/setup-fan-permissions.sh sets up both (module option + udev rule) without giving the plugin root.

Testing

Added the bar widget and opened the panel; picked several fan levels (Auto, Full/disengaged, and fixed 0–7) and confirmed the writes take effect and the RPM/level/temperature update; verified the status coloring (red when fans are forced off) and the multi-row tooltip (speed / level / temperature).

  • 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

ThinkPad Fan & Thermal Control 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. blocking - thinkpad-fan/scripts/setup-fan-permissions.sh:32

The installed udev rule runs /bin/chmod 0666 /proc/acpi/ibm/fan on every boot, and
thinkpad-fan/scripts/setup-fan-permissions.sh:40 applies the same mode immediately. This grants
every local process on the machine (any user, any unsandboxed or sandboxed app, any service)
permanent write access to the fan controller, not just the Noctalia session that needs it.

The plugin's own documentation states the risk at thinkpad-fan/README.md:53 ("Forcing the fans off
(level 0) or to a fixed low level can let the machine overheat"), so the rule hands a
thermal-damage capability to anything running locally.

Two existing plugins in this repository solve the identical problem with a dedicated group instead
of world-write: battery-threshold/setup_rules.sh:87 (chgrp battery_ctl + chmod g+w) and
ideapad-conservation-mode/scripts/setup-permissions.sh:44 (chgrp lenovoctl + chmod 664). Please
follow that pattern.

The mode is also not disclosed to the user in the README. thinkpad-fan/README.md:42 says only
"write access to /proc/acpi/ibm/fan", and the "What it does to your system" section at
thinkpad-fan/README.md:77 through thinkpad-fan/README.md:84 never mentions that setup makes a
kernel interface world-writable. The script header comment does say it, but the user-facing README
should too.

  1. non-blocking - thinkpad-fan/README.md:84

"No external commands, no network access." is inaccurate for the first half. Both
thinkpad-fan/service.luau:86 and thinkpad-fan/service.luau:111 call noctalia.runAsync, which
executes through /bin/sh -c. The plugin therefore spawns a shell plus cat every 2500 ms
(thinkpad-fan/service.luau:7) and a shell plus echo on each level change. dependencies = [] at
thinkpad-fan/plugin.toml:10 likewise omits the shell/coreutils requirement.

Nothing here is unsafe: the only interpolated command value is whitelist-checked at
thinkpad-fan/service.luau:106, the configurable thermal zone is stripped to a single path segment
at thinkpad-fan/service.luau:43, and both paths are correctly single-quoted by the helper at
thinkpad-fan/service.luau:34. The issue is only that the disclosure does not match the behavior.
noctalia.readFile / noctalia.writeFile would remove the subprocesses entirely if you prefer to
make the claim true instead of amending it.

  1. non-blocking - thinkpad-fan/scripts/setup-fan-permissions.sh:26

The script truncates /etc/modprobe.d/thinkpad_acpi.conf with >. A user who already has that
filename in use for other thinkpad_acpi options (a very common name, e.g. for experimental=1,
brightness_mode, or fan_control set by another tool) silently loses that configuration, with no
backup and no existence check. Use a plugin-specific filename such as
99-noctalia-thinkpad-fan.conf, or check before overwriting.

  1. non-blocking - thinkpad-fan/scripts/setup-fan-permissions.sh:23

The fan_control=1 modprobe option is written only when /sys/module/thinkpad_acpi/parameters/
fan_control exists and reads "N". If thinkpad_acpi is not currently loaded, that path is absent, so
the guard falls through and the modprobe.d file is never created. The user gets only the note at
thinkpad-fan/scripts/setup-fan-permissions.sh:43 and ends up without the option that manual control
requires.

  1. non-blocking - thinkpad-fan/README.md:45

The setup and install instructions hardcode the author's personal checkout path
(~/Documents/Projects/community-plugins/thinkpad-fan/scripts), and thinkpad-fan/README.md:61
tells users to register that path as a development source. For a published community plugin, users
install through the plugin manager, so the script location should be described relative to the
installed plugin directory and the dev-source instructions dropped or clearly marked as
contributor-only.

@ItsLemmy
ItsLemmy marked this pull request as draft August 1, 2026 03:08
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