// SPDX-License-Identifier: GPL-3.0-or-later pub mod rules; use std::{cell::RefCell, ops::Deref}; use smithay::{ desktop::{space::SpaceElement, Window, WindowSurface}, output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{IsAlive, Logical, Point, Rectangle}, wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData}, }; use crate::state::{State, WithState}; use self::window_state::WindowElementState; pub mod window_state; #[derive(Debug, Clone, PartialEq)] pub struct WindowElement(Window); impl Deref for WindowElement { type Target = Window; fn deref(&self) -> &Self::Target { &self.0 } } impl WindowElement { pub fn new(window: Window) -> Self { Self(window) } /// Send a geometry change without mapping windows or sending /// configures to Wayland windows. /// /// Xwayland windows will still receive a configure. /// /// RefCell Safety: This method uses a [`RefCell`] on this window. pub fn change_geometry(&self, new_geo: Rectangle) { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { toplevel.with_pending_state(|state| { state.size = Some(new_geo.size); }); } WindowSurface::X11(surface) => { if !surface.is_override_redirect() { surface .configure(new_geo) .expect("failed to configure x11 win"); } } } self.with_state_mut(|state| { state.target_loc = Some(new_geo.loc); }); } /// Get this window's class (app id in Wayland but hey old habits die hard). pub fn class(&self) -> Option { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { compositor::with_states(toplevel.wl_surface(), |states| { states .data_map .get::() .expect("XdgToplevelSurfaceData wasn't in surface's data map") .lock() .expect("Failed to lock Mutex") .app_id .clone() }) } WindowSurface::X11(surface) => Some(surface.class()), } } /// Get this window's title. pub fn title(&self) -> Option { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { compositor::with_states(toplevel.wl_surface(), |states| { states .data_map .get::() .expect("XdgToplevelSurfaceData wasn't in surface's data map") .lock() .expect("Failed to lock Mutex") .title .clone() }) } WindowSurface::X11(surface) => Some(surface.title()), } } /// Get the output this window is on. /// /// This method gets the first tag the window has and returns its output. /// /// RefCell Safety: This method uses a [`RefCell`] on this window and every mapped output. pub fn output(&self, state: &State) -> Option { self.with_state(|st| st.tags.first().and_then(|tag| tag.output(state))) } /// Returns whether or not this window has an active tag. /// /// RefCell Safety: This calls `with_state` on `self`. pub fn is_on_active_tag(&self) -> bool { self.with_state(|state| state.tags.iter().any(|tag| tag.active())) } /// Place this window on the given output, giving it the output's focused tags. /// /// RefCell Safety: Uses `with_state_mut` on the window and `with_state` on the output pub fn place_on_output(&self, output: &Output) { self.with_state_mut(|state| { state.tags = output.with_state(|state| { let output_tags = state.focused_tags().cloned().collect::>(); if !output_tags.is_empty() { output_tags } else if let Some(first_tag) = state.tags.first() { vec![first_tag.clone()] } else { vec![] } }); tracing::debug!( "Placed window on {} with tags {:?}", output.name(), state.tags ); }); } pub fn is_x11_override_redirect(&self) -> bool { matches!(self.x11_surface(), Some(surface) if surface.is_override_redirect()) } } impl SpaceElement for WindowElement { fn bbox(&self) -> Rectangle { self.0.bbox() } fn is_in_input_region(&self, point: &Point) -> bool { self.0.is_in_input_region(point) } fn set_activate(&self, activated: bool) { self.0.set_activate(activated) } fn output_enter(&self, output: &Output, overlap: Rectangle) { self.0.output_enter(output, overlap) } fn output_leave(&self, output: &Output) { self.0.output_leave(output) } fn geometry(&self) -> Rectangle { self.0.geometry() } fn z_index(&self) -> u8 { self.0.z_index() } fn refresh(&self) { self.0.refresh(); } } impl IsAlive for WindowElement { fn alive(&self) -> bool { self.0.alive() } } impl WithState for WindowElement { type State = WindowElementState; fn with_state(&self, func: F) -> T where F: FnOnce(&Self::State) -> T, { let state = self .user_data() .get_or_insert(|| RefCell::new(WindowElementState::new())); func(&state.borrow()) } fn with_state_mut(&self, func: F) -> T where F: FnOnce(&mut Self::State) -> T, { let state = self .user_data() .get_or_insert(|| RefCell::new(WindowElementState::new())); func(&mut state.borrow_mut()) } } impl State { /// Returns the [Window] associated with a given [WlSurface]. pub fn window_for_surface(&self, surface: &WlSurface) -> Option { self.space .elements() .find(|window| window.wl_surface().map(|s| s == *surface).unwrap_or(false)) .or_else(|| { self.windows .iter() .find(|&win| win.wl_surface().is_some_and(|surf| &surf == surface)) }) .cloned() } /// `window_for_surface` but for windows that haven't commited a buffer yet. /// /// Currently only used in `ensure_initial_configure` in [`handlers`][crate::handlers]. pub fn new_window_for_surface(&self, surface: &WlSurface) -> Option { self.new_windows .iter() .find(|&win| win.wl_surface().is_some_and(|surf| &surf == surface)) .cloned() } }