diff options
Diffstat (limited to 'src/linux')
-rw-r--r-- | src/linux/.gitignore | 5 | ||||
-rw-r--r-- | src/linux/Makefile.am | 37 | ||||
-rw-r--r-- | src/linux/dkp-backend.c | 408 | ||||
-rw-r--r-- | src/linux/dkp-device-csr.c | 346 | ||||
-rw-r--r-- | src/linux/dkp-device-csr.h | 56 | ||||
-rw-r--r-- | src/linux/dkp-device-hid.c | 487 | ||||
-rw-r--r-- | src/linux/dkp-device-hid.h | 55 | ||||
-rw-r--r-- | src/linux/dkp-device-supply.c | 795 | ||||
-rw-r--r-- | src/linux/dkp-device-supply.h | 56 | ||||
-rw-r--r-- | src/linux/dkp-device-wup.c | 486 | ||||
-rw-r--r-- | src/linux/dkp-device-wup.h | 55 | ||||
-rw-r--r-- | src/linux/dkp-input.c | 320 | ||||
-rw-r--r-- | src/linux/dkp-input.h | 59 | ||||
-rw-r--r-- | src/linux/sysfs-utils.c | 241 | ||||
-rw-r--r-- | src/linux/sysfs-utils.h | 38 |
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__ */ |