// gKeyMapper daemon - raw USB HID access to the keyboard's vendor interface. #include "g910_device.h" #include // libusb.h uses zero-length arrays, which -Wpedantic flags. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop namespace gkeymapper::daemon { GKeyDevice::~GKeyDevice() { close(); } bool GKeyDevice::open() { 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) { 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) { 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) { 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(command.data()), static_cast(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