// gKeyMapper daemon - raw HID access to the keyboard's vendor interface. // // Uses the kernel's hidraw interface instead of libusb. hidraw provides // non-exclusive access: the kernel driver (usbhid) stays attached, and other // tools (OpenRGB, etc.) can open the same device at the same time. This // avoids the exclusive claim that blocked OpenRGB from detecting the keyboard. #include "g910_device.h" #include #include #include #include #include #include #include #include #include namespace gkeymapper::daemon { GKeyDevice::~GKeyDevice() { close(); } // Scan /sys/class/hidraw for a node whose HID_ID matches vendor:product and // whose sysfs path points at interface 1 (the vendor-specific one). Returns // the minor number (>= 0) or -1. int GKeyDevice::find_hidraw_node(uint16_t vendor, uint16_t product) { char id_str[64]; std::snprintf(id_str, sizeof(id_str), "0003:%08X:%08X", (unsigned)vendor, (unsigned)product); for (int i = 0; i < 64; ++i) { char path[512]; std::snprintf(path, sizeof(path), "/sys/class/hidraw/hidraw%d/device/uevent", i); FILE* f = std::fopen(path, "r"); if (!f) { continue; } char buf[2048]; size_t n = std::fread(buf, 1, sizeof(buf) - 1, f); std::fclose(f); buf[n] = '\0'; // Check HID_ID matches our vendor:product. if (std::strstr(buf, id_str) == nullptr) { continue; } // Check the sysfs path contains ":1." (interface 1, the vendor one). // The readlink gives us the full device path. char linkpath[512]; std::snprintf(linkpath, sizeof(linkpath), "/sys/class/hidraw/hidraw%d", i); char resolved[1024]; ssize_t rlen = readlink(linkpath, resolved, sizeof(resolved) - 1); if (rlen < 0) { continue; } resolved[rlen] = '\0'; // The resolved path contains the interface number in the form // :.. Interface 1 (vendor-specific) has // config 1, so the path contains ":1.1" (altsetting 1) or ":1.0". // We check the uevent's HID_PHYS instead, which contains "input1" // for interface 1 and "input0" for interface 0. if (std::strstr(buf, "input1") == nullptr) { continue; } return i; } return -1; } bool GKeyDevice::open(bool quiet) { if (fd_ >= 0) { return true; } // Find a supported profile first (by vendor/product). // We iterate all profiles and try to find a matching hidraw node. const auto& profs = profiles(); const DeviceProfile* matched = nullptr; int node = -1; for (const DeviceProfile& p : profs) { node = find_hidraw_node(p.vendor, p.product); if (node >= 0) { matched = &p; break; } } if (!matched || node < 0) { if (!quiet) { std::fprintf(stderr, "gkeymapper-daemon: no supported keyboard found\n"); } return false; } profile_ = matched; char devpath[64]; std::snprintf(devpath, sizeof(devpath), "/dev/hidraw%d", node); fd_ = ::open(devpath, O_RDWR | O_NONBLOCK); if (fd_ < 0) { if (!quiet) { std::fprintf(stderr, "gkeymapper-daemon: could not open %s (%s)\n", devpath, std::strerror(errno)); } profile_ = nullptr; return false; } // Disable the default G-key binding (G-keys mapped to F-keys) if needed. // Done after open so disable_binding() can use the fd. // (disable_binding is called separately by main.cpp) std::fprintf(stderr, "gkeymapper-daemon: %s found (hidraw%d)\n", profile_->name, node); return true; } bool GKeyDevice::disable_binding() { if (fd_ < 0 || !profile_) { return false; } if (!profile_->disableBinding) { return true; } for (const Report& command : profile_->disableCommands) { // HID SET_REPORT (feature report). The first byte of the command // is the report ID. int len = static_cast(command.size()); // HIDIOCSFEATURE takes a buffer where byte 0 is the report ID. std::vector buf(command.begin(), command.end()); int rc = ioctl(fd_, HIDIOCSFEATURE(len), buf.data()); if (rc < 0) { std::fprintf(stderr, "gkeymapper-daemon: disable-binding ioctl failed (%s)\n", std::strerror(errno)); return false; } } // Drain confirmation reports. 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 (fd_ < 0) { return -1; } struct pollfd pfd; pfd.fd = fd_; pfd.events = POLLIN; pfd.revents = 0; int rc = poll(&pfd, 1, timeoutMs); if (rc < 0) { if (errno == EINTR) { return 0; } return -1; } if (rc == 0) { return 0; // timeout } if (!(pfd.revents & POLLIN)) { return 0; } ssize_t n = ::read(fd_, buffer, length); if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } return -1; } return static_cast(n); } void GKeyDevice::close() { if (fd_ >= 0) { ::close(fd_); } fd_ = -1; profile_ = nullptr; } } // namespace gkeymapper::daemon