USB Clickity Clack
A device by itself isn't enough to communicate. All device I/O in USB occurs with an endpoint. What's more, every device has to have at least a control endpoint (endpoint 0), although you can have multiple input or output endpoints.
HID devices always have a control endpoint and an input endpoint. Many also use an output endpoint. All data appears in a special format called a report.
If you had to actually write a custom driver for USB devices, it would be a lot of work and a lot of repeated work. However, most platforms have support for user programs to directly work with USB or HID devices. For example, the WINUSB driver on Windows will work. A better answer, though, is to use libusb. This library provides a stable API to a variety of platform-specific back ends. Practically, that means you should be able to communicate the same way using Linux, Windows, or Mac.
There are also some wrappers (often around libusb) specifically for HID devices. One example is HIDAPI. I didn't use HIDAPI, however, since it was simple enough to directly use libusb for my purposes. It is worth noting that there are two versions of libusb. The 1.0 version is the one you should be using, but there are many examples on the Internet using the 0.1 library, which is older. However, there is a translation layer for the 1.0 library if you want to make the old-style calls.
If you look at the libusb API, you will see that many of the APIs are for finding a specific USB device. For now, these don't matter much because I know what device I want. By the same token, since I have an already-built report, I can defer worrying about the exact format of the report.
Here's a stripped down version of the code (the full version is available online):
int main(int argc, char *argv[])
{
struct libusb_device_handle *dev;
// init USB lib (this is the 1.0 lib)
if (libusb_init(NULL)<0)
{
printf("Can't open libusb\n");
return 1;
}
// Open the device
dev=libusb_open_device_with_vid_pid(NULL,VID,PID);
if (!dev)
{
printf("Can't find device\n");
rv=1;
}
else
{
// unhook the kernel's driver on
rv=libusb_detach_kernel_driver(dev,INTERFACE);
// Get the interface for us
rv=libusb_claim_interface(dev,INTERFACE);
// Write the command
rv=libusb_control_transfer(dev,
LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|
LIBUSB_ENDPOINT_OUT, // type of write
0x9, // request type (SET_REPORT)
0x300, // value (0x3 = feature, 00=report ID)
INTERFACE, // INTERFACE
cdata, // data packet
sizeof(cdata), // byte count
1000); // timeout
// put everything back and close up
libusb_release_interface(dev,INTERFACE);
libusb_attach_kernel_driver(dev,INTERFACE);
libusb_close(dev);
libusb_exit(NULL);
}
}
Between the comments and the API documentation, you shouldn't have much trouble deciphering the operation. The keyboard's interface for the special key enable is #2 (INTERFACE in the program). The cdata array holds the reverse-engineered report necessary. VID and PID are just #defines that hold the vendor and product IDs, respectively.
Did it work? Yes it did. After the program runs, Linux is able to see the special keys and I can use any number of methods to map them to actions. Next time, I'll talk more about libusb and how you can apply this library to your own designs.

