Skip to content

Commit 2bbcc09

Browse files
system-monitor-graph@rcassani: Address fan review feedback
1 parent a13e9d5 commit 2bbcc09

18 files changed

Lines changed: 557 additions & 507 deletions

File tree

system-monitor-graph@rcassani/README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ Four instances of the Desklet in action.
1010
This project has been inspired from other Desklets such as [Disk Space](https://cinnamon-spices.linuxmint.com/desklets/view/39), [CPU Load](https://cinnamon-spices.linuxmint.com/desklets/view/44), [Simple system monitor](https://cinnamon-spices.linuxmint.com/desklets/view/29), [Network usage monitor](https://cinnamon-spices.linuxmint.com/desklets/view/15), [Top](https://cinnamon-spices.linuxmint.com/desklets/view/41), and the [Rainmeter Win10 Widgets](https://win10widgets.com/).
1111

1212
## Features
13-
### Variables to monitor (v2.0 - July 2025)
13+
### Variables to monitor (v2.2 - July 2026)
1414

1515
| System variable | Description |
1616
| ----------- | ----------- |
1717
| CPU | CPU usage in % |
1818
| CPU Temperature | CPU temperature in C or F |
19-
| CPU Fan Speed | Fan speed in RPM, on/off status, and activity history when exposed by hwmon |
19+
| CPU Fan | CPU fan speed in RPM and on/off status |
2020
| RAM | Used RAM as % of total, and in GB |
2121
| Swap | Used Swap space as % of total, and in GB |
2222
| HDD | % of I/O activity, and free and total space in the filesystem (partition) indicated by the user |
@@ -30,12 +30,6 @@ Each variable is calculated every `Refresh interval` seconds (Min. 1 s, Max. 60
3030
GPU graphs work for NVIDIA, discrete AMD and built-in AMD.
3131
Fan monitoring uses CPU-labelled sensors and known laptop cooling drivers. It avoids guessing from unrelated unlabelled fans, and reports when an identifiable CPU fan is not detected.
3232

33-
<p align="center">
34-
<img src="fan-speed-on.png" width="350" alt="CPU fan speed graph showing 522 RPM and On">
35-
<img src="fan-speed-off.png" width="350" alt="CPU fan speed graph showing 0 RPM and Off"><br>
36-
CPU fan speed history while the fan is running and stopped.
37-
</p>
38-
3933
### Customizable visual elements
4034
<p align="center">
4135
<img src="https://cinnamon-spices.linuxmint.com/git/desklets/system-monitor-graph@rcassani/settings.png" width="700" align="middle"><br>
@@ -65,6 +59,11 @@ Another screenshot.
6559
Screenshot with temperature and battery graphs.
6660
</p>
6761

62+
<p align="center">
63+
<img src="https://cinnamon-spices.linuxmint.com/git/desklets/system-monitor-graph@rcassani/screenshot5.png" width="700" align="middle"><br>
64+
CPU fan speed history while the fan is running and stopped.
65+
</p>
66+
6867
## TODO
6968
- [x] Add network
7069
- [x] Battery levels for main battery (laptop)
-64.5 KB
Binary file not shown.
-65.8 KB
Binary file not shown.

system-monitor-graph@rcassani/files/system-monitor-graph@rcassani/desklet.js

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ SystemMonitorGraph.prototype = {
190190
// temperature values
191191
this.cpu_temperature = NaN;
192192
this.gpu_temperature = NaN;
193+
// cpu fan values
193194
this.cpu_fan_rpm = NaN;
194195
this.cpu_fan_file = "";
195196
this.cpu_fan_discovery_complete = false;
@@ -289,7 +290,7 @@ SystemMonitorGraph.prototype = {
289290
// scale CPU temperature based on [CPU_TEMP_MIN, CPU_TEMP_MAX]
290291
value = 1.0 * (this.cpu_temperature - CPU_TEMP_MIN) / (CPU_TEMP_MAX - CPU_TEMP_MIN);
291292
value = value < 0 ? 0 : value > 1 ? 1 : value;
292-
text1 = _("CPU Temp");
293+
text1 = _("CPU Temperature");
293294
if (this.temperature_units_cpu == "C") {
294295
text2 = this.cpu_temperature.toString() + "°C";
295296
} else if (this.temperature_units_cpu == "F") {
@@ -300,11 +301,11 @@ SystemMonitorGraph.prototype = {
300301
case "fan":
301302
this.get_cpu_fan_speed(this.cpu_fan_file);
302303
value = isNaN(this.cpu_fan_rpm) ? 0 : this.cpu_fan_rpm;
303-
text1 = _("CPU Fan Speed");
304+
text1 = _("CPU Fan");
304305
text2_size = Math.max(1, text2_size - 1);
305306
if (!isNaN(this.cpu_fan_rpm)) {
306307
if (this.cpu_fan_rpm >= 1) {
307-
text2 = this.cpu_fan_rpm + " RPM";
308+
text2 = this.cpu_fan_rpm + " " + _("RPM");
308309
text3 = "🟢 " + _("On");
309310
} else {
310311
text3 = "🔴 " + _("Off");
@@ -1193,63 +1194,60 @@ SystemMonitorGraph.prototype = {
11931194
});
11941195
},
11951196

1196-
get_cpu_fan_file: function(callback) {
1197+
get_cpu_fan_file: function(excluded_file, callback) {
11971198
// Prefer explicitly labelled CPU fans, then known laptop cooling drivers.
11981199
// Do not guess from an unrelated, unlabelled hwmon fan.
1199-
let script = `
1200-
for label_file in /sys/class/hwmon/hwmon*/fan*_label; do
1201-
[ -r "$label_file" ] || continue
1202-
label=$(cat "$label_file" 2>/dev/null) || continue
1203-
case "$label" in
1204-
*CPU*|*cpu*|*Processor*|*processor*)
1205-
fan_file="\${label_file%_label}_input"
1206-
[ -r "$fan_file" ] && { printf '%s' "$fan_file"; exit; }
1207-
;;
1208-
esac
1209-
done
1210-
for name_file in /sys/class/hwmon/hwmon*/name; do
1211-
[ -r "$name_file" ] || continue
1212-
driver=$(cat "$name_file" 2>/dev/null) || continue
1213-
case "$driver" in
1214-
dell_smm|thinkpad)
1215-
hwmon_dir=\${name_file%/name}
1216-
fan_file="$hwmon_dir/fan1_input"
1217-
[ -r "$fan_file" ] && { printf '%s' "$fan_file"; exit; }
1218-
;;
1219-
esac
1220-
done
1221-
`;
1222-
spawnAsyncWithOutput(['/bin/sh', '-c', script], (success, output) => {
1200+
let argv = ['/bin/sh', '-c', "for fan_file in $({ grep -s -i -l -d skip -E 'cpu|processor' /sys/class/hwmon/hwmon*/fan*_label | sed 's/_label/_input/'; grep -s -l -d skip -E '^(dell_smm|thinkpad)$' /sys/class/hwmon/hwmon*/name | sed 's#/name#/fan1_input#'; }); do [ \"$fan_file\" = \"$1\" ] && continue; [ -r \"$fan_file\" ] && { printf '%s' \"$fan_file\"; break; }; done", 'cpu-fan-discovery', excluded_file];
1201+
spawnAsyncWithOutput(argv, (success, output) => {
12231202
callback(success && output ? output.trim() : "");
12241203
});
12251204
},
12261205

1227-
rediscover_cpu_fan: function() {
1228-
// Clear a stale hwmon path first. If discovery finds nothing, the fan
1229-
// graph reports that no sensor was detected instead of retrying forever.
1206+
rediscover_cpu_fan: function(failed_file = "") {
1207+
// Clear a stale hwmon path first. Initial discovery retries with bounded
1208+
// backoff; a failed known path is excluded to avoid looping every refresh.
1209+
if (this.cpu_fan_discovery_in_progress) return;
1210+
if (this.cpu_fan_retry_delay == null) this.cpu_fan_retry_delay = 5;
1211+
this.cpu_fan_discovery_in_progress = true;
1212+
this.cpu_fan_retry_if_missing = failed_file == "";
12301213
this.cpu_fan_file = "";
12311214
this.cpu_fan_rpm = NaN;
12321215
this.cpu_fan_discovery_complete = false;
1233-
this.get_cpu_fan_file((result) => {
1216+
this.get_cpu_fan_file(failed_file, (result) => {
12341217
this.cpu_fan_file = result;
12351218
this.cpu_fan_discovery_complete = true;
1219+
this.cpu_fan_discovery_in_progress = false;
1220+
if (result == "" && this.cpu_fan_retry_if_missing) {
1221+
this.cpu_fan_next_discovery_time = GLib.get_monotonic_time()
1222+
+ (this.cpu_fan_retry_delay * GLib.USEC_PER_SEC);
1223+
this.cpu_fan_retry_delay = Math.min(this.cpu_fan_retry_delay * 2, 300);
1224+
} else if (result != "") {
1225+
this.cpu_fan_retry_delay = 5;
1226+
this.cpu_fan_next_discovery_time = 0;
1227+
}
12361228
});
12371229
},
12381230

12391231
get_cpu_fan_speed: function(fan_file) {
1240-
if(fan_file == null || fan_file == "") return;
1232+
if(fan_file == null || fan_file == "") {
1233+
if (this.cpu_fan_discovery_complete && this.cpu_fan_retry_if_missing
1234+
&& GLib.get_monotonic_time() >= this.cpu_fan_next_discovery_time) {
1235+
this.rediscover_cpu_fan();
1236+
}
1237+
return;
1238+
}
12411239
Gio.file_new_for_path(fan_file).load_contents_async(null, (file, response) => {
12421240
try {
12431241
let [success, contents, tag] = file.load_contents_finish(response);
12441242
if (success) {
12451243
let rpm = parseInt(ByteArray.toString(contents).trim());
12461244
this.cpu_fan_rpm = isNaN(rpm) ? NaN : Math.max(0, rpm);
12471245
} else {
1248-
this.rediscover_cpu_fan();
1246+
this.rediscover_cpu_fan(fan_file);
12491247
}
12501248
GLib.free(contents);
12511249
} catch(error) {
1252-
this.rediscover_cpu_fan();
1250+
this.rediscover_cpu_fan(fan_file);
12531251
global.log('CPU fan speed file read error: ' + error.toString());
12541252
}
12551253
});

system-monitor-graph@rcassani/files/system-monitor-graph@rcassani/po/ca.po

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
msgid ""
77
msgstr ""
88
"Project-Id-Version: \n"
9-
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/"
10-
"issues\n"
11-
"POT-Creation-Date: 2026-07-20 21:19+0530\n"
9+
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/issues\n"
10+
"POT-Creation-Date: 2026-07-22 03:33+0530\n"
1211
"PO-Revision-Date: 2025-09-29 04:41+0200\n"
1312
"Last-Translator: Daniel <d3vf4n@tutanota.com>\n"
1413
"Language-Team: \n"
@@ -20,124 +19,128 @@ msgstr ""
2019
"X-Generator: Poedit 3.6\n"
2120

2221
#. settings-schema.json->type->options
23-
#. desklet.js:283
22+
#. desklet.js:284
2423
msgid "CPU"
2524
msgstr "CPU"
2625

27-
#. desklet.js:292
28-
msgid "CPU Temp"
26+
#. desklet.js:293
27+
msgid "CPU Temperature"
2928
msgstr ""
3029

31-
#. desklet.js:303
32-
msgid "CPU Fan Speed"
30+
#. desklet.js:304
31+
msgid "CPU Fan"
3332
msgstr ""
3433

35-
#. desklet.js:307
36-
msgid "On"
34+
#. desklet.js:308
35+
msgid "RPM"
3736
msgstr ""
3837

3938
#. desklet.js:309
39+
msgid "On"
40+
msgstr ""
41+
42+
#. desklet.js:311
4043
msgid "Off"
4144
msgstr ""
4245

43-
#. desklet.js:312
46+
#. desklet.js:314
4447
msgid "Not detected"
4548
msgstr ""
4649

47-
#. desklet.js:314
50+
#. desklet.js:316
4851
msgid "Detecting..."
4952
msgstr ""
5053

51-
#. desklet.js:327 desklet.js:345 desklet.js:365 desklet.js:406 desklet.js:736
54+
#. desklet.js:329 desklet.js:347 desklet.js:367 desklet.js:408 desklet.js:738
5255
msgid "GB"
5356
msgstr "GB"
5457

55-
#. desklet.js:330 desklet.js:348 desklet.js:368 desklet.js:409 desklet.js:750
58+
#. desklet.js:332 desklet.js:350 desklet.js:370 desklet.js:411 desklet.js:752
5659
msgid "GiB"
5760
msgstr "GiB"
5861

5962
#. settings-schema.json->type->options
60-
#. desklet.js:332
63+
#. desklet.js:334
6164
msgid "RAM"
6265
msgstr "RAM"
6366

6467
#. settings-schema.json->type->options
65-
#. desklet.js:350
68+
#. desklet.js:352
6669
msgid "Swap"
6770
msgstr "Swap"
6871

69-
#. desklet.js:373
72+
#. desklet.js:375
7073
msgid "free of"
7174
msgstr "lliure de"
7275

73-
#. desklet.js:389
76+
#. desklet.js:391
7477
msgid "GPU Usage"
7578
msgstr "Ús de GPU"
7679

77-
#. desklet.js:411
80+
#. desklet.js:413
7881
msgid "GPU Memory"
7982
msgstr "Memòria de la GPU"
8083

81-
#. desklet.js:427
84+
#. desklet.js:429
8285
#, fuzzy
8386
msgid "GPU Temperature"
8487
msgstr "Fabricant de la GPU"
8588

8689
#. settings-schema.json->type->options
87-
#. desklet.js:441
90+
#. desklet.js:443
8891
msgid "Network"
8992
msgstr "Xarxa"
9093

9194
#. settings-schema.json->type->options
92-
#. desklet.js:454
95+
#. desklet.js:456
9396
msgid "Battery"
9497
msgstr ""
9598

96-
#. desklet.js:457
99+
#. desklet.js:459
97100
msgid "Fully charged"
98101
msgstr ""
99102

100-
#. desklet.js:458
103+
#. desklet.js:460
101104
msgid "Not charging"
102105
msgstr ""
103106

104-
#. desklet.js:460
107+
#. desklet.js:462
105108
msgid " hrs"
106109
msgstr ""
107110

108-
#. desklet.js:707 desklet.js:730
111+
#. desklet.js:709 desklet.js:732
109112
msgid "b"
110113
msgstr "b"
111114

112-
#. desklet.js:709 desklet.js:744 desklet.js:758
115+
#. desklet.js:711 desklet.js:746 desklet.js:760
113116
msgid "B"
114117
msgstr "B"
115118

116-
#. desklet.js:722
119+
#. desklet.js:724
117120
msgid "Gb"
118121
msgstr "Gb"
119122

120-
#. desklet.js:725
123+
#. desklet.js:727
121124
msgid "Mb"
122125
msgstr "Mb"
123126

124-
#. desklet.js:728
127+
#. desklet.js:730
125128
msgid "Kb"
126129
msgstr "Kb"
127130

128-
#. desklet.js:739
131+
#. desklet.js:741
129132
msgid "MB"
130133
msgstr "MB"
131134

132-
#. desklet.js:742
135+
#. desklet.js:744
133136
msgid "KB"
134137
msgstr "KB"
135138

136-
#. desklet.js:753
139+
#. desklet.js:755
137140
msgid "MiB"
138141
msgstr "MiB"
139142

140-
#. desklet.js:756
143+
#. desklet.js:758
141144
msgid "KiB"
142145
msgstr "KiB"
143146

@@ -276,10 +279,11 @@ msgstr "Interfície de xarxa a monitorar"
276279

277280
#. settings-schema.json->network-interface->tooltip
278281
msgid ""
279-
"Leave empty to monitor all interfaces combined. Examples: eth0, wlan0, enp0s3"
282+
"Leave empty to monitor all interfaces combined. Examples: eth0, wlan0, "
283+
"enp0s3"
280284
msgstr ""
281-
"Deixeu-ho en blanc per a monitorar totes les interfícies. Per exemple: eth0, "
282-
"wlan0, enp0s3"
285+
"Deixeu-ho en blanc per a monitorar totes les interfícies. Per exemple: eth0,"
286+
" wlan0, enp0s3"
283287

284288
#. settings-schema.json->battery-name->description
285289
#, fuzzy

0 commit comments

Comments
 (0)