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:
Benjamin Tissoires
2017-08-02 14:21:43 +02:00
committed by Peter Hutterer
parent 97a927bb7d
commit c647d6df99
11 changed files with 228 additions and 3 deletions

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -261,6 +261,7 @@ struct ratbag_led {
struct ratbag_profile {
int refcount;
void *userdata;
char *name;
struct list link;
unsigned index;

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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
*

View File

@@ -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;

View File

@@ -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',

View File

@@ -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")

View File

@@ -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")

View File

@@ -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,
]