75e1a2afa6fdbc3326612654bf83649ef4572584
[lxde/lxpanel.git] / src / 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 typedef gint (*GetTempFunc)(char const *);
49
50 typedef struct thermal {
51 LXPanel *panel;
52 config_setting_t *settings;
53 GtkWidget *namew;
54 GString *tip;
55 int critical;
56 int warning1;
57 int warning2;
58 int not_custom_levels, auto_sensor;
59 char *sensor,
60 *str_cl_normal,
61 *str_cl_warning1,
62 *str_cl_warning2;
63 unsigned int timer;
64 GdkColor cl_normal,
65 cl_warning1,
66 cl_warning2;
67 int numsensors;
68 char *sensor_array[MAX_NUM_SENSORS];
69 char *sensor_name[MAX_NUM_SENSORS];
70 GetTempFunc get_temperature[MAX_NUM_SENSORS];
71 GetTempFunc get_critical[MAX_NUM_SENSORS];
72 gint temperature[MAX_NUM_SENSORS];
73 } thermal;
74
75
76 static gint
77 proc_get_critical(char const* sensor_path){
78 FILE *state;
79 char buf[ 256 ], sstmp [ 100 ];
80 char* pstr;
81
82 if(sensor_path == NULL) return -1;
83
84 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,PROC_THERMAL_TRIP);
85
86 if (!(state = fopen( sstmp, "r"))) {
87 ERR("thermal: cannot open %s\n", sstmp);
88 return -1;
89 }
90
91 while( fgets(buf, 256, state) &&
92 ! ( pstr = strstr(buf, PROC_TRIP_CRITICAL) ) );
93 if( pstr )
94 {
95 pstr += strlen(PROC_TRIP_CRITICAL);
96 while( *pstr && *pstr == ' ' )
97 ++pstr;
98
99 pstr[strlen(pstr)-3] = '\0';
100 fclose(state);
101 return atoi(pstr);
102 }
103
104 fclose(state);
105 return -1;
106 }
107
108 static gint
109 proc_get_temperature(char const* sensor_path){
110 FILE *state;
111 char buf[ 256 ], sstmp [ 100 ];
112 char* pstr;
113
114 if(sensor_path == NULL) return -1;
115
116 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,PROC_THERMAL_TEMPF);
117
118 if (!(state = fopen( sstmp, "r"))) {
119 ERR("thermal: cannot open %s\n", sstmp);
120 return -1;
121 }
122
123 while( fgets(buf, 256, state) &&
124 ! ( pstr = strstr(buf, "temperature:") ) );
125 if( pstr )
126 {
127 pstr += 12;
128 while( *pstr && *pstr == ' ' )
129 ++pstr;
130
131 pstr[strlen(pstr)-3] = '\0';
132 fclose(state);
133 return atoi(pstr);
134 }
135
136 fclose(state);
137 return -1;
138 }
139
140 static gint _get_reading(const char *path)
141 {
142 FILE *state;
143 char buf[256];
144 char* pstr;
145
146 if (!(state = fopen(path, "r"))) {
147 ERR("thermal: cannot open %s\n", path);
148 return -1;
149 }
150
151 while( fgets(buf, 256, state) &&
152 ! ( pstr = buf ) );
153 if( pstr )
154 {
155 fclose(state);
156 return atoi(pstr)/1000;
157 }
158
159 fclose(state);
160 return -1;
161 }
162
163 static gint
164 sysfs_get_critical(char const* sensor_path){
165 char sstmp [ 100 ];
166
167 if(sensor_path == NULL) return -1;
168
169 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,SYSFS_THERMAL_TRIP);
170
171 return _get_reading(sstmp);
172 }
173
174 static gint
175 sysfs_get_temperature(char const* sensor_path){
176 char sstmp [ 100 ];
177
178 if(sensor_path == NULL) return -1;
179
180 snprintf(sstmp,sizeof(sstmp),"%s%s",sensor_path,SYSFS_THERMAL_TEMPF);
181
182 return _get_reading(sstmp);
183 }
184
185 static gint
186 hwmon_get_critical(char const* sensor_path)
187 {
188 char sstmp [ 100 ];
189 int spl;
190
191 if(sensor_path == NULL) return -1;
192
193 spl = strlen(sensor_path) - 6;
194 if (spl < 17 || spl > 94)
195 return -1;
196
197 snprintf(sstmp, sizeof(sstmp), "%.*s_crit", spl, sensor_path);
198
199 return _get_reading(sstmp);
200 }
201
202 static gint
203 hwmon_get_temperature(char const* sensor_path)
204 {
205 if(sensor_path == NULL) return -1;
206
207 return _get_reading(sensor_path);
208 }
209
210 static gint get_temperature(thermal *th)
211 {
212 gint max = -273;
213 gint cur, i;
214
215 for(i = 0; i < th->numsensors; i++){
216 cur = th->get_temperature[i](th->sensor_array[i]);
217 if (cur > max)
218 max = cur;
219 th->temperature[i] = cur;
220 }
221
222 return max;
223 }
224
225 static gint get_critical(thermal *th)
226 {
227 gint min = MAX_AUTOMATIC_CRITICAL_TEMP;
228 gint cur, i;
229
230 for(i = 0; i < th->numsensors; i++){
231 cur = th->get_critical[i](th->sensor_array[i]);
232 if (cur < min)
233 min = cur;
234 }
235
236 return min;
237 }
238
239 static void
240 update_display(thermal *th)
241 {
242 char buffer [60];
243 int i;
244 int temp;
245 GdkColor color;
246 gchar *separator;
247
248 temp = get_temperature(th);
249 if(temp >= th->warning2)
250 color = th->cl_warning2;
251 else if(temp >= th->warning1)
252 color = th->cl_warning1;
253 else
254 color = th->cl_normal;
255
256 if(temp == -1)
257 lxpanel_draw_label_text(th->panel, th->namew, "NA", TRUE, 1, TRUE);
258 else
259 {
260 snprintf(buffer, sizeof(buffer), "<span color=\"#%06x\"><b>%02d</b></span>",
261 gcolor2rgb24(&color), temp);
262 gtk_label_set_markup (GTK_LABEL(th->namew), buffer) ;
263 }
264
265 g_string_truncate(th->tip, 0);
266 separator = "";
267 for (i = 0; i < th->numsensors; i++){
268 g_string_append_printf(th->tip, "%s%s:\t%2d°C", separator, th->sensor_name[i], th->temperature[i]);
269 separator = "\n";
270 }
271 gtk_widget_set_tooltip_text(th->namew, th->tip->str);
272 }
273
274 static gboolean update_display_timeout(gpointer user_data)
275 {
276 if (g_source_is_destroyed(g_main_current_source()))
277 return FALSE;
278 update_display(user_data);
279 return TRUE; /* repeat later */
280 }
281
282 static int
283 add_sensor(thermal* th, char const* sensor_path, const char *sensor_name,
284 GetTempFunc get_temp, GetTempFunc get_crit)
285 {
286 if (th->numsensors + 1 > MAX_NUM_SENSORS){
287 ERR("thermal: Too many sensors (max %d), ignoring '%s'\n",
288 MAX_NUM_SENSORS, sensor_path);
289 return -1;
290 }
291
292 th->sensor_array[th->numsensors] = g_strdup(sensor_path);
293 th->sensor_name[th->numsensors] = g_strdup(sensor_name);
294 th->get_critical[th->numsensors] = get_crit;
295 th->get_temperature[th->numsensors] = get_temp;
296 th->numsensors++;
297
298 LOG(LOG_ALL, "thermal: Added sensor %s\n", sensor_path);
299
300 return 0;
301 }
302
303 /* find_sensors():
304 * - Get the sensor directory, and store it in '*sensor'.
305 * - It is searched for in 'directory'.
306 * - Only the subdirectories starting with 'subdir_prefix' are accepted as sensors.
307 * - 'subdir_prefix' may be NULL, in which case any subdir is considered a sensor. */
308 static void
309 find_sensors(thermal* th, char const* directory, char const* subdir_prefix,
310 GetTempFunc get_temp, GetTempFunc get_crit)
311 {
312 GDir *sensorsDirectory;
313 const char *sensor_name;
314 char sensor_path[100];
315
316 if (! (sensorsDirectory = g_dir_open(directory, 0, NULL)))
317 return;
318
319 /* Scan the thermal_zone directory for available sensors */
320 while ((sensor_name = g_dir_read_name(sensorsDirectory))) {
321 if (sensor_name[0] == '.')
322 continue;
323 if (subdir_prefix) {
324 if (strncmp(sensor_name, subdir_prefix, strlen(subdir_prefix)) != 0)
325 continue;
326 }
327 snprintf(sensor_path,sizeof(sensor_path),"%s%s/", directory, sensor_name);
328 add_sensor(th, sensor_path, sensor_name, get_temp, get_crit);
329 }
330 g_dir_close(sensorsDirectory);
331 }
332
333 static void find_hwmon_sensors(thermal* th)
334 {
335 GDir *sensorsDirectory;
336 const char *sensor_name;
337 char sensor_path[100], buf[256];
338 FILE *fp;
339
340 if (!(sensorsDirectory = g_dir_open("/sys/class/hwmon/hwmon0/device/", 0, NULL)))
341 return;
342 /* FIXME: do scanning hwmonX other than 0 */
343
344 while ((sensor_name = g_dir_read_name(sensorsDirectory)))
345 {
346 if (strncmp(sensor_name, "temp", 4) == 0 &&
347 strcmp(&sensor_name[5], "_input") == 0)
348 {
349 snprintf(sensor_path, sizeof(sensor_path),
350 "/sys/class/hwmon/hwmon0/device/temp%c_label", sensor_name[4]);
351 fp = fopen(sensor_path, "r");
352 buf[0] = '\0';
353 if (fp)
354 {
355 if (fgets(buf, 256, fp))
356 {
357 char *pp = strchr(buf, '\n');
358 if (pp)
359 *pp = '\0';
360 }
361 fclose(fp);
362 }
363 snprintf(sensor_path, sizeof(sensor_path),
364 "/sys/class/hwmon/hwmon0/device/%s", sensor_name);
365 add_sensor(th, sensor_path, buf[0] ? buf : sensor_name,
366 hwmon_get_temperature, hwmon_get_critical);
367 }
368 }
369 g_dir_close(sensorsDirectory);
370 }
371
372
373 static void
374 remove_all_sensors(thermal *th)
375 {
376 int i;
377
378 LOG(LOG_ALL, "thermal: Removing all sensors (%d)\n", th->numsensors);
379
380 for (i = 0; i < th->numsensors; i++)
381 {
382 g_free(th->sensor_array[i]);
383 g_free(th->sensor_name[i]);
384 }
385
386 th->numsensors = 0;
387 }
388
389 static void
390 check_sensors( thermal *th )
391 {
392 find_sensors(th, PROC_THERMAL_DIRECTORY, NULL, proc_get_temperature, proc_get_critical);
393 find_sensors(th, SYSFS_THERMAL_DIRECTORY, SYSFS_THERMAL_SUBDIR_PREFIX, sysfs_get_temperature, sysfs_get_critical);
394 if (th->numsensors == 0)
395 find_hwmon_sensors(th);
396 LOG(LOG_INFO, "thermal: Found %d sensors\n", th->numsensors);
397 }
398
399
400 static gboolean applyConfig(gpointer p)
401 {
402 thermal *th = lxpanel_plugin_get_data(p);
403 ENTER;
404
405 if (th->str_cl_normal) gdk_color_parse(th->str_cl_normal, &th->cl_normal);
406 if (th->str_cl_warning1) gdk_color_parse(th->str_cl_warning1, &th->cl_warning1);
407 if (th->str_cl_warning2) gdk_color_parse(th->str_cl_warning2, &th->cl_warning2);
408
409 remove_all_sensors(th);
410 if(th->sensor == NULL) th->auto_sensor = TRUE;
411 if(th->auto_sensor) check_sensors(th);
412 else if (strncmp(th->sensor, "/sys/", 5) != 0)
413 add_sensor(th, th->sensor, th->sensor, proc_get_temperature, proc_get_critical);
414 else if (strncmp(th->sensor, "/sys/class/hwmon/", 17) != 0)
415 add_sensor(th, th->sensor, th->sensor, sysfs_get_temperature, sysfs_get_critical);
416 else
417 add_sensor(th, th->sensor, th->sensor, hwmon_get_temperature, hwmon_get_critical);
418
419 th->critical = get_critical(th);
420
421 if(th->not_custom_levels){
422 th->warning1 = th->critical - 10;
423 th->warning2 = th->critical - 5;
424 }
425
426 config_group_set_string(th->settings, "NormalColor", th->str_cl_normal);
427 config_group_set_string(th->settings, "Warning1Color", th->str_cl_warning1);
428 config_group_set_string(th->settings, "Warning2Color", th->str_cl_warning2);
429 config_group_set_int(th->settings, "CustomLevels", th->not_custom_levels);
430 config_group_set_int(th->settings, "Warning1Temp", th->warning1);
431 config_group_set_int(th->settings, "Warning2Temp", th->warning2);
432 config_group_set_int(th->settings, "AutomaticSensor", th->auto_sensor);
433 config_group_set_string(th->settings, "Sensor", th->sensor);
434 RET(FALSE);
435 }
436
437 static void
438 thermal_destructor(gpointer user_data)
439 {
440 thermal *th = (thermal *)user_data;
441
442 ENTER;
443 remove_all_sensors(th);
444 g_string_free(th->tip, TRUE);
445 g_free(th->sensor);
446 g_free(th->str_cl_normal);
447 g_free(th->str_cl_warning1);
448 g_free(th->str_cl_warning2);
449 g_source_remove(th->timer);
450 g_free(th);
451 RET();
452 }
453
454 static GtkWidget *
455 thermal_constructor(LXPanel *panel, config_setting_t *settings)
456 {
457 thermal *th;
458 GtkWidget *p;
459 const char *tmp;
460
461 ENTER;
462 th = g_new0(thermal, 1);
463 th->panel = panel;
464 th->settings = settings;
465
466 p = gtk_event_box_new();
467 lxpanel_plugin_set_data(p, th, thermal_destructor);
468 gtk_widget_set_has_window(p, FALSE);
469 gtk_container_set_border_width( GTK_CONTAINER(p), 2 );
470
471 th->namew = gtk_label_new("ww");
472 gtk_container_add(GTK_CONTAINER(p), th->namew);
473
474 th->tip = g_string_new(NULL);
475
476 /* By default, use automatic, that is, "not custom" temperature levels. If
477 * we were using custom levels, they would be 0°C at startup, so we would
478 * display in warning colors by default. */
479 th->not_custom_levels = TRUE;
480
481 if (config_setting_lookup_string(settings, "NormalColor", &tmp))
482 th->str_cl_normal = g_strdup(tmp);
483 if (config_setting_lookup_string(settings, "Warning1Color", &tmp))
484 th->str_cl_warning1 = g_strdup(tmp);
485 if (config_setting_lookup_string(settings, "Warning2Color", &tmp))
486 th->str_cl_warning2 = g_strdup(tmp);
487 config_setting_lookup_int(settings, "AutomaticSensor", &th->auto_sensor);
488 config_setting_lookup_int(settings, "CustomLevels", &th->not_custom_levels);
489 if (config_setting_lookup_string(settings, "Sensor", &tmp))
490 th->sensor = g_strdup(tmp);
491 config_setting_lookup_int(settings, "Warning1Temp", &th->warning1);
492 config_setting_lookup_int(settings, "Warning2Temp", &th->warning2);
493
494 if(!th->str_cl_normal)
495 th->str_cl_normal = g_strdup("#00ff00");
496 if(!th->str_cl_warning1)
497 th->str_cl_warning1 = g_strdup("#fff000");
498 if(!th->str_cl_warning2)
499 th->str_cl_warning2 = g_strdup("#ff0000");
500
501 applyConfig(p);
502
503 gtk_widget_show(th->namew);
504
505 update_display(th);
506 th->timer = g_timeout_add_seconds(3, (GSourceFunc) update_display_timeout, (gpointer)th);
507
508 RET(p);
509 }
510
511 static GtkWidget *config(LXPanel *panel, GtkWidget *p)
512 {
513 ENTER;
514
515 GtkWidget *dialog;
516 thermal *th = lxpanel_plugin_get_data(p);
517 dialog = lxpanel_generic_config_dlg(_("Temperature Monitor"),
518 panel, applyConfig, p,
519 _("Normal"), &th->str_cl_normal, CONF_TYPE_STR,
520 _("Warning1"), &th->str_cl_warning1, CONF_TYPE_STR,
521 _("Warning2"), &th->str_cl_warning2, CONF_TYPE_STR,
522 _("Automatic sensor location"), &th->auto_sensor, CONF_TYPE_BOOL,
523 _("Sensor"), &th->sensor, CONF_TYPE_STR,
524 _("Automatic temperature levels"), &th->not_custom_levels, CONF_TYPE_BOOL,
525 _("Warning1 Temperature"), &th->warning1, CONF_TYPE_INT,
526 _("Warning2 Temperature"), &th->warning2, CONF_TYPE_INT,
527 NULL);
528
529 RET(dialog);
530 }
531
532 FM_DEFINE_MODULE(lxpanel_gtk, thermal)
533
534 LXPanelPluginInit fm_module_init_lxpanel_gtk = {
535 .name = N_("Temperature Monitor"),
536 .description = N_("Display system temperature"),
537
538 .new_instance = thermal_constructor,
539 .config = config,
540 };
541
542
543 /* vim: set sw=4 sts=4 et : */