mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +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>,
|
||||
#[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: [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+19
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
-2
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user