1 /**************************************************************************
5 * Copyright (C) 2009 Sebastian Reichel <elektranox@gmail.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License version 2
9 * or any later version as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **************************************************************************/
24 #include <cairo-xlib.h>
25 #include <pango/pangocairo.h>
27 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
28 #include <machine/apmvar.h>
30 #include <sys/ioctl.h>
43 PangoFontDescription
*bat1_font_desc
;
44 PangoFontDescription
*bat2_font_desc
;
45 struct batstate battery_state
;
48 static timeout
* battery_timeout
;
50 static char buf_bat_percentage
[10];
51 static char buf_bat_time
[20];
53 int8_t battery_low_status
;
54 unsigned char battery_low_cmd_send
;
55 char *battery_low_cmd
;
56 char *path_energy_now
;
57 char *path_energy_full
;
58 char *path_current_now
;
61 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
66 void update_batterys(void* arg
)
70 for (i
=0 ; i
< nb_panel
; i
++) {
71 if (battery_state
.percentage
>= percentage_hide
) {
72 if (panel1
[i
].battery
.area
.on_screen
== 1) {
73 panel1
[i
].battery
.area
.on_screen
= 0;
74 // force resize on panel
75 panel1
[i
].area
.resize
= 1;
81 if (panel1
[i
].battery
.area
.on_screen
== 0) {
82 panel1
[i
].battery
.area
.on_screen
= 1;
83 // force resize on panel
84 panel1
[i
].area
.resize
= 1;
88 panel1
[i
].battery
.area
.resize
= 1;
92 void default_battery()
95 percentage_hide
= 101;
96 battery_low_cmd_send
= 0;
102 path_energy_full
= 0;
103 path_current_now
= 0;
105 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
110 void cleanup_battery()
112 if (bat1_font_desc
) pango_font_description_free(bat1_font_desc
);
113 if (bat2_font_desc
) pango_font_description_free(bat2_font_desc
);
114 if (path_energy_now
) g_free(path_energy_now
);
115 if (path_energy_full
) g_free(path_energy_full
);
116 if (path_current_now
) g_free(path_current_now
);
117 if (path_status
) g_free(path_status
);
118 if (battery_low_cmd
) g_free(battery_low_cmd
);
120 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
121 if ((apm_fd
!= -1) && (close(apm_fd
) == -1))
122 warn("cannot close /dev/apm");
129 if (!battery_enabled
) return;
131 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
132 apm_fd
= open("/dev/apm", O_RDONLY
);
134 warn("init_battery: failed to open /dev/apm.");
142 GError
*error
= NULL
;
143 const char *entryname
;
144 char *battery_dir
= 0;
146 directory
= g_dir_open("/sys/class/power_supply", 0, &error
);
150 while ((entryname
=g_dir_read_name(directory
))) {
151 if (strncmp(entryname
,"AC", 2) == 0) continue;
153 char *path1
= g_build_filename("/sys/class/power_supply", entryname
, "present", NULL
);
154 if (g_file_test (path1
, G_FILE_TEST_EXISTS
)) {
156 battery_dir
= g_build_filename("/sys/class/power_supply", entryname
, NULL
);
163 g_dir_close(directory
);
165 fprintf(stderr
, "ERROR: battery applet can't found power_supply\n");
170 char *path1
= g_build_filename(battery_dir
, "energy_now", NULL
);
171 if (g_file_test (path1
, G_FILE_TEST_EXISTS
)) {
172 path_energy_now
= g_build_filename(battery_dir
, "energy_now", NULL
);
173 path_energy_full
= g_build_filename(battery_dir
, "energy_full", NULL
);
176 char *path2
= g_build_filename(battery_dir
, "charge_now", NULL
);
177 if (g_file_test (path2
, G_FILE_TEST_EXISTS
)) {
178 path_energy_now
= g_build_filename(battery_dir
, "charge_now", NULL
);
179 path_energy_full
= g_build_filename(battery_dir
, "charge_full", NULL
);
182 fprintf(stderr
, "ERROR: can't found energy_* or charge_*\n");
186 if (path_energy_now
&& path_energy_full
) {
187 path_current_now
= g_build_filename(battery_dir
, "current_now", NULL
);
188 path_status
= g_build_filename(battery_dir
, "status", NULL
);
191 FILE *fp1
, *fp2
, *fp3
, *fp4
;
192 fp1
= fopen(path_energy_now
, "r");
193 fp2
= fopen(path_energy_full
, "r");
194 fp3
= fopen(path_current_now
, "r");
195 fp4
= fopen(path_status
, "r");
196 if (fp1
== NULL
|| fp2
== NULL
|| fp3
== NULL
|| fp4
== NULL
) {
199 fprintf(stderr
, "ERROR: battery applet can't open energy_now\n");
211 if (battery_enabled
&& battery_timeout
==0)
212 battery_timeout
= add_timeout(10, 10000, update_batterys
, 0);
216 void init_battery_panel(void *p
)
218 Panel
*panel
= (Panel
*)p
;
219 Battery
*battery
= &panel
->battery
;
220 int bat_percentage_height
, bat_percentage_height_ink
, bat_time_height
, bat_time_height_ink
;
222 if (!battery_enabled
)
225 battery
->area
.parent
= p
;
226 battery
->area
.panel
= p
;
227 battery
->area
._draw_foreground
= draw_battery
;
228 battery
->area
._resize
= resize_battery
;
229 battery
->area
.resize
= 1;
230 battery
->area
.redraw
= 1;
231 battery
->area
.on_screen
= 1;
233 update_battery(&battery_state
);
234 snprintf(buf_bat_percentage
, sizeof(buf_bat_percentage
), "%d%%", battery_state
.percentage
);
235 snprintf(buf_bat_time
, sizeof(buf_bat_time
), "%02d:%02d", battery_state
.time
.hours
, battery_state
.time
.minutes
);
237 get_text_size(bat1_font_desc
, &bat_percentage_height_ink
, &bat_percentage_height
, panel
->area
.height
, buf_bat_percentage
, strlen(buf_bat_percentage
));
238 get_text_size(bat2_font_desc
, &bat_time_height_ink
, &bat_time_height
, panel
->area
.height
, buf_bat_time
, strlen(buf_bat_time
));
240 if (panel_horizontal
) {
241 // panel horizonal => fixed height and posy
242 battery
->area
.posy
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
;
243 battery
->area
.height
= panel
->area
.height
- (2 * battery
->area
.posy
);
246 // panel vertical => fixed width, height, posy and posx
247 battery
->area
.posy
= panel
->clock
.area
.posy
+ panel
->clock
.area
.height
+ panel
->area
.paddingx
;
248 battery
->area
.height
= (2 * battery
->area
.paddingxlr
) + (bat_time_height
+ bat_percentage_height
);
249 battery
->area
.posx
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
;
250 battery
->area
.width
= panel
->area
.width
- (2 * panel
->area
.bg
->border
.width
) - (2 * panel
->area
.paddingy
);
253 battery
->bat1_posy
= (battery
->area
.height
- bat_percentage_height
) / 2;
254 battery
->bat1_posy
-= ((bat_time_height_ink
+ 2) / 2);
255 battery
->bat2_posy
= battery
->bat1_posy
+ bat_percentage_height
+ 2 - (bat_percentage_height
- bat_percentage_height_ink
)/2 - (bat_time_height
- bat_time_height_ink
)/2;
259 void update_battery() {
260 #if !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__NetBSD__)
261 // unused on OpenBSD, silence compiler warnings
264 int64_t current_now
= 0;
266 int64_t energy_now
= 0, energy_full
= 0;
268 int8_t new_percentage
= 0;
270 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
271 struct apm_power_info info
;
272 if (ioctl(apm_fd
, APM_IOC_GETPOWER
, &(info
)) < 0)
273 warn("power update: APM_IOC_GETPOWER");
275 // best attempt at mapping to linux battery states
276 battery_state
.state
= BATTERY_UNKNOWN
;
277 switch (info
.battery_state
) {
278 case APM_BATT_CHARGING
:
279 battery_state
.state
= BATTERY_CHARGING
;
282 battery_state
.state
= BATTERY_DISCHARGING
;
286 if (info
.battery_life
== 100)
287 battery_state
.state
= BATTERY_FULL
;
289 // no mapping for openbsd really
293 if (info
.minutes_left
!= -1)
294 seconds
= info
.minutes_left
* 60;
298 new_percentage
= info
.battery_life
;
301 fp
= fopen(path_status
, "r");
303 if (fgets(tmp
, sizeof tmp
, fp
)) {
304 battery_state
.state
= BATTERY_UNKNOWN
;
305 if(strcasecmp(tmp
, "Charging\n")==0) battery_state
.state
= BATTERY_CHARGING
;
306 if(strcasecmp(tmp
, "Discharging\n")==0) battery_state
.state
= BATTERY_DISCHARGING
;
307 if(strcasecmp(tmp
, "Full\n")==0) battery_state
.state
= BATTERY_FULL
;
312 fp
= fopen(path_energy_now
, "r");
314 if (fgets(tmp
, sizeof tmp
, fp
)) energy_now
= atoi(tmp
);
318 fp
= fopen(path_energy_full
, "r");
320 if (fgets(tmp
, sizeof tmp
, fp
)) energy_full
= atoi(tmp
);
324 fp
= fopen(path_current_now
, "r");
326 if (fgets(tmp
, sizeof tmp
, fp
)) current_now
= atoi(tmp
);
330 if(current_now
> 0) {
331 switch(battery_state
.state
) {
332 case BATTERY_CHARGING
:
333 seconds
= 3600 * (energy_full
- energy_now
) / current_now
;
335 case BATTERY_DISCHARGING
:
336 seconds
= 3600 * energy_now
/ current_now
;
345 battery_state
.time
.hours
= seconds
/ 3600;
346 seconds
-= 3600 * battery_state
.time
.hours
;
347 battery_state
.time
.minutes
= seconds
/ 60;
348 seconds
-= 60 * battery_state
.time
.minutes
;
349 battery_state
.time
.seconds
= seconds
;
352 new_percentage
= (energy_now
*100)/energy_full
;
354 if(battery_low_status
> new_percentage
&& battery_state
.state
== BATTERY_DISCHARGING
&& !battery_low_cmd_send
) {
355 system(battery_low_cmd
); // return value == -1, since we've set SIGCHLD to SIGIGN
356 battery_low_cmd_send
= 1;
358 if(battery_low_status
< new_percentage
&& battery_state
.state
== BATTERY_CHARGING
&& battery_low_cmd_send
) {
359 battery_low_cmd_send
= 0;
362 battery_state
.percentage
= new_percentage
;
364 // clamp percentage to 100 in case battery is misreporting that its current charge is more than its max
365 if(battery_state
.percentage
> 100) {
366 battery_state
.percentage
= 100;
371 void draw_battery (void *obj
, cairo_t
*c
)
373 Battery
*battery
= obj
;
376 layout
= pango_cairo_create_layout (c
);
379 pango_layout_set_font_description(layout
, bat1_font_desc
);
380 pango_layout_set_width(layout
, battery
->area
.width
* PANGO_SCALE
);
381 pango_layout_set_alignment(layout
, PANGO_ALIGN_CENTER
);
382 pango_layout_set_text(layout
, buf_bat_percentage
, strlen(buf_bat_percentage
));
384 cairo_set_source_rgba(c
, battery
->font
.color
[0], battery
->font
.color
[1], battery
->font
.color
[2], battery
->font
.alpha
);
386 pango_cairo_update_layout(c
, layout
);
387 cairo_move_to(c
, 0, battery
->bat1_posy
);
388 pango_cairo_show_layout(c
, layout
);
390 pango_layout_set_font_description(layout
, bat2_font_desc
);
391 pango_layout_set_indent(layout
, 0);
392 pango_layout_set_text(layout
, buf_bat_time
, strlen(buf_bat_time
));
393 pango_layout_set_width(layout
, battery
->area
.width
* PANGO_SCALE
);
395 pango_cairo_update_layout(c
, layout
);
396 cairo_move_to(c
, 0, battery
->bat2_posy
);
397 pango_cairo_show_layout(c
, layout
);
399 g_object_unref(layout
);
403 void resize_battery(void *obj
)
405 Battery
*battery
= obj
;
407 int percentage_width
, time_width
, new_width
;
409 percentage_width
= time_width
= 0;
410 battery
->area
.redraw
= 1;
412 snprintf(buf_bat_percentage
, sizeof(buf_bat_percentage
), "%d%%", battery_state
.percentage
);
413 if(battery_state
.state
== BATTERY_FULL
) {
414 strcpy(buf_bat_time
, "Full");
416 snprintf(buf_bat_time
, sizeof(buf_bat_time
), "%02d:%02d", battery_state
.time
.hours
, battery_state
.time
.minutes
);
418 // vertical panel doen't adjust width
419 if (!panel_horizontal
) return;
424 pmap
= XCreatePixmap(server
.dsp
, server
.root_win
, battery
->area
.width
, battery
->area
.height
, server
.depth
);
426 cs
= cairo_xlib_surface_create(server
.dsp
, pmap
, server
.visual
, battery
->area
.width
, battery
->area
.height
);
427 c
= cairo_create(cs
);
428 layout
= pango_cairo_create_layout(c
);
431 pango_layout_set_font_description(layout
, bat1_font_desc
);
432 pango_layout_set_indent(layout
, 0);
433 pango_layout_set_text(layout
, buf_bat_percentage
, strlen(buf_bat_percentage
));
434 pango_layout_get_pixel_size(layout
, &percentage_width
, NULL
);
436 pango_layout_set_font_description(layout
, bat2_font_desc
);
437 pango_layout_set_indent(layout
, 0);
438 pango_layout_set_text(layout
, buf_bat_time
, strlen(buf_bat_time
));
439 pango_layout_get_pixel_size(layout
, &time_width
, NULL
);
441 if(percentage_width
> time_width
) new_width
= percentage_width
;
442 else new_width
= time_width
;
444 new_width
+= (2*battery
->area
.paddingxlr
) + (2*battery
->area
.bg
->border
.width
);
446 int old_width
= battery
->area
.width
;
448 Panel
*panel
= ((Area
*)obj
)->panel
;
449 battery
->area
.width
= new_width
+ 1;
450 battery
->area
.posx
= panel
->area
.width
- battery
->area
.width
- panel
->area
.paddingxlr
- panel
->area
.bg
->border
.width
;
451 if (panel
->clock
.area
.on_screen
)
452 battery
->area
.posx
-= (panel
->clock
.area
.width
+ panel
->area
.paddingx
);
454 if(new_width
> old_width
|| new_width
< (old_width
-6)) {
455 // refresh and resize other objects on panel
456 // we try to limit the number of refresh
457 // printf("battery_width %d, new_width %d\n", battery->area.width, new_width);
458 panel
->area
.resize
= 1;
459 systray
.area
.resize
= 1;
463 g_object_unref (layout
);
465 cairo_surface_destroy (cs
);
466 XFreePixmap (server
.dsp
, pmap
);