summaryrefslogtreecommitdiff
path: root/battstat/battstat-hal.c
diff options
context:
space:
mode:
Diffstat (limited to 'battstat/battstat-hal.c')
-rw-r--r--battstat/battstat-hal.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/battstat/battstat-hal.c b/battstat/battstat-hal.c
new file mode 100644
index 00000000..af9428db
--- /dev/null
+++ b/battstat/battstat-hal.c
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2005 by Ryan Lortie <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#include <config.h>
+
+#ifdef HAVE_HAL
+
+#include <dbus/dbus-glib-lowlevel.h>
+#include <libhal.h>
+#include <string.h>
+#include <math.h>
+
+#include "battstat-hal.h"
+
+static LibHalContext *battstat_hal_ctx;
+static GSList *batteries;
+static GSList *adaptors;
+static void (*status_updated_callback) (void);
+
+struct battery_status
+{
+ int present;
+ int current_level, design_level, full_level;
+ int remaining_time;
+ int charging, discharging;
+ int rate;
+};
+
+struct battery_info
+{
+ char *udi; /* must be first member */
+ struct battery_status status;
+};
+
+struct adaptor_info
+{
+ char *udi; /* must be first member */
+ int present;
+};
+
+static gboolean
+battery_update_property( LibHalContext *ctx, struct battery_info *battery,
+ const char *key )
+{
+ DBusError error;
+ gboolean ret;
+
+ ret = TRUE;
+ dbus_error_init( &error );
+
+ if( !strcmp( key, "battery.present" ) )
+ battery->status.present =
+ libhal_device_get_property_bool( ctx, battery->udi, key, &error );
+
+ if( !strcmp( key, "battery.charge_level.current" ) )
+ battery->status.current_level =
+ libhal_device_get_property_int( ctx, battery->udi, key, &error );
+
+ if( !strcmp( key, "battery.charge_level.rate" ) )
+ battery->status.rate =
+ libhal_device_get_property_int( ctx, battery->udi, key, &error );
+
+ else if( !strcmp( key, "battery.charge_level.design" ) )
+ battery->status.design_level =
+ libhal_device_get_property_int( ctx, battery->udi, key, &error );
+
+ else if( !strcmp( key, "battery.charge_level.last_full" ) )
+ battery->status.full_level =
+ libhal_device_get_property_int( ctx, battery->udi, key, &error );
+
+ else if( !strcmp( key, "battery.remaining_time" ) )
+ battery->status.remaining_time =
+ libhal_device_get_property_int( ctx, battery->udi, key, &error );
+
+ else if( !strcmp( key, "battery.rechargeable.is_charging" ) )
+ battery->status.charging =
+ libhal_device_get_property_bool( ctx, battery->udi, key, &error );
+
+ else if( !strcmp( key, "battery.rechargeable.is_discharging" ) )
+ battery->status.discharging =
+ libhal_device_get_property_bool( ctx, battery->udi, key, &error );
+
+ else
+ ret = FALSE;
+
+ dbus_error_free( &error );
+
+ return ret;
+}
+
+static gboolean
+adaptor_update_property( LibHalContext *ctx, struct adaptor_info *adaptor,
+ const char *key )
+{
+ DBusError error;
+ gboolean ret;
+
+ ret = TRUE;
+ dbus_error_init( &error );
+
+ if( !strcmp( key, "ac_adapter.present" ) )
+ adaptor->present =
+ libhal_device_get_property_bool( ctx, adaptor->udi, key, &error );
+ else
+ ret = FALSE;
+
+ dbus_error_free( &error );
+
+ return ret;
+}
+
+static GSList *
+find_link_by_udi( GSList *list, const char *udi )
+{
+ GSList *link;
+
+ for( link = list; link; link = g_slist_next( link ) )
+ {
+ /* this might be an adaptor! this is OK as long as 'udi' is the first
+ * member of both structures.
+ */
+ struct battery_info *battery = link->data;
+
+ if( !strcmp( udi, battery->udi ) )
+ return link;
+ }
+
+ return NULL;
+}
+
+static void *
+find_device_by_udi( GSList *list, const char *udi )
+{
+ GSList *link;
+
+ link = find_link_by_udi( list, udi );
+
+ if( link )
+ return link->data;
+ else
+ return NULL;
+}
+
+static gboolean status_update_scheduled;
+
+static gboolean
+update_status_idle (gpointer junk)
+{
+ if (status_updated_callback)
+ status_updated_callback ();
+
+ return status_update_scheduled = FALSE;
+}
+
+static void
+schedule_status_callback (void)
+{
+ if (status_update_scheduled)
+ return;
+
+ status_update_scheduled = TRUE;
+ g_idle_add (update_status_idle, NULL);
+}
+
+static void
+property_callback( LibHalContext *ctx, const char *udi, const char *key,
+ dbus_bool_t is_removed, dbus_bool_t is_added )
+{
+ struct battery_info *battery;
+ struct adaptor_info *adaptor;
+ gboolean changed;
+
+ /* It is safe to do nothing since the device will have been marked as
+ * not present and the old information will be ignored.
+ */
+ if( is_removed )
+ return;
+
+ changed = FALSE;
+
+ if( (battery = find_device_by_udi( batteries, udi )) )
+ changed |= battery_update_property( ctx, battery, key );
+
+ if( (adaptor = find_device_by_udi( adaptors, udi )) )
+ changed |= adaptor_update_property( ctx, adaptor, key );
+
+ if (changed && status_updated_callback)
+ schedule_status_callback ();
+}
+
+static void
+add_to_list( LibHalContext *ctx, GSList **list, const char *udi, int size )
+{
+ struct battery_info *battery = g_malloc0( size );
+ LibHalPropertySetIterator iter;
+ LibHalPropertySet *properties;
+ DBusError error;
+
+ /* this might be an adaptor! this is OK as long as 'udi' is the first
+ * member of both structures.
+ */
+ battery->udi = g_strdup( udi );
+ *list = g_slist_append( *list, battery );
+
+ dbus_error_init( &error );
+
+ libhal_device_add_property_watch( ctx, udi, &error );
+
+ properties = libhal_device_get_all_properties( ctx, udi, &error );
+
+ for( libhal_psi_init( &iter, properties );
+ libhal_psi_has_more( &iter );
+ libhal_psi_next( &iter ) )
+ {
+ const char *key = libhal_psi_get_key( &iter );
+ property_callback( ctx, udi, key, FALSE, FALSE );
+ }
+
+ libhal_free_property_set( properties );
+ dbus_error_free( &error );
+}
+
+static void
+free_list_data( void *data )
+{
+ /* this might be an adaptor! this is OK as long as 'udi' is the first
+ * member of both structures.
+ */
+ struct battery_info *battery = data;
+
+ g_free( battery->udi );
+ g_free( battery );
+}
+
+static GSList *
+remove_from_list( LibHalContext *ctx, GSList *list, const char *udi )
+{
+ GSList *link = find_link_by_udi( list, udi );
+
+ if( link )
+ {
+ DBusError error;
+
+ dbus_error_init( &error );
+ libhal_device_remove_property_watch( ctx, udi, &error );
+ dbus_error_free( &error );
+
+ free_list_data( link->data );
+ list = g_slist_delete_link( list, link );
+ }
+
+ return list;
+}
+
+static GSList *
+free_entire_list( GSList *list )
+{
+ GSList *item;
+
+ for( item = list; item; item = g_slist_next( item ) )
+ free_list_data( item->data );
+
+ g_slist_free( list );
+
+ return NULL;
+}
+
+static void
+device_added_callback( LibHalContext *ctx, const char *udi )
+{
+ if( libhal_device_property_exists( ctx, udi, "battery", NULL ) )
+ {
+ char *type = libhal_device_get_property_string( ctx, udi,
+ "battery.type",
+ NULL );
+
+ if( type )
+ {
+ /* We only track 'primary' batteries (ie: to avoid monitoring
+ * batteries in cordless mice or UPSes etc.)
+ */
+ if( !strcmp( type, "primary" ) )
+ add_to_list( ctx, &batteries, udi, sizeof (struct battery_info) );
+
+ libhal_free_string( type );
+ }
+ }
+
+ if( libhal_device_property_exists( ctx, udi, "ac_adapter", NULL ) )
+ add_to_list( ctx, &adaptors, udi, sizeof (struct adaptor_info) );
+}
+
+static void
+device_removed_callback( LibHalContext *ctx, const char *udi )
+{
+ batteries = remove_from_list( ctx, batteries, udi );
+ adaptors = remove_from_list( ctx, adaptors, udi );
+}
+
+/* ---- public functions ---- */
+
+char *
+battstat_hal_initialise (void (*callback) (void))
+{
+ DBusConnection *connection;
+ LibHalContext *ctx;
+ DBusError error;
+ char *error_str;
+ char **devices;
+ int i, num;
+
+ status_updated_callback = callback;
+
+ if( battstat_hal_ctx != NULL )
+ return g_strdup( "Already initialised!" );
+
+ dbus_error_init( &error );
+
+ if( (connection = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL )
+ goto error_out;
+
+ dbus_connection_setup_with_g_main( connection, g_main_context_default() );
+
+ if( (ctx = libhal_ctx_new()) == NULL )
+ {
+ dbus_set_error( &error, _("HAL error"), _("Could not create libhal_ctx") );
+ goto error_out;
+ }
+
+ libhal_ctx_set_device_property_modified( ctx, property_callback );
+ libhal_ctx_set_device_added( ctx, device_added_callback );
+ libhal_ctx_set_device_removed( ctx, device_removed_callback );
+ libhal_ctx_set_dbus_connection( ctx, connection );
+
+ if( libhal_ctx_init( ctx, &error ) == 0 )
+ goto error_freectx;
+
+ devices = libhal_find_device_by_capability( ctx, "battery", &num, &error );
+
+ if( devices == NULL )
+ goto error_shutdownctx;
+
+ /* FIXME: for now, if 0 battery devices are present on first scan, then fail.
+ * This allows fallover to the legacy (ACPI, APM, etc) backends if the
+ * installed version of HAL doesn't know about batteries. This check should
+ * be removed at some point in the future (maybe circa MATE 2.13..).
+ */
+ if( num == 0 )
+ {
+ dbus_free_string_array( devices );
+ dbus_set_error( &error, _("HAL error"), _("No batteries found") );
+ goto error_shutdownctx;
+ }
+
+ for( i = 0; i < num; i++ )
+ {
+ char *type = libhal_device_get_property_string( ctx, devices[i],
+ "battery.type",
+ &error );
+
+ if( type )
+ {
+ /* We only track 'primary' batteries (ie: to avoid monitoring
+ * batteries in cordless mice or UPSes etc.)
+ */
+ if( !strcmp( type, "primary" ) )
+ add_to_list( ctx, &batteries, devices[i],
+ sizeof (struct battery_info) );
+
+ libhal_free_string( type );
+ }
+ }
+ dbus_free_string_array( devices );
+
+ devices = libhal_find_device_by_capability( ctx, "ac_adapter", &num, &error );
+
+ if( devices == NULL )
+ {
+ batteries = free_entire_list( batteries );
+ goto error_shutdownctx;
+ }
+
+ for( i = 0; i < num; i++ )
+ add_to_list( ctx, &adaptors, devices[i], sizeof (struct adaptor_info) );
+ dbus_free_string_array( devices );
+
+ dbus_error_free( &error );
+
+ battstat_hal_ctx = ctx;
+
+ return NULL;
+
+error_shutdownctx:
+ libhal_ctx_shutdown( ctx, NULL );
+
+error_freectx:
+ libhal_ctx_free( ctx );
+
+error_out:
+ error_str = g_strdup_printf( _("Unable to initialise HAL: %s: %s"),
+ error.name, error.message );
+ dbus_error_free( &error );
+ return error_str;
+}
+
+void
+battstat_hal_cleanup( void )
+{
+ if( battstat_hal_ctx == NULL )
+ return;
+
+ libhal_ctx_free( battstat_hal_ctx );
+ batteries = free_entire_list( batteries );
+ adaptors = free_entire_list( adaptors );
+}
+
+#include "battstat.h"
+
+/* This function currently exists to allow the multiple batteries supported
+ * by the HAL backend to appear as a single composite battery device (since
+ * at the current time this is all that battstat supports).
+ *
+ * This entire function is filled with logic to make multiple batteries
+ * appear as one "composite" battery. Comments included as appropriate.
+ *
+ * For more information about some of the assumptions made in the following
+ * code please see the following mailing list post and the resulting thread:
+ *
+ * http://lists.freedesktop.org/archives/hal/2005-July/002841.html
+ */
+void
+battstat_hal_get_battery_info( BatteryStatus *status )
+{
+ /* The calculation to get overall percentage power remaining is as follows:
+ *
+ * Sum( Current charges ) / Sum( Full Capacities )
+ *
+ * We can't just take an average of all of the percentages since this
+ * doesn't deal with the case that one battery might have a larger
+ * capacity than the other.
+ *
+ * In order to do this calculation, we need to keep a running total of
+ * current charge and full capacities.
+ */
+ int current_charge_total = 0, full_capacity_total = 0;
+
+ /* Record the time remaining as reported by HAL. This is used in the event
+ * that the system has exactly one battery (since, then, the HAL is capable
+ * of providing an accurate time remaining report and we should trust it.)
+ */
+ int remaining_time = 0;
+
+ /* The total (dis)charge rate of the system is the sum of the rates of
+ * the individual batteries.
+ */
+ int rate_total = 0;
+
+ /* We need to know if we should report the composite battery as present
+ * at all. The logic is that if at least one actual battery is installed
+ * then the composite battery will be reported to exist.
+ */
+ int present = 0;
+
+ /* We need to know if we are on AC power or not. Eventually, we can look
+ * at the AC adaptor HAL devices to determine that. For now, we assume that
+ * if any battery is discharging then we must not be on AC power. Else, by
+ * default, we must be on AC.
+ */
+ int on_ac_power = 1;
+
+ /* Finally, we consider the composite battery to be "charging" if at least
+ * one of the actual batteries in the system is charging.
+ */
+ int charging = 0;
+
+ /* A list iterator. */
+ GSList *item;
+
+ /* For each physical battery bay... */
+ for( item = batteries; item; item = item->next )
+ {
+ struct battery_info *battery = item->data;
+
+ /* If no battery is installed here, don't count it toward the totals. */
+ if( !battery->status.present )
+ continue;
+
+ /* This battery is present. */
+
+ /* At least one battery present -> composite battery is present. */
+ present++;
+
+ /* At least one battery charging -> composite battery is charging. */
+ if( battery->status.charging )
+ charging = 1;
+
+ /* At least one battery is discharging -> we're not on AC. */
+ if( battery->status.discharging )
+ on_ac_power = 0;
+
+ /* Sum the totals for current charge, design capacity, (dis)charge rate. */
+ current_charge_total += battery->status.current_level;
+ full_capacity_total += battery->status.full_level;
+ rate_total += battery->status.rate;
+
+ /* Record remaining time too, incase this is the only battery. */
+ remaining_time = battery->status.remaining_time;
+ }
+
+ if( !present || full_capacity_total <= 0 || (charging && !on_ac_power) )
+ {
+ /* Either no battery is present or something has gone horribly wrong.
+ * In either case we must return that the composite battery is not
+ * present.
+ */
+ status->present = FALSE;
+ status->percent = 0;
+ status->minutes = -1;
+ status->on_ac_power = TRUE;
+ status->charging = FALSE;
+
+ return;
+ }
+
+ /* Else, our composite battery is present. */
+ status->present = TRUE;
+
+ /* As per above, overall charge is:
+ *
+ * Sum( Current charges ) / Sum( Full Capacities )
+ */
+ status->percent = ( ((double) current_charge_total) /
+ ((double) full_capacity_total) ) * 100.0 + 0.5;
+
+ if( present == 1 )
+ {
+ /* In the case of exactly one battery, report the time remaining figure
+ * from HAL directly since it might have come from an authorative source
+ * (ie: the PMU or APM subsystem).
+ *
+ * HAL gives remaining time in seconds with a 0 to mean that the
+ * remaining time is unknown. Battstat uses minutes and -1 for
+ * unknown time remaining.
+ */
+
+ if( remaining_time == 0 )
+ status->minutes = -1;
+ else
+ status->minutes = (remaining_time + 30) / 60;
+ }
+ /* Rest of cases to deal with multiple battery systems... */
+ else if( !on_ac_power && rate_total != 0 )
+ {
+ /* Then we're discharging. Calculate time remaining until at zero. */
+
+ double remaining;
+
+ remaining = (double) current_charge_total;
+ remaining /= (double) rate_total;
+ status->minutes = (int) floor( remaining * 60.0 + 0.5 );
+ }
+ else if( charging && rate_total != 0 )
+ {
+ /* Calculate time remaining until charged. For systems with more than
+ * one battery, this code is very approximate. The assumption is that if
+ * one battery reaches full charge before the other that the other will
+ * start charging faster due to the increase in available power (similar
+ * to how a laptop will charge faster if you're not using it).
+ */
+
+ double remaining;
+
+ remaining = (double) (full_capacity_total - current_charge_total);
+ if( remaining < 0 )
+ remaining = 0;
+ remaining /= (double) rate_total;
+
+ status->minutes = (int) floor( remaining * 60.0 + 0.5 );
+ }
+ else
+ {
+ /* On AC power and not charging -or- rate is unknown. */
+ status->minutes = -1;
+ }
+
+ /* These are simple and well-explained above. */
+ status->charging = charging;
+ status->on_ac_power = on_ac_power;
+}
+
+#endif /* HAVE_HAL */