diff options
| -rw-r--r-- | niri-config/src/lib.rs | 15 | ||||
| -rw-r--r-- | src/layout/floating.rs | 14 | ||||
| -rw-r--r-- | src/layout/tile.rs | 5 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 19 | ||||
| -rw-r--r-- | src/window/mod.rs | 12 | ||||
| -rw-r--r-- | wiki/Configuration:-Window-Rules.md | 24 |
6 files changed, 78 insertions, 11 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index cfc1853c..747ae9cb 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1019,6 +1019,8 @@ pub struct WindowRule { pub block_out_from: Option<BlockOutFrom>, #[knuffel(child, unwrap(argument))] pub variable_refresh_rate: Option<bool>, + #[knuffel(child)] + pub default_floating_position: Option<FoIPosition>, } #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] @@ -1082,6 +1084,14 @@ pub struct BorderRule { pub inactive_gradient: Option<Gradient>, } +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub struct FoIPosition { + #[knuffel(property)] + pub x: FloatOrInt<-65535, 65535>, + #[knuffel(property)] + pub y: FloatOrInt<-65535, 65535>, +} + #[derive(Debug, Default, PartialEq)] pub struct Binds(pub Vec<Bind>); @@ -3192,6 +3202,7 @@ mod tests { open-floating false open-focused true default-window-height { fixed 500; } + default-floating-position x=100 y=-200 focus-ring { off @@ -3476,6 +3487,10 @@ mod tests { open_floating: Some(false), open_focused: Some(true), default_window_height: Some(DefaultPresetSize(Some(PresetSize::Fixed(500)))), + default_floating_position: Some(FoIPosition { + x: FloatOrInt(100.), + y: FloatOrInt(-200.), + }), focus_ring: BorderRule { off: true, width: Some(FloatOrInt(3.)), diff --git a/src/layout/floating.rs b/src/layout/floating.rs index af208615..d40d1b17 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -415,12 +415,14 @@ impl<W: LayoutElement> FloatingSpace<W> { } } - let pos = tile - .floating_pos - .map(|pos| self.scale_by_working_area(pos)) - .unwrap_or_else(|| { - center_preferring_top_left_in_area(self.working_area, tile.tile_size()) - }); + let pos = tile.floating_pos.map(|pos| self.scale_by_working_area(pos)); + let pos = pos.or_else(|| { + tile.default_floating_logical_pos() + .map(|pos| pos + self.working_area.loc) + }); + let pos = pos.unwrap_or_else(|| { + center_preferring_top_left_in_area(self.working_area, tile.tile_size()) + }); let data = Data::new(self.working_area, &tile, pos); self.data.insert(idx, data); diff --git a/src/layout/tile.rs b/src/layout/tile.rs index 96936d26..d0750d33 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -999,6 +999,11 @@ impl<W: LayoutElement> Tile<W> { &self.options } + pub fn default_floating_logical_pos(&self) -> Option<Point<f64, Logical>> { + let pos = self.window().rules().default_floating_position?; + Some(Point::from((pos.x.0, pos.y.0))) + } + #[cfg(test)] pub fn view_size(&self) -> Size<f64, Logical> { self.view_size diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index e67b4e76..aef7203b 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1123,7 +1123,9 @@ impl<W: LayoutElement> Workspace<W> { removed.tile.stop_move_animations(); // Come up with a default floating position close to the tile position. - if removed.tile.floating_pos.is_none() { + if removed.tile.floating_pos.is_none() + && removed.tile.default_floating_logical_pos().is_none() + { let offset = if self.options.center_focused_column == CenterFocusedColumn::Always { Point::from((0., 0.)) } else { @@ -1214,17 +1216,26 @@ impl<W: LayoutElement> Workspace<W> { }; let working_area_loc = self.floating.working_area().loc; + let pos = tile + .floating_pos + .map(|pos| self.floating.scale_by_working_area(pos)); + let pos = pos.or_else(|| { + tile.default_floating_logical_pos() + .map(|pos| pos + working_area_loc) + }); + // If there's no stored floating position, we can only set both components at once, not // adjust. - let Some(pos) = tile.floating_pos.or_else(|| { + let pos = pos.or_else(|| { (matches!(x, PositionChange::SetFixed(_)) && matches!(y, PositionChange::SetFixed(_))) .then_some(Point::default()) - }) else { + }); + + let Some(mut pos) = pos else { return; }; - let mut pos = self.floating.scale_by_working_area(pos); match x { PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x, PositionChange::AdjustFixed(x) => pos.x += x, diff --git a/src/window/mod.rs b/src/window/mod.rs index 1cf576df..868620f0 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -1,6 +1,8 @@ use std::cmp::{max, min}; -use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, PresetSize, WindowRule}; +use niri_config::{ + BlockOutFrom, BorderRule, CornerRadius, FoIPosition, Match, PresetSize, WindowRule, +}; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::utils::{Logical, Size}; use smithay::wayland::compositor::with_states; @@ -41,6 +43,9 @@ pub struct ResolvedWindowRules { /// - `Some(Some(width))`: set to a particular height. pub default_height: Option<Option<PresetSize>>, + /// Default floating position for this window. + pub default_floating_position: Option<FoIPosition>, + /// Output to open this window on. pub open_on_output: Option<String>, @@ -137,6 +142,7 @@ impl ResolvedWindowRules { Self { default_width: None, default_height: None, + default_floating_position: None, open_on_output: None, open_on_workspace: None, open_maximized: None, @@ -219,6 +225,10 @@ impl ResolvedWindowRules { resolved.default_height = Some(x.0); } + if let Some(x) = rule.default_floating_position { + resolved.default_floating_position = Some(x); + } + if let Some(x) = rule.open_on_output.as_deref() { open_on_output = Some(x); } diff --git a/wiki/Configuration:-Window-Rules.md b/wiki/Configuration:-Window-Rules.md index d39140ff..8dc7c4be 100644 --- a/wiki/Configuration:-Window-Rules.md +++ b/wiki/Configuration:-Window-Rules.md @@ -52,6 +52,7 @@ window-rule { block-out-from "screencast" // block-out-from "screen-capture" variable-refresh-rate true + default-floating-position x=100 y=200 focus-ring { // off @@ -507,6 +508,29 @@ window-rule { } ``` +#### `default-floating-position` + +<sup>Since: next release</sup> + +Set the initial position for this window when it opens on, or moves to the floating layout. + +Afterward, the window will remember its last floating position. + +By default, new floating windows open at the center of the screen, and windows from the tiling layout open close to their visual screen position. + +The position uses logical coordinates relative to the working area. +For example, if you have a bar at the top, then `x=0 y=0` will put the top-left corner of the window directly below the bar. + +```kdl +// Open the Firefox picture-in-picture window at the top-left corner of the screen +// with a small gap. +window-rule { + match app-id="firefox$" title="^Picture-in-Picture$" + + default-floating-position x=32 y=32 +} +``` + #### `draw-border-with-background` Override whether the border and the focus ring draw with a background. |
