1 /* $Id$ */
2 /* Copyright (c) 2010-2015 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Panel */
4 /* This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16
17
18 #include <sys/time.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <time.h>
22 #include <errno.h>
23 #if defined(__NetBSD__)
24 # include <sys/types.h>
25 # include <sys/ioctl.h>
26 # include <sys/envsys.h>
27 # include <fcntl.h>
28 # include <unistd.h>
29 # include <paths.h>
30 #elif defined(__linux__)
31 # include <fcntl.h>
32 # include <unistd.h>
33 # include <stdio.h>
34 #endif
35 #include <libintl.h>
36 #include <System.h>
37 #include "Panel/applet.h"
38 #define _(string) gettext(string)
39
40
41 /* Battery */
42 /* private */
43 /* types */
44 typedef enum _BatteryLevel
45 {
46 BATTERY_LEVEL_UNKNOWN = 0,
47 BATTERY_LEVEL_ERROR,
48 BATTERY_LEVEL_EMPTY,
49 BATTERY_LEVEL_CAUTION,
50 BATTERY_LEVEL_LOW,
51 BATTERY_LEVEL_GOOD,
52 BATTERY_LEVEL_FULL
53 } BatteryLevel;
54 #define BATTERY_LEVEL_LAST BATTERY_LEVEL_FULL
55 #define BATTERY_LEVEL_COUNT (BATTERY_LEVEL_LAST + 1)
56
57 typedef struct _PanelApplet
58 {
59 PanelAppletHelper * helper;
60 BatteryLevel level;
61 int charging;
62
63 /* widgets */
64 GtkWidget * box;
65 GtkWidget * image;
66 GtkWidget * label;
67 GtkWidget * progress;
68 guint timeout;
69
70 /* preferences */
71 GtkWidget * pr_level;
72
73 /* platform-specific */
74 #if defined(__NetBSD__) || defined(__linux__)
75 int fd;
76 #endif
77 } Battery;
78
79
80 /* prototypes */
81 static Battery * _battery_init(PanelAppletHelper * helper, GtkWidget ** widget);
82 static void _battery_destroy(Battery * battery);
83 static GtkWidget * _battery_settings(Battery * battery, gboolean apply,
84 gboolean reset);
85
86 /* accessors */
87 static gboolean _battery_get(Battery * battery, gdouble * level,
88 gboolean * charging);
89 static void _battery_set(Battery * battery, gdouble value, gboolean charging);
90
91 /* callbacks */
92 static gboolean _battery_on_timeout(gpointer data);
93
94
95 /* public */
96 /* variables */
97 PanelAppletDefinition applet =
98 {
99 "Battery",
100 "battery",
101 NULL,
102 _battery_init,
103 _battery_destroy,
104 _battery_settings,
105 FALSE,
106 TRUE
107 };
108
109
110 /* private */
111 /* functions */
112 /* battery_init */
_battery_init(PanelAppletHelper * helper,GtkWidget ** widget)113 static Battery * _battery_init(PanelAppletHelper * helper, GtkWidget ** widget)
114 {
115 const int timeout = 5000;
116 Battery * battery;
117 GtkIconSize iconsize;
118 GtkWidget * vbox;
119 GtkWidget * hbox;
120 PangoFontDescription * bold;
121
122 if((battery = object_new(sizeof(*battery))) == NULL)
123 return NULL;
124 battery->helper = helper;
125 battery->level = -1;
126 battery->charging = -1;
127 battery->timeout = 0;
128 #if defined(__NetBSD__) || defined(__linux__)
129 battery->fd = -1;
130 #endif
131 iconsize = panel_window_get_icon_size(helper->window);
132 #if GTK_CHECK_VERSION(3, 0, 0)
133 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
134 #else
135 hbox = gtk_hbox_new(FALSE, 4);
136 #endif
137 battery->box = hbox;
138 battery->image = gtk_image_new_from_icon_name("battery", iconsize);
139 gtk_box_pack_start(GTK_BOX(hbox), battery->image, TRUE, TRUE, 0);
140 battery->label = NULL;
141 battery->progress = NULL;
142 battery->pr_level = NULL;
143 if(panel_window_get_type(helper->window)
144 == PANEL_WINDOW_TYPE_NOTIFICATION)
145 {
146 bold = pango_font_description_new();
147 pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD);
148 #if GTK_CHECK_VERSION(3, 0, 0)
149 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
150 #else
151 vbox = gtk_vbox_new(FALSE, 4);
152 #endif
153 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
154 gtk_widget_show(hbox);
155 battery->progress = gtk_progress_bar_new();
156 gtk_box_pack_start(GTK_BOX(vbox), battery->progress, TRUE, TRUE,
157 0);
158 battery->box = vbox;
159 pango_font_description_free(bold);
160 }
161 else
162 {
163 #ifndef EMBEDDED
164 battery->label = gtk_label_new(" ");
165 gtk_box_pack_start(GTK_BOX(hbox), battery->label, FALSE, TRUE,
166 0);
167 gtk_widget_show(battery->label);
168 #endif
169 battery->box = hbox;
170 }
171 battery->timeout = g_timeout_add(timeout, _battery_on_timeout, battery);
172 _battery_on_timeout(battery);
173 gtk_widget_show(battery->image);
174 *widget = battery->box;
175 return battery;
176 }
177
178
179 /* battery_destroy */
_battery_destroy(Battery * battery)180 static void _battery_destroy(Battery * battery)
181 {
182 if(battery->timeout > 0)
183 g_source_remove(battery->timeout);
184 #if defined(__NetBSD__) || defined(__linux__)
185 if(battery->fd != -1)
186 close(battery->fd);
187 #endif
188 gtk_widget_destroy(battery->box);
189 object_delete(battery);
190 }
191
192
193 /* battery_settings */
194 static void _settings_apply(Battery * battery, PanelAppletHelper * helper);
195 static void _settings_reset(Battery * battery, PanelAppletHelper * helper);
196
_battery_settings(Battery * battery,gboolean apply,gboolean reset)197 static GtkWidget * _battery_settings(Battery * battery, gboolean apply,
198 gboolean reset)
199 {
200 PanelAppletHelper * helper = battery->helper;
201
202 if(battery->pr_level == NULL)
203 {
204 battery->pr_level = gtk_check_button_new_with_label(
205 _("Show the battery level"));
206 gtk_widget_show(battery->pr_level);
207 reset = TRUE;
208 }
209 if(reset == TRUE)
210 _settings_reset(battery, helper);
211 if(apply == TRUE)
212 _settings_apply(battery, helper);
213 return battery->pr_level;
214 }
215
_settings_apply(Battery * battery,PanelAppletHelper * helper)216 static void _settings_apply(Battery * battery, PanelAppletHelper * helper)
217 {
218 gboolean active;
219
220 active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
221 battery->pr_level));
222 if(battery->label != NULL)
223 {
224 if(active)
225 gtk_widget_show(battery->label);
226 else
227 gtk_widget_hide(battery->label);
228 }
229 helper->config_set(helper->panel, "battery", "level",
230 active ? "1" : "0");
231 }
232
_settings_reset(Battery * battery,PanelAppletHelper * helper)233 static void _settings_reset(Battery * battery, PanelAppletHelper * helper)
234 {
235 #ifndef EMBEDDED
236 gboolean active = TRUE;
237 #else
238 gboolean active = FALSE;
239 #endif
240 char const * p;
241
242 if((p = helper->config_get(helper->panel, "battery", "level")) != NULL)
243 active = strtol(p, NULL, 10) ? TRUE : FALSE;
244 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(battery->pr_level),
245 active);
246 }
247
248
249 /* accessors */
250 /* battery_get */
251 #if defined(__NetBSD__)
252 static int _get_tre(int fd, int sensor, envsys_tre_data_t * tre);
253
_battery_get(Battery * battery,gdouble * level,gboolean * charging)254 static gboolean _battery_get(Battery * battery, gdouble * level,
255 gboolean * charging)
256 {
257 int i;
258 envsys_basic_info_t info;
259 envsys_tre_data_t tre;
260 unsigned int rate = 0;
261 unsigned int charge = 0;
262 unsigned int maxcharge = 0;
263
264 *charging = FALSE;
265 if(battery->fd < 0 && (battery->fd = open(_PATH_SYSMON, O_RDONLY)) < 0)
266 {
267 error_set("%s: %s: %s", applet.name, _PATH_SYSMON,
268 strerror(errno));
269 *level = -1.0;
270 return TRUE;
271 }
272 for(i = 0; i >= 0; i++)
273 {
274 memset(&info, 0, sizeof(info));
275 info.sensor = i;
276 if(ioctl(battery->fd, ENVSYS_GTREINFO, &info) == -1)
277 {
278 close(battery->fd);
279 battery->fd = -1;
280 error_set("%s: %s: %s", applet.name, "ENVSYS_GTREINFO",
281 strerror(errno));
282 *level = -1.0;
283 return TRUE;
284 }
285 if(!(info.validflags & ENVSYS_FVALID))
286 break;
287 #ifdef DEBUG
288 fprintf(stderr, "DEBUG: %s() %d \"%s\"\n", __func__, i,
289 info.desc);
290 #endif
291 if(strcmp("acpiacad0 connected", info.desc) == 0
292 && _get_tre(battery->fd, i, &tre) == 0
293 && tre.validflags & ENVSYS_FCURVALID)
294 /* FIXME implement */
295 continue;
296 if(strncmp("acpibat", info.desc, 7) != 0
297 || info.desc[7] == '\0'
298 || info.desc[8] != ' ')
299 continue;
300 if(strcmp("charge", &info.desc[9]) == 0
301 && _get_tre(battery->fd, i, &tre) == 0
302 && tre.validflags & ENVSYS_FCURVALID
303 && tre.validflags & ENVSYS_FMAXVALID)
304 {
305 charge += tre.cur.data_us;
306 maxcharge += tre.max.data_us;
307 }
308 else if(strcmp("charge rate", &info.desc[9]) == 0
309 && _get_tre(battery->fd, i, &tre) == 0
310 && tre.validflags & ENVSYS_FCURVALID)
311 rate += tre.cur.data_us;
312 else if(strcmp("charging", &info.desc[9]) == 0
313 && _get_tre(battery->fd, i, &tre) == 0
314 && tre.validflags & ENVSYS_FCURVALID
315 && tre.cur.data_us > 0)
316 {
317 *charging = TRUE;
318 continue;
319 }
320 else if(strcmp("discharge rate", &info.desc[9]) == 0
321 && _get_tre(battery->fd, i, &tre) == 0
322 && tre.validflags & ENVSYS_FCURVALID)
323 rate += tre.cur.data_us;
324 }
325 *level = (charge * 100.0) / maxcharge;
326 return TRUE;
327 }
328
_get_tre(int fd,int sensor,envsys_tre_data_t * tre)329 static int _get_tre(int fd, int sensor, envsys_tre_data_t * tre)
330 {
331 memset(tre, 0, sizeof(*tre));
332 tre->sensor = sensor;
333 if(ioctl(fd, ENVSYS_GTREDATA, tre) == -1)
334 return 1;
335 return !(tre->validflags & ENVSYS_FVALID);
336 }
337 #elif defined(__linux__)
_battery_get(Battery * battery,gdouble * level,gboolean * charging)338 static gboolean _battery_get(Battery * battery, gdouble * level,
339 gboolean * charging)
340 {
341 const char apm[] = "/proc/apm";
342 char buf[80];
343 ssize_t buf_cnt;
344 double d;
345 unsigned int u;
346 unsigned int x = 0;
347 int i;
348 int b;
349
350 *charging = FALSE;
351 if(battery->fd < 0 && (battery->fd = open(apm, O_RDONLY)) < 0)
352 {
353 error_set("%s: %s: %s", applet.name, apm, strerror(errno));
354 *level = 0.0 / 0.0;
355 return TRUE;
356 }
357 errno = ENODATA;
358 if((buf_cnt = read(battery->fd, buf, sizeof(buf))) <= 0)
359 {
360 error_set("%s: %s: %s", applet.name, apm, strerror(errno));
361 close(battery->fd);
362 battery->fd = -1;
363 *level = 0.0 / 0.0;
364 return TRUE;
365 }
366 buf[--buf_cnt] = '\0';
367 if(sscanf(buf, "%f %f %x %x %x %x %d%% %d min", &d, &d, &u, &x, &u,
368 &u, &b, &i) != 8)
369 {
370 error_set("%s: %s: %s", applet.name, apm, strerror(errno));
371 *level = 0.0 / 0.0;
372 }
373 else
374 *level = b;
375 *charging = (x != 0) ? TRUE : FALSE;
376 close(battery->fd);
377 battery->fd = -1;
378 return TRUE;
379 }
380 #else
381 # warning Unsupported platform: battery is not available
_battery_get(Battery * battery,gdouble * level,gboolean * charging)382 static gboolean _battery_get(Battery * battery, gdouble * level,
383 gboolean * charging)
384 {
385 const gdouble error = 0.0 / 0.0;
386 PanelAppletHelper * helper = battery->helper;
387
388 *level = error;
389 *charging = FALSE;
390 error_set("%s: %s", applet.name, strerror(ENOSYS));
391 return 0.0 / 0.0;
392 }
393 #endif
394
395
396 /* battery_set */
397 static void _set_image(Battery * battery, BatteryLevel level,
398 gboolean charging);
399
_battery_set(Battery * battery,gdouble value,gboolean charging)400 static void _battery_set(Battery * battery, gdouble value, gboolean charging)
401 {
402 char buf[256];
403
404 snprintf(buf, sizeof(buf), "%.0f%% ", value);
405 /* XXX only show when necessary? */
406 if(value >= 0.0 && value <= 100.0)
407 {
408 gtk_widget_show(battery->box);
409 if(battery->progress != NULL)
410 {
411 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(
412 battery->progress),
413 value / 100.0);
414 gtk_widget_show(battery->progress);
415 }
416 }
417 else if(battery->progress != NULL)
418 gtk_widget_hide(battery->progress);
419 if(value < 0.0)
420 {
421 _set_image(battery, BATTERY_LEVEL_UNKNOWN, FALSE);
422 value = 0.0;
423 snprintf(buf, sizeof(buf), "%s", _("Unknown"));
424 }
425 else if(value <= 1.0)
426 _set_image(battery, BATTERY_LEVEL_EMPTY, charging);
427 else if(value <= 10.0)
428 _set_image(battery, BATTERY_LEVEL_CAUTION, charging);
429 else if(value <= 20.0)
430 _set_image(battery, BATTERY_LEVEL_LOW, charging);
431 else if(value <= 75.0)
432 _set_image(battery, BATTERY_LEVEL_GOOD, charging);
433 else if(value <= 100.0)
434 _set_image(battery, BATTERY_LEVEL_FULL, charging);
435 else
436 {
437 _set_image(battery, BATTERY_LEVEL_ERROR, FALSE);
438 value = 0.0;
439 snprintf(buf, sizeof(buf), "%s", _("Error"));
440 }
441 if(battery->label != NULL)
442 gtk_label_set_text(GTK_LABEL(battery->label), buf);
443 if(battery->progress != NULL)
444 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(battery->progress),
445 buf);
446 #if GTK_CHECK_VERSION(2, 12, 0)
447 snprintf(buf, sizeof(buf), _("Battery level: %.0f%%%s"), value,
448 charging ? _(" (charging)") : "");
449 gtk_widget_set_tooltip_text(battery->box, buf);
450 #endif
451 }
452
_set_image(Battery * battery,BatteryLevel level,gboolean charging)453 static void _set_image(Battery * battery, BatteryLevel level, gboolean charging)
454 {
455 char const * icons[BATTERY_LEVEL_COUNT][2] =
456 {
457 { "stock_dialog-question", "stock_dialog-question" },
458 { "battery-missing", "battery-missing" },
459 { "battery-empty", "battery-caution-charging" },
460 { "battery-caution", "battery-caution-charging" },
461 { "battery-low", "battery-low-charging" },
462 { "battery-good", "battery-good-charging" },
463 { "battery-full", "battery-full-charging" }
464 };
465
466 if(battery->level == level && battery->charging == charging)
467 return;
468 battery->level = level;
469 battery->charging = charging;
470 gtk_image_set_from_icon_name(GTK_IMAGE(battery->image),
471 icons[level][charging ? 1 : 0],
472 panel_window_get_icon_size(battery->helper->window));
473 }
474
475
476 /* callbacks */
477 /* battery_on_timeout */
_battery_on_timeout(gpointer data)478 static gboolean _battery_on_timeout(gpointer data)
479 {
480 const gdouble error = 0.0 / 0.0;
481 Battery * battery = data;
482 PanelAppletHelper * helper = battery->helper;
483 gdouble level;
484 gboolean charging;
485 int timeout;
486
487 if(_battery_get(battery, &level, &charging) == FALSE)
488 {
489 helper->error(NULL, error_get(NULL), 1);
490 timeout = 0;
491 }
492 else if(level == error || level < 0.0)
493 {
494 helper->error(NULL, error_get(NULL), 1);
495 timeout = 30000;
496 }
497 else
498 timeout = 5000;
499 _battery_set(battery, level, charging);
500 battery->timeout = (timeout > 0)
501 ? g_timeout_add(timeout, _battery_on_timeout, battery) : 0;
502 return FALSE;
503 }
504