waybackend_scanner/
lib.rs

1use std::{fs::File, io::BufReader, num::NonZeroU32, path::Path};
2
3use proc_macro2::TokenStream;
4use quick_xml::{Reader, events::attributes::Attributes};
5use quote::quote;
6
7use quick_xml::events::Event as Ev;
8
9#[derive(Debug)]
10pub struct Protocol {
11    _name: String,
12    interfaces: Vec<Interface>,
13    _summary: String,
14    _description: String,
15}
16
17impl Protocol {
18    pub fn new<P: AsRef<Path>>(file: P) -> Self {
19        let mut reader = Reader::from_file(file).unwrap();
20        let mut name = None;
21        let mut interfaces = Vec::new();
22        let mut summary = "".to_string();
23        let mut description = "".to_string();
24
25        let mut buf = Vec::new();
26        loop {
27            match reader.read_event_into(&mut buf) {
28                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
29                // exits the loop when reaching end of file
30                Ok(Ev::Eof) => break,
31                Ok(Ev::Start(e)) => match e.name().as_ref() {
32                    b"protocol" => {
33                        for attr in e.attributes() {
34                            match attr {
35                                Ok(attr) => {
36                                    if attr.key.0 == b"name" {
37                                        name = Some(
38                                            std::str::from_utf8(&attr.value).unwrap().to_string(),
39                                        );
40                                    }
41                                }
42                                Err(e) => panic!("failed to read xml: {e}"),
43                            }
44                        }
45                    }
46
47                    b"interface" => interfaces.push(Interface::new(&mut reader, e.attributes())),
48
49                    b"copyright" => {
50                        // ignore the text
51                        let _ = reader.read_event_into(&mut buf);
52                        // ignore the closing </copyright>
53                        let _ = reader.read_event_into(&mut buf);
54                    }
55
56                    b"description" => {
57                        (summary, description) = parse_description(&mut reader, e.attributes())
58                    }
59                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
60                },
61                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
62                Ok(Ev::Decl(decl)) => {
63                    if let Some(Ok(enc)) = decl.encoding() {
64                        if *enc != *b"UTF-8" && *enc != *b"utf-8" {
65                            panic!("xml file is not enconded with utf-8");
66                        }
67                    }
68                }
69                Ok(Ev::End(end)) => match end.name().as_ref() {
70                    b"protocol" => break,
71                    b"description" => continue,
72                    _ => panic!("bad xml file: {}", reader.buffer_position()),
73                },
74                e => panic!("unprocessed xml event: {e:?}"),
75            }
76        }
77
78        Self {
79            _name: name.unwrap(),
80            interfaces,
81            _summary: summary,
82            _description: description,
83        }
84    }
85
86    pub fn generate(self) -> TokenStream {
87        //let name = format_ident!("{}", self.name);
88        //let mut doc = self.summary;
89        //for line in self.description.lines() {
90        //    doc.push('\n');
91        //    doc += line.trim();
92        //}
93        self.interfaces
94            .into_iter()
95            .map(Interface::generate)
96            .collect()
97    }
98}
99
100#[derive(Debug)]
101struct Interface {
102    name: String,
103    summary: String,
104    description: String,
105    version: NonZeroU32,
106    requests: Vec<Request>,
107    events: Vec<Event>,
108    enums: Vec<Enum>,
109}
110
111impl Interface {
112    fn new(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> Self {
113        let (name, version) = {
114            let mut name = "".to_string();
115            let mut version = 0;
116            for attr in attrs.flatten() {
117                match attr.key.as_ref() {
118                    b"name" => name = attr.unescape_value().unwrap().to_string(),
119                    b"version" => version = attr.unescape_value().unwrap().parse::<u32>().unwrap(),
120
121                    e => panic!(
122                        "unrecognized attribute in interface: {}",
123                        String::from_utf8_lossy(e)
124                    ),
125                }
126            }
127            assert!(!name.is_empty());
128            (name, NonZeroU32::new(version).unwrap())
129        };
130
131        let mut summary = "".to_string();
132        let mut description = "".to_string();
133        let mut requests = Vec::new();
134        let mut events = Vec::new();
135        let mut enums = Vec::new();
136
137        let mut buf = Vec::new();
138        loop {
139            match reader.read_event_into(&mut buf) {
140                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
141                Ok(Ev::Start(e)) => match e.name().as_ref() {
142                    b"description" => {
143                        (summary, description) = parse_description(reader, e.attributes())
144                    }
145                    b"request" => requests.push(Request::new(reader, e.attributes())),
146                    b"event" => events.push(Event::new(reader, e.attributes())),
147                    b"enum" => enums.push(Enum::new(reader, e.attributes())),
148                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
149                },
150                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
151                Ok(Ev::End(end)) => match end.name().as_ref() {
152                    b"description" => continue,
153                    b"interface" => break,
154                    _ => panic!("bad xml file: {end:?} at {}", reader.buffer_position()),
155                },
156                e => panic!("unprocessed xml event: {e:?}"),
157            }
158        }
159
160        assert!(!summary.is_empty());
161        assert!(!description.is_empty());
162
163        Self {
164            name,
165            summary,
166            description,
167            version,
168            requests,
169            events,
170            enums,
171        }
172    }
173
174    fn generate(self) -> TokenStream {
175        let name = &self.name;
176        let name_ident = ident(name);
177        let mut doc = self.summary;
178        for line in self.description.lines() {
179            doc.push('\n');
180            doc += line.trim();
181        }
182        let version = self.version.get();
183
184        let events = self.events.iter().map(|ev| {
185            let mut doc = ev.summary.clone();
186            for line in ev.description.lines() {
187                doc.push('\n');
188                doc += line.trim();
189            }
190
191            if ev.destructor {
192                doc += "\nTHIS IS A DESTRUCTOR"
193            }
194
195            // note: we cannot issue a proper deprecation warning because we will be using this
196            // function in the event handler implementation
197            if let Some(d) = ev.deprecated_since {
198                doc.push_str(&format!("\nDeprecated since interface version {d}"));
199            }
200
201            let args = ev.args.iter().map(Arg::gen_fn_args);
202
203            let fn_name = ident(&ev.name);
204            quote! {
205                #[doc = #doc]
206                fn #fn_name(&mut self, sender_id: waybackend::types::ObjectId, #(#args),*);
207            }
208        });
209
210        let requests = self.requests.iter().enumerate().map(|(i, req)| {
211            let ident = ident(&req.name.to_uppercase());
212            let mut doc = req.summary.clone();
213            if let Some(desc) = req.description.as_ref() {
214                for line in desc.lines() {
215                    doc.push('\n');
216                    doc += line.trim();
217                }
218            }
219
220            if req.destructor {
221                doc += "\nTHIS IS A DESTRUCTOR"
222            }
223
224            let deprecation_warning = if let Some(d) = req.deprecated_since {
225                let note = format!("deprecation since interface version {d}");
226                quote! {#[deprecated(note=#note)]}
227            } else {
228                TokenStream::new()
229            };
230
231            let i = i as u16;
232            let function = req.generate();
233            quote! {
234                pub const #ident : u16 = #i;
235                #[doc = #doc]
236                #deprecation_warning
237                #function
238            }
239        });
240
241        let events_dispatch = self
242            .events
243            .iter()
244            .enumerate()
245            .map(|(i, ev)| ev.generate(i as u16));
246        let enums = self.enums.into_iter().map(|i| i.generate());
247
248        let event = quote! {
249            pub fn event<T: EvHandler>(
250                state: &mut T,
251                wire_msg: &mut waybackend::wire::Messages<'_>,
252            ) -> Result<(), waybackend::wire::Error> {
253                match wire_msg.op() {
254                    #(#events_dispatch)*
255                    otherwise => Err(waybackend::wire::Error::UnrecognizedEventOpCode((#name, otherwise)))
256                }
257            }
258        };
259
260        quote! {
261            #[doc = #doc]
262            #[allow(
263                non_upper_case_globals,
264                non_camel_case_types,
265                unused_imports,
266                unused,
267                clippy::too_many_arguments,
268                clippy::match_single_binding,
269                clippy::empty_docs,
270                clippy::just_underscores_and_digits
271            )]
272            pub mod #name_ident {
273                use super::*;
274
275                pub const MAX_VERSION: u32 = #version;
276                pub const NAME: &str = #name;
277
278                #[doc = "Events for this interface"]
279                pub trait EvHandler {
280                    #(#events)*
281                }
282
283                #event
284
285                #[doc = "Requests for this interface"]
286                pub mod req {
287                    use super::*;
288                    #(#requests)*
289                }
290
291                #(#enums)*
292            }
293        }
294    }
295}
296
297/// Returns the summary and the description
298fn parse_description(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> (String, String) {
299    if let Some(attr) = attrs.flatten().next() {
300        match attr.key.as_ref() {
301            b"summary" => {
302                let summary = std::str::from_utf8(&attr.value).unwrap().to_string();
303                let mut buf = Vec::new();
304                loop {
305                    match reader.read_event_into(&mut buf) {
306                        Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
307                        Ok(Ev::Text(text)) => return (summary, text.unescape().unwrap().into()),
308                        Ok(Ev::Comment(_)) => continue,
309                        Ok(ev) => {
310                            panic!(
311                                "Unexpected xml tag {:?} at: {}",
312                                ev,
313                                reader.buffer_position()
314                            )
315                        }
316                    }
317                }
318            }
319            other => panic!("found unexpected attribute in description: {:?}", other),
320        }
321    }
322    unreachable!("failed to parse descrition's summary and description")
323}
324
325#[derive(Debug)]
326struct Request {
327    name: String,
328    summary: String,
329    destructor: bool,
330    deprecated_since: Option<u16>,
331    description: Option<String>,
332    args: Vec<Arg>,
333}
334
335impl Request {
336    fn new(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> Self {
337        let mut destructor = false;
338        let mut deprecated_since = None;
339        let name = {
340            let mut name = "".to_string();
341            for attr in attrs.flatten() {
342                match attr.key.as_ref() {
343                    b"name" => name = attr.unescape_value().unwrap().to_string(),
344                    b"deprecated-since" => {
345                        deprecated_since = Some(attr.unescape_value().unwrap().parse().unwrap())
346                    }
347                    b"since" => continue, // do not do anything for now
348                    b"type" => match attr.unescape_value().unwrap().as_ref() {
349                        "destructor" => destructor = true,
350                        e => panic!("unrecognized request type: {e:?}"),
351                    },
352                    e => panic!(
353                        "unrecognized attribute in interface request: {}",
354                        String::from_utf8_lossy(e)
355                    ),
356                }
357            }
358            assert!(!name.is_empty());
359            name
360        };
361
362        let mut summary = "".to_string();
363        let mut description = None;
364        let mut args = Vec::new();
365
366        let mut buf = Vec::new();
367        loop {
368            match reader.read_event_into(&mut buf) {
369                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
370                Ok(Ev::Start(e)) => match e.name().as_ref() {
371                    b"description" => {
372                        let parsed = parse_description(reader, e.attributes());
373                        summary = parsed.0;
374                        description = Some(parsed.1);
375                    }
376                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
377                },
378                Ok(Ev::Empty(e)) => match e.name().as_ref() {
379                    b"arg" => args.push(Arg::new(e.attributes())),
380                    b"description" => {
381                        if let Some(attr) = e.attributes().flatten().next() {
382                            match attr.key.as_ref() {
383                                b"summary" => {
384                                    summary = std::str::from_utf8(&attr.value).unwrap().to_string()
385                                }
386                                e => panic!("unrecognized description attribute: {e:?}"),
387                            }
388                        }
389                    }
390                    e => panic!(
391                        "unrecognized tag: {:?} at: {}",
392                        std::str::from_utf8(e),
393                        reader.buffer_position()
394                    ),
395                },
396                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
397                Ok(Ev::End(end)) => match end.name().as_ref() {
398                    b"description" => continue,
399                    b"request" => break,
400                    _ => panic!("bad xml file: {end:?} at {}", reader.buffer_position()),
401                },
402                e => panic!("unprocessed xml event: {e:?}"),
403            }
404        }
405
406        assert!(!summary.is_empty());
407        Self {
408            name,
409            summary,
410            description,
411            deprecated_since,
412            destructor,
413            args,
414        }
415    }
416
417    fn generate(&self) -> TokenStream {
418        let name = ident(&self.name);
419
420        let opcode = ident(&self.name.to_uppercase());
421
422        let args = self.args.iter().map(|arg| arg.gen_fn_args());
423        let args_builder = self.args.iter().map(|arg| arg.gen_builder());
424
425        quote! {
426            pub fn #name(
427                backend: &mut waybackend::Waybackend,
428                sender_id: waybackend::types::ObjectId,
429                #(#args),*
430            ) -> Result<(), waybackend::wire::Error> {
431                let waybackend::Waybackend {wire_msg_builder, wayland_fd, ..} = backend;
432                wire_msg_builder.add_header(wayland_fd, sender_id, #opcode)?;
433                #(#args_builder)*
434                wire_msg_builder.finish();
435                Ok(())
436            }
437        }
438    }
439}
440
441#[derive(Debug)]
442struct Event {
443    name: String,
444    summary: String,
445    destructor: bool,
446    deprecated_since: Option<u16>,
447    description: String,
448    args: Vec<Arg>,
449}
450
451impl Event {
452    fn new(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> Self {
453        let mut destructor = false;
454        let mut deprecated_since = None;
455        let name = {
456            let mut name = "".to_string();
457            for attr in attrs.flatten() {
458                match attr.key.as_ref() {
459                    b"name" => name = attr.unescape_value().unwrap().to_string(),
460                    b"deprecated-since" => {
461                        deprecated_since = Some(attr.unescape_value().unwrap().parse().unwrap())
462                    }
463                    b"since" => continue, // do not do anything for now
464                    b"type" => match attr.unescape_value().unwrap().as_ref() {
465                        "destructor" => destructor = true,
466                        e => panic!("unrecognized event type: {e:?}"),
467                    },
468                    e => panic!(
469                        "unrecognized attribute in interface event: {}",
470                        String::from_utf8_lossy(e)
471                    ),
472                }
473            }
474            assert!(!name.is_empty());
475            name
476        };
477
478        let mut summary = "".to_string();
479        let mut description = "".to_string();
480        let mut args = Vec::new();
481
482        let mut buf = Vec::new();
483        loop {
484            match reader.read_event_into(&mut buf) {
485                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
486                Ok(Ev::Start(e)) => match e.name().as_ref() {
487                    b"description" => {
488                        (summary, description) = parse_description(reader, e.attributes())
489                    }
490                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
491                },
492                Ok(Ev::Empty(e)) => match e.name().as_ref() {
493                    b"arg" => args.push(Arg::new(e.attributes())),
494                    b"description" => {
495                        if let Some(attr) = e.attributes().flatten().next() {
496                            match attr.key.as_ref() {
497                                b"summary" => {
498                                    summary = std::str::from_utf8(&attr.value).unwrap().to_string()
499                                }
500                                e => panic!("unrecognized description attribute: {e:?}"),
501                            }
502                        }
503                    }
504                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
505                },
506                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
507                Ok(Ev::End(end)) => match end.name().as_ref() {
508                    b"description" => continue,
509                    b"event" => break,
510                    _ => panic!("bad xml file: {end:?} at {}", reader.buffer_position()),
511                },
512                e => panic!("unprocessed xml event: {e:?}"),
513            }
514        }
515
516        assert!(!summary.is_empty());
517        assert!(!description.is_empty());
518
519        Self {
520            name,
521            summary,
522            description,
523            deprecated_since,
524            destructor,
525            args,
526        }
527    }
528
529    fn generate(&self, opcode: u16) -> TokenStream {
530        let arg_idents = self.args.iter().map(|arg| ident(&arg.name));
531        let args = self.args.iter().map(|arg| arg.gen_recv());
532
533        let function = ident(&self.name);
534
535        quote! {
536            #opcode => {
537                //parse args
538                #(#args)*
539
540                // call function
541                state.#function(wire_msg.sender_id(), #(#arg_idents),*);
542                Ok(())
543            },
544        }
545    }
546}
547
548#[derive(Debug)]
549struct EnumEntry {
550    name: String,
551    summary: Option<String>,
552    description: Option<String>,
553    value: u32,
554}
555
556impl EnumEntry {
557    fn new(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> Self {
558        let mut name = "".to_string();
559        let mut summary = None;
560        let mut description = None;
561        let mut value = None;
562
563        for attr in attrs.flatten() {
564            match attr.key.as_ref() {
565                b"name" => name = name_to_pascal_case(&attr.unescape_value().unwrap()),
566                b"value" => {
567                    let s = attr.unescape_value().unwrap();
568                    value = if let Some(s) = s.as_ref().strip_prefix("0x") {
569                        Some(u32::from_str_radix(s, 16).unwrap())
570                    } else {
571                        Some(s.as_ref().parse::<u32>().unwrap())
572                    };
573                }
574                b"since" => continue, // do not do anything for now
575                b"summary" => summary = Some(attr.unescape_value().unwrap().to_string()),
576                e => panic!(
577                    "unrecognized attribute in interface: {}",
578                    std::str::from_utf8(e).unwrap()
579                ),
580            }
581        }
582        let mut buf = Vec::new();
583        loop {
584            match reader.read_event_into(&mut buf) {
585                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
586                Ok(Ev::Start(e)) => match e.name().as_ref() {
587                    b"description" => {
588                        let parsed = parse_description(reader, e.attributes());
589                        summary = Some(parsed.0);
590                        description = Some(parsed.1);
591                    }
592                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
593                },
594                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
595                Ok(Ev::End(end)) => match end.name().as_ref() {
596                    b"entry" => break,
597                    b"description" => continue,
598                    _ => panic!("bad xml file: {end:?} at {}", reader.buffer_position()),
599                },
600                e => panic!("unprocessed xml event: {e:?}"),
601            }
602        }
603
604        assert!(!name.is_empty());
605        assert!(value.is_some());
606
607        Self {
608            name,
609            summary,
610            description,
611            value: value.unwrap(),
612        }
613    }
614    fn new_from_empty_tag(attrs: Attributes) -> Self {
615        let mut name = "".to_string();
616        let mut summary = None;
617        let mut value = None;
618
619        for attr in attrs.flatten() {
620            match attr.key.as_ref() {
621                b"name" => name = attr.unescape_value().unwrap().to_string(),
622                b"value" => {
623                    let s = attr.unescape_value().unwrap();
624                    value = if let Some(s) = s.as_ref().strip_prefix("0x") {
625                        Some(u32::from_str_radix(s, 16).unwrap())
626                    } else {
627                        Some(s.as_ref().parse::<u32>().unwrap())
628                    };
629                }
630                b"since" => continue, // do not do anything for now
631                b"summary" => summary = Some(attr.unescape_value().unwrap().to_string()),
632                e => panic!(
633                    "unrecognized attribute in interface: {}",
634                    std::str::from_utf8(e).unwrap()
635                ),
636            }
637        }
638
639        assert!(!name.is_empty());
640        assert!(value.is_some());
641
642        Self {
643            name,
644            summary,
645            description: None,
646            value: value.unwrap(),
647        }
648    }
649
650    fn generate(&self) -> TokenStream {
651        let name = ident(&self.name);
652
653        let mut doc = self.summary.as_ref().cloned().unwrap_or_default();
654        if let Some(desc) = self.description.as_ref() {
655            for line in desc.lines() {
656                doc.push('\n');
657                doc += line.trim();
658            }
659        }
660
661        let value = self.value;
662
663        quote! {
664            #[doc = #doc]
665            #name = #value
666        }
667    }
668
669    fn generate_as_consts(&self) -> TokenStream {
670        let name = ident(&self.name);
671
672        let mut doc = self.summary.as_ref().cloned().unwrap_or_default();
673        if let Some(desc) = self.description.as_ref() {
674            for line in desc.lines() {
675                doc.push('\n');
676                doc += line.trim();
677            }
678        }
679
680        let value = self.value;
681
682        quote! {
683            #[doc = #doc]
684            const #name = #value
685        }
686    }
687}
688
689#[derive(Debug)]
690struct Enum {
691    name: String,
692    description: Option<String>,
693    summary: Option<String>,
694    is_bitfield: bool,
695    variants: Vec<EnumEntry>,
696}
697
698impl Enum {
699    fn new(reader: &mut Reader<BufReader<File>>, attrs: Attributes) -> Self {
700        let mut is_bitfield = false;
701
702        let name = {
703            let mut name = "".to_string();
704            for attr in attrs.flatten() {
705                match attr.key.as_ref() {
706                    b"name" => name = name_to_pascal_case(&attr.unescape_value().unwrap()),
707                    b"since" => continue, // do not do anything for now
708                    b"bitfield" => is_bitfield = attr.unescape_value().unwrap().as_ref().eq("true"),
709                    e => panic!("unrecognized attribute in interface: {e:?}"),
710                }
711            }
712            assert!(!name.is_empty());
713            name
714        };
715
716        let mut summary = None;
717        let mut description = None;
718        let mut variants = Vec::new();
719
720        let mut buf = Vec::new();
721        loop {
722            match reader.read_event_into(&mut buf) {
723                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
724                Ok(Ev::Start(e)) => match e.name().as_ref() {
725                    b"description" => {
726                        let parsed = parse_description(reader, e.attributes());
727                        summary = Some(parsed.0);
728                        description = Some(parsed.1);
729                    }
730                    b"entry" => variants.push(EnumEntry::new(reader, e.attributes())),
731                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
732                },
733                Ok(Ev::Empty(e)) => match e.name().as_ref() {
734                    b"entry" => variants.push(EnumEntry::new_from_empty_tag(e.attributes())),
735                    e => panic!("unrecognized tag: {:?}", std::str::from_utf8(e)),
736                },
737                Ok(Ev::Text(_)) | Ok(Ev::Comment(_)) => continue,
738                Ok(Ev::End(end)) => match end.name().as_ref() {
739                    b"description" => continue,
740                    b"enum" => break,
741                    _ => panic!("bad xml file: {end:?} at {}", reader.buffer_position()),
742                },
743                e => panic!("unprocessed xml event: {e:?}"),
744            }
745        }
746
747        if is_bitfield {
748            for var in variants.iter_mut() {
749                var.name = var.name.to_uppercase();
750            }
751        }
752
753        Self {
754            name,
755            is_bitfield,
756            summary,
757            description,
758            variants,
759        }
760    }
761
762    fn generate(self) -> TokenStream {
763        let name = ident(&self.name);
764        let name_str = &self.name;
765        let mut doc = String::new();
766        doc.push_str(&self.summary.unwrap_or_default());
767        if let Some(desc) = self.description {
768            for line in desc.lines() {
769                doc.push('\n');
770                doc += line.trim();
771            }
772        }
773        if self.is_bitfield {
774            let variants = self.variants.iter().map(|v| v.generate_as_consts());
775            quote! {
776                waybackend::bitflags::bitflags! {
777                    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
778                    #[doc = #doc]
779                    pub struct #name : u32 {
780                        #(#variants ;)*
781                        const _ = !0;
782                    }
783                }
784
785                impl std::convert::TryFrom<u32> for #name {
786                    type Error = waybackend::wire::Error;
787                    fn try_from(value: u32) -> Result<Self, Self::Error> {
788                        Ok(Self::from_bits_retain(value))
789                    }
790                }
791
792                impl std::convert::From<#name> for u32 {
793                    fn from(value: #name) -> u32 {
794                        value.bits()
795                    }
796                }
797            }
798        } else {
799            let variants = self.variants.iter().map(|v| v.generate());
800            let variants2 = self.variants.iter().map(|v| ident(&v.name));
801            let variants3 = self.variants.iter().map(|v| ident(&v.name));
802            quote! {
803                #[doc = #doc]
804                #[repr(u32)]
805                #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
806                pub enum #name {
807                    #(#variants,)*
808                }
809
810                impl std::convert::TryFrom<u32> for #name {
811                    type Error = waybackend::wire::Error;
812
813                    #[allow(non_upper_case_globals)]
814                    fn try_from(value: u32) -> Result<Self, Self::Error> {
815                        #(const #variants2: u32 = #name::#variants2 as u32;)*
816                        match value {
817                            #(#variants3 => Ok(Self::#variants3),)*
818                            otherwise => Err(waybackend::wire::Error::InvalidEnumDiscriminant((#name_str, otherwise))),
819                        }
820                    }
821                }
822
823                impl std::convert::From<#name> for u32 {
824                    fn from(value: #name) -> u32 {
825                        value as u32
826                    }
827                }
828            }
829        }
830    }
831}
832
833#[derive(Debug)]
834struct Arg {
835    name: String,
836    arg_type: ArgType,
837    #[allow(unused)]
838    summary: String,
839    interface: Option<String>,
840    r#enum: Option<String>,
841    allow_null: bool,
842}
843
844#[derive(Debug, PartialEq, Eq)]
845enum ArgType {
846    Uint,
847    Int,
848    Fixed,
849    String,
850    Object,
851    NewId,
852    Array,
853    Fd,
854}
855
856impl ArgType {
857    fn from_raw(raw: &str) -> Self {
858        match raw {
859            "uint" => Self::Uint,
860            "int" => Self::Int,
861            "fixed" => Self::Fixed,
862            "string" => Self::String,
863            "object" => Self::Object,
864            "new_id" => Self::NewId,
865            "array" => Self::Array,
866            "fd" => Self::Fd,
867            e => panic!("unrecognized type: {e}"),
868        }
869    }
870}
871
872impl Arg {
873    fn new(attrs: Attributes) -> Self {
874        let mut name = "".to_string();
875        let mut summary = "".to_string();
876        let mut arg_type = None;
877        let mut interface = None;
878        let mut r#enum = None;
879        let mut allow_null = false;
880        for attr in attrs.flatten() {
881            match attr.key.as_ref() {
882                b"name" => name = attr.unescape_value().unwrap().to_string(),
883                b"summary" => summary = attr.unescape_value().unwrap().to_string(),
884                b"type" => arg_type = Some(ArgType::from_raw(&attr.unescape_value().unwrap())),
885                b"interface" => interface = Some(attr.unescape_value().unwrap().to_string()),
886                b"enum" => r#enum = Some(attr.unescape_value().unwrap().to_string()),
887                b"allow-null" => allow_null = attr.unescape_value().unwrap().eq("true"),
888                e => panic!(
889                    "unrecognized attribute in interface: {}",
890                    std::str::from_utf8(e).unwrap()
891                ),
892            }
893        }
894
895        assert!(!name.is_empty());
896        assert!(arg_type.is_some());
897
898        Self {
899            name,
900            arg_type: arg_type.unwrap(),
901            summary,
902            interface,
903            r#enum,
904            allow_null,
905        }
906    }
907
908    fn gen_enum_type(&self, e: &str) -> TokenStream {
909        let mut t = TokenStream::new();
910        let mut i = 0;
911        while let Some(j) = e[i..].find(".") {
912            let id = ident(&e[i..i + j]);
913            t = if t.is_empty() {
914                quote! {#id}
915            } else {
916                quote! {#t :: #id}
917            };
918            i += j + 1;
919        }
920        let id = ident(&name_to_pascal_case(&e[i..]));
921        if t.is_empty() {
922            quote! {#id}
923        } else {
924            quote! {#t :: #id}
925        }
926    }
927
928    fn gen_recv(&self) -> TokenStream {
929        let name = ident(&self.name);
930
931        if let Some(e) = &self.r#enum {
932            let enum_ident = self.gen_enum_type(e);
933            return quote! { let #name: #enum_ident = wire_msg.next_u32().try_into()?; };
934        }
935
936        match self.arg_type {
937            ArgType::Uint => quote! { let #name = wire_msg.next_u32(); },
938            ArgType::Int => quote! { let #name = wire_msg.next_i32(); },
939            ArgType::Fixed => quote! { let #name = wire_msg.next_fixed(); },
940            ArgType::String => quote! { let #name = wire_msg.next_string()?; },
941            ArgType::Object => {
942                if self.allow_null {
943                    quote! { let #name = wire_msg.next_object(); }
944                } else {
945                    quote! { let #name = wire_msg.next_object().ok_or(waybackend::wire::Error::NullObjectId)?; }
946                }
947            }
948            ArgType::NewId => {
949                if self.interface.is_some() {
950                    quote! { let #name = wire_msg.next_new_specified_id()?; }
951                } else {
952                    quote! { let #name = wire_msg.next_new_unspecified_id()?; }
953                }
954            }
955            ArgType::Array => quote! { let #name = wire_msg.next_array(); },
956            ArgType::Fd => quote! { let #name = wire_msg.next_fd(); let #name = &#name; },
957        }
958    }
959
960    fn gen_fn_args(&self) -> TokenStream {
961        let name = ident(&self.name);
962
963        if let Some(e) = &self.r#enum {
964            let enum_ident = self.gen_enum_type(e);
965            return quote! { #name: #enum_ident };
966        }
967
968        match self.arg_type {
969            ArgType::Uint => quote! {  #name: u32 },
970            ArgType::Int => quote! {  #name: i32 },
971            ArgType::Fixed => quote! {  #name: waybackend::types::WlFixed },
972            ArgType::String => quote! {  #name: &str },
973            ArgType::Object => {
974                if self.allow_null {
975                    quote! {  #name: Option<waybackend::types::ObjectId> }
976                } else {
977                    quote! {  #name: waybackend::types::ObjectId }
978                }
979            }
980            ArgType::NewId => {
981                if self.interface.is_some() {
982                    quote! {  #name: waybackend::types::ObjectId }
983                } else {
984                    quote! {  #name: waybackend::types::ObjectId, interface: &str, version: u32 }
985                }
986            }
987            ArgType::Array => quote! {  #name: &[u8] },
988            ArgType::Fd => quote! { #name: &impl waybackend::rustix::fd::AsRawFd },
989        }
990    }
991
992    fn gen_builder(&self) -> TokenStream {
993        let name = ident(&self.name);
994
995        if self.r#enum.is_some() {
996            return quote! { wire_msg_builder.add_u32(wayland_fd, #name.into())?; };
997        }
998
999        match self.arg_type {
1000            ArgType::Uint => quote! { wire_msg_builder.add_u32(wayland_fd, #name)?; },
1001            ArgType::Int => quote! { wire_msg_builder.add_i32(wayland_fd, #name)?; },
1002            ArgType::Fixed => quote! {  wire_msg_builder.add_fixed(wayland_fd, #name)?;  },
1003            ArgType::String => quote! { wire_msg_builder.add_string(wayland_fd, #name)?; },
1004            ArgType::Object => {
1005                if self.allow_null {
1006                    quote! { wire_msg_builder.add_object(wayland_fd, #name)?; }
1007                } else {
1008                    quote! { wire_msg_builder.add_object(wayland_fd, Some(#name))?; }
1009                }
1010            }
1011            ArgType::NewId => {
1012                if self.interface.is_some() {
1013                    quote! { wire_msg_builder.add_new_specified_id(wayland_fd, #name)?; }
1014                } else {
1015                    quote! { wire_msg_builder.add_new_unspecified_id(wayland_fd, #name, interface, version)?; }
1016                }
1017            }
1018            ArgType::Array => quote! {  wire_msg_builder.add_array(wayland_fd, #name)?; },
1019            ArgType::Fd => quote! {   wire_msg_builder.add_fd(wayland_fd, #name)?; },
1020        }
1021    }
1022}
1023
1024fn ident(name: &str) -> proc_macro2::Ident {
1025    if name == "state" {
1026        return proc_macro2::Ident::new("_state", proc_macro2::Span::mixed_site());
1027    }
1028    syn::parse_str(name).unwrap_or_else(|_| syn::parse_str(&format!("_{name}")).unwrap())
1029}
1030
1031fn name_to_pascal_case(name: &str) -> String {
1032    let mut s = String::new();
1033    let mut capitalize = true;
1034    for ch in name.chars() {
1035        if capitalize {
1036            capitalize = false;
1037            s.extend(ch.to_uppercase());
1038        } else if ch == '_' || ch == '-' {
1039            capitalize = true;
1040        } else {
1041            s.push(ch);
1042        }
1043    }
1044    s
1045}
1046
1047#[cfg(feature = "build-script")]
1048/// Which Wayland protocol to generate
1049pub enum WaylandProtocol {
1050    /// Generate wayland-client bindinds.
1051    ///
1052    /// You will pretty much always want this
1053    Client,
1054    /// Generate bindings to a global wayland protocol that is expected to be found through
1055    /// `pkg-config`
1056    ///
1057    /// We will prepend `pkg-config`'s `wayland-prococol` variable value to this path. So, for
1058    /// example, if you want to use `xdg-output`, you would have to specify:
1059    /// `unstable/xdg-output/xdg-output-unstable-v1.xml`.
1060    System(std::path::PathBuf),
1061    /// Generate bindings to a local wayland protocol in a path relative to the build script's
1062    /// current working directory
1063    Local(std::path::PathBuf),
1064}
1065
1066#[cfg(feature = "build-script")]
1067/// Generate the wayland code for every protocol in `protocols`. The code will be outputed to
1068/// `out_file`.
1069///
1070/// NOTE: we currently panic if:
1071///   * the file was not found; or
1072///   * we failed to write to `out_file`
1073pub fn build_script_generate(protocols: &[WaylandProtocol], out_file: &std::fs::File) {
1074    use std::io::{BufWriter, Write};
1075    let mut writer = BufWriter::new(out_file);
1076
1077    let wayland_client = pkg_config::get_variable("wayland-client", "pkgdatadir")
1078        .expect("failed to find wayland-client protocol directory");
1079    let wayland_client = std::path::PathBuf::from(&wayland_client);
1080    let wayland_protocols = pkg_config::get_variable("wayland-protocols", "pkgdatadir")
1081        .expect("failed to find wayland-protocols directory");
1082    let wayland_protocols = std::path::PathBuf::from(&wayland_protocols);
1083    let cwd = std::env::current_dir().expect("failed to get current working directory");
1084
1085    for protocol in protocols {
1086        let path = match protocol {
1087            WaylandProtocol::Client => wayland_client.join("wayland.xml"),
1088            WaylandProtocol::System(path) => wayland_protocols.join(path),
1089            WaylandProtocol::Local(path) => cwd.join(path),
1090        };
1091
1092        if !path.is_file() {
1093            panic!("could not find wayland protocol file {}", path.display());
1094        }
1095
1096        let code = Protocol::new(&path).generate();
1097        println!("cargo::rerun-if-changed={}", path.display());
1098        writeln!(writer, "{code}").unwrap();
1099    }
1100}