fix(deps): backport secure Wayland XML parser

This commit is contained in:
2026-07-10 00:37:49 -04:00
parent fcc8443c45
commit 670f7abe60
24 changed files with 6886 additions and 5 deletions
+67
View File
@@ -0,0 +1,67 @@
# CHANGELOG: wayland-scanner
## Unreleased
## 0.31.8 -- 2025-12-30
- Update `quick-xml` to 0.38
- Generate bindings for `WlRegistry`
* Needed for client/server to use `wl_fixes`
## 0.31.7 -- 2025-07-28
- Update `quick-xml` to 0.37
## 0.31.5 -- 2024-09-04
- Update `quick-xml` to 0.36
- Allow `<!DOCTYPE>` in XML input
## 0.31.3 -- 2024-07-03
- Update `quick-xml` to 0.34
## 0.31.2 -- 2024-05-30
- Use wrapper type implementing `Sync` instead of `static mut`s.
- Add headerless xml file parsing possibility for `parse` function.
## 0.31.1 -- 2024-01-29
- Include an `std::convert::Infallible` in hidden `__phantom_lifetime` enum variants,
so they're explicitly unconstructable.
## 0.31.0 -- 2023-09-02
#### Breaking changes
- Bump bitflags to 2.0
- Remove `io-lifetimes` from the generated code following wayland-backend 0.3 dropping it.
- Generate `BorrowedFd<'_>` arguments instead of `RawFd`
# 0.30.1 -- 2023-06-17
- Generated protocol object types now implement `Borrow<ObjectId>` and `Hash`
## 0.30.0 -- 2022-12-27
## 0.30.0-beta.10
#### Additions
- An `opcode()` method is added to message enums to retrive the opcode associated with a message variant.
## 0.30.0-beta.9
- Migrate from xml-rs to quick-xml
## 0.30.0-beta.6
- Generated enums now derive `Ord` and `Hash`.
- The scanner now generates constants for the opcode values of the protocol messages.
## 0.30.0-alpha1
Full rework of the crate together of the reworks of `wayland-client` and `wayland-server`.
The code generation is now achieved using procedural macros rather than build scripts.
+60
View File
@@ -0,0 +1,60 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.71"
name = "wayland-scanner"
version = "0.31.10"
authors = ["Elinor Berger <elinor@safaradeg.net>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Wayland Scanner for generating rust APIs from XML wayland protocol files."
documentation = "https://docs.rs/wayland-scanner/"
readme = "README.md"
keywords = [
"wayland",
"codegen",
]
categories = [
"gui",
"api-bindings",
]
license = "MIT"
repository = "https://github.com/smithay/wayland-rs"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[lib]
name = "wayland_scanner"
path = "src/lib.rs"
proc-macro = true
[dependencies.proc-macro2]
version = "1.0.11"
[dependencies.quick-xml]
version = "0.41"
[dependencies.quote]
version = "1.0"
[dev-dependencies.similar]
version = "2"
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "wayland-scanner"
version = "0.31.10"
authors = ["Elinor Berger <elinor@safaradeg.net>"]
repository = "https://github.com/smithay/wayland-rs"
documentation = "https://docs.rs/wayland-scanner/"
description = "Wayland Scanner for generating rust APIs from XML wayland protocol files."
license = "MIT"
categories = ["gui", "api-bindings"]
keywords = ["wayland", "codegen"]
edition = "2021"
rust-version = "1.71"
readme = "README.md"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.11"
quote = "1.0"
quick-xml = "0.41"
[dev-dependencies]
similar = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+16
View File
@@ -0,0 +1,16 @@
# ELY security backport
Source: `wayland-scanner` 0.31.10 from crates.io, archive SHA-256
`9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a`, published under the MIT license.
ELY carries the following changes while the upstream security release remains unpublished:
- Upgrade `quick-xml` from 0.39 to 0.41 for RUSTSEC-2026-0194 and RUSTSEC-2026-0195.
- Use `GeneralRef::xml10_content()` for the quick-xml 0.41 API.
- Split message parsing and writing generation into a dedicated module so production source files remain below 500 lines.
Upstream references:
- Security dependency update: `d07c4f91f28b42e5a485823ffd9d8d5a210b1053`.
- quick-xml API migration: `ec2d932855593d48aa83c76820f3efbcfea86d39`.
- Equivalent boolean attribute parsing cleanup: `006b508ee49669fc42031a150ea9b316297e9cf8`.
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2015 Elinor Berger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+13
View File
@@ -0,0 +1,13 @@
[![crates.io](https://img.shields.io/crates/v/wayland-scanner.svg)](https://crates.io/crates/wayland-scanner)
[![docs.rs](https://docs.rs/wayland-scanner/badge.svg)](https://docs.rs/wayland-scanner)
[![Continuous Integration](https://github.com/Smithay/wayland-rs/workflows/Continuous%20Integration/badge.svg)](https://github.com/Smithay/wayland-rs/actions?query=workflow%3A%22Continuous+Integration%22)
[![codecov](https://codecov.io/gh/Smithay/wayland-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/Smithay/wayland-rs)
# wayland-scanner
This crate provides procedural macros for generating the rust code associated with a
Wayland XML protocol specification, for use with the `wayland-client`, `wayland-server`
and `wayland-backend` crates.
Before trying to use this crate, you may check if the protocol extension you want to use
is not already exposed in the `wayland-protocols` crate.
+172
View File
@@ -0,0 +1,172 @@
use std::cmp;
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote};
use crate::protocol::{Interface, Message, Protocol, Type};
pub(crate) fn generate_interfaces_prefix(protocol: &Protocol) -> TokenStream {
let longest_nulls = protocol.interfaces.iter().fold(0, |max, interface| {
let request_longest_null = interface.requests.iter().fold(0, |max, request| {
if request.all_null() {
cmp::max(request.args.len(), max)
} else {
max
}
});
let events_longest_null = interface.events.iter().fold(0, |max, event| {
if event.all_null() {
cmp::max(event.args.len(), max)
} else {
max
}
});
cmp::max(max, cmp::max(request_longest_null, events_longest_null))
});
let types_null_len = Literal::usize_unsuffixed(longest_nulls);
quote! {
use std::ptr::null;
struct SyncWrapper<T>(T);
unsafe impl<T> Sync for SyncWrapper<T> {}
static types_null: SyncWrapper<[*const wayland_backend::protocol::wl_interface; #types_null_len]> = SyncWrapper([
null::<wayland_backend::protocol::wl_interface>(); #types_null_len
]);
}
}
pub(crate) fn generate_interface(interface: &Interface) -> TokenStream {
let requests = gen_messages(interface, &interface.requests, "requests");
let events = gen_messages(interface, &interface.events, "events");
let interface_ident = format_ident!("{}_interface", interface.name);
let name_value = null_terminated_byte_string_literal(&interface.name);
let version_value = Literal::i32_unsuffixed(interface.version as i32);
let request_count_value = Literal::i32_unsuffixed(interface.requests.len() as i32);
let requests_value = if interface.requests.is_empty() {
quote! { null::<wayland_backend::protocol::wl_message>() }
} else {
let requests_ident = format_ident!("{}_requests", interface.name);
quote! { #requests_ident.0.as_ptr() }
};
let event_count_value = Literal::i32_unsuffixed(interface.events.len() as i32);
let events_value = if interface.events.is_empty() {
quote! { null::<wayland_backend::protocol::wl_message>() }
} else {
let events_ident = format_ident!("{}_events", interface.name);
quote! { #events_ident.0.as_ptr() }
};
quote! {
#requests
#events
pub static #interface_ident: wayland_backend::protocol::wl_interface = wayland_backend::protocol::wl_interface {
name: #name_value as *const u8 as *const std::os::raw::c_char,
version: #version_value,
request_count: #request_count_value,
requests: #requests_value,
event_count: #event_count_value,
events: #events_value,
};
}
}
fn gen_messages(interface: &Interface, messages: &[Message], which: &str) -> TokenStream {
if messages.is_empty() {
return TokenStream::new();
}
let types_arrays = messages.iter().filter_map(|msg| {
if msg.all_null() {
None
} else {
let array_ident = format_ident!("{}_{}_{}_types", interface.name, which, msg.name);
let array_len = Literal::usize_unsuffixed(msg.args.len());
let array_values = msg.args.iter().map(|arg| match (arg.typ, &arg.interface) {
(Type::Object, &Some(ref inter)) | (Type::NewId, &Some(ref inter)) => {
let interface_ident =format_ident!("{}_interface", inter);
quote! { &#interface_ident as *const wayland_backend::protocol::wl_interface }
}
_ => quote! { null::<wayland_backend::protocol::wl_interface>() },
});
Some(quote! {
static #array_ident: SyncWrapper<[*const wayland_backend::protocol::wl_interface; #array_len]> = SyncWrapper([
#(#array_values,)*
]);
})
}
});
let message_array_ident = format_ident!("{}_{}", interface.name, which);
let message_array_len = Literal::usize_unsuffixed(messages.len());
let message_array_values = messages.iter().map(|msg| {
let name_value = null_terminated_byte_string_literal(&msg.name);
let signature_value = Literal::byte_string(&message_signature(msg));
let types_ident = if msg.all_null() {
format_ident!("types_null")
} else {
format_ident!("{}_{}_{}_types", interface.name, which, msg.name)
};
quote! {
wayland_backend::protocol::wl_message {
name: #name_value as *const u8 as *const std::os::raw::c_char,
signature: #signature_value as *const u8 as *const std::os::raw::c_char,
types: #types_ident.0.as_ptr(),
}
}
});
quote! {
#(#types_arrays)*
static #message_array_ident: SyncWrapper<[wayland_backend::protocol::wl_message; #message_array_len]> = SyncWrapper([
#(#message_array_values,)*
]);
}
}
fn message_signature(msg: &Message) -> Vec<u8> {
let mut res = Vec::new();
if msg.since > 1 {
res.extend_from_slice(msg.since.to_string().as_bytes());
}
for arg in &msg.args {
if arg.typ.nullable() && arg.allow_null {
res.push(b'?');
}
match arg.typ {
Type::NewId => {
if arg.interface.is_none() {
res.extend_from_slice(b"su");
}
res.push(b'n');
}
Type::Uint => res.push(b'u'),
Type::Fixed => res.push(b'f'),
Type::String => res.push(b's'),
Type::Object => res.push(b'o'),
Type::Array => res.push(b'a'),
Type::Fd => res.push(b'h'),
Type::Int => res.push(b'i'),
_ => {}
}
}
res.push(0);
res
}
pub fn null_terminated_byte_string_literal(string: &str) -> Literal {
let mut val = Vec::with_capacity(string.len() + 1);
val.extend_from_slice(string.as_bytes());
val.push(0);
Literal::byte_string(&val)
}
+337
View File
@@ -0,0 +1,337 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use crate::{
protocol::{Interface, Protocol, Type},
util::{description_to_doc_attr, dotted_to_relname, is_keyword, snake_to_camel, to_doc_attr},
Side,
};
pub fn generate_client_objects(protocol: &Protocol) -> TokenStream {
protocol.interfaces.iter().map(generate_objects_for).collect()
}
fn generate_objects_for(interface: &Interface) -> TokenStream {
let mod_name = Ident::new(&interface.name, Span::call_site());
let mod_doc = interface.description.as_ref().map(description_to_doc_attr);
let iface_name = Ident::new(&snake_to_camel(&interface.name), Span::call_site());
let iface_const_name = format_ident!("{}_INTERFACE", interface.name.to_ascii_uppercase());
let enums = crate::common::generate_enums_for(interface);
let sinces = crate::common::gen_msg_constants(&interface.requests, &interface.events);
let requests = crate::common::gen_message_enum(
&format_ident!("Request"),
Side::Client,
false,
&interface.requests,
);
let events = crate::common::gen_message_enum(
&format_ident!("Event"),
Side::Client,
true,
&interface.events,
);
let parse_body = crate::message_io::gen_parse_body(interface, Side::Client);
let write_body = crate::message_io::gen_write_body(interface, Side::Client);
let methods = gen_methods(interface);
let event_ref = if interface.events.is_empty() {
"This interface has no events."
} else {
"See also the [Event] enum for this interface."
};
let docs = match &interface.description {
Some((short, long)) => format!("{short}\n\n{long}\n\n{event_ref}"),
None => format!("{}\n\n{}", interface.name, event_ref),
};
let doc_attr = to_doc_attr(&docs);
quote! {
#mod_doc
pub mod #mod_name {
use std::sync::Arc;
use std::os::unix::io::OwnedFd;
use super::wayland_client::{
backend::{
Backend, WeakBackend, smallvec, ObjectData, ObjectId, InvalidId,
protocol::{WEnum, Argument, Message, Interface, same_interface}
},
QueueProxyData, Proxy, Connection, Dispatch, QueueHandle, DispatchError, Weak,
};
#enums
#sinces
#requests
#events
#doc_attr
#[derive(Debug, Clone)]
pub struct #iface_name {
id: ObjectId,
version: u32,
data: Option<Arc<dyn ObjectData>>,
backend: WeakBackend,
}
impl std::cmp::PartialEq for #iface_name {
fn eq(&self, other: &#iface_name) -> bool {
self.id == other.id
}
}
impl std::cmp::Eq for #iface_name {}
impl PartialEq<Weak<#iface_name>> for #iface_name {
fn eq(&self, other: &Weak<#iface_name>) -> bool {
self.id == other.id()
}
}
impl std::borrow::Borrow<ObjectId> for #iface_name {
fn borrow(&self) -> &ObjectId {
&self.id
}
}
impl std::hash::Hash for #iface_name {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl super::wayland_client::Proxy for #iface_name {
type Request<'request> = Request<'request>;
type Event = Event;
#[inline]
fn interface() -> &'static Interface{
&super::#iface_const_name
}
#[inline]
fn id(&self) -> ObjectId {
self.id.clone()
}
#[inline]
fn version(&self) -> u32 {
self.version
}
#[inline]
fn data<U: Send + Sync + 'static>(&self) -> Option<&U> {
self.data.as_ref().and_then(|arc| arc.data_as_any().downcast_ref::<U>())
}
fn object_data(&self) -> Option<&Arc<dyn ObjectData>> {
self.data.as_ref()
}
fn backend(&self) -> &WeakBackend {
&self.backend
}
fn send_request(&self, req: Self::Request<'_>) -> Result<(), InvalidId> {
let conn = Connection::from_backend(self.backend.upgrade().ok_or(InvalidId)?);
let id = conn.send_request(self, req, None)?;
debug_assert!(id.is_null());
Ok(())
}
fn send_constructor<I: Proxy>(&self, req: Self::Request<'_>, data: Arc<dyn ObjectData>) -> Result<I, InvalidId> {
let conn = Connection::from_backend(self.backend.upgrade().ok_or(InvalidId)?);
let id = conn.send_request(self, req, Some(data))?;
Proxy::from_id(&conn, id)
}
#[inline]
fn from_id(conn: &Connection, id: ObjectId) -> Result<Self, InvalidId> {
if !same_interface(id.interface(), Self::interface()) && !id.is_null() {
return Err(InvalidId);
}
let version = conn.object_info(id.clone()).map(|info| info.version).unwrap_or(0);
let data = conn.get_object_data(id.clone()).ok();
let backend = conn.backend().downgrade();
Ok(#iface_name { id, data, version, backend })
}
#[inline]
fn inert(backend: WeakBackend) -> Self {
#iface_name { id: ObjectId::null(), data: None, version: 0, backend }
}
fn parse_event(conn: &Connection, msg: Message<ObjectId, OwnedFd>) -> Result<(Self, Self::Event), DispatchError> {
#parse_body
}
fn write_request<'a>(&self, conn: &Connection, msg: Self::Request<'a>) -> Result<(Message<ObjectId, std::os::unix::io::BorrowedFd<'a>>, Option<(&'static Interface, u32)>), InvalidId> {
#write_body
}
}
impl #iface_name {
#methods
}
}
}
}
fn gen_methods(interface: &Interface) -> TokenStream {
interface.requests.iter().map(|request| {
let created_interface = request.args.iter().find(|arg| arg.typ == Type::NewId).map(|arg| &arg.interface);
let method_name = format_ident!("{}{}", if is_keyword(&request.name) { "_" } else { "" }, request.name);
let enum_variant = Ident::new(&snake_to_camel(&request.name), Span::call_site());
let fn_args = request.args.iter().flat_map(|arg| {
if arg.typ == Type::NewId {
if arg.interface.is_none() {
// the new_id argument of a bind-like method
// it shoudl expand as a (interface, type) tuple, but the type is already handled by
// the prototype type parameter, so just put a version here
return Some(quote! { version: u32 });
} else {
// this is a regular new_id, skip it
return None;
}
}
let arg_name = format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
let arg_type = if let Some(ref enu) = arg.enum_ {
let enum_type = dotted_to_relname(enu);
quote! { #enum_type }
} else {
match arg.typ {
Type::Uint => quote! { u32 },
Type::Int => quote! { i32 },
Type::Fixed => quote! { f64 },
Type::String => if arg.allow_null { quote!{ Option<String> } } else { quote!{ String } },
Type::Array => if arg.allow_null { quote!{ Option<Vec<u8>> } } else { quote!{ Vec<u8> } },
Type::Fd => quote! { ::std::os::unix::io::BorrowedFd<'_> },
Type::Object => {
let iface = arg.interface.as_ref().unwrap();
let iface_mod = Ident::new(iface, Span::call_site());
let iface_type =
Ident::new(&snake_to_camel(iface), Span::call_site());
if arg.allow_null { quote! { Option<&super::#iface_mod::#iface_type> } } else { quote! { &super::#iface_mod::#iface_type } }
},
Type::NewId => unreachable!(),
Type::Destructor => panic!("An argument cannot have type \"destructor\"."),
}
};
Some(quote! {
#arg_name: #arg_type
})
});
let enum_args = request.args.iter().flat_map(|arg| {
let arg_name = format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
if arg.enum_.is_some() {
Some(quote! { #arg_name: WEnum::Value(#arg_name) })
} else if arg.typ == Type::NewId {
if arg.interface.is_none() {
Some(quote! { #arg_name: (I::interface(), version) })
} else {
None
}
} else if arg.typ == Type::Object {
if arg.allow_null {
Some(quote! { #arg_name: #arg_name.cloned() })
} else {
Some(quote! { #arg_name: #arg_name.clone() })
}
} else {
Some(quote! { #arg_name })
}
});
let doc_attr = request
.description
.as_ref()
.map(description_to_doc_attr);
match created_interface {
Some(Some(ref created_interface)) => {
// a regular creating request
let created_iface_mod = Ident::new(created_interface, Span::call_site());
let created_iface_type = Ident::new(&snake_to_camel(created_interface), Span::call_site());
quote! {
#doc_attr
#[allow(clippy::too_many_arguments)]
pub fn #method_name<U: Send + Sync + 'static, D: Dispatch<super::#created_iface_mod::#created_iface_type, U> + 'static>(&self, #(#fn_args,)* qh: &QueueHandle<D>, udata: U) -> super::#created_iface_mod::#created_iface_type {
self.send_constructor(
Request::#enum_variant {
#(#enum_args),*
},
qh.make_data::<super::#created_iface_mod::#created_iface_type, U>(udata),
).unwrap_or_else(|_| Proxy::inert(self.backend.clone()))
}
}
},
Some(None) => {
// a bind-like request
quote! {
#doc_attr
#[allow(clippy::too_many_arguments)]
pub fn #method_name<I: Proxy + 'static, U: Send + Sync + 'static, D: Dispatch<I, U> + 'static>(&self, #(#fn_args,)* qh: &QueueHandle<D>, udata: U) -> I {
self.send_constructor(
Request::#enum_variant {
#(#enum_args),*
},
qh.make_data::<I, U>(udata),
).unwrap_or_else(|_| Proxy::inert(self.backend.clone()))
}
}
},
None => {
// a non-creating request
quote! {
#doc_attr
#[allow(clippy::too_many_arguments)]
pub fn #method_name(&self, #(#fn_args),*) {
let backend = match self.backend.upgrade() {
Some(b) => b,
None => return,
};
let conn = Connection::from_backend(backend);
let _ = conn.send_request(
self,
Request::#enum_variant {
#(#enum_args),*
},
None
);
}
}
}
}
}).collect()
}
#[cfg(test)]
mod tests {
#[test]
fn client_gen() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let protocol_parsed = crate::parse::parse(protocol_file);
let generated: String = super::generate_client_objects(&protocol_parsed).to_string();
let generated = crate::format_rust_code(&generated);
let reference =
std::fs::read_to_string("./tests/scanner_assets/test-client-code.rs").unwrap();
let reference = crate::format_rust_code(&reference);
if reference != generated {
let diff = similar::TextDiff::from_lines(&reference, &generated);
print!("{}", diff.unified_diff().context_radius(10).header("reference", "generated"));
panic!("Generated does not match reference!")
}
}
}
+330
View File
@@ -0,0 +1,330 @@
use std::fmt::Write;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use crate::{protocol::*, util::*, Side};
pub(crate) fn generate_enums_for(interface: &Interface) -> TokenStream {
interface.enums.iter().map(ToTokens::into_token_stream).collect()
}
impl ToTokens for Enum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let enum_decl;
let enum_impl;
let doc_attr = self.description.as_ref().map(description_to_doc_attr);
let ident = Ident::new(&snake_to_camel(&self.name), Span::call_site());
if self.bitfield {
let entries = self.entries.iter().map(|entry| {
let doc_attr = entry
.description
.as_ref()
.map(description_to_doc_attr)
.or_else(|| entry.summary.as_ref().map(|s| to_doc_attr(s)));
let prefix = if entry.name.chars().next().unwrap().is_numeric() { "_" } else { "" };
let ident = format_ident!("{}{}", prefix, snake_to_camel(&entry.name));
let value = Literal::u32_unsuffixed(entry.value);
quote! {
#doc_attr
const #ident = #value;
}
});
enum_decl = quote! {
bitflags::bitflags! {
#doc_attr
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct #ident: u32 {
#(#entries)*
}
}
};
enum_impl = quote! {
impl std::convert::TryFrom<u32> for #ident {
type Error = ();
fn try_from(val: u32) -> Result<#ident, ()> {
#ident::from_bits(val).ok_or(())
}
}
impl std::convert::From<#ident> for u32 {
fn from(val: #ident) -> u32 {
val.bits()
}
}
};
} else {
let variants = self.entries.iter().map(|entry| {
let doc_attr = entry
.description
.as_ref()
.map(description_to_doc_attr)
.or_else(|| entry.summary.as_ref().map(|s| to_doc_attr(s)));
let prefix = if entry.name.chars().next().unwrap().is_numeric() { "_" } else { "" };
let variant = format_ident!("{}{}", prefix, snake_to_camel(&entry.name));
let value = Literal::u32_unsuffixed(entry.value);
quote! {
#doc_attr
#variant = #value
}
});
enum_decl = quote! {
#doc_attr
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum #ident {
#(#variants,)*
}
};
let match_arms = self.entries.iter().map(|entry| {
let value = Literal::u32_unsuffixed(entry.value);
let prefix = if entry.name.chars().next().unwrap().is_numeric() { "_" } else { "" };
let variant = format_ident!("{}{}", prefix, snake_to_camel(&entry.name));
quote! {
#value => Ok(#ident::#variant)
}
});
enum_impl = quote! {
impl std::convert::TryFrom<u32> for #ident {
type Error = ();
fn try_from(val: u32) -> Result<#ident, ()> {
match val {
#(#match_arms,)*
_ => Err(())
}
}
}
impl std::convert::From<#ident> for u32 {
fn from(val: #ident) -> u32 {
val as u32
}
}
};
}
enum_decl.to_tokens(tokens);
enum_impl.to_tokens(tokens);
}
}
pub(crate) fn gen_msg_constants(requests: &[Message], events: &[Message]) -> TokenStream {
let req_constants = requests.iter().enumerate().map(|(opcode, msg)| {
let since_cstname = format_ident!("REQ_{}_SINCE", msg.name.to_ascii_uppercase());
let opcode_cstname = format_ident!("REQ_{}_OPCODE", msg.name.to_ascii_uppercase());
let since = msg.since;
let opcode = opcode as u16;
quote! {
/// The minimal object version supporting this request
pub const #since_cstname: u32 = #since;
/// The wire opcode for this request
pub const #opcode_cstname: u16 = #opcode;
}
});
let evt_constants = events.iter().enumerate().map(|(opcode, msg)| {
let since_cstname = format_ident!("EVT_{}_SINCE", msg.name.to_ascii_uppercase());
let opcode_cstname = format_ident!("EVT_{}_OPCODE", msg.name.to_ascii_uppercase());
let since = msg.since;
let opcode = opcode as u16;
quote! {
/// The minimal object version supporting this event
pub const #since_cstname: u32 = #since;
/// The wire opcode for this event
pub const #opcode_cstname: u16 = #opcode;
}
});
quote! {
#(#req_constants)*
#(#evt_constants)*
}
}
pub(crate) fn gen_message_enum(
name: &Ident,
side: Side,
receiver: bool,
messages: &[Message],
) -> TokenStream {
let variants = messages
.iter()
.map(|msg| {
let mut docs = String::new();
if let Some((ref short, ref long)) = msg.description {
write!(docs, "{}\n\n{}\n", short, long.trim()).unwrap();
}
if let Some(Type::Destructor) = msg.typ {
write!(
docs,
"\nThis is a destructor, once {} this object cannot be used any longer.",
if receiver { "received" } else { "sent" },
)
.unwrap()
}
if msg.since > 1 {
write!(docs, "\nOnly available since version {} of the interface", msg.since)
.unwrap();
}
let doc_attr = to_doc_attr(&docs);
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let msg_variant_decl =
if msg.args.is_empty() {
msg_name.into_token_stream()
} else {
let fields = msg.args.iter().flat_map(|arg| {
let field_name =
format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
let field_type_inner = if let Some(ref enu) = arg.enum_ {
let enum_type = dotted_to_relname(enu);
quote! { WEnum<#enum_type> }
} else {
match arg.typ {
Type::Uint => quote! { u32 },
Type::Int => quote! { i32 },
Type::Fixed => quote! { f64 },
Type::String => quote! { String },
Type::Array => quote! { Vec<u8> },
Type::Fd => {
if receiver {
quote! { OwnedFd }
} else {
quote! { std::os::unix::io::BorrowedFd<'a> }
}
}
Type::Object => {
if let Some(ref iface) = arg.interface {
let iface_mod = Ident::new(iface, Span::call_site());
let iface_type =
Ident::new(&snake_to_camel(iface), Span::call_site());
quote! { super::#iface_mod::#iface_type }
} else if side == Side::Client {
quote! { super::wayland_client::ObjectId }
} else {
quote! { super::wayland_server::ObjectId }
}
}
Type::NewId if !receiver && side == Side::Client => {
// Client-side sending does not have a pre-existing object
// so skip serializing it
if arg.interface.is_some() {
return None;
} else {
quote! { (&'static Interface, u32) }
}
}
Type::NewId => {
if let Some(ref iface) = arg.interface {
let iface_mod = Ident::new(iface, Span::call_site());
let iface_type =
Ident::new(&snake_to_camel(iface), Span::call_site());
if receiver && side == Side::Server {
quote! { New<super::#iface_mod::#iface_type> }
} else {
quote! { super::#iface_mod::#iface_type }
}
} else {
// bind-like function
if side == Side::Client {
quote! { (String, u32, super::wayland_client::ObjectId) }
} else {
quote! { (String, u32, super::wayland_server::ObjectId) }
}
}
}
Type::Destructor => panic!("An argument cannot have type \"destructor\"."),
}
};
let field_type = if arg.allow_null {
quote! { Option<#field_type_inner> }
} else {
field_type_inner.into_token_stream()
};
let doc_attr = arg
.description
.as_ref()
.map(description_to_doc_attr)
.or_else(|| arg.summary.as_ref().map(|s| to_doc_attr(s)));
Some(quote! {
#doc_attr
#field_name: #field_type
})
});
quote! {
#msg_name {
#(#fields,)*
}
}
};
quote! {
#doc_attr
#msg_variant_decl
}
})
.collect::<Vec<_>>();
let opcodes = messages.iter().enumerate().map(|(opcode, msg)| {
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let opcode = opcode as u16;
if msg.args.is_empty() {
quote! {
#name::#msg_name => #opcode
}
} else {
quote! {
#name::#msg_name { .. } => #opcode
}
}
});
// Placeholder to allow generic argument to be added later, without ABI
// break.
// TODO Use never type.
let (generic, phantom_variant, phantom_case) = if !receiver {
(
quote! { 'a },
quote! { #[doc(hidden)] __phantom_lifetime { phantom: std::marker::PhantomData<&'a ()>, never: std::convert::Infallible } },
quote! { #name::__phantom_lifetime { never, .. } => match never {} },
)
} else {
(quote! {}, quote! {}, quote! {})
};
quote! {
#[derive(Debug)]
#[non_exhaustive]
pub enum #name<#generic> {
#(#variants,)*
#phantom_variant
}
impl<#generic> #name<#generic> {
#[doc="Get the opcode number of this message"]
pub fn opcode(&self) -> u16 {
match *self {
#(#opcodes,)*
#phantom_case
}
}
}
}
}
+142
View File
@@ -0,0 +1,142 @@
use proc_macro2::TokenStream;
use crate::protocol::{Interface, Message, Protocol, Type};
use quote::{format_ident, quote};
pub fn generate(protocol: &Protocol, with_c_interfaces: bool) -> TokenStream {
let interfaces =
protocol.interfaces.iter().map(|iface| generate_interface(iface, with_c_interfaces));
if with_c_interfaces {
let prefix = super::c_interfaces::generate_interfaces_prefix(protocol);
quote! {
#prefix
#(#interfaces)*
}
} else {
interfaces.collect()
}
}
pub(crate) fn generate_interface(interface: &Interface, with_c: bool) -> TokenStream {
let const_name = format_ident!("{}_INTERFACE", interface.name.to_ascii_uppercase());
let iface_name = &interface.name;
let iface_version = interface.version;
let requests = build_messagedesc_list(&interface.requests);
let events = build_messagedesc_list(&interface.events);
let c_name = format_ident!("{}_interface", interface.name);
if with_c {
let c_iface = super::c_interfaces::generate_interface(interface);
quote! {
pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
name: #iface_name,
version: #iface_version,
requests: #requests,
events: #events,
c_ptr: Some(unsafe { & #c_name }),
};
#c_iface
}
} else {
quote! {
pub static #const_name: wayland_backend::protocol::Interface = wayland_backend::protocol::Interface {
name: #iface_name,
version: #iface_version,
requests: #requests,
events: #events,
c_ptr: None,
};
}
}
}
fn build_messagedesc_list(list: &[Message]) -> TokenStream {
let desc_list = list.iter().map(|message| {
let name = &message.name;
let since = message.since;
let is_destructor = message.typ == Some(Type::Destructor);
let signature = message.args.iter().map(|arg| {
if arg.typ == Type::NewId && arg.interface.is_none() {
// this is a special generic message, it expands to multiple arguments
quote! {
wayland_backend::protocol::ArgumentType::Str(wayland_backend::protocol::AllowNull::No),
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::NewId
}
} else {
let typ = arg.typ.common_type();
if arg.typ.nullable() {
if arg.allow_null {
quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::Yes) }
} else {
quote! { wayland_backend::protocol::ArgumentType::#typ(wayland_backend::protocol::AllowNull::No) }
}
} else {
quote! { wayland_backend::protocol::ArgumentType::#typ }
}
}
});
let child_interface = match message
.args
.iter()
.find(|arg| arg.typ == Type::NewId)
.and_then(|arg| arg.interface.as_ref())
{
Some(name) => {
let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
quote! { Some(&#target_iface) }
}
None => quote! { None },
};
let arg_interfaces = message.args.iter().filter(|arg| arg.typ == Type::Object).map(|arg| {
match arg.interface {
Some(ref name) => {
let target_iface = format_ident!("{}_INTERFACE", name.to_ascii_uppercase());
quote! { &#target_iface }
}
None => {
quote! { &wayland_backend::protocol::ANONYMOUS_INTERFACE }
}
}
});
quote! {
wayland_backend::protocol::MessageDesc {
name: #name,
signature: &[ #(#signature),* ],
since: #since,
is_destructor: #is_destructor,
child_interface: #child_interface,
arg_interfaces: &[ #(#arg_interfaces),* ],
}
}
});
quote!(
&[ #(#desc_list),* ]
)
}
#[cfg(test)]
mod tests {
#[test]
fn interface_gen() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let protocol_parsed = crate::parse::parse(protocol_file);
let generated: String = super::generate(&protocol_parsed, true).to_string();
let generated = crate::format_rust_code(&generated);
let reference =
std::fs::read_to_string("./tests/scanner_assets/test-interfaces.rs").unwrap();
let reference = crate::format_rust_code(&reference);
if reference != generated {
let diff = similar::TextDiff::from_lines(&reference, &generated);
print!("{}", diff.unified_diff().context_radius(10).header("reference", "generated"));
panic!("Generated does not match reference!")
}
}
}
+142
View File
@@ -0,0 +1,142 @@
//! Wayland protocol code-generation machinnery
//!
//! This crate provides procedural macros for generating the rust code associated with a
//! Wayland XML protocol specification, for use with the `wayland-client`, `wayland-server`
//! and `wayland-backend` crates.
//!
//! Before trying to use this crate, you may check if the protocol extension you want to use
//! is not already exposed in the `wayland-protocols` crate.
//!
//! ## Example usage
//!
//! Below is a template for generating the code for a custom protocol client-side. Server-side
//! is identical, just replacing `client` by `server`. The path to the XML file is relative to the
//! crate root.
//!
//! ```rust,ignore
//! // Generate the bindings in their own module
//! pub mod my_protocol {
//! use wayland_client;
//! // import objects from the core protocol if needed
//! use wayland_client::protocol::*;
//!
//! // This module hosts a low-level representation of the protocol objects
//! // you will not need to interact with it yourself, but the code generated
//! // by the generate_client_code! macro will use it
//! pub mod __interfaces {
//! // import the interfaces from the core protocol if needed
//! use wayland_client::protocol::__interfaces::*;
//! wayland_scanner::generate_interfaces!("./path/to/the/protocol.xml");
//! }
//! use self::__interfaces::*;
//!
//! // This macro generates the actual types that represent the wayland objects of
//! // your custom protocol
//! wayland_scanner::generate_client_code!("./path/to/the/protocol.xml");
//! }
//! ```
use std::{ffi::OsString, path::PathBuf};
mod c_interfaces;
mod client_gen;
mod common;
mod interfaces;
mod message_io;
mod parse;
mod protocol;
mod server_gen;
mod token;
mod util;
/// Proc-macro for generating low-level interfaces associated with an XML specification
#[proc_macro]
pub fn generate_interfaces(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
let path: OsString = token::parse_lit_str_token(stream).into();
let path = if let Some(manifest_dir) = std::env::var_os("CARGO_MANIFEST_DIR") {
let mut buf = PathBuf::from(manifest_dir);
buf.push(path);
buf
} else {
path.into()
};
let file = match std::fs::File::open(&path) {
Ok(file) => file,
Err(e) => panic!("Failed to open protocol file {}: {}", path.display(), e),
};
let protocol = parse::parse(file);
interfaces::generate(&protocol, true).into()
}
/// Proc-macro for generating client-side API associated with an XML specification
#[proc_macro]
pub fn generate_client_code(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
let path: OsString = token::parse_lit_str_token(stream).into();
let path = if let Some(manifest_dir) = std::env::var_os("CARGO_MANIFEST_DIR") {
let mut buf = PathBuf::from(manifest_dir);
buf.push(path);
buf
} else {
path.into()
};
let file = match std::fs::File::open(&path) {
Ok(file) => file,
Err(e) => panic!("Failed to open protocol file {}: {}", path.display(), e),
};
let protocol = parse::parse(file);
client_gen::generate_client_objects(&protocol).into()
}
/// Proc-macro for generating server-side API associated with an XML specification
#[proc_macro]
pub fn generate_server_code(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
let path: OsString = token::parse_lit_str_token(stream).into();
let path = if let Some(manifest_dir) = std::env::var_os("CARGO_MANIFEST_DIR") {
let mut buf = PathBuf::from(manifest_dir);
buf.push(path);
buf
} else {
path.into()
};
let file = match std::fs::File::open(&path) {
Ok(file) => file,
Err(e) => panic!("Failed to open protocol file {}: {}", path.display(), e),
};
let protocol = parse::parse(file);
server_gen::generate_server_objects(&protocol).into()
}
#[cfg(test)]
fn format_rust_code(code: &str) -> String {
use std::{
io::Write,
process::{Command, Stdio},
};
if let Ok(mut proc) = Command::new("rustfmt")
.arg("--emit=stdout")
.arg("--edition=2018")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
//.stderr(Stdio::null())
.spawn()
{
{
let stdin = proc.stdin.as_mut().unwrap();
stdin.write_all(code.as_bytes()).unwrap();
}
if let Ok(output) = proc.wait_with_output() {
if output.status.success() {
return std::str::from_utf8(&output.stdout).unwrap().to_owned();
}
}
}
panic!("Rustfmt failed!");
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum Side {
/// wayland client applications
Client,
/// wayland compositors
Server,
}
+305
View File
@@ -0,0 +1,305 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use crate::{protocol::*, util::*, Side};
pub(crate) fn gen_parse_body(interface: &Interface, side: Side) -> TokenStream {
let msgs = match side {
Side::Client => &interface.events,
Side::Server => &interface.requests,
};
let object_type = Ident::new(
match side {
Side::Client => "Proxy",
Side::Server => "Resource",
},
Span::call_site(),
);
let msg_type = Ident::new(
match side {
Side::Client => "Event",
Side::Server => "Request",
},
Span::call_site(),
);
let match_arms = msgs.iter().enumerate().map(|(opcode, msg)| {
let opcode = opcode as u16;
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let args_pat = msg.args.iter().map(|arg| {
let arg_name = Ident::new(
&format!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name),
Span::call_site(),
);
match arg.typ {
Type::Uint => quote!{ Some(Argument::Uint(#arg_name)) },
Type::Int => quote!{ Some(Argument::Int(#arg_name)) },
Type::String => quote!{ Some(Argument::Str(#arg_name)) },
Type::Fixed => quote!{ Some(Argument::Fixed(#arg_name)) },
Type::Array => quote!{ Some(Argument::Array(#arg_name)) },
Type::Object => quote!{ Some(Argument::Object(#arg_name)) },
Type::NewId => quote!{ Some(Argument::NewId(#arg_name)) },
Type::Fd => quote!{ Some(Argument::Fd(#arg_name)) },
Type::Destructor => panic!("Argument {}.{}.{} has type destructor ?!", interface.name, msg.name, arg.name),
}
});
let args_iter = msg.args.iter().map(|_| quote!{ arg_iter.next() });
let arg_names = msg.args.iter().map(|arg| {
let arg_name = format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
if arg.enum_.is_some() {
quote! { #arg_name: From::from(#arg_name as u32) }
} else {
match arg.typ {
Type::Uint | Type::Int | Type::Fd => quote!{ #arg_name },
Type::Fixed => quote!{ #arg_name: (#arg_name as f64) / 256.},
Type::String => {
if arg.allow_null {
quote! {
#arg_name: #arg_name.as_ref().map(|s| String::from_utf8_lossy(s.as_bytes()).into_owned())
}
} else {
quote! {
#arg_name: String::from_utf8_lossy(#arg_name.as_ref().unwrap().as_bytes()).into_owned()
}
}
},
Type::Object => {
let create_proxy = if let Some(ref created_interface) = arg.interface {
let created_iface_mod = Ident::new(created_interface, Span::call_site());
let created_iface_type = Ident::new(&snake_to_camel(created_interface), Span::call_site());
quote! {
match <super::#created_iface_mod::#created_iface_type as #object_type>::from_id(conn, #arg_name.clone()) {
Ok(p) => p,
Err(_) => return Err(DispatchError::BadMessage {
sender_id: msg.sender_id,
interface: Self::interface().name,
opcode: msg.opcode
}),
}
}
} else {
quote! { #arg_name.clone() }
};
if arg.allow_null {
quote! {
#arg_name: if #arg_name.is_null() { None } else { Some(#create_proxy) }
}
} else {
quote! {
#arg_name: #create_proxy
}
}
},
Type::NewId => {
let create_proxy = if let Some(ref created_interface) = arg.interface {
let created_iface_mod = Ident::new(created_interface, Span::call_site());
let created_iface_type = Ident::new(&snake_to_camel(created_interface), Span::call_site());
quote! {
match <super::#created_iface_mod::#created_iface_type as #object_type>::from_id(conn, #arg_name.clone()) {
Ok(p) => p,
Err(_) => return Err(DispatchError::BadMessage {
sender_id: msg.sender_id,
interface: Self::interface().name,
opcode: msg.opcode,
}),
}
}
} else if side == Side::Server {
quote! { New::wrap(#arg_name.clone()) }
} else {
quote! { #arg_name.clone() }
};
if arg.allow_null {
if side == Side::Server {
quote! {
#arg_name: if #arg_name.is_null() { None } else { Some(New::wrap(#create_proxy)) }
}
} else {
quote! {
#arg_name: if #arg_name.is_null() { None } else { Some(#create_proxy) }
}
}
} else if side == Side::Server {
quote! {
#arg_name: New::wrap(#create_proxy)
}
} else {
quote! {
#arg_name: #create_proxy
}
}
},
Type::Array => {
if arg.allow_null {
quote! { if #arg_name.len() == 0 { None } else { Some(*#arg_name) } }
} else {
quote! { #arg_name: *#arg_name }
}
},
Type::Destructor => unreachable!(),
}
}
});
quote! {
#opcode => {
if let (#(#args_pat),*) = (#(#args_iter),*) {
Ok((me, #msg_type::#msg_name { #(#arg_names),* }))
} else {
Err(DispatchError::BadMessage { sender_id: msg.sender_id, interface: Self::interface().name, opcode: msg.opcode })
}
}
}
});
quote! {
let me = Self::from_id(conn, msg.sender_id.clone()).unwrap();
let mut arg_iter = msg.args.into_iter();
match msg.opcode {
#(#match_arms),*
_ => Err(DispatchError::BadMessage { sender_id: msg.sender_id, interface: Self::interface().name, opcode: msg.opcode }),
}
}
}
pub(crate) fn gen_write_body(interface: &Interface, side: Side) -> TokenStream {
let msgs = match side {
Side::Client => &interface.requests,
Side::Server => &interface.events,
};
let msg_type = Ident::new(
match side {
Side::Client => "Request",
Side::Server => "Event",
},
Span::call_site(),
);
let arms = msgs.iter().enumerate().map(|(opcode, msg)| {
let msg_name = Ident::new(&snake_to_camel(&msg.name), Span::call_site());
let opcode = opcode as u16;
let arg_names = msg.args.iter().flat_map(|arg| {
if arg.typ == Type::NewId && arg.interface.is_some() && side == Side::Client {
None
} else {
Some(format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name))
}
});
let mut child_spec = None;
let args = msg.args.iter().flat_map(|arg| {
let arg_name = format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
match arg.typ {
Type::Int => vec![if arg.enum_.is_some() { quote!{ Argument::Int(Into::<u32>::into(#arg_name) as i32) } } else { quote!{ Argument::Int(#arg_name) } }],
Type::Uint => vec![if arg.enum_.is_some() { quote!{ Argument::Uint(#arg_name.into()) } } else { quote!{ Argument::Uint(#arg_name) } }],
Type::Fd => vec![quote!{ Argument::Fd(#arg_name) }],
Type::Fixed => vec![quote! { Argument::Fixed((#arg_name * 256.) as i32) }],
Type::Object => if arg.allow_null {
if side == Side::Server {
vec![quote! { if let Some(obj) = #arg_name { Argument::Object(Resource::id(&obj)) } else { Argument::Object(ObjectId::null()) } }]
} else {
vec![quote! { if let Some(obj) = #arg_name { Argument::Object(Proxy::id(&obj)) } else { Argument::Object(ObjectId::null()) } }]
}
} else if side == Side::Server {
vec![quote!{ Argument::Object(Resource::id(&#arg_name)) }]
} else {
vec![quote!{ Argument::Object(Proxy::id(&#arg_name)) }]
},
Type::Array => if arg.allow_null {
vec![quote! { if let Some(array) = #arg_name { Argument::Array(Box::new(array)) } else { Argument::Array(Box::new(Vec::new()))}}]
} else {
vec![quote! { Argument::Array(Box::new(#arg_name)) }]
},
Type::String => if arg.allow_null {
vec![quote! { Argument::Str(#arg_name.map(|s| Box::new(std::ffi::CString::new(s).unwrap()))) }]
} else {
vec![quote! { Argument::Str(Some(Box::new(std::ffi::CString::new(#arg_name).unwrap()))) }]
},
Type::NewId => if side == Side::Client {
if let Some(ref created_interface) = arg.interface {
let created_iface_mod = Ident::new(created_interface, Span::call_site());
let created_iface_type = Ident::new(&snake_to_camel(created_interface), Span::call_site());
assert!(child_spec.is_none());
child_spec = Some(quote! { {
let my_info = conn.object_info(self.id())?;
Some((super::#created_iface_mod::#created_iface_type::interface(), my_info.version))
} });
vec![quote! { Argument::NewId(ObjectId::null()) }]
} else {
assert!(child_spec.is_none());
child_spec = Some(quote! {
Some((#arg_name.0, #arg_name.1))
});
vec![
quote! {
Argument::Str(Some(Box::new(std::ffi::CString::new(#arg_name.0.name).unwrap())))
},
quote! {
Argument::Uint(#arg_name.1)
},
quote! {
Argument::NewId(ObjectId::null())
},
]
}
} else {
// server-side NewId is the same as Object
if arg.allow_null {
vec![quote! { if let Some(obj) = #arg_name { Argument::NewId(Resource::id(&obj)) } else { Argument::NewId(ObjectId::null()) } }]
} else {
vec![quote!{ Argument::NewId(Resource::id(&#arg_name)) }]
}
},
Type::Destructor => panic!("Argument {}.{}.{} has type destructor ?!", interface.name, msg.name, arg.name),
}
});
let args = if msg.args.is_empty() {
quote! {
smallvec::SmallVec::new()
}
} else if msg.args.len() <= 4 {
// Note: Keep in sync with `wayland_backend::protocol::INLINE_ARGS`.
// Fits in SmallVec inline capacity
quote! { {
let mut vec = smallvec::SmallVec::new();
#(
vec.push(#args);
)*
vec
} }
} else {
quote! {
smallvec::SmallVec::from_vec(vec![#(#args),*])
}
};
if side == Side::Client {
let child_spec = child_spec.unwrap_or_else(|| quote! { None });
quote! {
#msg_type::#msg_name { #(#arg_names),* } => {
let child_spec = #child_spec;
let args = #args;
Ok((Message {
sender_id: self.id.clone(),
opcode: #opcode,
args
}, child_spec))
}
}
} else {
quote! {
#msg_type::#msg_name { #(#arg_names),* } => Ok(Message {
sender_id: self.id.clone(),
opcode: #opcode,
args: #args,
})
}
}
});
quote! {
match msg {
#(#arms,)*
#msg_type::__phantom_lifetime { never, .. } => match never {}
}
}
}
+405
View File
@@ -0,0 +1,405 @@
use super::protocol::*;
use std::{
io::{BufRead, BufReader, Read},
str::FromStr,
};
use quick_xml::{
events::{attributes::Attributes, Event},
Reader,
};
pub fn parse<S: Read>(stream: S) -> Protocol {
let mut reader = Reader::from_reader(BufReader::new(stream));
let reader_config = reader.config_mut();
reader_config.trim_text(true);
reader_config.expand_empty_elements = true;
parse_protocol(reader)
}
fn decode_utf8_or_panic(txt: Vec<u8>) -> String {
match String::from_utf8(txt) {
Ok(txt) => txt,
Err(e) => panic!("Invalid UTF8: '{}'", String::from_utf8_lossy(&e.into_bytes())),
}
}
fn parse_or_panic<T: FromStr>(txt: &[u8]) -> T {
match std::str::from_utf8(txt).ok().and_then(|val| val.parse().ok()) {
Some(version) => version,
None => panic!(
"Invalid value '{}' for parsing type '{}'",
String::from_utf8_lossy(txt),
std::any::type_name::<T>()
),
}
}
fn init_protocol<R: BufRead>(reader: &mut Reader<R>) -> Protocol {
// Check two firsts lines for protocol tag
for _ in 0..3 {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Decl(_) | Event::DocType(_) | Event::Comment(_)) => {
continue;
}
Ok(Event::Start(bytes)) => {
assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag");
if let Some(attr) = bytes
.attributes()
.filter_map(|res| res.ok())
.find(|attr| attr.key.into_inner() == b"name")
{
return Protocol::new(decode_utf8_or_panic(attr.value.into_owned()));
} else {
panic!("Protocol must have a name");
}
}
_ => panic!("Ill-formed protocol file"),
}
}
panic!("Ill-formed protocol file");
}
fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Protocol {
let mut protocol = init_protocol(&mut reader);
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => {
match bytes.name().into_inner() {
b"copyright" => {
// parse the copyright
let mut copyright = String::new();
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Text(text)) => {
if let Ok(text) = text.decode() {
copyright.push_str(&text);
}
}
Ok(Event::CData(cdata)) => {
if let Ok(cdata) = String::from_utf8(cdata.into_inner().into())
{
copyright.push_str(&cdata);
}
}
Ok(Event::GeneralRef(byte_ref)) => {
if let Ok(Some(c)) = byte_ref.resolve_char_ref() {
copyright.push(c);
} else if let Ok(content) = byte_ref.xml10_content() {
if let Some(s) =
quick_xml::escape::resolve_xml_entity(&content)
{
copyright.push_str(s);
}
}
}
Ok(Event::End(bytes)) => {
assert!(
bytes.name().into_inner() == "copyright".as_bytes(),
"Ill-formed protocol file"
);
break;
}
e => {
panic!("Ill-formed protocol file: {e:?}");
}
}
}
protocol.copyright = Some(copyright)
}
b"interface" => {
protocol.interfaces.push(parse_interface(&mut reader, bytes.attributes()));
}
b"description" => {
protocol.description =
Some(parse_description(&mut reader, bytes.attributes()));
}
name => panic!(
"Ill-formed protocol file: unexpected token `{}` in protocol {}",
String::from_utf8_lossy(name),
protocol.name
),
}
}
Ok(Event::End(bytes)) => {
let name = bytes.name().into_inner();
assert!(
name == b"protocol",
"Unexpected closing token `{}`",
String::from_utf8_lossy(name)
);
break;
}
// ignore comments
Ok(Event::Comment(_)) => {}
e => panic!("Ill-formed protocol file: unexpected token {e:?}"),
}
}
protocol
}
fn parse_interface<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Interface {
let mut interface = Interface::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => interface.name = decode_utf8_or_panic(attr.value.into_owned()),
b"version" => interface.version = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
interface.description = Some(parse_description(reader, bytes.attributes()))
}
b"request" => interface.requests.push(parse_request(reader, bytes.attributes())),
b"event" => interface.events.push(parse_event(reader, bytes.attributes())),
b"enum" => interface.enums.push(parse_enum(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"interface" => break,
_ => {}
}
}
interface
}
fn parse_description<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> (String, String) {
let mut summary = String::new();
for attr in attrs.filter_map(|res| res.ok()) {
if attr.key.into_inner() == b"summary" {
summary = String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
}
}
let mut description = String::new();
// Some protocols have comments inside their descriptions, so we need to parse them in a loop and
// concatenate the parts into a single block of text
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Text(bytes)) => {
if !description.is_empty() {
description.push_str("\n\n");
}
description.push_str(&bytes.decode().unwrap_or_default())
}
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"description" => break,
Ok(Event::Comment(_)) => {}
e => panic!("Ill-formed protocol file: {e:?}"),
}
}
(summary, description)
}
fn parse_request<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
let mut request = Message::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => request.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => request.typ = Some(parse_type(&attr.value)),
b"since" => request.since = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
request.description = Some(parse_description(reader, bytes.attributes()))
}
b"arg" => request.args.push(parse_arg(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"request" => break,
_ => {}
}
}
request
}
fn parse_enum<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Enum {
let mut enu = Enum::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => enu.name = decode_utf8_or_panic(attr.value.into_owned()),
b"since" => enu.since = parse_or_panic(&attr.value),
b"bitfield" if attr.value.as_ref() == b"true" => enu.bitfield = true,
b"bitfield" => {}
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
enu.description = Some(parse_description(reader, bytes.attributes()))
}
b"entry" => enu.entries.push(parse_entry(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"enum" => break,
_ => {}
}
}
enu
}
fn parse_event<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
let mut event = Message::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => event.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => event.typ = Some(parse_type(&attr.value)),
b"since" => event.since = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
event.description = Some(parse_description(reader, bytes.attributes()))
}
b"arg" => event.args.push(parse_arg(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"event" => break,
_ => {}
}
}
event
}
fn parse_arg<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Arg {
let mut arg = Arg::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => arg.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => arg.typ = parse_type(&attr.value),
b"summary" => {
arg.summary = Some(
String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
b"interface" => arg.interface = Some(parse_or_panic(&attr.value)),
b"allow-null" if attr.value.as_ref() == b"true" => arg.allow_null = true,
b"allow-null" => {}
b"enum" => arg.enum_ = Some(decode_utf8_or_panic(attr.value.into_owned())),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
arg.description = Some(parse_description(reader, bytes.attributes()))
}
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"arg" => break,
_ => {}
}
}
arg
}
fn parse_type(txt: &[u8]) -> Type {
match txt {
b"int" => Type::Int,
b"uint" => Type::Uint,
b"fixed" => Type::Fixed,
b"string" => Type::String,
b"object" => Type::Object,
b"new_id" => Type::NewId,
b"array" => Type::Array,
b"fd" => Type::Fd,
b"destructor" => Type::Destructor,
e => panic!("Unexpected type: {}", String::from_utf8_lossy(e)),
}
}
fn parse_entry<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Entry {
let mut entry = Entry::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => entry.name = decode_utf8_or_panic(attr.value.into_owned()),
b"value" => {
entry.value = if attr.value.starts_with(b"0x") {
if let Some(val) = std::str::from_utf8(&attr.value[2..])
.ok()
.and_then(|s| u32::from_str_radix(s, 16).ok())
{
val
} else {
panic!("Invalid number: {}", String::from_utf8_lossy(&attr.value))
}
} else {
parse_or_panic(&attr.value)
};
}
b"since" => entry.since = parse_or_panic(&attr.value),
b"summary" => {
entry.summary = Some(
String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
entry.description = Some(parse_description(reader, bytes.attributes()))
}
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"entry" => break,
_ => {}
}
}
entry
}
#[cfg(test)]
mod tests {
#[test]
fn xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}
#[test]
fn headerless_xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-headerless-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}
}
+154
View File
@@ -0,0 +1,154 @@
use proc_macro2::TokenStream;
use quote::quote;
#[derive(Clone, Debug)]
pub struct Protocol {
pub name: String,
pub copyright: Option<String>,
pub description: Option<(String, String)>,
pub interfaces: Vec<Interface>,
}
impl Protocol {
pub fn new(name: String) -> Protocol {
Protocol { name, copyright: None, description: None, interfaces: Vec::new() }
}
}
#[derive(Clone, Debug)]
pub struct Interface {
pub name: String,
pub version: u32,
pub description: Option<(String, String)>,
pub requests: Vec<Message>,
pub events: Vec<Message>,
pub enums: Vec<Enum>,
}
impl Interface {
pub fn new() -> Interface {
Interface {
name: String::new(),
version: 1,
description: None,
requests: Vec::new(),
events: Vec::new(),
enums: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Message {
pub name: String,
pub typ: Option<Type>,
pub since: u32,
pub description: Option<(String, String)>,
pub args: Vec<Arg>,
}
impl Message {
pub fn new() -> Message {
Message { name: String::new(), typ: None, since: 1, description: None, args: Vec::new() }
}
pub fn all_null(&self) -> bool {
self.args
.iter()
.all(|a| !((a.typ == Type::Object || a.typ == Type::NewId) && a.interface.is_some()))
}
}
#[derive(Clone, Debug)]
pub struct Arg {
pub name: String,
pub typ: Type,
pub interface: Option<String>,
pub summary: Option<String>,
pub description: Option<(String, String)>,
pub allow_null: bool,
pub enum_: Option<String>,
}
impl Arg {
pub fn new() -> Arg {
Arg {
name: String::new(),
typ: Type::Object,
interface: None,
summary: None,
description: None,
allow_null: false,
enum_: None,
}
}
}
#[derive(Clone, Debug)]
pub struct Enum {
pub name: String,
pub since: u16,
pub description: Option<(String, String)>,
pub entries: Vec<Entry>,
pub bitfield: bool,
}
impl Enum {
pub fn new() -> Enum {
Enum {
name: String::new(),
since: 1,
description: None,
entries: Vec::new(),
bitfield: false,
}
}
}
#[derive(Clone, Debug)]
pub struct Entry {
pub name: String,
pub value: u32,
pub since: u16,
pub description: Option<(String, String)>,
pub summary: Option<String>,
}
impl Entry {
pub fn new() -> Entry {
Entry { name: String::new(), value: 0, since: 1, description: None, summary: None }
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Type {
Int,
Uint,
Fixed,
String,
Object,
NewId,
Array,
Fd,
Destructor,
}
impl Type {
pub fn nullable(self) -> bool {
matches!(self, Type::String | Type::Object)
}
pub fn common_type(self) -> TokenStream {
match self {
Type::Int => quote!(Int),
Type::Uint => quote!(Uint),
Type::Fixed => quote!(Fixed),
Type::Array => quote!(Array),
Type::Fd => quote!(Fd),
Type::String => quote!(Str),
Type::Object => quote!(Object),
Type::NewId => quote!(NewId),
Type::Destructor => panic!("Destructor is not a valid argument type."),
}
}
}
+298
View File
@@ -0,0 +1,298 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use crate::{
protocol::{Interface, Protocol, Type},
util::{description_to_doc_attr, dotted_to_relname, is_keyword, snake_to_camel, to_doc_attr},
Side,
};
pub fn generate_server_objects(protocol: &Protocol) -> TokenStream {
protocol
.interfaces
.iter()
.filter(|iface| iface.name != "wl_display")
.map(generate_objects_for)
.collect()
}
fn generate_objects_for(interface: &Interface) -> TokenStream {
let mod_name = Ident::new(&interface.name, Span::call_site());
let mod_doc = interface.description.as_ref().map(description_to_doc_attr);
let iface_name = Ident::new(&snake_to_camel(&interface.name), Span::call_site());
let iface_const_name = format_ident!("{}_INTERFACE", interface.name.to_ascii_uppercase());
let enums = crate::common::generate_enums_for(interface);
let msg_constants = crate::common::gen_msg_constants(&interface.requests, &interface.events);
let requests = crate::common::gen_message_enum(
&format_ident!("Request"),
Side::Server,
true,
&interface.requests,
);
let events = crate::common::gen_message_enum(
&format_ident!("Event"),
Side::Server,
false,
&interface.events,
);
let parse_body = if interface.name == "wl_registry" {
quote! { unimplemented!("`wl_registry` is implemented internally in `wayland-server`") }
} else {
crate::message_io::gen_parse_body(interface, Side::Server)
};
let write_body = crate::message_io::gen_write_body(interface, Side::Server);
let methods = gen_methods(interface);
let event_ref = if interface.requests.is_empty() {
"This interface has no requests."
} else {
"See also the [Request] enum for this interface."
};
let docs = match &interface.description {
Some((short, long)) => format!("{short}\n\n{long}\n\n{event_ref}"),
None => format!("{}\n\n{}", interface.name, event_ref),
};
let doc_attr = to_doc_attr(&docs);
quote! {
#mod_doc
pub mod #mod_name {
use std::sync::Arc;
use std::os::unix::io::OwnedFd;
use super::wayland_server::{
backend::{
smallvec, ObjectData, ObjectId, InvalidId, WeakHandle,
protocol::{WEnum, Argument, Message, Interface, same_interface}
},
Resource, Dispatch, DisplayHandle, DispatchError, ResourceData, New, Weak,
};
#enums
#msg_constants
#requests
#events
#doc_attr
#[derive(Debug, Clone)]
pub struct #iface_name {
id: ObjectId,
version: u32,
data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
handle: WeakHandle,
}
impl std::cmp::PartialEq for #iface_name {
#[inline]
fn eq(&self, other: &#iface_name) -> bool {
self.id == other.id
}
}
impl std::cmp::Eq for #iface_name {}
impl PartialEq<Weak<#iface_name>> for #iface_name {
#[inline]
fn eq(&self, other: &Weak<#iface_name>) -> bool {
self.id == other.id()
}
}
impl std::borrow::Borrow<ObjectId> for #iface_name {
#[inline]
fn borrow(&self) -> &ObjectId {
&self.id
}
}
impl std::hash::Hash for #iface_name {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl super::wayland_server::Resource for #iface_name {
type Request = Request;
type Event<'event> = Event<'event>;
#[inline]
fn interface() -> &'static Interface{
&super::#iface_const_name
}
#[inline]
fn id(&self) -> ObjectId {
self.id.clone()
}
#[inline]
fn version(&self) -> u32 {
self.version
}
#[inline]
fn data<U: 'static>(&self) -> Option<&U> {
self.data.as_ref().and_then(|arc| (&**arc).downcast_ref::<ResourceData<Self, U>>()).map(|data| &data.udata)
}
#[inline]
fn object_data(&self) -> Option<&Arc<dyn std::any::Any + Send + Sync>> {
self.data.as_ref()
}
fn handle(&self) -> &WeakHandle {
&self.handle
}
#[inline]
fn from_id(conn: &DisplayHandle, id: ObjectId) -> Result<Self, InvalidId> {
if !same_interface(id.interface(), Self::interface()) && !id.is_null(){
return Err(InvalidId)
}
let version = conn.object_info(id.clone()).map(|info| info.version).unwrap_or(0);
let data = conn.get_object_data(id.clone()).ok();
Ok(#iface_name { id, data, version, handle: conn.backend_handle().downgrade() })
}
fn send_event(&self, evt: Self::Event<'_>) -> Result<(), InvalidId> {
let handle = DisplayHandle::from(self.handle.upgrade().ok_or(InvalidId)?);
handle.send_event(self, evt)
}
fn parse_request(conn: &DisplayHandle, msg: Message<ObjectId, OwnedFd>) -> Result<(Self, Self::Request), DispatchError> {
#parse_body
}
fn write_event<'a>(&self, conn: &DisplayHandle, msg: Self::Event<'a>) -> Result<Message<ObjectId, std::os::unix::io::BorrowedFd<'a>>, InvalidId> {
#write_body
}
fn __set_object_data(&mut self, odata: std::sync::Arc<dyn std::any::Any + Send + Sync + 'static>) {
self.data = Some(odata);
}
}
impl #iface_name {
#methods
}
}
}
}
fn gen_methods(interface: &Interface) -> TokenStream {
interface
.events
.iter()
.map(|request| {
let method_name = format_ident!(
"{}{}",
if is_keyword(&request.name) { "_" } else { "" },
request.name
);
let enum_variant = Ident::new(&snake_to_camel(&request.name), Span::call_site());
let fn_args = request.args.iter().flat_map(|arg| {
let arg_name =
format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
let arg_type = if let Some(ref enu) = arg.enum_ {
let enum_type = dotted_to_relname(enu);
quote! { #enum_type }
} else {
match arg.typ {
Type::Uint => quote! { u32 },
Type::Int => quote! { i32 },
Type::Fixed => quote! { f64 },
Type::String => {
if arg.allow_null {
quote! { Option<String> }
} else {
quote! { String }
}
}
Type::Array => {
if arg.allow_null {
quote! { Option<Vec<u8>> }
} else {
quote! { Vec<u8> }
}
}
Type::Fd => quote! { ::std::os::unix::io::BorrowedFd<'_> },
Type::Object | Type::NewId => {
let iface = arg.interface.as_ref().unwrap();
let iface_mod = Ident::new(iface, Span::call_site());
let iface_type = Ident::new(&snake_to_camel(iface), Span::call_site());
if arg.allow_null {
quote! { Option<&super::#iface_mod::#iface_type> }
} else {
quote! { &super::#iface_mod::#iface_type }
}
}
Type::Destructor => panic!("An argument cannot have type \"destructor\"."),
}
};
Some(quote! {
#arg_name: #arg_type
})
});
let enum_args = request.args.iter().flat_map(|arg| {
let arg_name =
format_ident!("{}{}", if is_keyword(&arg.name) { "_" } else { "" }, arg.name);
if arg.enum_.is_some() {
Some(quote! { #arg_name: WEnum::Value(#arg_name) })
} else if arg.typ == Type::Object || arg.typ == Type::NewId {
if arg.allow_null {
Some(quote! { #arg_name: #arg_name.cloned() })
} else {
Some(quote! { #arg_name: #arg_name.clone() })
}
} else {
Some(quote! { #arg_name })
}
});
let doc_attr = request.description.as_ref().map(description_to_doc_attr);
quote! {
#doc_attr
#[allow(clippy::too_many_arguments)]
pub fn #method_name(&self, #(#fn_args),*) {
let _ = self.send_event(
Event::#enum_variant {
#(#enum_args),*
}
);
}
}
})
.collect()
}
#[cfg(test)]
mod tests {
#[test]
fn server_gen() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let protocol_parsed = crate::parse::parse(protocol_file);
let generated: String = super::generate_server_objects(&protocol_parsed).to_string();
let generated = crate::format_rust_code(&generated);
let reference =
std::fs::read_to_string("./tests/scanner_assets/test-server-code.rs").unwrap();
let reference = crate::format_rust_code(&reference);
if reference != generated {
let diff = similar::TextDiff::from_lines(&reference, &generated);
print!("{}", diff.unified_diff().context_radius(10).header("reference", "generated"));
panic!("Generated does not match reference!")
}
}
}
+186
View File
@@ -0,0 +1,186 @@
// `bytes`, `next_chr`, `parse_lit_str`, `parse_lit_str_cooked` and `parse_lit_str_raw` are adapted
// from syn:
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1062-L1167
// and
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1327-L1388
/// Get the byte at offset idx, or a default of `b'\0'` if we're looking
/// past the end of the input buffer.
fn byte(s: &str, idx: usize) -> u8 {
if idx < s.len() {
s.as_bytes()[idx]
} else {
0
}
}
fn next_chr(s: &str) -> char {
s.chars().next().unwrap_or('\0')
}
// Returns (content, suffix).
fn parse_lit_str(s: &str) -> String {
match byte(s, 0) {
b'"' => parse_lit_str_cooked(s),
b'r' => parse_lit_str_raw(s),
_ => unreachable!(),
}
}
// Clippy false positive
// https://github.com/rust-lang-nursery/rust-clippy/issues/2329
#[allow(clippy::needless_continue)]
fn parse_lit_str_cooked(mut s: &str) -> String {
assert_eq!(byte(s, 0), b'"');
s = &s[1..];
let mut content = String::new();
'outer: loop {
let ch = match byte(s, 0) {
b'"' => break,
b'\\' => {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (byte, rest) = backslash_x(s);
s = rest;
assert!(byte <= 0x80, "Invalid \\x byte in string literal");
char::from_u32(u32::from(byte)).unwrap()
}
b'u' => {
let (chr, rest) = backslash_u(s);
s = rest;
chr
}
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'\\' => '\\',
b'0' => '\0',
b'\'' => '\'',
b'"' => '"',
b'\r' | b'\n' => loop {
let ch = next_chr(s);
if ch.is_whitespace() {
s = &s[ch.len_utf8()..];
} else {
continue 'outer;
}
},
b => panic!("unexpected byte {b:?} after \\ character in byte literal"),
}
}
b'\r' => {
assert_eq!(byte(s, 1), b'\n', "Bare CR not allowed in string");
s = &s[2..];
'\n'
}
_ => {
let ch = next_chr(s);
s = &s[ch.len_utf8()..];
ch
}
};
content.push(ch);
}
assert!(s.starts_with('"'));
content
}
fn parse_lit_str_raw(mut s: &str) -> String {
assert_eq!(byte(s, 0), b'r');
s = &s[1..];
let mut pounds = 0;
while byte(s, pounds) == b'#' {
pounds += 1;
}
assert_eq!(byte(s, pounds), b'"');
let close = s.rfind('"').unwrap();
for end in s[close + 1..close + 1 + pounds].bytes() {
assert_eq!(end, b'#');
}
s[pounds + 1..close].to_owned()
}
fn backslash_x(s: &str) -> (u8, &str) {
let mut ch = 0;
let b0 = byte(s, 0);
let b1 = byte(s, 1);
ch += 0x10
* match b0 {
b'0'..=b'9' => b0 - b'0',
b'a'..=b'f' => 10 + (b0 - b'a'),
b'A'..=b'F' => 10 + (b0 - b'A'),
_ => panic!("unexpected non-hex character after \\x"),
};
ch += match b1 {
b'0'..=b'9' => b1 - b'0',
b'a'..=b'f' => 10 + (b1 - b'a'),
b'A'..=b'F' => 10 + (b1 - b'A'),
_ => panic!("unexpected non-hex character after \\x"),
};
(ch, &s[2..])
}
fn backslash_u(mut s: &str) -> (char, &str) {
if byte(s, 0) != b'{' {
panic!("{}", "expected { after \\u");
}
s = &s[1..];
let mut ch = 0;
let mut digits = 0;
loop {
let b = byte(s, 0);
let digit = match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => 10 + b - b'a',
b'A'..=b'F' => 10 + b - b'A',
b'_' if digits > 0 => {
s = &s[1..];
continue;
}
b'}' if digits == 0 => panic!("invalid empty unicode escape"),
b'}' => break,
_ => panic!("unexpected non-hex character after \\u"),
};
if digits == 6 {
panic!("overlong unicode escape (must have at most 6 hex digits)");
}
ch *= 0x10;
ch += u32::from(digit);
digits += 1;
s = &s[1..];
}
assert!(byte(s, 0) == b'}');
s = &s[1..];
if let Some(ch) = char::from_u32(ch) {
(ch, s)
} else {
panic!("character code {ch:x} is not a valid unicode character");
}
}
// End of code adapted from syn
pub fn parse_lit_str_token(mut stream: proc_macro::TokenStream) -> String {
loop {
let mut iter = stream.into_iter();
let token = iter.next().expect("expected string argument");
assert!(iter.next().is_none(), "unexpected trailing token");
let literal = match token {
proc_macro::TokenTree::Literal(literal) => literal,
proc_macro::TokenTree::Group(group) => {
stream = group.stream();
continue;
}
_ => panic!("expected string argument found `{token:?}`"),
};
return parse_lit_str(&literal.to_string());
}
}
+115
View File
@@ -0,0 +1,115 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
pub(crate) fn to_doc_attr(text: &str) -> TokenStream {
let text = text.lines().map(str::trim).collect::<Vec<_>>().join("\n");
let text = text.trim();
quote!(#[doc = #text])
}
pub(crate) fn description_to_doc_attr((short, long): &(String, String)) -> TokenStream {
to_doc_attr(&format!("{short}\n\n{long}"))
}
pub fn is_keyword(txt: &str) -> bool {
matches!(
txt,
"abstract"
| "alignof"
| "as"
| "become"
| "box"
| "break"
| "const"
| "continue"
| "crate"
| "do"
| "else"
| "enum"
| "extern"
| "false"
| "final"
| "fn"
| "for"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "macro"
| "match"
| "mod"
| "move"
| "mut"
| "offsetof"
| "override"
| "priv"
| "proc"
| "pub"
| "pure"
| "ref"
| "return"
| "Self"
| "self"
| "sizeof"
| "static"
| "struct"
| "super"
| "trait"
| "true"
| "type"
| "typeof"
| "unsafe"
| "unsized"
| "use"
| "virtual"
| "where"
| "while"
| "yield"
| "__handler"
| "__object"
)
}
pub fn is_camel_keyword(txt: &str) -> bool {
matches!(txt, "Self")
}
pub fn snake_to_camel(input: &str) -> String {
let result = input
.split('_')
.flat_map(|s| {
let mut first = true;
s.chars().map(move |c| {
if first {
first = false;
c.to_ascii_uppercase()
} else {
c
}
})
})
.collect::<String>();
if is_camel_keyword(&result) {
format!("_{}", &result)
} else {
result
}
}
pub fn dotted_to_relname(input: &str) -> TokenStream {
let mut it = input.split('.');
match (it.next(), it.next()) {
(Some(module), Some(name)) => {
let module = Ident::new(module, Span::call_site());
let ident = Ident::new(&snake_to_camel(name), Span::call_site());
quote::quote!(super::#module::#ident)
}
(Some(name), None) => {
Ident::new(&snake_to_camel(name), Span::call_site()).into_token_stream()
}
_ => unreachable!(),
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
<protocol name="test-protocol">
</protocol>
@@ -0,0 +1,524 @@
use std::ptr::null;
struct SyncWrapper<T>(T);
unsafe impl<T> Sync for SyncWrapper<T> {}
static types_null: SyncWrapper<[*const wayland_backend::protocol::wl_interface; 6]> =
SyncWrapper([null::<wayland_backend::protocol::wl_interface>(); 6]);
pub static WL_DISPLAY_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "wl_display",
version: 1u32,
requests: &[
wayland_backend::protocol::MessageDesc {
name: "sync",
signature: &[wayland_backend::protocol::ArgumentType::NewId],
since: 1u32,
is_destructor: false,
child_interface: Some(&WL_CALLBACK_INTERFACE),
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "get_registry",
signature: &[wayland_backend::protocol::ArgumentType::NewId],
since: 1u32,
is_destructor: false,
child_interface: Some(&WL_REGISTRY_INTERFACE),
arg_interfaces: &[],
},
],
events: &[
wayland_backend::protocol::MessageDesc {
name: "error",
signature: &[
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::Str(
wayland_backend::protocol::AllowNull::No,
),
],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[&wayland_backend::protocol::ANONYMOUS_INTERFACE],
},
wayland_backend::protocol::MessageDesc {
name: "delete_id",
signature: &[wayland_backend::protocol::ArgumentType::Uint],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
},
],
c_ptr: Some(unsafe { &wl_display_interface }),
};
static wl_display_requests_sync_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 1],
> = SyncWrapper([&wl_callback_interface as *const wayland_backend::protocol::wl_interface]);
static wl_display_requests_get_registry_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 1],
> = SyncWrapper([&wl_registry_interface as *const wayland_backend::protocol::wl_interface]);
static wl_display_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 2]> =
SyncWrapper([
wayland_backend::protocol::wl_message {
name: b"sync\0" as *const u8 as *const std::os::raw::c_char,
signature: b"n\0" as *const u8 as *const std::os::raw::c_char,
types: wl_display_requests_sync_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"get_registry\0" as *const u8 as *const std::os::raw::c_char,
signature: b"n\0" as *const u8 as *const std::os::raw::c_char,
types: wl_display_requests_get_registry_types.0.as_ptr(),
},
]);
static wl_display_events: SyncWrapper<[wayland_backend::protocol::wl_message; 2]> = SyncWrapper([
wayland_backend::protocol::wl_message {
name: b"error\0" as *const u8 as *const std::os::raw::c_char,
signature: b"ous\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"delete_id\0" as *const u8 as *const std::os::raw::c_char,
signature: b"u\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
]);
pub static wl_display_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"wl_display\0" as *const u8 as *const std::os::raw::c_char,
version: 1,
request_count: 2,
requests: wl_display_requests.0.as_ptr(),
event_count: 2,
events: wl_display_events.0.as_ptr(),
};
pub static WL_REGISTRY_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "wl_registry",
version: 1u32,
requests: &[wayland_backend::protocol::MessageDesc {
name: "bind",
signature: &[
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::Str(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::NewId,
],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
}],
events: &[
wayland_backend::protocol::MessageDesc {
name: "global",
signature: &[
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::Str(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Uint,
],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "global_remove",
signature: &[wayland_backend::protocol::ArgumentType::Uint],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
},
],
c_ptr: Some(unsafe { &wl_registry_interface }),
};
static wl_registry_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 1]> =
SyncWrapper([wayland_backend::protocol::wl_message {
name: b"bind\0" as *const u8 as *const std::os::raw::c_char,
signature: b"usun\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
}]);
static wl_registry_events: SyncWrapper<[wayland_backend::protocol::wl_message; 2]> = SyncWrapper([
wayland_backend::protocol::wl_message {
name: b"global\0" as *const u8 as *const std::os::raw::c_char,
signature: b"usu\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"global_remove\0" as *const u8 as *const std::os::raw::c_char,
signature: b"u\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
]);
pub static wl_registry_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"wl_registry\0" as *const u8 as *const std::os::raw::c_char,
version: 1,
request_count: 1,
requests: wl_registry_requests.0.as_ptr(),
event_count: 2,
events: wl_registry_events.0.as_ptr(),
};
pub static WL_CALLBACK_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "wl_callback",
version: 1u32,
requests: &[],
events: &[wayland_backend::protocol::MessageDesc {
name: "done",
signature: &[wayland_backend::protocol::ArgumentType::Uint],
since: 1u32,
is_destructor: true,
child_interface: None,
arg_interfaces: &[],
}],
c_ptr: Some(unsafe { &wl_callback_interface }),
};
static wl_callback_events: SyncWrapper<[wayland_backend::protocol::wl_message; 1]> =
SyncWrapper([wayland_backend::protocol::wl_message {
name: b"done\0" as *const u8 as *const std::os::raw::c_char,
signature: b"u\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
}]);
pub static wl_callback_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"wl_callback\0" as *const u8 as *const std::os::raw::c_char,
version: 1,
request_count: 0,
requests: null::<wayland_backend::protocol::wl_message>(),
event_count: 1,
events: wl_callback_events.0.as_ptr(),
};
pub static TEST_GLOBAL_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "test_global",
version: 5u32,
requests: &[
wayland_backend::protocol::MessageDesc {
name: "many_args",
signature: &[
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::Int,
wayland_backend::protocol::ArgumentType::Fixed,
wayland_backend::protocol::ArgumentType::Array,
wayland_backend::protocol::ArgumentType::Str(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Fd,
],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "get_secondary",
signature: &[wayland_backend::protocol::ArgumentType::NewId],
since: 2u32,
is_destructor: false,
child_interface: Some(&SECONDARY_INTERFACE),
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "get_tertiary",
signature: &[wayland_backend::protocol::ArgumentType::NewId],
since: 3u32,
is_destructor: false,
child_interface: Some(&TERTIARY_INTERFACE),
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "link",
signature: &[
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::Yes,
),
wayland_backend::protocol::ArgumentType::Uint,
],
since: 3u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[&SECONDARY_INTERFACE, &TERTIARY_INTERFACE],
},
wayland_backend::protocol::MessageDesc {
name: "destroy",
signature: &[],
since: 4u32,
is_destructor: true,
child_interface: None,
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "reverse_link",
signature: &[
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::Yes,
),
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::No,
),
],
since: 5u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[&SECONDARY_INTERFACE, &TERTIARY_INTERFACE],
},
wayland_backend::protocol::MessageDesc {
name: "newid_and_allow_null",
signature: &[
wayland_backend::protocol::ArgumentType::NewId,
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::Yes,
),
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::No,
),
],
since: 5u32,
is_destructor: false,
child_interface: Some(&QUAD_INTERFACE),
arg_interfaces: &[&SECONDARY_INTERFACE, &TERTIARY_INTERFACE],
},
],
events: &[
wayland_backend::protocol::MessageDesc {
name: "many_args_evt",
signature: &[
wayland_backend::protocol::ArgumentType::Uint,
wayland_backend::protocol::ArgumentType::Int,
wayland_backend::protocol::ArgumentType::Fixed,
wayland_backend::protocol::ArgumentType::Array,
wayland_backend::protocol::ArgumentType::Str(
wayland_backend::protocol::AllowNull::No,
),
wayland_backend::protocol::ArgumentType::Fd,
],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[],
},
wayland_backend::protocol::MessageDesc {
name: "ack_secondary",
signature: &[wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::No,
)],
since: 1u32,
is_destructor: false,
child_interface: None,
arg_interfaces: &[&SECONDARY_INTERFACE],
},
wayland_backend::protocol::MessageDesc {
name: "cycle_quad",
signature: &[
wayland_backend::protocol::ArgumentType::NewId,
wayland_backend::protocol::ArgumentType::Object(
wayland_backend::protocol::AllowNull::Yes,
),
],
since: 1u32,
is_destructor: false,
child_interface: Some(&QUAD_INTERFACE),
arg_interfaces: &[&QUAD_INTERFACE],
},
],
c_ptr: Some(unsafe { &test_global_interface }),
};
static test_global_requests_get_secondary_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 1],
> = SyncWrapper([&secondary_interface as *const wayland_backend::protocol::wl_interface]);
static test_global_requests_get_tertiary_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 1],
> = SyncWrapper([&tertiary_interface as *const wayland_backend::protocol::wl_interface]);
static test_global_requests_link_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 3],
> = SyncWrapper([
&secondary_interface as *const wayland_backend::protocol::wl_interface,
&tertiary_interface as *const wayland_backend::protocol::wl_interface,
null::<wayland_backend::protocol::wl_interface>(),
]);
static test_global_requests_reverse_link_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 2],
> = SyncWrapper([
&secondary_interface as *const wayland_backend::protocol::wl_interface,
&tertiary_interface as *const wayland_backend::protocol::wl_interface,
]);
static test_global_requests_newid_and_allow_null_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 3],
> = SyncWrapper([
&quad_interface as *const wayland_backend::protocol::wl_interface,
&secondary_interface as *const wayland_backend::protocol::wl_interface,
&tertiary_interface as *const wayland_backend::protocol::wl_interface,
]);
static test_global_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 7]> =
SyncWrapper([
wayland_backend::protocol::wl_message {
name: b"many_args\0" as *const u8 as *const std::os::raw::c_char,
signature: b"uifash\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"get_secondary\0" as *const u8 as *const std::os::raw::c_char,
signature: b"2n\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_requests_get_secondary_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"get_tertiary\0" as *const u8 as *const std::os::raw::c_char,
signature: b"3n\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_requests_get_tertiary_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"link\0" as *const u8 as *const std::os::raw::c_char,
signature: b"3o?ou\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_requests_link_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"destroy\0" as *const u8 as *const std::os::raw::c_char,
signature: b"4\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"reverse_link\0" as *const u8 as *const std::os::raw::c_char,
signature: b"5?oo\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_requests_reverse_link_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"newid_and_allow_null\0" as *const u8 as *const std::os::raw::c_char,
signature: b"5n?oo\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_requests_newid_and_allow_null_types.0.as_ptr(),
},
]);
static test_global_events_ack_secondary_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 1],
> = SyncWrapper([&secondary_interface as *const wayland_backend::protocol::wl_interface]);
static test_global_events_cycle_quad_types: SyncWrapper<
[*const wayland_backend::protocol::wl_interface; 2],
> = SyncWrapper([
&quad_interface as *const wayland_backend::protocol::wl_interface,
&quad_interface as *const wayland_backend::protocol::wl_interface,
]);
static test_global_events: SyncWrapper<[wayland_backend::protocol::wl_message; 3]> = SyncWrapper([
wayland_backend::protocol::wl_message {
name: b"many_args_evt\0" as *const u8 as *const std::os::raw::c_char,
signature: b"uifash\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"ack_secondary\0" as *const u8 as *const std::os::raw::c_char,
signature: b"o\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_events_ack_secondary_types.0.as_ptr(),
},
wayland_backend::protocol::wl_message {
name: b"cycle_quad\0" as *const u8 as *const std::os::raw::c_char,
signature: b"n?o\0" as *const u8 as *const std::os::raw::c_char,
types: test_global_events_cycle_quad_types.0.as_ptr(),
},
]);
pub static test_global_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"test_global\0" as *const u8 as *const std::os::raw::c_char,
version: 5,
request_count: 7,
requests: test_global_requests.0.as_ptr(),
event_count: 3,
events: test_global_events.0.as_ptr(),
};
pub static SECONDARY_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "secondary",
version: 5u32,
requests: &[wayland_backend::protocol::MessageDesc {
name: "destroy",
signature: &[],
since: 2u32,
is_destructor: true,
child_interface: None,
arg_interfaces: &[],
}],
events: &[],
c_ptr: Some(unsafe { &secondary_interface }),
};
static secondary_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 1]> =
SyncWrapper([wayland_backend::protocol::wl_message {
name: b"destroy\0" as *const u8 as *const std::os::raw::c_char,
signature: b"2\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
}]);
pub static secondary_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"secondary\0" as *const u8 as *const std::os::raw::c_char,
version: 5,
request_count: 1,
requests: secondary_requests.0.as_ptr(),
event_count: 0,
events: null::<wayland_backend::protocol::wl_message>(),
};
pub static TERTIARY_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "tertiary",
version: 5u32,
requests: &[wayland_backend::protocol::MessageDesc {
name: "destroy",
signature: &[],
since: 3u32,
is_destructor: true,
child_interface: None,
arg_interfaces: &[],
}],
events: &[],
c_ptr: Some(unsafe { &tertiary_interface }),
};
static tertiary_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 1]> =
SyncWrapper([wayland_backend::protocol::wl_message {
name: b"destroy\0" as *const u8 as *const std::os::raw::c_char,
signature: b"3\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
}]);
pub static tertiary_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"tertiary\0" as *const u8 as *const std::os::raw::c_char,
version: 5,
request_count: 1,
requests: tertiary_requests.0.as_ptr(),
event_count: 0,
events: null::<wayland_backend::protocol::wl_message>(),
};
pub static QUAD_INTERFACE: wayland_backend::protocol::Interface =
wayland_backend::protocol::Interface {
name: "quad",
version: 5u32,
requests: &[wayland_backend::protocol::MessageDesc {
name: "destroy",
signature: &[],
since: 3u32,
is_destructor: true,
child_interface: None,
arg_interfaces: &[],
}],
events: &[],
c_ptr: Some(unsafe { &quad_interface }),
};
static quad_requests: SyncWrapper<[wayland_backend::protocol::wl_message; 1]> =
SyncWrapper([wayland_backend::protocol::wl_message {
name: b"destroy\0" as *const u8 as *const std::os::raw::c_char,
signature: b"3\0" as *const u8 as *const std::os::raw::c_char,
types: types_null.0.as_ptr(),
}]);
pub static quad_interface: wayland_backend::protocol::wl_interface =
wayland_backend::protocol::wl_interface {
name: b"quad\0" as *const u8 as *const std::os::raw::c_char,
version: 5,
request_count: 1,
requests: quad_requests.0.as_ptr(),
event_count: 0,
events: null::<wayland_backend::protocol::wl_message>(),
};
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="test-protocol">
<copyright>
A dummy copyright to make the parser work
Including a few more lines.
Of text.
</copyright>
<!-- Include the core interfaces display/registry/callback -->
<interface name="wl_display" version="1">
<description summary="core global object">
The core global object. This is a special singleton object. It
is used for internal Wayland protocol features.
</description>
<request name="sync">
<description summary="asynchronous roundtrip">
The sync request asks the server to emit the 'done' event
on the returned wl_callback object. Since requests are
handled in-order and events are delivered in-order, this can
be used as a barrier to ensure all previous requests and the
resulting events have been handled.
The object returned by this request will be destroyed by the
compositor after the callback is fired and as such the client must not
attempt to use it after that point.
The callback_data passed in the callback is the event serial.
</description>
<arg name="callback" type="new_id" interface="wl_callback"
summary="callback object for the sync request"/>
</request>
<request name="get_registry">
<description summary="get global registry object">
This request creates a registry object that allows the client
to list and bind the global objects available from the
compositor.
It should be noted that the server side resources consumed in
response to a get_registry request can only be released when the
client disconnects, not when the client side proxy is destroyed.
Therefore, clients should invoke get_registry as infrequently as
possible to avoid wasting memory.
</description>
<arg name="registry" type="new_id" interface="wl_registry"
summary="global registry object"/>
</request>
<event name="error">
<description summary="fatal error event">
The error event is sent out when a fatal (non-recoverable)
error has occurred. The object_id argument is the object
where the error occurred, most often in response to a request
to that object. The code identifies the error and is defined
by the object interface. As such, each interface defines its
own set of error codes. The message is a brief description
of the error, for (debugging) convenience.
</description>
<arg name="object_id" type="object" summary="object where the error occurred"/>
<arg name="code" type="uint" summary="error code"/>
<arg name="message" type="string" summary="error description"/>
</event>
<enum name="error">
<description summary="global error values">
These errors are global and can be emitted in response to any
server request.
</description>
<entry name="invalid_object" value="0"
summary="server couldn't find object"/>
<entry name="invalid_method" value="1"
summary="method doesn't exist on the specified interface or malformed request"/>
<entry name="no_memory" value="2"
summary="server is out of memory"/>
<entry name="implementation" value="3"
summary="implementation error in compositor"/>
</enum>
<event name="delete_id">
<description summary="acknowledge object ID deletion">
This event is used internally by the object ID management
logic. When a client deletes an object that it had created,
the server will send this event to acknowledge that it has
seen the delete request. When the client receives this event,
it will know that it can safely reuse the object ID.
</description>
<arg name="id" type="uint" summary="deleted object ID"/>
</event>
</interface>
<interface name="wl_registry" version="1">
<description summary="global registry object">
The singleton global registry object. The server has a number of
global objects that are available to all clients. These objects
typically represent an actual object in the server (for example,
an input device) or they are singleton objects that provide
extension functionality.
When a client creates a registry object, the registry object
will emit a global event for each global currently in the
registry. Globals come and go as a result of device or
monitor hotplugs, reconfiguration or other events, and the
registry will send out global and global_remove events to
keep the client up to date with the changes. To mark the end
of the initial burst of events, the client can use the
wl_display.sync request immediately after calling
wl_display.get_registry.
A client can bind to a global object by using the bind
request. This creates a client-side handle that lets the object
emit events to the client and lets the client invoke requests on
the object.
</description>
<request name="bind">
<description summary="bind an object to the display">
Binds a new, client-created object to the server using the
specified name as the identifier.
</description>
<arg name="name" type="uint" summary="unique numeric name of the object"/>
<arg name="id" type="new_id" summary="bounded object"/>
</request>
<event name="global">
<description summary="announce global object">
Notify the client of global objects.
The event notifies the client that a global object with
the given name is now available, and it implements the
given version of the given interface.
</description>
<arg name="name" type="uint" summary="numeric name of the global object"/>
<arg name="interface" type="string" summary="interface implemented by the object"/>
<arg name="version" type="uint" summary="interface version"/>
</event>
<event name="global_remove">
<description summary="announce removal of global object">
Notify the client of removed global objects.
This event notifies the client that the global identified
by name is no longer available. If the client bound to
the global using the bind request, the client should now
destroy that object.
The object remains valid and requests to the object will be
ignored until the client destroys it, to avoid races between
the global going away and a client sending a request to it.
</description>
<arg name="name" type="uint" summary="numeric name of the global object"/>
</event>
</interface>
<interface name="wl_callback" version="1">
<description summary="callback object">
Clients can handle the 'done' event to get notified when
the related request is done.
</description>
<event name="done" type="destructor">
<description summary="done event">
Notify the client when the related request is done.
</description>
<arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
</event>
</interface>
<!-- And now the test interfaces -->
<interface name="test_global" version="5">
<request name="many_args">
<description summary="a request with every possible non-object arg"></description>
<arg name="unsigned_int" type="uint" summary="an unsigned int" />
<arg name="signed_int" type="int" summary="a singed int" />
<arg name="fixed_point" type="fixed" summary="a fixed point number" />
<arg name="number_array" type="array" summary="an array" />
<arg name="some_text" type="string" summary="some text" />
<arg name="file_descriptor" type="fd" summary="a file descriptor" />
</request>
<request name="get_secondary" since="2">
<arg name="sec" type="new_id" interface="secondary" summary="create a secondary" />
</request>
<request name="get_tertiary" since="3">
<arg name="ter" type="new_id" interface="tertiary" summary="create a tertiary" />
</request>
<request name="link" since="3">
<description summary="link a secondary and a tertiary"></description>
<arg name="sec" type="object" interface="secondary" />
<arg name="ter" type="object" interface="tertiary" allow-null="true" />
<arg name="time" type="uint" />
</request>
<request name="destroy" type="destructor" since="4">
</request>
<request name="reverse_link" since="5">
<description summary="reverse link a secondary and a tertiary"></description>
<arg name="sec" type="object" interface="secondary" allow-null="true" />
<arg name="ter" type="object" interface="tertiary" />
</request>
<request name="newid_and_allow_null" since="5">
<description summary="a newid request that also takes allow null arg"></description>
<arg name="quad" type="new_id" interface="quad" />
<arg name="sec" type="object" interface="secondary" allow-null="true" />
<arg name="ter" type="object" interface="tertiary" />
</request>
<event name="many_args_evt">
<description summary="an event with every possible non-object arg"></description>
<arg name="unsigned_int" type="uint" summary="an unsigned int" />
<arg name="signed_int" type="int" summary="a singed int" />
<arg name="fixed_point" type="fixed" summary="a fixed point number" />
<arg name="number_array" type="array" summary="an array" />
<arg name="some_text" type="string" summary="some text" />
<arg name="file_descriptor" type="fd" summary="a file descriptor" />
</event>
<event name="ack_secondary">
<description summary="acking the creation of a secondary"></description>
<arg name="sec" type="object" interface="secondary" />
</event>
<event name="cycle_quad">
<description summary="create a new quad optionally replacing a previous one"></description>
<arg name="new_quad" type="new_id" interface="quad" />
<arg name="old_quad" type="object" interface="quad" allow-null="true" />
</event>
</interface>
<interface name="secondary" version="5">
<request name="destroy" type="destructor" since="2">
</request>
</interface>
<interface name="tertiary" version="5">
<request name="destroy" type="destructor" since="3">
</request>
</interface>
<interface name="quad" version="5">
<request name="destroy" type="destructor" since="3">
</request>
</interface>
</protocol>
File diff suppressed because it is too large Load Diff