28 Commits
v0.2 ... master

Author SHA1 Message Date
Peter Wu
b68dc9af6d ltunify: fix harmless compiler warning
bInterfaceNumber is expected to return the `%02x` format according to
the current kernel sources. Even if that is not true, a user can always
manually specify the hidraw device if the wrong device is detected.
2020-06-14 22:59:19 +02:00
Peter Wu
872a781e07 Release 0.3 2020-06-14 22:34:05 +02:00
Peter Wu
b639b7f5c6 ltunify: fix minor alignment warning with the receiver-info command
Caught by UndefinedBehaviorSanitizer (UBSan):

    ltunify.c:785:32: runtime error: load of misaligned address
    0x7ffc0e4c38a5 for type 'uint32_t' (aka 'unsigned int'), which
    requires 4 byte alignment
2020-06-14 22:16:49 +02:00
Peter Wu
5c8283480d ltunify: add Nano receiver c534 (MK270) support
Fix "Unknown protocol 0x0a in devcon notif" errors when trying to list
or pair new devices connected to the Nano receiver with device ID c534.
This receiver is limited to 2 devices only and does not allow unpairing.

Update the hidraw autodetection since the first detected hidraw device
is wrong. Check the interface number, similar to Solaar, this avoids
more complicated HID descriptor parsing.

To confirm whether a device supports the HID++ protocol, either use
`sudo lsusb -v046d:` (without a kernel driver bound to it) or use

    hidrd-convert -o spec /sys/class/hidraw/hidrawX/device/report_descriptor output.txt

to find a Vendor-Specific page.
2020-06-14 22:06:58 +02:00
Rob Wu
f664d1d41d Use correct index in process_notif_dev_connect
`devices[dev_idx]` should have been `devices[dev_idx - 1]`, aka `dev`.
2018-03-30 16:36:37 +02:00
Peter Wu
955b9feb9e shell: support ':' separator for multiple args
It becomes a pain to remove all colons just to add a single argument.
2014-04-05 18:41:42 +02:00
Peter Wu
c3a263ff97 shell: support ":" separator
Useful for tshark.
2014-03-31 18:12:12 +02:00
Peter Wu
fe7edfe3a3 shell: extend with string functionality 2014-03-29 16:08:39 +01:00
Peter Wu
222301408b usbmon-setperms: do not grant unnecessary write
Also convert tabs to spaces.
2014-03-18 21:04:09 +01:00
Peter Wu
6a1cf3b75d hidpp20: add more possible features
Added on 24 January 2014, it is possible that features from newer
versions are missing.
2014-02-19 11:14:11 +01:00
Peter Wu
1938dc08d1 Warn when CONFIG_HIDRAW is disabled 2014-02-19 11:13:40 +01:00
Peter Wu
f6523bc6bc ltunify: Add --version (-V) option, use git version if possible 2013-09-28 16:38:04 +02:00
Peter Wu
89dce89837 ltunify: fix crash on unrecognised parameter 2013-09-28 16:37:34 +02:00
Peter Wu
8fbf5f92d2 ltunify: fix do_read failure when receiving unrelated messages
When a touchpad event occurs while the device is being queried (e.g.
for features), then do_io (for do_read) would fail because the report
was not of the correct type (0x20 vs 0x11 for example). To fix this,
make do_read retry reads (within the allowed timeout).

Previously the expected report type was hidden in the msg parameter,
make this expected report type more explicit now in a new parameter.
2013-09-28 16:19:14 +02:00
Peter Wu
bcdc930b76 hidpp20: update feature list from SetPoint 6.61.15
cases are now sorted by feature ID, some names have been adjusted for
consistency with the documented names.

New feature:
- 0x0002 FeatureInfo
- 0x6120 Touchmouse6120

Renamed fetures:
- IRoot -> Root
- IFeatureSet -> FeatureSet
- IFirmwareInfo -> DeviceFwVersion
- GetDeviceNameType -> DeviceName
- DFUControl -> Dfucontrol
- batteryLevelStatus -> BatteryStatus
- SpecialKeysMSEButtons -> ReprogControls
2013-09-28 12:27:32 +02:00
Peter Wu
09219eb747 Revert "Makefile: make user append to CFLAGS instead of overwriting it"
This reverts commit 98f9432088. This was a
lazy trick from me to allow adding `-fsanitize=address` easier, but
apparantly not all users are so happy with it.

Conflicts:
	Makefile
2013-09-04 17:42:23 +02:00
Peter Wu
c42bd6685c hidraw.c: fix permissions
Why the heck was this executable?
2013-09-04 17:40:40 +02:00
Peter Wu
9f243e3704 shell: support DJ reports 2013-08-30 22:48:32 +02:00
Peter Wu
ffaa47be57 hidraw: do not interpret report ID for unknown report type 2013-08-30 22:48:19 +02:00
Peter Wu
2e2b21d3cc ltunify: ignore DJ reports
Fixes the warning messages below:

$ ltunify unpair 2
Unexpected disconnection type 0000
Device 0x02 Touchpad successfully unpaired
$ ltunify pair
Please turn your wireless device off and on to start pairing.
Found new device, id=0x02 Touchpad
Dev conn notif is expected to be short, got 0x20 instead
Dev conn notif is expected to be short, got 0x20 instead
Dev conn notif is expected to be short, got 0x20 instead
Dev conn notif is expected to be short, got 0x20 instead
Dev conn notif is expected to be short, got 0x20 instead
2013-08-30 12:01:05 +02:00
Peter Wu
ab2cd7e619 hidraw: document DJ report types 2013-08-30 11:57:49 +02:00
Peter Wu
14c1aa6fbf shell: convenience script for accessing hidraw dev 2013-08-28 11:46:31 +02:00
Peter Wu
87c92eac32 usbmon-setperms: ignore duplicate devices 2013-08-25 16:31:37 +02:00
Peter Wu
be1199bc79 usbmon-setperms: fix when group does not match user 2013-08-25 16:29:50 +02:00
Peter Wu
7e7bbbdabd Fix missing device version information for hid10
This got broken in e2faf04 (version 0.2).
2013-08-21 00:57:05 +02:00
Peter Wu
590422bf54 hidpp20: add flag 0x20 for feature type
featureType flag 0x20 is "Reserved for Logitech internal use" according
to hidpp20 spec.
2013-08-20 12:31:39 +02:00
Peter Wu
fbfd70a51a usbmon-setperms: add helper for usbmon permissions
For unprivileged use of usbmon.
2013-08-06 15:55:25 +02:00
Peter Wu
9591ee2254 Fix wrong names for HID++ 1.0 error codes 2013-08-06 15:45:19 +02:00
8 changed files with 478 additions and 113 deletions

View File

@@ -1,4 +1,4 @@
override CFLAGS := -g -O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector --param ssp-buffer-size=4 $(CFLAGS)
CFLAGS ?= -g -O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector --param ssp-buffer-size=4
# for install-home
BINDIR ?= $(HOME)/bin
@@ -9,6 +9,13 @@ udevrulesdir ?= /etc/udev/rules.d
udevrule = 42-logitech-unify-permissions.rules
PACKAGE_VERSION ?= $(shell git describe --dirty 2>/dev/null | sed s/^v//)
ifeq (PACKAGE_VERSION, "")
LTUNIFY_DEFINES :=
else
LTUNIFY_DEFINES := -DPACKAGE_VERSION=\"$(PACKAGE_VERSION)\"
endif
%: %.c
$(CC) $(CFLAGS) -o $(OUTDIR)$@ $<
@@ -17,6 +24,7 @@ all: ltunify read-dev-usbmon
read-dev-usbmon: read-dev-usbmon.c hidraw.c
ltunify: ltunify.c hidpp20.c
$(CC) $(CFLAGS) -o $(OUTDIR)$@ $< -lrt $(LTUNIFY_DEFINES)
.PHONY: all clean install-home install install-udevrule uninstall
clean:

12
NEWS
View File

@@ -1,3 +1,15 @@
Version 0.3 - 14 June 2020
* ltunify: fix device version reporting for HID++ 1.0 devices.
* ltunify: ignore DJ reports that resulted in messages such as "Dev conn notif
is expected to be short, got 0x20 instead".
* ltunify: report some more HID++ 2.0 feature names.
* ltunify: fix failures when another event (such as touchpad motions) occur
between sending the command and reading the response.
* ltunify: fix crash on unrecognised command-line parameter.
* ltunify: add --version option.
* ltunify: add support for Nano Receiver c534 as used by the MK270 combo.
Version 0.2 - 23 July 2013
* ltunify: display HID++ version and supported HID++ 2.0 features for `info`

View File

@@ -18,9 +18,10 @@ struct hidpp2_message {
struct feature {
uint16_t featureId;
#define FEAT_TYPE_MASK 0xc0
#define FEAT_TYPE_MASK 0xe0
#define FEAT_TYPE_OBSOLETE 0x80
#define FEAT_TYPE_SWHIDDEN 0x40
#define FEAT_TYPE_RSVD_INTERNAL 0x20
u8 featureType;
};
@@ -32,34 +33,50 @@ struct feature {
static
const char *
get_feature_name(uint16_t featureId) {
/* With '?' prefix are taken from SetPointP/KEMUI.xml */
switch (featureId) {
case 0x0000: return "IRoot";
case FID_IFEATURESET: return "IFeatureSet";
case 0x0003: return "IFirmwareInfo";
case 0x0005: return "GetDeviceNameType";
case 0x1000: return "batteryLevelStatus";
case 0x1B00: return "SpecialKeysMSEButtons";
case 0x1D4B: return "WirelessDeviceStatus";
/* from FeaturesSupported.xml */
case 0x0000: return "Root";
case 0x0001: return "FeatureSet";
case 0x0002: return "FeatureInfo";
case 0x0003: return "DeviceFwVersion";
case 0x0005: return "DeviceName";
case 0x0006: return "DeviceGroups";
case 0x00C0: return "Dfucontrol"; /* Firmware Update */
case 0x1000: return "BatteryStatus";
case 0x1900: return "?SoundNotif"; /* Sound Notification */
case 0x1920: return "?AudioControls"; /* Audio Controls */
case 0x1940: return "?VOIP"; /* Internet Telephony */
case 0x1960: return "?VideoCalling";
case 0x1980: return "?Backlighting";
case 0x1981: return "Backlight";
case 0x1B00: return "ReprogControls";
case 0x1B01: return "ReprogControlsV2";
case 0x1B03: return "ReprogControlsV3";
case 0x1D4B: return "WirelessDeviceStatus"; /* Wireless Update */
case 0x2000: return "?MouseFeature"; /* Pointing Device Feature */
case 0x2001: return "LeftRightSwap";
case 0x2100: return "VerticalScrolling";
case 0x2120: return "HiResScrolling";
case 0x2200: return "MousePointer";
case 0x2510: return "?ProfileMgmt"; /* Profile Management */
case 0x4000: return "?KeyboardFeature";
case 0x40A0: return "FnInversion";
case 0x40A2: return "NewFnInversion";
case 0x4100: return "Encryption";
case 0x4301: return "SolarDashboard";
case 0x4520: return "KeyboardLayout";
case 0x00C0: return "DFUControl";
case 0x6010: return "TouchpadFwItems";
case 0x6011: return "TouchpadSwItems";
case 0x2001: return "LeftRightSwap";
case 0x1981: return "Backlight";
case 0x4400: return "?DisplayFeature";
case 0x4520: return "KeyboardLayout"; /* Inactive key */
case 0x5500: return "?SliderControls";
case 0x6000: return "?TouchpadFeature";
case 0x6010: return "TouchpadFwItems"; /* Basic Touchpad settings */
case 0x6011: return "TouchpadSwItems"; /* Enhanced Touchpad settings */
case 0x6012: return "TouchpadWin8FwItems";
case 0x40A2: return "NewFnInversion";
case 0x6020: return "?TouchpadTapSelect"; /* Tap to select */
case 0x6030: return "?DisablePointerAccel"; /* Disable pointer acceleration */
case 0x6100: return "TouchpadRawXy";
case 0x6110: return "TouchmouseRawPoints";
case 0x0006: return "DeviceGroups";
case 0x6120: return "Touchmouse6120";
case 0x6300: return "?HandwritingRecog"; /* Handwriting recognition" */
default: return "unknown";
}
}
@@ -158,9 +175,10 @@ hidpp20_print_features(int fd, u8 device_index) {
for (i = 0; i <= count; i++) {
struct feature feat;
if (get_featureId(fd, device_index, ifeatIndex, i, &feat)) {
printf(" %2i: [%04X] %c%c %s\n", i, feat.featureId,
printf(" %2i: [%04X] %c%c%c %s\n", i, feat.featureId,
feat.featureType & FEAT_TYPE_OBSOLETE ? 'O' : ' ',
feat.featureType & FEAT_TYPE_SWHIDDEN ? 'H' : ' ',
feat.featureType & FEAT_TYPE_RSVD_INTERNAL ? 'I' : ' ',
get_feature_name(feat.featureId));
if (feat.featureType & ~FEAT_TYPE_MASK) {
printf("Warning: unrecognized feature flags: %#04x\n",
@@ -170,5 +188,6 @@ hidpp20_print_features(int fd, u8 device_index) {
fprintf(stderr, "Failed to get feature, is device connected?\n");
}
}
puts("(O = obsolete feature; H = SW hidden feature)");
puts("(O = obsolete feature; H = SW hidden feature;\n"
" I = reserved for internal use)");
}

88
hidraw.c Executable file → Normal file
View File

@@ -24,11 +24,16 @@
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
typedef unsigned char u8;
#define SHORT_MSG 0x10
#define SHORT_MSG_LEN 7
#define DJ_SHORT 0x20
#define DJ_SHORT_LEN 15
#define DJ_LONG 0x21
#define DJ_LONG_LEN 32
#define LONG_MSG 0x11
#define LONG_MSG_LEN 20
@@ -50,6 +55,7 @@ struct report {
};
} __attribute__((__packed__));
/* types for HID++ report IDs 0x10 and 0x11 */
static const char * report_types[0x100] = {
[0x00] = "_HIDPP20", // fake type
// 0x00 - 0x3F HID reports
@@ -80,21 +86,43 @@ static const char * report_types[0x100] = {
[0xFF] = "_HIDPP20_ERROR_MSG",
};
/* types for DJ report IDS 0x20 and 0x21 */
static const char *dj_report_types[0x100] = {
/* 0x00 - 0x3F: RF reports */
[0x01] = "KEYBOARD",
[0x02] = "MOUSE",
[0x03] = "CONSUMER_CONTROL",
[0x04] = "SYSTEM_CONTROL",
[0x08] = "MEDIA_CENTER",
[0x0E] = "KEYBOARD_LEDS",
/* 0x40 - 0x7F: DJ notifications */
[0x40] = "NOTIF_DEVICE_UNPAIRED",
[0x41] = "NOTIF_DEVICE_PAIRED",
[0x42] = "NOTIF_CONNECTION_STATUS",
[0x7F] = "NOTIF_ERROR",
/* 0x80 - 0xFF: DJ commands */
[0x80] = "CMD_SWITCH_N_KEEPALIVE",
[0x81] = "CMD_GET_PAIRED_DEVICES"
};
static const char * error_messages[0x100] = {
// error messages for type=8F (ERROR_MSG)
[0x01] = "SUCCESS",
[0x02] = "INVALID_SUBID",
[0x03] = "INVALID_ADDRESS",
[0x04] = "INVALID_VALUE",
[0x05] = "CONNECT_FAIL",
[0x06] = "TOO_MANY_DEVICES",
[0x07] = "ALREADY_EXISTS",
[0x08] = "BUSY",
[0x09] = "UNKNOWN_DEVICE",
[0x0a] = "RESOURCE_ERROR",
[0x0b] = "REQUEST_UNAVAILABLE",
[0x0c] = "INVALID_PARAM_VALUE",
[0x0d] = "WRONG_PIN_CODE",
[0x00] = "SUCCESS",
[0x01] = "INVALID_SUBID",
[0x02] = "INVALID_ADDRESS",
[0x03] = "INVALID_VALUE",
[0x04] = "CONNECT_FAIL",
[0x05] = "TOO_MANY_DEVICES",
[0x06] = "ALREADY_EXISTS",
[0x07] = "BUSY",
[0x08] = "UNKNOWN_DEVICE",
[0x09] = "RESOURCE_ERROR",
[0x0A] = "REQUEST_UNAVAILABLE",
[0x0B] = "INVALID_PARAM_VALUE",
[0x0C] = "WRONG_PIN_CODE",
};
// I don't know the upper bound, perhaps 0x10 is enough
@@ -125,8 +153,19 @@ static const char * registers[0x100] = {
[0xf1] = "VERSION_INFO?",
};
const char * report_type_str(u8 type) {
const char * str = report_types[type];
bool report_type_is_hidpp(u8 report_id) {
return report_id == SHORT_MSG || report_id == LONG_MSG;
}
bool report_type_is_dj(u8 report_id) {
return report_id == DJ_SHORT || report_id == DJ_LONG;
}
const char * report_type_str(u8 report_id, u8 type) {
const char *str = NULL;
if (report_type_is_hidpp(report_id))
str = report_types[type];
else if (report_type_is_dj(report_id))
str = dj_report_types[type];
return str ? str : "";
}
const char * device_type_str(u8 type) {
@@ -160,6 +199,7 @@ void process_msg_payload(struct report *r, u8 data_len) {
pos = 0; // nothing has been processed
if (report_type_is_hidpp(r->report_id))
switch (r->sub_id) {
case 0x00: // assume HID++ 2.0 request/response for feature IRoot
if (data_len == 4 || data_len == 17) {
@@ -179,7 +219,7 @@ void process_msg_payload(struct report *r, u8 data_len) {
break;
case 0x8F: // error
// TODO: length check
printf("SubID=%02X %s ", bytes[0], report_type_str(bytes[0]));
printf("SubID=%02X %s ", bytes[0], report_type_str(r->report_id, bytes[0]));
printf("reg=%02X %s ", bytes[1], register_str(bytes[1]));
printf("err=%02X %s ", bytes[2], error_str(bytes[2]));
pos = 4; // everything is processed
@@ -223,6 +263,20 @@ void process_msg(struct report *report, ssize_t size) {
return;
}
break;
case DJ_SHORT:
report_type = "dj_s";
if (size != DJ_SHORT_LEN) {
fprintf(stderr, "Invalid DJ short msg len %zi\n", size);
return;
}
break;
case DJ_LONG:
report_type = "dj_l";
if (size != DJ_LONG_LEN) {
fprintf(stderr, "Invalid DJ long msg len %zi\n", size);
return;
}
break;
default:
report_type = "unkn";
//fprintf(stderr, "Unknown report ID %02x, len=%zi\n", report->report_id, size);
@@ -236,7 +290,7 @@ void process_msg(struct report *report, ssize_t size) {
printf("device=%02X %-4s ", report->device_index,
device_type_str(report->device_index));
printf("type=%02X %-23s ", report->sub_id,
report_type_str(report->sub_id));
report_type_str(report->report_id, report->sub_id));
if (size > 3) {
process_msg_payload(report, size - 3);

247
ltunify.c
View File

@@ -31,20 +31,24 @@
#include <getopt.h> /* for getopt_long */
#include <poll.h>
#include <libgen.h> /* for basename, used during discovery */
#include <time.h> /* needs -lrt, for clock_gettime as timeout helper */
#include <stdarg.h>
#ifndef PACKAGE_VERSION
# define PACKAGE_VERSION "0.2"
# define PACKAGE_VERSION "0.3"
#endif
#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
// pass -D option to print very verbose details like protocol communication
static bool debug_enabled;
#define DPRINTF(...) if (debug_enabled) { fprintf(stderr, __VA_ARGS__); }
typedef unsigned char u8;
#define VID_LOGITECH 0x046d
#define PID_NANO_RECEIVER 0xc52f
#define PID_NANO_RECEIVER_2 0xc534
#define HEADER_SIZE 3
#define SHORT_MESSAGE 0x10
@@ -134,6 +138,7 @@ struct msg_dev_name {
struct notif_devcon {
#define DEVCON_PROT_UNIFYING 0x04
#define DEVCON_PROT_NANO_LITE 0x0a
u8 prot_type; // bits 0..2 is protocol type (4 for unifying), 3..7 is reserved
#define DEVCON_DEV_TYPE_MASK 0x0f
// Link status: 0 is established (in range), 1 is not established (out of range)
@@ -225,19 +230,19 @@ struct receiver_info receiver;
// error messages for type=8F (ERROR_MSG)
static const char * error_messages[0x100] = {
[0x01] = "SUCCESS",
[0x02] = "INVALID_SUBID",
[0x03] = "INVALID_ADDRESS",
[0x04] = "INVALID_VALUE",
[0x05] = "CONNECT_FAIL",
[0x06] = "TOO_MANY_DEVICES",
[0x07] = "ALREADY_EXISTS",
[0x08] = "BUSY",
[0x09] = "UNKNOWN_DEVICE",
[0x0a] = "RESOURCE_ERROR",
[0x0b] = "REQUEST_UNAVAILABLE",
[0x0c] = "INVALID_PARAM_VALUE",
[0x0d] = "WRONG_PIN_CODE",
[0x00] = "SUCCESS",
[0x01] = "INVALID_SUBID",
[0x02] = "INVALID_ADDRESS",
[0x03] = "INVALID_VALUE",
[0x04] = "CONNECT_FAIL",
[0x05] = "TOO_MANY_DEVICES",
[0x06] = "ALREADY_EXISTS",
[0x07] = "BUSY",
[0x08] = "UNKNOWN_DEVICE",
[0x09] = "RESOURCE_ERROR",
[0x0A] = "REQUEST_UNAVAILABLE",
[0x0B] = "INVALID_PARAM_VALUE",
[0x0C] = "WRONG_PIN_CODE",
};
static const char * device_type[0x10] = {
@@ -306,18 +311,25 @@ static void dump_msg(struct hidpp_message *msg, size_t payload_size, const char
fflush(NULL);
}
static ssize_t do_io(int fd, struct hidpp_message *msg, bool is_write, int timeout) {
ssize_t r;
size_t payload_size = SHORT_MESSAGE_LEN;
static long long unsigned get_timestamp_ms(void) {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
}
if (msg->report_id == LONG_MESSAGE) {
payload_size = LONG_MESSAGE_LEN;
}
#include <execinfo.h>
static ssize_t do_read(int fd, struct hidpp_message *msg, u8 expected_report_id, int timeout) {
ssize_t r;
size_t payload_size = LONG_MESSAGE_LEN;
long long unsigned begin_ms, end_ms;
if (is_write) {
dump_msg(msg, payload_size, "wr");
r = write(fd, msg, payload_size);
} else {
if (expected_report_id == SHORT_MESSAGE) {
payload_size = SHORT_MESSAGE_LEN;
}
begin_ms = get_timestamp_ms();
while (timeout > 0) {
struct pollfd pollfd;
pollfd.fd = fd;
pollfd.events = POLLIN;
@@ -327,32 +339,55 @@ static ssize_t do_io(int fd, struct hidpp_message *msg, bool is_write, int timeo
perror("poll");
return 0;
} else if (r == 0) {
DPRINTF("poll timeout reached!\n");
// timeout
return 0;
}
memset(msg, 0, payload_size);
r = read(fd, msg, payload_size);
if (r >= 0) {
int saved_errno = errno;
if (r < 0) {
perror("read");
return 0;
} else if (r > 0) {
dump_msg(msg, r, "rd");
errno = saved_errno;
if (msg->report_id == expected_report_id) {
return r;
} else if (expected_report_id == 0 &&
(msg->report_id == SHORT_MESSAGE ||
msg->report_id == LONG_MESSAGE)) {
/* HACK: ping response for HID++ 2.0 is a LONG
* message, but for HID++ 1.0 it is a SHORT one. */
return r;
} else {
DPRINTF("Skipping unexpected report ID %#x (want %#x)\n",
msg->report_id, expected_report_id);
}
}
}
if ((size_t) r == payload_size
|| (msg->report_id == SUB_ERROR_MSG && r == SHORT_MESSAGE_LEN)) {
return payload_size;
/* unexpected message, try again with updated timeout */
end_ms = get_timestamp_ms();
timeout -= end_ms - begin_ms;
begin_ms = end_ms;
}
if (r < 0) {
perror(is_write ? "write" : "read");
}
/* timeout expired, no report found unfortunately */
return 0;
}
static ssize_t do_read(int fd, struct hidpp_message *msg, int timeout) {
return do_io(fd, msg, false, timeout);
}
static ssize_t do_write(int fd, struct hidpp_message *msg) {
return do_io(fd, msg, true, 0);
ssize_t r, payload_size = SHORT_MESSAGE_LEN;
if (msg->report_id == LONG_MESSAGE) {
payload_size = LONG_MESSAGE_LEN;
}
dump_msg(msg, payload_size, "wr");
r = write(fd, msg, payload_size);
if (r < 0) {
perror("write");
}
return payload_size == r ? payload_size : 0;
}
const char *get_report_id_str(u8 report_type) {
@@ -381,7 +416,8 @@ bool process_notif_dev_connect(struct hidpp_message *msg, u8 *device_index,
"%#04x instead\n", msg->report_id);
return false;
}
if (dcon->prot_type != DEVCON_PROT_UNIFYING) {
if (dcon->prot_type != DEVCON_PROT_UNIFYING &&
dcon->prot_type != DEVCON_PROT_NANO_LITE) {
fprintf(stderr, "Unknown protocol %#04x in devcon notif\n",
dcon->prot_type);
return false;
@@ -395,7 +431,7 @@ bool process_notif_dev_connect(struct hidpp_message *msg, u8 *device_index,
if (device_index) *device_index = dev_idx;
if (is_new_device) *is_new_device = !dev->device_present;
memset(&devices[dev_idx], 0, sizeof devices[dev_idx]);
memset(dev, 0, sizeof *dev);
dev->device_type = dcon->device_info & DEVCON_DEV_TYPE_MASK;
dev->wireless_pid = (dcon->pid_msb << 8) | dcon->pid_lsb;
dev->device_present = true;
@@ -407,13 +443,18 @@ bool process_notif_dev_connect(struct hidpp_message *msg, u8 *device_index,
static bool do_read_skippy(int fd, struct hidpp_message *msg,
u8 exp_report_id, u8 exp_sub_id) {
for (;;) {
msg->report_id = exp_report_id;
if (!do_read(fd, msg, 2000)) {
if (!do_read(fd, msg, exp_report_id, 2000)) {
return false;
}
if (msg->report_id == exp_report_id && msg->sub_id == exp_sub_id) {
return true;
}
/* ignore non-HID++ reports (e.g. DJ reports) */
if (msg->report_id != SHORT_MESSAGE && msg->report_id != LONG_MESSAGE) {
continue;
}
// guess: 0xFF is error message in HID++ 2.0?
if (msg->report_id == LONG_MESSAGE && msg->sub_id == 0xFF) {
if (debug_enabled) {
@@ -647,8 +688,7 @@ void perform_pair(int fd, u8 timeout) {
puts("Please turn your wireless device off and on to start pairing.");
// WARNING: mess ahead. I knew it would become messy before writing it.
for (;;) {
msg.report_id = SHORT_MESSAGE;
if (!do_read(fd, &msg, timeout * 1000 + 2000)) {
if (!do_read(fd, &msg, SHORT_MESSAGE, timeout * 1000 + 2000)) {
fprintf(stderr, "Failed to read short message\n");
break;
}
@@ -738,11 +778,10 @@ bool get_receiver_info(int fd, struct receiver_info *rinfo) {
params[0] = 0x03; // undocumented
if (get_long_register(fd, DEVICE_RECEIVER, REG_PAIRING_INFO, params, &msg)) {
struct msg_receiver_info *info = (struct msg_receiver_info *) &msg.msg_long.str;
uint32_t *serial_numberp;
uint32_t serial_number;
serial_numberp = (uint32_t *) &info->serial_number;
rinfo->serial_number = ntohl(*serial_numberp);
memcpy(&serial_number, &info->serial_number, sizeof(serial_number));
rinfo->serial_number = ntohl(serial_number);
return true;
}
return false;
@@ -770,12 +809,11 @@ bool get_device_ext_pair_info(int fd, u8 device_index) {
params[0] = 0x30 | (device_index - 1); // 0x30..0x3F Unifying Device extended pairing info
if (get_long_register(fd, DEVICE_RECEIVER, REG_PAIRING_INFO, params, &msg)) {
struct msg_dev_ext_pair_info *info;
uint32_t *serial_numberp;
uint32_t serial_number;
info = (struct msg_dev_ext_pair_info *) &msg.msg_long.str;
serial_numberp = (uint32_t *) &info->serial_number;
dev->serial_number = ntohl(*serial_numberp);
memcpy(&serial_number, &info->serial_number, sizeof(serial_number));
dev->serial_number = ntohl(serial_number);
dev->power_switch_location = info->usability_info & 0x0F;
return true;
}
@@ -818,7 +856,7 @@ bool get_hidpp_version(int fd, u8 device_index, struct hidpp_version *version) {
return false;
}
for (;;) {
if (!do_read(fd, &msg, 3000)) {
if (!do_read(fd, &msg, 0, 3000)) {
if (debug_enabled) {
fprintf(stderr, "Failed to read HID++ version, device does not respond!\n");
}
@@ -905,7 +943,7 @@ void gather_device_info(int fd, u8 device_index) {
get_hidpp_version(fd, device_index, &dev->hidpp_version);
get_device_ext_pair_info(fd, device_index);
get_device_name(fd, device_index);
if (dev->hidpp_version.major == 0 && dev->hidpp_version.minor == 0) {
if (dev->hidpp_version.major == 1 && dev->hidpp_version.minor == 0) {
if (get_device_versions(fd, device_index, &dev->version)) {
dev->device_available = true;
}
@@ -974,6 +1012,13 @@ void get_device_names(int fd) {
}
}
}
static void print_version(void) {
fprintf(stderr,
"Logitech Unifying tool version " PACKAGE_VERSION "\n"
"Copyright (C) 2013 Peter Wu <lekensteyn@gmail.com>\n");
}
void print_all_devices(void) {
unsigned i;
puts("Connected devices:");
@@ -989,9 +1034,10 @@ void print_all_devices(void) {
}
static void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] cmd [cmd options]\n"
"Logitech Unifying tool version %s\n"
"Copyright (C) 2013 Peter Wu <lekensteyn@gmail.com>\n"
fprintf(stderr, "Usage: %s [options] cmd [cmd options]\n",
program_name);
print_version();
fprintf(stderr,
"\n"
"Generic options:\n"
" -d, --device path Bypass detection, specify custom hidraw device.\n"
@@ -1007,8 +1053,7 @@ static void print_usage(const char *program_name) {
" receiver-info - Show information about the receiver\n"
"In the above lines, \"idx\" refers to the device number shown in the\n"
" first column of the list command (between 1 and 6). Alternatively, you\n"
" can use the following names (case-insensitive):\n"
, program_name, PACKAGE_VERSION);
" can use the following names (case-insensitive):\n");
print_device_types();
}
@@ -1031,11 +1076,13 @@ static int validate_args(int argc, char **argv, char ***argsp, char **hidraw_pat
struct option longopts[] = {
{ "device", 1, NULL, 'd' },
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ 0, 0, 0, 0 },
};
*argsp = NULL;
while ((opt = getopt_long(argc, argv, "+Dd:h", longopts, NULL)) != -1) {
while ((opt = getopt_long(argc, argv, "+Dd:hV", longopts, NULL)) != -1) {
switch (opt) {
case 'D':
debug_enabled = true;
@@ -1043,6 +1090,9 @@ static int validate_args(int argc, char **argv, char ***argsp, char **hidraw_pat
case 'd':
*hidraw_path = optarg;
break;
case 'V':
print_version();
return 0;
case 'h':
print_usage(*argv);
return 0;
@@ -1090,6 +1140,21 @@ static int validate_args(int argc, char **argv, char ***argsp, char **hidraw_pat
return args_count;
}
#ifdef __GNUC__
static FILE *fopen_format(const char *format, ...)
__attribute__((format(printf, 1, 2)));
#endif
static FILE *fopen_format(const char *format, ...) {
char buf[1024];
va_list ap;
va_start(ap, format);
vsnprintf(buf, sizeof buf, format, ap);
va_end(ap);
return fopen(buf, "r");
}
#define RECEIVER_NAME "logitech-djreceiver"
int open_hidraw(void) {
int fd = -1;
@@ -1104,6 +1169,8 @@ int open_hidraw(void) {
char *name = matches.gl_pathv[i];
const char *last_comp;
char *dev_name;
FILE *fp;
uint32_t vid = 0, pid = 0;
r = readlink(name, buf, (sizeof buf) - 1);
if (r < 0) {
@@ -1118,25 +1185,49 @@ int open_hidraw(void) {
dev_name = name + sizeof "/sys/class/hidraw";
*(strchr(dev_name, '/')) = 0;
// Assume that the first match is the receiver. Devices bound to the
// same receiver may have the same modalias.
if ((fp = fopen_format("/sys/class/hidraw/%s/device/modalias", dev_name))) {
int m = fscanf(fp, "hid:b%*04Xg%*04Xv%08Xp%08X", &vid, &pid);
if (m != 2) {
pid = 0;
}
fclose(fp);
}
if (vid != VID_LOGITECH) {
continue;
}
if (!strcmp(last_comp, RECEIVER_NAME)) {
/* Logitech receiver c52b and c532 - pass */
/* Logitech receiver c52b and c532 - pass.
*
* Logitech Nano receiver c534 however has
* multiple hidraw devices, but the first one is
* only used for keyboard events and should be
* ignored. The second one is for the mouse, and
* that interface has a vendor-specific HID page
* for HID++.
* Parsing .../device/report_descriptor is much
* more complicated, so stick with knowledge
* about device-specific interfaces for now.
*/
if (pid == PID_NANO_RECEIVER_2) {
int iface = -1;
if ((fp = fopen_format("/sys/class/hidraw/%s/device/../bInterfaceNumber", dev_name))) {
int m = fscanf(fp, "%02x", &iface);
if (m != 1) {
iface = -1;
}
fclose(fp);
}
if (iface == 0) {
/* Skip first interface. */
continue;
}
}
} else if (!strcmp(last_comp, "hid-generic")) {
/* need to test for older nano receiver c52f */
FILE *fp;
uint32_t vid = 0, pid = 0;
// Assume that the first match is the receiver. Devices bound to the
// same receiver may have the same modalias.
snprintf(buf, sizeof buf, "/sys/class/hidraw/%s/device/modalias", dev_name);
if ((fp = fopen(buf, "r"))) {
int m = fscanf(fp, "hid:b%*04Xg%*04Xv%08Xp%08X", &vid, &pid);
if (m != 2) {
pid = 0;
}
fclose(fp);
}
if (vid != VID_LOGITECH || pid != PID_NANO_RECEIVER) {
if (pid != PID_NANO_RECEIVER) {
continue;
}
} else { /* unknown driver */
@@ -1160,6 +1251,10 @@ int open_hidraw(void) {
"for %s\n", hiddev_name);
} else {
fprintf(stderr, "No Logitech Unifying Receiver device found\n");
if (access("/sys/class/hidraw", R_OK)) {
fputs("The kernel must have CONFIG_HIDRAW enabled.\n",
stderr);
}
if (access("/sys/module/hid_logitech_dj", F_OK)) {
fprintf(stderr, "Driver is not loaded, try:"
" sudo modprobe hid-logitech-dj\n");

134
shell Executable file
View File

@@ -0,0 +1,134 @@
#!/bin/bash
# Utilities for HID++ accesses.
#
# Author: Peter Wu <lekensteyn@gmail.com>
hidw() {
local hiddev arg bytes fillchar length oIFS actualArgs
hiddev=$1; shift
bytes=()
case $hiddev in
''|-h|--help|-?)
cat <<HELP
Usage: hidw /dev/hidrawX [data]
data are hexadecimal numbers in the range 0x0-0xff ('0x' prefix is
optional). Missing bytes for HID++ reports (0x10 and 0x11) and DJ
reports (0x20 and 0x21) will be padded by zeroes unless you end with ..
as in the following (non-meaningful) example:
hidw /dev/hidraw0 10 ff 81 ff..
In this case, the message is padded with '0xff'.
The data can also be interleaved with chars by starting an argument
with a underscore (_). For example, "_foo" is equivalent to "66 6f 6f".
A colon can also be used as separator, the previous hidw example is
equivalent to:
hidw /dev/hidraw0 10:ff 81:ff..
This function does not show replies, use the 'read-dev-usbmon' program
instead.
HELP
return 1 ;;
esac
if [ ! -c "$hiddev" ]; then
echo "$hiddev: not a block special"
return 1
fi
# Support ":" as separator, but ignore string arguments (_...).
actualArgs=()
for arg; do
if [[ $arg != _* ]] && [[ $arg == *:* ]]; then
# Strip leading+trailing such that "11:02: 83 01" is still valid.
arg="${arg##:}"
arg="${arg%%:}"
oIFS="$IFS"; IFS=:; actualArgs+=( $arg ); IFS="$oIFS"
else
actualArgs+=( "$arg" )
fi
done
set -- "${actualArgs[@]}"
for arg; do
# clear the fill char, makes only sense as last argument
fillchar=
if [[ $arg =~ ^(0[xX])?[0-9a-fA-F]{1,2}(\.\.)?$ ]]; then
byte=${arg#0[Xx]}
byte=${byte%..}
[ ${#byte} = 2 ] || byte=0$byte
bytes+=($byte)
# extend with last byte
[[ $arg != *.. ]] || fillchar=$byte
elif [[ $arg =~ ^_ ]]; then
# Literal string
bytes+=( $(echo -n "${arg#_}" | xxd -ps | fold -w2) )
else
echo "Invalid argument: $arg"
return 1
fi
done
case ${bytes[0]} in
10) length=7 ;;
11) length=20 ;;
20) length=15 ;;
21) length=32 ;;
*) echo "Unknown report type ${bytes[0]}, not padding " ;;
esac
if [ $length ]; then
[ -n "$fillchar" ] || fillchar=00
while [ ${#bytes[@]} -lt $length ]; do
bytes+=($fillchar)
done
if [ ${#bytes[@]} -gt $length ]; then
echo "Too many arguments: ${#bytes[@]} > $length"
return 1
fi
fi
echo "${bytes[@]}" | xxd -ps -r > "$hiddev"
}
_hidpp_main() {
local cmd
local cmds=(hidw)
cmd=$1; shift
for c in "${cmds[@]}"; do
if [[ $cmd == $c ]]; then
$cmd "$@"
return
fi
done
case $cmd in
''|-h|--help|-?)
cat <<HELP
Usage: $0 command [command args]
Available commands:
${cmds[*]}
You can also source this file to export those functions.
HELP
;;
*)
echo "Unknown command, invoke with no arguments for help."
;;
esac
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
_hidpp_main "$@"
fi
# vim: set syntax=sh:

View File

@@ -12,6 +12,9 @@ SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c532", GOTO="un
# "Unifying Ready" Nano receiver
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52f", GOTO="unify_dev"
# Nano receiver for devices such as the MK270 mouse and keyboard combo
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c534", GOTO="unify_dev"
GOTO="not_unify_recv"
LABEL="unify_dev"

40
usbmon-setperms Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/sh
# helper for allowing the developer to read usbmon as non-root.
if [ "$USER" = "root" ]; then
echo "Please run as regular user or set USER"
exit 1
fi
GROUP="$(id -g)"
if [ $GROUP = 0 ]; then
GROUP="$(id -g "$USER")"
fi
if [ ! -e /dev/usbmon0 ]; then
echo "Attempting to modprobe usbmon..."
sudo modprobe -v usbmon
if [ ! -e /dev/usbmon0 ]; then
echo "Cannot locate 'usbmon' kernel module"
exit 1
fi
fi
usbmons=$(lsusb -d046d: | cut -c7 | sed 's,^,/dev/usbmon,' | sort -u)
echo Found devices: $usbmons
usbmonsw=
for dev in $usbmons; do
if [ ! -r "$dev" ]; then
usbmonsw="$usbmonsw $dev"
fi
done
if [ -n "$usbmonsw" ]; then
echo "Attempting to make $usbmonsw readable"
sudo chgrp -v $GROUP $usbmonsw
sudo chmod -v g+r $usbmonsw
else
echo "No devices found"
fi