mirror of
https://github.com/libratbag/libratbag.git
synced 2026-11-07 23:23:52 +01:00
In case the udev property is not present, we might want to query the driver for the SVG name. This is useful for driver-test as there is no udev device attached to it, and for hidpp20 as we have auto-detection. We should then be able to implement this in hidpp20 also so that the name gets automatically created. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
1744 lines
40 KiB
C
1744 lines
40 KiB
C
/*
|
|
* Copyright © 2015 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 <assert.h>
|
|
#include <errno.h>
|
|
#include <libudev.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
#include "usb-ids.h"
|
|
#include "libratbag-private.h"
|
|
#include "libratbag-util.h"
|
|
|
|
static enum ratbag_error_code
|
|
error_code(enum ratbag_error_code code)
|
|
{
|
|
switch(code) {
|
|
case RATBAG_SUCCESS:
|
|
case RATBAG_ERROR_DEVICE:
|
|
case RATBAG_ERROR_CAPABILITY:
|
|
case RATBAG_ERROR_VALUE:
|
|
case RATBAG_ERROR_SYSTEM:
|
|
case RATBAG_ERROR_IMPLEMENTATION:
|
|
break;
|
|
default:
|
|
assert(!"Invalid error code. This is a library bug.");
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static void
|
|
ratbag_profile_destroy(struct ratbag_profile *profile);
|
|
static void
|
|
ratbag_button_destroy(struct ratbag_button *button);
|
|
static void
|
|
ratbag_led_destroy(struct ratbag_led *led);
|
|
|
|
static void
|
|
ratbag_default_log_func(struct ratbag *ratbag,
|
|
enum ratbag_log_priority priority,
|
|
const char *format, va_list args)
|
|
{
|
|
const char *prefix;
|
|
|
|
switch(priority) {
|
|
case RATBAG_LOG_PRIORITY_RAW: prefix = "raw"; break;
|
|
case RATBAG_LOG_PRIORITY_DEBUG: prefix = "debug"; break;
|
|
case RATBAG_LOG_PRIORITY_INFO: prefix = "info"; break;
|
|
case RATBAG_LOG_PRIORITY_ERROR: prefix = "error"; break;
|
|
default: prefix="<invalid priority>"; break;
|
|
}
|
|
|
|
fprintf(stderr, "ratbag %s: ", prefix);
|
|
vfprintf(stderr, format, args);
|
|
}
|
|
|
|
void
|
|
log_msg_va(struct ratbag *ratbag,
|
|
enum ratbag_log_priority priority,
|
|
const char *format,
|
|
va_list args)
|
|
{
|
|
if (ratbag->log_handler &&
|
|
ratbag->log_priority <= priority)
|
|
ratbag->log_handler(ratbag, priority, format, args);
|
|
}
|
|
|
|
void
|
|
log_msg(struct ratbag *ratbag,
|
|
enum ratbag_log_priority priority,
|
|
const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
log_msg_va(ratbag, priority, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void
|
|
log_buffer(struct ratbag *ratbag,
|
|
enum ratbag_log_priority priority,
|
|
const char *header,
|
|
uint8_t *buf, size_t len)
|
|
{
|
|
_cleanup_free_ char *output_buf = NULL;
|
|
char *sep = "";
|
|
unsigned int i, n;
|
|
unsigned int buf_len;
|
|
|
|
if (ratbag->log_handler &&
|
|
ratbag->log_priority > priority)
|
|
return;
|
|
|
|
buf_len = header ? strlen(header) : 0;
|
|
buf_len += len * 3;
|
|
buf_len += 1; /* terminating '\0' */
|
|
|
|
output_buf = zalloc(buf_len);
|
|
n = 0;
|
|
if (header)
|
|
n += snprintf_safe(output_buf, buf_len - n, "%s", header);
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
n += snprintf_safe(&output_buf[n], buf_len - n, "%s%02x", sep, buf[i] & 0xFF);
|
|
sep = " ";
|
|
}
|
|
|
|
log_msg(ratbag, priority, "%s\n", output_buf);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_log_set_priority(struct ratbag *ratbag,
|
|
enum ratbag_log_priority priority)
|
|
{
|
|
switch (priority) {
|
|
case RATBAG_LOG_PRIORITY_RAW:
|
|
case RATBAG_LOG_PRIORITY_DEBUG:
|
|
case RATBAG_LOG_PRIORITY_INFO:
|
|
case RATBAG_LOG_PRIORITY_ERROR:
|
|
break;
|
|
default:
|
|
log_bug_client(ratbag,
|
|
"Invalid log priority %d. Using INFO instead\n",
|
|
priority);
|
|
priority = RATBAG_LOG_PRIORITY_INFO;
|
|
}
|
|
ratbag->log_priority = priority;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_log_priority
|
|
ratbag_log_get_priority(const struct ratbag *ratbag)
|
|
{
|
|
return ratbag->log_priority;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_log_set_handler(struct ratbag *ratbag,
|
|
ratbag_log_handler log_handler)
|
|
{
|
|
ratbag->log_handler = log_handler;
|
|
}
|
|
|
|
struct ratbag_device*
|
|
ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device,
|
|
const char *name, const struct input_id *id)
|
|
{
|
|
struct ratbag_device *device = NULL;
|
|
|
|
device = zalloc(sizeof(*device));
|
|
device->name = strdup_safe(name);
|
|
device->ratbag = ratbag_ref(ratbag);
|
|
device->refcount = 1;
|
|
device->udev_device = udev_device_ref(udev_device);
|
|
device->ids = *id;
|
|
list_init(&device->profiles);
|
|
|
|
list_insert(&ratbag->devices, &device->link);
|
|
|
|
/* We assume that most devices have this capability, so let's set it
|
|
* by default. The few devices that miss this capability should
|
|
* unset it instead.
|
|
*/
|
|
ratbag_device_set_capability(device,
|
|
RATBAG_DEVICE_CAP_QUERY_CONFIGURATION);
|
|
|
|
return device;
|
|
}
|
|
|
|
void
|
|
ratbag_device_destroy(struct ratbag_device *device)
|
|
{
|
|
struct ratbag_profile *profile, *next;
|
|
|
|
if (!device)
|
|
return;
|
|
|
|
/* if we get to the point where the device is destroyed, profiles,
|
|
* buttons, etc. are at a refcount of 0, so we can destroy
|
|
* everything */
|
|
if (device->driver && device->driver->remove)
|
|
device->driver->remove(device);
|
|
|
|
list_for_each_safe(profile, next, &device->profiles, link)
|
|
ratbag_profile_destroy(profile);
|
|
|
|
if (device->udev_device)
|
|
udev_device_unref(device->udev_device);
|
|
|
|
list_remove(&device->link);
|
|
|
|
ratbag_unref(device->ratbag);
|
|
free(device->name);
|
|
free(device);
|
|
}
|
|
|
|
static inline bool
|
|
ratbag_sanity_check_device(struct ratbag_device *device)
|
|
{
|
|
struct ratbag *ratbag = device->ratbag;
|
|
_cleanup_profile_ struct ratbag_profile *profile = NULL;
|
|
bool has_active = false;
|
|
unsigned int nres, nprofiles;
|
|
bool rc = false;
|
|
unsigned int i;
|
|
|
|
/* arbitrary number: max 16 profiles, does any mouse have more? but
|
|
* since we have num_profiles unsigned, it also checks for
|
|
* accidental negative */
|
|
if (device->num_profiles == 0 || device->num_profiles > 16) {
|
|
log_bug_libratbag(ratbag,
|
|
"%s: invalid number of profiles (%d)\n",
|
|
device->name,
|
|
device->num_profiles);
|
|
goto out;
|
|
}
|
|
|
|
nprofiles = ratbag_device_get_num_profiles(device);
|
|
for (i = 0; i < nprofiles; i++) {
|
|
profile = ratbag_device_get_profile(device, i);
|
|
if (!profile)
|
|
goto out;
|
|
|
|
/* Allow max 1 active profile */
|
|
if (profile->is_active) {
|
|
if (has_active) {
|
|
log_bug_libratbag(ratbag,
|
|
"%s: multiple active profiles\n",
|
|
device->name);
|
|
goto out;
|
|
}
|
|
has_active = true;
|
|
}
|
|
|
|
nres = ratbag_profile_get_num_resolutions(profile);
|
|
if (nres == 0 || nres > 16) {
|
|
log_bug_libratbag(ratbag,
|
|
"%s: invalid number of resolutions (%d)\n",
|
|
device->name,
|
|
nres);
|
|
goto out;
|
|
}
|
|
|
|
ratbag_profile_unref(profile);
|
|
profile = NULL;
|
|
}
|
|
|
|
/* Require 1 active profile */
|
|
if (!has_active) {
|
|
log_bug_libratbag(ratbag,
|
|
"%s: no profile set as active profile\n",
|
|
device->name);
|
|
goto out;
|
|
}
|
|
|
|
rc = true;
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static inline bool
|
|
ratbag_test_driver(struct ratbag_device *device,
|
|
const struct input_id *dev_id,
|
|
const char *driver_name,
|
|
struct ratbag_test_device *test_device)
|
|
{
|
|
struct ratbag *ratbag = device->ratbag;
|
|
struct ratbag_driver *driver;
|
|
int rc;
|
|
|
|
list_for_each(driver, &ratbag->drivers, link) {
|
|
if (streq(driver->id, driver_name)) {
|
|
device->driver = driver;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!device->driver) {
|
|
log_error(ratbag, "%s: driver '%s' does not exist\n",
|
|
device->name, driver_name);
|
|
goto error;
|
|
}
|
|
|
|
if (test_device)
|
|
rc = device->driver->test_probe(device, test_device);
|
|
else
|
|
rc = device->driver->probe(device);
|
|
if (rc == 0) {
|
|
if (!ratbag_sanity_check_device(device)) {
|
|
goto error;
|
|
} else {
|
|
log_debug(ratbag,
|
|
"driver match found: %s\n",
|
|
device->driver->name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (rc != -ENODEV)
|
|
log_error(ratbag, "%s: error opening hidraw node (%s)\n",
|
|
device->name, strerror(-rc));
|
|
|
|
error:
|
|
device->driver = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
ratbag_driver_fallback_logitech(struct ratbag_device *device,
|
|
const struct input_id *dev_id)
|
|
{
|
|
int rc;
|
|
|
|
if (dev_id->vendor != USB_VENDOR_ID_LOGITECH)
|
|
return false;
|
|
|
|
rc = ratbag_test_driver(device, dev_id, "hidpp20", NULL);
|
|
if (!rc)
|
|
rc = ratbag_test_driver(device, dev_id, "hidpp10", NULL);
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool
|
|
ratbag_assign_driver(struct ratbag_device *device,
|
|
const struct input_id *dev_id,
|
|
struct ratbag_test_device *test_device)
|
|
{
|
|
bool rc;
|
|
const char *driver_name;
|
|
|
|
if (!test_device) {
|
|
driver_name = udev_prop_value(device->udev_device, "RATBAG_DRIVER");
|
|
} else {
|
|
driver_name = "test_driver";
|
|
}
|
|
|
|
if (driver_name) {
|
|
rc = ratbag_test_driver(device, dev_id, driver_name, test_device);
|
|
} else {
|
|
rc = ratbag_driver_fallback_logitech(device, dev_id);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
get_device_name(struct udev_device *device,
|
|
char *name, size_t sz)
|
|
{
|
|
const char *prop;
|
|
|
|
prop = udev_prop_value(device, "NAME");
|
|
if (!prop)
|
|
return -EINVAL;
|
|
|
|
/* udev name is enclosed by " */
|
|
sz = min(sz, strlen(prop) - 1);
|
|
snprintf(name, sz, "%s", &prop[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
get_product_id(struct udev_device *device, struct input_id *id)
|
|
{
|
|
const char *product;
|
|
struct input_id ids;
|
|
int rc;
|
|
|
|
product = udev_prop_value(device, "PRODUCT");
|
|
if (!product)
|
|
return -1;
|
|
|
|
rc = sscanf(product, "%hx/%hx/%hx/%hx", &ids.bustype,
|
|
&ids.vendor, &ids.product, &ids.version);
|
|
if (rc != 4)
|
|
return -1;
|
|
|
|
*id = ids;
|
|
return 0;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_device_new_from_udev_device(struct ratbag *ratbag,
|
|
struct udev_device *udev_device,
|
|
struct ratbag_device **device_out)
|
|
{
|
|
struct ratbag_device *device = NULL;
|
|
enum ratbag_error_code error = RATBAG_ERROR_DEVICE;
|
|
char name[128];
|
|
struct input_id id;
|
|
|
|
assert(ratbag != NULL);
|
|
assert(udev_device != NULL);
|
|
assert(device_out != NULL);
|
|
|
|
if (udev_prop_value(udev_device, "ID_INPUT_MOUSE") == NULL)
|
|
goto out_err;
|
|
|
|
if (get_product_id(udev_device, &id) != 0)
|
|
goto out_err;
|
|
|
|
if (get_device_name(udev_device, name, sizeof(name)) != 0)
|
|
goto out_err;
|
|
|
|
device = ratbag_device_new(ratbag, udev_device, name, &id);
|
|
if (!device)
|
|
goto out_err;
|
|
|
|
if (!ratbag_assign_driver(device, &device->ids, NULL))
|
|
goto out_err;
|
|
|
|
error = RATBAG_SUCCESS;
|
|
|
|
out_err:
|
|
|
|
if (error != RATBAG_SUCCESS)
|
|
ratbag_device_destroy(device);
|
|
else
|
|
*device_out = device;
|
|
|
|
return error_code(error);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_device *
|
|
ratbag_device_ref(struct ratbag_device *device)
|
|
{
|
|
assert(device->refcount < INT_MAX);
|
|
|
|
device->refcount++;
|
|
return device;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_device *
|
|
ratbag_device_unref(struct ratbag_device *device)
|
|
{
|
|
if (device == NULL)
|
|
return NULL;
|
|
|
|
assert(device->refcount > 0);
|
|
device->refcount--;
|
|
if (device->refcount == 0)
|
|
ratbag_device_destroy(device);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT const char *
|
|
ratbag_device_get_name(const struct ratbag_device* device)
|
|
{
|
|
return device->name;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT const char *
|
|
ratbag_device_get_svg_name(const struct ratbag_device* device)
|
|
{
|
|
const char *name;
|
|
|
|
name = udev_prop_value(device->udev_device, "RATBAG_SVG");
|
|
if (!name && device->driver->get_svg_name)
|
|
name = device->driver->get_svg_name(device);
|
|
|
|
return name;
|
|
}
|
|
|
|
const char *
|
|
ratbag_device_get_udev_property(const struct ratbag_device* device,
|
|
const char *name)
|
|
{
|
|
return udev_prop_value(device->udev_device, name);
|
|
}
|
|
|
|
void
|
|
ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver)
|
|
{
|
|
if (!driver->name) {
|
|
log_bug_libratbag(ratbag, "Driver is missing name\n");
|
|
return;
|
|
}
|
|
|
|
if (!driver->probe || !driver->remove) {
|
|
log_bug_libratbag(ratbag, "Driver %s is incomplete.\n", driver->name);
|
|
return;
|
|
}
|
|
list_insert(&ratbag->drivers, &driver->link);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag *
|
|
ratbag_create_context(const struct ratbag_interface *interface,
|
|
void *userdata)
|
|
{
|
|
struct ratbag *ratbag;
|
|
|
|
assert(interface != NULL);
|
|
assert(interface->open_restricted != NULL);
|
|
assert(interface->close_restricted != NULL);
|
|
|
|
ratbag = zalloc(sizeof(*ratbag));
|
|
ratbag->refcount = 1;
|
|
ratbag->interface = interface;
|
|
ratbag->userdata = userdata;
|
|
|
|
list_init(&ratbag->drivers);
|
|
list_init(&ratbag->devices);
|
|
ratbag->udev = udev_new();
|
|
if (!ratbag->udev) {
|
|
free(ratbag);
|
|
return NULL;
|
|
}
|
|
|
|
ratbag->log_handler = ratbag_default_log_func;
|
|
ratbag->log_priority = RATBAG_LOG_PRIORITY_INFO;
|
|
|
|
ratbag_register_driver(ratbag, &etekcity_driver);
|
|
ratbag_register_driver(ratbag, &hidpp20_driver);
|
|
ratbag_register_driver(ratbag, &hidpp10_driver);
|
|
ratbag_register_driver(ratbag, &logitech_g300_driver);
|
|
ratbag_register_driver(ratbag, &roccat_driver);
|
|
ratbag_register_driver(ratbag, &gskill_driver);
|
|
|
|
return ratbag;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag *
|
|
ratbag_ref(struct ratbag *ratbag)
|
|
{
|
|
ratbag->refcount++;
|
|
return ratbag;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag *
|
|
ratbag_unref(struct ratbag *ratbag)
|
|
{
|
|
if (ratbag == NULL)
|
|
return NULL;
|
|
|
|
assert(ratbag->refcount > 0);
|
|
ratbag->refcount--;
|
|
if (ratbag->refcount == 0) {
|
|
ratbag->udev = udev_unref(ratbag->udev);
|
|
free(ratbag);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct ratbag_button *
|
|
ratbag_create_button(struct ratbag_profile *profile, unsigned int index)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_button *button;
|
|
|
|
button = zalloc(sizeof(*button));
|
|
button->refcount = 0;
|
|
button->profile = profile;
|
|
button->index = index;
|
|
|
|
list_insert(&profile->buttons, &button->link);
|
|
|
|
if (device->driver->read_button)
|
|
device->driver->read_button(button);
|
|
|
|
return button;
|
|
}
|
|
|
|
static struct ratbag_led *
|
|
ratbag_create_led(struct ratbag_profile *profile, unsigned int index)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_led *led;
|
|
|
|
led = zalloc(sizeof(*led));
|
|
led->refcount = 0;
|
|
led->profile = profile;
|
|
led->index = index;
|
|
|
|
list_insert(&profile->leds, &led->link);
|
|
|
|
if (device->driver->read_led)
|
|
device->driver->read_led(led);
|
|
|
|
return led;
|
|
}
|
|
|
|
static int
|
|
ratbag_profile_init_buttons(struct ratbag_profile *profile, unsigned int count)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
ratbag_create_button(profile, i);
|
|
|
|
profile->device->num_buttons = count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ratbag_profile_init_leds(struct ratbag_profile *profile, unsigned int count)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
ratbag_create_led(profile, i);
|
|
|
|
profile->device->num_leds = count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct ratbag_profile *
|
|
ratbag_create_profile(struct ratbag_device *device,
|
|
unsigned int index,
|
|
unsigned int num_resolutions,
|
|
unsigned int num_buttons,
|
|
unsigned int num_leds)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
unsigned i;
|
|
|
|
profile = zalloc(sizeof(*profile));
|
|
profile->refcount = 0;
|
|
profile->device = device;
|
|
profile->index = index;
|
|
if (num_resolutions)
|
|
profile->resolution.modes = zalloc(num_resolutions *
|
|
sizeof(*profile->resolution.modes));
|
|
profile->resolution.num_modes = num_resolutions;
|
|
profile->is_enabled = true;
|
|
|
|
list_insert(&device->profiles, &profile->link);
|
|
list_init(&profile->buttons);
|
|
list_init(&profile->leds);
|
|
|
|
for (i = 0; i < num_resolutions; i++)
|
|
ratbag_resolution_init(profile, i, 0, 0, 0);
|
|
|
|
assert(device->driver->read_profile);
|
|
device->driver->read_profile(profile, index);
|
|
|
|
ratbag_profile_init_buttons(profile, num_buttons);
|
|
ratbag_profile_init_leds(profile, num_leds);
|
|
|
|
return profile;
|
|
}
|
|
|
|
int
|
|
ratbag_device_init_profiles(struct ratbag_device *device,
|
|
unsigned int num_profiles,
|
|
unsigned int num_resolutions,
|
|
unsigned int num_buttons,
|
|
unsigned int num_leds)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_profiles; i++) {
|
|
ratbag_create_profile(device, i, num_resolutions, num_buttons, num_leds);
|
|
}
|
|
|
|
device->num_profiles = num_profiles;
|
|
|
|
if (num_profiles > 1) {
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE);
|
|
|
|
/* having more than one profile means we can remap the buttons
|
|
* at least */
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY);
|
|
}
|
|
|
|
if (num_resolutions > 1)
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION);
|
|
|
|
if (num_leds > 1)
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_LED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_profile *
|
|
ratbag_profile_ref(struct ratbag_profile *profile)
|
|
{
|
|
assert(profile->refcount < INT_MAX);
|
|
|
|
ratbag_device_ref(profile->device);
|
|
profile->refcount++;
|
|
return profile;
|
|
}
|
|
|
|
static void
|
|
ratbag_profile_destroy(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_button *button, *b_next;
|
|
struct ratbag_led *led, *l_next;
|
|
|
|
/* if we get to the point where the profile is destroyed, buttons,
|
|
* resolutions , etc. are at a refcount of 0, so we can destroy
|
|
* everything */
|
|
list_for_each_safe(button, b_next, &profile->buttons, link)
|
|
ratbag_button_destroy(button);
|
|
|
|
list_for_each_safe(led, l_next, &profile->leds, link)
|
|
ratbag_led_destroy(led);
|
|
|
|
free(profile->resolution.modes);
|
|
|
|
list_remove(&profile->link);
|
|
free(profile);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_profile *
|
|
ratbag_profile_unref(struct ratbag_profile *profile)
|
|
{
|
|
if (profile == NULL)
|
|
return NULL;
|
|
|
|
assert(profile->refcount > 0);
|
|
profile->refcount--;
|
|
|
|
ratbag_device_unref(profile->device);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_profile *
|
|
ratbag_device_get_profile(struct ratbag_device *device, unsigned int index)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
|
|
if (index >= ratbag_device_get_num_profiles(device)) {
|
|
log_bug_client(device->ratbag, "Requested invalid profile %d\n", index);
|
|
return NULL;
|
|
}
|
|
|
|
list_for_each(profile, &device->profiles, link) {
|
|
if (profile->index == index)
|
|
return ratbag_profile_ref(profile);
|
|
}
|
|
|
|
log_bug_libratbag(device->ratbag, "Profile %d not found\n", index);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_profile_set_enabled(struct ratbag_profile *profile, bool enabled)
|
|
{
|
|
if (!ratbag_device_has_capability(profile->device,
|
|
RATBAG_DEVICE_CAP_DISABLE_PROFILE))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
profile->is_enabled = enabled;
|
|
profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_profile_is_active(struct ratbag_profile *profile)
|
|
{
|
|
return profile->is_active;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT bool
|
|
ratbag_profile_is_enabled(const struct ratbag_profile *profile)
|
|
{
|
|
return profile->is_enabled;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_device_get_num_profiles(struct ratbag_device *device)
|
|
{
|
|
return device->num_profiles;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_device_get_num_buttons(struct ratbag_device *device)
|
|
{
|
|
return device->num_buttons;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_device_get_num_leds(struct ratbag_device *device)
|
|
{
|
|
return device->num_leds;
|
|
}
|
|
|
|
void
|
|
ratbag_device_set_capability(struct ratbag_device *device,
|
|
enum ratbag_device_capability cap)
|
|
{
|
|
if (cap == RATBAG_DEVICE_CAP_NONE || cap >= MAX_CAP)
|
|
abort();
|
|
|
|
long_set_bit(device->capabilities, cap);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_device_has_capability(const struct ratbag_device *device,
|
|
enum ratbag_device_capability cap)
|
|
{
|
|
if (cap == RATBAG_DEVICE_CAP_NONE || cap >= MAX_CAP)
|
|
abort();
|
|
|
|
return long_bit_is_set(device->capabilities, cap);
|
|
}
|
|
|
|
static inline enum ratbag_error_code
|
|
write_led_helper(struct ratbag_device *device, struct ratbag_led *led)
|
|
{
|
|
return device->driver->write_led(led, led->mode, led->color, led->hz, led->brightness);
|
|
}
|
|
|
|
/* FIXME: This is a temporary fix for all of the drivers that have yet to be
|
|
* converted to the new profile-oriented API. Once all of the drivers have been
|
|
* converted, this code should be removed.
|
|
*/
|
|
static enum ratbag_error_code
|
|
ratbag_old_write_profile(struct ratbag_device *device)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
struct ratbag_resolution *resolution;
|
|
int rc;
|
|
unsigned int i;
|
|
|
|
assert(device->driver->write_profile);
|
|
|
|
list_for_each(profile, &device->profiles, link) {
|
|
if (!profile->dirty)
|
|
continue;
|
|
|
|
rc = device->driver->write_profile(profile);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
|
|
if (device->driver->write_resolution_dpi) {
|
|
for (i = 0; i < profile->resolution.num_modes; i++) {
|
|
resolution = &profile->resolution.modes[i];
|
|
|
|
rc = device->driver->write_resolution_dpi(
|
|
resolution, resolution->dpi_x,
|
|
resolution->dpi_y);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
}
|
|
}
|
|
|
|
if (device->driver->write_button) {
|
|
list_for_each(button, &profile->buttons, link) {
|
|
struct ratbag_button_action action =
|
|
button->action;
|
|
|
|
if (!button->dirty)
|
|
continue;
|
|
|
|
rc = device->driver->write_button(button,
|
|
&action);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
}
|
|
}
|
|
|
|
if (device->driver->write_led) {
|
|
list_for_each(led, &profile->leds, link) {
|
|
if (!led->dirty)
|
|
continue;
|
|
|
|
rc = write_led_helper(device, led);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_device_commit(struct ratbag_device *device)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
struct ratbag_button *button;
|
|
int rc;
|
|
|
|
if (!device->driver->commit) {
|
|
rc = ratbag_old_write_profile(device);
|
|
if (rc != RATBAG_SUCCESS)
|
|
return rc;
|
|
} else {
|
|
rc = device->driver->commit(device);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
}
|
|
|
|
list_for_each(profile, &device->profiles, link) {
|
|
profile->dirty = false;
|
|
|
|
list_for_each(button, &profile->buttons, link)
|
|
button->dirty = false;
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_profile_set_active(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_profile *p;
|
|
int rc;
|
|
|
|
if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
assert(device->driver->set_active_profile);
|
|
rc = device->driver->set_active_profile(device, profile->index);
|
|
if (rc)
|
|
return RATBAG_ERROR_DEVICE;
|
|
|
|
list_for_each(p, &device->profiles, link)
|
|
p->is_active = false;
|
|
|
|
profile->is_active = true;
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_profile_get_num_resolutions(struct ratbag_profile *profile)
|
|
{
|
|
return profile->resolution.num_modes;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_resolution *
|
|
ratbag_profile_get_resolution(struct ratbag_profile *profile, unsigned int idx)
|
|
{
|
|
struct ratbag_resolution *res;
|
|
unsigned max = ratbag_profile_get_num_resolutions(profile);
|
|
|
|
if (idx >= max) {
|
|
log_bug_client(profile->device->ratbag,
|
|
"Requested invalid resolution %d\n", idx);
|
|
return NULL;
|
|
}
|
|
|
|
res = &profile->resolution.modes[idx];
|
|
|
|
return ratbag_resolution_ref(res);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_resolution *
|
|
ratbag_resolution_ref(struct ratbag_resolution *resolution)
|
|
{
|
|
assert(resolution->refcount < INT_MAX);
|
|
|
|
ratbag_profile_ref(resolution->profile);
|
|
resolution->refcount++;
|
|
return resolution;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_resolution *
|
|
ratbag_resolution_unref(struct ratbag_resolution *resolution)
|
|
{
|
|
if (resolution == NULL)
|
|
return NULL;
|
|
|
|
assert(resolution->refcount > 0);
|
|
resolution->refcount--;
|
|
|
|
ratbag_profile_unref(resolution->profile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_has_capability(struct ratbag_resolution *resolution,
|
|
enum ratbag_resolution_capability cap)
|
|
{
|
|
switch (cap) {
|
|
case RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE:
|
|
case RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return !!(resolution->capabilities & (1 << cap));
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_resolution_set_dpi(struct ratbag_resolution *resolution,
|
|
unsigned int dpi)
|
|
{
|
|
struct ratbag_profile *profile = resolution->profile;
|
|
|
|
if (resolution->dpi_x != dpi || resolution->dpi_y != dpi) {
|
|
resolution->dpi_x = dpi;
|
|
resolution->dpi_y = dpi;
|
|
profile->dirty = true;
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_resolution_set_dpi_xy(struct ratbag_resolution *resolution,
|
|
unsigned int x, unsigned int y)
|
|
{
|
|
struct ratbag_profile *profile = resolution->profile;
|
|
|
|
if (!ratbag_resolution_has_capability(resolution,
|
|
RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
if ((x == 0 && y != 0) || (x != 0 && y == 0))
|
|
return RATBAG_ERROR_VALUE;
|
|
|
|
if (resolution->dpi_x != x || resolution->dpi_y != y) {
|
|
resolution->dpi_x = x;
|
|
resolution->dpi_y = y;
|
|
profile->dirty = true;
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_resolution_set_report_rate(struct ratbag_resolution *resolution,
|
|
unsigned int hz)
|
|
{
|
|
if (ratbag_resolution_has_capability(resolution,
|
|
RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE)) {
|
|
if (resolution->hz != hz) {
|
|
resolution->hz = hz;
|
|
resolution->profile->dirty = true;
|
|
}
|
|
} else {
|
|
struct ratbag_profile *profile = resolution->profile;
|
|
unsigned int i;
|
|
|
|
/* No indvidual report rate per resolution. Loop through all of
|
|
* them and update. */
|
|
for (i = 0; i < profile->resolution.num_modes; i++) {
|
|
struct ratbag_resolution *res = &profile->resolution.modes[i];
|
|
if (res->hz != hz) {
|
|
res->hz = hz;
|
|
profile->dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_dpi(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->dpi_x;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_dpi_maximum(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->dpi_max;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_dpi_minimum(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->dpi_min;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_dpi_x(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->dpi_x;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_dpi_y(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->dpi_y;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_get_report_rate(struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->hz;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_is_active(const struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->is_active;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_resolution_set_active(struct ratbag_resolution *resolution)
|
|
{
|
|
resolution->is_active = true;
|
|
/* FIXME: call into the driver */
|
|
return RATBAG_ERROR_IMPLEMENTATION;
|
|
}
|
|
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_resolution_is_default(const struct ratbag_resolution *resolution)
|
|
{
|
|
return resolution->is_default;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_resolution_set_default(struct ratbag_resolution *resolution)
|
|
{
|
|
struct ratbag_profile *profile = resolution->profile;
|
|
unsigned int i;
|
|
|
|
/* Unset the default on the other resolutions */
|
|
for (i = 0; i < profile->resolution.num_modes; i++) {
|
|
struct ratbag_resolution *other = &profile->resolution.modes[i];
|
|
|
|
if (other == resolution || !other->is_default)
|
|
continue;
|
|
|
|
other->is_default = false;
|
|
profile->dirty = true;
|
|
}
|
|
|
|
if (!resolution->is_default) {
|
|
resolution->is_default = true;
|
|
profile->dirty = true;
|
|
}
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button*
|
|
ratbag_profile_get_button(struct ratbag_profile *profile,
|
|
unsigned int index)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_button *button;
|
|
|
|
if (index >= ratbag_device_get_num_buttons(device)) {
|
|
log_bug_client(device->ratbag, "Requested invalid button %d\n", index);
|
|
return NULL;
|
|
}
|
|
|
|
list_for_each(button, &profile->buttons, link) {
|
|
if (button->index == index)
|
|
return ratbag_button_ref(button);
|
|
}
|
|
|
|
log_bug_libratbag(device->ratbag, "Button %d, profile %d not found\n",
|
|
index, profile->index);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_button_type
|
|
ratbag_button_get_type(struct ratbag_button *button)
|
|
{
|
|
return button->type;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_button_action_type
|
|
ratbag_button_get_action_type(struct ratbag_button *button)
|
|
{
|
|
return button->action.type;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_button_has_action_type(struct ratbag_button *button,
|
|
enum ratbag_button_action_type action_type)
|
|
{
|
|
switch (action_type) {
|
|
case RATBAG_BUTTON_ACTION_TYPE_BUTTON:
|
|
case RATBAG_BUTTON_ACTION_TYPE_SPECIAL:
|
|
case RATBAG_BUTTON_ACTION_TYPE_KEY:
|
|
case RATBAG_BUTTON_ACTION_TYPE_MACRO:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return !!(button->action_caps & (1 << action_type));
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_button_get_button(struct ratbag_button *button)
|
|
{
|
|
if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_BUTTON)
|
|
return 0;
|
|
|
|
return button->action.action.button;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_set_button(struct ratbag_button *button, unsigned int btn)
|
|
{
|
|
struct ratbag_button_action action = {0};
|
|
|
|
if (!ratbag_device_has_capability(button->profile->device,
|
|
RATBAG_DEVICE_CAP_BUTTON_KEY))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON;
|
|
action.action.button = btn;
|
|
|
|
button->action = action;
|
|
button->dirty = true;
|
|
button->profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_button_action_special
|
|
ratbag_button_get_special(struct ratbag_button *button)
|
|
{
|
|
if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_SPECIAL)
|
|
return RATBAG_BUTTON_ACTION_SPECIAL_INVALID;
|
|
|
|
return button->action.action.special;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_set_special(struct ratbag_button *button,
|
|
enum ratbag_button_action_special act)
|
|
{
|
|
struct ratbag_button_action action = {0};
|
|
|
|
/* FIXME: range checks */
|
|
|
|
if (!ratbag_device_has_capability(button->profile->device,
|
|
RATBAG_DEVICE_CAP_BUTTON_KEY))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL;
|
|
action.action.special = act;
|
|
|
|
button->action = action;
|
|
button->dirty = true;
|
|
button->profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_button_get_key(struct ratbag_button *button,
|
|
unsigned int *modifiers,
|
|
size_t *sz)
|
|
{
|
|
if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_KEY)
|
|
return 0;
|
|
|
|
/* FIXME: modifiers */
|
|
*sz = 0;
|
|
return button->action.action.key.key;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_set_key(struct ratbag_button *button,
|
|
unsigned int key,
|
|
unsigned int *modifiers,
|
|
size_t sz)
|
|
{
|
|
struct ratbag_button_action action = {0};
|
|
|
|
/* FIXME: range checks */
|
|
|
|
if (!ratbag_device_has_capability(button->profile->device,
|
|
RATBAG_DEVICE_CAP_BUTTON_KEY))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
/* FIXME: modifiers */
|
|
|
|
action.type = RATBAG_BUTTON_ACTION_TYPE_KEY;
|
|
action.action.key.key = key;
|
|
|
|
button->action = action;
|
|
button->dirty = true;
|
|
button->profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_disable(struct ratbag_button *button)
|
|
{
|
|
struct ratbag_button_action action = {0};
|
|
|
|
if (!ratbag_device_has_capability(button->profile->device,
|
|
RATBAG_DEVICE_CAP_BUTTON_KEY))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
action.type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
|
|
button->action = action;
|
|
button->dirty = true;
|
|
button->profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button *
|
|
ratbag_button_ref(struct ratbag_button *button)
|
|
{
|
|
assert(button->refcount < INT_MAX);
|
|
|
|
ratbag_profile_ref(button->profile);
|
|
button->refcount++;
|
|
return button;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_led *
|
|
ratbag_led_ref(struct ratbag_led *led)
|
|
{
|
|
assert(led->refcount < INT_MAX);
|
|
|
|
ratbag_profile_ref(led->profile);
|
|
led->refcount++;
|
|
return led;
|
|
}
|
|
|
|
static void
|
|
ratbag_button_destroy(struct ratbag_button *button)
|
|
{
|
|
list_remove(&button->link);
|
|
if (button->action.macro) {
|
|
if (button->action.macro->name)
|
|
free(button->action.macro->name);
|
|
free(button->action.macro);
|
|
}
|
|
free(button);
|
|
}
|
|
|
|
static void
|
|
ratbag_led_destroy(struct ratbag_led *led)
|
|
{
|
|
list_remove(&led->link);
|
|
free(led);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button *
|
|
ratbag_button_unref(struct ratbag_button *button)
|
|
{
|
|
if (button == NULL)
|
|
return NULL;
|
|
|
|
assert(button->refcount > 0);
|
|
button->refcount--;
|
|
|
|
ratbag_profile_unref(button->profile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_led *
|
|
ratbag_led_unref(struct ratbag_led *led)
|
|
{
|
|
if (led == NULL)
|
|
return NULL;
|
|
|
|
assert(led->refcount > 0);
|
|
led->refcount--;
|
|
|
|
ratbag_profile_unref(led->profile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_led_mode
|
|
ratbag_led_get_mode(struct ratbag_led *led)
|
|
{
|
|
return led->mode;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_led_type
|
|
ratbag_led_get_type(struct ratbag_led *led)
|
|
{
|
|
return led->type;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_color
|
|
ratbag_led_get_color(struct ratbag_led *led)
|
|
{
|
|
return led->color;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_led_get_effect_rate(struct ratbag_led *led)
|
|
{
|
|
return led->hz;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_led_get_brightness(struct ratbag_led *led)
|
|
{
|
|
return led->brightness;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode)
|
|
{
|
|
|
|
if (!ratbag_device_has_capability(led->profile->device,
|
|
RATBAG_DEVICE_CAP_LED))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
led->mode = mode;
|
|
led->dirty = true;
|
|
led->profile->dirty = true;
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color)
|
|
{
|
|
if (!ratbag_device_has_capability(led->profile->device,
|
|
RATBAG_DEVICE_CAP_LED))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
led->color = color;
|
|
led->dirty = true;
|
|
led->profile->dirty = true;
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_led_set_effect_rate(struct ratbag_led *led, unsigned int hz)
|
|
{
|
|
if (!ratbag_device_has_capability(led->profile->device,
|
|
RATBAG_DEVICE_CAP_LED))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
led->hz = hz;
|
|
led->dirty = true;
|
|
led->profile->dirty = true;
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness)
|
|
{
|
|
if (!ratbag_device_has_capability(led->profile->device,
|
|
RATBAG_DEVICE_CAP_LED))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
led->brightness = brightness;
|
|
led->dirty = true;
|
|
led->profile->dirty = true;
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_led *
|
|
ratbag_profile_get_led(struct ratbag_profile *profile,
|
|
unsigned int index)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_led *led;
|
|
|
|
if (index >= ratbag_device_get_num_leds(device)) {
|
|
log_bug_client(device->ratbag, "Requested invalid led %d\n", index);
|
|
return NULL;
|
|
}
|
|
|
|
list_for_each(led, &profile->leds, link) {
|
|
if (led->index == index)
|
|
return ratbag_led_ref(led);
|
|
}
|
|
|
|
log_bug_libratbag(device->ratbag, "Led %d, profile %d not found\n",
|
|
index, profile->index);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_set_user_data(struct ratbag *ratbag, void *userdata)
|
|
{
|
|
ratbag->userdata = userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_device_set_user_data(struct ratbag_device *ratbag_device, void *userdata)
|
|
{
|
|
ratbag_device->userdata = userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_profile_set_user_data(struct ratbag_profile *ratbag_profile, void *userdata)
|
|
{
|
|
ratbag_profile->userdata = userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_button_set_user_data(struct ratbag_button *ratbag_button, void *userdata)
|
|
{
|
|
ratbag_button->userdata = userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void
|
|
ratbag_resolution_set_user_data(struct ratbag_resolution *ratbag_resolution, void *userdata)
|
|
{
|
|
ratbag_resolution->userdata = userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void*
|
|
ratbag_get_user_data(const struct ratbag *ratbag)
|
|
{
|
|
return ratbag->userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void*
|
|
ratbag_device_get_user_data(const struct ratbag_device *ratbag_device)
|
|
{
|
|
return ratbag_device->userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void*
|
|
ratbag_profile_get_user_data(const struct ratbag_profile *ratbag_profile)
|
|
{
|
|
return ratbag_profile->userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void*
|
|
ratbag_button_get_user_data(const struct ratbag_button *ratbag_button)
|
|
{
|
|
return ratbag_button->userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT void*
|
|
ratbag_resolution_get_user_data(const struct ratbag_resolution *ratbag_resolution)
|
|
{
|
|
return ratbag_resolution->userdata;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button_macro *
|
|
ratbag_button_get_macro(struct ratbag_button *button)
|
|
{
|
|
struct ratbag_button_macro *macro;
|
|
|
|
if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO)
|
|
return NULL;
|
|
|
|
macro = ratbag_button_macro_new(button->action.macro->name);
|
|
memcpy(macro->macro.events,
|
|
button->action.macro->events,
|
|
sizeof(macro->macro.events));
|
|
|
|
return macro;
|
|
}
|
|
|
|
void
|
|
ratbag_button_copy_macro(struct ratbag_button *button,
|
|
const struct ratbag_button_macro *macro)
|
|
{
|
|
if (!button->action.macro)
|
|
button->action.macro = zalloc(sizeof(struct ratbag_macro));
|
|
else {
|
|
if (button->action.macro->name)
|
|
free(button->action.macro->name);
|
|
memset(button->action.macro, 0, sizeof(struct ratbag_macro));
|
|
}
|
|
|
|
button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO;
|
|
memcpy(button->action.macro->events,
|
|
macro->macro.events,
|
|
sizeof(macro->macro.events));
|
|
button->action.macro->name = strdup_safe(macro->macro.name);
|
|
button->action.macro->group = strdup_safe(macro->macro.group);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_set_macro(struct ratbag_button *button,
|
|
const struct ratbag_button_macro *macro)
|
|
{
|
|
if (!ratbag_device_has_capability(button->profile->device,
|
|
RATBAG_DEVICE_CAP_BUTTON_MACROS))
|
|
return RATBAG_ERROR_CAPABILITY;
|
|
|
|
ratbag_button_copy_macro(button, macro);
|
|
button->dirty = true;
|
|
button->profile->dirty = true;
|
|
|
|
return RATBAG_SUCCESS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_error_code
|
|
ratbag_button_macro_set_event(struct ratbag_button_macro *m,
|
|
unsigned int index,
|
|
enum ratbag_macro_event_type type,
|
|
unsigned int data)
|
|
{
|
|
struct ratbag_macro *macro = &m->macro;
|
|
|
|
if (index >= MAX_MACRO_EVENTS)
|
|
return RATBAG_ERROR_VALUE;
|
|
|
|
switch (type) {
|
|
case RATBAG_MACRO_EVENT_KEY_PRESSED:
|
|
case RATBAG_MACRO_EVENT_KEY_RELEASED:
|
|
macro->events[index].type = type;
|
|
macro->events[index].event.key = data;
|
|
break;
|
|
case RATBAG_MACRO_EVENT_WAIT:
|
|
macro->events[index].type = type;
|
|
macro->events[index].event.timeout = data;
|
|
break;
|
|
case RATBAG_MACRO_EVENT_NONE:
|
|
macro->events[index].type = type;
|
|
break;
|
|
default:
|
|
return RATBAG_ERROR_VALUE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT enum ratbag_macro_event_type
|
|
ratbag_button_macro_get_event_type(struct ratbag_button_macro *macro, unsigned int index)
|
|
{
|
|
if (index >= MAX_MACRO_EVENTS)
|
|
return RATBAG_MACRO_EVENT_INVALID;
|
|
|
|
return macro->macro.events[index].type;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_button_macro_get_event_key(struct ratbag_button_macro *m, unsigned int index)
|
|
{
|
|
struct ratbag_macro *macro = &m->macro;
|
|
|
|
if (index >= MAX_MACRO_EVENTS)
|
|
return 0;
|
|
|
|
if (macro->events[index].type != RATBAG_MACRO_EVENT_KEY_PRESSED &&
|
|
macro->events[index].type != RATBAG_MACRO_EVENT_KEY_RELEASED)
|
|
return -EINVAL;
|
|
|
|
return macro->events[index].event.key;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT int
|
|
ratbag_button_macro_get_event_timeout(struct ratbag_button_macro *m,
|
|
unsigned int index)
|
|
{
|
|
struct ratbag_macro *macro = &m->macro;
|
|
|
|
if (index >= MAX_MACRO_EVENTS)
|
|
return 0;
|
|
|
|
if (macro->events[index].type != RATBAG_MACRO_EVENT_WAIT)
|
|
return 0;
|
|
|
|
return macro->events[index].event.timeout;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT unsigned int
|
|
ratbag_button_macro_get_num_events(struct ratbag_button_macro *macro)
|
|
{
|
|
return MAX_MACRO_EVENTS;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT const char *
|
|
ratbag_button_macro_get_name(struct ratbag_button_macro *macro)
|
|
{
|
|
return macro->macro.name;
|
|
}
|
|
|
|
static void
|
|
ratbag_button_macro_destroy(struct ratbag_button_macro *macro)
|
|
{
|
|
assert(macro->refcount == 0);
|
|
free(macro->macro.name);
|
|
free(macro->macro.group);
|
|
free(macro);
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button_macro *
|
|
ratbag_button_macro_ref(struct ratbag_button_macro *macro)
|
|
{
|
|
assert(macro->refcount < INT_MAX);
|
|
|
|
macro->refcount++;
|
|
return macro;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button_macro *
|
|
ratbag_button_macro_unref(struct ratbag_button_macro *macro)
|
|
{
|
|
if (macro == NULL)
|
|
return NULL;
|
|
|
|
assert(macro->refcount > 0);
|
|
macro->refcount--;
|
|
if (macro->refcount == 0)
|
|
ratbag_button_macro_destroy(macro);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LIBRATBAG_EXPORT struct ratbag_button_macro *
|
|
ratbag_button_macro_new(const char *name)
|
|
{
|
|
struct ratbag_button_macro *macro;
|
|
|
|
macro = zalloc(sizeof *macro);
|
|
macro->refcount = 1;
|
|
macro->macro.name = strdup_safe(name);
|
|
|
|
return macro;
|
|
}
|