x11rb/
cookie.rs

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
47use std::marker::PhantomData;
48
49use crate::connection::{BufWithFds, RequestConnection, RequestKind};
50use crate::errors::{ConnectionError, ReplyError};
51#[cfg(feature = "record")]
52use crate::protocol::record::EnableContextReply;
53use crate::protocol::xproto::ListFontsWithInfoReply;
54use crate::x11_utils::{TryParse, TryParseFd};
55
56use 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)]
66pub struct VoidCookie<'a, C>
67where
68    C: RequestConnection + ?Sized,
69{
70    connection: &'a C,
71    sequence_number: SequenceNumber,
72}
73
74impl<'a, C> VoidCookie<'a, C>
75where
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
136impl<C> Drop for VoidCookie<'_, C>
137where
138    C: RequestConnection + ?Sized,
139{
140    fn drop(&mut self) {
141        self.connection.discard_reply(
142            self.sequence_number,
143            RequestKind::IsVoid,
144            DiscardMode::DiscardReply,
145        )
146    }
147}
148
149/// Internal helper for a cookie with an response
150#[derive(Debug)]
151struct RawCookie<'a, C>
152where
153    C: RequestConnection + ?Sized,
154{
155    connection: &'a C,
156    sequence_number: SequenceNumber,
157}
158
159impl<C> RawCookie<'_, C>
160where
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
198impl<C> Drop for RawCookie<'_, C>
199where
200    C: RequestConnection + ?Sized,
201{
202    fn drop(&mut self) {
203        self.connection.discard_reply(
204            self.sequence_number,
205            RequestKind::HasResponse,
206            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)]
219pub struct Cookie<'a, C, R>
220where
221    C: RequestConnection + ?Sized,
222{
223    raw_cookie: RawCookie<'a, C>,
224    phantom: PhantomData<R>,
225}
226
227impl<C, R> Cookie<'_, C, R>
228where
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)]
316pub struct CookieWithFds<'a, C, R>
317where
318    C: RequestConnection + ?Sized,
319{
320    raw_cookie: RawCookie<'a, C>,
321    phantom: PhantomData<R>,
322}
323
324impl<C, R> CookieWithFds<'_, C, R>
325where
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
373macro_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
428multiple_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
436impl<C> ListFontsWithInfoCookie<'_, C>
437where
438    C: RequestConnection + ?Sized,
439{
440    fn is_last(reply: &ListFontsWithInfoReply) -> bool {
441        reply.name.is_empty()
442    }
443}
444
445#[cfg(feature = "record")]
446multiple_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")]
455impl<C> RecordEnableContextCookie<'_, C>
456where
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}