Implement disabled resolutions

Implements feature to disable resolutions from a static resolution list.
Updates DBus API and tests accordingly.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
This commit is contained in:
Stephan Lachnit
2019-12-26 15:16:40 +01:00
committed by Yaroslav Chvanov
parent c99fd654e3
commit 45118b8fa5
13 changed files with 379 additions and 18 deletions

View File

@@ -291,6 +291,30 @@ org.freedesktop.ratbag1.Resolution
Index of the resolution
.. attribute:: Capabilities
:type: au
:flags: read-only, constant
Bit-mask of capabilities.
+-------+-----------------------------------------------------+
| Value | Definition |
+=======+=====================================================+
| 1 | Resolution can be set for x and y axes separately. |
+-------+-----------------------------------------------------+
| 2 | Resolution can be disabled and enabled. |
| | Note that this capability only notes the general |
| | capability. A specific resolution may still fail to |
| | be disabled, e.g. when it is the active resolution |
| | on the device. |
+-------+-----------------------------------------------------+
(Source: :cpp:enum:`ratbag_resolution_capability` in `libratbag.h`).
In the future, extra values may get added. Clients must ignore
unknown Capabilities.
.. attribute:: IsActive
:type: b
@@ -319,6 +343,19 @@ org.freedesktop.ratbag1.Resolution
:func:`SetDefault` method to set a resolution as
the default resolution.
.. attribute:: IsDisabled
:type: b
:flags: read-only, mutable
True if this resolution is disabled, false otherwise.
If the device does not have the disabled resolution
capability, this property is always false.
This property is read-only, use the :func:`SetDisabled`
or :func:`SetEnabled` method to set a resolution as
disabled or enabled .
.. attribute:: Resolution
:type: v
@@ -358,6 +395,14 @@ org.freedesktop.ratbag1.Resolution
Set this resolution to be the default
.. function:: SetDisabled() → ()
Set this resolution to be disabled
.. function:: SetEnabled() → ()
Set this resolution to be enabled
.. _button:
org.freedesktop.ratbag1.Button

View File

@@ -41,6 +41,7 @@
"dpi_max", int,
"is_active": bool,
"is_default": bool,
"is_disabled": bool,
"capabilities": [int, ...] # resolution capabilities
}
]
@@ -129,13 +130,17 @@ static void parse_resolution_member(JsonObject *obj, const gchar *name,
gboolean v = json_object_get_boolean_member(obj, name);
resolution->dflt = v;
log_verbose("json: is_default: %d\n", v);
} else if (streq(name, "is_disabled")) {
gboolean v = json_object_get_boolean_member(obj, name);
resolution->disabled = v;
log_verbose("json: is_disabled: %d\n", v);
} else if (streq(name, "capabilities")) {
JsonArray *a = json_object_get_array_member(obj, name);
assert(json_array_get_length(a) < ARRAY_LENGTH(resolution->caps));
for (size_t s = 0; s < json_array_get_length(a); s++) {
int v = json_array_get_int_element(a, s);
if (v > RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION)
if (v > RATBAG_RESOLUTION_CAP_DISABLE)
parser_error("capabilities");
resolution->caps[s] = v;
}

View File

@@ -55,6 +55,7 @@ int ratbagd_resolution_resync(sd_bus *bus,
"ReportRate",
"IsActive",
"IsDefault",
"IsDisabled",
NULL);
}
@@ -168,6 +169,51 @@ ratbagd_resolution_is_default(sd_bus *bus,
return sd_bus_message_append(reply, "b", is_default);
}
static int
ratbagd_resolution_is_disabled(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error)
{
struct ratbagd_resolution *resolution = userdata;
struct ratbag_resolution *lib_resolution = resolution->lib_resolution;
const int is_disabled = ratbag_resolution_is_disabled(lib_resolution);
CHECK_CALL(sd_bus_message_append(reply, "b", is_disabled));
return 0;
}
static int ratbagd_resolution_set_disabled(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *m,
void *userdata,
sd_bus_error *error)
{
struct ratbagd_resolution *resolution = userdata;
bool is_disabled;
int r;
CHECK_CALL(sd_bus_message_read(m, "b", &is_disabled));
r = ratbag_resolution_set_disabled(resolution->lib_resolution, is_disabled);
if (r == 0) {
sd_bus *bus = sd_bus_message_get_bus(m);
sd_bus_emit_properties_changed(bus,
resolution->path,
RATBAGD_NAME_ROOT ".Resolution",
"IsDisabled",
NULL);
}
return 0;
}
static int
ratbagd_resolution_get_resolutions(sd_bus *bus,
const char *path,
@@ -264,16 +310,53 @@ ratbagd_resolution_set_resolution(sd_bus *bus,
return 0;
}
static int
ratbagd_resolution_get_capabilities(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error)
{
struct ratbagd_resolution *resolution = userdata;
struct ratbag_resolution *lib_resolution = resolution->lib_resolution;
enum ratbag_resolution_capability cap;
enum ratbag_resolution_capability caps[] = {
RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION,
RATBAG_RESOLUTION_CAP_DISABLE,
};
size_t i;
CHECK_CALL(sd_bus_message_open_container(reply, 'a', "u"));
for (i = 0; i < ELEMENTSOF(caps); i++) {
cap = caps[i];
if (ratbag_resolution_has_capability(lib_resolution, cap)) {
CHECK_CALL(sd_bus_message_append(reply, "u", cap));
}
}
CHECK_CALL(sd_bus_message_close_container(reply));
return 0;
}
const sd_bus_vtable ratbagd_resolution_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_resolution, index), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IsActive", "b", ratbagd_resolution_is_active, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IsDefault", "b", ratbagd_resolution_is_default, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_WRITABLE_PROPERTY("IsDisabled", "b",
ratbagd_resolution_is_disabled,
ratbagd_resolution_set_disabled,
0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_WRITABLE_PROPERTY("Resolution", "v",
ratbagd_resolution_get_resolution,
ratbagd_resolution_set_resolution, 0,
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Resolutions", "au", ratbagd_resolution_get_resolutions, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Capabilities", "au", ratbagd_resolution_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("SetActive", "", "u", ratbagd_resolution_set_active, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetDefault", "", "u", ratbagd_resolution_set_default, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,

View File

@@ -190,6 +190,7 @@ test_read_profile(struct ratbag_profile *profile)
res->is_default = r->dflt;
if (r->dflt)
default_set = true;
res->is_disabled = r->disabled;
for (size_t j = 0; j < ARRAY_LENGTH(r->caps) && r->caps[j]; j++) {
ratbag_resolution_set_cap(res, r->caps[j]);

View File

@@ -129,6 +129,20 @@ enum ratbag_resolution_capability {
* The resolution can be set for x and y separately.
*/
RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION = 1,
/**
* The resolution can be disabled and enabled. This is intended for
* devices with a static number of resolutions that can not be
* changed.
*
* Note that this capability only notes the general capability. A
* specific resolution may still fail to be disabled, e.g. when it
* is the active resolution on the device.
*
* TODO: Devices with a dynamic amount of resolutions need an own
* capability to add or remove resolutions.
*/
RATBAG_RESOLUTION_CAP_DISABLE,
};
/**

View File

@@ -227,6 +227,7 @@ struct ratbag_resolution {
bool is_active;
bool is_default;
bool is_disabled;
bool dirty;
uint32_t capabilities;
};
@@ -542,7 +543,7 @@ static inline void
ratbag_resolution_set_cap(struct ratbag_resolution *res,
enum ratbag_resolution_capability cap)
{
assert(cap <= RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
assert(cap <= RATBAG_RESOLUTION_CAP_DISABLE);
res->capabilities |= (1 << cap);
}

View File

@@ -53,6 +53,7 @@ struct ratbag_test_resolution {
int xres, yres;
bool active;
bool dflt;
bool disabled;
uint32_t caps[10];
int dpi_min, dpi_max;

View File

@@ -976,12 +976,7 @@ LIBRATBAG_EXPORT bool
ratbag_resolution_has_capability(struct ratbag_resolution *resolution,
enum ratbag_resolution_capability cap)
{
switch (cap) {
case RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION:
break;
default:
return 0;
}
assert(cap <= RATBAG_RESOLUTION_CAP_DISABLE);
return !!(resolution->capabilities & (1 << cap));
}
@@ -1128,6 +1123,11 @@ ratbag_resolution_set_active(struct ratbag_resolution *resolution)
struct ratbag_profile *profile = resolution->profile;
struct ratbag_resolution *res;
if (resolution->is_disabled) {
log_error(profile->device->ratbag, "%s: setting the active resolution to a disabled resolution is not allowed\n", profile->device->name);
return RATBAG_ERROR_VALUE;
}
ratbag_profile_for_each_resolution(profile, res)
res->is_active = false;
@@ -1137,7 +1137,6 @@ ratbag_resolution_set_active(struct ratbag_resolution *resolution)
return RATBAG_SUCCESS;
}
LIBRATBAG_EXPORT bool
ratbag_resolution_is_default(const struct ratbag_resolution *resolution)
{
@@ -1150,6 +1149,11 @@ ratbag_resolution_set_default(struct ratbag_resolution *resolution)
struct ratbag_profile *profile = resolution->profile;
struct ratbag_resolution *other;
if (resolution->is_disabled) {
log_error(profile->device->ratbag, "%s: setting the default resolution to a disabled resolution is not allowed\n", profile->device->name);
return RATBAG_ERROR_VALUE;
}
/* Unset the default on the other resolutions */
ratbag_profile_for_each_resolution(profile, other) {
if (other == resolution || !other->is_default)
@@ -1169,6 +1173,38 @@ ratbag_resolution_set_default(struct ratbag_resolution *resolution)
return RATBAG_SUCCESS;
}
LIBRATBAG_EXPORT bool
ratbag_resolution_is_disabled(const struct ratbag_resolution *resolution)
{
return !!resolution->is_disabled;
}
LIBRATBAG_EXPORT enum ratbag_error_code
ratbag_resolution_set_disabled(struct ratbag_resolution *resolution, bool disable)
{
struct ratbag_profile *profile = resolution->profile;
if (!ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_DISABLE))
return RATBAG_ERROR_CAPABILITY;
if (disable) {
if (resolution->is_active) {
log_error(profile->device->ratbag, "%s: disabling the active resolution is not allowed\n", profile->device->name);
return RATBAG_ERROR_VALUE;
}
if (resolution->is_default) {
log_error(profile->device->ratbag, "%s: disabling the default resolution is not allowed\n", profile->device->name);
return RATBAG_ERROR_VALUE;
}
}
resolution->is_disabled = disable;
resolution->dirty = true;
profile->dirty = true;
return RATBAG_SUCCESS;
}
LIBRATBAG_EXPORT struct ratbag_button*
ratbag_profile_get_button(struct ratbag_profile *profile,
unsigned int index)

View File

@@ -1035,6 +1035,34 @@ ratbag_resolution_set_default(struct ratbag_resolution *resolution);
bool
ratbag_resolution_is_default(const struct ratbag_resolution *resolution);
/**
* @ingroup resolution
*
* Set disabled state of the given resolution mode if supported by the device.
*
* The mode must not be active or default, otherwise nothing
* will be changed and an error is returned.
*
* @param resolution A previously initialized ratbag resolution
* @param disable If true disable resolution, else enable
*
* @return zero on success or an error code on failure
*/
enum ratbag_error_code
ratbag_resolution_set_disabled(struct ratbag_resolution *resolution, bool disable);
/**
* @ingroup resolution
*
* Check if the resolution mode is currently disabled.
*
* @param resolution A previously initialized ratbag resolution
*
* @return Non-zero if the resolution mode is disabled, zero otherwise.
*/
bool
ratbag_resolution_is_disabled(const struct ratbag_resolution *resolution);
/**
* @ingroup profile
*

View File

@@ -438,6 +438,16 @@ class RatbagdResolution(metaclass=MetaRatbag):
def __exit__(self):
libratbag.ratbag_resolution_unref(self._res)
@property
def capabilities(self):
"""The capabilities of this resolution as a list. Capabilities not
present on the resolution are not in the list. Thus use e.g.
if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION in resolution.capabilities:
do something
"""
return self._capabilities
@property
def index(self):
"""The index of this resolution."""
@@ -482,6 +492,11 @@ class RatbagdResolution(metaclass=MetaRatbag):
otherwise"""
return libratbag.ratbag_resolution_is_default(self._res)
@property
def is_disabled(self):
"""True if this is currently disabled, False otherwise"""
return libratbag.ratbag_resolution_is_disabled(self._res)
def set_active(self):
"""Set this resolution to be the active one."""
return libratbag.ratbag_resolution_set_active(self._res)
@@ -490,6 +505,10 @@ class RatbagdResolution(metaclass=MetaRatbag):
"""Set this resolution to be the default."""
return libratbag.ratbag_resolution_set_default(self._res)
def set_disabled(self, disable):
"""Set this resolution to be disabled."""
return libratbag.ratbag_resolution_set_disabled(self._res, disable)
class RatbagdButton(metaclass=MetaRatbag):
"""Represents a ratbagd button."""

View File

@@ -195,11 +195,12 @@ def print_resolution(d, p, r, level):
else:
dpi = "{}".format(r.resolution[0])
print(" " * level + "{}: {}dpi{}{}".format(r.index,
dpi,
" (active)" if r.is_active else "",
" (default)" if r.is_default else "",
))
print(" " * level + "{}: {}dpi{}{}{}".format(r.index,
dpi,
" (active)" if r.is_active else "",
" (default)" if r.is_default else "",
" (disabled)" if r.is_disabled else "",
))
def print_profile(d, p, level):
@@ -480,6 +481,28 @@ def func_default_resolution_set(r, args):
commit(d, args)
def func_resolution_disabled_get(r, args):
r, p, d = find_resolution(r, args)
print(r.is_disabled)
def func_resolution_enabled_get(r, args):
r, p, d = find_resolution(r, args)
print(not r.is_disabled)
def func_resolution_disabled_set(r, args):
r, p, d = find_resolution(r, args)
r.set_disabled(True)
commit(d, args)
def func_resolution_enabled_set(r, args):
r, p, d = find_resolution(r, args)
r.set_disabled(False)
commit(d, args)
def func_profile_get(r, args):
p, d = find_profile(r, args)
print_profile(d, p, 0)
@@ -840,6 +863,44 @@ active profile if none is given.""",
help_str: 'Show selected resolution',
func: func_resolution_get,
},
{
of_type: switch,
name: 'disabled',
help_str: 'access disabled resolution information',
switch: [
{
of_type: command,
name: 'get',
help_str: 'Show if resolution is disabled',
func: func_resolution_disabled_get,
},
{
of_type: command,
name: 'set',
help_str: 'Set resolution as disabled',
func: func_resolution_disabled_set,
},
]
},
{
of_type: switch,
name: 'enabled',
help_str: 'access enabled resolution information',
switch: [
{
of_type: command,
name: 'get',
help_str: 'Show if resolution is enabled',
func: func_resolution_enabled_get,
},
{
of_type: command,
name: 'set',
help_str: 'Set resolution as disabled',
func: func_resolution_enabled_set,
},
]
},
{
of_type: link,
dest: 'dpi',

View File

@@ -244,9 +244,9 @@ class TestRatbagCtlProfile(TestRatbagCtl):
def test_profile_enable(self):
r = self.launch_good_test("test_device profile 0 get")
self.assertNotIn('disabled', r)
self.assertNotIn('Profile 0: (disabled)', r)
r = self.launch_good_test("test_device profile 3 get")
self.assertIn('disabled', r)
self.assertIn('Profile 3: (disabled)', r)
self.launch_good_test("test_device profile 3 enable")
r = self.launch_good_test("test_device profile 3 get")
self.assertNotIn('Profile 3: (disabled)', r)
@@ -257,7 +257,7 @@ class TestRatbagCtlProfile(TestRatbagCtl):
def test_profile_disable(self):
r = self.launch_good_test("test_device profile 2 get")
self.assertNotIn('disabled', r)
self.assertNotIn('Profile 2: (disabled)', r)
self.launch_good_test("test_device profile 2 disable")
r = self.launch_good_test("test_device profile 2 get")
self.assertEqual('Profile 2: (disabled)', r)
@@ -280,7 +280,8 @@ class TestRatbagCtlResolution(TestRatbagCtl):
{
"is_active": true,
"resolutions": [
{ "is_active": false,
{ "xres": 800,
"is_active": false,
"is_default": true },
{ "xres": 1200, "yres": 1300,
"capabilities": [1],
@@ -289,6 +290,11 @@ class TestRatbagCtlResolution(TestRatbagCtl):
{ "xres": 2300, "yres": 2400,
"capabilities": [1],
"is_active": true,
"is_default": false },
{ "xres": 400,
"capabilities": [2],
"is_active": false,
"is_disabled": false,
"is_default": false }
]
},
@@ -334,6 +340,38 @@ class TestRatbagCtlResolution(TestRatbagCtl):
self.launch_fail_test("test_device " + command + " X")
self.launch_fail_test("test_device " + command + " 1 X")
def test_resolution_disabled_get(self):
command = "disabled get"
r = self.launch_good_test("test_device resolution 3 " + command)
self.assertEqual(r, "False")
self.launch_fail_test("test_device resolution 10 " + command)
self.launch_fail_test("test_device resolution 3 " + command + " X")
def test_resolution_disabled_set(self):
command = "disabled set"
self.launch_good_test("test_device resolution 3 " + command)
r = self.launch_good_test("test_device resolution 3 disabled get")
self.assertEqual(r, "True")
self.launch_fail_test("resolution " + command)
self.launch_fail_test("test_device resolution 10 " + command)
self.launch_fail_test("test_device resolution 3 " + command + " X")
def test_resolution_enabled_get(self):
command = "enabled get"
r = self.launch_good_test("test_device resolution 3 " + command)
self.assertEqual(r, "False")
self.launch_fail_test("test_device resolution 10 " + command)
self.launch_fail_test("test_device resolution 3 " + command + " X")
def test_resolution_enabled_set(self):
command = "enabled set"
self.launch_good_test("test_device resolution 3 " + command)
r = self.launch_good_test("test_device resolution 3 enabled get")
self.assertEqual(r, "True")
self.launch_fail_test("resolution " + command)
self.launch_fail_test("test_device resolution 10 " + command)
self.launch_fail_test("test_device resolution 3 " + command + " X")
def test_resolution_default_get(self):
command = "resolution default get"
r = self.launch_good_test("test_device " + command)

View File

@@ -540,10 +540,14 @@ class RatbagdProfile(_RatbagdDBus):
class RatbagdResolution(_RatbagdDBus):
"""Represents a ratbagd resolution."""
CAP_SEPARATE_XY_RESOLUTION = 1
CAP_DISABLE = 2
def __init__(self, object_path):
super().__init__("Resolution", object_path)
self._active = self._get_dbus_property("IsActive")
self._default = self._get_dbus_property("IsDefault")
self._disabled = self._get_dbus_property("IsDisabled")
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
if "IsActive" in changed_props.keys():
@@ -558,6 +562,22 @@ class RatbagdResolution(_RatbagdDBus):
self._default = default
self.notify("is-default")
if "IsDisabled" in changed_props.keys():
disabled = changed_props["IsDisabled"]
if disabled != self._disabled:
self._disabled = disabled
self.notify("is-disabled")
@GObject.Property
def capabilities(self):
"""The capabilities of this resolution as an array. Capabilities not
present on the resolution are not in the list. Thus use e.g.
if resolution.CAP_DISABLE in resolution.capabilities:
do something
"""
return self._get_dbus_property("Capabilities") or []
@GObject.Property
def index(self):
"""The index of this resolution."""
@@ -605,6 +625,11 @@ class RatbagdResolution(_RatbagdDBus):
otherwise"""
return self._default
@GObject.Property
def is_disabled(self):
"""True if this is currently disabled, False otherwise"""
return self._disabled
def set_active(self):
"""Set this resolution to be the active one."""
ret = self._dbus_call("SetActive", "")
@@ -617,6 +642,10 @@ class RatbagdResolution(_RatbagdDBus):
self._set_dbus_property("IsDefault", "b", True, readwrite=False)
return ret
def set_disabled(self, disable):
"""Set this resolution to be disabled."""
return self._set_dbus_property("IsDisabled", "b", disable)
class RatbagdButton(_RatbagdDBus):
"""Represents a ratbagd button."""