187 lines
5.5 KiB
C++
187 lines
5.5 KiB
C++
// gKeyMapper daemon - raw USB HID access to the keyboard's vendor interface.
|
|
|
|
#include "g910_device.h"
|
|
|
|
#include <cstdio>
|
|
|
|
// libusb.h uses zero-length arrays, which -Wpedantic flags.
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wpedantic"
|
|
#include <libusb.h>
|
|
#pragma GCC diagnostic pop
|
|
|
|
namespace gkeymapper::daemon {
|
|
|
|
GKeyDevice::~GKeyDevice() {
|
|
close();
|
|
}
|
|
|
|
bool GKeyDevice::open(bool quiet) {
|
|
if (handle_) {
|
|
return true;
|
|
}
|
|
|
|
if (libusb_init(&context_) != 0) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: libusb_init failed\n");
|
|
return false;
|
|
}
|
|
|
|
libusb_device** devices = nullptr;
|
|
ssize_t count = libusb_get_device_list(context_, &devices);
|
|
if (count < 0) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: no USB devices\n");
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
libusb_device* target = nullptr;
|
|
for (ssize_t i = 0; i < count && !target; ++i) {
|
|
libusb_device_descriptor desc{};
|
|
if (libusb_get_device_descriptor(devices[i], &desc) != 0) {
|
|
continue;
|
|
}
|
|
if (find_profile(desc.idVendor, desc.idProduct) != nullptr) {
|
|
target = devices[i];
|
|
profile_ = find_profile(desc.idVendor, desc.idProduct);
|
|
}
|
|
}
|
|
|
|
if (!target) {
|
|
if (!quiet) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: no supported keyboard found\n");
|
|
}
|
|
libusb_free_device_list(devices, 1);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (libusb_open(target, &handle_) != 0) {
|
|
if (!quiet) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: could not open %s (permissions?)\n",
|
|
profile_->name);
|
|
}
|
|
libusb_free_device_list(devices, 1);
|
|
close();
|
|
return false;
|
|
}
|
|
libusb_free_device_list(devices, 1);
|
|
|
|
if (!find_endpoint()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Detach and claim the vendor interface, like the reference driver.
|
|
if (libusb_kernel_driver_active(handle_, profile_->interfaceNumber) == 1) {
|
|
if (libusb_detach_kernel_driver(handle_, profile_->interfaceNumber) == 0) {
|
|
detached_kernel_driver_ = true;
|
|
}
|
|
}
|
|
if (libusb_claim_interface(handle_, profile_->interfaceNumber) != 0) {
|
|
if (!quiet) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: could not claim interface %d\n",
|
|
profile_->interfaceNumber);
|
|
}
|
|
close();
|
|
return false;
|
|
}
|
|
claimed_interface_ = profile_->interfaceNumber;
|
|
|
|
std::fprintf(stderr, "gkeymapper-daemon: %s found (endpoint 0x%02x)\n",
|
|
profile_->name, endpoint_);
|
|
return true;
|
|
}
|
|
|
|
bool GKeyDevice::find_endpoint() {
|
|
libusb_device* device = libusb_get_device(handle_);
|
|
libusb_config_descriptor* config = nullptr;
|
|
if (libusb_get_config_descriptor(device, profile_->configuration, &config) != 0) {
|
|
return false;
|
|
}
|
|
|
|
bool found = false;
|
|
for (int i = 0; i < config->bNumInterfaces && !found; ++i) {
|
|
const libusb_interface& iface = config->interface[i];
|
|
for (int a = 0; a < iface.num_altsetting && !found; ++a) {
|
|
const libusb_interface_descriptor& desc = iface.altsetting[a];
|
|
if (desc.bInterfaceNumber == profile_->interfaceNumber &&
|
|
desc.bAlternateSetting == profile_->altSetting &&
|
|
desc.bNumEndpoints > 0) {
|
|
endpoint_ = desc.endpoint[0].bEndpointAddress;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
libusb_free_config_descriptor(config);
|
|
return found;
|
|
}
|
|
|
|
bool GKeyDevice::disable_binding() {
|
|
if (!handle_ || !profile_) {
|
|
return false;
|
|
}
|
|
if (!profile_->disableBinding) {
|
|
return true;
|
|
}
|
|
|
|
for (const Report& command : profile_->disableCommands) {
|
|
int written = libusb_control_transfer(handle_, 0x21, 0x09, 0x0212,
|
|
profile_->interfaceNumber,
|
|
const_cast<unsigned char*>(command.data()),
|
|
static_cast<uint16_t>(command.size()),
|
|
1000);
|
|
if (written < 0) {
|
|
std::fprintf(stderr, "gkeymapper-daemon: disable-binding write failed (%d)\n",
|
|
written);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Drain the confirmation reports so they do not show up as key events.
|
|
uint8_t scratch[64];
|
|
for (int i = 0; i < 6; ++i) {
|
|
if (read_report(scratch, sizeof(scratch), 200) <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int GKeyDevice::read_report(uint8_t* buffer, int length, int timeoutMs) {
|
|
if (!handle_) {
|
|
return -1;
|
|
}
|
|
int transferred = 0;
|
|
int rc = libusb_interrupt_transfer(handle_, endpoint_, buffer, length,
|
|
&transferred, timeoutMs);
|
|
if (rc == 0) {
|
|
return transferred;
|
|
}
|
|
if (rc == LIBUSB_ERROR_TIMEOUT) {
|
|
return 0;
|
|
}
|
|
return rc; // negative
|
|
}
|
|
|
|
void GKeyDevice::close() {
|
|
if (handle_) {
|
|
if (claimed_interface_ >= 0) {
|
|
libusb_release_interface(handle_, claimed_interface_);
|
|
}
|
|
if (detached_kernel_driver_) {
|
|
libusb_attach_kernel_driver(handle_, profile_->interfaceNumber);
|
|
}
|
|
libusb_close(handle_);
|
|
}
|
|
handle_ = nullptr;
|
|
if (context_) {
|
|
libusb_exit(context_);
|
|
context_ = nullptr;
|
|
}
|
|
claimed_interface_ = -1;
|
|
detached_kernel_driver_ = false;
|
|
profile_ = nullptr;
|
|
}
|
|
|
|
} // namespace gkeymapper::daemon
|