aboutsummaryrefslogtreecommitdiff
path: root/src/layout.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout.rs')
-rw-r--r--src/layout.rs4025
1 files changed, 0 insertions, 4025 deletions
diff --git a/src/layout.rs b/src/layout.rs
deleted file mode 100644
index bf012311..00000000
--- a/src/layout.rs
+++ /dev/null
@@ -1,4025 +0,0 @@
-//! Window layout logic.
-//!
-//! Niri implements scrollable tiling with workspaces. There's one primary output, and potentially
-//! multiple other outputs.
-//!
-//! Our layout has the following invariants:
-//!
-//! 1. Disconnecting and reconnecting the same output must not change the layout.
-//! * This includes both secondary outputs and the primary output.
-//! 2. Connecting an output must not change the layout for any workspaces that were never on that
-//! output.
-//!
-//! Therefore, we implement the following logic: every workspace keeps track of which output it
-//! originated on. When an output disconnects, its workspace (or workspaces, in case of the primary
-//! output disconnecting) are appended to the (potentially new) primary output, but remember their
-//! original output. Then, if the original output connects again, all workspaces originally from
-//! there move back to that output.
-//!
-//! In order to avoid surprising behavior, if the user creates or moves any new windows onto a
-//! workspace, it forgets its original output, and its current output becomes its original output.
-//! Imagine a scenario: the user works with a laptop and a monitor at home, then takes their laptop
-//! with them, disconnecting the monitor, and keeps working as normal, using the second monitor's
-//! workspace just like any other. Then they come back, reconnect the second monitor, and now we
-//! don't want an unassuming workspace to end up on it.
-//!
-//! ## Workspaces-only-on-primary considerations
-//!
-//! If this logic results in more than one workspace present on a secondary output, then as a
-//! compromise we only keep the first workspace there, and move the rest to the primary output,
-//! making the primary output their original output.
-
-use std::cmp::{max, min};
-use std::iter::zip;
-use std::mem;
-use std::rc::Rc;
-use std::time::Duration;
-
-use arrayvec::ArrayVec;
-use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
-use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
-use smithay::backend::renderer::element::utils::{
- CropRenderElement, Relocate, RelocateRenderElement,
-};
-use smithay::backend::renderer::element::{AsRenderElements, Kind};
-use smithay::backend::renderer::gles::GlesRenderer;
-use smithay::backend::renderer::ImportAll;
-use smithay::desktop::space::SpaceElement;
-use smithay::desktop::{layer_map_for_output, Window};
-use smithay::output::Output;
-use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
-use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
-use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
-use smithay::render_elements;
-use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
-use smithay::wayland::compositor::{send_surface_state, with_states};
-use smithay::wayland::shell::xdg::SurfaceCachedState;
-
-use crate::animation::Animation;
-use crate::config::{self, Color, Config, PresetWidth, SizeChange, Struts};
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct OutputId(String);
-
-render_elements! {
- #[derive(Debug)]
- pub WorkspaceRenderElement<R> where R: ImportAll;
- Wayland = WaylandSurfaceRenderElement<R>,
- FocusRing = SolidColorRenderElement,
-}
-pub type MonitorRenderElement<R> =
- RelocateRenderElement<CropRenderElement<WorkspaceRenderElement<R>>>;
-
-pub trait LayoutElement: SpaceElement + PartialEq + Clone {
- fn request_size(&self, size: Size<i32, Logical>);
- fn request_fullscreen(&self, size: Size<i32, Logical>);
- fn min_size(&self) -> Size<i32, Logical>;
- fn max_size(&self) -> Size<i32, Logical>;
- fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool;
- fn has_ssd(&self) -> bool;
- fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
-}
-
-#[derive(Debug)]
-pub struct Layout<W: LayoutElement> {
- /// Monitors and workspaes in the layout.
- monitor_set: MonitorSet<W>,
- /// Configurable properties of the layout.
- options: Rc<Options>,
-}
-
-#[derive(Debug)]
-enum MonitorSet<W: LayoutElement> {
- /// At least one output is connected.
- Normal {
- /// Connected monitors.
- monitors: Vec<Monitor<W>>,
- /// Index of the primary monitor.
- primary_idx: usize,
- /// Index of the active monitor.
- active_monitor_idx: usize,
- },
- /// No outputs are connected, and these are the workspaces.
- NoOutputs {
- /// The workspaces.
- workspaces: Vec<Workspace<W>>,
- },
-}
-
-#[derive(Debug)]
-pub struct Monitor<W: LayoutElement> {
- /// Output for this monitor.
- output: Output,
- // Must always contain at least one.
- workspaces: Vec<Workspace<W>>,
- /// Index of the currently active workspace.
- active_workspace_idx: usize,
- /// In-progress switch between workspaces.
- workspace_switch: Option<WorkspaceSwitch>,
- /// Configurable properties of the layout.
- options: Rc<Options>,
-}
-
-#[derive(Debug)]
-enum WorkspaceSwitch {
- Animation(Animation),
- Gesture(WorkspaceSwitchGesture),
-}
-
-#[derive(Debug)]
-struct WorkspaceSwitchGesture {
- /// Index of the workspace where the gesture was started.
- center_idx: usize,
- /// Current, fractional workspace index.
- current_idx: f64,
-}
-
-#[derive(Debug)]
-pub struct Workspace<W: LayoutElement> {
- /// The original output of this workspace.
- ///
- /// Most of the time this will be the workspace's current output, however, after an output
- /// disconnection, it may remain pointing to the disconnected output.
- original_output: OutputId,
-
- /// Current output of this workspace.
- output: Option<Output>,
-
- /// Latest known view size for this workspace.
- ///
- /// This should be computed from the current workspace output size, or, if all outputs have
- /// been disconnected, preserved until a new output is connected.
- view_size: Size<i32, Logical>,
-
- /// Latest known working area for this workspace.
- ///
- /// This is similar to view size, but takes into account things like layer shell exclusive
- /// zones.
- working_area: Rectangle<i32, Logical>,
-
- /// Columns of windows on this workspace.
- columns: Vec<Column<W>>,
-
- /// Index of the currently active column, if any.
- active_column_idx: usize,
-
- /// Focus ring buffer and parameters.
- focus_ring: FocusRing,
-
- /// Offset of the view computed from the active column.
- ///
- /// Any gaps, including left padding from work area left exclusive zone, is handled
- /// with this view offset (rather than added as a constant elsewhere in the code). This allows
- /// for natural handling of fullscreen windows, which must ignore work area padding.
- view_offset: i32,
-
- /// Animation of the view offset, if one is currently ongoing.
- view_offset_anim: Option<Animation>,
-
- /// Whether to activate the previous, rather than the next, column upon column removal.
- ///
- /// When a new column is created and removed with no focus changes in-between, it is more
- /// natural to activate the previously-focused column. This variable tracks that.
- ///
- /// Since we only create-and-activate columns immediately to the right of the active column (in
- /// contrast to tabs in Firefox, for example), we can track this as a bool, rather than an
- /// index of the previous column to activate.
- activate_prev_column_on_removal: bool,
-
- /// Configurable properties of the layout.
- options: Rc<Options>,
-}
-
-#[derive(Debug)]
-struct FocusRing {
- buffers: [SolidColorBuffer; 4],
- locations: [Point<i32, Logical>; 4],
- is_off: bool,
- is_border: bool,
- width: i32,
- active_color: Color,
- inactive_color: Color,
-}
-
-#[derive(Debug, PartialEq)]
-struct Options {
- /// Padding around windows in logical pixels.
- gaps: i32,
- /// Extra padding around the working area in logical pixels.
- struts: Struts,
- focus_ring: config::FocusRing,
- /// Column widths that `toggle_width()` switches between.
- preset_widths: Vec<ColumnWidth>,
- /// Initial width for new windows.
- default_width: Option<ColumnWidth>,
-}
-
-impl Default for Options {
- fn default() -> Self {
- Self {
- gaps: 16,
- struts: Default::default(),
- focus_ring: Default::default(),
- preset_widths: vec![
- ColumnWidth::Proportion(1. / 3.),
- ColumnWidth::Proportion(0.5),
- ColumnWidth::Proportion(2. / 3.),
- ],
- default_width: None,
- }
- }
-}
-
-impl Options {
- fn from_config(config: &Config) -> Self {
- let preset_column_widths = &config.preset_column_widths;
-
- let preset_widths = if preset_column_widths.is_empty() {
- Options::default().preset_widths
- } else {
- preset_column_widths
- .iter()
- .copied()
- .map(ColumnWidth::from)
- .collect()
- };
-
- // Missing default_column_width maps to Some(ColumnWidth::Proportion(0.5)),
- // while present, but empty, maps to None.
- let default_width = config
- .default_column_width
- .as_ref()
- .map(|w| w.0.first().copied().map(ColumnWidth::from))
- .unwrap_or(Some(ColumnWidth::Proportion(0.5)));
-
- Self {
- gaps: config.gaps.into(),
- struts: config.struts,
- focus_ring: config.focus_ring,
- preset_widths,
- default_width,
- }
- }
-}
-
-/// Width of a column.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ColumnWidth {
- /// Proportion of the current view width.
- Proportion(f64),
- /// One of the proportion presets.
- ///
- /// This is separate from Proportion in order to be able to reliably cycle between preset
- /// proportions.
- Preset(usize),
- /// Fixed width in logical pixels.
- Fixed(i32),
-}
-
-impl From<PresetWidth> for ColumnWidth {
- fn from(value: PresetWidth) -> Self {
- match value {
- PresetWidth::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)),
- PresetWidth::Fixed(f) => Self::Fixed(f.clamp(1, 100000)),
- }
- }
-}
-
-/// Height of a window in a column.
-///
-/// Proportional height is intentionally omitted. With column widths you frequently want e.g. two
-/// columns side-by-side with 50% width each, and you want them to remain this way when moving to a
-/// differently sized monitor. Windows in a column, however, already auto-size to fill the available
-/// height, giving you this behavior. The only reason to set a different window height, then, is
-/// when you want something in the window to fit exactly, e.g. to fit 30 lines in a terminal, which
-/// corresponds to the `Fixed` variant.
-///
-/// This does not preclude the usual set of binds to set or resize a window proportionally. Just,
-/// they are converted to, and stored as fixed height right away, so that once you resize a window
-/// to fit the desired content, it can never become smaller than that when moving between monitors.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum WindowHeight {
- /// Automatically computed height, evenly distributed across the column.
- Auto,
- /// Fixed height in logical pixels.
- Fixed(i32),
-}
-
-#[derive(Debug)]
-struct Column<W: LayoutElement> {
- /// Windows in this column.
- ///
- /// Must be non-empty.
- windows: Vec<W>,
-
- /// Heights of the windows.
- ///
- /// Must have the same number of elements as `windows`.
- heights: Vec<WindowHeight>,
-
- /// Index of the currently active window.
- active_window_idx: usize,
-
- /// Desired width of this column.
- ///
- /// If the column is full-width or full-screened, this is the width that should be restored
- /// upon unfullscreening and untoggling full-width.
- width: ColumnWidth,
-
- /// Whether this column is full-width.
- is_full_width: bool,
-
- /// Whether this column contains a single full-screened window.
- is_fullscreen: bool,
-
- /// Latest known view size for this column's workspace.
- view_size: Size<i32, Logical>,
-
- /// Latest known working area for this column's workspace.
- working_area: Rectangle<i32, Logical>,
-
- /// Configurable properties of the layout.
- options: Rc<Options>,
-}
-
-impl OutputId {
- pub fn new(output: &Output) -> Self {
- Self(output.name())
- }
-}
-
-impl LayoutElement for Window {
- fn request_size(&self, size: Size<i32, Logical>) {
- self.toplevel().with_pending_state(|state| {
- state.size = Some(size);
- state.states.unset(xdg_toplevel::State::Fullscreen);
- });
- }
-
- fn request_fullscreen(&self, size: Size<i32, Logical>) {
- self.toplevel().with_pending_state(|state| {
- state.size = Some(size);
- state.states.set(xdg_toplevel::State::Fullscreen);
- });
- }
-
- fn min_size(&self) -> Size<i32, Logical> {
- with_states(self.toplevel().wl_surface(), |state| {
- let curr = state.cached_state.current::<SurfaceCachedState>();
- curr.min_size
- })
- }
-
- fn max_size(&self) -> Size<i32, Logical> {
- with_states(self.toplevel().wl_surface(), |state| {
- let curr = state.cached_state.current::<SurfaceCachedState>();
- curr.max_size
- })
- }
-
- fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
- self.toplevel().wl_surface() == wl_surface
- }
-
- fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) {
- self.with_surfaces(|surface, data| {
- send_surface_state(surface, data, scale, transform);
- });
- }
-
- fn has_ssd(&self) -> bool {
- self.toplevel().current_state().decoration_mode
- == Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
- }
-}
-
-impl FocusRing {
- fn update(
- &mut self,
- win_pos: Point<i32, Logical>,
- win_size: Size<i32, Logical>,
- is_border: bool,
- ) {
- if is_border {
- self.buffers[0].resize((win_size.w + self.width * 2, self.width));
- self.buffers[1].resize((win_size.w + self.width * 2, self.width));
- self.buffers[2].resize((self.width, win_size.h));
- self.buffers[3].resize((self.width, win_size.h));
-
- self.locations[0] = win_pos + Point::from((-self.width, -self.width));
- self.locations[1] = win_pos + Point::from((-self.width, win_size.h));
- self.locations[2] = win_pos + Point::from((-self.width, 0));
- self.locations[3] = win_pos + Point::from((win_size.w, 0));
- } else {
- let size = win_size + Size::from((self.width * 2, self.width * 2));
- self.buffers[0].resize(size);
- self.locations[0] = win_pos - Point::from((self.width, self.width));
- }
-
- self.is_border = is_border;
- }
-
- fn set_active(&mut self, is_active: bool) {
- let color = if is_active {
- self.active_color.into()
- } else {
- self.inactive_color.into()
- };
-
- for buf in &mut self.buffers {
- buf.set_color(color);
- }
- }
-
- fn render(&self, scale: Scale<f64>) -> impl Iterator<Item = SolidColorRenderElement> {
- let mut rv = ArrayVec::<_, 4>::new();
-
- if self.is_off {
- return rv.into_iter();
- }
-
- let mut push = |buffer, location: Point<i32, Logical>| {
- let elem = SolidColorRenderElement::from_buffer(
- buffer,
- location.to_physical_precise_round(scale),
- scale,
- 1.,
- Kind::Unspecified,
- );
- rv.push(elem);
- };
-
- if self.is_border {
- for (buf, loc) in zip(&self.buffers, self.locations) {
- push(buf, loc);
- }
- } else {
- push(&self.buffers[0], self.locations[0]);
- }
-
- rv.into_iter()
- }
-}
-
-impl FocusRing {
- fn new(config: config::FocusRing) -> Self {
- Self {
- buffers: Default::default(),
- locations: Default::default(),
- is_off: config.off,
- is_border: false,
- width: config.width.into(),
- active_color: config.active_color,
- inactive_color: config.inactive_color,
- }
- }
-}
-
-impl WorkspaceSwitch {
- fn current_idx(&self) -> f64 {
- match self {
- WorkspaceSwitch::Animation(anim) => anim.value(),
- WorkspaceSwitch::Gesture(gesture) => gesture.current_idx,
- }
- }
-
- /// Returns `true` if the workspace switch is [`Animation`].
- ///
- /// [`Animation`]: WorkspaceSwitch::Animation
- #[must_use]
- fn is_animation(&self) -> bool {
- matches!(self, Self::Animation(..))
- }
-}
-
-impl ColumnWidth {
- fn resolve(self, options: &Options, view_width: i32) -> i32 {
- match self {
- ColumnWidth::Proportion(proportion) => {
- ((view_width - options.gaps) as f64 * proportion).floor() as i32 - options.gaps
- }
- ColumnWidth::Preset(idx) => options.preset_widths[idx].resolve(options, view_width),
- ColumnWidth::Fixed(width) => width,
- }
- }
-}
-
-impl<W: LayoutElement> Layout<W> {
- pub fn new(config: &Config) -> Self {
- Self {
- monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
- options: Rc::new(Options::from_config(config)),
- }
- }
-
- pub fn add_output(&mut self, output: Output) {
- let id = OutputId::new(&output);
-
- self.monitor_set = match mem::take(&mut self.monitor_set) {
- MonitorSet::Normal {
- mut monitors,
- primary_idx,
- active_monitor_idx,
- } => {
- let primary = &mut monitors[primary_idx];
-
- let mut workspaces = vec![];
- for i in (0..primary.workspaces.len()).rev() {
- if primary.workspaces[i].original_output == id {
- let ws = primary.workspaces.remove(i);
-
- // The user could've closed a window while remaining on this workspace, on
- // another monitor. However, we will add an empty workspace in the end
- // instead.
- if ws.has_windows() {
- workspaces.push(ws);
- }
-
- if i <= primary.active_workspace_idx {
- primary.active_workspace_idx =
- primary.active_workspace_idx.saturating_sub(1);
- }
- }
- }
- workspaces.reverse();
-
- // Make sure there's always an empty workspace.
- workspaces.push(Workspace::new(output.clone(), self.options.clone()));
-
- for ws in &mut workspaces {
- ws.set_output(Some(output.clone()));
- }
-
- monitors.push(Monitor::new(output, workspaces, self.options.clone()));
- MonitorSet::Normal {
- monitors,
- primary_idx,
- active_monitor_idx,
- }
- }
- MonitorSet::NoOutputs { mut workspaces } => {
- // We know there are no empty workspaces there, so add one.
- workspaces.push(Workspace::new(output.clone(), self.options.clone()));
-
- for workspace in &mut workspaces {
- workspace.set_output(Some(output.clone()));
- }
-
- let monitor = Monitor::new(output, workspaces, self.options.clone());
-
- MonitorSet::Normal {
- monitors: vec![monitor],
- primary_idx: 0,
- active_monitor_idx: 0,
- }
- }
- }
- }
-
- pub fn remove_output(&mut self, output: &Output) {
- self.monitor_set = match mem::take(&mut self.monitor_set) {
- MonitorSet::Normal {
- mut monitors,
- mut primary_idx,
- mut active_monitor_idx,
- } => {
- let idx = monitors
- .iter()
- .position(|mon| &mon.output == output)
- .expect("trying to remove non-existing output");
- let monitor = monitors.remove(idx);
- let mut workspaces = monitor.workspaces;
-
- for ws in &mut workspaces {
- ws.set_output(None);
- }
-
- // Get rid of empty workspaces.
- workspaces.retain(|ws| ws.has_windows());
-
- if monitors.is_empty() {
- // Removed the last monitor.
- MonitorSet::NoOutputs { workspaces }
- } else {
- if primary_idx >= idx {
- // Update primary_idx to either still point at the same monitor, or at some
- // other monitor if the primary has been removed.
- primary_idx = primary_idx.saturating_sub(1);
- }
- if active_monitor_idx >= idx {
- // Update active_monitor_idx to either still point at the same monitor, or
- // at some other monitor if the active monitor has
- // been removed.
- active_monitor_idx = active_monitor_idx.saturating_sub(1);
- }
-
- let primary = &mut monitors[primary_idx];
- for ws in &mut workspaces {
- ws.set_output(Some(primary.output.clone()));
- }
-
- let empty_was_focused =
- primary.active_workspace_idx == primary.workspaces.len() - 1;
-
- // Push the workspaces from the removed monitor in the end, right before the
- // last, empty, workspace.
- let empty = primary.workspaces.remove(primary.workspaces.len() - 1);
- primary.workspaces.extend(workspaces);
- primary.workspaces.push(empty);
-
- // If the empty workspace was focused on the primary monitor, keep it focused.
- if empty_was_focused {
- primary.active_workspace_idx = primary.workspaces.len() - 1;
- }
-
- MonitorSet::Normal {
- monitors,
- primary_idx,
- active_monitor_idx,
- }
- }
- }
- MonitorSet::NoOutputs { .. } => {
- panic!("tried to remove output when there were already none")
- }
- }
- }
-
- pub fn add_window_by_idx(
- &mut self,
- monitor_idx: usize,
- workspace_idx: usize,
- window: W,
- activate: bool,
- width: ColumnWidth,
- is_full_width: bool,
- ) {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &mut self.monitor_set
- else {
- panic!()
- };
-
- monitors[monitor_idx].add_window(workspace_idx, window, activate, width, is_full_width);
-
- if activate {
- *active_monitor_idx = monitor_idx;
- }
- }
-
- /// Adds a new window to the layout.
- ///
- /// Returns an output that the window was added to, if there were any outputs.
- pub fn add_window(
- &mut self,
- window: W,
- width: Option<ColumnWidth>,
- is_full_width: bool,
- ) -> Option<&Output> {
- let width = width
- .or(self.options.default_width)
- .unwrap_or_else(|| ColumnWidth::Fixed(window.geometry().size.w));
-
- match &mut self.monitor_set {
- MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } => {
- let mon = &mut monitors[*active_monitor_idx];
-
- // Don't steal focus from an active fullscreen window.
- let mut activate = true;
- let ws = &mon.workspaces[mon.active_workspace_idx];
- if !ws.columns.is_empty() && ws.columns[ws.active_column_idx].is_fullscreen {
- activate = false;
- }
-
- mon.add_window(
- mon.active_workspace_idx,
- window,
- activate,
- width,
- is_full_width,
- );
- Some(&mon.output)
- }
- MonitorSet::NoOutputs { workspaces } => {
- let ws = if let Some(ws) = workspaces.get_mut(0) {
- ws
- } else {
- workspaces.push(Workspace::new_no_outputs(self.options.clone()));
- &mut workspaces[0]
- };
- ws.add_window(window, true, width, is_full_width);
- None
- }
- }
- }
-
- pub fn remove_window(&mut self, window: &W) {
- match &mut self.monitor_set {
- MonitorSet::Normal { monitors, .. } => {
- for mon in monitors {
- for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
- if ws.has_window(window) {
- ws.remove_window(window);
-
- // Clean up empty workspaces that are not active and not last.
- if !ws.has_windows()
- && idx != mon.active_workspace_idx
- && idx != mon.workspaces.len() - 1
- {
- mon.workspaces.remove(idx);
-
- if idx < mon.active_workspace_idx {
- mon.active_workspace_idx -= 1;
- }
- }
-
- break;
- }
- }
- }
- }
- MonitorSet::NoOutputs { workspaces, .. } => {
- for (idx, ws) in workspaces.iter_mut().enumerate() {
- if ws.has_window(window) {
- ws.remove_window(window);
-
- // Clean up empty workspaces.
- if !ws.has_windows() {
- workspaces.remove(idx);
- }
-
- break;
- }
- }
- }
- }
- }
-
- pub fn update_window(&mut self, window: &W) {
- match &mut self.monitor_set {
- MonitorSet::Normal { monitors, .. } => {
- for mon in monitors {
- for ws in &mut mon.workspaces {
- if ws.has_window(window) {
- ws.update_window(window);
- return;
- }
- }
- }
- }
- MonitorSet::NoOutputs { workspaces, .. } => {
- for ws in workspaces {
- if ws.has_window(window) {
- ws.update_window(window);
- return;
- }
- }
- }
- }
- }
-
- pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(W, Output)> {
- if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
- for mon in monitors {
- for ws in &mon.workspaces {
- if let Some(window) = ws.find_wl_surface(wl_surface) {
- return Some((window.clone(), mon.output.clone()));
- }
- }
- }
- }
-
- None
- }
-
- pub fn window_y(&self, window: &W) -> Option<i32> {
- match &self.monitor_set {
- MonitorSet::Normal { monitors, .. } => {
- for mon in monitors {
- for ws in &mon.workspaces {
- for col in &ws.columns {
- if let Some(idx) = col.windows.iter().position(|w| w == window) {
- return Some(col.window_y(idx));
- }
- }
- }
- }
- }
- MonitorSet::NoOutputs { workspaces, .. } => {
- for ws in workspaces {
- for col in &ws.columns {
- if let Some(idx) = col.windows.iter().position(|w| w == window) {
- return Some(col.window_y(idx));
- }
- }
- }
- }
- }
-
- None
- }
-
- pub fn update_output_size(&mut self, output: &Output) {
- let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
- panic!()
- };
-
- for mon in monitors {
- if &mon.output == output {
- let view_size = output_size(output);
- let working_area = compute_working_area(output, self.options.struts);
-
- for ws in &mut mon.workspaces {
- ws.set_view_size(view_size, working_area);
- }
-
- break;
- }
- }
- }
-
- pub fn activate_window(&mut self, window: &W) {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &mut self.monitor_set
- else {
- todo!()
- };
-
- for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
- for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
- if ws.has_window(window) {
- *active_monitor_idx = monitor_idx;
- ws.activate_window(window);
-
- // Switch to that workspace if not already during a transition.
- if mon.workspace_switch.is_none() {
- mon.switch_workspace(workspace_idx);
- }
-
- break;
- }
- }
- }
- }
-
- pub fn activate_output(&mut self, output: &Output) {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &mut self.monitor_set
- else {
- return;
- };
-
- let idx = monitors
- .iter()
- .position(|mon| &mon.output == output)
- .unwrap();
- *active_monitor_idx = idx;
- }
-
- pub fn active_output(&self) -> Option<&Output> {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &self.monitor_set
- else {
- return None;
- };
-
- Some(&monitors[*active_monitor_idx].output)
- }
-
- pub fn active_workspace(&self) -> Option<&Workspace<W>> {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &self.monitor_set
- else {
- return None;
- };
-
- let mon = &monitors[*active_monitor_idx];
- Some(&mon.workspaces[mon.active_workspace_idx])
- }
-
- pub fn active_window(&self) -> Option<(W, Output)> {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &self.monitor_set
- else {
- return None;
- };
-
- let mon = &monitors[*active_monitor_idx];
- let ws = &mon.workspaces[mon.active_workspace_idx];
-
- if ws.columns.is_empty() {
- return None;
- }
-
- let col = &ws.columns[ws.active_column_idx];
- Some((
- col.windows[col.active_window_idx].clone(),
- mon.output.clone(),
- ))
- }
-
- pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
- let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
- panic!()
- };
-
- let mon = monitors.iter().find(|mon| &mon.output == output).unwrap();
- mon.workspaces.iter().flat_map(|ws| ws.windows())
- }
-
- fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
- let MonitorSet::Normal {
- monitors,
- active_monitor_idx,
- ..
- } = &mut self.monitor_set
- else {
- return None;
- };
-
- Some(&mut monitors[*active_monitor_idx])
- }
-
- pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
- let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
- return None;
- };
-
- monitors.iter().find(|monitor| &monitor.output == output)
- }
-
- pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ {
- let monitors = if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
- &monitors[..]
- } else {
- &[][..]
- };
-
- monitors.iter().map(|mon| &mon.output)
- }
-
- pub fn move_left(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_left();
- }
-
- pub fn move_right(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_right();
- }
-
- pub fn move_down(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_down();
- }
-
- pub fn move_up(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_up();
- }
-
- pub fn move_down_or_to_workspace_down(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_down_or_to_workspace_down();
- }
-
- pub fn move_up_or_to_workspace_up(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.move_up_or_to_workspace_up();
- }
-
- pub fn focus_left(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.focus_left();
- }
-
- pub fn focus_right(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.focus_right();
- }
-
- pub fn focus_down(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.focus_down();
- }
-
- pub fn focus_up(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };
- monitor.focus_up();
- }
-
- pub fn focus_window_or_workspace_down(&mut self) {
- let Some(monitor) = self.active_monitor() else {
- return;
- };