From aaed003ec853a035babe9737a358985f1126bc17 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 10:22:32 +0200 Subject: [PATCH 01/24] render: sync before rendering single-pixel buffer --- src/renderer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer.rs b/src/renderer.rs index 93922a9d..87b50776 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -405,6 +405,7 @@ impl Renderer<'_> { Some(bounds) => rect.intersect(*bounds), }; if !rect.is_empty() { + self.base.ops.push(GfxApiOpt::Sync); self.base.fill_boxes(&[rect], color); } } From f562f887f0254d584709e1074f581195dbacceb4 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 10:23:55 +0200 Subject: [PATCH 02/24] it: use single-pixel buffer instead of shm --- src/it/test_client.rs | 17 +++--- src/it/test_ifs.rs | 4 ++ src/it/test_ifs/test_buffer.rs | 48 +++++++++++++++++ src/it/test_ifs/test_registry.rs | 35 ++++++++++++- src/it/test_ifs/test_shm.rs | 2 + src/it/test_ifs/test_shm_buffer.rs | 42 ++------------- src/it/test_ifs/test_shm_pool.rs | 23 +++++--- .../test_single_pixel_buffer_manager.rs | 44 ++++++++++++++++ src/it/test_ifs/test_viewport.rs | 52 +++++++++++++++++++ src/it/test_ifs/test_viewporter.rs | 39 ++++++++++++++ src/it/test_mem.rs | 3 ++ src/it/test_transport.rs | 2 + src/it/test_utils/test_window.rs | 24 ++++----- src/it/testrun.rs | 2 + src/it/tests/t0007_subsurface.rs | 10 ++-- src/it/tests/t0012_subsurface_focus.rs | 6 ++- src/theme.rs | 3 +- 17 files changed, 280 insertions(+), 76 deletions(-) create mode 100644 src/it/test_ifs/test_buffer.rs create mode 100644 src/it/test_ifs/test_single_pixel_buffer_manager.rs create mode 100644 src/it/test_ifs/test_viewport.rs create mode 100644 src/it/test_ifs/test_viewporter.rs diff --git a/src/it/test_client.rs b/src/it/test_client.rs index e6eb7ced..31580baf 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -2,7 +2,6 @@ use { crate::{ cli::screenshot::buf_to_qoi, client::Client, - format::ARGB8888, globals::GlobalBase, it::{ test_error::{TestError, TestResult}, @@ -10,14 +9,15 @@ use { test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor, test_keyboard::TestKeyboard, test_pointer::TestPointer, test_registry::TestRegistry, test_seat::TestSeat, test_shm::TestShm, - test_subcompositor::TestSubcompositor, test_xdg_base::TestXdgWmBase, + test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, + test_xdg_base::TestXdgWmBase, }, test_transport::TestTransport, test_utils::test_window::TestWindow, testrun::TestRun, }, theme::Color, - utils::clonecell::CloneCell, }, std::{cell::Cell, rc::Rc}, }; @@ -31,6 +31,8 @@ pub struct TestClient { pub comp: Rc, pub sub: Rc, pub shm: Rc, + pub spbm: Rc, + pub viewporter: Rc, pub xdg: Rc, } @@ -114,19 +116,18 @@ impl TestClient { pub async fn create_window(&self) -> Result, TestError> { let surface = self.comp.create_surface().await?; - let shm = self.shm.create_pool(0)?; - let buffer = shm.create_buffer(0, 0, 0, 0, ARGB8888)?; + let viewport = self.viewporter.get_viewport(&surface)?; let xdg = self.xdg.create_xdg_surface(surface.id).await?; let tl = xdg.create_toplevel().await?; surface.commit()?; self.sync().await; Ok(Rc::new(TestWindow { surface, + spbm: self.spbm.clone(), + viewport, xdg, tl, - shm, - buffer: CloneCell::new(buffer), - color: Cell::new(Color::from_rgba_straight(0, 0, 0, 0)), + color: Cell::new(Color::SOLID_BLACK), })) } } diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index 58ca7d15..b8440987 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -1,3 +1,4 @@ +mod test_buffer; pub mod test_callback; pub mod test_compositor; pub mod test_display; @@ -11,9 +12,12 @@ pub mod test_seat; pub mod test_shm; pub mod test_shm_buffer; pub mod test_shm_pool; +pub mod test_single_pixel_buffer_manager; pub mod test_subcompositor; pub mod test_subsurface; pub mod test_surface; +pub mod test_viewport; +pub mod test_viewporter; pub mod test_xdg_base; pub mod test_xdg_surface; pub mod test_xdg_toplevel; diff --git a/src/it/test_ifs/test_buffer.rs b/src/it/test_ifs/test_buffer.rs new file mode 100644 index 00000000..8ed6fa6b --- /dev/null +++ b/src/it/test_ifs/test_buffer.rs @@ -0,0 +1,48 @@ +use { + crate::{ + it::{ + test_error::TestError, test_object::TestObject, test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{wl_buffer::*, WlBufferId}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestBuffer { + pub id: WlBufferId, + pub tran: Rc, + pub released: Cell, + pub destroyed: Cell, +} + +impl TestBuffer { + pub fn destroy(&self) -> Result<(), TestError> { + if self.destroyed.replace(true) { + return Ok(()); + } + self.tran.send(Destroy { self_id: self.id })?; + Ok(()) + } + + fn handle_release(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let _ev = Release::parse_full(parser)?; + self.released.set(true); + Ok(()) + } +} + +impl Drop for TestBuffer { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestBuffer, WlBuffer; + + RELEASE => handle_release, +} + +impl TestObject for TestBuffer {} diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 824d8dee..3154dee6 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -6,7 +6,8 @@ use { test_error::TestError, test_ifs::{ test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor, - test_shm::TestShm, test_subcompositor::TestSubcompositor, + test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, test_xdg_base::TestXdgWmBase, }, test_object::TestObject, @@ -31,6 +32,8 @@ pub struct TestRegistrySingletons { pub wl_subcompositor: u32, pub wl_shm: u32, pub xdg_wm_base: u32, + pub wp_single_pixel_buffer_manager_v1: u32, + pub wp_viewporter: u32, } pub struct TestRegistry { @@ -42,6 +45,8 @@ pub struct TestRegistry { pub compositor: CloneCell>>, pub subcompositor: CloneCell>>, pub shm: CloneCell>>, + pub spbm: CloneCell>>, + pub viewporter: CloneCell>>, pub xdg: CloneCell>>, pub seats: CopyHashMap>, } @@ -90,6 +95,8 @@ impl TestRegistry { wl_subcompositor, wl_shm, xdg_wm_base, + wp_single_pixel_buffer_manager_v1, + wp_viewporter, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -151,6 +158,32 @@ impl TestRegistry { Ok(jc) } + pub async fn get_spbm(&self) -> Result, TestError> { + singleton!(self.spbm); + let singletons = self.get_singletons().await?; + singleton!(self.spbm); + let jc = Rc::new(TestSinglePixelBufferManager { + id: self.tran.id(), + tran: self.tran.clone(), + }); + self.bind(&jc, singletons.wp_single_pixel_buffer_manager_v1, 1)?; + self.spbm.set(Some(jc.clone())); + Ok(jc) + } + + pub async fn get_viewporter(&self) -> Result, TestError> { + singleton!(self.viewporter); + let singletons = self.get_singletons().await?; + singleton!(self.viewporter); + let jc = Rc::new(TestViewporter { + id: self.tran.id(), + tran: self.tran.clone(), + }); + self.bind(&jc, singletons.wp_viewporter, 1)?; + self.viewporter.set(Some(jc.clone())); + Ok(jc) + } + pub async fn get_xdg(&self) -> Result, TestError> { singleton!(self.xdg); let singletons = self.get_singletons().await?; diff --git a/src/it/test_ifs/test_shm.rs b/src/it/test_ifs/test_shm.rs index d58d3a7e..b78eb862 100644 --- a/src/it/test_ifs/test_shm.rs +++ b/src/it/test_ifs/test_shm.rs @@ -30,6 +30,7 @@ impl TestShm { &self.formats } + #[allow(dead_code)] pub fn create_pool(&self, size: usize) -> Result, TestError> { let mem = TestMem::new(size)?; let pool = Rc::new(TestShmPool { @@ -48,6 +49,7 @@ impl TestShm { Ok(pool) } + #[allow(dead_code)] pub fn create_buffer(&self, width: i32, height: i32) -> TestResult> { let pool = self.create_pool((width * height * 4) as _)?; pool.create_buffer(0, width, height, width * 4, ARGB8888) diff --git a/src/it/test_ifs/test_shm_buffer.rs b/src/it/test_ifs/test_shm_buffer.rs index 0c291faf..93cb7c3f 100644 --- a/src/it/test_ifs/test_shm_buffer.rs +++ b/src/it/test_ifs/test_shm_buffer.rs @@ -1,12 +1,8 @@ use { crate::{ - it::{ - test_error::TestError, test_mem::TestMem, test_object::TestObject, - test_transport::TestTransport, testrun::ParseFull, - }, + it::{test_ifs::test_buffer::TestBuffer, test_mem::TestMem}, theme::Color, - utils::{buffd::MsgParser, windows::WindowsExt}, - wire::{wl_buffer::*, WlBufferId}, + utils::windows::WindowsExt, }, std::{ cell::Cell, @@ -16,15 +12,13 @@ use { }; pub struct TestShmBuffer { - pub id: WlBufferId, - pub tran: Rc, + pub buffer: Rc, pub range: Range, pub mem: Rc, - pub released: Cell, - pub destroyed: Cell, } impl TestShmBuffer { + #[allow(dead_code)] pub fn fill(&self, color: Color) { let [cr, cg, cb, ca] = color.to_rgba_premultiplied(); for [b, g, r, a] in self.deref().array_chunks_ext::<4>() { @@ -34,20 +28,6 @@ impl TestShmBuffer { a.set(ca); } } - - pub fn destroy(&self) -> Result<(), TestError> { - if self.destroyed.replace(true) { - return Ok(()); - } - self.tran.send(Destroy { self_id: self.id })?; - Ok(()) - } - - fn handle_release(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { - let _ev = Release::parse_full(parser)?; - self.released.set(true); - Ok(()) - } } impl Deref for TestShmBuffer { @@ -57,17 +37,3 @@ impl Deref for TestShmBuffer { &self.mem[self.range.clone()] } } - -impl Drop for TestShmBuffer { - fn drop(&mut self) { - let _ = self.destroy(); - } -} - -test_object! { - TestShmBuffer, WlBuffer; - - RELEASE => handle_release, -} - -impl TestObject for TestShmBuffer {} diff --git a/src/it/test_ifs/test_shm_pool.rs b/src/it/test_ifs/test_shm_pool.rs index 8d4053f1..639a6b4d 100644 --- a/src/it/test_ifs/test_shm_pool.rs +++ b/src/it/test_ifs/test_shm_pool.rs @@ -2,8 +2,11 @@ use { crate::{ format::Format, it::{ - test_error::TestError, test_ifs::test_shm_buffer::TestShmBuffer, test_mem::TestMem, - test_object::TestObject, test_transport::TestTransport, + test_error::TestError, + test_ifs::{test_buffer::TestBuffer, test_shm_buffer::TestShmBuffer}, + test_mem::TestMem, + test_object::TestObject, + test_transport::TestTransport, }, utils::clonecell::CloneCell, wire::{wl_shm_pool::*, WlShmPoolId}, @@ -19,6 +22,7 @@ pub struct TestShmPool { } impl TestShmPool { + #[allow(dead_code)] pub fn create_buffer( &self, offset: i32, @@ -35,17 +39,19 @@ impl TestShmPool { bail!("Out-of-bounds buffer"); } let buffer = Rc::new(TestShmBuffer { - id: self.tran.id(), - tran: self.tran.clone(), + buffer: Rc::new(TestBuffer { + id: self.tran.id(), + tran: self.tran.clone(), + released: Cell::new(true), + destroyed: Cell::new(false), + }), range: start..end, mem, - released: Cell::new(true), - destroyed: Cell::new(false), }); - self.tran.add_obj(buffer.clone())?; + self.tran.add_obj(buffer.buffer.clone())?; self.tran.send(CreateBuffer { self_id: self.id, - id: buffer.id, + id: buffer.buffer.id, offset, width, height, @@ -55,6 +61,7 @@ impl TestShmPool { Ok(buffer) } + #[allow(dead_code)] pub fn resize(&self, size: usize) -> Result<(), TestError> { let mem = self.mem.get().grow(size)?; self.mem.set(mem); diff --git a/src/it/test_ifs/test_single_pixel_buffer_manager.rs b/src/it/test_ifs/test_single_pixel_buffer_manager.rs new file mode 100644 index 00000000..a5652419 --- /dev/null +++ b/src/it/test_ifs/test_single_pixel_buffer_manager.rs @@ -0,0 +1,44 @@ +use { + crate::{ + it::{ + test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject, + test_transport::TestTransport, + }, + theme::Color, + wire::{wp_single_pixel_buffer_manager_v1::*, WpSinglePixelBufferManagerV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestSinglePixelBufferManager { + pub id: WpSinglePixelBufferManagerV1Id, + pub tran: Rc, +} + +impl TestSinglePixelBufferManager { + pub fn create_buffer(&self, color: Color) -> TestResult> { + let obj = Rc::new(TestBuffer { + id: self.tran.id(), + tran: self.tran.clone(), + released: Cell::new(true), + destroyed: Cell::new(false), + }); + let map = |c: f32| (c as f64 * u32::MAX as f64) as u32; + self.tran.send(CreateU32RgbaBuffer { + self_id: self.id, + id: obj.id, + r: map(color.r), + g: map(color.g), + b: map(color.b), + a: map(color.a), + })?; + self.tran.add_obj(obj.clone())?; + Ok(obj) + } +} + +test_object! { + TestSinglePixelBufferManager, WpSinglePixelBufferManagerV1; +} + +impl TestObject for TestSinglePixelBufferManager {} diff --git a/src/it/test_ifs/test_viewport.rs b/src/it/test_ifs/test_viewport.rs new file mode 100644 index 00000000..a22df787 --- /dev/null +++ b/src/it/test_ifs/test_viewport.rs @@ -0,0 +1,52 @@ +use { + crate::{ + fixed::Fixed, + it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport}, + wire::{wp_viewport::*, WpViewportId}, + }, + std::rc::Rc, +}; + +pub struct TestViewport { + pub id: WpViewportId, + pub tran: Rc, +} + +impl TestViewport { + pub fn destroy(&self) -> Result<(), TestError> { + self.tran.send(Destroy { self_id: self.id })?; + Ok(()) + } + + pub fn set_source(&self, x: i32, y: i32, width: i32, height: i32) -> Result<(), TestError> { + self.tran.send(SetSource { + self_id: self.id, + x: Fixed::from_int(x), + y: Fixed::from_int(y), + width: Fixed::from_int(width), + height: Fixed::from_int(height), + })?; + Ok(()) + } + + pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> { + self.tran.send(SetDestination { + self_id: self.id, + width: width.max(1), + height: height.max(1), + })?; + Ok(()) + } +} + +impl Drop for TestViewport { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestViewport, WpViewport; +} + +impl TestObject for TestViewport {} diff --git a/src/it/test_ifs/test_viewporter.rs b/src/it/test_ifs/test_viewporter.rs new file mode 100644 index 00000000..d3023b9e --- /dev/null +++ b/src/it/test_ifs/test_viewporter.rs @@ -0,0 +1,39 @@ +use { + crate::{ + it::{ + test_error::TestResult, + test_ifs::{test_surface::TestSurface, test_viewport::TestViewport}, + test_object::TestObject, + test_transport::TestTransport, + }, + wire::{wp_viewporter::*, WpViewporterId}, + }, + std::rc::Rc, +}; + +pub struct TestViewporter { + pub id: WpViewporterId, + pub tran: Rc, +} + +impl TestViewporter { + pub fn get_viewport(&self, surface: &TestSurface) -> TestResult> { + let obj = Rc::new(TestViewport { + id: self.tran.id(), + tran: self.tran.clone(), + }); + self.tran.send(GetViewport { + self_id: self.id, + id: obj.id, + surface: surface.id, + })?; + self.tran.add_obj(obj.clone())?; + Ok(obj) + } +} + +test_object! { + TestViewporter, WpViewporter; +} + +impl TestObject for TestViewporter {} diff --git a/src/it/test_mem.rs b/src/it/test_mem.rs index 236808b3..fbdd9256 100644 --- a/src/it/test_mem.rs +++ b/src/it/test_mem.rs @@ -13,6 +13,7 @@ pub struct TestMem { } impl TestMem { + #[allow(dead_code)] pub fn new(size: usize) -> Result, TestError> { let fd = uapi::memfd_create("test_pool", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING)?; uapi::fcntl_add_seals(fd.raw(), c::F_SEAL_SHRINK)?; @@ -24,6 +25,7 @@ impl TestMem { })) } + #[allow(dead_code)] pub fn grow(&self, size: usize) -> Result, TestError> { let cur_len = uapi::fstat(self.fd.raw())?; if size > cur_len.st_size as _ { @@ -45,6 +47,7 @@ impl Deref for TestMem { } } +#[allow(dead_code)] fn map(fd: c::c_int, size: usize) -> Result<*const [Cell], TestError> { if size == 0 { return Ok(&[]); diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index f9744ae1..d7708da2 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -56,6 +56,8 @@ impl TestTransport { compositor: Default::default(), subcompositor: Default::default(), shm: Default::default(), + spbm: Default::default(), + viewporter: Default::default(), xdg: Default::default(), seats: Default::default(), }); diff --git a/src/it/test_utils/test_window.rs b/src/it/test_utils/test_window.rs index 3f78d3df..20abd167 100644 --- a/src/it/test_utils/test_window.rs +++ b/src/it/test_utils/test_window.rs @@ -1,42 +1,36 @@ use { crate::{ - format::ARGB8888, it::{ test_error::{TestError, TestResult}, test_ifs::{ - test_shm_buffer::TestShmBuffer, test_shm_pool::TestShmPool, - test_surface::TestSurface, test_xdg_surface::TestXdgSurface, - test_xdg_toplevel::TestXdgToplevel, + test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_surface::TestSurface, test_viewport::TestViewport, + test_xdg_surface::TestXdgSurface, test_xdg_toplevel::TestXdgToplevel, }, }, theme::Color, - utils::clonecell::CloneCell, }, std::{cell::Cell, rc::Rc}, }; pub struct TestWindow { pub surface: Rc, + pub spbm: Rc, + pub viewport: Rc, pub xdg: Rc, pub tl: Rc, - pub shm: Rc, - pub buffer: CloneCell>, pub color: Cell, } impl TestWindow { pub async fn map(&self) -> Result<(), TestError> { - let width = self.tl.width.get(); - let height = self.tl.height.get(); - let stride = width * 4; - let size = (stride * height) as usize; - self.shm.resize(size)?; - let buffer = self.shm.create_buffer(0, width, height, stride, ARGB8888)?; - buffer.fill(self.color.get()); + let buffer = self.spbm.create_buffer(self.color.get())?; self.surface.attach(buffer.id)?; + self.viewport.set_source(0, 0, 1, 1)?; + self.viewport + .set_destination(self.tl.width.get(), self.tl.height.get())?; self.xdg.ack_configure(self.xdg.last_serial.get())?; self.surface.commit()?; - self.buffer.set(buffer); self.surface.tran.sync().await; Ok(()) } diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 97718e41..81b88db0 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -83,6 +83,8 @@ impl TestRun { comp: registry.get_compositor().await?, sub: registry.get_subcompositor().await?, shm: registry.get_shm().await?, + spbm: registry.get_spbm().await?, + viewporter: registry.get_viewporter().await?, xdg: registry.get_xdg().await?, registry, })) diff --git a/src/it/tests/t0007_subsurface.rs b/src/it/tests/t0007_subsurface.rs index 93b1a245..34fec5a8 100644 --- a/src/it/tests/t0007_subsurface.rs +++ b/src/it/tests/t0007_subsurface.rs @@ -1,6 +1,5 @@ use { crate::{ - format::ARGB8888, it::{test_error::TestError, testrun::TestRun}, theme::Color, }, @@ -26,17 +25,20 @@ async fn test(run: Rc) -> Result<(), TestError> { parent.set_color(0, 0, 0, 255); let child = client.comp.create_surface().await?; + let child_viewport = client.viewporter.get_viewport(&child)?; let sub = client .sub .get_subsurface(child.id, parent.surface.id) .await?; sub.set_position(100, 100)?; - let pool = client.shm.create_pool(100 * 100 * 4)?; - let buffer = pool.create_buffer(0, 100, 100, 100 * 4, ARGB8888)?; - buffer.fill(Color::from_rgba_straight(255, 255, 255, 255)); + let buffer = client + .spbm + .create_buffer(Color::from_rgba_straight(255, 255, 255, 255))?; child.attach(buffer.id)?; + child_viewport.set_source(0, 0, 1, 1)?; + child_viewport.set_destination(100, 100)?; child.commit()?; parent.map().await?; diff --git a/src/it/tests/t0012_subsurface_focus.rs b/src/it/tests/t0012_subsurface_focus.rs index 4d978603..7fc2ccab 100644 --- a/src/it/tests/t0012_subsurface_focus.rs +++ b/src/it/tests/t0012_subsurface_focus.rs @@ -5,6 +5,7 @@ use { test_error::{TestErrorExt, TestResult}, testrun::TestRun, }, + theme::Color, }, std::rc::Rc, }; @@ -25,10 +26,13 @@ async fn test(run: Rc) -> TestResult { window.map().await?; let ns = client.comp.create_surface().await?; + let nsv = client.viewporter.get_viewport(&ns)?; let nss = client.sub.get_subsurface(ns.id, window.surface.id).await?; nss.set_position(100, 100)?; - let buffer = client.shm.create_buffer(100, 100)?; + let buffer = client.spbm.create_buffer(Color::SOLID_BLACK)?; ns.attach(buffer.id)?; + nsv.set_source(0, 0, 1, 1)?; + nsv.set_destination(100, 100)?; ns.commit()?; run.cfg.set_fullscreen(ds.seat.id(), true)?; diff --git a/src/theme.rs b/src/theme.rs index 35380133..98fd5db1 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -33,6 +33,7 @@ fn to_f32(c: u8) -> f32 { c as f32 / 255f32 } +#[allow(dead_code)] fn to_u8(c: f32) -> u8 { (c * 255f32) as u8 } @@ -87,7 +88,7 @@ impl Color { } } - #[cfg_attr(not(feature = "it"), allow(dead_code))] + #[allow(dead_code)] pub fn to_rgba_premultiplied(self) -> [u8; 4] { [to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)] } From adf6d2ae2ba7248edb616efe1240ca9b100d7e8b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 10:26:42 +0200 Subject: [PATCH 03/24] it: test natural scrolling --- src/it/test_backend.rs | 6 ++- src/it/test_client.rs | 2 +- src/it/test_ifs/test_pointer.rs | 8 ++++ src/it/test_ifs/test_seat.rs | 1 + src/it/test_utils.rs | 4 ++ src/it/test_utils/test_container_node_ext.rs | 20 +++++++++ src/it/test_utils/test_ouput_node_ext.rs | 20 +++++++++ src/it/test_utils/test_toplevel_node_ext.rs | 12 +++++ src/it/test_utils/test_workspace_node_ext.rs | 20 +++++++++ src/it/testrun.rs | 10 +++++ src/it/tests.rs | 2 + src/it/tests/t0019_natural_scrolling.rs | 46 ++++++++++++++++++++ 12 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/it/test_utils/test_container_node_ext.rs create mode 100644 src/it/test_utils/test_ouput_node_ext.rs create mode 100644 src/it/test_utils/test_toplevel_node_ext.rs create mode 100644 src/it/test_utils/test_workspace_node_ext.rs create mode 100644 src/it/tests/t0019_natural_scrolling.rs diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 17ff055f..cab5267e 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -319,13 +319,17 @@ impl TestBackendMouse { } pub fn scroll_px(&self, dy: i32) { + self.scroll_px2(dy, false); + } + + pub fn scroll_px2(&self, dy: i32, inverted: bool) { self.common.event(InputEvent::AxisSource { source: AxisSource::Finger, }); self.common.event(InputEvent::AxisPx { dist: Fixed::from_int(dy), axis: ScrollAxis::Vertical, - inverted: false, + inverted, }); self.common.event(InputEvent::AxisFrame { time_usec: now_usec(), diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 31580baf..3f8b5e46 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -67,7 +67,7 @@ impl TestClient { caps: Cell::new(0), name: Default::default(), }); - self.registry.bind(&tseat, seat.name().raw(), 7)?; + self.registry.bind(&tseat, seat.name().raw(), 9)?; self.tran.sync().await; let server = self.tran.get_server_obj(tseat.id)?; tseat.server.set(Some(server)); diff --git a/src/it/test_ifs/test_pointer.rs b/src/it/test_ifs/test_pointer.rs index 05249f84..f858c743 100644 --- a/src/it/test_ifs/test_pointer.rs +++ b/src/it/test_ifs/test_pointer.rs @@ -19,6 +19,7 @@ pub struct TestPointer { pub leave: TEEH, pub enter: TEEH, pub motion: TEEH, + pub axis_relative_direction: TEEH, } impl TestPointer { @@ -76,6 +77,12 @@ impl TestPointer { let _ev = AxisDiscrete::parse_full(parser)?; Ok(()) } + + fn handle_axis_relative_direction(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = AxisRelativeDirection::parse_full(parser)?; + self.axis_relative_direction.push(ev); + Ok(()) + } } impl Drop for TestPointer { @@ -96,6 +103,7 @@ test_object! { AXIS_SOURCE => handle_axis_source, AXIS_STOP => handle_axis_stop, AXIS_DISCRETE => handle_axis_discrete, + AXIS_RELATIVE_DIRECTION => handle_axis_relative_direction, } impl TestObject for TestPointer {} diff --git a/src/it/test_ifs/test_seat.rs b/src/it/test_ifs/test_seat.rs index 9e65b876..9d40b2d7 100644 --- a/src/it/test_ifs/test_seat.rs +++ b/src/it/test_ifs/test_seat.rs @@ -66,6 +66,7 @@ impl TestSeat { leave: Rc::new(Default::default()), enter: Rc::new(Default::default()), motion: Rc::new(Default::default()), + axis_relative_direction: Rc::new(Default::default()), }); self.tran.add_obj(pointer.clone())?; self.tran.sync().await; diff --git a/src/it/test_utils.rs b/src/it/test_utils.rs index e3a3396b..74db15cd 100644 --- a/src/it/test_utils.rs +++ b/src/it/test_utils.rs @@ -1,3 +1,7 @@ +pub mod test_container_node_ext; pub mod test_expected_event; pub mod test_object_ext; +pub mod test_ouput_node_ext; +pub mod test_toplevel_node_ext; pub mod test_window; +pub mod test_workspace_node_ext; diff --git a/src/it/test_utils/test_container_node_ext.rs b/src/it/test_utils/test_container_node_ext.rs new file mode 100644 index 00000000..8c8f8150 --- /dev/null +++ b/src/it/test_utils/test_container_node_ext.rs @@ -0,0 +1,20 @@ +use { + crate::{ + it::test_error::TestResult, + tree::{ContainerNode, ToplevelNode}, + }, + std::rc::Rc, +}; + +pub trait TestContainerExt { + fn first_toplevel(&self) -> TestResult>; +} + +impl TestContainerExt for ContainerNode { + fn first_toplevel(&self) -> TestResult> { + match self.children.first() { + None => bail!("container does not have children"), + Some(c) => Ok(c.node.clone()), + } + } +} diff --git a/src/it/test_utils/test_ouput_node_ext.rs b/src/it/test_utils/test_ouput_node_ext.rs new file mode 100644 index 00000000..472b4f98 --- /dev/null +++ b/src/it/test_utils/test_ouput_node_ext.rs @@ -0,0 +1,20 @@ +use { + crate::{ + it::test_error::TestResult, + tree::{OutputNode, WorkspaceNode}, + }, + std::rc::Rc, +}; + +pub trait TestOutputNodeExt { + fn workspace(&self) -> TestResult>; +} + +impl TestOutputNodeExt for OutputNode { + fn workspace(&self) -> TestResult> { + match self.workspace.get() { + None => bail!("Output node does not have a container"), + Some(w) => Ok(w), + } + } +} diff --git a/src/it/test_utils/test_toplevel_node_ext.rs b/src/it/test_utils/test_toplevel_node_ext.rs new file mode 100644 index 00000000..ed299f75 --- /dev/null +++ b/src/it/test_utils/test_toplevel_node_ext.rs @@ -0,0 +1,12 @@ +use crate::tree::ToplevelNode; + +pub trait TestToplevelNodeExt { + fn center(&self) -> (i32, i32); +} + +impl TestToplevelNodeExt for dyn ToplevelNode { + fn center(&self) -> (i32, i32) { + let rect = self.node_absolute_position(); + ((rect.x1() + rect.x2()) / 2, (rect.y1() + rect.y2()) / 2) + } +} diff --git a/src/it/test_utils/test_workspace_node_ext.rs b/src/it/test_utils/test_workspace_node_ext.rs new file mode 100644 index 00000000..48f2200c --- /dev/null +++ b/src/it/test_utils/test_workspace_node_ext.rs @@ -0,0 +1,20 @@ +use { + crate::{ + it::test_error::TestResult, + tree::{ContainerNode, WorkspaceNode}, + }, + std::rc::Rc, +}; + +pub trait TestWorkspaceNodeExt { + fn container(&self) -> TestResult>; +} + +impl TestWorkspaceNodeExt for WorkspaceNode { + fn container(&self) -> TestResult> { + match self.container.get() { + None => bail!("workspace does not have a container"), + Some(c) => Ok(c), + } + } +} diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 81b88db0..ee5751d9 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -1,6 +1,7 @@ use { crate::{ client::{ClientId, RequestParser}, + fixed::Fixed, ifs::wl_seat::WlSeatGlobal, it::{ test_backend::{TestBackend, TestBackendKb, TestBackendMouse, TestConnector}, @@ -146,3 +147,12 @@ pub struct DefaultSetup { pub mouse: Rc, pub seat: Rc, } + +impl DefaultSetup { + pub fn move_to(&self, x: i32, y: i32) { + let (ox, oy) = self.seat.position(); + let (nx, ny) = (Fixed::from_int(x), Fixed::from_int(y)); + let (dx, dy) = (nx - ox, ny - oy); + self.mouse.rel(dx.to_f64(), dy.to_f64()) + } +} diff --git a/src/it/tests.rs b/src/it/tests.rs index a67da9c3..f47ef0dc 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -44,6 +44,7 @@ mod t0015_scroll_partial; mod t0016_scroll_ws; mod t0017_remove_unused_ws; mod t0018_click_to_active_ws; +mod t0019_natural_scrolling; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -80,5 +81,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0016_scroll_ws, t0017_remove_unused_ws, t0018_click_to_active_ws, + t0019_natural_scrolling, } } diff --git a/src/it/tests/t0019_natural_scrolling.rs b/src/it/tests/t0019_natural_scrolling.rs new file mode 100644 index 00000000..cdfa66d5 --- /dev/null +++ b/src/it/tests/t0019_natural_scrolling.rs @@ -0,0 +1,46 @@ +use { + crate::{ + ifs::wl_seat::wl_pointer::{IDENTICAL, INVERTED}, + it::{ + test_error::TestResult, + test_utils::{ + test_container_node_ext::TestContainerExt, test_ouput_node_ext::TestOutputNodeExt, + test_toplevel_node_ext::TestToplevelNodeExt, + test_workspace_node_ext::TestWorkspaceNodeExt, + }, + testrun::TestRun, + }, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + let win1 = client.create_window().await?; + win1.map2().await?; + + let (x, y) = ds + .output + .workspace()? + .container()? + .first_toplevel()? + .center(); + ds.move_to(x, y); + + let seat = client.get_default_seat().await?; + let ard = seat.pointer.axis_relative_direction.expect()?; + + ds.mouse.scroll_px2(1, false); + client.sync().await; + tassert_eq!(ard.next()?.direction, IDENTICAL); + + ds.mouse.scroll_px2(1, true); + client.sync().await; + tassert_eq!(ard.next()?.direction, INVERTED); + + Ok(()) +} From 9cddeb964d52cb28a0b42085b21b0e7d65e9f7d5 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 13:22:21 +0200 Subject: [PATCH 04/24] surface: process offset request without a buffer change --- src/ifs/wl_surface.rs | 16 ++--- src/it/test_ifs/test_pointer.rs | 22 ++++++- src/it/test_ifs/test_registry.rs | 2 +- src/it/test_ifs/test_surface.rs | 9 +++ src/it/testrun.rs | 2 + src/it/tests.rs | 3 + src/it/tests/t0020_surface_offset.rs | 55 ++++++++++++++++++ .../t0020_surface_offset/screenshot_1.qoi | Bin 0 -> 8141 bytes .../t0020_surface_offset/screenshot_2.qoi | Bin 0 -> 8141 bytes 9 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 src/it/tests/t0020_surface_offset.rs create mode 100644 src/it/tests/t0020_surface_offset/screenshot_1.qoi create mode 100644 src/it/tests/t0020_surface_offset/screenshot_2.qoi diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index e90f6004..decbdf1b 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -910,14 +910,6 @@ impl WlSurface { release, }; self.buffer.set(Some(Rc::new(surface_buffer))); - self.buf_x.fetch_add(dx); - self.buf_y.fetch_add(dy); - if (dx, dy) != (0, 0) { - self.need_extents_update.set(true); - for (_, cursor) in &self.cursors { - cursor.dec_hotspot(dx, dy); - } - } } else { self.buf_x.set(0); self.buf_y.set(0); @@ -926,6 +918,14 @@ impl WlSurface { } } } + if self.buffer.is_some() && (dx, dy) != (0, 0) { + self.buf_x.fetch_add(dx); + self.buf_y.fetch_add(dy); + self.need_extents_update.set(true); + for (_, cursor) in &self.cursors { + cursor.dec_hotspot(dx, dy); + } + } let transform_changed = viewport_changed || scale_changed || buffer_transform_changed; if buffer_changed || transform_changed { let mut buffer_points = self.buffer_points.borrow_mut(); diff --git a/src/it/test_ifs/test_pointer.rs b/src/it/test_ifs/test_pointer.rs index f858c743..e76f9ffa 100644 --- a/src/it/test_ifs/test_pointer.rs +++ b/src/it/test_ifs/test_pointer.rs @@ -2,8 +2,9 @@ use { crate::{ ifs::wl_seat::wl_pointer::WlPointer, it::{ - test_error::TestResult, test_object::TestObject, test_transport::TestTransport, - test_utils::test_expected_event::TEEH, testrun::ParseFull, + test_error::TestResult, test_ifs::test_surface::TestSurface, test_object::TestObject, + test_transport::TestTransport, test_utils::test_expected_event::TEEH, + testrun::ParseFull, }, utils::{buffd::MsgParser, clonecell::CloneCell}, wire::{wl_pointer::*, WlPointerId}, @@ -30,6 +31,23 @@ impl TestPointer { Ok(()) } + pub fn set_cursor( + &self, + serial: u32, + surface: &TestSurface, + hotspot_x: i32, + hotspot_y: i32, + ) -> TestResult { + self.tran.send(SetCursor { + self_id: self.id, + serial, + surface: surface.id, + hotspot_x, + hotspot_y, + })?; + Ok(()) + } + fn handle_enter(&self, parser: MsgParser<'_, '_>) -> TestResult { let ev = Enter::parse_full(parser)?; self.enter.push(ev); diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 3154dee6..5f31a73a 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -124,7 +124,7 @@ impl TestRegistry { id: self.tran.id(), tran: self.tran.clone(), }); - self.bind(&jc, singletons.wl_compositor, 4)?; + self.bind(&jc, singletons.wl_compositor, 6)?; self.compositor.set(Some(jc.clone())); Ok(jc) } diff --git a/src/it/test_ifs/test_surface.rs b/src/it/test_ifs/test_surface.rs index 837f7894..fa05c11e 100644 --- a/src/it/test_ifs/test_surface.rs +++ b/src/it/test_ifs/test_surface.rs @@ -36,6 +36,15 @@ impl TestSurface { Ok(()) } + pub fn offset(&self, dx: i32, dy: i32) -> Result<(), TestError> { + self.tran.send(Offset { + self_id: self.id, + x: dx, + y: dy, + })?; + Ok(()) + } + pub fn commit(&self) -> Result<(), TestError> { self.tran.send(Commit { self_id: self.id })?; Ok(()) diff --git a/src/it/testrun.rs b/src/it/testrun.rs index ee5751d9..157258cc 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -113,7 +113,9 @@ impl TestRun { .set_input_device_seat(self.backend.default_kb.common.id, seat.id())?; self.cfg .set_input_device_seat(self.backend.default_mouse.common.id, seat.id())?; + self.backend.default_mouse.click(1); self.state.eng.yield_now().await; + self.cfg.show_workspace(seat.id(), "")?; Ok(DefaultSetup { connector: self.backend.default_connector.clone(), output, diff --git a/src/it/tests.rs b/src/it/tests.rs index f47ef0dc..a6931da7 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -45,6 +45,7 @@ mod t0016_scroll_ws; mod t0017_remove_unused_ws; mod t0018_click_to_active_ws; mod t0019_natural_scrolling; +mod t0020_surface_offset; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -62,6 +63,7 @@ pub fn tests() -> Vec<&'static dyn TestCase> { ] } } + tests! { t0001_shm_formats, t0002_window, @@ -82,5 +84,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0017_remove_unused_ws, t0018_click_to_active_ws, t0019_natural_scrolling, + t0020_surface_offset, } } diff --git a/src/it/tests/t0020_surface_offset.rs b/src/it/tests/t0020_surface_offset.rs new file mode 100644 index 00000000..351cdfd6 --- /dev/null +++ b/src/it/tests/t0020_surface_offset.rs @@ -0,0 +1,55 @@ +use { + crate::{ + it::{ + test_error::TestError, + test_utils::{ + test_container_node_ext::TestContainerExt, test_ouput_node_ext::TestOutputNodeExt, + test_toplevel_node_ext::TestToplevelNodeExt, + test_workspace_node_ext::TestWorkspaceNodeExt, + }, + testrun::TestRun, + }, + theme::Color, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> Result<(), TestError> { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + let seat = client.get_default_seat().await?; + let enter = seat.pointer.enter.expect()?; + let win1 = client.create_window().await?; + win1.map2().await?; + + let buffer = client.spbm.create_buffer(Color::from_rgb(255, 0, 0))?; + let surface = client.comp.create_surface().await?; + let vp = client.viewporter.get_viewport(&surface)?; + vp.set_destination(100, 100)?; + surface.attach(buffer.id)?; + surface.commit()?; + + let (x, y) = ds + .output + .workspace()? + .container()? + .first_toplevel()? + .center(); + ds.move_to(x, y); + + client.sync().await; + let enter = enter.next()?; + seat.pointer.set_cursor(enter.serial, &surface, 0, 0)?; + + client.compare_screenshot("1").await?; + + surface.offset(-100, -100)?; + surface.commit()?; + + client.compare_screenshot("2").await?; + + Ok(()) +} diff --git a/src/it/tests/t0020_surface_offset/screenshot_1.qoi b/src/it/tests/t0020_surface_offset/screenshot_1.qoi new file mode 100644 index 0000000000000000000000000000000000000000..eef5f37a645f72caeef422b0da0ac30b6bbd7161 GIT binary patch literal 8141 zcmeI%F%Cd56oBDJ4`DT$HI5djI0Ao{zSmCu+SfE`Q&*)( z5Q+GCATG|@!HI}fZ3%}|rS^BZGsb9uHPqS1;wjM!S|2;FXNVC%009ILKmY**PJzC# zvwk&=2{|5jd2k-62kL=(xCGw!!8J~g00IagfB*srAbF?2qPc Date: Tue, 2 Apr 2024 13:33:56 +0200 Subject: [PATCH 05/24] it: add test for preferred buffer scale --- src/it/test_config.rs | 9 ++++++ src/it/test_ifs/test_compositor.rs | 1 + src/it/test_ifs/test_surface.rs | 10 ++++++- src/it/tests.rs | 2 ++ src/it/tests/t0021_preferred_buffer_scale.rs | 29 ++++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/it/tests/t0021_preferred_buffer_scale.rs diff --git a/src/it/test_config.rs b/src/it/test_config.rs index 1768786a..a5e7adf9 100644 --- a/src/it/test_config.rs +++ b/src/it/test_config.rs @@ -3,6 +3,7 @@ use { backend::InputDeviceId, ifs::wl_seat::SeatId, it::test_error::{TestError, TestResult}, + tree::OutputNode, utils::{copyhashmap::CopyHashMap, stack::Stack}, }, bincode::Options, @@ -15,6 +16,7 @@ use { }, input::{InputDevice, Seat}, keyboard::{Keymap, ModifiedKeySym}, + video::Connector, Axis, Direction, }, std::{cell::Cell, ops::Deref, ptr, rc::Rc}, @@ -251,6 +253,13 @@ impl TestConfig { } } } + + pub fn set_scale(&self, output: &OutputNode, scale: f64) -> TestResult { + self.send(ClientMessage::ConnectorSetScale { + connector: Connector(output.global.connector.connector.id().raw() as _), + scale, + }) + } } impl Drop for TestConfig { diff --git a/src/it/test_ifs/test_compositor.rs b/src/it/test_ifs/test_compositor.rs index 7bfc989a..5c6162c6 100644 --- a/src/it/test_ifs/test_compositor.rs +++ b/src/it/test_ifs/test_compositor.rs @@ -34,6 +34,7 @@ impl TestCompositor { tran: self.tran.clone(), server, destroyed: Cell::new(false), + preferred_buffer_scale: Rc::new(Default::default()), }); self.tran.add_obj(surface.clone())?; Ok(surface) diff --git a/src/it/test_ifs/test_surface.rs b/src/it/test_ifs/test_surface.rs index fa05c11e..72c27524 100644 --- a/src/it/test_ifs/test_surface.rs +++ b/src/it/test_ifs/test_surface.rs @@ -3,7 +3,7 @@ use { ifs::wl_surface::WlSurface, it::{ test_error::TestError, test_object::TestObject, test_transport::TestTransport, - testrun::ParseFull, + test_utils::test_expected_event::TEEH, testrun::ParseFull, }, utils::buffd::MsgParser, wire::{wl_surface::*, WlBufferId, WlSurfaceId}, @@ -16,6 +16,7 @@ pub struct TestSurface { pub tran: Rc, pub server: Rc, pub destroyed: Cell, + pub preferred_buffer_scale: TEEH, } impl TestSurface { @@ -59,6 +60,12 @@ impl TestSurface { let _ev = Leave::parse_full(parser)?; Ok(()) } + + fn handle_preferred_buffer_scale(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = PreferredBufferScale::parse_full(parser)?; + self.preferred_buffer_scale.push(ev.factor); + Ok(()) + } } impl Drop for TestSurface { @@ -72,6 +79,7 @@ test_object! { ENTER => handle_enter, LEAVE => handle_leave, + PREFERRED_BUFFER_SCALE => handle_preferred_buffer_scale, } impl TestObject for TestSurface {} diff --git a/src/it/tests.rs b/src/it/tests.rs index a6931da7..c1e85851 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -46,6 +46,7 @@ mod t0017_remove_unused_ws; mod t0018_click_to_active_ws; mod t0019_natural_scrolling; mod t0020_surface_offset; +mod t0021_preferred_buffer_scale; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -85,5 +86,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0018_click_to_active_ws, t0019_natural_scrolling, t0020_surface_offset, + t0021_preferred_buffer_scale, } } diff --git a/src/it/tests/t0021_preferred_buffer_scale.rs b/src/it/tests/t0021_preferred_buffer_scale.rs new file mode 100644 index 00000000..91304116 --- /dev/null +++ b/src/it/tests/t0021_preferred_buffer_scale.rs @@ -0,0 +1,29 @@ +use { + crate::it::{test_error::TestResult, testrun::TestRun}, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + + let win1 = client.create_window().await?; + win1.map2().await?; + + let scale = win1.surface.preferred_buffer_scale.expect()?; + + run.cfg.set_scale(&ds.output, 2.0)?; + + client.sync().await; + tassert_eq!(scale.next()?, 2); + + run.cfg.set_scale(&ds.output, 3.0)?; + + client.sync().await; + tassert_eq!(scale.next()?, 3); + + Ok(()) +} From 3f4386609e82432a6d64907dc80a1717ffc385e9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 13:45:49 +0200 Subject: [PATCH 06/24] leaks: fix leak detection code --- src/leaks.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/leaks.rs b/src/leaks.rs index 10ae0af5..343e023c 100644 --- a/src/leaks.rs +++ b/src/leaks.rs @@ -65,15 +65,13 @@ mod leaks { } pub fn init() { - unsafe { - if INITIALIZED { - return; - } - MAP.set(Box::into_raw(Box::new(AHashMap::new()))); - ALLOCATIONS.set(Box::into_raw(Box::new(AHashMap::new()))); - IN_ALLOCATOR.set(0); - INITIALIZED.set(true); + if INITIALIZED.get() { + return; } + MAP.set(Box::into_raw(Box::new(AHashMap::new()))); + ALLOCATIONS.set(Box::into_raw(Box::new(AHashMap::new()))); + IN_ALLOCATOR.set(0); + INITIALIZED.set(true); } fn log_containers( @@ -153,7 +151,7 @@ mod leaks { unsafe { IN_ALLOCATOR.set(IN_ALLOCATOR.get() + 1); let mut map: AHashMap> = AHashMap::new(); - for (id, obj) in MAP.deref_mut().drain() { + for (id, obj) in MAP.get().deref_mut().drain() { map.entry(obj.client).or_default().push((id, obj)); } if map.is_empty() { @@ -166,7 +164,8 @@ mod leaks { objs.sort_by_key(|o| o.0); log::info!("Client {} leaked {} objects", objs[0].1.client, objs.len()); for (_, obj) in objs { - let time = chrono::NaiveDateTime::from_timestamp(obj.time.0, obj.time.1); + let time = + chrono::NaiveDateTime::from_timestamp_opt(obj.time.0, obj.time.1).unwrap(); log::info!(" [{}] {}", time.format("%H:%M:%S%.3f"), obj.ty,); match find_allocation_containing(obj.addr) { Some(mut alloc) => { @@ -209,7 +208,7 @@ mod leaks { impl Default for Tracker { fn default() -> Self { Self { - id: unsafe { + id: { let id = ID.get(); ID.set(id + 1); id @@ -228,7 +227,7 @@ mod leaks { }; uapi::clock_gettime(c::CLOCK_REALTIME, &mut time).unwrap(); IN_ALLOCATOR.set(IN_ALLOCATOR.get() + 1); - MAP.deref_mut().insert( + MAP.get().deref_mut().insert( self.id, Tracked { addr: self as *const _ as usize, @@ -245,7 +244,7 @@ mod leaks { impl Drop for Tracker { fn drop(&mut self) { unsafe { - MAP.deref_mut().remove(&self.id); + MAP.get().deref_mut().remove(&self.id); } } } @@ -273,7 +272,7 @@ mod leaks { let res = c::calloc(layout.size(), 1) as *mut u8; if IN_ALLOCATOR.get() == 0 { IN_ALLOCATOR.set(1); - ALLOCATIONS.deref_mut().insert( + ALLOCATIONS.get().deref_mut().insert( res, Allocation { addr: res, @@ -282,14 +281,14 @@ mod leaks { }, ); // log::info!("allocated [0x{:x}, 0x{:x})", res as usize, res as usize + layout.size()); - IN_ALLOCATOR = 0; + IN_ALLOCATOR.set(0); } res } unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - if INITIALIZED { - ALLOCATIONS.deref_mut().remove(&ptr); + if INITIALIZED.get() { + ALLOCATIONS.get().deref_mut().remove(&ptr); } // c::memset(ptr as _, 0, layout.size()); c::free(ptr as _); @@ -300,7 +299,7 @@ mod leaks { unsafe { IN_ALLOCATOR.set(IN_ALLOCATOR.get() + 1); let mut res = vec![]; - for allocation in ALLOCATIONS.deref().values() { + for allocation in ALLOCATIONS.get().deref().values() { let num = allocation.len / mem::size_of::(); let elements = std::slice::from_raw_parts(allocation.addr as *const *mut u8, num); for (offset, pos) in elements.iter().enumerate() { @@ -319,7 +318,7 @@ mod leaks { unsafe { IN_ALLOCATOR.set(IN_ALLOCATOR.get() + 1); let mut res = None; - for allocation in ALLOCATIONS.deref().values() { + for allocation in ALLOCATIONS.get().deref().values() { let aaddr = allocation.addr as usize; if aaddr <= addr && addr < aaddr + allocation.len { res = Some(allocation.clone()); From 91022cd1c8965ca808a8d8bc2e276862c32057e9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 14:30:24 +0200 Subject: [PATCH 07/24] it: test suspended state --- .../wl_surface/xdg_surface/xdg_toplevel.rs | 2 +- src/it.rs | 10 ++-- src/it/test_client.rs | 1 + src/it/test_ifs/test_registry.rs | 2 +- src/it/test_ifs/test_xdg_surface.rs | 19 ++++---- src/it/test_ifs/test_xdg_toplevel.rs | 36 +++++++++----- src/it/test_utils/test_ouput_node_ext.rs | 15 +++++- src/it/test_utils/test_window.rs | 2 +- src/it/tests.rs | 7 +++ src/it/tests/t0002_window.rs | 8 ++-- src/it/tests/t0014_container_scroll_focus.rs | 2 +- src/it/tests/t0022_toplevel_suspended.rs | 48 +++++++++++++++++++ 12 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 src/it/tests/t0022_toplevel_suspended.rs diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index f77943ba..f8909b09 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -64,7 +64,7 @@ const STATE_TILED_LEFT: u32 = 5; const STATE_TILED_RIGHT: u32 = 6; const STATE_TILED_TOP: u32 = 7; const STATE_TILED_BOTTOM: u32 = 8; -const STATE_SUSPENDED: u32 = 9; +pub const STATE_SUSPENDED: u32 = 9; #[allow(dead_code)] const CAP_WINDOW_MENU: u32 = 1; diff --git a/src/it.rs b/src/it.rs index f77d8285..d1b93262 100644 --- a/src/it.rs +++ b/src/it.rs @@ -42,6 +42,10 @@ mod tests; const SINGLE_THREAD: bool = false; pub fn run_tests() { + run_tests_(tests::tests()); +} + +fn run_tests_(tests: Vec<&'static dyn TestCase>) { leaks::init(); test_logger::install(); test_logger::set_level(Level::Trace); @@ -54,13 +58,13 @@ pub fn run_tests() { failed: Default::default(), }); if SINGLE_THREAD { - for test in tests::tests() { + for test in tests { with_test_config(|cfg| { run_test(&it_run, test, cfg); }) } } else { - let queue = Arc::new(Mutex::new(VecDeque::from_iter(tests::tests()))); + let queue = Arc::new(Mutex::new(VecDeque::from_iter(tests))); let mut threads = vec![]; let num_cpus = match num_cpus() { Ok(n) => n, @@ -139,7 +143,7 @@ fn run_test(it_run: &ItRun, test: &'static dyn TestCase, cfg: Rc) { Box::new(async move { let future: Pin<_> = test.run(testrun.clone()).into(); let future = state.eng.spawn2(Phase::Present, future); - let timeout = state.wheel.timeout(5000); + let timeout = state.wheel.timeout(500000); match future::select(future, timeout).await { Either::Left((Ok(..), _)) => {} Either::Left((Err(e), _)) => { diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 3f8b5e46..323f6db5 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -81,6 +81,7 @@ impl TestClient { } pub async fn sync(&self) { + self.run.state.eng.yield_now().await; self.run.sync().await; self.tran.sync().await; } diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 5f31a73a..75b0d813 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -193,7 +193,7 @@ impl TestRegistry { tran: self.tran.clone(), destroyed: Cell::new(false), }); - self.bind(&jc, singletons.xdg_wm_base, 3)?; + self.bind(&jc, singletons.xdg_wm_base, 6)?; self.xdg.set(Some(jc.clone())); Ok(jc) } diff --git a/src/it/test_ifs/test_xdg_surface.rs b/src/it/test_ifs/test_xdg_surface.rs index 17c2d6e7..6ba3bdcb 100644 --- a/src/it/test_ifs/test_xdg_surface.rs +++ b/src/it/test_ifs/test_xdg_surface.rs @@ -2,8 +2,11 @@ use { crate::{ ifs::wl_surface::xdg_surface::XdgSurface, it::{ - test_error::TestError, test_ifs::test_xdg_toplevel::TestXdgToplevel, - test_object::TestObject, test_transport::TestTransport, testrun::ParseFull, + test_error::TestError, + test_ifs::test_xdg_toplevel::{TestXdgToplevel, TestXdgToplevelCore}, + test_object::TestObject, + test_transport::TestTransport, + testrun::ParseFull, }, utils::buffd::MsgParser, wire::{xdg_surface::*, XdgSurfaceId}, @@ -33,20 +36,20 @@ impl TestXdgSurface { self_id: self.id, id, })?; - self.tran.sync().await; - let client = self.tran.get_client()?; - let server = client.lookup(id)?; - let tl = Rc::new(TestXdgToplevel { + let core = Rc::new(TestXdgToplevelCore { id, tran: self.tran.clone(), destroyed: Cell::new(false), - server, width: Cell::new(0), height: Cell::new(0), states: Default::default(), close_requested: Cell::new(false), }); - self.tran.add_obj(tl.clone())?; + self.tran.add_obj(core.clone())?; + self.tran.sync().await; + let client = self.tran.get_client()?; + let server = client.lookup(id)?; + let tl = Rc::new(TestXdgToplevel { core, server }); Ok(tl) } diff --git a/src/it/test_ifs/test_xdg_toplevel.rs b/src/it/test_ifs/test_xdg_toplevel.rs index 5ea32b13..a921d4e7 100644 --- a/src/it/test_ifs/test_xdg_toplevel.rs +++ b/src/it/test_ifs/test_xdg_toplevel.rs @@ -18,11 +18,10 @@ use { }, }; -pub struct TestXdgToplevel { +pub struct TestXdgToplevelCore { pub id: XdgToplevelId, pub tran: Rc, pub destroyed: Cell, - pub server: Rc, pub width: Cell, pub height: Cell, @@ -31,14 +30,12 @@ pub struct TestXdgToplevel { pub close_requested: Cell, } -impl TestXdgToplevel { - pub fn destroy(&self) -> Result<(), TestError> { - if !self.destroyed.replace(true) { - self.tran.send(Destroy { self_id: self.id })?; - } - Ok(()) - } +pub struct TestXdgToplevel { + pub core: Rc, + pub server: Rc, +} +impl TestXdgToplevel { pub fn container_parent(&self) -> TestResult> { let parent = match self.server.tl_data().parent.get() { Some(p) => p, @@ -49,6 +46,15 @@ impl TestXdgToplevel { _ => bail!("toplevel parent is not a container"), } } +} + +impl TestXdgToplevelCore { + pub fn destroy(&self) -> Result<(), TestError> { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } fn handle_configure(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { let ev = Configure::parse_full(parser)?; @@ -68,20 +74,26 @@ impl TestXdgToplevel { let _ev = ConfigureBounds::parse_full(parser)?; Ok(()) } + + fn handle_wm_capabilities(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let _ev = WmCapabilities::parse_full(parser)?; + Ok(()) + } } -impl Drop for TestXdgToplevel { +impl Drop for TestXdgToplevelCore { fn drop(&mut self) { let _ = self.destroy(); } } test_object! { - TestXdgToplevel, XdgToplevel; + TestXdgToplevelCore, XdgToplevel; CONFIGURE => handle_configure, CLOSE => handle_close, CONFIGURE_BOUNDS => handle_configure_bounds, + WM_CAPABILITIES => handle_wm_capabilities, } -impl TestObject for TestXdgToplevel {} +impl TestObject for TestXdgToplevelCore {} diff --git a/src/it/test_utils/test_ouput_node_ext.rs b/src/it/test_utils/test_ouput_node_ext.rs index 472b4f98..f4e22847 100644 --- a/src/it/test_utils/test_ouput_node_ext.rs +++ b/src/it/test_utils/test_ouput_node_ext.rs @@ -1,13 +1,20 @@ use { crate::{ - it::test_error::TestResult, - tree::{OutputNode, WorkspaceNode}, + it::{ + test_error::TestResult, + test_utils::{ + test_container_node_ext::TestContainerExt, + test_workspace_node_ext::TestWorkspaceNodeExt, + }, + }, + tree::{OutputNode, ToplevelNode, WorkspaceNode}, }, std::rc::Rc, }; pub trait TestOutputNodeExt { fn workspace(&self) -> TestResult>; + fn first_toplevel(&self) -> TestResult>; } impl TestOutputNodeExt for OutputNode { @@ -17,4 +24,8 @@ impl TestOutputNodeExt for OutputNode { Some(w) => Ok(w), } } + + fn first_toplevel(&self) -> TestResult> { + self.workspace()?.container()?.first_toplevel() + } } diff --git a/src/it/test_utils/test_window.rs b/src/it/test_utils/test_window.rs index 20abd167..2363f78a 100644 --- a/src/it/test_utils/test_window.rs +++ b/src/it/test_utils/test_window.rs @@ -28,7 +28,7 @@ impl TestWindow { self.surface.attach(buffer.id)?; self.viewport.set_source(0, 0, 1, 1)?; self.viewport - .set_destination(self.tl.width.get(), self.tl.height.get())?; + .set_destination(self.tl.core.width.get(), self.tl.core.height.get())?; self.xdg.ack_configure(self.xdg.last_serial.get())?; self.surface.commit()?; self.surface.tran.sync().await; diff --git a/src/it/tests.rs b/src/it/tests.rs index c1e85851..375fe9f5 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -23,6 +23,11 @@ macro_rules! testcase { Box::new(test(testrun)) } } + + #[test] + fn single() { + crate::it::run_tests_(vec![&Test]) + } }; } @@ -47,6 +52,7 @@ mod t0018_click_to_active_ws; mod t0019_natural_scrolling; mod t0020_surface_offset; mod t0021_preferred_buffer_scale; +mod t0022_toplevel_suspended; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -87,5 +93,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0019_natural_scrolling, t0020_surface_offset, t0021_preferred_buffer_scale, + t0022_toplevel_suspended, } } diff --git a/src/it/tests/t0002_window.rs b/src/it/tests/t0002_window.rs index 00f18237..f0b1c343 100644 --- a/src/it/tests/t0002_window.rs +++ b/src/it/tests/t0002_window.rs @@ -18,9 +18,9 @@ async fn test(run: Rc) -> Result<(), TestError> { let window = client.create_window().await?; window.map().await?; - tassert_eq!(window.tl.width.get(), 800); + tassert_eq!(window.tl.core.width.get(), 800); tassert_eq!( - window.tl.height.get(), + window.tl.core.height.get(), 600 - 2 * (run.state.theme.sizes.title_height.get() + 1) ); @@ -29,8 +29,8 @@ async fn test(run: Rc) -> Result<(), TestError> { Rect::new_sized( 0, 2 * (run.state.theme.sizes.title_height.get() + 1), - window.tl.width.get(), - window.tl.height.get(), + window.tl.core.width.get(), + window.tl.core.height.get(), ) .unwrap() ); diff --git a/src/it/tests/t0014_container_scroll_focus.rs b/src/it/tests/t0014_container_scroll_focus.rs index 4c9ffcb2..0186cbaf 100644 --- a/src/it/tests/t0014_container_scroll_focus.rs +++ b/src/it/tests/t0014_container_scroll_focus.rs @@ -35,7 +35,7 @@ async fn test(run: Rc) -> TestResult { // | w_tiled | [ w_mono1 | w_mono2 ] | with w_mono2 visible and active client.sync().await; - tassert_eq!(w_tiled.tl.width.get(), w_mono2.tl.width.get()); + tassert_eq!(w_tiled.tl.core.width.get(), w_mono2.tl.core.width.get()); let enters = dss.kb.enter.expect()?; diff --git a/src/it/tests/t0022_toplevel_suspended.rs b/src/it/tests/t0022_toplevel_suspended.rs new file mode 100644 index 00000000..328524da --- /dev/null +++ b/src/it/tests/t0022_toplevel_suspended.rs @@ -0,0 +1,48 @@ +use { + crate::{ + ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, + it::{ + test_error::TestResult, + test_utils::{ + test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, + }, + testrun::TestRun, + }, + }, + isnt::std_1::collections::IsntHashSet2Ext, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + + let win1 = client.create_window().await?; + win1.set_color(255, 0, 0, 255); + win1.map2().await?; + + let win2 = client.create_window().await?; + win2.set_color(0, 255, 0, 255); + win2.map2().await?; + + let (x, y) = ds.output.first_toplevel()?.center(); + ds.move_to(x, y); + + tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + + client.sync().await; + run.cfg.set_mono(ds.seat.id(), true)?; + + client.sync().await; + tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED)); + + run.cfg.set_mono(ds.seat.id(), false)?; + + client.sync().await; + tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + + Ok(()) +} From 6fe6b1b4914e5d2f2c2e4c83ddeb7d34622b028d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 15:03:24 +0200 Subject: [PATCH 08/24] it: test xdg-activation --- src/ifs/jay_compositor.rs | 20 +++++- src/it/test_client.rs | 19 +++-- src/it/test_ifs.rs | 2 + src/it/test_ifs/test_jay_compositor.rs | 5 +- src/it/test_ifs/test_registry.rs | 19 ++++- src/it/test_ifs/test_xdg_activation.rs | 66 ++++++++++++++++++ src/it/test_ifs/test_xdg_activation_token.rs | 56 +++++++++++++++ src/it/test_transport.rs | 8 ++- src/it/testrun.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0007_subsurface.rs | 8 +-- src/it/tests/t0020_surface_offset.rs | 4 +- src/it/tests/t0023_xdg_activation.rs | 40 +++++++++++ .../t0023_xdg_activation/screenshot_1.qoi | Bin 0 -> 7925 bytes src/screenshoter.rs | 7 +- wire/jay_compositor.txt | 5 ++ 16 files changed, 239 insertions(+), 23 deletions(-) create mode 100644 src/it/test_ifs/test_xdg_activation.rs create mode 100644 src/it/test_ifs/test_xdg_activation_token.rs create mode 100644 src/it/tests/t0023_xdg_activation.rs create mode 100644 src/it/tests/t0023_xdg_activation/screenshot_1.qoi diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 9acf0035..66c7b778 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -18,7 +18,7 @@ use { clonecell::CloneCell, errorfmt::ErrorFmt, }, - wire::{jay_compositor::*, JayCompositorId}, + wire::{jay_compositor::*, JayCompositorId, JayScreenshotId}, }, bstr::ByteSlice, log::Level, @@ -125,14 +125,27 @@ impl JayCompositor { fn take_screenshot(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> { let req: TakeScreenshot = self.client.parse(self, parser)?; + self.take_screenshot_impl(req.id, false) + } + + fn take_screenshot2(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> { + let req: TakeScreenshot2 = self.client.parse(self, parser)?; + self.take_screenshot_impl(req.id, req.include_cursor != 0) + } + + fn take_screenshot_impl( + &self, + id: JayScreenshotId, + include_cursor: bool, + ) -> Result<(), JayCompositorError> { let ss = Rc::new(JayScreenshot { - id: req.id, + id, client: self.client.clone(), tracker: Default::default(), }); track!(self.client, ss); self.client.add_client_obj(&ss)?; - match take_screenshot(&self.client.state) { + match take_screenshot(&self.client.state, include_cursor) { Ok(s) => { let dmabuf = s.bo.dmabuf(); let plane = &dmabuf.planes[0]; @@ -347,6 +360,7 @@ object_base! { CREATE_SCREENCAST => create_screencast, GET_RANDR => get_randr, GET_INPUT => get_input, + TAKE_SCREENSHOT2 => take_screenshot2, } impl Object for JayCompositor {} diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 323f6db5..41bcfbb3 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -11,7 +11,7 @@ use { test_registry::TestRegistry, test_seat::TestSeat, test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, - test_xdg_base::TestXdgWmBase, + test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, test_transport::TestTransport, test_utils::test_window::TestWindow, @@ -34,6 +34,7 @@ pub struct TestClient { pub spbm: Rc, pub viewporter: Rc, pub xdg: Rc, + pub activation: Rc, } pub struct DefaultSeat { @@ -86,22 +87,26 @@ impl TestClient { self.tran.sync().await; } - pub async fn take_screenshot(&self) -> Result, TestError> { - let dmabuf = self.jc.take_screenshot().await?; + pub async fn take_screenshot(&self, include_cursor: bool) -> Result, TestError> { + let dmabuf = self.jc.take_screenshot(include_cursor).await?; let qoi = buf_to_qoi(&self.server.state.dma_buf_ids, &dmabuf); Ok(qoi) } #[allow(dead_code)] - pub async fn save_screenshot(&self, name: &str) -> Result<(), TestError> { - let qoi = self.take_screenshot().await?; + pub async fn save_screenshot(&self, name: &str, include_cursor: bool) -> Result<(), TestError> { + let qoi = self.take_screenshot(include_cursor).await?; let path = format!("{}/screenshot_{}.qoi", self.run.out_dir, name); std::fs::write(path, qoi)?; Ok(()) } - pub async fn compare_screenshot(&self, name: &str) -> Result<(), TestError> { - let actual = self.take_screenshot().await?; + pub async fn compare_screenshot( + &self, + name: &str, + include_cursor: bool, + ) -> Result<(), TestError> { + let actual = self.take_screenshot(include_cursor).await?; let expected_path = format!("{}/screenshot_{}.qoi", self.run.in_dir, name); let expected = std::fs::read(expected_path)?; if actual != expected { diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index b8440987..43c477ee 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -18,6 +18,8 @@ pub mod test_subsurface; pub mod test_surface; pub mod test_viewport; pub mod test_viewporter; +pub mod test_xdg_activation; +pub mod test_xdg_activation_token; pub mod test_xdg_base; pub mod test_xdg_surface; pub mod test_xdg_toplevel; diff --git a/src/it/test_ifs/test_jay_compositor.rs b/src/it/test_ifs/test_jay_compositor.rs index c17d65f7..70e806d1 100644 --- a/src/it/test_ifs/test_jay_compositor.rs +++ b/src/it/test_ifs/test_jay_compositor.rs @@ -33,14 +33,15 @@ impl TestJayCompositor { } } - pub async fn take_screenshot(&self) -> Result { + pub async fn take_screenshot(&self, include_cursor: bool) -> Result { let js = Rc::new(TestJayScreenshot { id: self.tran.id(), result: Cell::new(None), }); - self.tran.send(TakeScreenshot { + self.tran.send(TakeScreenshot2 { self_id: self.id, id: js.id, + include_cursor: include_cursor as _, })?; self.tran.add_obj(js.clone())?; self.tran.sync().await; diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 75b0d813..0e4f384d 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -8,7 +8,7 @@ use { test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor, test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, - test_xdg_base::TestXdgWmBase, + test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, test_object::TestObject, test_transport::TestTransport, @@ -34,6 +34,7 @@ pub struct TestRegistrySingletons { pub xdg_wm_base: u32, pub wp_single_pixel_buffer_manager_v1: u32, pub wp_viewporter: u32, + pub xdg_activation_v1: u32, } pub struct TestRegistry { @@ -48,6 +49,7 @@ pub struct TestRegistry { pub spbm: CloneCell>>, pub viewporter: CloneCell>>, pub xdg: CloneCell>>, + pub activation: CloneCell>>, pub seats: CopyHashMap>, } @@ -97,6 +99,7 @@ impl TestRegistry { xdg_wm_base, wp_single_pixel_buffer_manager_v1, wp_viewporter, + xdg_activation_v1, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -184,6 +187,20 @@ impl TestRegistry { Ok(jc) } + pub async fn get_activation(&self) -> Result, TestError> { + singleton!(self.activation); + let singletons = self.get_singletons().await?; + singleton!(self.activation); + let jc = Rc::new(TestXdgActivation { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + }); + self.bind(&jc, singletons.xdg_activation_v1, 1)?; + self.activation.set(Some(jc.clone())); + Ok(jc) + } + pub async fn get_xdg(&self) -> Result, TestError> { singleton!(self.xdg); let singletons = self.get_singletons().await?; diff --git a/src/it/test_ifs/test_xdg_activation.rs b/src/it/test_ifs/test_xdg_activation.rs new file mode 100644 index 00000000..6cc6aa86 --- /dev/null +++ b/src/it/test_ifs/test_xdg_activation.rs @@ -0,0 +1,66 @@ +use { + crate::{ + it::{ + test_error::{TestError, TestResult}, + test_ifs::{ + test_surface::TestSurface, test_xdg_activation_token::TestXdgActivationToken, + }, + test_object::TestObject, + test_transport::TestTransport, + }, + wire::{xdg_activation_v1::*, XdgActivationV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestXdgActivation { + pub id: XdgActivationV1Id, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestXdgActivation { + pub fn destroy(&self) -> Result<(), TestError> { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + pub async fn get_token(&self) -> Result { + let token = Rc::new(TestXdgActivationToken { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + token: Cell::new(None), + }); + self.tran.add_obj(token.clone())?; + self.tran.send(GetActivationToken { + self_id: self.id, + id: token.id, + })?; + let res = token.commit().await?; + token.destroy()?; + Ok(res) + } + + pub fn activate(&self, tl: &TestSurface, token: &str) -> TestResult { + self.tran.send(Activate { + self_id: self.id, + token, + surface: tl.id, + }) + } +} + +test_object! { + TestXdgActivation, XdgActivationV1; +} + +impl TestObject for TestXdgActivation {} + +impl Drop for TestXdgActivation { + fn drop(&mut self) { + let _ = self.destroy(); + } +} diff --git a/src/it/test_ifs/test_xdg_activation_token.rs b/src/it/test_ifs/test_xdg_activation_token.rs new file mode 100644 index 00000000..fba8312b --- /dev/null +++ b/src/it/test_ifs/test_xdg_activation_token.rs @@ -0,0 +1,56 @@ +use { + crate::{ + it::{ + test_error::TestError, test_object::TestObject, test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{xdg_activation_token_v1::*, XdgActivationTokenV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestXdgActivationToken { + pub id: XdgActivationTokenV1Id, + pub tran: Rc, + pub destroyed: Cell, + pub token: Cell>, +} + +impl TestXdgActivationToken { + pub fn destroy(&self) -> Result<(), TestError> { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + pub async fn commit(&self) -> Result { + self.tran.send(Commit { self_id: self.id })?; + self.tran.sync().await; + match self.token.take() { + Some(t) => Ok(t), + _ => bail!("Server did not send a token"), + } + } + + fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Done::parse_full(parser)?; + self.token.set(Some(ev.token.to_string())); + Ok(()) + } +} + +test_object! { + TestXdgActivationToken, XdgActivationTokenV1; + + DONE => handle_done, +} + +impl TestObject for TestXdgActivationToken {} + +impl Drop for TestXdgActivationToken { + fn drop(&mut self) { + let _ = self.destroy(); + } +} diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index d7708da2..da57bc21 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -59,6 +59,7 @@ impl TestTransport { spbm: Default::default(), viewporter: Default::default(), xdg: Default::default(), + activation: Default::default(), seats: Default::default(), }); self.send(wl_display::GetRegistry { @@ -161,7 +162,12 @@ impl TestTransport { _ => bail!("Object with id {} has already been deleted", msg.id()), }; if obj.interface().name() != msg.interface().name() { - bail!("Object with id {} has an incompatible interface", msg.id()); + bail!( + "Object with id {} has an incompatible interface: {} != {}", + msg.id(), + obj.interface().name(), + msg.interface().name() + ); } let mut fds = vec![]; let mut swapchain = self.swapchain.borrow_mut(); diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 157258cc..e8a40247 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -87,6 +87,7 @@ impl TestRun { spbm: registry.get_spbm().await?, viewporter: registry.get_viewporter().await?, xdg: registry.get_xdg().await?, + activation: registry.get_activation().await?, registry, })) } diff --git a/src/it/tests.rs b/src/it/tests.rs index 375fe9f5..fb56e4c1 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -53,6 +53,7 @@ mod t0019_natural_scrolling; mod t0020_surface_offset; mod t0021_preferred_buffer_scale; mod t0022_toplevel_suspended; +mod t0023_xdg_activation; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -94,5 +95,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0020_surface_offset, t0021_preferred_buffer_scale, t0022_toplevel_suspended, + t0023_xdg_activation, } } diff --git a/src/it/tests/t0007_subsurface.rs b/src/it/tests/t0007_subsurface.rs index 34fec5a8..087b564d 100644 --- a/src/it/tests/t0007_subsurface.rs +++ b/src/it/tests/t0007_subsurface.rs @@ -43,19 +43,17 @@ async fn test(run: Rc) -> Result<(), TestError> { parent.map().await?; - seat.set_app_cursor(None); - - client.compare_screenshot("1").await?; + client.compare_screenshot("1", false).await?; sub.place_below(parent.surface.id)?; child.commit()?; parent.map().await?; - client.compare_screenshot("2").await?; + client.compare_screenshot("2", false).await?; sub.place_above(parent.surface.id)?; child.commit()?; parent.map().await?; - client.compare_screenshot("1").await?; + client.compare_screenshot("1", false).await?; Ok(()) } diff --git a/src/it/tests/t0020_surface_offset.rs b/src/it/tests/t0020_surface_offset.rs index 351cdfd6..f33940e9 100644 --- a/src/it/tests/t0020_surface_offset.rs +++ b/src/it/tests/t0020_surface_offset.rs @@ -44,12 +44,12 @@ async fn test(run: Rc) -> Result<(), TestError> { let enter = enter.next()?; seat.pointer.set_cursor(enter.serial, &surface, 0, 0)?; - client.compare_screenshot("1").await?; + client.compare_screenshot("1", true).await?; surface.offset(-100, -100)?; surface.commit()?; - client.compare_screenshot("2").await?; + client.compare_screenshot("2", true).await?; Ok(()) } diff --git a/src/it/tests/t0023_xdg_activation.rs b/src/it/tests/t0023_xdg_activation.rs new file mode 100644 index 00000000..035ebb03 --- /dev/null +++ b/src/it/tests/t0023_xdg_activation.rs @@ -0,0 +1,40 @@ +use { + crate::it::{ + test_error::TestResult, + test_utils::{ + test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, + }, + testrun::TestRun, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + + let win1 = client.create_window().await?; + win1.set_color(255, 0, 0, 255); + win1.map2().await?; + + let win2 = client.create_window().await?; + win2.set_color(0, 255, 0, 255); + win2.map2().await?; + + let (x, y) = ds.output.first_toplevel()?.center(); + ds.move_to(x, y); + + client.sync().await; + run.cfg.set_mono(ds.seat.id(), true)?; + + let token = client.activation.get_token().await?; + client.activation.activate(&win2.surface, &token)?; + client.sync().await; + + client.compare_screenshot("1", false).await?; + + Ok(()) +} diff --git a/src/it/tests/t0023_xdg_activation/screenshot_1.qoi b/src/it/tests/t0023_xdg_activation/screenshot_1.qoi new file mode 100644 index 0000000000000000000000000000000000000000..1fa8d204eb5c8fb68a00ebb0272d52f80a8fbd23 GIT binary patch literal 7925 zcmeI%Jqp4=5QgE2Tjc~>*a&GXtg={d8L$uJ1c6jymHXuaY4-wN!EPr%Xkdfj1$?KP zZ>D>gZXWlp6)`7~Lt{kGbw8?z#H5~fye2XJT~_B@3h=V;6x3I@L5uwI$=m!mcqZXn zqueR#w7;`-Nsj;m2q1s}0tg_000IagfB*srAb>d#!8+ DPs$~y literal 0 HcmV?d00001 diff --git a/src/screenshoter.rs b/src/screenshoter.rs index acfbaab4..28e80129 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -39,7 +39,10 @@ pub struct Screenshot { pub bo: GbmBo, } -pub fn take_screenshot(state: &State) -> Result { +pub fn take_screenshot( + state: &State, + include_cursor: bool, +) -> Result { let ctx = match state.render_ctx.get() { Some(ctx) => ctx, _ => return Err(ScreenshooterError::NoRenderContext), @@ -72,7 +75,7 @@ pub fn take_screenshot(state: &State) -> Result fb.render_node( state.root.deref(), state, - Some(state.root.extents.get()), + include_cursor.then_some(state.root.extents.get()), None, Scale::from_int(1), true, diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index 10fb4d60..03fc2ebf 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -73,6 +73,11 @@ msg get_input = 17 { id: id(jay_input), } +msg take_screenshot2 = 18 { + id: id(jay_screenshot), + include_cursor: u32, +} + # events msg client_id = 0 { From a39031d4f936e8d253a5f0019a70d710631051ae Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 15:57:41 +0200 Subject: [PATCH 09/24] it: test foreign-toplevel-list --- src/it/test_ifs.rs | 2 + src/it/test_ifs/test_display.rs | 5 +- .../test_ext_foreign_toplevel_handle.rs | 74 +++++++++++++++++++ .../test_ext_foreign_toplevel_list.rs | 70 ++++++++++++++++++ src/it/test_ifs/test_jay_compositor.rs | 12 ++- src/it/test_ifs/test_registry.rs | 31 +++++++- src/it/test_ifs/test_xdg_toplevel.rs | 8 ++ src/it/test_macros.rs | 26 ++++--- src/it/test_transport.rs | 1 + src/it/testrun.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0024_foreign_toplevel_list.rs | 62 ++++++++++++++++ 12 files changed, 276 insertions(+), 18 deletions(-) create mode 100644 src/it/test_ifs/test_ext_foreign_toplevel_handle.rs create mode 100644 src/it/test_ifs/test_ext_foreign_toplevel_list.rs create mode 100644 src/it/tests/t0024_foreign_toplevel_list.rs diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index 43c477ee..e08f6784 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -2,6 +2,8 @@ mod test_buffer; pub mod test_callback; pub mod test_compositor; pub mod test_display; +pub mod test_ext_foreign_toplevel_handle; +pub mod test_ext_foreign_toplevel_list; pub mod test_jay_compositor; pub mod test_keyboard; pub mod test_pointer; diff --git a/src/it/test_ifs/test_display.rs b/src/it/test_ifs/test_display.rs index 531beed9..e71d5c81 100644 --- a/src/it/test_ifs/test_display.rs +++ b/src/it/test_ifs/test_display.rs @@ -1,5 +1,6 @@ use { crate::{ + client::MIN_SERVER_ID, it::{ test_error::TestError, test_object::TestObject, test_transport::TestTransport, testrun::ParseFull, @@ -36,7 +37,9 @@ impl TestDisplay { } Some(obj) => { obj.on_remove(&self.tran); - self.tran.obj_ids.borrow_mut().release(ev.id); + if ev.id < MIN_SERVER_ID { + self.tran.obj_ids.borrow_mut().release(ev.id); + } } } Ok(()) diff --git a/src/it/test_ifs/test_ext_foreign_toplevel_handle.rs b/src/it/test_ifs/test_ext_foreign_toplevel_handle.rs new file mode 100644 index 00000000..a2d0127b --- /dev/null +++ b/src/it/test_ifs/test_ext_foreign_toplevel_handle.rs @@ -0,0 +1,74 @@ +use { + crate::{ + it::{ + test_error::{TestError, TestResult}, + test_object::TestObject, + test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{ext_foreign_toplevel_handle_v1::*, ExtForeignToplevelHandleV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestExtForeignToplevelHandle { + pub id: ExtForeignToplevelHandleV1Id, + pub tran: Rc, + pub destroyed: Cell, + pub closed: Cell, + pub title: Cell>, + pub app_id: Cell>, + pub identifier: Cell>, +} + +impl TestExtForeignToplevelHandle { + fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + fn handle_closed(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let _ev = Closed::parse_full(parser)?; + self.closed.set(true); + self.destroy()?; + Ok(()) + } + + fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let _ev = Done::parse_full(parser)?; + Ok(()) + } + + fn handle_title(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Title::parse_full(parser)?; + self.title.set(Some(ev.title.to_string())); + Ok(()) + } + + fn handle_app_id(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = AppId::parse_full(parser)?; + self.app_id.set(Some(ev.app_id.to_string())); + Ok(()) + } + + fn handle_identifier(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Identifier::parse_full(parser)?; + self.identifier.set(Some(ev.identifier.to_string())); + Ok(()) + } +} + +test_object! { + TestExtForeignToplevelHandle, ExtForeignToplevelHandleV1; + + CLOSED => handle_closed, + DONE => handle_done, + TITLE => handle_title, + APP_ID => handle_app_id, + IDENTIFIER => handle_identifier, +} + +impl TestObject for TestExtForeignToplevelHandle {} diff --git a/src/it/test_ifs/test_ext_foreign_toplevel_list.rs b/src/it/test_ifs/test_ext_foreign_toplevel_list.rs new file mode 100644 index 00000000..cff7f979 --- /dev/null +++ b/src/it/test_ifs/test_ext_foreign_toplevel_list.rs @@ -0,0 +1,70 @@ +use { + crate::{ + it::{ + test_error::{TestError, TestResult}, + test_ifs::test_ext_foreign_toplevel_handle::TestExtForeignToplevelHandle, + test_object::TestObject, + test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{ext_foreign_toplevel_list_v1::*, ExtForeignToplevelListV1Id}, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +pub struct TestExtForeignToplevelList { + pub id: ExtForeignToplevelListV1Id, + pub tran: Rc, + pub destroyed: Cell, + pub toplevels: RefCell>>, +} + +impl TestExtForeignToplevelList { + #[allow(dead_code)] + pub fn stop(&self) -> TestResult { + self.tran.send(Stop { self_id: self.id })?; + Ok(()) + } + + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + fn handle_toplevel(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let ev = Toplevel::parse_full(parser)?; + let tl = Rc::new(TestExtForeignToplevelHandle { + id: ev.toplevel, + tran: self.tran.clone(), + destroyed: Cell::new(false), + closed: Cell::new(false), + title: Cell::new(None), + app_id: Cell::new(None), + identifier: Cell::new(None), + }); + self.tran.add_obj(tl.clone())?; + self.toplevels.borrow_mut().push(tl); + Ok(()) + } + + fn handle_finished(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { + let _ev = Finished::parse_full(parser)?; + self.destroy()?; + Ok(()) + } +} + +test_object! { + TestExtForeignToplevelList, ExtForeignToplevelListV1; + + TOPLEVEL => handle_toplevel, + FINISHED => handle_finished, +} + +impl TestObject for TestExtForeignToplevelList {} diff --git a/src/it/test_ifs/test_jay_compositor.rs b/src/it/test_ifs/test_jay_compositor.rs index 70e806d1..aea1265f 100644 --- a/src/it/test_ifs/test_jay_compositor.rs +++ b/src/it/test_ifs/test_jay_compositor.rs @@ -2,8 +2,11 @@ use { crate::{ client::ClientId, it::{ - test_error::TestError, test_ifs::test_screenshot::TestJayScreenshot, - test_object::TestObject, test_transport::TestTransport, testrun::ParseFull, + test_error::{TestError, TestResult}, + test_ifs::test_screenshot::TestJayScreenshot, + test_object::TestObject, + test_transport::TestTransport, + testrun::ParseFull, }, utils::{buffd::MsgParser, cell_ext::CellExt}, wire::{ @@ -33,6 +36,11 @@ impl TestJayCompositor { } } + pub fn enable_symmetric_delete(&self) -> TestResult { + self.tran.send(EnableSymmetricDelete { self_id: self.id })?; + Ok(()) + } + pub async fn take_screenshot(&self, include_cursor: bool) -> Result { let js = Rc::new(TestJayScreenshot { id: self.tran.id(), diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 0e4f384d..990b4dfb 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -5,8 +5,10 @@ use { it::{ test_error::TestError, test_ifs::{ - test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor, - test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_compositor::TestCompositor, + test_ext_foreign_toplevel_list::TestExtForeignToplevelList, + test_jay_compositor::TestJayCompositor, test_shm::TestShm, + test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, @@ -17,7 +19,10 @@ use { utils::{buffd::MsgParser, clonecell::CloneCell, copyhashmap::CopyHashMap}, wire::{wl_registry::*, WlRegistryId, WlSeat}, }, - std::{cell::Cell, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, }; pub struct TestGlobal { @@ -35,6 +40,7 @@ pub struct TestRegistrySingletons { pub wp_single_pixel_buffer_manager_v1: u32, pub wp_viewporter: u32, pub xdg_activation_v1: u32, + pub ext_foreign_toplevel_list_v1: u32, } pub struct TestRegistry { @@ -50,6 +56,7 @@ pub struct TestRegistry { pub viewporter: CloneCell>>, pub xdg: CloneCell>>, pub activation: CloneCell>>, + pub foreign_toplevel_list: CloneCell>>, pub seats: CopyHashMap>, } @@ -100,6 +107,7 @@ impl TestRegistry { wp_single_pixel_buffer_manager_v1, wp_viewporter, xdg_activation_v1, + ext_foreign_toplevel_list_v1, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -215,6 +223,23 @@ impl TestRegistry { Ok(jc) } + pub async fn get_foreign_toplevel_list( + &self, + ) -> Result, TestError> { + singleton!(self.foreign_toplevel_list); + let singletons = self.get_singletons().await?; + singleton!(self.foreign_toplevel_list); + let jc = Rc::new(TestExtForeignToplevelList { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + toplevels: RefCell::new(vec![]), + }); + self.bind(&jc, singletons.ext_foreign_toplevel_list_v1, 1)?; + self.foreign_toplevel_list.set(Some(jc.clone())); + Ok(jc) + } + pub fn bind( &self, obj: &Rc, diff --git a/src/it/test_ifs/test_xdg_toplevel.rs b/src/it/test_ifs/test_xdg_toplevel.rs index a921d4e7..ca9ee1e8 100644 --- a/src/it/test_ifs/test_xdg_toplevel.rs +++ b/src/it/test_ifs/test_xdg_toplevel.rs @@ -56,6 +56,14 @@ impl TestXdgToplevelCore { Ok(()) } + pub fn set_title(&self, title: &str) -> Result<(), TestError> { + self.tran.send(SetTitle { + self_id: self.id, + title, + })?; + Ok(()) + } + fn handle_configure(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> { let ev = Configure::parse_full(parser)?; self.width.set(ev.width); diff --git a/src/it/test_macros.rs b/src/it/test_macros.rs index 69549a0b..beba05ff 100644 --- a/src/it/test_macros.rs +++ b/src/it/test_macros.rs @@ -13,18 +13,20 @@ macro_rules! tassert { macro_rules! tassert_eq { ($left:expr, $right:expr) => {{ - let left = $left; - let right = $right; - if left != right { - bail!( - "Assert `{} = {:?} = {:?} = {}` failed ({}:{})", - stringify!($left), - left, - right, - stringify!($right), - file!(), - line!() - ); + match ($left, $right) { + (left, right) => { + if left != right { + bail!( + "Assert `{} = {:?} = {:?} = {}` failed ({}:{})", + stringify!($left), + left, + right, + stringify!($right), + file!(), + line!() + ); + } + } } }}; } diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index da57bc21..621fce11 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -60,6 +60,7 @@ impl TestTransport { viewporter: Default::default(), xdg: Default::default(), activation: Default::default(), + foreign_toplevel_list: Default::default(), seats: Default::default(), }); self.send(wl_display::GetRegistry { diff --git a/src/it/testrun.rs b/src/it/testrun.rs index e8a40247..03924926 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -74,6 +74,7 @@ impl TestRun { tran.init(); let registry = tran.get_registry(); let jc = registry.get_jay_compositor().await?; + jc.enable_symmetric_delete()?; let client_id = jc.get_client_id().await?; let client = self.state.clients.get(client_id)?; Ok(Rc::new(TestClient { diff --git a/src/it/tests.rs b/src/it/tests.rs index fb56e4c1..40c250e0 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -54,6 +54,7 @@ mod t0020_surface_offset; mod t0021_preferred_buffer_scale; mod t0022_toplevel_suspended; mod t0023_xdg_activation; +mod t0024_foreign_toplevel_list; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -96,5 +97,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0021_preferred_buffer_scale, t0022_toplevel_suspended, t0023_xdg_activation, + t0024_foreign_toplevel_list, } } diff --git a/src/it/tests/t0024_foreign_toplevel_list.rs b/src/it/tests/t0024_foreign_toplevel_list.rs new file mode 100644 index 00000000..ed0aaa28 --- /dev/null +++ b/src/it/tests/t0024_foreign_toplevel_list.rs @@ -0,0 +1,62 @@ +use { + crate::{ + it::{test_error::TestResult, testrun::TestRun}, + wire::WlBufferId, + }, + ahash::AHashSet, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let _ds = run.create_default_setup().await?; + + let client1 = run.create_client().await?; + let client2 = run.create_client().await?; + + let list = client2.registry.get_foreign_toplevel_list().await?; + + let win1 = client1.create_window().await?; + win1.tl.core.set_title("a")?; + win1.map().await?; + let win2 = client1.create_window().await?; + win2.tl.core.set_title("b")?; + win2.map().await?; + + client2.sync().await; + let tls = list.toplevels.take(); + tassert_eq!(tls.len(), 2); + + tassert_eq!(tls[0].title.take().as_deref(), Some("a")); + tassert_eq!(tls[1].title.take().as_deref(), Some("b")); + + let mut ids = AHashSet::new(); + ids.insert(tls[0].identifier.take().unwrap()); + ids.insert(tls[1].identifier.take().unwrap()); + + win2.tl.core.set_title("c")?; + client1.sync().await; + + client2.sync().await; + tassert_eq!(tls[1].title.take().as_deref(), Some("c")); + + win2.surface.attach(WlBufferId::NONE)?; + win2.surface.commit()?; + client1.sync().await; + + client2.sync().await; + tassert!(tls[1].closed.get()); + + win2.map().await?; + + client1.sync().await; + let tls = list.toplevels.take(); + tassert_eq!(tls.len(), 1); + tassert_eq!(tls[0].title.take().as_deref(), Some("c")); + + ids.insert(tls[0].identifier.take().unwrap()); + tassert_eq!(ids.len(), 3); + + Ok(()) +} From c6b34550d8417363be5f1558ff5250405c7deed8 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Apr 2024 16:52:32 +0200 Subject: [PATCH 10/24] it: test dnd focus change on drop --- src/it/test_client.rs | 9 +- src/it/test_ifs.rs | 4 + src/it/test_ifs/test_data_device.rs | 115 ++++++++++++++++++++ src/it/test_ifs/test_data_device_manager.rs | 57 ++++++++++ src/it/test_ifs/test_data_offer.rs | 57 ++++++++++ src/it/test_ifs/test_data_source.rs | 93 ++++++++++++++++ src/it/test_ifs/test_pointer.rs | 4 +- src/it/test_ifs/test_registry.rs | 18 ++- src/it/test_ifs/test_seat.rs | 1 + src/it/test_transport.rs | 1 + src/it/test_utils.rs | 1 + src/it/test_utils/test_rect_ext.rs | 11 ++ src/it/test_utils/test_toplevel_node_ext.rs | 5 +- src/it/testrun.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0025_dnd_focus_change.rs | 51 +++++++++ 16 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 src/it/test_ifs/test_data_device.rs create mode 100644 src/it/test_ifs/test_data_device_manager.rs create mode 100644 src/it/test_ifs/test_data_offer.rs create mode 100644 src/it/test_ifs/test_data_source.rs create mode 100644 src/it/test_utils/test_rect_ext.rs create mode 100644 src/it/tests/t0025_dnd_focus_change.rs diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 41bcfbb3..00627a2c 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -6,10 +6,10 @@ use { it::{ test_error::{TestError, TestResult}, test_ifs::{ - test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor, - test_keyboard::TestKeyboard, test_pointer::TestPointer, - test_registry::TestRegistry, test_seat::TestSeat, test_shm::TestShm, - test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_compositor::TestCompositor, test_data_device_manager::TestDataDeviceManager, + test_jay_compositor::TestJayCompositor, test_keyboard::TestKeyboard, + test_pointer::TestPointer, test_registry::TestRegistry, test_seat::TestSeat, + test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, @@ -35,6 +35,7 @@ pub struct TestClient { pub viewporter: Rc, pub xdg: Rc, pub activation: Rc, + pub data_device_manager: Rc, } pub struct DefaultSeat { diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index e08f6784..c2bd8587 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -1,6 +1,10 @@ mod test_buffer; pub mod test_callback; pub mod test_compositor; +pub mod test_data_device; +pub mod test_data_device_manager; +pub mod test_data_offer; +pub mod test_data_source; pub mod test_display; pub mod test_ext_foreign_toplevel_handle; pub mod test_ext_foreign_toplevel_list; diff --git a/src/it/test_ifs/test_data_device.rs b/src/it/test_ifs/test_data_device.rs new file mode 100644 index 00000000..3bbbb744 --- /dev/null +++ b/src/it/test_ifs/test_data_device.rs @@ -0,0 +1,115 @@ +use { + crate::{ + it::{ + test_error::TestResult, + test_ifs::{ + test_data_offer::TestDataOffer, test_data_source::TestDataSource, + test_surface::TestSurface, + }, + test_object::TestObject, + test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{wl_data_device::*, WlDataDeviceId, WlSurfaceId}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestDataDevice { + pub id: WlDataDeviceId, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestDataDevice { + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Release { self_id: self.id })?; + } + Ok(()) + } + + pub fn start_drag( + &self, + source: &TestDataSource, + origin: &TestSurface, + icon: Option<&TestSurface>, + serial: u32, + ) -> TestResult { + self.tran.send(StartDrag { + self_id: self.id, + source: source.id, + origin: origin.id, + icon: icon.map(|i| i.id).unwrap_or(WlSurfaceId::NONE), + serial, + })?; + Ok(()) + } + + #[allow(dead_code)] + pub fn set_selection(&self, source: &TestDataSource, serial: u32) -> TestResult { + self.tran.send(SetSelection { + self_id: self.id, + source: source.id, + serial, + })?; + Ok(()) + } + + fn handle_data_offer(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = DataOffer::parse_full(parser)?; + let offer = Rc::new(TestDataOffer { + id: ev.id, + tran: self.tran.clone(), + destroyed: Cell::new(false), + }); + self.tran.add_obj(offer.clone())?; + offer.destroy()?; + Ok(()) + } + + fn handle_enter(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Enter::parse_full(parser)?; + Ok(()) + } + + fn handle_leave(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Leave::parse_full(parser)?; + Ok(()) + } + + fn handle_motion(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Motion::parse_full(parser)?; + Ok(()) + } + + fn handle_drop(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Drop::parse_full(parser)?; + Ok(()) + } + + fn handle_selection(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Selection::parse_full(parser)?; + Ok(()) + } +} + +impl std::ops::Drop for TestDataDevice { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestDataDevice, WlDataDevice; + + DATA_OFFER => handle_data_offer, + ENTER => handle_enter, + LEAVE => handle_leave, + MOTION => handle_motion, + DROP => handle_drop, + SELECTION => handle_selection, +} + +impl TestObject for TestDataDevice {} diff --git a/src/it/test_ifs/test_data_device_manager.rs b/src/it/test_ifs/test_data_device_manager.rs new file mode 100644 index 00000000..285ecaff --- /dev/null +++ b/src/it/test_ifs/test_data_device_manager.rs @@ -0,0 +1,57 @@ +use { + crate::{ + it::{ + test_error::TestResult, + test_ifs::{ + test_data_device::TestDataDevice, test_data_source::TestDataSource, + test_seat::TestSeat, + }, + test_object::TestObject, + test_transport::TestTransport, + }, + wire::{wl_data_device_manager::*, WlDataDeviceManagerId}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestDataDeviceManager { + pub id: WlDataDeviceManagerId, + pub tran: Rc, +} + +impl TestDataDeviceManager { + pub fn create_data_source(&self) -> TestResult> { + let data_source = Rc::new(TestDataSource { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + }); + self.tran.add_obj(data_source.clone())?; + self.tran.send(CreateDataSource { + self_id: self.id, + id: data_source.id, + })?; + Ok(data_source) + } + + pub fn get_data_device(&self, seat: &TestSeat) -> TestResult> { + let data_device = Rc::new(TestDataDevice { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + }); + self.tran.add_obj(data_device.clone())?; + self.tran.send(GetDataDevice { + self_id: self.id, + id: data_device.id, + seat: seat.id, + })?; + Ok(data_device) + } +} + +test_object! { + TestDataDeviceManager, WlDataDeviceManager; +} + +impl TestObject for TestDataDeviceManager {} diff --git a/src/it/test_ifs/test_data_offer.rs b/src/it/test_ifs/test_data_offer.rs new file mode 100644 index 00000000..508048bb --- /dev/null +++ b/src/it/test_ifs/test_data_offer.rs @@ -0,0 +1,57 @@ +use { + crate::{ + it::{ + test_error::TestResult, test_object::TestObject, test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{wl_data_offer::*, WlDataOfferId}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestDataOffer { + pub id: WlDataOfferId, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestDataOffer { + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + fn handle_offer(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Offer::parse_full(parser)?; + Ok(()) + } + + fn handle_source_actions(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = SourceActions::parse_full(parser)?; + Ok(()) + } + + fn handle_action(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Action::parse_full(parser)?; + Ok(()) + } +} + +impl Drop for TestDataOffer { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestDataOffer, WlDataOffer; + + OFFER => handle_offer, + SOURCE_ACTIONS => handle_source_actions, + ACTION => handle_action, +} + +impl TestObject for TestDataOffer {} diff --git a/src/it/test_ifs/test_data_source.rs b/src/it/test_ifs/test_data_source.rs new file mode 100644 index 00000000..bd07b450 --- /dev/null +++ b/src/it/test_ifs/test_data_source.rs @@ -0,0 +1,93 @@ +use { + crate::{ + it::{ + test_error::TestResult, test_object::TestObject, test_transport::TestTransport, + testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{wl_data_source::*, WlDataSourceId}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestDataSource { + pub id: WlDataSourceId, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestDataSource { + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + #[allow(dead_code)] + pub fn offer(&self, mime_type: &str) -> TestResult { + self.tran.send(Offer { + self_id: self.id, + mime_type, + })?; + Ok(()) + } + + #[allow(dead_code)] + pub fn set_actions(&self, actions: u32) -> TestResult { + self.tran.send(SetActions { + self_id: self.id, + dnd_actions: actions, + })?; + Ok(()) + } + + fn handle_target(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Target::parse_full(parser)?; + Ok(()) + } + + fn handle_send(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Send::parse_full(parser)?; + Ok(()) + } + + fn handle_cancelled(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Cancelled::parse_full(parser)?; + Ok(()) + } + + fn handle_dnd_drop_performed(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = DndDropPerformed::parse_full(parser)?; + Ok(()) + } + + fn handle_dnd_finished(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = DndFinished::parse_full(parser)?; + Ok(()) + } + + fn handle_action(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Action::parse_full(parser)?; + Ok(()) + } +} + +impl Drop for TestDataSource { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestDataSource, WlDataSource; + + TARGET => handle_target, + SEND => handle_send, + CANCELLED => handle_cancelled, + DND_DROP_PERFORMED => handle_dnd_drop_performed, + DND_FINISHED => handle_dnd_finished, + ACTION => handle_action, +} + +impl TestObject for TestDataSource {} diff --git a/src/it/test_ifs/test_pointer.rs b/src/it/test_ifs/test_pointer.rs index e76f9ffa..e0f6fe54 100644 --- a/src/it/test_ifs/test_pointer.rs +++ b/src/it/test_ifs/test_pointer.rs @@ -20,6 +20,7 @@ pub struct TestPointer { pub leave: TEEH, pub enter: TEEH, pub motion: TEEH, + pub button: TEEH