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