Media keys working again

This commit is contained in:
2026-10-18 03:50:33 +02:00
parent 726a7ae92d
commit 798708b3e7
20 changed files with 590 additions and 112 deletions

View File

@@ -5,6 +5,8 @@
#include <algorithm>
#include <initializer_list>
#include <linux/input-event-codes.h>
namespace gkeymapper::daemon {
namespace {
@@ -36,6 +38,25 @@ std::vector<Report> g910DisableCommands() {
return {report20({0x11, 0xff, 0x08, 0x2e, 0x01})};
}
// Media keys: a 2-byte report, "02" followed by one bit per key ("02 00" is
// "all released"). The bit assignment and the Linux codes they map to were
// measured on a G910 Orion Spark (2026-10-18) by reading the hidraw node of the
// vendor interface while the kernel driver owned it, and cross-checked against
// the key codes the kernel emits for that interface (input/event* with
// Phys …/input1). The keyboard reports these keys *only* there, so the daemon
// has to replay them once it holds the interface (see README).
std::vector<MediaKey> g910MediaKeys() {
return {
{0x01, KEY_NEXTSONG, "next track"},
{0x02, KEY_PREVIOUSSONG, "previous track"},
{0x04, KEY_STOPCD, "stop"},
{0x08, KEY_PLAYPAUSE, "play/pause"},
{0x10, KEY_VOLUMEUP, "volume up"},
{0x20, KEY_VOLUMEDOWN, "volume down"},
{0x40, KEY_MUTE, "mute"},
};
}
DeviceProfile make_g910(const char* name, uint16_t product, bool disableBinding) {
DeviceProfile profile;
profile.name = name;
@@ -45,7 +66,8 @@ DeviceProfile make_g910(const char* name, uint16_t product, bool disableBinding)
profile.interfaceNumber = 1;
profile.altSetting = 0;
profile.gKeyReports = g910GKeys();
profile.releaseReport = report20({0x11, 0xff, 0x08, 0x00});
profile.mediaPrefix = 0x02;
profile.mediaKeys = g910MediaKeys();
profile.disableBinding = disableBinding;
if (disableBinding) {
profile.disableCommands = g910DisableCommands();
@@ -74,23 +96,67 @@ const DeviceProfile* find_profile(uint16_t vendor, uint16_t product) {
return nullptr;
}
int match_gkey(const DeviceProfile& profile, const uint8_t* data, int length) {
if (length <= 0) {
return -1;
bool decode_gkey_state(const DeviceProfile& profile, const uint8_t* data, int length,
uint32_t& pressed) {
pressed = 0;
if (data == nullptr || length <= 0 || profile.gKeyReports.empty()) {
return false;
}
for (size_t i = 0; i < profile.gKeyReports.size(); ++i) {
const Report& report = profile.gKeyReports[i];
if (static_cast<int>(report.size()) == length &&
std::equal(report.begin(), report.end(), data)) {
return static_cast<int>(i);
const Report& first = profile.gKeyReports.front();
if (static_cast<int>(first.size()) != length) {
return false; // a different report family, e.g. the 2-byte media keys
}
// Bytes that are the same in every key report are that model's fixed header;
// the bytes after it are the key bitmap (G1..G8 in byte 4, G9 in byte 5).
size_t header = 0;
for (; header < first.size(); ++header) {
bool shared = true;
for (const Report& report : profile.gKeyReports) {
if (report.size() != first.size() || report[header] != first[header]) {
shared = false;
break;
}
}
if (!shared) {
break;
}
}
return -1;
if (header >= first.size()) {
return false; // no bit to read: pressed and released cannot be told apart
}
for (size_t i = 0; i < header; ++i) {
if (data[i] != first[i]) {
return false; // disable/enable G-key command, memory key, LED answer
}
}
for (size_t key = 0; key < profile.gKeyReports.size() && key < 32; ++key) {
const Report& press = profile.gKeyReports[key];
for (size_t i = header; i < press.size() && i < static_cast<size_t>(length); ++i) {
if (press[i] != 0 && (data[i] & press[i]) == press[i]) {
pressed |= 1u << key;
break;
}
}
}
return true;
}
bool is_release_report(const DeviceProfile& profile, const uint8_t* data, int length) {
return static_cast<int>(profile.releaseReport.size()) == length &&
std::equal(profile.releaseReport.begin(), profile.releaseReport.end(), data);
bool decode_media_key(const DeviceProfile& profile, const uint8_t* data, int length,
uint32_t& bits) {
bits = 0;
if (data == nullptr || profile.mediaPrefix == 0 || length < 2) {
return false;
}
if (data[0] != profile.mediaPrefix) {
return false; // a macro-key report, a memory key, a command or LED answer
}
// The report is two bytes; a longer read would be the same report padded by
// the endpoint, so only the bit byte is of interest.
bits = data[1];
return true;
}
} // namespace gkeymapper::daemon