forked from drath/libratbag
At least on HID++ 2.0, the LED types appear to be random and don't match the protocol specs. Other protocols don't even have those bits, so let's make this a generic entry. This is pre-filled for all LEDs but drivers can override it where required. Fixes #377 Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
482 lines
10 KiB
C
482 lines
10 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 "libratbag.h"
|
|
#include "libratbag-private.h"
|
|
#include "libratbag-data.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,
|
|
ETEKCITY,
|
|
GSKILL,
|
|
LOGITECH_G300
|
|
};
|
|
|
|
struct data_hidpp20 {
|
|
int index;
|
|
};
|
|
|
|
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 ratbag_device_data {
|
|
int refcount;
|
|
char *name;
|
|
char *driver;
|
|
char *svg;
|
|
|
|
enum driver drivertype;
|
|
|
|
union {
|
|
struct data_hidpp20 hidpp20;
|
|
struct data_hidpp10 hidpp10;
|
|
};
|
|
|
|
enum ratbag_led_type led_types[20];
|
|
size_t nled_types;
|
|
};
|
|
|
|
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 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.index = -1;
|
|
|
|
num = g_key_file_get_integer(keyfile, group, "DeviceIndex", &error);
|
|
if (num != 0 || !error)
|
|
data->hidpp20.index = num;
|
|
if (error)
|
|
g_error_free(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 },
|
|
{ ETEKCITY, "etekcity", NULL},
|
|
{ GSKILL, "gskill", NULL },
|
|
{ LOGITECH_G300, "logitech_g300", 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;
|
|
}
|
|
|
|
const char *
|
|
ratbag_device_data_get_svg(const struct ratbag_device_data *data)
|
|
{
|
|
return data->svg;
|
|
}
|
|
|
|
enum ratbag_led_type
|
|
ratbag_device_data_get_led_type(const struct ratbag_device_data *data,
|
|
unsigned int index)
|
|
{
|
|
assert(index < ARRAY_LENGTH(data->led_types));
|
|
|
|
return data->led_types[index];
|
|
}
|
|
|
|
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:
|
|
free(data->hidpp10.profile_type);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(data->name);
|
|
free(data->driver);
|
|
free(data->svg);
|
|
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 int
|
|
parse_ledtypes(char **strv, enum ratbag_led_type *types, size_t ntypes)
|
|
{
|
|
unsigned int i;
|
|
int count = 0;
|
|
|
|
for (i = 0; i < ntypes; i++)
|
|
types[i] = RATBAG_LED_TYPE_UNKNOWN;
|
|
|
|
if (!strv)
|
|
return count;
|
|
|
|
i = 0;
|
|
while(strv[i]) {
|
|
const char *s = strv[i];
|
|
|
|
if (streq(s, "logo"))
|
|
types[i] = RATBAG_LED_TYPE_LOGO;
|
|
else if (streq(s, "side"))
|
|
types[i] = RATBAG_LED_TYPE_SIDE;
|
|
else if (streq(s, "dpi"))
|
|
types[i] = RATBAG_LED_TYPE_SIDE;
|
|
else if (streq(s, "battery"))
|
|
types[i] = RATBAG_LED_TYPE_SIDE;
|
|
else
|
|
return -1;
|
|
|
|
count++;
|
|
i++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
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_(g_strfreevp) char **ledtypes_strv = NULL;
|
|
_cleanup_(ratbag_device_data_unrefp) struct ratbag_device_data *data = 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
data->svg = g_key_file_get_string(keyfile, GROUP_DEVICE, "Svg", NULL);
|
|
|
|
ledtypes_strv = g_key_file_get_string_list(keyfile, GROUP_DEVICE, "LedTypes", NULL, NULL);
|
|
if (parse_ledtypes(ledtypes_strv, data->led_types, ARRAY_LENGTH(data->led_types)) < 0) {
|
|
log_error(ratbag, "Invalid LedTypes string in '%s'\n", basename(path));
|
|
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;
|
|
int len, slen;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
out:
|
|
while(nfiles--)
|
|
free(files[nfiles]);
|
|
free(files);
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|