summaryrefslogtreecommitdiff
path: root/src/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux')
-rw-r--r--src/linux/.gitignore5
-rw-r--r--src/linux/Makefile.am37
-rw-r--r--src/linux/dkp-backend.c408
-rw-r--r--src/linux/dkp-device-csr.c346
-rw-r--r--src/linux/dkp-device-csr.h56
-rw-r--r--src/linux/dkp-device-hid.c487
-rw-r--r--src/linux/dkp-device-hid.h55
-rw-r--r--src/linux/dkp-device-supply.c795
-rw-r--r--src/linux/dkp-device-supply.h56
-rw-r--r--src/linux/dkp-device-wup.c486
-rw-r--r--src/linux/dkp-device-wup.h55
-rw-r--r--src/linux/dkp-input.c320
-rw-r--r--src/linux/dkp-input.h59
-rw-r--r--src/linux/sysfs-utils.c241
-rw-r--r--src/linux/sysfs-utils.h38
15 files changed, 3444 insertions, 0 deletions
diff --git a/src/linux/.gitignore b/src/linux/.gitignore
new file mode 100644
index 0000000..ed89e2f
--- /dev/null
+++ b/src/linux/.gitignore
@@ -0,0 +1,5 @@
+.deps
+.libs
+*.o
+*.a
+
diff --git a/src/linux/Makefile.am b/src/linux/Makefile.am
new file mode 100644
index 0000000..489563b
--- /dev/null
+++ b/src/linux/Makefile.am
@@ -0,0 +1,37 @@
+## Process this file with automake to produce Makefile.in
+
+INCLUDES = \
+ -I$(top_builddir)/src -I$(top_srcdir)/src \
+ -DEGG_LOG_FILE=\""$(DKP_LOG_DIR)/DeviceKit-power"\" \
+ -DEGG_VERBOSE="\"DKP_VERBOSE\"" \
+ -DEGG_LOGGING="\"DKP_LOGGING\"" \
+ -DEGG_CONSOLE="\"DKP_CONSOLE\"" \
+ -DDKP_COMPILATION \
+ -I$(top_srcdir)/devkit-power-gobject \
+ $(GIO_CFLAGS) \
+ $(DBUS_GLIB_CFLAGS) \
+ $(GUDEV_CFLAGS) \
+ $(POLKIT_CFLAGS) \
+ $(GLIB_CFLAGS)
+
+noinst_LIBRARIES = libdkpshared.a
+libdkpshared_a_SOURCES = \
+ dkp-device-supply.c \
+ dkp-device-supply.h \
+ dkp-device-csr.c \
+ dkp-device-csr.h \
+ dkp-device-hid.c \
+ dkp-device-hid.h \
+ dkp-device-wup.c \
+ dkp-device-wup.h \
+ dkp-input.c \
+ dkp-input.h \
+ dkp-backend.c \
+ dkp-native.c \
+ sysfs-utils.c \
+ sysfs-utils.h \
+ $(BUILT_SOURCES)
+
+clean-local :
+ rm -f *~
+
diff --git a/src/linux/dkp-backend.c b/src/linux/dkp-backend.c
new file mode 100644
index 0000000..d4cf421
--- /dev/null
+++ b/src/linux/dkp-backend.c
@@ -0,0 +1,408 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gudev/gudev.h>
+
+#include "egg-debug.h"
+
+#include "dkp-backend.h"
+#include "dkp-daemon.h"
+#include "dkp-marshal.h"
+#include "dkp-device.h"
+
+#include "dkp-device-supply.h"
+#include "dkp-device-csr.h"
+#include "dkp-device-wup.h"
+#include "dkp-device-hid.h"
+#include "dkp-input.h"
+
+static void dkp_backend_class_init (DkpBackendClass *klass);
+static void dkp_backend_init (DkpBackend *backend);
+static void dkp_backend_finalize (GObject *object);
+
+#define DKP_BACKEND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_BACKEND, DkpBackendPrivate))
+
+struct DkpBackendPrivate
+{
+ DkpDaemon *daemon;
+ DkpDevice *device;
+ DkpDeviceList *device_list;
+ GObject *native;
+ GUdevClient *gudev_client;
+ DkpDeviceList *managed_devices;
+};
+
+enum {
+ SIGNAL_DEVICE_ADDED,
+ SIGNAL_DEVICE_CHANGED,
+ SIGNAL_DEVICE_REMOVED,
+ SIGNAL_LAST
+};
+
+static guint signals [SIGNAL_LAST] = { 0 };
+
+G_DEFINE_TYPE (DkpBackend, dkp_backend, G_TYPE_OBJECT)
+
+static gboolean dkp_backend_device_add (DkpBackend *backend, GUdevDevice *native, gboolean emit_signal);
+static void dkp_backend_device_remove (DkpBackend *backend, GUdevDevice *native);
+
+/**
+ * dkp_backend_device_changed:
+ **/
+static void
+dkp_backend_device_changed (DkpBackend *backend, GUdevDevice *native, gboolean emit_signal)
+{
+ GObject *object;
+ DkpDevice *device;
+
+ /* first, change the device and add it if it doesn't exist */
+ object = dkp_device_list_lookup (backend->priv->device_list, native);
+ if (object == NULL) {
+ egg_debug ("treating change event as add on %s", g_udev_device_get_sysfs_path (native));
+ dkp_backend_device_add (backend, native, TRUE);
+ goto out;
+ }
+
+ device = DKP_DEVICE (object);
+ egg_debug ("changed %s", dkp_device_get_object_path (device));
+
+ /* emit */
+ g_signal_emit (backend, signals[SIGNAL_DEVICE_CHANGED], 0, native, device, emit_signal);
+}
+
+/**
+ * dkp_backend_device_get:
+ **/
+static DkpDevice *
+dkp_backend_device_get (DkpBackend *backend, GUdevDevice *native)
+{
+ const gchar *subsys;
+ const gchar *native_path;
+ DkpDevice *device = NULL;
+ DkpInput *input;
+ gboolean ret;
+
+ subsys = g_udev_device_get_subsystem (native);
+ if (g_strcmp0 (subsys, "power_supply") == 0) {
+
+ /* are we a valid power supply */
+ device = DKP_DEVICE (dkp_device_supply_new ());
+ ret = dkp_device_coldplug (device, backend, native);
+ if (ret)
+ goto out;
+ g_object_unref (device);
+
+ /* no valid power supply object */
+ device = NULL;
+
+ } else if (g_strcmp0 (subsys, "tty") == 0) {
+
+ /* try to detect a Watts Up? Pro monitor */
+ device = DKP_DEVICE (dkp_device_wup_new ());
+ ret = dkp_device_coldplug (device, backend, native);
+ if (ret)
+ goto out;
+ g_object_unref (device);
+
+ /* no valid TTY object ;-( */
+ device = NULL;
+
+ } else if (g_strcmp0 (subsys, "usb") == 0) {
+
+ /* see if this is a CSR mouse or keyboard */
+ device = DKP_DEVICE (dkp_device_csr_new ());
+ ret = dkp_device_coldplug (device, backend, native);
+ if (ret)
+ goto out;
+ g_object_unref (device);
+
+ /* try to detect a HID UPS */
+ device = DKP_DEVICE (dkp_device_hid_new ());
+ ret = dkp_device_coldplug (device, backend, native);
+ if (ret)
+ goto out;
+ g_object_unref (device);
+
+ /* no valid USB object ;-( */
+ device = NULL;
+
+ } else if (g_strcmp0 (subsys, "input") == 0) {
+
+ /* check input device */
+ input = dkp_input_new ();
+ ret = dkp_input_coldplug (input, backend, native);
+ if (!ret) {
+ g_object_unref (input);
+ goto out;
+ }
+
+ /* we now have a lid */
+ g_object_set (backend->priv->daemon,
+ "lid-is-present, TRUE,
+ NULL);
+
+ /* not a power device */
+ dkp_device_list_insert (backend->priv->managed_devices, native, G_OBJECT (input));
+
+ /* no valid input object */
+ device = NULL;
+
+ } else {
+ native_path = g_udev_device_get_sysfs_path (native);
+ egg_warning ("native path %s (%s) ignoring", native_path, subsys);
+ }
+out:
+ return device;
+}
+
+/**
+ * dkp_backend_device_add:
+ **/
+static gboolean
+dkp_backend_device_add (DkpBackend *backend, GUdevDevice *native, gboolean emit_signal)
+{
+ GObject *object;
+ DkpDevice *device;
+ gboolean ret = TRUE;
+
+ /* does device exist in db? */
+ object = dkp_device_list_lookup (backend->priv->device_list, native);
+ if (object != NULL) {
+ device = DKP_DEVICE (object);
+ /* we already have the device; treat as change event */
+ egg_debug ("treating add event as change event on %s", dkp_device_get_object_path (device));
+ dkp_backend_device_changed (backend, native, FALSE);
+ goto out;
+ }
+
+ /* get the right sort of device */
+ device = dkp_backend_device_get (backend, native);
+ if (device == NULL) {
+ egg_debug ("not adding device %s", g_udev_device_get_sysfs_path (native));
+ ret = FALSE;
+ goto out;
+ }
+
+ /* emit */
+ g_signal_emit (backend, signals[SIGNAL_DEVICE_ADDED], 0, native, device, emit_signal);
+out:
+ return ret;
+}
+
+/**
+ * dkp_backend_device_remove:
+ **/
+static void
+dkp_backend_device_remove (DkpBackend *backend, GUdevDevice *native)
+{
+ GObject *object;
+ DkpDevice *device;
+
+ /* does device exist in db? */
+ object = dkp_device_list_lookup (backend->priv->device_list, native);
+ if (object == NULL) {
+ egg_debug ("ignoring remove event on %s", g_udev_device_get_sysfs_path (native));
+ } else {
+ device = DKP_DEVICE (object);
+ /* emit */
+ g_signal_emit (backend, signals[SIGNAL_DEVICE_REMOVED], 0, native, device);
+ }
+}
+
+/**
+ * dkp_backend_uevent_signal_handler_cb:
+ **/
+static void
+dkp_backend_uevent_signal_handler_cb (GUdevClient *client, const gchar *action,
+ GUdevDevice *device, gpointer user_data)
+{
+ DkpBackend *backend = DKP_DAEMON (user_data);
+
+ if (g_strcmp0 (action, "add") == 0) {
+ egg_debug ("add %s", g_udev_device_get_sysfs_path (device));
+ dkp_backend_device_add (backend, device, TRUE);
+ } else if (g_strcmp0 (action, "remove") == 0) {
+ egg_debug ("remove %s", g_udev_device_get_sysfs_path (device));
+ dkp_backend_device_remove (backend, device);
+ } else if (g_strcmp0 (action, "change") == 0) {
+ egg_debug ("change %s", g_udev_device_get_sysfs_path (device));
+ dkp_backend_device_changed (backend, device, FALSE);
+ } else {
+ egg_warning ("unhandled action '%s' on %s", action, g_udev_device_get_sysfs_path (device));
+ }
+}
+
+/**
+ * dkp_backend_add_cb:
+ **/
+static gboolean
+dkp_backend_add_cb (DkpBackend *backend)
+{
+ gboolean ret;
+
+ /* coldplug */
+ ret = dkp_device_coldplug (backend->priv->device, backend->priv->daemon, backend->priv->native);
+ if (!ret) {
+ egg_warning ("failed to coldplug");
+ goto out;
+ }
+
+ /* emit */
+ g_signal_emit (backend, signals[SIGNAL_DEVICE_ADDED], 0, backend->priv->native, backend->priv->device, TRUE);
+out:
+ return FALSE;
+}
+
+/**
+ * dkp_backend_coldplug:
+ * @backend: The %DkpBackend class instance
+ * @daemon: The %DkpDaemon controlling instance
+ *
+ * Finds all the devices already plugged in, and emits device-add signals for
+ * each of them.
+ *
+ * Return value: %TRUE for success
+ **/
+gboolean
+dkp_backend_coldplug (DkpBackend *backend, DkpDaemon *daemon)
+{
+ GUdevDevice *native;
+ GList *devices;
+ GList *l;
+ guint i;
+ const gchar *subsystems[] = {"power_supply", "usb", "tty", "input", NULL};
+
+ backend->priv->daemon = g_object_ref (daemon);
+ backend->priv->device_list = dkp_daemon_get_device_list (daemon);
+
+ /* add all subsystems */
+ for (i=0; subsystems[i] != NULL; i++) {
+ devices = g_udev_client_query_by_subsystem (backend->priv->gudev_client, subsystems[i]);
+ for (l = devices; l != NULL; l = l->next) {
+ native = l->data;
+ dkp_backend_device_add (backend, native, FALSE);
+ }
+ g_list_foreach (devices, (GFunc) g_object_unref, NULL);
+ g_list_free (devices);
+ }
+
+ /* connect to the DeviceKit backend */
+ for (i=0; subsystems[i] != NULL; i++)
+ egg_debug ("registering subsystem : %s", subsystems[i]);
+ backend->priv->gudev_client = g_udev_client_new (subsystems);
+ g_signal_connect (backend->priv->gudev_client, "uevent",
+ G_CALLBACK (dkp_backend_uevent_signal_handler_cb), backend);
+
+ return TRUE;
+}
+
+/**
+ * dkp_backend_class_init:
+ * @klass: The DkpBackendClass
+ **/
+static void
+dkp_backend_class_init (DkpBackendClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = dkp_backend_finalize;
+
+ signals [SIGNAL_DEVICE_ADDED] =
+ g_signal_new ("device-added",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DkpBackendClass, device_added),
+ NULL, NULL, dkp_marshal_VOID__POINTER_POINTER_BOOLEAN,
+ G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_BOOLEAN);
+ signals [SIGNAL_DEVICE_CHANGED] =
+ g_signal_new ("device-changed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DkpBackendClass, device_changed),
+ NULL, NULL, dkp_marshal_VOID__POINTER_POINTER_BOOLEAN,
+ G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_BOOLEAN);
+ signals [SIGNAL_DEVICE_REMOVED] =
+ g_signal_new ("device-removed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DkpBackendClass, device_removed),
+ NULL, NULL, dkp_marshal_VOID__POINTER_POINTER,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
+
+ g_type_class_add_private (klass, sizeof (DkpBackendPrivate));
+}
+
+/**
+ * dkp_backend_init:
+ **/
+static void
+dkp_backend_init (DkpBackend *backend)
+{
+ backend->priv = DKP_BACKEND_GET_PRIVATE (backend);
+ backend->priv->daemon = NULL;
+ backend->priv->device_list = NULL;
+ backend->priv->native = g_object_new (DKP_TYPE_DEVICE, NULL);
+ backend->priv->device = dkp_device_new ();
+ backend->priv->managed_devices = dkp_device_list_new ();
+}
+
+/**
+ * dkp_backend_finalize:
+ **/
+static void
+dkp_backend_finalize (GObject *object)
+{
+ DkpBackend *backend;
+
+ g_return_if_fail (DKP_IS_BACKEND (object));
+
+ backend = DKP_BACKEND (object);
+
+ if (backend->priv->daemon != NULL)
+ g_object_unref (backend->priv->daemon);
+ if (backend->priv->device_list != NULL)
+ g_object_unref (backend->priv->device_list);
+ if (backend->priv->gudev_client != NULL)
+ g_object_unref (backend->priv->gudev_client);
+
+ g_object_unref (backend->priv->native);
+ g_object_unref (backend->priv->device);
+ g_object_unref (backend->priv->managed_devices);
+
+ G_OBJECT_CLASS (dkp_backend_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_backend_new:
+ *
+ * Return value: a new %DkpBackend object.
+ **/
+DkpBackend *
+dkp_backend_new (void)
+{
+ DkpBackend *backend;
+ backend = g_object_new (DKP_TYPE_BACKEND, NULL);
+ return DKP_BACKEND (backend);
+}
+
diff --git a/src/linux/dkp-device-csr.c b/src/linux/dkp-device-csr.c
new file mode 100644
index 0000000..2110180
--- /dev/null
+++ b/src/linux/dkp-device-csr.c
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005-2008 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2004 Sergey V. Udaltsov <svu@gnome.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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+#include <usb.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "dkp-enum.h"
+#include "dkp-device-csr.h"
+
+#define DKP_DEVICE_CSR_REFRESH_TIMEOUT 30L
+
+/* Internal CSR registers */
+#define CSR_P6 (buf[0])
+#define CSR_P0 (buf[1])
+#define CSR_P4 (buf[2])
+#define CSR_P5 (buf[3])
+#define CSR_P8 (buf[4])
+#define CSR_P9 (buf[5])
+#define CSR_PB0 (buf[6])
+#define CSR_PB1 (buf[7])
+
+struct DkpDeviceCsrPrivate
+{
+ guint poll_timer_id;
+ gboolean is_dual;
+ guint bus_num;
+ guint dev_num;
+ gint raw_value;
+ struct usb_device *device;
+};
+
+static void dkp_device_csr_class_init (DkpDeviceCsrClass *klass);
+
+G_DEFINE_TYPE (DkpDeviceCsr, dkp_device_csr, DKP_TYPE_DEVICE)
+#define DKP_DEVICE_CSR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_CSR, DkpDeviceCsrPrivate))
+
+static gboolean dkp_device_csr_refresh (DkpDevice *device);
+
+/**
+ * dkp_device_csr_poll_cb:
+ **/
+static gboolean
+dkp_device_csr_poll_cb (DkpDeviceCsr *csr)
+{
+ gboolean ret;
+ DkpDevice *device = DKP_DEVICE (csr);
+
+ egg_debug ("Polling: %s", dkp_device_get_object_path (device));
+ ret = dkp_device_csr_refresh (device);
+ if (ret)
+ dkp_device_emit_changed (device);
+ return TRUE;
+}
+
+/**
+ * dkp_device_csr_find_device:
+ **/
+static struct usb_device *
+dkp_device_csr_find_device (DkpDeviceCsr *csr)
+{
+ struct usb_bus *curr_bus;
+ struct usb_device *curr_device;
+ gchar *dir_name;
+ gchar *filename;
+
+ dir_name = g_strdup_printf ("%03d", csr->priv->bus_num);
+ filename = g_strdup_printf ("%03d",csr->priv->dev_num);
+ egg_debug ("Looking for: [%s][%s]", dir_name, filename);
+
+ for (curr_bus = usb_busses; curr_bus != NULL; curr_bus = curr_bus->next) {
+ /* egg_debug ("Checking bus: [%s]", curr_bus->dirname); */
+ if (g_strcasecmp (dir_name, curr_bus->dirname))
+ continue;
+
+ for (curr_device = curr_bus->devices; curr_device != NULL;
+ curr_device = curr_device->next) {
+ /* egg_debug ("Checking port: [%s]", curr_device->filename); */
+ if (g_strcasecmp (filename, curr_device->filename))
+ continue;
+ egg_debug ("Matched device: [%s][%s][%04X:%04X]", curr_bus->dirname,
+ curr_device->filename,
+ curr_device->descriptor.idVendor,
+ curr_device->descriptor.idProduct);
+ goto out;
+ }
+ }
+ /* nothing found */
+ curr_device = NULL;
+out:
+ g_free (dir_name);
+ g_free (filename);
+ return curr_device;
+}
+
+/**
+ * dkp_device_csr_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+dkp_device_csr_coldplug (DkpDevice *device)
+{
+ DkpDeviceCsr *csr = DKP_DEVICE_CSR (device);
+ GUdevDevice *d;
+ gboolean ret = FALSE;
+ const gchar *type;
+ const gchar *native_path;
+ const gchar *vendor;
+ const gchar *product;
+
+ /* detect what kind of device we are */
+ d = dkp_device_get_native (device);
+ if (d == NULL)
+ egg_error ("could not get device");
+
+ /* get the type */
+ type = g_udev_device_get_property (d, "DKP_BATTERY_TYPE");
+ if (type == NULL)
+ goto out;
+
+ /* which one? */
+ if (g_strcmp0 (type, "mouse") == 0)
+ g_object_set (device, "type", DKP_DEVICE_TYPE_MOUSE, NULL);
+ else if (g_strcmp0 (type, "keyboard") == 0)
+ g_object_set (device, "type", DKP_DEVICE_TYPE_KEYBOARD, NULL);
+ else {
+ egg_debug ("not a recognised csr device");
+ goto out;
+ }
+
+ /* get what USB device we are */
+ native_path = g_udev_device_get_sysfs_path (d);
+ csr->priv->bus_num = sysfs_get_int (native_path, "busnum");
+ csr->priv->dev_num = sysfs_get_int (native_path, "devnum");
+
+ /* get correct bus numbers? */
+ if (csr->priv->bus_num == 0 || csr->priv->dev_num == 0) {
+ egg_warning ("unable to get bus or device numbers");
+ goto out;
+ }
+
+ /* try to get the usb device */
+ csr->priv->device = dkp_device_csr_find_device (csr);
+ if (csr->priv->device == NULL) {
+ egg_debug ("failed to get device %p", csr);
+ goto out;
+ }
+
+ /* get optional quirk parameters */
+ ret = g_udev_device_has_property (d, "DKP_CSR_DUAL");
+ if (ret)
+ csr->priv->is_dual = g_udev_device_get_property_as_boolean (d, "DKP_CSR_DUAL");
+ egg_debug ("is_dual=%i", csr->priv->is_dual);
+
+ /* prefer DKP names */
+ vendor = g_udev_device_get_property (d, "DKP_VENDOR");
+ if (vendor == NULL)
+ vendor = g_udev_device_get_property (d, "ID_VENDOR");
+ product = g_udev_device_get_property (d, "DKP_PRODUCT");
+ if (product == NULL)
+ product = g_udev_device_get_property (d, "ID_PRODUCT");
+
+ /* hardcode some values */
+ g_object_set (device,
+ "vendor", vendor,
+ "model", product,
+ "power-supply", FALSE,
+ "is-present", TRUE,
+ "is-rechargeable", TRUE,
+ "state", DKP_DEVICE_STATE_DISCHARGING,
+ "has-history", TRUE,
+ NULL);
+
+ /* coldplug */
+ ret = dkp_device_csr_refresh (device);
+ if (!ret)
+ goto out;
+
+ /* set up a poll */
+ csr->priv->poll_timer_id = g_timeout_add_seconds (DKP_DEVICE_CSR_REFRESH_TIMEOUT,
+ (GSourceFunc) dkp_device_csr_poll_cb, csr);
+
+out:
+ return ret;
+}
+
+/**
+ * dkp_device_csr_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_csr_refresh (DkpDevice *device)
+{
+ gboolean ret = FALSE;
+ GTimeVal time;
+ DkpDeviceCsr *csr = DKP_DEVICE_CSR (device);
+ usb_dev_handle *handle = NULL;
+ char buf[80];
+ unsigned int addr;
+ gdouble percentage;
+ guint written;
+
+ g_get_current_time (&time);
+ g_object_set (device, "update-time", (guint64) time.tv_sec, NULL);
+
+ /* For dual receivers C502, C504 and C505, the mouse is the
+ * second device and uses an addr of 1 in the value and index
+ * fields' high byte */
+ addr = csr->priv->is_dual ? 1<<8 : 0;
+
+ if (csr->priv->device == NULL) {
+ egg_warning ("no device!");
+ goto out;
+ }
+
+ /* open USB device */
+ handle = usb_open (csr->priv->device);
+ if (handle == NULL) {
+ egg_warning ("could not open device");
+ goto out;
+ }
+
+ /* get the charge */
+ written = usb_control_msg (handle, 0xc0, 0x09, 0x03|addr, 0x00|addr, buf, 8, DKP_DEVICE_CSR_REFRESH_TIMEOUT);
+ ret = (written == 8);
+ if (!ret) {
+ egg_warning ("failed to write to device, wrote %i bytes", written);
+ goto out;
+ }
+
+ /* is a C504 receiver busy? */
+ if (CSR_P0 == 0x3b && CSR_P4 == 0) {
+ egg_debug ("receiver busy");
+ goto out;
+ }
+
+ /* get battery status */
+ csr->priv->raw_value = CSR_P5 & 0x07;
+ egg_debug ("charge level: %d", csr->priv->raw_value);
+ if (csr->priv->raw_value != 0) {
+ percentage = (100.0 / 7.0) * csr->priv->raw_value;
+ g_object_set (device, "percentage", percentage, NULL);
+ egg_debug ("percentage=%f", percentage);
+ }
+
+out:
+ if (handle != NULL)
+ usb_close (handle);
+ return ret;
+}
+
+/**
+ * dkp_device_csr_init:
+ **/
+static void
+dkp_device_csr_init (DkpDeviceCsr *csr)
+{
+ csr->priv = DKP_DEVICE_CSR_GET_PRIVATE (csr);
+
+ usb_init ();
+ usb_find_busses ();
+ usb_find_devices ();
+
+ csr->priv->is_dual = FALSE;
+ csr->priv->raw_value = -1;
+ csr->priv->poll_timer_id = 0;
+}
+
+/**
+ * dkp_device_csr_finalize:
+ **/
+static void
+dkp_device_csr_finalize (GObject *object)
+{
+ DkpDeviceCsr *csr;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (DKP_IS_CSR (object));
+
+ csr = DKP_DEVICE_CSR (object);
+ g_return_if_fail (csr->priv != NULL);
+
+ if (csr->priv->poll_timer_id > 0)
+ g_source_remove (csr->priv->poll_timer_id);
+
+ G_OBJECT_CLASS (dkp_device_csr_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_device_csr_class_init:
+ **/
+static void
+dkp_device_csr_class_init (DkpDeviceCsrClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass);
+
+ object_class->finalize = dkp_device_csr_finalize;
+ device_class->coldplug = dkp_device_csr_coldplug;
+ device_class->refresh = dkp_device_csr_refresh;
+
+ g_type_class_add_private (klass, sizeof (DkpDeviceCsrPrivate));
+}
+
+/**
+ * dkp_device_csr_new:
+ **/
+DkpDeviceCsr *
+dkp_device_csr_new (void)
+{
+ return g_object_new (DKP_TYPE_CSR, NULL);
+}
+
diff --git a/src/linux/dkp-device-csr.h b/src/linux/dkp-device-csr.h
new file mode 100644
index 0000000..04e101b
--- /dev/null
+++ b/src/linux/dkp-device-csr.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __DKP_DEVICE_CSR_H__
+#define __DKP_DEVICE_CSR_H__
+
+#include <glib-object.h>
+#include "dkp-device.h"
+
+G_BEGIN_DECLS
+
+#define DKP_TYPE_CSR (dkp_device_csr_get_type ())
+#define DKP_DEVICE_CSR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DKP_TYPE_CSR, DkpDeviceCsr))
+#define DKP_DEVICE_CSR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DKP_TYPE_CSR, DkpDeviceCsrClass))
+#define DKP_IS_CSR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DKP_TYPE_CSR))
+#define DKP_IS_CSR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DKP_TYPE_CSR))
+#define DKP_DEVICE_CSR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DKP_TYPE_CSR, DkpDeviceCsrClass))
+
+typedef struct DkpDeviceCsrPrivate DkpDeviceCsrPrivate;
+
+typedef struct
+{
+ DkpDevice parent;
+ DkpDeviceCsrPrivate *priv;
+} DkpDeviceCsr;
+
+typedef struct
+{
+ DkpDeviceClass parent_class;
+} DkpDeviceCsrClass;
+
+GType dkp_device_csr_get_type (void);
+DkpDeviceCsr *dkp_device_csr_new (void);
+
+G_END_DECLS
+
+#endif /* __DKP_DEVICE_CSR_H__ */
+
diff --git a/src/linux/dkp-device-hid.c b/src/linux/dkp-device-hid.c
new file mode 100644
index 0000000..60abb9d
--- /dev/null
+++ b/src/linux/dkp-device-hid.c
@@ -0,0 +1,487 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006-2008 Richard Hughes <richard@hughsie.com>
+ *
+ * Based on hid-ups.c: Copyright (c) 2001 Vojtech Pavlik <vojtech@ucw.cz>
+ * Copyright (c) 2001 Paul Stewart <hiddev@wetlogic.net>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+
+/* asm/types.h required for __s32 in linux/hiddev.h */
+#include <asm/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/hiddev.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "dkp-enum.h"
+#include "dkp-device-hid.h"
+
+#define DKP_DEVICE_HID_REFRESH_TIMEOUT 30l
+
+#define DKP_DEVICE_HID_USAGE 0x840000
+#define DKP_DEVICE_HID_SERIAL 0x8400fe
+#define DKP_DEVICE_HID_CHEMISTRY 0x850089
+#define DKP_DEVICE_HID_CAPACITY_MODE 0x85002c
+#define DKP_DEVICE_HID_BATTERY_VOLTAGE 0x840030
+#define DKP_DEVICE_HID_BELOW_RCL 0x840042
+#define DKP_DEVICE_HID_SHUTDOWN_IMMINENT 0x840069
+#define DKP_DEVICE_HID_PRODUCT 0x8400fe
+#define DKP_DEVICE_HID_SERIAL_NUMBER 0x8400ff
+#define DKP_DEVICE_HID_CHARGING 0x850044
+#define DKP_DEVICE_HID_DISCHARGING 0x850045
+#define DKP_DEVICE_HID_REMAINING_CAPACITY 0x850066
+#define DKP_DEVICE_HID_RUNTIME_TO_EMPTY 0x850068
+#define DKP_DEVICE_HID_AC_PRESENT 0x8500d0
+#define DKP_DEVICE_HID_BATTERY_PRESENT 0x8500d1
+#define DKP_DEVICE_HID_DESIGN_CAPACITY 0x850083
+#define DKP_DEVICE_HID_DEVICE_NAME 0x850088
+#define DKP_DEVICE_HID_DEVICE_CHEMISTRY 0x850089
+#define DKP_DEVICE_HID_RECHARGEABLE 0x85008b
+#define DKP_DEVICE_HID_OEM_INFORMATION 0x85008f
+
+#define DKP_DEVICE_HID_PAGE_GENERIC_DESKTOP 0x01
+#define DKP_DEVICE_HID_PAGE_CONSUMER_PRODUCT 0x0c
+#define DKP_DEVICE_HID_PAGE_USB_MONITOR 0x80
+#define DKP_DEVICE_HID_PAGE_USB_ENUMERATED_VALUES 0x81
+#define DKP_DEVICE_HID_PAGE_VESA_VIRTUAL_CONTROLS 0x82
+#define DKP_DEVICE_HID_PAGE_RESERVED_MONITOR 0x83
+#define DKP_DEVICE_HID_PAGE_POWER_DEVICE 0x84
+#define DKP_DEVICE_HID_PAGE_BATTERY_SYSTEM 0x85
+
+struct DkpDeviceHidPrivate
+{
+ guint poll_timer_id;
+ int fd;
+};
+
+static void dkp_device_hid_class_init (DkpDeviceHidClass *klass);
+
+G_DEFINE_TYPE (DkpDeviceHid, dkp_device_hid, DKP_TYPE_DEVICE)
+#define DKP_DEVICE_HID_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_HID, DkpDeviceHidPrivate))
+
+static gboolean dkp_device_hid_refresh (DkpDevice *device);
+
+/**
+ * dkp_device_hid_is_ups:
+ **/
+static gboolean
+dkp_device_hid_is_ups (DkpDeviceHid *hid)
+{
+ guint i;
+ int retval;
+ gboolean ret = FALSE;
+ struct hiddev_devinfo device_info;
+
+ /* get device info */
+ retval = ioctl (hid->priv->fd, HIDIOCGDEVINFO, &device_info);
+ if (retval < 0) {
+ egg_debug ("HIDIOCGDEVINFO failed: %s", strerror (errno));
+ goto out;
+ }
+
+ /* can we use the hid device as a UPS? */
+ for (i = 0; i < device_info.num_applications; i++) {
+ retval = ioctl (hid->priv->fd, HIDIOCAPPLICATION, i);
+ if (retval >> 16 == DKP_DEVICE_HID_PAGE_POWER_DEVICE) {
+ ret = TRUE;
+ goto out;
+ }
+ }
+out:
+ return ret;
+}
+
+/**
+ * dkp_device_hid_poll:
+ **/
+static gboolean
+dkp_device_hid_poll (DkpDeviceHid *hid)
+{
+ gboolean ret;
+ DkpDevice *device = DKP_DEVICE (hid);
+
+ egg_debug ("Polling: %s", dkp_device_get_object_path (device));
+ ret = dkp_device_hid_refresh (device);
+ if (ret)
+ dkp_device_emit_changed (device);
+ return TRUE;
+}
+
+/**
+ * dkp_device_hid_get_string:
+ **/
+static const gchar *
+dkp_device_hid_get_string (DkpDeviceHid *hid, int sindex)
+{
+ static struct hiddev_string_descriptor sdesc;
+
+ /* nothing to get */
+ if (sindex == 0)
+ return "";
+
+ sdesc.index = sindex;
+
+ /* failed */
+ if (ioctl (hid->priv->fd, HIDIOCGSTRING, &sdesc) < 0)
+ return "";
+
+ egg_debug ("value: '%s'", sdesc.value);
+ return sdesc.value;
+}
+
+/**
+ * dkp_device_hid_convert_device_technology:
+ **/
+static DkpDeviceTechnology
+dkp_device_hid_convert_device_technology (const gchar *type)
+{
+ if (type == NULL)
+ return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
+ if (strcasecmp (type, "pb") == 0 ||
+ strcasecmp (type, "pbac") == 0)
+ return DKP_DEVICE_TECHNOLOGY_LEAD_ACID;
+ return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
+}
+
+/**
+ * dkp_device_hid_set_values:
+ **/
+static gboolean
+dkp_device_hid_set_values (DkpDeviceHid *hid, int code, int value)
+{
+ const gchar *type;
+ gboolean ret = TRUE;
+ DkpDevice *device = DKP_DEVICE (hid);
+
+ switch (code) {
+ case DKP_DEVICE_HID_REMAINING_CAPACITY:
+ g_object_set (device, "percentage", (gfloat) value, NULL);
+ break;
+ case DKP_DEVICE_HID_RUNTIME_TO_EMPTY:
+ g_object_set (device, "time-to-empty", (gint64) value, NULL);
+ break;
+ case DKP_DEVICE_HID_CHARGING:
+ if (value != 0)
+ g_object_set (device, "state", DKP_DEVICE_STATE_CHARGING, NULL);
+ break;
+ case DKP_DEVICE_HID_DISCHARGING:
+ if (value != 0)
+ g_object_set (device, "state", DKP_DEVICE_STATE_DISCHARGING, NULL);
+ break;
+ case DKP_DEVICE_HID_BATTERY_PRESENT:
+ g_object_set (device, "is-present", (value != 0), NULL);
+ break;
+ case DKP_DEVICE_HID_DEVICE_NAME:
+ g_object_set (device, "device-name", dkp_device_hid_get_string (hid, value), NULL);
+ break;
+ case DKP_DEVICE_HID_CHEMISTRY:
+ type = dkp_device_hid_get_string (hid, value);
+ g_object_set (device, "technology", dkp_device_hid_convert_device_technology (type), NULL);
+ break;
+ case DKP_DEVICE_HID_RECHARGEABLE:
+ g_object_set (device, "is-rechargeable", (value != 0), NULL);
+ break;
+ case DKP_DEVICE_HID_OEM_INFORMATION:
+ g_object_set (device, "vendor", dkp_device_hid_get_string (hid, value), NULL);
+ break;
+ case DKP_DEVICE_HID_PRODUCT:
+ g_object_set (device, "model", dkp_device_hid_get_string (hid, value), NULL);
+ break;
+ case DKP_DEVICE_HID_SERIAL_NUMBER:
+ g_object_set (device, "serial", dkp_device_hid_get_string (hid, value), NULL);
+ break;
+ case DKP_DEVICE_HID_DESIGN_CAPACITY:
+ g_object_set (device, "energy-full-design", (gfloat) value, NULL);
+ break;
+ default:
+ ret = FALSE;
+ break;
+ }
+ return ret;
+}
+
+/**
+ * dkp_device_hid_get_all_data:
+ **/
+static gboolean
+dkp_device_hid_get_all_data (DkpDeviceHid *hid)
+{
+ struct hiddev_report_info rinfo;
+ struct hiddev_field_info finfo;
+ struct hiddev_usage_ref uref;
+ int rtype;
+ guint i, j;
+ gboolean ret = FALSE;
+
+ /* get all results */
+ for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX; rtype++) {
+ rinfo.report_type = rtype;
+ rinfo.report_id = HID_REPORT_ID_FIRST;
+ while (ioctl (hid->priv->fd, HIDIOCGREPORTINFO, &rinfo) >= 0) {
+ for (i = 0; i < rinfo.num_fields; i++) {
+ memset (&finfo, 0, sizeof (finfo));
+ finfo.report_type = rinfo.report_type;
+ finfo.report_id = rinfo.report_id;
+ finfo.field_index = i;
+ ioctl (hid->priv->fd, HIDIOCGFIELDINFO, &finfo);
+
+ memset (&uref, 0, sizeof (uref));
+ for (j = 0; j < finfo.maxusage; j++) {
+ uref.report_type = finfo.report_type;
+ uref.report_id = finfo.report_id;
+ uref.field_index = i;
+ uref.usage_index = j;
+ ioctl (hid->priv->fd, HIDIOCGUCODE, &uref);
+ ioctl (hid->priv->fd, HIDIOCGUSAGE, &uref);
+
+ /* process each */
+ dkp_device_hid_set_values (hid, uref.usage_code, uref.value);
+
+ /* we got some data */
+ ret = TRUE;
+ }
+ }
+ rinfo.report_id |= HID_REPORT_ID_NEXT;
+ }
+ }
+ return ret;
+}
+
+/**
+ * dkp_device_hid_fixup_state:
+ **/
+static void
+dkp_device_hid_fixup_state (DkpDevice *device)
+{
+ gdouble percentage;
+
+ /* map states the UPS cannot express */
+ g_object_get (device, "percentage", &percentage, NULL);
+ if (percentage < 0.01)
+ g_object_set (device, "state", DKP_DEVICE_STATE_EMPTY, NULL);
+ if (percentage > 99.9)
+ g_object_set (device, "state", DKP_DEVICE_STATE_FULLY_CHARGED, NULL);
+}
+
+/**
+ * dkp_device_hid_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+dkp_device_hid_coldplug (DkpDevice *device)
+{
+ DkpDeviceHid *hid = DKP_DEVICE_HID (device);
+ GUdevDevice *d;
+ gboolean ret = FALSE;
+ const gchar *device_file;
+ const gchar *type;
+ const gchar *vendor;
+
+ /* detect what kind of device we are */
+ d = dkp_device_get_native (device);
+ if (d == NULL)
+ egg_error ("could not get device");
+
+ /* get the type */
+ type = g_udev_device_get_property (d, "DKP_BATTERY_TYPE");
+ if (type == NULL || g_strcmp0 (type, "ups") != 0)
+ goto out;
+
+ /* get the device file */
+ device_file = g_udev_device_get_device_file (d);
+ if (device_file == NULL) {
+ egg_debug ("could not get device file for HID device");
+ goto out;
+ }
+
+ /* connect to the device */
+ egg_debug ("using device: %s", device_file);
+ hid->priv->fd = open (device_file, O_RDONLY | O_NONBLOCK);
+ if (hid->priv->fd < 0) {
+ egg_debug ("cannot open device file %s", device_file);
+ goto out;
+ }
+
+ /* first check that we are an UPS */
+ ret = dkp_device_hid_is_ups (hid);
+ if (!ret) {
+ egg_debug ("not a HID device: %s", device_file);
+ goto out;
+ }
+
+ /* prefer DKP names */
+ vendor = g_udev_device_get_property (d, "DKP_VENDOR");
+ if (vendor == NULL)
+ vendor = g_udev_device_get_property (d, "ID_VENDOR");
+
+ /* hardcode some values */
+ g_object_set (device,
+ "type", DKP_DEVICE_TYPE_UPS,
+ "is-rechargeable", TRUE,
+ "power-supply", TRUE,
+ "is-present", TRUE,
+ "vendor", vendor,
+ "has-history", TRUE,
+ "has-statistics", TRUE,
+ NULL);
+
+ /* coldplug everything */
+ ret = dkp_device_hid_get_all_data (hid);
+ if (!ret) {
+ egg_debug ("failed to coldplug: %s", device_file);
+ goto out;
+ }
+
+ /* fix up device states */
+ dkp_device_hid_fixup_state (device);
+out:
+ return ret;
+}
+
+/**
+ * dkp_device_hid_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_hid_refresh (DkpDevice *device)
+{
+ gboolean set = FALSE;
+ gboolean ret = FALSE;
+ GTimeVal time;
+ guint i;
+ struct hiddev_event ev[64];
+ int rd;
+ DkpDeviceHid *hid = DKP_DEVICE_HID (device);
+
+ /* reset time */
+ g_get_current_time (&time);
+ g_object_set (device, "update-time", (guint64) time.tv_sec, NULL);
+
+ /* read any data */
+ rd = read (hid->priv->fd, ev, sizeof (ev));
+
+ /* it's okay if there's nothing as we are non-blocking */
+ if (rd == -1) {
+ egg_debug ("no data");
+ ret = FALSE;
+ goto out;
+ }
+
+ /* did we read enough data? */
+ if (rd < (int) sizeof (ev[0])) {
+ egg_warning ("incomplete read (%i<%i)", rd, (int) sizeof (ev[0]));
+ goto out;
+ }
+
+ /* process each event */
+ for (i=0; i < rd / sizeof (ev[0]); i++) {
+ set = dkp_device_hid_set_values (hid, ev[i].hid, ev[i].value);
+
+ /* if only takes one match to make refresh a success */
+ if (set)
+ ret = TRUE;
+ }
+
+ /* fix up device states */
+ dkp_device_hid_fixup_state (device);
+out:
+ return ret;
+}
+
+/**
+ * dkp_device_hid_init:
+ **/
+static void
+dkp_device_hid_init (DkpDeviceHid *hid)
+{
+ hid->priv = DKP_DEVICE_HID_GET_PRIVATE (hid);
+ hid->priv->fd = -1;
+ hid->priv->poll_timer_id = g_timeout_add_seconds (DKP_DEVICE_HID_REFRESH_TIMEOUT,
+ (GSourceFunc) dkp_device_hid_poll, hid);
+}
+
+/**
+ * dkp_device_hid_finalize:
+ **/
+static void
+dkp_device_hid_finalize (GObject *object)
+{
+ DkpDeviceHid *hid;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (DKP_IS_HID (object));
+
+ hid = DKP_DEVICE_HID (object);
+ g_return_if_fail (hid->priv != NULL);
+
+ if (hid->priv->fd > 0)
+ close (hid->priv->fd);
+ if (hid->priv->poll_timer_id > 0)
+ g_source_remove (hid->priv->poll_timer_id);
+
+ G_OBJECT_CLASS (dkp_device_hid_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_device_hid_class_init:
+ **/
+static void
+dkp_device_hid_class_init (DkpDeviceHidClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass);
+
+ object_class->finalize = dkp_device_hid_finalize;
+ device_class->coldplug = dkp_device_hid_coldplug;
+ device_class->refresh = dkp_device_hid_refresh;
+
+ g_type_class_add_private (klass, sizeof (DkpDeviceHidPrivate));
+}
+
+/**
+ * dkp_device_hid_new:
+ **/
+DkpDeviceHid *
+dkp_device_hid_new (void)
+{
+ return g_object_new (DKP_TYPE_HID, NULL);
+}
+
diff --git a/src/linux/dkp-device-hid.h b/src/linux/dkp-device-hid.h
new file mode 100644
index 0000000..8607331
--- /dev/null
+++ b/src/linux/dkp-device-hid.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __DKP_DEVICE_HID_H__
+#define __DKP_DEVICE_HID_H__
+
+#include <glib-object.h>
+#include "dkp-device.h"
+
+G_BEGIN_DECLS
+
+#define DKP_TYPE_HID (dkp_device_hid_get_type ())
+#define DKP_DEVICE_HID(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DKP_TYPE_HID, DkpDeviceHid))
+#define DKP_DEVICE_HID_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DKP_TYPE_HID, DkpDeviceHidClass))
+#define DKP_IS_HID(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DKP_TYPE_HID))
+#define DKP_IS_HID_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DKP_TYPE_HID))
+#define DKP_DEVICE_HID_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DKP_TYPE_HID, DkpDeviceHidClass))
+
+typedef struct DkpDeviceHidPrivate DkpDeviceHidPrivate;
+
+typedef struct
+{
+ DkpDevice parent;
+ DkpDeviceHidPrivate *priv;
+} DkpDeviceHid;
+
+typedef struct
+{
+ DkpDeviceClass parent_class;
+} DkpDeviceHidClass;
+
+GType dkp_device_hid_get_type (void);
+DkpDeviceHid *dkp_device_hid_new (void);
+
+G_END_DECLS
+
+#endif /* __DKP_DEVICE_HID_H__ */
+
diff --git a/src/linux/dkp-device-supply.c b/src/linux/dkp-device-supply.c
new file mode 100644
index 0000000..43ca7f1
--- /dev/null
+++ b/src/linux/dkp-device-supply.c
@@ -0,0 +1,795 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "dkp-enum.h"
+#include "dkp-device-supply.h"
+
+#define DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT 30 /* seconds */
+#define DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT 2 /* seconds */
+#define DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES 30
+#define DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD 90.0f /* % */
+
+struct DkpDeviceSupplyPrivate
+{
+ guint poll_timer_id;
+ gboolean has_coldplug_values;
+ gdouble energy_old;
+ GTimeVal energy_old_timespec;
+ guint unknown_retries;
+};
+
+static void dkp_device_supply_class_init (DkpDeviceSupplyClass *klass);
+
+G_DEFINE_TYPE (DkpDeviceSupply, dkp_device_supply, DKP_TYPE_DEVICE)
+#define DKP_DEVICE_SUPPLY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_SUPPLY, DkpDeviceSupplyPrivate))
+
+static gboolean dkp_device_supply_refresh (DkpDevice *device);
+
+/**
+ * dkp_device_supply_refresh_line_power:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_supply_refresh_line_power (DkpDeviceSupply *supply)
+{
+ DkpDevice *device = DKP_DEVICE (supply);
+ GUdevDevice *d;
+ const gchar *native_path;
+
+ d = dkp_device_get_native (device);
+ if (d == NULL)
+ egg_error ("could not get device");
+
+ /* force true */
+ g_object_set (device, "power-supply", TRUE, NULL);
+
+ /* get new AC value */
+ native_path = g_udev_device_get_sysfs_path (d);
+ g_object_set (device, "online", sysfs_get_int (native_path, "online"), NULL);
+
+ return TRUE;
+}
+
+/**
+ * dkp_device_supply_reset_values:
+ **/
+static void
+dkp_device_supply_reset_values (DkpDeviceSupply *supply)
+{
+ DkpDevice *device = DKP_DEVICE (supply);
+
+ supply->priv->has_coldplug_values = FALSE;
+ supply->priv->energy_old = 0;
+ supply->priv->energy_old_timespec.tv_sec = 0;
+
+ /* reset to default */
+ g_object_set (device,
+ "vendor", NULL,
+ "model", NULL,
+ "serial", NULL,
+ "update-time", 0,
+ "power-supply", FALSE,
+ "online", FALSE,
+ "energy", 0.0,
+ "is-present", FALSE,
+ "is-rechargeable", FALSE,
+ "has-history", FALSE,
+ "has-statistics", FALSE,
+ "state", NULL,
+ "capacity", 0.0,
+ "energy-empty", 0.0,
+ "energy-full", 0.0,
+ "energy-full-design", 0.0,
+ "energy-rate", 0.0,
+ "voltage", 0.0,
+ "time-to-empty", 0,
+ "time-to-full", 0,
+ "percentage", 0,
+ "technology", NULL,
+ NULL);
+}
+
+/**
+ * dkp_device_supply_get_on_battery:
+ **/
+static gboolean
+dkp_device_supply_get_on_battery (DkpDevice *device, gboolean *on_battery)
+{
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+ DkpDeviceType type;
+ DkpDeviceState state;
+ gboolean is_present;
+
+ g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
+ g_return_val_if_fail (on_battery != NULL, FALSE);
+
+ g_object_get (device,
+ "type", &type,
+ "state", &state,
+ "is-present", &is_present,
+ NULL);
+
+ if (type != DKP_DEVICE_TYPE_BATTERY)
+ return FALSE;
+ if (!is_present)
+ return FALSE;
+
+ *on_battery = (state == DKP_DEVICE_STATE_DISCHARGING);
+ return TRUE;
+}
+
+/**
+ * dkp_device_supply_get_low_battery:
+ **/
+static gboolean
+dkp_device_supply_get_low_battery (DkpDevice *device, gboolean *low_battery)
+{
+ gboolean ret;
+ gboolean on_battery;
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+ guint percentage;
+
+ g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
+ g_return_val_if_fail (low_battery != NULL, FALSE);
+
+ /* reuse the common checks */
+ ret = dkp_device_supply_get_on_battery (device, &on_battery);
+ if (!ret)
+ return FALSE;
+
+ /* shortcut */
+ if (!on_battery) {
+ *low_battery = FALSE;
+ return TRUE;
+ }
+
+ g_object_get (device, "percentage", &percentage, NULL);
+ *low_battery = (percentage < 10);
+ return TRUE;
+}
+
+/**
+ * dkp_device_supply_get_online:
+ **/
+static gboolean
+dkp_device_supply_get_online (DkpDevice *device, gboolean *online)
+{
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+ DkpDeviceType type;
+ gboolean online_tmp;
+
+ g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
+ g_return_val_if_fail (online != NULL, FALSE);
+
+ g_object_get (device,
+ "type", &type,
+ "online", &online_tmp,
+ NULL);
+
+ if (type != DKP_DEVICE_TYPE_LINE_POWER)
+ return FALSE;
+
+ *online = online_tmp;
+
+ return TRUE;
+}
+
+/**
+ * dkp_device_supply_calculate_rate:
+ **/
+static void
+dkp_device_supply_calculate_rate (DkpDeviceSupply *supply)
+{
+ guint time;
+ gdouble energy;
+ gdouble energy_rate;
+ GTimeVal now;
+ DkpDevice *device = DKP_DEVICE (supply);
+
+ g_object_get (device, "energy", &energy, NULL);
+
+ if (energy < 0)
+ return;
+
+ if (supply->priv->energy_old < 0)
+ return;
+
+ if (supply->priv->energy_old == energy)
+ return;
+
+ /* get the time difference */
+ g_get_current_time (&now);
+ time = now.tv_sec - supply->priv->energy_old_timespec.tv_sec;
+
+ if (time == 0)
+ return;
+
+ /* get the difference in charge */
+ energy = supply->priv->energy_old - energy;
+ if (energy < 0.1)
+ return;
+
+ /* probably okay */
+ energy_rate = energy * 3600 / time;
+ g_object_set (device, "energy-rate", energy_rate, NULL);
+}
+
+/**
+ * dkp_device_supply_convert_device_technology:
+ **/
+static DkpDeviceTechnology
+dkp_device_supply_convert_device_technology (const gchar *type)
+{
+ if (type == NULL)
+ return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
+ /* every case combination of Li-Ion is commonly used.. */
+ if (strcasecmp (type, "li-ion") == 0 ||
+ strcasecmp (type, "lion") == 0)
+ return DKP_DEVICE_TECHNOLOGY_LITHIUM_ION;
+ if (strcasecmp (type, "pb") == 0 ||
+ strcasecmp (type, "pbac") == 0)
+ return DKP_DEVICE_TECHNOLOGY_LEAD_ACID;
+ if (strcasecmp (type, "lip") == 0 ||
+ strcasecmp (type, "lipo") == 0 ||
+ strcasecmp (type, "li-poly") == 0)
+ return DKP_DEVICE_TECHNOLOGY_LITHIUM_POLYMER;
+ if (strcasecmp (type, "nimh") == 0)
+ return DKP_DEVICE_TECHNOLOGY_NICKEL_METAL_HYDRIDE;
+ if (strcasecmp (type, "lifo") == 0)
+ return DKP_DEVICE_TECHNOLOGY_LITHIUM_IRON_PHOSPHATE;
+ return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
+}
+
+/**
+ * dkp_device_supply_get_string:
+ **/
+static gchar *
+dkp_device_supply_get_string (const gchar *native_path, const gchar *key)
+{
+ gchar *value;
+
+ /* get value, and strip to remove spaces */
+ value = g_strstrip (sysfs_get_string (native_path, key));
+
+ /* no value */
+ if (value == NULL)
+ goto out;
+
+ /* empty value */
+ if (value[0] == '\0') {
+ g_free (value);
+ value = NULL;
+ goto out;
+ }
+out:
+ return value;
+}
+
+/**
+ * dkp_device_supply_refresh_battery:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_supply_refresh_battery (DkpDeviceSupply *supply)
+{
+ gchar *status = NULL;
+ gchar *technology_native;
+ gboolean ret = TRUE;
+ gdouble voltage_design;
+ DkpDeviceState old_state;
+ DkpDeviceState state;
+ DkpDevice *device = DKP_DEVICE (supply);
+ const gchar *native_path;
+ GUdevDevice *d;
+ gboolean is_present;
+ gdouble energy;
+ gdouble energy_full;
+ gdouble energy_full_design;
+ gdouble energy_rate;
+ gdouble capacity;
+ gdouble percentage = 0.0f;
+ gdouble voltage;
+ guint64 time_to_empty;
+ guint64 time_to_full;
+ gchar *manufacturer;
+ gchar *model_name;
+ gchar *serial_number;
+ gboolean recall_notice;
+ const gchar *recall_vendor = NULL;
+ const gchar *recall_url = NULL;
+ DkpDaemon *daemon;
+ gboolean on_battery;
+ guint battery_count;
+
+ d = dkp_device_get_native (device);
+ if (d == NULL) {
+ egg_warning ("could not get device");
+ ret = FALSE;
+ goto out;
+ }
+
+ native_path = g_udev_device_get_sysfs_path (d);
+
+ /* have we just been removed? */
+ is_present = sysfs_get_bool (native_path, "present");
+ g_object_set (device, "is-present", is_present, NULL);
+ if (!is_present) {
+ dkp_device_supply_reset_values (supply);
+ goto out;
+ }
+
+ /* get the currect charge */
+ energy = sysfs_get_double (native_path, "energy_now") / 1000000.0;
+ if (energy == 0)
+ energy = sysfs_get_double (native_path, "energy_avg") / 1000000.0;
+
+ /* used to convert A to W later */
+ voltage_design = sysfs_get_double (native_path, "voltage_max_design") / 1000000.0;
+ if (voltage_design < 1.00) {
+ voltage_design = sysfs_get_double (native_path, "voltage_min_design") / 1000000.0;
+ if (voltage_design < 1.00) {
+ egg_debug ("using present voltage as design voltage");
+ voltage_design = sysfs_get_double (native_path, "voltage_present") / 1000000.0;
+ }
+ }
+
+ /* initial values */
+ if (!supply->priv->has_coldplug_values) {
+
+ /* when we add via sysfs power_supply class then we know this is true */
+ g_object_set (device, "power-supply", TRUE, NULL);
+
+ /* the ACPI spec is bad at defining battery type constants */
+ technology_native = dkp_device_supply_get_string (native_path, "technology");
+ g_object_set (device, "technology", dkp_device_supply_convert_device_technology (technology_native), NULL);
+ g_free (technology_native);
+
+ /* get values which may be blank */
+ manufacturer = dkp_device_supply_get_string (native_path, "manufacturer");
+ model_name = dkp_device_supply_get_string (native_path, "model_name");
+ serial_number = dkp_device_supply_get_string (native_path, "serial_number");
+
+ /* are we possibly recalled by the vendor? */
+ recall_notice = g_udev_device_has_property (d, "DKP_RECALL_NOTICE");
+ if (recall_notice) {
+ recall_vendor = g_udev_device_get_property (d, "DKP_RECALL_VENDOR");
+ recall_url = g_udev_device_get_property (d, "DKP_RECALL_URL");
+ }
+
+ g_object_set (device,
+ "vendor", manufacturer,
+ "model", model_name,
+ "serial", serial_number,
+ "is-rechargeable", TRUE, /* assume true for laptops */
+ "has-history", TRUE,
+ "has-statistics", TRUE,
+ "recall-notice", recall_notice,
+ "recall-vendor", recall_vendor,
+ "recall-url", recall_url,
+ NULL);
+
+ g_free (manufacturer);
+ g_free (model_name);
+ g_free (serial_number);
+
+ /* these don't change at runtime */
+ energy_full = sysfs_get_double (native_path, "energy_full") / 1000000.0;
+ energy_full_design = sysfs_get_double (native_path, "energy_full_design") / 1000000.0;
+
+ /* convert charge to energy */
+ if (energy == 0) {
+ energy_full = sysfs_get_double (native_path, "charge_full") / 1000000.0;
+ energy_full_design = sysfs_get_double (native_path, "charge_full_design") / 1000000.0;
+ energy_full *= voltage_design;
+ energy_full_design *= voltage_design;
+ }
+
+ /* the last full should not be bigger than the design */
+ if (energy_full > energy_full_design)
+ egg_warning ("energy_full (%f) is greater than energy_full_design (%f)",
+ energy_full, energy_full_design);
+
+ /* some systems don't have this */
+ if (energy_full < 0.01) {
+ egg_warning ("correcting energy_full (%f) using energy_full_design (%f)",
+ energy_full, energy_full_design);
+ energy_full = energy_full_design;
+ }
+
+ /* calculate how broken our battery is */
+ if (energy_full > 0) {
+ capacity = (energy_full / energy_full_design) * 100.0f;
+ if (capacity < 0)
+ capacity = 0.0;
+ if (capacity > 100.0)
+ capacity = 100.0;
+ }
+ g_object_set (device, "capacity", capacity, NULL);
+
+ /* we only coldplug once, as these values will never change */
+ supply->priv->has_coldplug_values = TRUE;
+ } else {
+ /* get the old full */
+ g_object_get (device,
+ "energy-full", &energy_full,
+ "energy-full-design", &energy_full_design,
+ NULL);
+ }
+
+ status = g_strstrip (sysfs_get_string (native_path, "status"));
+ if (strcasecmp (status, "charging") == 0)
+ state = DKP_DEVICE_STATE_CHARGING;
+ else if (strcasecmp (status, "discharging") == 0)
+ state = DKP_DEVICE_STATE_DISCHARGING;
+ else if (strcasecmp (status, "full") == 0)
+ state = DKP_DEVICE_STATE_FULLY_CHARGED;
+ else if (strcasecmp (status, "empty") == 0)
+ state = DKP_DEVICE_STATE_EMPTY;
+ else if (strcasecmp (status, "unknown") == 0)
+ state = DKP_DEVICE_STATE_UNKNOWN;
+ else {
+ egg_warning ("unknown status string: %s", status);
+ state = DKP_DEVICE_STATE_UNKNOWN;
+ }
+
+ /* reset unknown counter */
+ if (state != DKP_DEVICE_STATE_UNKNOWN) {
+ egg_debug ("resetting unknown timeout after %i retries", supply->priv->unknown_retries);
+ supply->priv->unknown_retries = 0;
+ }
+
+ /* get rate; it seems odd as it's either in uVh or uWh */
+ energy_rate = fabs (sysfs_get_double (native_path, "current_now") / 1000000.0);
+
+ /* convert charge to energy */
+ if (energy == 0) {
+ energy = sysfs_get_double (native_path, "charge_now") / 1000000.0;
+ if (energy == 0)
+ energy = sysfs_get_double (native_path, "charge_avg") / 1000000.0;
+ energy *= voltage_design;
+ energy_rate *= voltage_design;
+ }
+
+ /* some batteries don't update last_full attribute */
+ if (energy > energy_full) {
+ egg_warning ("energy %f bigger than full %f", energy, energy_full);
+ energy_full = energy;
+ }
+
+ /* present voltage */
+ voltage = sysfs_get_double (native_path, "voltage_now") / 1000000.0;
+ if (voltage == 0)
+ voltage = sysfs_get_double (native_path, "voltage_avg") / 1000000.0;
+
+ /* ACPI gives out the special 'Ones' value for rate when it's unable
+ * to calculate the true rate. We should set the rate zero, and wait
+ * for the BIOS to stabilise. */
+ if (energy_rate == 0xffff)
+ energy_rate = 0;
+
+ /* sanity check to less than 100W */
+ if (energy_rate > 100*1000)
+ energy_rate = 0;
+
+ /* the hardware reporting failed -- try to calculate this */
+ if (energy_rate < 0)
+ dkp_device_supply_calculate_rate (supply);
+
+ /* get a precise percentage */
+ if (energy_full > 0.0f) {
+ percentage = 100.0 * energy / energy_full;
+ if (percentage < 0.0f)
+ percentage = 0.0f;
+ if (percentage > 100.0f)
+ percentage = 100.0f;
+ }
+
+ /* some batteries stop charging much before 100% */
+ if (state == DKP_DEVICE_STATE_UNKNOWN &&
+ percentage > DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD) {
+ egg_warning ("fixing up unknown %f", percentage);
+ state = DKP_DEVICE_STATE_FULLY_CHARGED;
+ }
+
+ /* the battery isn't charging or discharging, it's just
+ * sitting there half full doing nothing: try to guess a state */
+ if (state == DKP_DEVICE_STATE_UNKNOWN) {
+
+ /* get global battery status */
+ daemon = dkp_device_get_nativeaemon (device);
+ g_object_get (daemon,
+ "on-battery", &on_battery,
+ NULL);
+
+ /* only guess when we have more than one battery devices */
+ battery_count = dkp_daemon_get_number_devices_of_type (daemon, DKP_DEVICE_TYPE_BATTERY);
+
+ /* try to find a suitable icon depending on AC state */
+ if (on_battery && battery_count > 1)
+ state = DKP_DEVICE_STATE_PENDING_DISCHARGE;
+ else if (battery_count > 1)
+ state = DKP_DEVICE_STATE_PENDING_CHARGE;
+ else if (on_battery)
+ state = DKP_DEVICE_STATE_DISCHARGING;
+ else
+ state = DKP_DEVICE_STATE_FULLY_CHARGED;
+
+ /* print what we did */
+ egg_debug ("guessing battery state '%s' using global on-battery:%i",
+ dkp_device_state_to_text (state), on_battery);
+
+ g_object_unref (daemon);
+ }
+
+ /* if empty, and BIOS does not know what to do */
+ if (state == DKP_DEVICE_STATE_UNKNOWN && energy < 0.01) {
+ egg_warning ("Setting %s state empty as unknown and very low", native_path);
+ state = DKP_DEVICE_STATE_EMPTY;
+ }
+
+ /* calculate a quick and dirty time remaining value */
+ time_to_empty = 0;
+ time_to_full = 0;
+ if (energy_rate > 0) {
+ if (state == DKP_DEVICE_STATE_DISCHARGING)
+ time_to_empty = 3600 * (energy / energy_rate);
+ else if (state == DKP_DEVICE_STATE_CHARGING)
+ time_to_full = 3600 * ((energy_full - energy) / energy_rate);
+ /* TODO: need to factor in battery charge metrics */
+ }
+
+ /* check the remaining time is under a set limit, to deal with broken
+ primary batteries rate */
+ if (time_to_empty > (20 * 60 * 60))
+ time_to_empty = 0;
+ if (time_to_full > (20 * 60 * 60))
+ time_to_full = 0;
+
+ /* set the old status */
+ supply->priv->energy_old = energy;
+ g_get_current_time (&supply->priv->energy_old_timespec);
+
+ /* we changed state */
+ g_object_get (device, "state", &old_state, NULL);
+ if (old_state != state)
+ supply->priv->energy_old = 0;
+
+ g_object_set (device,
+ "energy", energy,
+ "energy-full", energy_full,
+ "energy-full-design", energy_full_design,
+ "energy-rate", energy_rate,
+ "percentage", percentage,
+ "state", state,
+ "voltage", voltage,
+ "time-to-empty", time_to_empty,
+ "time-to-full", time_to_full,
+ NULL);
+
+out:
+ g_free (status);
+ return ret;
+}
+
+/**
+ * dkp_device_supply_poll_battery:
+ **/
+static gboolean
+dkp_device_supply_poll_battery (DkpDeviceSupply *supply)
+{
+ gboolean ret;
+ DkpDevice *device = DKP_DEVICE (supply);
+
+ egg_debug ("No updates on supply %s for %i seconds; forcing update", dkp_device_get_object_path (device), DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT);
+ supply->priv->poll_timer_id = 0;
+ ret = dkp_device_supply_refresh (device);
+ if (ret)
+ dkp_device_emit_changed (device);
+ return FALSE;
+}
+
+/**
+ * dkp_device_supply_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+dkp_device_supply_coldplug (DkpDevice *device)
+{
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+ gboolean ret;
+ GUdevDevice *d;
+ const gchar *native_path;
+
+ dkp_device_supply_reset_values (supply);
+
+ /* detect what kind of device we are */
+ d = dkp_device_get_native (device);
+ if (d == NULL)
+ egg_error ("could not get device");
+
+ native_path = g_udev_device_get_sysfs_path (d);
+ if (native_path == NULL)
+ egg_error ("could not get native path");
+
+ if (sysfs_file_exists (native_path, "online")) {
+ g_object_set (device, "type", DKP_DEVICE_TYPE_LINE_POWER, NULL);
+ } else {
+ /* this is correct, UPS and CSR are not in the kernel */
+ g_object_set (device, "type", DKP_DEVICE_TYPE_BATTERY, NULL);
+ }
+
+ /* coldplug values */
+ ret = dkp_device_supply_refresh (device);
+
+ return ret;
+}
+
+/**
+ * dkp_device_supply_setup_poll:
+ **/
+static gboolean
+dkp_device_supply_setup_poll (DkpDevice *device)
+{
+ DkpDeviceState state;
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+
+ g_object_get (device, "state", &state, NULL);
+
+ /* if it's fully charged, don't poll at all */
+ if (state == DKP_DEVICE_STATE_FULLY_CHARGED)
+ goto out;
+
+ /* if it's unknown, poll faster than we would normally */
+ if (state == DKP_DEVICE_STATE_UNKNOWN &&
+ supply->priv->unknown_retries < DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES) {
+ supply->priv->poll_timer_id =
+ g_timeout_add_seconds (DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT,
+ (GSourceFunc) dkp_device_supply_poll_battery, supply);
+ /* increase count, we don't want to poll at 0.5Hz forever */
+ supply->priv->unknown_retries++;
+ goto out;
+ }
+
+ /* any other state just fall back */
+ supply->priv->poll_timer_id =
+ g_timeout_add_seconds (DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT,
+ (GSourceFunc) dkp_device_supply_poll_battery, supply);
+out:
+ return (supply->priv->poll_timer_id != 0);
+}
+
+/**
+ * dkp_device_supply_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_supply_refresh (DkpDevice *device)
+{
+ gboolean ret;
+ GTimeVal time;
+ DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
+ DkpDeviceType type;
+
+ if (supply->priv->poll_timer_id > 0) {
+ g_source_remove (supply->priv->poll_timer_id);
+ supply->priv->poll_timer_id = 0;
+ }
+
+ g_get_current_time (&time);
+ g_object_set (device, "update-time", (guint64) time.tv_sec, NULL);
+ g_object_get (device, "type", &type, NULL);
+ switch (type) {
+ case DKP_DEVICE_TYPE_LINE_POWER:
+ ret = dkp_device_supply_refresh_line_power (supply);
+ break;
+ case DKP_DEVICE_TYPE_BATTERY:
+ ret = dkp_device_supply_refresh_battery (supply);
+
+ /* Seems that we don't get change uevents from the
+ * kernel on some BIOS types */
+ dkp_device_supply_setup_poll (device);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ return ret;
+}
+
+/**
+ * dkp_device_supply_init:
+ **/
+static void
+dkp_device_supply_init (DkpDeviceSupply *supply)
+{
+ supply->priv = DKP_DEVICE_SUPPLY_GET_PRIVATE (supply);
+ supply->priv->unknown_retries = 0;
+ supply->priv->poll_timer_id = 0;
+}
+
+/**
+ * dkp_device_supply_finalize:
+ **/
+static void
+dkp_device_supply_finalize (GObject *object)
+{
+ DkpDeviceSupply *supply;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (DKP_IS_SUPPLY (object));
+
+ supply = DKP_DEVICE_SUPPLY (object);
+ g_return_if_fail (supply->priv != NULL);
+
+ if (supply->priv->poll_timer_id > 0)
+ g_source_remove (supply->priv->poll_timer_id);
+
+ G_OBJECT_CLASS (dkp_device_supply_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_device_supply_class_init:
+ **/
+static void
+dkp_device_supply_class_init (DkpDeviceSupplyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass);
+
+ object_class->finalize = dkp_device_supply_finalize;
+ device_class->get_on_battery = dkp_device_supply_get_on_battery;
+ device_class->get_low_battery = dkp_device_supply_get_low_battery;
+ device_class->get_online = dkp_device_supply_get_online;
+ device_class->coldplug = dkp_device_supply_coldplug;
+ device_class->refresh = dkp_device_supply_refresh;
+
+ g_type_class_add_private (klass, sizeof (DkpDeviceSupplyPrivate));
+}
+
+/**
+ * dkp_device_supply_new:
+ **/
+DkpDeviceSupply *
+dkp_device_supply_new (void)
+{
+ return g_object_new (DKP_TYPE_SUPPLY, NULL);
+}
+
diff --git a/src/linux/dkp-device-supply.h b/src/linux/dkp-device-supply.h
new file mode 100644
index 0000000..b767eac
--- /dev/null
+++ b/src/linux/dkp-device-supply.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __DKP_DEVICE_SUPPLY_H__
+#define __DKP_DEVICE_SUPPLY_H__
+
+#include <glib-object.h>
+#include "dkp-device.h"
+
+G_BEGIN_DECLS
+
+#define DKP_TYPE_SUPPLY (dkp_device_supply_get_type ())
+#define DKP_DEVICE_SUPPLY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DKP_TYPE_SUPPLY, DkpDeviceSupply))
+#define DKP_DEVICE_SUPPLY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DKP_TYPE_SUPPLY, DkpDeviceSupplyClass))
+#define DKP_IS_SUPPLY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DKP_TYPE_SUPPLY))
+#define DKP_IS_SUPPLY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DKP_TYPE_SUPPLY))
+#define DKP_DEVICE_SUPPLY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DKP_TYPE_SUPPLY, DkpDeviceSupplyClass))
+
+typedef struct DkpDeviceSupplyPrivate DkpDeviceSupplyPrivate;
+
+typedef struct
+{
+ DkpDevice parent;
+ DkpDeviceSupplyPrivate *priv;
+} DkpDeviceSupply;
+
+typedef struct
+{
+ DkpDeviceClass parent_class;
+} DkpDeviceSupplyClass;
+
+GType dkp_device_supply_get_type (void);
+DkpDeviceSupply *dkp_device_supply_new (void);
+
+G_END_DECLS
+
+#endif /* __DKP_DEVICE_SUPPLY_H__ */
+
diff --git a/src/linux/dkp-device-wup.c b/src/linux/dkp-device-wup.c
new file mode 100644
index 0000000..a5373a0
--- /dev/null
+++ b/src/linux/dkp-device-wup.c
@@ -0,0 +1,486 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006-2008 Richard Hughes <richard@hughsie.com>
+ *
+ * Data values taken from wattsup.c: Copyright (C) 2005 Patrick Mochel
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "dkp-enum.h"
+#include "dkp-device-wup.h"
+
+#define DKP_DEVICE_WUP_REFRESH_TIMEOUT 10 /* seconds */
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_WATTS 0x0
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_VOLTS 0x1
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_AMPS 0x2
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_KWH 0x3
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_COST 0x4
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MONTHLY_KWH 0x5
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MONTHLY_COST 0x6
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MAX_WATTS 0x7
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MAX_VOLTS 0x8
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MAX_AMPS 0x9
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MIN_WATTS 0xa
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MIN_VOLTS 0xb
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_MIN_AMPS 0xc
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_POWER_FACTOR 0xd
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_DUTY_CYCLE 0xe
+#define DKP_DEVICE_WUP_RESPONSE_OFFSET_POWER_CYCLE 0xf
+
+/* commands can never be bigger then this */
+#define DKP_DEVICE_WUP_COMMAND_LEN 256
+
+struct DkpDeviceWupPrivate
+{
+ guint poll_timer_id;
+ int fd;
+};
+
+static void dkp_device_wup_class_init (DkpDeviceWupClass *klass);
+
+G_DEFINE_TYPE (DkpDeviceWup, dkp_device_wup, DKP_TYPE_DEVICE)
+#define DKP_DEVICE_WUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_WUP, DkpDeviceWupPrivate))
+
+static gboolean dkp_device_wup_refresh (DkpDevice *device);
+
+/**
+ * dkp_device_wup_poll_cb:
+ **/
+static gboolean
+dkp_device_wup_poll_cb (DkpDeviceWup *wup)
+{
+ gboolean ret;
+ DkpDevice *device = DKP_DEVICE (wup);
+
+ egg_debug ("Polling: %s", dkp_device_get_object_path (device));
+ ret = dkp_device_wup_refresh (device);
+ if (ret)
+ dkp_device_emit_changed (device);
+ return TRUE;
+}
+
+/**
+ * dkp_device_wup_set_speed:
+ **/
+static gboolean
+dkp_device_wup_set_speed (DkpDeviceWup *wup)
+{
+ struct termios t;
+ int retval;
+
+ retval = tcgetattr (wup->priv->fd, &t);
+ if (retval != 0) {
+ egg_debug ("failed to get speed");
+ return FALSE;
+ }
+
+ cfmakeraw (&t);
+ cfsetispeed (&t, B115200);
+ cfsetospeed (&t, B115200);
+ tcflush (wup->priv->fd, TCIFLUSH);
+
+ t.c_iflag |= IGNPAR;
+ t.c_cflag &= ~CSTOPB;
+ retval = tcsetattr (wup->priv->fd, TCSANOW, &t);
+ if (retval != 0) {
+ egg_debug ("failed to set speed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * dkp_device_wup_write_command:
+ *
+ * data: a command string in the form "#command,subcommand,datalen,data[n]", e.g. "#R,W,0"
+ **/
+static gboolean
+dkp_device_wup_write_command (DkpDeviceWup *wup, const gchar *data)
+{
+ guint ret = TRUE;
+ gint retval;
+ gint length;
+
+ length = strlen (data);
+ egg_debug ("writing [%s]", data);
+ retval = write (wup->priv->fd, data, length);
+ if (retval != length) {
+ egg_debug ("Writing [%s] to device failed", data);
+ ret = FALSE;
+ }
+ return ret;
+}
+
+/**
+ * dkp_device_wup_read_command:
+ *
+ * Return value: Some data to parse
+ **/
+static gchar *
+dkp_device_wup_read_command (DkpDeviceWup *wup)
+{
+ int retval;
+ gchar buffer[DKP_DEVICE_WUP_COMMAND_LEN];
+ retval = read (wup->priv->fd, &buffer, DKP_DEVICE_WUP_COMMAND_LEN);
+ if (retval < 0) {
+ egg_debug ("failed to read from fd: %s", strerror (errno));
+ return NULL;
+ }
+ return g_strdup (buffer);
+}
+
+/**
+ * dkp_device_wup_parse_command:
+ *
+ * Return value: Som data to parse
+ **/
+static gboolean
+dkp_device_wup_parse_command (DkpDeviceWup *wup, const gchar *data)
+{
+ gboolean ret = FALSE;
+ gchar command;
+ gchar subcommand;
+ gchar **tokens = NULL;
+ gchar *packet = NULL;
+ guint i;
+ guint size;
+ guint length;
+ guint number_tokens;
+ DkpDevice *device = DKP_DEVICE (wup);
+ const guint offset = 3;
+
+ /* invalid */
+ if (data == NULL)
+ goto out;
+
+ /* Try to find a valid packet in the data stream
+ * Data may be sdfsd#P,-,0;sdfs and we only want this bit:
+ * \-----/
+ * so try to find the start and the end */
+
+ /* ensure we have a long enough response */
+ length = strlen (data);
+ if (length < 3) {
+ egg_debug ("not enough data '%s'", data);
+ goto out;
+ }
+
+ /* strip to the first '#' char */
+ for (i=0; i<length-1; i++)
+ if (data[i] == '#')
+ packet = g_strdup (data+i);
+
+ /* does packet exist? */
+ if (packet == NULL) {
+ egg_debug ("no start char in %s", data);
+ goto out;
+ }
+
+ /* replace the first ';' char with a NULL if it exists */
+ length = strlen (packet);
+ for (i=0; i<length; i++) {
+ if (packet[i] < 0x20 && packet[i] > 0x7e)
+ packet[i] = '?';
+ if (packet[i] == ';') {
+ packet[i] = '\0';
+ break;
+ }
+ }
+
+ /* check we have enough data inthe packet */
+ tokens = g_strsplit (packet, ",", -1);
+ number_tokens = g_strv_length (tokens);
+ if (number_tokens < 3) {
+ egg_debug ("not enough tokens '%s'", packet);
+ goto out;
+ }
+
+ /* remove leading or trailing whitespace in tokens */
+ for (i=0; i<number_tokens; i++)
+ g_strstrip (tokens[i]);
+
+ /* check the first token */
+ length = strlen (tokens[0]);
+ if (length != 2) {
+ egg_debug ("expected command '#?' but got '%s'", tokens[0]);
+ goto out;
+ }
+ if (tokens[0][0] != '#') {
+ egg_debug ("expected command '#?' but got '%s'", tokens[0]);
+ goto out;
+ }
+ command = tokens[0][1];
+
+ /* check the second token */
+ length = strlen (tokens[1]);
+ if (length != 1) {
+ egg_debug ("expected command '?' but got '%s'", tokens[1]);
+ goto out;
+ }
+ subcommand = tokens[1][0]; /* expect to be '-' */
+
+ /* check the length is present */
+ length = strlen (tokens[2]);
+ if (length == 0) {
+ egg_debug ("length value not present");
+ goto out;
+ }
+
+ /* check the length matches what data we've got*/
+ size = atoi (tokens[2]);
+ if (size != number_tokens - offset) {
+ egg_debug ("size expected to be '%i' but got '%i'", number_tokens - offset, size);
+ goto out;
+ }
+
+ /* update the command fields */
+ if (command == 'd' && subcommand == '-' && number_tokens - offset == 18) {
+ g_object_set (device,
+ "energy-rate", strtod (tokens[offset+DKP_DEVICE_WUP_RESPONSE_OFFSET_WATTS], NULL) / 10.0f,
+ "voltage", strtod (tokens[offset+DKP_DEVICE_WUP_RESPONSE_OFFSET_VOLTS], NULL) / 10.0f,
+ NULL);
+ ret = TRUE;
+ } else {
+ egg_debug ("ignoring command '%c'", command);
+ }
+
+out:
+ g_free (packet);
+ g_strfreev (tokens);
+ return ret;
+}
+
+/**
+ * dkp_device_wup_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+dkp_device_wup_coldplug (DkpDevice *device)
+{
+ DkpDeviceWup *wup = DKP_DEVICE_WUP (device);
+ GUdevDevice *d;
+ gboolean ret = FALSE;
+ const gchar *device_file;
+ const gchar *type;
+ const gchar *native_path;
+ gchar *data;
+ const gchar *vendor;
+ const gchar *product;
+
+ /* detect what kind of device we are */
+ d = dkp_device_get_native (device);
+ if (d == NULL)
+ egg_error ("could not get device");
+
+ /* get the type */
+ type = g_udev_device_get_property (d, "DKP_MONITOR_TYPE");
+ if (type == NULL || g_strcmp0 (type, "wup") != 0)
+ goto out;
+
+ /* get the device file */
+ device_file = g_udev_device_get_device_file (d);
+ if (device_file == NULL) {
+ egg_debug ("could not get device file for WUP device");
+ goto out;
+ }
+
+ /* connect to the device */
+ wup->priv->fd = open (device_file, O_RDWR | O_NONBLOCK);
+ if (wup->priv->fd < 0) {
+ egg_debug ("cannot open device file %s", device_file);
+ goto out;
+ }
+ egg_debug ("opened %s", device_file);
+
+ /* set speed */
+ ret = dkp_device_wup_set_speed (wup);
+ if (!ret) {
+ egg_debug ("not a WUP device (cannot set speed): %s", device_file);
+ goto out;
+ }
+
+ /* attempt to clear */
+ ret = dkp_device_wup_write_command (wup, "#R,W,0;");
+
+ /* setup logging interval */
+ data = g_strdup_printf ("#L,W,3,E,1,%i;", DKP_DEVICE_WUP_REFRESH_TIMEOUT);
+ ret = dkp_device_wup_write_command (wup, data);
+ g_free (data);
+
+ /* dummy read */
+ data = dkp_device_wup_read_command (wup);
+ egg_debug ("data after clear %s", data);
+
+ /* shouldn't do anything */
+ dkp_device_wup_parse_command (wup, data);
+ g_free (data);
+
+ /* prefer DKP names */
+ vendor = g_udev_device_get_property (d, "DKP_VENDOR");
+ if (vendor == NULL)
+ vendor = g_udev_device_get_property (d, "ID_VENDOR");
+ product = g_udev_device_get_property (d, "DKP_PRODUCT");
+ if (product == NULL)
+ product = g_udev_device_get_property (d, "ID_PRODUCT");
+
+ /* hardcode some values */
+ native_path = g_udev_device_get_sysfs_path (d);
+ g_object_set (device,
+ "type", DKP_DEVICE_TYPE_MONITOR,
+ "is-rechargeable", FALSE,
+ "power-supply", FALSE,
+ "is-present", FALSE,
+ "vendor", vendor,
+ "model", product,
+ "serial", g_strstrip (sysfs_get_string (native_path, "serial")),
+ "has-history", TRUE,
+ "state", DKP_DEVICE_STATE_DISCHARGING,
+ NULL);
+
+ /* coldplug */
+ egg_debug ("coldplug");
+ ret = dkp_device_wup_refresh (device);
+
+ /* hardcode true, as we'll retry later if busy */
+ ret = TRUE;
+
+out:
+ return ret;
+}
+
+/**
+ * dkp_device_wup_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+dkp_device_wup_refresh (DkpDevice *device)
+{
+ gboolean ret = FALSE;
+ GTimeVal time;
+ gchar *data = NULL;
+ DkpDeviceWup *wup = DKP_DEVICE_WUP (device);
+
+ /* get data */
+ data = dkp_device_wup_read_command (wup);
+ if (data == NULL) {
+ egg_debug ("no data");
+ goto out;
+ }
+
+ /* parse */
+ ret = dkp_device_wup_parse_command (wup, data);
+ if (!ret) {
+ egg_debug ("failed to parse %s", data);
+ goto out;
+ }
+
+ /* reset time */
+ g_get_current_time (&time);
+ g_object_set (device, "update-time", (guint64) time.tv_sec, NULL);
+
+out:
+ g_free (data);
+ /* FIXME: always true? */
+ return TRUE;
+}
+
+/**
+ * dkp_device_wup_init:
+ **/
+static void
+dkp_device_wup_init (DkpDeviceWup *wup)
+{
+ wup->priv = DKP_DEVICE_WUP_GET_PRIVATE (wup);
+ wup->priv->fd = -1;
+ wup->priv->poll_timer_id = g_timeout_add_seconds (DKP_DEVICE_WUP_REFRESH_TIMEOUT,
+ (GSourceFunc) dkp_device_wup_poll_cb, wup);
+}
+
+/**
+ * dkp_device_wup_finalize:
+ **/
+static void
+dkp_device_wup_finalize (GObject *object)
+{
+ DkpDeviceWup *wup;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (DKP_IS_WUP (object));
+
+ wup = DKP_DEVICE_WUP (object);
+ g_return_if_fail (wup->priv != NULL);
+
+ if (wup->priv->fd > 0)
+ close (wup->priv->fd);
+ if (wup->priv->poll_timer_id > 0)
+ g_source_remove (wup->priv->poll_timer_id);
+
+ G_OBJECT_CLASS (dkp_device_wup_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_device_wup_class_init:
+ **/
+static void
+dkp_device_wup_class_init (DkpDeviceWupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass);
+
+ object_class->finalize = dkp_device_wup_finalize;
+ device_class->coldplug = dkp_device_wup_coldplug;
+ device_class->refresh = dkp_device_wup_refresh;
+
+ g_type_class_add_private (klass, sizeof (DkpDeviceWupPrivate));
+}
+
+/**
+ * dkp_device_wup_new:
+ **/
+DkpDeviceWup *
+dkp_device_wup_new (void)
+{
+ return g_object_new (DKP_TYPE_WUP, NULL);
+}
+
diff --git a/src/linux/dkp-device-wup.h b/src/linux/dkp-device-wup.h
new file mode 100644
index 0000000..02eaa4b
--- /dev/null
+++ b/src/linux/dkp-device-wup.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __DKP_DEVICE_WUP_H__
+#define __DKP_DEVICE_WUP_H__
+
+#include <glib-object.h>
+#include "dkp-device.h"
+
+G_BEGIN_DECLS
+
+#define DKP_TYPE_WUP (dkp_device_wup_get_type ())
+#define DKP_DEVICE_WUP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DKP_TYPE_WUP, DkpDeviceWup))
+#define DKP_DEVICE_WUP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DKP_TYPE_WUP, DkpDeviceWupClass))
+#define DKP_IS_WUP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DKP_TYPE_WUP))
+#define DKP_IS_WUP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DKP_TYPE_WUP))
+#define DKP_DEVICE_WUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DKP_TYPE_WUP, DkpDeviceWupClass))
+
+typedef struct DkpDeviceWupPrivate DkpDeviceWupPrivate;
+
+typedef struct
+{
+ DkpDevice parent;
+ DkpDeviceWupPrivate *priv;
+} DkpDeviceWup;
+
+typedef struct
+{
+ DkpDeviceClass parent_class;
+} DkpDeviceWupClass;
+
+GType dkp_device_wup_get_type (void);
+DkpDeviceWup *dkp_device_wup_new (void);
+
+G_END_DECLS
+
+#endif /* __DKP_DEVICE_WUP_H__ */
+
diff --git a/src/linux/dkp-input.c b/src/linux/dkp-input.c
new file mode 100644
index 0000000..e36f603
--- /dev/null
+++ b/src/linux/dkp-input.c
@@ -0,0 +1,320 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/input.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "dkp-enum.h"
+#include "dkp-daemon.h"
+#include "dkp-input.h"
+#include "dkp-daemon.h"
+
+struct DkpInputPrivate
+{
+ int eventfp;
+ struct input_event event;
+ gsize offset;
+ GIOChannel *channel;
+ DkpDaemon *daemon;
+};
+
+static void dkp_input_class_init (DkpInputClass *klass);
+
+G_DEFINE_TYPE (DkpInput, dkp_input, G_TYPE_OBJECT)
+#define DKP_INPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_INPUT, DkpInputPrivate))
+
+/* we must use this kernel-compatible implementation */
+#define BITS_PER_LONG (sizeof(long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x) ((x)%BITS_PER_LONG)
+#define BIT(x) (1UL<<OFF(x))
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+/**
+ * dkp_input_str_to_bitmask:
+ **/
+static gint
+dkp_input_str_to_bitmask (const gchar *s, glong *bitmask, size_t max_size)
+{
+ gint i, j;
+ gchar **v;
+ gint num_bits_set = 0;
+
+ memset (bitmask, 0, max_size);
+ v = g_strsplit (s, " ", max_size);
+ for (i = g_strv_length (v) - 1, j = 0; i >= 0; i--, j++) {
+ gulong val;
+
+ val = strtoul (v[i], NULL, 16);
+ bitmask[j] = val;
+
+ while (val != 0) {
+ num_bits_set++;
+ val &= (val - 1);
+ }
+ }
+ g_strfreev(v);
+
+ return num_bits_set;
+}
+
+/**
+ * dkp_input_event_io:
+ **/
+static gboolean
+dkp_input_event_io (GIOChannel *channel, GIOCondition condition, gpointer data)
+{
+ DkpInput *input = (DkpInput*) data;
+ GError *error = NULL;
+ gsize read_bytes;
+ glong bitmask[NBITS(SW_MAX)];
+ gboolean ret;
+
+ /* uninteresting */
+ if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
+ return FALSE;
+
+ /* read event */
+ while (g_io_channel_read_chars (channel,
+ ((gchar*)&input->priv->event) + input->priv->offset,
+ sizeof(struct input_event) - input->priv->offset,
+ &read_bytes, &error) == G_IO_STATUS_NORMAL) {
+
+ /* not enough data */
+ if (input->priv->offset + read_bytes < sizeof (struct input_event)) {
+ input->priv->offset = input->priv->offset + read_bytes;
+ egg_debug ("incomplete read");
+ goto out;
+ }
+
+ /* we have all the data */
+ input->priv->offset = 0;
+
+ egg_debug ("event.value=%d ; event.code=%d (0x%02x)",
+ input->priv->event.value,
+ input->priv->event.code,
+ input->priv->event.code);
+
+ /* switch? */
+ if (input->priv->event.type != EV_SW) {
+ egg_debug ("not a switch event");
+ continue;
+ }
+
+ /* is not lid */
+ if (input->priv->event.code != SW_LID) {
+ egg_debug ("not a lid");
+ continue;
+ }
+
+ /* check switch state */
+ if (ioctl (g_io_channel_unix_get_fd(channel), EVIOCGSW(sizeof (bitmask)), bitmask) < 0) {
+ egg_debug ("ioctl EVIOCGSW failed");
+ continue;
+ }
+
+ /* are we set */
+ ret = test_bit (input->priv->event.code, bitmask);
+ dkp_daemon_set_lid_is_closed (input->priv->daemon, ret, TRUE);
+ }
+out:
+ return TRUE;
+}
+
+/**
+ * dkp_input_coldplug:
+ **/
+gboolean
+dkp_input_coldplug (DkpInput *input, DkpDaemon *daemon, GUdevDevice *d)
+{
+ gboolean ret = FALSE;
+ gchar *path;
+ gchar *contents = NULL;
+ const gchar *native_path;
+ const gchar *device_file;
+ GError *error = NULL;
+ glong bitmask[NBITS(SW_MAX)];
+ gint num_bits;
+ GIOStatus status;
+
+ /* get sysfs path */
+ native_path = g_udev_device_get_sysfs_path (d);
+
+ /* is a switch */
+ path = g_build_filename (native_path, "../capabilities/sw", NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+ egg_debug ("not a switch [%s]", path);
+ goto out;
+ }
+
+ /* get caps */
+ ret = g_file_get_contents (path, &contents, NULL, &error);
+ if (!ret) {
+ egg_debug ("failed to get contents for [%s]: %s", path, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* convert to a bitmask */
+ num_bits = dkp_input_str_to_bitmask (contents, bitmask, sizeof (bitmask));
+ if (num_bits != 1) {
+ egg_debug ("not one bitmask entry for %s", native_path);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* is this a lid? */
+ if (!test_bit (SW_LID, bitmask)) {
+ egg_debug ("not a lid: %s", native_path);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* get device file */
+ device_file = g_udev_device_get_device_file (d);
+ if (device_file == NULL || device_file[0] == '\0') {
+ egg_warning ("no device file");
+ ret = FALSE;
+ goto out;
+ }
+
+ /* open device file */
+ input->priv->eventfp = open (device_file, O_RDONLY | O_NONBLOCK);
+ if (input->priv->eventfp <= 0) {
+ egg_warning ("cannot open '%s': %s", device_file, strerror (errno));
+ ret = FALSE;
+ goto out;
+ }
+
+ /* get initial state */
+ if (ioctl (input->priv->eventfp, EVIOCGSW(sizeof (bitmask)), bitmask) < 0) {
+ egg_warning ("ioctl EVIOCGSW on %s failed", native_path);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* create channel */
+ egg_debug ("watching %s (%i)", device_file, input->priv->eventfp);
+ input->priv->channel = g_io_channel_unix_new (input->priv->eventfp);
+
+ /* set binary encoding */
+ status = g_io_channel_set_encoding (input->priv->channel, NULL, &error);
+ if (status != G_IO_STATUS_NORMAL) {
+ egg_warning ("failed to set encoding: %s", error->message);
+ g_error_free (error);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* save daemon */
+ input->priv->daemon = g_object_ref (daemon);
+
+ /* watch this */
+ g_io_add_watch (input->priv->channel, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, dkp_input_event_io, input);
+
+ /* set if we are closed */
+ egg_debug ("using %s for lid event", native_path);
+ dkp_daemon_set_lid_is_closed (daemon, test_bit (SW_LID, bitmask), FALSE);
+
+out:
+ g_free (path);
+ g_free (contents);
+ return ret;
+}
+
+/**
+ * dkp_input_init:
+ **/
+static void
+dkp_input_init (DkpInput *input)
+{
+ input->priv = DKP_INPUT_GET_PRIVATE (input);
+ input->priv->eventfp = -1;
+ input->priv->channel = NULL;
+ input->priv->daemon = NULL;
+}
+
+/**
+ * dkp_input_finalize:
+ **/
+static void
+dkp_input_finalize (GObject *object)
+{
+ DkpInput *input;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (DKP_IS_INPUT (object));
+
+ input = DKP_INPUT (object);
+ g_return_if_fail (input->priv != NULL);
+
+ if (input->priv->daemon != NULL)
+ g_object_unref (input->priv->daemon);
+ if (input->priv->eventfp >= 0)
+ close (input->priv->eventfp);
+ if (input->priv->channel) {
+ g_io_channel_shutdown (input->priv->channel, FALSE, NULL);
+ g_io_channel_unref (input->priv->channel);
+ }
+ G_OBJECT_CLASS (dkp_input_parent_class)->finalize (object);
+}
+
+/**
+ * dkp_input_class_init:
+ **/
+static void
+dkp_input_class_init (DkpInputClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = dkp_input_finalize;
+ g_type_class_add_private (klass, sizeof (DkpInputPrivate));
+}
+
+/**
+ * dkp_input_new:
+ **/
+DkpInput *
+dkp_input_new (void)
+{
+ return g_object_new (DKP_TYPE_INPUT, NULL);
+}
+
diff --git a/src/linux/dkp-input.h b/src/linux/dkp-input.h
new file mode 100644
index 0000000..bae5005
--- /dev/null
+++ b/src/linux/dkp-input.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard@hughsie.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __DKP_INPUT_H__
+#define __DKP_INPUT_H__
+
+#include <glib-object.h>
+
+#include "dkp-daemon.h"
+
+G_BEGIN_DECLS
+
+#define DKP_TYPE_INPUT (dkp_input_get_type ())
+#define DKP_INPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DKP_TYPE_INPUT, DkpInput))
+#define DKP_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DKP_TYPE_INPUT, DkpInputClass))
+#define DKP_IS_INPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DKP_TYPE_INPUT))
+#define DKP_IS_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DKP_TYPE_INPUT))
+#define DKP_INPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DKP_TYPE_INPUT, DkpInputClass))
+
+typedef struct DkpInputPrivate DkpInputPrivate;
+
+typedef struct
+{
+ GObject parent;
+ DkpInputPrivate *priv;
+} DkpInput;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} DkpInputClass;
+
+GType dkp_input_get_type (void);
+DkpInput *dkp_input_new (void);
+gboolean dkp_input_coldplug (DkpInput *input,
+ DkpDaemon *daemon,
+ GUdevDevice *d);
+
+G_END_DECLS
+
+#endif /* __DKP_INPUT_H__ */
+
diff --git a/src/linux/sysfs-utils.c b/src/linux/sysfs-utils.c
new file mode 100644
index 0000000..bd7510b
--- /dev/null
+++ b/src/linux/sysfs-utils.c
@@ -0,0 +1,241 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <glib.h>
+
+#include "sysfs-utils.h"
+
+double
+sysfs_get_double (const char *dir, const char *attribute)
+{
+ double result;
+ char *contents;
+ char *filename;
+
+ result = 0.0;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &contents, NULL, NULL)) {
+ result = atof (contents);
+ g_free (contents);
+ }
+ g_free (filename);
+
+
+ return result;
+}
+
+gboolean
+sysfs_file_contains (const char *dir, const char *attribute, const char *string)
+{
+ gboolean result;
+ char *filename;
+ char *s;
+
+ result = FALSE;
+
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &s, NULL, NULL)) {
+ result = (strstr(s, string) != NULL);
+ g_free (s);
+ }
+ g_free (filename);
+
+ return result;
+}
+
+char *
+sysfs_get_string (const char *dir, const char *attribute)
+{
+ char *result;
+ char *filename;
+
+ result = NULL;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (!g_file_get_contents (filename, &result, NULL, NULL)) {
+ result = g_strdup ("");
+ }
+ g_free (filename);
+
+ return result;
+}
+
+int
+sysfs_get_int (const char *dir, const char *attribute)
+{
+ int result;
+ char *contents;
+ char *filename;
+
+ result = 0;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &contents, NULL, NULL)) {
+ result = atoi (contents);
+ g_free (contents);
+ }
+ g_free (filename);
+
+ return result;
+}
+
+guint
+sysfs_get_hex (const char *dir, const char *attribute)
+{
+ guint result;
+ char *contents;
+ char *filename;
+
+ result = 0;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &contents, NULL, NULL)) {
+ result = strtol (contents, (char **) NULL, 16);
+ g_free (contents);
+ }
+ g_free (filename);
+
+ return result;
+}
+
+gboolean
+sysfs_get_bool (const char *dir, const char *attribute)
+{
+ gboolean result = FALSE;
+ char *contents;
+ char *filename;
+
+ result = 0;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &contents, NULL, NULL)) {
+ g_strdelimit (contents, "\n", '\0');
+ if (g_strcmp0 (contents, "1") == 0)
+ result = TRUE;
+ g_free (contents);
+ }
+ g_free (filename);
+
+ return result;
+}
+
+guint64
+sysfs_get_uint64 (const char *dir, const char *attribute)
+{
+ guint64 result;
+ char *contents;
+ char *filename;
+
+ result = 0;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_get_contents (filename, &contents, NULL, NULL)) {
+ result = atoll (contents);
+ g_free (contents);
+ }
+ g_free (filename);
+
+
+ return result;
+}
+
+gboolean
+sysfs_file_exists (const char *dir, const char *attribute)
+{
+ gboolean result;
+ char *filename;
+
+ result = FALSE;
+ filename = g_build_filename (dir, attribute, NULL);
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ result = TRUE;
+ }
+ g_free (filename);
+
+ return result;
+}
+
+char *
+_dupv8 (const char *s)
+{
+ const char *end_valid;
+
+ if (!g_utf8_validate (s,
+ -1,
+ &end_valid)) {
+ g_warning ("The string '%s' is not valid UTF-8. Invalid characters begins at '%s'", s, end_valid);
+ return g_strndup (s, end_valid - s);
+ } else {
+ return g_strdup (s);
+ }
+}
+
+char *
+sysfs_resolve_link (const char *dir, const char *attribute)
+{
+ char *full_path;
+ char link_path[PATH_MAX];
+ char resolved_path[PATH_MAX];
+ ssize_t num;
+ gboolean found_it;
+
+ found_it = FALSE;
+
+ full_path = g_build_filename (dir, attribute, NULL);
+
+ //egg_warning ("attribute='%s'", attribute);
+ //egg_warning ("full_path='%s'", full_path);
+ num = readlink (full_path, link_path, sizeof (link_path) - 1);
+ if (num != -1) {
+ char *absolute_path;
+
+ link_path[num] = '\0';
+
+ //egg_warning ("link_path='%s'", link_path);
+ absolute_path = g_build_filename (dir, link_path, NULL);
+ //egg_warning ("absolute_path='%s'", absolute_path);
+ if (realpath (absolute_path, resolved_path) != NULL) {
+ //egg_warning ("resolved_path='%s'", resolved_path);
+ found_it = TRUE;
+ }
+ g_free (absolute_path);
+ }
+ g_free (full_path);
+
+ if (found_it)
+ return g_strdup (resolved_path);
+ else
+ return NULL;
+}
diff --git a/src/linux/sysfs-utils.h b/src/linux/sysfs-utils.h
new file mode 100644
index 0000000..43f3802
--- /dev/null
+++ b/src/linux/sysfs-utils.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __SYSFS_UTILS_H__
+#define __SYSFS_UTILS_H__
+
+#include <glib.h>
+
+double sysfs_get_double (const char *dir, const char *attribute);
+gboolean sysfs_file_contains (const char *dir, const char *attribute, const char *string);
+char *sysfs_get_string (const char *dir, const char *attribute);
+int sysfs_get_int (const char *dir, const char *attribute);
+guint sysfs_get_hex (const char *dir, const char *attribute);
+gboolean sysfs_get_bool (const char *dir, const char *attribute);
+guint64 sysfs_get_uint64 (const char *dir, const char *attribute);
+gboolean sysfs_file_exists (const char *dir, const char *attribute);
+char *sysfs_resolve_link (const char *dir, const char *attribute);
+
+char *_dupv8 (const char *s);
+
+#endif /* __SYSFS_UTILS_H__ */