diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs index 48fa9038..786e5d47 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -86,9 +86,7 @@ impl ExtImageCopyCaptureFrameV1 { let buffer = self.session.buffer.get().unwrap(); if size != buffer.rect.size() { self.session.buffer_size_changed(); - // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/222 - // self.fail(FrameFailureReason::BufferConstraints); - // return; + return Err(FrameFailureReason::BufferConstraints); } if let Err(e) = buffer.update_framebuffer() { log::error!("Could not import buffer: {}", ErrorFmt(e)); @@ -102,6 +100,13 @@ impl ExtImageCopyCaptureFrameV1 { let mut shm_staging = self.session.shm_staging.take(); match storage { WlBufferStorage::Shm { mem, stride, .. } => { + log::debug!( + "ext-image-copy frame {:?} using wl_shm readback path: {}x{}, stride {}", + self.id, + buffer.rect.width(), + buffer.rect.height(), + *stride, + ); if let Some(b) = &shm_bridge && (b.physical_size() != buffer.rect.size() || b.format() != buffer.format @@ -159,6 +164,12 @@ impl ExtImageCopyCaptureFrameV1 { self.session.shm_staging.set(Some(staging)); } WlBufferStorage::Dmabuf { fb, .. } => { + log::debug!( + "ext-image-copy frame {:?} using dmabuf GPU copy path: {}x{}", + self.id, + buffer.rect.width(), + buffer.rect.height(), + ); let Some(fb) = fb else { return Err(FrameFailureReason::BufferConstraints); }; @@ -187,7 +198,11 @@ impl ExtImageCopyCaptureFrameV1 { ) { match self.try_copy(on, size, f) { Ok(()) => self.session.status.set(FrameStatus::Captured), - Err(e) => self.fail(e), + Err(e) => { + if self.session.status.get() != FrameStatus::Failed { + self.fail(e); + } + } } } diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs index 286e9850..eb7154e7 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs @@ -83,6 +83,11 @@ impl ExtImageCopyCaptureSessionV1 { if self.size_debounce.replace(true) { return; } + if let Some(frame) = self.frame.get() + && let FrameStatus::Capturing | FrameStatus::Captured = self.status.get() + { + frame.fail(FrameFailureReason::BufferConstraints); + } self.force_capture.set(true); self.send_current_buffer_size(); self.send_done(); diff --git a/src/ifs/zwlr_screencopy_frame_v1.rs b/src/ifs/zwlr_screencopy_frame_v1.rs index d66035e9..d27b9c29 100644 --- a/src/ifs/zwlr_screencopy_frame_v1.rs +++ b/src/ifs/zwlr_screencopy_frame_v1.rs @@ -48,16 +48,13 @@ impl ZwlrScreencopyFrameV1 { } pub fn send_damage(&self) { - if let Some(output) = self.output.get() { - let pos = output.pos.get(); - self.client.event(Damage { - self_id: self.id, - x: 0, - y: 0, - width: pos.width() as _, - height: pos.height() as _, - }); - } + self.client.event(Damage { + self_id: self.id, + x: 0, + y: 0, + width: self.rect.width() as _, + height: self.rect.height() as _, + }); } pub fn send_buffer(&self) { @@ -111,10 +108,28 @@ impl ZwlrScreencopyFrameV1 { return Err(ZwlrScreencopyFrameV1Error::InvalidBufferFormat); } buffer.update_framebuffer()?; - if let Some(WlBufferStorage::Shm { stride, .. }) = buffer.storage.borrow_mut().deref() - && *stride != self.rect.width() * 4 - { - return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); + match buffer.storage.borrow_mut().deref() { + Some(WlBufferStorage::Shm { stride, .. }) => { + if *stride != self.rect.width() * 4 { + return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); + } + log::debug!( + "zwlr_screencopy frame {:?} using wl_shm readback path: {}x{}, stride {}", + self.id, + self.rect.width(), + self.rect.height(), + *stride, + ); + } + Some(WlBufferStorage::Dmabuf { .. }) => { + log::debug!( + "zwlr_screencopy frame {:?} using dmabuf GPU copy path: {}x{}", + self.id, + self.rect.width(), + self.rect.height(), + ); + } + _ => {} } self.buffer.set(Some(buffer)); if !with_damage && let Some(global) = self.output.get() { @@ -134,6 +149,12 @@ impl ZwlrScreencopyFrameV1 { } self.pending.take(); } + + pub fn cancel(&self) { + self.buffer.take(); + self.pending.take(); + self.send_failed(); + } } impl ZwlrScreencopyFrameV1RequestHandler for ZwlrScreencopyFrameV1 { diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 2ff6c8a4..5f70072f 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -104,8 +104,8 @@ impl ZwlrScreencopyManagerV1 { let Some(global) = output.global.get() else { return Ok(()); }; - let mode = global.mode.get(); - let mut rect = Rect::new_sized_saturating(0, 0, mode.width, mode.height); + let (width, height) = global.pixel_size(); + let mut rect = Rect::new_sized_saturating(0, 0, width, height); if let Some(region) = region { let scale = global.persistent.scale.get().to_f64(); let x1 = (region.x1() as f64 * scale).round() as i32; diff --git a/src/it/tests/t0022_toplevel_suspended.rs b/src/it/tests/t0022_toplevel_suspended.rs index 524856e3..5e871575 100644 --- a/src/it/tests/t0022_toplevel_suspended.rs +++ b/src/it/tests/t0022_toplevel_suspended.rs @@ -3,11 +3,9 @@ use { ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, it::{ test_error::{TestErrorExt, TestResult}, - test_utils::{ - test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, - }, testrun::TestRun, }, + tree::Node, }, isnt::std_1::collections::IsntHashSetExt, std::{rc::Rc, time::Duration}, @@ -29,7 +27,7 @@ async fn test(run: Rc) -> TestResult { win2.set_color(0, 255, 0, 255); win2.map2().await?; - let (x, y) = ds.output.first_toplevel()?.center(); + let (x, y) = win1.tl.server.node_absolute_position().center(); ds.move_to(x, y); tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); @@ -45,6 +43,10 @@ async fn test(run: Rc) -> TestResult { client.sync().await; tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + let (x, y) = win2.tl.server.node_absolute_position().center(); + ds.move_to(x, y); + client.sync().await; + let leaves = default_seat.kb.leave.expect()?; let enters = default_seat.kb.enter.expect()?; diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index d3563f0f..20d21f4a 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -315,6 +315,14 @@ impl PwClientNodeOwner for StartedScreencast { } } } + log::debug!( + "Portal screencast using PipeWire dmabuf GPU copy path: {} buffers, format {}, modifier 0x{:08x}, size {}x{}", + self.buffers.borrow().len(), + self.format.get().name, + self.modifier.get(), + self.width.get(), + self.height.get(), + ); self.node .send_port_output_buffers(&self.port, &self.buffers.borrow()); } @@ -633,15 +641,18 @@ impl UsrJayScreencastOwner for StartedScreencast { fn ready(&self, ev: &Ready) { let idx = ev.idx as usize; - let buffers = &*self.buffers.borrow(); - let pbuffers = self.port.buffers.borrow(); - let buffer = &buffers[idx]; let discard_buffer = || { self.jay_screencast.release_buffer(idx); }; if !self.buffers_valid.get() { return; } + let buffers = self.buffers.borrow(); + let Some(buffer) = buffers.get(idx) else { + log::warn!("Ignoring ready event for unknown screencast buffer {idx}"); + return; + }; + let pbuffers = self.port.buffers.borrow(); let Some(io) = self.port.io_buffers.get() else { discard_buffer(); return; @@ -767,7 +778,7 @@ pub(super) fn add_screencast_dbus_members( object.add_method::(move |req, pr| { dbus_start(&state, req, pr); }); - object.set_property::(Variant::U32(MONITOR.0)); + object.set_property::(Variant::U32((MONITOR | WINDOW).0)); object.set_property::(Variant::U32(EMBEDDED.0)); object.set_property::(Variant::U32(5)); } diff --git a/src/state.rs b/src/state.rs index 74facaf1..1af6c209 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,12 +4,11 @@ use { allocator::BufferObject, animation::{ AnimationCurve, AnimationState, AnimationStyle, AnimationTick, RetainedExitLayer, - RetainedToplevel, - expand_damage_rect, + RetainedToplevel, expand_damage_rect, multiphase::{ MultiphasePhase, MultiphasePlan, MultiphasePlanFailure, MultiphaseRequest, - MultiphaseWindow, MultiphaseWindowHierarchy, - partition_motion_groups, plan_no_overlap_with_diagnostics, validate_phase_paths, + MultiphaseWindow, MultiphaseWindowHierarchy, partition_motion_groups, + plan_no_overlap_with_diagnostics, validate_phase_paths, }, spawn_in_start_rect, }, @@ -224,10 +223,7 @@ fn bridged_retarget_plan( return Err(MultiphasePlanFailure::NoPattern); }; let mut path = bridge_path.clone(); - let mut current = path - .last() - .map(|(_, to)| *to) - .unwrap_or(window.from); + let mut current = path.last().map(|(_, to)| *to).unwrap_or(window.from); while path.len() < bridge_phase_count { path.push((current, current)); } @@ -995,6 +991,12 @@ impl State { } else { lap.add_child_after(&*la, node); } + } else if let Some(last) = c.children.last() { + if autotile { + c.add_tiled_child_after(&*last.node, node); + } else { + c.add_child_after(&*last.node, node); + } } else { c.append_child(node); } @@ -1773,12 +1775,7 @@ impl State { self.eng.now().msec() } - pub fn queue_tiled_animation( - self: &Rc, - node_id: NodeId, - old: Rect, - new: Rect, - ) { + pub fn queue_tiled_animation(self: &Rc, node_id: NodeId, old: Rect, new: Rect) { let curve = self .layout_animation_curve_override .get() @@ -1806,12 +1803,7 @@ impl State { self.queue_layout_animation(node_id, old, new, curve, hierarchy); } - pub fn queue_linear_layout_animation( - self: &Rc, - node_id: NodeId, - old: Rect, - new: Rect, - ) { + pub fn queue_linear_layout_animation(self: &Rc, node_id: NodeId, old: Rect, new: Rect) { self.queue_layout_animation( node_id, old, @@ -2164,11 +2156,7 @@ impl State { started_any } - pub fn queue_spawn_in_animation( - self: &Rc, - node_id: NodeId, - target: Rect, - ) { + pub fn queue_spawn_in_animation(self: &Rc, node_id: NodeId, target: Rect) { if !self.animations.enabled.get() || target.is_empty() { return; } @@ -2829,10 +2817,7 @@ impl State { #[cfg(test)] mod tests { - use { - super::*, - crate::animation::multiphase::MultiphaseHierarchyPosition, - }; + use {super::*, crate::animation::multiphase::MultiphaseHierarchyPosition}; fn rect(x1: i32, y1: i32, x2: i32, y2: i32) -> Rect { Rect::new_saturating(x1, y1, x2, y2) @@ -2846,12 +2831,7 @@ mod tests { } fn candidate(node_id: u32, style: AnimationStyle) -> LayoutAnimationCandidate { - candidate_rects( - node_id, - rect(0, 0, 100, 100), - rect(100, 0, 200, 100), - style, - ) + candidate_rects(node_id, rect(0, 0, 100, 100), rect(100, 0, 200, 100), style) } fn candidate_rects( @@ -2922,14 +2902,16 @@ mod tests { ) .unwrap(); - assert!(plan - .phases - .iter() - .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(1)))); - assert!(plan - .phases - .iter() - .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(3)))); + assert!( + plan.phases + .iter() + .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(1))) + ); + assert!( + plan.phases + .iter() + .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(3))) + ); } #[test] diff --git a/src/tree/container.rs b/src/tree/container.rs index 44a6a778..e9363106 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -2633,6 +2633,9 @@ impl ToplevelNodeBase for ContainerNode { if let Some(last) = self.focus_history.last() { return last.node.clone().tl_last_active_child(); } + if let Some(last) = self.children.last() { + return last.node.clone().tl_last_active_child(); + } self } diff --git a/src/tree/output.rs b/src/tree/output.rs index d21b3e5e..456e7a5d 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -460,9 +460,15 @@ impl OutputNode { } self.lock_surface.take(); self.jay_outputs.clear(); - self.screencasts.clear(); - self.screencopies.clear(); - self.ext_copy_sessions.clear(); + for screencast in self.screencasts.lock().drain_values() { + screencast.do_destroy(); + } + for screencopy in self.screencopies.lock().drain_values() { + screencopy.cancel(); + } + for session in self.ext_copy_sessions.lock().drain_values() { + session.stop(); + } self.ext_workspace_groups.clear(); self.latch_event.clear(); self.vblank_event.clear();