1 | //! Cookies are handles to future replies or errors from the X11 server. |
2 | //! |
3 | //! When sending a request, you get back a cookie. There are different cookies for different |
4 | //! kinds of requests. |
5 | //! |
6 | //! For requests without a reply, you get a [`VoidCookie`]. Requests with a reply are represented |
7 | //! by a [`Cookie`] or a [`CookieWithFds`] if the reply also contains file descriptors. |
8 | //! Additionally, there are two special cases for requests which generate more than one reply: |
9 | //! [`ListFontsWithInfoCookie`] and [`RecordEnableContextCookie`]. |
10 | //! |
11 | //! # Handling X11 errors |
12 | //! |
13 | //! The X11 server can answer requests with an error packet for various reasons, e.g. because an |
14 | //! invalid window ID was given. There are three options what can be done with errors: |
15 | //! |
16 | //! - Errors can appear as X11 events in `wait_for_event()` (in XCB, this is called "unchecked") |
17 | //! - Errors can be checked for locally after a request was sent (in XCB, this is called "checked") |
18 | //! - Errors can be completely ignored (the closest analog in XCB would be `xcb_discard_reply()`) |
19 | //! |
20 | //! There is an additional difference between requests with and without replies. |
21 | //! |
22 | //! ## Requests without a reply |
23 | //! |
24 | //! For requests that do not have a reply, you get an instance of `VoidCookie` after sending the |
25 | //! request. The different behaviors can be achieved via interacting with this cookie as foolows: |
26 | //! |
27 | //! | What? | How? | |
28 | //! | --------------- | -------------------------- | |
29 | //! | Treat as events | Just drop the cookie | |
30 | //! | Check locally | `VoidCookie::check` | |
31 | //! | Ignore | `VoidCookie::ignore_error` | |
32 | //! |
33 | //! ## Requests with a reply |
34 | //! |
35 | //! For requests with a reply, an additional option is what should happen to the reply. You can get |
36 | //! the reply, but any errors are still treated as events. This allows to centralise X11 error |
37 | //! handling a bit in case you only want to log errors. |
38 | //! |
39 | //! The following things can be done with the `Cookie` that you get after sending a request with an |
40 | //! error. |
41 | //! |
42 | //! | Reply | Errors locally/ignored | Errors as events | |
43 | //! | ------ | ---------------------------------- | ------------------------- | |
44 | //! | Get | `Cookie::reply` | `Cookie::reply_unchecked` | |
45 | //! | Ignore | `Cookie::discard_reply_and_errors` | Just drop the cookie | |
46 | |
47 | use std::marker::PhantomData; |
48 | |
49 | use crate::connection::{BufWithFds, RequestConnection, RequestKind}; |
50 | use crate::errors::{ConnectionError, ReplyError}; |
51 | #[cfg (feature = "record" )] |
52 | use crate::protocol::record::EnableContextReply; |
53 | use crate::protocol::xproto::ListFontsWithInfoReply; |
54 | use crate::x11_utils::{TryParse, TryParseFd}; |
55 | |
56 | use x11rb_protocol::{DiscardMode, SequenceNumber}; |
57 | |
58 | /// A handle to a possible error from the X11 server. |
59 | /// |
60 | /// When sending a request for which no reply is expected, this library returns a `VoidCookie`. |
61 | /// This `VoidCookie` can then later be used to check if the X11 server sent an error. |
62 | /// |
63 | /// See [crate::cookie#requests-without-a-reply] for infos on the different ways to handle X11 |
64 | /// errors in response to a request. |
65 | #[derive (Debug)] |
66 | pub struct VoidCookie<'a, C> |
67 | where |
68 | C: RequestConnection + ?Sized, |
69 | { |
70 | connection: &'a C, |
71 | sequence_number: SequenceNumber, |
72 | } |
73 | |
74 | impl<'a, C> VoidCookie<'a, C> |
75 | where |
76 | C: RequestConnection + ?Sized, |
77 | { |
78 | /// Construct a new cookie. |
79 | /// |
80 | /// This function should only be used by implementations of |
81 | /// `Connection::send_request_without_reply`. |
82 | pub fn new(connection: &C, sequence_number: SequenceNumber) -> VoidCookie<'_, C> { |
83 | VoidCookie { |
84 | connection, |
85 | sequence_number, |
86 | } |
87 | } |
88 | |
89 | /// Get the sequence number of the request that generated this cookie. |
90 | pub fn sequence_number(&self) -> SequenceNumber { |
91 | self.sequence_number |
92 | } |
93 | |
94 | fn consume(self) -> (&'a C, SequenceNumber) { |
95 | let result = (self.connection, self.sequence_number); |
96 | std::mem::forget(self); |
97 | result |
98 | } |
99 | |
100 | /// Check if the original request caused an X11 error. |
101 | pub fn check(self) -> Result<(), ReplyError> { |
102 | let (connection, sequence) = self.consume(); |
103 | connection.check_for_error(sequence) |
104 | } |
105 | |
106 | /// Ignore all errors to this request. |
107 | /// |
108 | /// Without calling this method, an error becomes available on the connection as an event after |
109 | /// this cookie was dropped. This function causes errors to be ignored instead. |
110 | pub fn ignore_error(self) { |
111 | let (connection, sequence) = self.consume(); |
112 | connection.discard_reply( |
113 | sequence, |
114 | RequestKind::IsVoid, |
115 | DiscardMode::DiscardReplyAndError, |
116 | ) |
117 | } |
118 | |
119 | /// Move this cookie to refer to another connection instance. |
120 | /// |
121 | /// This function may only be used if both connections are "basically the same". For example, a |
122 | /// Cookie for a connection `C` can be moved to `Rc<C>` since that still refers to the same |
123 | /// underlying connection. |
124 | pub(crate) fn replace_connection<C2: RequestConnection + ?Sized>( |
125 | self, |
126 | connection: &C2, |
127 | ) -> VoidCookie<'_, C2> { |
128 | let (_, sequence_number) = self.consume(); |
129 | VoidCookie { |
130 | connection, |
131 | sequence_number, |
132 | } |
133 | } |
134 | } |
135 | |
136 | impl<C> Drop for VoidCookie<'_, C> |
137 | where |
138 | C: RequestConnection + ?Sized, |
139 | { |
140 | fn drop(&mut self) { |
141 | self.connection.discard_reply( |
142 | self.sequence_number, |
143 | kind:RequestKind::IsVoid, |
144 | mode:DiscardMode::DiscardReply, |
145 | ) |
146 | } |
147 | } |
148 | |
149 | /// Internal helper for a cookie with an response |
150 | #[derive (Debug)] |
151 | struct RawCookie<'a, C> |
152 | where |
153 | C: RequestConnection + ?Sized, |
154 | { |
155 | connection: &'a C, |
156 | sequence_number: SequenceNumber, |
157 | } |
158 | |
159 | impl<C> RawCookie<'_, C> |
160 | where |
161 | C: RequestConnection + ?Sized, |
162 | { |
163 | /// Construct a new raw cookie. |
164 | /// |
165 | /// This function should only be used by implementations of |
166 | /// `RequestConnection::send_request_with_reply`. |
167 | fn new(connection: &C, sequence_number: SequenceNumber) -> RawCookie<'_, C> { |
168 | RawCookie { |
169 | connection, |
170 | sequence_number, |
171 | } |
172 | } |
173 | |
174 | /// Consume this instance and get the contained sequence number out. |
175 | fn into_sequence_number(self) -> SequenceNumber { |
176 | let number = self.sequence_number; |
177 | // Prevent drop() from running |
178 | std::mem::forget(self); |
179 | number |
180 | } |
181 | |
182 | /// Move this cookie to refer to another connection instance. |
183 | /// |
184 | /// This function may only be used if both connections are "basically the same". For example, a |
185 | /// Cookie for a connection `C` can be moved to `Rc<C>` since that still refers to the same |
186 | /// underlying connection. |
187 | fn replace_connection<C2: RequestConnection + ?Sized>( |
188 | self, |
189 | connection: &C2, |
190 | ) -> RawCookie<'_, C2> { |
191 | RawCookie { |
192 | connection, |
193 | sequence_number: self.into_sequence_number(), |
194 | } |
195 | } |
196 | } |
197 | |
198 | impl<C> Drop for RawCookie<'_, C> |
199 | where |
200 | C: RequestConnection + ?Sized, |
201 | { |
202 | fn drop(&mut self) { |
203 | self.connection.discard_reply( |
204 | self.sequence_number, |
205 | kind:RequestKind::HasResponse, |
206 | mode:DiscardMode::DiscardReply, |
207 | ); |
208 | } |
209 | } |
210 | |
211 | /// A handle to a response from the X11 server. |
212 | /// |
213 | /// When sending a request to the X11 server, this library returns a `Cookie`. This `Cookie` can |
214 | /// then later be used to get the response that the server sent. |
215 | /// |
216 | /// See [crate::cookie#requests-with-a-reply] for infos on the different ways to handle X11 |
217 | /// errors in response to a request. |
218 | #[derive (Debug)] |
219 | pub struct Cookie<'a, C, R> |
220 | where |
221 | C: RequestConnection + ?Sized, |
222 | { |
223 | raw_cookie: RawCookie<'a, C>, |
224 | phantom: PhantomData<R>, |
225 | } |
226 | |
227 | impl<C, R> Cookie<'_, C, R> |
228 | where |
229 | R: TryParse, |
230 | C: RequestConnection + ?Sized, |
231 | { |
232 | /// Construct a new cookie. |
233 | /// |
234 | /// This function should only be used by implementations of |
235 | /// `RequestConnection::send_request_with_reply`. |
236 | pub fn new(connection: &C, sequence_number: SequenceNumber) -> Cookie<'_, C, R> { |
237 | Cookie { |
238 | raw_cookie: RawCookie::new(connection, sequence_number), |
239 | phantom: PhantomData, |
240 | } |
241 | } |
242 | |
243 | /// Get the sequence number of the request that generated this cookie. |
244 | pub fn sequence_number(&self) -> SequenceNumber { |
245 | self.raw_cookie.sequence_number |
246 | } |
247 | |
248 | /// Get the raw reply that the server sent. |
249 | pub fn raw_reply(self) -> Result<C::Buf, ReplyError> { |
250 | let conn = self.raw_cookie.connection; |
251 | conn.wait_for_reply_or_error(self.raw_cookie.into_sequence_number()) |
252 | } |
253 | |
254 | /// Get the raw reply that the server sent, but have errors handled as events. |
255 | pub fn raw_reply_unchecked(self) -> Result<Option<C::Buf>, ConnectionError> { |
256 | let conn = self.raw_cookie.connection; |
257 | conn.wait_for_reply(self.raw_cookie.into_sequence_number()) |
258 | } |
259 | |
260 | /// Get the reply that the server sent. |
261 | pub fn reply(self) -> Result<R, ReplyError> { |
262 | Ok(R::try_parse(self.raw_reply()?.as_ref())?.0) |
263 | } |
264 | |
265 | /// Get the reply that the server sent, but have errors handled as events. |
266 | pub fn reply_unchecked(self) -> Result<Option<R>, ConnectionError> { |
267 | self.raw_reply_unchecked()? |
268 | .map(|buf| R::try_parse(buf.as_ref()).map(|r| r.0)) |
269 | .transpose() |
270 | .map_err(Into::into) |
271 | } |
272 | |
273 | /// Discard all responses to the request this cookie represents, even errors. |
274 | /// |
275 | /// Without this function, errors are treated as events after the cookie is dropped. |
276 | pub fn discard_reply_and_errors(self) { |
277 | let conn = self.raw_cookie.connection; |
278 | conn.discard_reply( |
279 | self.raw_cookie.into_sequence_number(), |
280 | RequestKind::HasResponse, |
281 | DiscardMode::DiscardReplyAndError, |
282 | ) |
283 | } |
284 | |
285 | /// Consume this instance and get the contained sequence number out. |
286 | pub(crate) fn into_sequence_number(self) -> SequenceNumber { |
287 | self.raw_cookie.into_sequence_number() |
288 | } |
289 | |
290 | /// Move this cookie to refer to another connection instance. |
291 | /// |
292 | /// This function may only be used if both connections are "basically the same". For example, a |
293 | /// Cookie for a connection `C` can be moved to `Rc<C>` since that still refers to the same |
294 | /// underlying connection. |
295 | pub(crate) fn replace_connection<C2: RequestConnection + ?Sized>( |
296 | self, |
297 | connection: &C2, |
298 | ) -> Cookie<'_, C2, R> { |
299 | Cookie { |
300 | raw_cookie: self.raw_cookie.replace_connection(connection), |
301 | phantom: PhantomData, |
302 | } |
303 | } |
304 | } |
305 | |
306 | /// A handle to a response containing `RawFd` from the X11 server. |
307 | /// |
308 | /// When sending a request to the X11 server, this library returns a `Cookie`. This `Cookie` can |
309 | /// then later be used to get the response that the server sent. |
310 | /// |
311 | /// This variant of `Cookie` represents a response that can contain `RawFd`s. |
312 | /// |
313 | /// See [crate::cookie#requests-with-a-reply] for infos on the different ways to handle X11 |
314 | /// errors in response to a request. |
315 | #[derive (Debug)] |
316 | pub struct CookieWithFds<'a, C, R> |
317 | where |
318 | C: RequestConnection + ?Sized, |
319 | { |
320 | raw_cookie: RawCookie<'a, C>, |
321 | phantom: PhantomData<R>, |
322 | } |
323 | |
324 | impl<C, R> CookieWithFds<'_, C, R> |
325 | where |
326 | R: TryParseFd, |
327 | C: RequestConnection + ?Sized, |
328 | { |
329 | /// Construct a new cookie. |
330 | /// |
331 | /// This function should only be used by implementations of |
332 | /// `RequestConnection::send_request_with_reply`. |
333 | pub fn new(connection: &C, sequence_number: SequenceNumber) -> CookieWithFds<'_, C, R> { |
334 | CookieWithFds { |
335 | raw_cookie: RawCookie::new(connection, sequence_number), |
336 | phantom: PhantomData, |
337 | } |
338 | } |
339 | |
340 | /// Get the sequence number of the request that generated this cookie. |
341 | pub fn sequence_number(&self) -> SequenceNumber { |
342 | self.raw_cookie.sequence_number |
343 | } |
344 | |
345 | /// Get the raw reply that the server sent. |
346 | pub fn raw_reply(self) -> Result<BufWithFds<C::Buf>, ReplyError> { |
347 | let conn = self.raw_cookie.connection; |
348 | conn.wait_for_reply_with_fds(self.raw_cookie.into_sequence_number()) |
349 | } |
350 | |
351 | /// Get the reply that the server sent. |
352 | pub fn reply(self) -> Result<R, ReplyError> { |
353 | let (buffer, mut fds) = self.raw_reply()?; |
354 | Ok(R::try_parse_fd(buffer.as_ref(), &mut fds)?.0) |
355 | } |
356 | |
357 | /// Move this cookie to refer to another connection instance. |
358 | /// |
359 | /// This function may only be used if both connections are "basically the same". For example, a |
360 | /// Cookie for a connection `C` can be moved to `Rc<C>` since that still refers to the same |
361 | /// underlying connection. |
362 | pub(crate) fn replace_connection<C2: RequestConnection + ?Sized>( |
363 | self, |
364 | connection: &C2, |
365 | ) -> CookieWithFds<'_, C2, R> { |
366 | CookieWithFds { |
367 | raw_cookie: self.raw_cookie.replace_connection(connection), |
368 | phantom: PhantomData, |
369 | } |
370 | } |
371 | } |
372 | |
373 | macro_rules! multiple_reply_cookie { |
374 | ( |
375 | $(#[$meta:meta])* |
376 | pub struct $name:ident for $reply:ident |
377 | ) => { |
378 | $(#[$meta])* |
379 | #[derive(Debug)] |
380 | pub struct $name<'a, C: RequestConnection + ?Sized>(Option<RawCookie<'a, C>>); |
381 | |
382 | impl<'c, C> $name<'c, C> |
383 | where |
384 | C: RequestConnection + ?Sized, |
385 | { |
386 | pub(crate) fn new( |
387 | cookie: Cookie<'c, C, $reply>, |
388 | ) -> Self { |
389 | Self(Some(cookie.raw_cookie)) |
390 | } |
391 | |
392 | /// Get the sequence number of the request that generated this cookie. |
393 | pub fn sequence_number(&self) -> Option<SequenceNumber> { |
394 | self.0.as_ref().map(|x| x.sequence_number) |
395 | } |
396 | } |
397 | |
398 | impl<C> Iterator for $name<'_, C> |
399 | where |
400 | C: RequestConnection + ?Sized, |
401 | { |
402 | type Item = Result<$reply, ReplyError>; |
403 | |
404 | fn next(&mut self) -> Option<Self::Item> { |
405 | let cookie = self.0.take()?; |
406 | let reply = cookie |
407 | .connection |
408 | .wait_for_reply_or_error(cookie.sequence_number); |
409 | let reply = match reply { |
410 | Err(e) => return Some(Err(e)), |
411 | Ok(v) => v, |
412 | }; |
413 | let reply = $reply::try_parse(reply.as_ref()); |
414 | match reply { |
415 | // Is this an indicator that no more replies follow? |
416 | Ok(ref reply) if Self::is_last(&reply.0) => None, |
417 | Ok(reply) => { |
418 | self.0 = Some(cookie); |
419 | Some(Ok(reply.0)) |
420 | } |
421 | Err(e) => Some(Err(e.into())), |
422 | } |
423 | } |
424 | } |
425 | } |
426 | } |
427 | |
428 | multiple_reply_cookie!( |
429 | /// A handle to the replies to a `ListFontsWithInfo` request. |
430 | /// |
431 | /// `ListFontsWithInfo` generated more than one reply, but `Cookie` only allows getting one reply. |
432 | /// This structure implements `Iterator` and allows to get all the replies. |
433 | pub struct ListFontsWithInfoCookie for ListFontsWithInfoReply |
434 | ); |
435 | |
436 | impl<C> ListFontsWithInfoCookie<'_, C> |
437 | where |
438 | C: RequestConnection + ?Sized, |
439 | { |
440 | fn is_last(reply: &ListFontsWithInfoReply) -> bool { |
441 | reply.name.is_empty() |
442 | } |
443 | } |
444 | |
445 | #[cfg (feature = "record" )] |
446 | multiple_reply_cookie!( |
447 | /// A handle to the replies to a `record::EnableContext` request. |
448 | /// |
449 | /// `EnableContext` generated more than one reply, but `Cookie` only allows getting one reply. |
450 | /// This structure implements `Iterator` and allows to get all the replies. |
451 | pub struct RecordEnableContextCookie for EnableContextReply |
452 | ); |
453 | |
454 | #[cfg (feature = "record" )] |
455 | impl<C> RecordEnableContextCookie<'_, C> |
456 | where |
457 | C: RequestConnection + ?Sized, |
458 | { |
459 | fn is_last(reply: &EnableContextReply) -> bool { |
460 | // FIXME: There does not seem to be an enumeration of the category values, (value 5 is |
461 | // EndOfData) |
462 | reply.category == 5 |
463 | } |
464 | } |
465 | |