Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Braille displays that conform to the HID Braille standard #12523

Merged
merged 39 commits into from Jul 26, 2021

Conversation

michaelDCurran
Copy link
Member

@michaelDCurran michaelDCurran commented Jun 7, 2021

Link to issue number:

None

Summary of the issue:

Braille Display devices allow being controlled by Screen Readers using a variety of connections such as Serial, USB and Bluetooth. The protocols used over these channels have traditionally been Braille Display manufacturer specific.
This has meant that in order for a Screen Reader to support a particular Braille display, it must have specific logic in the Screen Reader that knows how to speak the required device-specific protocol. Further to this, On Windows at least, an OS-level driver provided by the Braille Display manufacturer must also be installed by the user in order for the computer to detect and communicate with the device.
With the introduction of the HID (Human Interface Device) standard for USB (and later Bluetooth), it became possible to remove the need for the required OS-level device driver on Windows if the Braille Display manufacturer exposed the device as a custom HID device, however the Screen Reader still needed device-specific code, as being custom HID only simplified the low-level bytes communication, but did not standardize what those bytes actually meant.
In 2018, The HID specification was extended to define the concept of a Braille Display device, including setting of braille cells, and the standardizing of common buttons found on Braille displays such as routing keys, Braille dot input keys, braille space and panning keys.
NVDA should add specific support for a Braille HID device, so that Braille Displays that support this standard can automatically be detected and usable by NVDA.

Description of how this pull request fixes the issue:

  • Adds hidpi.py which contains all necessary types and defines from Windows' hidpi.h (the HID parser header).
  • Some HidP types that were previously added to hwIo.py have been moved to hidpi.py.
  • the hwIo.Hid class has been extended to include some useful properties such as caps (HidP_GetCaps), inputButtonCaps (HidP_GetButtonCaps), inputValueCaps, and outputValueCaps (HidP_GetValueCaps).
  • a new brailleDisplayDriver (brailleDisplayDrivers/hid.py) has been added which supports Braille displays that use the Braille HID specification.
    • On construction:
      • It opens the given device port by instantiating an hwIo.Hid object.
      • It ensures the Hid device is truly a Braille display by checking that the HIDP_CAPS.UsagePage of the HID device's top-level collection is set to HID_USAGE_PAGE_BRAILLE (0x41).
      • It finds the correct output HIDP_VALUE_CAPS structure which represents the array of braille cells. I.e. the Usage ID is either EIGHT_DOT_BRAILLE_CELL or six_dot_braille_cell. The ReportCount member of this struct states the number of cells for the device. This structure is also saved off as it is later needed when writing braille to the display.
      • It collects all input button HIDP_VALUE_CAPS structures supported by the device, and stores them in a dictionary, keyed by their DataIndex member.
        • If the HIDP_VALUE_CAPS structure represents a range of data indices, then the HIDP_VALUE_CAPS structure is added to the dictionary for each data index in that range. For example a device may expose the Braille dot input keys this way.
        • Each value for the dictionary is a tuple containing the HIDP_VALUE_CAPS structure, and a calculated relative DataIndex from the first HIDP_VALUE_CAPS structure with the same LinkCollection member. Examples of collections are Router_set_1 or Braille_row. We certainly need this relative index for routing keys.
    • The BrailleDisplayDriver.display method (for writing cells to the device) works as follows:
      • Creates a HID output report, setting the report ID to the value of the ReportID member of the cell HIDP_VALUE_CAPS structure found at construction.
      • Calls HidP_SetUsageValueArray to place the data (braille cell dot patterns) into the report at the appropriate place, Using the Usage ID and collection number etc from the cell HIDP_VALUE_CAPS structure.
      • Sends the report to the Braille display using WriteFile. The number of bytes written will be the value of HIDP_CAPS.OutputReportByteLength.
    • the BrailleDisplayDriver receives input from the Braille display by providing a callback to the hwIo.Hid object. The Hid object uses overlapped IO and ReadFile to continuously read blocks of data of the device's HID input report size (HIDP_CAPS.InputReportByteLength).
    • the BrailleDisplayDriver's _hidOnReceive callback does the following:
      • Treating the received data as a HID input report, HidP_GetData is used to extract all HIDP_DATA structures from the report. these represent the state of all input buttons and other controls.
      • For each data item that represents a input button and is currently on, the value of the data item's DataIndex member is recorded in a keys list (keys currently being pressed). As long as this list keeps growing, all these keys are treated as being a part of the upcoming gesture. If the list decreases, then these keys are used to create an NVDA InputGesture which is executed.
    • This BrailleDisplayDriver's InputGesture class processes the given data indices as follows:
      • It looks up the correct input button HIDP_VALUE_CAPS structure and relative index in collection value for the data index, using the dictionary created in the BrailleDisplayDriver's construction.
      • If the HIDP_VALUE_CAPS structure represents a range of buttons, it calculates the correct Usage ID using the DataIndex value and the DataIndexMin and UsageMin HIDP_VALUE_CAPS members.
      • If the Usage ID is one of the BRAILLE_KEYBOARD_DOT_* values, it sets the appropriate bit in the InputGesture's dots member.
      • If the Usage ID is one of the BRAILLE_KEYBOARD_SPACE, BRAILLE_KEYBOARD_LEFT_SPACE or BRAILLE_KEYBOARD_RIGHT_SPACE, it sets the InputGesture's space member to true.
      • If the Usage ID is ROUTER_KEY, then the InputGesture's routingIndex member is set using the calculated relativeIndexInCollection value for this data index.
      • The generic gesture name is calculated from the Usage ID's enum name, converted to camelCase, with prefixes such as BRAILLE_KEYBOARD_ and Braille_ removed. E.g. BRAILLE_PAN_LEFT becomes panLeft. And for router collections, the router collection name is prefixed E.g. routerSet1_routerKey.
    • A gestureMap has been provided for this BrailleDisplayDriver, which is roughly based on the Brailliant B driver, for all gestures that are supported by Braille HID.
  • hwPortUtils._getHidInfo now includes the device's top-level collection's UsagePage value in the info it returns. Note that this does mean now that all HID devices are opened to get this, where as before we only opened Bluetooth devices.
  • When bdDetect looks up an appropriate BrailleDisplayDriver for a USB or Bluetooth device, or when it looks up compatible USB or Bluetooth devices for a given BrailleDisplayDriver, if the USB or Bluetooth device is a HID device and its to-level collection's UsagePage value is HID_USAGE_PAGE_BRAILLE (0x41), then the hid BrailleDisplayDriver is chosen before any other match. Thus if a HID device reports it is a HID braille display, the hid BrailleDisplayDriver will automatically be used.

Testing strategy:

  • Manually tested with unofficial custom firmware from a manufacturer. This needs real-world testing, such as on the latest Orbit Reader which apparently supports Braille HID out of the box.

Known issues with pull request:

None known

Change log entries:

New features
Changes
Bug fixes
For Developers

Code Review Checklist:

  • Pull Request description is up to date.
  • Unit tests.
  • System (end to end) tests.
  • Manual tests.
  • User Documentation.
  • Change log entry.
  • Context sensitive help for GUI changes.

…eeded to implement the Braille HID standard.
…erties to the Hid class to easily fetch caps and input and output value caps.
…HID standard to talk to braille displays that support it.
…SAGE_PAGE_BRAILLE then try using the Braille HID driver before anything else.
…g what devices a particular driver supports.
….hid module.

Bulk and Serial could be moved into their own modules at a later date.
This document includes many notes on how to develope with HID on Windows, and more specifically the HID braille specification.
…ing removing the final statement as it is not really relevant.
… should continuously be called until it returns false, meaning there are no more devices left.
… number of collections. usages and reports for braille displays at runtime.
…into the background section directly before it.
@cary-rowen
Copy link
Contributor

This is an amazing new feature.

Copy link
Collaborator

@LeonarddeR LeonarddeR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether HID should have precedence before all other drivers. Imagine if a manufacturer introduces a new firmware that enabled HID support and NVDA instantly uses the new HID driver instead of the manufacturer specific one. This would mean that key assignments instantly change.

@feerrenrut
Copy link
Contributor

It's a good point @LeonarddeR. I think for any such device we would want a way to set the driver automatically used. That said, I think I'd prefer to handle that problem at the time, if it ever came up.

Copy link
Member

@seanbudd seanbudd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation reads well to me, and the driver code is well documented and easy to follow as a result.

source/brailleDisplayDrivers/hid.py Outdated Show resolved Hide resolved
source/brailleDisplayDrivers/hid.py Outdated Show resolved Hide resolved
user_docs/en/userGuide.t2t Outdated Show resolved Hide resolved
source/hwPortUtils.py Outdated Show resolved Hide resolved
source/hwPortUtils.py Outdated Show resolved Hide resolved
michaelDCurran and others added 4 commits July 14, 2021 15:24
Convert older style percent strings to f-strings.

Co-authored-by: Sean Budd <sean@nvaccess.org>
Unneeded unicode prefix.

Co-authored-by: Sean Budd <sean@nvaccess.org>
Remove incorrect comment.

Co-authored-by: Sean Budd <sean@nvaccess.org>
Remove unneeded space.

Co-authored-by: Sean Budd <sean@nvaccess.org>
@michaelDCurran
Copy link
Member Author

We are holding this from being merged to master until we have branched for 2021.2 beta1. We would like it on master for a significant amount of time before a beta. Plus I still have no real-world in-market device to test this with yet.

@AppVeyorBot
Copy link

See test results for failed build of commit 82a7be7782

Copy link
Member

@Qchristensen Qchristensen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looking at the UserGuide changes, all fine, good work.

@michaelDCurran michaelDCurran merged commit 00cd67b into master Jul 26, 2021
@michaelDCurran michaelDCurran deleted the hidBraille branch July 26, 2021 00:32
@nvaccessAuto nvaccessAuto added this to the 2021.3 milestone Jul 26, 2021
@codeofdusk
Copy link
Contributor

After the changelog was updated for this PR, only one blank line now appears between new features and changes, causing the "changes" section to not appear as a header.

michaelDCurran added a commit that referenced this pull request Jul 26, 2021
@michaelDCurran
Copy link
Member Author

michaelDCurran commented Jul 26, 2021 via email

LeonarddeR added a commit to LeonarddeR/nvda that referenced this pull request Jul 26, 2021
michaelDCurran pushed a commit that referenced this pull request Jul 26, 2021
@FelixGruetzmacher
Copy link
Contributor

FelixGruetzmacher commented Sep 14, 2021

Lovely! I haven't looked at the driver code yet, but I'm currently testing against a firmware draft that speaks Braille HID (at least as far as I understand it).

Good news:
The device is auto-detected, Brailles fine, and takes most keys. I'm amazed!

The only issue I'm facing right now is that cursor routing keys are not recognized, and that joystick left and right are swapped, which may very well be my fault.

For the curious, here is the report descriptor I am using. I'd appreciated it if someone could have a look at those cursor routing keys, and maybe suggest a correction.

Descriptor code

char ReportDescriptor[154] = {
    0x05, 0x41,                    // USAGE_PAGE (Braille Display Page)
    0x09, 0x01,                    // USAGE (Braille Display)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x1a, 0x01, 0x02,              //   USAGE_MINIMUM (Braille Keyboard Dot 1)
    0x2a, 0x08, 0x02,              //   USAGE_MAXIMUM (Braille Keyboard Dot 8)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0a, 0x02,              //   USAGE (Braille Keyboard Left Space)
    0x0a, 0x0b, 0x02,              //   USAGE (Braille Keyboard Right Space)
    0x0a, 0x10, 0x02,              //   USAGE (Braille Joystick Center)
    0x0a, 0x11, 0x02,              //   USAGE (Braille Joystick Up)
    0x0a, 0x12, 0x02,              //   USAGE (Braille Joystick Down)
    0x0a, 0x13, 0x02,              //   USAGE (Braille Joystick Left)
    0x0a, 0x14, 0x02,              //   USAGE (Braille Joystick Right)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x07,                    //   REPORT_COUNT (7)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0d, 0x02,              //   USAGE (Braille Left Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0e, 0x02,              //   USAGE (Braille Right Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x09, 0x02,                    //   USAGE (Braille Row)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x09, 0x03,                    //     USAGE (8 Dot Braille Cell)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x10,                    //     REPORT_COUNT (16)
    0x91, 0x02,                    //     OUTPUT (Data,Var,Abs)
    0x09, 0xfa,                    //     USAGE (Router Set 1)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x15, 0x01,                    //       LOGICAL_MINIMUM (1)
    0x25, 0x10,                    //       LOGICAL_MAXIMUM (16)
    0x75, 0x08,                    //       REPORT_SIZE (8)
    0x95, 0x01,                    //       REPORT_COUNT (1)
    0x81, 0x00,                    //       INPUT (Data,Ary,Abs)
    0xc0,                          //         END_COLLECTION
    0xc0,                          //     END_COLLECTION
    0xc0                           // END_COLLECTION
};

If anything specifically needs testing, I'd love to do so.

@FelixGruetzmacher
Copy link
Contributor

FelixGruetzmacher commented Sep 16, 2021

I fixed my report descriptor by specifying the cursor routing keys as a bitmap rather than via an array index. The new descriptor is as follows:

Descriptor Code

const BYTE ReportDescriptor[199] = {
    0x05, 0x41,                    // USAGE_PAGE (Braille Display Page)
    0x09, 0x01,                    // USAGE (Braille Display)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x1a, 0x01, 0x02,              //   USAGE_MINIMUM (Braille Keyboard Dot 1)
    0x2a, 0x08, 0x02,              //   USAGE_MAXIMUM (Braille Keyboard Dot 8)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0a, 0x02,              //   USAGE (Braille Keyboard Left Space)
    0x0a, 0x0b, 0x02,              //   USAGE (Braille Keyboard Right Space)
    0x0a, 0x10, 0x02,              //   USAGE (Braille Joystick Center)
    0x0a, 0x11, 0x02,              //   USAGE (Braille Joystick Up)
    0x0a, 0x12, 0x02,              //   USAGE (Braille Joystick Down)
    0x0a, 0x13, 0x02,              //   USAGE (Braille Joystick Left)
    0x0a, 0x14, 0x02,              //   USAGE (Braille Joystick Right)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x07,                    //   REPORT_COUNT (7)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0d, 0x02,              //   USAGE (Braille Left Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0e, 0x02,              //   USAGE (Braille Right Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x09, 0x02,                    //   USAGE (Braille Row)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x09, 0x03,                    //     USAGE (8 Dot Braille Cell)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x10,                    //     REPORT_COUNT (16)
    0x91, 0x02,                    //     OUTPUT (Data,Var,Abs)
    0x09, 0xfa,                    //     USAGE (Router Set 1)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x15, 0x00,                    //       LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //       LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //       REPORT_SIZE (1)
    0x95, 0x10,                    //       REPORT_COUNT (16)
    0x81, 0x02,                    //       INPUT (Data,Var,Abs)
    0xc0,                          //         END_COLLECTION
    0xc0,                          //     END_COLLECTION
    0xc0                           // END_COLLECTION
};

Now the keys are recognized, but in reverse order. CR1 is seen as 17. CR2 as 16. CR3 as 15. CR16 as 2.
There are two problems.

  • The first is that the driver adds one to the relative index when creating the gesture, which it shouldn't.
  • The second is that the CR keys are collected into the caps dictionary in reverse.
    This is actually per spec.
    The documentation at https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/button-capability-arrays states:

    "Each usage or usage range for a button array main item specified in a report descriptor is described by its own capability structure in a button capability array. The order in which the capability structures are added to a capability array is the reverse of the order in which the usages are specified for a main item."

When I reverse the caps list before collecting, the order of CR keys is correct:

for buttonCaps in reversed(self._dev.inputButtonCaps):
    # ...

Note that sorting by data index does not fix this. The indices are already in ascending order, and when processing the raw input report, the HID parser actually reverses the data so they once again match. I initially didn't believe it myself but that's what it's doing.

@michaelDCurran
Copy link
Member Author

michaelDCurran commented Sep 16, 2021 via email

@moyanming
Copy link
Contributor

I fixed my report descriptor by specifying the cursor routing keys as a bitmap rather than via an array index. The new descriptor is as follows:

Descriptor Code


const BYTE ReportDescriptor[199] = {
    0x05, 0x41,                    // USAGE_PAGE (Braille Display Page)
    0x09, 0x01,                    // USAGE (Braille Display)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x1a, 0x01, 0x02,              //   USAGE_MINIMUM (Braille Keyboard Dot 1)
    0x2a, 0x08, 0x02,              //   USAGE_MAXIMUM (Braille Keyboard Dot 8)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0a, 0x02,              //   USAGE (Braille Keyboard Left Space)
    0x0a, 0x0b, 0x02,              //   USAGE (Braille Keyboard Right Space)
    0x0a, 0x10, 0x02,              //   USAGE (Braille Joystick Center)
    0x0a, 0x11, 0x02,              //   USAGE (Braille Joystick Up)
    0x0a, 0x12, 0x02,              //   USAGE (Braille Joystick Down)
    0x0a, 0x13, 0x02,              //   USAGE (Braille Joystick Left)
    0x0a, 0x14, 0x02,              //   USAGE (Braille Joystick Right)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x07,                    //   REPORT_COUNT (7)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0d, 0x02,              //   USAGE (Braille Left Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x0a, 0x0e, 0x02,              //   USAGE (Braille Right Controls)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x0a, 0x1a, 0x02,              //     USAGE (Braille Pan Left)
    0x0a, 0x1b, 0x02,              //     USAGE (Braille Pan Right)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x05, 0x41,                    //   USAGE_PAGE (Braille Display Page)
    0x09, 0x02,                    //   USAGE (Braille Row)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x09, 0x03,                    //     USAGE (8 Dot Braille Cell)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x10,                    //     REPORT_COUNT (16)
    0x91, 0x02,                    //     OUTPUT (Data,Var,Abs)
    0x09, 0xfa,                    //     USAGE (Router Set 1)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x0a, 0x00, 0x01,              //       USAGE (Router Key)
    0x15, 0x00,                    //       LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //       LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //       REPORT_SIZE (1)
    0x95, 0x10,                    //       REPORT_COUNT (16)
    0x81, 0x02,                    //       INPUT (Data,Var,Abs)
    0xc0,                          //         END_COLLECTION
    0xc0,                          //     END_COLLECTION
    0xc0                           // END_COLLECTION
};

Now the keys are recognized, but in reverse order. CR1 is seen as 17. CR2 as 16. CR3 as 15. CR16 as 2. There are two problems.

  • The first is that the driver adds one to the relative index when creating the gesture, which it shouldn't.
  • The second is that the CR keys are collected into the caps dictionary in reverse.
    This is actually per spec.
    The documentation at https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/button-capability-arrays states:

    "Each usage or usage range for a button array main item specified in a report descriptor is described by its own capability structure in a button capability array. The order in which the capability structures are added to a capability array is the reverse of the order in which the usages are specified for a main item."

When I reverse the caps list before collecting, the order of CR keys is correct:

for buttonCaps in reversed(self._dev.inputButtonCaps):
    # ...

Note that sorting by data index does not fix this. The indices are already in ascending order, and when processing the raw input report, the HID parser actually reverses the data so they once again match. I initially didn't believe it myself but that's what it's doing.

Hi @FelixGruetzmacher
is there an efficient way to add the USAGE (Router Key)? 16 router keys need 3 bytes * 16=48 bytes, but the 40 router keys need 3 bytes * 40=120 bytes.

I have test the following report descriptor but the router key never worked:
`
0x09, 0xfa, // USAGE (Router Set 1)

0xa1, 0x02, // COLLECTION (Logical)

0x0a, 0x00, 0x01, // USAGE (Router Key)

0x15, 0x01, // LOGICAL_MINIMUM (1)

0x25, 0x10, // LOGICAL_MAXIMUM (16)

0x75, 0x08, // REPORT_SIZE (8)

0x95, 0x01, // REPORT_COUNT (1)

0x81, 0x00, // INPUT (Data,Ary,Abs)

0xc0, // END_COLLECTION
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet