x11rb_async/
cookie.rs

1// This code is dual licensed under MIT OR Apache 2.0.
2
3//! Cookies!
4
5use x11rb::connection::{BufWithFds, ReplyOrError, RequestKind};
6use x11rb_protocol::protocol::xproto::ListFontsWithInfoReply;
7use x11rb_protocol::{DiscardMode, SequenceNumber};
8
9use crate::connection::{Connection, RequestConnection};
10use crate::errors::{ConnectionError, ReplyError};
11use crate::x11_utils::{TryParse, TryParseFd};
12
13use futures_lite::{ready, stream::Stream};
14use std::future::Future;
15use std::marker::PhantomData;
16use std::mem;
17use std::pin::Pin;
18use std::task::{Context, Poll};
19
20#[cfg(feature = "record")]
21use crate::protocol::record::EnableContextReply;
22
23/// A cookie for a request without a reply.
24#[derive(Debug)]
25pub struct VoidCookie<'conn, C: RequestConnection + ?Sized> {
26    conn: &'conn C,
27    sequence: SequenceNumber,
28}
29
30impl<'conn, C: Connection + ?Sized> VoidCookie<'conn, C> {
31    /// Create a new cookie from its raw parts.
32    pub fn new(conn: &'conn C, sequence: SequenceNumber) -> Self {
33        Self { conn, sequence }
34    }
35
36    /// Get the sequence number of this cookie.
37    pub fn sequence_number(&self) -> SequenceNumber {
38        self.sequence
39    }
40
41    /// Check if this request caused an X11 error.
42    pub async fn check(self) -> Result<(), ReplyError> {
43        let res = self.conn.check_for_raw_error(self.sequence).await;
44
45        // Wait until after the `await` to consume the cookie.
46        let (conn, _) = self.consume();
47
48        match res? {
49            Some(e) => Err(conn.parse_error(e.as_ref())?.into()),
50            None => Ok(()),
51        }
52    }
53
54    /// Ignore errors associated with this request.
55    pub fn ignore_error(self) {
56        let (conn, seq) = self.consume();
57        conn.discard_reply(seq, RequestKind::IsVoid, DiscardMode::DiscardReplyAndError);
58    }
59
60    /// Eat the cookie and return the connection.
61    fn consume(self) -> (&'conn C, SequenceNumber) {
62        let res = (self.conn, self.sequence);
63        mem::forget(self);
64        res
65    }
66}
67
68impl<'conn, C: RequestConnection + ?Sized> Drop for VoidCookie<'conn, C> {
69    fn drop(&mut self) {
70        self.conn.discard_reply(
71            self.sequence,
72            RequestKind::IsVoid,
73            DiscardMode::DiscardReply,
74        );
75    }
76}
77
78/// Helper for cookies that hold a reply.
79#[derive(Debug)]
80struct RawCookie<'a, C: RequestConnection + ?Sized> {
81    conn: &'a C,
82    sequence: SequenceNumber,
83}
84
85impl<'a, C: RequestConnection + ?Sized> RawCookie<'a, C> {
86    fn new(conn: &'a C, sequence: SequenceNumber) -> Self {
87        Self { conn, sequence }
88    }
89
90    fn consume(self) -> (&'a C, SequenceNumber) {
91        let res = (self.conn, self.sequence);
92        mem::forget(self);
93        res
94    }
95}
96
97impl<'a, C: RequestConnection + ?Sized> Drop for RawCookie<'a, C> {
98    fn drop(&mut self) {
99        self.conn.discard_reply(
100            self.sequence,
101            RequestKind::HasResponse,
102            DiscardMode::DiscardReply,
103        );
104    }
105}
106
107/// A cookie for a request that has a reply.
108#[derive(Debug)]
109pub struct Cookie<'conn, C: RequestConnection + ?Sized, R> {
110    raw: RawCookie<'conn, C>,
111    capture: PhantomData<R>,
112}
113
114impl<'conn, C: Connection + ?Sized, R: TryParse> Cookie<'conn, C, R> {
115    /// Create a new cookie from its raw parts.
116    pub fn new(conn: &'conn C, sequence: SequenceNumber) -> Self {
117        Self {
118            raw: RawCookie::new(conn, sequence),
119            capture: PhantomData,
120        }
121    }
122
123    /// Get the sequence number of this cookie.
124    pub fn sequence_number(&self) -> SequenceNumber {
125        self.raw.sequence
126    }
127
128    /// Get the raw reply that the server sent.
129    pub async fn raw_reply(self) -> Result<C::Buf, ReplyError> {
130        // Wait for the reply
131        let reply_or_error = self
132            .raw
133            .conn
134            .wait_for_reply_or_raw_error(self.raw.sequence)
135            .await;
136
137        // Wait until after the `await` to consume the cookie.
138        let (conn, _) = self.raw.consume();
139
140        // Check for errors
141        match reply_or_error? {
142            ReplyOrError::Reply(reply) => Ok(reply),
143            ReplyOrError::Error(error) => Err(conn.parse_error(error.as_ref())?.into()),
144        }
145    }
146
147    /// Get the reply, but have errors handled as events.
148    pub async fn raw_reply_unchecked(self) -> Result<Option<C::Buf>, ConnectionError> {
149        // Wait for the reply
150        let res = self.raw.conn.wait_for_reply(self.raw.sequence).await;
151
152        // Wait until after the `await` to consume the cookie.
153        let _ = self.raw.consume();
154
155        // Check for errors
156        res
157    }
158
159    /// Get the reply that the server sent.
160    pub async fn reply(self) -> Result<R, ReplyError> {
161        let buf = self.raw_reply().await?;
162
163        // Parse the reply
164        let (reply, _) = R::try_parse(buf.as_ref())?;
165        Ok(reply)
166    }
167
168    /// Get the reply, but have errors handled as events.
169    pub async fn reply_unchecked(self) -> Result<Option<R>, ConnectionError> {
170        let buf = self.raw_reply_unchecked().await?;
171
172        // Parse the reply
173        let reply = buf.map(|buf| R::try_parse(buf.as_ref()).unwrap().0);
174        Ok(reply)
175    }
176}
177
178/// A cookie for a request that has a reply containing file descriptors.
179#[derive(Debug)]
180pub struct CookieWithFds<'conn, C: RequestConnection + ?Sized, R> {
181    raw: RawCookie<'conn, C>,
182    capture: PhantomData<R>,
183}
184
185impl<'conn, C: Connection + ?Sized, R: TryParseFd> CookieWithFds<'conn, C, R> {
186    /// Create a new cookie from its raw parts.
187    pub fn new(conn: &'conn C, sequence: SequenceNumber) -> Self {
188        Self {
189            raw: RawCookie::new(conn, sequence),
190            capture: PhantomData,
191        }
192    }
193
194    /// Get the sequence number of this cookie.
195    pub fn sequence_number(&self) -> SequenceNumber {
196        self.raw.sequence
197    }
198
199    /// Get the raw reply that the server sent.
200    pub async fn raw_reply(self) -> Result<BufWithFds<C::Buf>, ReplyError> {
201        // Wait for the reply
202        let reply_or_error = self
203            .raw
204            .conn
205            .wait_for_reply_with_fds_raw(self.raw.sequence)
206            .await;
207
208        // Wait until after the `await` to consume the cookie.
209        let (conn, _) = self.raw.consume();
210
211        // Check for errors
212        match reply_or_error? {
213            ReplyOrError::Reply(reply) => Ok(reply),
214            ReplyOrError::Error(error) => Err(conn.parse_error(error.as_ref())?.into()),
215        }
216    }
217
218    /// Get the reply that the server sent.
219    pub async fn reply(self) -> Result<R, ReplyError> {
220        let (buf, mut fds) = self.raw_reply().await?;
221
222        // Parse the reply
223        let (reply, _) = R::try_parse_fd(buf.as_ref(), &mut fds)?;
224        Ok(reply)
225    }
226}
227
228macro_rules! multiple_reply_cookie {
229    (
230        $(#[$outer:meta])*
231        pub struct $name:ident for $reply:ident
232    ) => {
233        $(#[$outer])*
234        pub struct $name<'conn, C> where C: RequestConnection + ?Sized {
235            // The raw cookie we're polling.
236            raw: Option<RawCookie<'conn, C>>,
237
238            // Current wait future we're polling.
239            wait: Option<Pin<Box<dyn Future<Output = Result<C::Buf, ReplyError>> + Send + 'conn>>>,
240        }
241
242        impl<'conn, C: RequestConnection + std::fmt::Debug + ?Sized> std::fmt::Debug for $name<'conn, C> {
243            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244                f.debug_struct(stringify!($name))
245                    .field("raw", &self.raw)
246                    .finish_non_exhaustive()
247            }
248        }
249
250        impl<'conn, C: RequestConnection + ?Sized> $name<'conn, C> {
251            pub(crate) fn new(
252                cookie: Cookie<'conn, C, $reply>,
253            ) -> Self {
254                Self {
255                    raw: Some(cookie.raw),
256                    wait: None,
257                }
258            }
259
260            /// Get the sequence number of this cookie.
261            pub fn sequence_number(&self) -> Option<SequenceNumber> {
262                self.raw.as_ref().map(|raw| raw.sequence)
263            }
264        }
265
266        impl<C: RequestConnection + ?Sized> Stream for $name<'_, C> {
267            type Item = Result<$reply, ReplyError>;
268
269            fn poll_next(
270                mut self: Pin<&mut Self>,
271                cx: &mut Context<'_>,
272            ) -> Poll<Option<Self::Item>> {
273                loop {
274                    // If we have a reply, try taking it.
275                    if let Some(wait) = self.wait.as_mut() {
276                        // Poll for the reply.
277                        let reply = {
278                            let raw_reply = match ready!(wait.as_mut().poll(cx)) {
279                                Ok(reply) => reply,
280                                Err(e) => return Poll::Ready(Some(Err(e))),
281                            };
282
283                            // Parse the reply.
284                            match $reply::try_parse(raw_reply.as_ref()) {
285                                Ok((reply, _)) => reply,
286                                Err(e) => return Poll::Ready(Some(Err(e.into()))),
287                            }
288                        };
289
290                        if Self::is_last(&reply) {
291                            // Last one, end this stream.
292                            self.wait = None;
293                            self.raw = None;
294                            return Poll::Ready(Some(Ok(reply)));
295                        } else {
296                            // More replies to come.
297                            return Poll::Ready(Some(Ok(reply)));
298                        }
299                    }
300
301                    // Take out the cookie.
302                    let cookie = match self.raw.take() {
303                        Some(cookie) => cookie,
304                        None => return Poll::Ready(None),
305                    };
306
307                    // Begin waiting for a reply to this cookie.
308                    self.wait = Some(
309                        cookie.conn.wait_for_reply_or_error(cookie.sequence)
310                    );
311                    self.raw = Some(cookie);
312                }
313            }
314        }
315    };
316}
317
318multiple_reply_cookie!(
319    /// A handle to the replies to a `ListFontsWithInfo` request.
320    ///
321    /// `ListFontsWithInfo` generated more than one reply, but `Cookie` only allows getting one reply.
322    /// This structure implements `Iterator` and allows to get all the replies.
323    pub struct ListFontsWithInfoCookie for ListFontsWithInfoReply
324);
325
326impl<C> ListFontsWithInfoCookie<'_, C>
327where
328    C: RequestConnection + ?Sized,
329{
330    fn is_last(reply: &ListFontsWithInfoReply) -> bool {
331        reply.name.is_empty()
332    }
333}
334
335#[cfg(feature = "record")]
336multiple_reply_cookie!(
337    /// A handle to the replies to a `record::EnableContext` request.
338    ///
339    /// `EnableContext` generated more than one reply, but `Cookie` only allows getting one reply.
340    /// This structure implements `Iterator` and allows to get all the replies.
341    pub struct RecordEnableContextCookie for EnableContextReply
342);
343
344#[cfg(feature = "record")]
345impl<C> RecordEnableContextCookie<'_, C>
346where
347    C: RequestConnection + ?Sized,
348{
349    fn is_last(reply: &EnableContextReply) -> bool {
350        // FIXME: There does not seem to be an enumeration of the category values, (value 5 is
351        // EndOfData)
352        reply.category == 5
353    }
354}