Adding upstream version 0.9.0.
[debian/lxpanel.git] / plugins / cpu / cpu.c
CommitLineData
0688b017 1/*
6cc5e1a6
DB
2 * CPU usage plugin to lxpanel
3 *
0688b017
AG
4 * Copyright (C) 2006-2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 * 2006-2008 Jim Huang <jserv.tw@gmail.com>
6 * 2009 Marty Jack <martyj19@comcast.net>
7 * 2009 Jürgen Hötzel <juergen@archlinux.org>
8 * 2012 Rafał Mużyło <galtgendo@gmail.com>
9 * 2012-2013 Henry Gebhardt <hsggebhardt@gmail.com>
10 * 2013 Marko Rauhamaa <marko@pacujo.net>
11 * 2014 Andriy Grytsenko <andrej@rep.kiev.ua>
7a1c5048 12 * 2015 Rafał Mużyło <galtgendo@gmail.com>
0688b017
AG
13 *
14 * This file is a part of LXPanel project.
15 *
6cc5e1a6
DB
16 * Copyright (C) 2004 by Alexandre Pereira da Silva <alexandre.pereira@poli.usp.br>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 *
32 */
33/*A little bug fixed by Mykola <mykola@2ka.mipt.ru>:) */
34
6cc5e1a6
DB
35#include <string.h>
36#include <sys/time.h>
37#include <time.h>
38#include <sys/sysinfo.h>
39#include <stdlib.h>
40#include <glib/gi18n.h>
41
42#include "plugin.h"
6cc5e1a6 43
7486d297 44#define BORDER_SIZE 2
6b775dbb 45#define PANEL_HEIGHT_DEFAULT 26 /* from panel defaults */
7486d297 46
6b775dbb 47/* #include "../../dbg.h" */
2ba86315 48
6b775dbb 49typedef unsigned long long CPUTick; /* Value from /proc/stat */
2ba86315 50typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
6cc5e1a6
DB
51
52struct cpu_stat {
2ba86315 53 CPUTick u, n, s, i; /* User, nice, system, idle */
6cc5e1a6
DB
54};
55
2ba86315 56/* Private context for CPU plugin. */
6cc5e1a6 57typedef struct {
2ba86315
DB
58 GdkColor foreground_color; /* Foreground color for drawing area */
59 GtkWidget * da; /* Drawing area */
aaccad27 60 cairo_surface_t * pixmap; /* Pixmap to be drawn on drawing area */
2ba86315
DB
61
62 guint timer; /* Timer for periodic update */
63 CPUSample * stats_cpu; /* Ring buffer of CPU utilization values */
64 unsigned int ring_cursor; /* Cursor for ring buffer */
6b775dbb
AG
65 guint pixmap_width; /* Width of drawing area pixmap; also size of ring buffer; does not include border size */
66 guint pixmap_height; /* Height of drawing area pixmap; does not include border size */
2ba86315
DB
67 struct cpu_stat previous_cpu_stat; /* Previous value of cpu_stat */
68} CPUPlugin;
69
70static void redraw_pixmap(CPUPlugin * c);
71static gboolean cpu_update(CPUPlugin * c);
72static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c);
7a1c5048 73#if !GTK_CHECK_VERSION(3, 0, 0)
2ba86315 74static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c);
7a1c5048
AG
75#else
76static gboolean draw(GtkWidget * widget, cairo_t * cr, CPUPlugin * c);
77#endif
6b775dbb
AG
78
79static void cpu_destructor(gpointer user_data);
2ba86315
DB
80
81/* Redraw after timer callback or resize. */
82static void redraw_pixmap(CPUPlugin * c)
6cc5e1a6 83{
aaccad27 84 cairo_t * cr = cairo_create(c->pixmap);
6b775dbb 85 GtkStyle * style = gtk_widget_get_style(c->da);
aaccad27 86 cairo_set_line_width (cr, 1.0);
2ba86315 87 /* Erase pixmap. */
aaccad27 88 cairo_rectangle(cr, 0, 0, c->pixmap_width, c->pixmap_height);
6b775dbb 89 gdk_cairo_set_source_color(cr, &style->black);
aaccad27 90 cairo_fill(cr);
2ba86315
DB
91
92 /* Recompute pixmap. */
6cc5e1a6 93 unsigned int i;
2ba86315 94 unsigned int drawing_cursor = c->ring_cursor;
aaccad27 95 gdk_cairo_set_source_color(cr, &c->foreground_color);
2ba86315
DB
96 for (i = 0; i < c->pixmap_width; i++)
97 {
98 /* Draw one bar of the CPU usage graph. */
99 if (c->stats_cpu[drawing_cursor] != 0.0)
aaccad27
AL
100 {
101 cairo_move_to(cr, i + 0.5, c->pixmap_height);
102 cairo_line_to(cr, i + 0.5, c->pixmap_height - c->stats_cpu[drawing_cursor] * c->pixmap_height);
103 cairo_stroke(cr);
104 }
2ba86315
DB
105
106 /* Increment and wrap drawing cursor. */
107 drawing_cursor += 1;
6b775dbb 108 if (drawing_cursor >= c->pixmap_width)
2ba86315 109 drawing_cursor = 0;
6cc5e1a6 110 }
2ba86315 111
6b775dbb 112 /* check_cairo_status(cr); */
aaccad27
AL
113 cairo_destroy(cr);
114
2ba86315 115 /* Redraw pixmap. */
6cc5e1a6 116 gtk_widget_queue_draw(c->da);
6cc5e1a6
DB
117}
118
2ba86315
DB
119/* Periodic timer callback. */
120static gboolean cpu_update(CPUPlugin * c)
6cc5e1a6 121{
6b775dbb
AG
122 if (g_source_is_destroyed(g_main_current_source()))
123 return FALSE;
2ba86315
DB
124 if ((c->stats_cpu != NULL) && (c->pixmap != NULL))
125 {
126 /* Open statistics file and scan out CPU usage. */
127 struct cpu_stat cpu;
128 FILE * stat = fopen("/proc/stat", "r");
129 if (stat == NULL)
130 return TRUE;
6b775dbb 131 int fscanf_result = fscanf(stat, "cpu %llu %llu %llu %llu", &cpu.u, &cpu.n, &cpu.s, &cpu.i);
2ba86315
DB
132 fclose(stat);
133
134 /* Ensure that fscanf succeeded. */
135 if (fscanf_result == 4)
136 {
137 /* Compute delta from previous statistics. */
138 struct cpu_stat cpu_delta;
139 cpu_delta.u = cpu.u - c->previous_cpu_stat.u;
140 cpu_delta.n = cpu.n - c->previous_cpu_stat.n;
141 cpu_delta.s = cpu.s - c->previous_cpu_stat.s;
142 cpu_delta.i = cpu.i - c->previous_cpu_stat.i;
143
144 /* Copy current to previous. */
145 memcpy(&c->previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
146
147 /* Compute user+nice+system as a fraction of total.
148 * Introduce this sample to ring buffer, increment and wrap ring buffer cursor. */
149 float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
150 c->stats_cpu[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
151 c->ring_cursor += 1;
152 if (c->ring_cursor >= c->pixmap_width)
153 c->ring_cursor = 0;
154
155 /* Redraw with the new sample. */
156 redraw_pixmap(c);
157 }
158 }
159 return TRUE;
6cc5e1a6
DB
160}
161
2ba86315
DB
162/* Handler for configure_event on drawing area. */
163static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c)
6cc5e1a6 164{
6b775dbb
AG
165 GtkAllocation allocation;
166
167 gtk_widget_get_allocation(widget, &allocation);
2ba86315 168 /* Allocate pixmap and statistics buffer without border pixels. */
6b775dbb
AG
169 guint new_pixmap_width = MAX(allocation.width - BORDER_SIZE * 2, 0);
170 guint new_pixmap_height = MAX(allocation.height - BORDER_SIZE * 2, 0);
2ba86315
DB
171 if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
172 {
173 /* If statistics buffer does not exist or it changed size, reallocate and preserve existing data. */
174 if ((c->stats_cpu == NULL) || (new_pixmap_width != c->pixmap_width))
175 {
176 CPUSample * new_stats_cpu = g_new0(typeof(*c->stats_cpu), new_pixmap_width);
177 if (c->stats_cpu != NULL)
178 {
179 if (new_pixmap_width > c->pixmap_width)
180 {
181 /* New allocation is larger.
182 * Introduce new "oldest" samples of zero following the cursor. */
183 memcpy(&new_stats_cpu[0],
184 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
185 memcpy(&new_stats_cpu[new_pixmap_width - c->pixmap_width + c->ring_cursor],
186 &c->stats_cpu[c->ring_cursor], (c->pixmap_width - c->ring_cursor) * sizeof(CPUSample));
187 }
188 else if (c->ring_cursor <= new_pixmap_width)
189 {
190 /* New allocation is smaller, but still larger than the ring buffer cursor.
191 * Discard the oldest samples following the cursor. */
192 memcpy(&new_stats_cpu[0],
193 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
194 memcpy(&new_stats_cpu[c->ring_cursor],
195 &c->stats_cpu[c->pixmap_width - new_pixmap_width + c->ring_cursor], (new_pixmap_width - c->ring_cursor) * sizeof(CPUSample));
196 }
197 else
198 {
199 /* New allocation is smaller, and also smaller than the ring buffer cursor.
200 * Discard all oldest samples following the ring buffer cursor and additional samples at the beginning of the buffer. */
201 memcpy(&new_stats_cpu[0],
202 &c->stats_cpu[c->ring_cursor - new_pixmap_width], new_pixmap_width * sizeof(CPUSample));
203 c->ring_cursor = 0;
204 }
205 g_free(c->stats_cpu);
206 }
207 c->stats_cpu = new_stats_cpu;
208 }
209
210 /* Allocate or reallocate pixmap. */
211 c->pixmap_width = new_pixmap_width;
212 c->pixmap_height = new_pixmap_height;
213 if (c->pixmap)
aaccad27
AL
214 cairo_surface_destroy(c->pixmap);
215 c->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24, c->pixmap_width, c->pixmap_height);
6b775dbb 216 /* check_cairo_surface_status(&c->pixmap); */
2ba86315
DB
217
218 /* Redraw pixmap at the new size. */
219 redraw_pixmap(c);
220 }
221 return TRUE;
6cc5e1a6
DB
222}
223
2ba86315 224/* Handler for expose_event on drawing area. */
7a1c5048 225#if !GTK_CHECK_VERSION(3, 0, 0)
2ba86315 226static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c)
7a1c5048
AG
227#else
228static gboolean draw(GtkWidget * widget, cairo_t * cr, CPUPlugin * c)
229#endif
6cc5e1a6 230{
2ba86315
DB
231 /* Draw the requested part of the pixmap onto the drawing area.
232 * Translate it in both x and y by the border size. */
233 if (c->pixmap != NULL)
6cc5e1a6 234 {
7a1c5048 235#if !GTK_CHECK_VERSION(3, 0, 0)
6b775dbb
AG
236 cairo_t * cr = gdk_cairo_create(gtk_widget_get_window(widget));
237 GtkStyle * style = gtk_widget_get_style(c->da);
aaccad27
AL
238 gdk_cairo_region(cr, event->region);
239 cairo_clip(cr);
6b775dbb 240 gdk_cairo_set_source_color(cr, &style->black);
7a1c5048
AG
241#else
242 cairo_set_source_rgb(cr, 0, 0, 0); // FIXME: use black color from style
243#endif
aaccad27
AL
244 cairo_set_source_surface(cr, c->pixmap,
245 BORDER_SIZE, BORDER_SIZE);
246 cairo_paint(cr);
6b775dbb 247 /* check_cairo_status(cr); */
7a1c5048 248#if !GTK_CHECK_VERSION(3, 0, 0)
aaccad27 249 cairo_destroy(cr);
7a1c5048 250#endif
6cc5e1a6
DB
251 }
252 return FALSE;
253}
254
2ba86315 255/* Plugin constructor. */
6b775dbb 256static GtkWidget *cpu_constructor(LXPanel *panel, config_setting_t *settings)
6cc5e1a6 257{
2ba86315
DB
258 /* Allocate plugin context and set into Plugin private data pointer. */
259 CPUPlugin * c = g_new0(CPUPlugin, 1);
6b775dbb 260 GtkWidget * p;
6cc5e1a6 261
2ba86315 262 /* Allocate top level widget and set into Plugin widget pointer. */
6b775dbb
AG
263 p = gtk_event_box_new();
264 gtk_widget_set_has_window(p, FALSE);
265 lxpanel_plugin_set_data(p, c, cpu_destructor);
6cc5e1a6 266
7a1c5048 267 /* Allocate drawing area as a child of top level widget. */
6cc5e1a6 268 c->da = gtk_drawing_area_new();
7a1c5048
AG
269 gtk_widget_add_events(c->da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
270 GDK_BUTTON_MOTION_MASK);
2ba86315 271 gtk_widget_set_size_request(c->da, 40, PANEL_HEIGHT_DEFAULT);
6b775dbb 272 gtk_container_add(GTK_CONTAINER(p), c->da);
6cc5e1a6 273
2ba86315
DB
274 /* Clone a graphics context and set "green" as its foreground color.
275 * We will use this to draw the graph. */
2ba86315 276 gdk_color_parse("green", &c->foreground_color);
2ba86315
DB
277
278 /* Connect signals. */
6b775dbb 279 g_signal_connect(G_OBJECT(c->da), "configure-event", G_CALLBACK(configure_event), (gpointer) c);
7a1c5048 280#if !GTK_CHECK_VERSION(3, 0, 0)
6b775dbb 281 g_signal_connect(G_OBJECT(c->da), "expose-event", G_CALLBACK(expose_event), (gpointer) c);
7a1c5048
AG
282#else
283 g_signal_connect(G_OBJECT(c->da), "draw", G_CALLBACK(draw), (gpointer) c);
284#endif
6cc5e1a6 285
2ba86315
DB
286 /* Show the widget. Connect a timer to refresh the statistics. */
287 gtk_widget_show(c->da);
7486d297 288 c->timer = g_timeout_add(1500, (GSourceFunc) cpu_update, (gpointer) c);
6b775dbb 289 return p;
6cc5e1a6
DB
290}
291
2ba86315 292/* Plugin destructor. */
6b775dbb 293static void cpu_destructor(gpointer user_data)
6cc5e1a6 294{
6b775dbb 295 CPUPlugin * c = (CPUPlugin *)user_data;
2ba86315
DB
296
297 /* Disconnect the timer. */
298 g_source_remove(c->timer);
6cc5e1a6 299
2ba86315 300 /* Deallocate memory. */
aaccad27 301 cairo_surface_destroy(c->pixmap);
6cc5e1a6 302 g_free(c->stats_cpu);
2ba86315 303 g_free(c);
6cc5e1a6
DB
304}
305
6b775dbb 306FM_DEFINE_MODULE(lxpanel_gtk, cpu)
6cc5e1a6 307
6b775dbb
AG
308/* Plugin descriptor. */
309LXPanelPluginInit fm_module_init_lxpanel_gtk = {
310 .name = N_("CPU Usage Monitor"),
311 .description = N_("Display CPU usage"),
312 .new_instance = cpu_constructor,
6cc5e1a6 313};