aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/handlers/xdg_shell.rs18
-rw-r--r--src/niri.rs19
-rw-r--r--src/utils/mod.rs65
-rw-r--r--src/window/mapped.rs7
-rw-r--r--src/window/mod.rs7
5 files changed, 103 insertions, 13 deletions
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());