mirror of
https://github.com/Lekensteyn/ltunify.git
synced 2025-12-09 02:03:22 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b68dc9af6d | ||
|
|
872a781e07 | ||
|
|
b639b7f5c6 | ||
|
|
5c8283480d | ||
|
|
f664d1d41d | ||
|
|
955b9feb9e | ||
|
|
c3a263ff97 | ||
|
|
fe7edfe3a3 | ||
|
|
222301408b | ||
|
|
6a1cf3b75d | ||
|
|
1938dc08d1 | ||
|
|
f6523bc6bc | ||
|
|
89dce89837 | ||
|
|
8fbf5f92d2 | ||
|
|
bcdc930b76 | ||
|
|
09219eb747 | ||
|
|
c42bd6685c | ||
|
|
9f243e3704 | ||
|
|
ffaa47be57 | ||
|
|
2e2b21d3cc | ||
|
|
ab2cd7e619 | ||
|
|
14c1aa6fbf | ||
|
|
87c92eac32 | ||
|
|
be1199bc79 | ||
|
|
7e7bbbdabd | ||
|
|
590422bf54 | ||
|
|
fbfd70a51a | ||
|
|
9591ee2254 |
10
Makefile
10
Makefile
@@ -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
12
NEWS
@@ -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`
|
||||
|
||||
57
hidpp20.c
57
hidpp20.c
@@ -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
88
hidraw.c
Executable file → Normal 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
247
ltunify.c
@@ -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
134
shell
Executable 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:
|
||||
@@ -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
40
usbmon-setperms
Executable 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
|
||||
Reference in New Issue
Block a user