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};
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) = *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(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) => err.fmt(f),
43            GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"),
44            GetPropertyError::FormatMismatch(err) => 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.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}
96
97/// An iterator over the "windows" of the property that we are fetching.
98struct PropIterator<'a, C: ?Sized, T> {
99    /// Handle to the connection.
100    conn: &'a C,
101
102    /// The window that we're fetching the property from.
103    window: xproto::Window,
104
105    /// The property that we're fetching.
106    property: xproto::Atom,
107
108    /// The type of the property that we're fetching.
109    property_type: xproto::Atom,
110
111    /// The offset of the next window, in 32-bit chunks.
112    offset: u32,
113
114    /// The format of the type.
115    format: u8,
116
117    /// Keep a reference to `T`.
118    _phantom: std::marker::PhantomData<T>,
119}
120
121impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> {
122    /// Create a new property iterator.
123    fn new(
124        conn: &'a C,
125        window: xproto::Window,
126        property: xproto::Atom,
127        property_type: xproto::Atom,
128    ) -> Self {
129        let format = match mem::size_of::<T>() {
130            1 => 8,
131            2 => 16,
132            4 => 32,
133            _ => unreachable!(),
134        };
135
136        Self {
137            conn,
138            window,
139            property,
140            property_type,
141            offset: 0,
142            format,
143            _phantom: Default::default(),
144        }
145    }
146
147    /// Get the next window and append it to `data`.
148    ///
149    /// Returns whether there are more windows to fetch.
150    fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> {
151        // Send the request and wait for the reply.
152        let reply = self
153            .conn
154            .get_property(
155                false,
156                self.window,
157                self.property,
158                self.property_type,
159                self.offset,
160                PROPERTY_BUFFER_SIZE,
161            )?
162            .reply()?;
163
164        // Make sure that the reply is of the correct type.
165        if reply.type_ != self.property_type {
166            return Err(GetPropertyError::TypeMismatch(reply.type_));
167        }
168
169        // Make sure that the reply is of the correct format.
170        if reply.format != self.format {
171            return Err(GetPropertyError::FormatMismatch(reply.format.into()));
172        }
173
174        // Append the data to the output.
175        if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
176            // We can just do a bytewise append.
177            data.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!
186            let old_len = data.len();
187            let 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        }
191
192        // Check `bytes_after` to see if there are more windows to fetch.
193        self.offset += PROPERTY_BUFFER_SIZE;
194        Ok(reply.bytes_after != 0)
195    }
196}