mirror of
https://github.com/libratbag/libratbag.git
synced 2026-11-07 15:14:38 +01:00
Mice behind an ASUS Omni receiver all enumerate with the same usb id (0b05:1ace), so the regular bus:vid:pid DeviceMatch cannot tell models apart. Identify the model during probe instead: - asus-rog-omni-receiver.device is a minimal bootstrap file that only declares Quirks=OMNI_RECEIVER, so the asus driver gets probed on the receiver's hidraw nodes - the probe queries the serial number signature (03 12 12 02) and matches its prefix against the OmniSignature field of the .device files, then re-resolves the device data for the identified usb id - an unknown signature fails the probe with -ENODEV, which also drops the receiver's keyboard and media control nodes The Strix Impact III family answers with data two bytes earlier than the original protocol (code plus params echo instead of code plus pad), so two quirks cover the new layouts: - LED_V2: per-zone LED responses with data at offset 4, brightness in 0-100 - SETTINGS_V2: settings and DPI responses with data at offset 4, rate/debounce/snapping as single bytes Also write DPI values with two bytes so presets above 12850 dpi work. Verified on a ROG Strix Impact III Wireless: identification on hidraw4, all 5 profiles with 8 buttons, 1 LED and DPI presets 400/800/1600/3200, report rate 1000 Hz, debounce 12 ms, LED brightness 127 (50%). Writes round-trip and survive a ratbagd restart.
1063 lines
27 KiB
C
1063 lines
27 KiB
C
/*
|
|
* Copyright © 2017 Red Hat, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <glib.h>
|
|
#include <limits.h>
|
|
|
|
#include "asus.h"
|
|
#include "driver-sinowealth.h"
|
|
#include "driver-steelseries.h"
|
|
#include "libratbag.h"
|
|
#include "libratbag-private.h"
|
|
#include "libratbag-data.h"
|
|
#include "hidpp20.h"
|
|
#include "shared-macro.h"
|
|
#include "usb-ids.h"
|
|
|
|
#define GROUP_DEVICE "Device"
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(GKeyFile *, g_key_file_free);
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(GError *, g_error_free);
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(char **, g_strfreev);
|
|
|
|
enum driver {
|
|
NONE = 0,
|
|
HIDPP10,
|
|
HIDPP20,
|
|
ROCCAT,
|
|
ROCCAT_KONE_PURE,
|
|
ROCCAT_EMP,
|
|
ETEKCITY,
|
|
GSKILL,
|
|
LOGITECH_G300,
|
|
LOGITECH_G600,
|
|
STEELSERIES,
|
|
ASUS,
|
|
SINOWEALTH,
|
|
SINOWEALTH_NUBWO,
|
|
OPENINPUT,
|
|
MARSGAMING,
|
|
};
|
|
|
|
struct data_hidpp20 {
|
|
int index;
|
|
uint32_t quirks; /* bitmask of enum hidpp20_quirk */
|
|
int led_count;
|
|
int report_rate;
|
|
int button_count;
|
|
};
|
|
|
|
struct data_hidpp10 {
|
|
int index;
|
|
int profile_count;
|
|
char *profile_type;
|
|
|
|
struct dpi_list *dpi_list;
|
|
struct dpi_range *dpi_range;
|
|
int led_count;
|
|
};
|
|
|
|
struct data_sinowealth {
|
|
struct list supported_devices;
|
|
};
|
|
|
|
struct data_steelseries {
|
|
int device_version;
|
|
int button_count;
|
|
int led_count;
|
|
struct dpi_list *dpi_list;
|
|
struct dpi_range *dpi_range;
|
|
int macro_length;
|
|
enum steelseries_quirk quirk;
|
|
};
|
|
|
|
struct data_asus {
|
|
int profile_count;
|
|
int button_count;
|
|
int button_mapping[ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP];
|
|
int led_count;
|
|
int dpi_count;
|
|
int is_wireless;
|
|
struct dpi_range *dpi_range;
|
|
uint32_t quirks;
|
|
int led_modes[ASUS_MAX_NUM_LED_MODES];
|
|
char *omni_signature; /* serial number prefix of mice behind an Omni receiver */
|
|
};
|
|
|
|
struct ratbag_device_data {
|
|
int refcount;
|
|
char *name;
|
|
char *driver;
|
|
|
|
enum driver drivertype;
|
|
enum ratbag_device_type devicetype;
|
|
|
|
union {
|
|
struct data_hidpp20 hidpp20;
|
|
struct data_hidpp10 hidpp10;
|
|
struct data_sinowealth sinowealth;
|
|
struct data_steelseries steelseries;
|
|
struct data_asus asus;
|
|
};
|
|
};
|
|
|
|
static void
|
|
init_data_hidpp10(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data)
|
|
{
|
|
const char *group = "Driver/hidpp10";
|
|
char *profile_type;
|
|
GError *error = NULL;
|
|
_cleanup_(freep) char *str = NULL;
|
|
int num;
|
|
|
|
data->hidpp10.index = -1;
|
|
data->hidpp10.profile_count = -1;
|
|
data->hidpp10.profile_type = NULL;
|
|
data->hidpp10.led_count = -1;
|
|
|
|
num = g_key_file_get_integer(keyfile, group, "DeviceIndex", &error);
|
|
if (num != 0 || !error)
|
|
data->hidpp10.index = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "Profiles", &error);
|
|
if (num > 0 || !error)
|
|
data->hidpp10.profile_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "Leds", &error);
|
|
if (num > 0 || !error)
|
|
data->hidpp10.led_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
profile_type = g_key_file_get_string(keyfile, group, "ProfileType", NULL);
|
|
if (profile_type)
|
|
data->hidpp10.profile_type = profile_type;
|
|
|
|
str = g_key_file_get_string(keyfile, group, "DpiRange", NULL);
|
|
if (str) {
|
|
data->hidpp10.dpi_range = dpi_range_from_string(str);
|
|
} else {
|
|
str = g_key_file_get_string(keyfile, group, "DpiList", NULL);
|
|
if (str)
|
|
data->hidpp10.dpi_list = dpi_list_from_string(str);
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
hidpp20_quirk_from_string(const char *str)
|
|
{
|
|
if (streq(str, "G305"))
|
|
return HIDPP20_QUIRK_G305;
|
|
if (streq(str, "G602"))
|
|
return HIDPP20_QUIRK_G602;
|
|
if (streq(str, "G502X_PLUS"))
|
|
return HIDPP20_QUIRK_G502X_PLUS;
|
|
if (streq(str, "INDEX_OFFSET"))
|
|
return HIDPP20_QUIRK_INDEX_OFFSET;
|
|
return HIDPP20_QUIRK_NONE;
|
|
}
|
|
|
|
static void
|
|
init_data_hidpp20(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data)
|
|
{
|
|
const char *group = "Driver/hidpp20";
|
|
GError *error = NULL;
|
|
int num;
|
|
|
|
data->hidpp20.button_count = -1;
|
|
data->hidpp20.index = -1;
|
|
data->hidpp20.led_count = -1;
|
|
data->hidpp20.report_rate = -1;
|
|
|
|
num = g_key_file_get_integer(keyfile, group, "Buttons", &error);
|
|
if (num > 0 || !error)
|
|
data->hidpp20.button_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "DeviceIndex", &error);
|
|
if (num != 0 || !error)
|
|
data->hidpp20.index = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "Leds", &error);
|
|
if (!error)
|
|
data->hidpp20.led_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "ReportRate", &error);
|
|
if (num > 0 || !error)
|
|
data->hidpp20.report_rate = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
/* Quirks are a bitmask: "Quirk" takes a ;-separated list. */
|
|
data->hidpp20.quirks = HIDPP20_QUIRK_NONE;
|
|
|
|
gsize quirks_count = 0;
|
|
_cleanup_(g_strfreevp) char **quirks = g_key_file_get_string_list(keyfile, group, "Quirk", &quirks_count, NULL);
|
|
for (gsize i = 0; quirks && i < quirks_count; i++)
|
|
data->hidpp20.quirks |= hidpp20_quirk_from_string(quirks[i]);
|
|
}
|
|
|
|
static void
|
|
init_data_sinowealth(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data)
|
|
{
|
|
const char *devices_group = "Driver/sinowealth/devices/";
|
|
|
|
GError *error = NULL;
|
|
int rc;
|
|
|
|
size_t group_count = 0;
|
|
_cleanup_(g_strfreevp) char **groups = g_key_file_get_groups(keyfile, &group_count);
|
|
|
|
list_init(&data->sinowealth.supported_devices);
|
|
|
|
for (size_t i = 0; i < group_count; ++i) {
|
|
const char *device_group = groups[i];
|
|
const char* fw_version = startswith(device_group, devices_group);
|
|
if (fw_version == NULL)
|
|
continue;
|
|
rc = (int)strlen(fw_version);
|
|
if (rc != SINOWEALTH_FW_VERSION_LEN) {
|
|
log_error(ratbag,
|
|
"Group '%s': incorrect firmware version string length `%d` (must be `%d`)\n",
|
|
device_group, rc, SINOWEALTH_FW_VERSION_LEN);
|
|
continue;
|
|
}
|
|
|
|
struct sinowealth_device_data *device = zalloc(sizeof(struct sinowealth_device_data));
|
|
|
|
device->fw_version = strdup_safe(fw_version);
|
|
|
|
rc = g_key_file_get_integer(keyfile, device_group, "Buttons", &error);
|
|
if (rc != 0 && !error) {
|
|
device->button_count = rc;
|
|
} else {
|
|
device->button_count = -1;
|
|
}
|
|
g_clear_error(&error);
|
|
|
|
device->device_name = g_key_file_get_string(keyfile, device_group, "DeviceName", &error);
|
|
g_clear_error(&error);
|
|
|
|
_cleanup_free_ char *led_type_str = g_key_file_get_string(keyfile, device_group, "LedType", &error);
|
|
if (led_type_str) {
|
|
if (streq(led_type_str, "RGB")) {
|
|
device->led_type = SINOWEALTH_LED_TYPE_RGB;
|
|
} else if (streq(led_type_str, "RBG")) {
|
|
device->led_type = SINOWEALTH_LED_TYPE_RBG;
|
|
} else if (streq(led_type_str, "None")) {
|
|
device->led_type = SINOWEALTH_LED_TYPE_NONE;
|
|
} else {
|
|
log_error(ratbag, "Unknown LED type '%s' in group '%s'\n", led_type_str, device_group);
|
|
|
|
device->led_type = SINOWEALTH_LED_TYPE_NONE;
|
|
}
|
|
}
|
|
g_clear_error(&error);
|
|
|
|
rc = g_key_file_get_integer(keyfile, device_group, "Profiles", &error);
|
|
if (rc != 0 && !error) {
|
|
device->profile_count = rc;
|
|
} else {
|
|
device->profile_count = -1;
|
|
}
|
|
|
|
list_insert(&data->sinowealth.supported_devices, &device->link);
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_data_steelseries(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data)
|
|
{
|
|
const char *group = "Driver/steelseries";
|
|
GError *error = NULL;
|
|
_cleanup_(freep) char *dpi_range = NULL;
|
|
_cleanup_(freep) char *quirk = NULL;
|
|
int num;
|
|
|
|
data->steelseries.device_version = -1;
|
|
data->steelseries.button_count = -1;
|
|
data->steelseries.led_count = -1;
|
|
data->steelseries.dpi_list = NULL;
|
|
data->steelseries.dpi_range = NULL;
|
|
data->steelseries.quirk = STEELSERIES_QURIK_NONE;
|
|
|
|
num = g_key_file_get_integer(keyfile, group, "Buttons", &error);
|
|
if (num != 0 || !error)
|
|
data->steelseries.button_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "Leds", &error);
|
|
if (num > 0 || !error)
|
|
data->steelseries.led_count = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "DeviceVersion", &error);
|
|
if (num > 0 || !error)
|
|
data->steelseries.device_version = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
dpi_range = g_key_file_get_string(keyfile, group, "DpiRange", NULL);
|
|
if (dpi_range) {
|
|
data->steelseries.dpi_range = dpi_range_from_string(dpi_range);
|
|
} else {
|
|
dpi_range = g_key_file_get_string(keyfile, group, "DpiList", NULL);
|
|
if (dpi_range)
|
|
data->steelseries.dpi_list = dpi_list_from_string(dpi_range);
|
|
}
|
|
|
|
error = NULL;
|
|
num = g_key_file_get_integer(keyfile, group, "MacroLength", &error);
|
|
if (num > 0 || !error)
|
|
data->steelseries.macro_length = num;
|
|
if (error)
|
|
g_error_free(error);
|
|
|
|
quirk = g_key_file_get_string(keyfile, group, "Quirk", NULL);
|
|
if (quirk) {
|
|
if (streq(quirk, "Rival100"))
|
|
data->steelseries.quirk = STEELSERIES_QUIRK_RIVAL100;
|
|
if (streq(quirk, "SenseiRAW"))
|
|
data->steelseries.quirk = STEELSERIES_QUIRK_SENSEIRAW;
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_data_asus(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data)
|
|
{
|
|
const char *group = "Driver/asus";
|
|
GError *error = NULL;
|
|
|
|
data->asus.profile_count = -1;
|
|
data->asus.button_count = -1;
|
|
data->asus.led_count = -1;
|
|
data->asus.dpi_count = -1;
|
|
data->asus.dpi_range = NULL;
|
|
data->asus.is_wireless = -1;
|
|
data->asus.quirks = 0;
|
|
for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON * ASUS_MAX_NUM_BUTTON_GROUP; i++)
|
|
data->asus.button_mapping[i] = -1;
|
|
for (unsigned int i = 0; i < ASUS_MAX_NUM_LED_MODES; i++)
|
|
data->asus.led_modes[i] = -1;
|
|
|
|
int profiles = g_key_file_get_integer(keyfile, group, "Profiles", &error);
|
|
if (!error && profiles >= 0)
|
|
data->asus.profile_count = profiles;
|
|
g_clear_error(&error);
|
|
|
|
int buttons = g_key_file_get_integer(keyfile, group, "Buttons", &error);
|
|
if (!error && buttons >= 0 && buttons <= ASUS_MAX_NUM_BUTTON)
|
|
data->asus.button_count = buttons;
|
|
g_clear_error(&error);
|
|
|
|
gsize btn_map_count = 0;
|
|
_cleanup_(g_strfreevp) char **btn_map = g_key_file_get_string_list(keyfile, group, "ButtonMapping", &btn_map_count, &error);
|
|
if (!error && btn_map) {
|
|
for (unsigned int i = 0; i < btn_map_count; i++) {
|
|
data->asus.button_mapping[i] = strtoul(btn_map[i], NULL, 16);
|
|
}
|
|
}
|
|
g_clear_error(&error);
|
|
|
|
gsize btn_map_2nd_count = 0;
|
|
_cleanup_(g_strfreevp) char **btn_map_2nd = g_key_file_get_string_list(keyfile, group, "ButtonMappingSecondary", &btn_map_2nd_count, &error);
|
|
if (!error && btn_map_2nd) {
|
|
for (unsigned int i = 0; i < btn_map_2nd_count; i++) {
|
|
data->asus.button_mapping[i + ASUS_MAX_NUM_BUTTON] = strtoul(btn_map_2nd[i], NULL, 16);
|
|
}
|
|
}
|
|
g_clear_error(&error);
|
|
|
|
int leds = g_key_file_get_integer(keyfile, group, "Leds", &error);
|
|
if (!error && leds >= 0 && leds <= ASUS_MAX_NUM_LED)
|
|
data->asus.led_count = leds;
|
|
g_clear_error(&error);
|
|
|
|
int dpis = g_key_file_get_integer(keyfile, group, "Dpis", &error);
|
|
if (!error && dpis >= 2 && dpis <= ASUS_MAX_NUM_DPI)
|
|
data->asus.dpi_count = dpis;
|
|
g_clear_error(&error);
|
|
|
|
_cleanup_(freep) char *dpi_range = g_key_file_get_string(keyfile, group, "DpiRange", &error);
|
|
if (!error && dpi_range)
|
|
data->asus.dpi_range = dpi_range_from_string(dpi_range);
|
|
g_clear_error(&error);
|
|
|
|
int wireless = g_key_file_get_integer(keyfile, group, "Wireless", &error);
|
|
if (!error && (wireless == 0 || wireless == 1))
|
|
data->asus.is_wireless = wireless;
|
|
g_clear_error(&error);
|
|
|
|
data->asus.omni_signature = g_key_file_get_string(keyfile, group, "OmniSignature", &error);
|
|
g_clear_error(&error);
|
|
|
|
gsize quirks_count = 0;
|
|
_cleanup_(g_strfreevp) char **quirks = g_key_file_get_string_list(keyfile, group, "Quirks", &quirks_count, &error);
|
|
if (!error && quirks) {
|
|
for (unsigned int i = 0; i < quirks_count; i++) {
|
|
if (streq(quirks[i], "DOUBLE_DPI")) {
|
|
data->asus.quirks |= ASUS_QUIRK_DOUBLE_DPI;
|
|
} else if (streq(quirks[i], "STRIX_PROFILE")) {
|
|
data->asus.quirks |= ASUS_QUIRK_STRIX_PROFILE;
|
|
} else if (streq(quirks[i], "RAW_BRIGHTNESS")) {
|
|
data->asus.quirks |= ASUS_QUIRK_RAW_BRIGHTNESS;
|
|
} else if (streq(quirks[i], "SEPARATE_XY_DPI")) {
|
|
data->asus.quirks |= ASUS_QUIRK_SEPARATE_XY_DPI;
|
|
} else if (streq(quirks[i], "SEPARATE_LEDS")) {
|
|
data->asus.quirks |= ASUS_QUIRK_SEPARATE_LEDS;
|
|
} else if (streq(quirks[i], "BUTTONS_SECONDARY")) {
|
|
data->asus.quirks |= ASUS_QUIRK_BUTTONS_SECONDARY;
|
|
} else if (streq(quirks[i], "OMNI_RECEIVER")) {
|
|
data->asus.quirks |= ASUS_QUIRK_OMNI_RECEIVER;
|
|
} else if (streq(quirks[i], "LED_V2")) {
|
|
data->asus.quirks |= ASUS_QUIRK_LED_V2;
|
|
} else if (streq(quirks[i], "SETTINGS_V2")) {
|
|
data->asus.quirks |= ASUS_QUIRK_SETTINGS_V2;
|
|
} else {
|
|
log_error(ratbag, "%s is invalid quirk. Ignoring...\n", quirks[i]);
|
|
}
|
|
}
|
|
}
|
|
g_clear_error(&error);
|
|
|
|
gsize led_modes_count = 0;
|
|
_cleanup_(g_strfreevp) char **led_modes = g_key_file_get_string_list(keyfile, group, "LedModes", &led_modes_count, &error);
|
|
if (!error && led_modes) {
|
|
for (unsigned int i = 0; i < led_modes_count; i++) {
|
|
if (streq(led_modes[i], "ON")) {
|
|
data->asus.led_modes[i] = RATBAG_LED_ON;
|
|
} else if (streq(led_modes[i], "BREATHING")) {
|
|
data->asus.led_modes[i] = RATBAG_LED_BREATHING;
|
|
} else if (streq(led_modes[i], "CYCLE")) {
|
|
data->asus.led_modes[i] = RATBAG_LED_CYCLE;
|
|
} else {
|
|
log_error(ratbag, "%s is invalid LED mode. Ignoring...\n", led_modes[i]);
|
|
}
|
|
}
|
|
}
|
|
g_clear_error(&error);
|
|
}
|
|
|
|
static const struct driver_map {
|
|
enum driver map;
|
|
const char *driver;
|
|
void (*init)(struct ratbag *ratbag,
|
|
GKeyFile *keyfile,
|
|
struct ratbag_device_data *data);
|
|
} driver_map[] = {
|
|
{ HIDPP10, "hidpp10", init_data_hidpp10 },
|
|
{ HIDPP20, "hidpp20", init_data_hidpp20 },
|
|
{ ROCCAT, "roccat", NULL },
|
|
{ ROCCAT_KONE_PURE, "roccat-kone-pure", NULL },
|
|
{ ROCCAT_EMP, "roccat-kone-emp", NULL },
|
|
{ ETEKCITY, "etekcity", NULL},
|
|
{ GSKILL, "gskill", NULL },
|
|
{ LOGITECH_G300, "logitech_g300", NULL},
|
|
{ LOGITECH_G600, "logitech_g600", NULL},
|
|
{ STEELSERIES, "steelseries", init_data_steelseries },
|
|
{ ASUS, "asus", init_data_asus },
|
|
{ SINOWEALTH, "sinowealth", init_data_sinowealth },
|
|
{ SINOWEALTH_NUBWO, "sinowealth_nubwo", NULL},
|
|
{ OPENINPUT, "openinput", NULL },
|
|
{ MARSGAMING, "marsgaming", NULL },
|
|
};
|
|
|
|
const char *
|
|
ratbag_device_data_get_driver(const struct ratbag_device_data *data)
|
|
{
|
|
return data->driver;
|
|
}
|
|
|
|
const char *
|
|
ratbag_device_data_get_name(const struct ratbag_device_data *data)
|
|
{
|
|
return data->name;
|
|
}
|
|
|
|
enum ratbag_device_type
|
|
ratbag_device_data_get_device_type(const struct ratbag_device_data *data)
|
|
{
|
|
return data->devicetype;
|
|
}
|
|
|
|
struct ratbag_device_data *
|
|
ratbag_device_data_ref(struct ratbag_device_data *data)
|
|
{
|
|
data->refcount++;
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
ratbag_device_data_destroy(struct ratbag_device_data *data)
|
|
{
|
|
switch (data->drivertype) {
|
|
case HIDPP10:
|
|
dpi_list_free(data->hidpp10.dpi_list);
|
|
free(data->hidpp10.dpi_range);
|
|
free(data->hidpp10.profile_type);
|
|
break;
|
|
case SINOWEALTH: {
|
|
struct sinowealth_device_data *device_data = NULL;
|
|
struct sinowealth_device_data *device_data_next = NULL;
|
|
|
|
list_for_each_safe(device_data, device_data_next, &data->sinowealth.supported_devices, link) {
|
|
free(device_data->device_name);
|
|
free(device_data->fw_version);
|
|
free(device_data);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case STEELSERIES:
|
|
dpi_list_free(data->steelseries.dpi_list);
|
|
free(data->steelseries.dpi_range);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(data->name);
|
|
free(data->driver);
|
|
free(data);
|
|
}
|
|
|
|
struct ratbag_device_data *
|
|
ratbag_device_data_unref(struct ratbag_device_data *data)
|
|
{
|
|
if (data == NULL)
|
|
return NULL;
|
|
|
|
assert(data->refcount > 0);
|
|
data->refcount--;
|
|
|
|
if (data->refcount == 0)
|
|
ratbag_device_data_destroy(data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbag_device_data *, ratbag_device_data_unref);
|
|
|
|
static bool
|
|
match(const struct input_id *id, char **strv)
|
|
{
|
|
const char *bus;
|
|
char str[64];
|
|
int i = 0;
|
|
|
|
switch (id->bustype) {
|
|
case BUS_USB: bus = "usb"; break;
|
|
case BUS_BLUETOOTH: bus = "bluetooth"; break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
snprintf(str, sizeof(str), "%s:%04x:%04x", bus, id->vendor, id->product);
|
|
|
|
while (strv[i]) {
|
|
if (streq(strv[i], str))
|
|
return true;
|
|
i++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
file_data_matches(struct ratbag *ratbag,
|
|
const char *path, const struct input_id *id,
|
|
struct ratbag_device_data **data_out)
|
|
{
|
|
_cleanup_(g_key_file_freep) GKeyFile *keyfile = NULL;
|
|
_cleanup_(g_error_freep) GError *error = NULL;
|
|
_cleanup_(g_strfreevp) char **match_strv = NULL;
|
|
_cleanup_(ratbag_device_data_unrefp) struct ratbag_device_data *data = NULL;
|
|
_cleanup_free_ char *devicetype = NULL;
|
|
int rc;
|
|
|
|
keyfile = g_key_file_new();
|
|
rc = g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, &error);
|
|
if (!rc) {
|
|
log_error(ratbag, "Failed to parse keyfile %s: %s\n", path, error->message);
|
|
return false;
|
|
}
|
|
|
|
match_strv = g_key_file_get_string_list(keyfile, GROUP_DEVICE, "DeviceMatch", NULL, NULL);
|
|
if (!match_strv) {
|
|
log_error(ratbag, "Missing DeviceMatch in %s\n", basename(path));
|
|
return false;
|
|
}
|
|
|
|
if (!match(id, match_strv))
|
|
return false;
|
|
|
|
data = zalloc(sizeof(*data));
|
|
data->refcount = 1;
|
|
data->name = g_key_file_get_string(keyfile, GROUP_DEVICE, "Name", NULL);
|
|
if (!data->name) {
|
|
return false; // ignore_clang_sa_mem_leak
|
|
}
|
|
|
|
data->driver = g_key_file_get_string(keyfile, GROUP_DEVICE, "Driver", NULL);
|
|
if (!data->driver) {
|
|
log_error(ratbag, "Missing Driver in %s\n", basename(path));
|
|
return false;
|
|
} else {
|
|
const struct driver_map *map;
|
|
|
|
data->drivertype = NONE;
|
|
ARRAY_FOR_EACH(driver_map, map) {
|
|
if (streq(map->driver, data->driver)) {
|
|
data->drivertype = map->map;
|
|
if (map->init)
|
|
map->init(ratbag, keyfile, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (data->drivertype == NONE) {
|
|
log_error(ratbag, "Unknown driver %s in %s\n",
|
|
data->driver, basename(path));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
devicetype = g_key_file_get_string(keyfile, GROUP_DEVICE, "DeviceType", NULL);
|
|
if (devicetype == NULL) {
|
|
log_error(ratbag, "No DeviceType found in '%s'\n", basename(path));
|
|
data->devicetype = TYPE_UNSPECIFIED;
|
|
return false;
|
|
}
|
|
if (streq(devicetype, "keyboard")) {
|
|
data->devicetype = TYPE_KEYBOARD;
|
|
} else if (streq(devicetype, "mouse")) {
|
|
data->devicetype = TYPE_MOUSE;
|
|
} else if (streq(devicetype, "other")) {
|
|
data->devicetype = TYPE_OTHER;
|
|
} else {
|
|
log_error(ratbag, "Invalid DeviceType '%s' in '%s'\n", devicetype, basename(path));
|
|
data->devicetype = TYPE_UNSPECIFIED;
|
|
return false;
|
|
}
|
|
|
|
*data_out = data;
|
|
data = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
filter_device_files(const struct dirent *entry)
|
|
{
|
|
const char *SUFFIX = ".device";
|
|
const char *name = entry->d_name;
|
|
size_t len = 0;
|
|
size_t slen = 0;
|
|
|
|
if (!name || name[0] == '.')
|
|
return 0;
|
|
|
|
len = strlen(name);
|
|
slen = strlen(SUFFIX);
|
|
if (len <= slen)
|
|
return 0;
|
|
|
|
return streq(&name[len - slen], SUFFIX);
|
|
}
|
|
|
|
struct ratbag_device_data *
|
|
ratbag_device_data_new_for_id(struct ratbag *ratbag, const struct input_id *id)
|
|
{
|
|
struct ratbag_device_data *data = NULL;
|
|
struct dirent **files;
|
|
int n, nfiles;
|
|
const char *datadir;
|
|
|
|
datadir = getenv("LIBRATBAG_DATA_DIR");
|
|
if (!datadir)
|
|
datadir = LIBRATBAG_DATA_DIR;
|
|
log_debug(ratbag, "Using data directory '%s'\n", datadir);
|
|
|
|
n = scandir(datadir, &files, filter_device_files, alphasort);
|
|
if (n <= 0) {
|
|
log_error(ratbag, "Unable to locate device files in %s: %s\n",
|
|
datadir, n == 0 ? "No files found" : strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
nfiles = n;
|
|
while(n--) {
|
|
_cleanup_(freep) char *file = NULL;
|
|
int rc;
|
|
|
|
rc = xasprintf(&file, "%s/%s", datadir, files[n]->d_name);
|
|
if (rc == -1)
|
|
goto out;
|
|
if (file_data_matches(ratbag, file, id, &data))
|
|
goto out;
|
|
}
|
|
|
|
if (id->vendor == USB_VENDOR_ID_LOGITECH && (id->product & 0xff00) == 0xc500)
|
|
log_debug(ratbag, "%04x:%04x is a Logitech receiver, not a device. Ignoring...\n", id->vendor, id->product);
|
|
else if (!data)
|
|
log_debug(ratbag, "No data file found for %04x:%04x\n", id->vendor, id->product);
|
|
|
|
out:
|
|
while(nfiles--)
|
|
free(files[nfiles]);
|
|
free(files);
|
|
|
|
return data;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_omni_signature_match(struct ratbag *ratbag,
|
|
const char *signature,
|
|
struct input_id *id)
|
|
{
|
|
struct dirent **files;
|
|
const char *datadir;
|
|
int n, nfiles;
|
|
size_t best_len = 0;
|
|
int rc = -ENODEV;
|
|
|
|
datadir = getenv("LIBRATBAG_DATA_DIR");
|
|
if (!datadir)
|
|
datadir = LIBRATBAG_DATA_DIR;
|
|
|
|
n = scandir(datadir, &files, filter_device_files, alphasort);
|
|
if (n <= 0) {
|
|
log_error(ratbag, "Unable to locate device files in %s: %s\n",
|
|
datadir, n == 0 ? "No files found" : strerror(errno));
|
|
return rc;
|
|
}
|
|
|
|
nfiles = n;
|
|
while (n--) {
|
|
_cleanup_(g_key_file_freep) GKeyFile *keyfile = NULL;
|
|
_cleanup_(g_error_freep) GError *error = NULL;
|
|
_cleanup_(g_strfreevp) char **match_strv = NULL;
|
|
_cleanup_free_ char *omni_signature = NULL;
|
|
_cleanup_(freep) char *file = NULL;
|
|
size_t len;
|
|
unsigned int vendor, product;
|
|
|
|
if (xasprintf(&file, "%s/%s", datadir, files[n]->d_name) == -1)
|
|
goto out;
|
|
|
|
keyfile = g_key_file_new();
|
|
if (!g_key_file_load_from_file(keyfile, file, G_KEY_FILE_NONE, &error)) {
|
|
log_error(ratbag, "Failed to parse keyfile %s: %s\n",
|
|
file, error->message);
|
|
continue;
|
|
}
|
|
|
|
omni_signature = g_key_file_get_string(keyfile, "Driver/asus",
|
|
"OmniSignature", NULL);
|
|
if (!omni_signature)
|
|
continue;
|
|
|
|
len = strlen(omni_signature);
|
|
if (len <= best_len || strncmp(signature, omni_signature, len) != 0)
|
|
continue;
|
|
|
|
match_strv = g_key_file_get_string_list(keyfile, GROUP_DEVICE,
|
|
"DeviceMatch", NULL, NULL);
|
|
if (!match_strv)
|
|
continue;
|
|
|
|
/* the device must have a usb id to be resolvable */
|
|
if (sscanf(match_strv[0], "usb:%x:%x", &vendor, &product) != 2)
|
|
continue;
|
|
|
|
id->vendor = vendor;
|
|
id->product = product;
|
|
best_len = len;
|
|
rc = 0;
|
|
}
|
|
|
|
out:
|
|
while (nfiles--)
|
|
free(files[nfiles]);
|
|
free(files);
|
|
|
|
return rc;
|
|
}
|
|
/* HID++ 1.0 */
|
|
|
|
int
|
|
ratbag_device_data_hidpp10_get_index(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.index;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_hidpp10_get_profile_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.profile_count;
|
|
}
|
|
|
|
const char *
|
|
ratbag_device_data_hidpp10_get_profile_type(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.profile_type;
|
|
}
|
|
|
|
struct dpi_list *
|
|
ratbag_device_data_hidpp10_get_dpi_list(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.dpi_list;
|
|
}
|
|
|
|
struct dpi_range *
|
|
ratbag_device_data_hidpp10_get_dpi_range(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.dpi_range;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_hidpp10_get_led_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP10);
|
|
|
|
return data->hidpp10.led_count;
|
|
}
|
|
|
|
/* HID++ 2.0 */
|
|
|
|
int
|
|
ratbag_device_data_hidpp20_get_index(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP20);
|
|
|
|
return data->hidpp20.index;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_hidpp20_get_button_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP20);
|
|
|
|
return data->hidpp20.button_count;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_hidpp20_get_led_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP20);
|
|
|
|
return data->hidpp20.led_count;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_hidpp20_get_report_rate(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP20);
|
|
|
|
return data->hidpp20.report_rate;
|
|
}
|
|
|
|
uint32_t
|
|
ratbag_device_data_hidpp20_get_quirks(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == HIDPP20);
|
|
|
|
return data->hidpp20.quirks;
|
|
}
|
|
|
|
/* SinoWealth */
|
|
|
|
const struct list *
|
|
ratbag_device_data_sinowealth_get_supported_devices(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == SINOWEALTH);
|
|
|
|
return &data->sinowealth.supported_devices;
|
|
}
|
|
|
|
/* SteelSeries */
|
|
|
|
int
|
|
ratbag_device_data_steelseries_get_device_version(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.device_version;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_steelseries_get_button_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.button_count;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_steelseries_get_led_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.led_count;
|
|
}
|
|
|
|
struct dpi_list *
|
|
ratbag_device_data_steelseries_get_dpi_list(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.dpi_list;
|
|
}
|
|
|
|
struct dpi_range *
|
|
ratbag_device_data_steelseries_get_dpi_range(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.dpi_range;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_steelseries_get_macro_length(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.macro_length;
|
|
}
|
|
|
|
enum steelseries_quirk
|
|
ratbag_device_data_steelseries_get_quirk(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == STEELSERIES);
|
|
|
|
return data->steelseries.quirk;
|
|
}
|
|
|
|
/* ASUS */
|
|
|
|
int
|
|
ratbag_device_data_asus_get_profile_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.profile_count;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_asus_get_button_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.button_count;
|
|
}
|
|
|
|
const int *
|
|
ratbag_device_data_asus_get_button_mapping(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.button_mapping;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_asus_get_led_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.led_count;
|
|
}
|
|
|
|
const int *
|
|
ratbag_device_data_asus_get_led_modes(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.led_modes;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_asus_get_dpi_count(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.dpi_count;
|
|
}
|
|
|
|
struct dpi_range *
|
|
ratbag_device_data_asus_get_dpi_range(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.dpi_range;
|
|
}
|
|
|
|
int
|
|
ratbag_device_data_asus_is_wireless(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.is_wireless;
|
|
}
|
|
|
|
uint32_t
|
|
ratbag_device_data_asus_get_quirks(const struct ratbag_device_data *data)
|
|
{
|
|
assert(data->drivertype == ASUS);
|
|
return data->asus.quirks;
|
|
}
|