Fix broken panel menus for 'batt' and 'monitors' plugins.
[lxde/lxpanel.git] / plugins / monitors / monitors.c
CommitLineData
9770479a
JL
1/**
2 * Plugin for the lxpanel.
3 *
4 * Displays several monitors in the panel.
5 *
6 * A lot of code in this plugin comes from the CPU plugin (that only displays a
7 * CPU monitor), that is distributed under the following terms :
8 *
b021864b
AG
9 * Copyright (C) 2010 Cyril Roelandt <steap@users.sourceforge.net>
10 * 2012-2014 Henry Gebhardt <hsggebhardt@googlemail.com>
11 * 2012 Rafał Mużyło <galtgendo@gmail.com>
5782c9b5 12 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
a013464e 13 * 2015 Rafał Mużyło <galtgendo@gmail.com>
b021864b 14 *
9770479a 15 * <terms>
b840f7cc 16 * Copyright (c) 2008-2014 LxDE Developers, see the file AUTHORS for details.
9770479a
JL
17 * Copyright (C) 2004 by Alexandre Pereira da Silva <alexandre.pereira@poli.usp.br>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 * </terms>
33 *
34 */
35
36/*
37 * HOWTO : Add your own monitor for the resource "foo".
38 *
39 * 1) Write the foo_update() function, that fills in the stats.
40 * 2) Write the foo_tooltip_update() function, that updates your tooltip. This
41 * is optional, but recommended.
42 * 3) Add a #define FOO_POSITION, and increment N_MONITORS.
43 * 4) Add :
44 * - the default color of your plugin ("default_colors" table)
45 * - the update function ("update_functions" table)
46 * - the tooltip update function ("tooltip_update" table)
47 * 5) Configuration :
48 * - edit the monitors_config() function so that a "Display FOO usage"
49 * checkbox appears in the prefs dialog.
50 * - edit the monitors_save() function so that a "DisplayFOO" string appears
51 * in the config file ("~/.config/lxpanel/<profile>/config")
52 * - edit the monitors_config() function so that a "FOO color" entry appears
53 * in the prefs dialog.
54 * - edit the monitors_save() function so that a "FOOColor" string appears
55 * in the config file.
56 * - edit the monitors_constructor() function so that options are correctly
57 * aplied. Adding something like :
0bcf9045 58 *
9770479a
JL
59 * else if (g_ascii_strcasecmp(s.t[0], "DisplayFOO") == 0)
60 * mp->displayed_monitors[FOO_POSITION] = atoi(s.t[1])
61 * else if (g_ascii_strcasecmp(s.t[0], "FOOColor") == 0)
0bcf9045 62 * colors[FOO_POSITION] = g_strndup(s.t[1], COLOR_SIZE-1);
9770479a
JL
63 *
64 * should be enough.
65 * 6) Enjoy.
66 */
67
68/*
0bcf9045 69 * FIXME : known BUGS :
9770479a
JL
70 * - when removing a monitor and re-adding it, it is drawn with a white
71 * border of BORDER_SIZE pixels around it.
72 */
73
359de8bc 74#include <stdlib.h>
9770479a 75#include <glib/gi18n.h>
5fd21529 76#include <errno.h>
9c843a52 77#include <libfm/fm-gtk.h>
9770479a 78
9c843a52 79#include "plugin.h"
9770479a
JL
80
81#include "dbg.h"
82
83
84#define PLUGIN_NAME "MonitorsPlugin"
85#define BORDER_SIZE 2 /* Pixels */
86#define DEFAULT_WIDTH 40 /* Pixels */
13d82f29 87#define UPDATE_PERIOD 1 /* Seconds */
9770479a
JL
88#define COLOR_SIZE 8 /* In chars : #xxxxxx\0 */
89
90#ifndef ENTER
91#define ENTER fprintf(stderr, "Entering %s\n", __func__);
92#endif
93
94/*
0bcf9045 95 * Stats are stored in a circular buffer.
9770479a
JL
96 * Newest values are on the left of the ring cursor.
97 * Oldest values are on the right of the ring cursor.
98 */
99typedef float stats_set;
100
101struct Monitor {
9770479a
JL
102 GdkColor foreground_color; /* Foreground color for drawing area */
103 GtkWidget *da; /* Drawing area */
e4f8b15f 104 cairo_surface_t *pixmap; /* Pixmap to be drawn on drawing area */
9770479a
JL
105 gint pixmap_width; /* Width and size of the buffer */
106 gint pixmap_height; /* Does not include border size */
107 stats_set *stats; /* Circular buffer of values */
7e6a8c48 108 stats_set total; /* Maximum possible value, as in mem_total*/
9770479a 109 gint ring_cursor; /* Cursor for ring/circular buffer */
e1d11c0b 110 gchar *color; /* Color of the graph */
9770479a
JL
111 gboolean (*update) (struct Monitor *); /* Update function */
112 void (*update_tooltip) (struct Monitor *);
113};
114
115typedef struct Monitor Monitor;
116typedef gboolean (*update_func) (Monitor *);
117typedef void (*tooltip_update_func) (Monitor *);
118
0bcf9045 119/*
9770479a
JL
120 * Position of our monitors : monitor 0 will always be on the left of the
121 * plugin, monitor 1 on the right of monitor 0 (or on the left of the plugin if
122 * monitor 0 is not displayed), etc.
123 */
124#define CPU_POSITION 0
125#define MEM_POSITION 1
126#define N_MONITORS 2
127
128/* Our plugin */
129typedef struct {
a7bd16a4 130 LXPanel *panel;
9c843a52 131 config_setting_t *settings;
9770479a
JL
132 Monitor *monitors[N_MONITORS]; /* Monitors */
133 int displayed_monitors[N_MONITORS]; /* Booleans */
b7c830a3 134 char *action; /* What to do on click */
9770479a
JL
135 guint timer; /* Timer for regular updates */
136} MonitorsPlugin;
137
0bcf9045
AG
138/*
139 * Prototypes
9770479a 140 */
9c843a52 141static void monitor_set_foreground_color(MonitorsPlugin *, Monitor *, const gchar *);
9770479a
JL
142
143/* CPU Monitor */
144static gboolean cpu_update(Monitor *);
145static void cpu_tooltip_update (Monitor *m);
146
147/* RAM Monitor */
148static gboolean mem_update(Monitor *);
149static void mem_tooltip_update (Monitor *m);
150
151
152static gboolean configure_event(GtkWidget*, GdkEventConfigure*, gpointer);
ca8dfdff 153#if !GTK_CHECK_VERSION(3, 0, 0)
9770479a 154static gboolean expose_event(GtkWidget *, GdkEventExpose *, Monitor *);
ca8dfdff
RM
155#else
156static gboolean draw(GtkWidget *, cairo_t *, Monitor *);
157#endif
9770479a
JL
158static void redraw_pixmap (Monitor *m);
159
160/* Monitors functions */
9c843a52
AG
161static void monitors_destructor(gpointer);
162static gboolean monitors_apply_config(gpointer);
9770479a
JL
163
164
165/******************************************************************************
166 * Monitor functions *
167 ******************************************************************************/
168static Monitor*
0bcf9045 169monitor_init(MonitorsPlugin *mp, Monitor *m, gchar *color)
9770479a
JL
170{
171 ENTER;
172
173 m->da = gtk_drawing_area_new();
cf07fc23 174 gtk_widget_add_events(m->da, GDK_BUTTON_PRESS_MASK);
9c843a52 175 gtk_widget_set_size_request(m->da, DEFAULT_WIDTH, panel_get_height(mp->panel));
9770479a 176
9c843a52 177 monitor_set_foreground_color(mp, m, color);
9770479a
JL
178
179 /* Signals */
0bcf9045 180 g_signal_connect(G_OBJECT(m->da), "configure-event",
9770479a 181 G_CALLBACK(configure_event), (gpointer) m);
a013464e 182#if !GTK_CHECK_VERSION(3, 0, 0)
9770479a
JL
183 g_signal_connect (G_OBJECT(m->da), "expose-event",
184 G_CALLBACK(expose_event), (gpointer) m);
a013464e 185#else
ca8dfdff
RM
186 g_signal_connect (G_OBJECT(m->da), "draw",
187 G_CALLBACK(draw), (gpointer) m);
a013464e 188#endif
9770479a
JL
189
190 return m;
191}
192
193static void
194monitor_free(Monitor *m)
195{
196 if (!m)
197 return;
198
e1d11c0b 199 g_free(m->color);
9770479a 200 if (m->pixmap)
f176735a 201 cairo_surface_destroy(m->pixmap);
9770479a
JL
202 if (m->stats)
203 g_free(m->stats);
204 g_free(m);
205
206 return;
207}
208
209static void
9c843a52 210monitor_set_foreground_color(MonitorsPlugin *mp, Monitor *m, const gchar *color)
9770479a 211{
e1d11c0b
HG
212 g_free(m->color);
213 m->color = g_strndup(color, COLOR_SIZE - 1);
9770479a 214 gdk_color_parse(color, &m->foreground_color);
9770479a
JL
215}
216/******************************************************************************
217 * End of monitor functions *
218 ******************************************************************************/
219
220/******************************************************************************
221 * CPU monitor *
222 ******************************************************************************/
201ffd7e 223typedef unsigned long long CPUTick;/* Value from /proc/stat */
9770479a
JL
224typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
225
226struct cpu_stat {
227 CPUTick u, n, s, i; /* User, nice, system, idle */
228};
229
230static gboolean
231cpu_update(Monitor * c)
232{
233 static struct cpu_stat previous_cpu_stat = { 0, 0, 0, 0 };
234
235 if ((c->stats != NULL) && (c->pixmap != NULL))
236 {
237 /* Open statistics file and scan out CPU usage. */
238 struct cpu_stat cpu;
239 FILE * stat = fopen("/proc/stat", "r");
240 if (stat == NULL)
241 return TRUE;
201ffd7e 242 int fscanf_result = fscanf(stat, "cpu %llu %llu %llu %llu",
9770479a
JL
243 &cpu.u, &cpu.n, &cpu.s, &cpu.i);
244 fclose(stat);
245
246 /* Ensure that fscanf succeeded. */
247 if (fscanf_result == 4)
248 {
249 /* Comcolors delta from previous statistics. */
250 struct cpu_stat cpu_delta;
251 cpu_delta.u = cpu.u - previous_cpu_stat.u;
252 cpu_delta.n = cpu.n - previous_cpu_stat.n;
253 cpu_delta.s = cpu.s - previous_cpu_stat.s;
254 cpu_delta.i = cpu.i - previous_cpu_stat.i;
255
256 /* Copy current to previous. */
257 memcpy(&previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
258
259 /* Comcolors user+nice+system as a fraction of total.
260 * Introduce this sample to ring buffer, increment and wrap ring
261 * buffer cursor. */
262 float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
263 c->stats[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
264 c->ring_cursor += 1;
265 if (c->ring_cursor >= c->pixmap_width)
266 c->ring_cursor = 0;
267
268 /* Redraw with the new sample. */
269 redraw_pixmap(c);
270 }
271 }
272 return TRUE;
273}
274
275static void
276cpu_tooltip_update (Monitor *m)
277{
ce6d9cc8 278 if (m && m->stats) {
6bd86e36 279 gchar *tooltip_text;
ce6d9cc8
HG
280 gint ring_pos = (m->ring_cursor == 0)
281 ? m->pixmap_width - 1 : m->ring_cursor - 1;
6bd86e36 282 tooltip_text = g_strdup_printf(_("CPU usage: %.2f%%"),
ce6d9cc8
HG
283 m->stats[ring_pos] * 100);
284 gtk_widget_set_tooltip_text(m->da, tooltip_text);
6bd86e36 285 g_free(tooltip_text);
ce6d9cc8 286 }
9770479a
JL
287}
288
289/******************************************************************************
290 * End of CPU Monitor *
291 ******************************************************************************/
292
293/******************************************************************************
294 * RAM Monitor *
295 ******************************************************************************/
296static gboolean
297mem_update(Monitor * m)
298{
0bcf9045 299 ENTER;
9770479a
JL
300
301 FILE *meminfo;
2357084d 302 char buf[80];
5fd21529
HG
303 long int mem_total = 0;
304 long int mem_free = 0;
305 long int mem_buffers = 0;
306 long int mem_cached = 0;
307 unsigned int readmask = 0x8 | 0x4 | 0x2 | 0x1;
308
309 if (!m->stats || !m->pixmap)
310 RET(TRUE);
311
312 meminfo = fopen("/proc/meminfo", "r");
313 if (!meminfo) {
06e29ce1
AG
314 g_warning("monitors: Could not open /proc/meminfo: %d, %s",
315 errno, strerror(errno));
5fd21529
HG
316 RET(FALSE);
317 }
9770479a 318
2357084d 319 while (readmask && fgets(buf, sizeof(buf), meminfo)) {
5fd21529
HG
320 if (sscanf(buf, "MemTotal: %ld kB\n", &mem_total) == 1) {
321 readmask ^= 0x1;
322 continue;
9770479a 323 }
5fd21529
HG
324 if (sscanf(buf, "MemFree: %ld kB\n", &mem_free) == 1) {
325 readmask ^= 0x2;
326 continue;
9770479a 327 }
5fd21529
HG
328 if (sscanf(buf, "Buffers: %ld kB\n", &mem_buffers) == 1) {
329 readmask ^= 0x4;
330 continue;
cffb0261 331 }
5fd21529
HG
332 if (sscanf(buf, "Cached: %ld kB\n", &mem_cached) == 1) {
333 readmask ^= 0x8;
334 continue;
cffb0261 335 }
5fd21529 336 }
9770479a 337
5fd21529 338 fclose(meminfo);
9770479a 339
5fd21529 340 if (readmask) {
06e29ce1
AG
341 g_warning("monitors: Couldn't read all values from /proc/meminfo: "
342 "readmask %x", readmask);
5fd21529 343 RET(FALSE);
9770479a
JL
344 }
345
5fd21529
HG
346 m->total = mem_total;
347
348 /* Adding stats to the buffer:
349 * It is debatable if 'mem_buffers' counts as free or not. I'll go with
350 * 'free', because it can be flushed fairly quickly, and generally
351 * isn't necessary to keep in memory.
352 * It is hard to draw the line, which caches should be counted as free,
353 * and which not. Pagecaches, dentry, and inode caches are quickly
354 * filled up again for almost any use case. Hence I would not count
355 * them as 'free'.
356 * 'mem_cached' definitely counts as 'free' because it is immediately
357 * released should any application need it. */
358 m->stats[m->ring_cursor] = (mem_total - mem_buffers - mem_free -
359 mem_cached) / (float)mem_total;
360
361 m->ring_cursor++;
362 if (m->ring_cursor >= m->pixmap_width)
363 m->ring_cursor = 0;
364
365 /* Redraw the pixmap, with the new sample */
366 redraw_pixmap (m);
367
9770479a
JL
368 RET(TRUE);
369}
370
371static void
372mem_tooltip_update (Monitor *m)
373{
ce6d9cc8 374 if (m && m->stats) {
6bd86e36 375 gchar *tooltip_text;
ce6d9cc8
HG
376 gint ring_pos = (m->ring_cursor == 0)
377 ? m->pixmap_width - 1 : m->ring_cursor - 1;
6bd86e36 378 tooltip_text = g_strdup_printf(_("RAM usage: %.1fMB (%.2f%%)"),
7e6a8c48 379 m->stats[ring_pos] * m->total / 1024,
ce6d9cc8
HG
380 m->stats[ring_pos] * 100);
381 gtk_widget_set_tooltip_text(m->da, tooltip_text);
6bd86e36 382 g_free(tooltip_text);
ce6d9cc8 383 }
9770479a
JL
384}
385/******************************************************************************
386 * End of RAM Monitor *
387 ******************************************************************************/
388
389/******************************************************************************
390 * Basic events handlers *
391 ******************************************************************************/
392static gboolean
0bcf9045 393configure_event(GtkWidget* widget, GdkEventConfigure* dummy, gpointer data)
9770479a
JL
394{
395 (void) dummy;
175f73d1 396 GtkAllocation allocation;
9770479a
JL
397
398 int new_pixmap_width, new_pixmap_height;
399
175f73d1
AG
400 gtk_widget_get_allocation(widget, &allocation);
401 new_pixmap_width = allocation.width - BORDER_SIZE * 2;
402 new_pixmap_height = allocation.height - BORDER_SIZE *2;
9770479a
JL
403 Monitor *m;
404
405 m = (Monitor *) data;
406
407 if (new_pixmap_width > 0 && new_pixmap_height > 0)
408 {
409 /*
410 * If the stats buffer does not exist (first time we get inside this
411 * function) or its size changed, reallocate the buffer and preserve
412 * existing data.
413 */
414 if (!m->stats || (new_pixmap_width != m->pixmap_width))
415 {
416 stats_set *new_stats = g_new0(stats_set, new_pixmap_width);
9c843a52 417
9770479a
JL
418 if (!new_stats)
419 return TRUE;
420
421 if (m->stats)
422 {
423 /* New allocation is larger.
424 * Add new "oldest" samples of zero following the cursor*/
425 if (new_pixmap_width > m->pixmap_width)
426 {
427 /* Number of values between the ring cursor and the end of
428 * the buffer */
429 int nvalues = m->pixmap_width - m->ring_cursor;
430
431 memcpy(new_stats,
432 m->stats,
433 m->ring_cursor * sizeof (stats_set));
434 memcpy(new_stats + nvalues,
435 m->stats + m->ring_cursor,
436 nvalues * sizeof(stats_set));
437 }
438 /* New allocation is smaller, but still larger than the ring
439 * buffer cursor */
440 else if (m->ring_cursor <= new_pixmap_width)
441 {
442 /* Numver of values that can be stored between the end of
443 * the new buffer and the ring cursor */
444 int nvalues = new_pixmap_width - m->ring_cursor;
445 memcpy(new_stats,
446 m->stats,
447 m->ring_cursor * sizeof(stats_set));
448 memcpy(new_stats + m->ring_cursor,
449 m->stats + m->pixmap_width - nvalues,
450 nvalues * sizeof(stats_set));
451 }
452 /* New allocation is smaller, and also smaller than the ring
0bcf9045
AG
453 * buffer cursor. Discard all oldest samples following the ring
454 * buffer cursor and additional samples at the beginning of the
9770479a
JL
455 * buffer. */
456 else
457 {
458 memcpy(new_stats,
459 m->stats + m->ring_cursor - new_pixmap_width,
460 new_pixmap_width * sizeof(stats_set));
461 }
462 g_free(m->stats);
463 }
464 m->stats = new_stats;
465 }
466
467 m->pixmap_width = new_pixmap_width;
468 m->pixmap_height = new_pixmap_height;
469 if (m->pixmap)
f176735a
RM
470 cairo_surface_destroy(m->pixmap);
471 m->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
9770479a 472 m->pixmap_width,
f176735a 473 m->pixmap_height);
e4f8b15f 474 check_cairo_surface_status(&m->pixmap);
9770479a
JL
475 redraw_pixmap(m);
476 }
9c843a52 477
9770479a
JL
478 return TRUE;
479}
480
ca8dfdff 481#if !GTK_CHECK_VERSION(3, 0, 0)
9770479a 482static gboolean
0bcf9045 483expose_event(GtkWidget * widget, GdkEventExpose * event, Monitor *m)
a013464e
AG
484#else
485static gboolean
486draw(GtkWidget * widget, cairo_t * cr, Monitor *m)
487#endif
9770479a
JL
488{
489 /* Draw the requested part of the pixmap onto the drawing area.
490 * Translate it in both x and y by the border size. */
491 if (m->pixmap != NULL)
492 {
a013464e 493#if !GTK_CHECK_VERSION(3, 0, 0)
175f73d1
AG
494 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
495 GtkStyle *style = gtk_widget_get_style(m->da);
f176735a
RM
496 gdk_cairo_region(cr, event->region);
497 cairo_clip(cr);
175f73d1 498 gdk_cairo_set_source_color(cr, &style->black);
ca8dfdff 499#else
a013464e
AG
500 cairo_set_source_rgb(cr, 0, 0, 0); // FIXME: set the color from style
501#endif
ca8dfdff
RM
502 cairo_set_source_surface(cr, m->pixmap, BORDER_SIZE, BORDER_SIZE);
503 cairo_paint(cr);
504 check_cairo_status(cr);
a013464e
AG
505#if !GTK_CHECK_VERSION(3, 0, 0)
506 cairo_destroy(cr);
507#endif
ca8dfdff
RM
508 }
509
510 return FALSE;
511}
b7c830a3
HG
512
513
a7bd16a4 514static gboolean monitors_button_press_event(GtkWidget* widget, GdkEventButton* evt, LXPanel *panel)
b7c830a3 515{
5782c9b5 516 MonitorsPlugin* mp;
b7c830a3 517
5782c9b5
AG
518 if (evt->button != 1)
519 return FALSE;
520
521 mp = lxpanel_plugin_get_data(widget);
b7c830a3 522 if (mp->action != NULL)
9c843a52 523 fm_launch_command_simple(NULL, NULL, 0, mp->action, NULL);
b7c830a3 524 else
9c843a52 525 fm_launch_command_simple(NULL, NULL, 0, "lxtask", NULL);
b7c830a3
HG
526
527 return TRUE;
528}
9770479a
JL
529/******************************************************************************
530 * End of basic events handlers *
531 ******************************************************************************/
532
533static void
534redraw_pixmap (Monitor *m)
535{
536 int i;
f176735a 537 cairo_t *cr = cairo_create(m->pixmap);
175f73d1
AG
538 GtkStyle *style = gtk_widget_get_style(m->da);
539
f176735a 540 cairo_set_line_width (cr, 1.0);
9770479a
JL
541
542 /* Erase pixmap */
175f73d1 543 gdk_cairo_set_source_color(cr, &style->black);
61a23b4e 544 cairo_paint(cr);
9770479a 545
61a23b4e 546 gdk_cairo_set_source_color(cr, &m->foreground_color);
9770479a
JL
547 for (i = 0; i < m->pixmap_width; i++)
548 {
206ad65a 549 unsigned int drawing_cursor = (m->ring_cursor + i) % m->pixmap_width;
9770479a 550
206ad65a 551 /* Draw one bar of the graph */
61a23b4e
HG
552 cairo_move_to(cr, i + 0.5, m->pixmap_height);
553 cairo_line_to(cr, i + 0.5, (1.0 - m->stats[drawing_cursor]) * m->pixmap_height);
f176735a 554 cairo_stroke(cr);
9770479a
JL
555 }
556
e4f8b15f 557 check_cairo_status(cr);
f176735a 558 cairo_destroy(cr);
9770479a
JL
559 /* Redraw pixmap */
560 gtk_widget_queue_draw(m->da);
561}
562
563
0bcf9045
AG
564static update_func update_functions [N_MONITORS] = {
565 [CPU_POSITION] = cpu_update,
566 [MEM_POSITION] = mem_update
9770479a
JL
567};
568
569static char *default_colors[N_MONITORS] = {
570 [CPU_POSITION] = "#0000FF",
571 [MEM_POSITION] = "#FF0000"
572};
573
574
575static tooltip_update_func tooltip_update[N_MONITORS] = {
576 [CPU_POSITION] = cpu_tooltip_update,
577 [MEM_POSITION] = mem_tooltip_update
578};
579
580/* Colors currently used. We cannot store them in the "struct Monitor"s where
581 * they belong, because we free these when the user removes them. And since we
582 * want the colors to stay the same even after removing/adding a widget... */
0bcf9045
AG
583static char *colors[N_MONITORS] = {
584 NULL,
585 NULL
9770479a
JL
586};
587
0bcf9045 588/*
e25a2539 589 * This function is called every UPDATE_PERIOD seconds. It updates all
9770479a
JL
590 * monitors.
591 */
592static gboolean
593monitors_update(gpointer data)
594{
595 MonitorsPlugin *mp;
596 int i;
251cfd3e
AG
597
598 if (g_source_is_destroyed(g_main_current_source()))
599 return FALSE;
9770479a
JL
600 mp = (MonitorsPlugin *) data;
601 if (!mp)
602 RET(FALSE);
603
604 for (i = 0; i < N_MONITORS; i++)
605 {
606 if (mp->monitors[i])
607 {
608 mp->monitors[i]->update(mp->monitors[i]);
609 if (mp->monitors[i]->update_tooltip)
610 mp->monitors[i]->update_tooltip(mp->monitors[i]);
611 }
612 }
613
614 return TRUE;
615}
616
617static Monitor*
9c843a52 618monitors_add_monitor (GtkWidget *p, MonitorsPlugin *mp, update_func update,
9770479a
JL
619 tooltip_update_func update_tooltip, gchar *color)
620{
621 ENTER;
622
623 Monitor *m;
624
625 m = g_new0(Monitor, 1);
9c843a52 626 m = monitor_init(mp, m, color);
9770479a
JL
627 m->update = update;
628 m->update_tooltip = update_tooltip;
9c843a52 629 gtk_box_pack_start(GTK_BOX(p), m->da, FALSE, FALSE, 0);
9770479a
JL
630 gtk_widget_show(m->da);
631
632 RET(m);
633}
634
9c843a52 635static GtkWidget *
a7bd16a4 636monitors_constructor(LXPanel *panel, config_setting_t *settings)
9770479a
JL
637{
638 ENTER;
639 int i;
640 MonitorsPlugin *mp;
9c843a52
AG
641 GtkWidget *p;
642 const char *tmp;
9770479a
JL
643
644 mp = g_new0(MonitorsPlugin, 1);
9c843a52
AG
645 mp->panel = panel;
646 mp->settings = settings;
9770479a 647
9c843a52
AG
648 p = gtk_hbox_new(TRUE, 2);
649 lxpanel_plugin_set_data(p, mp, monitors_destructor);
9c843a52
AG
650
651 /* First time we use this plugin : only display CPU usage */
652 mp->displayed_monitors[CPU_POSITION] = 1;
9770479a
JL
653
654 /* Apply options */
9c843a52
AG
655 config_setting_lookup_int(settings, "DisplayCPU",
656 &mp->displayed_monitors[CPU_POSITION]);
657 config_setting_lookup_int(settings, "DisplayRAM",
658 &mp->displayed_monitors[MEM_POSITION]);
659 if (config_setting_lookup_string(settings, "Action", &tmp))
660 mp->action = g_strdup(tmp);
661 if (config_setting_lookup_string(settings, "CPUColor", &tmp))
662 colors[CPU_POSITION] = g_strndup(tmp, COLOR_SIZE-1);
663 if (config_setting_lookup_string(settings, "RAMColor", &tmp))
664 colors[MEM_POSITION] = g_strndup(tmp, COLOR_SIZE-1);
9770479a 665
0bcf9045 666 /* Initializing monitors */
9770479a
JL
667 for (i = 0; i < N_MONITORS; i++)
668 {
669 if (!colors[i])
670 colors[i] = g_strndup(default_colors[i], COLOR_SIZE-1);
671
672 if (mp->displayed_monitors[i])
673 {
0bcf9045
AG
674 mp->monitors[i] = monitors_add_monitor(p, mp,
675 update_functions[i],
676 tooltip_update[i],
9770479a
JL
677 colors[i]);
678 }
679 }
9c843a52 680
9770479a 681 /* Adding a timer : monitors will be updated every UPDATE_PERIOD
e25a2539
HG
682 * seconds */
683 mp->timer = g_timeout_add_seconds(UPDATE_PERIOD, (GSourceFunc) monitors_update,
9770479a 684 (gpointer) mp);
9c843a52 685 RET(p);
9770479a
JL
686}
687
688static void
9c843a52 689monitors_destructor(gpointer user_data)
9770479a
JL
690{
691 ENTER;
692 int i;
693 MonitorsPlugin *mp;
9c843a52
AG
694
695 mp = (MonitorsPlugin *) user_data;
9770479a
JL
696
697 /* Removing timer */
698 g_source_remove(mp->timer);
9c843a52 699
9770479a
JL
700 /* Freeing all monitors */
701 for (i = 0; i < N_MONITORS; i++)
702 {
703 if (mp->monitors[i])
704 monitor_free(mp->monitors[i]);
705 }
706
b7c830a3 707 g_free(mp->action);
0bcf9045 708 g_free(mp);
9770479a
JL
709
710 RET();
711}
712
713
9c843a52 714static GtkWidget *
752ee4e2 715monitors_config (LXPanel *panel, GtkWidget *p)
9770479a
JL
716{
717 ENTER;
718
719 GtkWidget *dialog;
720 MonitorsPlugin *mp;
721
9c843a52 722 mp = lxpanel_plugin_get_data(p);
9770479a 723
9c843a52
AG
724 dialog = lxpanel_generic_config_dlg(_("Resource monitors"),
725 panel, monitors_apply_config, p,
9770479a
JL
726 _("Display CPU usage"), &mp->displayed_monitors[0], CONF_TYPE_BOOL,
727 _("CPU color"), &colors[CPU_POSITION], CONF_TYPE_STR,
728 _("Display RAM usage"), &mp->displayed_monitors[1], CONF_TYPE_BOOL,
729 _("RAM color"), &colors[MEM_POSITION], CONF_TYPE_STR,
b7c830a3 730 _("Action when clicked (default: lxtask)"), &mp->action, CONF_TYPE_STR,
9770479a 731 NULL);
9770479a 732
9c843a52 733 RET(dialog);
9770479a
JL
734}
735
9c843a52
AG
736static gboolean
737monitors_apply_config (gpointer user_data)
9770479a
JL
738{
739 ENTER;
9c843a52 740 GtkWidget *p = user_data;
9770479a 741 MonitorsPlugin *mp;
9c843a52 742 mp = lxpanel_plugin_get_data(p);
9770479a 743
9770479a
JL
744 int i;
745 int current_n_monitors = 0;
746
747start:
748 for (i = 0; i < N_MONITORS; i++)
749 {
750 if (mp->displayed_monitors[i])
751 current_n_monitors++;
752
753 if (mp->displayed_monitors[i] && !mp->monitors[i])
754 {
755 /* We've just activated monitor<i> */
0bcf9045
AG
756 mp->monitors[i] = monitors_add_monitor(p, mp,
757 update_functions[i],
758 tooltip_update[i],
9770479a
JL
759 colors[i]);
760 /*
761 * It is probably best for users if their monitors are always
762 * displayed in the same order : the CPU monitor always on the left,
763 * the RAM monitor always on the right of the CPU monitor (if the
764 * CPU monitor is displayed), etc. That's why we do not just use
765 * gtk_box_pack_start/gtk_box_pack_end, and use
766 * gtk_box_reorder_child.
767 */
0bcf9045 768 gtk_box_reorder_child(GTK_BOX(p),
9770479a
JL
769 mp->monitors[i]->da,current_n_monitors-1);
770 }
771 else if (!mp->displayed_monitors[i] && mp->monitors[i])
772 {
773 /* We've just removed monitor<i> */
5b397638 774 gtk_widget_destroy(mp->monitors[i]->da);
9770479a
JL
775 monitor_free(mp->monitors[i]);
776 mp->monitors[i] = NULL;
777 }
0bcf9045 778 if (mp->monitors[i] &&
9770479a
JL
779 strncmp(mp->monitors[i]->color, colors[i], COLOR_SIZE) != 0)
780 {
781 /* We've changed the color */
9c843a52 782 monitor_set_foreground_color(mp, mp->monitors[i], colors[i]);
9770479a
JL
783 }
784 }
785
786 /* Workaround meant to prevent users to display no monitor at all.
787 * FIXME : write something clean. When there is only one monitor displayed,
788 * its toggle button should not be clickable in the prefs. */
789 if (current_n_monitors == 0)
790 {
791 mp->displayed_monitors[0] = 1;
792 goto start;
793 }
9c843a52
AG
794 config_group_set_int(mp->settings, "DisplayCPU", mp->displayed_monitors[CPU_POSITION]);
795 config_group_set_int(mp->settings, "DisplayRAM", mp->displayed_monitors[MEM_POSITION]);
796 config_group_set_string(mp->settings, "Action", mp->action);
797 config_group_set_string(mp->settings, "CPUColor",
798 mp->monitors[CPU_POSITION] ? colors[CPU_POSITION] : NULL);
799 config_group_set_string(mp->settings, "RAMColor",
800 mp->monitors[MEM_POSITION] ? colors[MEM_POSITION] : NULL);
801
802 RET(FALSE);
9770479a
JL
803}
804
9770479a 805
9c843a52 806FM_DEFINE_MODULE(lxpanel_gtk, monitors)
9770479a 807
9c843a52 808LXPanelPluginInit fm_module_init_lxpanel_gtk = {
3c3e9c9e 809 .name = N_("Resource monitors"),
3c3e9c9e 810 .description = N_("Display monitors (CPU, RAM)"),
9c843a52 811 .new_instance = monitors_constructor,
3c3e9c9e 812 .config = monitors_config,
9c843a52 813 .button_press_event = monitors_button_press_event
9770479a 814};
b7c830a3
HG
815
816/* vim: set sw=4 sts=4 et : */