Skip to content

Commit 9679b6b

Browse files
committed
gio-macros: Add #[dbus_interface] macro
1 parent 25eeadb commit 9679b6b

17 files changed

Lines changed: 1320 additions & 8 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use gio::DBusCallFlags;
2+
use std::time::Duration;
3+
4+
#[gio::dbus_interface(
5+
name = "com.github.gtk_rs.examples.HelloWorld",
6+
type_name = "GtkRustHelloWorld",
7+
emits_changed_signal = "true",
8+
// crate = gio,
9+
)]
10+
pub(crate) trait HelloWorld {
11+
#[deprecated]
12+
fn hello(&self, name: String) -> String;
13+
14+
#[dbus(name = "Hello2", out_args("name", "effective_delay"))]
15+
fn hello_with_delay(&self, name: String, delay: u32) -> (String, u32);
16+
17+
#[dbus(no_reply)]
18+
fn no_greeting(&self, name: String);
19+
20+
#[dbus(property, get, name = "Name")]
21+
fn last_greeted_name(&self) -> String;
22+
23+
#[dbus(property, get)]
24+
#[deprecated]
25+
fn count(&self) -> i64;
26+
27+
#[dbus(signal)]
28+
fn greeted(&self, name: String);
29+
}
30+
31+
async fn hello_tau(iface: &HelloWorld) -> Result<String, glib::Error> {
32+
iface
33+
.hello("Tau".to_owned())
34+
.timeout(Duration::from_secs(5))
35+
.flags(DBusCallFlags::ALLOW_INTERACTIVE_AUTHORIZATION)
36+
.await
37+
}

examples/gio_dbus_register_object/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use gio::prelude::*;
22

3+
mod hello_world;
4+
35
glib::wrapper! {
46
pub struct SampleApplication(ObjectSubclass<imp::SampleApplication>)
57
@extends gio::Application,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use crate::dbus_interface::attributes::ATTRIBUTE_NAME;
4+
use syn::{Attribute, LitStr};
5+
6+
pub(crate) struct DBusMethodArgumentAttribute {
7+
pub(crate) name: Option<LitStr>,
8+
}
9+
10+
impl DBusMethodArgumentAttribute {
11+
pub(crate) fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
12+
let mut name: Option<LitStr> = None;
13+
for attr in attrs {
14+
if attr.path().is_ident(ATTRIBUTE_NAME) {
15+
attr.parse_nested_meta(|meta| {
16+
if meta.path.is_ident("name") {
17+
name = Some(meta.value()?.parse()?);
18+
Ok(())
19+
} else {
20+
Err(meta.error(format!(
21+
"unknown attribute `{}`. Possible attributes are `name`",
22+
meta.path.get_ident().unwrap(),
23+
)))
24+
}
25+
})?;
26+
}
27+
}
28+
Ok(Self { name })
29+
}
30+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use crate::dbus_interface::attributes::EmitsChangedSignal;
4+
use proc_macro2::TokenStream;
5+
use syn::spanned::Spanned;
6+
use syn::{LitStr, Path};
7+
8+
/// The `#[dbus_interface(...)]` attribute.
9+
pub(crate) struct DBusInterfaceAttribute {
10+
pub(crate) name: LitStr,
11+
pub(crate) type_name: Option<LitStr>,
12+
pub(crate) crate_: Option<Path>,
13+
pub(crate) emits_changed_signal: Option<EmitsChangedSignal>,
14+
pub(crate) generate_proxy: bool,
15+
pub(crate) generate_skeleton: bool,
16+
}
17+
18+
impl DBusInterfaceAttribute {
19+
pub(crate) fn parse(attr: TokenStream) -> syn::Result<Self> {
20+
let span = attr.span();
21+
let mut name: Option<LitStr> = None;
22+
let mut type_name: Option<LitStr> = None;
23+
let mut crate_: Option<Path> = None;
24+
let mut emits_changed_signal: Option<EmitsChangedSignal> = None;
25+
let mut generate_proxy = false;
26+
let mut generate_skeleton = false;
27+
let p = syn::meta::parser(|meta| {
28+
if meta.path.is_ident("name") {
29+
name = Some(meta.value()?.parse()?);
30+
Ok(())
31+
} else if meta.path.is_ident("type_name") {
32+
type_name = Some(meta.value()?.parse()?);
33+
Ok(())
34+
} else if meta.path.is_ident("crate") {
35+
crate_ = Some(meta.value()?.parse()?);
36+
Ok(())
37+
} else if meta.path.is_ident("emits_changed_signal") {
38+
emits_changed_signal = Some(meta.value()?.parse()?);
39+
Ok(())
40+
} else if meta.path.is_ident("proxy") {
41+
generate_proxy = true;
42+
Ok(())
43+
} else if meta.path.is_ident("skeleton") {
44+
generate_skeleton = true;
45+
Ok(())
46+
} else {
47+
Err(meta.error(format!(
48+
"unknown attribute `{}`. Possible attributes are `name`, `type_name`, `crate`, `emits_changed_signal`",
49+
meta.path.get_ident().unwrap(),
50+
)))
51+
}
52+
});
53+
syn::parse::Parser::parse2(p, attr)?;
54+
55+
let Some(name) = name else {
56+
return Err(syn::Error::new(
57+
span,
58+
"attribute `#[dbus_interface(name = ...)]` must be specified",
59+
));
60+
};
61+
Ok(Self {
62+
name,
63+
type_name,
64+
crate_,
65+
emits_changed_signal,
66+
generate_proxy,
67+
generate_skeleton,
68+
})
69+
}
70+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use proc_macro2::Span;
4+
use syn::LitStr;
5+
use syn::parse::Parse;
6+
7+
#[derive(Clone)]
8+
pub(crate) struct EmitsChangedSignal {
9+
pub(crate) kind: EmitsChangedSignalKind,
10+
pub(crate) span: Span,
11+
}
12+
13+
#[derive(Debug, Clone, Copy)]
14+
pub(crate) enum EmitsChangedSignalKind {
15+
True,
16+
Invalidates,
17+
Const,
18+
False,
19+
}
20+
21+
impl Parse for EmitsChangedSignal {
22+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
23+
use EmitsChangedSignalKind::*;
24+
let lit: LitStr = input.parse()?;
25+
let value = lit.value();
26+
let kind = match value.as_str() {
27+
"true" => True,
28+
"invalidates" => Invalidates,
29+
"const" => Const,
30+
"false" => False,
31+
_ => {
32+
return Err(syn::Error::new(
33+
lit.span(),
34+
format!(
35+
"unknown value {value:?}. Possible values are \"true\", \"invalidates\", \"const\", \"false\""
36+
),
37+
));
38+
}
39+
};
40+
Ok(EmitsChangedSignal {
41+
kind,
42+
span: lit.span(),
43+
})
44+
}
45+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use crate::dbus_interface::attributes::{ATTRIBUTE_NAME, EmitsChangedSignal};
4+
use proc_macro2::Span;
5+
use syn::punctuated::Punctuated;
6+
use syn::{Attribute, LitStr, Meta, Token, parenthesized};
7+
8+
/// The attribute placed on an `fn` item in the `#[dbus_interface]` trait.
9+
pub(crate) enum DBusItemAttribute {
10+
/// `#[dbus(...)]`
11+
Method(DBusItemAttributeMethod),
12+
/// `#[dbus(signal, ...)]`
13+
Signal(DBusItemAttributeSignal),
14+
/// `#[dbus(property, ...)]
15+
Property(DBusItemAttributeProperty),
16+
}
17+
18+
pub(crate) struct DBusItemAttributeMethod {
19+
pub(crate) name: Option<LitStr>,
20+
pub(crate) out_args: Option<Punctuated<LitStr, Token![,]>>,
21+
pub(crate) no_reply: bool,
22+
}
23+
24+
pub(crate) struct DBusItemAttributeSignal {
25+
pub(crate) name: Option<LitStr>,
26+
}
27+
28+
pub(crate) struct DBusItemAttributeProperty {
29+
pub(crate) name: Option<LitStr>,
30+
pub(crate) access: DBusPropertyAccess,
31+
pub(crate) emits_changed_signal: Option<EmitsChangedSignal>,
32+
}
33+
34+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35+
pub(crate) enum DBusPropertyAccess {
36+
Read,
37+
Write,
38+
ReadWrite,
39+
}
40+
41+
impl DBusPropertyAccess {
42+
pub(crate) fn or(self, other: DBusPropertyAccess) -> Self {
43+
if self == other {
44+
self
45+
} else {
46+
DBusPropertyAccess::ReadWrite
47+
}
48+
}
49+
}
50+
51+
impl DBusItemAttribute {
52+
pub(crate) fn parse(attrs: &[Attribute], span: Span) -> syn::Result<Self> {
53+
let tag = DBusItemAttributeTag::parse(attrs)?;
54+
match tag {
55+
None => DBusItemAttributeMethod::parse(attrs).map(Self::Method),
56+
Some(DBusItemAttributeTag::Signal) => {
57+
DBusItemAttributeSignal::parse(attrs).map(Self::Signal)
58+
}
59+
Some(DBusItemAttributeTag::Property) => {
60+
DBusItemAttributeProperty::parse(attrs, span).map(Self::Property)
61+
}
62+
}
63+
}
64+
}
65+
66+
impl DBusItemAttributeMethod {
67+
fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
68+
let mut name: Option<LitStr> = None;
69+
let mut out_args: Option<Punctuated<LitStr, Token![,]>> = None;
70+
let mut no_reply = false;
71+
for attr in attrs {
72+
if attr.path().is_ident(ATTRIBUTE_NAME) {
73+
attr.parse_nested_meta(|meta| {
74+
if meta.path.is_ident("name") {
75+
name = Some(meta.value()?.parse()?);
76+
Ok(())
77+
} else if meta.path.is_ident("out_args") {
78+
let content;
79+
parenthesized!(content in meta.input);
80+
out_args = Some(content.call(Punctuated::parse_terminated)?);
81+
Ok(())
82+
} else if meta.path.is_ident("no_reply") {
83+
no_reply = true;
84+
Ok(())
85+
} else {
86+
Err(meta.error(format!(
87+
"unknown attribute `{}`. Possible attributes are `name`, `out_args`, `no_reply`",
88+
meta.path.get_ident().unwrap(),
89+
)))
90+
}
91+
})?;
92+
}
93+
}
94+
Ok(Self {
95+
name,
96+
out_args,
97+
no_reply,
98+
})
99+
}
100+
}
101+
102+
impl DBusItemAttributeSignal {
103+
fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
104+
let mut name: Option<LitStr> = None;
105+
for attr in attrs {
106+
if attr.path().is_ident(ATTRIBUTE_NAME) {
107+
attr.parse_nested_meta(|meta| {
108+
if meta.path.is_ident("signal") {
109+
Ok(())
110+
} else if meta.path.is_ident("name") {
111+
name = Some(meta.value()?.parse()?);
112+
Ok(())
113+
} else {
114+
Err(meta.error(format!(
115+
"unknown attribute `{}`. Possible attributes are `name`",
116+
meta.path.get_ident().unwrap(),
117+
)))
118+
}
119+
})?;
120+
}
121+
}
122+
Ok(Self { name })
123+
}
124+
}
125+
126+
impl DBusItemAttributeProperty {
127+
fn parse(attrs: &[Attribute], span: Span) -> syn::Result<Self> {
128+
let mut name: Option<LitStr> = None;
129+
let mut access: Option<DBusPropertyAccess> = None;
130+
let mut emits_changed_signal: Option<EmitsChangedSignal> = None;
131+
for attr in attrs {
132+
if attr.path().is_ident(ATTRIBUTE_NAME) {
133+
attr.parse_nested_meta(|meta| {
134+
if meta.path.is_ident("property") {
135+
Ok(())
136+
} else if meta.path.is_ident("name") {
137+
name = Some(meta.value()?.parse()?);
138+
Ok(())
139+
} else if meta.path.is_ident("get") {
140+
access = Some(access
141+
.unwrap_or(DBusPropertyAccess::Read)
142+
.or(DBusPropertyAccess::Read));
143+
Ok(())
144+
} else if meta.path.is_ident("set") {
145+
access = Some(access
146+
.unwrap_or(DBusPropertyAccess::Write)
147+
.or(DBusPropertyAccess::Write));
148+
Ok(())
149+
} else if meta.path.is_ident("emits_changed_signal") {
150+
emits_changed_signal = Some(meta.value()?.parse()?);
151+
Ok(())
152+
} else {
153+
Err(meta.error(format!(
154+
"unknown attribute `{}`. Possible attributes are `name`, `get`, `set`, `emits_changed_signal`",
155+
meta.path.get_ident().unwrap(),
156+
)))
157+
}
158+
})?;
159+
}
160+
}
161+
let Some(access) = access else {
162+
return Err(syn::Error::new(
163+
span,
164+
"either the attribute `get` or `set` attribute (or both) must be specified",
165+
));
166+
};
167+
Ok(Self {
168+
name,
169+
access,
170+
emits_changed_signal,
171+
})
172+
}
173+
}
174+
175+
enum DBusItemAttributeTag {
176+
Signal,
177+
Property,
178+
}
179+
180+
impl DBusItemAttributeTag {
181+
pub(crate) fn parse(attrs: &[Attribute]) -> syn::Result<Option<Self>> {
182+
let mut tag = None;
183+
for attr in attrs {
184+
if attr.path().is_ident(ATTRIBUTE_NAME) {
185+
let nested =
186+
attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
187+
if let Some(first_nested) = nested.first() {
188+
if first_nested.path().is_ident("signal") {
189+
tag = Some(Self::Signal);
190+
first_nested.require_path_only()?;
191+
} else if first_nested.path().is_ident("property") {
192+
tag = Some(Self::Property);
193+
first_nested.require_path_only()?;
194+
}
195+
}
196+
}
197+
}
198+
Ok(tag)
199+
}
200+
}

0 commit comments

Comments
 (0)