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
120impl<C> Drop for VoidCookie<'_, C>
121where
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)]
135struct RawCookie<'a, C>
136where
137 C: RequestConnection + ?Sized,
138{
139 connection: &'a C,
140 sequence_number: SequenceNumber,
141}
142
143impl<C> RawCookie<'_, C>
144where
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
167impl<C> Drop for RawCookie<'_, C>
168where
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)]
188pub struct Cookie<'a, C, R>
189where
190 C: RequestConnection + ?Sized,
191{
192 raw_cookie: RawCookie<'a, C>,
193 phantom: PhantomData<R>,
194}
195
196impl<C, R> Cookie<'_, C, R>
197where
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)]
270pub struct CookieWithFds<'a, C, R>
271where
272 C: RequestConnection + ?Sized,
273{
274 raw_cookie: RawCookie<'a, C>,
275 phantom: PhantomData<R>,
276}
277
278impl<C, R> CookieWithFds<'_, C, R>
279where
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
312macro_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
370multiple_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
378impl<C> ListFontsWithInfoCookie<'_, C>
379where
380 C: RequestConnection + ?Sized,
381{
382 fn is_last(reply: &ListFontsWithInfoReply) -> bool {
383 reply.name.is_empty()
384 }
385}
386
387#[cfg(feature = "record")]
388multiple_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")]
397impl<C> RecordEnableContextCookie<'_, C>
398where
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