rustbus icon indicating copy to clipboard operation
rustbus copied to clipboard

Make 'org.freedesktop.DBus.Properties' easy to implement for services

Open KillingSpark opened this issue 5 years ago • 4 comments

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.

KillingSpark avatar Nov 15 '20 17:11 KillingSpark

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?

MaxVerevkin avatar Feb 16 '24 18:02 MaxVerevkin

https://github.com/MaxVerevkin/rustbus-service

MaxVerevkin avatar Feb 17 '24 10:02 MaxVerevkin

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!

KillingSpark avatar Feb 17 '24 18:02 KillingSpark

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).

MaxVerevkin avatar Feb 17 '24 18:02 MaxVerevkin