| 1 | use std::error::Error; |
| 2 | use std::fmt; |
| 3 | use std::sync::Arc; |
| 4 | |
| 5 | use bytemuck::{NoUninit, Pod}; |
| 6 | |
| 7 | use x11rb::connection::Connection; |
| 8 | use x11rb::errors::ReplyError; |
| 9 | |
| 10 | use super::*; |
| 11 | |
| 12 | pub const CARDINAL_SIZE: usize = mem::size_of::<u32>(); |
| 13 | |
| 14 | pub type Cardinal = u32; |
| 15 | |
| 16 | #[derive (Debug, Clone)] |
| 17 | pub enum GetPropertyError { |
| 18 | X11rbError(Arc<ReplyError>), |
| 19 | TypeMismatch(xproto::Atom), |
| 20 | FormatMismatch(c_int), |
| 21 | } |
| 22 | |
| 23 | impl GetPropertyError { |
| 24 | pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool { |
| 25 | if let GetPropertyError::TypeMismatch(actual_type: u32) = *self { |
| 26 | actual_type == t |
| 27 | } else { |
| 28 | false |
| 29 | } |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | impl<T: Into<ReplyError>> From<T> for GetPropertyError { |
| 34 | fn from(e: T) -> Self { |
| 35 | Self::X11rbError(Arc::new(data:e.into())) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | impl fmt::Display for GetPropertyError { |
| 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 41 | match self { |
| 42 | GetPropertyError::X11rbError(err: &Arc) => err.fmt(f), |
| 43 | GetPropertyError::TypeMismatch(err: &u32) => write!(f, "type mismatch: {err}" ), |
| 44 | GetPropertyError::FormatMismatch(err: &{unknown}) => write!(f, "format mismatch: {err}" ), |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | impl Error for GetPropertyError {} |
| 50 | |
| 51 | // Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. |
| 52 | // To test if `get_property` works correctly, set this to 1. |
| 53 | const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone! |
| 54 | |
| 55 | impl XConnection { |
| 56 | pub fn get_property<T: Pod>( |
| 57 | &self, |
| 58 | window: xproto::Window, |
| 59 | property: xproto::Atom, |
| 60 | property_type: xproto::Atom, |
| 61 | ) -> Result<Vec<T>, GetPropertyError> { |
| 62 | let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type); |
| 63 | let mut data = vec![]; |
| 64 | |
| 65 | loop { |
| 66 | if !iter.next_window(&mut data)? { |
| 67 | break; |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | Ok(data) |
| 72 | } |
| 73 | |
| 74 | pub fn change_property<'a, T: NoUninit>( |
| 75 | &'a self, |
| 76 | window: xproto::Window, |
| 77 | property: xproto::Atom, |
| 78 | property_type: xproto::Atom, |
| 79 | mode: xproto::PropMode, |
| 80 | new_value: &[T], |
| 81 | ) -> Result<VoidCookie<'a>, X11Error> { |
| 82 | assert!([1usize, 2, 4].contains(&mem::size_of::<T>())); |
| 83 | self.xcb_connection() |
| 84 | .change_property( |
| 85 | mode, |
| 86 | window, |
| 87 | property, |
| 88 | property_type, |
| 89 | (mem::size_of::<T>() * 8) as u8, |
| 90 | new_value.len().try_into().expect("too many items for property" ), |
| 91 | bytemuck::cast_slice::<T, u8>(new_value), |
| 92 | ) |
| 93 | .map_err(Into::into) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// An iterator over the "windows" of the property that we are fetching. |
| 98 | struct PropIterator<'a, C: ?Sized, T> { |
| 99 | /// Handle to the connection. |
| 100 | conn: &'a C, |
| 101 | |
| 102 | /// The window that we're fetching the property from. |
| 103 | window: xproto::Window, |
| 104 | |
| 105 | /// The property that we're fetching. |
| 106 | property: xproto::Atom, |
| 107 | |
| 108 | /// The type of the property that we're fetching. |
| 109 | property_type: xproto::Atom, |
| 110 | |
| 111 | /// The offset of the next window, in 32-bit chunks. |
| 112 | offset: u32, |
| 113 | |
| 114 | /// The format of the type. |
| 115 | format: u8, |
| 116 | |
| 117 | /// Keep a reference to `T`. |
| 118 | _phantom: std::marker::PhantomData<T>, |
| 119 | } |
| 120 | |
| 121 | impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> { |
| 122 | /// Create a new property iterator. |
| 123 | fn new( |
| 124 | conn: &'a C, |
| 125 | window: xproto::Window, |
| 126 | property: xproto::Atom, |
| 127 | property_type: xproto::Atom, |
| 128 | ) -> Self { |
| 129 | let format = match mem::size_of::<T>() { |
| 130 | 1 => 8, |
| 131 | 2 => 16, |
| 132 | 4 => 32, |
| 133 | _ => unreachable!(), |
| 134 | }; |
| 135 | |
| 136 | Self { |
| 137 | conn, |
| 138 | window, |
| 139 | property, |
| 140 | property_type, |
| 141 | offset: 0, |
| 142 | format, |
| 143 | _phantom: Default::default(), |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | /// Get the next window and append it to `data`. |
| 148 | /// |
| 149 | /// Returns whether there are more windows to fetch. |
| 150 | fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> { |
| 151 | // Send the request and wait for the reply. |
| 152 | let reply = self |
| 153 | .conn |
| 154 | .get_property( |
| 155 | false, |
| 156 | self.window, |
| 157 | self.property, |
| 158 | self.property_type, |
| 159 | self.offset, |
| 160 | PROPERTY_BUFFER_SIZE, |
| 161 | )? |
| 162 | .reply()?; |
| 163 | |
| 164 | // Make sure that the reply is of the correct type. |
| 165 | if reply.type_ != self.property_type { |
| 166 | return Err(GetPropertyError::TypeMismatch(reply.type_)); |
| 167 | } |
| 168 | |
| 169 | // Make sure that the reply is of the correct format. |
| 170 | if reply.format != self.format { |
| 171 | return Err(GetPropertyError::FormatMismatch(reply.format.into())); |
| 172 | } |
| 173 | |
| 174 | // Append the data to the output. |
| 175 | if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 { |
| 176 | // We can just do a bytewise append. |
| 177 | data.extend_from_slice(bytemuck::cast_slice(&reply.value)); |
| 178 | } else { |
| 179 | // Rust's borrowing and types system makes this a bit tricky. |
| 180 | // |
| 181 | // We need to make sure that the data is properly aligned. Unfortunately the best |
| 182 | // safe way to do this is to copy the data to another buffer and then append. |
| 183 | // |
| 184 | // TODO(notgull): It may be worth it to use `unsafe` to copy directly from |
| 185 | // `reply.value` to `data`; check if this is faster. Use benchmarks! |
| 186 | let old_len = data.len(); |
| 187 | let added_len = reply.value.len() / mem::size_of::<T>(); |
| 188 | data.resize(old_len + added_len, T::zeroed()); |
| 189 | bytemuck::cast_slice_mut::<T, u8>(&mut data[old_len..]).copy_from_slice(&reply.value); |
| 190 | } |
| 191 | |
| 192 | // Check `bytes_after` to see if there are more windows to fetch. |
| 193 | self.offset += PROPERTY_BUFFER_SIZE; |
| 194 | Ok(reply.bytes_after != 0) |
| 195 | } |
| 196 | } |
| 197 | |