Skip to content

Repository files navigation

ThinkPad Fan Control

A PyQt6 desktop app for controlling fans, thermals, CPU power limits, and battery charge thresholds on Lenovo ThinkPads under Linux, via thinkpad_acpi and standard kernel sysfs. Manual level, temperature curve, or closed-loop "hold this temperature" thermostat — with a live temp/RPM graph and AC/battery-aware profiles.

ThinkPad Fan Control — Fan tab

More screenshots — Sensors, Battery, Power, GPU

Sensors Battery Power GPU

Features

  • Three modes — Manual (pin a fixed level), Curve (temp → level with hysteresis, preset or custom), and Target (thermostat that adapts the fan to the workload).
  • AC / battery-aware — separate curves and power presets, switched automatically on unplug.
  • CPU power limits (Intel RAPL) — PL1/PL2 presets (Quiet → Max), with a built-in benchmark that runs a sustained load at each preset and compares steady-state clock/temp.
  • Intel iGPU frequency caps — min/max clock limits with Quiet/Balanced/Performance/Max presets, optional auto-cap on battery, and a 15-second keep-or-revert confirmation on every change. Busy% is read from RC6 idle residency, so it works without root (intel_gpu_top needs CAP_PERFMON). Note: most laptops already ship at the hardware ceiling, so this can only cap downward — for heat, noise, and battery, not extra speed.
  • Battery longevity — set charge start/stop thresholds.
  • Live graph — temps, fan RPM, and measured package power draw; CSV export.
  • Auto-profile — detects games / compiles / AI workloads and switches presets.
  • Critical-temp protection — forces full-speed above a threshold, independent of the curve.
  • Optional telemetry — push metrics to a Prometheus/VictoriaMetrics endpoint and fire ntfy alerts on critical temps. Off by default; no endpoints preconfigured.

Requirements

  • A Lenovo ThinkPad with the thinkpad_acpi kernel module (standard on Linux).
  • Python 3 + PyQt6 (pip install PyQt6).
  • pkexec/sudo for the one-time privileged setup.
  • Optional: stress-ng for the in-app benchmark.

Compatibility

Tested with app version 2.2 on:

ThinkPad model CPU generation Kernel Distro
P1 Gen 3 Intel 10th-gen (Comet Lake-H) Linux 6.12 Debian 13
P1 Gen 4 Intel 11th-gen (Tiger Lake-H) Linux 7.1 (zen) Arch

Everything it drives — thinkpad_acpi fan control, Intel RAPL power limits, platform_profile, BAT0 charge thresholds, and i915 gt_*_freq_mhz — is common across recent ThinkPads, and per-chassis details (fan count, hwmon indices, PL ceilings) are auto-detected; missing sensors are handled gracefully. Other models (incl. P1 Gen 2) very likely work but are unverified — reports welcome. Nothing here is model-hardcoded.

Install

git clone https://github.com/Scanner771/thinkpad-fan-control && cd thinkpad-fan-control
pip install -r requirements.txt
sudo bash setup-fan-control.sh
python3 fan-control.py

setup-fan-control.sh (run once) installs the fanctl privileged helper to /usr/local/bin, grants passwordless sudo for that one command only, and persists thinkpad_acpi fan_control=1 (the kernel gate that permits fan writes) via /etc/modprobe.d/.

Run at login (systemd user service)

cp systemd/fan-control.service ~/.config/systemd/user/ && systemctl --user enable --now fan-control.service

Uninstall

sudo rm -f /usr/local/bin/fanctl /etc/sudoers.d/fan-control /etc/modprobe.d/thinkpad_acpi.conf && systemctl --user disable --now fan-control.service

That removes the helper, the passwordless-sudo grant, and the fan_control=1 persistence. Fan control returns to firmware on the next reboot (or sudo modprobe -r thinkpad_acpi && sudo modprobe thinkpad_acpi). Settings in ~/.config/fan-control/ are left alone — delete that directory too if you want a clean slate.

Keyboard shortcuts

Key Action
Ctrl+1Ctrl+5 jump to tab
Ctrl+Tab / Ctrl+Shift+Tab cycle tabs
Ctrl+R / F5 force a refresh
Esc / Ctrl+W hide to tray (or close if there's no tray)
Ctrl+Q quit

How it works

The GUI only reads kernel sysfs directly (/sys/class/hwmon, /sys/class/powercap, platform_profile, BAT0). Every privileged write goes through fanctl, a small shell helper that validates its arguments before touching /proc/acpi/ibm/fan, the RAPL power-limit files, the i915 GPU frequency nodes, or the battery thresholds — so the passwordless-sudo grant is scoped to one auditable command, not to arbitrary root writes. GPU writes are clamped to the hardware-reported RPn..RP0 range and the i915 card is resolved by driver symlink rather than a hardcoded cardN (the number is not stable across boots). Only one instance can run at a time (QLockFile), so an autostart entry and a session restore can't end up both driving the fan.

Configuration

Settings live in ~/.config/fan-control/config.json (curves, presets, PL limits, telemetry). See config.example.json. The app rewrites this file on exit, so edit it only while the app is stopped.

Optional telemetry

Disabled by default with no endpoints set. To enable, fill in telemetry.vm_url (Prometheus/VictoriaMetrics import URL) and/or telemetry.ntfy_url + ntfy_topic, and set telemetry.enabled: true.

Safety

This app writes fan levels and CPU power limits. Curves are pre-ramped (idle above a dead stop) so a fan isn't spooling from standstill under a sudden all-core load, and a critical-temperature failsafe forces full speed regardless of the active curve. GPU frequency caps are staged behind a 15-second keep-or-revert dialog and are clamped to the hardware range; nothing GPU-side persists, so a reboot always restores stock clocks. Still, you are overriding firmware defaults — understand your hardware's limits.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.

About

PyQt6 desktop app for ThinkPad fan curves, thermals, Intel RAPL power limits, iGPU clock caps, and battery charge thresholds on Linux

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages