diff options
author | Richard Hughes <richard@hughsie.com> | 2013-01-09 10:28:58 +0000 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2013-01-10 12:08:46 +0000 |
commit | b1f12feb1fd4535255f04c91bef90ae11ce57311 (patch) | |
tree | 2d99025ec329b8e295a8c42c9ab15d54f28090bf | |
parent | b188a49a9c9a2521429552c644151ae4a3acdfc6 (diff) |
Factor out the Logitech Unifying support to support other devices
-rw-r--r-- | rules/95-upower-csr.rules | 5 | ||||
-rw-r--r-- | src/linux/Makefile.am | 18 | ||||
-rw-r--r-- | src/linux/hidpp-device.c | 815 | ||||
-rw-r--r-- | src/linux/hidpp-device.h | 94 | ||||
-rw-r--r-- | src/linux/hidpp-test.c | 79 | ||||
-rw-r--r-- | src/linux/up-backend.c | 28 | ||||
-rw-r--r-- | src/linux/up-device-lg-unifying.c | 775 | ||||
-rw-r--r-- | src/linux/up-device-unifying.c | 284 | ||||
-rw-r--r-- | src/linux/up-device-unifying.h (renamed from src/linux/up-device-lg-unifying.h) | 0 |
9 files changed, 1308 insertions, 790 deletions
diff --git a/rules/95-upower-csr.rules b/rules/95-upower-csr.rules index 17cb36f..bd171b2 100644 --- a/rules/95-upower-csr.rules +++ b/rules/95-upower-csr.rules | |||
@@ -20,3 +20,8 @@ ATTR{idVendor}=="046d", ATTR{idProduct}=="c702", ENV{UPOWER_PRODUCT}="Presenter" | |||
20 | 20 | ||
21 | LABEL="up_csr_end" | 21 | LABEL="up_csr_end" |
22 | 22 | ||
23 | # Unifying HID++ devices | ||
24 | SUBSYSTEM!="hid", GOTO="up_unifying_end" | ||
25 | ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", DRIVER=="logitech-djdevice", ENV{UPOWER_BATTERY_TYPE}="unifying" | ||
26 | ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c532", DRIVER=="logitech-djdevice", ENV{UPOWER_BATTERY_TYPE}="unifying" | ||
27 | 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 = \ | |||
30 | up-device-supply.h \ | 30 | up-device-supply.h \ |
31 | up-device-csr.c \ | 31 | up-device-csr.c \ |
32 | up-device-csr.h \ | 32 | up-device-csr.h \ |
33 | up-device-lg-unifying.c \ | 33 | up-device-unifying.c \ |
34 | up-device-lg-unifying.h \ | 34 | up-device-unifying.h \ |
35 | up-device-hid.c \ | 35 | up-device-hid.c \ |
36 | up-device-hid.h \ | 36 | up-device-hid.h \ |
37 | up-device-wup.c \ | 37 | up-device-wup.c \ |
@@ -42,11 +42,25 @@ libupshared_la_SOURCES = \ | |||
42 | up-dock.h \ | 42 | up-dock.h \ |
43 | up-backend.c \ | 43 | up-backend.c \ |
44 | up-native.c \ | 44 | up-native.c \ |
45 | hidpp-device.c \ | ||
46 | hidpp-device.h \ | ||
45 | sysfs-utils.c \ | 47 | sysfs-utils.c \ |
46 | sysfs-utils.h \ | 48 | sysfs-utils.h \ |
47 | $(idevice_files) \ | 49 | $(idevice_files) \ |
48 | $(BUILT_SOURCES) | 50 | $(BUILT_SOURCES) |
49 | 51 | ||
52 | noinst_PROGRAMS = \ | ||
53 | hidpp-test | ||
54 | hidpp_test_SOURCES = \ | ||
55 | hidpp-device.c \ | ||
56 | hidpp-device.h \ | ||
57 | hidpp-test.c | ||
58 | hidpp_test_LDADD = \ | ||
59 | -lm \ | ||
60 | $(GLIB_LIBS) \ | ||
61 | $(GIO_LIBS) | ||
62 | hidpp_test_CFLAGS = $(AM_CFLAGS) $(WARNINGFLAGS_C) | ||
63 | |||
50 | EXTRA_DIST = $(libupshared_la_SOURCES) \ | 64 | EXTRA_DIST = $(libupshared_la_SOURCES) \ |
51 | integration-test | 65 | integration-test |
52 | 66 | ||
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 @@ | |||
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | ||
2 | * | ||
3 | * Copyright (C) 2012 Julien Danjou <julien@danjou.info> | ||
4 | * Copyright (C) 2012 Richard Hughes <richard@hughsie.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifdef HAVE_CONFIG_H | ||
23 | # include "config.h" | ||
24 | #endif | ||
25 | |||
26 | #include <fcntl.h> | ||
27 | #include <glib-object.h> | ||
28 | #include <string.h> | ||
29 | #include <sys/stat.h> | ||
30 | #include <sys/types.h> | ||
31 | #include <unistd.h> | ||
32 | |||
33 | #include "hidpp-device.h" | ||
34 | |||
35 | /* Arbitrary value used in ping */ | ||
36 | #define HIDPP_PING_DATA 0x42 | ||
37 | |||
38 | #define HIDPP_RECEIVER_ADDRESS 0xff | ||
39 | |||
40 | #define HIDPP_RESPONSE_SHORT_LENGTH 7 | ||
41 | #define HIDPP_RESPONSE_LONG_LENGTH 20 | ||
42 | |||
43 | #define HIDPP_HEADER_REQUEST 0x10 | ||
44 | #define HIDPP_HEADER_RESPONSE 0x11 | ||
45 | |||
46 | /* HID++ 1.0 */ | ||
47 | #define HIDPP_READ_SHORT_REGISTER 0x81 | ||
48 | #define HIDPP_READ_SHORT_REGISTER_BATTERY 0x0d | ||
49 | |||
50 | #define HIDPP_READ_LONG_REGISTER 0x83 | ||
51 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE 11 | ||
52 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD 0x1 | ||
53 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE 0x2 | ||
54 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD 0x3 | ||
55 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER 0x4 | ||
56 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL 0x7 | ||
57 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL 0x8 | ||
58 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD 0x9 | ||
59 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET 0xa | ||
60 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD 0xb | ||
61 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK 0xc | ||
62 | |||
63 | #define HIDPP_ERR_INVALID_SUBID 0x8f | ||
64 | |||
65 | /* HID++ 2.0 */ | ||
66 | |||
67 | /* HID++2.0 error codes */ | ||
68 | #define HIDPP_ERROR_CODE_NOERROR 0x00 | ||
69 | #define HIDPP_ERROR_CODE_UNKNOWN 0x01 | ||
70 | #define HIDPP_ERROR_CODE_INVALIDARGUMENT 0x02 | ||
71 | #define HIDPP_ERROR_CODE_OUTOFRANGE 0x03 | ||
72 | #define HIDPP_ERROR_CODE_HWERROR 0x04 | ||
73 | #define HIDPP_ERROR_CODE_LOGITECH_INTERNAL 0x05 | ||
74 | #define HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX 0x06 | ||
75 | #define HIDPP_ERROR_CODE_INVALID_FUNCTION_ID 0x07 | ||
76 | #define HIDPP_ERROR_CODE_BUSY 0x08 | ||
77 | #define HIDPP_ERROR_CODE_UNSUPPORTED 0x09 | ||
78 | |||
79 | #define HIDPP_FEATURE_ROOT 0x0000 | ||
80 | #define HIDPP_FEATURE_ROOT_INDEX 0x00 | ||
81 | #define HIDPP_FEATURE_ROOT_FN_GET_FEATURE (0x00 << 4) | ||
82 | #define HIDPP_FEATURE_ROOT_FN_PING (0x01 << 4) | ||
83 | #define HIDPP_FEATURE_I_FEATURE_SET 0x0001 | ||
84 | #define HIDPP_FEATURE_I_FEATURE_SET_FN_GET_COUNT (0x00 << 4) | ||
85 | #define HIDPP_FEATURE_I_FEATURE_SET_FN_GET_FEATURE_ID (0x01 << 4) | ||
86 | #define HIDPP_FEATURE_I_FIRMWARE_INFO 0x0003 | ||
87 | #define HIDPP_FEATURE_I_FIRMWARE_INFO_FN_GET_COUNT (0x00 << 4) | ||
88 | #define HIDPP_FEATURE_I_FIRMWARE_INFO_FN_GET_INFO (0x01 << 4) | ||
89 | #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE 0x0005 | ||
90 | #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_COUNT (0x00 << 4) | ||
91 | #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_NAME (0x01 << 4) | ||
92 | #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_TYPE (0x02 << 4) | ||
93 | #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS 0x1000 | ||
94 | //#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS (0x00 << 4) | ||
95 | //#define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_BE (0x01 << 4) | ||
96 | #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_CAPABILITY (0x02 << 4) | ||
97 | |||
98 | #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS 0x02 | ||
99 | #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS_BE 0x02 | ||
100 | |||
101 | #define HIDPP_FEATURE_SPECIAL_KEYS_MSE_BUTTONS 0x1B00 | ||
102 | #define HIDPP_FEATURE_WIRELESS_DEVICE_STATUS 0x1D4B | ||
103 | #define HIDPP_FEATURE_WIRELESS_DEVICE_STATUS_BE (0x00 << 4) | ||
104 | |||
105 | #define HIDPP_FEATURE_SOLAR_DASHBOARD 0x4301 | ||
106 | #define HIDPP_FEATURE_SOLAR_DASHBOARD_FN_SET_LIGHT_MEASURE (0x00 << 4) | ||
107 | #define HIDPP_FEATURE_SOLAR_DASHBOARD_BE_BATTERY_LEVEL_STATUS (0x01 << 4) | ||
108 | |||
109 | #define HIDPP_DEVICE_READ_RESPONSE_TIMEOUT 3000 /* miliseconds */ | ||
110 | |||
111 | struct HidppDevicePrivate | ||
112 | { | ||
113 | gboolean enable_debug; | ||
114 | gchar *hidraw_device; | ||
115 | gchar *model; | ||
116 | GIOChannel *channel; | ||
117 | GPtrArray *feature_index; | ||
118 | guint batt_percentage; | ||
119 | guint channel_source_id; | ||
120 | guint device_idx; | ||
121 | guint version; | ||
122 | HidppDeviceBattStatus batt_status; | ||
123 | HidppDeviceKind kind; | ||
124 | int fd; | ||
125 | }; | ||
126 | |||
127 | typedef struct { | ||
128 | gint idx; | ||
129 | guint16 feature; | ||
130 | gchar *name; | ||
131 | } HidppDeviceMap; | ||
132 | |||
133 | G_DEFINE_TYPE (HidppDevice, hidpp_device, G_TYPE_OBJECT) | ||
134 | #define HIDPP_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), HIDPP_TYPE_DEVICE, HidppDevicePrivate)) | ||
135 | |||
136 | /** | ||
137 | * hidpp_device_map_print: | ||
138 | **/ | ||
139 | static void | ||
140 | hidpp_device_map_print (HidppDevice *device) | ||
141 | { | ||
142 | guint i; | ||
143 | HidppDeviceMap *map; | ||
144 | HidppDevicePrivate *priv = device->priv; | ||
145 | |||
146 | if (!device->priv->enable_debug) | ||
147 | return; | ||
148 | for (i = 0; i < priv->feature_index->len; i++) { | ||
149 | map = g_ptr_array_index (priv->feature_index, i); | ||
150 | g_print ("%02x\t%s [%i]\n", map->idx, map->name, map->feature); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * hidpp_device_map_get_by_feature: | ||
156 | * | ||
157 | * Gets the cached index from the function number. | ||
158 | **/ | ||
159 | static const HidppDeviceMap * | ||
160 | hidpp_device_map_get_by_feature (HidppDevice *device, guint16 feature) | ||
161 | { | ||
162 | guint i; | ||
163 | HidppDeviceMap *map; | ||
164 | HidppDevicePrivate *priv = device->priv; | ||
165 | |||
166 | for (i = 0; i < priv->feature_index->len; i++) { | ||
167 | map = g_ptr_array_index (priv->feature_index, i); | ||
168 | if (map->feature == feature) | ||
169 | return map; | ||
170 | } | ||
171 | return NULL; | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * hidpp_device_map_get_by_idx: | ||
176 | * | ||
177 | * Gets the cached index from the function index. | ||
178 | **/ | ||
179 | static const HidppDeviceMap * | ||
180 | hidpp_device_map_get_by_idx (HidppDevice *device, gint idx) | ||
181 | { | ||
182 | guint i; | ||
183 | HidppDeviceMap *map; | ||
184 | HidppDevicePrivate *priv = device->priv; | ||
185 | |||
186 | for (i = 0; i < priv->feature_index->len; i++) { | ||
187 | map = g_ptr_array_index (priv->feature_index, i); | ||
188 | if (map->idx == idx) | ||
189 | return map; | ||
190 | } | ||
191 | return NULL; | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * hidpp_device_print_buffer: | ||
196 | * | ||
197 | * Pretty print the send/recieve buffer. | ||
198 | **/ | ||
199 | static void | ||
200 | hidpp_device_print_buffer (HidppDevice *device, const guint8 *buffer) | ||
201 | { | ||
202 | guint i; | ||
203 | const HidppDeviceMap *map; | ||
204 | |||
205 | if (!device->priv->enable_debug) | ||
206 | return; | ||
207 | for (i = 0; i < HIDPP_RESPONSE_LONG_LENGTH; i++) | ||
208 | g_print ("%02x ", buffer[i]); | ||
209 | g_print ("\n"); | ||
210 | |||
211 | /* direction */ | ||
212 | if (buffer[0] == HIDPP_HEADER_REQUEST) | ||
213 | g_print ("REQUEST\n"); | ||
214 | else if (buffer[0] == HIDPP_HEADER_RESPONSE) | ||
215 | g_print ("RESPONSE\n"); | ||
216 | else | ||
217 | g_print ("??\n"); | ||
218 | |||
219 | /* dev index */ | ||
220 | g_print ("device-idx=%02x ", buffer[1]); | ||
221 | if (buffer[1] == HIDPP_RECEIVER_ADDRESS) { | ||
222 | g_print ("[Receiver]\n"); | ||
223 | } else if (device->priv->device_idx == buffer[1]) { | ||
224 | g_print ("[This Device]\n"); | ||
225 | } else { | ||
226 | g_print ("[Random Device]\n"); | ||
227 | } | ||
228 | |||
229 | /* feature index */ | ||
230 | if (buffer[2] == HIDPP_READ_LONG_REGISTER) { | ||
231 | g_print ("feature-idx=%s [%02x]\n", | ||
232 | "v1(ReadLongRegister)", buffer[2]); | ||
233 | } else { | ||
234 | map = hidpp_device_map_get_by_idx (device, buffer[2]); | ||
235 | g_print ("feature-idx=v2(%s) [%02x]\n", | ||
236 | map != NULL ? map->name : "unknown", buffer[2]); | ||
237 | } | ||
238 | |||
239 | g_print ("function-id=%01x\n", buffer[3] & 0xf); | ||
240 | g_print ("software-id=%01x\n", buffer[3] >> 4); | ||
241 | g_print ("param[0]=%02x\n\n", buffer[4]); | ||
242 | } | ||
243 | |||
244 | /** | ||
245 | * hidpp_device_cmd: | ||
246 | **/ | ||
247 | static gboolean | ||
248 | hidpp_device_cmd (HidppDevice *device, | ||
249 | guint8 device_idx, | ||
250 | guint8 feature_idx, | ||
251 | guint8 function_idx, | ||
252 | guint8 *request_data, | ||
253 | gsize request_len, | ||
254 | guint8 *response_data, | ||
255 | gsize response_len, | ||
256 | GError **error) | ||
257 | { | ||
258 | gboolean ret = TRUE; | ||
259 | gssize wrote; | ||
260 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
261 | guint i; | ||
262 | HidppDevicePrivate *priv = device->priv; | ||
263 | GPollFD poll[] = { | ||
264 | { | ||
265 | .fd = priv->fd, | ||
266 | .events = G_IO_IN | G_IO_OUT | G_IO_ERR, | ||
267 | }, | ||
268 | }; | ||
269 | |||
270 | /* make the request packet */ | ||
271 | memset (buf, 0x00, HIDPP_RESPONSE_LONG_LENGTH); | ||
272 | buf[0] = HIDPP_HEADER_REQUEST; | ||
273 | buf[1] = device_idx; | ||
274 | buf[2] = feature_idx; | ||
275 | buf[3] = function_idx; | ||
276 | for (i = 0; i < request_len; i++) | ||
277 | buf[4 + i] = request_data[i]; | ||
278 | |||
279 | /* write to the device */ | ||
280 | hidpp_device_print_buffer (device, buf); | ||
281 | wrote = write (priv->fd, buf, 4 + request_len); | ||
282 | if ((gsize) wrote != 4 + request_len) { | ||
283 | g_set_error (error, 1, 0, | ||
284 | "Unable to write request to device: %" G_GSIZE_FORMAT, | ||
285 | wrote); | ||
286 | ret = FALSE; | ||
287 | goto out; | ||
288 | } | ||
289 | |||
290 | /* read from the device */ | ||
291 | wrote = g_poll (poll, G_N_ELEMENTS(poll), | ||
292 | HIDPP_DEVICE_READ_RESPONSE_TIMEOUT); | ||
293 | if (wrote <= 0) { | ||
294 | g_set_error (error, 1, 0, | ||
295 | "Attempt to read response from device timed out: %" G_GSIZE_FORMAT, | ||
296 | wrote); | ||
297 | ret = FALSE; | ||
298 | goto out; | ||
299 | } | ||
300 | memset (buf, 0x00, HIDPP_RESPONSE_LONG_LENGTH); | ||
301 | wrote = read (priv->fd, buf, sizeof (buf)); | ||
302 | if (wrote <= 0) { | ||
303 | g_set_error (error, 1, 0, | ||
304 | "Unable to read response from device: %" G_GSIZE_FORMAT, | ||
305 | wrote); | ||
306 | ret = FALSE; | ||
307 | goto out; | ||
308 | } | ||
309 | |||
310 | /* is device offline */ | ||
311 | hidpp_device_print_buffer (device, buf); | ||
312 | if (buf[0] == HIDPP_HEADER_REQUEST && | ||
313 | buf[1] == device_idx && | ||
314 | buf[2] == HIDPP_ERR_INVALID_SUBID && | ||
315 | buf[3] == 0x00 && | ||
316 | buf[4] == HIDPP_FEATURE_ROOT_FN_PING) { | ||
317 | /* HID++ 1.0 ping reply, so fake success */ | ||
318 | if (buf[5] == HIDPP_ERROR_CODE_UNKNOWN) { | ||
319 | buf[0] = 1; | ||
320 | goto out; | ||
321 | } | ||
322 | if (buf[5] == HIDPP_ERROR_CODE_UNSUPPORTED) { | ||
323 | /* device offline / unreachable */ | ||
324 | g_set_error_literal (error, 1, 0, | ||
325 | "device is unreachable"); | ||
326 | ret = FALSE; | ||
327 | goto out; | ||
328 | } | ||
329 | } | ||
330 | if (buf[0] != HIDPP_HEADER_RESPONSE || | ||
331 | buf[1] != device_idx || | ||
332 | buf[2] != feature_idx || | ||
333 | buf[3] != function_idx) { | ||
334 | g_set_error (error, 1, 0, | ||
335 | "invalid response from device: %" G_GSIZE_FORMAT, | ||
336 | wrote); | ||
337 | ret = FALSE; | ||
338 | goto out; | ||
339 | } | ||
340 | for (i = 0; i < response_len; i++) | ||
341 | response_data[i] = buf[4 + i]; | ||
342 | out: | ||
343 | return ret; | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * hidpp_device_map_add: | ||
348 | * | ||
349 | * Requests the index for a function, and adds it to the memeory cache | ||
350 | * if it exists. | ||
351 | **/ | ||
352 | static gboolean | ||
353 | hidpp_device_map_add (HidppDevice *device, | ||
354 | guint16 feature, | ||
355 | const gchar *name) | ||
356 | { | ||
357 | gboolean ret; | ||
358 | GError *error = NULL; | ||
359 | guint8 buf[3]; | ||
360 | HidppDeviceMap *map; | ||
361 | HidppDevicePrivate *priv = device->priv; | ||
362 | |||
363 | buf[0] = feature >> 8; | ||
364 | buf[1] = feature; | ||
365 | buf[2] = 0x00; | ||
366 | |||
367 | g_debug ("Getting idx for feature %s [%02x]", name, feature); | ||
368 | ret = hidpp_device_cmd (device, | ||
369 | priv->device_idx, | ||
370 | HIDPP_FEATURE_ROOT_INDEX, | ||
371 | HIDPP_FEATURE_ROOT_FN_GET_FEATURE, | ||
372 | buf, sizeof (buf), | ||
373 | buf, sizeof (buf), | ||
374 | &error); | ||
375 | if (!ret) { | ||
376 | g_warning ("Failed to get feature idx: %s", error->message); | ||
377 | g_error_free (error); | ||
378 | goto out; | ||
379 | } | ||
380 | |||
381 | /* zero index */ | ||
382 | if (buf[0] == 0x00) { | ||
383 | ret = FALSE; | ||
384 | g_debug ("Feature not found"); | ||
385 | goto out; | ||
386 | } | ||
387 | |||
388 | /* add to map */ | ||
389 | map = g_new0 (HidppDeviceMap, 1); | ||
390 | map->idx = buf[0]; | ||
391 | map->feature = feature; | ||
392 | map->name = g_strdup (name); | ||
393 | g_ptr_array_add (priv->feature_index, map); | ||
394 | g_debug ("Added feature %s [%02x] as idx %02x", | ||
395 | name, feature, map->idx); | ||
396 | out: | ||
397 | return ret; | ||
398 | } | ||
399 | |||
400 | /** | ||
401 | * hidpp_device_get_model: | ||
402 | **/ | ||
403 | const gchar * | ||
404 | hidpp_device_get_model (HidppDevice *device) | ||
405 | { | ||
406 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), NULL); | ||
407 | return device->priv->model; | ||
408 | } | ||
409 | |||
410 | /** | ||
411 | * hidpp_device_get_batt_percentage: | ||
412 | **/ | ||
413 | guint | ||
414 | hidpp_device_get_batt_percentage (HidppDevice *device) | ||
415 | { | ||
416 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), 0); | ||
417 | return device->priv->batt_percentage; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * hidpp_device_get_version: | ||
422 | **/ | ||
423 | guint | ||
424 | hidpp_device_get_version (HidppDevice *device) | ||
425 | { | ||
426 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), 0); | ||
427 | return device->priv->version; | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * hidpp_device_get_batt_status: | ||
432 | **/ | ||
433 | HidppDeviceBattStatus | ||
434 | hidpp_device_get_batt_status (HidppDevice *device) | ||
435 | { | ||
436 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), HIDPP_DEVICE_BATT_STATUS_UNKNOWN); | ||
437 | return device->priv->batt_status; | ||
438 | } | ||
439 | |||
440 | /** | ||
441 | * hidpp_device_get_kind: | ||
442 | **/ | ||
443 | HidppDeviceKind | ||
444 | hidpp_device_get_kind (HidppDevice *device) | ||
445 | { | ||
446 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), HIDPP_DEVICE_KIND_UNKNOWN); | ||
447 | return device->priv->kind; | ||
448 | } | ||
449 | |||
450 | /** | ||
451 | * hidpp_device_set_hidraw_device: | ||
452 | **/ | ||
453 | void | ||
454 | hidpp_device_set_hidraw_device (HidppDevice *device, | ||
455 | const gchar *hidraw_device) | ||
456 | { | ||
457 | g_return_if_fail (HIDPP_IS_DEVICE (device)); | ||
458 | device->priv->hidraw_device = g_strdup (hidraw_device); | ||
459 | } | ||
460 | |||
461 | /** | ||
462 | * hidpp_device_set_index: | ||
463 | **/ | ||
464 | void | ||
465 | hidpp_device_set_index (HidppDevice *device, | ||
466 | guint device_idx) | ||
467 | { | ||
468 | g_return_if_fail (HIDPP_IS_DEVICE (device)); | ||
469 | device->priv->device_idx = device_idx; | ||
470 | } | ||
471 | |||
472 | /** | ||
473 | * hidpp_device_set_enable_debug: | ||
474 | **/ | ||
475 | void | ||
476 | hidpp_device_set_enable_debug (HidppDevice *device, | ||
477 | gboolean enable_debug) | ||
478 | { | ||
479 | g_return_if_fail (HIDPP_IS_DEVICE (device)); | ||
480 | device->priv->enable_debug = enable_debug; | ||
481 | } | ||
482 | |||
483 | /** | ||
484 | * hidpp_device_refresh: | ||
485 | **/ | ||
486 | gboolean | ||
487 | hidpp_device_refresh (HidppDevice *device, | ||
488 | HidppRefreshFlags refresh_flags, | ||
489 | GError **error) | ||
490 | { | ||
491 | const HidppDeviceMap *map; | ||
492 | gboolean ret = TRUE; | ||
493 | GString *name = NULL; | ||
494 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
495 | guint i; | ||
496 | guint len; | ||
497 | HidppDevicePrivate *priv = device->priv; | ||
498 | |||
499 | g_return_val_if_fail (HIDPP_IS_DEVICE (device), FALSE); | ||
500 | |||
501 | /* open the device if it's not already opened */ | ||
502 | if (priv->fd < 0) { | ||
503 | priv->fd = open (device->priv->hidraw_device, O_RDWR | O_NONBLOCK); | ||
504 | if (priv->fd < 0) { | ||
505 | g_set_error (error, 1, 0, | ||
506 | "cannot open device file %s", | ||
507 | priv->hidraw_device); | ||
508 | ret = FALSE; | ||
509 | goto out; | ||
510 | } | ||
511 | |||
512 | /* add features we are going to use */ | ||
513 | // hidpp_device_map_add (device, | ||
514 | // HIDPP_FEATURE_I_FEATURE_SET, | ||
515 | // "IFeatureSet"); | ||
516 | // hidpp_device_map_add (device, | ||
517 | // HIDPP_FEATURE_I_FIRMWARE_INFO, | ||
518 | // "IFirmwareInfo"); | ||
519 | hidpp_device_map_add (device, | ||
520 | HIDPP_FEATURE_GET_DEVICE_NAME_TYPE, | ||
521 | "GetDeviceNameType"); | ||
522 | hidpp_device_map_add (device, | ||
523 | HIDPP_FEATURE_BATTERY_LEVEL_STATUS, | ||
524 | "BatteryLevelStatus"); | ||
525 | // hidpp_device_map_add (device, | ||
526 | // HIDPP_FEATURE_WIRELESS_DEVICE_STATUS, | ||
527 | // "WirelessDeviceStatus"); | ||
528 | hidpp_device_map_add (device, | ||
529 | HIDPP_FEATURE_SOLAR_DASHBOARD, | ||
530 | "SolarDashboard"); | ||
531 | hidpp_device_map_print (device); | ||
532 | } | ||
533 | |||
534 | /* get version */ | ||
535 | if ((refresh_flags & HIDPP_REFRESH_FLAGS_VERSION) > 0) { | ||
536 | buf[0] = 0x00; | ||
537 | buf[1] = 0x00; | ||
538 | buf[2] = HIDPP_PING_DATA; | ||
539 | ret = hidpp_device_cmd (device, | ||
540 | priv->device_idx, | ||
541 | HIDPP_FEATURE_ROOT_INDEX, | ||
542 | HIDPP_FEATURE_ROOT_FN_PING, | ||
543 | buf, 3, | ||
544 | buf, 4, | ||
545 | error); | ||
546 | if (!ret) | ||
547 | goto out; | ||
548 | priv->version = buf[0]; | ||
549 | } | ||
550 | |||
551 | /* get device kind */ | ||
552 | if ((refresh_flags & HIDPP_REFRESH_FLAGS_KIND) > 0) { | ||
553 | |||
554 | if (priv->version == 1) { | ||
555 | buf[0] = 0x20 | (priv->device_idx - 1); | ||
556 | buf[1] = 0x00; | ||
557 | buf[2] = 0x00; | ||
558 | ret = hidpp_device_cmd (device, | ||
559 | HIDPP_RECEIVER_ADDRESS, | ||
560 | HIDPP_READ_LONG_REGISTER, | ||
561 | 0xb5, | ||
562 | buf, 3, | ||
563 | buf, 7, | ||
564 | error); | ||
565 | if (!ret) | ||
566 | goto out; | ||
567 | switch (buf[7]) { | ||
568 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD: | ||
569 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD: | ||
570 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL: | ||
571 | priv->kind = HIDPP_DEVICE_KIND_KEYBOARD; | ||
572 | break; | ||
573 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE: | ||
574 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL: | ||
575 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD: | ||
576 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER: | ||
577 | priv->kind = HIDPP_DEVICE_KIND_MOUSE; | ||
578 | break; | ||
579 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET: | ||
580 | priv->kind = HIDPP_DEVICE_KIND_TABLET; | ||
581 | break; | ||
582 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD: | ||
583 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK: | ||
584 | /* upower doesn't have something for this yet */ | ||
585 | priv->kind = HIDPP_DEVICE_KIND_UNKNOWN; | ||
586 | break; | ||
587 | } | ||
588 | } else if (priv->version == 2) { | ||
589 | |||
590 | /* send a BatteryLevelStatus report */ | ||
591 | map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); | ||
592 | if (map != NULL) { | ||
593 | buf[0] = 0x00; | ||
594 | buf[1] = 0x00; | ||
595 | buf[2] = 0x00; | ||
596 | ret = hidpp_device_cmd (device, | ||
597 | priv->device_idx, | ||
598 | map->idx, | ||
599 | HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_TYPE, | ||
600 | buf, 3, | ||
601 | buf, 1, | ||
602 | error); | ||
603 | if (!ret) | ||
604 | goto out; | ||
605 | switch (buf[0]) { | ||
606 | case 0: /* keyboard */ | ||
607 | case 2: /* numpad */ | ||
608 | priv->kind = HIDPP_DEVICE_KIND_KEYBOARD; | ||
609 | break; | ||
610 | case 3: /* mouse */ | ||
611 | case 4: /* touchpad */ | ||
612 | case 5: /* trackball */ | ||
613 | priv->kind = HIDPP_DEVICE_KIND_MOUSE; | ||
614 | break; | ||
615 | case 1: /* remote-control */ | ||
616 | case 6: /* presenter */ | ||
617 | case 7: /* receiver */ | ||
618 | priv->kind = HIDPP_DEVICE_KIND_UNKNOWN; | ||
619 | break; | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | } | ||
624 | |||
625 | /* get device model string */ | ||
626 | if ((refresh_flags & HIDPP_REFRESH_FLAGS_MODEL) > 0) { | ||
627 | buf[0] = 0x00; | ||
628 | buf[1] = 0x00; | ||
629 | buf[2] = 0x00; | ||
630 | map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); | ||
631 | if (map != NULL) { | ||
632 | ret = hidpp_device_cmd (device, | ||
633 | priv->device_idx, | ||
634 | map->idx, | ||
635 | HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_COUNT, | ||
636 | buf, 3, | ||
637 | buf, 1, | ||
638 | error); | ||
639 | if (!ret) | ||
640 | goto out; | ||
641 | } | ||
642 | len = buf[0]; | ||
643 | name = g_string_new (""); | ||
644 | for (i = 0; i < len; i +=4 ) { | ||
645 | buf[0] = i; | ||
646 | buf[1] = 0x00; | ||
647 | buf[2] = 0x00; | ||
648 | ret = hidpp_device_cmd (device, | ||
649 | priv->device_idx, | ||
650 | map->idx, | ||
651 | HIDPP_FEATURE_GET_DEVICE_NAME_TYPE_FN_GET_NAME, | ||
652 | buf, 3, | ||
653 | buf, 4, | ||
654 | error); | ||
655 | if (!ret) | ||
656 | goto out; | ||
657 | g_string_append_len (name, (gchar *) &buf[0], 4); | ||
658 | } | ||
659 | priv->model = g_strdup (name->str); | ||
660 | } | ||
661 | |||
662 | /* get battery status */ | ||
663 | if ((refresh_flags & HIDPP_REFRESH_FLAGS_BATTERY) > 0) { | ||
664 | if (priv->version == 1) { | ||
665 | buf[0] = HIDPP_READ_SHORT_REGISTER; | ||
666 | buf[1] = HIDPP_READ_SHORT_REGISTER_BATTERY; | ||
667 | buf[2] = 0x00; | ||
668 | buf[3] = 0x00; | ||
669 | buf[4] = 0x00; | ||
670 | ret = hidpp_device_cmd (device, | ||
671 | priv->device_idx, | ||
672 | HIDPP_FEATURE_ROOT_INDEX, | ||
673 | HIDPP_FEATURE_ROOT_FN_PING, | ||
674 | buf, 5, | ||
675 | buf, 1, | ||
676 | error); | ||
677 | if (!ret) | ||
678 | goto out; | ||
679 | priv->batt_percentage = buf[0]; | ||
680 | priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING; | ||
681 | } else if (priv->version == 2) { | ||
682 | |||
683 | /* sent a SetLightMeasure report */ | ||
684 | map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_SOLAR_DASHBOARD); | ||
685 | if (map != NULL) { | ||
686 | buf[0] = 0x01; /* Max number of reports: number of report sent after function call */ | ||
687 | buf[1] = 0x01; /* Report period: time between reports, in seconds */ | ||
688 | ret = hidpp_device_cmd (device, | ||
689 | priv->device_idx, | ||
690 | map->idx, | ||
691 | HIDPP_FEATURE_SOLAR_DASHBOARD_FN_SET_LIGHT_MEASURE, | ||
692 | buf, 2, | ||
693 | buf, 3, | ||
694 | error); | ||
695 | if (!ret) | ||
696 | goto out; | ||
697 | priv->batt_percentage = buf[0]; | ||
698 | priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING; | ||
699 | } | ||
700 | |||
701 | /* send a BatteryLevelStatus report */ | ||
702 | map = hidpp_device_map_get_by_feature (device, HIDPP_FEATURE_BATTERY_LEVEL_STATUS); | ||
703 | if (map != NULL) { | ||
704 | buf[0] = 0x00; | ||
705 | buf[1] = 0x00; | ||
706 | buf[2] = 0x00; | ||
707 | ret = hidpp_device_cmd (device, | ||
708 | priv->device_idx, | ||
709 | map->idx, | ||
710 | HIDPP_FEATURE_BATTERY_LEVEL_STATUS_FN_GET_STATUS, | ||
711 | buf, 3, | ||
712 | buf, 3, | ||
713 | error); | ||
714 | if (!ret) | ||
715 | goto out; | ||
716 | |||
717 | /* convert the HID++ v2 status into something | ||
718 | * we can set on the device */ | ||
719 | switch (buf[2]) { | ||
720 | case 0: /* discharging */ | ||
721 | priv->batt_status = HIDPP_DEVICE_BATT_STATUS_DISCHARGING; | ||
722 | break; | ||
723 | case 1: /* recharging */ | ||
724 | case 2: /* charge nearly complete */ | ||
725 | case 4: /* charging slowly */ | ||
726 | priv->batt_status = HIDPP_DEVICE_BATT_STATUS_CHARGING; | ||
727 | break; | ||
728 | case 3: /* charging complete */ | ||
729 | priv->batt_status = HIDPP_DEVICE_BATT_STATUS_CHARGED; | ||
730 | break; | ||
731 | default: | ||
732 | break; | ||
733 | } | ||
734 | priv->batt_percentage = buf[0]; | ||
735 | g_debug ("level=%i%%, next-level=%i%%, battery-status=%i", | ||
736 | buf[0], buf[1], buf[2]); | ||
737 | } | ||
738 | } | ||
739 | } | ||
740 | out: | ||
741 | if (name != NULL) | ||
742 | g_string_free (name, TRUE); | ||
743 | return ret; | ||
744 | } | ||
745 | |||
746 | /** | ||
747 | * hidpp_device_init: | ||
748 | **/ | ||
749 | static void | ||
750 | hidpp_device_init (HidppDevice *device) | ||
751 | { | ||
752 | HidppDeviceMap *map; | ||
753 | |||
754 | device->priv = HIDPP_DEVICE_GET_PRIVATE (device); | ||
755 | device->priv->fd = -1; | ||
756 | device->priv->feature_index = g_ptr_array_new_with_free_func (g_free); | ||
757 | device->priv->batt_status = HIDPP_DEVICE_BATT_STATUS_UNKNOWN; | ||
758 | device->priv->kind = HIDPP_DEVICE_KIND_UNKNOWN; | ||
759 | |||
760 | /* add known root */ | ||
761 | map = g_new0 (HidppDeviceMap, 1); | ||
762 | map->idx = HIDPP_FEATURE_ROOT_INDEX; | ||
763 | map->feature = HIDPP_FEATURE_ROOT; | ||
764 | map->name = g_strdup ("Root"); | ||
765 | g_ptr_array_add (device->priv->feature_index, map); | ||
766 | } | ||
767 | |||
768 | /** | ||
769 | * hidpp_device_finalize: | ||
770 | **/ | ||
771 | static void | ||
772 | hidpp_device_finalize (GObject *object) | ||
773 | { | ||
774 | HidppDevice *device; | ||
775 | |||
776 | g_return_if_fail (object != NULL); | ||
777 | g_return_if_fail (HIDPP_IS_DEVICE (object)); | ||
778 | |||
779 | device = HIDPP_DEVICE (object); | ||
780 | g_return_if_fail (device->priv != NULL); | ||
781 | |||
782 | if (device->priv->channel_source_id > 0) | ||
783 | g_source_remove (device->priv->channel_source_id); | ||
784 | |||
785 | if (device->priv->channel) { | ||
786 | g_io_channel_shutdown (device->priv->channel, FALSE, NULL); | ||
787 | g_io_channel_unref (device->priv->channel); | ||
788 | } | ||
789 | g_ptr_array_unref (device->priv->feature_index); | ||
790 | |||
791 | g_free (device->priv->hidraw_device); | ||
792 | g_free (device->priv->model); | ||
793 | |||
794 | G_OBJECT_CLASS (hidpp_device_parent_class)->finalize (object); | ||
795 | } | ||
796 | |||
797 | /** | ||
798 | * hidpp_device_class_init: | ||
799 | **/ | ||
800 | static void | ||
801 | hidpp_device_class_init (HidppDeviceClass *klass) | ||
802 | { | ||
803 | GObjectClass *object_class = G_OBJECT_CLASS (klass); | ||
804 | object_class->finalize = hidpp_device_finalize; | ||
805 | g_type_class_add_private (klass, sizeof (HidppDevicePrivate)); | ||
806 | } | ||
807 | |||
808 | /** | ||
809 | * hidpp_device_new: | ||
810 | **/ | ||
811 | HidppDevice * | ||
812 | hidpp_device_new (void) | ||
813 | { | ||
814 | return g_object_new (HIDPP_TYPE_DEVICE, NULL); | ||
815 | } | ||
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 @@ | |||
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | ||
2 | * | ||
3 | * Copyright (C) 2012 Julien Danjou <julien@danjou.info> | ||
4 | * Copyright (C) 2012 Richard Hughes <richard@hughsie.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifndef __HIDPP_DEVICE_H__ | ||
23 | #define __HIDPP_DEVICE_H__ | ||
24 | |||
25 | #include <glib-object.h> | ||
26 | |||
27 | #include "hidpp-device.h" | ||
28 | |||
29 | G_BEGIN_DECLS | ||
30 | |||
31 | #define HIDPP_TYPE_DEVICE (hidpp_device_get_type ()) | ||
32 | #define HIDPP_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), HIDPP_TYPE_DEVICE, HidppDevice)) | ||
33 | #define HIDPP_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), HIDPP_TYPE_DEVICE, HidppDeviceClass)) | ||
34 | #define HIDPP_IS_DEVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), HIDPP_TYPE_DEVICE)) | ||
35 | #define HIDPP_IS_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), HIDPP_TYPE_DEVICE)) | ||
36 | #define HIDPP_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), HIDPP_TYPE_DEVICE, HidppDeviceClass)) | ||
37 | |||
38 | typedef struct HidppDevicePrivate HidppDevicePrivate; | ||
39 | |||
40 | typedef struct | ||
41 | { | ||
42 | GObject parent; | ||
43 | HidppDevicePrivate *priv; | ||
44 | } HidppDevice; | ||
45 | |||
46 | typedef struct | ||
47 | { | ||
48 | GObjectClass parent_class; | ||
49 | } HidppDeviceClass; | ||
50 | |||
51 | typedef enum { | ||
52 | HIDPP_DEVICE_KIND_KEYBOARD, | ||
53 | HIDPP_DEVICE_KIND_MOUSE, | ||
54 | HIDPP_DEVICE_KIND_TOUCHPAD, | ||
55 | HIDPP_DEVICE_KIND_TRACKBALL, | ||
56 | HIDPP_DEVICE_KIND_TABLET, | ||
57 | HIDPP_DEVICE_KIND_UNKNOWN | ||
58 | } HidppDeviceKind; | ||
59 | |||
60 | typedef enum { | ||
61 | HIDPP_DEVICE_BATT_STATUS_CHARGING, | ||
62 | HIDPP_DEVICE_BATT_STATUS_DISCHARGING, | ||
63 | HIDPP_DEVICE_BATT_STATUS_CHARGED, | ||
64 | HIDPP_DEVICE_BATT_STATUS_UNKNOWN | ||
65 | } HidppDeviceBattStatus; | ||
66 | |||
67 | typedef enum { | ||
68 | HIDPP_REFRESH_FLAGS_VERSION = 1, | ||
69 | HIDPP_REFRESH_FLAGS_KIND = 2, | ||
70 | HIDPP_REFRESH_FLAGS_BATTERY = 4, | ||
71 | HIDPP_REFRESH_FLAGS_MODEL = 8 | ||
72 | } HidppRefreshFlags; | ||
73 | |||
74 | GType hidpp_device_get_type (void); | ||
75 | const gchar *hidpp_device_get_model (HidppDevice *device); | ||
76 | guint hidpp_device_get_batt_percentage (HidppDevice *device); | ||
77 | guint hidpp_device_get_version (HidppDevice *device); | ||
78 | HidppDeviceBattStatus hidpp_device_get_batt_status (HidppDevice *device); | ||
79 | HidppDeviceKind hidpp_device_get_kind (HidppDevice *device); | ||
80 | void hidpp_device_set_hidraw_device (HidppDevice *device, | ||
81 | const gchar *hidraw_device); | ||
82 | void hidpp_device_set_index (HidppDevice *device, | ||
83 | guint device_idx); | ||
84 | void hidpp_device_set_enable_debug (HidppDevice *device, | ||
85 | gboolean enable_debug); | ||
86 | gboolean hidpp_device_refresh (HidppDevice *device, | ||
87 | HidppRefreshFlags refresh_flags, | ||
88 | GError **error); | ||
89 | HidppDevice *hidpp_device_new (void); | ||
90 | |||
91 | G_END_DECLS | ||
92 | |||
93 | #endif /* __HIDPP_DEVICE_H__ */ | ||
94 | |||
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 @@ | |||
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | ||
2 | * | ||
3 | * Copyright (C) 2012 Richard Hughes <richard@hughsie.com> | ||
4 | * | ||
5 | * Licensed under the GNU General Public License Version 2 | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
20 | */ | ||
21 | |||
22 | |||
23 | #include "config.h" | ||
24 | |||
25 | #include <glib.h> | ||
26 | #include <glib-object.h> | ||
27 | |||
28 | #include "hidpp-device.h" | ||
29 | |||
30 | int | ||
31 | main (int argc, char **argv) | ||
32 | { | ||
33 | // const gchar *model; | ||
34 | // guint batt_percentage; | ||
35 | // guint version; | ||
36 | // HidppDeviceBattStatus batt_status; | ||
37 | HidppDevice *d; | ||
38 | // HidppDeviceKind kind; | ||
39 | gboolean ret; | ||
40 | GError *error = NULL; | ||
41 | |||
42 | g_type_init (); | ||
43 | g_test_init (&argc, &argv, NULL); | ||
44 | |||
45 | d = hidpp_device_new (); | ||
46 | hidpp_device_set_enable_debug (d, TRUE); | ||
47 | g_assert_cmpint (hidpp_device_get_version (d), ==, 0); | ||
48 | g_assert_cmpstr (hidpp_device_get_model (d), ==, NULL); | ||
49 | g_assert_cmpint (hidpp_device_get_batt_percentage (d), ==, 0); | ||
50 | g_assert_cmpint (hidpp_device_get_batt_status (d), ==, HIDPP_DEVICE_BATT_STATUS_UNKNOWN); | ||
51 | g_assert_cmpint (hidpp_device_get_kind (d), ==, HIDPP_DEVICE_KIND_UNKNOWN); | ||
52 | |||
53 | /* setup */ | ||
54 | hidpp_device_set_hidraw_device (d, "/dev/hidraw0"); | ||
55 | hidpp_device_set_index (d, 1); | ||
56 | ret = hidpp_device_refresh (d, | ||
57 | HIDPP_REFRESH_FLAGS_VERSION | | ||
58 | HIDPP_REFRESH_FLAGS_KIND | | ||
59 | HIDPP_REFRESH_FLAGS_BATTERY | | ||
60 | HIDPP_REFRESH_FLAGS_MODEL, | ||
61 | &error); | ||
62 | g_assert_no_error (error); | ||
63 | g_assert (ret); | ||
64 | |||
65 | g_assert_cmpint (hidpp_device_get_version (d), !=, 0); | ||
66 | g_assert_cmpstr (hidpp_device_get_model (d), !=, NULL); | ||
67 | g_assert_cmpint (hidpp_device_get_batt_percentage (d), !=, 0); | ||
68 | g_assert_cmpint (hidpp_device_get_batt_status (d), !=, HIDPP_DEVICE_BATT_STATUS_UNKNOWN); | ||
69 | g_assert_cmpint (hidpp_device_get_kind (d), !=, HIDPP_DEVICE_KIND_UNKNOWN); | ||
70 | |||
71 | g_print ("Version:\t\t%i\n", hidpp_device_get_version (d)); | ||
72 | g_print ("Kind:\t\t\t%i\n", hidpp_device_get_kind (d)); | ||
73 | g_print ("Model:\t\t\t%s\n", hidpp_device_get_model (d)); | ||
74 | g_print ("Battery Percentage:\t%i\n", hidpp_device_get_batt_percentage (d)); | ||
75 | g_print ("Battery Status:\t\t%i\n", hidpp_device_get_batt_status (d)); | ||
76 | |||
77 | g_object_unref (d); | ||
78 | return 0; | ||
79 | } | ||
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 @@ | |||
36 | 36 | ||
37 | #include "up-device-supply.h" | 37 | #include "up-device-supply.h" |
38 | #include "up-device-csr.h" | 38 | #include "up-device-csr.h" |
39 | #include "up-device-lg-unifying.h" | 39 | #include "up-device-unifying.h" |
40 | #include "up-device-wup.h" | 40 | #include "up-device-wup.h" |
41 | #include "up-device-hid.h" | 41 | #include "up-device-hid.h" |
42 | #include "up-input.h" | 42 | #include "up-input.h" |
@@ -118,6 +118,18 @@ up_backend_device_new (UpBackend *backend, GUdevDevice *native) | |||
118 | /* no valid power supply object */ | 118 | /* no valid power supply object */ |
119 | device = NULL; | 119 | device = NULL; |
120 | 120 | ||
121 | } else if (g_strcmp0 (subsys, "hid") == 0) { | ||
122 | |||
123 | /* see if this is a Unifying mouse or keyboard */ | ||
124 | device = UP_DEVICE (up_device_unifying_new ()); | ||
125 | ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (native)); | ||
126 | if (ret) | ||
127 | goto out; | ||
128 | g_object_unref (device); | ||
129 | |||
130 | /* no valid power supply object */ | ||
131 | device = NULL; | ||
132 | |||
121 | } else if (g_strcmp0 (subsys, "tty") == 0) { | 133 | } else if (g_strcmp0 (subsys, "tty") == 0) { |
122 | 134 | ||
123 | /* try to detect a Watts Up? Pro monitor */ | 135 | /* try to detect a Watts Up? Pro monitor */ |
@@ -176,18 +188,8 @@ up_backend_device_new (UpBackend *backend, GUdevDevice *native) | |||
176 | 188 | ||
177 | /* no valid input object */ | 189 | /* no valid input object */ |
178 | device = NULL; | 190 | device = NULL; |
179 | } else { | ||
180 | g_object_unref (input); | ||
181 | |||
182 | /* see if this is a Unifying mouse or keyboard */ | ||
183 | device = UP_DEVICE (up_device_unifying_new ()); | ||
184 | ret = up_device_coldplug (device, backend->priv->daemon, G_OBJECT (native)); | ||
185 | if (!ret) { | ||
186 | g_object_unref (device); | ||
187 | /* no valid input object */ | ||
188 | device = NULL; | ||
189 | } | ||
190 | } | 191 | } |
192 | g_object_unref (input); | ||
191 | } else { | 193 | } else { |
192 | native_path = g_udev_device_get_sysfs_path (native); | 194 | native_path = g_udev_device_get_sysfs_path (native); |
193 | g_warning ("native path %s (%s) ignoring", native_path, subsys); | 195 | g_warning ("native path %s (%s) ignoring", native_path, subsys); |
@@ -328,7 +330,7 @@ up_backend_coldplug (UpBackend *backend, UpDaemon *daemon) | |||
328 | GList *l; | 330 | GList *l; |
329 | guint i; | 331 | guint i; |
330 | gboolean ret; | 332 | gboolean ret; |
331 | const gchar *subsystems[] = {"power_supply", "usb", "usbmisc", "tty", "input", NULL}; | 333 | const gchar *subsystems[] = {"power_supply", "usb", "usbmisc", "tty", "input", "hid", NULL}; |
332 | 334 | ||
333 | backend->priv->daemon = g_object_ref (daemon); | 335 | backend->priv->daemon = g_object_ref (daemon); |
334 | backend->priv->device_list = up_daemon_get_device_list (daemon); | 336 | 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 @@ | |||
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | ||
2 | * | ||
3 | * Copyright (C) 2012 Julien Danjou <julien@danjou.info> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | #ifdef HAVE_CONFIG_H | ||
22 | # include "config.h" | ||
23 | #endif | ||
24 | |||
25 | #include <linux/hidraw.h> | ||
26 | #include <linux/input.h> | ||
27 | #include <string.h> | ||
28 | #include <math.h> | ||
29 | #include <sys/types.h> | ||
30 | #include <sys/stat.h> | ||
31 | #include <fcntl.h> | ||
32 | #include <sys/ioctl.h> | ||
33 | #include <errno.h> | ||
34 | |||
35 | #include <glib.h> | ||
36 | #include <glib/gstdio.h> | ||
37 | #include <glib/gprintf.h> | ||
38 | #include <glib/gi18n-lib.h> | ||
39 | #include <glib-object.h> | ||
40 | #include <gudev/gudev.h> | ||
41 | |||
42 | #include "sysfs-utils.h" | ||
43 | #include "up-types.h" | ||
44 | #include "up-device-lg-unifying.h" | ||
45 | |||
46 | /* Arbitrary value used in ping */ | ||
47 | #define HIDPP_PING_DATA 0x42 | ||
48 | |||
49 | #define HIDPP_RECEIVER_ADDRESS 0xff | ||
50 | |||
51 | #define HIDPP_RESPONSE_SHORT_LENGTH 7 | ||
52 | #define HIDPP_RESPONSE_LONG_LENGTH 20 | ||
53 | |||
54 | #define HIDPP_HEADER_REQUEST 0x10 | ||
55 | #define HIDPP_HEADER_RESPONSE 0x11 | ||
56 | |||
57 | /* HID++ 1.0 */ | ||
58 | #define HIDPP_READ_SHORT_REGISTER 0x81 | ||
59 | #define HIDPP_READ_SHORT_REGISTER_BATTERY 0x0d | ||
60 | |||
61 | #define HIDPP_READ_LONG_REGISTER 0x83 | ||
62 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE 11 | ||
63 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD 0x1 | ||
64 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE 0x2 | ||
65 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD 0x3 | ||
66 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER 0x4 | ||
67 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL 0x7 | ||
68 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL 0x8 | ||
69 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD 0x9 | ||
70 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET 0xa | ||
71 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD 0xb | ||
72 | #define HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK 0xc | ||
73 | |||
74 | #define HIDPP_ERR_INVALID_SUBID 0x8f | ||
75 | |||
76 | /* HID++ 2.0 */ | ||
77 | #define HIDPP_FEATURE_ROOT 0x0000 | ||
78 | /* This is the only feature that has an hard coded index */ | ||
79 | #define HIDPP_FEATURE_ROOT_INDEX 0x00 | ||
80 | #define HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE (0x00 << 4) | ||
81 | #define HIDPP_FEATURE_ROOT_FUNCTION_PING (0x01 << 4) | ||
82 | |||
83 | #define HIDPP_FEATURE_GETDEVICENAMETYPE 0x0005 | ||
84 | #define HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT (0x00 << 4) | ||
85 | #define HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME (0x01 << 4) | ||
86 | |||
87 | #define HIDPP_FEATURE_SOLAR_DASHBOARD 0x4301 | ||
88 | #define HIDPP_FEATURE_SOLAR_DASHBOARD_FUNCTION_SetLightMeasure (0x00 << 4) | ||
89 | #define HIDPP_FEATURE_SOLAR_DASHBOARD_BattLightMeasureBroadcastEvent (0x01 << 4) | ||
90 | |||
91 | #define HIDPP_FEATURE_FUNCTION_AS_ARG(feature) \ | ||
92 | feature >> 8, feature, 0x00 | ||
93 | |||
94 | #define USB_VENDOR_ID_LOGITECH "046d" | ||
95 | #define USB_DEVICE_ID_UNIFYING_RECEIVER "c52b" | ||
96 | #define USB_DEVICE_ID_UNIFYING_RECEIVER_2 "c532" | ||
97 | |||
98 | #define UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT 3000 /* miliseconds */ | ||
99 | #define UP_DEVICE_UNIFYING_REFRESH_TIMEOUT 60L /* seconds */ | ||
100 | |||
101 | struct UpDeviceUnifyingPrivate | ||
102 | { | ||
103 | guint poll_timer_id; | ||
104 | int fd; | ||
105 | /* Device index on the Unifying "bus" */ | ||
106 | gint device_index; | ||
107 | gint feature_solar_dashboard_index; | ||
108 | GIOChannel *channel; | ||
109 | guint channel_source_id; | ||
110 | }; | ||
111 | |||
112 | G_DEFINE_TYPE (UpDeviceUnifying, up_device_unifying, UP_TYPE_DEVICE) | ||
113 | #define UP_DEVICE_UNIFYING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingPrivate)) | ||
114 | |||
115 | /** | ||
116 | * up_device_unifying_event_io: | ||
117 | * | ||
118 | * Read events from Unifying device, and treats them. | ||
119 | **/ | ||
120 | static gboolean | ||
121 | up_device_unifying_event_io (GIOChannel *channel, GIOCondition condition, gpointer data) | ||
122 | { | ||
123 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
124 | UpDeviceUnifying *unifying = data; | ||
125 | UpDevice *device = UP_DEVICE (unifying); | ||
126 | GTimeVal timeval; | ||
127 | guint16 lux; | ||
128 | |||
129 | while (read (unifying->priv->fd, buf, sizeof(buf)) > 0) | ||
130 | if (buf[0] == HIDPP_HEADER_RESPONSE && | ||
131 | buf[1] == unifying->priv->device_index && | ||
132 | buf[2] == unifying->priv->feature_solar_dashboard_index && | ||
133 | buf[3] == HIDPP_FEATURE_SOLAR_DASHBOARD_BattLightMeasureBroadcastEvent) { | ||
134 | lux = (buf[5] << 8) | buf[6]; | ||
135 | if (lux > 200) { | ||
136 | g_object_set (device, | ||
137 | "state", UP_DEVICE_STATE_CHARGING, | ||
138 | "power-supply", TRUE, | ||
139 | NULL); | ||
140 | } else if (lux > 0) { | ||
141 | g_object_set (device, | ||
142 | "state", UP_DEVICE_STATE_DISCHARGING, | ||
143 | "power-supply", TRUE, | ||
144 | NULL); | ||
145 | } else { | ||
146 | g_object_set (device, | ||
147 | "state", UP_DEVICE_STATE_DISCHARGING, | ||
148 | "power-supply", FALSE, | ||
149 | NULL); | ||
150 | } | ||
151 | |||
152 | g_get_current_time (&timeval); | ||
153 | |||
154 | g_object_set (device, | ||
155 | "update-time", (guint64) timeval.tv_sec, | ||
156 | "percentage", (gdouble) (guint8) buf[4], | ||
157 | "luminosity", (gdouble) lux, | ||
158 | NULL); | ||
159 | } | ||
160 | |||
161 | return TRUE; | ||
162 | } | ||
163 | |||
164 | static gint | ||
165 | up_device_unifying_read_response (int fd, | ||
166 | guint8 request[], | ||
167 | size_t count, | ||
168 | gint64 start_time) | ||
169 | { | ||
170 | GPollFD poll[] = { | ||
171 | { | ||
172 | .fd = fd, | ||
173 | .events = G_IO_IN | G_IO_HUP | G_IO_ERR, | ||
174 | }, | ||
175 | }; | ||
176 | gint ret; | ||
177 | |||
178 | /* If we started to wait for a particular response more than some | ||
179 | * time ago, abort */ | ||
180 | if (g_get_monotonic_time () - start_time | ||
181 | >= UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT * 1000) | ||
182 | return -1; | ||
183 | |||
184 | ret = g_poll (poll, G_N_ELEMENTS(poll), | ||
185 | UP_DEVICE_UNIFYING_READ_RESPONSE_TIMEOUT); | ||
186 | |||
187 | if (ret > 0) | ||
188 | return read (fd, request, count); | ||
189 | |||
190 | return ret; | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * up_device_unifying_hidpp1_set_battery: | ||
195 | * | ||
196 | * Send a READ SHORT REGISTER call to the device, and set battery status. | ||
197 | **/ | ||
198 | static gboolean | ||
199 | up_device_unifying_hidpp1_set_battery (UpDeviceUnifying *unifying) | ||
200 | { | ||
201 | UpDevice *device = UP_DEVICE (unifying); | ||
202 | guint8 request[] = { | ||
203 | HIDPP_HEADER_REQUEST, | ||
204 | unifying->priv->device_index, | ||
205 | HIDPP_READ_SHORT_REGISTER, | ||
206 | HIDPP_READ_SHORT_REGISTER_BATTERY, | ||
207 | 0x00, 0x00, 0x00, | ||
208 | }; | ||
209 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
210 | gint64 start_time; | ||
211 | |||
212 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
213 | g_debug ("Unable to read battery status from Unifying device %d", | ||
214 | unifying->priv->device_index); | ||
215 | return FALSE; | ||
216 | } | ||
217 | |||
218 | start_time = g_get_monotonic_time (); | ||
219 | |||
220 | while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0) | ||
221 | if (buf[0] == HIDPP_HEADER_REQUEST | ||
222 | && buf[1] == unifying->priv->device_index | ||
223 | && buf[2] == HIDPP_READ_SHORT_REGISTER | ||
224 | && buf[3] == HIDPP_READ_SHORT_REGISTER_BATTERY) { | ||
225 | g_object_set (device, | ||
226 | "percentage", (gdouble) buf[4], | ||
227 | NULL); | ||
228 | return TRUE; | ||
229 | } | ||
230 | |||
231 | return FALSE; | ||
232 | } | ||
233 | |||
234 | /** | ||
235 | * up_device_unifying_hidpp2_get_feature_index: | ||
236 | * | ||
237 | * Get a Unifying HID++ 2.0 feature index and return it. | ||
238 | * Returns 0 if the feature does not exists on this device. | ||
239 | **/ | ||
240 | static guint8 | ||
241 | up_device_unifying_hidpp2_get_feature_index (UpDeviceUnifying *unifying, guint16 feature) | ||
242 | { | ||
243 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
244 | guint8 request[] = { | ||
245 | HIDPP_HEADER_REQUEST, | ||
246 | unifying->priv->device_index, | ||
247 | HIDPP_FEATURE_ROOT_INDEX, | ||
248 | HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE, | ||
249 | HIDPP_FEATURE_FUNCTION_AS_ARG(feature) | ||
250 | }; | ||
251 | gint64 start_time; | ||
252 | |||
253 | /* Request the device name feature index */ | ||
254 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
255 | g_debug ("Unable to send GetFeature request to device"); | ||
256 | return -1; | ||
257 | } | ||
258 | |||
259 | start_time = g_get_monotonic_time (); | ||
260 | |||
261 | while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0) | ||
262 | if (buf[0] == HIDPP_HEADER_RESPONSE && | ||
263 | buf[1] == unifying->priv->device_index && | ||
264 | buf[2] == HIDPP_FEATURE_ROOT_INDEX && | ||
265 | buf[3] == HIDPP_FEATURE_ROOT_FUNCTION_GETFEATURE) | ||
266 | return buf[4]; | ||
267 | return -1; | ||
268 | } | ||
269 | |||
270 | |||
271 | /** | ||
272 | * up_device_unifying_hidpp2_set_battery: | ||
273 | * | ||
274 | * Send a bunch of HID++ requests to get the device battery and set it. | ||
275 | **/ | ||
276 | static gboolean | ||
277 | up_device_unifying_hidpp2_set_battery (UpDeviceUnifying *unifying) | ||
278 | { | ||
279 | guint8 request[] = { | ||
280 | HIDPP_HEADER_REQUEST, | ||
281 | unifying->priv->device_index, | ||
282 | 0x00, 0x00, 0x00, 0x00, 0x00, | ||
283 | }; | ||
284 | |||
285 | if (unifying->priv->feature_solar_dashboard_index == -1) | ||
286 | unifying->priv->feature_solar_dashboard_index = | ||
287 | up_device_unifying_hidpp2_get_feature_index (unifying, HIDPP_FEATURE_SOLAR_DASHBOARD); | ||
288 | |||
289 | if (unifying->priv->feature_solar_dashboard_index == 0) { | ||
290 | /* Probably not a solar keyboard */ | ||
291 | /* TODO: add support for BatteryLevelStatus */ | ||
292 | } else { | ||
293 | /* This request will make the keyboard send a bunch of packets | ||
294 | * (events) with lux-meter and battery information */ | ||
295 | request[2] = unifying->priv->feature_solar_dashboard_index; | ||
296 | request[3] = HIDPP_FEATURE_SOLAR_DASHBOARD_FUNCTION_SetLightMeasure; | ||
297 | request[4] = 0x01; /* Max number of reports: number of report sent after function call */ | ||
298 | request[5] = 0x01; /* Report period: time between reports, in seconds */ | ||
299 | |||
300 | |||
301 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
302 | g_debug ("Unable to send solar battery/lux events start request to device"); | ||
303 | return FALSE; | ||
304 | } | ||
305 | |||
306 | return TRUE; | ||
307 | } | ||
308 | |||
309 | return FALSE; | ||
310 | } | ||
311 | |||
312 | /** | ||
313 | * up_device_unifying_hidpp2_get_device_name: | ||
314 | * | ||
315 | * Send a bunch of HID++ requests to get the device name (model) and return | ||
316 | * it. | ||
317 | **/ | ||
318 | static GString * | ||
319 | up_device_unifying_hidpp2_get_device_name (UpDeviceUnifying *unifying) | ||
320 | { | ||
321 | GString *name = NULL; | ||
322 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
323 | ssize_t res; | ||
324 | guint8 request[] = { | ||
325 | HIDPP_HEADER_REQUEST, | ||
326 | unifying->priv->device_index, | ||
327 | 0x00, | ||
328 | HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT, | ||
329 | 0x00, 0x00, 0x00, | ||
330 | }; | ||
331 | ssize_t name_length = 0; | ||
332 | gint64 start_time; | ||
333 | |||
334 | request[2] = up_device_unifying_hidpp2_get_feature_index (unifying, HIDPP_FEATURE_GETDEVICENAMETYPE); | ||
335 | |||
336 | if (request[2] == 0) { | ||
337 | g_debug ("Unable to find GetDeviceNameType feature index"); | ||
338 | return NULL; | ||
339 | } | ||
340 | |||
341 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
342 | g_debug ("Unable to send GetDeviceNameType.GetCount request to device"); | ||
343 | return NULL; | ||
344 | } | ||
345 | |||
346 | start_time = g_get_monotonic_time (); | ||
347 | |||
348 | while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0) | ||
349 | if (buf[0] == HIDPP_HEADER_RESPONSE && | ||
350 | buf[1] == unifying->priv->device_index && | ||
351 | buf[2] == request[2] && | ||
352 | buf[3] == HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETCOUNT) { | ||
353 | name_length = buf[4]; | ||
354 | break; | ||
355 | } | ||
356 | |||
357 | name = g_string_new_len (NULL, name_length); | ||
358 | |||
359 | while (name_length > 0) { | ||
360 | request[3] = HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME; | ||
361 | request[4] = name->len; | ||
362 | |||
363 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
364 | g_debug ("Unable to send GetDeviceNameType.GetDeviceName request to device"); | ||
365 | g_string_free (name, TRUE); | ||
366 | return NULL; | ||
367 | } | ||
368 | |||
369 | start_time = g_get_monotonic_time (); | ||
370 | |||
371 | while ((res = up_device_unifying_read_response (unifying->priv->fd, buf, | ||
372 | sizeof (buf), start_time)) > 0) | ||
373 | if (buf[0] == HIDPP_HEADER_RESPONSE && | ||
374 | buf[1] == unifying->priv->device_index && | ||
375 | buf[2] == request[2] && | ||
376 | buf[3] == HIDPP_FEATURE_GETDEVICENAMETYPE_FUNCTION_GETDEVICENAME) { | ||
377 | g_string_append_len (name, (gchar *) &buf[4], MIN(res - 4, name_length)); | ||
378 | name_length -= MIN(res - 4, name_length); | ||
379 | break; | ||
380 | } | ||
381 | |||
382 | /* Handle no response case */ | ||
383 | if (res <= 0) { | ||
384 | g_debug ("Error reading GetDeviceNameType.GetDeviceName response"); | ||
385 | g_string_free (name, TRUE); | ||
386 | return NULL; | ||
387 | } | ||
388 | } | ||
389 | |||
390 | return name; | ||
391 | } | ||
392 | |||
393 | /** | ||
394 | * up_device_unifying_set_device_type: | ||
395 | * | ||
396 | * Send a Read Long Register HID++ 1.0 command to the device. This allows to | ||
397 | * retrieve the type of the device, and then set it. | ||
398 | **/ | ||
399 | static gboolean | ||
400 | up_device_unifying_set_device_type (UpDeviceUnifying *unifying) | ||
401 | { | ||
402 | guint8 request[] = { | ||
403 | HIDPP_HEADER_REQUEST, | ||
404 | HIDPP_RECEIVER_ADDRESS, | ||
405 | 0x83, 0xb5, | ||
406 | 0x20 | (unifying->priv->device_index - 1), | ||
407 | 0x00, 0x00, | ||
408 | }; | ||
409 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
410 | UpDevice *device = UP_DEVICE (unifying); | ||
411 | gint64 start_time; | ||
412 | |||
413 | if (write (unifying->priv->fd, request, sizeof(request)) != sizeof(request)) { | ||
414 | g_debug ("Unable to send a HID++ read long register request to device %d", | ||
415 | unifying->priv->device_index); | ||
416 | return FALSE; | ||
417 | } | ||
418 | |||
419 | start_time = g_get_monotonic_time (); | ||
420 | |||
421 | while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0) | ||
422 | if (buf[0] == HIDPP_HEADER_RESPONSE | ||
423 | && buf[1] == HIDPP_RECEIVER_ADDRESS | ||
424 | && buf[2] == HIDPP_READ_LONG_REGISTER | ||
425 | && buf[3] == 0xb5 | ||
426 | && buf[4] == (0x20 | (unifying->priv->device_index - 1))) { | ||
427 | switch (buf[HIDPP_READ_LONG_REGISTER_DEVICE_TYPE]) { | ||
428 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_KEYBOARD: | ||
429 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_NUMPAD: | ||
430 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_REMOTE_CONTROL: | ||
431 | g_object_set (device, "type", UP_DEVICE_KIND_KEYBOARD, NULL); | ||
432 | break; | ||
433 | |||
434 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_MOUSE: | ||
435 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TRACKBALL: | ||
436 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TOUCHPAD: | ||
437 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_PRESENTER: | ||
438 | g_object_set (device, "type", UP_DEVICE_KIND_MOUSE, NULL); | ||
439 | break; | ||
440 | |||
441 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_TABLET: | ||
442 | g_object_set (device, "type", UP_DEVICE_KIND_TABLET, NULL); | ||
443 | break; | ||
444 | |||
445 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_GAMEPAD: | ||
446 | case HIDPP_READ_LONG_REGISTER_DEVICE_TYPE_JOYSTICK: | ||
447 | /* upower doesn't have something for this yet */ | ||
448 | g_object_set (device, "type", UP_DEVICE_KIND_UNKNOWN, NULL); | ||
449 | break; | ||
450 | } | ||
451 | return TRUE; | ||
452 | } | ||
453 | |||
454 | return FALSE; | ||
455 | } | ||
456 | |||
457 | /** | ||
458 | * up_device_unifying_get_hidpp_version | ||
459 | * | ||
460 | * Return the version of HID++ used by a device. | ||
461 | **/ | ||
462 | static gint | ||
463 | up_device_unifying_get_hidpp_version (UpDeviceUnifying *unifying) | ||
464 | { | ||
465 | guint8 ping[] = { | ||
466 | HIDPP_HEADER_REQUEST, | ||
467 | unifying->priv->device_index, | ||
468 | HIDPP_FEATURE_ROOT_INDEX, | ||
469 | HIDPP_FEATURE_ROOT_FUNCTION_PING, | ||
470 | 0x00, 0x00, HIDPP_PING_DATA | ||
471 | }; | ||
472 | guint8 buf[HIDPP_RESPONSE_LONG_LENGTH]; | ||
473 | gint64 start_time; | ||
474 | |||
475 | if (write(unifying->priv->fd, ping, sizeof(ping)) != sizeof(ping)) { | ||
476 | g_debug ("Unable to send a HID++ ping to device %d", | ||
477 | unifying->priv->device_index); | ||
478 | return -1; | ||
479 | } | ||
480 | |||
481 | /* read event */ | ||
482 | |||
483 | start_time = g_get_monotonic_time (); | ||
484 | |||
485 | while (up_device_unifying_read_response (unifying->priv->fd, buf, sizeof (buf), start_time) > 0) | ||
486 | if(buf[0] == HIDPP_HEADER_REQUEST | ||
487 | && buf[1] == unifying->priv->device_index | ||
488 | && buf[2] == HIDPP_ERR_INVALID_SUBID | ||
489 | && buf[3] == 0x00 | ||
490 | && buf[4] == HIDPP_FEATURE_ROOT_FUNCTION_PING) { | ||
491 | /* HID++ 1.0 ping reply */ | ||
492 | if (buf[5] == 0x01) | ||
493 | return 1; | ||
494 | else if (buf[5] == 0x09) | ||
495 | /* device offline / unreachable */ | ||
496 | return 0; | ||
497 | } else if (buf[0] == HIDPP_HEADER_RESPONSE | ||
498 | && buf[1] == unifying->priv->device_index | ||
499 | && buf[2] == HIDPP_FEATURE_ROOT_INDEX | ||
500 | && buf[3] == HIDPP_FEATURE_ROOT_FUNCTION_PING | ||
501 | && buf[6] == HIDPP_PING_DATA) | ||
502 | /* HID++ >= 2.0 ping reply: buf[4] is major | ||
503 | version, buf[5] is minor version but we | ||
504 | only care about major for now*/ | ||
505 | return buf[4]; | ||
506 | |||
507 | return -1; | ||
508 | } | ||
509 | |||
510 | /** | ||
511 | * up_device_unifying_refresh: | ||
512 | * | ||
513 | * Return %TRUE on success, %FALSE if we failed to refresh or no data | ||
514 | **/ | ||
515 | static gboolean | ||
516 | up_device_unifying_refresh (UpDevice *device) | ||
517 | { | ||
518 | UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device); | ||
519 | gint hidpp_version = up_device_unifying_get_hidpp_version (unifying); | ||
520 | GString *name; | ||
521 | char *model; | ||
522 | GTimeVal timeval; | ||
523 | |||
524 | if (hidpp_version > 0) | ||
525 | g_debug ("Unifying device %d uses HID++ version %d", | ||
526 | unifying->priv->device_index, hidpp_version); | ||
527 | |||
528 | switch (hidpp_version) { | ||
529 | case 0: | ||
530 | g_debug ("Unifying device %d is offline", | ||
531 | unifying->priv->device_index); | ||
532 | g_object_set (device, | ||
533 | "is-present", FALSE, | ||
534 | "state", UP_DEVICE_STATE_UNKNOWN, | ||
535 | NULL); | ||
536 | break; | ||
537 | case 1: | ||
538 | g_object_set (device, | ||
539 | "state", UP_DEVICE_STATE_DISCHARGING, | ||
540 | "is-present", TRUE, | ||
541 | NULL); | ||
542 | up_device_unifying_hidpp1_set_battery (unifying); | ||
543 | break; | ||
544 | case 2: | ||
545 | g_object_set (device, | ||
546 | "is-present", TRUE, | ||
547 | NULL); | ||
548 | |||
549 | g_object_get (device, "model", &model, NULL); | ||
550 | if (!model) { | ||
551 | name = up_device_unifying_hidpp2_get_device_name (unifying); | ||
552 | if (name) { | ||
553 | g_object_set (device, "model", name->str, NULL); | ||
554 | g_string_free (name, TRUE); | ||
555 | } | ||
556 | } else | ||
557 | g_free (model); | ||
558 | up_device_unifying_hidpp2_set_battery (unifying); | ||
559 | break; | ||
560 | } | ||
561 | |||
562 | g_get_current_time (&timeval); | ||
563 | g_object_set (device, "update-time", (guint64) timeval.tv_sec, NULL); | ||
564 | |||
565 | return TRUE; | ||
566 | } | ||
567 | |||
568 | /** | ||
569 | * up_device_unifying_coldplug: | ||
570 | * | ||
571 | * Return %TRUE on success, %FALSE if we failed to get data and should be removed | ||
572 | **/ | ||
573 | static gboolean | ||
574 | up_device_unifying_coldplug (UpDevice *device) | ||
575 | { | ||
576 | UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device); | ||
577 | GUdevDevice *native; | ||
578 | const gchar *device_file; | ||
579 | const gchar *vendor; | ||
580 | const gchar *parent_sysfs_path; | ||
581 | const gchar *bus_address; | ||
582 | GList *hidraw_list, *entry; | ||
583 | size_t len; | ||
584 | GIOStatus status; | ||
585 | GError *error = NULL; | ||
586 | GUdevClient *gudev_client; | ||
587 | GUdevDevice *parent, *hidraw, *receiver = NULL; | ||
588 | gboolean ret = FALSE; | ||
589 | |||
590 | native = G_UDEV_DEVICE (up_device_get_native (device)); | ||
591 | |||
592 | if(g_strcmp0(g_udev_device_get_property (native, "ID_VENDOR_ID"), | ||
593 | USB_VENDOR_ID_LOGITECH) || | ||
594 | (g_strcmp0(g_udev_device_get_property (native, "ID_MODEL_ID"), | ||
595 | USB_DEVICE_ID_UNIFYING_RECEIVER) && | ||
596 | g_strcmp0(g_udev_device_get_property (native, "ID_MODEL_ID"), | ||
597 | USB_DEVICE_ID_UNIFYING_RECEIVER_2))) { | ||
598 | g_debug ("Not an Unifying device, ignoring"); | ||
599 | return FALSE; | ||
600 | } | ||
601 | |||
602 | bus_address = g_udev_device_get_property (native, "PHYS"); | ||
603 | |||
604 | if (!bus_address) { | ||
605 | g_debug ("Device has no physical bus address, ignoring"); | ||
606 | return FALSE; | ||
607 | } | ||
608 | |||
609 | len = strlen (bus_address); | ||
610 | |||
611 | if (len < 3 || bus_address[len - 3] != ':' || !g_ascii_isdigit (bus_address[len - 2])) { | ||
612 | g_debug ("Invalid Unifying device index, ignoring"); | ||
613 | return FALSE; | ||
614 | } | ||
615 | |||
616 | unifying->priv->device_index = g_ascii_digit_value (bus_address[len - 2]); | ||
617 | |||
618 | /* Find the hidraw device of the parent (the receiver) to | ||
619 | * communicate with the devices */ | ||
620 | gudev_client = g_udev_client_new (NULL); | ||
621 | |||
622 | parent = g_udev_device_get_parent (native); | ||
623 | parent_sysfs_path = g_udev_device_get_sysfs_path (parent); | ||
624 | g_object_unref (parent); | ||
625 | |||
626 | hidraw_list = g_udev_client_query_by_subsystem (gudev_client, "hidraw"); | ||
627 | |||
628 | for (entry = hidraw_list; entry; entry = entry->next) { | ||
629 | hidraw = entry->data; | ||
630 | if (!g_strcmp0 (g_udev_device_get_sysfs_attr (hidraw, "device"), | ||
631 | parent_sysfs_path)) | ||
632 | receiver = hidraw; | ||
633 | else | ||
634 | g_object_unref (hidraw); | ||
635 | } | ||
636 | |||
637 | if (!receiver) { | ||
638 | g_debug ("Unable to find an hidraw device for Unifying receiver"); | ||
639 | return FALSE; | ||
640 | } | ||
641 | |||
642 | /* get device file */ | ||
643 | device_file = g_udev_device_get_device_file (receiver); | ||
644 | |||
645 | /* connect to the device */ | ||
646 | g_debug ("Using Unifying receiver hidraw device file: %s", device_file); | ||
647 | |||
648 | if (device_file == NULL) { | ||
649 | g_debug ("Could not get device file for Unifying receiver device"); | ||
650 | goto out; | ||
651 | } | ||
652 | |||
653 | unifying->priv->fd = open (device_file, O_RDWR | O_NONBLOCK); | ||
654 | if (unifying->priv->fd < 0) { | ||
655 | g_debug ("cannot open device file %s", device_file); | ||
656 | return FALSE; | ||
657 | } | ||
658 | |||
659 | vendor = g_udev_device_get_property (native, "ID_VENDOR"); | ||
660 | |||
661 | /* hardcode some default values */ | ||
662 | g_object_set (device, | ||
663 | "vendor", vendor, | ||
664 | "is-present", TRUE, | ||
665 | "has-history", TRUE, | ||
666 | "is-rechargeable", TRUE, | ||
667 | "state", UP_DEVICE_STATE_DISCHARGING, | ||
668 | "power-supply", FALSE, | ||
669 | NULL); | ||
670 | |||
671 | /* Set device type */ | ||
672 | if (!up_device_unifying_set_device_type(unifying)) { | ||
673 | g_debug ("Unable to guess device type, ignoring the device"); | ||
674 | goto out; | ||
675 | } | ||
676 | |||
677 | unifying->priv->channel = g_io_channel_unix_new (unifying->priv->fd); | ||
678 | |||
679 | /* set binary encoding */ | ||
680 | status = g_io_channel_set_encoding (unifying->priv->channel, NULL, &error); | ||
681 | if (status != G_IO_STATUS_NORMAL) { | ||
682 | g_warning ("failed to set encoding: %s", error->message); | ||
683 | g_error_free (error); | ||
684 | goto out; | ||
685 | } | ||
686 | |||
687 | /* watch this */ | ||
688 | unifying->priv->channel_source_id = g_io_add_watch (unifying->priv->channel, | ||
689 | G_IO_IN, | ||
690 | up_device_unifying_event_io, | ||
691 | unifying); | ||
692 | |||
693 | /* set up a poll to send the magic packet */ | ||
694 | unifying->priv->poll_timer_id = g_timeout_add_seconds (UP_DEVICE_UNIFYING_REFRESH_TIMEOUT, | ||
695 | (GSourceFunc) up_device_unifying_refresh, | ||
696 | device); | ||
697 | |||
698 | ret = TRUE; | ||
699 | |||
700 | out: | ||
701 | g_object_unref (gudev_client); | ||
702 | g_object_unref (receiver); | ||
703 | g_list_free (hidraw_list); | ||
704 | |||
705 | if (!ret && unifying->priv->fd >= 0) | ||
706 | close (unifying->priv->fd); | ||
707 | |||
708 | return ret; | ||
709 | } | ||
710 | |||
711 | /** | ||
712 | * up_device_unifying_init: | ||
713 | **/ | ||
714 | static void | ||
715 | up_device_unifying_init (UpDeviceUnifying *unifying) | ||
716 | { | ||
717 | unifying->priv = UP_DEVICE_UNIFYING_GET_PRIVATE (unifying); | ||
718 | unifying->priv->poll_timer_id = 0; | ||
719 | unifying->priv->fd = -1; | ||
720 | unifying->priv->feature_solar_dashboard_index = -1; | ||
721 | } | ||
722 | |||
723 | /** | ||
724 | * up_device_unifying_finalize: | ||
725 | **/ | ||
726 | static void | ||
727 | up_device_unifying_finalize (GObject *object) | ||
728 | { | ||
729 | UpDeviceUnifying *unifying; | ||
730 | |||
731 | g_return_if_fail (object != NULL); | ||
732 | g_return_if_fail (UP_IS_DEVICE_UNIFYING (object)); | ||
733 | |||
734 | unifying = UP_DEVICE_UNIFYING (object); | ||
735 | g_return_if_fail (unifying->priv != NULL); | ||
736 | |||
737 | if (unifying->priv->poll_timer_id > 0) | ||
738 | g_source_remove (unifying->priv->poll_timer_id); | ||
739 | |||
740 | if (unifying->priv->channel_source_id > 0) | ||
741 | g_source_remove (unifying->priv->channel_source_id); | ||
742 | |||
743 | if (unifying->priv->channel) { | ||
744 | g_io_channel_shutdown (unifying->priv->channel, FALSE, NULL); | ||
745 | g_io_channel_unref (unifying->priv->channel); | ||
746 | } | ||
747 | |||
748 | G_OBJECT_CLASS (up_device_unifying_parent_class)->finalize (object); | ||
749 | } | ||
750 | |||
751 | /** | ||
752 | * up_device_unifying_class_init: | ||
753 | **/ | ||
754 | static void | ||
755 | up_device_unifying_class_init (UpDeviceUnifyingClass *klass) | ||
756 | { | ||
757 | GObjectClass *object_class = G_OBJECT_CLASS (klass); | ||
758 | UpDeviceClass *device_class = UP_DEVICE_CLASS (klass); | ||
759 | |||
760 | object_class->finalize = up_device_unifying_finalize; | ||
761 | device_class->coldplug = up_device_unifying_coldplug; | ||
762 | device_class->refresh = up_device_unifying_refresh; | ||
763 | |||
764 | g_type_class_add_private (klass, sizeof (UpDeviceUnifyingPrivate)); | ||
765 | } | ||
766 | |||
767 | /** | ||
768 | * up_device_unifying_new: | ||
769 | **/ | ||
770 | UpDeviceUnifying * | ||
771 | up_device_unifying_new (void) | ||
772 | { | ||
773 | return g_object_new (UP_TYPE_DEVICE_UNIFYING, NULL); | ||
774 | } | ||
775 | |||
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 @@ | |||
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | ||
2 | * | ||
3 | * Copyright (C) 2012 Julien Danjou <julien@danjou.info> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | #ifdef HAVE_CONFIG_H | ||
22 | # include "config.h" | ||
23 | #endif | ||
24 | |||
25 | #include <glib-object.h> | ||
26 | #include <gudev/gudev.h> | ||
27 | |||
28 | #include "hidpp-device.h" | ||
29 | |||
30 | #include "up-device-unifying.h" | ||
31 | #include "up-types.h" | ||
32 | |||
33 | #define UP_DEVICE_UNIFYING_REFRESH_TIMEOUT 60 /* seconds */ | ||
34 | |||
35 | struct UpDeviceUnifyingPrivate | ||
36 | { | ||
37 | guint poll_timer_id; | ||
38 | HidppDevice *hidpp_device; | ||
39 | }; | ||
40 | |||
41 | G_DEFINE_TYPE (UpDeviceUnifying, up_device_unifying, UP_TYPE_DEVICE) | ||
42 | #define UP_DEVICE_UNIFYING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DEVICE_UNIFYING, UpDeviceUnifyingPrivate)) | ||
43 | |||
44 | /** | ||
45 | * up_device_unifying_refresh: | ||
46 | * | ||
47 | * Return %TRUE on success, %FALSE if we failed to refresh or no data | ||
48 | **/ | ||
49 | static gboolean | ||
50 | up_device_unifying_refresh (UpDevice *device) | ||
51 | { | ||
52 | gboolean ret; | ||
53 | GError *error = NULL; | ||
54 | GTimeVal timeval; | ||
55 | UpDeviceState state = UP_DEVICE_STATE_UNKNOWN; | ||
56 | UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device); | ||
57 | UpDeviceUnifyingPrivate *priv = unifying->priv; | ||
58 | |||
59 | /* refresh just the battery stats */ | ||
60 | ret = hidpp_device_refresh (priv->hidpp_device, | ||
61 | HIDPP_REFRESH_FLAGS_BATTERY, | ||
62 | &error); | ||
63 | if (!ret) { | ||
64 | g_warning ("failed to coldplug unifying device: %s", | ||
65 | error->message); | ||
66 | g_error_free (error); | ||
67 | goto out; | ||
68 | } | ||
69 | switch (hidpp_device_get_batt_status (priv->hidpp_device)) { | ||
70 | case HIDPP_DEVICE_BATT_STATUS_CHARGING: | ||
71 | state = UP_DEVICE_STATE_CHARGING; | ||
72 | break; | ||
73 | case HIDPP_DEVICE_BATT_STATUS_DISCHARGING: | ||
74 | state = UP_DEVICE_STATE_DISCHARGING; | ||
75 | break; | ||
76 | case HIDPP_DEVICE_BATT_STATUS_CHARGED: | ||
77 | state = UP_DEVICE_STATE_FULLY_CHARGED; | ||
78 | break; | ||
79 | default: | ||
80 | break; | ||
81 | } | ||
82 | g_get_current_time (&timeval); | ||
83 | g_object_set (device, | ||
84 | "is-present", hidpp_device_get_version (priv->hidpp_device) > 0, | ||
85 | "percentage", (gdouble) hidpp_device_get_batt_percentage (priv->hidpp_device), | ||
86 | "state", state, | ||
87 | "update-time", (guint64) timeval.tv_sec, | ||
88 | NULL); | ||
89 | out: | ||
90 | return TRUE; | ||
91 | } | ||
92 | |||
93 | static UpDeviceKind | ||
94 | up_device_unifying_get_device_kind (UpDeviceUnifying *unifying) | ||
95 | { | ||
96 | UpDeviceKind kind; | ||
97 | switch (hidpp_device_get_kind (unifying->priv->hidpp_device)) { | ||
98 | case HIDPP_DEVICE_KIND_MOUSE: | ||
99 | case HIDPP_DEVICE_KIND_TOUCHPAD: | ||
100 | case HIDPP_DEVICE_KIND_TRACKBALL: | ||
101 | kind = UP_DEVICE_KIND_MOUSE; | ||
102 | break; | ||
103 | case HIDPP_DEVICE_KIND_KEYBOARD: | ||
104 | kind = UP_DEVICE_KIND_KEYBOARD; | ||
105 | break; | ||
106 | case HIDPP_DEVICE_KIND_TABLET: | ||
107 | kind = UP_DEVICE_KIND_TABLET; | ||
108 | break; | ||
109 | default: | ||
110 | kind = UP_DEVICE_KIND_UNKNOWN; | ||
111 | } | ||
112 | return kind; | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * up_device_unifying_coldplug: | ||
117 | * | ||
118 | * Return %TRUE on success, %FALSE if we failed to get data and should be removed | ||
119 | **/ | ||
120 | static gboolean | ||
121 | up_device_unifying_coldplug (UpDevice *device) | ||
122 | { | ||
123 | const gchar *bus_address; | ||
124 | const gchar *device_file; | ||
125 | const gchar *type; | ||
126 | gboolean ret = FALSE; | ||
127 | gchar *endptr = NULL; | ||
128 | gchar *tmp; | ||
129 | GError *error = NULL; | ||
130 | GUdevDevice *native; | ||
131 | GUdevDevice *parent = NULL; | ||
132 | GUdevDevice *receiver = NULL; | ||
133 | UpDeviceUnifying *unifying = UP_DEVICE_UNIFYING (device); | ||
134 | GUdevClient *client = NULL; | ||
135 | GList *hidraw_list = NULL; | ||
136 | GList *l; | ||
137 | |||
138 | native = G_UDEV_DEVICE (up_device_get_native (device)); | ||
139 | |||
140 | /* check if we have the right device */ | ||
141 | type = g_udev_device_get_property (native, "UPOWER_BATTERY_TYPE"); | ||
142 | if (type == NULL) | ||
143 | goto out; | ||
144 | if (g_strcmp0 (type, "unifying") != 0) | ||
145 | goto out; | ||
146 | |||
147 | /* get the device index */ | ||
148 | unifying->priv->hidpp_device = hidpp_device_new (); | ||
149 | bus_address = g_udev_device_get_property (native, "HID_PHYS"); | ||
150 | tmp = g_strrstr (bus_address, ":"); | ||
151 | if (tmp == NULL) { | ||
152 | g_debug ("Could not get physical device index"); | ||
153 | goto out; | ||
154 | } | ||
155 | hidpp_device_set_index (unifying->priv->hidpp_device, | ||
156 | g_ascii_strtoull (tmp + 1, &endptr, 10)); | ||
157 | if (endptr != NULL && endptr[0] != '\0') { | ||
158 | g_debug ("HID_PHYS malformed: '%s'", bus_address); | ||
159 | goto out; | ||
160 | } | ||
161 | |||
162 | /* find the hidraw device that matches the parent */ | ||
163 | parent = g_udev_device_get_parent (native); | ||
164 | client = g_udev_client_new (NULL); | ||
165 | hidraw_list = g_udev_client_query_by_subsystem (client, "hidraw"); | ||
166 | for (l = hidraw_list; l != NULL; l = l->next) { | ||
167 | if (g_strcmp0 (g_udev_device_get_sysfs_path (parent), | ||
168 | g_udev_device_get_sysfs_attr (l->data, "device")) == 0) { | ||
169 | receiver = g_object_ref (l->data); | ||
170 | break; | ||
171 | } | ||
172 | } | ||
173 | if (receiver == NULL) { | ||
174 | g_debug ("Unable to find an hidraw device for Unifying receiver"); | ||
175 | return FALSE; | ||
176 | } | ||
177 | |||
178 | /* connect to the receiver */ | ||
179 | device_file = g_udev_device_get_device_file (receiver); | ||
180 | if (device_file == NULL) { | ||
181 | g_debug ("Could not get device file for Unifying receiver device"); | ||
182 | goto out; | ||
183 | } | ||
184 | g_debug ("Using Unifying receiver hidraw device file: %s", device_file); | ||
185 | hidpp_device_set_hidraw_device (unifying->priv->hidpp_device, | ||
186 | device_file); | ||
187 | |||
188 | /* coldplug initial parameters */ | ||
189 | ret = hidpp_device_refresh (unifying->priv->hidpp_device, | ||
190 | HIDPP_REFRESH_FLAGS_VERSION | | ||
191 | HIDPP_REFRESH_FLAGS_KIND | | ||
192 | HIDPP_REFRESH_FLAGS_MODEL, | ||
193 | &error); | ||
194 | if (!ret) { | ||
195 | g_warning ("failed to coldplug unifying device: %s", | ||
196 | error->message); | ||
197 | g_error_free (error); | ||
198 | goto out; | ||
199 | } | ||
200 | |||
201 | /* set some default values */ | ||
202 | g_object_set (device, | ||
203 | "vendor", g_udev_device_get_property (native, "ID_VENDOR"), | ||
204 | "type", up_device_unifying_get_device_kind (unifying), | ||
205 | "model", hidpp_device_get_model (unifying->priv->hidpp_device), | ||
206 | "has-history", TRUE, | ||
207 | "is-rechargeable", TRUE, | ||
208 | "power-supply", FALSE, | ||
209 | NULL); | ||
210 | |||
211 | /* set up a poll to send the magic packet */ | ||
212 | up_device_unifying_refresh (device); | ||
213 | unifying->priv->poll_timer_id = g_timeout_add_seconds (UP_DEVICE_UNIFYING_REFRESH_TIMEOUT, | ||
214 | (GSourceFunc) up_device_unifying_refresh, | ||
215 | device); | ||
216 | ret = TRUE; | ||
217 | out: | ||
218 | g_list_foreach (hidraw_list, (GFunc) g_object_unref, NULL); | ||
219 | g_list_free (hidraw_list); | ||
220 | if (parent != NULL) | ||
221 | g_object_unref (parent); | ||
222 | if (receiver != NULL) | ||
223 | g_object_unref (receiver); | ||
224 | if (client != NULL) | ||
225 | g_object_unref (client); | ||
226 | return ret; | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * up_device_unifying_init: | ||
231 | **/ | ||
232 | static void | ||
233 | up_device_unifying_init (UpDeviceUnifying *unifying) | ||
234 | { | ||
235 | unifying->priv = UP_DEVICE_UNIFYING_GET_PRIVATE (unifying); | ||
236 | unifying->priv->poll_timer_id = 0; | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * up_device_unifying_finalize: | ||
241 | **/ | ||
242 | static void | ||
243 | up_device_unifying_finalize (GObject *object) | ||
244 | { | ||
245 | UpDeviceUnifying *unifying; | ||
246 | |||
247 | g_return_if_fail (object != NULL); | ||
248 | g_return_if_fail (UP_IS_DEVICE_UNIFYING (object)); | ||
249 | |||
250 | unifying = UP_DEVICE_UNIFYING (object); | ||
251 | g_return_if_fail (unifying->priv != NULL); | ||
252 | |||
253 | if (unifying->priv->poll_timer_id > 0) | ||
254 | g_source_remove (unifying->priv->poll_timer_id); | ||
255 | if (unifying->priv->hidpp_device != NULL) | ||
256 | g_object_unref (unifying->priv->hidpp_device); | ||
257 | |||
258 | G_OBJECT_CLASS (up_device_unifying_parent_class)->finalize (object); | ||
259 | } | ||
260 | |||
261 | /** | ||
262 | * up_device_unifying_class_init: | ||
263 | **/ | ||
264 | static void | ||
265 | up_device_unifying_class_init (UpDeviceUnifyingClass *klass) | ||
266 | { | ||
267 | GObjectClass *object_class = G_OBJECT_CLASS (klass); | ||
268 | UpDeviceClass *device_class = UP_DEVICE_CLASS (klass); | ||
269 | |||
270 | object_class->finalize = up_device_unifying_finalize; | ||
271 | device_class->coldplug = up_device_unifying_coldplug; | ||
272 | device_class->refresh = up_device_unifying_refresh; | ||
273 | |||
274 | g_type_class_add_private (klass, sizeof (UpDeviceUnifyingPrivate)); | ||
275 | } | ||
276 | |||
277 | /** | ||
278 | * up_device_unifying_new: | ||
279 | **/ | ||
280 | UpDeviceUnifying * | ||
281 | up_device_unifying_new (void) | ||
282 | { | ||
283 | return g_object_new (UP_TYPE_DEVICE_UNIFYING, NULL); | ||
284 | } | ||
diff --git a/src/linux/up-device-lg-unifying.h b/src/linux/up-device-unifying.h index d1debf3..d1debf3 100644 --- a/src/linux/up-device-lg-unifying.h +++ b/src/linux/up-device-unifying.h | |||