diff mbox

hid: uhid: improve uhid example client

Message ID 1378071918-1152-1-git-send-email-dh.herrmann@gmail.com (mailing list archive)
State New, archived
Delegated to: Jiri Kosina
Headers show

Commit Message

David Herrmann Sept. 1, 2013, 9:45 p.m. UTC
This extends the uhid example client. It properly documents the built-in
report-descriptor an adds explicit report-numbers.

Furthermore, LED output reports are added to utilize the new UHID output
reports of the kernel. Support for 3 basic LEDs is added and a small
report-parser to print debug messages if output reports were received.

To test this, simply write the EV_LED+LED_CAPSL+1 event to the evdev
device-node of the uhid-device and the kernel will forward it to your uhid
client.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
Hi Jiri

This is actually the first time that I wrote an HID report-descriptor. The old
3-button mouse was just copied from a real mouse. My descriptor works perfectly
well but I am kind of scared I am no longer allowed to make fun of firmware
authors..

Anyway, I extended the example to have a test-case for uhid output reports and
it all works as expected. I got asked for some help on this so I wrote up this
patch to push it mainline.

Cheers
David

#include <assert.h>
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
        int fd, r;
        struct input_event ev;

        assert(argc >= 3);

        fd = open(argv[1], O_RDWR | O_CLOEXEC);
        assert(fd >= 0);

        memset(&ev, 0, sizeof(ev));
        ev.type = EV_LED;
        ev.code = LED_CAPSL;
        ev.value = atoi(argv[2]);

        r = write(fd, &ev, sizeof(ev));
        assert(r == sizeof(ev));

        close(fd);

        return 0;
}

 samples/uhid/uhid-example.c | 123 +++++++++++++++++++++++++++++++++++++-------
 1 file changed, 103 insertions(+), 20 deletions(-)

Comments

Benjamin Tissoires Sept. 2, 2013, 1:16 p.m. UTC | #1
Hi David,

On Sun, Sep 1, 2013 at 11:45 PM, David Herrmann <dh.herrmann@gmail.com> wrote:
> This extends the uhid example client. It properly documents the built-in
> report-descriptor an adds explicit report-numbers.
>
> Furthermore, LED output reports are added to utilize the new UHID output
> reports of the kernel. Support for 3 basic LEDs is added and a small
> report-parser to print debug messages if output reports were received.
>
> To test this, simply write the EV_LED+LED_CAPSL+1 event to the evdev
> device-node of the uhid-device and the kernel will forward it to your uhid
> client.
>
> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
> ---
> Hi Jiri
>
> This is actually the first time that I wrote an HID report-descriptor. The old
> 3-button mouse was just copied from a real mouse. My descriptor works perfectly
> well but I am kind of scared I am no longer allowed to make fun of firmware
> authors..

If you are affraid of legal issues, you should consider using the
keyboard report descriptor given as an example on the page 69
(Appendix E.6) of the USB HID specification 1.1.

Other than that, I had also to deal with raw reports and report
descriptors, so I have written some tools to decode them on:
https://github.com/bentiss/hid-replay/tree/master/tools
I am expecting in input the hid-replay file format, which you can
retrieve an example from hid-recorder, and from the man.

It's not 100% accurate (I had corner cases for some keyboards), but
it's doing the decoding job quite properly.
I am also thinking of a way to "reencode" it more efficiently from the
human readable version, but this is not done yet.

>
> Anyway, I extended the example to have a test-case for uhid output reports and
> it all works as expected. I got asked for some help on this so I wrote up this
> patch to push it mainline.

Great thanks!

On overall, the example looks sane, but I will not be able to do an
exhaustive review before Friday. So Jiri, I think you can take it
unless David decides to change the report descriptors with the one I
mentioned earlier.

Cheers,
Benjamin

>
> Cheers
> David
>
> #include <assert.h>
> #include <linux/input.h>
> #include <fcntl.h>
> #include <unistd.h>
> #include <string.h>
> #include <stdlib.h>
>
> int main(int argc, char **argv)
> {
>         int fd, r;
>         struct input_event ev;
>
>         assert(argc >= 3);
>
>         fd = open(argv[1], O_RDWR | O_CLOEXEC);
>         assert(fd >= 0);
>
>         memset(&ev, 0, sizeof(ev));
>         ev.type = EV_LED;
>         ev.code = LED_CAPSL;
>         ev.value = atoi(argv[2]);
>
>         r = write(fd, &ev, sizeof(ev));
>         assert(r == sizeof(ev));
>
>         close(fd);
>
>         return 0;
> }
>
>  samples/uhid/uhid-example.c | 123 +++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 103 insertions(+), 20 deletions(-)
>
> diff --git a/samples/uhid/uhid-example.c b/samples/uhid/uhid-example.c
> index 03ce3c0..7d58a4b 100644
> --- a/samples/uhid/uhid-example.c
> +++ b/samples/uhid/uhid-example.c
> @@ -1,14 +1,15 @@
>  /*
>   * UHID Example
>   *
> - * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
> + * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
>   *
>   * The code may be used by anyone for any purpose,
>   * and can serve as a starting point for developing
>   * applications using uhid.
>   */
>
> -/* UHID Example
> +/*
> + * UHID Example
>   * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
>   * program as root and then use the following keys to control the mouse:
>   *   q: Quit the application
> @@ -22,6 +23,11 @@
>   *   r: Move wheel up
>   *   f: Move wheel down
>   *
> + * Additionally to 3 button mouse, 3 keyboard LEDs are also supported (LED_NUML,
> + * LED_CAPSL and LED_SCROLLL). The device doesn't generate any related keyboard
> + * events, though. You need to manually write the EV_LED/LED_XY/1 activation
> + * input event to the evdev device to see it being sent to this device.
> + *
>   * If uhid is not available as /dev/uhid, then you can pass a different path as
>   * first argument.
>   * If <linux/uhid.h> is not installed in /usr, then compile this with:
> @@ -41,11 +47,12 @@
>  #include <unistd.h>
>  #include <linux/uhid.h>
>
> -/* HID Report Desciptor
> - * We emulate a basic 3 button mouse with wheel. This is the report-descriptor
> - * as the kernel will parse it:
> +/*
> + * HID Report Desciptor
> + * We emulate a basic 3 button mouse with wheel and 3 keyboard LEDs. This is
> + * the report-descriptor as the kernel will parse it:
>   *
> - * INPUT[INPUT]
> + * INPUT(1)[INPUT]
>   *   Field(0)
>   *     Physical(GenericDesktop.Pointer)
>   *     Application(GenericDesktop.Mouse)
> @@ -72,6 +79,19 @@
>   *     Report Count(3)
>   *     Report Offset(8)
>   *     Flags( Variable Relative )
> + * OUTPUT(2)[OUTPUT]
> + *   Field(0)
> + *     Application(GenericDesktop.Keyboard)
> + *     Usage(3)
> + *       LED.NumLock
> + *       LED.CapsLock
> + *       LED.ScrollLock
> + *     Logical Minimum(0)
> + *     Logical Maximum(1)
> + *     Report Size(1)
> + *     Report Count(3)
> + *     Report Offset(0)
> + *     Flags( Variable Absolute )
>   *
>   * This is the mapping that we expect:
>   *   Button.0001 ---> Key.LeftBtn
> @@ -80,19 +100,59 @@
>   *   GenericDesktop.X ---> Relative.X
>   *   GenericDesktop.Y ---> Relative.Y
>   *   GenericDesktop.Wheel ---> Relative.Wheel
> + *   LED.NumLock ---> LED.NumLock
> + *   LED.CapsLock ---> LED.CapsLock
> + *   LED.ScrollLock ---> LED.ScrollLock
>   *
>   * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
>   * This file should print the same information as showed above.
>   */
>
>  static unsigned char rdesc[] = {
> -       0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
> -       0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
> -       0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
> -       0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
> -       0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
> -       0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
> -       0x81, 0x06, 0xc0, 0xc0,
> +       0x05, 0x01,     /* USAGE_PAGE (Generic Desktop) */
> +       0x09, 0x02,     /* USAGE (Mouse) */
> +       0xa1, 0x01,     /* COLLECTION (Application) */
> +       0x09, 0x01,             /* USAGE (Pointer) */
> +       0xa1, 0x00,             /* COLLECTION (Physical) */
> +       0x85, 0x01,                     /* REPORT_ID (1) */
> +       0x05, 0x09,                     /* USAGE_PAGE (Button) */
> +       0x19, 0x01,                     /* USAGE_MINIMUM (Button 1) */
> +       0x29, 0x03,                     /* USAGE_MAXIMUM (Button 3) */
> +       0x15, 0x00,                     /* LOGICAL_MINIMUM (0) */
> +       0x25, 0x01,                     /* LOGICAL_MAXIMUM (1) */
> +       0x95, 0x03,                     /* REPORT_COUNT (3) */
> +       0x75, 0x01,                     /* REPORT_SIZE (1) */
> +       0x81, 0x02,                     /* INPUT (Data,Var,Abs) */
> +       0x95, 0x01,                     /* REPORT_COUNT (1) */
> +       0x75, 0x05,                     /* REPORT_SIZE (5) */
> +       0x81, 0x01,                     /* INPUT (Cnst,Var,Abs) */
> +       0x05, 0x01,                     /* USAGE_PAGE (Generic Desktop) */
> +       0x09, 0x30,                     /* USAGE (X) */
> +       0x09, 0x31,                     /* USAGE (Y) */
> +       0x09, 0x38,                     /* USAGE (WHEEL) */
> +       0x15, 0x81,                     /* LOGICAL_MINIMUM (-127) */
> +       0x25, 0x7f,                     /* LOGICAL_MAXIMUM (127) */
> +       0x75, 0x08,                     /* REPORT_SIZE (8) */
> +       0x95, 0x03,                     /* REPORT_COUNT (3) */
> +       0x81, 0x06,                     /* INPUT (Data,Var,Rel) */
> +       0xc0,                   /* END_COLLECTION */
> +       0xc0,           /* END_COLLECTION */
> +       0x05, 0x01,     /* USAGE_PAGE (Generic Desktop) */
> +       0x09, 0x06,     /* USAGE (Keyboard) */
> +       0xa1, 0x01,     /* COLLECTION (Application) */
> +       0x85, 0x02,             /* REPORT_ID (2) */
> +       0x05, 0x08,             /* USAGE_PAGE (Led) */
> +       0x19, 0x01,             /* USAGE_MINIMUM (1) */
> +       0x29, 0x03,             /* USAGE_MAXIMUM (3) */
> +       0x15, 0x00,             /* LOGICAL_MINIMUM (0) */
> +       0x25, 0x01,             /* LOGICAL_MAXIMUM (1) */
> +       0x95, 0x03,             /* REPORT_COUNT (3) */
> +       0x75, 0x01,             /* REPORT_SIZE (1) */
> +       0x91, 0x02,             /* Output (Data,Var,Abs) */
> +       0x95, 0x01,             /* REPORT_COUNT (1) */
> +       0x75, 0x05,             /* REPORT_SIZE (5) */
> +       0x91, 0x01,             /* Output (Cnst,Var,Abs) */
> +       0xc0,           /* END_COLLECTION */
>  };
>
>  static int uhid_write(int fd, const struct uhid_event *ev)
> @@ -140,6 +200,27 @@ static void destroy(int fd)
>         uhid_write(fd, &ev);
>  }
>
> +/* This parses raw output reports sent by the kernel to the device. A normal
> + * uhid program shouldn't do this but instead just forward the raw report.
> + * However, for ducomentational purposes, we try to detect LED events here and
> + * print debug messages for it. */
> +static void handle_output(struct uhid_event *ev)
> +{
> +       /* LED messages are adverised via OUTPUT reports; ignore the rest */
> +       if (ev->u.output.rtype != UHID_OUTPUT_REPORT)
> +               return;
> +       /* LED reports have length 2 bytes */
> +       if (ev->u.output.size != 2)
> +               return;
> +       /* first byte is report-id which is 0x02 for LEDs in our rdesc */
> +       if (ev->u.output.data[0] != 0x2)
> +               return;
> +
> +       /* print flags payload */
> +       fprintf(stderr, "LED output report received with flags %x\n",
> +               ev->u.output.data[1]);
> +}
> +
>  static int event(int fd)
>  {
>         struct uhid_event ev;
> @@ -174,6 +255,7 @@ static int event(int fd)
>                 break;
>         case UHID_OUTPUT:
>                 fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
> +               handle_output(&ev);
>                 break;
>         case UHID_OUTPUT_EV:
>                 fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
> @@ -198,18 +280,19 @@ static int send_event(int fd)
>
>         memset(&ev, 0, sizeof(ev));
>         ev.type = UHID_INPUT;
> -       ev.u.input.size = 4;
> +       ev.u.input.size = 5;
>
> +       ev.u.input.data[0] = 0x1;
>         if (btn1_down)
> -               ev.u.input.data[0] |= 0x1;
> +               ev.u.input.data[1] |= 0x1;
>         if (btn2_down)
> -               ev.u.input.data[0] |= 0x2;
> +               ev.u.input.data[1] |= 0x2;
>         if (btn3_down)
> -               ev.u.input.data[0] |= 0x4;
> +               ev.u.input.data[1] |= 0x4;
>
> -       ev.u.input.data[1] = abs_hor;
> -       ev.u.input.data[2] = abs_ver;
> -       ev.u.input.data[3] = wheel;
> +       ev.u.input.data[2] = abs_hor;
> +       ev.u.input.data[3] = abs_ver;
> +       ev.u.input.data[4] = wheel;
>
>         return uhid_write(fd, &ev);
>  }
> --
> 1.8.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Herrmann Sept. 2, 2013, 1:21 p.m. UTC | #2
Hi

On Mon, Sep 2, 2013 at 3:16 PM, Benjamin Tissoires
<benjamin.tissoires@gmail.com> wrote:
> Hi David,
>
> On Sun, Sep 1, 2013 at 11:45 PM, David Herrmann <dh.herrmann@gmail.com> wrote:
>> This extends the uhid example client. It properly documents the built-in
>> report-descriptor an adds explicit report-numbers.
>>
>> Furthermore, LED output reports are added to utilize the new UHID output
>> reports of the kernel. Support for 3 basic LEDs is added and a small
>> report-parser to print debug messages if output reports were received.
>>
>> To test this, simply write the EV_LED+LED_CAPSL+1 event to the evdev
>> device-node of the uhid-device and the kernel will forward it to your uhid
>> client.
>>
>> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
>> ---
>> Hi Jiri
>>
>> This is actually the first time that I wrote an HID report-descriptor. The old
>> 3-button mouse was just copied from a real mouse. My descriptor works perfectly
>> well but I am kind of scared I am no longer allowed to make fun of firmware
>> authors..
>
> If you are affraid of legal issues, you should consider using the
> keyboard report descriptor given as an example on the page 69
> (Appendix E.6) of the USB HID specification 1.1.

Not afraid of legal issues at all. I reviewed it thoroughly,
documented the new one and I added my own extensions (like WHEEL). So
it is no longer based on the old one. But thanks for the usb-spec
hint, haven't looked there, yet.

> Other than that, I had also to deal with raw reports and report
> descriptors, so I have written some tools to decode them on:
> https://github.com/bentiss/hid-replay/tree/master/tools
> I am expecting in input the hid-replay file format, which you can
> retrieve an example from hid-recorder, and from the man.
>
> It's not 100% accurate (I had corner cases for some keyboards), but
> it's doing the decoding job quite properly.
> I am also thinking of a way to "reencode" it more efficiently from the
> human readable version, but this is not done yet.

Cool! Encoding-support is actually what I need, but the decoding is
nice to verify correctness.

>>
>> Anyway, I extended the example to have a test-case for uhid output reports and
>> it all works as expected. I got asked for some help on this so I wrote up this
>> patch to push it mainline.
>
> Great thanks!
>
> On overall, the example looks sane, but I will not be able to do an
> exhaustive review before Friday. So Jiri, I think you can take it
> unless David decides to change the report descriptors with the one I
> mentioned earlier.

No intention to modify it. Looks all good to me now.

Thanks!
David
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jiri Kosina Sept. 4, 2013, 9:37 a.m. UTC | #3
On Mon, 2 Sep 2013, David Herrmann wrote:

> But thanks for the usb-spec hint, haven't looked there, yet.

There is also HID report descriptor tool available at

	http://www.usb.org/developers/hidpage/

The tool itself isn't that earth-shaking (and you have to run it in wine 
:) ), but it contains a load of pre-defined example descriptors for 
various devices.

> No intention to modify it. Looks all good to me now.

Now applied, thanks.
diff mbox

Patch

diff --git a/samples/uhid/uhid-example.c b/samples/uhid/uhid-example.c
index 03ce3c0..7d58a4b 100644
--- a/samples/uhid/uhid-example.c
+++ b/samples/uhid/uhid-example.c
@@ -1,14 +1,15 @@ 
 /*
  * UHID Example
  *
- * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
  *
  * The code may be used by anyone for any purpose,
  * and can serve as a starting point for developing
  * applications using uhid.
  */
 
-/* UHID Example
+/*
+ * UHID Example
  * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
  * program as root and then use the following keys to control the mouse:
  *   q: Quit the application
@@ -22,6 +23,11 @@ 
  *   r: Move wheel up
  *   f: Move wheel down
  *
+ * Additionally to 3 button mouse, 3 keyboard LEDs are also supported (LED_NUML,
+ * LED_CAPSL and LED_SCROLLL). The device doesn't generate any related keyboard
+ * events, though. You need to manually write the EV_LED/LED_XY/1 activation
+ * input event to the evdev device to see it being sent to this device.
+ *
  * If uhid is not available as /dev/uhid, then you can pass a different path as
  * first argument.
  * If <linux/uhid.h> is not installed in /usr, then compile this with:
@@ -41,11 +47,12 @@ 
 #include <unistd.h>
 #include <linux/uhid.h>
 
-/* HID Report Desciptor
- * We emulate a basic 3 button mouse with wheel. This is the report-descriptor
- * as the kernel will parse it:
+/*
+ * HID Report Desciptor
+ * We emulate a basic 3 button mouse with wheel and 3 keyboard LEDs. This is
+ * the report-descriptor as the kernel will parse it:
  *
- * INPUT[INPUT]
+ * INPUT(1)[INPUT]
  *   Field(0)
  *     Physical(GenericDesktop.Pointer)
  *     Application(GenericDesktop.Mouse)
@@ -72,6 +79,19 @@ 
  *     Report Count(3)
  *     Report Offset(8)
  *     Flags( Variable Relative )
+ * OUTPUT(2)[OUTPUT]
+ *   Field(0)
+ *     Application(GenericDesktop.Keyboard)
+ *     Usage(3)
+ *       LED.NumLock
+ *       LED.CapsLock
+ *       LED.ScrollLock
+ *     Logical Minimum(0)
+ *     Logical Maximum(1)
+ *     Report Size(1)
+ *     Report Count(3)
+ *     Report Offset(0)
+ *     Flags( Variable Absolute )
  *
  * This is the mapping that we expect:
  *   Button.0001 ---> Key.LeftBtn
@@ -80,19 +100,59 @@ 
  *   GenericDesktop.X ---> Relative.X
  *   GenericDesktop.Y ---> Relative.Y
  *   GenericDesktop.Wheel ---> Relative.Wheel
+ *   LED.NumLock ---> LED.NumLock
+ *   LED.CapsLock ---> LED.CapsLock
+ *   LED.ScrollLock ---> LED.ScrollLock
  *
  * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
  * This file should print the same information as showed above.
  */
 
 static unsigned char rdesc[] = {
-	0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
-	0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
-	0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
-	0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
-	0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
-	0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
-	0x81, 0x06, 0xc0, 0xc0,
+	0x05, 0x01,	/* USAGE_PAGE (Generic Desktop) */
+	0x09, 0x02,	/* USAGE (Mouse) */
+	0xa1, 0x01,	/* COLLECTION (Application) */
+	0x09, 0x01,		/* USAGE (Pointer) */
+	0xa1, 0x00,		/* COLLECTION (Physical) */
+	0x85, 0x01,			/* REPORT_ID (1) */
+	0x05, 0x09,			/* USAGE_PAGE (Button) */
+	0x19, 0x01,			/* USAGE_MINIMUM (Button 1) */
+	0x29, 0x03,			/* USAGE_MAXIMUM (Button 3) */
+	0x15, 0x00,			/* LOGICAL_MINIMUM (0) */
+	0x25, 0x01,			/* LOGICAL_MAXIMUM (1) */
+	0x95, 0x03,			/* REPORT_COUNT (3) */
+	0x75, 0x01,			/* REPORT_SIZE (1) */
+	0x81, 0x02,			/* INPUT (Data,Var,Abs) */
+	0x95, 0x01,			/* REPORT_COUNT (1) */
+	0x75, 0x05,			/* REPORT_SIZE (5) */
+	0x81, 0x01,			/* INPUT (Cnst,Var,Abs) */
+	0x05, 0x01,			/* USAGE_PAGE (Generic Desktop) */
+	0x09, 0x30,			/* USAGE (X) */
+	0x09, 0x31,			/* USAGE (Y) */
+	0x09, 0x38,			/* USAGE (WHEEL) */
+	0x15, 0x81,			/* LOGICAL_MINIMUM (-127) */
+	0x25, 0x7f,			/* LOGICAL_MAXIMUM (127) */
+	0x75, 0x08,			/* REPORT_SIZE (8) */
+	0x95, 0x03,			/* REPORT_COUNT (3) */
+	0x81, 0x06,			/* INPUT (Data,Var,Rel) */
+	0xc0,			/* END_COLLECTION */
+	0xc0,		/* END_COLLECTION */
+	0x05, 0x01,	/* USAGE_PAGE (Generic Desktop) */
+	0x09, 0x06,	/* USAGE (Keyboard) */
+	0xa1, 0x01,	/* COLLECTION (Application) */
+	0x85, 0x02,		/* REPORT_ID (2) */
+	0x05, 0x08,		/* USAGE_PAGE (Led) */
+	0x19, 0x01,		/* USAGE_MINIMUM (1) */
+	0x29, 0x03,		/* USAGE_MAXIMUM (3) */
+	0x15, 0x00,		/* LOGICAL_MINIMUM (0) */
+	0x25, 0x01,		/* LOGICAL_MAXIMUM (1) */
+	0x95, 0x03,		/* REPORT_COUNT (3) */
+	0x75, 0x01,		/* REPORT_SIZE (1) */
+	0x91, 0x02,		/* Output (Data,Var,Abs) */
+	0x95, 0x01,		/* REPORT_COUNT (1) */
+	0x75, 0x05,		/* REPORT_SIZE (5) */
+	0x91, 0x01,		/* Output (Cnst,Var,Abs) */
+	0xc0,		/* END_COLLECTION */
 };
 
 static int uhid_write(int fd, const struct uhid_event *ev)
@@ -140,6 +200,27 @@  static void destroy(int fd)
 	uhid_write(fd, &ev);
 }
 
+/* This parses raw output reports sent by the kernel to the device. A normal
+ * uhid program shouldn't do this but instead just forward the raw report.
+ * However, for ducomentational purposes, we try to detect LED events here and
+ * print debug messages for it. */
+static void handle_output(struct uhid_event *ev)
+{
+	/* LED messages are adverised via OUTPUT reports; ignore the rest */
+	if (ev->u.output.rtype != UHID_OUTPUT_REPORT)
+		return;
+	/* LED reports have length 2 bytes */
+	if (ev->u.output.size != 2)
+		return;
+	/* first byte is report-id which is 0x02 for LEDs in our rdesc */
+	if (ev->u.output.data[0] != 0x2)
+		return;
+
+	/* print flags payload */
+	fprintf(stderr, "LED output report received with flags %x\n",
+		ev->u.output.data[1]);
+}
+
 static int event(int fd)
 {
 	struct uhid_event ev;
@@ -174,6 +255,7 @@  static int event(int fd)
 		break;
 	case UHID_OUTPUT:
 		fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
+		handle_output(&ev);
 		break;
 	case UHID_OUTPUT_EV:
 		fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
@@ -198,18 +280,19 @@  static int send_event(int fd)
 
 	memset(&ev, 0, sizeof(ev));
 	ev.type = UHID_INPUT;
-	ev.u.input.size = 4;
+	ev.u.input.size = 5;
 
+	ev.u.input.data[0] = 0x1;
 	if (btn1_down)
-		ev.u.input.data[0] |= 0x1;
+		ev.u.input.data[1] |= 0x1;
 	if (btn2_down)
-		ev.u.input.data[0] |= 0x2;
+		ev.u.input.data[1] |= 0x2;
 	if (btn3_down)
-		ev.u.input.data[0] |= 0x4;
+		ev.u.input.data[1] |= 0x4;
 
-	ev.u.input.data[1] = abs_hor;
-	ev.u.input.data[2] = abs_ver;
-	ev.u.input.data[3] = wheel;
+	ev.u.input.data[2] = abs_hor;
+	ev.u.input.data[3] = abs_ver;
+	ev.u.input.data[4] = wheel;
 
 	return uhid_write(fd, &ev);
 }