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