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