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