Skip to main content

xlib_display_server/xwrap/
window.rs

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