diff options
Diffstat (limited to 'battstat/acpi-linux.c')
-rw-r--r-- | battstat/acpi-linux.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/battstat/acpi-linux.c b/battstat/acpi-linux.c new file mode 100644 index 00000000..d982f6cc --- /dev/null +++ b/battstat/acpi-linux.c @@ -0,0 +1,439 @@ +/* battstat A MATE battery meter for laptops. + * Copyright (C) 2000 by J�rgen Pehrson <[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$ + */ + +/* + * ACPI battery read-out functions for Linux >= 2.4.12 + * October 2001 by Lennart Poettering <[email protected]> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef __linux__ + +#include <stdio.h> +#include <apm.h> +#include <glib.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <dirent.h> +#include "acpi-linux.h" + +static GHashTable * +read_file (const char *file, char *buf, size_t bufsize) +{ + GHashTable *hash = NULL; + + int fd, len, i; + char *key, *value; + gboolean reading_key; + + fd = open (file, O_RDONLY); + + if (fd == -1) { + return hash; + } + + len = read (fd, buf, bufsize); + + close (fd); + + if (len < 0) { + if (getenv ("BATTSTAT_DEBUG")) + g_message ("Error reading %s: %s", file, g_strerror (errno)); + return hash; + } + + hash = g_hash_table_new (g_str_hash, g_str_equal); + + for (i = 0, value = key = buf, reading_key = TRUE; i < len; i++) { + if (buf[i] == ':' && reading_key) { + reading_key = FALSE; + buf[i] = '\0'; + value = buf + i + 1; + } else if (buf[i] == '\n') { + reading_key = TRUE; + buf[i] = '\0'; + /* g_message ("Read: %s => %s\n", key, value); */ + g_hash_table_insert (hash, key, g_strstrip (value)); + key = buf + i + 1; + } else if (reading_key) { + /* in acpi 20020214 it switched to lower-case proc + * entries. fixing this up here simplifies the + * code. + */ + buf[i] = g_ascii_tolower (buf[i]); + } + } + + return hash; +} + +#if 0 +static gboolean +read_bool (GHashTable *hash, const char *key) +{ + char *s; + + g_return_val_if_fail (hash, FALSE); + g_return_val_if_fail (key, FALSE); + + s = g_hash_table_lookup (hash, key); + return s && (*s == 'y'); +} +#endif + +static long +read_long (GHashTable *hash, const char *key) +{ + char *s; + + g_return_val_if_fail (hash, 0); + g_return_val_if_fail (key, 0); + + s = g_hash_table_lookup (hash, key); + return s ? strtol (s, NULL, 10) : 0; +} + +static gulong +read_ulong (GHashTable *hash, const char *key) +{ + char *s; + + g_return_val_if_fail (hash, 0); + g_return_val_if_fail (key, 0); + + s = g_hash_table_lookup (hash, key); + return s ? strtoul (s, NULL, 10) : 0; +} + +static const char * +read_string (GHashTable *hash, const char *key) +{ + return g_hash_table_lookup (hash, key); +} + +/* Reads the current status of the AC adapter and stores the + * result in acpiinfo->ac_online. */ +static gboolean update_ac_info(struct acpi_info * acpiinfo) +{ + gchar *ac_state = NULL; + DIR * procdir; + struct dirent * procdirentry; + char buf[BUFSIZ]; + GHashTable *hash; + gboolean have_adaptor = FALSE; + + acpiinfo->ac_online = FALSE; + + procdir=opendir("/proc/acpi/ac_adapter/"); + if (!procdir) + return FALSE; + + while ((procdirentry=readdir(procdir))) + { + if (procdirentry->d_name[0]!='.') + { + have_adaptor = TRUE; + ac_state = g_strconcat("/proc/acpi/ac_adapter/", + procdirentry->d_name, + "/", + acpiinfo->ac_state_state, + NULL); + hash = read_file (ac_state, buf, sizeof (buf)); + if (hash && !acpiinfo->ac_online) + { + const char *s; + s = read_string (hash, acpiinfo->ac_state_state); + acpiinfo->ac_online = s ? (strcmp (s, "on-line") == 0) : 0; + g_hash_table_destroy (hash); + } + g_free(ac_state); + } + } + + /* If there are no AC adaptors registered in the system, then we're + probably on a desktop (and therefore, on AC power). + */ + if (have_adaptor == FALSE) + acpiinfo->ac_online = 1; + + closedir(procdir); + + return TRUE; +} + +/* Reads the ACPI info for the system batteries, and finds + * the total capacity, which is stored in acpiinfo. */ +static gboolean update_battery_info(struct acpi_info * acpiinfo) +{ + gchar* batt_info = NULL; + GHashTable *hash; + DIR * procdir; + struct dirent * procdirentry; + char buf[BUFSIZ]; + + acpiinfo->max_capacity = 0; + acpiinfo->low_capacity = 0; + acpiinfo->critical_capacity = 0; + + procdir=opendir("/proc/acpi/battery/"); + if (!procdir) + return FALSE; + + while ((procdirentry=readdir(procdir))) + { + if (procdirentry->d_name[0]!='.') + { + batt_info = g_strconcat("/proc/acpi/battery/", + procdirentry->d_name, + "/info", + NULL); + hash = read_file (batt_info, buf, sizeof (buf)); + if (hash) + { + acpiinfo->max_capacity += read_long (hash, "last full capacity"); + acpiinfo->low_capacity += read_long (hash, "design capacity warning"); + acpiinfo->critical_capacity += read_long (hash, "design capacity low"); + g_hash_table_destroy (hash); + } + g_free(batt_info); + } + } + closedir(procdir); + + return TRUE; +} + + +/* Initializes the ACPI-reading subsystem by opening a file + * descriptor to the ACPI event file. This can either be the + * /proc/acpi/event exported by the kernel, or if it's already + * in use, the /var/run/acpid.socket maintained by acpid. Also + * initializes the stored battery and AC adapter information. */ +gboolean acpi_linux_init(struct acpi_info * acpiinfo) +{ + GHashTable *hash; + char buf[BUFSIZ]; + gchar *pbuf; + gulong acpi_ver; + int fd; + + g_assert(acpiinfo); + + if (g_file_get_contents ("/sys/module/acpi/parameters/acpica_version", &pbuf, NULL, NULL)) { + acpi_ver = strtoul (pbuf, NULL, 10); + g_free (pbuf); + } else if (hash = read_file ("/proc/acpi/info", buf, sizeof (buf))) { + acpi_ver = read_ulong (hash, "version"); + g_hash_table_destroy (hash); + } else + return FALSE; + + if (acpi_ver < (gulong)20020208) { + acpiinfo->ac_state_state = "status"; + acpiinfo->batt_state_state = "status"; + acpiinfo->charging_state = "state"; + } else { + acpiinfo->ac_state_state = "state"; + acpiinfo->batt_state_state = "state"; + acpiinfo->charging_state = "charging state"; + } + + if (!update_battery_info(acpiinfo) || !update_ac_info(acpiinfo)) + return FALSE; + + fd = open("/proc/acpi/event", 0); + if (fd >= 0) { + acpiinfo->event_fd = fd; + acpiinfo->channel = g_io_channel_unix_new(fd); + return TRUE; + } + + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "/var/run/acpid.socket"); + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == 0) { + acpiinfo->event_fd = fd; + acpiinfo->channel = g_io_channel_unix_new(fd); + return TRUE; + } + } + + close(fd); + acpiinfo->event_fd = -1; + return FALSE; +} + +/* Cleans up ACPI */ +void acpi_linux_cleanup(struct acpi_info * acpiinfo) +{ + g_assert(acpiinfo); + + if (acpiinfo->event_fd >= 0) { + g_io_channel_unref(acpiinfo->channel); + close(acpiinfo->event_fd); + acpiinfo->event_fd = -1; + } +} + +#define ACPI_EVENT_IGNORE 0 +#define ACPI_EVENT_DONE 1 +#define ACPI_EVENT_AC 2 +#define ACPI_EVENT_BATTERY_INFO 3 + +/* Given a string event from the ACPI system, returns the type + * of event if we're interested in it. str is updated to point + * to the next event. */ +static int parse_acpi_event(GString *buffer) +{ + + if (strstr(buffer->str, "ac_adapter")) + return ACPI_EVENT_AC; + if (strstr(buffer->str, "battery") ) + return ACPI_EVENT_BATTERY_INFO; + + return ACPI_EVENT_IGNORE; +} + +/* Handles a new ACPI event by reading it from the event file + * and calling any handlers. Since this does a blocking read, + * it should only be called when there is a new event as indicated + * by select(). */ +gboolean acpi_process_event(struct acpi_info * acpiinfo) +{ + gsize i; + int evt; + GString *buffer; + GError *gerror=NULL; + buffer=g_string_new(NULL); + g_io_channel_read_line_string ( acpiinfo->channel,buffer,&i,&gerror); + + + evt = parse_acpi_event(buffer); + switch (evt) { + case ACPI_EVENT_AC: + return update_ac_info(acpiinfo); + case ACPI_EVENT_BATTERY_INFO: + if (update_battery_info(acpiinfo)) { + /* Update AC info on battery info updates. This works around + * a bug in ACPI (as per bug #163013). + */ + return update_ac_info(acpiinfo); + } + /* fall-through */ + default: + return FALSE; + } +} + +/* + * Fills out a classic apm_info structure with the data gathered from + * the ACPI kernel interface in /proc + */ +gboolean acpi_linux_read(struct apm_info *apminfo, struct acpi_info * acpiinfo) +{ + guint32 remain; + guint32 rate; + gboolean charging; + GHashTable *hash; + gchar* batt_state = NULL; + DIR * procdir; + struct dirent * procdirentry; + char buf[BUFSIZ]; + + g_assert(acpiinfo); + + /* + * apminfo.ac_line_status must be one when on ac power + * apminfo.battery_status must be 0 for high, 1 for low, 2 for critical, 3 for charging + * apminfo.battery_percentage must contain batter charge percentage + * apminfo.battery_flags & 0x8 must be nonzero when charging + */ + + g_assert(apminfo); + + charging = FALSE; + remain = 0; + rate = 0; + + procdir=opendir("/proc/acpi/battery/"); + if (!procdir) + return FALSE; + + /* Get the remaining capacity for the batteries. Other information + * such as AC state and battery max capacity are read only when they + * change using acpi_process_event(). */ + while ((procdirentry=readdir(procdir))) + { + if (procdirentry->d_name[0]!='.') + { + batt_state = g_strconcat("/proc/acpi/battery/", + procdirentry->d_name, + "/", + acpiinfo->batt_state_state, + NULL); + hash = read_file (batt_state, buf, sizeof (buf)); + if (hash) + { + const char *s; + if (!charging) + { + s = read_string (hash, acpiinfo->charging_state); + charging = s ? (strcmp (s, "charging") == 0) : 0; + } + remain += read_long (hash, "remaining capacity"); + rate += read_long (hash, "present rate"); + g_hash_table_destroy (hash); + } + g_free(batt_state); + } + } + closedir(procdir); + + apminfo->ac_line_status = acpiinfo->ac_online ? 1 : 0; + apminfo->battery_status = remain < acpiinfo->low_capacity ? 1 : remain < acpiinfo->critical_capacity ? 2 : 0; + if (!acpiinfo->max_capacity) + apminfo->battery_percentage = -1; + else + apminfo->battery_percentage = (int) (remain/(float)acpiinfo->max_capacity*100); + apminfo->battery_flags = charging ? 0x8 : 0; + if (rate && !charging) + apminfo->battery_time = (int) (remain/(float)rate * 60); + else if (rate && charging) + apminfo->battery_time = (int) ((acpiinfo->max_capacity-remain)/(float)rate * 60); + else + apminfo->battery_time = -1; + + return TRUE; +} + + +#endif |