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

1use std::slice;
2use std::sync::Arc;
3
4use super::*;
5
6#[derive(Debug)]
7#[allow(dead_code)]
8pub enum StateOperation {
9    Remove = 0, // _NET_WM_STATE_REMOVE
10    Add = 1,    // _NET_WM_STATE_ADD
11    Toggle = 2, // _NET_WM_STATE_TOGGLE
12}
13
14impl From<bool> for StateOperation {
15    fn from(op: bool) -> Self {
16        if op {
17            StateOperation::Add
18        } else {
19            StateOperation::Remove
20        }
21    }
22}
23
24/// X window type. Maps directly to
25/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
26#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub enum WindowType {
29    /// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
30    /// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
31    /// root window clicks.
32    Desktop,
33    /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
34    Dock,
35    /// Toolbar windows. "Torn off" from the main application.
36    Toolbar,
37    /// Pinnable menu windows. "Torn off" from the main application.
38    Menu,
39    /// A small persistent utility window, such as a palette or toolbox.
40    Utility,
41    /// The window is a splash screen displayed as an application is starting up.
42    Splash,
43    /// This is a dialog window.
44    Dialog,
45    /// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
46    /// This property is typically used on override-redirect windows.
47    DropdownMenu,
48    /// A popup menu that usually appears when the user right clicks on an object.
49    /// This property is typically used on override-redirect windows.
50    PopupMenu,
51    /// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
52    /// This property is typically used on override-redirect windows.
53    Tooltip,
54    /// The window is a notification.
55    /// This property is typically used on override-redirect windows.
56    Notification,
57    /// This should be used on the windows that are popped up by combo boxes.
58    /// This property is typically used on override-redirect windows.
59    Combo,
60    /// This indicates the the window is being dragged.
61    /// This property is typically used on override-redirect windows.
62    Dnd,
63    /// This is a normal, top-level window.
64    Normal,
65}
66
67impl Default for WindowType {
68    fn default() -> Self {
69        WindowType::Normal
70    }
71}
72
73impl WindowType {
74    pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
75        use self::WindowType::*;
76        let atom_name: &[u8] = match *self {
77            Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
78            Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
79            Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
80            Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
81            Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
82            Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
83            Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
84            DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
85            PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
86            Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
87            Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
88            Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
89            Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
90            Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
91        };
92        unsafe { xconn.get_atom_unchecked(atom_name) }
93    }
94}
95
96pub struct MotifHints {
97    hints: MwmHints,
98}
99
100#[repr(C)]
101struct MwmHints {
102    flags: c_ulong,
103    functions: c_ulong,
104    decorations: c_ulong,
105    input_mode: c_long,
106    status: c_ulong,
107}
108
109#[allow(dead_code)]
110mod mwm {
111    use libc::c_ulong;
112
113    // Motif WM hints are obsolete, but still widely supported.
114    // https://stackoverflow.com/a/1909708
115    pub const MWM_HINTS_FUNCTIONS: c_ulong = 1 << 0;
116    pub const MWM_HINTS_DECORATIONS: c_ulong = 1 << 1;
117
118    pub const MWM_FUNC_ALL: c_ulong = 1 << 0;
119    pub const MWM_FUNC_RESIZE: c_ulong = 1 << 1;
120    pub const MWM_FUNC_MOVE: c_ulong = 1 << 2;
121    pub const MWM_FUNC_MINIMIZE: c_ulong = 1 << 3;
122    pub const MWM_FUNC_MAXIMIZE: c_ulong = 1 << 4;
123    pub const MWM_FUNC_CLOSE: c_ulong = 1 << 5;
124}
125
126impl MotifHints {
127    pub fn new() -> MotifHints {
128        MotifHints {
129            hints: MwmHints {
130                flags: 0,
131                functions: 0,
132                decorations: 0,
133                input_mode: 0,
134                status: 0,
135            },
136        }
137    }
138
139    pub fn set_decorations(&mut self, decorations: bool) {
140        self.hints.flags |= mwm::MWM_HINTS_DECORATIONS;
141        self.hints.decorations = decorations as c_ulong;
142    }
143
144    pub fn set_maximizable(&mut self, maximizable: bool) {
145        if maximizable {
146            self.add_func(mwm::MWM_FUNC_MAXIMIZE);
147        } else {
148            self.remove_func(mwm::MWM_FUNC_MAXIMIZE);
149        }
150    }
151
152    fn add_func(&mut self, func: c_ulong) {
153        if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 {
154            if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
155                self.hints.functions &= !func;
156            } else {
157                self.hints.functions |= func;
158            }
159        }
160    }
161
162    fn remove_func(&mut self, func: c_ulong) {
163        if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 {
164            self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS;
165            self.hints.functions = mwm::MWM_FUNC_ALL;
166        }
167
168        if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
169            self.hints.functions |= func;
170        } else {
171            self.hints.functions &= !func;
172        }
173    }
174}
175
176impl MwmHints {
177    fn as_slice(&self) -> &[c_ulong] {
178        unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) }
179    }
180}
181
182pub struct NormalHints<'a> {
183    size_hints: XSmartPointer<'a, ffi::XSizeHints>,
184}
185
186impl<'a> NormalHints<'a> {
187    pub fn new(xconn: &'a XConnection) -> Self {
188        NormalHints {
189            size_hints: xconn.alloc_size_hints(),
190        }
191    }
192
193    pub fn get_position(&self) -> Option<(i32, i32)> {
194        if has_flag(self.size_hints.flags, ffi::PPosition) {
195            Some((self.size_hints.x as i32, self.size_hints.y as i32))
196        } else {
197            None
198        }
199    }
200
201    pub fn set_position(&mut self, position: Option<(i32, i32)>) {
202        if let Some((x, y)) = position {
203            self.size_hints.flags |= ffi::PPosition;
204            self.size_hints.x = x as c_int;
205            self.size_hints.y = y as c_int;
206        } else {
207            self.size_hints.flags &= !ffi::PPosition;
208        }
209    }
210
211    // WARNING: This hint is obsolete
212    pub fn set_size(&mut self, size: Option<(u32, u32)>) {
213        if let Some((width, height)) = size {
214            self.size_hints.flags |= ffi::PSize;
215            self.size_hints.width = width as c_int;
216            self.size_hints.height = height as c_int;
217        } else {
218            self.size_hints.flags &= !ffi::PSize;
219        }
220    }
221
222    pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
223        if let Some((max_width, max_height)) = max_size {
224            self.size_hints.flags |= ffi::PMaxSize;
225            self.size_hints.max_width = max_width as c_int;
226            self.size_hints.max_height = max_height as c_int;
227        } else {
228            self.size_hints.flags &= !ffi::PMaxSize;
229        }
230    }
231
232    pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
233        if let Some((min_width, min_height)) = min_size {
234            self.size_hints.flags |= ffi::PMinSize;
235            self.size_hints.min_width = min_width as c_int;
236            self.size_hints.min_height = min_height as c_int;
237        } else {
238            self.size_hints.flags &= !ffi::PMinSize;
239        }
240    }
241
242    pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
243        if let Some((width_inc, height_inc)) = resize_increments {
244            self.size_hints.flags |= ffi::PResizeInc;
245            self.size_hints.width_inc = width_inc as c_int;
246            self.size_hints.height_inc = height_inc as c_int;
247        } else {
248            self.size_hints.flags &= !ffi::PResizeInc;
249        }
250    }
251
252    pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
253        if let Some((base_width, base_height)) = base_size {
254            self.size_hints.flags |= ffi::PBaseSize;
255            self.size_hints.base_width = base_width as c_int;
256            self.size_hints.base_height = base_height as c_int;
257        } else {
258            self.size_hints.flags &= !ffi::PBaseSize;
259        }
260    }
261}
262
263impl XConnection {
264    pub fn get_wm_hints(
265        &self,
266        window: ffi::Window,
267    ) -> Result<XSmartPointer<'_, ffi::XWMHints>, XError> {
268        let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
269        self.check_errors()?;
270        let wm_hints = if wm_hints.is_null() {
271            self.alloc_wm_hints()
272        } else {
273            XSmartPointer::new(self, wm_hints).unwrap()
274        };
275        Ok(wm_hints)
276    }
277
278    pub fn set_wm_hints(
279        &self,
280        window: ffi::Window,
281        wm_hints: XSmartPointer<'_, ffi::XWMHints>,
282    ) -> Flusher<'_> {
283        unsafe {
284            (self.xlib.XSetWMHints)(self.display, window, wm_hints.ptr);
285        }
286        Flusher::new(self)
287    }
288
289    pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
290        let size_hints = self.alloc_size_hints();
291        let mut supplied_by_user = MaybeUninit::uninit();
292        unsafe {
293            (self.xlib.XGetWMNormalHints)(
294                self.display,
295                window,
296                size_hints.ptr,
297                supplied_by_user.as_mut_ptr(),
298            );
299        }
300        self.check_errors().map(|_| NormalHints { size_hints })
301    }
302
303    pub fn set_normal_hints(
304        &self,
305        window: ffi::Window,
306        normal_hints: NormalHints<'_>,
307    ) -> Flusher<'_> {
308        unsafe {
309            (self.xlib.XSetWMNormalHints)(self.display, window, normal_hints.size_hints.ptr);
310        }
311        Flusher::new(self)
312    }
313
314    pub fn get_motif_hints(&self, window: ffi::Window) -> MotifHints {
315        let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
316
317        let mut hints = MotifHints::new();
318
319        if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
320            hints.hints.flags = props.get(0).cloned().unwrap_or(0);
321            hints.hints.functions = props.get(1).cloned().unwrap_or(0);
322            hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
323            hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long;
324            hints.hints.status = props.get(4).cloned().unwrap_or(0);
325        }
326
327        hints
328    }
329
330    pub fn set_motif_hints(&self, window: ffi::Window, hints: &MotifHints) -> Flusher<'_> {
331        let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
332
333        self.change_property(
334            window,
335            motif_hints,
336            motif_hints,
337            PropMode::Replace,
338            hints.hints.as_slice(),
339        )
340    }
341}