aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--niri-config/src/lib.rs15
-rw-r--r--src/layout/floating.rs14
-rw-r--r--src/layout/tile.rs5
-rw-r--r--src/layout/workspace.rs19
-rw-r--r--src/window/mod.rs12
-rw-r--r--wiki/Configuration:-Window-Rules.md24
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.