| 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 | |