aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/handlers/compositor.rs17
-rw-r--r--src/handlers/layer_shell.rs18
-rw-r--r--src/handlers/mod.rs12
-rw-r--r--src/handlers/xdg_shell.rs5
-rw-r--r--src/protocols/foreign_toplevel.rs27
-rw-r--r--src/tests/window_opening.rs14
-rw-r--r--src/utils/mod.rs22
-rw-r--r--src/window/mapped.rs94
8 files changed, 114 insertions, 95 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs
index 034ee0d0..cf2efaaf 100644
--- a/src/handlers/compositor.rs
+++ b/src/handlers/compositor.rs
@@ -14,7 +14,7 @@ use smithay::wayland::compositor::{
SurfaceAttributes,
};
use smithay::wayland::dmabuf::get_dmabuf;
-use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
+use smithay::wayland::shell::xdg::ToplevelCachedState;
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::{delegate_compositor, delegate_shm};
@@ -286,13 +286,14 @@ impl CompositorHandler for State {
.buffer_delta
.take();
- let role = states
- .data_map
- .get::<XdgToplevelSurfaceData>()
- .unwrap()
- .lock()
- .unwrap();
- (role.configure_serial, buffer_delta)
+ let serial = states
+ .cached_state
+ .get::<ToplevelCachedState>()
+ .current()
+ .last_acked
+ .as_ref()
+ .map(|c| c.serial);
+ (serial, buffer_delta)
});
if serial.is_none() {
error!("commit on a mapped surface without a configured serial");
diff --git a/src/handlers/layer_shell.rs b/src/handlers/layer_shell.rs
index fd2c592f..7678ccc1 100644
--- a/src/handlers/layer_shell.rs
+++ b/src/handlers/layer_shell.rs
@@ -174,24 +174,8 @@ impl State {
self.niri.layer_shell_on_demand_focus = Some(layer.clone());
}
} else {
- let was_mapped = self.niri.mapped_layer_surfaces.remove(layer).is_some();
+ self.niri.mapped_layer_surfaces.remove(layer);
self.niri.unmapped_layer_surfaces.insert(surface.clone());
-
- // After layer surface unmaps it has to perform the initial commit-configure
- // sequence again. This is a workaround until Smithay properly resets
- // initial_configure_sent upon the surface unmapping itself as it does for
- // toplevels.
- if was_mapped {
- with_states(surface, |states| {
- let mut data = states
- .data_map
- .get::<LayerSurfaceData>()
- .unwrap()
- .lock()
- .unwrap();
- data.initial_configure_sent = false;
- });
- }
}
} else {
let scale = output.current_scale();
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 5878e3be..8518f8f9 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -19,7 +19,6 @@ use smithay::input::pointer::{
use smithay::input::{keyboard, Seat, SeatHandler, SeatState};
use smithay::output::Output;
use smithay::reexports::rustix::fs::{fcntl_setfl, OFlags};
-use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
@@ -92,7 +91,7 @@ use crate::protocols::virtual_pointer::{
VirtualPointerInputBackend, VirtualPointerManagerState, VirtualPointerMotionAbsoluteEvent,
VirtualPointerMotionEvent,
};
-use crate::utils::{output_size, send_scale_transform, with_toplevel_role};
+use crate::utils::{output_size, send_scale_transform};
use crate::{
delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control,
delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy,
@@ -543,15 +542,6 @@ impl ForeignToplevelHandler for State {
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>) {
if let Some((mapped, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
{
- let has_fullscreen_cap = with_toplevel_role(mapped.toplevel(), |role| {
- role.current
- .capabilities
- .contains(xdg_toplevel::WmCapabilities::Fullscreen)
- });
- if !has_fullscreen_cap {
- return;
- }
-
let window = mapped.window.clone();
if let Some(requested_output) = wl_output.as_ref().and_then(Output::from_resource) {
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs
index 970dcb5b..5d8adc06 100644
--- a/src/handlers/xdg_shell.rs
+++ b/src/handlers/xdg_shell.rs
@@ -1259,8 +1259,9 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
.unwrap()
.lock()
.unwrap();
+ let serial = role.last_acked.as_ref().map(|c| c.serial);
- (got_unmapped, dmabuf, role.configure_serial)
+ (got_unmapped, dmabuf, serial)
});
let mut transaction_for_dmabuf = None;
@@ -1305,7 +1306,7 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
}
animate = mapped.should_animate_commit(serial);
- } else {
+ } else if !got_unmapped {
error!("commit on a mapped surface without a configured serial");
};
diff --git a/src/protocols/foreign_toplevel.rs b/src/protocols/foreign_toplevel.rs
index ad2f0890..5ef4735e 100644
--- a/src/protocols/foreign_toplevel.rs
+++ b/src/protocols/foreign_toplevel.rs
@@ -11,7 +11,9 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
-use smithay::wayland::shell::xdg::{ToplevelStateSet, XdgToplevelSurfaceRoleAttributes};
+use smithay::wayland::shell::xdg::{
+ ToplevelState, ToplevelStateSet, XdgToplevelSurfaceRoleAttributes,
+};
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
zwlr_foreign_toplevel_handle_v1, zwlr_foreign_toplevel_manager_v1,
};
@@ -19,7 +21,7 @@ use zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
use zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
use crate::niri::State;
-use crate::utils::with_toplevel_role;
+use crate::utils::with_toplevel_role_and_current;
const VERSION: u32 = 3;
@@ -96,11 +98,16 @@ pub fn refresh(state: &mut State) {
state.niri.layout.with_windows(|mapped, output, _, _| {
let toplevel = mapped.toplevel();
let wl_surface = toplevel.wl_surface();
- with_toplevel_role(toplevel, |role| {
+ with_toplevel_role_and_current(toplevel, |role, cur| {
+ let Some(cur) = cur else {
+ error!("mapped must have had initial commit");
+ return;
+ };
+
if state.niri.keyboard_focus.surface() == Some(wl_surface) {
focused = Some((mapped.window.clone(), output.cloned()));
} else {
- refresh_toplevel(protocol_state, wl_surface, role, output, false);
+ refresh_toplevel(protocol_state, wl_surface, role, cur, output, false);
}
});
});
@@ -109,8 +116,13 @@ pub fn refresh(state: &mut State) {
if let Some((window, output)) = focused {
let toplevel = window.toplevel().expect("no X11 support");
let wl_surface = toplevel.wl_surface();
- with_toplevel_role(toplevel, |role| {
- refresh_toplevel(protocol_state, wl_surface, role, output.as_ref(), true);
+ with_toplevel_role_and_current(toplevel, |role, cur| {
+ let Some(cur) = cur else {
+ error!("mapped must have had initial commit");
+ return;
+ };
+
+ refresh_toplevel(protocol_state, wl_surface, role, cur, output.as_ref(), true);
});
}
}
@@ -144,10 +156,11 @@ fn refresh_toplevel(
protocol_state: &mut ForeignToplevelManagerState,
wl_surface: &WlSurface,
role: &XdgToplevelSurfaceRoleAttributes,
+ current: &ToplevelState,
output: Option<&Output>,
has_focus: bool,
) {
- let states = to_state_vec(&role.current.states, has_focus);
+ let states = to_state_vec(&current.states, has_focus);
match protocol_state.toplevels.entry(wl_surface.clone()) {
Entry::Occupied(entry) => {
diff --git a/src/tests/window_opening.rs b/src/tests/window_opening.rs
index b824cc69..239fb512 100644
--- a/src/tests/window_opening.rs
+++ b/src/tests/window_opening.rs
@@ -65,6 +65,7 @@ fn simple() {
}
#[test]
+#[should_panic(expected = "Protocol error 3 on object xdg_surface")]
fn dont_ack_initial_configure() {
let mut f = Fixture::new();
f.add_output(1, (1920, 1080));
@@ -80,19 +81,6 @@ fn dont_ack_initial_configure() {
// Don't ack the configure.
window.commit();
f.double_roundtrip(id);
-
- // FIXME: Technically this is a protocol violation but uh. Smithay currently doesn't check it,
- // and I'm not sure if it can be done generically in Smithay (because a compositor may not use
- // its rendering helpers). I might add a check in niri itself sometime; I'm just not sure if
- // there might be clients that this could break.
- let window = f.client(id).window(&surface);
- assert_snapshot!(
- window.format_recent_configures(),
- @r"
- size: 936 × 1048, bounds: 1888 × 1048, states: []
- size: 936 × 1048, bounds: 1888 × 1048, states: [Activated]
- "
- );
}
#[derive(Clone, Copy)]
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 26bfe582..a96c1bfc 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -25,7 +25,8 @@ use smithay::utils::{Coordinate, Logical, Point, Rectangle, Size, Transform};
use smithay::wayland::compositor::{send_surface_state, with_states, SurfaceData};
use smithay::wayland::fractional_scale::with_fractional_scale;
use smithay::wayland::shell::xdg::{
- ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
+ ToplevelCachedState, ToplevelState, ToplevelSurface, XdgToplevelSurfaceData,
+ XdgToplevelSurfaceRoleAttributes,
};
use wayland_backend::server::Credentials;
@@ -279,6 +280,25 @@ pub fn with_toplevel_role<T>(
})
}
+pub fn with_toplevel_role_and_current<T>(
+ toplevel: &ToplevelSurface,
+ f: impl FnOnce(&mut XdgToplevelSurfaceRoleAttributes, Option<&ToplevelState>) -> T,
+) -> T {
+ with_states(toplevel.wl_surface(), |states| {
+ let mut role = states
+ .data_map
+ .get::<XdgToplevelSurfaceData>()
+ .unwrap()
+ .lock()
+ .unwrap();
+
+ let mut guard = states.cached_state.get::<ToplevelCachedState>();
+ let current = guard.current().last_acked.as_ref().map(|c| &c.state);
+
+ f(&mut role, current)
+ })
+}
+
pub fn update_tiled_state(
toplevel: &ToplevelSurface,
prefer_no_csd: bool,
diff --git a/src/window/mapped.rs b/src/window/mapped.rs
index f179bced..caa19ff4 100644
--- a/src/window/mapped.rs
+++ b/src/window/mapped.rs
@@ -15,7 +15,10 @@ use smithay::reexports::wayland_server::Resource as _;
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId, SurfaceData};
use smithay::wayland::seat::WaylandFocus;
-use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
+use smithay::wayland::shell::xdg::{
+ SurfaceCachedState, ToplevelCachedState, ToplevelConfigure, ToplevelSurface,
+ XdgToplevelSurfaceData,
+};
use wayland_backend::server::Credentials;
use super::{ResolvedWindowRules, WindowRef};
@@ -36,7 +39,7 @@ use crate::utils::id::IdCounter;
use crate::utils::transaction::Transaction;
use crate::utils::{
get_credentials_for_surface, send_scale_transform, update_tiled_state, with_toplevel_role,
- ResizeEdge,
+ with_toplevel_role_and_current, ResizeEdge,
};
#[derive(Debug)]
@@ -720,28 +723,35 @@ impl LayoutElement for Mapped {
// configure, whereas what we potentially want is to unfullscreen the window into its
// fullscreen size.
let already_sent = with_toplevel_role(self.toplevel(), |role| {
- let (last_sent, last_serial) = if let Some(configure) = role.pending_configures().last()
- {
+ let last_sent = if let Some(configure) = role.pending_configures().last() {
// FIXME: it would be more optimal to find the *oldest* pending configure that
// has the same size and fullscreen state to the last pending configure.
- (&configure.state, configure.serial)
+ configure
} else {
- (
- role.last_acked.as_ref().unwrap(),
- role.configure_serial.unwrap(),
- )
+ role.last_acked.as_ref().unwrap()
};
+ let ToplevelConfigure {
+ serial: last_serial,
+ state: last_sent,
+ } = last_sent;
let same_size = last_sent.size.unwrap_or_default() == size;
let has_fullscreen = last_sent.states.contains(xdg_toplevel::State::Fullscreen);
let same_fullscreen = has_fullscreen == self.is_pending_windowed_fullscreen;
- (same_size && same_fullscreen).then_some(last_serial)
+ (same_size && same_fullscreen).then_some(*last_serial)
});
if let Some(serial) = already_sent {
- if let Some(current_serial) =
- with_toplevel_role(self.toplevel(), |role| role.current_serial)
- {
+ let current_serial = with_states(self.toplevel().wl_surface(), |states| {
+ states
+ .cached_state
+ .get::<ToplevelCachedState>()
+ .current()
+ .last_acked
+ .as_ref()
+ .map(|c| c.serial)
+ });
+ if let Some(current_serial) = current_serial {
// God this triple negative...
if !current_serial.is_no_older_than(&serial) {
// We have already sent a request for the new size, but the surface has not
@@ -805,7 +815,9 @@ impl LayoutElement for Mapped {
fn has_ssd(&self) -> bool {
let toplevel = self.toplevel();
- let mode = with_toplevel_role(self.toplevel(), |role| role.current.decoration_mode);
+ let mode = self
+ .toplevel()
+ .with_committed_state(|current| current.and_then(|s| s.decoration_mode));
match mode {
Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) => true,
@@ -892,10 +904,10 @@ impl LayoutElement for Mapped {
return ConfigureIntent::ShouldSend;
}
- with_toplevel_role(self.toplevel(), |attributes| {
+ with_toplevel_role_and_current(self.toplevel(), |attributes, current_committed| {
if let Some(server_pending) = &attributes.server_pending {
let current_server = attributes.current_server_state();
- if server_pending != current_server {
+ if *server_pending != current_server {
// Something changed. Check if the only difference is the size, and if the
// current server size matches the current committed size.
let mut current_server_same_size = current_server.clone();
@@ -903,12 +915,17 @@ impl LayoutElement for Mapped {
if current_server_same_size == *server_pending {
// Only the size changed. Check if the window committed our previous size
// request.
- if attributes.current.size == current_server.size {
+ let Some(current_committed) = current_committed else {
+ error!("mapped must have had initial commit");
+ return ConfigureIntent::ShouldSend;
+ };
+
+ if current_committed.size == current_server.size {
// The window had committed for our previous size change, so we can
// change the size again.
trace!(
"current size matches server size: {:?}",
- attributes.current.size
+ current_committed.size
);
ConfigureIntent::CanSend
} else {
@@ -960,7 +977,7 @@ impl LayoutElement for Mapped {
}
let server_pending = role.server_pending.as_ref().unwrap();
- server_pending != role.current_server_state()
+ *server_pending != role.current_server_state()
});
if has_pending_changes {
@@ -1031,10 +1048,8 @@ impl LayoutElement for Mapped {
return false;
}
- with_toplevel_role(self.toplevel(), |role| {
- role.current
- .states
- .contains(xdg_toplevel::State::Fullscreen)
+ self.toplevel().with_committed_state(|current| {
+ current.is_some_and(|s| s.states.contains(xdg_toplevel::State::Fullscreen))
})
}
@@ -1076,7 +1091,14 @@ impl LayoutElement for Mapped {
return current_size;
}
- let pending = with_toplevel_role(self.toplevel(), |role| {
+ let pending = with_states(self.toplevel().wl_surface(), |states| {
+ let role = states
+ .data_map
+ .get::<XdgToplevelSurfaceData>()
+ .unwrap()
+ .lock()
+ .unwrap();
+
// If we have a server-pending size change that we haven't sent yet, use that size.
if let Some(server_pending) = &role.server_pending {
let current_server = role.current_server_state();
@@ -1091,18 +1113,18 @@ impl LayoutElement for Mapped {
}
// If we have a sent-but-not-committed-to size, use that.
- let (last_sent, last_serial) = if let Some(configure) = role.pending_configures().last()
- {
- (&configure.state, configure.serial)
- } else {
- (
- role.last_acked.as_ref().unwrap(),
- role.configure_serial.unwrap(),
- )
- };
-
- if let Some(current_serial) = role.current_serial {
- if !current_serial.is_no_older_than(&last_serial) {
+ let last_sent = role
+ .pending_configures()
+ .last()
+ .unwrap_or_else(|| role.last_acked.as_ref().unwrap());
+ let ToplevelConfigure {
+ state: last_sent,
+ serial: last_serial,
+ } = last_sent;
+
+ let mut guard = states.cached_state.get::<ToplevelCachedState>();
+ if let Some(current) = guard.current().last_acked.as_ref() {
+ if !current.serial.is_no_older_than(last_serial) {
return Some((
last_sent.size.unwrap_or_default(),
last_sent.states.contains(xdg_toplevel::State::Fullscreen),