wayland_scanner/
interfaces.rs

1use proc_macro2::TokenStream;
2
3use crate::protocol::{Interface, Message, Protocol, Type};
4
5use quote::{format_ident, quote};
6
7pub fn generate(protocol: &Protocol, with_c_interfaces: bool) -> TokenStream {
8    let interfaces =
9        protocol.interfaces.iter().map(|iface| generate_interface(iface, with_c_interfaces));
10    if with_c_interfaces {
11        let prefix = super::c_interfaces::generate_interfaces_prefix(protocol);
12        quote! {
13            #prefix
14            #(#interfaces)*
15        }
16    } else {
17        interfaces.collect()
18    }
19}
20
21pub(crate) fn generate_interface(interface: &Interface, with_c: bool) -> TokenStream {
22    let const_name = format_ident!("{}_INTERFACE", interface.name.to_ascii_uppercase());
23    let iface_name = &interface.name;
24    let iface_version = interface.version;
25    let requests = build_messagedesc_list(&interface.requests);
26    let events = build_messagedesc_list(&interface.events);
27
28    let c_name = format_ident!("{}_interface", interface.name);
29
30    if with_c {
31        let c_iface = super::c_interfaces::generate_interface(interface);
32        quote! {
33            pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
34                name: #iface_name,
35                version: #iface_version,
36                requests: #requests,
37                events: #events,
38                c_ptr: Some(unsafe { & #c_name }),
39            };
40
41            #c_iface
42        }
43    } else {
44        quote! {
45            pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
46                name: #iface_name,
47                version: #iface_version,
48                requests: #requests,
49                events: #events,
50                c_ptr: None,
51            };
52        }
53    }
54}
55
56fn build_messagedesc_list(list: &[Message]) -> TokenStream {
57    let desc_list = list.iter().map(|message| {
58        let name = &message.name;
59        let since = message.since;
60        let is_destructor = message.typ == Some(Type::Destructor);
61        let signature = message.args.iter().map(|arg| {
62            if arg.typ == Type::NewId && arg.interface.is_none() {
63                // this is a special generic message, it expands to multiple arguments
64                quote! {
65                    wayland_backend::protocol::ArgumentType::Str(wayland_backend::protocol::AllowNull::No),
66                    wayland_backend::protocol::ArgumentType::Uint,
67                    wayland_backend::protocol::ArgumentType::NewId
68                }
69            } else {
70                let typ = arg.typ.common_type();
71                if arg.typ.nullable() {
72                    if arg.allow_null {
73                        quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::Yes) }
74                    } else {
75                        quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::No) }
76                    }
77                } else {
78                    quote! { wayland_backend::protocol::ArgumentType::#typ }
79                }
80            }
81        });
82        let child_interface = match message
83            .args
84            .iter()
85            .find(|arg| arg.typ == Type::NewId)
86            .and_then(|arg| arg.interface.as_ref())
87        {
88            Some(name) => {
89                let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
90                quote! { Some(&#target_iface) }
91            }
92            None => quote! { None },
93        };
94        let arg_interfaces = message.args.iter().filter(|arg| arg.typ == Type::Object).map(|arg| {
95            match arg.interface {
96                Some(ref name) => {
97                    let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
98                    quote! { &#target_iface }
99                }
100                None => {
101                    quote! { &wayland_backend::protocol::ANONYMOUS_INTERFACE }
102                }
103            }
104        });
105        quote! {
106            wayland_backend::protocol::MessageDesc {
107                name: #name,
108                signature: &[ #(#signature),* ],
109                since: #since,
110                is_destructor: #is_destructor,
111                child_interface: #child_interface,
112                arg_interfaces: &[ #(#arg_interfaces),* ],
113            }
114        }
115    });
116
117    quote!(
118        &[ #(#desc_list),* ]
119    )
120}
121
122#[cfg(test)]
123mod tests {
124    #[test]
125    fn interface_gen() {
126        let protocol_file =
127            std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
128        let protocol_parsed = crate::parse::parse(protocol_file);
129        let generated: String = super::generate(&protocol_parsed, true).to_string();
130        let generated = crate::format_rust_code(&generated);
131
132        let reference =
133            std::fs::read_to_string("./tests/scanner_assets/test-interfaces.rs").unwrap();
134        let reference = crate::format_rust_code(&reference);
135
136        if reference != generated {
137            let diff = similar::TextDiff::from_lines(&reference, &generated);
138            print!("{}", diff.unified_diff().context_radius(10).header("reference", "generated"));
139            panic!("Generated does not match reference!")
140        }
141    }
142}