From 0eb3a258daaa15a2cde3a815a24553f9394c028d Mon Sep 17 00:00:00 2001
From: Bastien Nocera <hadess@hadess.net>
Date: Fri, 28 May 2010 01:56:35 +0100
Subject: [PATCH] Add battery query support for iDevices

https://bugs.freedesktop.org/show_bug.cgi?id=28286
---
 configure.ac                  |    8 +
 src/Makefile.am               |    3 +-
 src/linux/Makefile.am         |   12 ++-
 src/linux/up-backend.c        |   12 ++
 src/linux/up-device-idevice.c |  285 +++++++++++++++++++++++++++++++++++++++++
 src/linux/up-device-idevice.h |   57 ++++++++
 6 files changed, 375 insertions(+), 2 deletions(-)
 create mode 100644 src/linux/up-device-idevice.c
 create mode 100644 src/linux/up-device-idevice.h

diff --git a/configure.ac b/configure.ac
index 64a8af7..94b805e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -178,11 +178,18 @@ AC_DEFINE_UNQUOTED(BACKEND, "$with_backend", [backend])
 AC_SUBST(BACKEND, "$with_backend")
 
 # only need GUdev on linux
+with_idevice=no
 if test x$with_backend = xlinux; then
 	PKG_CHECK_MODULES(GUDEV, [gudev-1.0 >= 001])
 	PKG_CHECK_MODULES(USB, [libusb-1.0 >= 1.0.0])
+	PKG_CHECK_MODULES(IDEVICE, libimobiledevice-1.0 >= 0.9.7, with_idevice=yes)
+	if test x$with_idevice = xyes; then
+		AC_DEFINE(HAVE_IDEVICE, 1, [Define to 1 if AFC is going to be built])
+	fi
 fi
 
+AM_CONDITIONAL(HAVE_IDEVICE, [test x$with_idevice = xyes])
+
 # export to Makefile.am
 AM_CONDITIONAL(BACKEND_TYPE_DUMMY, [test x$with_backend = xdummy])
 AM_CONDITIONAL(BACKEND_TYPE_LINUX, [test x$with_backend = xlinux])
@@ -253,6 +260,7 @@ echo "
         xsltproc:                   ${XSLTPROC}
 
         Backend:                    ${with_backend}
+        libimobiledevice support:   ${with_idevice}
         Maintainer mode:            ${USE_MAINTAINER_MODE}
         Building api docs:          ${enable_gtk_doc}
         Building man pages:         ${enable_man_pages}
diff --git a/src/Makefile.am b/src/Makefile.am
index 967a3f1..7853172 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -109,7 +109,8 @@ if BACKEND_TYPE_LINUX
 upowerd_LDADD += 						\
 	linux/libupshared.la					\
 	$(USB_LIBS)						\
-	$(GUDEV_LIBS)
+	$(GUDEV_LIBS)						\
+	$(IDEVICE_LIBS)
 endif
 
 upowerd_CFLAGS =						\
diff --git a/src/linux/Makefile.am b/src/linux/Makefile.am
index 4d54ae3..fb36235 100644
--- a/src/linux/Makefile.am
+++ b/src/linux/Makefile.am
@@ -10,12 +10,19 @@ INCLUDES = \
 	$(DBUS_GLIB_CFLAGS)					\
 	$(GUDEV_CFLAGS)						\
 	$(POLKIT_CFLAGS)					\
-	$(GLIB_CFLAGS)
+	$(GLIB_CFLAGS)						\
+	$(IDEVICE_CFLAGS)
 
 if BACKEND_TYPE_LINUX
 noinst_LTLIBRARIES = libupshared.la
 endif
 
+if HAVE_IDEVICE
+idevice_files = up-device-idevice.c up-device-idevice.h
+else
+idevice_files =
+endif
+
 libupshared_la_SOURCES =					\
 	up-device-supply.c					\
 	up-device-supply.h					\
@@ -31,8 +38,11 @@ libupshared_la_SOURCES =					\
 	up-native.c						\
 	sysfs-utils.c						\
 	sysfs-utils.h						\
+	$(idevice_files)					\
 	$(BUILT_SOURCES)
 
+EXTRA_DIST = up-device-idevice.c up-device-idevice.h
+
 libupshared_la_CFLAGS =					\
 	$(WARNINGFLAGS_C)
 
diff --git a/src/linux/up-backend.c b/src/linux/up-backend.c
index 574921d..2739c37 100644
--- a/src/linux/up-backend.c
+++ b/src/linux/up-backend.c
@@ -40,6 +40,9 @@
 #include "up-device-wup.h"
 #include "up-device-hid.h"
 #include "up-input.h"
+#ifdef HAVE_IDEVICE
+#include "up-device-idevice.h"
+#endif /* HAVE_IDEVICE */
 
 static void	up_backend_class_init	(UpBackendClass	*klass);
 static void	up_backend_init	(UpBackend		*backend);
@@ -110,6 +113,15 @@ up_backend_device_new (UpBackend *backend, GUdevDevice *native)
 
 	} else if (g_strcmp0 (subsys, "usb") == 0) {
 
+#ifdef HAVE_IDEVICE
+		/* see if this is an iDevice */
+		device = UP_DEVICE (up_device_idevice_new ());
+		ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (native));
+		if (ret)
+			goto out;
+		g_object_unref (device);
+#endif /* HAVE_IDEVICE */
+
 		/* see if this is a CSR mouse or keyboard */
 		device = UP_DEVICE (up_device_csr_new ());
 		ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (native));
diff --git a/src/linux/up-device-idevice.c b/src/linux/up-device-idevice.c
new file mode 100644
index 0000000..fff8d62
--- /dev/null
+++ b/src/linux/up-device-idevice.c
@@ -0,0 +1,285 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Bastien Nocera <hadess@hadess.net>
+ * Copyright (C) 2005-2010 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/gprintf.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gudev/gudev.h>
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <plist/plist.h>
+
+#include "sysfs-utils.h"
+#include "egg-debug.h"
+
+#include "up-types.h"
+#include "up-device-idevice.h"
+
+#define DEFAULT_POLL_TIME 60
+
+struct UpDeviceIdevicePrivate
+{
+	idevice_t		dev;
+	lockdownd_client_t	client;
+	guint			poll_timer_id;
+};
+
+G_DEFINE_TYPE (UpDeviceIdevice, up_device_idevice, UP_TYPE_DEVICE)
+#define UP_DEVICE_IDEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DEVICE_IDEVICE, UpDeviceIdevicePrivate))
+
+static gboolean		 up_device_idevice_refresh		(UpDevice *device);
+
+/**
+ * up_device_idevice_poll_cb:
+ **/
+static gboolean
+up_device_idevice_poll_cb (UpDeviceIdevice *idevice)
+{
+	UpDevice *device = UP_DEVICE (idevice);
+
+	egg_debug ("Polling: %s", up_device_get_object_path (device));
+	up_device_idevice_refresh (device);
+
+	/* always continue polling */
+	return TRUE;
+}
+
+/**
+ * up_device_idevice_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+up_device_idevice_coldplug (UpDevice *device)
+{
+	UpDeviceIdevice *idevice = UP_DEVICE_IDEVICE (device);
+	GUdevDevice *native;
+	idevice_error_t ret;
+	const gchar *uuid;
+	const gchar *model;
+	plist_t dict, node;
+	guint64 poll_seconds;
+	idevice_t dev = NULL;
+	lockdownd_client_t client = NULL;
+	UpDeviceKind kind;
+
+	/* Is it an iDevice? */
+	native = G_UDEV_DEVICE (up_device_get_native (device));
+	if (g_udev_device_get_property_as_boolean (native, "USBMUX_SUPPORTED") == FALSE)
+		return FALSE;
+
+	/* Get the UUID */
+	uuid = g_udev_device_get_property (native, "ID_SERIAL_SHORT");
+	if (uuid == NULL)
+		return FALSE;
+
+	/* Connect to the device */
+	ret = idevice_new (&dev, uuid);
+	if (ret != IDEVICE_E_SUCCESS)
+		return FALSE;
+
+	if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake (dev, &client, "upower")) {
+		idevice_free (dev);
+		return FALSE;
+	}
+
+	/* Get the poll timeout */
+	if (lockdownd_get_value (client, "com.apple.mobile.iTunes", "BatteryPollInterval", &dict) != LOCKDOWN_E_SUCCESS) {
+		lockdownd_client_free (client);
+		idevice_free (dev);
+		return FALSE;
+	}
+
+	poll_seconds = 0;
+	node = plist_dict_get_item (dict, "BatteryPollInterval");
+	if (node != NULL)
+		plist_get_uint_val (node, &poll_seconds);
+	if (poll_seconds == 0)
+		poll_seconds = DEFAULT_POLL_TIME;
+	plist_free (dict);
+
+	/* Set up struct */
+	idevice->priv->dev = dev;
+	idevice->priv->client = client;
+
+	model = g_udev_device_get_property (native, "ID_MODEL");
+	kind = UP_DEVICE_KIND_PHONE;
+	if (model != NULL && strstr (model, "iPad")) {
+		kind = UP_DEVICE_KIND_TABLET;
+	} else if (model != NULL && strstr (model, "iPod")) {
+		kind = UP_DEVICE_KIND_MEDIA_PLAYER;
+	}
+
+	/* hardcode some values */
+	g_object_set (device,
+		      "type", kind,
+		      "vendor", g_udev_device_get_property (native, "ID_VENDOR"),
+		      "model", g_udev_device_get_property (native, "ID_MODEL"),
+		      "power-supply", FALSE,
+		      "is-present", TRUE,
+		      "is-rechargeable", TRUE,
+		      "has-history", TRUE,
+		      NULL);
+
+
+	/* coldplug */
+	ret = up_device_idevice_refresh (device);
+
+	lockdownd_client_free (idevice->priv->client);
+	idevice->priv->client = NULL;
+
+	/* set up a poll */
+	idevice->priv->poll_timer_id = g_timeout_add_seconds (poll_seconds,
+							      (GSourceFunc) up_device_idevice_poll_cb, idevice);
+
+	return TRUE;
+}
+
+/**
+ * up_device_idevice_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+up_device_idevice_refresh (UpDevice *device)
+{
+	GTimeVal timeval;
+	UpDeviceIdevice *idevice = UP_DEVICE_IDEVICE (device);
+	lockdownd_client_t client = NULL;
+	plist_t dict, node;
+	guint64 percentage;
+	guint8 charging;
+	UpDeviceState state;
+
+	/* Open a lockdown port, or re-use the one we have */
+	if (idevice->priv->client == NULL) {
+		if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake (idevice->priv->dev, &client, "upower"))
+			return FALSE;
+	} else {
+		client = idevice->priv->client;
+	}
+
+	if (lockdownd_get_value (client, "com.apple.mobile.battery", NULL, &dict) != LOCKDOWN_E_SUCCESS)
+		return FALSE;
+
+	/* get battery status */
+	node = plist_dict_get_item (dict, "BatteryCurrentCapacity");
+	plist_get_uint_val (node, &percentage);
+
+	g_object_set (device, "percentage", (double) percentage, NULL);
+	egg_debug ("percentage=%"G_GUINT64_FORMAT, percentage);
+
+	/* get charging status */
+	node = plist_dict_get_item (dict, "BatteryIsCharging");
+	plist_get_bool_val (node, &charging);
+
+	if (percentage == 100)
+		state = UP_DEVICE_STATE_FULLY_CHARGED;
+	else if (percentage == 0)
+		state = UP_DEVICE_STATE_EMPTY;
+	else if (charging)
+		state = UP_DEVICE_STATE_CHARGING;
+	else
+		state = UP_DEVICE_STATE_DISCHARGING; /* upower doesn't have a "not charging" state */
+
+	g_object_set (device, "state", charging ? UP_DEVICE_STATE_CHARGING : UP_DEVICE_STATE_DISCHARGING, NULL);
+	egg_debug ("state=%d", charging);
+
+	plist_free (dict);
+
+	/* reset time */
+	g_get_current_time (&timeval);
+	g_object_set (device, "update-time", (guint64) timeval.tv_sec, NULL);
+
+	/* Only free it if we opened it */
+	if (idevice->priv->client == NULL)
+		lockdownd_client_free (client);
+
+	return TRUE;
+}
+
+/**
+ * up_device_idevice_init:
+ **/
+static void
+up_device_idevice_init (UpDeviceIdevice *idevice)
+{
+	idevice->priv = UP_DEVICE_IDEVICE_GET_PRIVATE (idevice);
+}
+
+/**
+ * up_device_idevice_finalize:
+ **/
+static void
+up_device_idevice_finalize (GObject *object)
+{
+	UpDeviceIdevice *idevice;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (UP_IS_DEVICE_IDEVICE (object));
+
+	idevice = UP_DEVICE_IDEVICE (object);
+	g_return_if_fail (idevice->priv != NULL);
+
+	if (idevice->priv->poll_timer_id > 0)
+		g_source_remove (idevice->priv->poll_timer_id);
+	if (idevice->priv->client != NULL)
+		lockdownd_client_free (idevice->priv->client);
+	idevice_free (idevice->priv->dev);
+
+	G_OBJECT_CLASS (up_device_idevice_parent_class)->finalize (object);
+}
+
+/**
+ * up_device_idevice_class_init:
+ **/
+static void
+up_device_idevice_class_init (UpDeviceIdeviceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	UpDeviceClass *device_class = UP_DEVICE_CLASS (klass);
+
+	object_class->finalize = up_device_idevice_finalize;
+	device_class->coldplug = up_device_idevice_coldplug;
+	device_class->refresh = up_device_idevice_refresh;
+
+	g_type_class_add_private (klass, sizeof (UpDeviceIdevicePrivate));
+}
+
+/**
+ * up_device_idevice_new:
+ **/
+UpDeviceIdevice *
+up_device_idevice_new (void)
+{
+	return g_object_new (UP_TYPE_DEVICE_IDEVICE, NULL);
+}
+
diff --git a/src/linux/up-device-idevice.h b/src/linux/up-device-idevice.h
new file mode 100644
index 0000000..4f37ab5
--- /dev/null
+++ b/src/linux/up-device-idevice.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Bastien Nocera <hadess@hadess.net>
+ * 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 __UP_DEVICE_IDEVICE_H__
+#define __UP_DEVICE_IDEVICE_H__
+
+#include <glib-object.h>
+#include "up-device.h"
+
+G_BEGIN_DECLS
+
+#define UP_TYPE_DEVICE_IDEVICE  			(up_device_idevice_get_type ())
+#define UP_DEVICE_IDEVICE(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), UP_TYPE_DEVICE_IDEVICE, UpDeviceIdevice))
+#define UP_DEVICE_IDEVICE_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), UP_TYPE_DEVICE_IDEVICE, UpDeviceIdeviceClass))
+#define UP_IS_DEVICE_IDEVICE(o)			(G_TYPE_CHECK_INSTANCE_TYPE ((o), UP_TYPE_DEVICE_IDEVICE))
+#define UP_IS_DEVICE_IDEVICE_CLASS(k)		(G_TYPE_CHECK_CLASS_TYPE ((k), UP_TYPE_DEVICE_IDEVICE))
+#define UP_DEVICE_IDEVICE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), UP_TYPE_DEVICE_IDEVICE, UpDeviceIdeviceClass))
+
+typedef struct UpDeviceIdevicePrivate UpDeviceIdevicePrivate;
+
+typedef struct
+{
+	UpDevice		 parent;
+	UpDeviceIdevicePrivate	*priv;
+} UpDeviceIdevice;
+
+typedef struct
+{
+	UpDeviceClass		 parent_class;
+} UpDeviceIdeviceClass;
+
+GType		 up_device_idevice_get_type		(void);
+UpDeviceIdevice	*up_device_idevice_new			(void);
+
+G_END_DECLS
+
+#endif /* __UP_DEVICE_IDEVICE_H__ */
+
-- 
1.7.0.1