1
0
Fork 0
forked from wry/wry

Compare commits

..

1 commit

Author SHA1 Message Date
9b2550d567 fix: screencopy and portal capture state handling 2026-06-04 14:49:12 +10:00
38 changed files with 162 additions and 208 deletions

View file

@ -86,9 +86,7 @@ impl ExtImageCopyCaptureFrameV1 {
let buffer = self.session.buffer.get().unwrap(); let buffer = self.session.buffer.get().unwrap();
if size != buffer.rect.size() { if size != buffer.rect.size() {
self.session.buffer_size_changed(); self.session.buffer_size_changed();
// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/222 return Err(FrameFailureReason::BufferConstraints);
// self.fail(FrameFailureReason::BufferConstraints);
// return;
} }
if let Err(e) = buffer.update_framebuffer() { if let Err(e) = buffer.update_framebuffer() {
log::error!("Could not import buffer: {}", ErrorFmt(e)); log::error!("Could not import buffer: {}", ErrorFmt(e));
@ -102,6 +100,13 @@ impl ExtImageCopyCaptureFrameV1 {
let mut shm_staging = self.session.shm_staging.take(); let mut shm_staging = self.session.shm_staging.take();
match storage { match storage {
WlBufferStorage::Shm { mem, stride, .. } => { 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 if let Some(b) = &shm_bridge
&& (b.physical_size() != buffer.rect.size() && (b.physical_size() != buffer.rect.size()
|| b.format() != buffer.format || b.format() != buffer.format
@ -159,6 +164,12 @@ impl ExtImageCopyCaptureFrameV1 {
self.session.shm_staging.set(Some(staging)); self.session.shm_staging.set(Some(staging));
} }
WlBufferStorage::Dmabuf { fb, .. } => { 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 { let Some(fb) = fb else {
return Err(FrameFailureReason::BufferConstraints); return Err(FrameFailureReason::BufferConstraints);
}; };
@ -187,7 +198,11 @@ impl ExtImageCopyCaptureFrameV1 {
) { ) {
match self.try_copy(on, size, f) { match self.try_copy(on, size, f) {
Ok(()) => self.session.status.set(FrameStatus::Captured), Ok(()) => self.session.status.set(FrameStatus::Captured),
Err(e) => self.fail(e), Err(e) => {
if self.session.status.get() != FrameStatus::Failed {
self.fail(e);
}
}
} }
} }

View file

@ -83,6 +83,11 @@ impl ExtImageCopyCaptureSessionV1 {
if self.size_debounce.replace(true) { if self.size_debounce.replace(true) {
return; 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.force_capture.set(true);
self.send_current_buffer_size(); self.send_current_buffer_size();
self.send_done(); self.send_done();

View file

@ -1520,25 +1520,25 @@ impl WlSurface {
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds()); let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
let pos = self.buffer_abs_pos.get(); let pos = self.buffer_abs_pos.get();
let apply_damage = |pos: Rect| { let apply_damage = |pos: Rect| {
let clip_damage = |mut damage: Rect| { if pending.damage_full {
damage = damage.intersect(pos); let mut damage = pos;
if let Some(bounds) = bounds { if let Some(bounds) = bounds {
damage = damage.intersect(bounds); damage = damage.intersect(bounds);
} }
damage self.client.state.damage(damage);
};
if pending.damage_full {
self.client.state.damage(clip_damage(pos));
} else { } else {
let matrix = self.damage_matrix.get(); let matrix = self.damage_matrix.get();
if let Some(buffer) = self.buffer.get() { if let Some(buffer) = self.buffer.get() {
for damage in &pending.buffer_damage { for damage in &pending.buffer_damage {
let damage = matrix.apply( let mut damage = matrix.apply(
pos.x1(), pos.x1(),
pos.y1(), pos.y1(),
damage.intersect(buffer.buffer.buf.rect), damage.intersect(buffer.buffer.buf.rect),
); );
self.client.state.damage(clip_damage(damage)); if let Some(bounds) = bounds {
damage = damage.intersect(bounds);
}
self.client.state.damage(damage);
} }
} }
for damage in &pending.surface_damage { for damage in &pending.surface_damage {
@ -1550,7 +1550,8 @@ impl WlSurface {
let y2 = (damage.y2() + scale - 1) / scale; let y2 = (damage.y2() + scale - 1) / scale;
damage = Rect::new_saturating(x1, y1, x2, y2); damage = Rect::new_saturating(x1, y1, x2, y2);
} }
self.client.state.damage(clip_damage(damage)); damage = damage.intersect(bounds.unwrap_or(pos));
self.client.state.damage(damage);
} }
} }
}; };

View file

@ -48,16 +48,13 @@ impl ZwlrScreencopyFrameV1 {
} }
pub fn send_damage(&self) { pub fn send_damage(&self) {
if let Some(output) = self.output.get() { self.client.event(Damage {
let pos = output.pos.get(); self_id: self.id,
self.client.event(Damage { x: 0,
self_id: self.id, y: 0,
x: 0, width: self.rect.width() as _,
y: 0, height: self.rect.height() as _,
width: pos.width() as _, });
height: pos.height() as _,
});
}
} }
pub fn send_buffer(&self) { pub fn send_buffer(&self) {
@ -111,10 +108,28 @@ impl ZwlrScreencopyFrameV1 {
return Err(ZwlrScreencopyFrameV1Error::InvalidBufferFormat); return Err(ZwlrScreencopyFrameV1Error::InvalidBufferFormat);
} }
buffer.update_framebuffer()?; buffer.update_framebuffer()?;
if let Some(WlBufferStorage::Shm { stride, .. }) = buffer.storage.borrow_mut().deref() match buffer.storage.borrow_mut().deref() {
&& *stride != self.rect.width() * 4 Some(WlBufferStorage::Shm { stride, .. }) => {
{ if *stride != self.rect.width() * 4 {
return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); 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)); self.buffer.set(Some(buffer));
if !with_damage && let Some(global) = self.output.get() { if !with_damage && let Some(global) = self.output.get() {
@ -134,6 +149,12 @@ impl ZwlrScreencopyFrameV1 {
} }
self.pending.take(); self.pending.take();
} }
pub fn cancel(&self) {
self.buffer.take();
self.pending.take();
self.send_failed();
}
} }
impl ZwlrScreencopyFrameV1RequestHandler for ZwlrScreencopyFrameV1 { impl ZwlrScreencopyFrameV1RequestHandler for ZwlrScreencopyFrameV1 {

View file

@ -104,8 +104,8 @@ impl ZwlrScreencopyManagerV1 {
let Some(global) = output.global.get() else { let Some(global) = output.global.get() else {
return Ok(()); return Ok(());
}; };
let mode = global.mode.get(); let (width, height) = global.pixel_size();
let mut rect = Rect::new_sized_saturating(0, 0, mode.width, mode.height); let mut rect = Rect::new_sized_saturating(0, 0, width, height);
if let Some(region) = region { if let Some(region) = region {
let scale = global.persistent.scale.get().to_f64(); let scale = global.persistent.scale.get().to_f64();
let x1 = (region.x1() as f64 * scale).round() as i32; let x1 = (region.x1() as f64 * scale).round() as i32;

View file

@ -29,17 +29,6 @@ impl TestViewport {
Ok(()) Ok(())
} }
pub fn unset_source(&self) -> Result<(), TestError> {
self.tran.send(SetSource {
self_id: self.id,
x: Fixed::from_int(-1),
y: Fixed::from_int(-1),
width: Fixed::from_int(-1),
height: Fixed::from_int(-1),
})?;
Ok(())
}
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> { pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
self.tran.send(SetDestination { self.tran.send(SetDestination {
self_id: self.id, self_id: self.id,
@ -48,15 +37,6 @@ impl TestViewport {
})?; })?;
Ok(()) Ok(())
} }
pub fn unset_destination(&self) -> Result<(), TestError> {
self.tran.send(SetDestination {
self_id: self.id,
width: -1,
height: -1,
})?;
Ok(())
}
} }
impl Drop for TestViewport { impl Drop for TestViewport {

View file

@ -1,6 +1,7 @@
use { use {
crate::{ crate::{
it::{test_error::TestError, testrun::TestRun}, it::{test_error::TestError, testrun::TestRun},
rect::Rect,
tree::Node, tree::Node,
}, },
std::rc::Rc, std::rc::Rc,
@ -10,19 +11,29 @@ testcase!();
/// Create and map a single surface /// Create and map a single surface
async fn test(run: Rc<TestRun>) -> Result<(), TestError> { async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let ds = run.create_default_setup().await?; run.backend.install_default()?;
let client = run.create_client().await?; let client = run.create_client().await?;
let window = client.create_window().await?; let window = client.create_window().await?;
window.map().await?; window.map().await?;
let workspace_rect = ds.output.workspace_rect.get(); tassert_eq!(window.tl.core.width.get(), 800);
tassert_eq!(
window.tl.core.height.get(),
600 - 2 * run.state.theme.title_plus_underline_height()
);
tassert_eq!(window.tl.core.width.get(), workspace_rect.width()); tassert_eq!(
tassert_eq!(window.tl.core.height.get(), workspace_rect.height()); window.tl.server.node_absolute_position(),
Rect::new_sized(
tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect); 0,
2 * run.state.theme.title_plus_underline_height(),
window.tl.core.width.get(),
window.tl.core.height.get(),
)
.unwrap()
);
Ok(()) Ok(())
} }

View file

@ -11,7 +11,7 @@ testcase!();
/// Create and map two surfaces /// Create and map two surfaces
async fn test(run: Rc<TestRun>) -> Result<(), TestError> { async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let ds = run.create_default_setup().await?; run.backend.install_default()?;
let client = run.create_client().await?; let client = run.create_client().await?;
@ -21,30 +21,17 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let window2 = client.create_window().await?; let window2 = client.create_window().await?;
window2.map().await?; window2.map().await?;
let workspace_rect = ds.output.workspace_rect.get(); let otop = 2 * run.state.theme.title_plus_underline_height();
let bw = run.state.theme.sizes.border_width.get(); let bw = run.state.theme.sizes.border_width.get();
let child_width = (workspace_rect.width() - bw) / 2;
tassert_eq!( tassert_eq!(
window.tl.server.node_absolute_position(), window.tl.server.node_absolute_position(),
Rect::new_sized( Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap()
workspace_rect.x1(),
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
); );
tassert_eq!( tassert_eq!(
window2.tl.server.node_absolute_position(), window2.tl.server.node_absolute_position(),
Rect::new_sized( Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap()
workspace_rect.x1() + child_width + bw,
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
); );
Ok(()) Ok(())

View file

@ -48,18 +48,13 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let mono_container = w_mono2.tl.container_parent()?; let mono_container = w_mono2.tl.container_parent()?;
let container_pos = mono_container.tl_data().pos.get(); let container_pos = mono_container.tl_data().pos.get();
let (tab_x, tab_y) = { let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0]
let tab_bar = mono_container.tab_bar.borrow(); .move_(container_pos.x1(), container_pos.y1());
let Some(tab_bar) = tab_bar.as_ref() else { ds.mouse.abs(
bail!("no tab bar"); &ds.connector,
}; w_mono1_title.x1() as _,
let w_mono1_title = &tab_bar.entries[0]; w_mono1_title.y1() as _,
( );
container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
container_pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _);
client.sync().await; client.sync().await;
tassert!(enters.next().is_err()); tassert!(enters.next().is_err());

View file

@ -26,18 +26,12 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let container = w_mono2.tl.container_parent()?; let container = w_mono2.tl.container_parent()?;
let pos = container.tl_data().pos.get(); let pos = container.tl_data().pos.get();
let (tab_x, tab_y) = { let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1());
let tab_bar = container.tab_bar.borrow(); ds.mouse.abs(
let Some(tab_bar) = tab_bar.as_ref() else { &ds.connector,
bail!("no tab bar"); w_mono1_title.x1() as f64,
}; w_mono1_title.y1() as f64,
let w_mono1_title = &tab_bar.entries[0]; );
(
pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64);
client.sync().await; client.sync().await;
let enters = dss.kb.enter.expect()?; let enters = dss.kb.enter.expect()?;

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
it::{ it::{
test_error::{TestErrorExt, TestResult}, test_error::TestResult,
test_utils::{ test_utils::{
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
}, },
@ -10,7 +10,7 @@ use {
}, },
}, },
isnt::std_1::collections::IsntHashSetExt, isnt::std_1::collections::IsntHashSetExt,
std::{rc::Rc, time::Duration}, std::rc::Rc,
}; };
testcase!(); testcase!();
@ -19,7 +19,6 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let ds = run.create_default_setup().await?; let ds = run.create_default_setup().await?;
let client = run.create_client().await?; let client = run.create_client().await?;
let default_seat = client.get_default_seat().await?;
let win1 = client.create_window().await?; let win1 = client.create_window().await?;
win1.set_color(255, 0, 0, 255); win1.set_color(255, 0, 0, 255);
@ -45,23 +44,5 @@ async fn test(run: Rc<TestRun>) -> TestResult {
client.sync().await; client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let leaves = default_seat.kb.leave.expect()?;
let enters = default_seat.kb.enter.expect()?;
run.cfg.set_idle(Duration::from_micros(100))?;
run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
run.state.wheel.timeout(3).await?;
client.sync().await;
tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED));
let leave = leaves.next().with_context(|| "no leave on suspend")?;
tassert_eq!(leave.surface, win2.surface.id);
ds.mouse.rel(1.0, 1.0);
client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let enter = enters.next().with_context(|| "no enter on restore")?;
tassert_eq!(enter.surface, win2.surface.id);
Ok(()) Ok(())
} }

View file

@ -308,8 +308,9 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow(); let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty()); tassert!(!output_damage.is_empty());
// The test window maps its 1x1 buffer through a viewport to the full window size. // Buffer damage is transformed by the damage matrix which includes the surface position
let expected_buffer_damage = surface_pos; // The buffer damage (0,0,1,1) should be transformed to surface coordinates
let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1());
// Find the exact output damage that matches our expected buffer damage // Find the exact output damage that matches our expected buffer damage
let mut found_exact_buffer_damage = false; let mut found_exact_buffer_damage = false;
@ -330,12 +331,10 @@ async fn test(run: Rc<TestRun>) -> TestResult {
// Test 7: Check output damage from existing window's viewport (which already has scaling) // Test 7: Check output damage from existing window's viewport (which already has scaling)
connector_data.damage.borrow_mut().clear(); connector_data.damage.borrow_mut().clear();
// The existing window was created with create_surface_ext() which automatically creates a viewport. // The existing window was created with create_surface_ext() which automatically creates a viewport
// Commit the viewport size change separately; that commit intentionally damages the old/new extents. // Let's verify that the viewport's existing scaling affects buffer damage correctly
window.surface.viewport.set_destination(150, 100)?; // First, let's modify the viewport scaling that already exists on the window
window.surface.commit()?; window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100
client.sync().await;
connector_data.damage.borrow_mut().clear();
// Add buffer damage to test viewport scaling coordinate transformation // Add buffer damage to test viewport scaling coordinate transformation
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
@ -347,8 +346,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow(); let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty()); tassert!(!output_damage.is_empty());
// With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination. // With viewporter scaling, the 1x1 buffer damage should scale to 150x100
let surface_pos = window.surface.server.buffer_abs_pos.get(); // and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136)
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap(); let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
let expected_output_damage = let expected_output_damage =
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1()); expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
@ -403,9 +402,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
rotation_window.map().await?; rotation_window.map().await?;
client.sync().await; client.sync().await;
// Disable viewporter to rely purely on buffer dimensions. // Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions
rotation_window.surface.viewport.unset_source()?; rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter
rotation_window.surface.viewport.unset_destination()?;
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect // Use a rectangular buffer (4x2) so rotation has a visible geometric effect
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer // Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer

View file

@ -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 self.node
.send_port_output_buffers(&self.port, &self.buffers.borrow()); .send_port_output_buffers(&self.port, &self.buffers.borrow());
} }
@ -633,15 +641,18 @@ impl UsrJayScreencastOwner for StartedScreencast {
fn ready(&self, ev: &Ready) { fn ready(&self, ev: &Ready) {
let idx = ev.idx as usize; let idx = ev.idx as usize;
let buffers = &*self.buffers.borrow();
let pbuffers = self.port.buffers.borrow();
let buffer = &buffers[idx];
let discard_buffer = || { let discard_buffer = || {
self.jay_screencast.release_buffer(idx); self.jay_screencast.release_buffer(idx);
}; };
if !self.buffers_valid.get() { if !self.buffers_valid.get() {
return; 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 { let Some(io) = self.port.io_buffers.get() else {
discard_buffer(); discard_buffer();
return; return;
@ -767,7 +778,7 @@ pub(super) fn add_screencast_dbus_members(
object.add_method::<Start, _>(move |req, pr| { object.add_method::<Start, _>(move |req, pr| {
dbus_start(&state, req, pr); dbus_start(&state, req, pr);
}); });
object.set_property::<AvailableSourceTypes>(Variant::U32(MONITOR.0)); object.set_property::<AvailableSourceTypes>(Variant::U32((MONITOR | WINDOW).0));
object.set_property::<AvailableCursorModes>(Variant::U32(EMBEDDED.0)); object.set_property::<AvailableCursorModes>(Variant::U32(EMBEDDED.0));
object.set_property::<version>(Variant::U32(5)); object.set_property::<version>(Variant::U32(5));
} }

View file

@ -32,7 +32,6 @@ use {
numcell::NumCell, numcell::NumCell,
on_drop_event::OnDropEvent, on_drop_event::OnDropEvent,
rc_eq::rc_eq, rc_eq::rc_eq,
scroller::Scroller,
threshold_counter::ThresholdCounter, threshold_counter::ThresholdCounter,
}, },
}, },
@ -151,7 +150,6 @@ pub struct ContainerNode {
pub child_removed: Rc<LazyEventSource>, pub child_removed: Rc<LazyEventSource>,
pub all_children_resized: Rc<LazyEventSource>, pub all_children_resized: Rc<LazyEventSource>,
pub tab_bar: RefCell<Option<TabBar>>, pub tab_bar: RefCell<Option<TabBar>>,
scroll: Scroller,
pub update_tab_textures_scheduled: Cell<bool>, pub update_tab_textures_scheduled: Cell<bool>,
pub ephemeral: Cell<Ephemeral>, pub ephemeral: Cell<Ephemeral>,
} }
@ -268,7 +266,6 @@ impl ContainerNode {
child_removed: state.lazy_event_sources.create_source(), child_removed: state.lazy_event_sources.create_source(),
all_children_resized: state.post_layout_event_sources.create_source(), all_children_resized: state.post_layout_event_sources.create_source(),
tab_bar: RefCell::new(None), tab_bar: RefCell::new(None),
scroll: Default::default(),
update_tab_textures_scheduled: Cell::new(false), update_tab_textures_scheduled: Cell::new(false),
ephemeral: Cell::new(Ephemeral::Off), ephemeral: Cell::new(Ephemeral::Off),
}); });
@ -796,18 +793,6 @@ impl ContainerNode {
self.activate_child2(child, false); self.activate_child2(child, false);
} }
fn activate_child_from_input(
self: &Rc<Self>,
child: &NodeRef<ContainerChild>,
seat: &Rc<WlSeatGlobal>,
) {
self.activate_child(child);
child
.node
.clone()
.node_do_focus(seat, Direction::Unspecified);
}
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) { fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
if let Some(mc) = self.mono_child.get() { if let Some(mc) = self.mono_child.get() {
if mc.node.node_id() == child.node.node_id() { if mc.node.node_id() == child.node.node_id() {
@ -1534,7 +1519,7 @@ impl ContainerNode {
fn button( fn button(
self: Rc<Self>, self: Rc<Self>,
id: CursorType, id: CursorType,
seat: &Rc<WlSeatGlobal>, _seat: &Rc<WlSeatGlobal>,
_time_usec: u64, _time_usec: u64,
pressed: bool, pressed: bool,
button: u32, button: u32,
@ -1564,7 +1549,7 @@ impl ContainerNode {
if let Some(child) = children.get(&child_id) { if let Some(child) = children.get(&child_id) {
let child_ref = child.to_ref(); let child_ref = child.to_ref();
drop(children); drop(children);
self.activate_child_from_input(&child_ref, seat); self.activate_child(&child_ref);
} }
return; return;
} }
@ -2081,33 +2066,31 @@ impl Node for ContainerNode {
self.button(id, seat, time_usec, state == ButtonState::Pressed, button); self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
} }
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) { fn node_on_axis_event(self: Rc<Self>, _seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
if self.mono_child.is_none() { if self.mono_child.is_none() {
return; return;
} }
let steps = match self.scroll.handle(event) { // Use vertical scroll (index 1) to switch tabs.
Some(steps) => steps, let v = match event.v120[1].get() {
Some(v) if v != 0 => v,
_ => return, _ => return,
}; };
let mut target = match self.mono_child.get() { let mono = match self.mono_child.get() {
Some(m) => m, Some(m) => m,
None => return, None => return,
}; };
let current_id = target.node.node_id(); let next = if v > 0 {
for _ in 0..steps.abs() { // Scroll down → next tab.
let next = if steps > 0 { mono.next().or_else(|| self.children.first())
target.next().or_else(|| self.children.first()) } else {
} else { // Scroll up → previous tab.
target.prev().or_else(|| self.children.last()) mono.prev().or_else(|| self.children.last())
}; };
match next { if let Some(next) = next {
Some(next) => target = next, if next.node.node_id() != mono.node.node_id() {
None => break, self.activate_child(&next);
} }
} }
if target.node.node_id() != current_id {
self.activate_child_from_input(&target, seat);
}
} }
fn node_on_leave(&self, seat: &WlSeatGlobal) { fn node_on_leave(&self, seat: &WlSeatGlobal) {

View file

@ -8,25 +8,18 @@ use {
renderer::Renderer, renderer::Renderer,
state::State, state::State,
tree::{ tree::{
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation,
NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
WorkspaceNodeId, walker::NodeVisitor, WorkspaceNodeId, walker::NodeVisitor,
}, },
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
}, },
std::{ std::{cell::Cell, ops::Deref, rc::Rc},
cell::{Cell, RefCell},
mem,
ops::Deref,
rc::{Rc, Weak},
},
}; };
pub struct DisplayNode { pub struct DisplayNode {
pub id: NodeId, pub id: NodeId,
pub extents: Cell<Rect>, pub extents: Cell<Rect>,
visible: Cell<bool>,
suspend_restore_kb_foci: RefCell<Vec<(Rc<WlSeatGlobal>, Weak<dyn Node>)>>,
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>, pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>, pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>, pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
@ -38,8 +31,6 @@ impl DisplayNode {
let slf = Self { let slf = Self {
id, id,
extents: Default::default(), extents: Default::default(),
visible: Default::default(),
suspend_restore_kb_foci: Default::default(),
outputs: Default::default(), outputs: Default::default(),
stacked: Default::default(), stacked: Default::default(),
stacked_above_layers: Default::default(), stacked_above_layers: Default::default(),
@ -80,17 +71,6 @@ impl DisplayNode {
pub fn update_visible(&self, state: &State) { pub fn update_visible(&self, state: &State) {
let visible = state.root_visible(); let visible = state.root_visible();
let was_visible = self.visible.replace(visible);
if !visible && was_visible {
let mut foci = self.suspend_restore_kb_foci.borrow_mut();
foci.clear();
for seat in state.globals.seats.lock().values() {
let node = seat.get_keyboard_node();
if node.node_id() != self.id {
foci.push((seat.clone(), Rc::downgrade(&node)));
}
}
}
for output in self.outputs.lock().values() { for output in self.outputs.lock().values() {
output.update_visible(); output.update_visible();
} }
@ -102,20 +82,6 @@ impl DisplayNode {
for seat in state.globals.seats.lock().values() { for seat in state.globals.seats.lock().values() {
seat.set_visible(visible); seat.set_visible(visible);
} }
if visible && !was_visible {
for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) {
if seat.get_keyboard_node().node_id() == self.id {
if let Some(node) = node.upgrade()
&& node.node_visible()
{
seat.focus_node(node);
} else {
seat.get_fallback_output()
.take_keyboard_navigation_focus(&seat, Direction::Unspecified);
}
}
}
}
if visible { if visible {
state.damage(self.extents.get()); state.damage(self.extents.get());
} }

View file

@ -460,9 +460,15 @@ impl OutputNode {
} }
self.lock_surface.take(); self.lock_surface.take();
self.jay_outputs.clear(); self.jay_outputs.clear();
self.screencasts.clear(); for screencast in self.screencasts.lock().drain_values() {
self.screencopies.clear(); screencast.do_destroy();
self.ext_copy_sessions.clear(); }
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.ext_workspace_groups.clear();
self.latch_event.clear(); self.latch_event.clear();
self.vblank_event.clear(); self.vblank_event.clear();