summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules/95-upower-csr.rules5
-rw-r--r--src/linux/Makefile.am18
-rw-r--r--src/linux/hidpp-device.c815
-rw-r--r--src/linux/hidpp-device.h94
-rw-r--r--src/linux/hidpp-test.c79
-rw-r--r--src/linux/up-backend.c28
-rw-r--r--src/linux/up-device-lg-unifying.c775
-rw-r--r--src/linux/up-device-unifying.c284
-rw-r--r--src/linux/up-device-unifying.h (renamed from src/linux/up-device-lg-unifying.h)0
9 files changed, 1308 insertions, 790 deletions
diff --git a/rules/95-upower-csr.rules b/rules/95-upower-csr.rules
index 17cb36f..bd171b2 100644
--- a/rules/95-upower-csr.rules
+++ b/rules/95-upower-csr.rules
@@ -20,3 +20,8 @@ ATTR{idVendor}=="046d", ATTR{idProduct}=="c702", ENV{UPOWER_PRODUCT}="Presenter"
LABEL="up_csr_end"
+# Unifying HID++ devices
+SUBSYSTEM!="hid", GOTO="up_unifying_end"
+ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", DRIVER=="logitech-djdevice", ENV{UPOWER_BATTERY_TYPE}="unifying"
+ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c532", DRIVER=="logitech-djdevice", ENV{UPOWER_BATTERY_TYPE}="unifying"
+LABEL="up_unifying_end"
diff --git a/src/linux/Makefile.am b/src/linux/Makefile.am
index 66ad389..4bb64bc 100644
--- a/src/linux/Makefile.am
+++ b/src/linux/Makefile.am
@@ -30,8 +30,8 @@ libupshared_la_SOURCES = \
up-device-supply.h \
up-device-csr.c \
up-device-csr.h \
- up-device-lg-unifying.c \
- up-device-lg-unifying.h \
+ up-device-unifying.c \
+ up-device-unifying.h \
up-device-hid.c \
up-device-hid.h \
up-device-wup.c \
@@ -42,11 +42,25 @@ libupshared_la_SOURCES = \
up-dock.h \
up-backend.c \
up-native.c \
+ hidpp-device.c \
+ hidpp-device.h \
sysfs-utils.c \
sysfs-utils.h \
$(idevice_files) \
$(BUILT_SOURCES)
+noinst_PROGRAMS = \
+ hidpp-test
+hidpp_test_SOURCES = \
+ hidpp-device.c \
+ hidpp-device.h \
+ hidpp-test.c
+hidpp_test_LDADD = \
+ -lm \
+ $(GLIB_LIBS) \
+ $(GIO_LIBS)
+hidpp_test_CFLAGS = $(AM_CFLAGS) $(WARNINGFLAGS_C)
+
EXTRA_DIST = $(libupshared_la_SOURCES) \
integration-test
diff --git a/src/linux/hidpp-device.c b/src/linux/hidpp-device.c
new file mode 100644
index 0000000..0a27dbf
--- /dev/null
+++ b/src/linux/hidpp-device.c
@@ -0,0 +1,815 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Julien Danjou <julien@danjou.info>
+ * Copyright (C) 2012 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 <fcntl.h>
+#include <glib-object.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "hidpp-device.h"
+
+/* Arbitrary value used in ping */
+#define HIDPP_PING_DATA 0x42
+
+#define HIDPP_RECEIVER_ADDRESS 0xff
+
+#define HIDPP_RESPONSE_SHORT_LENGTH 7
+#define HIDPP_RESPONSE_LONG_LENGTH 20
+
+#define HIDPP_HEADER_REQUEST 0x10
+#define HIDPP_HEADER_RESPONSE 0x11
+
+/* HID++ 1.0 */
+#define HIDPP_READ_SHORT_REGISTER 0x81
+#define HIDPP_READ_SHORT_REGISTER_BATTERY 0x0d
+
+#define HIDPP_READ_LONG_REGISTER 0x83
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE 11
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD 0x1
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE 0x2
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD 0x3
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER 0x4
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL 0x7
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL 0x8
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD 0x9
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET 0xa
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD 0xb
+#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK 0xc
+
+#define HIDPP_ERR_INVALID_SUBID 0x8f
+
+/* HID++ 2.0 */
+
+/* HID++2.0 error codes */
+#define HIDPP_ERROR_CODE_NOERROR 0x00
+#define HIDPP_ERROR_CODE_UNKNOWN 0x01
+#define HIDPP_ERROR_CODE_INVALIDARGUMENT 0x02
+#define HIDPP_ERROR_CODE_OUTOFRANGE 0x03
+#define HIDPP_ERROR_CODE_HWERROR 0x04
+#define HIDPP_ERROR_CODE_LOGITECH_INTERNAL 0x05
+#define HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX 0x06
+#define HIDPP_ERROR_CODE_INVALID_FUNCTION_ID 0x07
+#define HIDPP_ERROR_CODE_BUSY 0x08
+#define HIDPP_ERROR_CODE_UNSUPPORTED 0x09
+
+#define HIDPP_FEATURE_ROOT 0x0000
+#define HIDPP_FEATURE_ROOT_INDEX 0x00
+#define HIDPP_FEATURE_ROOT_FN_GET_FEATURE (0x00 << 4)
+#define HIDPP_FEATURE_ROOT_FN_PING (0x01 << 4)
+#define HIDPP_FEATURE_I_FEATURE_SET 0x0001
+#define HIDPP_FEATURE_I_FEATURE_SET_FN_GET_COUNT (0x00 << 4)
+#define HIDPP_FEATURE_I_FEATURE_SET_FN_GET_FEATURE_ID (0x01 << 4)
+#define HIDPP_FEATURE_I_FIRMWARE_INFO 0x0003
+#define HIDPP_FEATURE_I_FIRMWARE_INFO_FN_GET_COUNT (0x00 << 4)
+#define HIDPP_FEATURE_I_FIRMWARE_INFO_FN_GET_INFO (0x01 << 4)
+#define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE 0x0005
+#define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_COUNT (0x00 << 4)
+#define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_NAME (0x01 << 4)
+#define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_TYPE (0x02 << 4)
+#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS 0x1000
+//#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS (0x00 << 4)
+//#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_BE (0x01 << 4)
+#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_CAPABILITY (0x02 << 4)
+
+#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS 0x02
+#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_BE 0x02
+
+#define HIDPP_FEATURE_SPECIAL_KEYS_MSE_BUTTONS 0x1B00
+#define HIDPP_FEATURE_WIRELESS_DEVICE_STATUS 0x1D4B
+#define HIDPP_FEATURE_WIRELESS_DEVICE_STATUS_BE (0x00 << 4)
+
+#define HIDPP_FEATURE_SOLAR_DASHBOARD 0x4301
+#define HIDPP_FEATURE_SOLAR_DASHBOARD_FN_SET_LIGHT_MEASURE (0x00 << 4)
+#define HIDPP_FEATURE_SOLAR_DASHBOARD_BE_BATTERY_LEVEL_STATUS (0x01 << 4)
+
+#define HIDPP_DEVICE_READ_RESPONSE_TIMEOUT 3000 /* miliseconds */
+
+struct HidppDevicePrivate
+{
+ gboolean enable_debug;
+ gchar *hidraw_device;
+ gchar *model;
+ GIOChannel *channel;
+ GPtrArray *feature_index;
+ guint batt_percentage;
+ guint channel_source_id;
+ guint device_idx;
+ guint version;
+ HidppDeviceBattStatus batt_status;
+ HidppDeviceKind kind;
+ int fd;
+};
+
+typedef struct {
+ gint idx;
+ guint16 feature;
+ gchar *name;
+} HidppDeviceMap;
+
+G_DEFINE_TYPE (HidppDevice, hidpp_device, G_TYPE_OBJECT)
+#define HIDPP_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), HIDPP_TYPE_DEVICE, HidppDevicePrivate))
+
+/**
+ * hidpp_device_map_print:
+ **/
+static void
+hidpp_device_map_print (HidppDevice *device)
+{
+ guint i;
+ HidppDeviceMap *map;
+ HidppDevicePrivate *priv = device->priv;
+
+ if (!device->priv->enable_debug)
+ return;
+ for (i = 0; i < priv->feature_index->len; i++) {
+ map = g_ptr_array_index (priv->feature_index, i);
+ g_print ("%02x\t%s [%i]\n", map->idx, map->name, map->feature);
+ }
+}
+
+/**
+ * hidpp_device_map_get_by_feature:
+ *
+ * Gets the cached index from the function number.
+ **/
+static const HidppDeviceMap *
+hidpp_device_map_get_by_feature (HidppDevice *device, guint16 feature)
+{
+ guint i;
+ HidppDeviceMap *map;
+ HidppDevicePrivate *priv = device->priv;
+
+ for (i = 0; i < priv->feature_index->len; i++) {
+ map = g_ptr_array_index (priv->feature_index, i);
+ if (map->feature == feature)
+ return map;
+ }
+ return NULL;
+}
+
+/**
+ * hidpp_device_map_get_by_idx:
+ *
+ * Gets the cached index from the function index.
+ **/
+static const HidppDeviceMap *
+hidpp_device_map_get_by_idx (HidppDevice *device, gint idx)
+{
+ guint i;
+ HidppDeviceMap *map;
+ HidppDevicePrivate *priv = device->priv;
+
+ for (i = 0; i < priv->feature_index->len; i++) {
+ map = g_ptr_array_index (priv->feature_index, i);
+ if (map->idx == idx)
+ return map;
+ }
+ return NULL;
+}
+
+/**
+ * hidpp_device_print_buffer:
+ *
+ * Pretty print the send/recieve buffer.
+ **/
+static void
+hidpp_device_print_buffer (HidppDevice *device, const guint8 *buffer)
+{
+ guint i;
+ const HidppDeviceMap *map;
+
+ if (!device->priv->enable_debug)
+ return;
+ for (i = 0; i < HIDPP_RESPONSE_LONG_LENGTH; i++)
+ g_print ("%02x ", buffer[i]);
+ g_print ("\n");
+
+ /* direction */
+ if (buffer[0] == HIDPP_HEADER_REQUEST)
+ g_print ("REQUEST\n");
+ else if (buffer[0] == HIDPP_HEADER_RESPONSE)
+ g_print ("RESPONSE\n");
+ else
+ g_print ("??\n");
+
+ /* dev index */
+ g_print ("device-idx=%02x ", buffer[1]);
+ if (buffer[1] == HIDPP_RECEIVER_ADDRESS) {
+ g_print ("[Receiver]\n");
+ } else if (device->priv->device_idx == buffer[1]) {
+ g_print ("[This Device]\n");
+ } else {
+ g_print ("[Random Device]\n");
+ }
+
+ /* feature index */
+ if (buffer[2] == HIDPP_READ_LONG_REGISTER) {
+ g_print ("feature-idx=%s [%02x]\n",
+ "v1(ReadLongRegister)", buffer[2]);
+ } else {
+ map = hidpp_device_map_get_by_idx (device, buffer[2]);
+ g_print ("feature-idx=v2(%s) [%02x]\n",
+ map != NULL ? map->name : "unknown", buffer[2]);
+ }
+
+ g_print ("function-id=%01x\n", buffer[3] & 0xf);
+ g_print ("software-id=%01x\n", buffer[3] >> 4);
+ g_print ("param[0]=%02x\n\n", buffer[4]);
+}
+
+/**
+ * hidpp_device_cmd:
+ **/
+static gboolean
+hidpp_device_cmd (HidppDevice *device,
+ guint8 device_idx,
+ guint8 feature_idx,
+ guint8 function_idx,
+ guint8 *request_data,
+ gsize request_len,
+ guint8 *response_data,
+ gsize response_len,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ gssize wrote;
+ guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
+ guint i;
+ HidppDevicePrivate *priv = device->priv;
+ GPollFD poll[] = {
+ {
+ .fd = priv->fd,
+ .events = G_IO_IN | G_IO_OUT | G_IO_ERR,
+ },
+ };
+
+ /* make the request packet */
+ memset (buf, 0x00, HIDPP_RESPONSE_LONG_LENGTH);
+ buf[0] = HIDPP_HEADER_REQUEST;
+ buf[1] = device_idx;
+ buf[2] = feature_idx;
+ buf[3] = function_idx;
+ for (i = 0; i < request_len; i++)
+ buf[4 + i] = request_data[i];
+
+ /* write to the device */
+ hidpp_device_print_buffer (device, buf);
+ wrote = write (priv->fd, buf, 4 + request_len);
+ if ((gsize) wrote != 4 + request_len) {
+ g_set_error (error, 1, 0,
+ "Unable to write request to device: %" G_GSIZE_FORMAT,
+ wrote);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* read from the device */
+ wrote = g_poll (poll, G_N_ELEMENTS(poll),
+ HIDPP_DEVICE_READ_RESPONSE_TIMEOUT);
+ if (wrote <= 0) {
+ g_set_error (error, 1, 0,
+ "Attempt to read response from device timed out: %" G_GSIZE_FORMAT,
+ wrote);
+ ret = FALSE;
+ goto out;
+ }
+ memset (buf, 0x00, HIDPP_RESPONSE_LONG_LENGTH);
+ wrote = read (priv->fd, buf, sizeof (buf));
+ if (wrote <= 0) {
+ g_set_error (error, 1, 0,
+ "Unable to read response from device: %" G_GSIZE_FORMAT,
+ wrote);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* is device offline */
+ hidpp_device_print_buffer (device, buf);
+ if (buf[0] == HIDPP_HEADER_REQUEST &&
+ buf[1] == device_idx &&
+ buf[2] == HIDPP_ERR_INVALID_SUBID &&
+ buf[3] == 0x00 &&
+ buf[4] == HIDPP_FEATURE_ROOT_FN_PING) {
+ /* HID++ 1.0 ping reply, so fake success */
+ if (buf[5] == HIDPP_ERROR_CODE_UNKNOWN) {
+ buf[0] = 1;
+ goto out;
+ }
+ if (buf[5] == HIDPP_ERROR_CODE_UNSUPPORTED) {
+ /* device offline / unreachable */
+ g_set_error_literal (error, 1, 0,
+ "device is unreachable");
+ ret = FALSE;
+ goto out;
+ }
+ }
+ if (buf[0] != HIDPP_HEADER_RESPONSE ||
+ buf[1] != device_idx ||
+ buf[2] != feature_idx ||
+ buf[3] != function_idx) {
+ g_set_error (error, 1, 0,
+ "invalid response from device: %" G_GSIZE_FORMAT,
+ wrote);
+ ret = FALSE;
+ goto out;
+ }
+ for (i = 0; i < response_len; i++)
+ response_data[i] = buf[4 + i];
+out:
+ return ret;
+}
+
+/**
+ * hidpp_device_map_add:
+ *
+ * Requests the index for a function, and adds it to the memeory cache
+ * if it exists.
+ **/
+static gboolean
+hidpp_device_map_add (HidppDevice *device,
+ guint16 feature,
+ const gchar *name)
+{
+ gboolean ret;
+ GError *error = NULL;
+ guint8 buf[3];
+ HidppDeviceMap *map;
+ HidppDevicePrivate *priv = device->priv;
+
+ buf[0] = feature >> 8;
+ buf[1] = feature;
+ buf[2] = 0x00;
+
+ g_debug ("Getting idx for feature %s [%02x]", name, feature);
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ HIDPP_FEATURE_ROOT_INDEX,
+ HIDPP_FEATURE_ROOT_FN_GET_FEATURE,
+ buf, sizeof (buf),
+ buf, sizeof (buf),
+ &error);
+ if (!ret) {
+ g_warning ("Failed to get feature idx: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* zero index */
+ if (buf[0] == 0x00) {
+ ret = FALSE;
+ g_debug ("Feature not found");
+ goto out;
+ }
+
+ /* add to map */
+ map = g_new0 (HidppDeviceMap, 1);
+ map->idx = buf[0];
+ map->feature = feature;
+ map->name = g_strdup (name);
+ g_ptr_array_add (priv->feature_index, map);
+ g_debug ("Added feature %s [%02x] as idx %02x",
+ name, feature, map->idx);
+out:
+ return ret;
+}
+
+/**
+ * hidpp_device_get_model:
+ **/
+const gchar *
+hidpp_device_get_model (HidppDevice *device)
+{
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), NULL);
+ return device->priv->model;
+}
+
+/**
+ * hidpp_device_get_batt_percentage:
+ **/
+guint
+hidpp_device_get_batt_percentage (HidppDevice *device)
+{
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), 0);
+ return device->priv->batt_percentage;
+}
+
+/**
+ * hidpp_device_get_version:
+ **/
+guint
+hidpp_device_get_version (HidppDevice *device)
+{
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), 0);
+ return device->priv->version;
+}
+
+/**
+ * hidpp_device_get_batt_status:
+ **/
+HidppDeviceBattStatus
+hidpp_device_get_batt_status (HidppDevice *device)
+{
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), HIDPP_DEVICE_BATT_STATUS_UNKNOWN);
+ return device->priv->batt_status;
+}
+
+/**
+ * hidpp_device_get_kind:
+ **/
+HidppDeviceKind
+hidpp_device_get_kind (HidppDevice *device)
+{
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), HIDPP_DEVICE_KIND_UNKNOWN);
+ return device->priv->kind;
+}
+
+/**
+ * hidpp_device_set_hidraw_device:
+ **/
+void
+hidpp_device_set_hidraw_device (HidppDevice *device,
+ const gchar *hidraw_device)
+{
+ g_return_if_fail (HIDPP_IS_DEVICE (device));
+ device->priv->hidraw_device = g_strdup (hidraw_device);
+}
+
+/**
+ * hidpp_device_set_index:
+ **/
+void
+hidpp_device_set_index (HidppDevice *device,
+ guint device_idx)
+{
+ g_return_if_fail (HIDPP_IS_DEVICE (device));
+ device->priv->device_idx = device_idx;
+}
+
+/**
+ * hidpp_device_set_enable_debug:
+ **/
+void
+hidpp_device_set_enable_debug (HidppDevice *device,
+ gboolean enable_debug)
+{
+ g_return_if_fail (HIDPP_IS_DEVICE (device));
+ device->priv->enable_debug = enable_debug;
+}
+
+/**
+ * hidpp_device_refresh:
+ **/
+gboolean
+hidpp_device_refresh (HidppDevice *device,
+ HidppRefreshFlags refresh_flags,
+ GError **error)
+{
+ const HidppDeviceMap *map;
+ gboolean ret = TRUE;
+ GString *name = NULL;
+ guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
+ guint i;
+ guint len;
+ HidppDevicePrivate *priv = device->priv;
+
+ g_return_val_if_fail (HIDPP_IS_DEVICE (device), FALSE);
+
+ /* open the device if it's not already opened */
+ if (priv->fd < 0) {
+ priv->fd = open (device->priv->hidraw_device, O_RDWR | O_NONBLOCK);
+ if (priv->fd < 0) {
+ g_set_error (error, 1, 0,
+ "cannot open device file %s",
+ priv->hidraw_device);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* add features we are going to use */
+// hidpp_device_map_add (device,
+// HIDPP_FEATURE_I_FEATURE_SET,
+// "IFeatureSet");
+// hidpp_device_map_add (device,
+// HIDPP_FEATURE_I_FIRMWARE_INFO,
+// "IFirmwareInfo");
+ hidpp_device_map_add (device,
+ HIDPP_FEATURE_GET_DEVICE_NAME_TYPE,
+ "GetDeviceNameType");
+ hidpp_device_map_add (device,
+ HIDPP_FEATURE_BATTERY_LEVEL_STATUS,
+ "BatteryLevelStatus");
+// hidpp_device_map_add (device,
+// HIDPP_FEATURE_WIRELESS_DEVICE_STATUS,
+// "WirelessDeviceStatus");
+ hidpp_device_map_add (device,
+ HIDPP_FEATURE_SOLAR_DASHBOARD,
+ "SolarDashboard");
+ hidpp_device_map_print (device);
+ }
+
+ /* get version */
+ if ((refresh_flags & HIDPP_REFRESH_FLAGS_VERSION) > 0) {
+ buf[0] = 0x00;
+ buf[1] = 0x00;
+ buf[2] = HIDPP_PING_DATA;
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ HIDPP_FEATURE_ROOT_INDEX,
+ HIDPP_FEATURE_ROOT_FN_PING,
+ buf, 3,
+ buf, 4,
+ error);
+ if (!ret)
+ goto out;
+ priv->version = buf[0];
+ }
+
+ /* get device kind */
+ if ((refresh_flags & HIDPP_REFRESH_FLAGS_KIND) > 0) {
+
+ if (priv->version == 1) {
+ buf[0] = 0x20 | (priv->device_idx - 1);
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ ret = hidpp_device_cmd (device,
+ HIDPP_RECEIVER_ADDRESS,
+ HIDPP_READ_LONG_REGISTER,
+ 0xb5,
+ buf, 3,
+ buf, 7,
+ error);
+ if (!ret)
+ goto out;
+ switch (buf[7]) {
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL:
+ priv->kind = HIDPP_DEVICE_KIND_KEYBOARD;
+ break;
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER:
+ priv->kind = HIDPP_DEVICE_KIND_MOUSE;
+ break;
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET:
+ priv->kind = HIDPP_DEVICE_KIND_TABLET;
+ break;
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD:
+ case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK:
+ /* upower doesn't have something for this yet */
+ priv->kind = HIDPP_DEVICE_KIND_UNKNOWN;
+ break;
+ }
+ } else if (priv->version == 2) {
+
+ /* send a BatteryLevelStatus report */
+ map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE);
+ if (map != NULL) {
+ buf[0] = 0x00;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ map->idx,
+ HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_TYPE,
+ buf, 3,
+ buf, 1,
+ error);
+ if (!ret)
+ goto out;
+ switch (buf[0]) {
+ case 0: /* keyboard */
+ case 2: /* numpad */
+ priv->kind = HIDPP_DEVICE_KIND_KEYBOARD;
+ break;
+ case 3: /* mouse */
+ case 4: /* touchpad */
+ case 5: /* trackball */
+ priv->kind = HIDPP_DEVICE_KIND_MOUSE;
+ break;
+ case 1: /* remote-control */
+ case 6: /* presenter */
+ case 7: /* receiver */
+ priv->kind = HIDPP_DEVICE_KIND_UNKNOWN;
+ break;
+ }
+ }
+ }
+ }
+
+ /* get device model string */
+ if ((refresh_flags & HIDPP_REFRESH_FLAGS_MODEL) > 0) {
+ buf[0] = 0x00;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE);
+ if (map != NULL) {
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ map->idx,
+ HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_COUNT,
+ buf, 3,
+ buf, 1,
+ error);
+ if (!ret)
+ goto out;
+ }
+ len = buf[0];
+ name = g_string_new ("");
+ for (i = 0; i < len; i +=4 ) {
+ buf[0] = i;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ map->idx,
+ HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_NAME,
+ buf, 3,
+ buf, 4,
+ error);
+ if (!ret)
+ goto out;
+ g_string_append_len (name, (gchar *) &buf[0], 4);
+ }
+ priv->model = g_strdup (name->str);
+ }
+
+ /* get battery status */
+ if ((refresh_flags & HIDPP_REFRESH_FLAGS_BATTERY) > 0) {
+ if (priv->version == 1) {
+ buf[0] = HIDPP_READ_SHORT_REGISTER;
+ buf[1] = HIDPP_READ_SHORT_REGISTER_BATTERY;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ HIDPP_FEATURE_ROOT_INDEX,
+ HIDPP_FEATURE_ROOT_FN_PING,
+ buf, 5,
+ buf, 1,
+ error);
+ if (!ret)
+ goto out;
+ priv->batt_percentage = buf[0];
+ priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING;
+ } else if (priv->version == 2) {
+
+ /* sent a SetLightMeasure report */
+ map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_SOLAR_DASHBOARD);
+ if (map != NULL) {
+ buf[0] = 0x01; /* Max number of reports: number of report sent after function call */
+ buf[1] = 0x01; /* Report period: time between reports, in seconds */
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ map->idx,
+ HIDPP_FEATURE_SOLAR_DASHBOARD_FN_SET_LIGHT_MEASURE,
+ buf, 2,
+ buf, 3,
+ error);
+ if (!ret)
+ goto out;
+ priv->batt_percentage = buf[0];
+ priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING;
+ }
+
+ /* send a BatteryLevelStatus report */
+ map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_BATTERY_LEVEL_STATUS);
+ if (map != NULL) {
+ buf[0] = 0x00;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ ret = hidpp_device_cmd (device,
+ priv->device_idx,
+ map->idx,
+ HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS,
+ buf, 3,
+ buf, 3,
+ error);
+ if (!ret)
+ goto out;
+
+ /* convert the HID++ v2 status into something
+ * we can set on the device */
+ switch (buf[2]) {
+ case 0: /* discharging */
+ priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING;
+ break;
+ case 1: /* recharging */
+ case 2: /* charge nearly complete */
+ case 4: /* charging slowly */
+ priv->batt_status = HIDPP_DEVICE_BATT_STATUS_CHARGING;
+ break;
+ case 3: /* charging complete */
+ priv->batt_status = HIDPP_DEVICE_BATT_STATUS_CHARGED;
+ break;
+ default:
+ break;
+ }
+ priv->batt_percentage = buf[0];
+ g_debug ("level=%i%%, next-level=%i%%, battery-status=%i",
+ buf[0], buf[1], buf[2]);
+ }
+ }
+ }
+out:
+ if (name != NULL)
+ g_string_free (name, TRUE);
+ return ret;
+}
+
+/**
+ * hidpp_device_init:
+ **/
+static void
+hidpp_device_init (HidppDevice *device)
+{
+ HidppDeviceMap *map;
+
+ device->priv = HIDPP_DEVICE_GET_PRIVATE (device);
+ device->priv->fd = -1;
+ device->priv->feature_index = g_ptr_array_new_with_free_func (g_free);
+ device->priv->batt_status = HIDPP_DEVICE_BATT_STATUS_UNKNOWN;
+ device->priv->kind = HIDPP_DEVICE_KIND_UNKNOWN;
+
+ /* add known root */
+ map = g_new0 (HidppDeviceMap, 1);
+ map->idx = HIDPP_FEATURE_ROOT_INDEX;
+ map->feature = HIDPP_FEATURE_ROOT;
+ map->name = g_strdup ("Root");
+ g_ptr_array_add (device->priv->feature_index, map);
+}
+
+/**
+ * hidpp_device_finalize:
+ **/
+static void
+hidpp_device_finalize (GObject *object)
+{
+ HidppDevice *device;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (HIDPP_IS_DEVICE (object));
+
+ device = HIDPP_DEVICE (object);
+ g_return_if_fail (device->priv != NULL);
+
+ if (device->priv->channel_source_id > 0)
+ g_source_remove (device->priv->channel_source_id);
+
+ if (device->priv->channel) {
+ g_io_channel_shutdown (device->priv->channel, FALSE, NULL);
+ g_io_channel_unref (device->priv->channel);
+ }
+ g_ptr_array_unref (device->priv->feature_index);
+
+ g_free (device->priv->hidraw_device);
+ g_free (device->priv->model);
+
+ G_OBJECT_CLASS (hidpp_device_parent_class)->finalize (object);
+}
+
+/**
+ * hidpp_device_class_init:
+ **/
+static void
+hidpp_device_class_init (HidppDeviceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = hidpp_device_finalize;
+ g_type_class_add_private (klass, sizeof (HidppDevicePrivate));
+}
+
+/**
+ * hidpp_device_new:
+ **/
+HidppDevice *
+hidpp_device_new (void)
+{
+ return g_object_new (HIDPP_TYPE_DEVICE, NULL);
+}
diff --git a/src/linux/hidpp-device.h b/src/linux/hidpp-device.h
new file mode 100644
index 0000000..935cd07
--- /dev/null
+++ b/src/linux/hidpp-device.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Julien Danjou <julien@danjou.info>
+ * Copyright (C) 2012 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 __HIDPP_DEVICE_H__
+#define __HIDPP_DEVICE_H__
+
+#include <glib-object.h>
+
+#include "hidpp-device.h"
+
+G_BEGIN_DECLS
+
+#define HIDPP_TYPE_DEVICE (hidpp_device_get_type ())
+#define HIDPP_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), HIDPP_TYPE_DEVICE, HidppDevice))
+#define HIDPP_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), HIDPP_TYPE_DEVICE, HidppDeviceClass))
+#define HIDPP_IS_DEVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), HIDPP_TYPE_DEVICE))
+#define HIDPP_IS_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), HIDPP_TYPE_DEVICE))
+#define HIDPP_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), HIDPP_TYPE_DEVICE, HidppDeviceClass))
+
+typedef struct HidppDevicePrivate HidppDevicePrivate;
+
+typedef struct
+{
+ GObject parent;
+ HidppDevicePrivate *priv;
+} HidppDevice;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} HidppDeviceClass;
+
+typedef enum {
+ HIDPP_DEVICE_KIND_KEYBOARD,
+ HIDPP_DEVICE_KIND_MOUSE,
+ HIDPP_DEVICE_KIND_TOUCHPAD,
+ HIDPP_DEVICE_KIND_TRACKBALL,
+ HIDPP_DEVICE_KIND_TABLET,
+ HIDPP_DEVICE_KIND_UNKNOWN
+} HidppDeviceKind;
+
+typedef enum {
+ HIDPP_DEVICE_BATT_STATUS_CHARGING,
+ HIDPP_DEVICE_BATT_STATUS_DISCHARGING,
+ HIDPP_DEVICE_BATT_STATUS_CHARGED,
+ HIDPP_DEVICE_BATT_STATUS_UNKNOWN
+} HidppDeviceBattStatus;
+
+typedef enum {
+ HIDPP_REFRESH_FLAGS_VERSION = 1,
+ HIDPP_REFRESH_FLAGS_KIND = 2,
+ HIDPP_REFRESH_FLAGS_BATTERY = 4,
+ HIDPP_REFRESH_FLAGS_MODEL = 8
+} HidppRefreshFlags;
+
+GType hidpp_device_get_type (void);
+const gchar *hidpp_device_get_model (HidppDevice *device);
+guint hidpp_device_get_batt_percentage (HidppDevice *device);
+guint hidpp_device_get_version (HidppDevice *device);
+HidppDeviceBattStatus hidpp_device_get_batt_status (HidppDevice *device);
+HidppDeviceKind hidpp_device_get_kind (HidppDevice *device);
+void hidpp_device_set_hidraw_device (HidppDevice *device,
+ const gchar *hidraw_device);
+void hidpp_device_set_index (HidppDevice *device,
+ guint device_idx);
+void hidpp_device_set_enable_debug (HidppDevice *device,
+ gboolean enable_debug);
+gboolean hidpp_device_refresh (HidppDevice *device,
+ HidppRefreshFlags refresh_flags,
+ GError **error);
+HidppDevice *hidpp_device_new (void);
+
+G_END_DECLS
+
+#endif /* __HIDPP_DEVICE_H__ */
+
diff --git a/src/linux/hidpp-test.c b/src/linux/hidpp-test.c
new file mode 100644
index 0000000..5ae85ce
--- /dev/null
+++ b/src/linux/hidpp-test.c
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "hidpp-device.h"
+
+int
+main (int argc, char **argv)
+{
+// const gchar *model;
+// guint batt_percentage;
+// guint version;
+// HidppDeviceBattStatus batt_status;
+ HidppDevice *d;
+// HidppDeviceKind kind;
+ gboolean ret;
+ GError *error = NULL;
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ d = hidpp_device_new ();
+ hidpp_device_set_enable_debug (d, TRUE);
+ g_assert_cmpint (hidpp_device_get_version (d), ==, 0);
+ g_assert_cmpstr (hidpp_device_get_model (d), ==, NULL);
+ g_assert_cmpint (hidpp_device_get_batt_percentage (d), ==, 0);
+ g_assert_cmpint (hidpp_device_get_batt_status (d), ==, HIDPP_DEVICE_BATT_STATUS_UNKNOWN);
+ g_assert_cmpint (hidpp_device_get_kind (d), ==, HIDPP_DEVICE_KIND_UNKNOWN);
+
+ /* setup */
+ hidpp_device_set_hidraw_device (d, "/dev/hidraw0");
+ hidpp_device_set_index (d, 1);
+ ret = hidpp_device_refresh (d,
+ HIDPP_REFRESH_FLAGS_VERSION |
+ HIDPP_REFRESH_FLAGS_KIND |
+ HIDPP_REFRESH_FLAGS_BATTERY |
+ HIDPP_REFRESH_FLAGS_MODEL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpint (hidpp_device_get_version (d), !=, 0);
+ g_assert_cmpstr (hidpp_device_get_model (d), !=, NULL);
+ g_assert_cmpint (hidpp_device_get_batt_percentage (d), !=, 0);
+ g_assert_cmpint (hidpp_device_get_batt_status (d), !=, HIDPP_DEVICE_BATT_STATUS_UNKNOWN);
+ g_assert_cmpint (hidpp_device_get_kind (d), !=, HIDPP_DEVICE_KIND_UNKNOWN);
+
+ g_print ("Version:\t\t%i\n", hidpp_device_get_version (d));
+ g_print ("Kind:\t\t\t%i\n", hidpp_device_get_kind (d));
+ g_print ("Model:\t\t\t%s\n", hidpp_device_get_model (d));
+ g_print ("Battery Percentage:\t%i\n", hidpp_device_get_batt_percentage (d));
+ g_print ("Battery Status:\t\t%i\n", hidpp_device_get_batt_status (d));
+
+ g_object_unref (d);
+ return 0;
+}
diff --git a/src/linux/up-backend.c b/src/linux/up-backend.c
index 0ae3413..3656b69 100644
--- a/src/linux/up-backend.c
+++ b/src/linux/up-backend.c
@@ -36,7 +36,7 @@
#include "up-device-supply.h"
#include "up-device-csr.h"
-#include "up-device-lg-unifying.h"
+#include "up-device-unifying.h"
#include "up-device-wup.h"
#include "up-device-hid.h"
#include "up-input.h"
@@ -118,6 +118,18 @@ up_backend_device_new (UpBackend *backend, GUdevDevice *native)
/* no valid power supply object */
device = NULL;
+ } else if (g_strcmp0 (subsys, "hid") == 0) {
+
+ /* see if this is a Unifying mouse or keyboard */
+ device = UP_DEVICE (up_device_unifying_new ());
+ ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (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 */
@@ -176,18 +188,8 @@ up_backend_device_new (UpBackend *backend, GUdevDevice *native)
/* no valid input object */
device = NULL;
- } else {
- g_object_unref (input);
-
- /* see if this is a Unifying mouse or keyboard */
- device = UP_DEVICE (up_device_unifying_new ());
- ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (native));
- if (!ret) {
- g_object_unref (device);
- /* no valid input object */
- device = NULL;
- }
}
+ g_object_unref (input);
} else {
native_path = g_udev_device_get_sysfs_path (native);
g_warning ("native path %s (%s) ignoring", native_path, subsys);
@@ -328,7 +330,7 @@ up_backend_coldplug (UpBackend *backend, UpDaemon *daemon)
GList *l;
guint i;
gboolean ret;
- const gchar *subsystems[] = {"power_supply", "usb", "usbmisc", "tty", "input", NULL};
+ const gchar *subsystems[] = {"power_supply", "usb", "usbmisc", "tty", "input", "hid", NULL};
backend->priv->daemon = g_object_ref (daemon);
backend->priv->device_list = up_daemon_get_device_list (daemon);
diff --git a/src/linux/up-device-lg-unifying.c b/src/linux/up-device-lg-unifying.c
deleted file mode 100644
index eddd1ec..0000000
--- a/src/linux/up-device-lg-unifying.c
+++ /dev/null
@@ -1,775 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
- *
- * Copyright (C) 2012 Julien Danjou <julien@danjou.info>
- *
- * 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 <linux/hidraw.h>
-#include <linux/input.h>
-#include <string.h>
-#include <math.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <errno.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 "sysfs-utils.h"
-#include "up-types.h"
-#include "up-device-lg-unifying.h"
-
-/* Arbitrary value used in ping */
-#define HIDPP_PING_DATA 0x42
-
-#define HIDPP_RECEIVER_ADDRESS 0xff
-
-#define HIDPP_RESPONSE_SHORT_LENGTH 7
-#define HIDPP_RESPONSE_LONG_LENGTH 20
-
-#define HIDPP_HEADER_REQUEST 0x10
-#define HIDPP_HEADER_RESPONSE 0x11
-
-/* HID++ 1.0 */
-#define HIDPP_READ_SHORT_REGISTER 0x81
-#define HIDPP_READ_SHORT_REGISTER_BATTERY 0x0d
-
-#define HIDPP_READ_LONG_REGISTER 0x83
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE 11
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD 0x1
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE 0x2
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD 0x3
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER 0x4
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL 0x7
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL 0x8
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD 0x9
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET 0xa
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD 0xb
-#define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK 0xc
-
-#define HIDPP_ERR_INVALID_SUBID 0x8f
-
-/* HID++ 2.0 */
-#define HIDPP_FEATURE_ROOT 0x0000
-/* This is the only feature that has an hard coded index */
-#define HIDPP_FEATURE_ROOT_INDEX 0x00
-#define HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE (0x00 << 4)
-#define HIDPP_FEATURE_ROOT_FUNCTION_PING (0x01 << 4)
-
-#define HIDPP_FEATURE_GETDEVICENAMETYPE 0x0005
-#define HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT (0x00 << 4)
-#define HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME (0x01 << 4)
-
-#define HIDPP_FEATURE_SOLAR_DASHBOARD 0x4301
-#define HIDPP_FEATURE_SOLAR_DASHBOARD_FUNCTION_SetLightMeasure (0x00 << 4)
-#define HIDPP_FEATURE_SOLAR_DASHBOARD_BattLightMeasureBroadcastEvent (0x01 << 4)
-
-#define HIDPP_FEATURE_FUNCTION_AS_ARG(feature) \
- feature >> 8, feature, 0x00
-
-#define USB_VENDOR_ID_LOGITECH "046d"
-#define USB_DEVICE_ID_UNIFYING_RECEIVER "c52b"
-#define USB_DEVICE_ID_UNIFYING_RECEIVER_2 "c532"
-
-#define UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT 3000 /* miliseconds */
-#define UP_DEVICE_UNIFYING_REFRESH_TIMEOUT 60L /* seconds */
-
-struct UpDeviceUnifyingPrivate
-{
- guint poll_timer_id;
- int fd;
- /* Device index on the Unifying "bus" */
- gint device_index;
- gint feature_solar_dashboard_index;
- GIOChannel *channel;
- guint channel_source_id;
-};
-
-G_DEFINE_TYPE (UpDeviceUnifying, up_device_unifying, UP_TYPE_DEVICE)
-#define UP_DEVICE_UNIFYING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingPrivate))
-
-/**
- * up_device_unifying_event_io:
- *
- * Read events from Unifying device, and treats them.
- **/
-static gboolean
-up_device_unifying_event_io (GIOChannel *channel, GIOCondition condition, gpointer data)
-{
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- UpDeviceUnifying *unifying = data;
- UpDevice *device = UP_DEVICE (unifying);
- GTimeVal timeval;
- guint16 lux;
-
- while (read (unifying->priv->fd, buf, sizeof(buf)) > 0)
- if (buf[0] == HIDPP_HEADER_RESPONSE &&
- buf[1] == unifying->priv->device_index &&
- buf[2] == unifying->priv->feature_solar_dashboard_index &&
- buf[3] == HIDPP_FEATURE_SOLAR_DASHBOARD_BattLightMeasureBroadcastEvent) {
- lux = (buf[5] << 8) | buf[6];
- if (lux > 200) {
- g_object_set (device,
- "state", UP_DEVICE_STATE_CHARGING,
- "power-supply", TRUE,
- NULL);
- } else if (lux > 0) {
- g_object_set (device,
- "state", UP_DEVICE_STATE_DISCHARGING,
- "power-supply", TRUE,
- NULL);
- } else {
- g_object_set (device,
- "state", UP_DEVICE_STATE_DISCHARGING,
- "power-supply", FALSE,
- NULL);
- }
-
- g_get_current_time (&timeval);
-
- g_object_set (device,
- "update-time", (guint64) timeval.tv_sec,
- "percentage", (gdouble) (guint8) buf[4],
- "luminosity", (gdouble) lux,
- NULL);
- }
-
- return TRUE;
-}
-
-static gint
-up_device_unifying_read_response (int fd,
- guint8 request[],
- size_t count,
- gint64 start_time)
-{
- GPollFD poll[] = {
- {
- .fd = fd,
- .events = G_IO_IN | G_IO_HUP | G_IO_ERR,
- },
- };
- gint ret;
-
- /* If we started to wait for a particular response more than some
- * time ago, abort */
- if (g_get_monotonic_time () - start_time
- >= UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT * 1000)
- return -1;
-
- ret = g_poll (poll, G_N_ELEMENTS(poll),
- UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT);
-
- if (ret > 0)
- return read (fd, request, count);
-
- return ret;
-}
-
-/**
- * up_device_unifying_hidpp1_set_battery:
- *
- * Send a READ SHORT REGISTER call to the device, and set battery status.
- **/
-static gboolean
-up_device_unifying_hidpp1_set_battery (UpDeviceUnifying *unifying)
-{
- UpDevice *device = UP_DEVICE (unifying);
- guint8 request[] = {
- HIDPP_HEADER_REQUEST,
- unifying->priv->device_index,
- HIDPP_READ_SHORT_REGISTER,
- HIDPP_READ_SHORT_REGISTER_BATTERY,
- 0x00, 0x00, 0x00,
- };
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- gint64 start_time;
-
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to read battery status from Unifying device %d",
- unifying->priv->device_index);
- return FALSE;
- }
-
- start_time = g_get_monotonic_time ();
-
- while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0)
- if (buf[0] == HIDPP_HEADER_REQUEST
- && buf[1] == unifying->priv->device_index
- && buf[2] == HIDPP_READ_SHORT_REGISTER
- && buf[3] == HIDPP_READ_SHORT_REGISTER_BATTERY) {
- g_object_set (device,
- "percentage", (gdouble) buf[4],
- NULL);
- return TRUE;
- }
-
- return FALSE;
-}
-
-/**
- * up_device_unifying_hidpp2_get_feature_index:
- *
- * Get a Unifying HID++ 2.0 feature index and return it.
- * Returns 0 if the feature does not exists on this device.
- **/
-static guint8
-up_device_unifying_hidpp2_get_feature_index (UpDeviceUnifying *unifying, guint16 feature)
-{
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- guint8 request[] = {
- HIDPP_HEADER_REQUEST,
- unifying->priv->device_index,
- HIDPP_FEATURE_ROOT_INDEX,
- HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE,
- HIDPP_FEATURE_FUNCTION_AS_ARG(feature)
- };
- gint64 start_time;
-
- /* Request the device name feature index */
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to send GetFeature request to device");
- return -1;
- }
-
- start_time = g_get_monotonic_time ();
-
- while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0)
- if (buf[0] == HIDPP_HEADER_RESPONSE &&
- buf[1] == unifying->priv->device_index &&
- buf[2] == HIDPP_FEATURE_ROOT_INDEX &&
- buf[3] == HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE)
- return buf[4];
- return -1;
-}
-
-
-/**
- * up_device_unifying_hidpp2_set_battery:
- *
- * Send a bunch of HID++ requests to get the device battery and set it.
- **/
-static gboolean
-up_device_unifying_hidpp2_set_battery (UpDeviceUnifying *unifying)
-{
- guint8 request[] = {
- HIDPP_HEADER_REQUEST,
- unifying->priv->device_index,
- 0x00, 0x00, 0x00, 0x00, 0x00,
- };
-
- if (unifying->priv->feature_solar_dashboard_index == -1)
- unifying->priv->feature_solar_dashboard_index =
- up_device_unifying_hidpp2_get_feature_index (unifying, HIDPP_FEATURE_SOLAR_DASHBOARD);
-
- if (unifying->priv->feature_solar_dashboard_index == 0) {
- /* Probably not a solar keyboard */
- /* TODO: add support for BatteryLevelStatus */
- } else {
- /* This request will make the keyboard send a bunch of packets
- * (events) with lux-meter and battery information */
- request[2] = unifying->priv->feature_solar_dashboard_index;
- request[3] = HIDPP_FEATURE_SOLAR_DASHBOARD_FUNCTION_SetLightMeasure;
- request[4] = 0x01; /* Max number of reports: number of report sent after function call */
- request[5] = 0x01; /* Report period: time between reports, in seconds */
-
-
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to send solar battery/lux events start request to device");
- return FALSE;
- }
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-/**
- * up_device_unifying_hidpp2_get_device_name:
- *
- * Send a bunch of HID++ requests to get the device name (model) and return
- * it.
- **/
-static GString *
-up_device_unifying_hidpp2_get_device_name (UpDeviceUnifying *unifying)
-{
- GString *name = NULL;
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- ssize_t res;
- guint8 request[] = {
- HIDPP_HEADER_REQUEST,
- unifying->priv->device_index,
- 0x00,
- HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT,
- 0x00, 0x00, 0x00,
- };
- ssize_t name_length = 0;
- gint64 start_time;
-
- request[2] = up_device_unifying_hidpp2_get_feature_index (unifying, HIDPP_FEATURE_GETDEVICENAMETYPE);
-
- if (request[2] == 0) {
- g_debug ("Unable to find GetDeviceNameType feature index");
- return NULL;
- }
-
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to send GetDeviceNameType.GetCount request to device");
- return NULL;
- }
-
- start_time = g_get_monotonic_time ();
-
- while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0)
- if (buf[0] == HIDPP_HEADER_RESPONSE &&
- buf[1] == unifying->priv->device_index &&
- buf[2] == request[2] &&
- buf[3] == HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT) {
- name_length = buf[4];
- break;
- }
-
- name = g_string_new_len (NULL, name_length);
-
- while (name_length > 0) {
- request[3] = HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME;
- request[4] = name->len;
-
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to send GetDeviceNameType.GetDeviceName request to device");
- g_string_free (name, TRUE);
- return NULL;
- }
-
- start_time = g_get_monotonic_time ();
-
- while ((res = up_device_unifying_read_response (unifying->priv->fd, buf,
- sizeof (buf), start_time)) > 0)
- if (buf[0] == HIDPP_HEADER_RESPONSE &&
- buf[1] == unifying->priv->device_index &&
- buf[2] == request[2] &&
- buf[3] == HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME) {
- g_string_append_len (name, (gchar *) &buf[4], MIN(res - 4, name_length));
- name_length -= MIN(res - 4, name_length);
- break;
- }
-
- /* Handle no response case */
- if (res <= 0) {
- g_debug ("Error reading GetDeviceNameType.GetDeviceName response");
- g_string_free (name, TRUE);
- return NULL;
- }
- }
-
- return name;
-}
-
-/**
- * up_device_unifying_set_device_type:
- *
- * Send a Read Long Register HID++ 1.0 command to the device. This allows to
- * retrieve the type of the device, and then set it.
- **/
-static gboolean
-up_device_unifying_set_device_type (UpDeviceUnifying *unifying)
-{
- guint8 request[] = {
- HIDPP_HEADER_REQUEST,
- HIDPP_RECEIVER_ADDRESS,
- 0x83, 0xb5,
- 0x20 | (unifying->priv->device_index - 1),
- 0x00, 0x00,
- };
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- UpDevice *device = UP_DEVICE (unifying);
- gint64 start_time;
-
- if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) {
- g_debug ("Unable to send a HID++ read long register request to device %d",
- unifying->priv->device_index);
- return FALSE;
- }
-
- start_time = g_get_monotonic_time ();
-
- while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0)
- if (buf[0] == HIDPP_HEADER_RESPONSE
- && buf[1] == HIDPP_RECEIVER_ADDRESS
- && buf[2] == HIDPP_READ_LONG_REGISTER
- && buf[3] == 0xb5
- && buf[4] == (0x20 | (unifying->priv->device_index - 1))) {
- switch (buf[HIDPP_READ_LONG_REGISTER_DEVICE_TYPE]) {
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL:
- g_object_set (device, "type", UP_DEVICE_KIND_KEYBOARD, NULL);
- break;
-
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER:
- g_object_set (device, "type", UP_DEVICE_KIND_MOUSE, NULL);
- break;
-
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET:
- g_object_set (device, "type", UP_DEVICE_KIND_TABLET, NULL);
- break;
-
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD:
- case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK:
- /* upower doesn't have something for this yet */
- g_object_set (device, "type", UP_DEVICE_KIND_UNKNOWN, NULL);
- break;
- }
- return TRUE;
- }
-
- return FALSE;
-}
-
-/**
- * up_device_unifying_get_hidpp_version
- *
- * Return the version of HID++ used by a device.
- **/
-static gint
-up_device_unifying_get_hidpp_version (UpDeviceUnifying *unifying)
-{
- guint8 ping[] = {
- HIDPP_HEADER_REQUEST,
- unifying->priv->device_index,
- HIDPP_FEATURE_ROOT_INDEX,
- HIDPP_FEATURE_ROOT_FUNCTION_PING,
- 0x00, 0x00, HIDPP_PING_DATA
- };
- guint8 buf[HIDPP_RESPONSE_LONG_LENGTH];
- gint64 start_time;
-
- if (write(unifying->priv->fd, ping, sizeof(ping)) != sizeof(ping)) {
- g_debug ("Unable to send a HID++ ping to device %d",
- unifying->priv->device_index);
- return -1;
- }
-
- /* read event */
-
- start_time = g_get_monotonic_time ();
-
- while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0)
- if(buf[0] == HIDPP_HEADER_REQUEST
- && buf[1] == unifying->priv->device_index
- && buf[2] == HIDPP_ERR_INVALID_SUBID
- && buf[3] == 0x00
- && buf[4] == HIDPP_FEATURE_ROOT_FUNCTION_PING) {
- /* HID++ 1.0 ping reply */
- if (buf[5] == 0x01)
- return 1;
- else if (buf[5] == 0x09)
- /* device offline / unreachable */
- return 0;
- } else if (buf[0] == HIDPP_HEADER_RESPONSE
- && buf[1] == unifying->priv->device_index
- && buf[2] == HIDPP_FEATURE_ROOT_INDEX
- && buf[3] == HIDPP_FEATURE_ROOT_FUNCTION_PING
- && buf[6] == HIDPP_PING_DATA)
- /* HID++ >= 2.0 ping reply: buf[4] is major
- version, buf[5] is minor version but we
- only care about major for now*/
- return buf[4];
-
- return -1;
-}
-
-/**
- * up_device_unifying_refresh:
- *
- * Return %TRUE on success, %FALSE if we failed to refresh or no data
- **/
-static gboolean
-up_device_unifying_refresh (UpDevice *device)
-{
- UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device);
- gint hidpp_version = up_device_unifying_get_hidpp_version (unifying);
- GString *name;
- char *model;
- GTimeVal timeval;
-
- if (hidpp_version > 0)
- g_debug ("Unifying device %d uses HID++ version %d",
- unifying->priv->device_index, hidpp_version);
-
- switch (hidpp_version) {
- case 0:
- g_debug ("Unifying device %d is offline",
- unifying->priv->device_index);
- g_object_set (device,
- "is-present", FALSE,
- "state", UP_DEVICE_STATE_UNKNOWN,
- NULL);
- break;
- case 1:
- g_object_set (device,
- "state", UP_DEVICE_STATE_DISCHARGING,
- "is-present", TRUE,
- NULL);
- up_device_unifying_hidpp1_set_battery (unifying);
- break;
- case 2:
- g_object_set (device,
- "is-present", TRUE,
- NULL);
-
- g_object_get (device, "model", &model, NULL);
- if (!model) {
- name = up_device_unifying_hidpp2_get_device_name (unifying);
- if (name) {
- g_object_set (device, "model", name->str, NULL);
- g_string_free (name, TRUE);
- }
- } else
- g_free (model);
- up_device_unifying_hidpp2_set_battery (unifying);
- break;
- }
-
- g_get_current_time (&timeval);
- g_object_set (device, "update-time", (guint64) timeval.tv_sec, NULL);
-
- return TRUE;
-}
-
-/**
- * up_device_unifying_coldplug:
- *
- * Return %TRUE on success, %FALSE if we failed to get data and should be removed
- **/
-static gboolean
-up_device_unifying_coldplug (UpDevice *device)
-{
- UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device);
- GUdevDevice *native;
- const gchar *device_file;
- const gchar *vendor;
- const gchar *parent_sysfs_path;
- const gchar *bus_address;
- GList *hidraw_list, *entry;
- size_t len;
- GIOStatus status;
- GError *error = NULL;
- GUdevClient *gudev_client;
- GUdevDevice *parent, *hidraw, *receiver = NULL;
- gboolean ret = FALSE;
-
- native = G_UDEV_DEVICE (up_device_get_native (device));
-
- if(g_strcmp0(g_udev_device_get_property (native, "ID_VENDOR_ID"),
- USB_VENDOR_ID_LOGITECH) ||
- (g_strcmp0(g_udev_device_get_property (native, "ID_MODEL_ID"),
- USB_DEVICE_ID_UNIFYING_RECEIVER) &&
- g_strcmp0(g_udev_device_get_property (native, "ID_MODEL_ID"),
- USB_DEVICE_ID_UNIFYING_RECEIVER_2))) {
- g_debug ("Not an Unifying device, ignoring");
- return FALSE;
- }
-
- bus_address = g_udev_device_get_property (native, "PHYS");
-
- if (!bus_address) {
- g_debug ("Device has no physical bus address, ignoring");
- return FALSE;
- }
-
- len = strlen (bus_address);
-
- if (len < 3 || bus_address[len - 3] != ':' || !g_ascii_isdigit (bus_address[len - 2])) {
- g_debug ("Invalid Unifying device index, ignoring");
- return FALSE;
- }
-
- unifying->priv->device_index = g_ascii_digit_value (bus_address[len - 2]);
-
- /* Find the hidraw device of the parent (the receiver) to
- * communicate with the devices */
- gudev_client = g_udev_client_new (NULL);
-
- parent = g_udev_device_get_parent (native);
- parent_sysfs_path = g_udev_device_get_sysfs_path (parent);
- g_object_unref (parent);
-
- hidraw_list = g_udev_client_query_by_subsystem (gudev_client, "hidraw");
-
- for (entry = hidraw_list; entry; entry = entry->next) {
- hidraw = entry->data;
- if (!g_strcmp0 (g_udev_device_get_sysfs_attr (hidraw, "device"),
- parent_sysfs_path))
- receiver = hidraw;
- else
- g_object_unref (hidraw);
- }
-
- if (!receiver) {
- g_debug ("Unable to find an hidraw device for Unifying receiver");
- return FALSE;
- }
-
- /* get device file */
- device_file = g_udev_device_get_device_file (receiver);
-
- /* connect to the device */
- g_debug ("Using Unifying receiver hidraw device file: %s", device_file);
-
- if (device_file == NULL) {
- g_debug ("Could not get device file for Unifying receiver device");
- goto out;
- }
-
- unifying->priv->fd = open (device_file, O_RDWR | O_NONBLOCK);
- if (unifying->priv->fd < 0) {
- g_debug ("cannot open device file %s", device_file);
- return FALSE;
- }
-
- vendor = g_udev_device_get_property (native, "ID_VENDOR");
-
- /* hardcode some default values */
- g_object_set (device,
- "vendor", vendor,
- "is-present", TRUE,
- "has-history", TRUE,
- "is-rechargeable", TRUE,
- "state", UP_DEVICE_STATE_DISCHARGING,
- "power-supply", FALSE,
- NULL);
-
- /* Set device type */
- if (!up_device_unifying_set_device_type(unifying)) {
- g_debug ("Unable to guess device type, ignoring the device");
- goto out;
- }
-
- unifying->priv->channel = g_io_channel_unix_new (unifying->priv->fd);
-
- /* set binary encoding */
- status = g_io_channel_set_encoding (unifying->priv->channel, NULL, &error);
- if (status != G_IO_STATUS_NORMAL) {
- g_warning ("failed to set encoding: %s", error->message);
- g_error_free (error);
- goto out;
- }
-
- /* watch this */
- unifying->priv->channel_source_id = g_io_add_watch (unifying->priv->channel,
- G_IO_IN,
- up_device_unifying_event_io,
- unifying);
-
- /* set up a poll to send the magic packet */
- unifying->priv->poll_timer_id = g_timeout_add_seconds (UP_DEVICE_UNIFYING_REFRESH_TIMEOUT,
- (GSourceFunc) up_device_unifying_refresh,
- device);
-
- ret = TRUE;
-
- out:
- g_object_unref (gudev_client);
- g_object_unref (receiver);
- g_list_free (hidraw_list);
-
- if (!ret && unifying->priv->fd >= 0)
- close (unifying->priv->fd);
-
- return ret;
-}
-
-/**
- * up_device_unifying_init:
- **/
-static void
-up_device_unifying_init (UpDeviceUnifying *unifying)
-{
- unifying->priv = UP_DEVICE_UNIFYING_GET_PRIVATE (unifying);
- unifying->priv->poll_timer_id = 0;
- unifying->priv->fd = -1;
- unifying->priv->feature_solar_dashboard_index = -1;
-}
-
-/**
- * up_device_unifying_finalize:
- **/
-static void
-up_device_unifying_finalize (GObject *object)
-{
- UpDeviceUnifying *unifying;
-
- g_return_if_fail (object != NULL);
- g_return_if_fail (UP_IS_DEVICE_UNIFYING (object));
-
- unifying = UP_DEVICE_UNIFYING (object);
- g_return_if_fail (unifying->priv != NULL);
-
- if (unifying->priv->poll_timer_id > 0)
- g_source_remove (unifying->priv->poll_timer_id);
-
- if (unifying->priv->channel_source_id > 0)
- g_source_remove (unifying->priv->channel_source_id);
-
- if (unifying->priv->channel) {
- g_io_channel_shutdown (unifying->priv->channel, FALSE, NULL);
- g_io_channel_unref (unifying->priv->channel);
- }
-
- G_OBJECT_CLASS (up_device_unifying_parent_class)->finalize (object);
-}
-
-/**
- * up_device_unifying_class_init:
- **/
-static void
-up_device_unifying_class_init (UpDeviceUnifyingClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- UpDeviceClass *device_class = UP_DEVICE_CLASS (klass);
-
- object_class->finalize = up_device_unifying_finalize;
- device_class->coldplug = up_device_unifying_coldplug;
- device_class->refresh = up_device_unifying_refresh;
-
- g_type_class_add_private (klass, sizeof (UpDeviceUnifyingPrivate));
-}
-
-/**
- * up_device_unifying_new:
- **/
-UpDeviceUnifying *
-up_device_unifying_new (void)
-{
- return g_object_new (UP_TYPE_DEVICE_UNIFYING, NULL);
-}
-
diff --git a/src/linux/up-device-unifying.c b/src/linux/up-device-unifying.c
new file mode 100644
index 0000000..c7f0103
--- /dev/null
+++ b/src/linux/up-device-unifying.c
@@ -0,0 +1,284 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Julien Danjou <julien@danjou.info>
+ *
+ * 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 <glib-object.h>
+#include <gudev/gudev.h>
+
+#include "hidpp-device.h"
+
+#include "up-device-unifying.h"
+#include "up-types.h"
+
+#define UP_DEVICE_UNIFYING_REFRESH_TIMEOUT 60 /* seconds */
+
+struct UpDeviceUnifyingPrivate
+{
+ guint poll_timer_id;
+ HidppDevice *hidpp_device;
+};
+
+G_DEFINE_TYPE (UpDeviceUnifying, up_device_unifying, UP_TYPE_DEVICE)
+#define UP_DEVICE_UNIFYING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingPrivate))
+
+/**
+ * up_device_unifying_refresh:
+ *
+ * Return %TRUE on success, %FALSE if we failed to refresh or no data
+ **/
+static gboolean
+up_device_unifying_refresh (UpDevice *device)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GTimeVal timeval;
+ UpDeviceState state = UP_DEVICE_STATE_UNKNOWN;
+ UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device);
+ UpDeviceUnifyingPrivate *priv = unifying->priv;
+
+ /* refresh just the battery stats */
+ ret = hidpp_device_refresh (priv->hidpp_device,
+ HIDPP_REFRESH_FLAGS_BATTERY,
+ &error);
+ if (!ret) {
+ g_warning ("failed to coldplug unifying device: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ switch (hidpp_device_get_batt_status (priv->hidpp_device)) {
+ case HIDPP_DEVICE_BATT_STATUS_CHARGING:
+ state = UP_DEVICE_STATE_CHARGING;
+ break;
+ case HIDPP_DEVICE_BATT_STATUS_DISCHARGING:
+ state = UP_DEVICE_STATE_DISCHARGING;
+ break;
+ case HIDPP_DEVICE_BATT_STATUS_CHARGED:
+ state = UP_DEVICE_STATE_FULLY_CHARGED;
+ break;
+ default:
+ break;
+ }
+ g_get_current_time (&timeval);
+ g_object_set (device,
+ "is-present", hidpp_device_get_version (priv->hidpp_device) > 0,
+ "percentage", (gdouble) hidpp_device_get_batt_percentage (priv->hidpp_device),
+ "state", state,
+ "update-time", (guint64) timeval.tv_sec,
+ NULL);
+out:
+ return TRUE;
+}
+
+static UpDeviceKind
+up_device_unifying_get_device_kind (UpDeviceUnifying *unifying)
+{
+ UpDeviceKind kind;
+ switch (hidpp_device_get_kind (unifying->priv->hidpp_device)) {
+ case HIDPP_DEVICE_KIND_MOUSE:
+ case HIDPP_DEVICE_KIND_TOUCHPAD:
+ case HIDPP_DEVICE_KIND_TRACKBALL:
+ kind = UP_DEVICE_KIND_MOUSE;
+ break;
+ case HIDPP_DEVICE_KIND_KEYBOARD:
+ kind = UP_DEVICE_KIND_KEYBOARD;
+ break;
+ case HIDPP_DEVICE_KIND_TABLET:
+ kind = UP_DEVICE_KIND_TABLET;
+ break;
+ default:
+ kind = UP_DEVICE_KIND_UNKNOWN;
+ }
+ return kind;
+}
+
+/**
+ * up_device_unifying_coldplug:
+ *
+ * Return %TRUE on success, %FALSE if we failed to get data and should be removed
+ **/
+static gboolean
+up_device_unifying_coldplug (UpDevice *device)
+{
+ const gchar *bus_address;
+ const gchar *device_file;
+ const gchar *type;
+ gboolean ret = FALSE;
+ gchar *endptr = NULL;
+ gchar *tmp;
+ GError *error = NULL;
+ GUdevDevice *native;
+ GUdevDevice *parent = NULL;
+ GUdevDevice *receiver = NULL;
+ UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device);
+ GUdevClient *client = NULL;
+ GList *hidraw_list = NULL;
+ GList *l;
+
+ native = G_UDEV_DEVICE (up_device_get_native (device));
+
+ /* check if we have the right device */
+ type = g_udev_device_get_property (native, "UPOWER_BATTERY_TYPE");
+ if (type == NULL)
+ goto out;
+ if (g_strcmp0 (type, "unifying") != 0)
+ goto out;
+
+ /* get the device index */
+ unifying->priv->hidpp_device = hidpp_device_new ();
+ bus_address = g_udev_device_get_property (native, "HID_PHYS");
+ tmp = g_strrstr (bus_address, ":");
+ if (tmp == NULL) {
+ g_debug ("Could not get physical device index");
+ goto out;
+ }
+ hidpp_device_set_index (unifying->priv->hidpp_device,
+ g_ascii_strtoull (tmp + 1, &endptr, 10));
+ if (endptr != NULL && endptr[0] != '\0') {
+ g_debug ("HID_PHYS malformed: '%s'", bus_address);
+ goto out;
+ }
+
+ /* find the hidraw device that matches the parent */
+ parent = g_udev_device_get_parent (native);
+ client = g_udev_client_new (NULL);
+ hidraw_list = g_udev_client_query_by_subsystem (client, "hidraw");
+ for (l = hidraw_list; l != NULL; l = l->next) {
+ if (g_strcmp0 (g_udev_device_get_sysfs_path (parent),
+ g_udev_device_get_sysfs_attr (l->data, "device")) == 0) {
+ receiver = g_object_ref (l->data);
+ break;
+ }
+ }
+ if (receiver == NULL) {
+ g_debug ("Unable to find an hidraw device for Unifying receiver");
+ return FALSE;
+ }
+
+ /* connect to the receiver */
+ device_file = g_udev_device_get_device_file (receiver);
+ if (device_file == NULL) {
+ g_debug ("Could not get device file for Unifying receiver device");
+ goto out;
+ }
+ g_debug ("Using Unifying receiver hidraw device file: %s", device_file);
+ hidpp_device_set_hidraw_device (unifying->priv->hidpp_device,
+ device_file);
+
+ /* coldplug initial parameters */
+ ret = hidpp_device_refresh (unifying->priv->hidpp_device,
+ HIDPP_REFRESH_FLAGS_VERSION |
+ HIDPP_REFRESH_FLAGS_KIND |
+ HIDPP_REFRESH_FLAGS_MODEL,
+ &error);
+ if (!ret) {
+ g_warning ("failed to coldplug unifying device: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* set some default values */
+ g_object_set (device,
+ "vendor", g_udev_device_get_property (native, "ID_VENDOR"),
+ "type", up_device_unifying_get_device_kind (unifying),
+ "model", hidpp_device_get_model (unifying->priv->hidpp_device),
+ "has-history", TRUE,
+ "is-rechargeable", TRUE,
+ "power-supply", FALSE,
+ NULL);
+
+ /* set up a poll to send the magic packet */
+ up_device_unifying_refresh (device);
+ unifying->priv->poll_timer_id = g_timeout_add_seconds (UP_DEVICE_UNIFYING_REFRESH_TIMEOUT,
+ (GSourceFunc) up_device_unifying_refresh,
+ device);
+ ret = TRUE;
+out:
+ g_list_foreach (hidraw_list, (GFunc) g_object_unref, NULL);
+ g_list_free (hidraw_list);
+ if (parent != NULL)
+ g_object_unref (parent);
+ if (receiver != NULL)
+ g_object_unref (receiver);
+ if (client != NULL)
+ g_object_unref (client);
+ return ret;
+}
+
+/**
+ * up_device_unifying_init:
+ **/
+static void
+up_device_unifying_init (UpDeviceUnifying *unifying)
+{
+ unifying->priv = UP_DEVICE_UNIFYING_GET_PRIVATE (unifying);
+ unifying->priv->poll_timer_id = 0;
+}
+
+/**
+ * up_device_unifying_finalize:
+ **/
+static void
+up_device_unifying_finalize (GObject *object)
+{
+ UpDeviceUnifying *unifying;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (UP_IS_DEVICE_UNIFYING (object));
+
+ unifying = UP_DEVICE_UNIFYING (object);
+ g_return_if_fail (unifying->priv != NULL);
+
+ if (unifying->priv->poll_timer_id > 0)
+ g_source_remove (unifying->priv->poll_timer_id);
+ if (unifying->priv->hidpp_device != NULL)
+ g_object_unref (unifying->priv->hidpp_device);
+
+ G_OBJECT_CLASS (up_device_unifying_parent_class)->finalize (object);
+}
+
+/**
+ * up_device_unifying_class_init:
+ **/
+static void
+up_device_unifying_class_init (UpDeviceUnifyingClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ UpDeviceClass *device_class = UP_DEVICE_CLASS (klass);
+
+ object_class->finalize = up_device_unifying_finalize;
+ device_class->coldplug = up_device_unifying_coldplug;
+ device_class->refresh = up_device_unifying_refresh;
+
+ g_type_class_add_private (klass, sizeof (UpDeviceUnifyingPrivate));
+}
+
+/**
+ * up_device_unifying_new:
+ **/
+UpDeviceUnifying *
+up_device_unifying_new (void)
+{
+ return g_object_new (UP_TYPE_DEVICE_UNIFYING, NULL);
+}
diff --git a/src/linux/up-device-lg-unifying.h b/src/linux/up-device-unifying.h
index d1debf3..d1debf3 100644
--- a/src/linux/up-device-lg-unifying.h
+++ b/src/linux/up-device-unifying.h