Skip to content

gio: Convenience macro for implementing DBus interfaces#1934

Draft
tautropfli wants to merge 6 commits into
gtk-rs:mainfrom
tautropfli:dbus-interface-derive
Draft

gio: Convenience macro for implementing DBus interfaces#1934
tautropfli wants to merge 6 commits into
gtk-rs:mainfrom
tautropfli:dbus-interface-derive

Conversation

@tautropfli

@tautropfli tautropfli commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

(Veeeery WIP)

Edit: I have started working on a new version with the shape suggested in the «Rust ❤️ GNOME» Matrix room. For reference, the old version (the one currently described in this PR's description) can be found in the dbus-interface-derive-v2 branch.

My goal is to have a macro very similar to what zbus has with their #[zbus::interface] macro.
There's no need to specify an XML as the DBusInterfaceInfo is also generated.

I originally wanted the macro to implement DBusInterfaceSkeleton but this interface has so many footguns that I decided to go with my own trait DBusExportableInterface for now.
It essentially exposes all the parts that are usually passed to register_object.

The macro implements the DBusInterfaceSkeleton interface.

#[derive(Default, gio::DBusInterfaceSkeleton)]
#[dbus_interface(name = "com.github.gtk_rs.examples.HelloWorld")]
pub struct HelloWorldSkeleton {
    // ...
}

#[gio::dbus_methods]
impl HelloWorldSkeleton {
    #[deprecated]
    fn hello(&self, name: String) -> String {
        let greet = format!("Hello {name}!");
        println!("{greet}");
        greet
    }

    #[dbus(out_args("name", "effective_delay"))]
    async fn slow_hello(
        &self,
        #[dbus(connection)] connection: gio::DBusConnection,
        #[dbus(invocation)] invocation: gio::DBusMethodInvocation,
        name: String,
        delay: u32,
    ) -> Result<(String, f64), glib::Error> {
        if delay > 4 {
            return Err(glib::Error::new(
                gio::DBusError::InvalidArgs,
                "delay must not be greater than 4 seconds",
            ));
        }

        let instant = Instant::now();
        glib::timeout_future(Duration::from_secs(delay as u64)).await;
        let greet = format!("Hello {name} after {delay} seconds!");
        println!("{greet}");
        Ok((greet, instant.elapsed().as_secs_f64()))
    }

    #[allow(clippy::manual_async_fn)]
    fn goodbye(&self) -> impl Future<Output = ()> {
        async {
            if let Some(app) = self.application.borrow().upgrade() {
                app.quit();
            }
        }
    }
}

Todo

  • Properties
  • Signals
  • [ ] Convenience for registering an object that implements DBusExportableInterface Not needed since we implement DBusInterfaceSkeleton.
  • Cleanup
  • Tests
  • Documentation

@tautropfli tautropfli changed the title Convenience macro for implementing DBus interfaces gio: Convenience macro for implementing DBus interfaces Mar 25, 2026
@tautropfli tautropfli force-pushed the dbus-interface-derive branch 6 times, most recently from d2013b8 to 2a95f9f Compare March 30, 2026 19:38
Comment thread gio-macros/src/dbus_interface/emit.rs Outdated
fn info(&self) -> *mut #gio::ffi::GDBusInterfaceInfo {
use #gio::glib::translate::*;
static INFO: ::std::sync::OnceLock<#gio::DBusInterfaceInfo> = ::std::sync::OnceLock::new();
let info = INFO.get_or_init(|| #interface_info);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need a OnceLock for this, it's all constant data that should reside in .rodata?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method return types are sadly not known at macro evaluation time, see my other reply

Comment thread gio-macros/src/dbus_interface/emit.rs Outdated
let variant = (#(#arg_idents,)*).to_variant();
for connection in connections {
connection.emit_signal(
None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a way to specify this. (See this patch for Vala.)

//
// Each function argument maps neatly to an in arg so we know the count and their names at generation time.
//
// The out args come from the return type for which we know how many tuple elements it has only at runtime

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand this. Why do we only know that at runtime?

@tautropfli tautropfli Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proc macros don't have access to type information, only syntax. So we can only recognize tuples that are directly specified as return type using tuple syntax but not type aliases for tuples or structs that marshal to tuple variants.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use const evaluation here, but that would require everything related to the StaticVariantType to be marked const.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proc macros don't have access to type information, only syntax.

That looks very unfortunate...

So we can only recognize tuples that are directly specified as return type using tuple syntax but not type aliases for tuples or structs that marshal to tuple variants.

We should require people to always use plain tuples without aliases then (i.e. emit a compile-time error if they don't). Lest we fall into the same trap as zbus.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, yes, using traits for this would make sense. Can't we require the return type to impl some trait (be it StaticVariantType or not) that has a const VARIANT_TYPE: &'static str?

@tautropfli tautropfli Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can only recognize tuples that are directly specified as return type using tuple syntax but not type aliases for tuples or structs that marshal to tuple variants.

We should require people to always use plain tuples without aliases then

I should have specified a bit better, my comment was about what information we have at macro evaluation time. At runtime (through StaticVariantType) we do know if a type is a tuple. That's why I currently generate code that puts together the out_args at runtime rather than at macro expansion time.

So right now there is no disconnect between introspection and return value.

Making a const version of StaticVariantType is probably a whole project onto itself 😅 I would rather not have to parse variant strings.

@tautropfli tautropfli force-pushed the dbus-interface-derive branch from 2a95f9f to f096ca3 Compare April 4, 2026 15:28
@tautropfli tautropfli force-pushed the dbus-interface-derive branch from f096ca3 to 9679b6b Compare April 24, 2026 14:16
@tautropfli tautropfli force-pushed the dbus-interface-derive branch from 9679b6b to 5109ab3 Compare April 24, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants