Factor out the Logitech Unifying support to support other devices

This commit is contained in:
Richard Hughes 2013-01-09 10:28:58 +00:00
parent b188a49a9c
commit b1f12feb1f
9 changed files with 1308 additions and 790 deletions

View file

@ -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"

View file

@ -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

815
src/linux/hidpp-device.c Normal file
View file

@ -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);
}

94
src/linux/hidpp-device.h Normal file
View file

@ -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__ */

79
src/linux/hidpp-test.c Normal file
View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}