From b1f12feb1fd4535255f04c91bef90ae11ce57311 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 9 Jan 2013 10:28:58 +0000 Subject: Factor out the Logitech Unifying support to support other devices --- rules/95-upower-csr.rules | 5 + src/linux/Makefile.am | 18 +- src/linux/hidpp-device.c | 815 ++++++++++++++++++++++++++++++++++++++ src/linux/hidpp-device.h | 94 +++++ src/linux/hidpp-test.c | 79 ++++ src/linux/up-backend.c | 28 +- src/linux/up-device-lg-unifying.c | 775 ------------------------------------ src/linux/up-device-lg-unifying.h | 55 --- src/linux/up-device-unifying.c | 284 +++++++++++++ src/linux/up-device-unifying.h | 55 +++ 10 files changed, 1363 insertions(+), 845 deletions(-) create mode 100644 src/linux/hidpp-device.c create mode 100644 src/linux/hidpp-device.h create mode 100644 src/linux/hidpp-test.c delete mode 100644 src/linux/up-device-lg-unifying.c delete mode 100644 src/linux/up-device-lg-unifying.h create mode 100644 src/linux/up-device-unifying.c create mode 100644 src/linux/up-device-unifying.h 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 + * Copyright (C) 2012 Richard Hughes + * + * 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 +#include +#include +#include +#include +#include + +#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 + * Copyright (C) 2012 Richard Hughes + * + * 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 + +#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 + * + * 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 +#include + +#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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#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-lg-unifying.h b/src/linux/up-device-lg-unifying.h deleted file mode 100644 index d1debf3..0000000 --- a/src/linux/up-device-lg-unifying.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- - * - * Copyright (C) 2012 Julien Danjou - * - * 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_UNIFYING_H__ -#define __UP_DEVICE_UNIFYING_H__ - -#include -#include "up-device.h" - -G_BEGIN_DECLS - -#define UP_TYPE_DEVICE_UNIFYING (up_device_unifying_get_type ()) -#define UP_DEVICE_UNIFYING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifying)) -#define UP_DEVICE_UNIFYING_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingClass)) -#define UP_IS_DEVICE_UNIFYING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UP_TYPE_DEVICE_UNIFYING)) -#define UP_IS_DEVICE_UNIFYING_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UP_TYPE_DEVICE_UNIFYING)) -#define UP_DEVICE_UNIFYING_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingClass)) - -typedef struct UpDeviceUnifyingPrivate UpDeviceUnifyingPrivate; - -typedef struct -{ - UpDevice parent; - UpDeviceUnifyingPrivate *priv; -} UpDeviceUnifying; - -typedef struct -{ - UpDeviceClass parent_class; -} UpDeviceUnifyingClass; - -GType up_device_unifying_get_type (void); -UpDeviceUnifying *up_device_unifying_new (void); - -G_END_DECLS - -#endif /* __UP_DEVICE_UNIFYING_H__ */ - 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 + * + * 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 +#include + +#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-unifying.h b/src/linux/up-device-unifying.h new file mode 100644 index 0000000..d1debf3 --- /dev/null +++ b/src/linux/up-device-unifying.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Julien Danjou + * + * 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_UNIFYING_H__ +#define __UP_DEVICE_UNIFYING_H__ + +#include +#include "up-device.h" + +G_BEGIN_DECLS + +#define UP_TYPE_DEVICE_UNIFYING (up_device_unifying_get_type ()) +#define UP_DEVICE_UNIFYING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifying)) +#define UP_DEVICE_UNIFYING_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingClass)) +#define UP_IS_DEVICE_UNIFYING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UP_TYPE_DEVICE_UNIFYING)) +#define UP_IS_DEVICE_UNIFYING_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UP_TYPE_DEVICE_UNIFYING)) +#define UP_DEVICE_UNIFYING_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingClass)) + +typedef struct UpDeviceUnifyingPrivate UpDeviceUnifyingPrivate; + +typedef struct +{ + UpDevice parent; + UpDeviceUnifyingPrivate *priv; +} UpDeviceUnifying; + +typedef struct +{ + UpDeviceClass parent_class; +} UpDeviceUnifyingClass; + +GType up_device_unifying_get_type (void); +UpDeviceUnifying *up_device_unifying_new (void); + +G_END_DECLS + +#endif /* __UP_DEVICE_UNIFYING_H__ */ + -- cgit v1.2.3