Make 'org.freedesktop.DBus.Properties' easy to implement for services
Currently you would have to write that boilerplate code on your own. A proc-macro that derives the relevant getters and setters would be nice.
Something like this trait should be fulfilled afterwards:
trait DbusProperties {
fn properties_get(&self, name: &str, request: &MarshalledMessage) -> MarshalledMessage;
fn properties_set(&mut self, name: &str, request: &MarshalledMessage) -> MarshalledMessage;
fn properties_get_all(&self, request: &MarshalledMessage) -> MarshalledMessage;
}
The returned messages are just either normal responses or error responses that adhere to the spec here.
org.freedesktop.DBus.Properties.Get (in STRING interface_name,
in STRING property_name,
out VARIANT value);
org.freedesktop.DBus.Properties.Set (in STRING interface_name,
in STRING property_name,
in VARIANT value);
org.freedesktop.DBus.Properties.GetAll (in STRING interface_name,
out DICT<STRING,VARIANT> props);
Sending the org.freedesktop.DBus.Properties.PropertiesChanged signal should be added at a later time. This is tied more directly to how and by whom the connection is handled.
As an somewhat trivial example:
#[derive(DbusProperties)]
struct Contact {
#[dbus_prop_name(ContactID), dbus_prop_ro]
id: u64,
#[dbus_prop_name(LastName), dbus_prop_rw]
name: String,
#[dbus_prop_name(MobileNumber), dbus_prop_rw]
mobile_number: String,
field_that_wont_be_visible: SecretKey,
}
fn properties_set(&mut self, name: &str, request: &MarshalledMessage) -> MarshalledMessage {
match name {
"ContactID" => {/* return error message because that is RO */},
"LastName" => {/* return normal success message */},
"MobileNumber" => {/* return normal success message */},
other => {/* return error message that says $other is not known */}
}
}
fn properties_get(&mut self, name: &str, request: &MarshalledMessage) -> MarshalledMessage {
match name {
"ContactID" => {/* return success message with self.id*/},
"LastName" => {/* return success message with self.name */},
"MobileNumber" => {/* return success message with self.mobile_number */},
other => {/* return error message that says $other is not known */}
}
}
The default should probably be a read-only property just to be on the safe side. Stuff that is not tagged with any dbus_prop_* attributes should never be read or written in the generated code. If no name is given, the field name should be transformed into camel-case, to fit the dbus conventions. Everything tagged with a dbus_property_* has to be Marshal + Unmarshal for this macro to work properly.
I am working on a abstraction layer similar to DispatchConn, but which handles introspection and properties. For now the API looks like this:
let toggle_inverted_method = MethodImp::new("ToggleInverted", toggle_inverted_cb);
let gammarelay_iface = InterfaceImp::new("rs.wl.gammarelay")
.add_method(toggle_inverted_method)
.add_prop(
"Inverted",
Access::ReadWrite(get_inverted_cb, set_inverted_cb),
)
.add_prop(
"Temperature",
Access::ReadWrite(get_temperature_cb, set_temperature_cb),
)
.add_prop("Gamma", Access::ReadWrite(get_gamma_cb, set_gamma_cb))
.add_prop(
"Brightness",
Access::ReadWrite(get_brightness_cb, set_brightness_cb),
);
let mut root_obj = ObjectImp::new("/");
root_obj.add_interface(gammarelay_iface);
let mut dispatch = DbusDispatch::new(conn);
dispatch.add_object(root_obj);
and
let get_all_method = MethodImp::new("GetAll", get_all_props_cb)
.add_arg::<String>("interface_name", false)
.add_arg::<HashMap<String, Variant>>("props", true);
let set_method = MethodImp::new("Set", set_prop_cb)
.add_arg::<String>("interface_name", false)
.add_arg::<String>("property_name", false)
.add_arg::<Variant>("value", false);
let props_iface = InterfaceImp::new("org.freedesktop.DBus.Properties")
.add_method(get_all_method)
.add_method(set_method);
object.add_interface(props_iface);
I will probably publish this as a crate when I make it generic and useful enough. Or do you think something like this may belong in the main crate?
https://github.com/MaxVerevkin/rustbus-service
I think it's good to have this in a separate crate. Even the DispatchConn feels a bit too much tbh. Seems like a cool project though!
Yeah, I agree, that would allow for more experimentation. With DispatchConn I feel like it is not high-level enough to build complex services (lack of introspection is one of the most noticeable things), but not low-level enough to build upon (for example the fact that it owns the context and run does not have a timeout).