Skip to content

Commit

Permalink
xdg shell: Introduce reset_initial_configure_sent() (#1542)
Browse files Browse the repository at this point in the history
* xdg-shell: `reset_initial_configure_sent` For xdg_surface remapping

* test-clients: XDG Surface Map/Unmap loop client

* xdg-shell: reset_initial_configure_sent automatically after unmap
  • Loading branch information
PolyMeilex authored Oct 25, 2024
1 parent dc2a3b4 commit de5ad9e
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ members = [
"smithay-drm-extras",
"smallvil",
"anvil",
"wlcs_anvil"
"wlcs_anvil",
"test_clients",
]

[dependencies]
Expand Down
68 changes: 68 additions & 0 deletions src/wayland/shell/xdg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,27 @@ impl ToplevelSurface {
})
}

/// A newly-unmapped toplevel surface has to perform the initial commit-configure sequence as if it was
/// a new toplevel.
///
/// This method is used to mark a surface for reinitialization.
///
/// NOTE: If you are using smithay's rendering abstractions you don't have to call this manually
///
/// Calls [`compositor::with_states`] internally.
pub fn reset_initial_configure_sent(&self) {
compositor::with_states(&self.wl_surface, |states| {
let mut data = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
data.initial_configure_sent = false;
data.initial_decoration_configure_sent = false;
});
}

/// Handles the role specific commit logic
///
/// This should be called when the underlying WlSurface
Expand All @@ -1551,13 +1572,27 @@ impl ToplevelSurface {
_dh: &DisplayHandle,
surface: &wl_surface::WlSurface,
) {
let is_mapped = crate::backend::renderer::utils::with_renderer_surface_state(surface, |state| {
state.buffer().is_some()
});

compositor::with_states(surface, |states| {
let mut guard = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();

// This can be None if rendering utils are not used by the user
if let Some(is_mapped) = is_mapped {
// After xdg surface unmaps it has to perform the initial commit-configure sequence again
if !is_mapped {
guard.initial_configure_sent = false;
guard.initial_decoration_configure_sent = false;
}
}

if let Some(state) = guard.last_acked.clone() {
guard.current = state;
}
Expand Down Expand Up @@ -1874,6 +1909,26 @@ impl PopupSurface {
})
}

/// A newly-unmapped popup surface has to perform the initial commit-configure sequence as if it was
/// a new popup.
///
/// This method is used to mark a surface for reinitialization.
///
/// NOTE: If you are using smithay's rendering abstractions you don't have to call this manually
///
/// Calls [`compositor::with_states`] internally.
pub fn reset_initial_configure_sent(&self) {
compositor::with_states(&self.wl_surface, |states| {
let mut data = states
.data_map
.get::<XdgPopupSurfaceData>()
.unwrap()
.lock()
.unwrap();
data.initial_configure_sent = false;
});
}

/// Send a configure event, including the `repositioned` event to the client
/// in response to a `reposition` request.
///
Expand Down Expand Up @@ -1922,6 +1977,10 @@ impl PopupSurface {
_dh: &DisplayHandle,
surface: &wl_surface::WlSurface,
) {
let is_mapped = crate::backend::renderer::utils::with_renderer_surface_state(surface, |state| {
state.buffer().is_some()
});

compositor::with_states(surface, |states| {
let mut attributes = states
.data_map
Expand All @@ -1930,6 +1989,15 @@ impl PopupSurface {
.lock()
.unwrap();
attributes.committed = true;

// This can be None if rendering utils are not used by the user
if let Some(is_mapped) = is_mapped {
// After xdg surface unmaps it has to perform the initial commit-configure sequence again
if !is_mapped {
attributes.initial_configure_sent = false;
}
}

if attributes.initial_configure_sent {
if let Some(state) = attributes.last_acked {
if state != attributes.current {
Expand Down
10 changes: 10 additions & 0 deletions test_clients/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "test_clients"
version = "0.1.0"
edition = "2021"
license = "MIT"

[dependencies]
smithay-client-toolkit = "0.19.2"
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
265 changes: 265 additions & 0 deletions test_clients/src/bin/test_xdg_map_unmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
//! Cycles through initial commit-configure sequence, over and over again (every 1s)

use std::{convert::TryInto, time::Duration};

use smithay_client_toolkit::reexports::{calloop, client as wayland_client};

use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell,
delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
shell::{
xdg::{
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
XdgShell,
},
WaylandSurface,
},
shm::{
slot::{Buffer, SlotPool},
Shm, ShmHandler,
},
};
use tracing::info;
use wayland_client::{
protocol::{
wl_output::{self, WlOutput},
wl_shm,
wl_surface::{self, WlSurface},
},
Connection, QueueHandle,
};

fn main() {
test_clients::init_logging();

let (mut event_loop, globals, qh) = test_clients::init_connection::<App>();

let compositor = CompositorState::bind(&globals, &qh).unwrap();
let xdg_shell = XdgShell::bind(&globals, &qh).unwrap();

let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);

let shm = Shm::bind(&globals, &qh).unwrap();
let pool = SlotPool::new(256 * 256 * 4, &shm).unwrap();

let mut simple_window = App {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
shm,

should_be_mapped: false,

first_configure: true,
pool,
width: 256,
height: 256,
shift: 0,
buffer: None,
window,
loop_signal: event_loop.get_signal(),
};

event_loop
.handle()
.insert_source(calloop::timer::Timer::immediate(), |_, _, app| {
app.map_toggle();
calloop::timer::TimeoutAction::ToDuration(Duration::from_secs(1))
})
.unwrap();

event_loop.run(None, &mut simple_window, |_| {}).unwrap();
}

struct App {
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,

should_be_mapped: bool,

first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
shift: u32,
buffer: Option<Buffer>,
window: Window,
loop_signal: calloop::LoopSignal,
}

impl CompositorHandler for App {
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(conn, qh);
}
fn surface_enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn surface_leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn scale_factor_changed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: i32) {}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: wl_output::Transform,
) {
}
}

impl WindowHandler for App {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.loop_signal.stop();
}

fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
if self.first_configure {
info!("Window initial configure");
} else {
info!("Window configure");
}

self.buffer = None;
self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);

if self.first_configure {
self.first_configure = false;
self.draw(conn, qh);
}
}
}

impl App {
fn map_toggle(&mut self) {
if self.should_be_mapped {
self.unmap()
} else {
self.map()
}
}

fn map(&mut self) {
self.first_configure = true;
self.should_be_mapped = true;
self.window.commit();
}

fn unmap(&mut self) {
self.buffer = None;
self.first_configure = false;
self.should_be_mapped = false;
self.window.attach(None, 0, 0);
self.window.commit();
}

fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
if !self.should_be_mapped {
return;
}

let width = self.width;
let height = self.height;
let stride = self.width as i32 * 4;

let buffer = self.buffer.get_or_insert_with(|| {
self.pool
.create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888)
.unwrap()
.0
});

let canvas = match self.pool.canvas(buffer) {
Some(canvas) => canvas,
None => {
// This should be rare, but if the compositor has not released the previous
// buffer, we need double-buffering.
let (second_buffer, canvas) = self
.pool
.create_buffer(
self.width as i32,
self.height as i32,
stride,
wl_shm::Format::Argb8888,
)
.unwrap();
*buffer = second_buffer;
canvas
}
};

// Draw to the window:
canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| {
let x = ((index + self.shift as usize) % width as usize) as u32;
let y = (index / width as usize) as u32;

let a = 0xFF;
let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height);
let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height);
let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height);
let color = (a << 24) + (r << 16) + (g << 8) + b;

let array: &mut [u8; 4] = chunk.try_into().unwrap();
*array = color.to_le_bytes();
});

self.shift = (self.shift + 1) % width;

self.window
.wl_surface()
.damage_buffer(0, 0, self.width as i32, self.height as i32);

self.window
.wl_surface()
.frame(qh, self.window.wl_surface().clone());

buffer.attach_to(self.window.wl_surface()).expect("buffer attach");
self.window.commit();
}
}

delegate_compositor!(App);
delegate_output!(App);
delegate_shm!(App);

delegate_xdg_shell!(App);
delegate_xdg_window!(App);

delegate_registry!(App);

impl OutputHandler for App {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
}

impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}

impl ProvidesRegistryState for App {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState,];
}
Loading

0 comments on commit de5ad9e

Please sign in to comment.