diff --git a/doc/dbus.rst b/doc/dbus.rst index 8a9a88c7..973759b1 100644 --- a/doc/dbus.rst +++ b/doc/dbus.rst @@ -120,6 +120,48 @@ known to ratbagd. content of the ID is undefined, the client should treat it as an opaque string. +.. attribute:: Model + + :type: s + :flags: read-only, constant + + An ID identifying the physical device model. This string is + guaranteed to be unique for a specific model and always identical + for devices of that model. + + This is a string of one of the following formats: + + - ``usb:1234:abcd:0`` + - ``bluetooth:5678:ef01:0`` + - ``unknown`` + + In the future, other formats may get added. Clients must ignore + unknown string formats. + + For a string starting with `usb:`, the format is the bus type (USB) + followed by a 4-digit lowercase hex USB vendor ID, followed by a + 4-digit lowercase hex USB product ID, followed by an decimal version + number of unspecified length. These four elements are separated by a + colon (``:``). + + For a string starting with `bluetooth:`, the format is the bus type + (Bluetooth) followed by a 4-digit lowercase hex Bluetooth vendor ID, + followed by a 4-digit lowercase hex Bluetooth product ID, followed + by an decimal version number of unspecified length. These four + elements are separated by a colon (``:``). + + For the string ``unknown``, the model of the device cannot be + determined. This is usually a bug in libratbag. + + For ``DeviceId``s of type ``usb`` and ``bluetooth``, the version + number is reserved for use by libratbag. Device with identical + vendor and product IDs but different versions must be considered + different devices. For example, the version may increase when a + manufacturer re-uses USB Ids. + + Vendor or product IDs of 0 are valid IDs (e.g. used used by test + devices). + .. attribute:: Name :type: s diff --git a/ratbagd/ratbagd-device.c b/ratbagd/ratbagd-device.c index 7dded9fa..bdbef9cf 100644 --- a/ratbagd/ratbagd-device.c +++ b/ratbagd/ratbagd-device.c @@ -232,9 +232,36 @@ static int ratbagd_device_commit(sd_bus_message *m, return 0; } +static int +ratbag_device_get_model(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbag_device *lib_device = device->lib_device; + const char *bustype = ratbag_device_get_bustype(lib_device); + uint32_t vid = ratbag_device_get_vendor_id(lib_device), + pid = ratbag_device_get_product_id(lib_device), + version = ratbag_device_get_product_version(lib_device); + char model[64]; + + if (!bustype) + return sd_bus_message_append(reply, "s", "unknown"); + + snprintf(model, sizeof(model), "%s:%04x:%04x:%d", + bustype, vid, pid, version); + + return sd_bus_message_append(reply, "s", model); +} + const sd_bus_vtable ratbagd_device_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Id", "s", NULL, offsetof(struct ratbagd_device, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Model", "s", ratbag_device_get_model, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Name", "s", ratbagd_device_get_device_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Profiles", "ao", ratbagd_device_get_profiles, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("GetSvgFd", "s", "h", ratbagd_device_get_theme_svg_fd, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/libratbag.c b/src/libratbag.c index 305730a2..671b1576 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -517,6 +517,39 @@ ratbag_device_get_svg_name(const struct ratbag_device* device) return name; } +LIBRATBAG_EXPORT const char * +ratbag_device_get_bustype(const struct ratbag_device *device) +{ + switch (device->ids.bustype) { + case BUS_USB: + return "usb"; + case BUS_BLUETOOTH: + return "bluetooth"; + default: + return NULL; + } +} + +LIBRATBAG_EXPORT uint32_t +ratbag_device_get_vendor_id(const struct ratbag_device *device) +{ + return device->ids.vendor; +} + +LIBRATBAG_EXPORT uint32_t +ratbag_device_get_product_id(const struct ratbag_device *device) +{ + return device->ids.product; +} + +LIBRATBAG_EXPORT uint32_t +ratbag_device_get_product_version(const struct ratbag_device *device) +{ + /* change this when we have a need for it, i.e. when we start supporting devices + * where the USB ID gets reused */ + return 0; +} + void ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver) { diff --git a/src/libratbag.h b/src/libratbag.h index 1d6168f5..7947fada 100644 --- a/src/libratbag.h +++ b/src/libratbag.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include #include @@ -439,6 +440,47 @@ ratbag_device_get_name(const struct ratbag_device* device); const char * ratbag_device_get_svg_name(const struct ratbag_device* device); +/** + * @ingroup device + * + * Returns the bustype of the device, "usb", "bluetooth" or NULL for + * unknown. + */ +const char * +ratbag_device_get_bustype(const struct ratbag_device *device); + +/** + * @ingroup device + * + * Returns the vendor ID of the device. + */ +uint32_t +ratbag_device_get_vendor_id(const struct ratbag_device *device); + + +/** + * @ingroup device + * + * Returns the product ID of the device. + */ +uint32_t +ratbag_device_get_product_id(const struct ratbag_device *device); + +/** + * @ingroup device + * + * Returns the product version of the device. This is a made-up number, + * internal to libratbag. It's purpose is to provide an extra bit of + * differentiation where devices use the same VID/PID for different models. + * + * Devices with identical VID/PID but different versions must be considered + * different devices. + * + * Usually the version will be 0. + */ +uint32_t +ratbag_device_get_product_version(const struct ratbag_device *device); + /** * @ingroup device * diff --git a/tools/ratbagc.py.in b/tools/ratbagc.py.in index 56626d64..e1ae49fd 100644 --- a/tools/ratbagc.py.in +++ b/tools/ratbagc.py.in @@ -249,6 +249,17 @@ class RatbagdDevice(metaclass=MetaRatbag): """The unique identifier of this device.""" return self._path + @property + def model(self): + """The unique identifier for this device model.""" + bus = libratbag.ratbag_device_get_bus(self._device) + if not bus: + return "unknown" + vid = libratbag.ratbag_device_get_vendor_id(self._device) + pid = libratbag.ratbag_device_get_product_id(self._device) + version = libratbag.ratbag_device_get_product_version(self._device) + return "{}:{04x}:{04x}:{d}".format(bus, vid, pid, version) + @property def name(self): """The device name, usually provided by the kernel.""" diff --git a/tools/ratbagd.py b/tools/ratbagd.py index 0fd4fcf9..caad8453 100644 --- a/tools/ratbagd.py +++ b/tools/ratbagd.py @@ -347,6 +347,11 @@ class RatbagdDevice(_RatbagdDBus): """The unique identifier of this device.""" return self._get_dbus_property("Id") + @GObject.Property + def model(self): + """The unique identifier for this device model.""" + return self._get_dbus_property("Model") + @GObject.Property def name(self): """The device name, usually provided by the kernel."""