Merging upstream version 0.5.9.
[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 GdkGC *bg,
69 *gc1,
70 *gc2;
71 GdkPixmap *pixmap;
72 GtkWidget *drawingArea;
73 int orientation;
74 unsigned int alarmTime,
75 border,
76 height,
77 length,
78 numSamples,
79 requestedBorder,
80 *rateSamples,
81 rateSamplesSum,
82 thickness,
83 timer,
84 state_elapsed_time,
85 info_elapsed_time,
86 wasCharging,
87 width,
88 hide_if_no_battery;
89 sem_t alarmProcessLock;
90 battery* b;
91 gboolean has_ac_adapter;
92 } lx_battery;
93
94
95 typedef struct {
96 char *command;
97 sem_t *lock;
98 } Alarm;
99
100 static void destructor(Plugin *p);
101 static void update_display(lx_battery *lx_b, gboolean repaint);
102
103 /* alarmProcess takes the address of a dynamically allocated alarm struct (which
104 it must free). It ensures that alarm commands do not run concurrently. */
105 static void * alarmProcess(void *arg) {
106 Alarm *a = (Alarm *) arg;
107
108 sem_wait(a->lock);
109 system(a->command);
110 sem_post(a->lock);
111
112 g_free(a);
113 return NULL;
114 }
115
116
117 /* FIXME:
118 Don't repaint if percentage of remaining charge and remaining time aren't changed. */
119 void update_display(lx_battery *lx_b, gboolean repaint) {
120 char tooltip[ 256 ];
121 battery *b = lx_b->b;
122 /* unit: mW */
123 int rate;
124 gboolean isCharging;
125
126 if (! lx_b->pixmap )
127 return;
128
129 /* no battery is found */
130 if( b == NULL )
131 {
132 gtk_widget_set_tooltip_text( lx_b->drawingArea, _("No batteries found") );
133 return;
134 }
135
136 /* draw background */
137 gdk_draw_rectangle(lx_b->pixmap, lx_b->bg, TRUE, 0, 0, lx_b->width, lx_b->height);
138
139 /* fixme: only one battery supported */
140
141 rate = lx_b->b->current_now;
142 isCharging = battery_is_charging ( b );
143
144 /* Consider running the alarm command */
145 if ( !isCharging && rate > 0 &&
146 ( ( battery_get_remaining( b ) / 60 ) < lx_b->alarmTime ) )
147 {
148 /* Shrug this should be done using glibs process functions */
149 /* Alarms should not run concurrently; determine whether an alarm is
150 already running */
151 int alarmCanRun;
152 sem_getvalue(&(lx_b->alarmProcessLock), &alarmCanRun);
153
154 /* Run the alarm command if it isn't already running */
155 if (alarmCanRun) {
156
157 Alarm *a = (Alarm *) malloc(sizeof(Alarm));
158 a->command = lx_b->alarmCommand;
159 a->lock = &(lx_b->alarmProcessLock);
160
161 /* Manage the alarm process in a new thread, which which will be
162 responsible for freeing the alarm struct it's given */
163 pthread_t alarmThread;
164 pthread_create(&alarmThread, NULL, alarmProcess, a);
165
166 }
167 }
168
169 /* Make a tooltip string, and display remaining charge time if the battery
170 is charging or remaining life if it's discharging */
171 if (isCharging) {
172 int hours = lx_b->b->seconds / 3600;
173 int left_seconds = b->seconds - 3600 * hours;
174 int minutes = left_seconds / 60;
175 snprintf(tooltip, 256,
176 _("Battery: %d%% charged, %d:%02d until full"),
177 lx_b->b->percentage,
178 hours,
179 minutes );
180 } else {
181 /* if we have enough rate information for battery */
182 if (lx_b->b->percentage != 100) {
183 int hours = lx_b->b->seconds / 3600;
184 int left_seconds = b->seconds - 3600 * hours;
185 int minutes = left_seconds / 60;
186 snprintf(tooltip, 256,
187 _("Battery: %d%% charged, %d:%02d left"),
188 lx_b->b->percentage,
189 hours,
190 minutes );
191 } else {
192 snprintf(tooltip, 256,
193 _("Battery: %d%% charged"),
194 100 );
195 }
196 }
197
198 gtk_widget_set_tooltip_text(lx_b->drawingArea, tooltip);
199
200 int chargeLevel = lx_b->b->percentage * (lx_b->length - 2 * lx_b->border) / 100;
201
202 /* Choose the right colors for the charge bar */
203 if (isCharging) {
204 gdk_gc_set_foreground(lx_b->gc1, &lx_b->charging1);
205 gdk_gc_set_foreground(lx_b->gc2, &lx_b->charging2);
206 }
207 else {
208 gdk_gc_set_foreground(lx_b->gc1, &lx_b->discharging1);
209 gdk_gc_set_foreground(lx_b->gc2, &lx_b->discharging2);
210 }
211
212 gdk_draw_rectangle(lx_b->pixmap, lx_b->bg, TRUE, 0, 0, lx_b->width, lx_b->height);
213
214 if (lx_b->orientation == ORIENT_HORIZ) {
215
216 /* Draw the battery bar vertically, using color 1 for the left half and
217 color 2 for the right half */
218 gdk_draw_rectangle(lx_b->pixmap, lx_b->gc1, TRUE, lx_b->border,
219 lx_b->height - lx_b->border - chargeLevel, lx_b->width / 2
220 - lx_b->border, chargeLevel);
221 gdk_draw_rectangle(lx_b->pixmap, lx_b->gc2, TRUE, lx_b->width / 2,
222 lx_b->height - lx_b->border - chargeLevel, (lx_b->width + 1) / 2
223 - lx_b->border, chargeLevel);
224
225 }
226 else {
227
228 /* Draw the battery bar horizontally, using color 1 for the top half and
229 color 2 for the bottom half */
230 gdk_draw_rectangle(lx_b->pixmap, lx_b->gc1, TRUE, lx_b->border,
231 lx_b->border, chargeLevel, lx_b->height / 2 - lx_b->border);
232 gdk_draw_rectangle(lx_b->pixmap, lx_b->gc2, TRUE, lx_b->border, (lx_b->height + 1)
233 / 2, chargeLevel, lx_b->height / 2 - lx_b->border);
234
235 }
236 if( repaint )
237 gtk_widget_queue_draw( lx_b->drawingArea );
238 }
239
240 /* This callback is called every 3 seconds */
241 static int update_timout(lx_battery *lx_b) {
242 GDK_THREADS_ENTER();
243 lx_b->state_elapsed_time++;
244 lx_b->info_elapsed_time++;
245
246 /* check the batteries every 3 seconds */
247 battery_update( lx_b->b );
248
249 update_display( lx_b, TRUE );
250
251 GDK_THREADS_LEAVE();
252 return TRUE;
253 }
254
255 /* An update will be performed whenever the user clicks on the charge bar */
256 static gint buttonPressEvent(GtkWidget *widget, GdkEventButton *event,
257 Plugin* plugin) {
258
259 lx_battery *lx_b = (lx_battery*)plugin->priv;
260
261 update_display(lx_b, TRUE);
262
263 if( event->button == 3 ) /* right button */
264 {
265 GtkMenu* popup = lxpanel_get_panel_menu( plugin->panel, plugin, FALSE );
266 gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
267 return TRUE;
268 }
269 return FALSE;
270 }
271
272
273 static gint configureEvent(GtkWidget *widget, GdkEventConfigure *event,
274 lx_battery *lx_b) {
275
276 ENTER;
277
278 if (lx_b->pixmap)
279 g_object_unref(lx_b->pixmap);
280
281 /* Update the plugin's dimensions */
282 lx_b->width = widget->allocation.width;
283 lx_b->height = widget->allocation.height;
284 if (lx_b->orientation == ORIENT_HORIZ) {
285 lx_b->length = lx_b->height;
286 lx_b->thickness = lx_b->width;
287 }
288 else {
289 lx_b->length = lx_b->width;
290 lx_b->thickness = lx_b->height;
291 }
292
293 lx_b->pixmap = gdk_pixmap_new (widget->window, widget->allocation.width,
294 widget->allocation.height, -1);
295
296 /* Perform an update so the bar will look right in its new orientation */
297 update_display(lx_b, FALSE);
298
299 RET(TRUE);
300
301 }
302
303
304 static gint exposeEvent(GtkWidget *widget, GdkEventExpose *event, lx_battery *lx_b) {
305
306 ENTER;
307
308 gdk_draw_drawable (widget->window, lx_b->drawingArea->style->black_gc,
309 lx_b->pixmap, event->area.x, event->area.y, event->area.x,
310 event->area.y, event->area.width, event->area.height);
311
312 RET(FALSE);
313
314 }
315
316
317 static int
318 constructor(Plugin *p, char **fp)
319 {
320 ENTER;
321
322 lx_battery *lx_b;
323 p->priv = lx_b = g_new0(lx_battery, 1);
324
325 /* get available battery */
326 lx_b->b = battery_get ();
327
328 /* no battery available */
329 if ( lx_b->b == NULL )
330 goto error;
331
332 p->pwid = gtk_event_box_new();
333 GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
334 gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 1 );
335
336 lx_b->drawingArea = gtk_drawing_area_new();
337 gtk_widget_add_events( lx_b->drawingArea, GDK_BUTTON_PRESS_MASK );
338
339 gtk_container_add( (GtkContainer*)p->pwid, lx_b->drawingArea );
340
341 if ((lx_b->orientation = p->panel->orientation) == ORIENT_HORIZ) {
342 lx_b->height = lx_b->length = 20;
343 lx_b->thickness = lx_b->width = 8;
344 }
345 else {
346 lx_b->height = lx_b->thickness = 8;
347 lx_b->length = lx_b->width = 20;
348 }
349 gtk_widget_set_size_request(lx_b->drawingArea, lx_b->width, lx_b->height);
350
351 gtk_widget_show(lx_b->drawingArea);
352
353 lx_b->bg = gdk_gc_new(p->panel->topgwin->window);
354 lx_b->gc1 = gdk_gc_new(p->panel->topgwin->window);
355 lx_b->gc2 = gdk_gc_new(p->panel->topgwin->window);
356
357 g_signal_connect (G_OBJECT (lx_b->drawingArea), "button_press_event",
358 G_CALLBACK(buttonPressEvent), (gpointer) p);
359 g_signal_connect (G_OBJECT (lx_b->drawingArea),"configure_event",
360 G_CALLBACK (configureEvent), (gpointer) lx_b);
361 g_signal_connect (G_OBJECT (lx_b->drawingArea), "expose_event",
362 G_CALLBACK (exposeEvent), (gpointer) lx_b);
363
364 sem_init(&(lx_b->alarmProcessLock), 0, 1);
365
366 lx_b->alarmCommand = lx_b->backgroundColor = lx_b->chargingColor1 = lx_b->chargingColor2
367 = lx_b->dischargingColor1 = lx_b->dischargingColor2 = NULL;
368
369 /* Set default values for integers */
370 lx_b->alarmTime = 5;
371 lx_b->requestedBorder = 1;
372
373 line s;
374 s.len = 256;
375
376 if (fp) {
377
378 /* Apply options */
379 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
380 if (s.type == LINE_NONE) {
381 ERR( "batt: illegal token %s\n", s.str);
382 goto error;
383 }
384 if (s.type == LINE_VAR) {
385 if (!g_ascii_strcasecmp(s.t[0], "HideIfNoBattery"))
386 lx_b->hide_if_no_battery = atoi(s.t[1]);
387 else if (!g_ascii_strcasecmp(s.t[0], "AlarmCommand"))
388 lx_b->alarmCommand = g_strdup(s.t[1]);
389 else if (!g_ascii_strcasecmp(s.t[0], "BackgroundColor"))
390 lx_b->backgroundColor = g_strdup(s.t[1]);
391 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor1"))
392 lx_b->chargingColor1 = g_strdup(s.t[1]);
393 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor2"))
394 lx_b->chargingColor2 = g_strdup(s.t[1]);
395 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor1"))
396 lx_b->dischargingColor1 = g_strdup(s.t[1]);
397 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor2"))
398 lx_b->dischargingColor2 = g_strdup(s.t[1]);
399 else if (!g_ascii_strcasecmp(s.t[0], "AlarmTime"))
400 lx_b->alarmTime = atoi(s.t[1]);
401 else if (!g_ascii_strcasecmp(s.t[0], "BorderWidth"))
402 lx_b->requestedBorder = atoi(s.t[1]);
403 else if (!g_ascii_strcasecmp(s.t[0], "Size")) {
404 lx_b->thickness = MAX(1, atoi(s.t[1]));
405 if (lx_b->orientation == ORIENT_HORIZ)
406 lx_b->width = lx_b->thickness;
407 else
408 lx_b->height = lx_b->thickness;
409 gtk_widget_set_size_request(lx_b->drawingArea, lx_b->width,
410 lx_b->height);
411 }
412 else {
413 ERR( "batt: unknown var %s\n", s.t[0]);
414 continue;
415 }
416 }
417 else {
418 ERR( "batt: illegal in this context %s\n", s.str);
419 goto error;
420 }
421 }
422
423 }
424
425 /* Make sure the border value is acceptable */
426 lx_b->border = MIN(MAX(0, lx_b->requestedBorder),
427 (MIN(lx_b->length, lx_b->thickness) - 1) / 2);
428
429 /* Apply more default options */
430 if (! lx_b->alarmCommand)
431 lx_b->alarmCommand = g_strdup("xmessage Battery low");
432 if (! lx_b->backgroundColor)
433 lx_b->backgroundColor = g_strdup("black");
434 if (! lx_b->chargingColor1)
435 lx_b->chargingColor1 = g_strdup("#28f200");
436 if (! lx_b->chargingColor2)
437 lx_b->chargingColor2 = g_strdup("#22cc00");
438 if (! lx_b->dischargingColor1)
439 lx_b->dischargingColor1 = g_strdup("#ffee00");
440 if (! lx_b->dischargingColor2)
441 lx_b->dischargingColor2 = g_strdup("#d9ca00");
442
443 gdk_color_parse(lx_b->backgroundColor, &lx_b->background);
444 gdk_color_parse(lx_b->chargingColor1, &lx_b->charging1);
445 gdk_color_parse(lx_b->chargingColor2, &lx_b->charging2);
446 gdk_color_parse(lx_b->dischargingColor1, &lx_b->discharging1);
447 gdk_color_parse(lx_b->dischargingColor2, &lx_b->discharging2);
448 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
449 p->panel->topgwin->window), &lx_b->background, FALSE, TRUE);
450 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
451 p->panel->topgwin->window), &lx_b->charging1, FALSE, TRUE);
452 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
453 p->panel->topgwin->window), &lx_b->charging2, FALSE, TRUE);
454 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
455 p->panel->topgwin->window), &lx_b->discharging1, FALSE, TRUE);
456 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
457 p->panel->topgwin->window), &lx_b->discharging2, FALSE, TRUE);
458 gdk_gc_set_foreground(lx_b->bg, &lx_b->background);
459
460
461 /* Start the update loop */
462 lx_b->timer = g_timeout_add_seconds( 3, (GSourceFunc) update_timout, (gpointer) lx_b);
463
464 RET(TRUE);
465
466 error:
467 RET(FALSE);
468 }
469
470
471 static void
472 destructor(Plugin *p)
473 {
474 ENTER;
475
476 lx_battery *b = (lx_battery *) p->priv;
477
478 if (b->pixmap)
479 g_object_unref(b->pixmap);
480
481 if (b->gc1)
482 g_object_unref(b->gc1);
483 if (b->gc2)
484 g_object_unref(b->gc2);
485 g_free(b->alarmCommand);
486 g_free(b->backgroundColor);
487 g_free(b->chargingColor1);
488 g_free(b->chargingColor2);
489 g_free(b->dischargingColor1);
490 g_free(b->dischargingColor2);
491
492 g_free(b->rateSamples);
493 sem_destroy(&(b->alarmProcessLock));
494 if (b->timer)
495 g_source_remove(b->timer);
496 g_free(b);
497
498 RET();
499
500 }
501
502
503 static void orientation(Plugin *p) {
504
505 ENTER;
506
507 lx_battery *b = (lx_battery *) p->priv;
508
509 if (b->orientation != p->panel->orientation) {
510 b->orientation = p->panel->orientation;
511 unsigned int swap = b->height;
512 b->height = b->width;
513 b->width = swap;
514 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
515 }
516
517 RET();
518 }
519
520
521 static void applyConfig(Plugin* p)
522 {
523 ENTER;
524
525 lx_battery *b = (lx_battery *) p->priv;
526
527 /* Update colors */
528 if (b->backgroundColor &&
529 gdk_color_parse(b->backgroundColor, &b->background)) {
530 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
531 p->panel->topgwin->window), &b->background, FALSE, TRUE);
532 gdk_gc_set_foreground(b->bg, &b->background);
533 }
534 if (b->chargingColor1 && gdk_color_parse(b->chargingColor1, &b->charging1))
535 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
536 p->panel->topgwin->window), &b->charging1, FALSE, TRUE);
537 if (b->chargingColor2 && gdk_color_parse(b->chargingColor2, &b->charging2))
538 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
539 p->panel->topgwin->window), &b->charging2, FALSE, TRUE);
540 if (b->dischargingColor1 &&
541 gdk_color_parse(b->dischargingColor1, &b->discharging1))
542 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
543 p->panel->topgwin->window), &b->discharging1, FALSE, TRUE);
544 if (b->dischargingColor2 &&
545 gdk_color_parse(b->dischargingColor2, &b->discharging2))
546 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
547 p->panel->topgwin->window), &b->discharging2, FALSE, TRUE);
548
549 /* Make sure the border value is acceptable */
550 b->border = MIN(MAX(0, b->requestedBorder),
551 (MIN(b->length, b->thickness) - 1) / 2);
552
553 /* Resize the widget */
554 if (b->orientation == ORIENT_HORIZ)
555 b->width = b->thickness;
556 else
557 b->height = b->thickness;
558 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
559
560 RET();
561 }
562
563
564 static void config(Plugin *p, GtkWindow* parent) {
565 ENTER;
566
567 GtkWidget *dialog;
568 lx_battery *b = (lx_battery *) p->priv;
569 dialog = create_generic_config_dlg(_(p->class->name),
570 GTK_WIDGET(parent),
571 (GSourceFunc) applyConfig, (gpointer) p,
572 #if 0
573 _("Hide if there is no battery"), &b->hide_if_no_battery, CONF_TYPE_BOOL,
574 #endif
575 _("Alarm command"), &b->alarmCommand, CONF_TYPE_STR,
576 _("Alarm time (minutes left)"), &b->alarmTime, CONF_TYPE_INT,
577 _("Background color"), &b->backgroundColor, CONF_TYPE_STR,
578 _("Charging color 1"), &b->chargingColor1, CONF_TYPE_STR,
579 _("Charging color 2"), &b->chargingColor2, CONF_TYPE_STR,
580 _("Discharging color 1"), &b->dischargingColor1, CONF_TYPE_STR,
581 _("Discharging color 2"), &b->dischargingColor2, CONF_TYPE_STR,
582 _("Border width"), &b->requestedBorder, CONF_TYPE_INT,
583 _("Size"), &b->thickness, CONF_TYPE_INT,
584 NULL);
585 gtk_window_present(GTK_WINDOW(dialog));
586
587 RET();
588 }
589
590
591 static void save(Plugin* p, FILE* fp) {
592 lx_battery *lx_b = (lx_battery *) p->priv;
593
594 lxpanel_put_bool(fp, "HideIfNoBattery",lx_b->hide_if_no_battery);
595 lxpanel_put_str(fp, "AlarmCommand", lx_b->alarmCommand);
596 lxpanel_put_int(fp, "AlarmTime", lx_b->alarmTime);
597 lxpanel_put_str(fp, "BackgroundColor", lx_b->backgroundColor);
598 lxpanel_put_int(fp, "BorderWidth", lx_b->requestedBorder);
599 lxpanel_put_str(fp, "ChargingColor1", lx_b->chargingColor1);
600 lxpanel_put_str(fp, "ChargingColor2", lx_b->chargingColor2);
601 lxpanel_put_str(fp, "DischargingColor1", lx_b->dischargingColor1);
602 lxpanel_put_str(fp, "DischargingColor2", lx_b->dischargingColor2);
603 lxpanel_put_int(fp, "Size", lx_b->thickness);
604 }
605
606
607 PluginClass batt_plugin_class = {
608
609 PLUGINCLASS_VERSIONING,
610
611 type : "batt",
612 name : N_("Battery Monitor"),
613 version : "2.0",
614 description : N_("Display battery status using ACPI"),
615
616 constructor : constructor,
617 destructor : destructor,
618 config : config,
619 save : save,
620 panel_configuration_changed : orientation
621 };