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