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 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 let _ = reader.read_event_into(&mut buf);
52 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
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 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
295fn 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, 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, 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 #(#args)*
533
534 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, 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, 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, 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")]
1048pub enum WaylandProtocol {
1050 Client,
1054 System(std::path::PathBuf),
1061 Local(std::path::PathBuf),
1064}
1065
1066#[cfg(feature = "build-script")]
1067pub 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 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}