Files
gKeyMapper/daemon/g910_device.cpp
2026-10-18 03:50:33 +02:00

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