/* * Copyright (C) 2005 by Ryan Lortie * * 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 02110-1301, USA. * * $Id$ */ #include #ifdef HAVE_HAL #include #include #include #include #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 */