Rename panel, plugin, and plugin_class to Panel, Plugin, and
[lxde/lxpanel.git] / src / plugins / batt / batt.c
CommitLineData
8f11d5f8
HJYP
1/*
2 * ACPI battery monitor plugin for LXPanel
3 *
4 * Copyright (C) 2007 by Greg McNew <gmcnew@gmail.com>
802d5540 5 * Copyright (C) 2008 by Hong Jen Yee <pcman.tw@gmail.com>
8f11d5f8
HJYP
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 *
22 * This plugin monitors battery usage on ACPI-enabled systems by reading the
23 * battery information found in /proc/acpi/battery/. The update interval is
24 * user-configurable and defaults to 1 second.
25 *
26 * The battery's remaining life is estimated from its current charge and current
27 * rate of discharge. The user may configure an alarm command to be run when
28 * their estimated remaining battery life reaches a certain level.
29 */
30
e751fe05
HJYP
31/* FIXME:
32 * Here are somethings need to be improvec:
33 * 1. Replace pthread stuff with gthread counterparts for portability.
34 * 2. Check "/proc/acpi/ac_adapter" for AC power.
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>
8f11d5f8 40#include <glib/gi18n.h>
c0ed75bf 41#include <pthread.h> /* used by pthread_create() and alarmThread */
8f11d5f8
HJYP
42#include <semaphore.h> /* used by update() and alarmProcess() for alarms */
43#include <stdlib.h>
802d5540 44#include <string.h>
8f11d5f8
HJYP
45
46#include "dbg.h"
8f11d5f8
HJYP
47#include "misc.h" /* used for the line struct */
48#include "panel.h" /* used to determine panel orientation */
49#include "plugin.h"
802d5540 50#include "glib-mem.h" /* compatibility macros for g_slice* */
8f11d5f8
HJYP
51
52#define BATTERY_DIRECTORY "/proc/acpi/battery/" /* must be slash-terminated */
3051b49c 53#define AC_ADAPTER_STATE_FILE "/proc/acpi/ac_adapter/AC/state"
8f11d5f8 54
8f11d5f8
HJYP
55/* The last MAX_SAMPLES samples are averaged when charge rates are evaluated.
56 This helps prevent spikes in the "time left" values the user sees. */
57#define MAX_SAMPLES 10
58
802d5540
HJYP
59typedef struct {
60 char* name;
61 int capacity, /* unit: mWh */
62 charge, /* unit: mWh */
63 is_charging,
64 last_rate, /* unit: mW */
65 rate; /* unit: mW */
66}batt_info;
8f11d5f8
HJYP
67
68typedef struct {
69 char *alarmCommand,
70 *backgroundColor,
71 *chargingColor1,
72 *chargingColor2,
73 *dischargingColor1,
802d5540
HJYP
74 *dischargingColor2;
75 GdkColor background,
76 charging1,
77 charging2,
78 discharging1,
79 discharging2;
8f11d5f8
HJYP
80 GdkGC *bg,
81 *gc1,
82 *gc2;
83 GdkPixmap *pixmap;
84 GtkTooltips *tooltip;
802d5540 85 GtkWidget *drawingArea;
8f11d5f8
HJYP
86 int orientation;
87 unsigned int alarmTime,
88 border,
89 height,
90 length,
91 numSamples,
92 requestedBorder,
93 *rateSamples,
94 rateSamplesSum,
95 thickness,
96 timer,
802d5540
HJYP
97 state_elapsed_time,
98 info_elapsed_time,
8f11d5f8 99 wasCharging,
04a2f050
HJYP
100 width,
101 hide_if_no_battery;
8f11d5f8 102 sem_t alarmProcessLock;
802d5540 103 GList* batteries;
3051b49c 104 gboolean has_ac_adapter;
8f11d5f8
HJYP
105} batt;
106
107
108typedef struct {
109 char *command;
110 sem_t *lock;
111} alarm;
112
22242ed4 113static void destructor(Plugin *p);
3051b49c 114static void update_display(batt *b, gboolean repaint);
8f11d5f8 115
802d5540
HJYP
116static void batt_info_free( batt_info* bi )
117{
118 g_free( bi->name );
119 g_slice_free( batt_info, bi );
120}
8f11d5f8 121
802d5540
HJYP
122static gboolean get_batt_info( batt_info* bi )
123{
124 FILE *info;
125 char buf[ 256 ];
126
127 /* Open the info file */
128 g_snprintf(buf, 256, "%s%s/info", BATTERY_DIRECTORY, bi->name);
129 if ((info = fopen(buf, "r"))) {
130 /* Read the file until the battery's capacity is found or until
131 there are no more lines to be read */
132 while( fgets(buf, 256, info) &&
133 ! sscanf(buf, "last full capacity: %d",
134 &bi->capacity) );
135 fclose(info);
136 return TRUE;
8f11d5f8 137 }
802d5540 138 return FALSE;
8f11d5f8
HJYP
139}
140
802d5540
HJYP
141static gboolean get_batt_state( batt_info* bi )
142{
143 FILE *state;
144 char buf[ 512 ];
8f11d5f8 145
802d5540
HJYP
146 g_snprintf( buf, 512, "%s%s/state", BATTERY_DIRECTORY, bi->name );
147 if((state = fopen( buf, "r"))) {
148 char *pstr;
149 fread(buf, sizeof(buf), 1, state);
8f11d5f8 150
802d5540 151 char thisState = 'c';
8f11d5f8 152
802d5540
HJYP
153 /* Read the file until the battery's charging state is found or
154 until there are no more lines to be read */
155 if (pstr = strstr(buf, "charging state:"))
156 thisState = *(pstr + 25);
8f11d5f8 157
802d5540
HJYP
158 /* Read the file until the battery's charge/discharge rate is
159 found or until there are no more lines to be read */
160 if (pstr = strstr(buf, "present rate:")) {
161 pstr += 25;
162 sscanf (pstr, "%d",&bi->rate );
8f11d5f8 163
802d5540
HJYP
164 if( bi->rate < 0 )
165 bi->rate = 0;
8f11d5f8 166 }
e751fe05 167
802d5540
HJYP
168 /* Read the file until the battery's charge is found or until
169 there are no more lines to be read */
170 if (pstr = strstr (buf, "remaining capacity")) {
171 pstr += 25;
172 sscanf (pstr, "%d",&bi->charge);
173 }
8f11d5f8 174
802d5540
HJYP
175 /* thisState will be 'c' if the batter is charging and 'd'
176 otherwise */
177 bi->is_charging = !( thisState - 'c' );
8f11d5f8 178
802d5540
HJYP
179 fclose(state);
180 return TRUE;
181 }
182 return FALSE;
8f11d5f8
HJYP
183}
184
3051b49c
HJYP
185static gboolean check_ac_adapter( batt* b )
186{
187 FILE *state;
188 char buf[ 256 ];
189 char* pstr;
190
191 if ((state = fopen( AC_ADAPTER_STATE_FILE, "r"))) {
192 gboolean has_ac_adapter = FALSE;
193
194 while( fgets(buf, 256, state) &&
195 ! ( pstr = strstr(buf, "state:") ) );
196 if( pstr )
197 {
198 pstr += 6;
199 while( *pstr && *pstr == ' ' )
200 ++pstr;
201 if( pstr[0] == 'o' && pstr[1] == 'n' )
202 has_ac_adapter = TRUE;
203 }
204 fclose(state);
205
206 /* if the state of AC adapter changed, is_charging of the batteries might change, too. */
207 if( has_ac_adapter != b->has_ac_adapter )
208 {
209 /* g_debug( "ac_state_changed: %d", has_ac_adapter ); */
210 b->has_ac_adapter = has_ac_adapter;
211 /* update the state of all batteries */
212 g_list_foreach( b->batteries, (GFunc)get_batt_state, NULL );
213 update_display( b, TRUE );
214 }
215 return TRUE;
216 }
217 return FALSE;
218}
219
8f11d5f8
HJYP
220/* alarmProcess takes the address of a dynamically allocated alarm struct (which
221 it must free). It ensures that alarm commands do not run concurrently. */
c0ed75bf 222static void * alarmProcess(void *arg) {
8f11d5f8
HJYP
223 alarm *a = (alarm *) arg;
224
225 sem_wait(a->lock);
c0ed75bf 226 system(a->command);
8f11d5f8
HJYP
227 sem_post(a->lock);
228
229 g_free(a);
c0ed75bf 230 return NULL;
8f11d5f8
HJYP
231}
232
233
234/* addRate adds a charge/discharge rate to the array of samples and returns the
235 average of all the rates in the array */
236static int addRate(batt *b, int isCharging, int lastRate) {
237
238 /* Clear the rate samples array if the charge/discharge status has just
239 changed */
240 if (b->wasCharging != isCharging) {
241 b->wasCharging = isCharging;
242 b->numSamples = b->rateSamplesSum = 0;
243 }
244
245 /* The rateSamples array acts as a circular array-based queue with a fixed
246 size. If it is full, there's a meaningful value at index numSamples which
247 should be subtracted from the sum before the new value is added; if it
248 isn't full, the value at index numSamples does not need to be
249 considered. */
250 int currentIndex = b->numSamples % MAX_SAMPLES;
251 if (b->numSamples >= MAX_SAMPLES)
252 b->rateSamplesSum -= b->rateSamples[currentIndex];
253 b->rateSamples[currentIndex] = lastRate;
254 b->rateSamplesSum += lastRate;
255
256 /* Increment numSamples, but don't let it get too big. As long as it's
257 greater than MAX_SAMPLES, we'll know that the next sample will be
258 replacing an older one. */
259 if (++b->numSamples >= MAX_SAMPLES * 2)
260 b->numSamples -= MAX_SAMPLES;
261
262 RET(b->rateSamplesSum / MIN(b->numSamples, MAX_SAMPLES));
263
264}
265
802d5540
HJYP
266/* FIXME:
267 Don't repaint if percentage of remaining charge and remaining time aren't changed. */
3051b49c 268void update_display(batt *b, gboolean repaint) {
802d5540
HJYP
269 GList* l;
270 char tooltip[ 256 ];
8f11d5f8
HJYP
271
272 if (! b->pixmap)
802d5540 273 return;
8f11d5f8 274
802d5540
HJYP
275 int capacity = 0, /* unit: mWh */
276 charge = 0, /* unit: mWh */
277 isCharging = 0,
278 lastRate = 0, /* unit: mW */
279 rate = 0; /* unit: mW */
8f11d5f8 280
802d5540
HJYP
281 /* draw background */
282 gdk_draw_rectangle(b->pixmap, b->bg, TRUE, 0, 0, b->width, b->height);
8f11d5f8 283
802d5540
HJYP
284 if( b->batteries )
285 {
286 /* Calculate the total capacity, charge, and charge/discharge rate */
287 for( l = b->batteries; l; l = l->next )
288 {
289 batt_info* bi = (batt_info*)l->data;
290 capacity += bi->capacity;
291 charge += bi->charge;
292 lastRate += bi->rate;
293 if( bi->is_charging )
294 isCharging = TRUE;
295 }
8f11d5f8 296
802d5540
HJYP
297 /* Add the last rate to the array of recent samples and get the average
298 rate */
299 rate = addRate(b, isCharging, lastRate);
8f11d5f8 300
802d5540
HJYP
301 /* Consider running the alarm command */
302 if (! isCharging && rate && charge * 60 / rate <= b->alarmTime) {
8f11d5f8 303
802d5540
HJYP
304 /* Alarms should not run concurrently; determine whether an alarm is
305 already running */
306 int alarmCanRun;
307 sem_getvalue(&(b->alarmProcessLock), &alarmCanRun);
8f11d5f8 308
802d5540
HJYP
309 /* Run the alarm command if it isn't already running */
310 if (alarmCanRun) {
8f11d5f8 311
802d5540
HJYP
312 alarm *a = (alarm *) malloc(sizeof(alarm));
313 a->command = b->alarmCommand;
314 a->lock = &(b->alarmProcessLock);
8f11d5f8 315
802d5540
HJYP
316 /* Manage the alarm process in a new thread, which which will be
317 responsible for freeing the alarm struct it's given */
318 pthread_t alarmThread;
319 pthread_create(&alarmThread, NULL, alarmProcess, a);
8f11d5f8 320
802d5540
HJYP
321 }
322 }
8f11d5f8 323
802d5540
HJYP
324 /* Make a tooltip string, and display remaining charge time if the battery
325 is charging or remaining life if it's discharging */
326 if (isCharging) {
327
328 if (rate)
329 snprintf(tooltip, 256,
330 _("Battery: %d%% charged, %d:%02d until full"),
331 capacity ? charge * 100 / capacity : 0,
332 (capacity - charge) / rate,
333 ((capacity - charge) * 60 / rate) % 60);
334
335 /* A battery will sometimes have a charge rate of 0, even if it isn't
336 finished charging */
337 else
338 snprintf(tooltip, 256,
04a2f050 339 _("Battery: %d%% charged, %s"),
802d5540 340 capacity ? charge * 100 / capacity : 0,
04a2f050 341 (charge >= capacity) ? _("charging finished") : _("not charging") );
8f11d5f8
HJYP
342
343 }
802d5540
HJYP
344 else
345 snprintf(tooltip, 256,
346 _("Battery: %d%% charged, %d:%02d left"),
347 capacity ? charge * 100 / capacity : 0,
348 rate ? charge / rate : 0,
349 rate ? (charge * 60 / rate) % 60 : 0);
8f11d5f8 350
802d5540 351 gtk_tooltips_set_tip(b->tooltip, b->drawingArea, tooltip, NULL);
8f11d5f8 352
802d5540
HJYP
353 int chargeLevel = capacity ?
354 charge * (b->length - 2 * b->border) / capacity : 0;
8f11d5f8 355
802d5540
HJYP
356 /* Choose the right colors for the charge bar */
357 if (isCharging) {
358 gdk_gc_set_foreground(b->gc1, &b->charging1);
359 gdk_gc_set_foreground(b->gc2, &b->charging2);
360 }
361 else {
362 gdk_gc_set_foreground(b->gc1, &b->discharging1);
363 gdk_gc_set_foreground(b->gc2, &b->discharging2);
364 }
8f11d5f8 365
802d5540 366 gdk_draw_rectangle(b->pixmap, b->bg, TRUE, 0, 0, b->width, b->height);
8f11d5f8 367
802d5540 368 if (b->orientation == ORIENT_HORIZ) {
8f11d5f8 369
802d5540
HJYP
370 /* Draw the battery bar vertically, using color 1 for the left half and
371 color 2 for the right half */
372 gdk_draw_rectangle(b->pixmap, b->gc1, TRUE, b->border,
373 b->height - b->border - chargeLevel, b->width / 2
374 - b->border, chargeLevel);
375 gdk_draw_rectangle(b->pixmap, b->gc2, TRUE, b->width / 2,
376 b->height - b->border - chargeLevel, (b->width + 1) / 2
377 - b->border, chargeLevel);
8f11d5f8 378
802d5540
HJYP
379 }
380 else {
8f11d5f8 381
802d5540
HJYP
382 /* Draw the battery bar horizontally, using color 1 for the top half and
383 color 2 for the bottom half */
384 gdk_draw_rectangle(b->pixmap, b->gc1, TRUE, b->border,
385 b->border, chargeLevel, b->height / 2 - b->border);
386 gdk_draw_rectangle(b->pixmap, b->gc2, TRUE, b->border, (b->height + 1)
387 / 2, chargeLevel, b->height / 2 - b->border);
8f11d5f8 388
802d5540 389 }
8f11d5f8 390 }
802d5540
HJYP
391 else /* no battery is found */
392 {
393 char tip[ 256 ];
394 g_snprintf( tip, 256, _("No batteries found") );
395 gtk_tooltips_set_tip(b->tooltip, b->drawingArea, tip, NULL);
8f11d5f8
HJYP
396 }
397
802d5540
HJYP
398 if( repaint )
399 gtk_widget_queue_draw( b->drawingArea );
400}
8f11d5f8 401
802d5540
HJYP
402static void check_batteries( batt* b )
403{
404 GDir *batteryDirectory;
405 const char *battery_name;
406 GList* l;
407 gboolean need_update_display = FALSE;
8f11d5f8 408
802d5540
HJYP
409 if (! (batteryDirectory = g_dir_open(BATTERY_DIRECTORY, 0, NULL)))
410 {
411 g_list_foreach( b->batteries, (GFunc)batt_info_free, NULL );
412 g_list_free( b->batteries );
413 b->batteries = NULL;
414 return;
8f11d5f8 415 }
8f11d5f8 416
802d5540
HJYP
417 /* Remove dead entries */
418 for( l = b->batteries; l; )
419 {
420 GList* next = l->next;
421 batt_info* bi = (batt_info*)l->data;
422 char* path;
423 path = g_build_filename( BATTERY_DIRECTORY, bi->name, NULL );
424 if( ! g_file_test( path, G_FILE_TEST_EXISTS ) ) /* file no more exists */
425 {
426 b->batteries = g_list_remove_link( b->batteries, l ); /* remove from the list */
427 need_update_display = TRUE;
428 }
429 g_free( path );
430 l = next;
8f11d5f8
HJYP
431 }
432
802d5540
HJYP
433 /* Scan the battery directory for available batteries */
434 while ((battery_name = g_dir_read_name(batteryDirectory))) {
435 if (battery_name[0] != '.') {
436 /* find the battery in our list */
437 for( l = b->batteries; l; l = l->next )
438 {
439 batt_info* bi = (batt_info*)l->data;
440 if( 0 == strcmp( bi->name, battery_name ) )
441 break;
442 }
443 if( ! l ) /* not found, this is a new battery */
444 {
445 batt_info* bi = g_slice_new0( batt_info );
446 bi->name = g_strdup( battery_name );
447 /* get battery info & state for the newly added entry */
448 get_batt_info( bi );
449 get_batt_state( bi );
450 b->batteries = g_list_prepend( b->batteries, bi ); /* add to our list */
451 need_update_display = TRUE;
452 }
453 }
454 }
455 g_dir_close(batteryDirectory);
8f11d5f8 456
802d5540
HJYP
457 if( need_update_display )
458 update_display( b, TRUE );
8f11d5f8
HJYP
459}
460
71671bf6 461/* This callback is called every 3 seconds */
52cb5b58
HJYP
462static int update_timout(batt *b) {
463 GDK_THREADS_ENTER();
802d5540
HJYP
464 gboolean changed = FALSE;
465
802d5540
HJYP
466 ++b->state_elapsed_time;
467 ++b->info_elapsed_time;
3051b49c 468
71671bf6
HJYP
469 /* check the existance of batteries every 3 seconds */
470 check_batteries( b );
471
472 /* check the existance of AC adapter every 3 seconds,
473 * and update charging state of batteries if needed. */
474 check_ac_adapter( b );
475
3051b49c 476 /* check state of batteries every 30 seconds */
71671bf6 477 if( b->state_elapsed_time == 30/3 ) /* 30 sec */
802d5540
HJYP
478 {
479 /* update state of batteries */
480 g_list_foreach( b->batteries, (GFunc)get_batt_state, NULL );
481 b->state_elapsed_time = 0;
482 }
3051b49c 483 /* check the capacity of batteries every 1 hour */
71671bf6 484 if( b->info_elapsed_time == 3600/3 ) /* 1 hour */
802d5540
HJYP
485 {
486 /* update info of batteries */
487 g_list_foreach( b->batteries, (GFunc)get_batt_info, NULL );
488 b->info_elapsed_time = 0;
489 }
490
491 update_display( b, TRUE );
492
52cb5b58 493 GDK_THREADS_LEAVE();
7c2a8701 494 return TRUE;
52cb5b58 495}
8f11d5f8
HJYP
496
497/* An update will be performed whenever the user clicks on the charge bar */
498static gint buttonPressEvent(GtkWidget *widget, GdkEventConfigure *event,
499 batt *b) {
500
501 ENTER;
802d5540 502 update_display(b, TRUE);
8f11d5f8
HJYP
503
504 RET(TRUE);
505
506}
507
508
509static gint configureEvent(GtkWidget *widget, GdkEventConfigure *event,
510 batt *b) {
511
512 ENTER;
513
802d5540
HJYP
514 if (b->pixmap)
515 g_object_unref(b->pixmap);
8f11d5f8
HJYP
516
517 /* Update the plugin's dimensions */
518 b->width = widget->allocation.width;
519 b->height = widget->allocation.height;
520 if (b->orientation == ORIENT_HORIZ) {
521 b->length = b->height;
522 b->thickness = b->width;
523 }
524 else {
525 b->length = b->width;
526 b->thickness = b->height;
527 }
528
529 b->pixmap = gdk_pixmap_new (widget->window, widget->allocation.width,
530 widget->allocation.height, -1);
531
532 /* Perform an update so the bar will look right in its new orientation */
802d5540 533 update_display(b, FALSE);
8f11d5f8
HJYP
534
535 RET(TRUE);
536
537}
538
539
540static gint exposeEvent(GtkWidget *widget, GdkEventExpose *event, batt *b) {
541
542 ENTER;
543
544 gdk_draw_drawable (widget->window, b->drawingArea->style->black_gc,
545 b->pixmap, event->area.x, event->area.y, event->area.x,
546 event->area.y, event->area.width, event->area.height);
e751fe05 547
8f11d5f8
HJYP
548 RET(FALSE);
549
550}
551
552
553static int
22242ed4 554constructor(Plugin *p, char **fp)
8f11d5f8 555{
8f11d5f8
HJYP
556 ENTER;
557
558 batt *b;
559 p->priv = b = g_new0(batt, 1);
4542c20d
HJYP
560 p->pwid = gtk_event_box_new();
561 GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
562 gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 1 );
563
8f11d5f8 564 b->drawingArea = gtk_drawing_area_new();
802d5540 565 gtk_widget_add_events( b->drawingArea, GDK_BUTTON_PRESS_MASK );
8f11d5f8 566
4542c20d
HJYP
567 gtk_container_add( (GtkContainer*)p->pwid, b->drawingArea );
568
8f11d5f8
HJYP
569 if ((b->orientation = p->panel->orientation) == ORIENT_HORIZ) {
570 b->height = b->length = 20;
571 b->thickness = b->width = 8;
572 }
573 else {
574 b->height = b->thickness = 8;
575 b->length = b->width = 20;
576 }
577 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
578
8f11d5f8
HJYP
579 gtk_widget_show(b->drawingArea);
580 b->tooltip = p->panel->tooltips;
581
582 b->bg = gdk_gc_new(p->panel->topgwin->window);
583 b->gc1 = gdk_gc_new(p->panel->topgwin->window);
584 b->gc2 = gdk_gc_new(p->panel->topgwin->window);
8f11d5f8 585
802d5540 586 g_signal_connect (G_OBJECT (b->drawingArea), "button_press_event",
8f11d5f8
HJYP
587 G_CALLBACK(buttonPressEvent), (gpointer) b);
588 g_signal_connect (G_OBJECT (b->drawingArea),"configure_event",
589 G_CALLBACK (configureEvent), (gpointer) b);
590 g_signal_connect (G_OBJECT (b->drawingArea), "expose_event",
591 G_CALLBACK (exposeEvent), (gpointer) b);
592
593 sem_init(&(b->alarmProcessLock), 0, 1);
594
595 b->alarmCommand = b->backgroundColor = b->chargingColor1 = b->chargingColor2
802d5540 596 = b->dischargingColor1 = b->dischargingColor2 = NULL;
8f11d5f8
HJYP
597
598 /* Set default values for integers */
599 b->alarmTime = 5;
600 b->requestedBorder = 1;
601 b->numSamples = b->rateSamplesSum = b->wasCharging = 0;
602
c0ed75bf 603 b->rateSamples = (unsigned int *) malloc(sizeof(int) * MAX_SAMPLES);
8f11d5f8
HJYP
604
605 line s;
606 s.len = 256;
607
608 if (fp) {
609
610 /* Apply options */
611 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
612 if (s.type == LINE_NONE) {
613 ERR( "batt: illegal token %s\n", s.str);
614 goto error;
615 }
616 if (s.type == LINE_VAR) {
04a2f050
HJYP
617 if (!g_ascii_strcasecmp(s.t[0], "HideIfNoBattery"))
618 b->hide_if_no_battery = atoi(s.t[1]);
619 else if (!g_ascii_strcasecmp(s.t[0], "AlarmCommand"))
8f11d5f8
HJYP
620 b->alarmCommand = g_strdup(s.t[1]);
621 else if (!g_ascii_strcasecmp(s.t[0], "BackgroundColor"))
622 b->backgroundColor = g_strdup(s.t[1]);
623 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor1"))
624 b->chargingColor1 = g_strdup(s.t[1]);
625 else if (!g_ascii_strcasecmp(s.t[0], "ChargingColor2"))
626 b->chargingColor2 = g_strdup(s.t[1]);
627 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor1"))
628 b->dischargingColor1 = g_strdup(s.t[1]);
629 else if (!g_ascii_strcasecmp(s.t[0], "DischargingColor2"))
630 b->dischargingColor2 = g_strdup(s.t[1]);
8f11d5f8
HJYP
631 else if (!g_ascii_strcasecmp(s.t[0], "AlarmTime"))
632 b->alarmTime = atoi(s.t[1]);
633 else if (!g_ascii_strcasecmp(s.t[0], "BorderWidth"))
634 b->requestedBorder = atoi(s.t[1]);
635 else if (!g_ascii_strcasecmp(s.t[0], "Size")) {
636 b->thickness = MAX(1, atoi(s.t[1]));
637 if (b->orientation == ORIENT_HORIZ)
638 b->width = b->thickness;
639 else
640 b->height = b->thickness;
641 gtk_widget_set_size_request(b->drawingArea, b->width,
642 b->height);
643 }
644 else {
645 ERR( "batt: unknown var %s\n", s.t[0]);
802d5540 646 continue;
8f11d5f8
HJYP
647 }
648 }
649 else {
650 ERR( "batt: illegal in this context %s\n", s.str);
651 goto error;
652 }
653 }
654
655 }
656
657 /* Make sure the border value is acceptable */
658 b->border = MIN(MAX(0, b->requestedBorder),
659 (MIN(b->length, b->thickness) - 1) / 2);
660
661 /* Apply more default options */
662 if (! b->alarmCommand)
663 b->alarmCommand = g_strdup("xmessage Battery low");
664 if (! b->backgroundColor)
665 b->backgroundColor = g_strdup("black");
666 if (! b->chargingColor1)
667 b->chargingColor1 = g_strdup("#28f200");
668 if (! b->chargingColor2)
669 b->chargingColor2 = g_strdup("#22cc00");
670 if (! b->dischargingColor1)
671 b->dischargingColor1 = g_strdup("#ffee00");
672 if (! b->dischargingColor2)
673 b->dischargingColor2 = g_strdup("#d9ca00");
802d5540
HJYP
674
675 gdk_color_parse(b->backgroundColor, &b->background);
676 gdk_color_parse(b->chargingColor1, &b->charging1);
677 gdk_color_parse(b->chargingColor2, &b->charging2);
678 gdk_color_parse(b->dischargingColor1, &b->discharging1);
679 gdk_color_parse(b->dischargingColor2, &b->discharging2);
8f11d5f8 680 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 681 p->panel->topgwin->window), &b->background, FALSE, TRUE);
8f11d5f8 682 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 683 p->panel->topgwin->window), &b->charging1, FALSE, TRUE);
8f11d5f8 684 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 685 p->panel->topgwin->window), &b->charging2, FALSE, TRUE);
8f11d5f8 686 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 687 p->panel->topgwin->window), &b->discharging1, FALSE, TRUE);
8f11d5f8 688 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540
HJYP
689 p->panel->topgwin->window), &b->discharging2, FALSE, TRUE);
690 gdk_gc_set_foreground(b->bg, &b->background);
691
692 check_batteries( b ); /* get available batteries */
8f11d5f8
HJYP
693
694 /* Start the update loop */
802d5540 695#if GTK_CHECK_VERSION( 2, 14, 0 )
71671bf6 696 b->timer = g_timeout_add_seconds( 3, (GSourceFunc) update_timout, (gpointer) b);
802d5540 697#else
71671bf6 698 b->timer = g_timeout_add( 3000,
52cb5b58 699 (GSourceFunc) update_timout, (gpointer) b);
802d5540 700#endif
8f11d5f8
HJYP
701 RET(TRUE);
702
703error:
52cb5b58 704 destructor( p );
8f11d5f8
HJYP
705 RET(FALSE);
706}
707
708
709static void
22242ed4 710destructor(Plugin *p)
8f11d5f8 711{
8f11d5f8
HJYP
712 ENTER;
713
714 batt *b = (batt *) p->priv;
52cb5b58
HJYP
715
716 if (b->pixmap)
717 g_object_unref(b->pixmap);
8f11d5f8 718
8f11d5f8
HJYP
719 g_object_unref(b->gc1);
720 g_object_unref(b->gc2);
721 g_free(b->alarmCommand);
722 g_free(b->backgroundColor);
723 g_free(b->chargingColor1);
724 g_free(b->chargingColor2);
725 g_free(b->dischargingColor1);
726 g_free(b->dischargingColor2);
802d5540 727
8f11d5f8 728 g_free(b->rateSamples);
8f11d5f8
HJYP
729 sem_destroy(&(b->alarmProcessLock));
730 g_source_remove(b->timer);
731 g_free(b);
732
733 RET();
734
735}
736
737
22242ed4 738static void orientation(Plugin *p) {
8f11d5f8
HJYP
739
740 ENTER;
741
742 batt *b = (batt *) p->priv;
743
744 if (b->orientation != p->panel->orientation) {
745 b->orientation = p->panel->orientation;
746 unsigned int swap = b->height;
747 b->height = b->width;
748 b->width = swap;
749 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
750 }
751
752 RET();
753}
754
755
22242ed4 756static void applyConfig(Plugin* p)
8f11d5f8 757{
8f11d5f8
HJYP
758 ENTER;
759
760 batt *b = (batt *) p->priv;
761
762 /* Update colors */
763 if (b->backgroundColor &&
802d5540 764 gdk_color_parse(b->backgroundColor, &b->background)) {
8f11d5f8 765 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540
HJYP
766 p->panel->topgwin->window), &b->background, FALSE, TRUE);
767 gdk_gc_set_foreground(b->bg, &b->background);
8f11d5f8 768 }
802d5540 769 if (b->chargingColor1 && gdk_color_parse(b->chargingColor1, &b->charging1))
8f11d5f8 770 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540
HJYP
771 p->panel->topgwin->window), &b->charging1, FALSE, TRUE);
772 if (b->chargingColor2 && gdk_color_parse(b->chargingColor2, &b->charging2))
8f11d5f8 773 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 774 p->panel->topgwin->window), &b->charging2, FALSE, TRUE);
8f11d5f8 775 if (b->dischargingColor1 &&
802d5540 776 gdk_color_parse(b->dischargingColor1, &b->discharging1))
8f11d5f8 777 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 778 p->panel->topgwin->window), &b->discharging1, FALSE, TRUE);
8f11d5f8 779 if (b->dischargingColor2 &&
802d5540 780 gdk_color_parse(b->dischargingColor2, &b->discharging2))
8f11d5f8 781 gdk_colormap_alloc_color(gdk_drawable_get_colormap(
802d5540 782 p->panel->topgwin->window), &b->discharging2, FALSE, TRUE);
8f11d5f8 783
e751fe05 784 /* Make sure the border value is acceptable */
8f11d5f8
HJYP
785 b->border = MIN(MAX(0, b->requestedBorder),
786 (MIN(b->length, b->thickness) - 1) / 2);
787
8f11d5f8
HJYP
788 /* Resize the widget */
789 if (b->orientation == ORIENT_HORIZ)
790 b->width = b->thickness;
791 else
792 b->height = b->thickness;
793 gtk_widget_set_size_request(b->drawingArea, b->width, b->height);
794
795 RET();
8f11d5f8
HJYP
796}
797
798
22242ed4 799static void config(Plugin *p, GtkWindow* parent) {
8f11d5f8
HJYP
800 ENTER;
801
802 GtkWidget *dialog;
803 batt *b = (batt *) p->priv;
804 dialog = create_generic_config_dlg(_(p->class->name),
805 GTK_WIDGET(parent),
806 (GSourceFunc) applyConfig, (gpointer) p,
04a2f050
HJYP
807#if 0
808 _("Hide if there is no battery"), &b->hide_if_no_battery, G_TYPE_BOOLEAN,
809#endif
8f11d5f8
HJYP
810 _("Alarm command"), &b->alarmCommand, G_TYPE_STRING,
811 _("Alarm time (minutes left)"), &b->alarmTime, G_TYPE_INT,
8f11d5f8
HJYP
812 _("Background color"), &b->backgroundColor, G_TYPE_STRING,
813 _("Charging color 1"), &b->chargingColor1, G_TYPE_STRING,
814 _("Charging color 2"), &b->chargingColor2, G_TYPE_STRING,
815 _("Discharging color 1"), &b->dischargingColor1, G_TYPE_STRING,
816 _("Discharging color 2"), &b->dischargingColor2, G_TYPE_STRING,
817 _("Border width"), &b->requestedBorder, G_TYPE_INT,
818 _("Size"), &b->thickness, G_TYPE_INT,
819 NULL);
820 gtk_window_present(GTK_WINDOW(dialog));
821
822 RET();
8f11d5f8
HJYP
823}
824
825
22242ed4 826static void save(Plugin* p, FILE* fp) {
8f11d5f8
HJYP
827
828 ENTER;
829
830 batt *b = (batt *) p->priv;
831
04a2f050 832 lxpanel_put_str(fp, "HideIfNoBattery", b->hide_if_no_battery);
8f11d5f8
HJYP
833 lxpanel_put_str(fp, "AlarmCommand", b->alarmCommand);
834 lxpanel_put_int(fp, "AlarmTime", b->alarmTime);
835 lxpanel_put_str(fp, "BackgroundColor", b->backgroundColor);
836 lxpanel_put_int(fp, "BorderWidth", b->requestedBorder);
837 lxpanel_put_str(fp, "ChargingColor1", b->chargingColor1);
838 lxpanel_put_str(fp, "ChargingColor2", b->chargingColor2);
839 lxpanel_put_str(fp, "DischargingColor1", b->dischargingColor1);
840 lxpanel_put_str(fp, "DischargingColor2", b->dischargingColor2);
841 lxpanel_put_int(fp, "Size", b->thickness);
8f11d5f8
HJYP
842
843 RET();
8f11d5f8
HJYP
844}
845
846
22242ed4 847PluginClass batt_plugin_class = {
8f11d5f8
HJYP
848 fname : NULL,
849 count : 0,
850
851 type : "batt",
852 name : N_("Battery Monitor"),
853 version : "1.0",
854 description : N_("Display battery status using ACPI"),
855
856 constructor : constructor,
857 destructor : destructor,
858 config : config,
859 save : save,
860 orientation : orientation
861};