winit/platform_impl/linux/x11/util/
window_property.rs

1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
4
5use bytemuck::{NoUninit, Pod};
6use x11rb::connection::Connection;
7use x11rb::errors::ReplyError;
8
9use super::*;
10
11pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
12
13pub type Cardinal = u32;
14
15#[derive(Debug, Clone)]
16pub enum GetPropertyError {
17    X11rbError(Arc<ReplyError>),
18    TypeMismatch(xproto::Atom),
19    FormatMismatch(c_int),
20}
21
22impl GetPropertyError {
23    pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool {
24        if let GetPropertyError::TypeMismatch(actual_type) = *self {
25            actual_type == t
26        } else {
27            false
28        }
29    }
30}
31
32impl<T: Into<ReplyError>> From<T> for GetPropertyError {
33    fn from(e: T) -> Self {
34        Self::X11rbError(Arc::new(e.into()))
35    }
36}
37
38impl fmt::Display for GetPropertyError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match 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}
47
48impl Error for GetPropertyError {}
49
50// 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!
53
54impl XConnection {
55    pub 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> {
61        let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type);
62        let mut data = vec![];
63
64        loop {
65            if !iter.next_window(&mut data)? {
66                break;
67            }
68        }
69
70        Ok(data)
71    }
72
73    pub 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> {
81        assert!([1usize, 2, 4].contains(&mem::size_of::<T>()));
82        self.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}
95
96/// An iterator over the "windows" of the property that we are fetching.
97struct PropIterator<'a, C: ?Sized, T> {
98    /// Handle to the connection.
99    conn: &'a C,
100
101    /// The window that we're fetching the property from.
102    window: xproto::Window,
103
104    /// The property that we're fetching.
105    property: xproto::Atom,
106
107    /// The type of the property that we're fetching.
108    property_type: xproto::Atom,
109
110    /// The offset of the next window, in 32-bit chunks.
111    offset: u32,
112
113    /// The format of the type.
114    format: u8,
115
116    /// Keep a reference to `T`.
117    _phantom: std::marker::PhantomData<T>,
118}
119
120impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> {
121    /// Create a new property iterator.
122    fn new(
123        conn: &'a C,
124        window: xproto::Window,
125        property: xproto::Atom,
126        property_type: xproto::Atom,
127    ) -> Self {
128        let format = match mem::size_of::<T>() {
129            1 => 8,
130            2 => 16,
131            4 => 32,
132            _ => unreachable!(),
133        };
134
135        Self {
136            conn,
137            window,
138            property,
139            property_type,
140            offset: 0,
141            format,
142            _phantom: Default::default(),
143        }
144    }
145
146    /// Get the next window and append it to `data`.
147    ///
148    /// Returns whether there are more windows to fetch.
149    fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> {
150        // Send the request and wait for the reply.
151        let reply = self
152            .conn
153            .get_property(
154                false,
155                self.window,
156                self.property,
157                self.property_type,
158                self.offset,
159                PROPERTY_BUFFER_SIZE,
160            )?
161            .reply()?;
162
163        // Make sure that the reply is of the correct type.
164        if reply.type_ != self.property_type {
165            return Err(GetPropertyError::TypeMismatch(reply.type_));
166        }
167
168        // Make sure that the reply is of the correct format.
169        if reply.format != self.format {
170            return Err(GetPropertyError::FormatMismatch(reply.format.into()));
171        }
172
173        // Append the data to the output.
174        if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
175            // We can just do a bytewise append.
176            data.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!
185            let old_len = data.len();
186            let 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        }
190
191        // Check `bytes_after` to see if there are more windows to fetch.
192        self.offset += PROPERTY_BUFFER_SIZE;
193        Ok(reply.bytes_after != 0)
194    }
195}