forked from drath/libratbag
profiles: add name get/set capability
Some devices can have a custom name, so let's export that capability. In ratbagd, libratbag/ratbag_profile_set_name() returns an error, we put it under silence, the client has to check for the capabilities before asking ratbagd to change the profile name. Fixes: #266 Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
This commit is contained in:
committed by
Peter Hutterer
parent
97a927bb7d
commit
c647d6df99
@@ -347,6 +347,57 @@ ratbagd_profile_is_enabled(sd_bus *bus,
|
||||
return sd_bus_message_append(reply, "b", enabled);
|
||||
}
|
||||
|
||||
static int
|
||||
ratbagd_profile_set_name(sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *m,
|
||||
void *userdata,
|
||||
sd_bus_error *error)
|
||||
{
|
||||
struct ratbagd_profile *profile = userdata;
|
||||
char *name;
|
||||
int r;
|
||||
|
||||
r = sd_bus_message_read(m, "s", &name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = ratbag_profile_set_name(profile->lib_profile, name);
|
||||
|
||||
if (r == 0) {
|
||||
sd_bus_emit_properties_changed(bus,
|
||||
profile->path,
|
||||
RATBAGD_NAME_ROOT ".Profile",
|
||||
"Name",
|
||||
NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: we should return `r`, but the d-bus bindings doesn't
|
||||
* like non standard errors. So just return success and let the client
|
||||
* check for the capability before-hand and let .commit() signal an
|
||||
* error.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ratbagd_profile_get_name(sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error)
|
||||
{
|
||||
struct ratbagd_profile *profile = userdata;
|
||||
const char *name = ratbag_profile_get_name(profile->lib_profile);
|
||||
|
||||
return sd_bus_message_append(reply, "s", name);
|
||||
}
|
||||
|
||||
static int
|
||||
ratbagd_profile_get_capabilities(sd_bus *bus,
|
||||
const char *path,
|
||||
@@ -360,6 +411,7 @@ ratbagd_profile_get_capabilities(sd_bus *bus,
|
||||
struct ratbag_profile *lib_profile = profile->lib_profile;
|
||||
enum ratbag_profile_capability cap;
|
||||
enum ratbag_profile_capability caps[] = {
|
||||
RATBAG_PROFILE_CAP_WRITABLE_NAME,
|
||||
};
|
||||
int r;
|
||||
size_t i;
|
||||
@@ -382,6 +434,10 @@ ratbagd_profile_get_capabilities(sd_bus *bus,
|
||||
|
||||
const sd_bus_vtable ratbagd_profile_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_WRITABLE_PROPERTY("Name", "s",
|
||||
ratbagd_profile_get_name,
|
||||
ratbagd_profile_set_name, 0,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_WRITABLE_PROPERTY("Enabled", "b",
|
||||
ratbagd_profile_is_enabled,
|
||||
ratbagd_profile_set_enabled, 0,
|
||||
|
||||
@@ -266,6 +266,7 @@ static const struct ratbag_test_device ratbagd_test_device_descr = {
|
||||
},
|
||||
.active = false,
|
||||
.dflt = true,
|
||||
.caps = { RATBAG_PROFILE_CAP_WRITABLE_NAME },
|
||||
},
|
||||
{
|
||||
.buttons = {
|
||||
|
||||
@@ -261,6 +261,7 @@ struct ratbag_led {
|
||||
struct ratbag_profile {
|
||||
int refcount;
|
||||
void *userdata;
|
||||
char *name;
|
||||
|
||||
struct list link;
|
||||
unsigned index;
|
||||
|
||||
@@ -118,6 +118,24 @@ snprintf_safe(char *buf, size_t n, const char *fmt, ...)
|
||||
#define sprintf_safe(buf, fmt, ...) \
|
||||
snprintf_safe(buf, ARRAY_LENGTH(buf), fmt, __VA_ARGS__)
|
||||
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
static inline void *
|
||||
asprintf_safe(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
int rc;
|
||||
char *result;
|
||||
|
||||
va_start(args, fmt);
|
||||
rc = vasprintf(&result, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (rc < 0)
|
||||
abort();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline void
|
||||
msleep(unsigned int ms)
|
||||
{
|
||||
|
||||
@@ -663,6 +663,7 @@ ratbag_create_profile(struct ratbag_device *device,
|
||||
sizeof(*profile->resolution.modes));
|
||||
profile->resolution.num_modes = num_resolutions;
|
||||
profile->is_enabled = true;
|
||||
profile->name = asprintf_safe("Profile %d", index + 1);
|
||||
|
||||
list_insert(&device->profiles, &profile->link);
|
||||
list_init(&profile->buttons);
|
||||
@@ -743,6 +744,9 @@ ratbag_profile_destroy(struct ratbag_profile *profile)
|
||||
|
||||
free(profile->resolution.modes);
|
||||
|
||||
if (profile->name)
|
||||
free(profile->name);
|
||||
|
||||
list_remove(&profile->link);
|
||||
free(profile);
|
||||
}
|
||||
@@ -1532,6 +1536,31 @@ ratbag_profile_get_led(struct ratbag_profile *profile,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LIBRATBAG_EXPORT const char *
|
||||
ratbag_profile_get_name(struct ratbag_profile *profile)
|
||||
{
|
||||
return profile->name;
|
||||
}
|
||||
|
||||
LIBRATBAG_EXPORT int
|
||||
ratbag_profile_set_name(struct ratbag_profile *profile,
|
||||
const char *name)
|
||||
{
|
||||
char *name_copy;
|
||||
|
||||
if (!ratbag_profile_has_capability(profile,
|
||||
RATBAG_PROFILE_CAP_WRITABLE_NAME))
|
||||
return RATBAG_ERROR_CAPABILITY;
|
||||
|
||||
name_copy = strdup_safe(name);
|
||||
if (profile->name)
|
||||
free(profile->name);
|
||||
|
||||
profile->name = name_copy;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LIBRATBAG_EXPORT void
|
||||
ratbag_set_user_data(struct ratbag *ratbag, void *userdata)
|
||||
{
|
||||
|
||||
@@ -651,6 +651,14 @@ ratbag_device_get_num_leds(struct ratbag_device *device);
|
||||
*/
|
||||
enum ratbag_profile_capability {
|
||||
RATBAG_PROFILE_CAP_NONE = 0,
|
||||
/**
|
||||
* The device has the capability to write a name to a profile
|
||||
* and store it in its flash.
|
||||
*
|
||||
* Any changes uploaded to the device will be cached in libratbag
|
||||
* and will be committed to it after a commit only.
|
||||
*/
|
||||
RATBAG_PROFILE_CAP_WRITABLE_NAME = 100,
|
||||
};
|
||||
/**
|
||||
* @ingroup profile
|
||||
@@ -688,6 +696,31 @@ int
|
||||
ratbag_profile_has_capability(const struct ratbag_profile *profile,
|
||||
enum ratbag_profile_capability cap);
|
||||
|
||||
/**
|
||||
* @ingroup profile
|
||||
*
|
||||
* Return the ratbag profile name.
|
||||
*
|
||||
* @param profile A previously initialized ratbag profile
|
||||
* @return the profile name
|
||||
*/
|
||||
const char *
|
||||
ratbag_profile_get_name(struct ratbag_profile *profile);
|
||||
|
||||
/**
|
||||
* @ingroup profile
|
||||
*
|
||||
* Set the name of a ratbag profile.
|
||||
*
|
||||
* @param profile A previously initialized ratbag profile
|
||||
* @param name the profile name
|
||||
*
|
||||
* @return 0 on success or an error code otherwise
|
||||
*/
|
||||
int
|
||||
ratbag_profile_set_name(struct ratbag_profile *profile,
|
||||
const char *name);
|
||||
|
||||
/**
|
||||
* @ingroup profile
|
||||
*
|
||||
|
||||
@@ -59,6 +59,7 @@ global:
|
||||
ratbag_led_unref;
|
||||
ratbag_device_get_num_leds;
|
||||
ratbag_profile_get_button;
|
||||
ratbag_profile_get_name;
|
||||
ratbag_profile_get_num_resolutions;
|
||||
ratbag_profile_get_resolution;
|
||||
ratbag_profile_get_user_data;
|
||||
@@ -69,6 +70,7 @@ global:
|
||||
ratbag_profile_set_user_data;
|
||||
ratbag_profile_set_active;
|
||||
ratbag_profile_set_enabled;
|
||||
ratbag_profile_set_name;
|
||||
ratbag_profile_unref;
|
||||
ratbag_resolution_get_dpi;
|
||||
ratbag_resolution_get_dpi_maximum;
|
||||
|
||||
@@ -27,7 +27,7 @@ import evdev
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from ratbagd import Ratbagd, RatbagdDevice, RatbagdProfile, RatbagdResolution, RatbagdButton, RatbagdLed, RatbagdDBusUnavailable # NOQA
|
||||
from ratbagd import Ratbagd, RatbagdDevice, RatbagdProfile, RatbagdResolution, RatbagdButton, RatbagdLed, RatbagdDBusUnavailable, RatbagError, RatbagErrorCapability # NOQA
|
||||
|
||||
button_specials_strmap = {
|
||||
RatbagdButton.ACTION_SPECIAL_UNKNOWN: "unknown",
|
||||
@@ -248,6 +248,7 @@ def print_profile(d, p, level):
|
||||
" (disabled)" if not p.enabled else "",
|
||||
" (active)" if p.is_active else ""))
|
||||
if p.enabled:
|
||||
print(" " * level + "Name : '{}'".format(p.name))
|
||||
print(" " * level + "Resolutions:")
|
||||
for r in p.resolutions:
|
||||
print_resolution(d, p, r, level + 2)
|
||||
@@ -495,6 +496,19 @@ def func_profile_get(r, args):
|
||||
print_profile(d, p, 0)
|
||||
|
||||
|
||||
def func_profile_name_get(r, args):
|
||||
p, d = find_profile(r, args)
|
||||
print(p.name)
|
||||
|
||||
|
||||
def func_profile_name_set(r, args):
|
||||
p, d = find_profile(r, args)
|
||||
if RatbagdProfile.CAP_WRITABLE_NAME not in p.capabilities:
|
||||
raise RatbagErrorCapability("assigning a profile name is not supported on this profile")
|
||||
p.name = args.name
|
||||
d.commit()
|
||||
|
||||
|
||||
def func_profile_active_get(r, args):
|
||||
d = find_device(r, args)
|
||||
for p in d.profiles:
|
||||
@@ -697,6 +711,33 @@ parser_def = [
|
||||
help_str: 'Show selected profile information',
|
||||
func: func_profile_get,
|
||||
},
|
||||
{
|
||||
of_type: switch,
|
||||
name: 'name',
|
||||
help_str: 'access profile name information',
|
||||
switch: [
|
||||
{
|
||||
of_type: command,
|
||||
name: 'get',
|
||||
help_str: 'Show the name of the profile',
|
||||
func: func_profile_name_get,
|
||||
},
|
||||
{
|
||||
of_type: command,
|
||||
name: 'set',
|
||||
help_str: 'Set the name of the profile',
|
||||
pos_args: [
|
||||
{
|
||||
of_type: argument,
|
||||
name: 'name',
|
||||
metavar: 'blah',
|
||||
help_str: 'The name to set',
|
||||
},
|
||||
],
|
||||
func: func_profile_name_set,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
of_type: command,
|
||||
name: 'enable',
|
||||
|
||||
@@ -97,6 +97,11 @@ class TestRatbagCtl(unittest.TestCase):
|
||||
self.assertNotEqual(returncode, 0)
|
||||
return stdout
|
||||
|
||||
def launch_fail_with_exception_test(self, params, exception):
|
||||
with self.assertRaises(exception):
|
||||
returncode, stdout, stderr = self.run_ratbagctl(params)
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
def setProfile(cls, profile):
|
||||
cls.run_ratbagctl("Profile active set {} test_device".format(profile))
|
||||
@@ -130,6 +135,29 @@ class TestRatbgagCtlProfile(TestRatbagCtl):
|
||||
super(TestRatbgagCtlProfile, cls).setUpClass()
|
||||
cls.setProfile(0)
|
||||
|
||||
def test_profile_name_get(self):
|
||||
command = "profile 0 name get"
|
||||
r = self.launch_good_test(command + " test_device")
|
||||
self.assertEqual(r, 'Profile 1')
|
||||
self.launch_fail_test(command)
|
||||
self.launch_fail_test(command + " X test_device")
|
||||
self.launch_fail_test("profile name get test_device")
|
||||
|
||||
def test_profile_name_set(self):
|
||||
r = self.launch_good_test("profile 1 name get test_device")
|
||||
self.assertEqual(r, 'Profile 2')
|
||||
self.launch_good_test("profile 1 name set banana test_device")
|
||||
r = self.launch_good_test("profile 1 name get test_device")
|
||||
self.assertEqual(r, 'banana')
|
||||
# profile 0 doesn't have the capability RATBAG_PROFILE_CAP_WRITABLE_NAME
|
||||
self.launch_fail_with_exception_test("profile 0 name set kiwi test_device", toolbox.RatbagErrorCapability)
|
||||
# better be safe than sorry, checking that the previous actually failed :)
|
||||
r = self.launch_good_test("profile 0 name get test_device")
|
||||
self.assertNotEqual(r, 'kiwi')
|
||||
self.launch_fail_test("profile 1 name set blah")
|
||||
self.launch_fail_test("profile 1 name set blah X test_device")
|
||||
self.launch_fail_test("profile name set blah test_device")
|
||||
|
||||
def test_profile_active_get(self):
|
||||
command = "profile active get"
|
||||
r = self.launch_good_test(command + " test_device")
|
||||
|
||||
@@ -289,6 +289,8 @@ class RatbagdDevice(_RatbagdDBus):
|
||||
class RatbagdProfile(_RatbagdDBus):
|
||||
"""Represents a ratbagd profile."""
|
||||
|
||||
CAP_WRITABLE_NAME = 100
|
||||
|
||||
def __init__(self, object_path):
|
||||
_RatbagdDBus.__init__(self, "Profile", object_path)
|
||||
|
||||
@@ -297,12 +299,24 @@ class RatbagdProfile(_RatbagdDBus):
|
||||
"""The capabilities of this profile as an array. Capabilities not
|
||||
present on the profile are not in the list. Thus use e.g.
|
||||
|
||||
if RatbagdPorfile.CAP_WRITABLE_NAME is in profile.capabilities:
|
||||
if RatbagdProfile.CAP_WRITABLE_NAME is in profile.capabilities:
|
||||
do something
|
||||
"""
|
||||
return self._get_dbus_property("Capabilities")
|
||||
|
||||
@GObject.property
|
||||
def name(self):
|
||||
"""The name of the profile"""
|
||||
return self._get_dbus_property("Name")
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
"""Set the name of this profile.
|
||||
|
||||
@param name The new name, as str"""
|
||||
self._set_dbus_property("Name", "s", name)
|
||||
|
||||
@GObject.Property
|
||||
def index(self):
|
||||
"""The index of this profile."""
|
||||
return self._get_dbus_property("Index")
|
||||
|
||||
@@ -100,7 +100,7 @@ def terminate_ratbagd(ratbagd):
|
||||
|
||||
ratbagctl = import_non_standard_path(RATBAGCTL_NAME, RATBAGCTL_PATH)
|
||||
|
||||
from ratbagctl import open_ratbagd, get_parser
|
||||
from ratbagctl import open_ratbagd, get_parser, RatbagError, RatbagErrorCapability
|
||||
|
||||
__all__ = [
|
||||
RATBAGCTL_NAME,
|
||||
@@ -112,4 +112,6 @@ __all__ = [
|
||||
terminate_ratbagd,
|
||||
open_ratbagd,
|
||||
get_parser,
|
||||
RatbagError,
|
||||
RatbagErrorCapability,
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user