1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
45use bytemuck::{NoUninit, Pod};
67use x11rb::connection::Connection;
8use x11rb::errors::ReplyError;
910use super::*;
1112pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
1314pub type Cardinal = u32;
1516#[derive(Debug, Clone)]
17pub enum GetPropertyError {
18 X11rbError(Arc<ReplyError>),
19 TypeMismatch(xproto::Atom),
20 FormatMismatch(c_int),
21}
2223impl GetPropertyError {
24pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool {
25if let GetPropertyError::TypeMismatch(actual_type) = *self {
26 actual_type == t
27 } else {
28false
29}
30 }
31}
3233impl<T: Into<ReplyError>> From<T> for GetPropertyError {
34fn from(e: T) -> Self {
35Self::X11rbError(Arc::new(e.into()))
36 }
37}
3839impl fmt::Display for GetPropertyError {
40fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41match self {
42 GetPropertyError::X11rbError(err) => err.fmt(f),
43 GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"),
44 GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"),
45 }
46 }
47}
4849impl Error for GetPropertyError {}
5051// 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.
53const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone!
5455impl XConnection {
56pub 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> {
62let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type);
63let mut data = vec![];
6465loop {
66if !iter.next_window(&mut data)? {
67break;
68 }
69 }
7071Ok(data)
72 }
7374pub 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> {
82assert!([1usize, 2, 4].contains(&mem::size_of::<T>()));
83self.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}
9697/// An iterator over the "windows" of the property that we are fetching.
98struct PropIterator<'a, C: ?Sized, T> {
99/// Handle to the connection.
100conn: &'a C,
101102/// The window that we're fetching the property from.
103window: xproto::Window,
104105/// The property that we're fetching.
106property: xproto::Atom,
107108/// The type of the property that we're fetching.
109property_type: xproto::Atom,
110111/// The offset of the next window, in 32-bit chunks.
112offset: u32,
113114/// The format of the type.
115format: u8,
116117/// Keep a reference to `T`.
118_phantom: std::marker::PhantomData<T>,
119}
120121impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> {
122/// Create a new property iterator.
123fn new(
124 conn: &'a C,
125 window: xproto::Window,
126 property: xproto::Atom,
127 property_type: xproto::Atom,
128 ) -> Self {
129let format = match mem::size_of::<T>() {
1301 => 8,
1312 => 16,
1324 => 32,
133_ => unreachable!(),
134 };
135136Self {
137 conn,
138 window,
139 property,
140 property_type,
141 offset: 0,
142 format,
143 _phantom: Default::default(),
144 }
145 }
146147/// Get the next window and append it to `data`.
148 ///
149 /// Returns whether there are more windows to fetch.
150fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> {
151// Send the request and wait for the reply.
152let reply = self
153.conn
154 .get_property(
155false,
156self.window,
157self.property,
158self.property_type,
159self.offset,
160 PROPERTY_BUFFER_SIZE,
161 )?
162.reply()?;
163164// Make sure that the reply is of the correct type.
165if reply.type_ != self.property_type {
166return Err(GetPropertyError::TypeMismatch(reply.type_));
167 }
168169// Make sure that the reply is of the correct format.
170if reply.format != self.format {
171return Err(GetPropertyError::FormatMismatch(reply.format.into()));
172 }
173174// Append the data to the output.
175if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
176// We can just do a bytewise append.
177data.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!
186let old_len = data.len();
187let 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 }
191192// Check `bytes_after` to see if there are more windows to fetch.
193self.offset += PROPERTY_BUFFER_SIZE;
194Ok(reply.bytes_after != 0)
195 }
196}