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