"Fossies" - the Fresh Open Source Software Archive 
Member "cbatticon-1.6.13/cbatticon.c" (27 Apr 2022, 41787 Bytes) of package /linux/privat/cbatticon-1.6.13.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "cbatticon.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
1.6.12_vs_1.6.13.
1 /*
2 * Copyright (C) 2011-2013 Colin Jones
3 * Copyright (C) 2014-2022 Valère Monseur
4 *
5 * Based on code by Matteo Marchesotti
6 * Copyright (C) 2007 Matteo Marchesotti <matteo.marchesotti@fsfe.org>
7 *
8 * cbatticon: a lightweight and fast battery icon that sits in your system tray.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #define CBATTICON_VERSION_NUMBER 1.6.13
25 #define CBATTICON_VERSION_STRING "1.6.13"
26 #define CBATTICON_STRING "cbatticon"
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <glib/gprintf.h>
31 #include <glib/gstdio.h>
32 #include <gtk/gtk.h>
33 #ifdef WITH_NOTIFY
34 #include <libnotify/notify.h>
35 #endif
36
37 #include <errno.h>
38 #include <libintl.h>
39 #include <locale.h>
40 #include <math.h>
41 #include <syslog.h>
42
43 static gint get_options (int argc, char **argv);
44 static gboolean changed_power_supplies (void);
45 static void get_power_supplies (void);
46
47 static gboolean get_sysattr_string (gchar *path, gchar *attribute, gchar **value);
48 static gboolean get_sysattr_double (gchar *path, gchar *attribute, gdouble *value);
49
50 static gboolean get_ac_online (gchar *path, gboolean *online);
51 static gboolean get_battery_present (gchar *path, gboolean *present);
52
53 static gboolean get_battery_status (gint *status);
54
55 static gboolean get_battery_full_capacity (gboolean *use_charge, gdouble *capacity);
56 static gboolean get_battery_remaining_capacity (gboolean use_charge, gdouble *capacity);
57 static gboolean get_battery_remaining_capacity_pct (gdouble *capacity);
58 static gboolean get_battery_current_rate (gboolean use_charge, gdouble *rate);
59
60 static gboolean get_battery_charge (gboolean remaining, gint *percentage, gint *time);
61 static gboolean get_battery_time_estimation (gdouble remaining_capacity, gdouble y, gint *time);
62 static void reset_battery_time_estimation (void);
63
64 static void create_tray_icon (void);
65 static gboolean update_tray_icon (GtkStatusIcon *tray_icon);
66 static void update_tray_icon_status (GtkStatusIcon *tray_icon);
67 static void on_tray_icon_click (GtkStatusIcon *tray_icon, gpointer user_data);
68
69 #ifdef WITH_NOTIFY
70 static void notify_message (NotifyNotification **notification, gchar *summary, gchar *body, gint timeout, NotifyUrgency urgency);
71 #define NOTIFY_MESSAGE(...) notify_message(__VA_ARGS__)
72 #else
73 #define NOTIFY_MESSAGE(...)
74 #endif
75
76 static gchar* get_tooltip_string (gchar *battery, gchar *time);
77 static gchar* get_battery_string (gint state, gint percentage);
78 static gchar* get_time_string (gint minutes);
79 static gchar* get_icon_name (gint state, gint percentage);
80
81 #define SYSFS_PATH "/sys/class/power_supply"
82
83 #define DEFAULT_UPDATE_INTERVAL 5
84 #define DEFAULT_LOW_LEVEL 20
85 #define DEFAULT_CRITICAL_LEVEL 5
86
87 #define STR_LTH 256
88
89 enum {
90 UNKNOWN_ICON = 0,
91 BATTERY_ICON,
92 BATTERY_ICON_SYMBOLIC,
93 BATTERY_ICON_NOTIFICATION
94 };
95
96 enum {
97 MISSING = 0,
98 UNKNOWN,
99 CHARGED,
100 CHARGING,
101 DISCHARGING,
102 NOT_CHARGING,
103 LOW_LEVEL,
104 CRITICAL_LEVEL
105 };
106
107 struct configuration {
108 gboolean display_version;
109 gboolean debug_output;
110 gint update_interval;
111 gint icon_type;
112 gint low_level;
113 gint critical_level;
114 gchar *command_low_level;
115 gchar *command_critical_level;
116 gchar *command_left_click;
117 #ifdef WITH_NOTIFY
118 gboolean hide_notification;
119 #endif
120 gboolean list_icon_types;
121 gboolean list_power_supplies;
122 } configuration = {
123 FALSE,
124 FALSE,
125 DEFAULT_UPDATE_INTERVAL,
126 UNKNOWN_ICON,
127 DEFAULT_LOW_LEVEL,
128 DEFAULT_CRITICAL_LEVEL,
129 NULL,
130 NULL,
131 #ifdef WITH_NOTIFY
132 FALSE,
133 #endif
134 FALSE,
135 FALSE
136 };
137
138 static gchar *battery_suffix = NULL;
139 static gchar *battery_path = NULL;
140 static gchar *ac_path = NULL;
141
142 /*
143 * workaround for limited/bugged batteries/drivers that don't provide current rate
144 * the next 4 variables are used to calculate estimated time
145 */
146
147 static gboolean estimation_needed = FALSE;
148 static gdouble estimation_remaining_capacity = -1;
149 static gint estimation_time = -1;
150 static GTimer *estimation_timer = NULL;
151
152 /*
153 * command line options function
154 */
155
156 static gint get_options (int argc, char **argv)
157 {
158 GError *error = NULL;
159
160 gchar *icon_type_string = NULL;
161 GOptionContext *option_context;
162 GOptionEntry option_entries[] = {
163 { "version" , 'v', 0, G_OPTION_ARG_NONE , &configuration.display_version , N_("Display the version") , NULL },
164 { "debug" , 'd', 0, G_OPTION_ARG_NONE , &configuration.debug_output , N_("Display debug information") , NULL },
165 { "update-interval" , 'u', 0, G_OPTION_ARG_INT , &configuration.update_interval , N_("Set update interval (in seconds)") , NULL },
166 { "icon-type" , 'i', 0, G_OPTION_ARG_STRING, &icon_type_string , N_("Set icon type ('standard', 'notification' or 'symbolic')") , NULL },
167 { "low-level" , 'l', 0, G_OPTION_ARG_INT , &configuration.low_level , N_("Set low battery level (in percent)") , NULL },
168 { "critical-level" , 'r', 0, G_OPTION_ARG_INT , &configuration.critical_level , N_("Set critical battery level (in percent)") , NULL },
169 { "command-low-level" , 'o', 0, G_OPTION_ARG_STRING, &configuration.command_low_level , N_("Command to execute when low battery level is reached") , NULL },
170 { "command-critical-level", 'c', 0, G_OPTION_ARG_STRING, &configuration.command_critical_level, N_("Command to execute when critical battery level is reached"), NULL },
171 { "command-left-click" , 'x', 0, G_OPTION_ARG_STRING, &configuration.command_left_click , N_("Command to execute when left clicking on tray icon") , NULL },
172 #ifdef WITH_NOTIFY
173 { "hide-notification" , 'n', 0, G_OPTION_ARG_NONE , &configuration.hide_notification , N_("Hide the notification popups") , NULL },
174 #endif
175 { "list-icon-types" , 't', 0, G_OPTION_ARG_NONE , &configuration.list_icon_types , N_("List available icon types") , NULL },
176 { "list-power-supplies" , 'p', 0, G_OPTION_ARG_NONE , &configuration.list_power_supplies , N_("List available power supplies (battery and AC)") , NULL },
177 { NULL }
178 };
179
180 option_context = g_option_context_new (_("[BATTERY ID]"));
181 g_option_context_add_main_entries (option_context, option_entries, CBATTICON_STRING);
182
183 if (g_option_context_parse (option_context, &argc, &argv, &error) == FALSE) {
184 g_printerr (_("Cannot parse command line arguments: %s\n"), error->message);
185 g_error_free (error); error = NULL;
186
187 return -1;
188 }
189
190 g_option_context_free (option_context);
191
192 /* option : display the version */
193
194 if (configuration.display_version == TRUE) {
195 g_print (_("cbatticon: a lightweight and fast battery icon that sits in your system tray\n"));
196 g_print (_("version %s\n"), CBATTICON_VERSION_STRING);
197
198 return 0;
199 }
200
201 /* option : list available power supplies (battery and AC) */
202
203 if (configuration.list_power_supplies == TRUE) {
204 g_print (_("List of available power supplies:\n"));
205 get_power_supplies ();
206
207 return 0;
208 }
209
210 /* option : list available icon types */
211
212 gtk_init (&argc, &argv); /* gtk is required as from this point */
213
214 #define HAS_STANDARD_ICON_TYPE gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), "battery-full")
215 #define HAS_NOTIFICATION_ICON_TYPE gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), "notification-battery-100")
216 #define HAS_SYMBOLIC_ICON_TYPE gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), "battery-full-symbolic")
217
218 if (configuration.list_icon_types == TRUE) {
219 g_print (_("List of available icon types:\n"));
220 g_print ("standard\t%s\n" , HAS_STANDARD_ICON_TYPE == TRUE ? _("available") : _("unavailable"));
221 g_print ("notification\t%s\n", HAS_NOTIFICATION_ICON_TYPE == TRUE ? _("available") : _("unavailable"));
222 g_print ("symbolic\t%s\n" , HAS_SYMBOLIC_ICON_TYPE == TRUE ? _("available") : _("unavailable"));
223
224 return 0;
225 }
226
227 /* option : set icon type */
228
229 if (icon_type_string != NULL) {
230 if (g_strcmp0 (icon_type_string, "standard") == 0 && HAS_STANDARD_ICON_TYPE == TRUE)
231 configuration.icon_type = BATTERY_ICON;
232 else if (g_strcmp0 (icon_type_string, "notification") == 0 && HAS_NOTIFICATION_ICON_TYPE == TRUE)
233 configuration.icon_type = BATTERY_ICON_NOTIFICATION;
234 else if (g_strcmp0 (icon_type_string, "symbolic") == 0 && HAS_SYMBOLIC_ICON_TYPE == TRUE)
235 configuration.icon_type = BATTERY_ICON_SYMBOLIC;
236 else g_printerr (_("Unknown icon type: %s\n"), icon_type_string);
237
238 g_free (icon_type_string);
239 }
240
241 if (configuration.icon_type == UNKNOWN_ICON) {
242 if (HAS_STANDARD_ICON_TYPE == TRUE)
243 configuration.icon_type = BATTERY_ICON;
244 else if (HAS_NOTIFICATION_ICON_TYPE == TRUE)
245 configuration.icon_type = BATTERY_ICON_NOTIFICATION;
246 else if (HAS_SYMBOLIC_ICON_TYPE == TRUE)
247 configuration.icon_type = BATTERY_ICON_SYMBOLIC;
248 else g_printerr (_("No icon type found!\n"));
249 }
250
251 /* option : update interval */
252
253 if (configuration.update_interval <= 0) {
254 configuration.update_interval = DEFAULT_UPDATE_INTERVAL;
255 g_printerr (_("Invalid update interval! It has been reset to default (%d seconds)\n"), DEFAULT_UPDATE_INTERVAL);
256 }
257
258 /* option : low and critical levels */
259
260 if (configuration.low_level < 0 || configuration.low_level > 100) {
261 configuration.low_level = DEFAULT_LOW_LEVEL;
262 g_printerr (_("Invalid low level! It has been reset to default (%d percent)\n"), DEFAULT_LOW_LEVEL);
263 }
264
265 if (configuration.critical_level < 0 || configuration.critical_level > 100) {
266 configuration.critical_level = DEFAULT_CRITICAL_LEVEL;
267 g_printerr (_("Invalid critical level! It has been reset to default (%d percent)\n"), DEFAULT_CRITICAL_LEVEL);
268 }
269
270 if (configuration.critical_level > configuration.low_level) {
271 configuration.critical_level = DEFAULT_CRITICAL_LEVEL;
272 configuration.low_level = DEFAULT_LOW_LEVEL;
273 g_printerr (_("Critical level is higher than low level! They have been reset to default\n"));
274 }
275
276 return 1;
277 }
278
279 /*
280 * sysfs functions
281 */
282
283 static gboolean changed_power_supplies (void)
284 {
285 GDir *directory;
286 const gchar *file;
287
288 static gint old_num_ps = 0;
289 static gint old_total_ps = 0;
290 gint num_ps = 0;
291 gint total_ps = 0;
292 gboolean power_supplies_changed;
293
294 directory = g_dir_open (SYSFS_PATH, 0, NULL);
295 if (directory != NULL) {
296 file = g_dir_read_name (directory);
297 while (file != NULL) {
298 if (ac_path != NULL && g_str_has_suffix (ac_path, file) == TRUE) {
299 num_ps++;
300 }
301
302 if (battery_path != NULL && g_str_has_suffix (battery_path, file) == TRUE) {
303 num_ps++;
304 }
305
306 total_ps++;
307
308 file = g_dir_read_name (directory);
309 }
310
311 g_dir_close (directory);
312 }
313
314 power_supplies_changed = (num_ps != old_num_ps) || (total_ps != old_total_ps);
315
316 if (configuration.debug_output == TRUE && power_supplies_changed == TRUE) {
317 g_printf ("power supplies changed: old total/num ps=%d/%d, new total/num ps=%d/%d\n",
318 old_total_ps, old_num_ps, total_ps, num_ps);
319 }
320
321 old_num_ps = num_ps;
322 old_total_ps = total_ps;
323
324 return power_supplies_changed;
325 }
326
327 static void get_power_supplies (void)
328 {
329 GError *error = NULL;
330
331 GDir *directory;
332 const gchar *file;
333 gchar *path;
334 gchar *sysattr_value;
335 gboolean sysattr_status;
336
337 /* reset power supplies information */
338
339 g_free (battery_path); battery_path = NULL;
340 g_free (ac_path); ac_path = NULL;
341
342 estimation_needed = FALSE;
343 estimation_remaining_capacity = -1;
344 estimation_time = -1;
345 if (estimation_timer != NULL) {
346 g_timer_stop (estimation_timer);
347 g_timer_destroy (estimation_timer);
348 estimation_timer = NULL;
349 }
350
351 /* retrieve power supplies information */
352
353 directory = g_dir_open (SYSFS_PATH, 0, &error);
354 if (directory != NULL) {
355 file = g_dir_read_name (directory);
356 while (file != NULL) {
357 path = g_build_filename (SYSFS_PATH, file, NULL);
358 sysattr_status = get_sysattr_string (path, "type", &sysattr_value);
359 if (sysattr_status == TRUE) {
360
361 /* process battery */
362
363 if (g_str_has_prefix (sysattr_value, "Battery") == TRUE &&
364 get_battery_present (path, NULL) == TRUE) {
365 if (configuration.list_power_supplies == TRUE) {
366 gchar *power_supply_id = g_path_get_basename (path);
367 g_print (_("type: %-*.*s\tid: %-*.*s\tpath: %s\n"), 12, 12, _("Battery"), 12, 12, power_supply_id, path);
368 g_free (power_supply_id);
369 }
370
371 if (battery_path == NULL) {
372 if (battery_suffix == NULL ||
373 g_str_has_suffix (path, battery_suffix) == TRUE) {
374 battery_path = g_strdup (path);
375
376 /* workaround for limited/bugged batteries/drivers */
377 /* that don't provide current rate */
378
379 if (get_battery_current_rate (FALSE, NULL) == FALSE &&
380 get_battery_current_rate (TRUE, NULL) == FALSE) {
381 estimation_needed = TRUE;
382 estimation_timer = g_timer_new ();
383
384 if (configuration.debug_output == TRUE) {
385 g_printf ("workaround: current rate is not available, estimating rate\n");
386 }
387 }
388
389 if (configuration.debug_output == TRUE) {
390 g_printf ("battery path: %s\n", battery_path);
391 }
392 }
393 }
394 }
395
396 /* process AC */
397
398 if (g_str_has_prefix (sysattr_value, "Mains") == TRUE &&
399 get_ac_online (path, NULL) == TRUE) {
400 if (configuration.list_power_supplies == TRUE) {
401 gchar *power_supply_id = g_path_get_basename (path);
402 g_print (_("type: %-*.*s\tid: %-*.*s\tpath: %s\n"), 12, 12, _("AC"), 12, 12, power_supply_id, path);
403 g_free (power_supply_id);
404 }
405
406 if (ac_path == NULL) {
407 ac_path = g_strdup (path);
408
409 if (configuration.debug_output == TRUE) {
410 g_printf ("ac path: %s\n", ac_path);
411 }
412 }
413 }
414
415 g_free (sysattr_value);
416 }
417
418 g_free (path);
419 file = g_dir_read_name (directory);
420 }
421
422 g_dir_close (directory);
423 } else {
424 g_printerr (_("Cannot open sysfs directory: %s (%s)\n"), SYSFS_PATH, error->message);
425 g_error_free (error); error = NULL;
426 return;
427 }
428
429 if (configuration.list_power_supplies == FALSE && battery_path == NULL) {
430 if (battery_suffix != NULL) {
431 g_printerr (_("No battery with suffix %s found!\n"), battery_suffix);
432 return;
433 }
434
435 if (ac_path == NULL) {
436 g_printerr (_("No battery nor AC power supply found!\n"));
437 return;
438 }
439 }
440 }
441
442 static gboolean get_sysattr_string (gchar *path, gchar *attribute, gchar **value)
443 {
444 gchar *sysattr_filename;
445 gboolean sysattr_status;
446
447 g_return_val_if_fail (path != NULL, FALSE);
448 g_return_val_if_fail (attribute != NULL, FALSE);
449 g_return_val_if_fail (value != NULL, FALSE);
450
451 sysattr_filename = g_build_filename (path, attribute, NULL);
452 sysattr_status = g_file_get_contents (sysattr_filename, value, NULL, NULL);
453 g_free (sysattr_filename);
454
455 return sysattr_status;
456 }
457
458 static gboolean get_sysattr_double (gchar *path, gchar *attribute, gdouble *value)
459 {
460 gchar *sysattr_filename, *sysattr_value;
461 gboolean sysattr_status;
462
463 g_return_val_if_fail (path != NULL, FALSE);
464 g_return_val_if_fail (attribute != NULL, FALSE);
465
466 sysattr_filename = g_build_filename (path, attribute, NULL);
467 sysattr_status = g_file_get_contents (sysattr_filename, &sysattr_value, NULL, NULL);
468 g_free (sysattr_filename);
469
470 if (sysattr_status == TRUE) {
471 gdouble double_value = g_ascii_strtod (sysattr_value, NULL);
472
473 if (errno != 0 || double_value < 0.01) {
474 sysattr_status = FALSE;
475 }
476
477 if (value != NULL) {
478 *value = double_value;
479 }
480
481 g_free (sysattr_value);
482 }
483
484 return sysattr_status;
485 }
486
487 static gboolean get_ac_online (gchar *path, gboolean *online)
488 {
489 gchar *sysattr_value;
490 gboolean sysattr_status;
491
492 if (path == NULL) {
493 return FALSE;
494 }
495
496 sysattr_status = get_sysattr_string (path, "online", &sysattr_value);
497 if (sysattr_status == TRUE) {
498 if (online != NULL) {
499 *online = g_str_has_prefix (sysattr_value, "1") ? TRUE : FALSE;
500 }
501
502 if (configuration.debug_output == TRUE) {
503 g_printf ("ac online: %s", sysattr_value);
504 }
505
506 g_free (sysattr_value);
507 }
508
509 return sysattr_status;
510 }
511
512 static gboolean get_battery_present (gchar *path, gboolean *present)
513 {
514 gchar *sysattr_value;
515 gboolean sysattr_status;
516
517 if (path == NULL) {
518 return FALSE;
519 }
520
521 sysattr_status = get_sysattr_string (path, "present", &sysattr_value);
522 if (sysattr_status == TRUE) {
523 if (present != NULL) {
524 *present = g_str_has_prefix (sysattr_value, "1") ? TRUE : FALSE;
525 }
526
527 if (configuration.debug_output == TRUE) {
528 g_printf ("battery present: %s", sysattr_value);
529 }
530
531 g_free (sysattr_value);
532 }
533
534 return sysattr_status;
535 }
536
537 static gboolean get_battery_status (gint *status)
538 {
539 gchar *sysattr_value;
540 gboolean sysattr_status;
541
542 g_return_val_if_fail (status != NULL, FALSE);
543
544 sysattr_status = get_sysattr_string (battery_path, "status", &sysattr_value);
545 if (sysattr_status == TRUE) {
546 if (g_str_has_prefix (sysattr_value, "Charging") == TRUE)
547 *status = CHARGING;
548 else if (g_str_has_prefix (sysattr_value, "Discharging") == TRUE)
549 *status = DISCHARGING;
550 else if (g_str_has_prefix (sysattr_value, "Not charging") == TRUE)
551 *status = NOT_CHARGING;
552 else if (g_str_has_prefix (sysattr_value, "Full") == TRUE)
553 *status = CHARGED;
554 else
555 *status = UNKNOWN;
556
557 if (configuration.debug_output == TRUE) {
558 g_printf ("battery status: %d - %s", *status, sysattr_value);
559 }
560
561 g_free (sysattr_value);
562 }
563
564 return sysattr_status;
565 }
566
567 static gboolean get_battery_full_capacity (gboolean *use_charge, gdouble *capacity)
568 {
569 gboolean sysattr_status;
570
571 g_return_val_if_fail (use_charge != NULL, FALSE);
572 g_return_val_if_fail (capacity != NULL, FALSE);
573
574 sysattr_status = get_sysattr_double (battery_path, "energy_full", capacity);
575 *use_charge = FALSE;
576
577 if (sysattr_status == FALSE) {
578 sysattr_status = get_sysattr_double (battery_path, "charge_full", capacity);
579 *use_charge = TRUE;
580 }
581
582 return sysattr_status;
583 }
584
585 static gboolean get_battery_remaining_capacity (gboolean use_charge, gdouble *capacity)
586 {
587 g_return_val_if_fail (capacity != NULL, FALSE);
588
589 if (use_charge == FALSE) {
590 return get_sysattr_double (battery_path, "energy_now", capacity);
591 } else {
592 return get_sysattr_double (battery_path, "charge_now", capacity);
593 }
594 }
595
596 static gboolean get_battery_remaining_capacity_pct (gdouble *capacity)
597 {
598 g_return_val_if_fail (capacity != NULL, FALSE);
599
600 return get_sysattr_double (battery_path, "capacity", capacity);
601 }
602
603 static gboolean get_battery_current_rate (gboolean use_charge, gdouble *rate)
604 {
605 if (use_charge == FALSE) {
606 return get_sysattr_double (battery_path, "power_now", rate);
607 } else {
608 return get_sysattr_double (battery_path, "current_now", rate);
609 }
610 }
611
612 /*
613 * computation functions
614 */
615
616 static gboolean get_battery_charge (gboolean remaining, gint *percentage, gint *time)
617 {
618 gdouble full_capacity, remaining_capacity, current_rate;
619 gboolean use_charge;
620
621 g_return_val_if_fail (percentage != NULL, FALSE);
622
623 if (get_battery_full_capacity (&use_charge, &full_capacity) == FALSE) {
624 if (configuration.debug_output == TRUE) {
625 g_printf ("full capacity: %s\n", "unavailable");
626 }
627
628 return FALSE;
629 }
630
631 if (get_battery_remaining_capacity (use_charge, &remaining_capacity) == FALSE) {
632 if (get_battery_remaining_capacity_pct (&remaining_capacity) == FALSE) {
633 if (configuration.debug_output == TRUE) {
634 g_printf ("remaining capacity: %s\n", "unavailable");
635 }
636
637 return FALSE;
638 }
639
640 /* remaining capacity is percentage, compute the actual remaining capacity */
641 remaining_capacity *= full_capacity / 100.0;
642 }
643
644 *percentage = (gint)fmin (floor (remaining_capacity / full_capacity * 100.0), 100.0);
645
646 if (time == NULL) {
647 return TRUE;
648 }
649
650 if (estimation_needed == TRUE) {
651 if (remaining == TRUE) {
652 return get_battery_time_estimation (remaining_capacity, 0, time);
653 } else {
654 return get_battery_time_estimation (remaining_capacity, full_capacity, time);
655 }
656 }
657
658 if (get_battery_current_rate (use_charge, ¤t_rate) == FALSE) {
659 if (configuration.debug_output == TRUE) {
660 g_printf ("current rate: %s\n", "unavailable");
661 }
662
663 return FALSE;
664 }
665
666 if (remaining == TRUE) {
667 *time = (gint)(remaining_capacity / current_rate * 60.0);
668 } else {
669 *time = (gint)((full_capacity - remaining_capacity) / current_rate * 60.0);
670 }
671
672 return TRUE;
673 }
674
675 static gboolean get_battery_time_estimation (gdouble remaining_capacity, gdouble y, gint *time)
676 {
677 if (estimation_remaining_capacity == -1) {
678 estimation_remaining_capacity = remaining_capacity;
679 }
680
681 /*
682 * y = mx + b ... x = (y - b) / m
683 * solving for when y = 0 (discharging) or full_capacity (charging)
684 */
685
686 if (remaining_capacity != estimation_remaining_capacity) {
687 gdouble estimation_elapsed = g_timer_elapsed (estimation_timer, NULL);
688 gdouble estimation_current_rate = (remaining_capacity - estimation_remaining_capacity) / estimation_elapsed;
689 gdouble estimation_seconds = (y - remaining_capacity) / estimation_current_rate;
690
691 *time = (gint)(estimation_seconds / 60.0);
692
693 estimation_remaining_capacity = remaining_capacity;
694 estimation_time = *time;
695 g_timer_start (estimation_timer);
696 } else {
697 *time = estimation_time;
698 }
699
700 return TRUE;
701 }
702
703 static void reset_battery_time_estimation (void)
704 {
705 estimation_remaining_capacity = -1;
706 estimation_time = -1;
707 g_timer_start (estimation_timer);
708 }
709
710 /*
711 * tray icon functions
712 */
713
714 static void create_tray_icon (void)
715 {
716 GtkStatusIcon *tray_icon = gtk_status_icon_new ();
717
718 gtk_status_icon_set_tooltip_text (tray_icon, CBATTICON_STRING);
719 gtk_status_icon_set_visible (tray_icon, TRUE);
720
721 update_tray_icon (tray_icon);
722 g_timeout_add_seconds (configuration.update_interval, (GSourceFunc)update_tray_icon, (gpointer)tray_icon);
723 g_signal_connect (G_OBJECT (tray_icon), "activate", G_CALLBACK (on_tray_icon_click), NULL);
724 }
725
726 static gboolean update_tray_icon (GtkStatusIcon *tray_icon)
727 {
728 g_return_val_if_fail (tray_icon != NULL, FALSE);
729
730 update_tray_icon_status (tray_icon);
731
732 return TRUE;
733 }
734
735 static void update_tray_icon_status (GtkStatusIcon *tray_icon)
736 {
737 GError *error = NULL;
738
739 gboolean battery_present = FALSE;
740 gboolean ac_online = FALSE;
741
742 gint battery_status = -1;
743 static gint old_battery_status = -1;
744
745 /* battery statuses: */
746 /* not present => ac_only, battery_missing */
747 /* present => charging, charged, discharging, unknown */
748 /* (present and not present are exclusive) */
749
750 static gboolean ac_only = FALSE;
751 static gboolean battery_low = FALSE;
752 static gboolean battery_critical = FALSE;
753 static gboolean spawn_command_low = FALSE;
754 static gboolean spawn_command_critical = FALSE;
755
756 gint percentage, time;
757 gchar *battery_string, *time_string;
758
759 #ifdef WITH_NOTIFY
760 static NotifyNotification *notification = NULL;
761 #endif
762
763 /* update power supplies */
764
765 if (changed_power_supplies () == TRUE)
766 {
767 get_power_supplies ();
768
769 old_battery_status = -1;
770
771 ac_only = FALSE;
772 battery_low = FALSE;
773 battery_critical = FALSE;
774 spawn_command_low = FALSE;
775 spawn_command_critical = FALSE;
776 }
777
778 /* update tray icon for AC only */
779
780 if (battery_path == NULL) {
781 if (ac_only == FALSE) {
782 ac_only = TRUE;
783
784 NOTIFY_MESSAGE (¬ification, _("AC only, no battery!"), NULL, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_NORMAL);
785
786 gtk_status_icon_set_tooltip_text (tray_icon, _("AC only, no battery!"));
787 gtk_status_icon_set_from_icon_name (tray_icon, "ac-adapter");
788 }
789
790 return;
791 }
792
793 /* update tray icon for battery */
794
795 if (get_battery_present (battery_path, &battery_present) == FALSE) {
796 return;
797 }
798
799 if (battery_present == FALSE) {
800 battery_status = MISSING;
801 } else {
802 if (get_battery_status (&battery_status) == FALSE) {
803 return;
804 }
805
806 /* workaround for limited/bugged batteries/drivers */
807 /* that unduly return unknown status */
808
809 if (battery_status == UNKNOWN && get_ac_online (ac_path, &ac_online) == TRUE) {
810 if (ac_online == TRUE) {
811 battery_status = CHARGING;
812
813 if (get_battery_charge (FALSE, &percentage, NULL) == TRUE && percentage >= 99) {
814 battery_status = CHARGED;
815 }
816 } else {
817 battery_status = DISCHARGING;
818 }
819 }
820 }
821
822 #define HANDLE_BATTERY_STATUS(PCT,TIM,EXP,URG) \
823 \
824 percentage = PCT; \
825 \
826 battery_string = get_battery_string (battery_status, percentage); \
827 time_string = get_time_string (TIM); \
828 \
829 if (old_battery_status != battery_status) { \
830 old_battery_status = battery_status; \
831 NOTIFY_MESSAGE (¬ification, battery_string, time_string, EXP, URG); \
832 } \
833 \
834 gtk_status_icon_set_tooltip_text (tray_icon, get_tooltip_string (battery_string, time_string)); \
835 gtk_status_icon_set_from_icon_name (tray_icon, get_icon_name (battery_status, percentage));
836
837 switch (battery_status) {
838 case MISSING:
839 HANDLE_BATTERY_STATUS (0, -1, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_NORMAL)
840 break;
841
842 case UNKNOWN:
843 HANDLE_BATTERY_STATUS (0, -1, NOTIFY_EXPIRES_DEFAULT, NOTIFY_URGENCY_NORMAL)
844 break;
845
846 case CHARGED:
847 HANDLE_BATTERY_STATUS (100, -1, NOTIFY_EXPIRES_DEFAULT, NOTIFY_URGENCY_NORMAL)
848 break;
849
850 case CHARGING:
851 if (old_battery_status != CHARGING && estimation_needed == TRUE) {
852 reset_battery_time_estimation ();
853 }
854
855 if (get_battery_charge (FALSE, &percentage, &time) == FALSE) {
856 return;
857 }
858
859 HANDLE_BATTERY_STATUS (percentage, time, NOTIFY_EXPIRES_DEFAULT, NOTIFY_URGENCY_NORMAL)
860 break;
861
862 case DISCHARGING:
863 case NOT_CHARGING:
864 if (old_battery_status != DISCHARGING && estimation_needed == TRUE) {
865 reset_battery_time_estimation ();
866 }
867
868 if (get_battery_charge (TRUE, &percentage, &time) == FALSE) {
869 return;
870 }
871
872 battery_string = get_battery_string (battery_status, percentage);
873 time_string = get_time_string (time);
874
875 if (old_battery_status != DISCHARGING) {
876 old_battery_status = DISCHARGING;
877 NOTIFY_MESSAGE (¬ification, battery_string, time_string, NOTIFY_EXPIRES_DEFAULT, NOTIFY_URGENCY_NORMAL);
878
879 battery_low = FALSE;
880 battery_critical = FALSE;
881 spawn_command_low = FALSE;
882 spawn_command_critical = FALSE;
883 }
884
885 if (battery_low == FALSE && percentage <= configuration.low_level) {
886 battery_low = TRUE;
887
888 battery_string = get_battery_string (LOW_LEVEL, percentage);
889 NOTIFY_MESSAGE (¬ification, battery_string, time_string, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_NORMAL);
890
891 spawn_command_low = TRUE;
892 }
893
894 if (battery_critical == FALSE && percentage <= configuration.critical_level) {
895 battery_critical = TRUE;
896
897 battery_string = get_battery_string (CRITICAL_LEVEL, percentage);
898 NOTIFY_MESSAGE (¬ification, battery_string, time_string, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_CRITICAL);
899
900 spawn_command_critical = TRUE;
901 }
902
903 gtk_status_icon_set_tooltip_text (tray_icon, get_tooltip_string (battery_string, time_string));
904 gtk_status_icon_set_from_icon_name (tray_icon, get_icon_name (battery_status, percentage));
905
906 if (spawn_command_low == TRUE) {
907 spawn_command_low = FALSE;
908
909 if (configuration.command_low_level != NULL) {
910 syslog (LOG_CRIT, _("Spawning low battery level command in 5 seconds: %s"), configuration.command_low_level);
911 g_usleep (G_USEC_PER_SEC * 5);
912
913 if (get_battery_status (&battery_status) == TRUE) {
914 if (battery_status != DISCHARGING && battery_status != NOT_CHARGING) {
915 syslog (LOG_NOTICE, _("Skipping low battery level command, no longer discharging"));
916 return;
917 }
918 }
919
920 if (g_spawn_command_line_async (configuration.command_low_level, &error) == FALSE) {
921 syslog (LOG_CRIT, _("Cannot spawn low battery level command: %s\n"), error->message);
922
923 g_printerr (_("Cannot spawn low battery level command: %s\n"), error->message);
924 g_error_free (error); error = NULL;
925
926 #ifdef WITH_NOTIFY
927 static NotifyNotification *spawn_notification = NULL;
928 NOTIFY_MESSAGE (&spawn_notification, _("Cannot spawn low battery level command!"), configuration.command_low_level, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_CRITICAL);
929 #endif
930 }
931 }
932 }
933
934 if (spawn_command_critical == TRUE) {
935 spawn_command_critical = FALSE;
936
937 if (configuration.command_critical_level != NULL) {
938 syslog (LOG_CRIT, _("Spawning critical battery level command in 30 seconds: %s"), configuration.command_critical_level);
939 g_usleep (G_USEC_PER_SEC * 30);
940
941 if (get_battery_status (&battery_status) == TRUE) {
942 if (battery_status != DISCHARGING && battery_status != NOT_CHARGING) {
943 syslog (LOG_NOTICE, _("Skipping critical battery level command, no longer discharging"));
944 return;
945 }
946 }
947
948 if (g_spawn_command_line_async (configuration.command_critical_level, &error) == FALSE) {
949 syslog (LOG_CRIT, _("Cannot spawn critical battery level command: %s\n"), error->message);
950
951 g_printerr (_("Cannot spawn critical battery level command: %s\n"), error->message);
952 g_error_free (error); error = NULL;
953
954 #ifdef WITH_NOTIFY
955 static NotifyNotification *spawn_notification = NULL;
956 NOTIFY_MESSAGE (&spawn_notification, _("Cannot spawn critical battery level command!"), configuration.command_critical_level, NOTIFY_EXPIRES_NEVER, NOTIFY_URGENCY_CRITICAL);
957 #endif
958 }
959 }
960 }
961 break;
962 }
963 }
964
965 static void on_tray_icon_click (GtkStatusIcon *tray_icon, gpointer user_data)
966 {
967 GError *error = NULL;
968
969 if (configuration.command_left_click != NULL) {
970 if (g_spawn_command_line_async (configuration.command_left_click, &error) == FALSE) {
971 syslog (LOG_ERR, _("Cannot spawn left click command: %s\n"), error->message);
972
973 g_printerr (_("Cannot spawn left click command: %s\n"), error->message);
974 g_error_free (error); error = NULL;
975
976 #ifdef WITH_NOTIFY
977 static NotifyNotification *spawn_notification = NULL;
978 NOTIFY_MESSAGE (&spawn_notification, _("Cannot spawn left click command!"), configuration.command_left_click, NOTIFY_EXPIRES_DEFAULT, NOTIFY_URGENCY_CRITICAL);
979 #endif
980 }
981 }
982 }
983
984 #ifdef WITH_NOTIFY
985 static void notify_message (NotifyNotification **notification, gchar *summary, gchar *body, gint timeout, NotifyUrgency urgency)
986 {
987 g_return_if_fail (notification != NULL);
988 g_return_if_fail (summary != NULL);
989
990 if (configuration.hide_notification == TRUE) {
991 return;
992 }
993
994 if (*notification == NULL) {
995 #if NOTIFY_CHECK_VERSION (0, 7, 0)
996 *notification = notify_notification_new (summary, body, NULL);
997 #else
998 *notification = notify_notification_new (summary, body, NULL, NULL);
999 #endif
1000 } else {
1001 notify_notification_update (*notification, summary, body, NULL);
1002 }
1003
1004 notify_notification_set_timeout (*notification, timeout);
1005 notify_notification_set_urgency (*notification, urgency);
1006 notify_notification_show (*notification, NULL);
1007 }
1008 #endif
1009
1010 static gchar* get_tooltip_string (gchar *battery, gchar *time)
1011 {
1012 static gchar tooltip_string[STR_LTH];
1013
1014 tooltip_string[0] = '\0';
1015
1016 g_return_val_if_fail (battery != NULL, tooltip_string);
1017
1018 g_strlcpy (tooltip_string, battery, STR_LTH);
1019
1020 if (configuration.debug_output == TRUE) {
1021 g_printf ("tooltip: %s\n", battery);
1022 }
1023
1024 if (time != NULL) {
1025 g_strlcat (tooltip_string, "\n", STR_LTH);
1026 g_strlcat (tooltip_string, time, STR_LTH);
1027
1028 if (configuration.debug_output == TRUE) {
1029 g_printf ("tooltip: %s\n", time);
1030 }
1031 }
1032
1033 return tooltip_string;
1034 }
1035
1036 static gchar* get_battery_string (gint state, gint percentage)
1037 {
1038 static gchar battery_string[STR_LTH];
1039
1040 switch (state) {
1041 case MISSING:
1042 g_strlcpy (battery_string, _("Battery is missing!"), STR_LTH);
1043 break;
1044
1045 case UNKNOWN:
1046 g_strlcpy (battery_string, _("Battery status is unknown!"), STR_LTH);
1047 break;
1048
1049 case CHARGED:
1050 g_strlcpy (battery_string, _("Battery is charged!"), STR_LTH);
1051 break;
1052
1053 case DISCHARGING:
1054 g_snprintf (battery_string, STR_LTH, _("Battery is discharging (%i%% remaining)"), percentage);
1055 break;
1056
1057 case NOT_CHARGING:
1058 g_snprintf (battery_string, STR_LTH, _("Battery is not charging (%i%% remaining)"), percentage);
1059 break;
1060
1061 case LOW_LEVEL:
1062 g_snprintf (battery_string, STR_LTH, _("Battery level is low! (%i%% remaining)"), percentage);
1063 break;
1064
1065 case CRITICAL_LEVEL:
1066 g_snprintf (battery_string, STR_LTH, _("Battery level is critical! (%i%% remaining)"), percentage);
1067 break;
1068
1069 case CHARGING:
1070 g_snprintf (battery_string, STR_LTH, _("Battery is charging (%i%%)"), percentage);
1071 break;
1072
1073 default:
1074 battery_string[0] = '\0';
1075 break;
1076 }
1077
1078 if (configuration.debug_output == TRUE) {
1079 g_printf ("battery string: %s\n", battery_string);
1080 }
1081
1082 return battery_string;
1083 }
1084
1085 static gchar* get_time_string (gint minutes)
1086 {
1087 static gchar time_string[STR_LTH];
1088 static gchar minutes_string[STR_LTH];
1089 gint hours;
1090
1091 if (minutes < 0) {
1092 return NULL;
1093 }
1094
1095 hours = minutes / 60;
1096 minutes = minutes % 60;
1097
1098 if (hours > 0) {
1099 g_sprintf (minutes_string, g_dngettext (NULL, "%d minute", "%d minutes", minutes), minutes);
1100 g_sprintf (time_string, g_dngettext (NULL, "%d hour, %s remaining", "%d hours, %s remaining", hours), hours, minutes_string);
1101 } else {
1102 g_sprintf (time_string, g_dngettext (NULL, "%d minute remaining", "%d minutes remaining", minutes), minutes);
1103 }
1104
1105 if (configuration.debug_output == TRUE) {
1106 g_printf ("time string: %s\n", time_string);
1107 }
1108
1109 return time_string;
1110 }
1111
1112 static gchar* get_icon_name (gint state, gint percentage)
1113 {
1114 static gchar icon_name[STR_LTH];
1115
1116 if (configuration.icon_type == BATTERY_ICON_NOTIFICATION) {
1117 g_strlcpy (icon_name, "notification-battery", STR_LTH);
1118 } else {
1119 g_strlcpy (icon_name, "battery", STR_LTH);
1120 }
1121
1122 if (state == MISSING || state == UNKNOWN) {
1123 if (configuration.icon_type == BATTERY_ICON_NOTIFICATION) {
1124 g_strlcat (icon_name, "-empty", STR_LTH);
1125 } else {
1126 g_strlcat (icon_name, "-missing", STR_LTH);
1127 }
1128 } else {
1129 if (configuration.icon_type == BATTERY_ICON_NOTIFICATION) {
1130 if (percentage <= 20) g_strlcat (icon_name, "-020", STR_LTH);
1131 else if (percentage <= 40) g_strlcat (icon_name, "-040", STR_LTH);
1132 else if (percentage <= 60) g_strlcat (icon_name, "-060", STR_LTH);
1133 else if (percentage <= 80) g_strlcat (icon_name, "-080", STR_LTH);
1134 else g_strlcat (icon_name, "-100", STR_LTH);
1135
1136 if (state == CHARGING) g_strlcat (icon_name, "-plugged", STR_LTH);
1137 else if (state == CHARGED) g_strlcat (icon_name, "-plugged", STR_LTH);
1138 } else {
1139 if (percentage <= 20) g_strlcat (icon_name, "-caution", STR_LTH);
1140 else if (percentage <= 40) g_strlcat (icon_name, "-low", STR_LTH);
1141 else if (percentage <= 80) g_strlcat (icon_name, "-good", STR_LTH);
1142 else g_strlcat (icon_name, "-full", STR_LTH);
1143
1144 if (state == CHARGING) g_strlcat (icon_name, "-charging", STR_LTH);
1145 else if (state == CHARGED) g_strlcat (icon_name, "-charged", STR_LTH);
1146 }
1147 }
1148
1149 if (configuration.icon_type == BATTERY_ICON_SYMBOLIC) {
1150 g_strlcat (icon_name, "-symbolic", STR_LTH);
1151 }
1152
1153 if (configuration.debug_output == TRUE) {
1154 g_printf ("icon name: %s\n", icon_name);
1155 }
1156
1157 return icon_name;
1158 }
1159
1160 int main (int argc, char **argv)
1161 {
1162 gint ret;
1163
1164 setlocale (LC_ALL, "");
1165 bindtextdomain (CBATTICON_STRING, NLSDIR);
1166 bind_textdomain_codeset (CBATTICON_STRING, "UTF-8");
1167 textdomain (CBATTICON_STRING);
1168
1169 ret = get_options (argc, argv);
1170 if (ret <= 0) {
1171 return ret;
1172 }
1173
1174 #ifdef WITH_NOTIFY
1175 if (configuration.hide_notification == FALSE) {
1176 if (notify_init (CBATTICON_STRING) == FALSE) {
1177 return -1;
1178 }
1179 }
1180 #endif
1181
1182 if (argc > 1) {
1183 battery_suffix = argv[1];
1184 }
1185
1186 get_power_supplies();
1187 create_tray_icon ();
1188 gtk_main();
1189
1190 return 0;
1191 }