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: &i32) => 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 |
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. |
101 | struct 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 | |
124 | impl<'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 | |