diff --git a/doc/dbus.rst b/doc/dbus.rst index 00494f19..44cb51e9 100644 --- a/doc/dbus.rst +++ b/doc/dbus.rst @@ -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 diff --git a/ratbagd/ratbagd-json.c b/ratbagd/ratbagd-json.c index 7bc35852..3f210bca 100644 --- a/ratbagd/ratbagd-json.c +++ b/ratbagd/ratbagd-json.c @@ -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; } diff --git a/ratbagd/ratbagd-resolution.c b/ratbagd/ratbagd-resolution.c index d6c75952..9ab9daa4 100644 --- a/ratbagd/ratbagd-resolution.c +++ b/ratbagd/ratbagd-resolution.c @@ -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, diff --git a/src/driver-test.c b/src/driver-test.c index b68b80f4..d9c180c3 100644 --- a/src/driver-test.c +++ b/src/driver-test.c @@ -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]); diff --git a/src/libratbag-enums.h b/src/libratbag-enums.h index 772fa9e8..5852b6e6 100644 --- a/src/libratbag-enums.h +++ b/src/libratbag-enums.h @@ -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, }; /** diff --git a/src/libratbag-private.h b/src/libratbag-private.h index 217b869e..baad44b4 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -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); } diff --git a/src/libratbag-test.h b/src/libratbag-test.h index 325dc0a5..93c66841 100644 --- a/src/libratbag-test.h +++ b/src/libratbag-test.h @@ -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; diff --git a/src/libratbag.c b/src/libratbag.c index fa4209d8..1046ca75 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -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) diff --git a/src/libratbag.h b/src/libratbag.h index 666af95d..36fdc5cb 100644 --- a/src/libratbag.h +++ b/src/libratbag.h @@ -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 * diff --git a/tools/ratbagc.py.in b/tools/ratbagc.py.in index c47fea89..72cf3fc3 100644 --- a/tools/ratbagc.py.in +++ b/tools/ratbagc.py.in @@ -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.""" diff --git a/tools/ratbagctl.in.in b/tools/ratbagctl.in.in index a069b6af..85897b6a 100755 --- a/tools/ratbagctl.in.in +++ b/tools/ratbagctl.in.in @@ -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', diff --git a/tools/ratbagctl.test.in b/tools/ratbagctl.test.in index 3313f68b..1c356784 100755 --- a/tools/ratbagctl.test.in +++ b/tools/ratbagctl.test.in @@ -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) diff --git a/tools/ratbagd.py b/tools/ratbagd.py index b62966c1..523bc40e 100644 --- a/tools/ratbagd.py +++ b/tools/ratbagd.py @@ -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."""