mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Add tiled-state window rule, update the tiled state live
This commit is contained in:
@@ -1234,6 +1234,8 @@ pub struct WindowRule {
|
|||||||
pub default_floating_position: Option<FloatingPosition>,
|
pub default_floating_position: Option<FloatingPosition>,
|
||||||
#[knuffel(child, unwrap(argument))]
|
#[knuffel(child, unwrap(argument))]
|
||||||
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub tiled_state: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||||
@@ -4503,6 +4505,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
scroll_factor: None,
|
scroll_factor: None,
|
||||||
|
tiled_state: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
layer_rules: [
|
layer_rules: [
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ use crate::input::touch_resize_grab::TouchResizeGrab;
|
|||||||
use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME};
|
use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME};
|
||||||
use crate::niri::{PopupGrabState, State};
|
use crate::niri::{PopupGrabState, State};
|
||||||
use crate::utils::transaction::Transaction;
|
use crate::utils::transaction::Transaction;
|
||||||
use crate::utils::{get_monotonic_time, output_matches_name, send_scale_transform, ResizeEdge};
|
use crate::utils::{
|
||||||
|
get_monotonic_time, output_matches_name, send_scale_transform, update_tiled_state, ResizeEdge,
|
||||||
|
};
|
||||||
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
|
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
|
||||||
|
|
||||||
impl XdgShellHandler for State {
|
impl XdgShellHandler for State {
|
||||||
@@ -773,7 +775,7 @@ impl XdgDecorationHandler for State {
|
|||||||
delegate_xdg_decoration!(State);
|
delegate_xdg_decoration!(State);
|
||||||
|
|
||||||
/// Whether KDE server decorations are in use.
|
/// Whether KDE server decorations are in use.
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
pub struct KdeDecorationsModeState {
|
pub struct KdeDecorationsModeState {
|
||||||
server: Cell<bool>,
|
server: Cell<bool>,
|
||||||
}
|
}
|
||||||
@@ -946,16 +948,8 @@ impl State {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user prefers no CSD, it's a reasonable assumption that they would prefer to get
|
// Set the tiled state for the initial configure.
|
||||||
// rid of the various client-side rounded corners also by using the tiled state.
|
update_tiled_state(toplevel, config.prefer_no_csd, rules.tiled_state);
|
||||||
if config.prefer_no_csd {
|
|
||||||
toplevel.with_pending_state(|state| {
|
|
||||||
state.states.set(xdg_toplevel::State::TiledLeft);
|
|
||||||
state.states.set(xdg_toplevel::State::TiledRight);
|
|
||||||
state.states.set(xdg_toplevel::State::TiledTop);
|
|
||||||
state.states.set(xdg_toplevel::State::TiledBottom);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the configured settings.
|
// Set the configured settings.
|
||||||
*state = InitialConfigureState::Configured {
|
*state = InitialConfigureState::Configured {
|
||||||
|
|||||||
+19
@@ -646,6 +646,10 @@ impl State {
|
|||||||
self.refresh_popup_grab();
|
self.refresh_popup_grab();
|
||||||
self.update_keyboard_focus();
|
self.update_keyboard_focus();
|
||||||
|
|
||||||
|
// Should be called before refresh_layout() because that one will refresh other window
|
||||||
|
// states and then send a pending configure.
|
||||||
|
self.niri.refresh_window_states();
|
||||||
|
|
||||||
// Needs to be called after updating the keyboard focus.
|
// Needs to be called after updating the keyboard focus.
|
||||||
self.niri.refresh_layout();
|
self.niri.refresh_layout();
|
||||||
|
|
||||||
@@ -3255,6 +3259,16 @@ impl Niri {
|
|||||||
self.idle_notifier_state.set_is_inhibited(is_inhibited);
|
self.idle_notifier_state.set_is_inhibited(is_inhibited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refresh_window_states(&mut self) {
|
||||||
|
let _span = tracy_client::span!("Niri::refresh_window_states");
|
||||||
|
|
||||||
|
let config = self.config.borrow();
|
||||||
|
self.layout.with_windows_mut(|mapped, _output| {
|
||||||
|
mapped.update_tiled_state(config.prefer_no_csd);
|
||||||
|
});
|
||||||
|
drop(config);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refresh_window_rules(&mut self) {
|
pub fn refresh_window_rules(&mut self) {
|
||||||
let _span = tracy_client::span!("Niri::refresh_window_rules");
|
let _span = tracy_client::span!("Niri::refresh_window_rules");
|
||||||
|
|
||||||
@@ -3270,6 +3284,11 @@ impl Niri {
|
|||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
outputs.insert(output.clone());
|
outputs.insert(output.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since refresh_window_rules() is called after refresh_layout(), we need to update
|
||||||
|
// the tiled state right here, so that it's picked up by the following
|
||||||
|
// send_pending_configure().
|
||||||
|
mapped.update_tiled_state(config.prefer_no_csd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
drop(config);
|
drop(config);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use niri_config::{Config, OutputName};
|
|||||||
use smithay::input::pointer::CursorIcon;
|
use smithay::input::pointer::CursorIcon;
|
||||||
use smithay::output::{self, Output};
|
use smithay::output::{self, Output};
|
||||||
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
||||||
|
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_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::reexports::wayland_server::{DisplayHandle, Resource as _};
|
use smithay::reexports::wayland_server::{DisplayHandle, Resource as _};
|
||||||
@@ -26,6 +27,7 @@ use smithay::wayland::shell::xdg::{
|
|||||||
};
|
};
|
||||||
use wayland_backend::server::Credentials;
|
use wayland_backend::server::Credentials;
|
||||||
|
|
||||||
|
use crate::handlers::KdeDecorationsModeState;
|
||||||
use crate::niri::ClientState;
|
use crate::niri::ClientState;
|
||||||
|
|
||||||
pub mod id;
|
pub mod id;
|
||||||
@@ -268,6 +270,69 @@ pub fn with_toplevel_role<T>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_tiled_state(
|
||||||
|
toplevel: &ToplevelSurface,
|
||||||
|
prefer_no_csd: bool,
|
||||||
|
force_tiled: Option<bool>,
|
||||||
|
) {
|
||||||
|
// Determine the default value for our tiled state. The idea is to use the tiled state to
|
||||||
|
// make windows rectangular even if they don't support xdg-decoration (e.g. GTK).
|
||||||
|
//
|
||||||
|
// If the user prefers no CSD, it's a reasonable assumption that they would prefer to get
|
||||||
|
// rid of the various client-side rounded corners also by using the tiled state.
|
||||||
|
let should_tile = || {
|
||||||
|
// Figure out if the client bound any decoration globals for this window. In this case,
|
||||||
|
// the pending decoration mode will be set to something (we always set it upon binding the
|
||||||
|
// global and never reset to None).
|
||||||
|
//
|
||||||
|
// If the client bound a decoration global, use the mode that we negotiated. This way,
|
||||||
|
// changing the decoration mode on the client at runtime will synchonize with the
|
||||||
|
// default tiled state.
|
||||||
|
if let Some(mode) = toplevel.with_pending_state(|state| state.decoration_mode) {
|
||||||
|
mode == zxdg_toplevel_decoration_v1::Mode::ServerSide
|
||||||
|
} else if let Some(mode) = with_states(toplevel.wl_surface(), |states| {
|
||||||
|
states.data_map.get::<KdeDecorationsModeState>().cloned()
|
||||||
|
}) {
|
||||||
|
// Actually, make the KDE decoration overridable with prefer_no_csd. GTK 3 likes to
|
||||||
|
// always request CSD through it, and we want prefer_no_csd to set the tiled state
|
||||||
|
// automatically for GTK 3. Also, unlike xdg-decoration, KDE decoration is not
|
||||||
|
// synchronized to commits, so that argument is less important.
|
||||||
|
mode.is_server() || prefer_no_csd
|
||||||
|
} else {
|
||||||
|
// The client doesn't see or doesn't care about the decoration protocols. In this
|
||||||
|
// case, use the current prefer_no_csd value as the user's intention.
|
||||||
|
//
|
||||||
|
// This is a bit weird because it makes it seem like prefer_no_csd can apply live,
|
||||||
|
// while that isn't really the case. That's because prefer_no_csd controls two separate
|
||||||
|
// things: whether the client sees the decoration globals, and the tiled state.
|
||||||
|
//
|
||||||
|
// A more accurate way would perhaps be to check if the client cannot see the
|
||||||
|
// decoration globals, and in this case behave as if prefer_no_csd was false. However,
|
||||||
|
// this also regresses the common case of GTK 4 applications that do not react to
|
||||||
|
// xdg-decoration in any way, and therefore the tiled state *is* the "no CSD" mode from
|
||||||
|
// the user's perspective, so by artificially gating it we would artificially make it
|
||||||
|
// impossible to apply it live for GTK 4 applications.
|
||||||
|
prefer_no_csd
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let should_tile = force_tiled.unwrap_or_else(should_tile);
|
||||||
|
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
if should_tile {
|
||||||
|
state.states.set(xdg_toplevel::State::TiledLeft);
|
||||||
|
state.states.set(xdg_toplevel::State::TiledRight);
|
||||||
|
state.states.set(xdg_toplevel::State::TiledTop);
|
||||||
|
state.states.set(xdg_toplevel::State::TiledBottom);
|
||||||
|
} else {
|
||||||
|
state.states.unset(xdg_toplevel::State::TiledLeft);
|
||||||
|
state.states.unset(xdg_toplevel::State::TiledRight);
|
||||||
|
state.states.unset(xdg_toplevel::State::TiledTop);
|
||||||
|
state.states.unset(xdg_toplevel::State::TiledBottom);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_credentials_for_surface(surface: &WlSurface) -> Option<Credentials> {
|
pub fn get_credentials_for_surface(surface: &WlSurface) -> Option<Credentials> {
|
||||||
let handle = surface.handle().upgrade()?;
|
let handle = surface.handle().upgrade()?;
|
||||||
let dh = DisplayHandle::from(handle);
|
let dh = DisplayHandle::from(handle);
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
|||||||
use crate::utils::id::IdCounter;
|
use crate::utils::id::IdCounter;
|
||||||
use crate::utils::transaction::Transaction;
|
use crate::utils::transaction::Transaction;
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
get_credentials_for_surface, send_scale_transform, with_toplevel_role, ResizeEdge,
|
get_credentials_for_surface, send_scale_transform, update_tiled_state, with_toplevel_role,
|
||||||
|
ResizeEdge,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -462,6 +463,10 @@ impl Mapped {
|
|||||||
};
|
};
|
||||||
self.window.send_frame(output, time, throttle, should_send);
|
self.window.send_frame(output, time, throttle, should_send);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_tiled_state(&self, prefer_no_csd: bool) {
|
||||||
|
update_tiled_state(self.toplevel(), prefer_no_csd, self.rules.tiled_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Mapped {
|
impl Drop for Mapped {
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ pub struct ResolvedWindowRules {
|
|||||||
|
|
||||||
/// Multiplier for all scroll events sent to this window.
|
/// Multiplier for all scroll events sent to this window.
|
||||||
pub scroll_factor: Option<f64>,
|
pub scroll_factor: Option<f64>,
|
||||||
|
|
||||||
|
/// Override whether to set the Tiled xdg-toplevel state on the window.
|
||||||
|
pub tiled_state: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WindowRef<'a> {
|
impl<'a> WindowRef<'a> {
|
||||||
@@ -217,6 +220,7 @@ impl ResolvedWindowRules {
|
|||||||
block_out_from: None,
|
block_out_from: None,
|
||||||
variable_refresh_rate: None,
|
variable_refresh_rate: None,
|
||||||
scroll_factor: None,
|
scroll_factor: None,
|
||||||
|
tiled_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +339,9 @@ impl ResolvedWindowRules {
|
|||||||
if let Some(x) = rule.scroll_factor {
|
if let Some(x) = rule.scroll_factor {
|
||||||
resolved.scroll_factor = Some(x.0);
|
resolved.scroll_factor = Some(x.0);
|
||||||
}
|
}
|
||||||
|
if let Some(x) = rule.tiled_state {
|
||||||
|
resolved.tiled_state = Some(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||||
|
|||||||
@@ -60,10 +60,11 @@ Additionally, clients will be informed that they are tiled, removing some rounde
|
|||||||
With `prefer-no-csd` set, applications that negotiate server-side decorations through the xdg-decoration protocol will have focus ring and border drawn around them *without* a solid colored background.
|
With `prefer-no-csd` set, applications that negotiate server-side decorations through the xdg-decoration protocol will have focus ring and border drawn around them *without* a solid colored background.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Unlike most other options, changing `prefer-no-csd` will not affect already running applications.
|
> Unlike most other options, changing `prefer-no-csd` will not entirely affect already running applications.
|
||||||
|
> It will make some windows rectangular, but won't remove the title bars.
|
||||||
> This mainly has to do with niri working around a [bug in SDL2](https://github.com/libsdl-org/SDL/issues/8173) that prevents SDL2 applications from starting.
|
> This mainly has to do with niri working around a [bug in SDL2](https://github.com/libsdl-org/SDL/issues/8173) that prevents SDL2 applications from starting.
|
||||||
>
|
>
|
||||||
> Restart applications after changing `prefer-no-csd` in the config to apply it.
|
> Restart applications after changing `prefer-no-csd` in the config to fully apply it.
|
||||||
|
|
||||||
```kdl
|
```kdl
|
||||||
prefer-no-csd
|
prefer-no-csd
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ window-rule {
|
|||||||
|
|
||||||
geometry-corner-radius 12
|
geometry-corner-radius 12
|
||||||
clip-to-geometry true
|
clip-to-geometry true
|
||||||
|
tiled-state true
|
||||||
|
|
||||||
min-width 100
|
min-width 100
|
||||||
max-width 200
|
max-width 200
|
||||||
@@ -825,6 +826,26 @@ window-rule {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `tiled-state`
|
||||||
|
|
||||||
|
<sup>Since: next release</sup>
|
||||||
|
|
||||||
|
Informs the window that it is tiled.
|
||||||
|
Usually, windows will react by becoming rectangular and hiding their client-side shadows.
|
||||||
|
Windows that snap their size to a grid (e.g. terminals like [foot](https://codeberg.org/dnkl/foot)) will usually disable this snapping when they are tiled.
|
||||||
|
|
||||||
|
By default, niri will set the tiled state to `true` together with [`prefer-no-csd`](./Configuration:-Miscellaneous.md) in order to improve behavior for apps that don't support server-side decorations.
|
||||||
|
You can use this window rule to override this, for example to get rectangular windows with CSD.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
// Make tiled windows rectangular while using CSD.
|
||||||
|
window-rule {
|
||||||
|
match is-floating=false
|
||||||
|
|
||||||
|
tiled-state true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Size Overrides
|
#### Size Overrides
|
||||||
|
|
||||||
You can amend the window's minimum and maximum size in logical pixels.
|
You can amend the window's minimum and maximum size in logical pixels.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
### How to disable client-side decorations/make windows rectangular?
|
### How to disable client-side decorations/make windows rectangular?
|
||||||
|
|
||||||
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config.
|
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config, and then restart your apps.
|
||||||
Then niri will ask windows to omit client-side decorations, and also inform them that they are being tiled (which makes some windows rectangular, even if they cannot omit the decorations).
|
Then niri will ask windows to omit client-side decorations, and also inform them that they are being tiled (which makes some windows rectangular, even if they cannot omit the decorations).
|
||||||
|
|
||||||
Note that currently this will prevent edge window resize handles from showing up.
|
Note that currently this will prevent edge window resize handles from showing up.
|
||||||
@@ -8,7 +8,7 @@ You can still resize windows by holding <kbd>Mod</kbd> and the right mouse butto
|
|||||||
|
|
||||||
### Why is the border/focus ring showing up through semitransparent windows?
|
### Why is the border/focus ring showing up through semitransparent windows?
|
||||||
|
|
||||||
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config.
|
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config, and then restart your apps.
|
||||||
Niri will draw focus rings and borders *around* windows that agree to omit their client-side decorations.
|
Niri will draw focus rings and borders *around* windows that agree to omit their client-side decorations.
|
||||||
|
|
||||||
By default, focus ring and border are rendered as a solid background rectangle behind windows.
|
By default, focus ring and border are rendered as a solid background rectangle behind windows.
|
||||||
|
|||||||
Reference in New Issue
Block a user