diff options
| -rw-r--r-- | niri-config/src/lib.rs | 3 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 18 | ||||
| -rw-r--r-- | src/niri.rs | 19 | ||||
| -rw-r--r-- | src/utils/mod.rs | 65 | ||||
| -rw-r--r-- | src/window/mapped.rs | 7 | ||||
| -rw-r--r-- | src/window/mod.rs | 7 | ||||
| -rw-r--r-- | wiki/Configuration:-Miscellaneous.md | 5 | ||||
| -rw-r--r-- | wiki/Configuration:-Window-Rules.md | 21 | ||||
| -rw-r--r-- | wiki/FAQ.md | 4 |
9 files changed, 132 insertions, 17 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 80bd2bdc..579db485 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1234,6 +1234,8 @@ pub struct WindowRule { pub default_floating_position: Option<FloatingPosition>, #[knuffel(child, unwrap(argument))] pub scroll_factor: Option<FloatOrInt<0, 100>>, + #[knuffel(child, unwrap(argument))] + pub tiled_state: Option<bool>, } #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] @@ -4503,6 +4505,7 @@ mod tests { }, ), scroll_factor: None, + tiled_state: None, }, ], layer_rules: [ diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 840dd599..0c757c92 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -45,7 +45,9 @@ use crate::input::touch_resize_grab::TouchResizeGrab; use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME}; use crate::niri::{PopupGrabState, State}; 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}; impl XdgShellHandler for State { @@ -773,7 +775,7 @@ impl XdgDecorationHandler for State { delegate_xdg_decoration!(State); /// Whether KDE server decorations are in use. -#[derive(Default)] +#[derive(Default, Clone)] pub struct KdeDecorationsModeState { 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 - // rid of the various client-side rounded corners also by using the 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 tiled state for the initial configure. + update_tiled_state(toplevel, config.prefer_no_csd, rules.tiled_state); // Set the configured settings. *state = InitialConfigureState::Configured { diff --git a/src/niri.rs b/src/niri.rs index 6ce11e59..56582d15 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -646,6 +646,10 @@ impl State { self.refresh_popup_grab(); 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. self.niri.refresh_layout(); @@ -3255,6 +3259,16 @@ impl Niri { 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) { let _span = tracy_client::span!("Niri::refresh_window_rules"); @@ -3270,6 +3284,11 @@ impl Niri { if let Some(output) = output { 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); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e43bad89..2f16e855 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -15,6 +15,7 @@ use niri_config::{Config, OutputName}; use smithay::input::pointer::CursorIcon; use smithay::output::{self, Output}; 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_server::protocol::wl_surface::WlSurface; use smithay::reexports::wayland_server::{DisplayHandle, Resource as _}; @@ -26,6 +27,7 @@ use smithay::wayland::shell::xdg::{ }; use wayland_backend::server::Credentials; +use crate::handlers::KdeDecorationsModeState; use crate::niri::ClientState; 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> { let handle = surface.handle().upgrade()?; let dh = DisplayHandle::from(handle); diff --git a/src/window/mapped.rs b/src/window/mapped.rs index 6b539e46..ac508990 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -35,7 +35,8 @@ use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements}; use crate::utils::id::IdCounter; use crate::utils::transaction::Transaction; 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)] @@ -462,6 +463,10 @@ impl Mapped { }; 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 { diff --git a/src/window/mod.rs b/src/window/mod.rs index c99d10d2..7c0da71b 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -111,6 +111,9 @@ pub struct ResolvedWindowRules { /// Multiplier for all scroll events sent to this window. 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> { @@ -217,6 +220,7 @@ impl ResolvedWindowRules { block_out_from: None, variable_refresh_rate: None, scroll_factor: None, + tiled_state: None, } } @@ -335,6 +339,9 @@ impl ResolvedWindowRules { if let Some(x) = rule.scroll_factor { 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()); diff --git a/wiki/Configuration:-Miscellaneous.md b/wiki/Configuration:-Miscellaneous.md index fe758c54..a478c472 100644 --- a/wiki/Configuration:-Miscellaneous.md +++ b/wiki/Configuration:-Miscellaneous.md @@ -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. > [!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. > -> 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 prefer-no-csd diff --git a/wiki/Configuration:-Window-Rules.md b/wiki/Configuration:-Window-Rules.md index 5f3d9b49..32ee253f 100644 --- a/wiki/Configuration:-Window-Rules.md +++ b/wiki/Configuration:-Window-Rules.md @@ -91,6 +91,7 @@ window-rule { geometry-corner-radius 12 clip-to-geometry true + tiled-state true min-width 100 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 You can amend the window's minimum and maximum size in logical pixels. diff --git a/wiki/FAQ.md b/wiki/FAQ.md index bf3bba83..85a9d6d6 100644 --- a/wiki/FAQ.md +++ b/wiki/FAQ.md @@ -1,6 +1,6 @@ ### 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). 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? -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. By default, focus ring and border are rendered as a solid background rectangle behind windows. |
