/* battstat A MATE battery meter for laptops. * Copyright (C) 2000 by Jörgen Pehrson <jp@spektr.eu.org> * * 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$ */ /* * ACPI battery read-out functions for Linux >= 2.4.12 * October 2001 by Lennart Poettering <lennart@poettering.de> */ #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; char* 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); if (gerror != NULL) { g_warning ("%s", gerror->message); g_error_free (gerror); } gboolean result; evt = parse_acpi_event(buffer); switch (evt) { case ACPI_EVENT_AC: result = update_ac_info(acpiinfo); break; 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). */ result = update_ac_info(acpiinfo); break; } /* fall-through */ default: result = FALSE; } g_string_free(buffer, FALSE); return result; } /* * 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 /* __linux__ */