webview2_com_callback_macros/
lib.rs1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4 parenthesized,
5 parse::{Parse, ParseStream},
6 parse_macro_input,
7 punctuated::Punctuated,
8 Ident, Result, Token, TypePath, Visibility,
9};
10
11struct CallbackTypes {
12 pub interface: TypePath,
13 pub arg_1: TypePath,
14 pub arg_2: Option<TypePath>,
15}
16
17impl Parse for CallbackTypes {
18 fn parse(input: ParseStream) -> Result<Self> {
19 let content;
20 parenthesized!(content in input);
21 let args: Punctuated<TypePath, Token![,]> = content.parse_terminated(TypePath::parse)?;
22 input.parse::<Token![;]>()?;
23 let mut args = args.into_iter();
24 if let (Some(interface), Some(arg_1), arg_2) = (args.next(), args.next(), args.next()) {
25 Ok(CallbackTypes {
26 interface,
27 arg_1,
28 arg_2,
29 })
30 } else {
31 Err(content.error("expected (interface, arg_1, arg_2)"))
32 }
33 }
34}
35
36struct CallbackStruct {
37 pub vis: Visibility,
38 _struct_token: Token![struct],
39 pub ident: Ident,
40 pub args: CallbackTypes,
41}
42
43impl Parse for CallbackStruct {
44 fn parse(input: ParseStream) -> Result<Self> {
45 Ok(CallbackStruct {
46 vis: input.parse()?,
47 _struct_token: input.parse()?,
48 ident: input.parse()?,
49 args: input.parse()?,
50 })
51 }
52}
53
54#[proc_macro_attribute]
56pub fn completed_callback(_attr: TokenStream, input: TokenStream) -> TokenStream {
57 let ast = parse_macro_input!(input as CallbackStruct);
58 impl_completed_callback(&ast)
59}
60
61fn impl_completed_callback(ast: &CallbackStruct) -> TokenStream {
62 let vis = &ast.vis;
63
64 let name = &ast.ident;
65 let closure = get_closure(name);
66 let interface = &ast.args.interface;
67
68 let arg_1 = &ast.args.arg_1;
69 let arg_2 = &ast.args.arg_2;
70
71 let msg = match interface.path.segments.last() {
72 Some(interface) => format!("Implementation of [`{}`].", interface.ident),
73 None => String::from("Implementation of unknown [`completed_callback`] interface."),
74 };
75
76 let gen = match arg_2 {
77 Some(arg_2) => quote! {
78 type #closure = CompletedClosure<#arg_1, #arg_2>;
79
80 #[doc = #msg]
81 #[implement(Microsoft::Web::WebView2::Win32::#interface)]
82 #vis struct #name(Option<#closure>);
83
84 #[allow(non_snake_case)]
85 impl #name {
86 pub fn create(
87 closure: #closure,
88 ) -> #interface {
89 Self(Some(closure)).into()
90 }
91
92 pub fn wait_for_async_operation(
93 closure: Box<
94 dyn FnOnce(#interface) -> crate::Result<()>,
95 >,
96 completed: #closure,
97 ) -> crate::Result<()> {
98 let (tx, rx) = mpsc::channel();
99 let completed: #closure =
100 Box::new(move |arg_1, arg_2| -> ::windows::Result<()> {
101 let result = completed(arg_1, arg_2).map_err(crate::Error::WindowsError);
102 tx.send(result).expect("send over mpsc channel");
103 Ok(())
104 });
105 let callback = Self::create(completed);
106
107 closure(callback)?;
108 crate::wait_with_pump(rx)?
109 }
110
111 fn Invoke<'a>(
112 &mut self,
113 arg_1: <#arg_1 as InvokeArg<'a>>::Input,
114 arg_2: <#arg_2 as InvokeArg<'a>>::Input,
115 ) -> ::windows::Result<()> {
116 match self.0.take() {
117 Some(completed) => completed(
118 <#arg_1 as InvokeArg<'a>>::convert(arg_1),
119 <#arg_2 as InvokeArg<'a>>::convert(arg_2),
120 ),
121 None => Ok(()),
122 }
123 }
124 }
125 },
126 None => quote! {
127 type #closure = Box<dyn FnOnce(<#arg_1 as ClosureArg>::Output) -> ::windows::Result<()>>;
128
129 #[doc = #msg]
130 #[implement(Microsoft::Web::WebView2::Win32::#interface)]
131 #vis struct #name(Option<#closure>);
132
133 #[allow(non_snake_case)]
134 impl #name {
135 pub fn create(
136 closure: #closure,
137 ) -> #interface {
138 Self(Some(closure)).into()
139 }
140
141 pub fn wait_for_async_operation(
142 closure: Box<
143 dyn FnOnce(#interface) -> crate::Result<()>,
144 >,
145 completed: #closure,
146 ) -> crate::Result<()> {
147 let (tx, rx) = mpsc::channel();
148 let completed: #closure =
149 Box::new(move |arg_1| -> ::windows::Result<()> {
150 let result = completed(arg_1).map_err(crate::Error::WindowsError);
151 tx.send(result).expect("send over mpsc channel");
152 Ok(())
153 });
154 let callback = Self::create(completed);
155
156 closure(callback)?;
157 crate::wait_with_pump(rx)?
158 }
159
160 fn Invoke<'a>(
161 &mut self,
162 arg_1: <#arg_1 as InvokeArg<'a>>::Input,
163 ) -> ::windows::Result<()> {
164 match self.0.take() {
165 Some(completed) => completed(
166 <#arg_1 as InvokeArg<'a>>::convert(arg_1),
167 ),
168 None => Ok(()),
169 }
170 }
171 }
172 },
173 };
174
175 gen.into()
176}
177
178#[proc_macro_attribute]
180pub fn event_callback(_attr: TokenStream, input: TokenStream) -> TokenStream {
181 let ast = parse_macro_input!(input as CallbackStruct);
182 impl_event_callback(&ast)
183}
184
185fn impl_event_callback(ast: &CallbackStruct) -> TokenStream {
186 let vis = &ast.vis;
187
188 let name = &ast.ident;
189 let closure = get_closure(name);
190
191 let interface = &ast.args.interface;
192
193 let arg_1 = &ast.args.arg_1;
194 let arg_2 = &ast
195 .args
196 .arg_2
197 .as_ref()
198 .expect("event_callback should always have 2 arguments");
199
200 let msg = match interface.path.segments.last() {
201 Some(interface) => format!("Implementation of [`{}`].", interface.ident),
202 None => String::from("Implementation of unknown [`event_callback`] interface."),
203 };
204
205 let gen = quote! {
206 type #closure = EventClosure<#arg_1, #arg_2>;
207
208 #[doc = #msg]
209 #[implement(Microsoft::Web::WebView2::Win32::#interface)]
210 #vis struct #name(#closure);
211
212 #[allow(non_snake_case)]
213 impl #name {
214 pub fn create(
215 closure: #closure,
216 ) -> #interface {
217 Self(closure).into()
218 }
219
220 fn Invoke<'a>(
221 &mut self,
222 arg_1: <#arg_1 as InvokeArg<'a>>::Input,
223 arg_2: <#arg_2 as InvokeArg<'a>>::Input,
224 ) -> ::windows::Result<()> {
225 self.0(
226 <#arg_1 as InvokeArg<'a>>::convert(arg_1),
227 <#arg_2 as InvokeArg<'a>>::convert(arg_2),
228 )
229 }
230 }
231 };
232
233 gen.into()
234}
235
236fn get_closure(name: &Ident) -> Ident {
237 format_ident!("{}Closure", name)
238}