Add default-floating-position window rule

This commit is contained in:
Ivan Molodetskikh
2024-12-29 10:39:21 +03:00
parent 744955ba69
commit 3c2e1554c6
6 changed files with 78 additions and 11 deletions
+15
View File
@@ -1019,6 +1019,8 @@ pub struct WindowRule {
pub block_out_from: Option<BlockOutFrom>, pub block_out_from: Option<BlockOutFrom>,
#[knuffel(child, unwrap(argument))] #[knuffel(child, unwrap(argument))]
pub variable_refresh_rate: Option<bool>, pub variable_refresh_rate: Option<bool>,
#[knuffel(child)]
pub default_floating_position: Option<FoIPosition>,
} }
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
@@ -1082,6 +1084,14 @@ pub struct BorderRule {
pub inactive_gradient: Option<Gradient>, 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)] #[derive(Debug, Default, PartialEq)]
pub struct Binds(pub Vec<Bind>); pub struct Binds(pub Vec<Bind>);
@@ -3192,6 +3202,7 @@ mod tests {
open-floating false open-floating false
open-focused true open-focused true
default-window-height { fixed 500; } default-window-height { fixed 500; }
default-floating-position x=100 y=-200
focus-ring { focus-ring {
off off
@@ -3476,6 +3487,10 @@ mod tests {
open_floating: Some(false), open_floating: Some(false),
open_focused: Some(true), open_focused: Some(true),
default_window_height: Some(DefaultPresetSize(Some(PresetSize::Fixed(500)))), default_window_height: Some(DefaultPresetSize(Some(PresetSize::Fixed(500)))),
default_floating_position: Some(FoIPosition {
x: FloatOrInt(100.),
y: FloatOrInt(-200.),
}),
focus_ring: BorderRule { focus_ring: BorderRule {
off: true, off: true,
width: Some(FloatOrInt(3.)), width: Some(FloatOrInt(3.)),
+8 -6
View File
@@ -415,12 +415,14 @@ impl<W: LayoutElement> FloatingSpace<W> {
} }
} }
let pos = tile let pos = tile.floating_pos.map(|pos| self.scale_by_working_area(pos));
.floating_pos let pos = pos.or_else(|| {
.map(|pos| self.scale_by_working_area(pos)) tile.default_floating_logical_pos()
.unwrap_or_else(|| { .map(|pos| pos + self.working_area.loc)
center_preferring_top_left_in_area(self.working_area, tile.tile_size()) });
}); 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); let data = Data::new(self.working_area, &tile, pos);
self.data.insert(idx, data); self.data.insert(idx, data);
+5
View File
@@ -999,6 +999,11 @@ impl<W: LayoutElement> Tile<W> {
&self.options &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)] #[cfg(test)]
pub fn view_size(&self) -> Size<f64, Logical> { pub fn view_size(&self) -> Size<f64, Logical> {
self.view_size self.view_size
+15 -4
View File
@@ -1123,7 +1123,9 @@ impl<W: LayoutElement> Workspace<W> {
removed.tile.stop_move_animations(); removed.tile.stop_move_animations();
// Come up with a default floating position close to the tile position. // 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 { let offset = if self.options.center_focused_column == CenterFocusedColumn::Always {
Point::from((0., 0.)) Point::from((0., 0.))
} else { } else {
@@ -1214,17 +1216,26 @@ impl<W: LayoutElement> Workspace<W> {
}; };
let working_area_loc = self.floating.working_area().loc; 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 // If there's no stored floating position, we can only set both components at once, not
// adjust. // adjust.
let Some(pos) = tile.floating_pos.or_else(|| { let pos = pos.or_else(|| {
(matches!(x, PositionChange::SetFixed(_)) (matches!(x, PositionChange::SetFixed(_))
&& matches!(y, PositionChange::SetFixed(_))) && matches!(y, PositionChange::SetFixed(_)))
.then_some(Point::default()) .then_some(Point::default())
}) else { });
let Some(mut pos) = pos else {
return; return;
}; };
let mut pos = self.floating.scale_by_working_area(pos);
match x { match x {
PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x, PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x,
PositionChange::AdjustFixed(x) => pos.x += x, PositionChange::AdjustFixed(x) => pos.x += x,
+11 -1
View File
@@ -1,6 +1,8 @@
use std::cmp::{max, min}; 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::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::utils::{Logical, Size}; use smithay::utils::{Logical, Size};
use smithay::wayland::compositor::with_states; use smithay::wayland::compositor::with_states;
@@ -41,6 +43,9 @@ pub struct ResolvedWindowRules {
/// - `Some(Some(width))`: set to a particular height. /// - `Some(Some(width))`: set to a particular height.
pub default_height: Option<Option<PresetSize>>, pub default_height: Option<Option<PresetSize>>,
/// Default floating position for this window.
pub default_floating_position: Option<FoIPosition>,
/// Output to open this window on. /// Output to open this window on.
pub open_on_output: Option<String>, pub open_on_output: Option<String>,
@@ -137,6 +142,7 @@ impl ResolvedWindowRules {
Self { Self {
default_width: None, default_width: None,
default_height: None, default_height: None,
default_floating_position: None,
open_on_output: None, open_on_output: None,
open_on_workspace: None, open_on_workspace: None,
open_maximized: None, open_maximized: None,
@@ -219,6 +225,10 @@ impl ResolvedWindowRules {
resolved.default_height = Some(x.0); 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() { if let Some(x) = rule.open_on_output.as_deref() {
open_on_output = Some(x); open_on_output = Some(x);
} }
+24
View File
@@ -52,6 +52,7 @@ window-rule {
block-out-from "screencast" block-out-from "screencast"
// block-out-from "screen-capture" // block-out-from "screen-capture"
variable-refresh-rate true variable-refresh-rate true
default-floating-position x=100 y=200
focus-ring { focus-ring {
// off // 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` #### `draw-border-with-background`
Override whether the border and the focus ring draw with a background. Override whether the border and the focus ring draw with a background.