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 *
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>
12 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
13 * 2015 Rafał Mużyło <galtgendo@gmail.com>
14 *
15 * <terms>
16 * Copyright (c) 2008-2014 LxDE Developers, see the file AUTHORS for details.
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 :
58 *
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)
62 * colors[FOO_POSITION] = g_strndup(s.t[1], COLOR_SIZE-1);
63 *
64 * should be enough.
65 * 6) Enjoy.
66 */
67
68 /*
69 * FIXME : known BUGS :
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
74 #include <stdlib.h>
75 #if defined(__DragonFly__) || (__FreeBSD__)
76 #include <unistd.h>
77 #include <sys/resource.h>
78 #include <sys/types.h>
79 #include <sys/sysctl.h>
80 #endif
81 #include <glib/gi18n.h>
82 #include <errno.h>
83 #include <libfm/fm-gtk.h>
84
85 #include "plugin.h"
86
87 #include "dbg.h"
88
89
90 #define PLUGIN_NAME "MonitorsPlugin"
91 #define BORDER_SIZE 2 /* Pixels */
92 #define DEFAULT_WIDTH 40 /* Pixels */
93 #define UPDATE_PERIOD 1 /* Seconds */
94 #define COLOR_SIZE 8 /* In chars : #xxxxxx\0 */
95
96 #ifndef ENTER
97 #define ENTER fprintf(stderr, "Entering %s\n", __func__);
98 #endif
99
100 /*
101 * Stats are stored in a circular buffer.
102 * Newest values are on the left of the ring cursor.
103 * Oldest values are on the right of the ring cursor.
104 */
105 typedef float stats_set;
106
107 struct Monitor {
108 GdkColor foreground_color; /* Foreground color for drawing area */
109 GtkWidget *da; /* Drawing area */
110 cairo_surface_t *pixmap; /* Pixmap to be drawn on drawing area */
111 gint pixmap_width; /* Width and size of the buffer */
112 gint pixmap_height; /* Does not include border size */
113 stats_set *stats; /* Circular buffer of values */
114 stats_set total; /* Maximum possible value, as in mem_total*/
115 gint ring_cursor; /* Cursor for ring/circular buffer */
116 gchar *color; /* Color of the graph */
117 gboolean (*update) (struct Monitor *); /* Update function */
118 void (*update_tooltip) (struct Monitor *);
119 };
120
121 typedef struct Monitor Monitor;
122 typedef gboolean (*update_func) (Monitor *);
123 typedef void (*tooltip_update_func) (Monitor *);
124
125 /*
126 * Position of our monitors : monitor 0 will always be on the left of the
127 * plugin, monitor 1 on the right of monitor 0 (or on the left of the plugin if
128 * monitor 0 is not displayed), etc.
129 */
130 #define CPU_POSITION 0
131 #define MEM_POSITION 1
132 #define N_MONITORS 2
133
134 /* Our plugin */
135 typedef struct {
136 LXPanel *panel;
137 config_setting_t *settings;
138 Monitor *monitors[N_MONITORS]; /* Monitors */
139 int displayed_monitors[N_MONITORS]; /* Booleans */
140 char *action; /* What to do on click */
141 guint timer; /* Timer for regular updates */
142 } MonitorsPlugin;
143
144 /*
145 * Prototypes
146 */
147 static void monitor_set_foreground_color(MonitorsPlugin *, Monitor *, const gchar *);
148
149 /* CPU Monitor */
150 static gboolean cpu_update(Monitor *);
151 static void cpu_tooltip_update (Monitor *m);
152
153 /* RAM Monitor */
154 static gboolean mem_update(Monitor *);
155 static void mem_tooltip_update (Monitor *m);
156
157
158 static gboolean configure_event(GtkWidget*, GdkEventConfigure*, gpointer);
159 #if !GTK_CHECK_VERSION(3, 0, 0)
160 static gboolean expose_event(GtkWidget *, GdkEventExpose *, Monitor *);
161 #else
162 static gboolean draw(GtkWidget *, cairo_t *, Monitor *);
163 #endif
164 static void redraw_pixmap (Monitor *m);
165
166 /* Monitors functions */
167 static void monitors_destructor(gpointer);
168 static gboolean monitors_apply_config(gpointer);
169
170
171 /******************************************************************************
172 * Monitor functions *
173 ******************************************************************************/
174 static Monitor*
monitor_init(MonitorsPlugin * mp,Monitor * m,gchar * color)175 monitor_init(MonitorsPlugin *mp, Monitor *m, gchar *color)
176 {
177 ENTER;
178
179 m->da = gtk_drawing_area_new();
180 gtk_widget_add_events(m->da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
181 GDK_BUTTON_MOTION_MASK);
182 gtk_widget_set_size_request(m->da, DEFAULT_WIDTH, panel_get_height(mp->panel));
183
184 monitor_set_foreground_color(mp, m, color);
185
186 /* Signals */
187 g_signal_connect(G_OBJECT(m->da), "configure-event",
188 G_CALLBACK(configure_event), (gpointer) m);
189 #if !GTK_CHECK_VERSION(3, 0, 0)
190 g_signal_connect (G_OBJECT(m->da), "expose-event",
191 G_CALLBACK(expose_event), (gpointer) m);
192 #else
193 g_signal_connect (G_OBJECT(m->da), "draw",
194 G_CALLBACK(draw), (gpointer) m);
195 #endif
196
197 return m;
198 }
199
200 static void
monitor_free(Monitor * m)201 monitor_free(Monitor *m)
202 {
203 if (!m)
204 return;
205
206 g_free(m->color);
207 if (m->pixmap)
208 cairo_surface_destroy(m->pixmap);
209 if (m->stats)
210 g_free(m->stats);
211 g_free(m);
212
213 return;
214 }
215
216 static void
monitor_set_foreground_color(MonitorsPlugin * mp,Monitor * m,const gchar * color)217 monitor_set_foreground_color(MonitorsPlugin *mp, Monitor *m, const gchar *color)
218 {
219 g_free(m->color);
220 m->color = g_strndup(color, COLOR_SIZE - 1);
221 gdk_color_parse(color, &m->foreground_color);
222 }
223 /******************************************************************************
224 * End of monitor functions *
225 ******************************************************************************/
226
227 /******************************************************************************
228 * CPU monitor *
229 ******************************************************************************/
230 typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
231
232 #if defined(__linux__)
233 typedef unsigned long long CPUTick;/* Value from /proc/stat */
234
235 struct cpu_stat {
236 CPUTick u, n, s, i; /* User, nice, system, idle */
237 };
238 #elif defined(__DragonFly__) || (__FreeBSD__)
239 typedef glong CPUTick;
240
241 struct cpu_stat {
242 CPUTick u, n, s, intr, i;
243 };
244
cpu_nb(void)245 static gint cpu_nb(void)
246 {
247 static gint mib[] = { CTL_HW, HW_NCPU };
248 gint res;
249 size_t len = sizeof(gint);
250
251 if (sysctl(mib, 2, &res, &len, NULL, 0) < 0)
252 return 0;
253 else
254 return res;
255 }
256 #endif
257
258 static gboolean
cpu_update(Monitor * c)259 cpu_update(Monitor * c)
260 {
261 #if defined(__linux__)
262 static struct cpu_stat previous_cpu_stat = { 0, 0, 0, 0 };
263 #elif defined(__DragonFly__) || (__FreeBSD__)
264 static struct cpu_stat previous_cpu_stat = { 0, 0, 0, 0, 0 };
265 #endif
266
267 if ((c->stats != NULL) && (c->pixmap != NULL))
268 {
269 #if defined(__linux__)
270 /* Open statistics file and scan out CPU usage. */
271 struct cpu_stat cpu;
272 FILE * stat = fopen("/proc/stat", "r");
273 if (stat == NULL)
274 return TRUE;
275 int fscanf_result = fscanf(stat, "cpu %llu %llu %llu %llu",
276 &cpu.u, &cpu.n, &cpu.s, &cpu.i);
277 fclose(stat);
278
279 /* Ensure that fscanf succeeded. */
280 if (fscanf_result == 4)
281 {
282 /* Comcolors delta from previous statistics. */
283 struct cpu_stat cpu_delta;
284 cpu_delta.u = cpu.u - previous_cpu_stat.u;
285 cpu_delta.n = cpu.n - previous_cpu_stat.n;
286 cpu_delta.s = cpu.s - previous_cpu_stat.s;
287 cpu_delta.i = cpu.i - previous_cpu_stat.i;
288
289 /* Copy current to previous. */
290 memcpy(&previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
291
292 /* Comcolors user+nice+system as a fraction of total.
293 * Introduce this sample to ring buffer, increment and wrap ring
294 * buffer cursor. */
295 float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
296 c->stats[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
297 c->ring_cursor += 1;
298 if (c->ring_cursor >= c->pixmap_width)
299 c->ring_cursor = 0;
300
301 /* Redraw with the new sample. */
302 redraw_pixmap(c);
303 }
304 #elif defined(__DragonFly__) || (__FreeBSD__)
305 size_t cp_size = sizeof(glong) * CPUSTATES * cpu_nb();
306 glong *cp_times = malloc(cp_size);
307
308 if (sysctlbyname("kern.cp_times", cp_times, &cp_size, NULL, 0) < 0)
309 {
310 g_free(cp_times);
311 return FALSE;
312 }
313 else
314 {
315 struct cpu_stat cpu;
316 struct cpu_stat cpu_delta;
317
318 cpu.u = cp_times[CP_USER];
319 cpu.n = cp_times[CP_NICE];
320 cpu.s = cp_times[CP_SYS];
321 cpu.intr = cp_times[CP_INTR];
322 cpu.i = cp_times[CP_IDLE];
323
324 g_free(cp_times);
325
326 /* Compute delta from previous statistics. */
327 cpu_delta.u = cpu.u - previous_cpu_stat.u;
328 cpu_delta.n = cpu.n - previous_cpu_stat.n;
329 cpu_delta.s = cpu.s - previous_cpu_stat.s;
330 cpu_delta.intr = cpu.intr - previous_cpu_stat.intr;
331 cpu_delta.i = cpu.i - previous_cpu_stat.i;
332
333 memcpy(&previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
334
335 float cpu_used = cpu_delta.u + cpu_delta.n;
336 float cpu_total = cpu_used + cpu_delta.s + cpu_delta.intr + cpu_delta.i;
337 c->stats[c->ring_cursor] = cpu_used / cpu_total;
338 c->ring_cursor += 1;
339 if (c->ring_cursor >= c->pixmap_width)
340 c->ring_cursor = 0;
341
342 /* Redraw with the new sample. */
343 redraw_pixmap(c);
344 }
345 #endif
346 }
347 return TRUE;
348 }
349
350 static void
cpu_tooltip_update(Monitor * m)351 cpu_tooltip_update (Monitor *m)
352 {
353 if (m && m->stats) {
354 gchar *tooltip_text;
355 gint ring_pos = (m->ring_cursor == 0)
356 ? m->pixmap_width - 1 : m->ring_cursor - 1;
357 tooltip_text = g_strdup_printf(_("CPU usage: %.2f%%"),
358 m->stats[ring_pos] * 100);
359 gtk_widget_set_tooltip_text(m->da, tooltip_text);
360 g_free(tooltip_text);
361 }
362 }
363
364 /******************************************************************************
365 * End of CPU Monitor *
366 ******************************************************************************/
367
368 /******************************************************************************
369 * RAM Monitor *
370 ******************************************************************************/
371 #if defined(__DragonFly__) || (__FreeBSD__)
372 static glong
mem_get_by_bytes(const gchar * name)373 mem_get_by_bytes(const gchar *name)
374 {
375 glong buf;
376 gsize len = sizeof(glong);
377
378 if (sysctlbyname(name, &buf, &len, NULL, 0) < 0)
379 return 0;
380 else
381 return buf;
382 }
383
384 static glong
mem_get_by_pages(const gchar * name)385 mem_get_by_pages(const gchar *name)
386 {
387 glong res = 0;
388
389 res = mem_get_by_bytes(name);
390 if (res > 0)
391 res = res * getpagesize();
392
393 return res;
394 }
395 #endif
396
397 static gboolean
mem_update(Monitor * m)398 mem_update(Monitor * m)
399 {
400 ENTER;
401
402 #if defined(__linux__)
403 FILE *meminfo;
404 char buf[80];
405 long int mem_total = 0;
406 long int mem_free = 0;
407 long int mem_buffers = 0;
408 long int mem_cached = 0;
409 unsigned int readmask = 0x8 | 0x4 | 0x2 | 0x1;
410
411 if (!m->stats || !m->pixmap)
412 RET(TRUE);
413
414 meminfo = fopen("/proc/meminfo", "r");
415 if (!meminfo) {
416 g_warning("monitors: Could not open /proc/meminfo: %d, %s",
417 errno, strerror(errno));
418 RET(FALSE);
419 }
420
421 while (readmask && fgets(buf, sizeof(buf), meminfo)) {
422 if (sscanf(buf, "MemTotal: %ld kB\n", &mem_total) == 1) {
423 readmask ^= 0x1;
424 continue;
425 }
426 if (sscanf(buf, "MemFree: %ld kB\n", &mem_free) == 1) {
427 readmask ^= 0x2;
428 continue;
429 }
430 if (sscanf(buf, "Buffers: %ld kB\n", &mem_buffers) == 1) {
431 readmask ^= 0x4;
432 continue;
433 }
434 if (sscanf(buf, "Cached: %ld kB\n", &mem_cached) == 1) {
435 readmask ^= 0x8;
436 continue;
437 }
438 }
439
440 fclose(meminfo);
441
442 if (readmask) {
443 g_warning("monitors: Couldn't read all values from /proc/meminfo: "
444 "readmask %x", readmask);
445 RET(FALSE);
446 }
447 #elif defined(__DragonFly__) || (__FreeBSD__)
448 if (!m->stats || !m->pixmap)
449 RET(TRUE);
450
451 glong mem_total, mem_free, mem_buffers, mem_cached;
452
453 mem_total = mem_get_by_bytes("hw.physmem");
454 mem_free = mem_get_by_pages("vm.stats.vm.v_free_count");
455 mem_buffers = mem_get_by_bytes("vfs.bufspace");
456 mem_cached = mem_get_by_pages("vm.stats.vm.v_inactive_count");
457 #endif
458
459 m->total = mem_total;
460
461 /* Adding stats to the buffer:
462 * It is debatable if 'mem_buffers' counts as free or not. I'll go with
463 * 'free', because it can be flushed fairly quickly, and generally
464 * isn't necessary to keep in memory.
465 * It is hard to draw the line, which caches should be counted as free,
466 * and which not. Pagecaches, dentry, and inode caches are quickly
467 * filled up again for almost any use case. Hence I would not count
468 * them as 'free'.
469 * 'mem_cached' definitely counts as 'free' because it is immediately
470 * released should any application need it. */
471 m->stats[m->ring_cursor] = (mem_total - mem_buffers - mem_free -
472 mem_cached) / (float)mem_total;
473
474 m->ring_cursor++;
475 if (m->ring_cursor >= m->pixmap_width)
476 m->ring_cursor = 0;
477
478 /* Redraw the pixmap, with the new sample */
479 redraw_pixmap (m);
480
481 RET(TRUE);
482 }
483
484 static void
mem_tooltip_update(Monitor * m)485 mem_tooltip_update (Monitor *m)
486 {
487 if (m && m->stats) {
488 gchar *tooltip_text;
489 gint ring_pos = (m->ring_cursor == 0)
490 ? m->pixmap_width - 1 : m->ring_cursor - 1;
491 tooltip_text = g_strdup_printf(_("RAM usage: %.1fMB (%.2f%%)"),
492 m->stats[ring_pos] * m->total / 1024,
493 m->stats[ring_pos] * 100);
494 gtk_widget_set_tooltip_text(m->da, tooltip_text);
495 g_free(tooltip_text);
496 }
497 }
498 /******************************************************************************
499 * End of RAM Monitor *
500 ******************************************************************************/
501
502 /******************************************************************************
503 * Basic events handlers *
504 ******************************************************************************/
505 static gboolean
configure_event(GtkWidget * widget,GdkEventConfigure * dummy,gpointer data)506 configure_event(GtkWidget* widget, GdkEventConfigure* dummy, gpointer data)
507 {
508 (void) dummy;
509 GtkAllocation allocation;
510
511 int new_pixmap_width, new_pixmap_height;
512
513 gtk_widget_get_allocation(widget, &allocation);
514 new_pixmap_width = allocation.width - BORDER_SIZE * 2;
515 new_pixmap_height = allocation.height - BORDER_SIZE *2;
516 Monitor *m;
517
518 m = (Monitor *) data;
519
520 if (new_pixmap_width > 0 && new_pixmap_height > 0)
521 {
522 /*
523 * If the stats buffer does not exist (first time we get inside this
524 * function) or its size changed, reallocate the buffer and preserve
525 * existing data.
526 */
527 if (!m->stats || (new_pixmap_width != m->pixmap_width))
528 {
529 stats_set *new_stats = g_new0(stats_set, new_pixmap_width);
530
531 if (!new_stats)
532 return TRUE;
533
534 if (m->stats)
535 {
536 /* New allocation is larger.
537 * Add new "oldest" samples of zero following the cursor*/
538 if (new_pixmap_width > m->pixmap_width)
539 {
540 /* Number of values between the ring cursor and the end of
541 * the buffer */
542 int nvalues = m->pixmap_width - m->ring_cursor;
543
544 memcpy(new_stats,
545 m->stats,
546 m->ring_cursor * sizeof (stats_set));
547 memcpy(new_stats + nvalues,
548 m->stats + m->ring_cursor,
549 nvalues * sizeof(stats_set));
550 }
551 /* New allocation is smaller, but still larger than the ring
552 * buffer cursor */
553 else if (m->ring_cursor <= new_pixmap_width)
554 {
555 /* Numver of values that can be stored between the end of
556 * the new buffer and the ring cursor */
557 int nvalues = new_pixmap_width - m->ring_cursor;
558 memcpy(new_stats,
559 m->stats,
560 m->ring_cursor * sizeof(stats_set));
561 memcpy(new_stats + m->ring_cursor,
562 m->stats + m->pixmap_width - nvalues,
563 nvalues * sizeof(stats_set));
564 }
565 /* New allocation is smaller, and also smaller than the ring
566 * buffer cursor. Discard all oldest samples following the ring
567 * buffer cursor and additional samples at the beginning of the
568 * buffer. */
569 else
570 {
571 memcpy(new_stats,
572 m->stats + m->ring_cursor - new_pixmap_width,
573 new_pixmap_width * sizeof(stats_set));
574 }
575 g_free(m->stats);
576 }
577 m->stats = new_stats;
578 }
579
580 m->pixmap_width = new_pixmap_width;
581 m->pixmap_height = new_pixmap_height;
582 if (m->pixmap)
583 cairo_surface_destroy(m->pixmap);
584 m->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
585 m->pixmap_width,
586 m->pixmap_height);
587 check_cairo_surface_status(&m->pixmap);
588 redraw_pixmap(m);
589 }
590
591 return TRUE;
592 }
593
594 #if !GTK_CHECK_VERSION(3, 0, 0)
595 static gboolean
expose_event(GtkWidget * widget,GdkEventExpose * event,Monitor * m)596 expose_event(GtkWidget * widget, GdkEventExpose * event, Monitor *m)
597 #else
598 static gboolean
599 draw(GtkWidget * widget, cairo_t * cr, Monitor *m)
600 #endif
601 {
602 /* Draw the requested part of the pixmap onto the drawing area.
603 * Translate it in both x and y by the border size. */
604 if (m->pixmap != NULL)
605 {
606 #if !GTK_CHECK_VERSION(3, 0, 0)
607 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
608 GtkStyle *style = gtk_widget_get_style(m->da);
609 gdk_cairo_region(cr, event->region);
610 cairo_clip(cr);
611 gdk_cairo_set_source_color(cr, &style->black);
612 #else
613 cairo_set_source_rgb(cr, 0, 0, 0); // FIXME: set the color from style
614 #endif
615 cairo_set_source_surface(cr, m->pixmap, BORDER_SIZE, BORDER_SIZE);
616 cairo_paint(cr);
617 check_cairo_status(cr);
618 #if !GTK_CHECK_VERSION(3, 0, 0)
619 cairo_destroy(cr);
620 #endif
621 }
622
623 return FALSE;
624 }
625
626
monitors_button_press_event(GtkWidget * widget,GdkEventButton * evt,LXPanel * panel)627 static gboolean monitors_button_press_event(GtkWidget* widget, GdkEventButton* evt, LXPanel *panel)
628 {
629 MonitorsPlugin* mp;
630
631 if (evt->button != 1)
632 return FALSE;
633
634 mp = lxpanel_plugin_get_data(widget);
635 if (mp->action != NULL)
636 fm_launch_command_simple(NULL, NULL, 0, mp->action, NULL);
637 else
638 fm_launch_command_simple(NULL, NULL, 0, "lxtask", NULL);
639
640 return TRUE;
641 }
642 /******************************************************************************
643 * End of basic events handlers *
644 ******************************************************************************/
645
646 static void
redraw_pixmap(Monitor * m)647 redraw_pixmap (Monitor *m)
648 {
649 int i;
650 cairo_t *cr = cairo_create(m->pixmap);
651 GtkStyle *style = gtk_widget_get_style(m->da);
652
653 cairo_set_line_width (cr, 1.0);
654
655 /* Erase pixmap */
656 gdk_cairo_set_source_color(cr, &style->black);
657 cairo_paint(cr);
658
659 gdk_cairo_set_source_color(cr, &m->foreground_color);
660 for (i = 0; i < m->pixmap_width; i++)
661 {
662 unsigned int drawing_cursor = (m->ring_cursor + i) % m->pixmap_width;
663
664 /* Draw one bar of the graph */
665 cairo_move_to(cr, i + 0.5, m->pixmap_height);
666 cairo_line_to(cr, i + 0.5, (1.0 - m->stats[drawing_cursor]) * m->pixmap_height);
667 cairo_stroke(cr);
668 }
669
670 check_cairo_status(cr);
671 cairo_destroy(cr);
672 /* Redraw pixmap */
673 gtk_widget_queue_draw(m->da);
674 }
675
676
677 static update_func update_functions [N_MONITORS] = {
678 [CPU_POSITION] = cpu_update,
679 [MEM_POSITION] = mem_update
680 };
681
682 static char *default_colors[N_MONITORS] = {
683 [CPU_POSITION] = "#0000FF",
684 [MEM_POSITION] = "#FF0000"
685 };
686
687
688 static tooltip_update_func tooltip_update[N_MONITORS] = {
689 [CPU_POSITION] = cpu_tooltip_update,
690 [MEM_POSITION] = mem_tooltip_update
691 };
692
693 /* Colors currently used. We cannot store them in the "struct Monitor"s where
694 * they belong, because we free these when the user removes them. And since we
695 * want the colors to stay the same even after removing/adding a widget... */
696 static char *colors[N_MONITORS] = {
697 NULL,
698 NULL
699 };
700
701 /*
702 * This function is called every UPDATE_PERIOD seconds. It updates all
703 * monitors.
704 */
705 static gboolean
monitors_update(gpointer data)706 monitors_update(gpointer data)
707 {
708 MonitorsPlugin *mp;
709 int i;
710
711 if (g_source_is_destroyed(g_main_current_source()))
712 return FALSE;
713 mp = (MonitorsPlugin *) data;
714 if (!mp)
715 RET(FALSE);
716
717 for (i = 0; i < N_MONITORS; i++)
718 {
719 if (mp->monitors[i])
720 {
721 mp->monitors[i]->update(mp->monitors[i]);
722 if (mp->monitors[i]->update_tooltip)
723 mp->monitors[i]->update_tooltip(mp->monitors[i]);
724 }
725 }
726
727 return TRUE;
728 }
729
730 static Monitor*
monitors_add_monitor(GtkWidget * p,MonitorsPlugin * mp,update_func update,tooltip_update_func update_tooltip,gchar * color)731 monitors_add_monitor (GtkWidget *p, MonitorsPlugin *mp, update_func update,
732 tooltip_update_func update_tooltip, gchar *color)
733 {
734 ENTER;
735
736 Monitor *m;
737
738 m = g_new0(Monitor, 1);
739 m = monitor_init(mp, m, color);
740 m->update = update;
741 m->update_tooltip = update_tooltip;
742 gtk_box_pack_start(GTK_BOX(p), m->da, FALSE, FALSE, 0);
743 gtk_widget_show(m->da);
744
745 RET(m);
746 }
747
748 static GtkWidget *
monitors_constructor(LXPanel * panel,config_setting_t * settings)749 monitors_constructor(LXPanel *panel, config_setting_t *settings)
750 {
751 ENTER;
752 int i;
753 MonitorsPlugin *mp;
754 GtkWidget *p;
755 const char *tmp;
756
757 mp = g_new0(MonitorsPlugin, 1);
758 mp->panel = panel;
759 mp->settings = settings;
760
761 p = gtk_hbox_new(TRUE, 2);
762 lxpanel_plugin_set_data(p, mp, monitors_destructor);
763
764 /* First time we use this plugin : only display CPU usage */
765 mp->displayed_monitors[CPU_POSITION] = 1;
766
767 /* Apply options */
768 config_setting_lookup_int(settings, "DisplayCPU",
769 &mp->displayed_monitors[CPU_POSITION]);
770 config_setting_lookup_int(settings, "DisplayRAM",
771 &mp->displayed_monitors[MEM_POSITION]);
772 if (config_setting_lookup_string(settings, "Action", &tmp))
773 mp->action = g_strdup(tmp);
774 if (config_setting_lookup_string(settings, "CPUColor", &tmp))
775 colors[CPU_POSITION] = g_strndup(tmp, COLOR_SIZE-1);
776 if (config_setting_lookup_string(settings, "RAMColor", &tmp))
777 colors[MEM_POSITION] = g_strndup(tmp, COLOR_SIZE-1);
778
779 /* Initializing monitors */
780 for (i = 0; i < N_MONITORS; i++)
781 {
782 if (!colors[i])
783 colors[i] = g_strndup(default_colors[i], COLOR_SIZE-1);
784
785 if (mp->displayed_monitors[i])
786 {
787 mp->monitors[i] = monitors_add_monitor(p, mp,
788 update_functions[i],
789 tooltip_update[i],
790 colors[i]);
791 }
792 }
793
794 /* Adding a timer : monitors will be updated every UPDATE_PERIOD
795 * seconds */
796 mp->timer = g_timeout_add_seconds(UPDATE_PERIOD, (GSourceFunc) monitors_update,
797 (gpointer) mp);
798 RET(p);
799 }
800
801 static void
monitors_destructor(gpointer user_data)802 monitors_destructor(gpointer user_data)
803 {
804 ENTER;
805 int i;
806 MonitorsPlugin *mp;
807
808 mp = (MonitorsPlugin *) user_data;
809
810 /* Removing timer */
811 g_source_remove(mp->timer);
812
813 /* Freeing all monitors */
814 for (i = 0; i < N_MONITORS; i++)
815 {
816 if (mp->monitors[i])
817 monitor_free(mp->monitors[i]);
818 }
819
820 g_free(mp->action);
821 g_free(mp);
822
823 RET();
824 }
825
826
827 static GtkWidget *
monitors_config(LXPanel * panel,GtkWidget * p)828 monitors_config (LXPanel *panel, GtkWidget *p)
829 {
830 ENTER;
831
832 GtkWidget *dialog;
833 MonitorsPlugin *mp;
834
835 mp = lxpanel_plugin_get_data(p);
836
837 dialog = lxpanel_generic_config_dlg(_("Resource monitors"),
838 panel, monitors_apply_config, p,
839 _("Display CPU usage"), &mp->displayed_monitors[0], CONF_TYPE_BOOL,
840 _("CPU color"), &colors[CPU_POSITION], CONF_TYPE_STR,
841 _("Display RAM usage"), &mp->displayed_monitors[1], CONF_TYPE_BOOL,
842 _("RAM color"), &colors[MEM_POSITION], CONF_TYPE_STR,
843 _("Action when clicked (default: lxtask)"), &mp->action, CONF_TYPE_STR,
844 NULL);
845
846 RET(dialog);
847 }
848
849 static gboolean
monitors_apply_config(gpointer user_data)850 monitors_apply_config (gpointer user_data)
851 {
852 ENTER;
853 GtkWidget *p = user_data;
854 MonitorsPlugin *mp;
855 mp = lxpanel_plugin_get_data(p);
856
857 int i;
858 int current_n_monitors = 0;
859
860 start:
861 for (i = 0; i < N_MONITORS; i++)
862 {
863 if (mp->displayed_monitors[i])
864 current_n_monitors++;
865
866 if (mp->displayed_monitors[i] && !mp->monitors[i])
867 {
868 /* We've just activated monitor<i> */
869 mp->monitors[i] = monitors_add_monitor(p, mp,
870 update_functions[i],
871 tooltip_update[i],
872 colors[i]);
873 /*
874 * It is probably best for users if their monitors are always
875 * displayed in the same order : the CPU monitor always on the left,
876 * the RAM monitor always on the right of the CPU monitor (if the
877 * CPU monitor is displayed), etc. That's why we do not just use
878 * gtk_box_pack_start/gtk_box_pack_end, and use
879 * gtk_box_reorder_child.
880 */
881 gtk_box_reorder_child(GTK_BOX(p),
882 mp->monitors[i]->da,current_n_monitors-1);
883 }
884 else if (!mp->displayed_monitors[i] && mp->monitors[i])
885 {
886 /* We've just removed monitor<i> */
887 gtk_widget_destroy(mp->monitors[i]->da);
888 monitor_free(mp->monitors[i]);
889 mp->monitors[i] = NULL;
890 }
891 if (mp->monitors[i] &&
892 strncmp(mp->monitors[i]->color, colors[i], COLOR_SIZE) != 0)
893 {
894 /* We've changed the color */
895 monitor_set_foreground_color(mp, mp->monitors[i], colors[i]);
896 }
897 }
898
899 /* Workaround meant to prevent users to display no monitor at all.
900 * FIXME : write something clean. When there is only one monitor displayed,
901 * its toggle button should not be clickable in the prefs. */
902 if (current_n_monitors == 0)
903 {
904 mp->displayed_monitors[0] = 1;
905 goto start;
906 }
907 config_group_set_int(mp->settings, "DisplayCPU", mp->displayed_monitors[CPU_POSITION]);
908 config_group_set_int(mp->settings, "DisplayRAM", mp->displayed_monitors[MEM_POSITION]);
909 config_group_set_string(mp->settings, "Action", mp->action);
910 config_group_set_string(mp->settings, "CPUColor",
911 mp->monitors[CPU_POSITION] ? colors[CPU_POSITION] : NULL);
912 config_group_set_string(mp->settings, "RAMColor",
913 mp->monitors[MEM_POSITION] ? colors[MEM_POSITION] : NULL);
914
915 RET(FALSE);
916 }
917
918
919 FM_DEFINE_MODULE(lxpanel_gtk, monitors)
920
921 LXPanelPluginInit fm_module_init_lxpanel_gtk = {
922 .name = N_("Resource monitors"),
923 .description = N_("Display monitors (CPU, RAM)"),
924 .new_instance = monitors_constructor,
925 .config = monitors_config,
926 .button_press_event = monitors_button_press_event
927 };
928
929 /* vim: set sw=4 sts=4 et : */
930