server: redraw decorations on window reconfigure

Fixes #270
This commit is contained in:
Shawn Wallace 2025-11-21 00:55:21 -05:00
parent 6338574bc5
commit f379ff5722
6 changed files with 117 additions and 43 deletions

View file

@ -138,21 +138,20 @@ impl DecorationsDataSatellite {
self.surface.commit(); self.surface.commit();
} }
/// Returns true if decorations were actually drawn
#[must_use] #[must_use]
pub fn draw_decorations( pub fn will_draw_decorations(&self, width: i32) -> bool {
&mut self, width > 0 && self.should_draw
world: &World, }
width: i32,
parent_scale_factor: f32, pub fn draw_decorations(&mut self, world: &World, width: i32, parent_scale_factor: f32) {
) -> bool { if !self.will_draw_decorations(width) {
if width <= 0 || !self.should_draw { println!("not drawing ({} {})", width, self.should_draw);
if self.remove_buffer { if self.remove_buffer {
self.surface.attach(None, 0, 0); self.surface.attach(None, 0, 0);
self.surface.commit(); self.surface.commit();
self.remove_buffer = false; self.remove_buffer = false;
} }
return false; return;
} }
self.scale = parent_scale_factor; self.scale = parent_scale_factor;
@ -212,7 +211,6 @@ impl DecorationsDataSatellite {
self.pixmap = bar; self.pixmap = bar;
self.viewport.set_destination(width, Self::TITLEBAR_HEIGHT); self.viewport.set_destination(width, Self::TITLEBAR_HEIGHT);
self.update_buffer(world); self.update_buffer(world);
true
} }
fn redraw_x_pixmap(&mut self, world: &World) { fn redraw_x_pixmap(&mut self, world: &World) {

View file

@ -113,7 +113,10 @@ impl Event for SurfaceEvents {
} }
} }
if entity.has::<WindowData>() { if entity.has::<WindowData>() {
update_surface_viewport(state.world.query_one(target).unwrap()); update_surface_viewport(
&state.world,
state.world.query_one(target).unwrap(),
);
} }
} }
_ => unreachable!(), _ => unreachable!(),
@ -228,7 +231,10 @@ impl SurfaceEvents {
let output_scale = output_data.get::<&OutputScaleFactor>().unwrap().get(); let output_scale = output_data.get::<&OutputScaleFactor>().unwrap().get();
data.get::<&mut SurfaceScaleFactor>().unwrap().0 = output_scale; data.get::<&mut SurfaceScaleFactor>().unwrap().0 = output_scale;
drop(query); drop(query);
update_surface_viewport(state.world.query_one(target).unwrap()); update_surface_viewport(
&state.world,
state.world.query_one(target).unwrap(),
);
} else { } else {
let scale = data.get::<&SurfaceScaleFactor>().unwrap(); let scale = data.get::<&SurfaceScaleFactor>().unwrap();
if update_output_scale( if update_output_scale(
@ -310,10 +316,10 @@ impl SurfaceEvents {
data.get::<&WlSurface>().unwrap().id(), data.get::<&WlSurface>().unwrap().id(),
); );
if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role { if let SurfaceRole::Toplevel(Some(toplevel)) = &*role {
if let Some(d) = &mut toplevel.decoration.satellite { if let Some(d) = &toplevel.decoration.satellite {
let surface_width = (width as f64 / scale_factor.0) as i32; let surface_width = (width as f64 / scale_factor.0) as i32;
if d.draw_decorations(&state.world, surface_width, scale_factor.0 as f32) { if d.will_draw_decorations(surface_width) {
height = height height = height
.saturating_sub( .saturating_sub(
(DecorationsDataSatellite::TITLEBAR_HEIGHT as f64 * scale_factor.0) (DecorationsDataSatellite::TITLEBAR_HEIGHT as f64 * scale_factor.0)
@ -341,7 +347,7 @@ impl SurfaceEvents {
}; };
drop(query); drop(query);
update_surface_viewport(state.world.query_one(target).unwrap()); update_surface_viewport(&state.world, state.world.query_one(target).unwrap());
} }
let (surface, attach, callback) = state let (surface, attach, callback) = state
@ -457,15 +463,16 @@ impl SurfaceEvents {
} }
pub(super) fn update_surface_viewport( pub(super) fn update_surface_viewport(
world: &World,
mut surface_query: hecs::QueryOne<( mut surface_query: hecs::QueryOne<(
&WindowData, &WindowData,
&WpViewport, &WpViewport,
&SurfaceScaleFactor, &SurfaceScaleFactor,
Option<&SurfaceRole>, Option<&mut SurfaceRole>,
&WlSurface, &WlSurface,
)>, )>,
) { ) {
let (window_data, viewport, scale_factor, role, surface) = surface_query.get().unwrap(); let (window_data, viewport, scale_factor, mut role, surface) = surface_query.get().unwrap();
let dims = &window_data.attrs.dims; let dims = &window_data.attrs.dims;
let size_hints = &window_data.attrs.size_hints; let size_hints = &window_data.attrs.size_hints;
@ -474,25 +481,22 @@ pub(super) fn update_surface_viewport(
if width > 0 && height > 0 { if width > 0 && height > 0 {
viewport.set_destination(width, height); viewport.set_destination(width, height);
} }
let mut toplevel_data = match &mut role {
Some(SurfaceRole::Toplevel(Some(data))) => Some(data),
_ => None,
};
if let Some(d) = toplevel_data
.as_mut()
.and_then(|d| d.decoration.satellite.as_deref_mut())
{
d.draw_decorations(world, width, scale_factor.0 as f32);
}
debug!("{} viewport: {width}x{height}", surface.id()); debug!("{} viewport: {width}x{height}", surface.id());
if let Some(hints) = size_hints { if let Some(hints) = size_hints {
let data = match &role { let Some(data) = toplevel_data else {
Some(SurfaceRole::Toplevel(Some(data))) => data, return;
Some(SurfaceRole::Toplevel(None)) => {
warn!(
"Trying to update size hints on {}, but toplevel role data is missing",
surface.id()
);
return;
}
Some(SurfaceRole::Popup(_)) => {
// Popups don't have min/max size hints.
return;
}
None => {
warn!("No role set on {}.", surface.id());
return;
}
}; };
if let Some(min) = hints.min_size { if let Some(min) = hints.min_size {

View file

@ -656,7 +656,10 @@ impl<C: XConnection> ServerState<C> {
drop(surface_query); drop(surface_query);
for surface in surfaces { for surface in surfaces {
update_surface_viewport(self.world.query_one(surface).unwrap()); update_surface_viewport(
&self.world,
self.world.query_one(surface).unwrap(),
);
} }
} }
} }
@ -952,7 +955,7 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
return; return;
} }
let mut query = data.query::<(&SurfaceRole, &SurfaceScaleFactor)>(); let mut query = data.query::<(&mut SurfaceRole, &SurfaceScaleFactor)>();
let Some((role, scale_factor)) = query.get() else { let Some((role, scale_factor)) = query.get() else {
return; return;
}; };
@ -974,7 +977,7 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
win.attrs.dims.height = dims.height; win.attrs.dims.height = dims.height;
drop(query); drop(query);
drop(win); drop(win);
update_surface_viewport(self.world.query_one(data.entity()).unwrap()); update_surface_viewport(&self.world, self.world.query_one(data.entity()).unwrap());
} }
other => warn!("Non popup ({other:?}) being reconfigured, behavior may be off."), other => warn!("Non popup ({other:?}) being reconfigured, behavior may be off."),
} }

View file

@ -2716,6 +2716,60 @@ fn client_side_decorations_no_global() {
assert_eq!(toplevel.unwrap(), subsurface_parent.unwrap()); assert_eq!(toplevel.unwrap(), subsurface_parent.unwrap());
} }
#[test]
fn resize_decorations_on_reconfigure() {
let (mut f, compositor) = TestFixture::new_with_compositor();
let window = unsafe { Window::new(1) };
let (_, id) = f.create_toplevel(&compositor, window);
f.testwl
.force_decoration_mode(id, zxdg_toplevel_decoration_v1::Mode::ClientSide);
f.testwl.configure_toplevel(id, 100, 100, vec![]);
f.run();
let data = f.connection().window(window);
assert_eq!(
data.dims,
WindowDims {
x: 0,
y: 0,
width: 100,
height: 75
}
);
let subsurface_id = f.testwl.last_created_surface_id().unwrap();
assert_ne!(subsurface_id, id);
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
let buf_dims = f
.testwl
.get_buffer_dimensions(data.buffer.as_ref().expect("Missing buffer for subsurface"));
assert_eq!(buf_dims, testwl::Vec2 { x: 100, y: 25 });
assert!(
matches!(data.role, Some(SurfaceRole::Subsurface(_))),
"surface was not a subsurface: {:?}",
data.role
);
f.satellite.reconfigure_window(x::ConfigureNotifyEvent::new(
window,
window,
x::WINDOW_NONE,
0,
0,
200,
200,
0,
false,
));
f.run();
f.run();
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
let buf_dims = f
.testwl
.get_buffer_dimensions(data.buffer.as_ref().expect("Missing buffer for subsurface"));
assert_eq!(buf_dims, testwl::Vec2 { x: 200, y: 25 });
}
/// See Pointer::handle_event for an explanation. /// See Pointer::handle_event for an explanation.
#[test] #[test]
fn popup_pointer_motion_workaround() {} fn popup_pointer_motion_workaround() {}

View file

@ -1362,7 +1362,6 @@ fn different_output_position() {
f.testwl.move_pointer_to(surface, 150.0, 12.0); f.testwl.move_pointer_to(surface, 150.0, 12.0);
f.wait_and_dispatch(); f.wait_and_dispatch();
let reply = connection.get_reply(&x::QueryPointer { window }); let reply = connection.get_reply(&x::QueryPointer { window });
println!("reply: {reply:?}");
assert!(reply.same_screen()); assert!(reply.same_screen());
assert_eq!(reply.win_x(), 150); assert_eq!(reply.win_x(), 150);
assert_eq!(reply.win_y(), 12); assert_eq!(reply.win_y(), 12);

View file

@ -260,7 +260,7 @@ struct State {
surfaces: HashMap<SurfaceId, SurfaceData>, surfaces: HashMap<SurfaceId, SurfaceData>,
outputs: HashMap<WlOutput, Output>, outputs: HashMap<WlOutput, Output>,
positioners: HashMap<PositionerId, PositionerState>, positioners: HashMap<PositionerId, PositionerState>,
buffers: HashSet<WlBuffer>, buffers: HashMap<WlBuffer, Vec2>,
begin: Instant, begin: Instant,
last_surface_id: Option<SurfaceId>, last_surface_id: Option<SurfaceId>,
created_surfaces: Vec<SurfaceId>, created_surfaces: Vec<SurfaceId>,
@ -559,7 +559,6 @@ impl Server {
} }
pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> { pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> {
println!("{:?}", self.state.surfaces);
self.state.surfaces.get(&surface_id) self.state.surfaces.get(&surface_id)
} }
@ -936,6 +935,15 @@ impl Server {
.handle() .handle()
.remove_global::<State>(self.decorations_global.clone()); .remove_global::<State>(self.decorations_global.clone());
} }
#[track_caller]
pub fn get_buffer_dimensions(&self, buffer: &WlBuffer) -> Vec2 {
*self
.state
.buffers
.get(buffer)
.expect("buffer does not exist!")
}
} }
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
@ -1861,9 +1869,17 @@ impl Dispatch<WlShmPool, ()> for State {
) { ) {
use proto::wl_shm_pool::Request::*; use proto::wl_shm_pool::Request::*;
match request { match request {
CreateBuffer { id, .. } => { CreateBuffer {
id, width, height, ..
} => {
let buf = data_init.init(id, ()); let buf = data_init.init(id, ());
state.buffers.insert(buf); state.buffers.insert(
buf,
Vec2 {
x: width,
y: height,
},
);
} }
Resize { .. } => {} Resize { .. } => {}
Destroy => {} Destroy => {}