xlib_display_server/xwrap/
window.rs

1//! Xlib calls related to a window.
2use super::{
3    on_error_from_xlib, on_error_from_xlib_dummy, Window, WindowHandle, ICONIC_STATE, NORMAL_STATE,
4    ROOT_EVENT_MASK, WITHDRAWN_STATE,
5};
6use crate::{XWrap, XlibWindowHandle};
7use leftwm_core::config::WindowHidingStrategy;
8use leftwm_core::models::{WindowChange, WindowType, Xyhw, XyhwChange};
9use leftwm_core::DisplayEvent;
10use std::os::raw::{c_long, c_ulong};
11use x11_dl::xlib;
12
13impl XWrap {
14    /// Sets up a window before we manage it.
15    #[must_use]
16    pub fn setup_window(&self, window: xlib::Window) -> Option<DisplayEvent<XlibWindowHandle>> {
17        // Check that the window isn't requesting to be unmanaged
18        let attrs = match self.get_window_attrs(window) {
19            Ok(attr) if attr.override_redirect == 0 && !self.managed_windows.contains(&window) => {
20                attr
21            }
22            _ => return None,
23        };
24        let handle = WindowHandle(XlibWindowHandle(window));
25        // Gather info about the window from xlib.
26        let name = self.get_window_name(window);
27        let legacy_name = self.get_window_legacy_name(window);
28        let class = self.get_window_class(window);
29        let pid = self.get_window_pid(window);
30        let r#type = self.get_window_type(window);
31        let states = self.get_window_states(window);
32        let actions = self.get_window_actions_atoms(window);
33        let mut can_resize = actions.contains(&self.atoms.NetWMActionResize);
34        let trans = self.get_transient_for(window);
35        let sizing_hint = self.get_hint_sizing_as_xyhw(window);
36        let wm_hint = self.get_wmhints(window);
37
38        // Build the new window, and fill in info about it.
39        let mut w = Window::new(handle, name, pid);
40        if let Some((res_name, res_class)) = class {
41            w.res_name = Some(res_name);
42            w.res_class = Some(res_class);
43        }
44        w.legacy_name = legacy_name;
45        w.r#type = r#type.clone();
46        w.states = states;
47        if let Some(trans) = trans {
48            w.transient = Some(WindowHandle(XlibWindowHandle(trans)));
49        }
50        // Initialise the windows floating with the pre-mapped settings.
51        let xyhw = XyhwChange {
52            x: Some(attrs.x),
53            y: Some(attrs.y),
54            w: Some(attrs.width),
55            h: Some(attrs.height),
56            ..XyhwChange::default()
57        };
58        xyhw.update_window_floating(&mut w);
59        let mut requested = Xyhw::default();
60        xyhw.update(&mut requested);
61        if let Some(mut hint) = sizing_hint {
62            // Ignore this for now for non-splashes as it causes issues, e.g. mintstick is non-resizable but is too
63            // small, issue #614: https://github.com/leftwm/leftwm/issues/614.
64            can_resize = match (r#type, hint.minw, hint.minh, hint.maxw, hint.maxh) {
65                (
66                    WindowType::Splash,
67                    Some(min_width),
68                    Some(min_height),
69                    Some(max_width),
70                    Some(max_height),
71                ) => can_resize || min_width != max_width || min_height != max_height,
72                _ => true,
73            };
74            // Use the pre-mapped sizes if they are bigger.
75            hint.w = std::cmp::max(xyhw.w, hint.w);
76            hint.h = std::cmp::max(xyhw.h, hint.h);
77            hint.update_window_floating(&mut w);
78            hint.update(&mut requested);
79        }
80        w.requested = Some(requested);
81        w.can_resize = can_resize;
82        if let Some(hint) = wm_hint {
83            w.never_focus = hint.flags & xlib::InputHint != 0 && hint.input == 0;
84        }
85        if let Some(hint) = wm_hint {
86            w.urgent = hint.flags & xlib::XUrgencyHint != 0;
87        }
88        // Is this needed? Made it so it doens't overwrite prior sizing.
89        if w.floating() && sizing_hint.is_none() {
90            if let Ok(geo) = self.get_window_geometry(window) {
91                geo.update_window_floating(&mut w);
92            }
93        }
94
95        let cursor = self.get_cursor_point().unwrap_or_default();
96        Some(DisplayEvent::WindowCreate(w, cursor.0, cursor.1))
97    }
98
99    /// Sets up a window that we want to manage.
100    // `XMapWindow`: https://tronche.com/gui/x/xlib/window/XMapWindow.html
101    pub fn setup_managed_window(
102        &mut self,
103        h: WindowHandle<XlibWindowHandle>,
104        floating: bool,
105        follow_mouse: bool,
106    ) -> Option<DisplayEvent<XlibWindowHandle>> {
107        let WindowHandle(XlibWindowHandle(handle)) = h;
108        self.subscribe_to_window_events(handle);
109        self.managed_windows.push(handle);
110        // Make sure the window is mapped.
111        unsafe { (self.xlib.XMapWindow)(self.display, handle) };
112        // Let Xlib know we are managing this window.
113        let list = vec![handle as c_long];
114        self.append_property_long(self.root, self.atoms.NetClientList, xlib::XA_WINDOW, &list);
115
116        // Make sure there is at least an empty list of _NET_WM_STATE.
117        let states = self.get_window_states_atoms(handle);
118        self.set_window_states_atoms(handle, &states);
119        // Set WM_STATE to normal state to allow window sharing.
120        self.set_wm_states(handle, &[NORMAL_STATE]);
121
122        let r#type = self.get_window_type(handle);
123        if r#type == WindowType::Dock || r#type == WindowType::Desktop {
124            if let Some(dock_area) = self.get_window_strut_array(handle) {
125                let dems = self.get_screens_area_dimensions();
126                let screen = self
127                    .get_screens()
128                    .iter()
129                    .find(|s| s.contains_dock_area(dock_area, dems))?
130                    .clone();
131
132                if let Some(xyhw) = dock_area.as_xyhw(dems.0, dems.1, &screen) {
133                    let mut change = WindowChange::new(h);
134                    change.strut = Some(xyhw.into());
135                    change.r#type = Some(r#type);
136                    return Some(DisplayEvent::WindowChange(change));
137                }
138            } else if let Ok(geo) = self.get_window_geometry(handle) {
139                let mut xyhw = Xyhw::default();
140                geo.update(&mut xyhw);
141                let mut change = WindowChange::new(h);
142                change.strut = Some(xyhw.into());
143                change.r#type = Some(r#type);
144                return Some(DisplayEvent::WindowChange(change));
145            }
146        } else {
147            let color = if floating {
148                self.colors.floating
149            } else {
150                self.colors.normal
151            };
152            self.set_window_border_color(handle, color);
153
154            if follow_mouse {
155                _ = self.move_cursor_to_window(handle);
156            }
157            if self.focus_behaviour.is_clickto() {
158                self.grab_mouse_clicks(handle, false);
159            }
160        }
161        None
162    }
163
164    /// Teardown a managed window when it is destroyed.
165    // `XGrabServer`: https://tronche.com/gui/x/xlib/window-and-session-manager/XGrabServer.html
166    // `XUngrabServer`: https://tronche.com/gui/x/xlib/window-and-session-manager/XUngrabServer.html
167    pub fn teardown_managed_window(&mut self, h: &WindowHandle<XlibWindowHandle>, destroyed: bool) {
168        let WindowHandle(XlibWindowHandle(handle)) = h;
169        self.managed_windows.retain(|x| *x != *handle);
170        if !destroyed {
171            unsafe {
172                (self.xlib.XGrabServer)(self.display);
173                (self.xlib.XSetErrorHandler)(Some(on_error_from_xlib_dummy));
174                self.ungrab_buttons(*handle);
175                self.set_wm_states(*handle, &[WITHDRAWN_STATE]);
176                self.sync();
177                (self.xlib.XSetErrorHandler)(Some(on_error_from_xlib));
178                (self.xlib.XUngrabServer)(self.display);
179            }
180        }
181        self.set_client_list();
182    }
183
184    /// Updates a window.
185    pub fn update_window(&self, window: &Window<XlibWindowHandle>) {
186        let WindowHandle(XlibWindowHandle(handle)) = window.handle;
187        if window.visible() {
188            let changes = xlib::XWindowChanges {
189                x: window.x(),
190                y: window.y(),
191                width: window.width(),
192                height: window.height(),
193                border_width: window.border(),
194                sibling: 0,    // Not unlocked.
195                stack_mode: 0, // Not unlocked.
196            };
197            let unlock =
198                xlib::CWX | xlib::CWY | xlib::CWWidth | xlib::CWHeight | xlib::CWBorderWidth;
199            self.set_window_config(handle, changes, u32::from(unlock));
200            self.configure_window(window);
201        }
202        let Some(state) = self.get_wm_state(handle) else {
203            return;
204        };
205        // Only change when needed. This prevents task bar icons flashing (especially with steam).
206        if window.visible() && state != NORMAL_STATE {
207            self.toggle_window_visibility(handle, true, window.hiding_strategy);
208        } else if !window.visible() && state != ICONIC_STATE {
209            self.toggle_window_visibility(handle, false, window.hiding_strategy);
210        }
211    }
212
213    /// Show or hide a window, depending on its current visibility.
214    /// Depending on the configured `window_hiding_strategy`, this will toggle window visibility by moving
215    /// the window out of / in to view, or map / unmap it in the display server.
216    ///
217    /// see `<https://github.com/leftwm/leftwm/issues/1100>` and `<https://github.com/leftwm/leftwm/pull/1274>` for details
218    pub fn toggle_window_visibility(
219        &self,
220        window: xlib::Window,
221        visible: bool,
222        preferred_stategy: Option<WindowHidingStrategy>,
223    ) {
224        let hiding_strategy = preferred_stategy.unwrap_or(self.window_hiding_strategy);
225        let maybe_change_mask = |mask| {
226            if let WindowHidingStrategy::Unmap = hiding_strategy {
227                let mut attrs: xlib::XSetWindowAttributes = unsafe { std::mem::zeroed() };
228                attrs.event_mask = mask;
229                self.change_window_attributes(self.root, xlib::CWEventMask, attrs);
230            }
231        };
232        // We don't want to receive this potential map or unmap event.
233        maybe_change_mask(ROOT_EVENT_MASK & !(xlib::SubstructureNotifyMask));
234
235        if visible {
236            // NOTE: The window does not need to be moved here in case of non-unmap strategy,
237            // if it's beeing made visible it's going to be naturally tiled or placed floating where it should.
238            if hiding_strategy == WindowHidingStrategy::Unmap {
239                unsafe { (self.xlib.XMapWindow)(self.display, window) };
240            }
241
242            // Set WM_STATE to normal state.
243            self.set_wm_states(window, &[NORMAL_STATE]);
244            // Regrab the mouse clicks but ignore `dock` windows as some don't handle click events put on them
245            if self.focus_behaviour.is_clickto() && self.get_window_type(window) != WindowType::Dock
246            {
247                self.grab_mouse_clicks(window, false);
248            }
249        } else {
250            // Ungrab the mouse clicks.
251            self.ungrab_buttons(window);
252
253            match hiding_strategy {
254                WindowHidingStrategy::Unmap => {
255                    unsafe { (self.xlib.XUnmapWindow)(self.display, window) };
256                }
257                WindowHidingStrategy::MoveMinimize | WindowHidingStrategy::MoveOnly => {
258                    // Move the window out of view, so it can still be captured if necessary
259                    let Ok(window_geometry) = self.get_window_geometry(window) else {
260                        tracing::error!("Error querying window geometry for window {}", window);
261                        return;
262                    };
263
264                    let (x, y) = (
265                        window_geometry
266                            .w
267                            .unwrap_or_else(|| self.get_screens_area_dimensions().0)
268                            * -2,
269                        window_geometry
270                            .h
271                            .unwrap_or_else(|| self.get_screens_area_dimensions().1)
272                            * -2,
273                    );
274
275                    let mut window_changes: xlib::XWindowChanges = unsafe { std::mem::zeroed() };
276                    window_changes.x = x;
277                    window_changes.y = y;
278                    self.set_window_config(
279                        window,
280                        window_changes,
281                        u32::from(xlib::CWX | xlib::CWY),
282                    );
283                    self.move_resize_window(window, x, y, 0, 0);
284                }
285            }
286
287            // Set WM_STATE to iconic state.
288            if hiding_strategy == WindowHidingStrategy::Unmap
289                || hiding_strategy == WindowHidingStrategy::MoveMinimize
290            {
291                self.set_wm_states(window, &[ICONIC_STATE]);
292            }
293        }
294
295        maybe_change_mask(ROOT_EVENT_MASK);
296    }
297
298    /// Makes a window take focus.
299    pub fn window_take_focus(
300        &mut self,
301        window: &Window<XlibWindowHandle>,
302        previous: Option<&Window<XlibWindowHandle>>,
303    ) {
304        let WindowHandle(XlibWindowHandle(handle)) = window.handle;
305        // Update previous window.
306        if let Some(previous) = previous {
307            let WindowHandle(XlibWindowHandle(previous_handle)) = previous.handle;
308            let color = if previous.floating() {
309                self.colors.floating
310            } else {
311                self.colors.normal
312            };
313            self.set_window_border_color(previous_handle, color);
314            // Open up button1 clicking on the previously focused window.
315            if self.focus_behaviour.is_clickto() {
316                self.grab_mouse_clicks(previous_handle, false);
317            }
318        }
319        self.focused_window = handle;
320        self.grab_mouse_clicks(handle, true);
321        self.set_window_urgency(handle, false);
322        self.set_window_border_color(handle, self.colors.active);
323        self.focus(handle, window.never_focus);
324        self.sync();
325    }
326
327    /// Focuses a window.
328    // `XSetInputFocus`: https://tronche.com/gui/x/xlib/input/XSetInputFocus.html
329    pub fn focus(&mut self, window: xlib::Window, never_focus: bool) {
330        if !never_focus {
331            unsafe {
332                (self.xlib.XSetInputFocus)(
333                    self.display,
334                    window,
335                    xlib::RevertToPointerRoot,
336                    xlib::CurrentTime,
337                );
338                let list = vec![window as c_long];
339                // Mark this window as the `_NET_ACTIVE_WINDOW`
340                self.replace_property_long(
341                    self.root,
342                    self.atoms.NetActiveWindow,
343                    xlib::XA_WINDOW,
344                    &list,
345                );
346                std::mem::forget(list);
347            }
348        }
349        // Tell the window to take focus
350        self.send_xevent_atom(window, self.atoms.WMTakeFocus);
351    }
352
353    /// Unfocuses all windows.
354    // `XSetInputFocus`: https://tronche.com/gui/x/xlib/input/XSetInputFocus.html
355    pub fn unfocus(&self, handle: Option<WindowHandle<XlibWindowHandle>>, floating: bool) {
356        if let Some(WindowHandle(XlibWindowHandle(handle))) = handle {
357            let color = if floating {
358                self.colors.floating
359            } else {
360                self.colors.normal
361            };
362            self.set_window_border_color(handle, color);
363
364            self.grab_mouse_clicks(handle, false);
365        }
366        unsafe {
367            (self.xlib.XSetInputFocus)(
368                self.display,
369                self.root,
370                xlib::RevertToPointerRoot,
371                xlib::CurrentTime,
372            );
373            self.replace_property_long(
374                self.root,
375                self.atoms.NetActiveWindow,
376                xlib::XA_WINDOW,
377                &[c_long::MAX],
378            );
379        }
380    }
381
382    /// Send a `XConfigureEvent` for a window to X.
383    pub fn configure_window(&self, window: &Window<XlibWindowHandle>) {
384        let WindowHandle(XlibWindowHandle(handle)) = window.handle;
385        let mut configure_event: xlib::XConfigureEvent = unsafe { std::mem::zeroed() };
386        configure_event.type_ = xlib::ConfigureNotify;
387        configure_event.display = self.display;
388        configure_event.event = handle;
389        configure_event.window = handle;
390        configure_event.x = window.x();
391        configure_event.y = window.y();
392        configure_event.width = window.width();
393        configure_event.height = window.height();
394        configure_event.border_width = window.border;
395        configure_event.above = 0;
396        configure_event.override_redirect = 0;
397        self.send_xevent(
398            handle,
399            0,
400            xlib::StructureNotifyMask,
401            &mut configure_event.into(),
402        );
403    }
404
405    /// Change a windows attributes.
406    // `XChangeWindowAttributes`: https://tronche.com/gui/x/xlib/window/XChangeWindowAttributes.html
407    pub fn change_window_attributes(
408        &self,
409        window: xlib::Window,
410        mask: c_ulong,
411        mut attrs: xlib::XSetWindowAttributes,
412    ) {
413        unsafe {
414            (self.xlib.XChangeWindowAttributes)(self.display, window, mask, &mut attrs);
415        }
416    }
417
418    /// Restacks the windows to the order of the vec.
419    // `XRestackWindows`: https://tronche.com/gui/x/xlib/window/XRestackWindows.html
420    pub fn restack(&self, handles: Vec<WindowHandle<XlibWindowHandle>>) {
421        let mut windows = vec![];
422        for handle in handles {
423            let WindowHandle(XlibWindowHandle(window)) = handle;
424            windows.push(window);
425        }
426        let size = windows.len();
427        let ptr = windows.as_mut_ptr();
428        unsafe {
429            (self.xlib.XRestackWindows)(self.display, ptr, size as i32);
430        }
431    }
432
433    pub fn move_resize_window(&self, window: xlib::Window, x: i32, y: i32, w: u32, h: u32) {
434        unsafe {
435            (self.xlib.XMoveResizeWindow)(self.display, window, x, y, w, h);
436        }
437    }
438
439    /// Raise a window.
440    // `XRaiseWindow`: https://tronche.com/gui/x/xlib/window/XRaiseWindow.html
441    pub fn move_to_top(&self, handle: &WindowHandle<XlibWindowHandle>) {
442        let WindowHandle(XlibWindowHandle(window)) = handle;
443        unsafe {
444            (self.xlib.XRaiseWindow)(self.display, *window);
445        }
446    }
447
448    /// Kills a window.
449    // `XGrabServer`: https://tronche.com/gui/x/xlib/window-and-session-manager/XGrabServer.html
450    // `XSetCloseDownMode`: https://tronche.com/gui/x/xlib/display/XSetCloseDownMode.html
451    // `XKillClient`: https://tronche.com/gui/x/xlib/window-and-session-manager/XKillClient.html
452    // `XUngrabServer`: https://tronche.com/gui/x/xlib/window-and-session-manager/XUngrabServer.html
453    pub fn kill_window(&self, h: &WindowHandle<XlibWindowHandle>) {
454        let WindowHandle(XlibWindowHandle(handle)) = h;
455        // Nicely ask the window to close.
456        if !self.send_xevent_atom(*handle, self.atoms.WMDelete) {
457            // Force kill the window.
458            unsafe {
459                (self.xlib.XGrabServer)(self.display);
460                (self.xlib.XSetErrorHandler)(Some(on_error_from_xlib_dummy));
461                (self.xlib.XSetCloseDownMode)(self.display, xlib::DestroyAll);
462                (self.xlib.XKillClient)(self.display, *handle);
463                self.sync();
464                (self.xlib.XSetErrorHandler)(Some(on_error_from_xlib));
465                (self.xlib.XUngrabServer)(self.display);
466            }
467        }
468    }
469
470    /// Forcibly unmap a window.
471    pub fn force_unmapped(&mut self, window: xlib::Window) {
472        let managed = self.managed_windows.contains(&window);
473        if managed {
474            self.managed_windows.retain(|x| *x != window);
475            self.set_client_list();
476        }
477    }
478
479    /// Subscribe to an event of a window.
480    // `XSelectInput`: https://tronche.com/gui/x/xlib/event-handling/XSelectInput.html
481    pub fn subscribe_to_event(&self, window: xlib::Window, mask: c_long) {
482        unsafe { (self.xlib.XSelectInput)(self.display, window, mask) };
483    }
484
485    /// Subscribe to the wanted events of a window.
486    pub fn subscribe_to_window_events(&self, window: xlib::Window) {
487        let mask = xlib::EnterWindowMask | xlib::FocusChangeMask | xlib::PropertyChangeMask;
488        self.subscribe_to_event(window, mask);
489    }
490}