libusb / hidapi

A Simple cross-platform library for communicating with HID devices
https://libusb.info/hidapi/
Other
1.61k stars 392 forks source link

Failed to send feature report to a Gaming Mouse #174

Closed andrfgs closed 3 years ago

andrfgs commented 4 years ago

I'm using hidapi on Linux through the hidraw backend to read a Gaming Mouse (I also tried with the other backend but it also doesn't work).

I want to read the device's feature data (pretty much like the ReadFeatureData(out byte[] data, byte reportId = 0) from this library).

The only problem is, I can't. The API only returns errors upon any write/get_report. I tried the following:

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_write(dev, buf, BUFFER_SIZE);
r = hid_read(dev, buf, BUFFER_SIZE);

Write fails with -1 and Read returns just 7 bytes and crashes the device

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_get_feature_report(dev, buf, BUFFER_SIZE);

Write fails with -1 returns error ioctl (GFEATURE): Broken pipe

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_write(dev, buf, BUFFER_SIZE);
r = hid_get_feature_report(dev, buf, BUFFER_SIZE);

Both writes fail with -1 return error ioctl (GFEATURE): Broken pipe

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_send_feature_report(dev, buf, BUFFER_SIZE);
r = hid_get_feature_report(dev, buf, BUFFER_SIZE);

Both lines return -1, the first returns error ioctl (SFEATURE): Connection timed out and the second error ioctl (GFEATURE): Broken pipe

Now this should work because it works on Windows using that C# library, but it doesn't work with this library. I tried both the hidraw and libusb (libusb is run as sudo) and both fail. How can I read this device's feature data without it crashing?

The C# code (using the aforementioned library) I am trying to port to C is:

HidDevice mDevice;
byte[] data = new byte[mFeatureLen];
mDevice.ReadFeatureData(out data, 4);

This code works on Windows and returns the array properly.

jonasmalacofilho commented 4 years ago

Write fails with -1 and Read returns just 7 bytes and crashes the device

Does BUFFER_SIZE include an extra byte for the report ID?

andrfgs commented 4 years ago

Yes, I also tried a very large BUFFER_SIZE. If I use a larger BUFFER_SIZE it will only use the bytes required +1 right?

The length should be 154. I gave BUFFER_SIZE a size of 155, 200, 1024 and all fail.

jonasmalacofilho commented 4 years ago

If I use a larger BUFFER_SIZE it will only use the bytes required +1 right?

For a read, yes... writes I'm not sure, but I think the extra data is sent,¹ which can cause the device to misbehave, or at the very least refuse it.

From what you're describing, the correct alternative would be:

#define BUFFER_SIZE        155

/* ... */

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_get_feature_report(dev, buf, BUFFER_SIZE);

What additional information does hid_error(dev) return in this case (assuming r < 0)?


¹ Taking the examples you showed above literally, you could have sent random data (depending on whether the compiler saved you or not with the uninitialized buffer). But I'm guessing the real code ensured sensible buffer contents for the writes.

andrfgs commented 4 years ago

It reports (null)

I tried using the hidraw example from the linux source and it also fails. I'm guessing this is a problem with Linux not liking my mouse?

jonasmalacofilho commented 4 years ago

I'm sorry, your first post already included this ("Broken pipe").

I'm guessing this is a problem with Linux not liking my mouse?

It's possible... Related to this, are there any special drivers or quirks for this device in the kernel?

Another possibility is that the device requires some specific write(s) before you can request a feature report from it.

andrfgs commented 4 years ago

The Broken pipe comes from ioctl not the hidapi library. The hid_error() is not reporting anything.

This is a T7 Gaming Mouse (which is a cheap Chinese unit with a very shady manufacturer Sinowealth). This repository has a custom driver using a C# library on Windows. I ran it and it works on Windows, but that library makes use of Windows' own proprietary hid library through native calls.

To the best of my knowledge, the program makes no other writes prior to reading the features.

As I said, this also happened in the hidraw example above, so maybe it's a problem in hidraw itself not recognising the device properly.

One more thing, do I need to initialise the other positions of the buffer for getting the feature report? Cause from what the library says, I should just set the report id part. Still I also attempted memsetting the buffer full of 0's still to no avail.

andrfgs commented 4 years ago

Can someone help me with this problem? I'd like to know if the following C code:

unsigned char buf[BUFFER_SIZE];
int r = -1;
buf[0] = 4; 
r = hid_get_feature_report(dev, buf, BUFFER_SIZE);

Is similar or not to the C# equivalent in this repository (lines 26-32):

byte[] data = new byte[mFeatureLen];
bool success = false;
success = mDevice.ReadFeatureData(out data, 4);

The C# uses HidLibrary, which calls a native Windows method:

NativeMethods.HidD_GetFeature(hidHandle, buffer, buffer.Length);

By knowing this I can determine if the problem lies in my usage of the library or in an internal ioctl problem with my hardware.

todbot commented 4 years ago

What does your HID Report Descriptor look like?

andrfgs commented 4 years ago

I can't get the report descriptor form the hidapi library.

This is the result of running the hidraw example (which also fails to get the feature report). I basically used the code from the example and replaced the path with /dev/hidraw0:

Report Descriptor Size: 71
Report Descriptor:
5 1 9 2 a1 1 9 1 a1 0 5 9 19 1 29 5 15 0 25 1 75 1 95 5 81 2 95 3 81 1 5 1 9 30 9 31 16 0 80 26 ff 7f 75 10 95 2 81 6 9 38 15 80 25 7f 75 8 95 1 81 6 5 c a 38 2 95 1 81 6 c0 c0

By the way, I switched to libusb and whenever the program ends, the mouse stops working and I have to replug it again. This doesn't happen with hidraw.

todbot commented 4 years ago

First, if this is on Linux, are you running your code as root or have set up udev rules for it?

From that report descriptor data, parsed by https://eleccelerator.com/usbdescreqparser/ , your device looks like a kind of mouse? Generally you cannot access keyboards and mice because the OS grabs them. In order to read them, you generally have to uninstall/deregister the OS's keyboard/mouse driver so you can own the mouse.

(Sorry I came onto this issue without seeing the initial issue, you state it's a mouse and it looks like you're trying to read the position reports)

andrfgs commented 4 years ago

I am running the code as root yes, I tried to use it in user mode first but it would complain about permissions. I understand the OS may grab control over Mice and Keyboards but here's the thing: I'm on a MSI laptop, I can use (https://github.com/Gibtnix/MSIKLM) just fine, which is a program that changes my keyboard's color and uses this exact library. So I thought this should work for all devices.

It does make sense the OS grabs control of the hardware, but then it doesn't make much sense that Linux only does it for some of the hardware, not all.

Is there any way of deregistering the mouse and reregister when I'm done through hidapi or libusb? Or am I really gonna need to write a Linux driver module? By the way, I suppose Windows doesn't have this restriction?

Youw commented 4 years ago

On Windows, the restrictions are even more strict. But a physical keyboard may report more than one logical HID device, one of which may be special led controlling device (which is not a keyboard) and the restrictions won't apply. Can you confirm is that could be the case or not?

andrfgs commented 4 years ago

This is the result of lsusb

> lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 002: ID 258a:1007 SINOWEALTH Game Mouse
Bus 001 Device 006: ID 8087:0aaa Intel Corp. 
Bus 001 Device 005: ID 5986:211b Acer, Inc HD Webcam
Bus 001 Device 004: ID 1770:ff00 MSI EPF USB MSI EPF USB
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

I can use hidapi on the keyboard, using that msiklm program. The msiklm opens the device using:

hid_device* dev = NULL;
if (hid_init() == 0)
    dev = hid_open(0x1770, 0xff00, 0);

And it sets the colors using:

hid_send_feature_report(dev, buffer, 8);
However, when using `lsusb` on verbose mode, it reports it can't open the device: ```Bash > lsusb -d 1770:ff00 -v Bus 001 Device 004: ID 1770:ff00 MSI EPF USB MSI EPF USB Couldn't open device, some information will be missing Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x1770 idProduct 0xff00 bcdDevice 1.10 iManufacturer 1 iProduct 1 iSerial 1 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0022 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 2mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 33 US bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 57 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 ```
Running the same command with sudo yields more information, but it still reports the device is busy ```Bash > sudo lsusb -d 1770:ff00 -v Bus 001 Device 004: ID 1770:ff00 MSI EPF USB MSI EPF USB Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x1770 idProduct 0xff00 bcdDevice 1.10 iManufacturer 1 MSI EPF USB iProduct 1 MSI EPF USB iSerial 1 MSI EPF USB bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0022 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 2mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 33 US bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 57 Report Descriptor: (length is 57) Item(Global): Usage Page, data= [ 0xa0 0xff ] 65440 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Usage Page, data= [ 0xa1 0xff ] 65441 (null) Item(Global): Report ID, data= [ 0x01 ] 1 Item(Local ): Usage, data= [ 0x03 ] 3 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x07 ] 7 Item(Main ): Feature, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report ID, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x04 ] 4 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x00 0x01 ] 256 Item(Main ): Feature, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 can't get debug descriptor: Resource temporarily unavailable cannot read device status, Resource temporarily unavailable (11) ```

So in short, I don't think the keyboard uses a custom device for the colors, but I may be wrong.

EDIT: NOTE, THIS CODE IS FOR THE KEYBOARD DEVICE, THE DEVICE I WANT TO WORK WITH HIDAPI IS THE MOUSE NOT THE KEYBOARD. THIS WAS JUST TO SHOW HOW THE KEYBOARD WORKED.

yelsnitrk commented 4 years ago

Como vai.

I am having the same issues with hidapi on ubuntu. my program just doesn't seem to run...just error out.

Youw commented 4 years ago

What about usage/usage_page pairs? Can you check the usage/usage_page of the devices you're opening on Windows and Linux? P.S.: in order to do that, you need to hid_enumerate all devices and check corresponding fields of device_info before opening the device.

jonasmalacofilho commented 4 years ago

@andrfgs

The code that showed as a reference also deals with two devices (in that case the usage/usage page are the same, but the lengths are not):

public T7Mouse()
{
    mDevice = new T7Device(0x480);
    mGeneral = new T7Device(0x9A);
    // ...

T7 Wired Gaming Mouse/T7/T7Mouse.cs#L30-L33

public T7Device(short featureLen)
{
    mFeatureLen = featureLen;
    mDevice = HidDevices.Enumerate(0x258A, 0x1007).ToList().Find((f) =>
    {
        return (ushort)f.Capabilities.UsagePage == 0xff00 && f.Capabilities.Usage == 1
            && f.Capabilities.FeatureReportByteLength == featureLen;
    });
}

T7 Wired Gaming Mouse/T7/T7Device.cs#L16-L23


P.S. I'm not sure how Random06457's library can work with your mouse, since the vendor/product IDs don't appear to match (I'm guessing they are the parameters of HidDevices.Enumerate), and according to the lsusb output you posted later, neither does the HID descriptor: different usage, usage page and report sizes.

andrfgs commented 4 years ago

@jonasmalacofilho The first code block is not the vendor ID, that's the length of the internal buffer. I know that value is in a confusing format but the code that opens the device is this one mDevice = HidDevices.Enumerate(0x258A, 0x1007) in the second block.

@Youw In Linux, I tried hidapi enumerate with this code

    struct hid_device_info* cur = hid_enumerate(0x258A, 0x1007);
    while (cur != NULL)
    {
        wprintf(L"%S, %d:%d, %S\n", cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number);
        cur = cur->next;
    }

Which prints:

Game Mouse, 9610:4103, (null)
Game Mouse, 9610:4103, (null)

Why is the serial number null? I ran this with sudo.

jonasmalacofilho commented 4 years ago

@andrfgs

The first code block is not the vendor ID

I didn't claim that it was.

What I pointed out is that the vendor/product ID (parameters to HidDevices.Enumerate) don't match the lsusb output you posted. Neither does HID usage, usage page or any of the two report sizes.


The serial number is a USB string descriptor, but it isn't required (according to the USB specification).

andrfgs commented 4 years ago

OK, perhaps I didn't explain myself properly. The device in question is: Bus 001 Device 002: ID 258a:1007 SINOWEALTH Game Mouse

The code I showed above:

hid_device* dev = NULL;
if (hid_init() == 0)
    dev = hid_open(0x1770, 0xff00, 0);

was to show the Keyboard worked in hidapi but not the mouse. I just showed it because someone asked me about it. And also to try to find why one works and the other doesn't. Perhaps I should've been more explicit about it.

The mouse vendor id is 0x258A and the product id is 0x1007

The REAL CODE I'm trying to use is this:

hid_device* dev = NULL;
if (hid_init() == 0)
    dev = hid_open(0x258A, 0x1007, 0);
andrfgs commented 4 years ago

By the way, hid_enumerate is reporting the same numbers (in decimal), but when I try to:

    struct hid_device_info* cur = hid_enumerate(0x258A, 0x1007);
    while (cur != NULL)
    {
        wprintf(L"%S, %S, %d:%d, %S\n", cur->manufacturer_string, cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number);
        cur = cur->next;
    }
    dev = hid_open(cur->vendor_id, cur->product_id, NULL);

    unsigned char buf[BUFFER_SIZE];
    memset(&buf, 0x0, BUFFER_SIZE);
    int r = -1;
    buf[0] = 4;
    r = hid_get_feature_report(dev, buf, BUFFER_SIZE);
    wprintf(L"Read a total of %d bytes\n", r);

It results in a Segmentation Fault. Am I using enumerate wrong?

EDIT: Nevermind, this was a stupid mistake, I was trying to access cur which is NULL after the iterations. Here is the fixed code:

    struct hid_device_info* cur = hid_enumerate(0x258A, 0x1007);
    struct hid_device_info* devs[10];
    int i = 0;
    while (cur != NULL)
    {
        wprintf(L"%S, %S, %d:%d, %S\n", cur->manufacturer_string, cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number);
        devs[i++] = cur;
        cur = cur->next;
    }
    dev = hid_open(devs[0]->vendor_id, devs[0]->product_id, NULL);

    unsigned char buf[BUFFER_SIZE];
    memset(&buf, 0x0, BUFFER_SIZE);
    int r = -1;
    buf[0] = 4;
    r = hid_get_feature_report(dev, buf, BUFFER_SIZE);
    wprintf(L"Read a total of %d bytes\n", r);

The opening works but hid_get_feature_report aborts with T7GammingMouseTest: io.c:2146: handle_events: Assertion `ctx->pollfds_cnt >= internal_nfds' failed.

EDIT2: The same happens if I use devs[1]

jonasmalacofilho commented 4 years ago

I suppose it is surprising that you posted the lsusb -v output for a device other than the mouse, especially when the HID descriptor was being discussed. But you did mention the keyboard (earlier) in that comment, so... mea culpa.


Which prints:

Game Mouse, 9610:4103, (null)
Game Mouse, 9610:4103, (null)

The next step would be to check which of those accepts the report you're trying to send.

As far as I know, on Linux each HID interface is mapped to a single HID device, unlike in Windows and/or Mac, where each usage/usage page pair becomes a separate "logical HID". (I could be wrong here, someone please correct me if necessary).

This difference could also explain why the HID descriptor you posted doesn't appear to match Random06457's code; the second interface would have a different descriptor that would more closely match that code.

(The sudo lsusb -v output for the mouse would confirm if there are indeed two interfaces).

Regardless, if I had to guess, the HID device you're interested in is probably the second one that hid_enumerate returns, since the first one isn't working ; )

Also, you still have to add one byte to your buffer size to account for the report ID in offset 0. You probably didn't forget this, but is worth mentioning since you aren't always showing what BUFFER_SIZE you're using.


The opening works but hid_get_feature_report aborts with

T7GammingMouseTest: io.c:2146: handle_events: Assertion `ctx->pollfds_cnt >= internal_nfds' failed.

Not sure what this assertion means, but this tells me that you're linking against libhidapi-libusb.so. From your mention to hidraw right at the beginning, it appeared you intended to link against libhidapi-hidraw.so.

andrfgs commented 4 years ago

Sorry for not showing BUFFER_SIZE, right now I have

#define BUFFER_SIZE 1024

The necessary size should be 154 but I had a large value just to be sure it wasn't a size problem. I changed to libusb to check if that was the problem. I was also advised to avoid hidraw for gaming mice and keyboards, so I might end up using libusb. But right now I just want one of them to work.

When using hidraw, this doesn't show that error, finishes successfully but still the feature read doesn't work.

By the way, enumerate was failing because I was accessing cur which is null after the iteration, sorry, my stupid mistake. Still, enumerate reports 2 devices with the exact same vendor and product ID's and the serial number is null in both. So nothing is there to distinguish them.

andrfgs commented 4 years ago

It's this (null) serial number reported by hidapi that concerns me. It reports nicely the keyboard, but the mouse is just null. I fear this might be due to the mouse being made by a shady Chinese manufacturer who didn't properly regard USB standards.

Here's a report of all devices found by hid_enumerate:

    struct hid_device_info* cur = hid_enumerate(0, 0);
    int i = 0;
    while (cur != NULL)
    {
        wprintf(L"%S, %S, %d:%d, Serial Number: %S, Path: %s\n", cur->manufacturer_string, cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number, cur->path);
        cur = cur->next;
    }

Output:

SINOWEALTH, Game Mouse, 9610:4103, Serial Number: (null), Path: 0001:0014:00
SINOWEALTH, Game Mouse, 9610:4103, Serial Number: (null), Path: 0001:0014:01
MSI EPF USB, MSI EPF USB, 6000:65280, Serial Number: MSI EPF USB, Path: 0001:0004:00

So the mouse shows two devices but the serial number is null in both, but the path clearly indicates they are different.

andrfgs commented 4 years ago

OK, I used hidviz and the mouse has 2 interfaces, the first acts as the mouse and the second acts as a keyboard. Using that program, the program reads nicely all the mouse input, so I now know that Linux recognises everything properly.

This is the parsed descriptor for the Mouse Keyboard interface:

Usage Page (Generic Desktop)
Usage (Keyboard)
Collection (Application)
  Report ID (1)
  Usage Page (Keyboard/Keypad)
  Usage Minimum (Keyboard LeftControl)
  Usage Maximum (Keyboard Right GUI)
  Logical minimum (0)
  Logical maximum (1)
  Report size (1)
  Report count (8)
  Item
  Report count (6)
  Report size (8)
  Logical minimum (0)
  Logical maximum (255)
  Usage Page (Keyboard/Keypad)
  Usage Minimum (Unknown {7:0))
  Usage Maximum (Unknown {7:ff))
  Item
End Collection
Usage Page (Consumer)
Usage (Consumer Control)
Collection (Application)
  Report ID (2)
  Logical maximum (1)
  Logical minimum (0)
  Report size (1)
  Usage (Scan Next Track)
  Usage (Scan Previous Track)
  Usage (Stop)
  Usage (Play/Pause)
  Usage (Mute)
  Usage (Daily)
  Usage (Volume Increment)
  Usage (Volume Decrement)
  Report count (8)
  Item
  Usage (AL Consumer Control Configuration)
  Usage (AL Local Machine Browser)
  Usage (AL Spreadsheet)
  Usage (AL Presentation App)
  Usage (AL Email Reader)
  Usage (AL Calculator)
  Usage (Unknown {c:2a8))
  Usage (AL Word Processor)
  Report count (8)
  Item
  Usage (AC Normal View)
  Usage (AC Scroll Up)
  Usage (AC Scroll Down)
  Usage (AC Scroll)
  Usage (AC Pan Left)
  Usage (AC Pan Right)
  Usage (AC Tile Horizontally)
  Usage (Keyboard Form Factor)
  Report count (8)
  Item
End Collection
Usage Page (Vendor-defined)
Usage (Vendor-defined {ff00:1))
Collection (Application)
  Report ID (4)
  Logical minimum (0)
  Logical maximum (255)
  Usage (Vendor-defined {ff00:0))
  Report size (8)
  Report count (153)
  Item
End Collection
Usage Page (Vendor-defined)
Usage (Vendor-defined {ff00:1))
Collection (Application)
  Report ID (6)
  Logical minimum (0)
  Logical maximum (255)
  Usage (Vendor-defined {ff00:0))
  Report size (8)
  Report count (1151)
  Item
End Collection
Usage Page (Vendor-defined)
Usage (Vendor-defined {ff00:1))
Collection (Application)
  Report ID (5)
  Logical minimum (0)
  Logical maximum (255)
  Usage (Vendor-defined {ff00:0))
  Report count (5)
  Report size (8)
  Item
End Collection

So there is some hope.

jonasmalacofilho commented 4 years ago

You're right, a larger buffer should do no harm.

I was also advised to avoid hidraw for gaming mice and keyboards

This may be open to some debate then.

I normally view hidraw as having a few advantages over libusb, like avoiding unbinding the kernel driver. This can be particularly useful if you want your mouse to keep working while you customize it.

I think libusb should only be preferred if you need something not supported by hidraw (for example, getting an input report), or if you have reason to want to temporarily block the kernel driver. But I'm by no means an export in HID devices...

Still, enumerate reports 2 devices with the exact same vendor and product IDs and the serial number is null in both. So nothing is there to distinguish them.

You should be able to distinguish the two interfaces with cur->interface_number.

It's this (null) serial number reported by hidapi that concerns me. [...] didn't properly regard USB standards.

As I mentioned before, a serial number is not required by the USB specification. Don't worry. Also, if present, it would still be the same for both interfaces (as it's a USB device, not interface, descriptor).

(from your edit to a previous comment) dev = hid_open(devs[0]->vendor_id, devs[0]->product_id, NULL);

Note that you need to use hid_open_path(cur->path) to open a specific device or interface.

hid_open will simply always open the first device/interface that matches the USB vendor and product IDs.

andrfgs commented 4 years ago

I tried to hid_open_path for the last interface.

    struct hid_device_info* cur = hid_enumerate(0x258A, 0x1007);
    struct hid_device_info* devs[10]; // Assumes up to 10 interfaces, I'll change later but for now it works
    int i = 0;
    while (cur != NULL)
    {
        wprintf(L"%S, %S, %d:%d, %S\n", cur->manufacturer_string, cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number);
        devs[i++] = cur;
        cur = cur->next;
    }
    dev = hid_open_path(devs[1]->path);

    unsigned char buf[BUFFER_SIZE];
    memset(&buf, 0x0, BUFFER_SIZE);
    int r = -1;
    buf[0] = 4;
    r = hid_get_feature_report(dev, buf, BUFFER_SIZE);
    wprintf(L"Read a total of %d bytes\n", r);

It still fails with:

T7GammingMouseTest: io.c:2146: handle_events: Assertion `ctx->pollfds_cnt >= internal_nfds' failed.
Aborted
jonasmalacofilho commented 4 years ago

Again, no idea what that assertion means, you'd need to check the corresponding libusb source code (well, actually, the one that matches the particular version of libusb your libhidapi-libusb is linked against).

How about trying with libhidapi-hidraw?

andrfgs commented 4 years ago

Using hidraw terminates without error but still doesn't read any bytes from the feature report.

andrfgs commented 3 years ago

Ok this clearly seems like it's a libhidapi bug. I used hidviz and 2 devices are detected: a mice and a keyboard (the macro part). So it's libhidapi not recognizing my device?

Doing:

struct hid_device_info* enum_result = hid_enumerate(0x258A, 0x1007);
struct hid_device_info* cur = enum_result;
while (cur != NULL) {
    wprintf(L"%S, %S, %#04x:%#04x, Serial Number: %S, Path: %s, Usage Page: %d, Usage: %d\n", cur->manufacturer_string, cur->product_string, cur->vendor_id, cur->product_id, cur->serial_number, cur->path, cur->usage_page, cur->usage);
    cur = cur->next;
}

Prints:

SINOWEALTH, Game Mouse, 0x258a:0x1007, Serial Number: (null), Path: 0001:0002:00, Usage Page: 0, Usage: 0
SINOWEALTH, Game Mouse, 0x258a:0x1007, Serial Number: (null), Path: 0001:0002:01, Usage Page: 0, Usage: 0

Which doesn't seem right.

Youw commented 3 years ago

hidapi never claimed to support Mouses/Keyboards

I used hidviz and 2 devices are detected: a mice and a keyboard

hidapi does detect those too. What extra functionality over those devices hidviz gives you?

andrfgs commented 3 years ago

Nevermind, what in the Windows C# library appears as Usage, in hidapi, it appears as Interface. So, sorry it is displaying correctly (though I find quite strange hidapi lists both interfaces as 0, when they should clearly by different as one is a mouse and the other is a keyboard). The thing is, why does sending a feature report in hidapi on Linux fail, when the supposedly equivalent code works on Windows? So let's forget about hidviz, I just thought it could be an alternative since I can't get it to work with hidapi

And what do you mean by:

hidapi never claimed to support Mouses/Keyboards

Isn't hidapi a library for USB HID devices? Aren't keyboards and mice HID devices after all? Is there something I'm not getting here? I first thought this was all failing due to my own error, but I've ran out of any options I could try. Perhaps I did miss something, but I can't really find anything I am doing wrong.

And if hidapi is not the most adequate library for USB Gaming devices, is there any alternative? I tried to google some alternatives but I can't find anything other than hidapi and hidraw.

Youw commented 3 years ago

A general statement: hidapi is a library, designed to communicate with a general HID devices, as per general HID specification. hidapi never tried to fully implement HID specification itself (well, libusb kind of does it, but only the case for USB HID devices, and it is only one of possible backends), but rather tried to use OS-specific API (hidraw/WinAPI/macOS Cocoa) to access HID devices, so user can rely on (presumably well-supported) OS-based implementation, but with ability to use simple cross-platform API. (I'm starting to thing, that we need such explicit statement in our README, if it is not already there). So far, hidapi does this exact job pretty much well.

Next, HID Mouse and Keyboards: those are only a small subset of possible HID devices. And those devices are special, as they are involved in OS-enforced security. Imagine:

Because of that, Windows/macOS doesn't allow opening Mouse/Keyboard as a general HID devices for read/write. In Linux world it may be a bit different sometime, but is doesn't change the fact, that Mouse/Keyboards are special devices, and they are threated specially by the OS. Essentially, your Mouse device is already claimed by the OS, and hidapi is always a second application that tries to access this device, and it may not be able to, as it is already locked.

When user needs to access some Mouse/Keyboard-specific devices/functionality (like be notified of input events, or so), there is several better options than hidapi, e.g.: OS-specific API for Mouse/Keyboard event - its API explicitly includes all security notes, or other libraries, like Qt, where QWidget::mouseEvent allows you to be notified of Mouse events for a specific widget.


More to your problem: On Windows, the C# library you're using, opens HID device without read-write permission: https://github.com/mikeobrien/HidLibrary/blob/28ead501d4b8097549d9c41b9cdf58cbce8918be/src/HidLibrary/HidDevice.cs#L232 So does hidapi: https://github.com/libusb/hidapi/blob/cdc473dfe43f6432dda7ad53d7656b8ae8ff968b/windows/hid.c#L598 on Windows, as a fallback, because Windows/WinAPI allows it. That is a Windows specific corner case.

libusb backend doesn't give an option to open a device in any mode than read-write, which likely isn't possible for Mouse, unless you open it as root, detach kernel driver, and claim the entire device only for yourself: in such case it won't be usable as mouse anymore but hidapi surely will have full access to the device.

with hidraw backend, you may try to play with device open permissions here: https://github.com/libusb/hidapi/blob/cdc473dfe43f6432dda7ad53d7656b8ae8ff968b/linux/hid.c#L652 as the default (and required to have all the functionality) is read-write. If you can suggest a motivated fallback as a PR - please go ahead make one.


And the last, but not the least: hidapi is an Open Source project developed and supported on enthusiasm and free will. Anyone can use this project under one of several possible licenses, none of which gives you any guarantee about any of the functionality, including the documented one. Basically this means, that community (including me) would try to help you as much as we can, out of good will, but don't make any expectation that someone would solve a problem you have, for you, unless you help yourself solving it.

andrfgs commented 3 years ago

Thank you for the reply. I was hoping my issue was something that was easy to solve but someone who lacks much of the knowledge about USB and the whole HID subject was simply missing.

Nevertheless, I found a library that actually implements the stuff for my mouse (https://github.com/libratbag/libratbag/blob/master/src/driver-sinowealth.c). That library uses hidraw. I still need to r

I still can't understand why some things such as https://github.com/Gibtnix/MSIKLM, which is a keyboard color manager for the MSI Steel Series keyboard that uses this exact library, works flawlessly, and my code fails. My code is probably failing due to lack of knowledge, but I really can't see what is wrong. I guess I must read more on the entire USB protocol. I really wish I could fix this myself, but I am seriously running out of ideas to try. I don't think there are much more ways of using hidapi that I didn't try, as the api is extremely simple and compact.

Still, thank you for all the input and time spent trying to help. Might I ask you where do developers that contribute to libusb got their knowledge from? Is there any specific literature that I should read asides from the USB HID protocol documentation?

Youw commented 3 years ago

I still can't understand why some things such as https://github.com/Gibtnix/MSIKLM, which is a keyboard color manager for the MSI Steel Series keyboard that uses this exact library, works flawlessly

If I had to guess - maybe those keyboards define 2 separate HID devices: one for keyboard (that has restricted access, enforced by OS), and another one for lights controls, that can be opened by hidapi with no issues.

Nevertheless, I found a library that actually implements the stuff for my mouse (https://github.com/libratbag/libratbag/blob/master/src/driver-sinowealth.c). That library uses hidraw

It uses hidraw in a bit different way than hidapi does. The library quite sophisticated I must say, well written for its specific purpose.

Might I ask you where do developers that contribute to libusb got their knowledge from? Is there any specific literature that I should read asides from the USB HID protocol documentation?

Your question reminds me a story:

- Professor, you and you colleagues are so smart, what books do you read to gain your knowledge from?
- We don't read books, we write them.

Well, there is USB Specification / HID Specification documents (those are difficult to read, if you unexperienced to read such documents). There is OS-specific documentation, e.g. for WinAPI/HID, which could give you some idea of what is under the hood. And, of course, years of trials and errors.

andrfgs commented 3 years ago

After some reading and a lot of trial and error, I finally managed to succeed. Apparently, the issue is that BUFFER_SIZE needs to be exactly 154 bytes, no more no less, (the expected size is 153 plus the report id).

This mouse acts as 2 devices (a keyboard and a mouse), the keyboard is the interface that I was concerned with. I actually managed to successfully get a feature report first using the raw hidraw code but this library also works using the hidraw backend.

Now it's more reverse engineering of the protocol to get more features working and structuring the program I want to use to control the mouse.

Youw commented 3 years ago

Perfect, you've figured it out!

Try if https://github.com/libusb/hidapi/commit/fb4135c7a87e87fb9f3f2fb95c3389677eb35ac9 (latest commit to master) does any better for you (it should allow you to pass smaller buffers).