diff options
Diffstat (limited to 'battstat/battstat-hal.c')
-rw-r--r-- | battstat/battstat-hal.c | 608 |
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 */ |