Skip to content

Commit d2013b8

Browse files
committed
gio-macros: Add signal support to DBusInterfaceSkeleton derive
1 parent b762c0e commit d2013b8

6 files changed

Lines changed: 151 additions & 16 deletions

File tree

examples/gio_dbus_register_object/hello_world.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ mod imp {
5353
fn hello(&self, name: String) -> String {
5454
let greet = format!("Hello {name}!");
5555
println!("{greet}");
56+
_ = self.emit_greeted(&name);
5657
greet
5758
}
5859

@@ -75,6 +76,7 @@ mod imp {
7576
glib::timeout_future(Duration::from_secs(delay as u64)).await;
7677
let greet = format!("Hello {name} after {delay} seconds!");
7778
println!("{greet}");
79+
_ = self.emit_greeted(&name);
7880
Ok((greet, instant.elapsed().as_secs_f64()))
7981
}
8082

@@ -107,5 +109,8 @@ mod imp {
107109
}
108110

109111
fn r#raw_ident(&self) {}
112+
113+
#[dbus(signal)]
114+
fn greeted(&self, name: &str) {}
110115
}
111116
}

gio-macros/src/dbus_interface/emit.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

33
use crate::dbus_interface::attributes::DBusInterfaceAttribute;
4-
use crate::dbus_interface::emit_info::{emit_interface_info, emit_method_info};
5-
use crate::dbus_interface::parse::{DBusItems, DBusMethod, DBusMethodArgumentProvider};
4+
use crate::dbus_interface::emit_info::{emit_interface_info, emit_method_info, emit_signal_info};
5+
use crate::dbus_interface::parse::{DBusItems, DBusMethod, DBusMethodArgumentProvider, DBusSignal};
6+
use crate::utils::ident_name;
67
use proc_macro2::{Span, TokenStream};
7-
use quote::{quote, quote_spanned};
8+
use quote::{ToTokens as _, quote, quote_spanned};
89
use syn::spanned::Spanned as _;
9-
use syn::{Attribute, Ident, Type};
10+
use syn::{Attribute, Ident, ReturnType, Token, Type, parse_quote};
1011

1112
pub(crate) fn emit_items(
1213
impl_attrs: &[Attribute],
1314
self_ty: &Type,
1415
items: &DBusItems,
16+
gio: &TokenStream,
1517
) -> syn::Result<TokenStream> {
1618
let emit_methods = items.methods.values().map(|method| &method.item);
19+
let emit_signals = items
20+
.signals
21+
.values()
22+
.map(|signal| emit_signal_fn(signal, gio));
1723
let emit_errors = items.errors.iter().map(|(item, error)| {
1824
let compile_error = error.to_compile_error();
1925
quote! { #item #compile_error }
@@ -23,6 +29,7 @@ pub(crate) fn emit_items(
2329
#(#impl_attrs)*
2430
impl #self_ty {
2531
#(#emit_methods)*
32+
#(#emit_signals)*
2633
#(#emit_errors)*
2734
}
2835
})
@@ -109,13 +116,22 @@ pub(crate) fn emit_methods_impl(
109116
.values()
110117
.map(|method| emit_method_info(method, gio))
111118
.collect::<Result<Vec<_>, _>>()?;
119+
let signal_infos = items
120+
.signals
121+
.values()
122+
.map(|signal| emit_signal_info(signal, gio))
123+
.collect::<Result<Vec<_>, _>>()?;
112124

113125
Ok(quote! {
114126
impl #gio::__macro_helpers::dbus_interface_skeleton::DBusMethods for #self_ty {
115127
fn method_infos() -> impl IntoIterator<Item = #gio::DBusMethodInfo> {
116128
[#(#method_infos,)*]
117129
}
118130

131+
fn signal_infos() -> impl IntoIterator<Item = #gio::DBusSignalInfo> {
132+
[#(#signal_infos,)*]
133+
}
134+
119135
fn method_call(
120136
&self,
121137
connection: #gio::DBusConnection,
@@ -191,3 +207,35 @@ fn emit_method_call_handler(method: &DBusMethod) -> TokenStream {
191207
}
192208
}
193209
}
210+
211+
fn emit_signal_fn(signal: &DBusSignal, gio: &TokenStream) -> TokenStream {
212+
let ident = &signal.item.sig.ident;
213+
let ident = Ident::new(&format!("emit_{}", ident_name(ident)), ident.span());
214+
let arg_idents = signal.args.iter().map(|arg| &arg.ident);
215+
let signal_name = &signal.dbus_name;
216+
let return_type: Type = parse_quote!(Result<(), #gio::glib::Error>);
217+
let block = parse_quote! {{
218+
let obj = &*#gio::glib::subclass::types::ObjectSubclassExt::obj(self);
219+
let connections = #gio::prelude::DBusInterfaceSkeletonExt::connections(obj);
220+
let object_path = #gio::prelude::DBusInterfaceSkeletonExt::object_path(obj);
221+
let object_path = object_path.as_deref();
222+
let info = gio::prelude::DBusInterfaceSkeletonExt::info(obj);
223+
let interface_name = info.name();
224+
let variant = (#(#arg_idents,)*).to_variant();
225+
for connection in connections {
226+
connection.emit_signal(
227+
None,
228+
object_path.expect("object path should be set when there is at least one connection"),
229+
interface_name,
230+
#signal_name,
231+
Some(&variant),
232+
)?;
233+
}
234+
Ok(())
235+
}};
236+
let mut item = signal.item.clone();
237+
item.block = block;
238+
item.sig.ident = ident;
239+
item.sig.output = ReturnType::Type(Token![->](Span::call_site()), Box::new(return_type));
240+
item.into_token_stream()
241+
}

gio-macros/src/dbus_interface/emit_info.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

33
use crate::dbus_interface::attributes::DBusInterfaceAttribute;
4-
use crate::dbus_interface::parse::{DBusMethod, DBusMethodArgument, DBusMethodArgumentProvider};
4+
use crate::dbus_interface::parse::{
5+
DBusMethod, DBusMethodArgument, DBusMethodArgumentProvider, DBusSignal, DBusSignalArgument,
6+
};
57
use proc_macro2::TokenStream;
68
use quote::quote;
79
use std::borrow::Cow;
@@ -19,6 +21,7 @@ pub(crate) fn emit_interface_info(
1921
#gio::DBusInterfaceInfo::builder()
2022
.name(#name)
2123
.methods(<#ident as #helpers::DBusMethods>::method_infos())
24+
.signals(<#ident as #helpers::DBusMethods>::signal_infos())
2225
.build()
2326
})
2427
}
@@ -33,15 +36,35 @@ pub(crate) fn emit_method_info(method: &DBusMethod, gio: &TokenStream) -> syn::R
3336
let out_args = emit_out_arg_infos(&return_type, method.out_arg_names.as_ref(), gio)?;
3437
let annotations = method
3538
.deprecated
36-
.then_some(("org.freedesktop.DBus.Deprecated", "true"))
39+
.then_some((DEPRECATED_ANNOTATION, "true"))
3740
.into_iter();
3841
let annotations = annotations.map(|(key, value)| emit_annotation(key, value, gio));
3942
Ok(quote! {
4043
#gio::DBusMethodInfo::builder()
44+
.name(#name)
4145
.in_args([#(#in_args,)*])
4246
.out_args(#out_args)
4347
.annotations([#(#annotations,)*])
48+
.build()
49+
})
50+
}
51+
52+
pub(crate) fn emit_signal_info(signal: &DBusSignal, gio: &TokenStream) -> syn::Result<TokenStream> {
53+
let name = &signal.dbus_name;
54+
let args = signal
55+
.args
56+
.iter()
57+
.filter_map(|arg| emit_signal_arg_info(arg, gio));
58+
let annotations = signal
59+
.deprecated
60+
.then_some((DEPRECATED_ANNOTATION, "true"))
61+
.into_iter();
62+
let annotations = annotations.map(|(key, value)| emit_annotation(key, value, gio));
63+
Ok(quote! {
64+
#gio::DBusSignalInfo::builder()
4465
.name(#name)
66+
.args([#(#args,)*])
67+
.annotations([#(#annotations,)*])
4568
.build()
4669
})
4770
}
@@ -77,6 +100,17 @@ fn emit_in_arg_info(argument: &DBusMethodArgument, gio: &TokenStream) -> Option<
77100
})
78101
}
79102

103+
fn emit_signal_arg_info(argument: &DBusSignalArgument, gio: &TokenStream) -> Option<TokenStream> {
104+
let name = &argument.dbus_name;
105+
let signature = emit_signature_str(&argument.arg.ty, gio);
106+
Some(quote! {
107+
#gio::DBusArgInfo::builder()
108+
.name(#name)
109+
.signature(#signature)
110+
.build()
111+
})
112+
}
113+
80114
// The out args are fundamentally different from the in args.
81115
//
82116
// Each function argument maps neatly to an in arg so we know the count and their names at generation time.
@@ -160,3 +194,5 @@ fn emit_annotation(key: &str, value: &str, gio: &TokenStream) -> TokenStream {
160194
.build()
161195
}
162196
}
197+
198+
const DEPRECATED_ANNOTATION: &str = "org.freedesktop.DBus.Deprecated";

gio-macros/src/dbus_interface/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn try_dbus_methods(
7777
) -> syn::Result<TokenStream> {
7878
let dbus_items = parse::parse_impl_items(items);
7979

80-
let items = emit_items(&attrs, &self_ty, &dbus_items)?;
80+
let items = emit_items(&attrs, &self_ty, &dbus_items, gio)?;
8181
let exportable_interface_impl = emit_methods_impl(&self_ty, &dbus_items, gio)?;
8282
Ok(quote! {
8383
#items

gio-macros/src/dbus_interface/parse.rs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use std::collections::BTreeMap;
1313
use std::collections::btree_map::Entry;
1414
use syn::punctuated::Punctuated;
1515
use syn::spanned::Spanned;
16-
use syn::{Attribute, FnArg, Ident, ImplItem, ImplItemFn, LitStr, Pat, PatType, Token, Type};
16+
use syn::{
17+
Attribute, FnArg, Ident, ImplItem, ImplItemFn, LitStr, Pat, PatIdent, PatType, Token, Type,
18+
};
1719

1820
#[derive(Default)]
1921
pub(crate) struct DBusItems {
@@ -52,9 +54,16 @@ pub(crate) enum DBusMethodArgumentProvider {
5254
pub(crate) struct DBusSignal {
5355
pub(crate) item: ImplItemFn,
5456
pub(crate) dbus_name: LitStr,
57+
pub(crate) args: Vec<DBusSignalArgument>,
5558
pub(crate) deprecated: bool,
5659
}
5760

61+
pub(crate) struct DBusSignalArgument {
62+
pub(crate) arg: PatType,
63+
pub(crate) ident: Ident,
64+
pub(crate) dbus_name: LitStr,
65+
}
66+
5867
pub(crate) fn parse_impl_items(items: Vec<ImplItem>) -> DBusItems {
5968
let mut output = DBusItems::default();
6069
for item in items {
@@ -71,13 +80,17 @@ pub(crate) fn parse_impl_items(items: Vec<ImplItem>) -> DBusItems {
7180
output.errors.push((ImplItem::Fn(method.item), error));
7281
}
7382
}
74-
DBusItem::Signal(dbus_signal) => {
75-
let mut impl_item = ImplItem::Fn(dbus_signal.item);
76-
remove_dbus_attribute_from_impl_item(&mut impl_item);
77-
let span = impl_item.span();
78-
output
79-
.errors
80-
.push((impl_item, syn::Error::new(span, "[TODO]")));
83+
DBusItem::Signal(mut signal) => {
84+
remove_dbus_attribute_from_item_fn(&mut signal.item);
85+
if let Entry::Vacant(entry) = output.signals.entry(signal.dbus_name.value()) {
86+
entry.insert(signal);
87+
} else {
88+
let error = syn::Error::new(
89+
signal.item.span(),
90+
"a signal with this name is already defined",
91+
);
92+
output.errors.push((ImplItem::Fn(signal.item), error));
93+
}
8194
}
8295
DBusItem::Error(mut impl_item, error) => {
8396
remove_dbus_attribute_from_impl_item(&mut impl_item);
@@ -149,6 +162,12 @@ fn parse_impl_item_fn_for_signal(
149162
.name
150163
.unwrap_or_else(|| default_method_name(&item.sig.ident));
151164
let deprecated = is_deprecated(&item.attrs);
165+
let args = item
166+
.sig
167+
.inputs
168+
.iter()
169+
.filter_map(|input| parse_signal_arg(input.clone()).transpose())
170+
.collect::<Result<_, _>>()?;
152171

153172
if !item.block.stmts.is_empty() {
154173
return Err(syn::Error::new(
@@ -157,9 +176,17 @@ fn parse_impl_item_fn_for_signal(
157176
));
158177
}
159178

179+
if matches!(item.sig.output, syn::ReturnType::Type(..)) {
180+
return Err(syn::Error::new(
181+
item.sig.output.span(),
182+
"signal must not specify a return type",
183+
));
184+
}
185+
160186
Ok(DBusSignal {
161187
item,
162188
dbus_name,
189+
args,
163190
deprecated,
164191
})
165192
}
@@ -187,6 +214,23 @@ fn parse_method_arg(
187214
}))
188215
}
189216

217+
fn parse_signal_arg(arg: FnArg) -> syn::Result<Option<DBusSignalArgument>> {
218+
let FnArg::Typed(typed) = arg else {
219+
return Ok(None);
220+
};
221+
let Pat::Ident(PatIdent { ident, .. }) = typed.pat.as_ref() else {
222+
return Err(syn::Error::new(
223+
typed.pat.span(),
224+
"signal parameter must have a name",
225+
));
226+
};
227+
Ok(Some(DBusSignalArgument {
228+
dbus_name: default_method_name(ident),
229+
ident: ident.clone(),
230+
arg: typed,
231+
}))
232+
}
233+
190234
fn is_deprecated(attrs: &[Attribute]) -> bool {
191235
attrs.iter().any(|attr| attr.path().is_ident("deprecated"))
192236
}

gio/src/macro_helpers/dbus_interface_skeleton.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

3-
use crate::{DBusConnection, DBusMethodInfo, DBusMethodInvocation, ffi};
3+
use crate::{DBusConnection, DBusMethodInfo, DBusMethodInvocation, DBusSignalInfo, ffi};
44

55
#[diagnostic::on_unimplemented(message = "No #[gio::dbus_methods] impl block found for {Self}")]
66
pub trait DBusMethods {
77
fn method_infos() -> impl IntoIterator<Item = DBusMethodInfo>;
88

9+
fn signal_infos() -> impl IntoIterator<Item = DBusSignalInfo>;
10+
911
fn method_call(
1012
&self,
1113
connection: DBusConnection,

0 commit comments

Comments
 (0)