Refine shell status controls and glass rendering; defer cursor redraw safely

This commit is contained in:
Samuel Onoja committed 2026-09-26 05:05:50 +01:00
1 parent e85fc1e032
commit b3fa82b53b
53 files changed
+3329 -259

No files matched your search

+81 -1
View File
@@ -77,7 +77,19 @@ fn main() -> Result<(), Box<dyn Error>> {
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
let mut child = spawn_client(&mut state, launch.client, launch.client_capabilities);
let result = event_loop.run(None, &mut state, |_| {});
let result = event_loop.run(None, &mut state, |state| {
// All input/Wayland callbacks have returned, releasing seat locks.
// Coalesce cursor changes and redraw here, never inside cursor_image.
if std::mem::take(&mut state.cursor_redraw_pending) {
backends::direct::render_all(state);
}
// Registry/sync/configure replies must not depend on a submitted
// frame: at startup clients need these before they can draw anything.
// This also keeps idle DRM sessions responsive when there is no damage.
if let Err(error) = state.display_handle.flush_clients() {
tracing::warn!(%error, "failed to flush Wayland clients");
}
});
if let Some(child) = &mut child {
terminate_child(child);
@@ -151,3 +163,71 @@ fn terminate_child(child: &mut Child) {
let _ = child.kill();
let _ = child.wait();
}
#[cfg(test)]
mod startup_tests {
use super::*;
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
sync::Arc,
};
#[test]
fn deferred_cursor_redraw_runs_after_input_lock_is_released() {
use std::sync::Mutex;
let mut event_loop = EventLoop::<bool>::try_new().unwrap();
let pointer_lock = Arc::new(Mutex::new(()));
let callback_lock = pointer_lock.clone();
event_loop.handle().insert_idle(move |pending| {
// Mirrors Smithay's cursor_image callback during pointer motion.
let _guard = callback_lock.lock().unwrap();
*pending = true;
*pending = true; // Multiple cursor changes must coalesce.
assert!(callback_lock.try_lock().is_err());
});
let signal = event_loop.get_signal();
let mut pending = false;
let mut redraws = 0;
event_loop
.run(Some(Duration::ZERO), &mut pending, |pending| {
if std::mem::take(pending) {
// Rendering reads the pointer location at this point.
let _guard = pointer_lock.try_lock().expect("input lock still held");
redraws += 1;
}
assert!(!std::mem::take(pending));
signal.stop();
})
.unwrap();
assert_eq!(redraws, 1);
}
#[test]
fn wayland_roundtrip_completes_without_a_rendered_frame() {
let mut display = Display::<()>::new().unwrap();
let (server, mut client) = UnixStream::pair().unwrap();
display
.handle()
.insert_client(server, Arc::new(state::ClientState::default()))
.unwrap();
// wl_display.sync(new_id=2), using native-endian Wayland wire format.
for word in [1u32, 12u32 << 16, 2u32] {
client.write_all(&word.to_ne_bytes()).unwrap();
}
display.dispatch_clients(&mut ()).unwrap();
display.handle().flush_clients().unwrap();
client
.set_read_timeout(Some(Duration::from_secs(1)))
.unwrap();
let mut reply = [0u8; 12];
client.read_exact(&mut reply).unwrap();
assert_eq!(u32::from_ne_bytes(reply[0..4].try_into().unwrap()), 2);
assert_eq!(
u32::from_ne_bytes(reply[4..8].try_into().unwrap()) & 0xffff,
0
);
}
}