[SF#727]Fix incorrect temperature sensors detection.
[lxde/lxpanel.git] / plugins / thermal / thermal.c
1 /**
2 * Thermal plugin to lxpanel
3 *
4 * Copyright (C) 2007 by Daniel Kesler <kesler.daniel@gmail.com>
5 * 2014 by Andriy Grytsenko <andrej@rep.kiev.ua>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23 #include <sys/types.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <glib/gi18n.h>
27
28 #include <string.h>
29
30 #include "plugin.h"
31 #include "misc.h"
32
33 #include "dbg.h"
34
35 #define PROC_THERMAL_DIRECTORY "/proc/acpi/thermal_zone/" /* must be slash-terminated */
36 #define PROC_THERMAL_TEMPF "temperature"
37 #define PROC_THERMAL_TRIP "trip_points"
38 #define PROC_TRIP_CRITICAL "critical (S5):"
39
40 #define SYSFS_THERMAL_DIRECTORY "/sys/class/thermal/" /* must be slash-terminated */
41 #define SYSFS_THERMAL_SUBDIR_PREFIX "thermal_zone"
42 #define SYSFS_THERMAL_TEMPF "temp"
43 #define SYSFS_THERMAL_TRIP "trip_point_0_temp"
44
45 #define MAX_NUM_SENSORS 10
46 #define MAX_AUTOMATIC_CRITICAL_TEMP 150 /* in degrees Celsius */
47
48 #if !GLIB_CHECK_VERSION(2, 40, 0)
49 # define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
50 #endif
51
52 typedef gint (*GetTempFunc)(char const *);
53
54 typedef struct thermal {
55 LXPanel *panel;
56 config_setting_t *settings;
57 GtkWidget *namew;
58 GString *tip;
59 int warning1;
60 int warning2;
61 int not_custom_levels, auto_sensor;
62 char *sensor,
63 *str_cl_normal,
64 *str_cl_warning1,
65 *str_cl_warning2;
66 unsigned int timer;
67 GdkColor cl_normal,
68 cl_warning1,
69 cl_warning2;
70 int numsensors;
71 char *sensor_array[MAX_NUM_SENSORS];
72 char *sensor_name[MAX_NUM_SENSORS];
73 GetTempFunc get_temperature[MAX_NUM_SENSORS];
74 GetTempFunc get_critical[MAX_NUM_SENSORS];
75 gint temperature[MAX_NUM_SENSORS];
76 gint critical[MAX_NUM_SENSORS];
77 } thermal;
78
79
80 static gint
81 proc_get_critical(char const* sensor_path){
82 FILE *state;
83 char buf[ 256 ], sstmp [ 100 ];
84 char* pstr;
85
86 if(sensor_path == NULL) return -1;
87
88 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,PROC_THERMAL_TRIP);
89
90 if (!(state = fopen( sstmp, "r"))) {
91 g_warning("thermal: cannot open %s", sstmp);
92 return -1;
93 }
94
95 while( fgets(buf, 256, state) &&
96 ! ( pstr = strstr(buf, PROC_TRIP_CRITICAL) ) );
97 if( pstr )
98 {
99 pstr += strlen(PROC_TRIP_CRITICAL);
100 while( *pstr && *pstr == ' ' )
101 ++pstr;
102
103 pstr[strlen(pstr)-3] = '\0';
104 fclose(state);
105 return atoi(pstr);
106 }
107
108 fclose(state);
109 return -1;
110 }
111
112 static gint
113 proc_get_temperature(char const* sensor_path){
114 FILE *state;
115 char buf[ 256 ], sstmp [ 100 ];
116 char* pstr;
117
118 if(sensor_path == NULL) return -1;
119
120 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,PROC_THERMAL_TEMPF);
121
122 if (!(state = fopen( sstmp, "r"))) {
123 g_warning("thermal: cannot open %s", sstmp);
124 return -1;
125 }
126
127 while( fgets(buf, 256, state) &&
128 ! ( pstr = strstr(buf, "temperature:") ) );
129 if( pstr )
130 {
131 pstr += 12;
132 while( *pstr && *pstr == ' ' )
133 ++pstr;
134
135 pstr[strlen(pstr)-3] = '\0';
136 fclose(state);
137 return atoi(pstr);
138 }
139
140 fclose(state);
141 return -1;
142 }
143
144 static gint _get_reading(const char *path, gboolean quiet)
145 {
146 FILE *state;
147 char buf[256];
148 char* pstr;
149
150 if (!(state = fopen(path, "r"))) {
151 if (!quiet)
152 g_warning("thermal: cannot open %s", path);
153 return -1;
154 }
155
156 while( fgets(buf, 256, state) &&
157 ! ( pstr = buf ) );
158 if( pstr )
159 {
160 fclose(state);
161 return atoi(pstr)/1000;
162 }
163
164 fclose(state);
165 return -1;
166 }
167
168 static gint
169 sysfs_get_critical(char const* sensor_path){
170 char sstmp [ 100 ];
171
172 if(sensor_path == NULL) return -1;
173
174 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,SYSFS_THERMAL_TRIP);
175
176 return _get_reading(sstmp, TRUE);
177 }
178
179 static gint
180 sysfs_get_temperature(char const* sensor_path){
181 char sstmp [ 100 ];
182
183 if(sensor_path == NULL) return -1;
184
185 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,SYSFS_THERMAL_TEMPF);
186
187 return _get_reading(sstmp, FALSE);
188 }
189
190 static gint
191 hwmon_get_critical(char const* sensor_path)
192 {
193 char sstmp [ 100 ];
194 int spl;
195
196 if(sensor_path == NULL) return -1;
197
198 spl = strlen(sensor_path) - 6;
199 if (spl < 17 || spl > 94)
200 return -1;
201
202 snprintf(sstmp, sizeof(sstmp), "%.*s_crit", spl, sensor_path);
203
204 return _get_reading(sstmp, TRUE);
205 }
206
207 static gint
208 hwmon_get_temperature(char const* sensor_path)
209 {
210 if(sensor_path == NULL) return -1;
211
212 return _get_reading(sensor_path, FALSE);
213 }
214
215 static gint get_temperature(thermal *th, gint *warn)
216 {
217 gint max = -273;
218 gint cur, i, w = 0;
219
220 for(i = 0; i < th->numsensors; i++){
221 cur = th->get_temperature[i](th->sensor_array[i]);
222 if (w == 2) ; /* already warning2 */
223 else if (th->not_custom_levels &&
224 th->critical[i] > 0 && cur >= th->critical[i] - 5)
225 w = 2;
226 else if ((!th->not_custom_levels || th->critical[i] < 0) &&
227 cur >= th->warning2)
228 w = 2;
229 else if (w == 1) ; /* already warning1 */
230 else if (th->not_custom_levels &&
231 th->critical[i] > 0 && cur >= th->critical[i] - 10)
232 w = 1;
233 else if ((!th->not_custom_levels || th->critical[i] < 0) &&
234 cur >= th->warning1)
235 w = 1;
236 if (cur > max)
237 max = cur;
238 th->temperature[i] = cur;
239 }
240 *warn = w;
241
242 return max;
243 }
244
245 static gint get_critical(thermal *th)
246 {
247 gint min = MAX_AUTOMATIC_CRITICAL_TEMP;
248 gint i;
249
250 for(i = 0; i < th->numsensors; i++){
251 th->critical[i] = th->get_critical[i](th->sensor_array[i]);
252 if (th->critical[i] > 0 && th->critical[i] < min)
253 min = th->critical[i];
254 }
255
256 return min;
257 }
258
259 static void
260 update_display(thermal *th)
261 {
262 char buffer [60];
263 int i;
264 int temp;
265 GdkColor color;
266 gchar *separator;
267
268 temp = get_temperature(th, &i);
269 if (i >= 2)
270 color = th->cl_warning2;
271 else if (i >= 1)
272 color = th->cl_warning1;
273 else
274 color = th->cl_normal;
275
276 if(temp == -1)
277 lxpanel_draw_label_text(th->panel, th->namew, "NA", TRUE, 1, TRUE);
278 else
279 {
280 snprintf(buffer, sizeof(buffer), "<span color=\"#%06x\"><b>%02d</b></span>",
281 gcolor2rgb24(&color), temp);
282 gtk_label_set_markup (GTK_LABEL(th->namew), buffer) ;
283 }
284
285 g_string_truncate(th->tip, 0);
286 separator = "";
287 for (i = 0; i < th->numsensors; i++){
288 g_string_append_printf(th->tip, "%s%s:\t%2d°C", separator, th->sensor_name[i], th->temperature[i]);
289 separator = "\n";
290 }
291 gtk_widget_set_tooltip_text(th->namew, th->tip->str);
292 }
293
294 static gboolean update_display_timeout(gpointer user_data)
295 {
296 if (g_source_is_destroyed(g_main_current_source()))
297 return FALSE;
298 update_display(user_data);
299 return TRUE; /* repeat later */
300 }
301
302 static int
303 add_sensor(thermal* th, char const* sensor_path, const char *sensor_name,
304 GetTempFunc get_temp, GetTempFunc get_crit)
305 {
306 if (th->numsensors + 1 > MAX_NUM_SENSORS){
307 g_warning("thermal: Too many sensors (max %d), ignoring '%s'",
308 MAX_NUM_SENSORS, sensor_path);
309 return -1;
310 }
311
312 th->sensor_array[th->numsensors] = g_strdup(sensor_path);
313 th->sensor_name[th->numsensors] = g_strdup(sensor_name);
314 th->get_critical[th->numsensors] = get_crit;
315 th->get_temperature[th->numsensors] = get_temp;
316 th->numsensors++;
317
318 g_debug("thermal: Added sensor %s", sensor_path);
319
320 return 0;
321 }
322
323 /* find_sensors():
324 * - Get the sensor directory, and store it in '*sensor'.
325 * - It is searched for in 'directory'.
326 * - Only the subdirectories starting with 'subdir_prefix' are accepted as sensors.
327 * - 'subdir_prefix' may be NULL, in which case any subdir is considered a sensor. */
328 static void
329 find_sensors(thermal* th, char const* directory, char const* subdir_prefix,
330 GetTempFunc get_temp, GetTempFunc get_crit)
331 {
332 GDir *sensorsDirectory;
333 const char *sensor_name;
334 char sensor_path[100];
335
336 if (! (sensorsDirectory = g_dir_open(directory, 0, NULL)))
337 return;
338
339 /* Scan the thermal_zone directory for available sensors */
340 while ((sensor_name = g_dir_read_name(sensorsDirectory))) {
341 if (sensor_name[0] == '.')
342 continue;
343 if (subdir_prefix) {
344 if (strncmp(sensor_name, subdir_prefix, strlen(subdir_prefix)) != 0)
345 continue;
346 }
347 snprintf(sensor_path,sizeof(sensor_path),"%s%s/", directory, sensor_name);
348 add_sensor(th, sensor_path, sensor_name, get_temp, get_crit);
349 }
350 g_dir_close(sensorsDirectory);
351 }
352
353 static gboolean try_hwmon_sensors(thermal* th, const char *path)
354 {
355 GDir *sensorsDirectory;
356 const char *sensor_name;
357 char sensor_path[100], buf[256];
358 FILE *fp;
359 gboolean found = FALSE;
360
361 if (!(sensorsDirectory = g_dir_open(path, 0, NULL)))
362 return found;
363
364 while ((sensor_name = g_dir_read_name(sensorsDirectory)))
365 {
366 if (strncmp(sensor_name, "temp", 4) == 0 &&
367 strcmp(&sensor_name[5], "_input") == 0)
368 {
369 snprintf(sensor_path, sizeof(sensor_path), "%s/temp%c_label", path,
370 sensor_name[4]);
371 fp = fopen(sensor_path, "r");
372 buf[0] = '\0';
373 if (fp)
374 {
375 if (fgets(buf, 256, fp))
376 {
377 char *pp = strchr(buf, '\n');
378 if (pp)
379 *pp = '\0';
380 }
381 fclose(fp);
382 }
383 snprintf(sensor_path, sizeof(sensor_path), "%s/%s", path, sensor_name);
384 add_sensor(th, sensor_path, buf[0] ? buf : sensor_name,
385 hwmon_get_temperature, hwmon_get_critical);
386 found = TRUE;
387 }
388 }
389 g_dir_close(sensorsDirectory);
390 return found;
391 }
392
393 static void find_hwmon_sensors(thermal* th)
394 {
395 char dir_path[100];
396 char *c;
397 int i; /* sensor type num, we'll try up to 4 */
398
399 for (i = 0; i < 4; i++)
400 {
401 snprintf(dir_path, sizeof(dir_path), "/sys/class/hwmon/hwmon%d/device", i);
402 if (try_hwmon_sensors(th, dir_path))
403 continue;
404 /* no sensors found under device/, try parent dir */
405 c = strrchr(dir_path, '/');
406 *c = '\0';
407 try_hwmon_sensors(th, dir_path);
408 }
409 }
410
411
412 static void
413 remove_all_sensors(thermal *th)
414 {
415 int i;
416
417 g_debug("thermal: Removing all sensors (%d)", th->numsensors);
418
419 for (i = 0; i < th->numsensors; i++)
420 {
421 g_free(th->sensor_array[i]);
422 g_free(th->sensor_name[i]);
423 }
424
425 th->numsensors = 0;
426 }
427
428 static void
429 check_sensors( thermal *th )
430 {
431 find_sensors(th, PROC_THERMAL_DIRECTORY, NULL, proc_get_temperature, proc_get_critical);
432 find_sensors(th, SYSFS_THERMAL_DIRECTORY, SYSFS_THERMAL_SUBDIR_PREFIX, sysfs_get_temperature, sysfs_get_critical);
433 if (th->numsensors == 0)
434 find_hwmon_sensors(th);
435 g_info("thermal: Found %d sensors", th->numsensors);
436 }
437
438
439 static gboolean applyConfig(gpointer p)
440 {
441 thermal *th = lxpanel_plugin_get_data(p);
442 int critical;
443 ENTER;
444
445 if (th->str_cl_normal) gdk_color_parse(th->str_cl_normal, &th->cl_normal);
446 if (th->str_cl_warning1) gdk_color_parse(th->str_cl_warning1, &th->cl_warning1);
447 if (th->str_cl_warning2) gdk_color_parse(th->str_cl_warning2, &th->cl_warning2);
448
449 remove_all_sensors(th);
450 /* FIXME: support wildcards in th->sensor */
451 if(th->sensor == NULL) th->auto_sensor = TRUE;
452 if(th->auto_sensor) check_sensors(th);
453 else if (strncmp(th->sensor, "/sys/", 5) != 0)
454 add_sensor(th, th->sensor, th->sensor, proc_get_temperature, proc_get_critical);
455 else if (strncmp(th->sensor, "/sys/class/hwmon/", 17) != 0)
456 add_sensor(th, th->sensor, th->sensor, sysfs_get_temperature, sysfs_get_critical);
457 else
458 add_sensor(th, th->sensor, th->sensor, hwmon_get_temperature, hwmon_get_critical);
459
460 critical = get_critical(th);
461
462 if(th->not_custom_levels){
463 th->warning1 = critical - 10;
464 th->warning2 = critical - 5;
465 }
466
467 config_group_set_string(th->settings, "NormalColor", th->str_cl_normal);
468 config_group_set_string(th->settings, "Warning1Color", th->str_cl_warning1);
469 config_group_set_string(th->settings, "Warning2Color", th->str_cl_warning2);
470 config_group_set_int(th->settings, "AutomaticLevels", th->not_custom_levels);
471 /* TODO: clean obsolete setting
472 config_setting_remove(th->settings, "CustomLevels"); */
473 config_group_set_int(th->settings, "Warning1Temp", th->warning1);
474 config_group_set_int(th->settings, "Warning2Temp", th->warning2);
475 config_group_set_int(th->settings, "AutomaticSensor", th->auto_sensor);
476 config_group_set_string(th->settings, "Sensor", th->sensor);
477 RET(FALSE);
478 }
479
480 static void
481 thermal_destructor(gpointer user_data)
482 {
483 thermal *th = (thermal *)user_data;
484
485 ENTER;
486 remove_all_sensors(th);
487 g_string_free(th->tip, TRUE);
488 g_free(th->sensor);
489 g_free(th->str_cl_normal);
490 g_free(th->str_cl_warning1);
491 g_free(th->str_cl_warning2);
492 g_source_remove(th->timer);
493 g_free(th);
494 RET();
495 }
496
497 static GtkWidget *
498 thermal_constructor(LXPanel *panel, config_setting_t *settings)
499 {
500 thermal *th;
501 GtkWidget *p;
502 const char *tmp;
503
504 ENTER;
505 th = g_new0(thermal, 1);
506 th->panel = panel;
507 th->settings = settings;
508
509 p = gtk_event_box_new();
510 lxpanel_plugin_set_data(p, th, thermal_destructor);
511 gtk_widget_set_has_window(p, FALSE);
512
513 th->namew = gtk_label_new("ww");
514 gtk_container_add(GTK_CONTAINER(p), th->namew);
515
516 th->tip = g_string_new(NULL);
517
518 /* By default, use automatic, that is, "not custom" temperature levels. If
519 * we were using custom levels, they would be 0°C at startup, so we would
520 * display in warning colors by default. */
521 th->not_custom_levels = TRUE;
522
523 if (config_setting_lookup_string(settings, "NormalColor", &tmp))
524 th->str_cl_normal = g_strdup(tmp);
525 if (config_setting_lookup_string(settings, "Warning1Color", &tmp))
526 th->str_cl_warning1 = g_strdup(tmp);
527 if (config_setting_lookup_string(settings, "Warning2Color", &tmp))
528 th->str_cl_warning2 = g_strdup(tmp);
529 config_setting_lookup_int(settings, "AutomaticSensor", &th->auto_sensor);
530 /* backward compatibility for wrong variable */
531 config_setting_lookup_int(settings, "CustomLevels", &th->not_custom_levels);
532 config_setting_lookup_int(settings, "AutomaticLevels", &th->not_custom_levels);
533 if (config_setting_lookup_string(settings, "Sensor", &tmp))
534 th->sensor = g_strdup(tmp);
535 config_setting_lookup_int(settings, "Warning1Temp", &th->warning1);
536 config_setting_lookup_int(settings, "Warning2Temp", &th->warning2);
537
538 if(!th->str_cl_normal)
539 th->str_cl_normal = g_strdup("#00ff00");
540 if(!th->str_cl_warning1)
541 th->str_cl_warning1 = g_strdup("#fff000");
542 if(!th->str_cl_warning2)
543 th->str_cl_warning2 = g_strdup("#ff0000");
544
545 applyConfig(p);
546
547 gtk_widget_show(th->namew);
548
549 update_display(th);
550 th->timer = g_timeout_add_seconds(3, (GSourceFunc) update_display_timeout, (gpointer)th);
551
552 RET(p);
553 }
554
555 static GtkWidget *config(LXPanel *panel, GtkWidget *p)
556 {
557 ENTER;
558
559 GtkWidget *dialog;
560 thermal *th = lxpanel_plugin_get_data(p);
561 dialog = lxpanel_generic_config_dlg(_("Temperature Monitor"),
562 panel, applyConfig, p,
563 _("Normal color"), &th->str_cl_normal, CONF_TYPE_STR,
564 _("Warning1 color"), &th->str_cl_warning1, CONF_TYPE_STR,
565 _("Warning2 color"), &th->str_cl_warning2, CONF_TYPE_STR,
566 _("Automatic sensor location"), &th->auto_sensor, CONF_TYPE_BOOL,
567 _("Sensor"), &th->sensor, CONF_TYPE_STR,
568 _("Automatic temperature levels"), &th->not_custom_levels, CONF_TYPE_BOOL,
569 _("Warning1 temperature"), &th->warning1, CONF_TYPE_INT,
570 _("Warning2 temperature"), &th->warning2, CONF_TYPE_INT,
571 NULL);
572
573 RET(dialog);
574 }
575
576 FM_DEFINE_MODULE(lxpanel_gtk, thermal)
577
578 LXPanelPluginInit fm_module_init_lxpanel_gtk = {
579 .name = N_("Temperature Monitor"),
580 .description = N_("Display system temperature"),
581
582 .new_instance = thermal_constructor,
583 .config = config,
584 };
585
586
587 /* vim: set sw=4 sts=4 et : */