adafruit / Adafruit_CircuitPython_HID

USB Human Interface Device drivers.
MIT License
373 stars 105 forks source link

consumer_control: Add press and release #63

Closed JPEWdev closed 3 years ago

JPEWdev commented 3 years ago

Adds support for pressing and releasing the consumer control code

dhalbert commented 3 years ago

Could you describe the use case(s) for separate press and release instead of just send? Do long presses repeat sometimes, or something like that?

JPEWdev commented 3 years ago

Could you describe the use case(s) for separate press and release instead of just send? Do long presses repeat sometimes, or something like that?

Yes, it does repeat. I'm not sure if this is because of the OS, or the USB device. For example if I send the volume down ID, the volume will decrease while it is held instead of just one step.

My specific use case is that I'm interacting with a device that treats a long press of the power button differently that a short press, so I need the press & release to get the timing correct.

JPEWdev commented 3 years ago

Thanks for the PR! The CI tests are failing due to missing function comments. Do you want this to work like Keyboard? https://github.com/adafruit/Adafruit_CircuitPython_HID/blob/master/adafruit_hid/keyboard.py#L67 It tracks everything that is currently pressed. Otherwise I think a second press call would release the code from the first.

Can you send multiple consumer codes in a report? I'm not positive, but it looks like you can only send one, so there isn't any need to track the current one because it will be released when either a new press comes in or release() is called (this is why release() doesn't take an argument; it will release the one and only key being pressed).

dhalbert commented 3 years ago

Yes, it does repeat.

I'm confused, though, is this something you want to do? What is it that you cannot do with send() that you want to do with press() and release()?

so there isn't any need to track the current one because it will be released when either a new press comes in

This does seem to be true on my keyboard with some multimedia keys. There is no n-key rollover. But I could not find this spec'd in the USB documents. Do you have a reference? thanks.

JPEWdev commented 3 years ago

Yes, it does repeat.

I'm confused, though, is this something you want to do? What is it that you cannot do with send() that you want to do with press() and release()?

I'm trying to control a device that distinguishes between a "long" press of it's power button from a "short" press, so I need a way to keep the key held down for a specific duration.

so there isn't any need to track the current one because it will be released when either a new press comes in

This does seem to be true on my keyboard with some multimedia keys. There is no n-key rollover. But I could not find this spec'd in the USB documents. Do you have a reference? thanks.

I do not have a reference; it was an empirical observation. If you know somewhere I can look, I'll try and dig a little more. However, I suspect that the repeating behavior is actually implemented in the OS, not the USB spec.

dhalbert commented 3 years ago

I'm trying to control a device that distinguishes between a "long" press of it's power button from a "short" press, so I need a way to keep the key held down for a specific duration.

Aha, got it. That is a clear use case. But, I'm interested, is the power button SysControl or Consumer Control? What is the code? We have a SysControl HID descriptor too, but it's not turned on by default.

dhalbert commented 3 years ago

I tried three differerent keyboards, and they all act rather differently. Sometimes the repeating appears to be done in the OS, and sometimes the keyboard is doing something special.

All this testing was done with sudo evtest on Ubuntu 20.04.

  1. Kinesis FreeStyle Edge: Trying to press another key while one of the keys is down does nothing. The value indicated is 0 or 1: it goes to 1 when the key is pressed, and 0 when released.
  2. Dell Multimedia Keyboard: Pressing a key sends a press and a release (1 and then 0 on evtest) immediately. There is no repeating beahvior.
  3. Logitech K400+: Multiple keys pressed at once works: there appears to be 2-key rollover for the ConsumerControl keys. If a key is held, the value goes to 2 (!).

The USB HID descriptors for these are vastly different from each other. I extracted the descriptors with sudo usbhid-dump and parsed them with http://eleccelerator.com/usbdescreqparser/.

The one we use in CircuitPython is this. It has one 16-bit slot:

0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x75, 0x10,        //   Report Size (16)
0x95, 0x01,        //   Report Count (1)
0x15, 0x01,        //   Logical Minimum (1)
0x26, 0x8C, 0x02,  //   Logical Maximum (652)
0x19, 0x01,        //   Usage Minimum (Consumer Control)
0x2A, 0x8C, 0x02,  //   Usage Maximum (AC Send)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Kinesis breaks out the specific functionality as bits.

0x05, 0x0C,        // Usage Page (Consumer)
0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x04,        //   Report Count (4)
0x09, 0xB5,        //   Usage (Scan Next Track)
0x09, 0xB6,        //   Usage (Scan Previous Track)
0x09, 0xCD,        //   Usage (Play/Pause)
0x09, 0xB7,        //   Usage (Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //   Report Count (1)
0x09, 0xE2,        //   Usage (Mute)
0x81, 0x22,        //   Input (Data,Var,Abs,No Wrap,Linear,No Preferred State,No Null Position)
0x95, 0x02,        //   Report Count (2)
0x09, 0xE9,        //   Usage (Volume Increment)
0x09, 0xEA,        //   Usage (Volume Decrement)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //   Report Count (1)
0x0A, 0x92, 0x01,  //   Usage (AL Calculator)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection
0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x80,        // Usage (Sys Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x02,        //   Report ID (2)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x01,        //   Report Count (1)
0x09, 0x81,        //   Usage (Sys Power Down)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x07,        //   Report Count (7)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Dell is also a bitwise report, with each function listed:

0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x01,        //   Report Count (1)
0x0A, 0x24, 0x02,  //   Usage (AC Back)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x25, 0x02,  //   Usage (AC Forward)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x26, 0x02,  //   Usage (AC Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x27, 0x02,  //   Usage (AC Refresh)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x23, 0x02,  //   Usage (AC Home)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x8A, 0x01,  //   Usage (AL Email Reader)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x94, 0x01,  //   Usage (AL Local Machine Browser)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x92, 0x01,  //   Usage (AL Calculator)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB5,        //   Usage (Scan Next Track)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB6,        //   Usage (Scan Previous Track)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xCD,        //   Usage (Play/Pause)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB7,        //   Usage (Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x83, 0x01,  //   Usage (AL Consumer Control Configuration)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x0B,        //   Report Count (11)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Logitech has two 16-bit slots, I think to get 2-key rollover. I'm still not sure where evtest is getting the value 2 from based on this report; it may be a function of the OS driver, but I'm not at all sure.

x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x75, 0x10,        //   Report Size (16)
0x95, 0x02,        //   Report Count (2)
0x15, 0x01,        //   Logical Minimum (1)
0x26, 0x8C, 0x02,  //   Logical Maximum (652)
0x19, 0x01,        //   Usage Minimum (Consumer Control)
0x2A, 0x8C, 0x02,  //   Usage Maximum (AC Send)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

From all this it appears that any particular behavior about what can be done is dependent on the descriptor. Does your long-press thing work for you right now? The press() and release() semantics you chose make sense for our particular descriptor, since it does not do rollover.

JPEWdev commented 3 years ago

xx

I tried three differerent keyboards, and they all act rather differently. Sometimes the repeating appears to be done in the OS, and sometimes the keyboard is doing something special.

All this testing was done with sudo evtest on Ubuntu 20.04.

1. Kinesis FreeStyle Edge: Trying to press another key while one of the keys is down does nothing. The value indicated is 0 or 1: it goes to 1 when the key is pressed, and 0 when released.

2. Dell Multimedia Keyboard: Pressing a key sends a press and a release (1 and then 0 on `evtest`) immediately. There is no repeating beahvior.

3. Logitech K400+: Multiple keys pressed at once works: there appears to be 2-key rollover for the ConsumerControl keys. If a key is held, the value goes to 2 (!).

The USB HID descriptors for these are vastly different from each other. I extracted the descriptors with sudo usbhid-dump and parsed them with http://eleccelerator.com/usbdescreqparser/.

The one we use in CircuitPython is this. It has one 16-bit slot:

0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x75, 0x10,        //   Report Size (16)
0x95, 0x01,        //   Report Count (1)
0x15, 0x01,        //   Logical Minimum (1)
0x26, 0x8C, 0x02,  //   Logical Maximum (652)
0x19, 0x01,        //   Usage Minimum (Consumer Control)
0x2A, 0x8C, 0x02,  //   Usage Maximum (AC Send)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Kinesis breaks out the specific functionality as bits.

0x05, 0x0C,        // Usage Page (Consumer)
0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x04,        //   Report Count (4)
0x09, 0xB5,        //   Usage (Scan Next Track)
0x09, 0xB6,        //   Usage (Scan Previous Track)
0x09, 0xCD,        //   Usage (Play/Pause)
0x09, 0xB7,        //   Usage (Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //   Report Count (1)
0x09, 0xE2,        //   Usage (Mute)
0x81, 0x22,        //   Input (Data,Var,Abs,No Wrap,Linear,No Preferred State,No Null Position)
0x95, 0x02,        //   Report Count (2)
0x09, 0xE9,        //   Usage (Volume Increment)
0x09, 0xEA,        //   Usage (Volume Decrement)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //   Report Count (1)
0x0A, 0x92, 0x01,  //   Usage (AL Calculator)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection
0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x80,        // Usage (Sys Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x02,        //   Report ID (2)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x01,        //   Report Count (1)
0x09, 0x81,        //   Usage (Sys Power Down)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x07,        //   Report Count (7)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Dell is also a bitwise report, with each function listed:

0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x01,        //   Report Count (1)
0x0A, 0x24, 0x02,  //   Usage (AC Back)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x25, 0x02,  //   Usage (AC Forward)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x26, 0x02,  //   Usage (AC Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x27, 0x02,  //   Usage (AC Refresh)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x23, 0x02,  //   Usage (AC Home)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x8A, 0x01,  //   Usage (AL Email Reader)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x94, 0x01,  //   Usage (AL Local Machine Browser)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x92, 0x01,  //   Usage (AL Calculator)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB5,        //   Usage (Scan Next Track)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB6,        //   Usage (Scan Previous Track)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xCD,        //   Usage (Play/Pause)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xB7,        //   Usage (Stop)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x83, 0x01,  //   Usage (AL Consumer Control Configuration)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x0B,        //   Report Count (11)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

The Logitech has two 16-bit slots, I think to get 2-key rollover. I'm still not sure where evtest is getting the value 2 from based on this report; it may be a function of the OS driver, but I'm not at all sure.

x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x75, 0x10,        //   Report Size (16)
0x95, 0x02,        //   Report Count (2)
0x15, 0x01,        //   Logical Minimum (1)
0x26, 0x8C, 0x02,  //   Logical Maximum (652)
0x19, 0x01,        //   Usage Minimum (Consumer Control)
0x2A, 0x8C, 0x02,  //   Usage Maximum (AC Send)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

Excellent. Thanks for digging into that; it all makes sense

From all this it appears that any particular behavior about what can be done is dependent on the descriptor. Does your long-press thing work for you right now? The press() and release() semantics you chose make sense for our particular descriptor, since it does not do rollover.

Yes, this change allows me to do the exact behavior that I'm looking for where I can do either a quick or long press of the power button and the device it's attached to behaves appropriately.

I'll fix up the comments so that the CI job passes