diff options
Diffstat (limited to 'battstat/power-management.c')
-rw-r--r-- | battstat/power-management.c | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/battstat/power-management.c b/battstat/power-management.c new file mode 100644 index 00000000..53ae081a --- /dev/null +++ b/battstat/power-management.c @@ -0,0 +1,535 @@ +/* battstat A MATE battery meter for laptops. + * Copyright (C) 2000 by Jörgen Pehrson <[email protected]> + * Copyright (C) 2002-2005 Free Software Foundation + * + * 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$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif + +#ifdef HAVE_ERR_H +#include <err.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "battstat.h" +#include "battstat-hal.h" + +#define ERR_ACPID _("Can't access ACPI events in /var/run/acpid.socket! " \ + "Make sure the ACPI subsystem is working and " \ + "the acpid daemon is running.") + +#define ERR_OPEN_APMDEV _("Can't open the APM device!\n\n" \ + "Make sure you have read permission to the\n" \ + "APM device.") + +#define ERR_APM_E _("The APM Management subsystem seems to be disabled.\n" \ + "Try executing \"apm -e 1\" (FreeBSD) and see if \n" \ + "that helps.\n") + +#define ERR_NO_SUPPORT _("Your platform is not supported! The battery\n" \ + "monitor applet will not work on your system.\n") + +#define ERR_FREEBSD_ACPI _("There was an error reading information from " \ + "the ACPI subsystem. Check to make sure the " \ + "ACPI subsystem is properly loaded.") + +static const char *apm_readinfo (BatteryStatus *status); +static int pm_initialised; +#ifdef HAVE_HAL +static int using_hal; +#endif + +/* + * What follows is a series of platform-specific apm_readinfo functions + * that take care of the quirks of getting battery information for different + * platforms. Each function takes a BatteryStatus pointer and fills it + * then returns a const char *. + * + * In the case of success, NULL is returned. In case of failure, a + * localised error message is returned to give the user hints about what + * the problem might be. This error message is not to be freed. + */ + + +/* Uncomment the following to enable a 'testing' backend. When you add the + applet to the panel a window will appear that allows you to manually + change the battery status values for testing purposes. + + NB: Be sure to unset this before committing!! + */ + +/* #define BATTSTAT_TESTING_BACKEND */ +#ifdef BATTSTAT_TESTING_BACKEND + +#include <gtk/gtk.h> + +BatteryStatus test_status; + +static void +test_update_boolean( GtkToggleButton *button, gboolean *value ) +{ + *value = gtk_toggle_button_get_active( button ); +} + +static void +test_update_integer( GtkSpinButton *spin, gint *value ) +{ + *value = gtk_spin_button_get_value_as_int( spin ); +} + +static void +initialise_test( void ) +{ + GtkWidget *w; + GtkBox *box; + + test_status.percent = 50; + test_status.minutes = 180; + test_status.present = TRUE; + test_status.on_ac_power = FALSE; + test_status.charging = FALSE; + + box = GTK_BOX( gtk_vbox_new( 5, FALSE ) ); + + gtk_box_pack_start( box, gtk_label_new( "percent" ), TRUE, TRUE, 0); + w = gtk_spin_button_new_with_range( -1.0, 100.0, 1 ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), 50.0 ); + g_signal_connect( G_OBJECT( w ), "value-changed", + G_CALLBACK( test_update_integer ), &test_status.percent ); + gtk_box_pack_start( box, w, TRUE, TRUE, 0 ); + + gtk_box_pack_start( box, gtk_label_new( "minutes" ), TRUE, TRUE, 0); + w = gtk_spin_button_new_with_range( -1.0, 1000.0, 1 ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), 180.0 ); + g_signal_connect( G_OBJECT( w ), "value-changed", + G_CALLBACK( test_update_integer ), &test_status.minutes ); + gtk_box_pack_start( box, w, TRUE, TRUE, 0); + + + w = gtk_toggle_button_new_with_label( "on_ac_power" ); + g_signal_connect( G_OBJECT( w ), "toggled", + G_CALLBACK( test_update_boolean ), + &test_status.on_ac_power ); + gtk_box_pack_start( box, w, TRUE, TRUE, 0); + + w = gtk_toggle_button_new_with_label( "charging" ); + g_signal_connect( G_OBJECT( w ), "toggled", + G_CALLBACK( test_update_boolean ), &test_status.charging ); + gtk_box_pack_start( box, w, TRUE, TRUE, 0); + + w = gtk_toggle_button_new_with_label( "present" ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), TRUE ); + g_signal_connect( G_OBJECT( w ), "toggled", + G_CALLBACK( test_update_boolean ), &test_status.present ); + gtk_box_pack_start( box, w, TRUE, TRUE, 0); + + w = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( box ) ); + gtk_widget_show_all( w ); +} + +static const char * +apm_readinfo (BatteryStatus *status) +{ + static int test_initialised; + + if( !test_initialised ) + initialise_test(); + + test_initialised = 1; + *status = test_status; + + return NULL; +} + +#undef __linux__ +#undef HAVE_HAL + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + +#include <machine/apm_bios.h> +#include "acpi-freebsd.h" + +static struct acpi_info acpiinfo; +static gboolean using_acpi; +static int acpi_count; +static struct apm_info apminfo; + +#define APMDEVICE "/dev/apm" + +static const char * +apm_readinfo (BatteryStatus *status) +{ + int fd; + + if (DEBUG) g_print("apm_readinfo() (FreeBSD)\n"); + + if (using_acpi) { + if (acpi_count <= 0) { + acpi_count = 30; + acpi_process_event(&acpiinfo); + if (acpi_freebsd_read(&apminfo, &acpiinfo) == FALSE) + return ERR_FREEBSD_ACPI; + } + acpi_count--; + } + else + { + /* This is how I read the information from the APM subsystem under + FreeBSD. Each time this functions is called (once every second) + the APM device is opened, read from and then closed. + */ + fd = open(APMDEVICE, O_RDONLY); + if (fd == -1) { + return ERR_OPEN_APMDEV; + } + + if (ioctl(fd, APMIO_GETINFO, &apminfo) == -1) + err(1, "ioctl(APMIO_GETINFO)"); + + close(fd); + + if(apminfo.ai_status == 0) + return ERR_APM_E; + } + + status->present = TRUE; + status->on_ac_power = apminfo.ai_acline ? 1 : 0; + status->percent = apminfo.ai_batt_life; + status->charging = (apminfo.ai_batt_stat) ? TRUE : FALSE; + if (using_acpi) + status->minutes = apminfo.ai_batt_time; + else + status->minutes = (int) (apminfo.ai_batt_time/60.0); + + return NULL; +} + +#elif defined(__NetBSD__) || defined(__OpenBSD__) + +#include <sys/param.h> +#include <machine/apmvar.h> + +#define APMDEVICE "/dev/apm" + +static const char * +apm_readinfo (BatteryStatus *status) +{ + /* Code for OpenBSD by Joe Ammond <[email protected]>. Using the same + procedure as for FreeBSD. + */ + struct apm_info apminfo; + int fd; + +#if defined(__NetBSD__) + if (DEBUG) g_print("apm_readinfo() (NetBSD)\n"); +#else /* __OpenBSD__ */ + if (DEBUG) g_print("apm_readinfo() (OpenBSD)\n"); +#endif + + fd = open(APMDEVICE, O_RDONLY); + if (fd == -1) + { + pm_initialised = 0; + return ERR_OPEN_APMDEV; + } + if (ioctl(fd, APM_IOC_GETPOWER, &apminfo) == -1) + err(1, "ioctl(APM_IOC_GETPOWER)"); + close(fd); + + status->present = TRUE; + status->on_ac_power = apminfo.ac_state ? 1 : 0; + status->percent = apminfo.battery_life; + status->charging = (apminfo.battery_state == 3) ? TRUE : FALSE; + status->minutes = apminfo.minutes_left; + + return NULL; +} + +#elif __linux__ + +#include <apm.h> +#include "acpi-linux.h" + +static struct acpi_info acpiinfo; +static gboolean using_acpi; +static int acpi_count; +static int acpiwatch; +static struct apm_info apminfo; + +/* Declared in acpi-linux.c */ +gboolean acpi_linux_read(struct apm_info *apminfo, struct acpi_info *acpiinfo); + +static gboolean acpi_callback (GIOChannel * chan, GIOCondition cond, gpointer data) +{ + if (cond & (G_IO_ERR | G_IO_HUP)) { + acpi_linux_cleanup(&acpiinfo); + apminfo.battery_percentage = -1; + return FALSE; + } + + if (acpi_process_event(&acpiinfo)) { + acpi_linux_read(&apminfo, &acpiinfo); + } + return TRUE; +} + +static const char * +apm_readinfo (BatteryStatus *status) +{ + /* Code for Linux by Thomas Hood <[email protected]>. apm_read() will + read from /proc/... instead and we do not need to open the device + ourselves. + */ + if (DEBUG) g_print("apm_readinfo() (Linux)\n"); + + /* ACPI support added by Lennart Poettering <[email protected]> 10/27/2001 + * Updated by David Moore <[email protected]> 5/29/2003 to poll less and + * use ACPI events. */ + if (using_acpi && acpiinfo.event_fd >= 0) { + if (acpi_count <= 0) { + /* Only call this one out of 30 calls to apm_readinfo() (every 30 seconds) + * since reading the ACPI system takes CPU cycles. */ + acpi_count=30; + acpi_linux_read(&apminfo, &acpiinfo); + } + acpi_count--; + } + /* If we lost the file descriptor with ACPI events, try to get it back. */ + else if (using_acpi) { + if (acpi_linux_init(&acpiinfo)) { + acpiwatch = g_io_add_watch (acpiinfo.channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, + acpi_callback, NULL); + acpi_linux_read(&apminfo, &acpiinfo); + } + } + else + apm_read(&apminfo); + + status->present = TRUE; + status->on_ac_power = apminfo.ac_line_status ? 1 : 0; + status->percent = (guint) apminfo.battery_percentage; + status->charging = (apminfo.battery_flags & 0x8) ? TRUE : FALSE; + status->minutes = apminfo.battery_time; + + return NULL; +} + +#else + +static const char * +apm_readinfo (BatteryStatus *status) +{ + status->present = FALSE; + status->on_ac_power = 1; + status->percent = 100; + status->charging = FALSE; + status->minutes = 0; + + return ERR_NO_SUPPORT; +} + +#endif + +/* + * End platform-specific code. + */ + +/* + * power_management_getinfo + * + * Main interface to the power management code. Fills 'status' for you so + * you don't have to worry about platform-specific details. + * + * In the case of success, NULL is returned. In case of failure, a + * localised error message is returned to give the user hints about what + * the problem might be. This error message is not to be freed. + */ +const char * +power_management_getinfo( BatteryStatus *status ) +{ + const char *retval; + + if( !pm_initialised ) + { + status->on_ac_power = TRUE; + status->minutes = -1; + status->percent = 0; + status->charging = FALSE; + status->present = FALSE; + + return NULL; + } + +#ifdef HAVE_HAL + if( using_hal ) + { + battstat_hal_get_battery_info( status ); + return NULL; + } +#endif + + retval = apm_readinfo( status ); + + if(status->percent == -1) { + status->percent = 0; + status->present = FALSE; + } + + if(status->percent > 100) + status->percent = 100; + + if(status->percent == 100) + status->charging = FALSE; + + if(!status->on_ac_power) + status->charging = FALSE; + + return retval; +} + +/* + * power_management_initialise + * + * Initialise the power management code. Call this before you call anything + * else. + * + * In the case of success, NULL is returned. In case of failure, a + * localised error message is returned to give the user hints about what + * the problem might be. This error message is not to be freed. + */ +const char * +power_management_initialise (int no_hal, void (*callback) (void)) +{ +#ifdef __linux__ + struct stat statbuf; +#endif +#ifdef HAVE_HAL + char *err; + + if( no_hal ) + err = g_strdup( ":(" ); + else + err = battstat_hal_initialise (callback); + + + if( err == NULL ) /* HAL is up */ + { + pm_initialised = 1; + using_hal = TRUE; + return NULL; + } + else + /* fallback to legacy methods */ + g_free( err ); +#endif + +#ifdef __linux__ + + if (acpi_linux_init(&acpiinfo)) { + using_acpi = TRUE; + acpi_count = 0; + } + else + using_acpi = FALSE; + + /* If neither ACPI nor APM could be read, but ACPI does seem to be + * installed, warn the user how to get ACPI working. */ + if (!using_acpi && (apm_exists() == 1) && + (stat("/proc/acpi", &statbuf) == 0)) { + using_acpi = TRUE; + acpi_count = 0; + return ERR_ACPID; + } + + /* Watch for ACPI events and handle them immediately with acpi_callback(). */ + if (using_acpi && acpiinfo.event_fd >= 0) { + acpiwatch = g_io_add_watch (acpiinfo.channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, + acpi_callback, NULL); + } +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + if (acpi_freebsd_init(&acpiinfo)) { + using_acpi = TRUE; + acpi_count = 0; + } + else + using_acpi = FALSE; +#endif + pm_initialised = 1; + + return NULL; +} + +/* + * power_management_cleanup + * + * Perform any cleanup that might be required. + */ +void +power_management_cleanup( void ) +{ +#ifdef HAVE_HAL + if( using_hal ) + { + battstat_hal_cleanup(); + pm_initialised = 1; + return; + } +#endif + +#ifdef __linux__ + if (using_acpi) + { + if (acpiwatch != 0) + g_source_remove(acpiwatch); + acpiwatch = 0; + acpi_linux_cleanup(&acpiinfo); + } +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + if (using_acpi) { + acpi_freebsd_cleanup(&acpiinfo); + } +#endif + + pm_initialised = 0; +} + +int +power_management_using_hal( void ) +{ +#ifdef HAVE_HAL + return using_hal; +#else + return 0; +#endif +} |