Enabling multithreaded compilation.
[debian/lxpanel.git] / src / plugins / batt / batt.c
1 /*
2 * ACPI battery monitor plugin for LXPanel
3 *
4 * Copyright (C) 2007 by Greg McNew <gmcnew@gmail.com>
5 * Copyright (C) 2008 by Hong Jen Yee <pcman.tw@gmail.com>
6 * Copyright (C) 2009 by Juergen Hoetzel <juergen@archlinux.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 *
23 * This plugin monitors battery usage on ACPI-enabled systems by reading the
24 * battery information found in /sys/class/power_supply. The update interval is
25 * user-configurable and defaults to 3 second.
26 *
27 * The battery's remaining life is estimated from its current charge and current
28 * rate of discharge. The user may configure an alarm command to be run when
29 * their estimated remaining battery life reaches a certain level.
30 */
31
32 /* FIXME:
33 * Here are somethings need to be improvec:
34 * 1. Replace pthread stuff with gthread counterparts for portability.
35 * 3. Add an option to hide the plugin when AC power is used or there is no battery.
36 * 4. Handle failure gracefully under systems other than Linux.
37 */
38
39 #include <glib.h>
40 #include <glib/gi18n.h>
41 #include <pthread.h> /* used by pthread_create() and alarmThread */
42 #include <semaphore.h> /* used by update() and alarmProcess() for alarms */
43 #include <stdlib.h>
44 #include <string.h>
45
46 #include "dbg.h"
47 #include "batt_sys.h"
48 #include "misc.h" /* used for the line struct */
49 #include "panel.h" /* used to determine panel orientation */
50 #include "plugin.h"
51
52 /* The last MAX_SAMPLES samples are averaged when charge rates are evaluated.
53 This helps prevent spikes in the "time left" values the user sees. */
54 #define MAX_SAMPLES 10
55
56 typedef struct {
57 char *alarmCommand,
58 *backgroundColor,
59 *chargingColor1,
60 *chargingColor2,
61 *dischargingColor1,
62 *dischargingColor2;
63 GdkColor background,
64 charging1,
65 charging2,
66 discharging1,
67 discharging2;
68 cairo_surface_t *pixmap;
69 GtkWidget *drawingArea;
70 int orientation;
71 unsigned int alarmTime,
72 border,
73 height,
74 length,
75 numSamples,
76 requestedBorder,
77 *rateSamples,
78 rateSamplesSum,
79 thickness,
80 timer,
81 state_elapsed_time,
82 info_elapsed_time,
83 wasCharging,
84 width,
85 hide_if_no_battery;
86 sem_t alarmProcessLock;
87 battery* b;
88 gboolean has_ac_adapter;
89 } lx_battery;
90
91
92 typedef struct {
93 char *command;
94 sem_t *lock;
95 } Alarm;
96
97 static void destructor(Plugin *p);
98 static void update_display(lx_battery *lx_b, gboolean repaint);
99
100 /* alarmProcess takes the address of a dynamically allocated alarm struct (which
101 it must free). It ensures that alarm commands do not run concurrently. */
102 static void * alarmProcess(void *arg) {
103 Alarm *a = (Alarm *) arg;
104
105 sem_wait(a->lock);
106 system(a->command);
107 sem_post(a->lock);
108
109 g_free(a);
110 return NULL;
111 }
112
113
114 /* FIXME:
115 Don't repaint if percentage of remaining charge and remaining time aren't changed. */
116 void update_display(lx_battery *lx_b, gboolean repaint) {
117 cairo_t *cr;
118 char tooltip[ 256 ];
119 battery *b = lx_b->b;
120 /* unit: mW */
121 int rate;
122 gboolean isCharging;
123
124 if (! lx_b->pixmap )
125 return;
126
127 cr = cairo_create(lx_b->pixmap);
128 cairo_set_line_width (cr, 1.0);
129
130 /* no battery is found */
131 if( b == NULL )
132 {
133 gtk_widget_set_tooltip_text( lx_b->drawingArea, _("No batteries found") );
134 return;
135 }
136
137 /* draw background */
138 gdk_cairo_set_source_color(cr, &lx_b->background);
139 cairo_rectangle(cr, 0, 0, lx_b->width, lx_b->height);
140 cairo_fill(cr);
141
142 /* fixme: only one battery supported */
143
144 rate = lx_b->b->current_now;
145 isCharging = battery_is_charging ( b );
146
147 /* Consider running the alarm command */
148 if ( !isCharging && rate > 0 &&
149 ( ( battery_get_remaining( b ) / 60 ) < lx_b->alarmTime ) )
150 {
151 /* Shrug this should be done using glibs process functions */
152 /* Alarms should not run concurrently; determine whether an alarm is
153 already running */
154 int alarmCanRun;
155 sem_getvalue(&(lx_b->alarmProcessLock), &alarmCanRun);
156
157 /* Run the alarm command if it isn't already running */
158 if (alarmCanRun) {
159
160 Alarm *a = (Alarm *) malloc(sizeof(Alarm));
161 a->command = lx_b->alarmCommand;
162 a->lock = &(lx_b->alarmProcessLock);
163
164 /* Manage the alarm process in a new thread, which which will be
165 responsible for freeing the alarm struct it's given */
166 pthread_t alarmThread;
167 pthread_create(&alarmThread, NULL, alarmProcess, a);
168
169 }
170 }
171
172 /* Make a tooltip string, and display remaining charge time if the battery
173 is charging or remaining life if it's discharging */
174 if (isCharging) {
175 int hours = lx_b->b->seconds / 3600;
176 int left_seconds = b->seconds - 3600 * hours;
177 int minutes = left_seconds / 60;
178 snprintf(tooltip, 256,
179 _("Battery: %d%% charged, %d:%02d until full"),
180 lx_b->b->percentage,
181 hours,
182 minutes );
183 } else {
184 /* if we have enough rate information for battery */
185 if (lx_b->b->percentage != 100) {
186 int hours = lx_b->b->seconds / 3600;
187 int left_seconds = b->seconds - 3600 * hours;
188 int minutes = left_seconds / 60;
189 snprintf(tooltip, 256,
190 _("Battery: %d%% charged, %d:%02d left"),
191 lx_b->b->percentage,
192 hours,
193 minutes );
194 } else {
195 snprintf(tooltip, 256,
196 _("Battery: %d%% charged"),
197 100 );
198 }
199 }
200
201 gtk_widget_set_tooltip_text(lx_b->drawingArea, tooltip);
202
203 int chargeLevel = lx_b->b->percentage * (lx_b->length - 2 * lx_b->border) / 100;
204
205 if (lx_b->orientation == ORIENT_HORIZ) {
206
207 /* Draw the battery bar vertically, using color 1 for the left half and
208 color 2 for the right half */
209 gdk_cairo_set_source_color(cr,
210 isCharging ? &lx_b->charging1 : &lx_b->discharging1);
211 cairo_rectangle(cr, lx_b->border,
212 lx_b->height - lx_b->border - chargeLevel, lx_b->width / 2
213 - lx_b->border, chargeLevel);
214 cairo_fill(cr);
215 gdk_cairo_set_source_color(cr,
216 isCharging ? &lx_b->charging2 : &lx_b->discharging2);
217 cairo_rectangle(cr, lx_b->width / 2,
218 lx_b->height - lx_b->border - chargeLevel, (lx_b->width + 1) / 2
219 - lx_b->border, chargeLevel);
220 cairo_fill(cr);
221
222 }
223 else {
224
225 /* Draw the battery bar horizontally, using color 1 for the top half and
226 color 2 for the bottom half */
227 gdk_cairo_set_source_color(cr,
228 isCharging ? &lx_b->charging1 : &lx_b->discharging1);
229 cairo_rectangle(cr, lx_b->border,
230 lx_b->border, chargeLevel, lx_b->height / 2 - lx_b->border);
231 cairo_fill(cr);
232 gdk_cairo_set_source_color(cr,
233 isCharging ? &lx_b->charging2 : &lx_b->discharging2);
234 cairo_rectangle(cr, lx_b->border, (lx_b->height + 1)
235 / 2, chargeLevel, lx_b->height / 2 - lx_b->border);
236 cairo_fill(cr);
237
238 }
239 if( repaint )
240 gtk_widget_queue_draw( lx_b->drawingArea );
241
242 check_cairo_status(cr);
243 cairo_destroy(cr);
244 }
245
246 /* This callback is called every 3 seconds */
247 static int update_timout(lx_battery *lx_b) {
248 GDK_THREADS_ENTER();
249 lx_b->state_elapsed_time++;
250 lx_b->info_elapsed_time++;
251
252 /* check the batteries every 3 seconds */
253 battery_update( lx_b->b );
254
255 update_display( lx_b, TRUE );
256
257 GDK_THREADS_LEAVE();
258 return TRUE;
259 }
260
261 /* An update will be performed whenever the user clicks on the charge bar */
262 static gint buttonPressEvent(GtkWidget *widget, GdkEventButton *event,
263 Plugin* plugin) {
264
265 lx_battery *lx_b = (lx_battery*)plugin->priv;
266
267 update_display(lx_b, TRUE);
268
269 if( event->button == 3 ) /* right button */
270 {
271 GtkMenu* popup = lxpanel_get_panel_menu( plugin->panel, plugin, FALSE );
272 gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
273 return TRUE;
274 }
275 return FALSE;
276 }
277
278
279 static gint configureEvent(GtkWidget *widget, GdkEventConfigure *event,
280 lx_battery *lx_b) {
281
282 ENTER;
283
284 if (lx_b->pixmap)
285 cairo_surface_destroy(lx_b->pixmap);
286
287 /* Update the plugin's dimensions */
288 lx_b->width = widget->allocation.width;
289 lx_b->height = widget->allocation.height;
290 if (lx_b->orientation == ORIENT_HORIZ) {
291 lx_b->length = lx_b->height;
292 lx_b->thickness = lx_b->width;
293 }
294 else {
295 lx_b->length = lx_b->width;
296 lx_b->thickness = lx_b->height;
297 }
298
299 lx_b->pixmap = cairo_image_surface_create (CAIRO_FORMAT_RGB24, widget->allocation.width,
300 widget->allocation.height);
301 check_cairo_surface_status(&lx_b->pixmap);
302
303 /* Perform an update so the bar will look right in its new orientation */
304 update_display(lx_b, FALSE);
305
306 RET(TRUE);
307
308 }
309
310
311 static gint exposeEvent(GtkWidget *widget, GdkEventExpose *event, lx_battery *lx_b) {
312
313 ENTER;
314 cairo_t *cr = gdk_cairo_create(widget->window);
315 gdk_cairo_region(cr, event->region);
316 cairo_clip(cr);
317
318 gdk_cairo_set_source_color(cr, &lx_b->drawingArea->style->black);
319 cairo_set_source_surface(cr, lx_b->pixmap, 0, 0);
320 cairo_paint(cr);
321
322 check_cairo_status(cr);
323 cairo_destroy(cr);
324
325 RET(FALSE);
326 }
327
328
329 static int
330 constructor(Plugin *p, char **fp)
331 {
332 ENTER;
333
334 lx_battery *lx_b;
335 p->priv = lx_b = g_new0(lx_battery, 1);
336
337 /* get available battery */
338 lx_b->b = battery_get ();
339
340 /* no battery available */
341 if ( lx_b->b == NULL )
342 goto error;
343
344 p->pwid = gtk_event_box_new();
345 GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
346 gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 1 );
347
348 lx_b->drawingArea = gtk_drawing_area_new();
349 gtk_widget_add_events( lx_b->drawingArea, GDK_BUTTON_PRESS_MASK );
350
351 gtk_container_add( (GtkContainer*)p->pwid, lx_b->drawingArea );
352
353 if ((lx_b->orientation = p->panel->orientation) == ORIENT_HORIZ) {
354 lx_b->height = lx_b->length = 20;
355 lx_b->thickness = lx_b->width = 8;
356 }
357 else {
358 lx_b->height = lx_b->thickness = 8;
359 lx_b->length = lx_b->width = 20;
360 }
361 gtk_widget_set_size_request(lx_b->drawingArea, lx_b->width, lx_b->height);
362
363 gtk_widget_show(lx_b->drawingArea);
364
365 g_signal_connect (G_OBJECT (lx_b->drawingArea), "button_press_event",
366 G_CALLBACK(buttonPressEvent), (gpointer) p);
367 g_signal_connect (G_OBJECT (lx_b->drawingArea),"configure_event",
368 G_CALLBACK (configureEvent), (gpointer) lx_b);
369 g_signal_connect (G_OBJECT (lx_b->drawingArea), "expose_event",
370 G_CALLBACK (exposeEvent), (gpointer) lx_b);
371
372 sem_init(&(lx_b->alarmProcessLock), 0, 1);
373
374 lx_b->alarmCommand = lx_b->backgroundColor = lx_b->chargingColor1 = lx_b->chargingColor2
375 = lx_b->dischargingColor1 = lx_b->dischargingColor2 = NULL;
376
377 /* Set default values for integers */
378 lx_b->alarmTime = 5;
379 lx_b->requestedBorder = 1;
380
381 line s;
382 s.len = 256;
383
384 if (fp) {
385
386 /* Apply options */
387 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
388 if (s.type == LINE_NONE) {
389 ERR( "batt: illegal token %s\n", s.str);
390 goto error;
391 }
392 if (s.type == LINE_VAR) {
393 if (!g_ascii_strcasecmp(s.t[0], "HideIfNoBattery"))
394 lx_b->hide_if_no_battery = atoi(s.t[1]);
395 else if (!g_ascii_strcasecmp(s.t[0], "AlarmCommand"))
396 lx_b->alarmCommand = g_strdup(s.t[1]);
397 else if (!g_ascii_strcasecmp(s.t[0], "BackgroundColor"))
398 lx_b->backgroundColor = g_strdup(s.t[1]);
399 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor1"))
400 lx_b->chargingColor1 = g_strdup(s.t[1]);
401 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor2"))
402 lx_b->chargingColor2 = g_strdup(s.t[1]);
403 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor1"))
404 lx_b->dischargingColor1 = g_strdup(s.t[1]);
405 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor2"))
406 lx_b->dischargingColor2 = g_strdup(s.t[1]);
407 else if (!g_ascii_strcasecmp(s.t[0], "AlarmTime"))
408 lx_b->alarmTime = atoi(s.t[1]);
409 else if (!g_ascii_strcasecmp(s.t[0], "BorderWidth"))
410 lx_b->requestedBorder = atoi(s.t[1]);
411 else if (!g_ascii_strcasecmp(s.t[0], "Size")) {
412 lx_b->thickness = MAX(1, atoi(s.t[1]));
413 if (lx_b->orientation == ORIENT_HORIZ)
414 lx_b->width = lx_b->thickness;
415 else
416 lx_b->height = lx_b->thickness;
417 gtk_widget_set_size_request(lx_b->drawingArea, lx_b->width,
418 lx_b->height);
419 }
420 else {
421 ERR( "batt: unknown var %s\n", s.t[0]);
422 continue;
423 }
424 }
425 else {
426 ERR( "batt: illegal in this context %s\n", s.str);
427 goto error;
428 }
429 }
430
431 }
432
433 /* Make sure the border value is acceptable */
434 lx_b->border = MIN(MAX(0, lx_b->requestedBorder),
435 (MIN(lx_b->length, lx_b->thickness) - 1) / 2);
436
437 /* Apply more default options */
438 if (! lx_b->alarmCommand)
439 lx_b->alarmCommand = g_strdup("xmessage Battery low");
440 if (! lx_b->backgroundColor)
441 lx_b->backgroundColor = g_strdup("black");
442 if (! lx_b->chargingColor1)
443 lx_b->chargingColor1 = g_strdup("#28f200");
444 if (! lx_b->chargingColor2)
445 lx_b->chargingColor2 = g_strdup("#22cc00");
446 if (! lx_b->dischargingColor1)
447 lx_b->dischargingColor1 = g_strdup("#ffee00");
448 if (! lx_b->dischargingColor2)
449 lx_b->dischargingColor2 = g_strdup("#d9ca00");
450
451 gdk_color_parse(lx_b->backgroundColor, &lx_b->background);
452 gdk_color_parse(lx_b->chargingColor1, &lx_b->charging1);
453 gdk_color_parse(lx_b->chargingColor2, &lx_b->charging2);
454 gdk_color_parse(lx_b->dischargingColor1, &lx_b->discharging1);
455 gdk_color_parse(lx_b->dischargingColor2, &lx_b->discharging2);
456
457
458 /* Start the update loop */
459 lx_b->timer = g_timeout_add_seconds( 9, (GSourceFunc) update_timout, (gpointer) lx_b);
460
461 RET(TRUE);
462
463 error:
464 RET(FALSE);
465 }
466
467
468 static void
469 destructor(Plugin *p)
470 {
471 ENTER;
472
473 lx_battery *b = (lx_battery *) p->priv;
474
475 if (b->pixmap)
476 cairo_surface_destroy(b->pixmap);
477
478 g_free(b->alarmCommand);
479 g_free(b->backgroundColor);
480 g_free(b->chargingColor1);
481 g_free(b->chargingColor2);
482 g_free(b->dischargingColor1);
483 g_free(b->dischargingColor2);
484
485 g_free(b->rateSamples);
486 sem_destroy(&(b->alarmProcessLock));
487 if (b->timer)
488 g_source_remove(b->timer);
489 g_free(b);
490
491 RET();
492
493 }
494
495
496 static void orientation(Plugin *p) {
497
498 ENTER;
499
500 lx_battery *b = (lx_battery *) p->priv;
501
502 if (b->orientation != p->panel->orientation) {
503 b->orientation = p->panel->orientation;
504 unsigned int swap = b->height;
505 b->height = b->width;
506 b->width = swap;
507 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
508 }
509
510 RET();
511 }
512
513
514 static void applyConfig(Plugin* p)
515 {
516 ENTER;
517
518 lx_battery *b = (lx_battery *) p->priv;
519
520 /* Update colors */
521 if (b->backgroundColor &&
522 gdk_color_parse(b->backgroundColor, &b->background));
523 if (b->chargingColor1 && gdk_color_parse(b->chargingColor1, &b->charging1));
524 if (b->chargingColor2 && gdk_color_parse(b->chargingColor2, &b->charging2));
525 if (b->dischargingColor1 &&
526 gdk_color_parse(b->dischargingColor1, &b->discharging1));
527 if (b->dischargingColor2 &&
528 gdk_color_parse(b->dischargingColor2, &b->discharging2));
529
530 /* Make sure the border value is acceptable */
531 b->border = MIN(MAX(0, b->requestedBorder),
532 (MIN(b->length, b->thickness) - 1) / 2);
533
534 /* Resize the widget */
535 if (b->orientation == ORIENT_HORIZ)
536 b->width = b->thickness;
537 else
538 b->height = b->thickness;
539 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
540
541 RET();
542 }
543
544
545 static void config(Plugin *p, GtkWindow* parent) {
546 ENTER;
547
548 GtkWidget *dialog;
549 lx_battery *b = (lx_battery *) p->priv;
550 dialog = create_generic_config_dlg(_(p->class->name),
551 GTK_WIDGET(parent),
552 (GSourceFunc) applyConfig, (gpointer) p,
553 #if 0
554 _("Hide if there is no battery"), &b->hide_if_no_battery, CONF_TYPE_BOOL,
555 #endif
556 _("Alarm command"), &b->alarmCommand, CONF_TYPE_STR,
557 _("Alarm time (minutes left)"), &b->alarmTime, CONF_TYPE_INT,
558 _("Background color"), &b->backgroundColor, CONF_TYPE_STR,
559 _("Charging color 1"), &b->chargingColor1, CONF_TYPE_STR,
560 _("Charging color 2"), &b->chargingColor2, CONF_TYPE_STR,
561 _("Discharging color 1"), &b->dischargingColor1, CONF_TYPE_STR,
562 _("Discharging color 2"), &b->dischargingColor2, CONF_TYPE_STR,
563 _("Border width"), &b->requestedBorder, CONF_TYPE_INT,
564 _("Size"), &b->thickness, CONF_TYPE_INT,
565 NULL);
566 gtk_window_present(GTK_WINDOW(dialog));
567
568 RET();
569 }
570
571
572 static void save(Plugin* p, FILE* fp) {
573 lx_battery *lx_b = (lx_battery *) p->priv;
574
575 lxpanel_put_bool(fp, "HideIfNoBattery",lx_b->hide_if_no_battery);
576 lxpanel_put_str(fp, "AlarmCommand", lx_b->alarmCommand);
577 lxpanel_put_int(fp, "AlarmTime", lx_b->alarmTime);
578 lxpanel_put_str(fp, "BackgroundColor", lx_b->backgroundColor);
579 lxpanel_put_int(fp, "BorderWidth", lx_b->requestedBorder);
580 lxpanel_put_str(fp, "ChargingColor1", lx_b->chargingColor1);
581 lxpanel_put_str(fp, "ChargingColor2", lx_b->chargingColor2);
582 lxpanel_put_str(fp, "DischargingColor1", lx_b->dischargingColor1);
583 lxpanel_put_str(fp, "DischargingColor2", lx_b->dischargingColor2);
584 lxpanel_put_int(fp, "Size", lx_b->thickness);
585 }
586
587
588 PluginClass batt_plugin_class = {
589
590 PLUGINCLASS_VERSIONING,
591
592 type : "batt",
593 name : N_("Battery Monitor"),
594 version : "2.0",
595 description : N_("Display battery status using ACPI"),
596
597 constructor : constructor,
598 destructor : destructor,
599 config : config,
600 save : save,
601 panel_configuration_changed : orientation
602 };