2
* Plugin for the lxpanel.
4
* Displays several monitors in the panel.
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 :
10
* Copyright (c) 2008 LxDE Developers, see the file AUTHORS for details.
11
* Copyright (C) 2004 by Alexandre Pereira da Silva <alexandre.pereira@poli.usp.br>
13
* This program is free software; you can redistribute it and/or modify
14
* it under the terms of the GNU General Public License as published by
15
* the Free Software Foundation; either version 2 of the License, or
16
* (at your option) any later version.
18
* This program is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
* General Public License for more details.
23
* You should have received a copy of the GNU General Public License
24
* along with this program; if not, write to the Free Software
25
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31
* HOWTO : Add your own monitor for the resource "foo".
33
* 1) Write the foo_update() function, that fills in the stats.
34
* 2) Write the foo_tooltip_update() function, that updates your tooltip. This
35
* is optional, but recommended.
36
* 3) Add a #define FOO_POSITION, and increment N_MONITORS.
38
* - the default color of your plugin ("default_colors" table)
39
* - the update function ("update_functions" table)
40
* - the tooltip update function ("tooltip_update" table)
42
* - edit the monitors_config() function so that a "Display FOO usage"
43
* checkbox appears in the prefs dialog.
44
* - edit the monitors_save() function so that a "DisplayFOO" string appears
45
* in the config file ("~/.config/lxpanel/<profile>/config")
46
* - edit the monitors_config() function so that a "FOO color" entry appears
47
* in the prefs dialog.
48
* - edit the monitors_save() function so that a "FOOColor" string appears
50
* - edit the monitors_constructor() function so that options are correctly
51
* aplied. Adding something like :
53
* else if (g_ascii_strcasecmp(s.t[0], "DisplayFOO") == 0)
54
* mp->displayed_monitors[FOO_POSITION] = atoi(s.t[1])
55
* else if (g_ascii_strcasecmp(s.t[0], "FOOColor") == 0)
56
* colors[FOO_POSITION] = g_strndup(s.t[1], COLOR_SIZE-1);
63
* FIXME : known BUGS :
64
* - when removing a monitor and re-adding it, it is drawn with a white
65
* border of BORDER_SIZE pixels around it.
69
#include <glib/gi18n.h>
79
#define PLUGIN_NAME "MonitorsPlugin"
80
#define BORDER_SIZE 2 /* Pixels */
81
#define DEFAULT_WIDTH 40 /* Pixels */
82
#define UPDATE_PERIOD 1 /* Seconds */
83
#define COLOR_SIZE 8 /* In chars : #xxxxxx\0 */
86
#define ENTER fprintf(stderr, "Entering %s\n", __func__);
90
* Stats are stored in a circular buffer.
91
* Newest values are on the left of the ring cursor.
92
* Oldest values are on the right of the ring cursor.
94
typedef float stats_set;
97
GdkColor foreground_color; /* Foreground color for drawing area */
98
GtkWidget *da; /* Drawing area */
99
cairo_surface_t *pixmap; /* Pixmap to be drawn on drawing area */
100
gint pixmap_width; /* Width and size of the buffer */
101
gint pixmap_height; /* Does not include border size */
102
stats_set *stats; /* Circular buffer of values */
103
stats_set total; /* Maximum possible value, as in mem_total*/
104
gint ring_cursor; /* Cursor for ring/circular buffer */
105
gchar *color; /* Color of the graph */
106
gboolean (*update) (struct Monitor *); /* Update function */
107
void (*update_tooltip) (struct Monitor *);
110
typedef struct Monitor Monitor;
111
typedef gboolean (*update_func) (Monitor *);
112
typedef void (*tooltip_update_func) (Monitor *);
115
* Position of our monitors : monitor 0 will always be on the left of the
116
* plugin, monitor 1 on the right of monitor 0 (or on the left of the plugin if
117
* monitor 0 is not displayed), etc.
119
#define CPU_POSITION 0
120
#define MEM_POSITION 1
125
Monitor *monitors[N_MONITORS]; /* Monitors */
126
int displayed_monitors[N_MONITORS]; /* Booleans */
127
char *action; /* What to do on click */
128
guint timer; /* Timer for regular updates */
134
static Monitor* monitor_init(Plugin *, Monitor *, gchar *);
135
static void monitor_free(Monitor *m);
136
static void monitor_set_foreground_color(Plugin *, Monitor *, const gchar *);
139
static gboolean cpu_update(Monitor *);
140
static void cpu_tooltip_update (Monitor *m);
143
static gboolean mem_update(Monitor *);
144
static void mem_tooltip_update (Monitor *m);
147
static gboolean configure_event(GtkWidget*, GdkEventConfigure*, gpointer);
148
static gboolean expose_event(GtkWidget *, GdkEventExpose *, Monitor *);
149
static void redraw_pixmap (Monitor *m);
150
static gboolean monitors_button_press_event(GtkWidget*, GdkEventButton*, Plugin*);
152
/* Monitors functions */
153
static int monitors_constructor(Plugin *, char **);
154
static void monitors_destructor(Plugin*p);
155
static void monitors_config (Plugin *p, GtkWindow *parent);
156
static void monitors_apply_config (Plugin *);
157
static void monitors_save(Plugin *p, FILE *fp);
158
static gboolean monitors_update(gpointer);
159
static Monitor* monitors_add_monitor (Plugin *p, MonitorsPlugin *mp,
160
update_func update, tooltip_update_func update_tooltip, gchar *color);
164
/******************************************************************************
165
* Monitor functions *
166
******************************************************************************/
168
monitor_init(Plugin *p, Monitor *m, gchar *color)
172
m->da = gtk_drawing_area_new();
173
gtk_widget_set_size_request(m->da, DEFAULT_WIDTH, PANEL_HEIGHT_DEFAULT);
174
gtk_widget_add_events(m->da, GDK_BUTTON_PRESS_MASK);
176
monitor_set_foreground_color(p, m, color);
179
g_signal_connect(G_OBJECT(m->da), "configure-event",
180
G_CALLBACK(configure_event), (gpointer) m);
181
g_signal_connect (G_OBJECT(m->da), "expose-event",
182
G_CALLBACK(expose_event), (gpointer) m);
183
g_signal_connect(G_OBJECT(m->da), "button-press-event",
184
G_CALLBACK(plugin_button_press_event), p);
190
monitor_free(Monitor *m)
197
cairo_surface_destroy(m->pixmap);
206
monitor_set_foreground_color(Plugin *p, Monitor *m, const gchar *color)
209
m->color = g_strndup(color, COLOR_SIZE - 1);
210
gdk_color_parse(color, &m->foreground_color);
212
/******************************************************************************
213
* End of monitor functions *
214
******************************************************************************/
216
/******************************************************************************
218
******************************************************************************/
219
typedef unsigned long long CPUTick;/* Value from /proc/stat */
220
typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
223
CPUTick u, n, s, i; /* User, nice, system, idle */
227
cpu_update(Monitor * c)
229
static struct cpu_stat previous_cpu_stat = { 0, 0, 0, 0 };
231
if ((c->stats != NULL) && (c->pixmap != NULL))
233
/* Open statistics file and scan out CPU usage. */
235
FILE * stat = fopen("/proc/stat", "r");
238
int fscanf_result = fscanf(stat, "cpu %llu %llu %llu %llu",
239
&cpu.u, &cpu.n, &cpu.s, &cpu.i);
242
/* Ensure that fscanf succeeded. */
243
if (fscanf_result == 4)
245
/* Comcolors delta from previous statistics. */
246
struct cpu_stat cpu_delta;
247
cpu_delta.u = cpu.u - previous_cpu_stat.u;
248
cpu_delta.n = cpu.n - previous_cpu_stat.n;
249
cpu_delta.s = cpu.s - previous_cpu_stat.s;
250
cpu_delta.i = cpu.i - previous_cpu_stat.i;
252
/* Copy current to previous. */
253
memcpy(&previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
255
/* Comcolors user+nice+system as a fraction of total.
256
* Introduce this sample to ring buffer, increment and wrap ring
258
float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
259
c->stats[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
261
if (c->ring_cursor >= c->pixmap_width)
264
/* Redraw with the new sample. */
272
cpu_tooltip_update (Monitor *m)
276
gint ring_pos = (m->ring_cursor == 0)
277
? m->pixmap_width - 1 : m->ring_cursor - 1;
278
tooltip_text = g_strdup_printf(_("CPU usage: %.2f%%"),
279
m->stats[ring_pos] * 100);
280
gtk_widget_set_tooltip_text(m->da, tooltip_text);
281
g_free(tooltip_text);
285
/******************************************************************************
286
* End of CPU Monitor *
287
******************************************************************************/
289
/******************************************************************************
291
******************************************************************************/
293
mem_update(Monitor * m)
298
int const buflen = 80;
300
long int mem_total = 0;
301
long int mem_free = 0;
302
long int mem_buffers = 0;
303
long int mem_cached = 0;
304
unsigned int readmask = 0x8 | 0x4 | 0x2 | 0x1;
306
if (!m->stats || !m->pixmap)
309
meminfo = fopen("/proc/meminfo", "r");
311
ERR("monitors: Could not open /proc/meminfo: %d, %s\n",
312
errno, strerror(errno));
316
while (readmask && fgets(buf, buflen, meminfo)) {
317
if (sscanf(buf, "MemTotal: %ld kB\n", &mem_total) == 1) {
321
if (sscanf(buf, "MemFree: %ld kB\n", &mem_free) == 1) {
325
if (sscanf(buf, "Buffers: %ld kB\n", &mem_buffers) == 1) {
329
if (sscanf(buf, "Cached: %ld kB\n", &mem_cached) == 1) {
338
ERR("monitors: Couldn't read all values from /proc/meminfo: "
339
"readmask %x\n", readmask);
343
m->total = mem_total;
345
/* Adding stats to the buffer:
346
* It is debatable if 'mem_buffers' counts as free or not. I'll go with
347
* 'free', because it can be flushed fairly quickly, and generally
348
* isn't necessary to keep in memory.
349
* It is hard to draw the line, which caches should be counted as free,
350
* and which not. Pagecaches, dentry, and inode caches are quickly
351
* filled up again for almost any use case. Hence I would not count
353
* 'mem_cached' definitely counts as 'free' because it is immediately
354
* released should any application need it. */
355
m->stats[m->ring_cursor] = (mem_total - mem_buffers - mem_free -
356
mem_cached) / (float)mem_total;
359
if (m->ring_cursor >= m->pixmap_width)
362
/* Redraw the pixmap, with the new sample */
369
mem_tooltip_update (Monitor *m)
373
gint ring_pos = (m->ring_cursor == 0)
374
? m->pixmap_width - 1 : m->ring_cursor - 1;
375
tooltip_text = g_strdup_printf(_("RAM usage: %.1fMB (%.2f%%)"),
376
m->stats[ring_pos] * m->total / 1024,
377
m->stats[ring_pos] * 100);
378
gtk_widget_set_tooltip_text(m->da, tooltip_text);
379
g_free(tooltip_text);
382
/******************************************************************************
383
* End of RAM Monitor *
384
******************************************************************************/
386
/******************************************************************************
387
* Basic events handlers *
388
******************************************************************************/
390
configure_event(GtkWidget* widget, GdkEventConfigure* dummy, gpointer data)
394
int new_pixmap_width, new_pixmap_height;
396
new_pixmap_width = widget->allocation.width - BORDER_SIZE * 2;
397
new_pixmap_height = widget->allocation.height - BORDER_SIZE *2;
400
m = (Monitor *) data;
402
if (new_pixmap_width > 0 && new_pixmap_height > 0)
405
* If the stats buffer does not exist (first time we get inside this
406
* function) or its size changed, reallocate the buffer and preserve
409
if (!m->stats || (new_pixmap_width != m->pixmap_width))
411
stats_set *new_stats = g_new0(stats_set, new_pixmap_width);
418
/* New allocation is larger.
419
* Add new "oldest" samples of zero following the cursor*/
420
if (new_pixmap_width > m->pixmap_width)
422
/* Number of values between the ring cursor and the end of
424
int nvalues = m->pixmap_width - m->ring_cursor;
428
m->ring_cursor * sizeof (stats_set));
429
memcpy(new_stats + nvalues,
430
m->stats + m->ring_cursor,
431
nvalues * sizeof(stats_set));
433
/* New allocation is smaller, but still larger than the ring
435
else if (m->ring_cursor <= new_pixmap_width)
437
/* Numver of values that can be stored between the end of
438
* the new buffer and the ring cursor */
439
int nvalues = new_pixmap_width - m->ring_cursor;
442
m->ring_cursor * sizeof(stats_set));
443
memcpy(new_stats + m->ring_cursor,
444
m->stats + m->pixmap_width - nvalues,
445
nvalues * sizeof(stats_set));
447
/* New allocation is smaller, and also smaller than the ring
448
* buffer cursor. Discard all oldest samples following the ring
449
* buffer cursor and additional samples at the beginning of the
454
m->stats + m->ring_cursor - new_pixmap_width,
455
new_pixmap_width * sizeof(stats_set));
459
m->stats = new_stats;
462
m->pixmap_width = new_pixmap_width;
463
m->pixmap_height = new_pixmap_height;
465
cairo_surface_destroy(m->pixmap);
466
m->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
469
check_cairo_surface_status(&m->pixmap);
477
expose_event(GtkWidget * widget, GdkEventExpose * event, Monitor *m)
479
/* Draw the requested part of the pixmap onto the drawing area.
480
* Translate it in both x and y by the border size. */
481
if (m->pixmap != NULL)
483
cairo_t *cr = gdk_cairo_create(widget->window);
484
gdk_cairo_region(cr, event->region);
486
gdk_cairo_set_source_color(cr, &m->da->style->black);
487
cairo_set_source_surface(cr, m->pixmap, BORDER_SIZE, BORDER_SIZE);
489
check_cairo_status(cr);
498
static gboolean monitors_button_press_event(GtkWidget* widget, GdkEventButton* evt, Plugin *plugin)
500
MonitorsPlugin* mp = plugin->priv;
502
/* Standard right-click handling. */
503
if (plugin_button_press_event(widget, evt, plugin))
506
if (mp->action != NULL)
507
g_spawn_command_line_async(mp->action, NULL);
509
g_spawn_command_line_async("lxtask", NULL);
513
/******************************************************************************
514
* End of basic events handlers *
515
******************************************************************************/
518
redraw_pixmap (Monitor *m)
521
cairo_t *cr = cairo_create(m->pixmap);
522
cairo_set_line_width (cr, 1.0);
525
gdk_cairo_set_source_color(cr, &m->da->style->black);
528
gdk_cairo_set_source_color(cr, &m->foreground_color);
529
for (i = 0; i < m->pixmap_width; i++)
531
unsigned int drawing_cursor = (m->ring_cursor + i) % m->pixmap_width;
533
/* Draw one bar of the graph */
534
cairo_move_to(cr, i + 0.5, m->pixmap_height);
535
cairo_line_to(cr, i + 0.5, (1.0 - m->stats[drawing_cursor]) * m->pixmap_height);
539
check_cairo_status(cr);
542
gtk_widget_queue_draw(m->da);
546
static update_func update_functions [N_MONITORS] = {
547
[CPU_POSITION] = cpu_update,
548
[MEM_POSITION] = mem_update
551
static char *default_colors[N_MONITORS] = {
552
[CPU_POSITION] = "#0000FF",
553
[MEM_POSITION] = "#FF0000"
557
static tooltip_update_func tooltip_update[N_MONITORS] = {
558
[CPU_POSITION] = cpu_tooltip_update,
559
[MEM_POSITION] = mem_tooltip_update
562
/* Colors currently used. We cannot store them in the "struct Monitor"s where
563
* they belong, because we free these when the user removes them. And since we
564
* want the colors to stay the same even after removing/adding a widget... */
565
static char *colors[N_MONITORS] = {
571
* This function is called every UPDATE_PERIOD seconds. It updates all
575
monitors_update(gpointer data)
580
mp = (MonitorsPlugin *) data;
584
for (i = 0; i < N_MONITORS; i++)
588
mp->monitors[i]->update(mp->monitors[i]);
589
if (mp->monitors[i]->update_tooltip)
590
mp->monitors[i]->update_tooltip(mp->monitors[i]);
598
monitors_add_monitor (Plugin *p, MonitorsPlugin *mp, update_func update,
599
tooltip_update_func update_tooltip, gchar *color)
605
m = g_new0(Monitor, 1);
606
m = monitor_init(p, m, color);
608
m->update_tooltip = update_tooltip;
609
gtk_box_pack_start(GTK_BOX(p->pwid), m->da, FALSE, FALSE, 0);
610
gtk_widget_show(m->da);
616
monitors_constructor(Plugin *p, char **fp)
622
mp = g_new0(MonitorsPlugin, 1);
625
p->pwid = gtk_hbox_new(TRUE, 2);
626
gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 1);
627
GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
628
g_signal_connect(G_OBJECT(p->pwid), "button_press_event", G_CALLBACK(monitors_button_press_event), (gpointer) p);
636
while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
637
if (s.type == LINE_NONE) {
638
ERR("%s : illegal token %s\n", PLUGIN_NAME, s.str);
641
if (s.type == LINE_VAR) {
642
if (g_ascii_strcasecmp(s.t[0], "DisplayCPU") == 0)
643
mp->displayed_monitors[CPU_POSITION] = atoi(s.t[1]);
644
else if (g_ascii_strcasecmp(s.t[0], "DisplayRAM") == 0)
645
mp->displayed_monitors[MEM_POSITION] = atoi(s.t[1]);
646
else if (g_ascii_strcasecmp(s.t[0], "Action") == 0)
647
mp->action = g_strdup(s.t[1]);
648
else if (g_ascii_strcasecmp(s.t[0], "CPUColor") == 0)
649
colors[CPU_POSITION] = g_strndup(s.t[1], COLOR_SIZE-1);
650
else if (g_ascii_strcasecmp(s.t[0], "RAMColor") == 0)
651
colors[MEM_POSITION] = g_strndup(s.t[1], COLOR_SIZE-1);
653
ERR("%s : unknown var %s\n", PLUGIN_NAME, s.t[0]);
661
/* First time we use this plugin : only display CPU usage */
662
mp->displayed_monitors[CPU_POSITION] = 1;
665
/* Initializing monitors */
666
for (i = 0; i < N_MONITORS; i++)
669
colors[i] = g_strndup(default_colors[i], COLOR_SIZE-1);
671
if (mp->displayed_monitors[i])
673
mp->monitors[i] = monitors_add_monitor(p, mp,
680
/* Adding a timer : monitors will be updated every UPDATE_PERIOD
682
mp->timer = g_timeout_add_seconds(UPDATE_PERIOD, (GSourceFunc) monitors_update,
688
monitors_destructor(Plugin *p)
694
mp = (MonitorsPlugin *) p->priv;
697
g_source_remove(mp->timer);
699
/* Freeing all monitors */
700
for (i = 0; i < N_MONITORS; i++)
703
monitor_free(mp->monitors[i]);
714
monitors_config (Plugin *p, GtkWindow *parent)
721
mp = (MonitorsPlugin *) p->priv;
723
dialog = create_generic_config_dlg(_(p->class->name),
725
(GSourceFunc) monitors_apply_config, (gpointer) p,
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,
730
_("Action when clicked (default: lxtask)"), &mp->action, CONF_TYPE_STR,
732
gtk_window_present(GTK_WINDOW(dialog));
738
monitors_apply_config (Plugin *p)
742
mp = (MonitorsPlugin *) p->priv;
745
int current_n_monitors = 0;
748
for (i = 0; i < N_MONITORS; i++)
750
if (mp->displayed_monitors[i])
751
current_n_monitors++;
753
if (mp->displayed_monitors[i] && !mp->monitors[i])
755
/* We've just activated monitor<i> */
756
mp->monitors[i] = monitors_add_monitor(p, mp,
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.
768
gtk_box_reorder_child(GTK_BOX(p->pwid),
769
mp->monitors[i]->da,current_n_monitors-1);
771
else if (!mp->displayed_monitors[i] && mp->monitors[i])
773
/* We've just removed monitor<i> */
774
gtk_container_remove(GTK_CONTAINER(p->pwid), mp->monitors[i]->da);
775
monitor_free(mp->monitors[i]);
776
mp->monitors[i] = NULL;
778
if (mp->monitors[i] &&
779
strncmp(mp->monitors[i]->color, colors[i], COLOR_SIZE) != 0)
781
/* We've changed the color */
782
monitor_set_foreground_color(p, mp->monitors[i], colors[i]);
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)
791
mp->displayed_monitors[0] = 1;
799
monitors_save(Plugin *p, FILE *fp)
805
mp = (MonitorsPlugin *) p->priv;
807
lxpanel_put_bool(fp, "DisplayCPU", mp->displayed_monitors[CPU_POSITION]);
808
lxpanel_put_bool(fp, "DisplayRAM", mp->displayed_monitors[MEM_POSITION]);
809
lxpanel_put_str(fp, "Action", mp->action);
811
if (mp->monitors[CPU_POSITION])
812
lxpanel_put_str(fp, "CPUColor", colors[CPU_POSITION]);
814
if (mp->monitors[MEM_POSITION])
815
lxpanel_put_str(fp, "RAMColor", colors[MEM_POSITION]);
820
PluginClass monitors_plugin_class = {
821
PLUGINCLASS_VERSIONING,
823
name : N_("Resource monitors"),
825
description: N_("Display monitors (CPU, RAM)"),
826
constructor: monitors_constructor,
827
destructor : monitors_destructor,
828
config: monitors_config,
830
panel_configuration_changed: NULL
833
/* vim: set sw=4 sts=4 et : */