Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions FanMeter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
htop - FanMeter.c
(C) 2026 Murad Karammaev
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "config.h" // IWYU pragma: keep

#include "FanMeter.h"

#include "CRT.h"
#include "Platform.h"
#include "XUtils.h"


static const int FanMeter_attributes[] = {
METER_VALUE
};

static void FanMeter_updateValues(Meter* this) {
int fanspeed = Platform_getFanSpeed();
if (fanspeed < 0) {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A");
} else {
xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%d rpm", fanspeed);
}
}

const MeterClass FanMeter_class = {
.super = {
.extends = Class(Meter),
.delete = Meter_delete
},
.updateValues = FanMeter_updateValues,
.defaultMode = TEXT_METERMODE,
.supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),
.maxItems = 0,
.total = 0.0,
.attributes = FanMeter_attributes,
.name = "Fan",
.uiName = "Fan speed",
.caption = "Fan speed: ",
};
15 changes: 15 additions & 0 deletions FanMeter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef HEADER_FanMeter
#define HEADER_FanMeter
/*
htop - FanMeter.h
(C) 2026 Murad Karammaev
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "Meter.h"


extern const MeterClass FanMeter_class;

#endif
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ myhtopheaders = \
DynamicMeter.h \
DynamicScreen.h \
EnvScreen.h \
FanMeter.h \
FileDescriptorMeter.h \
FunctionBar.h \
GPUMeter.h \
Expand Down Expand Up @@ -213,6 +214,7 @@ linux_platform_headers = \
zfs/ZfsCompressedArcMeter.h

linux_platform_sources = \
FanMeter.c \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File order is sorted alphabetically

GPUMeter.c \
generic/gettime.c \
generic/hostname.c \
Expand Down
84 changes: 84 additions & 0 deletions linux/Platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ in the source distribution for its full text.
#include "DiskIOMeter.h"
#include "FileDescriptorMeter.h"
#include "GPUMeter.h"
#include "FanMeter.h"
#include "HostnameMeter.h"
#include "HugePageMeter.h"
#include "LoadAverageMeter.h"
Expand Down Expand Up @@ -280,6 +281,7 @@ const MeterClass* const Platform_meterTypes[] = {
&OpenRCUserMeter_class,
&FileDescriptorMeter_class,
&GPUMeter_class,
&FanMeter_class,
NULL
};

Expand Down Expand Up @@ -441,6 +443,88 @@ void Platform_setGPUValues(Meter* this, double* totalUsage, unsigned long long*
this->values[residueIndex] = residuePercentage;
}

static int Platform_getFanSpeedFromHwmonDevice(openat_arg_t parent_dfd, const char *hwmon_device) {
DIR* dirp;

#ifdef HAVE_OPENAT
openat_arg_t dfd = Compat_openat(parent_dfd, hwmon_device, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (dfd == -1)
return -1;

dirp = fdopendir(dfd);
if (!dirp) {
close(dfd);
return -1;
}
#else
char dfd[4096];
if (xSnprintf(dfd, sizeof(dfd), "%s/%s", parent_dfd, hwmon_device) < 0)
return -1;

dirp = opendir(dfd);
if (!dirp)
return -1;
#endif

struct dirent* dir;
while ((dir = readdir(dirp)) != NULL) {
int fan_num = 0, scan_len = 0;
if (sscanf(dir->d_name, "fan%d%n", &fan_num, &scan_len) != 1 ||
!String_eq(&dir->d_name[scan_len], "_input"))
continue;

char buffer[32];
ssize_t ret = Compat_readfileat(dfd, dir->d_name, buffer, sizeof(buffer));
if (ret <= 0 || (size_t)ret > sizeof(buffer))
continue;

int fan_speed;
if (sscanf(buffer, "%d", &fan_speed) != 1)
continue;

closedir(dirp);
return fan_speed;
}

closedir(dirp);
return -1;
}

/* Traverse hwmon sensors, find first fan, read and return its speed. */
int Platform_getFanSpeed(void) {
DIR* dirp;
openat_arg_t dfd;

dirp = opendir(HWMONDIR);
if (!dirp)
return -1;

#ifdef HAVE_OPENAT
dfd = dirfd(dirp);
if (dfd == -1) {
closedir(dirp);
return -1;
}
#else
dfd = HWMONDIR;
#endif

struct dirent* dir;
while ((dir = readdir(dirp)) != NULL) {
if (String_eq(dir->d_name, ".") || String_eq(dir->d_name, ".."))
continue;

int fan_speed = Platform_getFanSpeedFromHwmonDevice(dfd, dir->d_name);
if (fan_speed >= 0) {
closedir(dirp);
return fan_speed;
}
}

closedir(dirp);
return -1;
}

void Platform_setMemoryValues(Meter* this) {
const Machine* host = this->host;
const LinuxMachine* lhost = (const LinuxMachine*) host;
Expand Down
4 changes: 4 additions & 0 deletions linux/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ in the source distribution for its full text.
#define PATH_MAX 4096
#endif

#define HWMONDIR "/sys/class/hwmon"


extern const ScreenDefaults Platform_defaultScreens[];

Expand Down Expand Up @@ -70,6 +72,8 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu);

void Platform_setGPUValues(Meter* this, double* totalUsage, unsigned long long* totalGPUTimeDiff);

int Platform_getFanSpeed(void);

void Platform_setMemoryValues(Meter* this);

void Platform_setSwapValues(Meter* this);
Expand Down