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 | |
120 | impl<C> Drop for VoidCookie<'_, C> |
121 | where |
122 | C: RequestConnection + ?Sized, |
123 | { |
124 | fn drop(&mut self) { |
125 | self.connection.discard_reply( |
126 | self.sequence_number, |
127 | kind:RequestKind::IsVoid, |
128 | mode:DiscardMode::DiscardReply, |
129 | ) |
130 | } |
131 | } |
132 | |
133 | /// Internal helper for a cookie with an response |
134 | #[derive (Debug)] |
135 | struct RawCookie<'a, C> |
136 | where |
137 | C: RequestConnection + ?Sized, |
138 | { |
139 | connection: &'a C, |
140 | sequence_number: SequenceNumber, |
141 | } |
142 | |
143 | impl<C> RawCookie<'_, C> |
144 | where |
145 | C: RequestConnection + ?Sized, |
146 | { |
147 | /// Construct a new raw cookie. |
148 | /// |
149 | /// This function should only be used by implementations of |
150 | /// `RequestConnection::send_request_with_reply`. |
151 | fn new(connection: &C, sequence_number: SequenceNumber) -> RawCookie<'_, C> { |
152 | RawCookie { |
153 | connection, |
154 | sequence_number, |
155 | } |
156 | } |
157 | |
158 | /// Consume this instance and get the contained sequence number out. |
159 | fn into_sequence_number(self) -> SequenceNumber { |
160 | let number: u64 = self.sequence_number; |
161 | // Prevent drop() from running |
162 | std::mem::forget(self); |
163 | number |
164 | } |
165 | } |
166 | |
167 | impl<C> Drop for RawCookie<'_, C> |
168 | where |
169 | C: RequestConnection + ?Sized, |
170 | { |
171 | fn drop(&mut self) { |
172 | self.connection.discard_reply( |
173 | self.sequence_number, |
174 | kind:RequestKind::HasResponse, |
175 | mode:DiscardMode::DiscardReply, |
176 | ); |
177 | } |
178 | } |
179 | |
180 | /// A handle to a response from the X11 server. |
181 | /// |
182 | /// When sending a request to the X11 server, this library returns a `Cookie`. This `Cookie` can |
183 | /// then later be used to get the response that the server sent. |
184 | /// |
185 | /// See [crate::cookie#requests-with-a-reply] for infos on the different ways to handle X11 |
186 | /// errors in response to a request. |
187 | #[derive (Debug)] |
188 | pub struct Cookie<'a, C, R> |
189 | where |
190 | C: RequestConnection + ?Sized, |
191 | { |
192 | raw_cookie: RawCookie<'a, C>, |
193 | phantom: PhantomData<R>, |
194 | } |
195 | |
196 | impl<C, R> Cookie<'_, C, R> |
197 | where |
198 | R: TryParse, |
199 | C: RequestConnection + ?Sized, |
200 | { |
201 | /// Construct a new cookie. |
202 | /// |
203 | /// This function should only be used by implementations of |
204 | /// `RequestConnection::send_request_with_reply`. |
205 | pub fn new(connection: &C, sequence_number: SequenceNumber) -> Cookie<'_, C, R> { |
206 | Cookie { |
207 | raw_cookie: RawCookie::new(connection, sequence_number), |
208 | phantom: PhantomData, |
209 | } |
210 | } |
211 | |
212 | /// Get the sequence number of the request that generated this cookie. |
213 | pub fn sequence_number(&self) -> SequenceNumber { |
214 | self.raw_cookie.sequence_number |
215 | } |
216 | |
217 | /// Get the raw reply that the server sent. |
218 | pub fn raw_reply(self) -> Result<C::Buf, ReplyError> { |
219 | let conn = self.raw_cookie.connection; |
220 | conn.wait_for_reply_or_error(self.raw_cookie.into_sequence_number()) |
221 | } |
222 | |
223 | /// Get the raw reply that the server sent, but have errors handled as events. |
224 | pub fn raw_reply_unchecked(self) -> Result<Option<C::Buf>, ConnectionError> { |
225 | let conn = self.raw_cookie.connection; |
226 | conn.wait_for_reply(self.raw_cookie.into_sequence_number()) |
227 | } |
228 | |
229 | /// Get the reply that the server sent. |
230 | pub fn reply(self) -> Result<R, ReplyError> { |
231 | Ok(R::try_parse(self.raw_reply()?.as_ref())?.0) |
232 | } |
233 | |
234 | /// Get the reply that the server sent, but have errors handled as events. |
235 | pub fn reply_unchecked(self) -> Result<Option<R>, ConnectionError> { |
236 | self.raw_reply_unchecked()? |
237 | .map(|buf| R::try_parse(buf.as_ref()).map(|r| r.0)) |
238 | .transpose() |
239 | .map_err(Into::into) |
240 | } |
241 | |
242 | /// Discard all responses to the request this cookie represents, even errors. |
243 | /// |
244 | /// Without this function, errors are treated as events after the cookie is dropped. |
245 | pub fn discard_reply_and_errors(self) { |
246 | let conn = self.raw_cookie.connection; |
247 | conn.discard_reply( |
248 | self.raw_cookie.into_sequence_number(), |
249 | RequestKind::HasResponse, |
250 | DiscardMode::DiscardReplyAndError, |
251 | ) |
252 | } |
253 | |
254 | /// Consume this instance and get the contained sequence number out. |
255 | pub(crate) fn into_sequence_number(self) -> SequenceNumber { |
256 | self.raw_cookie.into_sequence_number() |
257 | } |
258 | } |
259 | |
260 | /// A handle to a response containing `RawFd` from the X11 server. |
261 | /// |
262 | /// When sending a request to the X11 server, this library returns a `Cookie`. This `Cookie` can |
263 | /// then later be used to get the response that the server sent. |
264 | /// |
265 | /// This variant of `Cookie` represents a response that can contain `RawFd`s. |
266 | /// |
267 | /// See [crate::cookie#requests-with-a-reply] for infos on the different ways to handle X11 |
268 | /// errors in response to a request. |
269 | #[derive (Debug)] |
270 | pub struct CookieWithFds<'a, C, R> |
271 | where |
272 | C: RequestConnection + ?Sized, |
273 | { |
274 | raw_cookie: RawCookie<'a, C>, |
275 | phantom: PhantomData<R>, |
276 | } |
277 | |
278 | impl<C, R> CookieWithFds<'_, C, R> |
279 | where |
280 | R: TryParseFd, |
281 | C: RequestConnection + ?Sized, |
282 | { |
283 | /// Construct a new cookie. |
284 | /// |
285 | /// This function should only be used by implementations of |
286 | /// `RequestConnection::send_request_with_reply`. |
287 | pub fn new(connection: &C, sequence_number: SequenceNumber) -> CookieWithFds<'_, C, R> { |
288 | CookieWithFds { |
289 | raw_cookie: RawCookie::new(connection, sequence_number), |
290 | phantom: PhantomData, |
291 | } |
292 | } |
293 | |
294 | /// Get the sequence number of the request that generated this cookie. |
295 | pub fn sequence_number(&self) -> SequenceNumber { |
296 | self.raw_cookie.sequence_number |
297 | } |
298 | |
299 | /// Get the raw reply that the server sent. |
300 | pub fn raw_reply(self) -> Result<BufWithFds<C::Buf>, ReplyError> { |
301 | let conn = self.raw_cookie.connection; |
302 | conn.wait_for_reply_with_fds(self.raw_cookie.into_sequence_number()) |
303 | } |
304 | |
305 | /// Get the reply that the server sent. |
306 | pub fn reply(self) -> Result<R, ReplyError> { |
307 | let (buffer, mut fds) = self.raw_reply()?; |
308 | Ok(R::try_parse_fd(buffer.as_ref(), &mut fds)?.0) |
309 | } |
310 | } |
311 | |
312 | macro_rules! multiple_reply_cookie { |
313 | ( |
314 | $(#[$meta:meta])* |
315 | pub struct $name:ident for $reply:ident |
316 | ) => { |
317 | $(#[$meta])* |
318 | #[derive(Debug)] |
319 | pub struct $name<'a, C: RequestConnection + ?Sized>(Option<RawCookie<'a, C>>); |
320 | |
321 | impl<'c, C> $name<'c, C> |
322 | where |
323 | C: RequestConnection + ?Sized, |
324 | { |
325 | pub(crate) fn new( |
326 | cookie: Cookie<'c, C, $reply>, |
327 | ) -> Self { |
328 | Self(Some(cookie.raw_cookie)) |
329 | } |
330 | |
331 | /// Get the sequence number of the request that generated this cookie. |
332 | pub fn sequence_number(&self) -> Option<SequenceNumber> { |
333 | self.0.as_ref().map(|x| x.sequence_number) |
334 | } |
335 | } |
336 | |
337 | impl<C> Iterator for $name<'_, C> |
338 | where |
339 | C: RequestConnection + ?Sized, |
340 | { |
341 | type Item = Result<$reply, ReplyError>; |
342 | |
343 | fn next(&mut self) -> Option<Self::Item> { |
344 | let cookie = match self.0.take() { |
345 | None => return None, |
346 | Some(cookie) => cookie, |
347 | }; |
348 | let reply = cookie |
349 | .connection |
350 | .wait_for_reply_or_error(cookie.sequence_number); |
351 | let reply = match reply { |
352 | Err(e) => return Some(Err(e)), |
353 | Ok(v) => v, |
354 | }; |
355 | let reply = $reply::try_parse(reply.as_ref()); |
356 | match reply { |
357 | // Is this an indicator that no more replies follow? |
358 | Ok(ref reply) if Self::is_last(&reply.0) => None, |
359 | Ok(reply) => { |
360 | self.0 = Some(cookie); |
361 | Some(Ok(reply.0)) |
362 | } |
363 | Err(e) => Some(Err(e.into())), |
364 | } |
365 | } |
366 | } |
367 | } |
368 | } |
369 | |
370 | multiple_reply_cookie!( |
371 | /// A handle to the replies to a `ListFontsWithInfo` request. |
372 | /// |
373 | /// `ListFontsWithInfo` generated more than one reply, but `Cookie` only allows getting one reply. |
374 | /// This structure implements `Iterator` and allows to get all the replies. |
375 | pub struct ListFontsWithInfoCookie for ListFontsWithInfoReply |
376 | ); |
377 | |
378 | impl<C> ListFontsWithInfoCookie<'_, C> |
379 | where |
380 | C: RequestConnection + ?Sized, |
381 | { |
382 | fn is_last(reply: &ListFontsWithInfoReply) -> bool { |
383 | reply.name.is_empty() |
384 | } |
385 | } |
386 | |
387 | #[cfg (feature = "record" )] |
388 | multiple_reply_cookie!( |
389 | /// A handle to the replies to a `record::EnableContext` request. |
390 | /// |
391 | /// `EnableContext` generated more than one reply, but `Cookie` only allows getting one reply. |
392 | /// This structure implements `Iterator` and allows to get all the replies. |
393 | pub struct RecordEnableContextCookie for EnableContextReply |
394 | ); |
395 | |
396 | #[cfg (feature = "record" )] |
397 | impl<C> RecordEnableContextCookie<'_, C> |
398 | where |
399 | C: RequestConnection + ?Sized, |
400 | { |
401 | fn is_last(reply: &EnableContextReply) -> bool { |
402 | // FIXME: There does not seem to be an enumeration of the category values, (value 5 is |
403 | // EndOfData) |
404 | reply.category == 5 |
405 | } |
406 | } |
407 | |