1
0
Fork
You've already forked powercurve
0
A lightweight power management daemon for Linux desktops. Drop-in replacement for power-profiles-daemon with deeper hardware control and configurable fan curves.
This repository has been archived on 2026年03月05日. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
  • Rust 96.3%
  • Just 1.5%
  • Makefile 1.3%
  • Shell 0.5%
  • Fluent 0.4%
Find a file
2026年03月01日 18:17:37 +01:00
.forgejo/workflows fix arch deploy path after cd into repo directory 2026年02月26日 09:41:39 -05:00
.github ci: Remove use of actions-rs 2023年01月23日 13:29:05 -07:00
.vscode add rust-analyzer component and update build metadata 2026年02月23日 21:29:58 -05:00
data add channel passthrough mode 2026年02月27日 10:20:53 -05:00
debian add .deb packaging and bump to 0.1.0-beta.1 2026年02月26日 07:56:16 -05:00
examples add example configs, man page, bump to rc.1 2026年02月27日 10:20:59 -05:00
gui add COSMIC desktop GUI for fan monitoring and profile control 2026年02月28日 16:32:45 -05:00
man add example configs, man page, bump to rc.1 2026年02月27日 10:20:59 -05:00
src add channel passthrough mode 2026年02月27日 10:20:53 -05:00
zbus add channel passthrough mode 2026年02月27日 10:20:53 -05:00
.gitignore fix gitignore blocking project src directory 2026年02月27日 09:01:03 -05:00
Cargo.lock add COSMIC desktop GUI for fan monitoring and profile control 2026年02月28日 16:32:45 -05:00
Cargo.toml add COSMIC desktop GUI for fan monitoring and profile control 2026年02月28日 16:32:45 -05:00
CHANGELOG.md add changelog covering alpha.1 through rc.1 2026年03月01日 12:16:28 -05:00
LICENSE Create LICENSE 2018年04月20日 09:58:37 -06:00
Makefile add COSMIC desktop GUI for fan monitoring and profile control 2026年02月28日 16:32:45 -05:00
PKGBUILD rename project from vintagetechie-power to powercurve 2026年02月26日 07:29:04 -05:00
powercurve.install fix make uninstall path and arch post_remove cleanup 2026年02月27日 08:42:17 -05:00
README.md add changelog covering alpha.1 through rc.1 2026年03月01日 12:16:28 -05:00
ROLLBACK.md add .deb packaging and bump to 0.1.0-beta.1 2026年02月26日 07:56:16 -05:00
rust-toolchain.toml update cargo deps and bump toolchain to stable 2026年02月25日 18:27:30 -05:00
rustfmt.toml enforce and fix formatting 2021年04月05日 13:07:00 -06:00

powercurve

A lightweight power management daemon for Linux desktops. Drop-in replacement for power-profiles-daemon with deeper hardware control, configurable fan curves, and thermal protection.

Profiles adjust CPU governors, turbo boost, PCI runtime power management, SCSI link policies, and ACPI platform profiles. Desktop environments that talk to the org.freedesktop.UPower.PowerProfiles D-Bus interface (GNOME, KDE, powerprofilesctl) pick up profiles automatically.

Fan control runs through hwmon PWM outputs with a layered curve system that lets you tune each fan independently per profile. The daemon interpolates between curve points, applies hysteresis to prevent flapping, and handles thermal emergencies when temps get critical.

Desktop only. This is built for desktops and workstations. Most laptops manage fans through embedded controllers that don't expose PWM via hwmon, so fan control won't work. The power profile side functions fine on any hardware, but if fan curves are what you're after, stick with power-profiles-daemon on laptops.

Installation

Arch

Available from the VintageTechie Arch repo:

sudo pacman -S powercurve-git

Debian / Ubuntu / Pop!_OS

Download the latest .deb from the releases page:

sudo apt install ./powercurve_*.deb

Both packages provide and conflict with power-profiles-daemon, so the stock daemon gets replaced cleanly.

Power profiles

Three profiles, switchable via D-Bus or the CLI:

  • Quiet maps to power-saver. Conservative CPU governor (powersave, 50% max frequency), turbo disabled, aggressive power management on PCI and SCSI devices.
  • Balanced is the default. Dynamic CPU scaling with turbo enabled, moderate power management.
  • Performance runs the CPU at full frequency with turbo, disables PCI runtime power management for maximum throughput.

Set a profile:

powercurve profile quiet
powercurve profile balanced
powercurve profile performance

Query the current profile:

powercurve profile

The active profile persists across restarts.

Fan control

The daemon controls fans through hwmon PWM outputs using a config file at /etc/powercurve/fan.toml. The Arch package generates this automatically on install by scanning hwmon devices for PWM-capable controllers. Without a config, fan control is disabled and the daemon only manages power profiles.

Run fan-detect to see what the daemon found on your hardware:

powercurve fan-detect

This prints every hwmon device on the system with its temperature sensors, PWM outputs, fan RPMs, labels, and critical temp thresholds, then suggests a config with auto-detected critical temperatures.

To generate a config (or regenerate after hardware changes):

powercurve fan-detect --generate | sudo tee /etc/powercurve/fan.toml > /dev/null

The --generate flag outputs only the TOML config with no device summary, making it safe to pipe directly to the config path. Critical temperatures are set 15C below the CPU's thermal throttle point (read from sysfs when available, with per-driver fallbacks for chips that don't expose it).

Config format

The config has four layers of fan curves, from broadest to most specific. When the daemon picks a curve for a channel, it walks this list and uses the first match:

  1. Per-channel per-profile - this specific fan on this specific profile
  2. Per-channel default - this fan's curve regardless of profile
  3. Per-profile global - all fans without overrides on this profile
  4. Shared top-level - the fallback for everything else

You only need layers 4 and 1 (shared curve + channels) to get started. The rest is there when you want finer control.

Here's a config that uses all four layers:

platform = "nct6775"
critical_cpu_temp = 80
critical_gpu_temp = 75
hysteresis = 3.0
thermal_fallback = true
thermal_cooldown = 30
# Layer 4: shared curve (fallback for all channels)
[[curve]]
temp = 30.0
duty = 10
[[curve]]
temp = 50.0
duty = 30
[[curve]]
temp = 70.0
duty = 80
[[curve]]
temp = 75.0
duty = 100
# CPU fan, follows the shared/profile curve
[[channels]]
pwm = "pwm1"
source = "cpu"
# Case fan, responds to the hottest source
[[channels]]
pwm = "pwm2"
source = "all"
# GPU fan with its own default curve (layer 2)
# and per-profile overrides (layer 1)
[[channels]]
pwm = "pwm3"
source = "gpu"
curve = [
 { temp = 30.0, duty = 5.0 },
 { temp = 55.0, duty = 20.0 },
 { temp = 68.0, duty = 65.0 },
 { temp = 80.0, duty = 100.0 },
]
[channels.profiles.quiet]
curve = [
 { temp = 30.0, duty = 3.0 },
 { temp = 60.0, duty = 15.0 },
 { temp = 74.0, duty = 70.0 },
 { temp = 80.0, duty = 100.0 },
]
[channels.profiles.performance]
curve = [
 { temp = 30.0, duty = 8.0 },
 { temp = 55.0, duty = 35.0 },
 { temp = 68.0, duty = 80.0 },
 { temp = 80.0, duty = 100.0 },
]
# Layer 3: global profile curves (all channels without overrides)
[profiles.quiet]
curve = [
 { temp = 30.0, duty = 5.0 },
 { temp = 50.0, duty = 14.0 },
 { temp = 72.0, duty = 65.0 },
 { temp = 80.0, duty = 100.0 },
]
[profiles.performance]
curve = [
 { temp = 30.0, duty = 15.0 },
 { temp = 48.0, duty = 38.0 },
 { temp = 70.0, duty = 90.0 },
 { temp = 78.0, duty = 100.0 },
]

The platform field tells the daemon which hwmon device has your fan PWM outputs. Common values: nct6775, it8688, asus-ec-sensors. fan-detect fills this in automatically based on what it finds.

Temperatures are in Celsius, duty is a percentage (0-100). The daemon interpolates linearly between curve points, so you don't need a point at every degree.

The examples/ directory has ready-to-use configs at different complexity levels: fan-simple.toml for a single fan with a shared curve, fan-desktop.toml for a typical three-fan desktop with hysteresis, and fan-profiles.toml showing everything including per-channel per-profile curves, stall detection, and passthrough.

Config reference

Field Required Description
platform no hwmon device name for PWM outputs
critical_cpu_temp yes all fans go to max above this (Celsius)
critical_gpu_temp yes all fans go to max above this (Celsius)
hysteresis no temp must drop this many degrees before fans slow down (default 3.0)
thermal_fallback no auto-downshift profile on critical temps (default false)
thermal_cooldown no seconds to wait before restoring profile after thermal fallback (default 30)

Temperature sources

Each channel's source field controls which sensors drive it:

  • cpu reads from hwmon drivers: coretemp (Intel), k10temp or zenpower (AMD)
  • gpu combines AMD GPUs via the amdgpu hwmon driver and NVIDIA GPUs via NVML (loaded at runtime from libnvidia-ml.so.1, no dependency on nvidia-smi)
  • all takes the max of CPU and GPU temps

If NVIDIA hardware is detected but NVML can't load, GPU-sourced channels run at max duty as a safety measure.

Thermal protection

When any sensor crosses its critical threshold, all fans immediately go to maximum regardless of the curve. With thermal_fallback = true, the daemon also downshifts the power profile (Performance to Balanced, Balanced to Quiet) to reduce heat generation. Once temps stabilize for thermal_cooldown seconds, the original profile is restored.

Hysteresis

Fan curves apply hysteresis to prevent rapid cycling. When temps are falling, the fan holds its current duty until the temperature drops by the configured hysteresis amount (default 3C). Rising temps always update immediately. This keeps fans from bouncing between speeds when the CPU is hovering around a curve point.

Minimum duty floor

Some fans stall at low PWM values. If a channel's curve produces a duty below the fan's physical minimum, it stops spinning entirely. Setting min_duty on a channel prevents this:

[[channels]]
pwm = "pwm1"
source = "cpu"
min_duty = 15.0 # never drop below 15%

The floor applies even when the curve would otherwise turn the fan off. Overrides bypass the floor since they're explicitly set by the user. The value is a percentage (0-100), and channels without min_duty can still stop when cool.

Stall detection

If you don't know your fan's stall point up front, stall detection catches it at runtime by reading the tachometer. When a channel is writing duty > 0 but the RPM sensor reads 0 for several consecutive cycles, the daemon bumps duty to min_duty (or 15% if no floor is set) to restart the fan.

[[channels]]
pwm = "pwm1"
source = "cpu"
stall_detect = true # enable RPM-based stall detection
stall_threshold = 3 # consecutive zero-RPM reads before bump (default: 3)

Not all fans have tachometers, so this is opt-in per channel. The RPM sensor is mapped by index (pwm1 reads fan1_input from the platform hwmon). powercurve status shows current RPM and a [STALLED] tag on affected channels.

Passthrough mode

Some fans are better left under BIOS or firmware control. Setting passthrough = true on a channel tells the daemon to skip it entirely, no duty writes, no RPM reads, no curve evaluation. The channel keeps whatever control mode the system firmware set.

[[channels]]
pwm = "pwm4"
source = "all"
passthrough = true

Passthrough channels show up in powercurve status as [passthrough] and are excluded from curves, overrides, and stall detection. Useful for chipset fans or channels with no connected hardware.

Monitoring and status

Check the current state of the daemon:

powercurve status

This shows the active profile, CPU/GPU temperatures, per-channel PWM duties, and the active fan curve for each channel. Doesn't require root.

Check the version:

powercurve version

Temporary fan overrides

For testing and tuning, you can temporarily override a fan channel's duty cycle without changing the config:

powercurve fan pwm3 50

This sets pwm3 to 50% immediately. The override persists until you clear it or switch profiles:

powercurve fan pwm3 clear

Overrides show up in powercurve status with an [override] tag next to the affected channel. Switching profiles clears all overrides automatically.

Finding the spin-up floor

If you're not sure what min_duty to set for a channel, fan-test finds it for you. It ramps duty upward from 5% in 5% increments, reading RPM at each step, and reports the lowest duty where the fan actually spins:

powercurve fan-test pwm1

For finer resolution, a different starting point, or longer settle time between steps:

powercurve fan-test pwm3 --step 3 --start 10 --settle 3000

--settle controls how long (in milliseconds) the test waits at each duty level before reading RPM, default 2000ms. Increase it for fans that take longer to spin up.

The test uses the daemon's override mechanism so other fans keep running normally. The override is cleared when the test finishes or if you hit Ctrl-C. Use the reported value as your min_duty in fan.toml.

For desktop notifications on profile switches and thermal events, run the monitor as a user service:

systemctl --user enable --now powercurve-monitor

The monitor listens for D-Bus signals from the daemon and sends notifications through org.freedesktop.Notifications. It runs as a separate process so it doesn't need root.

Config validation

Validate your config without restarting the daemon:

powercurve config

This checks curve monotonicity, duty ranges, critical temp bounds, hysteresis/cooldown values, profile names, per-channel profile curves, and whether the referenced hwmon devices exist on the current machine. Errors prevent the daemon from loading the config, warnings are informational.

Hot reload

Edit the config and reload without restarting:

sudo systemctl reload powercurve

The daemon validates the new config before applying it. If validation fails, it keeps the running config and logs the errors. The active profile's curves are re-applied after reload so channels don't fall back to the shared curve.

Building

Requires a stable Rust toolchain:

make
sudo make install

The daemon runs as a systemd service:

sudo systemctl enable --now com.vintagetechie.PowerCurve

Documentation

man powercurve has the full reference covering all commands, config options, D-Bus interface, signals, and file locations.

See CHANGELOG.md for what changed in each release.

Rollback

See ROLLBACK.md for instructions on switching back to power-profiles-daemon or downgrading to a previous version.

License

GPL-3.0-only

Based on system76-power by System76, also licensed under GPL-3.0-only.