/* * HID++ 1.0 library. * * Copyright 2013 Benjamin Tissoires * Copyright 2013 Red Hat, Inc * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include #include #include #include #include #include #include #define MIN(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; \ }) #if DEBUG_LVL > 0 const char *hidpp_errors[0xFF] = { [0x00] = "ERR_SUCCESS", [0x01] = "ERR_INVALID_SUBID", [0x02] = "ERR_INVALID_ADDRESS", [0x03] = "ERR_INVALID_VALUE", [0x04] = "ERR_CONNECT_FAIL", [0x05] = "ERR_TOO_MANY_DEVICES", [0x06] = "ERR_ALREADY_EXISTS", [0x07] = "ERR_BUSY", [0x08] = "ERR_UNKNOWN_DEVICE", [0x09] = "ERR_RESOURCE_ERROR", [0x0A] = "ERR_REQUEST_UNAVAILABLE", [0x0B] = "ERR_INVALID_PARAM_VALUE", [0x0C] = "ERR_WRONG_PIN_CODE", [0x0D ... 0xFE] = NULL, }; #endif const char *device_types[0xFF] = { [0x00] = "Unknown", [0x01] = "Keyboard", [0x02] = "Mouse", [0x03] = "Numpad", [0x04] = "Presenter", [0x05 ... 0x07] = "Reserved for future", [0x06] = "ERR_ALREADY_EXISTS", [0x07] = "ERR_BUSY", [0x08] = "Trackball", [0x09] = "Touchpad", [0x0A ... 0xFE] = NULL, }; static int hidpp10_write_command(int fd, __u8 *cmd, int size) { int res = write(fd, cmd, size); if (res == size) return 0; if (res < 0) { printf("Error: %d\n", errno); perror("write"); } else { errno = ENOMEM; printf("write: %d were written instead of %d.\n", res, size); perror("write"); } return res; } int hidpp10_request_command(int fd, union hidpp10_message *msg) { union hidpp10_message read_buffer; union hidpp10_message expected_header; union hidpp10_message expected_error_recv = ERROR_MSG(msg, RECEIVER_IDX); union hidpp10_message expected_error_dev = ERROR_MSG(msg, msg->msg.device_idx); int ret; __u8 hidpp_err = 0; /* create the expected header */ expected_header = *msg; switch (msg->msg.sub_id) { case SET_REGISTER_REQ: expected_header.msg.report_id = REPORT_ID_SHORT; break; case GET_REGISTER_REQ: expected_header.msg.report_id = REPORT_ID_SHORT; break; case SET_LONG_REGISTER_REQ: expected_header.msg.report_id = REPORT_ID_LONG; break; case GET_LONG_REGISTER_REQ: expected_header.msg.report_id = REPORT_ID_LONG; break; } pr_dbg("sending: "); pr_buffer(msg->data, SHORT_MESSAGE_LENGTH); #if DEBUG_LVL > 1 pr_dbg(" expected_header: "); pr_buffer(expected_header.data, SHORT_MESSAGE_LENGTH); pr_dbg(" expected_error_recv: "); pr_buffer(expected_error_recv.data, SHORT_MESSAGE_LENGTH); pr_dbg(" expected_error_dev: "); pr_buffer(expected_error_dev.data, SHORT_MESSAGE_LENGTH); #endif /* Send the message to the Device */ ret = hidpp10_write_command(fd, msg->data, SHORT_MESSAGE_LENGTH); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = read(fd, read_buffer.data, LONG_MESSAGE_LENGTH); #if DEBUG_LVL > 2 printf(" *** received: "); pr_buffer(read_buffer.data, ret); #endif /* actual answer */ if (!memcmp(read_buffer.data, expected_header.data, 4)) break; /* error */ if (!memcmp(read_buffer.data, expected_error_recv.data, 5) || !memcmp(read_buffer.data, expected_error_dev.data, 5)) { hidpp_err = read_buffer.msg.parameters[1]; pr_dbg(" HID++ error from the %s (%d): %s (%02x)\n", read_buffer.msg.device_idx == RECEIVER_IDX ? "receiver" : "device", read_buffer.msg.device_idx, hidpp_errors[hidpp_err] ? hidpp_errors[hidpp_err] : "Undocumented error code", hidpp_err); break; } } while (ret > 0); if (ret < 0) { printf(" USB error: %d\n", errno); perror("write"); goto out_err; } if (!hidpp_err) { pr_dbg(" received: "); pr_buffer(read_buffer.data, ret); /* copy the answer for the caller */ *msg = read_buffer; } ret = hidpp_err; out_err: return ret; } int hidpp10_toggle_individual_feature(int fd, struct unifying_device *dev, int feature_bit_r0, int feature_bit_r2) { unsigned idx = dev->index; union hidpp10_message mode = CMD_ENABLE_INDIVIDUAL_FEATURES(idx, GET_REGISTER_REQ); int res; /* first read the current values */ res = hidpp10_request_command(fd, &mode); if (res) return -1; /* toggle bits of r0 */ if (feature_bit_r0 >= 0) mode.msg.parameters[0] ^= 1U << feature_bit_r0; /* toggle bits of r2 */ if (feature_bit_r2 >= 0) mode.msg.parameters[2] ^= 1U << feature_bit_r2; /* now write back the change */ mode.msg.sub_id = SET_REGISTER_REQ; res = hidpp10_request_command(fd, &mode); return res; } int hidpp10_open_lock(int fd) { union hidpp10_message open_lock = CMD_DEVICE_CONNECTION_DISCONNECTION(0x00, CONNECT_DEVICES_OPEN_LOCK, 0x08); return hidpp10_request_command(fd, &open_lock); } int hidpp10_disconnect(int fd, int idx) { union hidpp10_message disconnect = CMD_DEVICE_CONNECTION_DISCONNECTION(idx + 1, CONNECT_DEVICES_DISCONNECT, 0x00); return hidpp10_request_command(fd, &disconnect); } void hidpp10_list_devices(int fd) { struct unifying_device dev; int i, res; for (i = 0; i < 6; ++i) { res = hidpp10_get_device_from_idx(fd, i, &dev); if (res) continue; printf("[%d] %s %s (Wireless PID: %04x)\n", i, device_types[dev.device_type] ? device_types[dev.device_type] : "", dev.name, dev.wpid); } } static int hidpp10_get_device_info(int fd, struct unifying_device *dev) { unsigned idx = dev->index; union hidpp10_message pairing_information = CMD_PAIRING_INFORMATION(idx, DEVICE_PAIRING_INFORMATION); union hidpp10_message device_name = CMD_PAIRING_INFORMATION(idx, DEVICE_NAME); union hidpp10_message firmware_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_NAME_AND_VERSION(1)); union hidpp10_message build_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_BUILD_NUMBER(1)); int name_size; int res; res = hidpp10_request_command(fd, &pairing_information); if (res) return -1; dev->report_interval = pairing_information.msg.string[2]; dev->wpid = (pairing_information.msg.string[3] << 8) | pairing_information.msg.string[4]; dev->device_type = pairing_information.msg.string[7]; res = hidpp10_request_command(fd, &device_name); if (res) return -1; name_size = device_name.msg.string[1]; memcpy(dev->name, &device_name.msg.string[2], sizeof(device_name.msg.string)); dev->name[MIN(name_size, sizeof(dev->name) - 1)] = '\0'; /* * This may fail on some devices * => we can not retrieve their FW version through HID++ 1.0. */ res = hidpp10_request_command(fd, &firmware_information); if (res) return 0; dev->fw_major = firmware_information.msg.string[1]; dev->fw_minor = firmware_information.msg.string[2]; res = hidpp10_request_command(fd, &build_information); if (res) return 0; dev->build = (build_information.msg.string[1] << 8) | build_information.msg.string[2]; return 0; } int hidpp10_get_device_from_wpid(int fd, __u16 wpid, struct unifying_device *dev) { int i, res; for (i = 0; i < 6; i++) { res = hidpp10_get_device_from_idx(fd, i, dev); if (res) continue; if (dev->wpid == wpid) break; res = -1; } return res; } int hidpp10_get_device_from_idx(int fd, int idx, struct unifying_device *dev) { dev->index = idx; return hidpp10_get_device_info(fd, dev); }