"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, &current_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 (&notification, _("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 (&notification, 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 (&notification, 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 (&notification, 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 (&notification, 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 }