From f871ea1d675d20c75613d3af80ad8b54731a05ee Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 19:37:00 +0200 Subject: [PATCH 01/17] tree: update surface position when geometry changes --- src/ifs/wl_surface/xdg_surface.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index b88c0d9a..645f79d1 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -189,16 +189,20 @@ impl XdgSurface { } } + fn update_surface_position(&self) { + let (mut x1, mut y1) = self.absolute_desired_extents.get().position(); + if let Some(geo) = self.geometry.get() { + x1 -= geo.x1(); + y1 -= geo.y1(); + } + self.surface.set_absolute_position(x1, y1); + self.update_popup_positions(); + } + fn set_absolute_desired_extents(&self, ext: &Rect) { let prev = self.absolute_desired_extents.replace(*ext); if ext.position() != prev.position() { - let (mut x1, mut y1) = (ext.x1(), ext.y1()); - if let Some(geo) = self.geometry.get() { - x1 -= geo.x1(); - y1 -= geo.y1(); - } - self.surface.set_absolute_position(x1, y1); - self.update_popup_positions(); + self.update_surface_position(); } } @@ -489,8 +493,11 @@ impl SurfaceExt for XdgSurface { } if let Some(pending) = &mut pending.xdg_surface { if let Some(geometry) = pending.geometry.take() { - self.geometry.set(Some(geometry)); - self.update_extents(); + let prev = self.geometry.replace(Some(geometry)); + if prev != Some(geometry) { + self.update_extents(); + self.update_surface_position(); + } } } Ok(()) From af15e20e9b883b7fdc7f9ff3872da0b9ca621f94 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 19:43:54 +0200 Subject: [PATCH 02/17] wl_surface: move dnd logic to separate module --- src/gfx_api.rs | 9 +----- src/ifs/ipc/wl_data_device.rs | 5 ++- src/ifs/wl_seat.rs | 12 +++---- src/ifs/wl_seat/pointer_owner.rs | 26 +++++++-------- src/ifs/wl_surface.rs | 31 +++++++++--------- src/ifs/wl_surface/dnd_icon.rs | 55 ++++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 src/ifs/wl_surface/dnd_icon.rs diff --git a/src/gfx_api.rs b/src/gfx_api.rs index ec622241..51027c19 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -370,14 +370,7 @@ impl dyn GfxFramebuffer { } } if let Some(dnd_icon) = seat.dnd_icon() { - let extents = dnd_icon.extents.get().move_( - x.round_down() + dnd_icon.buf_x.get(), - y.round_down() + dnd_icon.buf_y.get(), - ); - if extents.intersects(&rect) { - let (x, y) = rect.translate(extents.x1(), extents.y1()); - renderer.render_surface(&dnd_icon, x, y, None); - } + dnd_icon.render(&mut renderer, &rect, x.round_down(), y.round_down()); } if render_cursor { let cursor_user_group = seat.cursor_group(); diff --git a/src/ifs/ipc/wl_data_device.rs b/src/ifs/ipc/wl_data_device.rs index 772438bd..5362dbc8 100644 --- a/src/ifs/ipc/wl_data_device.rs +++ b/src/ifs/ipc/wl_data_device.rs @@ -9,7 +9,7 @@ use { IterableIpcVtable, OfferData, Role, }, wl_seat::{WlSeatError, WlSeatGlobal}, - wl_surface::{SurfaceRole, WlSurfaceError}, + wl_surface::WlSurfaceError, }, leaks::Tracker, object::{Object, Version}, @@ -115,8 +115,7 @@ impl WlDataDeviceRequestHandler for WlDataDevice { }; let icon = if req.icon.is_some() { let icon = self.client.lookup(req.icon)?; - icon.set_role(SurfaceRole::DndIcon)?; - Some(icon) + Some(icon.into_dnd_icon(&self.seat)?) } else { None }; diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index d165878b..08847360 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -61,7 +61,7 @@ use { zwp_pointer_gesture_swipe_v1::ZwpPointerGestureSwipeV1, zwp_relative_pointer_v1::ZwpRelativePointerV1, }, - wl_surface::WlSurface, + wl_surface::{dnd_icon::DndIcon, WlSurface}, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, leaks::Tracker, @@ -723,11 +723,11 @@ impl WlSeatGlobal { self: &Rc, origin: &Rc, source: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { if let Some(icon) = &icon { - icon.set_output(&self.pointer_cursor.output()); + icon.surface().set_output(&self.pointer_cursor.output()); } self.pointer_owner .start_drag(self, origin, source, icon, serial) @@ -819,7 +819,7 @@ impl WlSeatGlobal { self.primary_selection.get() } - pub fn dnd_icon(&self) -> Option> { + pub fn dnd_icon(&self) -> Option> { self.pointer_owner.dnd_icon() } @@ -926,7 +926,7 @@ impl WlSeatGlobal { pub fn set_visible(&self, visible: bool) { self.cursor_user_group.set_visible(visible); if let Some(icon) = self.dnd_icon() { - icon.set_visible(visible); + icon.surface().set_visible(visible); } if let Some(tl_drag) = self.toplevel_drag() { if let Some(tl) = tl_drag.toplevel.get() { @@ -965,7 +965,7 @@ impl WlSeatGlobal { impl CursorUserOwner for WlSeatGlobal { fn output_changed(&self, output: &Rc) { if let Some(dnd) = self.pointer_owner.dnd_icon() { - dnd.set_output(output); + dnd.surface().set_output(output); } if let Some(drag) = self.pointer_owner.toplevel_drag() { if let Some(tl) = drag.toplevel.get() { diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index ed815ce5..5a436f2a 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -10,7 +10,7 @@ use { wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT, BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE, }, - wl_surface::WlSurface, + wl_surface::{dnd_icon::DndIcon, WlSurface}, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, state::DeviceHandlerData, @@ -120,7 +120,7 @@ impl PointerOwnerHolder { seat: &Rc, origin: &Rc, source: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { self.owner @@ -144,7 +144,7 @@ impl PointerOwnerHolder { self.owner.get().dnd_target_removed(seat); } - pub fn dnd_icon(&self) -> Option> { + pub fn dnd_icon(&self) -> Option> { self.owner.get().dnd_icon() } @@ -211,7 +211,7 @@ trait PointerOwner { seat: &Rc, origin: &Rc, source: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { let _ = origin; @@ -232,7 +232,7 @@ trait PointerOwner { fn dnd_target_removed(&self, seat: &Rc) { self.cancel_dnd(seat); } - fn dnd_icon(&self) -> Option> { + fn dnd_icon(&self) -> Option> { None } fn toplevel_drag(&self) -> Option> { @@ -264,7 +264,7 @@ struct DndPointerOwner { button: u32, dnd: Dnd, target: CloneCell>, - icon: CloneCell>>, + icon: CloneCell>>, pos_x: Cell, pos_y: Cell, } @@ -446,7 +446,7 @@ impl PointerOwner for SimpleGrabPointerOwner { seat: &Rc, origin: &Rc, src: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { self.usecase @@ -486,7 +486,7 @@ impl PointerOwner for DndPointerOwner { } } if let Some(icon) = self.icon.get() { - icon.set_dnd_icon_seat(seat.id(), None); + icon.disable(); } seat.pointer_owner.set_default_pointer_owner(seat); seat.tree_changed.trigger(); @@ -542,7 +542,7 @@ impl PointerOwner for DndPointerOwner { ipc::detach_seat(&**src, seat); } if let Some(icon) = self.icon.get() { - icon.set_dnd_icon_seat(seat.id(), None); + icon.disable(); } seat.pointer_owner.set_default_pointer_owner(seat); seat.tree_changed.trigger(); @@ -558,7 +558,7 @@ impl PointerOwner for DndPointerOwner { seat.state.tree_changed(); } - fn dnd_icon(&self) -> Option> { + fn dnd_icon(&self) -> Option> { self.icon.get() } @@ -593,7 +593,7 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static { seat: &Rc, origin: &Rc, src: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { let _ = grab; @@ -638,7 +638,7 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase { seat: &Rc, origin: &Rc, src: Option>, - icon: Option>, + icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { let button = match grab.buttons.iter().next() { @@ -655,7 +655,7 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase { return Ok(()); } if let Some(icon) = &icon { - icon.set_dnd_icon_seat(seat.id, Some(seat)); + icon.enable(); } if let Some(new) = &src { ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?; diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 9bb58ca4..1e66b20d 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1,5 +1,6 @@ pub mod commit_timeline; pub mod cursor; +pub mod dnd_icon; pub mod ext_session_lock_surface_v1; pub mod wl_subsurface; pub mod wp_alpha_modifier_surface_v1; @@ -41,6 +42,7 @@ use { wl_surface::{ commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError}, cursor::CursorSurface, + dnd_icon::DndIcon, wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface}, wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1, wp_fractional_scale_v1::WpFractionalScaleV1, @@ -273,7 +275,7 @@ pub struct WlSurface { seat_state: NodeSeatState, toplevel: CloneCell>>, cursors: SmallMap, 1>, - dnd_icons: SmallMap, 1>, + dnd_icons: SmallMap, 1>, pub tracker: Tracker, idle_inhibitors: SmallMap, 1>, viewporter: CloneCell>>, @@ -758,6 +760,17 @@ impl WlSurface { Ok(()) } + pub fn into_dnd_icon( + self: &Rc, + seat: &Rc, + ) -> Result, WlSurfaceError> { + self.set_role(SurfaceRole::DndIcon)?; + Ok(Rc::new(DndIcon { + surface: self.clone(), + seat: seat.clone(), + })) + } + fn unset_ext(&self) { self.ext.set(self.client.state.none_surface_ext.clone()); } @@ -806,8 +819,8 @@ impl WlSurface { } fn unset_dnd_icons(&self) { - while let Some((_, seat)) = self.dnd_icons.pop() { - seat.remove_dnd_icon() + while let Some((_, dnd_icon)) = self.dnd_icons.pop() { + dnd_icon.seat.remove_dnd_icon(); } } } @@ -1349,18 +1362,6 @@ impl WlSurface { .consume_pending_child(self, child, &mut consume) } - pub fn set_dnd_icon_seat(&self, id: SeatId, seat: Option<&Rc>) { - match seat { - None => { - self.dnd_icons.remove(&id); - } - Some(seat) => { - self.dnd_icons.insert(id, seat.clone()); - } - } - self.set_visible(self.dnd_icons.is_not_empty() && self.client.state.root_visible()); - } - pub fn alpha(&self) -> Option { self.alpha.get() } diff --git a/src/ifs/wl_surface/dnd_icon.rs b/src/ifs/wl_surface/dnd_icon.rs new file mode 100644 index 00000000..e41f11aa --- /dev/null +++ b/src/ifs/wl_surface/dnd_icon.rs @@ -0,0 +1,55 @@ +use { + crate::{ + ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, + rect::Rect, + renderer::Renderer, + }, + std::rc::Rc, +}; + +pub struct DndIcon { + pub(super) surface: Rc, + pub(super) seat: Rc, +} + +impl DndIcon { + pub fn surface(&self) -> &Rc { + &self.surface + } + + fn update_visible(&self) { + let is_visible = + self.surface.dnd_icons.is_not_empty() && self.surface.client.state.root_visible(); + self.surface.set_visible(is_visible); + } + + pub fn enable(self: &Rc) { + self.surface.dnd_icons.insert(self.seat.id(), self.clone()); + self.update_visible(); + } + + pub fn disable(self: &Rc) { + self.surface.dnd_icons.remove(&self.seat.id()); + self.update_visible(); + } + + pub fn surface_position(&self, seat_x: i32, seat_y: i32) -> (i32, i32) { + ( + seat_x + self.surface.buf_x.get(), + seat_y + self.surface.buf_y.get(), + ) + } + + fn extents(&self, x: i32, y: i32) -> Rect { + let (x, y) = self.surface_position(x, y); + self.surface.extents.get().move_(x, y) + } + + pub fn render(&self, renderer: &mut Renderer<'_>, cursor_rect: &Rect, x: i32, y: i32) { + let extents = self.extents(x, y); + if extents.intersects(&cursor_rect) { + let (x, y) = cursor_rect.translate(extents.x1(), extents.y1()); + renderer.render_surface(&self.surface, x, y, None); + } + } +} From 50b6999b0c06a17fe370ca24ab7ccad364113dfd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 19:46:57 +0200 Subject: [PATCH 03/17] wl_surface: store surface damage --- src/ifs/wl_surface.rs | 86 +++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 1e66b20d..c319354c 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -399,7 +399,8 @@ struct PendingState { input_region: Option>>, frame_request: Vec>, damage_full: bool, - damage: Vec, + buffer_damage: Vec, + surface_damage: Vec, presentation_feedback: Vec>, src_rect: Option>, dst_size: Option>, @@ -471,14 +472,23 @@ impl PendingState { self.offset = (dx1 + dx2, dy1 + dy2); } self.frame_request.append(&mut next.frame_request); + self.damage_full |= mem::take(&mut next.damage_full); if !self.damage_full { - if self.damage.len() + next.damage.len() > MAX_DAMAGE { - self.damage_full = true; - self.damage.clear(); + if self.buffer_damage.len() + next.buffer_damage.len() > MAX_DAMAGE { + self.damage_full(); } else { - self.damage.append(&mut next.damage); + self.buffer_damage.append(&mut next.buffer_damage); } } + if !self.damage_full { + if self.surface_damage.len() + next.surface_damage.len() > MAX_DAMAGE { + self.damage_full(); + } else { + self.surface_damage.append(&mut next.surface_damage); + } + } + next.surface_damage.clear(); + next.buffer_damage.clear(); mem::swap( &mut self.presentation_feedback, &mut next.presentation_feedback, @@ -520,6 +530,12 @@ impl PendingState { _ => Ok(()), } } + + fn damage_full(&mut self) { + self.damage_full = true; + self.buffer_damage.clear(); + self.surface_damage.clear(); + } } #[derive(Default)] @@ -823,6 +839,32 @@ impl WlSurface { dnd_icon.seat.remove_dnd_icon(); } } + + fn do_damage( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + f: F, + ) -> Result<(), WlSurfaceError> + where + F: Fn(&mut PendingState) -> &mut Vec, + { + let pending = &mut *self.pending.borrow_mut(); + if !pending.damage_full { + let damage = f(pending); + if damage.len() >= MAX_DAMAGE { + pending.damage_full(); + } else { + let Some(rect) = Rect::new_sized(x, y, width, height) else { + return Err(WlSurfaceError::InvalidRect); + }; + damage.push(rect); + } + } + Ok(()) + } } const MAX_DAMAGE: usize = 32; @@ -879,11 +921,10 @@ impl WlSurfaceRequestHandler for WlSurface { Ok(()) } - fn damage(&self, _req: Damage, _slf: &Rc) -> Result<(), Self::Error> { - let pending = &mut *self.pending.borrow_mut(); - pending.damage.clear(); - pending.damage_full = true; - Ok(()) + fn damage(&self, req: Damage, _slf: &Rc) -> Result<(), Self::Error> { + self.do_damage(req.x, req.y, req.width, req.height, |p| { + &mut p.surface_damage + }) } fn frame(&self, req: Frame, _slf: &Rc) -> Result<(), Self::Error> { @@ -949,19 +990,9 @@ impl WlSurfaceRequestHandler for WlSurface { } fn damage_buffer(&self, req: DamageBuffer, _slf: &Rc) -> Result<(), Self::Error> { - let pending = &mut *self.pending.borrow_mut(); - if !pending.damage_full { - if pending.damage.len() >= MAX_DAMAGE || self.shm_texture.is_none() { - pending.damage.clear(); - pending.damage_full = true; - } else { - let Some(rect) = Rect::new_sized(req.x, req.y, req.width, req.height) else { - return Err(WlSurfaceError::InvalidRect); - }; - pending.damage.push(rect); - } - } - Ok(()) + self.do_damage(req.x, req.y, req.width, req.height, |p| { + &mut p.buffer_damage + }) } fn offset(&self, req: Offset, _slf: &Rc) -> Result<(), Self::Error> { @@ -1021,13 +1052,11 @@ impl WlSurface { old_raw_size = Some(buffer.buffer.rect); } if let Some(buffer) = buffer_change { - let damage = match pending.damage_full { + let damage = match pending.damage_full || pending.surface_damage.is_not_empty() { true => None, - false => Some(&pending.damage[..]), + false => Some(&pending.buffer_damage[..]), }; buffer.update_texture_or_log(self, damage); - pending.damage.clear(); - pending.damage_full = false; let (sync, release_sync) = match pending.explicit_sync { false => (AcquireSync::Implicit, ReleaseSync::Implicit), true => (AcquireSync::Unnecessary, ReleaseSync::Explicit), @@ -1188,6 +1217,9 @@ impl WlSurface { } self.ext.get().after_apply_commit(); self.client.state.damage(); + pending.buffer_damage.clear(); + pending.surface_damage.clear(); + pending.damage_full = false; Ok(()) } From ac5a5ca9498f82e46d4892739da501ee596f2ef0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:01:59 +0200 Subject: [PATCH 04/17] toplevel-drag: move render logic into toplevel drag --- src/cursor_user.rs | 5 +++++ src/gfx_api.rs | 14 +++----------- src/ifs/xdg_toplevel_drag_v1.rs | 11 +++++++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 0c66c675..9459e393 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -352,6 +352,11 @@ impl CursorUser { self.pos.get() } + pub fn position_int(&self) -> (i32, i32) { + let (x, y) = self.pos.get(); + (x.round_down(), y.round_down()) + } + pub fn set_position(&self, mut x: Fixed, mut y: Fixed) -> (Fixed, Fixed) { let x_int = x.round_down(); let y_int = y.round_down(); diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 51027c19..a36c9a4e 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -345,7 +345,7 @@ impl dyn GfxFramebuffer { if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { - let (x, y) = seat.pointer_cursor().position(); + let (x, y) = seat.pointer_cursor().position_int(); if let Some(im) = seat.input_method() { for (_, popup) in &im.popups { if popup.surface.node_visible() { @@ -359,18 +359,10 @@ impl dyn GfxFramebuffer { } } if let Some(drag) = seat.toplevel_drag() { - if let Some(tl) = drag.toplevel.get() { - if tl.xdg.surface.buffer.get().is_some() { - let (x, y) = rect.translate( - x.round_down() - drag.x_off.get(), - y.round_down() - drag.y_off.get(), - ); - renderer.render_xdg_surface(&tl.xdg, x, y, None) - } - } + drag.render(&mut renderer, &rect, x, y); } if let Some(dnd_icon) = seat.dnd_icon() { - dnd_icon.render(&mut renderer, &rect, x.round_down(), y.round_down()); + dnd_icon.render(&mut renderer, &rect, x, y); } if render_cursor { let cursor_user_group = seat.cursor_group(); diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index d8ce9513..f6f062cd 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -7,6 +7,8 @@ use { }, leaks::Tracker, object::{Object, Version}, + rect::Rect, + renderer::Renderer, utils::clonecell::CloneCell, wire::{xdg_toplevel_drag_v1::*, XdgToplevelDragV1Id}, }, @@ -49,6 +51,15 @@ impl XdgToplevelDragV1 { tl.drag.take(); } } + + pub fn render(&self, renderer: &mut Renderer<'_>, cursor_rect: &Rect, x: i32, y: i32) { + if let Some(tl) = self.toplevel.get() { + if tl.xdg.surface.buffer.get().is_some() { + let (x, y) = cursor_rect.translate(x - self.x_off.get(), y - self.y_off.get()); + renderer.render_xdg_surface(&tl.xdg, x, y, None) + } + } + } } impl XdgToplevelDragV1RequestHandler for XdgToplevelDragV1 { From ac8b22f2bba8f502097bc358166d89f6049cc62f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:03:20 +0200 Subject: [PATCH 05/17] toplevel-drag: correctly update surface positions --- src/ifs/wl_seat/event_handling.rs | 14 ++++++++++++-- src/ifs/xdg_toplevel_drag_v1.rs | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index f64ab341..51bcb5a4 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -410,6 +410,16 @@ impl WlSeatGlobal { } } + fn set_pointer_cursor_position(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) { + let (x, y) = self.pointer_cursor.set_position(x, y); + if let Some(td) = self.pointer_owner.toplevel_drag() { + let x_int = x.round_down(); + let y_int = y.round_down(); + td.move_(x_int, y_int); + } + (x, y) + } + fn connector_position_event( self: &Rc, time_usec: u64, @@ -424,7 +434,7 @@ impl WlSeatGlobal { let pos = output.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); - (x, y) = self.pointer_cursor.set_position(x, y); + (x, y) = self.set_pointer_cursor_position(x, y); if let Some(c) = self.constraint.get() { if c.ty == ConstraintType::Lock || !c.contains(x.round_down(), y.round_down()) { c.deactivate(); @@ -484,7 +494,7 @@ impl WlSeatGlobal { dy_unaccelerated, ); }); - self.pointer_cursor.set_position(x, y); + self.set_pointer_cursor_position(x, y); self.cursor_moved(time_usec); } diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index f6f062cd..df34ec09 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -9,6 +9,7 @@ use { object::{Object, Version}, rect::Rect, renderer::Renderer, + tree::ToplevelNode, utils::clonecell::CloneCell, wire::{xdg_toplevel_drag_v1::*, XdgToplevelDragV1Id}, }, @@ -52,6 +53,18 @@ impl XdgToplevelDragV1 { } } + fn move2(&self, x: i32, y: i32) { + if let Some(tl) = self.toplevel.get() { + let extents = tl.xdg.absolute_desired_extents.get(); + let extents = extents.at_point(x - self.x_off.get(), y - self.y_off.get()); + tl.clone().tl_change_extents(&extents); + } + } + + pub fn move_(&self, x: i32, y: i32) { + self.move2(x, y); + } + pub fn render(&self, renderer: &mut Renderer<'_>, cursor_rect: &Rect, x: i32, y: i32) { if let Some(tl) = self.toplevel.get() { if tl.xdg.surface.buffer.get().is_some() { @@ -107,6 +120,10 @@ impl XdgToplevelDragV1 { }; tl.prepare_toplevel_drag(); self.client.state.tree_changed(); + if let Some(seat) = self.source.data.seat.get() { + let (x, y) = seat.pointer_cursor().position_int(); + self.move2(x, y) + } } pub fn finish_drag(&self, seat: &Rc) { From a9bba47449fecc7eee9d10f24f3fe7b1d491aa04 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:03:57 +0200 Subject: [PATCH 06/17] toplevel-drag: correctly update visibility --- src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 5c93bd6e..71f96da3 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -152,8 +152,10 @@ impl XdgToplevel { } pub fn send_current_configure(&self) { - let rect = self.xdg.absolute_desired_extents.get(); - self.send_configure_checked(rect.width(), rect.height()); + if self.drag.is_none() { + let rect = self.xdg.absolute_desired_extents.get(); + self.send_configure_checked(rect.width(), rect.height()); + } self.xdg.do_send_configure(); } @@ -404,8 +406,13 @@ impl XdgToplevel { self.xdg.set_output(&seat.get_output()); } self.toplevel_data.broadcast(self.clone()); + self.tl_set_visible(self.state.root_visible()); } self.extents_changed(); + } else { + if self.is_mapped.replace(false) { + self.tl_set_visible(false); + } } return; } From 5a53b742655817b79b2a22f4c192e18be37bc816 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:23:30 +0200 Subject: [PATCH 07/17] sub-surface: update sub-surface positions if parent does not move --- src/ifs/wl_surface/wl_subsurface.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index 377226f0..2e7d37c6 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -138,6 +138,9 @@ impl WlSubsurface { if let Some((x, y)) = pending.position.take() { self.position .set(self.surface.buffer_abs_pos.get().at_point(x, y)); + let (parent_x, parent_y) = self.parent.buffer_abs_pos.get().position(); + self.surface + .set_absolute_position(parent_x + x, parent_y + y); self.parent.need_extents_update.set(true); } Ok(()) @@ -172,6 +175,8 @@ impl WlSubsurface { self.surface.set_toplevel(self.parent.toplevel.get()); self.surface.ext.set(self.clone()); update_children_attach(self)?; + let (x, y) = self.parent.buffer_abs_pos.get().position(); + self.surface.set_absolute_position(x, y); Ok(()) } From 44bdca8235f23f11d64d7f73539c31af19b0bc39 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:27:27 +0200 Subject: [PATCH 08/17] session-lock: move unlock logic to single place --- src/ifs/ext_session_lock_v1.rs | 11 +---------- src/ifs/jay_compositor.rs | 13 +++---------- src/state.rs | 12 ++++++++++++ 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/ifs/ext_session_lock_v1.rs b/src/ifs/ext_session_lock_v1.rs index 51479322..5bb3bb07 100644 --- a/src/ifs/ext_session_lock_v1.rs +++ b/src/ifs/ext_session_lock_v1.rs @@ -87,16 +87,7 @@ impl ExtSessionLockV1RequestHandler for ExtSessionLockV1 { return Err(ExtSessionLockV1Error::NeverLocked); } if !self.finished.get() { - let state = &self.client.state; - state.lock.locked.set(false); - state.lock.lock.take(); - for output in state.root.outputs.lock().values() { - if let Some(surface) = output.set_lock_surface(None) { - surface.destroy_node(); - } - } - state.tree_changed(); - state.damage(); + self.client.state.do_unlock(); } self.client.remove_obj(self)?; Ok(()) diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index ddbdba84..e041a384 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -216,19 +216,12 @@ impl JayCompositorRequestHandler for JayCompositor { fn unlock(&self, _req: Unlock, _slf: &Rc) -> Result<(), Self::Error> { let state = &self.client.state; - if state.lock.locked.replace(false) { - if let Some(lock) = state.lock.lock.take() { + if state.lock.locked.get() { + if let Some(lock) = state.lock.lock.get() { lock.finish(); } - for output in state.root.outputs.lock().values() { - if let Some(surface) = output.set_lock_surface(None) { - surface.destroy_node(); - } - } - state.tree_changed(); - state.damage(); + state.do_unlock(); } - self.client.symmetric_delete.set(true); Ok(()) } diff --git a/src/state.rs b/src/state.rs index 9d51140d..07af9b1b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -736,6 +736,18 @@ impl State { } } + pub fn do_unlock(&self) { + self.lock.locked.set(false); + self.lock.lock.take(); + for output in self.root.outputs.lock().values() { + if let Some(surface) = output.set_lock_surface(None) { + surface.destroy_node(); + } + } + self.tree_changed(); + self.damage(); + } + pub fn clear(&self) { self.lock.lock.take(); self.xwayland.handler.borrow_mut().take(); From 0632990f61d738bd7454aa2b60e81a72e2a61094 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 12 Jul 2024 20:05:22 +0200 Subject: [PATCH 09/17] session-lock: fix surface initialization --- src/ifs/ext_session_lock_v1.rs | 1 + src/ifs/wl_surface/ext_session_lock_surface_v1.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ifs/ext_session_lock_v1.rs b/src/ifs/ext_session_lock_v1.rs index 5bb3bb07..cc08e3e2 100644 --- a/src/ifs/ext_session_lock_v1.rs +++ b/src/ifs/ext_session_lock_v1.rs @@ -72,6 +72,7 @@ impl ExtSessionLockV1RequestHandler for ExtSessionLockV1 { node.set_lock_surface(Some(new.clone())); let pos = node.global.pos.get(); new.change_extents(pos); + new.surface.set_output(&node); self.client.state.tree_changed(); } } diff --git a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs index 485a9a75..78a39db3 100644 --- a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs +++ b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs @@ -44,7 +44,7 @@ impl ExtSessionLockSurfaceV1 { pub fn change_extents(&self, rect: Rect) { self.send_configure(rect.width(), rect.height()); - self.surface.set_absolute_position(rect.x1(), rect.x2()); + self.surface.set_absolute_position(rect.x1(), rect.y1()); } fn send_configure(&self, width: i32, height: i32) { From a6cf1a1b3dd4f210b4c4c234fb994689e2b42cab Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:30:26 +0200 Subject: [PATCH 10/17] render: correctly render single-pixel buffers on scaled outputs --- src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer.rs b/src/renderer.rs index 12c2bfe7..fdd7df9a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -481,7 +481,7 @@ impl Renderer<'_> { if let Some(alpha) = alpha { color = color * alpha; } - self.base.fill_boxes(&[rect], &color); + self.base.fill_scaled_boxes(&[rect], &color); } } } else { From d8d6be1ef3e79a59ef9cfcd21eaf281a79ccfbf1 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 23:23:24 +0200 Subject: [PATCH 11/17] surface: dispatch frame request immediately if buffer has been presented --- src/backends/metal/video.rs | 1 + src/ifs/wl_surface.rs | 27 +++++++++++++++++++++++++-- src/macros.rs | 5 +++++ src/renderer.rs | 17 ++++++++++++++--- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 90a8426e..e8e63fff 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -923,6 +923,7 @@ impl MetalConnector { if let Some(node) = self.state.root.outputs.get(&self.connector_id) { let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut rr = self.render_result.borrow_mut(); + rr.output_id = node.id; let fb = self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?; rr.dispatch_frame_requests(); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index c319354c..103b1e50 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -63,7 +63,7 @@ use { renderer::Renderer, tree::{ ContainerNode, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, - OutputNode, PlaceholderNode, ToplevelNode, + OutputNode, OutputNodeId, PlaceholderNode, ToplevelNode, }, utils::{ cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, @@ -82,7 +82,7 @@ use { xwayland::XWaylandEvent, }, ahash::AHashMap, - isnt::std_1::primitive::IsntSliceExt, + isnt::std_1::{primitive::IsntSliceExt, vec::IsntVecExt}, jay_config::video::Transform, std::{ cell::{Cell, RefCell}, @@ -265,6 +265,7 @@ pub struct WlSurface { pub buffer_abs_pos: Cell, pub need_extents_update: Cell, pub buffer: CloneCell>>, + buffer_presented: Cell, pub shm_texture: CloneCell>>, pub buf_x: NumCell, pub buf_y: NumCell, @@ -536,6 +537,10 @@ impl PendingState { self.buffer_damage.clear(); self.surface_damage.clear(); } + + fn has_damage(&self) -> bool { + self.damage_full || self.buffer_damage.is_not_empty() || self.surface_damage.is_not_empty() + } } #[derive(Default)] @@ -571,6 +576,7 @@ impl WlSurface { buffer_abs_pos: Cell::new(Default::default()), need_extents_update: Default::default(), buffer: Default::default(), + buffer_presented: Default::default(), shm_texture: Default::default(), buf_x: Default::default(), buf_y: Default::default(), @@ -1073,6 +1079,9 @@ impl WlSurface { release, }; self.buffer.set(Some(Rc::new(surface_buffer))); + if pending.has_damage() { + self.buffer_presented.set(false); + } } else { self.shm_texture.take(); self.buf_x.set(0); @@ -1216,6 +1225,14 @@ impl WlSurface { } } self.ext.get().after_apply_commit(); + if self.visible.get() { + if self.buffer_presented.get() { + for fr in self.frame_requests.borrow_mut().drain(..) { + fr.send_done(); + let _ = fr.client.remove_obj(&*fr); + } + } + } self.client.state.damage(); pending.buffer_damage.clear(); pending.surface_damage.clear(); @@ -1328,6 +1345,12 @@ impl WlSurface { self.seat_state.set_visible(self, visible); } + pub fn presented(&self, on: OutputNodeId) { + if on == self.output.get().id { + self.buffer_presented.set(true); + } + } + pub fn detach_node(&self, set_invisible: bool) { for (_, constraint) in &self.constraints { constraint.deactivate(); diff --git a/src/macros.rs b/src/macros.rs index 680d771d..e9327d7b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -294,6 +294,11 @@ macro_rules! tree_id { pub fn raw(&self) -> u32 { self.0 } + + #[allow(dead_code)] + pub fn none() -> Self { + Self(0) + } } impl std::fmt::Display for $id { diff --git a/src/renderer.rs b/src/renderer.rs index fdd7df9a..7ee9106e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -17,8 +17,8 @@ use { state::State, theme::Color, tree::{ - ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData, - ToplevelNodeBase, WorkspaceNode, + ContainerNode, DisplayNode, FloatNode, OutputNode, OutputNodeId, PlaceholderNode, + ToplevelData, ToplevelNodeBase, WorkspaceNode, }, }, std::{ @@ -31,10 +31,20 @@ use { pub mod renderer_base; -#[derive(Default)] pub struct RenderResult { pub frame_requests: Vec>, pub presentation_feedbacks: Vec>, + pub output_id: OutputNodeId, +} + +impl Default for RenderResult { + fn default() -> Self { + Self { + frame_requests: Default::default(), + presentation_feedbacks: Default::default(), + output_id: OutputNodeId::none(), + } + } } impl RenderResult { @@ -441,6 +451,7 @@ impl Renderer<'_> { let mut fbs = surface.presentation_feedback.borrow_mut(); result.presentation_feedbacks.extend(fbs.drain(..)); } + surface.presented(result.output_id); } } From bb9e6ba3b5845c54a91bfe2efcf370491af63af2 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 11 Jul 2024 17:39:18 +0200 Subject: [PATCH 12/17] all: fetch current time only once per iteration --- src/async_engine.rs | 15 ++++++++++ src/backends/metal.rs | 3 +- src/backends/metal/video.rs | 3 +- src/backends/x.rs | 11 ++++---- src/client/tasks.rs | 5 ++-- src/cursor.rs | 11 +++++--- src/cursor_user.rs | 4 ++- src/ifs/ext_idle_notifier_v1.rs | 3 +- src/ifs/wl_seat.rs | 3 +- src/ifs/wl_seat/gesture_owner.rs | 11 +++----- src/ifs/wl_seat/tablet.rs | 5 ++-- src/ifs/wl_seat/tablet/pad.rs | 4 +-- src/ifs/wl_seat/tablet/pad_owner.rs | 9 ++++-- src/ifs/wl_seat/tablet/tool_owner.rs | 8 +++--- src/it/test_backend.rs | 20 ++++++++------ src/it/test_ifs/test_virtual_keyboard.rs | 3 +- src/state.rs | 18 ++++++++++++ src/time.rs | 35 ++++-------------------- src/tree/output.rs | 3 +- src/wheel.rs | 18 ++++-------- src/xwayland/xwm.rs | 4 +-- 21 files changed, 99 insertions(+), 97 deletions(-) diff --git a/src/async_engine.rs b/src/async_engine.rs index 8efcf941..3b323b4e 100644 --- a/src/async_engine.rs +++ b/src/async_engine.rs @@ -5,6 +5,7 @@ pub use {crate::async_engine::ae_yield::Yield, ae_task::SpawnedFuture}; use { crate::{ async_engine::ae_task::Runnable, + time::Time, utils::{array, numcell::NumCell, syncqueue::SyncQueue}, }, std::{ @@ -33,6 +34,7 @@ pub struct AsyncEngine { stash: RefCell>, yield_stash: RefCell>, stopped: Cell, + now: Cell>, } impl AsyncEngine { @@ -45,6 +47,7 @@ impl AsyncEngine { stash: Default::default(), yield_stash: Default::default(), stopped: Cell::new(false), + now: Default::default(), }) } @@ -84,6 +87,7 @@ impl AsyncEngine { let mut stash = self.stash.borrow_mut(); let mut yield_stash = self.yield_stash.borrow_mut(); while self.num_queued.get() > 0 { + self.now.take(); self.iteration.fetch_add(1); let mut phase = 0; while phase < NUM_PHASES { @@ -119,4 +123,15 @@ impl AsyncEngine { fn iteration(&self) -> u64 { self.iteration.get() } + + pub fn now(&self) -> Time { + match self.now.get() { + Some(t) => t, + None => { + let now = Time::now_unchecked(); + self.now.set(Some(now)); + now + } + } + } } diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 4b5e8c03..880b18da 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -29,7 +29,6 @@ use { }, logind::{LogindError, Session}, state::State, - time::now_usec, udev::{Udev, UdevError, UdevMonitor}, utils::{ clonecell::{CloneCell, UnsafeCellCloneSafe}, @@ -469,7 +468,7 @@ impl MetalInputDevice { } fn pre_pause(&self) { - let time_usec = now_usec(); + let time_usec = self.state.now_usec(); for (key, _) in self.pressed_keys.take() { self.event(InputEvent::Key { time_usec, diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index e8e63fff..70734efe 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -18,7 +18,6 @@ use { renderer::RenderResult, state::State, theme::Color, - time::now_nsec, tree::OutputNode, udev::UdevDevice, utils::{ @@ -591,7 +590,7 @@ impl MetalConnector { }); if let Some(delta) = *DELTA { let next_present = self.next_flip_nsec.get().saturating_sub(delta); - if now_nsec() < next_present { + if self.state.now_nsec() < next_present { self.state.ring.timeout(next_present).await.unwrap(); } } diff --git a/src/backends/x.rs b/src/backends/x.rs index 47545113..172bc8b3 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -12,7 +12,6 @@ use { gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, renderer::RenderResult, state::State, - time::now_usec, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, queue::AsyncQueue, syncqueue::SyncQueue, @@ -818,7 +817,7 @@ impl XBackend { inverted: false, }); seat.mouse_event(InputEvent::AxisFrame { - time_usec: now_usec(), + time_usec: self.state.now_usec(), }); } } else { @@ -834,7 +833,7 @@ impl XBackend { n => BTN_SIDE + n - 8, }; seat.mouse_event(InputEvent::Button { - time_usec: now_usec(), + time_usec: self.state.now_usec(), button, state, }); @@ -851,7 +850,7 @@ impl XBackend { let event: XiKeyPress = event.parse()?; if let Some(seat) = self.seats.get(&event.deviceid) { seat.kb_event(InputEvent::Key { - time_usec: now_usec(), + time_usec: self.state.now_usec(), key: event.detail - 8, state, }); @@ -885,7 +884,7 @@ impl XBackend { self.mouse_seats.get(&event.deviceid), ) { seat.mouse_event(InputEvent::ConnectorPosition { - time_usec: now_usec(), + time_usec: self.state.now_usec(), connector: win.id, x: Fixed::from_1616(event.event_x), y: Fixed::from_1616(event.event_y), @@ -904,7 +903,7 @@ impl XBackend { _ => return Ok(()), }; seat.mouse_event(InputEvent::ConnectorPosition { - time_usec: now_usec(), + time_usec: self.state.now_nsec(), connector: win.id, x: Fixed::from_1616(event.event_x), y: Fixed::from_1616(event.event_y), diff --git a/src/client/tasks.rs b/src/client/tasks.rs index 5e754bf5..f06bf75e 100644 --- a/src/client/tasks.rs +++ b/src/client/tasks.rs @@ -3,7 +3,6 @@ use { async_engine::Phase, client::{Client, ClientError}, object::ObjectId, - time::Time, utils::{ buffd::{BufFdIn, BufFdOut, MsgParser}, errorfmt::ErrorFmt, @@ -11,7 +10,7 @@ use { }, }, futures_util::{select, FutureExt}, - std::{collections::VecDeque, mem, rc::Rc}, + std::{collections::VecDeque, mem, rc::Rc, time::Duration}, }; pub async fn client(data: Rc) { @@ -112,7 +111,7 @@ async fn send(data: Rc) { swapchain.commit(); mem::swap(&mut swapchain.pending, &mut buffers); } - let timeout = Time::in_ms(5000).unwrap(); + let timeout = data.state.now() + Duration::from_millis(5000); while let Some(mut cur) = buffers.pop_front() { out.flush(&mut cur, timeout).await?; data.swapchain.borrow_mut().free.push(cur); diff --git a/src/cursor.rs b/src/cursor.rs index 2e8033e7..6d8e1d58 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,5 +1,6 @@ use { crate::{ + async_engine::AsyncEngine, fixed::Fixed, format::ARGB8888, gfx_api::{AcquireSync, GfxContext, GfxError, GfxTexture, ReleaseSync}, @@ -283,13 +284,14 @@ impl ServerCursorTemplate { } } - pub fn instantiate(&self, size: u32) -> Rc { + pub fn instantiate(&self, state: &State, size: u32) -> Rc { match &self.var { ServerCursorTemplateVariant::Static(s) => Rc::new(StaticCursor { image: s.for_size(size), }), ServerCursorTemplateVariant::Animated(a) => Rc::new(AnimatedCursor { - start: Time::now_unchecked(), + start: state.now(), + eng: state.eng.clone(), next: NumCell::new(a[0].delay_ns), idx: Cell::new(0), images: a.iter().map(|c| c.for_size(size)).collect(), @@ -424,6 +426,7 @@ impl Cursor for StaticCursor { struct AnimatedCursor { start: Time, + eng: Rc, next: NumCell, idx: Cell, images: Vec, @@ -463,7 +466,7 @@ impl Cursor for AnimatedCursor { } fn tick(&self) { - let dist = Time::now_unchecked() - self.start; + let dist = self.eng.now() - self.start; if (dist.as_nanos() as u64) < self.next.get() { return; } @@ -478,7 +481,7 @@ impl Cursor for AnimatedCursor { } fn time_until_tick(&self) -> Duration { - let dist = Time::now_unchecked() - self.start; + let dist = self.eng.now() - self.start; let dist = dist.as_nanos() as u64; let nanos = self.next.get().saturating_sub(dist); Duration::from_nanos(nanos) diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 9459e393..80bd480b 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -296,7 +296,9 @@ impl CursorUser { KnownCursor::ZoomIn => &cursors.zoom_in, KnownCursor::ZoomOut => &cursors.zoom_out, }; - self.set_cursor2(Some(tpl.instantiate(self.group.size.get()))); + self.set_cursor2(Some( + tpl.instantiate(&self.group.state, self.group.size.get()), + )); } fn set_output(&self, output: &Rc) { diff --git a/src/ifs/ext_idle_notifier_v1.rs b/src/ifs/ext_idle_notifier_v1.rs index ffba3c70..b443ef89 100644 --- a/src/ifs/ext_idle_notifier_v1.rs +++ b/src/ifs/ext_idle_notifier_v1.rs @@ -5,7 +5,6 @@ use { ifs::ext_idle_notification_v1::ExtIdleNotificationV1, leaks::Tracker, object::{Object, Version}, - time::now_usec, utils::errorfmt::ErrorFmt, wire::{ext_idle_notifier_v1::*, ExtIdleNotifierV1Id}, }, @@ -81,7 +80,7 @@ impl ExtIdleNotifierV1RequestHandler for ExtIdleNotifierV1 { async fn run(n: Rc) { loop { - let now = now_usec(); + let now = n.client.state.now_usec(); let elapsed = now.saturating_sub(n.seat.last_input()); if elapsed < n.duration_usec { let res = n diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 08847360..4323f643 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -68,7 +68,6 @@ use { object::{Object, Version}, rect::Rect, state::{DeviceHandlerData, State}, - time::now_usec, tree::{ generic_node_visitor, ContainerNode, ContainerSplit, Direction, FoundNode, Node, OutputNode, ToplevelNode, WorkspaceNode, @@ -245,7 +244,7 @@ impl WlSeatGlobal { changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE), constraint: Default::default(), idle_notifications: Default::default(), - last_input_usec: Cell::new(now_usec()), + last_input_usec: Cell::new(state.now_usec()), wlr_data_devices: Default::default(), text_inputs: Default::default(), text_input: Default::default(), diff --git a/src/ifs/wl_seat/gesture_owner.rs b/src/ifs/wl_seat/gesture_owner.rs index 43f275c0..2340f0bb 100644 --- a/src/ifs/wl_seat/gesture_owner.rs +++ b/src/ifs/wl_seat/gesture_owner.rs @@ -1,8 +1,5 @@ use { - crate::{ - fixed::Fixed, ifs::wl_seat::WlSeatGlobal, time::now_usec, tree::Node, - utils::clonecell::CloneCell, - }, + crate::{fixed::Fixed, ifs::wl_seat::WlSeatGlobal, tree::Node, utils::clonecell::CloneCell}, std::rc::Rc, }; @@ -179,7 +176,7 @@ struct SwipeGesture { impl GestureOwner for SwipeGesture { fn revert_to_default(&self, seat: &Rc) { - self.swipe_end(seat, now_usec(), true); + self.swipe_end(seat, seat.state.now_usec(), true); } fn swipe_update(&self, seat: &Rc, time_usec: u64, dx: Fixed, dy: Fixed) { @@ -199,7 +196,7 @@ struct PinchGesture { impl GestureOwner for PinchGesture { fn revert_to_default(&self, seat: &Rc) { - self.pinch_end(seat, now_usec(), true); + self.pinch_end(seat, seat.state.now_usec(), true); } fn pinch_update( @@ -228,7 +225,7 @@ struct HoldGesture { impl GestureOwner for HoldGesture { fn revert_to_default(&self, seat: &Rc) { - self.hold_end(seat, now_usec(), true); + self.hold_end(seat, seat.state.now_usec(), true); } fn hold_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { diff --git a/src/ifs/wl_seat/tablet.rs b/src/ifs/wl_seat/tablet.rs index 9bf7fc44..95f28408 100644 --- a/src/ifs/wl_seat/tablet.rs +++ b/src/ifs/wl_seat/tablet.rs @@ -17,7 +17,6 @@ use { wl_surface::WlSurface, }, object::Version, - time::now_usec, tree::{FoundNode, Node}, utils::{ bindings::PerClientBindings, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -328,7 +327,7 @@ impl WlSeatGlobal { return; }; for tool in tablet.tools.lock().drain_values() { - self.tablet_handle_remove_tool(now_usec(), tool.id); + self.tablet_handle_remove_tool(tablet.seat.state.now_usec(), tool.id); } for pad in tablet.pads.lock().drain_values() { pad.pad_owner.destroy(&pad); @@ -366,7 +365,7 @@ impl WlSeatGlobal { if self.tablet.tools.is_empty() { return; } - let now = now_usec(); + let now = self.state.now_usec(); for tool in self.tablet.tools.lock().values() { tool.tool_owner.apply_changes(tool, now, None); } diff --git a/src/ifs/wl_seat/tablet/pad.rs b/src/ifs/wl_seat/tablet/pad.rs index c39c5109..83b6c54a 100644 --- a/src/ifs/wl_seat/tablet/pad.rs +++ b/src/ifs/wl_seat/tablet/pad.rs @@ -13,7 +13,7 @@ use { }, wl_surface::WlSurface, }, - time::{now_usec, usec_to_msec}, + time::usec_to_msec, utils::{clonecell::CloneCell, hash_map_ext::HashMapExt}, }, std::{cell::Cell, rc::Rc}, @@ -192,7 +192,7 @@ impl TabletPad { pub fn surface_enter(self: &Rc, n: &WlSurface) { let mut serial = n.client.pending_serial(); - let time = usec_to_msec(now_usec()); + let time = n.client.state.now_msec() as u32; self.for_each_pair(n, |tablet, pad| { pad.send_enter(serial.get(), &tablet, n); for group in &self.groups { diff --git a/src/ifs/wl_seat/tablet/pad_owner.rs b/src/ifs/wl_seat/tablet/pad_owner.rs index ab473fa3..a1b4aafc 100644 --- a/src/ifs/wl_seat/tablet/pad_owner.rs +++ b/src/ifs/wl_seat/tablet/pad_owner.rs @@ -1,7 +1,6 @@ use { crate::{ ifs::wl_seat::tablet::{PadButtonState, TabletPad}, - time::now_usec, tree::Node, utils::{clonecell::CloneCell, smallmap::SmallMap}, }, @@ -42,7 +41,9 @@ impl PadOwnerHolder { } pub fn destroy(&self, pad: &Rc) { - self.owner.get().revert_to_default(pad, now_usec()); + self.owner + .get() + .revert_to_default(pad, pad.seat.state.now_usec()); let prev = pad.node.set(pad.seat.state.root.clone()); prev.node_on_tablet_pad_leave(pad); prev.node_seat_state().remove_tablet_pad_focus(pad); @@ -53,7 +54,9 @@ impl PadOwnerHolder { } pub fn focus_root(&self, pad: &Rc) { - self.owner.get().revert_to_default(pad, now_usec()); + self.owner + .get() + .revert_to_default(pad, pad.seat.state.now_usec()); let node = pad.seat.state.root.clone(); pad.focus_node(node); } diff --git a/src/ifs/wl_seat/tablet/tool_owner.rs b/src/ifs/wl_seat/tablet/tool_owner.rs index 2fd963d7..833dd576 100644 --- a/src/ifs/wl_seat/tablet/tool_owner.rs +++ b/src/ifs/wl_seat/tablet/tool_owner.rs @@ -2,7 +2,6 @@ use { crate::{ fixed::Fixed, ifs::wl_seat::tablet::{TabletTool, TabletToolChanges, ToolButtonState}, - time::now_usec, tree::{FindTreeUsecase, FoundNode, Node}, utils::{clonecell::CloneCell, smallmap::SmallMap}, }, @@ -35,14 +34,15 @@ impl ToolOwnerHolder { pub fn destroy(&self, tool: &Rc) { let root = tool.tablet.seat.state.root.clone(); let prev = tool.node.set(root); - prev.node_on_tablet_tool_leave(tool, now_usec()); + prev.node_on_tablet_tool_leave(tool, tool.tablet.seat.state.now_usec()); prev.node_seat_state().remove_tablet_tool_focus(tool); } pub fn focus_root(&self, tool: &Rc) { self.owner.set(self.default.clone()); - let root = tool.tablet.seat.state.root.clone(); - tool.set_node(root, now_usec()); + let state = &tool.tablet.seat.state; + let root = state.root.clone(); + tool.set_node(root, state.now_usec()); } pub fn button( diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 245ab281..ae284bb7 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -15,7 +15,6 @@ use { test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH, }, state::State, - time::now_usec, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, on_change::OnChange, oserror::OsError, syncqueue::SyncQueue, @@ -75,6 +74,7 @@ impl TestBackend { chm }, name: Rc::new("default-mouse".to_string()), + state: state.clone(), }, transform_matrix: Cell::new([[1.0, 0.0], [0.0, 1.0]]), accel_speed: Cell::new(1.0), @@ -93,6 +93,7 @@ impl TestBackend { chm }, name: Rc::new("default-keyboard".to_string()), + state: state.clone(), }, }); let mode = Mode { @@ -273,7 +274,7 @@ pub struct TestMouseClick { impl Drop for TestMouseClick { fn drop(&mut self) { self.mouse.common.event(InputEvent::Button { - time_usec: now_usec(), + time_usec: self.mouse.common.state.now_usec(), button: self.button, state: KeyState::Released, }); @@ -291,7 +292,7 @@ pub struct TestBackendMouse { impl TestBackendMouse { pub fn rel(&self, dx: f64, dy: f64) { self.common.event(InputEvent::Motion { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), dx: Fixed::from_f64(dx * self.accel_speed.get()), dy: Fixed::from_f64(dy * self.accel_speed.get()), dx_unaccelerated: Fixed::from_f64(dx), @@ -301,7 +302,7 @@ impl TestBackendMouse { pub fn abs(&self, connector: &TestConnector, x: f64, y: f64) { self.common.event(InputEvent::ConnectorPosition { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), connector: connector.id, x: Fixed::from_f64(x), y: Fixed::from_f64(y), @@ -310,7 +311,7 @@ impl TestBackendMouse { pub fn click(self: &Rc, button: u32) -> TestMouseClick { self.common.event(InputEvent::Button { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), button, state: KeyState::Pressed, }); @@ -330,7 +331,7 @@ impl TestBackendMouse { inverted: false, }); self.common.event(InputEvent::AxisFrame { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), }); } @@ -348,7 +349,7 @@ impl TestBackendMouse { inverted, }); self.common.event(InputEvent::AxisFrame { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), }); } } @@ -365,7 +366,7 @@ pub struct PressedKey { impl Drop for PressedKey { fn drop(&mut self) { self.kb.common.event(InputEvent::Key { - time_usec: now_usec(), + time_usec: self.kb.common.state.now_usec(), key: self.key, state: KeyState::Released, }); @@ -375,7 +376,7 @@ impl Drop for PressedKey { impl TestBackendKb { pub fn press(self: &Rc, key: u32) -> PressedKey { self.common.event(InputEvent::Key { - time_usec: now_usec(), + time_usec: self.common.state.now_usec(), key, state: KeyState::Pressed, }); @@ -421,6 +422,7 @@ pub struct TestInputDeviceCommon { pub on_change: CloneCell>>, pub capabilities: CopyHashMap, pub name: Rc, + pub state: Rc, } impl TestInputDeviceCommon { diff --git a/src/it/test_ifs/test_virtual_keyboard.rs b/src/it/test_ifs/test_virtual_keyboard.rs index 05496e78..9b45a267 100644 --- a/src/it/test_ifs/test_virtual_keyboard.rs +++ b/src/it/test_ifs/test_virtual_keyboard.rs @@ -3,7 +3,6 @@ use { backend::KeyState, ifs::wl_seat::wl_keyboard, it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport}, - time::now_usec, wire::{zwp_virtual_keyboard_v1::*, ZwpVirtualKeyboardV1Id}, }, std::{cell::Cell, io::Write, rc::Rc}, @@ -50,7 +49,7 @@ impl TestVirtualKeyboard { }; self.tran.send(Key { self_id: self.id, - time: (now_usec() / 1000) as u32, + time: self.tran.run.state.now_msec() as u32, key, state, }) diff --git a/src/state.rs b/src/state.rs index 07af9b1b..8d24ee02 100644 --- a/src/state.rs +++ b/src/state.rs @@ -60,6 +60,7 @@ use { scale::Scale, security_context_acceptor::SecurityContextAcceptors, theme::{Color, Theme}, + time::Time, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, Node, NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, ToplevelNode, ToplevelNodeBase, @@ -1061,6 +1062,23 @@ impl State { } (self.dummy_output.get().unwrap(), 0, 0) } + + pub fn now(&self) -> Time { + self.eng.now() + } + + pub fn now_nsec(&self) -> u64 { + self.eng.now().nsec() + } + + pub fn now_usec(&self) -> u64 { + self.eng.now().usec() + } + + #[allow(dead_code)] + pub fn now_msec(&self) -> u64 { + self.eng.now().msec() + } } #[derive(Debug, Error)] diff --git a/src/time.rs b/src/time.rs index f60ac160..909d052d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -5,16 +5,9 @@ use { ops::{Add, Sub}, time::Duration, }, - thiserror::Error, uapi::c, }; -#[derive(Debug, Error)] -pub enum TimeError { - #[error("clock_gettime failed: {0}")] - ClockGettime(crate::utils::oserror::OsError), -} - #[derive(Copy, Clone)] pub struct Time(pub c::timespec); @@ -28,20 +21,6 @@ impl Debug for Time { } impl Time { - pub fn now() -> Result { - let mut time = uapi::pod_zeroed(); - if let Err(e) = uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut time) { - return Err(TimeError::ClockGettime(e.into())); - } - Ok(Self(time)) - } - - pub fn in_ms(ms: u64) -> Result { - let now = Self::now()?; - Ok(now + Duration::from_millis(ms)) - } - - #[allow(dead_code)] pub fn now_unchecked() -> Time { let mut time = uapi::pod_zeroed(); let _ = uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut time); @@ -73,6 +52,12 @@ impl Time { let nsec = self.0.tv_nsec as u64 / 1_000; sec + nsec } + + pub fn msec(self) -> u64 { + let sec = self.0.tv_sec as u64 * 1_000; + let nsec = self.0.tv_nsec as u64 / 1_000_000; + sec + nsec + } } impl Eq for Time {} @@ -124,14 +109,6 @@ impl Add for Time { } } -pub fn now_nsec() -> u64 { - Time::now_unchecked().nsec() -} - -pub fn now_usec() -> u64 { - Time::now_unchecked().usec() -} - pub fn usec_to_msec(usec: u64) -> u32 { (usec / 1000) as u32 } diff --git a/src/tree/output.rs b/src/tree/output.rs index a9652984..92aa6253 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -29,7 +29,6 @@ use { scale::Scale, state::State, text::{self, TextTexture}, - time::Time, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode, WorkspaceNode, @@ -164,7 +163,7 @@ impl OutputNode { if self.screencopies.is_empty() { return; } - let now = Time::now().unwrap(); + let now = self.state.now(); for capture in self.screencopies.lock().drain_values() { let wl_buffer = match capture.buffer.take() { Some(b) => b, diff --git a/src/wheel.rs b/src/wheel.rs index 5c7dedb5..fdbb2dc3 100644 --- a/src/wheel.rs +++ b/src/wheel.rs @@ -2,7 +2,7 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, io_uring::{IoUring, IoUringError}, - time::{Time, TimeError}, + time::Time, utils::{ buf::TypedBuf, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, numcell::NumCell, oserror::OsError, stack::Stack, @@ -28,8 +28,6 @@ pub enum WheelError { CreateFailed(#[source] OsError), #[error("Could not set the timerfd")] SetFailed(#[source] OsError), - #[error("Cannot determine the time")] - TimeError(#[from] TimeError), #[error("The timer wheel is already destroyed")] Destroyed, #[error("Could not read from the timerfd")] @@ -99,6 +97,7 @@ impl Future for WheelTimeoutFuture { pub struct WheelData { destroyed: Cell, ring: Rc, + eng: Rc, fd: Rc, next_id: NumCell, start: Time, @@ -118,9 +117,10 @@ impl Wheel { let data = Rc::new(WheelData { destroyed: Cell::new(false), ring: ring.clone(), + eng: eng.clone(), fd, next_id: NumCell::new(1), - start: Time::now()?, + start: eng.now(), current_expiration: Default::default(), dispatchers: Default::default(), expirations: Default::default(), @@ -161,13 +161,7 @@ impl Wheel { }; } let future = self.future(); - let now = match Time::now() { - Ok(n) => n, - Err(e) => { - future.data.expired.set(Some(Err(WheelError::TimeError(e)))); - return future; - } - }; + let now = self.data.eng.now(); let expiration = (now + Duration::from_millis(ms)).round_to_ms(); let current = self.data.current_expiration.get(); if current.is_none() || expiration - self.data.start < current.unwrap() - self.data.start { @@ -224,7 +218,7 @@ impl WheelData { if let Err(e) = self.ring.read(&self.fd, n.buf()).await { return Err(WheelError::Read(e)); } - let now = Time::now()?; + let now = self.eng.now(); let dist = now - self.start; { let mut expirations = self.expirations.borrow_mut(); diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 5c65d28d..da60f4b4 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -23,7 +23,6 @@ use { io_uring::{IoUring, IoUringError}, rect::Rect, state::State, - time::Time, tree::{Node, ToplevelNode}, utils::{ bitflags::BitflagsExt, buf::Buf, cell_ext::CellExt, clonecell::CloneCell, @@ -71,6 +70,7 @@ use { mem::{self}, ops::{Deref, DerefMut}, rc::Rc, + time::Duration, }, uapi::{c, OwnedFd}, }; @@ -2514,7 +2514,7 @@ struct XToWaylandTransfer { impl XToWaylandTransfer { async fn run(mut self) { - let timeout = Time::in_ms(5000).unwrap(); + let timeout = self.state.now() + Duration::from_millis(5000); let mut pos = 0; while pos < self.data.len() { let res = self From ae87b3ef7a5c26e9f15681c23ac84efa2733b300 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 11 Jul 2024 17:48:50 +0200 Subject: [PATCH 13/17] surface: include current time in frame requests --- src/backends/metal/video.rs | 2 +- src/ifs/wl_callback.rs | 4 ++-- src/ifs/wl_display.rs | 2 +- src/ifs/wl_surface.rs | 3 ++- src/ifs/wl_surface/cursor.rs | 6 +++--- src/renderer.rs | 4 ++-- src/state.rs | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 70734efe..9dfa1792 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -925,7 +925,7 @@ impl MetalConnector { rr.output_id = node.id; let fb = self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?; - rr.dispatch_frame_requests(); + rr.dispatch_frame_requests(self.state.now_msec()); let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = match &fb.direct_scanout_data { None => { diff --git a/src/ifs/wl_callback.rs b/src/ifs/wl_callback.rs index c4f16ed1..f6b78d33 100644 --- a/src/ifs/wl_callback.rs +++ b/src/ifs/wl_callback.rs @@ -24,10 +24,10 @@ impl WlCallback { } } - pub fn send_done(&self) { + pub fn send_done(&self, data: u32) { self.client.event(Done { self_id: self.id, - callback_data: 0, + callback_data: data, }); } } diff --git a/src/ifs/wl_display.rs b/src/ifs/wl_display.rs index 47153044..d71ddf0b 100644 --- a/src/ifs/wl_display.rs +++ b/src/ifs/wl_display.rs @@ -39,7 +39,7 @@ impl WlDisplayRequestHandler for WlDisplay { let cb = Rc::new(WlCallback::new(req.callback, &self.client)); track!(self.client, cb); self.client.add_client_obj(&cb)?; - cb.send_done(); + cb.send_done(0); self.client.remove_obj(&*cb)?; Ok(()) } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 103b1e50..ea9018e4 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1227,8 +1227,9 @@ impl WlSurface { self.ext.get().after_apply_commit(); if self.visible.get() { if self.buffer_presented.get() { + let now = self.client.state.now_msec() as _; for fr in self.frame_requests.borrow_mut().drain(..) { - fr.send_done(); + fr.send_done(now); let _ = fr.client.remove_obj(&*fr); } } diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 2d6208c5..44900ca0 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -86,11 +86,11 @@ impl Cursor for CursorSurface { let extents = self.surface.extents.get(); renderer.render_surface(&self.surface, -extents.x1(), -extents.y1(), None); - struct FrameRequests; + struct FrameRequests(u64); impl NodeVisitorBase for FrameRequests { fn visit_surface(&mut self, node: &Rc) { for fr in node.frame_requests.borrow_mut().drain(..) { - fr.send_done(); + fr.send_done(self.0 as _); let _ = fr.client.remove_obj(fr.deref()); } for fr in node.presentation_feedback.borrow_mut().drain(..) { @@ -100,7 +100,7 @@ impl Cursor for CursorSurface { node.node_visit_children(self); } } - FrameRequests.visit_surface(&self.surface); + FrameRequests(self.surface.client.state.now_msec()).visit_surface(&self.surface); } fn extents_at_scale(&self, scale: Scale) -> Rect { diff --git a/src/renderer.rs b/src/renderer.rs index 7ee9106e..f9353103 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -48,9 +48,9 @@ impl Default for RenderResult { } impl RenderResult { - pub fn dispatch_frame_requests(&mut self) { + pub fn dispatch_frame_requests(&mut self, now: u64) { for fr in self.frame_requests.drain(..) { - fr.send_done(); + fr.send_done(now as _); let _ = fr.client.remove_obj(&*fr); } } diff --git a/src/state.rs b/src/state.rs index 8d24ee02..c0c74536 100644 --- a/src/state.rs +++ b/src/state.rs @@ -856,7 +856,7 @@ impl State { render_hw_cursor, )?; output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); - rr.dispatch_frame_requests(); + rr.dispatch_frame_requests(self.now_msec()); Ok(sync_file) } From 73c5010e4e85e593f2057653d1dd9c5c8cd3c816 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 11 Jul 2024 20:43:47 +0200 Subject: [PATCH 14/17] render: fix sampling of transformed buffers with crop --- src/gfx_api.rs | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/gfx_api.rs b/src/gfx_api.rs index a36c9a4e..8611a357 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -72,13 +72,43 @@ impl SampleRect { let y2 = self.y2; match self.buffer_transform { None => [[x2, y1], [x1, y1], [x2, y2], [x1, y2]], - Rotate90 => [[y1, x1], [y1, x2], [y2, x1], [y2, x2]], - Rotate180 => [[x1, y2], [x2, y2], [x1, y1], [x2, y1]], - Rotate270 => [[y2, x2], [y2, x1], [y1, x2], [y1, x1]], - Flip => [[x1, y1], [x2, y1], [x1, y2], [x2, y2]], + Rotate90 => [ + [y1, 1.0 - x2], + [y1, 1.0 - x1], + [y2, 1.0 - x2], + [y2, 1.0 - x1], + ], + Rotate180 => [ + [1.0 - x2, 1.0 - y1], + [1.0 - x1, 1.0 - y1], + [1.0 - x2, 1.0 - y2], + [1.0 - x1, 1.0 - y2], + ], + Rotate270 => [ + [1.0 - y1, x2], + [1.0 - y1, x1], + [1.0 - y2, x2], + [1.0 - y2, x1], + ], + Flip => [ + [1.0 - x2, y1], + [1.0 - x1, y1], + [1.0 - x2, y2], + [1.0 - x1, y2], + ], FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]], - FlipRotate180 => [[x2, y2], [x1, y2], [x2, y1], [x1, y1]], - FlipRotate270 => [[y2, x1], [y2, x2], [y1, x1], [y1, x2]], + FlipRotate180 => [ + [x2, 1.0 - y1], + [x1, 1.0 - y1], + [x2, 1.0 - y2], + [x1, 1.0 - y2], + ], + FlipRotate270 => [ + [1.0 - y1, 1.0 - x2], + [1.0 - y1, 1.0 - x1], + [1.0 - y2, 1.0 - x2], + [1.0 - y2, 1.0 - x1], + ], } } } From 3f4a677d0ce669b6b56107bd3f59acd68342c0ab Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 12 Jul 2024 00:47:01 +0200 Subject: [PATCH 15/17] metal: don't overwrite active FB in non-FB flip events --- src/backends/metal/video.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 9dfa1792..cfe9332a 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -2172,9 +2172,9 @@ impl MetalBackend { _ => return, }; connector.can_present.set(true); - connector - .active_framebuffer - .set(connector.next_framebuffer.take()); + if let Some(fb) = connector.next_framebuffer.take() { + connector.active_framebuffer.set(Some(fb)); + } if connector.has_damage.get() || connector.cursor_changed.get() { connector.schedule_present(); } From 76a3c50560881fdbb97b56d3892730def133ef2a Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 20:47:00 +0200 Subject: [PATCH 16/17] render: add a damage visualizer --- src/backends/metal/video.rs | 1 + src/cli.rs | 9 +- src/cli/color.rs | 36 ++++++ src/cli/damage_tracking.rs | 106 ++++++++++++++++ src/cli/duration.rs | 102 +++++++++++++++ src/cli/idle.rs | 93 ++------------ src/compositor.rs | 3 + src/damage.rs | 160 ++++++++++++++++++++++++ src/gfx_api.rs | 8 ++ src/globals.rs | 2 + src/ifs.rs | 1 + src/ifs/jay_damage_tracking.rs | 135 ++++++++++++++++++++ src/main.rs | 1 + src/rect/region.rs | 4 + src/state.rs | 2 + src/tools/tool_client.rs | 32 ++++- toml-config/src/config/parsers/color.rs | 2 +- wire/jay_damage_tracking.txt | 18 +++ 18 files changed, 625 insertions(+), 90 deletions(-) create mode 100644 src/cli/color.rs create mode 100644 src/cli/damage_tracking.rs create mode 100644 src/cli/duration.rs create mode 100644 src/damage.rs create mode 100644 src/ifs/jay_damage_tracking.rs create mode 100644 wire/jay_damage_tracking.txt diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index cfe9332a..3a5f6af6 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -835,6 +835,7 @@ impl MetalConnector { render_hw_cursor, output.has_fullscreen(), output.global.persistent.transform.get(), + Some(&self.state.damage_visualizer), ); let try_direct_scanout = try_direct_scanout && self.direct_scanout_enabled() diff --git a/src/cli.rs b/src/cli.rs index ba221c76..7d9366a8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,6 @@ +mod color; +mod damage_tracking; +mod duration; mod generate; mod idle; mod input; @@ -12,7 +15,7 @@ mod unlock; use { crate::{ - cli::{input::InputArgs, randr::RandrArgs}, + cli::{damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs}, compositor::start_compositor, portal, }, @@ -65,6 +68,9 @@ pub enum Cmd { Randr(RandrArgs), /// Inspect/modify input settings. Input(InputArgs), + /// Modify damage tracking settings. (Only for debugging.) + #[clap(hide = true)] + DamageTracking(DamageTrackingArgs), #[cfg(feature = "it")] RunTests, } @@ -241,6 +247,7 @@ pub fn main() { Cmd::Portal => portal::run_freestanding(cli.global), Cmd::Randr(a) => randr::main(cli.global, a), Cmd::Input(a) => input::main(cli.global, a), + Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/color.rs b/src/cli/color.rs new file mode 100644 index 00000000..24136ecc --- /dev/null +++ b/src/cli/color.rs @@ -0,0 +1,36 @@ +use { + crate::{theme::Color, utils::errorfmt::ErrorFmt}, + std::ops::Range, +}; + +pub fn parse_color(string: &str) -> Color { + let hex = match string.strip_prefix("#") { + Some(s) => s, + _ => fatal!("Color must start with #"), + }; + let d = |range: Range| match u8::from_str_radix(&hex[range.clone()], 16) { + Ok(n) => n, + Err(e) => { + fatal!( + "Could not parse color component {}: {}", + &hex[range], + ErrorFmt(e) + ) + } + }; + let s = |range: Range| { + let v = d(range); + (v << 4) | v + }; + let (r, g, b, a) = match hex.len() { + 3 => (s(0..1), s(1..2), s(2..3), u8::MAX), + 4 => (s(0..1), s(1..2), s(2..3), s(3..4)), + 6 => (d(0..2), d(2..4), d(4..6), u8::MAX), + 8 => (d(0..2), d(2..4), d(4..6), d(6..8)), + _ => fatal!( + "Unexpected length of color string (should be 3, 4, 6, or 8): {}", + hex.len() + ), + }; + jay_config::theme::Color::new_straight(r, g, b, a).into() +} diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs new file mode 100644 index 00000000..438e80ff --- /dev/null +++ b/src/cli/damage_tracking.rs @@ -0,0 +1,106 @@ +use { + crate::{ + cli::{color::parse_color, duration::parse_duration, GlobalArgs}, + tools::tool_client::{with_tool_client, ToolClient}, + wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled}, + }, + clap::{Args, Subcommand}, + std::rc::Rc, +}; + +#[derive(Args, Debug)] +pub struct DamageTrackingArgs { + #[clap(subcommand)] + pub command: DamageTrackingCmd, +} + +#[derive(Subcommand, Debug)] +pub enum DamageTrackingCmd { + /// Visualize damage. + Show, + /// Hide damage. + Hide, + /// Set the color used for damage visualization. + SetColor(ColorArgs), + /// Set the amount of time damage is shown. + SetDecay(DecayArgs), +} + +#[derive(Args, Debug)] +pub struct ColorArgs { + /// The color to visualize damage. + /// + /// Should be specified in one of the following formats: + /// + /// * `#rgb` + /// * `#rgba` + /// * `#rrggbb` + /// * `#rrggbbaa` + pub color: String, +} + +#[derive(Args, Debug)] +pub struct DecayArgs { + /// The interval of inactivity after which to disable the screens. + /// + /// Minutes, seconds, and milliseconds can be specified in any of the following formats: + /// + /// * 1m + /// * 1m5s + /// * 1m 5s + /// * 1min 5sec + /// * 1 minute 5 seconds. + pub duration: Vec, +} + +pub fn main(global: GlobalArgs, damage_tracking_args: DamageTrackingArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let damage_tracking = Rc::new(DamageTracking { tc: tc.clone() }); + damage_tracking.run(damage_tracking_args).await; + }); +} + +struct DamageTracking { + tc: Rc, +} + +impl DamageTracking { + async fn run(&self, args: DamageTrackingArgs) { + let tc = &self.tc; + let Some(dt) = tc.jay_damage_tracking().await else { + fatal!("Compositor does not support damage tracking"); + }; + match args.command { + DamageTrackingCmd::Show => { + tc.send(SetVisualizerEnabled { + self_id: dt, + enabled: 1, + }); + } + DamageTrackingCmd::Hide => { + tc.send(SetVisualizerEnabled { + self_id: dt, + enabled: 0, + }); + } + DamageTrackingCmd::SetColor(c) => { + let color = parse_color(&c.color); + tc.send(SetVisualizerColor { + self_id: dt, + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }); + } + DamageTrackingCmd::SetDecay(c) => { + let duration = parse_duration(&c.duration); + tc.send(SetVisualizerDecay { + self_id: dt, + millis: duration.as_millis() as _, + }); + } + } + tc.round_trip().await; + } +} diff --git a/src/cli/duration.rs b/src/cli/duration.rs new file mode 100644 index 00000000..823d40ba --- /dev/null +++ b/src/cli/duration.rs @@ -0,0 +1,102 @@ +use { + crate::utils::errorfmt::ErrorFmt, + std::{collections::VecDeque, str::FromStr, time::Duration}, +}; + +#[derive(Debug)] +enum Component { + Number(u64), + Minutes(String), + Seconds(String), + Milliseconds(String), +} + +pub fn parse_duration(args: &[String]) -> Duration { + let comp = parse_components(args); + let mut minutes = None; + let mut seconds = None; + let mut milliseconds = None; + let mut pending_num = None; + for comp in comp { + match comp { + Component::Number(_) if pending_num.is_some() => { + fatal!("missing number unit after {}", pending_num.unwrap()) + } + Component::Number(n) => pending_num = Some(n), + + Component::Minutes(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Minutes(_) if minutes.is_some() => { + fatal!("minutes specified multiple times") + } + Component::Minutes(_) => minutes = pending_num.take(), + + Component::Seconds(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Seconds(_) if seconds.is_some() => { + fatal!("seconds specified multiple times") + } + Component::Seconds(_) => seconds = pending_num.take(), + Component::Milliseconds(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Milliseconds(_) if milliseconds.is_some() => { + fatal!("milliseconds specified multiple times") + } + Component::Milliseconds(_) => milliseconds = pending_num.take(), + } + } + if pending_num.is_some() { + fatal!("missing number unit after {}", pending_num.unwrap()); + } + if minutes.is_none() && seconds.is_none() && milliseconds.is_none() { + fatal!("duration must be specified"); + } + let mut ms = minutes.unwrap_or(0) as u128 * 60 * 1000 + + seconds.unwrap_or(0) as u128 * 1000 + + milliseconds.unwrap_or(0) as u128; + if ms > u64::MAX as u128 { + ms = u64::MAX as u128; + } + Duration::from_millis(ms as u64) +} + +fn parse_components(args: &[String]) -> Vec { + let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase())); + let mut res = vec![]; + while let Some(arg) = args.pop_front() { + if arg.is_empty() { + continue; + } + let mut arg = &arg[..]; + if is_num(arg.as_bytes()[0]) { + if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) { + args.push_front(arg[pos..].to_string()); + arg = &arg[..pos]; + } + match u64::from_str(arg) { + Ok(n) => res.push(Component::Number(n)), + Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)), + } + } else { + if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) { + args.push_front(arg[pos..].to_string()); + arg = &arg[..pos]; + } + let comp = match arg { + "minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()), + "seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()), + "milliseconds" | "millisecond" | "ms" => Component::Milliseconds(arg.to_string()), + _ => fatal!("Could not parse `{}`", arg), + }; + res.push(comp); + } + } + res +} + +fn is_num(b: u8) -> bool { + matches!(b, b'0'..=b'9') +} diff --git a/src/cli/idle.rs b/src/cli/idle.rs index cbd388e9..ca859240 100644 --- a/src/cli/idle.rs +++ b/src/cli/idle.rs @@ -1,11 +1,11 @@ use { crate::{ - cli::{GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs}, + cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs}, tools::tool_client::{with_tool_client, Handle, ToolClient}, - utils::{errorfmt::ErrorFmt, stack::Stack}, + utils::stack::Stack, wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId}, }, - std::{cell::Cell, collections::VecDeque, rc::Rc, str::FromStr}, + std::{cell::Cell, rc::Rc}, }; pub fn main(global: GlobalArgs, args: IdleArgs) { @@ -93,46 +93,11 @@ impl Idle { async fn set(self, idle: JayIdleId, args: IdleSetArgs) { let tc = &self.tc; - let interval; - if args.interval.len() == 1 && args.interval[0] == "disabled" { - interval = 0; + let interval = if args.interval.len() == 1 && args.interval[0] == "disabled" { + 0 } else { - let comp = parse_components(&args.interval); - let mut minutes = None; - let mut seconds = None; - let mut pending_num = None; - for comp in comp { - match comp { - Component::Number(_) if pending_num.is_some() => { - fatal!("missing number unit after {}", pending_num.unwrap()) - } - Component::Number(n) => pending_num = Some(n), - - Component::Minutes(n) if pending_num.is_none() => { - fatal!("`{}` must be preceded by a number", n) - } - Component::Minutes(_) if minutes.is_some() => { - fatal!("minutes specified multiple times") - } - Component::Minutes(_) => minutes = pending_num.take(), - - Component::Seconds(n) if pending_num.is_none() => { - fatal!("`{}` must be preceded by a number", n) - } - Component::Seconds(_) if seconds.is_some() => { - fatal!("seconds specified multiple times") - } - Component::Seconds(_) => seconds = pending_num.take(), - } - } - if pending_num.is_some() { - fatal!("missing number unit after {}", pending_num.unwrap()); - } - if minutes.is_none() && seconds.is_none() { - fatal!("minutes and/or numbers must be specified"); - } - interval = minutes.unwrap_or(0) * 60 + seconds.unwrap_or(0); - } + parse_duration(&args.interval).as_secs() as u64 + }; tc.send(jay_idle::SetInterval { self_id: idle, interval, @@ -140,47 +105,3 @@ impl Idle { tc.round_trip().await; } } - -#[derive(Debug)] -enum Component { - Number(u64), - Minutes(String), - Seconds(String), -} - -fn parse_components(args: &[String]) -> Vec { - let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase())); - let mut res = vec![]; - while let Some(arg) = args.pop_front() { - if arg.is_empty() { - continue; - } - let mut arg = &arg[..]; - if is_num(arg.as_bytes()[0]) { - if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) { - args.push_front(arg[pos..].to_string()); - arg = &arg[..pos]; - } - match u64::from_str(arg) { - Ok(n) => res.push(Component::Number(n)), - Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)), - } - } else { - if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) { - args.push_front(arg[pos..].to_string()); - arg = &arg[..pos]; - } - let comp = match arg { - "minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()), - "seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()), - _ => fatal!("Could not parse `{}`", arg), - }; - res.push(comp); - } - } - res -} - -fn is_num(b: u8) -> bool { - matches!(b, b'0'..=b'9') -} diff --git a/src/compositor.rs b/src/compositor.rs index f8c427aa..5fd673d3 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -13,6 +13,7 @@ use { client::{ClientId, Clients}, clientmem::{self, ClientMemError}, config::ConfigProxy, + damage::{visualize_damage, DamageVisualizer}, dbus::Dbus, forker, globals::Globals, @@ -244,6 +245,7 @@ fn start_compositor2( tablet_ids: Default::default(), tablet_tool_ids: Default::default(), tablet_pad_ids: Default::default(), + damage_visualizer: DamageVisualizer::new(&engine), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -343,6 +345,7 @@ fn start_global_event_handlers( eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())), eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())), eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())), + eng.spawn2(Phase::PostLayout, visualize_damage(state.clone())), ] } diff --git a/src/damage.rs b/src/damage.rs new file mode 100644 index 00000000..9439a739 --- /dev/null +++ b/src/damage.rs @@ -0,0 +1,160 @@ +use { + crate::{ + async_engine::AsyncEngine, + rect::{Rect, Region}, + renderer::renderer_base::RendererBase, + state::State, + theme::Color, + time::Time, + utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd}, + }, + isnt::std_1::primitive::IsntSliceExt, + std::{ + cell::{Cell, RefCell}, + collections::VecDeque, + rc::Rc, + time::Duration, + }, + uapi::c::CLOCK_MONOTONIC, +}; + +pub async fn visualize_damage(state: Rc) { + let timer = match TimerFd::new(CLOCK_MONOTONIC) { + Ok(t) => t, + Err(e) => { + log::error!("Could not create timer fd: {}", ErrorFmt(e)); + return; + } + }; + loop { + state.damage_visualizer.entry_added.triggered().await; + let duration = Duration::from_millis(50); + let res = timer.program(Some(duration), Some(duration)); + if let Err(e) = res { + log::error!("Could not program timer: {}", ErrorFmt(e)); + return; + } + loop { + let res = timer.expired(&state.ring).await; + if let Err(e) = res { + log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); + return; + } + if state.damage_visualizer.entries.borrow_mut().is_empty() { + break; + } + damage_all(&state); + } + let res = timer.program(None, None); + if let Err(e) = res { + log::error!("Could not disable timer: {}", ErrorFmt(e)); + return; + } + } +} + +fn damage_all(state: &State) { + for connector in state.connectors.lock().values() { + if connector.connected.get() { + connector.connector.damage(); + } + } +} + +pub struct DamageVisualizer { + eng: Rc, + entries: RefCell>, + entry_added: AsyncEvent, + enabled: Cell, + decay: Cell, + color: Cell, +} + +const MAX_RECTS: usize = 100_000; + +struct Damage { + time: Time, + rect: Rect, +} + +impl DamageVisualizer { + pub fn new(eng: &Rc) -> Self { + Self { + eng: eng.clone(), + entries: Default::default(), + entry_added: Default::default(), + enabled: Default::default(), + decay: Cell::new(Duration::from_secs(2)), + color: Cell::new(Color::from_rgba_straight(255, 0, 0, 128)), + } + } + + #[allow(dead_code)] + pub fn add(&self, rect: Rect) { + if !self.enabled.get() { + return; + } + let entries = &mut *self.entries.borrow_mut(); + if entries.is_empty() { + self.entry_added.trigger(); + } + entries.push_back(Damage { + time: self.eng.now(), + rect, + }); + if entries.len() > MAX_RECTS { + entries.pop_front(); + } + } + + pub fn set_enabled(&self, state: &State, enabled: bool) { + self.enabled.set(enabled); + if !enabled { + self.entries.borrow_mut().clear(); + damage_all(state); + } + } + + pub fn set_decay(&self, decay: Duration) { + let millis = decay.as_millis(); + if millis == 0 || millis > u64::MAX as u128 { + return; + } + self.decay.set(decay); + } + + pub fn set_color(&self, color: Color) { + self.color.set(color); + } + + pub fn render(&self, cursor_rect: &Rect, renderer: &mut RendererBase<'_>) { + if !self.enabled.get() { + return; + } + let now = self.eng.now(); + let entries = &mut *self.entries.borrow_mut(); + let decay = self.decay.get(); + while let Some(first) = entries.front() { + if now - first.time >= decay { + entries.pop_front(); + } else { + break; + } + } + let base_color = self.color.get(); + let mut used = Region::empty(); + let dx = -cursor_rect.x1(); + let dy = -cursor_rect.y1(); + let decay_millis = decay.as_millis() as u64 as f32; + for entry in entries.iter().rev() { + let region = Region::new(entry.rect); + let region = region.subtract(&used); + if region.is_not_empty() { + let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis; + let color = base_color * (1.0 - age); + renderer.fill_boxes2(region.rects(), &color, dx, dy); + used = used.union(®ion); + } + } + } +} diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 8611a357..043e0172 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,6 +1,7 @@ use { crate::{ cursor::Cursor, + damage::DamageVisualizer, fixed::Fixed, format::Format, rect::Rect, @@ -359,6 +360,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor: bool, black_background: bool, transform: Transform, + visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { let mut ops = self.take_render_ops(); let mut renderer = Renderer { @@ -410,6 +412,11 @@ impl dyn GfxFramebuffer { } } } + if let Some(visualizer) = visualizer { + if let Some(cursor_rect) = cursor_rect { + visualizer.render(&cursor_rect, &mut renderer.base); + } + } let c = match black_background { true => Color::SOLID_BLACK, false => state.theme.colors.background.get(), @@ -468,6 +475,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor, black_background, transform, + None, ); self.perform_render_pass(pass) } diff --git a/src/globals.rs b/src/globals.rs index d893a06c..73a92f1f 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -12,6 +12,7 @@ use { zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global, }, jay_compositor::JayCompositorGlobal, + jay_damage_tracking::JayDamageTrackingGlobal, org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal, wl_compositor::WlCompositorGlobal, wl_output::WlOutputGlobal, @@ -195,6 +196,7 @@ impl Globals { add_singleton!(ExtTransientSeatManagerV1Global); add_singleton!(ZwpPointerGesturesV1Global); add_singleton!(ZwpTabletManagerV2Global); + add_singleton!(JayDamageTrackingGlobal); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 9efdf7b0..851dd9fa 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -6,6 +6,7 @@ pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod ipc; pub mod jay_compositor; +pub mod jay_damage_tracking; pub mod jay_idle; pub mod jay_input; pub mod jay_log_file; diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs new file mode 100644 index 00000000..10015f34 --- /dev/null +++ b/src/ifs/jay_damage_tracking.rs @@ -0,0 +1,135 @@ +use { + crate::{ + client::{Client, ClientCaps, ClientError, CAP_JAY_COMPOSITOR}, + globals::{Global, GlobalName}, + leaks::Tracker, + object::{Object, Version}, + theme::Color, + wire::{ + jay_damage_tracking::{ + Destroy, JayDamageTrackingRequestHandler, SetVisualizerColor, SetVisualizerDecay, + SetVisualizerEnabled, + }, + JayCompositorId, + }, + }, + std::{rc::Rc, time::Duration}, + thiserror::Error, +}; + +pub struct JayDamageTrackingGlobal { + name: GlobalName, +} + +impl JayDamageTrackingGlobal { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: JayCompositorId, + client: &Rc, + version: Version, + ) -> Result<(), JayDamageTrackingError> { + let obj = Rc::new(JayDamageTracking { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + JayDamageTrackingGlobal, + JayDamageTracking, + JayDamageTrackingError +); + +impl Global for JayDamageTrackingGlobal { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_JAY_COMPOSITOR + } +} + +simple_add_global!(JayDamageTrackingGlobal); + +pub struct JayDamageTracking { + id: JayCompositorId, + client: Rc, + tracker: Tracker, + version: Version, +} + +impl JayDamageTrackingRequestHandler for JayDamageTracking { + type Error = JayDamageTrackingError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_visualizer_enabled( + &self, + req: SetVisualizerEnabled, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let state = &self.client.state; + state.damage_visualizer.set_enabled(state, req.enabled != 0); + Ok(()) + } + + fn set_visualizer_color( + &self, + req: SetVisualizerColor, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.client.state.damage_visualizer.set_color(Color { + r: req.r, + g: req.g, + b: req.b, + a: req.a, + }); + Ok(()) + } + + fn set_visualizer_decay( + &self, + req: SetVisualizerDecay, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.client + .state + .damage_visualizer + .set_decay(Duration::from_millis(req.millis)); + Ok(()) + } +} + +object_base! { + self = JayDamageTracking; + version = self.version; +} + +impl Object for JayDamageTracking {} + +simple_add_obj!(JayDamageTracking); + +#[derive(Debug, Error)] +pub enum JayDamageTrackingError { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayDamageTrackingError, ClientError); diff --git a/src/main.rs b/src/main.rs index d7cc40b5..264305db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,7 @@ mod compositor; mod config; mod cursor; mod cursor_user; +mod damage; mod dbus; mod drm_feedback; mod edid; diff --git a/src/rect/region.rs b/src/rect/region.rs index 0ef3dc28..cf9e8c86 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -78,6 +78,10 @@ impl Region { self.extents } + pub fn rects(&self) -> &[Rect] { + unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) } + } + pub fn contains(&self, x: i32, y: i32) -> bool { if !self.extents.contains(x, y) { return false; diff --git a/src/state.rs b/src/state.rs index c0c74536..975568ec 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,6 +14,7 @@ use { config::ConfigProxy, cursor::{Cursor, ServerCursors}, cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, + damage::DamageVisualizer, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, fixed::Fixed, @@ -199,6 +200,7 @@ pub struct State { pub tablet_ids: TabletIds, pub tablet_tool_ids: TabletToolIds, pub tablet_pad_ids: TabletPadIds, + pub damage_visualizer: DamageVisualizer, } // impl Drop for State { diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 43c58b5b..0abd322b 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -23,8 +23,8 @@ use { }, wheel::{Wheel, WheelError}, wire::{ - wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId, WlCallbackId, - WlRegistryId, + wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId, + JayDamageTracking, JayDamageTrackingId, WlCallbackId, WlRegistryId, }, }, ahash::AHashMap, @@ -92,6 +92,7 @@ pub struct ToolClient { outgoing: Cell>>, singletons: CloneCell>>, jay_compositor: Cell>, + jay_damage_tracking: Cell>>, } pub fn with_tool_client(level: Level, f: F) @@ -188,6 +189,7 @@ impl ToolClient { outgoing: Default::default(), singletons: Default::default(), jay_compositor: Default::default(), + jay_damage_tracking: Default::default(), }); wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| { fatal!("The compositor returned a fatal error: {}", val.message); @@ -285,6 +287,7 @@ impl ToolClient { #[derive(Default)] struct S { jay_compositor: Cell>, + jay_damage_tracking: Cell>, } let s = Rc::new(S::default()); let registry: WlRegistryId = self.id(); @@ -295,6 +298,8 @@ impl ToolClient { wl_registry::Global::handle(self, registry, s.clone(), |s, g| { if g.interface == JayCompositor.name() { s.jay_compositor.set(Some(g.name)); + } else if g.interface == JayDamageTracking.name() { + s.jay_damage_tracking.set(Some(g.name)); } }); self.round_trip().await; @@ -309,6 +314,7 @@ impl ToolClient { let res = Rc::new(Singletons { registry, jay_compositor: get!(jay_compositor, JayCompositor), + jay_damage_tracking: s.jay_damage_tracking.get(), }); self.singletons.set(Some(res.clone())); res @@ -330,11 +336,33 @@ impl ToolClient { self.jay_compositor.set(Some(id)); id } + + pub async fn jay_damage_tracking(self: &Rc) -> Option { + if let Some(id) = self.jay_damage_tracking.get() { + return id; + } + let s = self.singletons().await; + let Some(name) = s.jay_damage_tracking else { + self.jay_damage_tracking.set(Some(None)); + return None; + }; + let id: JayDamageTrackingId = self.id(); + self.send(wl_registry::Bind { + self_id: s.registry, + name, + interface: JayDamageTracking.name(), + version: 1, + id: id.into(), + }); + self.jay_damage_tracking.set(Some(Some(id))); + Some(id) + } } pub struct Singletons { registry: WlRegistryId, pub jay_compositor: u32, + pub jay_damage_tracking: Option, } pub const NONE_FUTURE: Option> = None; diff --git a/toml-config/src/config/parsers/color.rs b/toml-config/src/config/parsers/color.rs index a4418b0c..25073b20 100644 --- a/toml-config/src/config/parsers/color.rs +++ b/toml-config/src/config/parsers/color.rs @@ -49,7 +49,7 @@ impl Parser for ColorParser { 3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX), 4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?), 6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX), - 8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?), + 8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(6..8)?), _ => return Err(ColorParserError::Length.spanned(span)), }; Ok(Color::new_straight(r, g, b, a)) diff --git a/wire/jay_damage_tracking.txt b/wire/jay_damage_tracking.txt new file mode 100644 index 00000000..96b6d6da --- /dev/null +++ b/wire/jay_damage_tracking.txt @@ -0,0 +1,18 @@ +request destroy { + +} + +request set_visualizer_enabled { + enabled: u32, +} + +request set_visualizer_color { + r: pod(f32), + g: pod(f32), + b: pod(f32), + a: pod(f32), +} + +request set_visualizer_decay { + millis: pod(u64), +} From bb66abb817c83044664c7b9f77636117a9272038 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 10 Jul 2024 19:58:17 +0200 Subject: [PATCH 17/17] all: implement damage tracking --- docs/features.md | 1 - release-notes.md | 2 + src/compositor.rs | 2 + src/config/handler.rs | 7 +- src/cursor_user.rs | 49 ++++- src/ifs/ext_session_lock_manager_v1.rs | 2 +- src/ifs/jay_screencast.rs | 24 ++- src/ifs/wl_seat.rs | 1 - src/ifs/wl_seat/event_handling.rs | 13 +- src/ifs/wl_seat/pointer_owner.rs | 21 +- src/ifs/wl_surface.rs | 193 +++++++++++++++++- src/ifs/wl_surface/cursor.rs | 10 + src/ifs/wl_surface/dnd_icon.rs | 14 ++ src/ifs/wl_surface/wl_subsurface.rs | 3 + src/ifs/wl_surface/x_surface/xwindow.rs | 15 +- src/ifs/wl_surface/xdg_surface.rs | 13 ++ .../wl_surface/xdg_surface/xdg_toplevel.rs | 10 + src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 5 + .../wl_surface/zwp_input_popup_surface_v2.rs | 20 +- src/ifs/xdg_toplevel_drag_v1.rs | 14 +- src/state.rs | 16 +- src/tasks/connector.rs | 4 +- src/tree/container.rs | 21 ++ src/tree/display.rs | 3 + src/tree/float.rs | 36 +++- src/tree/output.rs | 37 +++- src/tree/toplevel.rs | 4 - src/tree/workspace.rs | 15 +- 28 files changed, 473 insertions(+), 82 deletions(-) diff --git a/docs/features.md b/docs/features.md index ff1b7d7f..4a4660e6 100644 --- a/docs/features.md +++ b/docs/features.md @@ -180,6 +180,5 @@ Jay supports the following wayland protocols: The following features are currently not supported but might get implemented in the future: -- Fine-grained damage tracking. - Touch support. - Tearing updates of fullscreen games. diff --git a/release-notes.md b/release-notes.md index 701c2dae..ec924a06 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Add fine-grained damage tracking. + # 1.4.0 (2024-07-07) - Add window management mode. diff --git a/src/compositor.rs b/src/compositor.rs index 5fd673d3..0fa5caff 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -468,9 +468,11 @@ fn create_dummy_output(state: &Rc) { screencasts: Default::default(), hardware_cursor_needs_render: Cell::new(false), screencopies: Default::default(), + title_visible: Cell::new(false), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), + state: state.clone(), is_dummy: true, output: CloneCell::new(dummy_output.clone()), position: Default::default(), diff --git a/src/config/handler.rs b/src/config/handler.rs index e53f4fde..755f8e4c 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -720,8 +720,6 @@ impl ConfigProxyHandler { if let Some(ws) = self.state.workspaces.get(name.as_str()) { ws.may_capture.set(capture); ws.update_has_captures(); - ws.output.get().schedule_update_render_data(); - self.state.damage(); } Ok(()) } @@ -863,7 +861,6 @@ impl ConfigProxyHandler { move_ws_to_output(&link, &output, config); ws.desired_output.set(output.global.output_id.clone()); self.state.tree_changed(); - self.state.damage(); Ok(()) } @@ -1032,7 +1029,6 @@ impl ConfigProxyHandler { let scale = Scale::from_f64(scale); let connector = self.get_output_node(connector)?; connector.set_preferred_scale(scale); - self.state.damage(); Ok(()) } @@ -1043,7 +1039,6 @@ impl ConfigProxyHandler { ) -> Result<(), CphError> { let connector = self.get_output_node(connector)?; connector.update_transform(transform); - self.state.damage(); Ok(()) } @@ -1371,6 +1366,7 @@ impl ConfigProxyHandler { } } self.state.root.clone().node_visit(&mut V); + self.state.damage(self.state.root.extents.get()); } fn colors_changed(&self) { @@ -1386,6 +1382,7 @@ impl ConfigProxyHandler { } } self.state.root.clone().node_visit(&mut V); + self.state.damage(self.state.root.extents.get()); } fn get_sized(&self, sized: Resizable) -> Result { diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 80bd480b..e84d50be 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -3,6 +3,7 @@ use { cursor::{Cursor, KnownCursor, DEFAULT_CURSOR_SIZE}, fixed::Fixed, rect::Rect, + scale::Scale, state::State, tree::OutputNode, utils::{ @@ -74,13 +75,26 @@ impl CursorUserGroup { group } + fn damage_active(&self) { + if let Some(active) = self.active.get() { + if let Some(cursor) = active.cursor.get() { + let (x, y) = active.pos.get(); + let x_int = x.round_down(); + let y_int = y.round_down(); + let extents = cursor.extents_at_scale(Scale::default()); + self.state.damage(extents.move_(x_int, y_int)); + } + } + } + pub fn deactivate(&self) { if self.hardware_cursor.get() { self.remove_hardware_cursor(); + } else { + self.damage_active(); } self.active_id.take(); self.active.take(); - self.state.damage(); } pub fn latest_output(&self) -> Rc { @@ -150,6 +164,7 @@ impl CursorUserGroup { if self.hardware_cursor.replace(hardware_cursor) == hardware_cursor { return; } + self.damage_active(); if hardware_cursor { let prev = self .state @@ -157,6 +172,7 @@ impl CursorUserGroup { .set(Some(self.clone())); if let Some(prev) = prev { prev.hardware_cursor.set(false); + prev.damage_active(); } match self.active.get() { None => self.remove_hardware_cursor(), @@ -230,9 +246,7 @@ impl CursorUser { self.owner.take(); self.group.users.remove(&self.id); if self.group.active_id.get() == Some(self.id) { - self.group.active_id.take(); - self.group.active.take(); - self.group.state.damage(); + self.group.deactivate(); } } @@ -240,10 +254,15 @@ impl CursorUser { if self.group.active_id.replace(Some(self.id)) == Some(self.id) { return; } + if self.software_cursor() { + self.group.damage_active(); + } self.group.latest_output.set(self.output.get()); self.group.active.set(Some(self.clone())); self.update_hardware_cursor(); - self.group.state.damage(); + if self.software_cursor() { + self.group.damage_active(); + } } #[cfg_attr(not(feature = "it"), allow(dead_code))] @@ -341,6 +360,9 @@ impl CursorUser { } } old.handle_unset(); + if self.software_cursor() { + self.group.damage_active(); + } } if let Some(cursor) = cursor.as_ref() { cursor.clone().handle_set(); @@ -348,6 +370,9 @@ impl CursorUser { } self.cursor.set(cursor.clone()); self.update_hardware_cursor(); + if self.software_cursor() { + self.group.damage_active(); + } } pub fn position(&self) -> (Fixed, Fixed) { @@ -368,6 +393,16 @@ impl CursorUser { x = x.apply_fract(x_tmp); y = y.apply_fract(y_tmp); } + if self.software_cursor() { + if let Some(cursor) = self.cursor.get() { + let (old_x, old_y) = self.pos.get(); + let old_x_int = old_x.round_down(); + let old_y_int = old_y.round_down(); + let extents = cursor.extents_at_scale(Scale::default()); + self.group.state.damage(extents.move_(old_x_int, old_y_int)); + self.group.state.damage(extents.move_(x_int, y_int)); + } + } self.pos.set((x, y)); self.update_hardware_cursor_(false); (x, y) @@ -381,6 +416,10 @@ impl CursorUser { self.is_active() && self.group.hardware_cursor.get() } + pub fn software_cursor(&self) -> bool { + self.is_active() && !self.group.hardware_cursor.get() + } + fn update_hardware_cursor_(&self, render: bool) { if !self.hardware_cursor() { return; diff --git a/src/ifs/ext_session_lock_manager_v1.rs b/src/ifs/ext_session_lock_manager_v1.rs index b2981ddf..3d80fd17 100644 --- a/src/ifs/ext_session_lock_manager_v1.rs +++ b/src/ifs/ext_session_lock_manager_v1.rs @@ -74,7 +74,7 @@ impl ExtSessionLockManagerV1RequestHandler for ExtSessionLockManagerV1 { state.lock.locked.set(true); state.lock.lock.set(Some(new.clone())); state.tree_changed(); - state.damage(); + state.damage(state.root.extents.get()); new.send_locked(); } else { new.finish(); diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 344a1b06..9c4d7301 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -327,9 +327,6 @@ impl JayScreencast { Target::Toplevel(tl) => { let data = tl.tl_data(); data.jay_screencasts.remove(&(self.client.id, self.id)); - if data.jay_screencasts.is_empty() { - self.client.state.damage(); - } } } } @@ -418,10 +415,16 @@ impl JayScreencast { fn damage(&self) { if let Some(target) = self.target.get() { - match target { - Target::Output(o) => o.global.connector.connector.damage(), - Target::Toplevel(_) => self.client.state.damage(), - } + let rect = match target { + Target::Output(o) => o.global.pos.get(), + Target::Toplevel(t) => { + if !t.node_visible() { + return; + } + t.node_absolute_position() + } + }; + self.client.state.damage(rect); } } } @@ -535,9 +538,6 @@ impl JayScreencastRequestHandler for JayScreencast { } let t = t.toplevel.clone(); let data = t.tl_data(); - if data.jay_screencasts.is_empty() { - data.state.damage(); - } data.jay_screencasts .set((self.client.id, self.id), slf.clone()); new_target = Some(Target::Toplevel(t)); @@ -577,6 +577,10 @@ impl JayScreencastRequestHandler for JayScreencast { } } + if self.running.get() { + self.damage(); + } + Ok(()) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 4323f643..7fa17c21 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -561,7 +561,6 @@ impl WlSeatGlobal { if let Some(parent) = tl.tl_data().parent.get() { if let Some(tl) = parent.node_toplevel() { self.focus_node(tl.tl_into_node()); - self.state.damage(); } } } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 51bcb5a4..5cbbf231 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -411,10 +411,18 @@ impl WlSeatGlobal { } fn set_pointer_cursor_position(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) { + let dnd_icon = self.pointer_owner.dnd_icon(); + if let Some(dnd_icon) = &dnd_icon { + let (x_old, y_old) = self.pointer_cursor.position_int(); + dnd_icon.damage_at(x_old, y_old); + } let (x, y) = self.pointer_cursor.set_position(x, y); + let x_int = x.round_down(); + let y_int = y.round_down(); + if let Some(dnd_icon) = &dnd_icon { + dnd_icon.damage_at(x_int, y_int); + } if let Some(td) = self.pointer_owner.toplevel_drag() { - let x_int = x.round_down(); - let y_int = y.round_down(); td.move_(x_int, y_int); } (x, y) @@ -894,7 +902,6 @@ impl WlSeatGlobal { } pub(super) fn apply_changes(self: &Rc) { - self.state.damage(); self.pointer_owner.apply_changes(self); if self.changes.get().contains(CHANGE_TREE) { self.tablet_apply_changes(); diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 5a436f2a..b5e760c7 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -385,7 +385,6 @@ impl PointerOwner for SimplePointerOwner { if !T::IS_DEFAULT { seat.pointer_owner.set_default_pointer_owner(seat); seat.trigger_tree_changed(); - seat.state.damage(); } } @@ -763,21 +762,17 @@ impl NodeSelectorUsecase for SelectToplevelUsecase { } fn node_focus(self: &Rc, seat: &Rc, node: &Rc) { - let mut damage = false; let tl = node.clone().node_into_toplevel(); if let Some(tl) = &tl { tl.tl_data().render_highlight.fetch_add(1); if !tl.tl_admits_children() { seat.pointer_cursor().set_known(KnownCursor::Pointer); } - damage = true; + seat.state.damage(tl.node_absolute_position()); } if let Some(prev) = self.latest.set(tl) { prev.tl_data().render_highlight.fetch_sub(1); - damage = true; - } - if damage { - seat.state.damage(); + seat.state.damage(prev.node_absolute_position()); } } } @@ -787,7 +782,7 @@ impl Drop for SelectToplevelUsecase { if let Some(prev) = self.latest.take() { prev.tl_data().render_highlight.fetch_sub(1); if let Some(seat) = self.seat.upgrade() { - seat.state.damage(); + seat.state.damage(prev.node_absolute_position()); } } } @@ -812,19 +807,15 @@ impl NodeSelectorUsecase for SelectWorkspaceUsecase { } fn node_focus(self: &Rc, seat: &Rc, node: &Rc) { - let mut damage = false; let ws = node.clone().node_into_workspace(); if let Some(ws) = &ws { ws.render_highlight.fetch_add(1); seat.pointer_cursor().set_known(KnownCursor::Pointer); - damage = true; + seat.state.damage(ws.position.get()); } if let Some(prev) = self.latest.set(ws) { prev.render_highlight.fetch_sub(1); - damage = true; - } - if damage { - seat.state.damage(); + seat.state.damage(prev.position.get()); } } } @@ -834,7 +825,7 @@ impl Drop for SelectWorkspaceUsecase { if let Some(prev) = self.latest.take() { prev.render_highlight.fetch_sub(1); if let Some(seat) = self.seat.upgrade() { - seat.state.damage(); + seat.state.damage(prev.position.get()); } } } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index ea9018e4..dcea1930 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -257,6 +257,7 @@ pub struct WlSurface { opaque_region: Cell>>, buffer_points: RefCell, pub buffer_points_norm: RefCell, + damage_matrix: Cell, buffer_transform: Cell, buffer_scale: Cell, src_rect: Cell>, @@ -568,6 +569,7 @@ impl WlSurface { opaque_region: Default::default(), buffer_points: Default::default(), buffer_points_norm: Default::default(), + damage_matrix: Default::default(), buffer_transform: Cell::new(Transform::None), buffer_scale: Cell::new(1), src_rect: Cell::new(None), @@ -669,8 +671,13 @@ impl WlSurface { } fn set_absolute_position(&self, x1: i32, y1: i32) { - self.buffer_abs_pos - .set(self.buffer_abs_pos.get().at_point(x1, y1)); + let old_pos = self.buffer_abs_pos.get(); + let new_pos = old_pos.at_point(x1, y1); + if self.visible.get() && self.toplevel.is_none() { + self.client.state.damage(old_pos); + self.client.state.damage(new_pos); + } + self.buffer_abs_pos.set(new_pos); if let Some(children) = self.children.borrow_mut().deref_mut() { for ss in children.subsurfaces.values() { let pos = ss.position.get(); @@ -843,6 +850,9 @@ impl WlSurface { fn unset_dnd_icons(&self) { while let Some((_, dnd_icon)) = self.dnd_icons.pop() { dnd_icon.seat.remove_dnd_icon(); + if self.visible.get() { + dnd_icon.damage(); + } } } @@ -1169,6 +1179,18 @@ impl WlSurface { y2, buffer_transform: self.buffer_transform.get(), }; + let (buffer_width, buffer_height) = buffer.buffer.rect.size(); + let (dst_width, dst_height) = new_size.unwrap_or_default(); + let damage_matrix = DamageMatrix::new( + self.buffer_transform.get(), + self.buffer_scale.get(), + buffer_width, + buffer_height, + self.src_rect.get(), + dst_width, + dst_height, + ); + self.damage_matrix.set(damage_matrix); } } let (width, height) = new_size.unwrap_or_default(); @@ -1232,15 +1254,65 @@ impl WlSurface { fr.send_done(now); let _ = fr.client.remove_obj(&*fr); } + } else { + self.apply_damage(pending); } } - self.client.state.damage(); pending.buffer_damage.clear(); pending.surface_damage.clear(); pending.damage_full = false; Ok(()) } + fn apply_damage(&self, pending: &PendingState) { + let bounds = self.toplevel.get().map(|tl| tl.node_absolute_position()); + let pos = self.buffer_abs_pos.get(); + let apply_damage = |pos: Rect| { + if pending.damage_full { + let mut damage = pos; + if let Some(bounds) = bounds { + damage = damage.intersect(bounds); + } + self.client.state.damage(damage); + } else { + let matrix = self.damage_matrix.get(); + if let Some(buffer) = self.buffer.get() { + for damage in &pending.buffer_damage { + let mut damage = + matrix.apply(pos.x1(), pos.y1(), damage.intersect(buffer.buffer.rect)); + if let Some(bounds) = bounds { + damage = damage.intersect(bounds); + } + self.client.state.damage(damage); + } + } + for damage in &pending.surface_damage { + let mut damage = damage.move_(pos.x1(), pos.y1()); + damage = damage.intersect(bounds.unwrap_or(pos)); + self.client.state.damage(damage); + } + } + }; + match self.role.get() { + SurfaceRole::Cursor => { + for (_, cursor) in &self.cursors { + if cursor.needs_damage_tracking() { + let (x, y) = cursor.surface_position(); + apply_damage(pos.at_point(x, y)); + } + } + } + SurfaceRole::DndIcon => { + for (_, dnd_icon) in &self.dnd_icons { + let (x, y) = dnd_icon.seat.pointer_cursor().position_int(); + let (x, y) = dnd_icon.surface_position(x, y); + apply_damage(pos.at_point(x, y)); + } + } + _ => apply_damage(pos), + } + } + fn verify_explicit_sync(&self, pending: &mut PendingState) -> Result<(), WlSurfaceError> { pending.explicit_sync = self.sync_obj_surface.is_some(); if !pending.explicit_sync { @@ -1378,8 +1450,8 @@ impl WlSurface { } } self.seat_state.destroy_node(self); - if self.visible.get() { - self.client.state.damage(); + if self.visible.get() && self.toplevel.is_none() { + self.client.state.damage(self.buffer_abs_pos.get()); } if set_invisible { self.visible.set(false); @@ -1757,3 +1829,114 @@ efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, XdgSurfaceError); efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); efrom!(WlSurfaceError, CommitTimelineError); + +#[derive(Copy, Clone, Debug)] +struct DamageMatrix { + transform: Transform, + mx: f64, + my: f64, + dx: f64, + dy: f64, + smear: i32, +} + +impl Default for DamageMatrix { + fn default() -> Self { + Self { + transform: Default::default(), + mx: 1.0, + my: 1.0, + dx: 0.0, + dy: 0.0, + smear: 0, + } + } +} + +impl DamageMatrix { + fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect { + let x1 = rect.x1() - self.smear; + let x2 = rect.x2() + self.smear; + let y1 = rect.y1() - self.smear; + let y2 = rect.y2() + self.smear; + let [x1, y1, x2, y2] = match self.transform { + Transform::None => [x1, y1, x2, y2], + Transform::Rotate90 => [-y2, x1, -y1, x2], + Transform::Rotate180 => [-x2, -y2, -x1, -y1], + Transform::Rotate270 => [y1, -x2, y2, -x1], + Transform::Flip => [-x2, y1, -x1, y2], + Transform::FlipRotate90 => [y1, x1, y2, x2], + Transform::FlipRotate180 => [x1, -y2, x2, -y1], + Transform::FlipRotate270 => [-y2, -x2, -y1, -x1], + }; + let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx; + let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy; + let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx; + let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy; + Rect::new(x1, y1, x2, y2).unwrap() + } + + fn new( + transform: Transform, + legacy_scale: i32, + buffer_width: i32, + buffer_height: i32, + viewport: Option<[Fixed; 4]>, + dst_width: i32, + dst_height: i32, + ) -> DamageMatrix { + let mut buffer_width = buffer_width as f64; + let mut buffer_height = buffer_height as f64; + let dst_width = dst_width as f64; + let dst_height = dst_height as f64; + + let mut mx = 1.0; + let mut my = 1.0; + if legacy_scale != 1 { + let scale_inv = 1.0 / (legacy_scale as f64); + mx = scale_inv; + my = scale_inv; + buffer_width *= scale_inv; + buffer_height *= scale_inv; + } + let (mut buffer_width, mut buffer_height) = + transform.maybe_swap((buffer_width, buffer_height)); + let (mut dx, mut dy) = match transform { + Transform::None => (0.0, 0.0), + Transform::Rotate90 => (buffer_width, 0.0), + Transform::Rotate180 => (buffer_width, buffer_height), + Transform::Rotate270 => (0.0, buffer_height), + Transform::Flip => (buffer_width, 0.0), + Transform::FlipRotate90 => (0.0, 0.0), + Transform::FlipRotate180 => (0.0, buffer_height), + Transform::FlipRotate270 => (buffer_width, buffer_height), + }; + if let Some([x, y, w, h]) = viewport { + dx -= x.to_f64(); + dy -= y.to_f64(); + buffer_width = w.to_f64(); + buffer_height = h.to_f64(); + } + let mut smear = false; + if dst_width != buffer_width { + let scale = dst_width / buffer_width; + mx *= scale; + dx *= scale; + smear |= dst_width > buffer_width; + } + if dst_height != buffer_height { + let scale = dst_height / buffer_height; + my *= scale; + dy *= scale; + smear |= dst_height > buffer_height; + } + DamageMatrix { + transform, + mx, + my, + dx, + dy, + smear: smear as _, + } + } +} diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 44900ca0..00163a0b 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -60,6 +60,16 @@ impl CursorSurface { pub fn update_hardware_cursor(&self) { self.user.update_hardware_cursor(); } + + pub fn needs_damage_tracking(&self) -> bool { + self.user.software_cursor() + } + + pub fn surface_position(&self) -> (i32, i32) { + let (x, y) = self.user.position(); + let (dx, dy) = self.hotspot.get(); + (x.to_int() - dx, y.to_int() - dy) + } } impl Cursor for CursorSurface { diff --git a/src/ifs/wl_surface/dnd_icon.rs b/src/ifs/wl_surface/dnd_icon.rs index e41f11aa..ef1bc180 100644 --- a/src/ifs/wl_surface/dnd_icon.rs +++ b/src/ifs/wl_surface/dnd_icon.rs @@ -18,9 +18,13 @@ impl DndIcon { } fn update_visible(&self) { + let was_visible = self.surface.visible.get(); let is_visible = self.surface.dnd_icons.is_not_empty() && self.surface.client.state.root_visible(); self.surface.set_visible(is_visible); + if was_visible != is_visible { + self.damage(); + } } pub fn enable(self: &Rc) { @@ -45,6 +49,16 @@ impl DndIcon { self.surface.extents.get().move_(x, y) } + pub fn damage(&self) { + let (x, y) = self.seat.pointer_cursor().position_int(); + self.damage_at(x, y); + } + + pub fn damage_at(&self, x: i32, y: i32) { + let extents = self.extents(x, y); + self.surface.client.state.damage(extents); + } + pub fn render(&self, renderer: &mut Renderer<'_>, cursor_rect: &Rect, x: i32, y: i32) { let extents = self.extents(x, y); if extents.intersects(&cursor_rect) { diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index 2e7d37c6..68edb39a 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -352,6 +352,9 @@ impl SurfaceExt for WlSubsurface { if self.had_buffer.replace(has_buffer) != has_buffer { if has_buffer { if self.parent.visible.get() { + let (x, y) = self.surface.buffer_abs_pos.get().position(); + let extents = self.surface.extents.get(); + self.surface.client.state.damage(extents.move_(x, y)); self.surface.set_visible(true); } } else { diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index d9506fbe..49110421 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -252,6 +252,7 @@ impl Xwindow { pub fn map_status_changed(self: &Rc) { let map_change = self.map_change(); + let override_redirect = self.data.info.override_redirect.get(); match map_change { Change::None => return, Change::Unmap => { @@ -261,7 +262,7 @@ impl Xwindow { .set(self.data.info.extents.take()); self.tl_destroy(); } - Change::Map if self.data.info.override_redirect.get() => { + Change::Map if override_redirect => { self.clone() .tl_change_extents(&self.data.info.pending_extents.get()); *self.display_link.borrow_mut() = @@ -284,12 +285,17 @@ impl Xwindow { match map_change { Change::Unmap => self.tl_set_visible(false), Change::Map => { - self.tl_set_visible(true); + if override_redirect { + self.tl_set_visible(true); + } self.toplevel_data.broadcast(self.clone()); } Change::None => {} } self.data.state.tree_changed(); + if override_redirect { + self.data.state.damage(self.data.info.pending_extents.get()); + } } } @@ -414,7 +420,10 @@ impl ToplevelNodeBase for Xwindow { // log::info!("xwin {} change_extents {:?}", self.data.window_id, rect); let old = self.data.info.extents.replace(*rect); if old != *rect { - if !self.data.info.override_redirect.get() { + if self.data.info.override_redirect.get() { + self.data.state.damage(old); + self.data.state.damage(*rect); + } else { self.data .state .xwayland diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 645f79d1..3cf3d575 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -167,6 +167,10 @@ pub trait XdgSurfaceExt: Debug { fn extents_changed(&self) { // nothing } + + fn geometry_changed(&self) { + // nothing + } } impl XdgSurface { @@ -258,6 +262,12 @@ impl XdgSurface { } } + pub fn damage(&self) { + let (x, y) = self.surface.buffer_abs_pos.get().position(); + let extents = self.surface.extents.get(); + self.surface.client.state.damage(extents.move_(x, y)); + } + pub fn geometry(&self) -> Option { self.geometry.get() } @@ -497,6 +507,9 @@ impl SurfaceExt for XdgSurface { if prev != Some(geometry) { self.update_extents(); self.update_surface_position(); + if let Some(ext) = self.ext.get() { + ext.geometry_changed(); + } } } } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 71f96da3..6b608d5e 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -407,11 +407,13 @@ impl XdgToplevel { } self.toplevel_data.broadcast(self.clone()); self.tl_set_visible(self.state.root_visible()); + self.xdg.damage(); } self.extents_changed(); } else { if self.is_mapped.replace(false) { self.tl_set_visible(false); + self.xdg.damage(); } } return; @@ -677,6 +679,14 @@ impl XdgSurfaceExt for XdgToplevel { self.toplevel_data.pos.set(self.xdg.extents.get()); self.tl_extents_changed(); } + + fn geometry_changed(&self) { + self.xdg + .surface + .client + .state + .damage(self.node_absolute_position()); + } } #[derive(Debug, Error)] diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 7c16bc69..f232f532 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -588,6 +588,11 @@ impl SurfaceExt for ZwlrLayerSurfaceV1 { } if self.mapped.get() != was_mapped { output.update_visible(); + if self.mapped.get() { + let (x, y) = self.surface.buffer_abs_pos.get().position(); + let extents = self.surface.extents.get().move_(x, y); + self.client.state.damage(extents); + } } if self.mapped.get() { match self.keyboard_interactivity.get() { diff --git a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs index 022a35c6..9f326e35 100644 --- a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs +++ b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs @@ -44,17 +44,24 @@ pub async fn input_popup_positioning(state: Rc) { } impl ZwpInputPopupSurfaceV2 { + fn damage(&self) { + let (x, y) = self.surface.buffer_abs_pos.get().position(); + let extents = self.surface.extents.get(); + self.client.state.damage(extents.move_(x, y)); + } + pub fn update_visible(self: &Rc) { let was_visible = self.surface.visible.get(); let is_visible = self.surface.buffer.is_some() && self.input_method.connection.is_some() && self.client.state.root_visible(); self.surface.set_visible(is_visible); - if was_visible || is_visible { - self.client.state.damage(); - } - if !was_visible && is_visible { - self.schedule_positioning(); + if was_visible != is_visible { + if is_visible { + self.schedule_positioning(); + } else { + self.damage(); + } } } @@ -132,6 +139,9 @@ impl ZwpInputPopupSurfaceV2 { } fn detach(&self) { + if self.surface.visible.get() { + self.damage(); + } self.surface.destroy_node(); self.surface.unset_ext(); self.input_method.popups.remove(&self.id); diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index df34ec09..e41f5f25 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -9,7 +9,7 @@ use { object::{Object, Version}, rect::Rect, renderer::Renderer, - tree::ToplevelNode, + tree::{Node, ToplevelNode}, utils::clonecell::CloneCell, wire::{xdg_toplevel_drag_v1::*, XdgToplevelDragV1Id}, }, @@ -53,16 +53,22 @@ impl XdgToplevelDragV1 { } } - fn move2(&self, x: i32, y: i32) { + fn move2(&self, x: i32, y: i32, damage_initial: bool) { if let Some(tl) = self.toplevel.get() { + if damage_initial && tl.node_visible() { + tl.xdg.damage(); + } let extents = tl.xdg.absolute_desired_extents.get(); let extents = extents.at_point(x - self.x_off.get(), y - self.y_off.get()); tl.clone().tl_change_extents(&extents); + if tl.node_visible() { + tl.xdg.damage(); + } } } pub fn move_(&self, x: i32, y: i32) { - self.move2(x, y); + self.move2(x, y, true); } pub fn render(&self, renderer: &mut Renderer<'_>, cursor_rect: &Rect, x: i32, y: i32) { @@ -122,7 +128,7 @@ impl XdgToplevelDragV1 { self.client.state.tree_changed(); if let Some(seat) = self.source.data.seat.get() { let (x, y) = seat.pointer_cursor().position_int(); - self.move2(x, y) + self.move2(x, y, false) } } diff --git a/src/state.rs b/src/state.rs index 975568ec..b98bd2d5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -662,7 +662,6 @@ impl State { ws.flush_jay_workspaces(); output.schedule_update_render_data(); self.tree_changed(); - self.damage(); // let seats = self.globals.seats.lock(); // for seat in seats.values() { // seat.workspace_changed(&output); @@ -689,7 +688,6 @@ impl State { for output in outputs.values() { output.set_status(&status); } - self.damage(); } pub fn input_occurred(&self) { @@ -731,10 +729,14 @@ impl State { serial as _ } - pub fn damage(&self) { - for connector in self.connectors.lock().values() { - if connector.connected.get() { - connector.connector.damage(); + pub fn damage(&self, rect: Rect) { + if rect.is_empty() { + return; + } + self.damage_visualizer.add(rect); + for output in self.root.outputs.lock().values() { + if output.global.pos.get().intersects(&rect) { + output.global.connector.connector.damage(); } } } @@ -748,7 +750,7 @@ impl State { } } self.tree_changed(); - self.damage(); + self.damage(self.root.extents.get()); } pub fn clear(&self) { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 09184858..dabeb8f5 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -172,7 +172,9 @@ impl ConnectorHandler { update_render_data_scheduled: Cell::new(false), hardware_cursor_needs_render: Cell::new(false), screencopies: Default::default(), + title_visible: Default::default(), }); + on.update_visible(); on.update_rects(); self.state .add_output_scale(on.global.persistent.scale.get()); @@ -295,7 +297,7 @@ impl ConnectorHandler { .remove_output_scale(on.global.persistent.scale.get()); let _ = self.state.remove_global(&*global); self.state.tree_changed(); - self.state.damage(); + self.state.damage(self.state.root.extents.get()); } async fn handle_non_desktop_connected(&self, monitor_info: MonitorInfo) { diff --git a/src/tree/container.rs b/src/tree/container.rs index 7396c6d5..6f0c6563 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -355,9 +355,24 @@ impl ContainerNode { self.schedule_compute_render_data(); } + fn damage(&self) { + self.state.damage( + Rect::new_sized( + self.abs_x1.get(), + self.abs_y1.get(), + self.width.get(), + self.height.get(), + ) + .unwrap(), + ); + } + fn schedule_layout(self: &Rc) { if !self.layout_scheduled.replace(true) { self.state.pending_container_layout.push(self.clone()); + if self.toplevel_data.visible.get() { + self.damage(); + } } } @@ -677,8 +692,13 @@ impl ContainerNode { let split = self.split.get(); let have_active = self.children.iter().any(|c| c.active.get()); let scales = self.state.scales.lock(); + let abs_x = self.abs_x1.get(); + let abs_y = self.abs_y1.get(); for (i, child) in self.children.iter().enumerate() { let rect = child.title_rect.get(); + if self.toplevel_data.visible.get() { + self.state.damage(rect.move_(abs_x, abs_y)); + } if i > 0 { let rect = if mono { Rect::new_sized(rect.x1() - bw, 0, bw, th) @@ -1471,6 +1491,7 @@ impl ContainingNode for ContainerNode { if let Some(body) = body { let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); new.clone().tl_change_extents(&body); + self.state.damage(body); } } diff --git a/src/tree/display.rs b/src/tree/display.rs index 87c3cf61..0507372d 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -79,6 +79,9 @@ impl DisplayNode { for seat in state.globals.seats.lock().values() { seat.set_visible(visible); } + if visible { + state.damage(self.extents.get()); + } } } diff --git a/src/tree/float.rs b/src/tree/float.rs index a14a5194..1fc4350f 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -135,6 +135,9 @@ impl FloatNode { child.tl_set_visible(floater.visible.get()); child.tl_restack_popups(); floater.schedule_layout(); + if floater.visible.get() { + state.damage(position); + } floater } @@ -199,11 +202,12 @@ impl FloatNode { _ => return, }; let scales = self.state.scales.lock(); + let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap(); for (scale, _) in scales.iter() { let old_tex = self.title_textures.remove(scale); - let mut th = th; + let mut th = tr.height(); let mut scalef = None; - let mut width = pos.width() - 2 * bw; + let mut width = tr.width(); if *scale != 1 { let scale = scale.to_f64(); th = (th as f64 * scale).round() as _; @@ -222,6 +226,9 @@ impl FloatNode { }; self.title_textures.set(*scale, texture); } + if self.visible.get() { + self.state.damage(tr); + } } fn pointer_move( @@ -307,8 +314,12 @@ impl FloatNode { y2 = y2.max(y1 + 2 * bw + th + 1); } } - self.position.set(Rect::new(x1, y1, x2, y2).unwrap()); - self.state.damage(); + let new_pos = Rect::new(x1, y1, x2, y2).unwrap(); + self.position.set(new_pos); + if self.visible.get() { + self.state.damage(pos); + self.state.damage(new_pos); + } self.schedule_layout(); return; } @@ -684,6 +695,9 @@ impl ContainingNode for FloatNode { self.pull_child_properties(); new.tl_set_visible(self.visible.get()); self.schedule_layout(); + if self.visible.get() { + self.state.damage(self.position.get()); + } } fn cnode_remove_child2(self: Rc, _child: &dyn Node, _preserve_focus: bool) { @@ -691,6 +705,9 @@ impl ContainingNode for FloatNode { self.child.set(None); self.display_link.borrow_mut().take(); self.workspace_link.set(None); + if self.visible.get() { + self.state.damage(self.position.get()); + } } fn cnode_accepts_child(&self, _node: &dyn Node) -> bool { @@ -716,8 +733,10 @@ impl ContainingNode for FloatNode { let (x, y) = (x - bw, y - th - bw - 1); let pos = self.position.get(); if pos.position() != (x, y) { - self.position.set(pos.at_point(x, y)); - self.state.damage(); + let new_pos = pos.at_point(x, y); + self.position.set(new_pos); + self.state.damage(pos); + self.state.damage(new_pos); self.schedule_layout(); } } @@ -753,7 +772,10 @@ impl ContainingNode for FloatNode { let new_pos = Rect::new(x1, y1, x2, y2).unwrap(); if new_pos != pos { self.position.set(new_pos); - self.state.damage(); + if self.visible.get() { + self.state.damage(pos); + self.state.damage(new_pos); + } self.schedule_layout(); } } diff --git a/src/tree/output.rs b/src/tree/output.rs index 92aa6253..08b1a4ee 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -76,6 +76,7 @@ pub struct OutputNode { pub update_render_data_scheduled: Cell, pub screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc>, + pub title_visible: Cell, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -114,6 +115,9 @@ impl OutputNode { if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect.get()); } + if self.node_visible() { + self.state.damage(self.global.pos.get()); + } } } @@ -307,7 +311,8 @@ impl OutputNode { texture_height = (th as f64 * scale).round() as _; } let active_id = self.workspace.get().map(|w| w.id); - let output_width = self.non_exclusive_rect.get().width(); + let non_exclusive_rect = self.non_exclusive_rect.get(); + let output_width = non_exclusive_rect.width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { let old_tex = ws.title_texture.take(); @@ -414,7 +419,16 @@ impl OutputNode { tex: title, }); } - self.state.damage(); + if self.title_visible.get() { + let title_rect = Rect::new_sized( + non_exclusive_rect.x1(), + non_exclusive_rect.y1(), + non_exclusive_rect.width(), + th, + ) + .unwrap(); + self.state.damage(title_rect); + } } pub fn ensure_workspace(self: &Rc) -> Rc { @@ -460,12 +474,16 @@ impl OutputNode { for seat in seats { ws.clone().node_do_focus(&seat, Direction::Unspecified); } + if self.node_visible() { + self.state.damage(self.global.pos.get()); + } true } pub fn create_workspace(self: &Rc, name: &str) -> Rc { let ws = Rc::new(WorkspaceNode { id: self.state.node_ids.next(), + state: self.state.clone(), is_dummy: false, output: CloneCell::new(self.clone()), position: Cell::new(Default::default()), @@ -582,6 +600,11 @@ impl OutputNode { } fn change_extents_(self: &Rc, rect: &Rect) { + if self.node_visible() { + let old_pos = self.global.pos.get(); + self.state.damage(old_pos); + self.state.damage(*rect); + } self.global.persistent.pos.set((rect.x1(), rect.y1())); self.global.pos.set(*rect); self.state.root.update_extents(); @@ -702,6 +725,13 @@ impl OutputNode { prev } + pub fn fullscreen_changed(&self) { + self.update_visible(); + if self.node_visible() { + self.state.damage(self.global.pos.get()); + } + } + pub fn update_visible(&self) { let mut visible = self.state.root_visible(); if self.state.lock.locked.get() { @@ -722,6 +752,7 @@ impl OutputNode { have_fullscreen = ws.fullscreen.is_some(); } let lower_visible = visible && !have_fullscreen; + self.title_visible.set(lower_visible); set_layer_visible!(self.layers[0], lower_visible); set_layer_visible!(self.layers[1], lower_visible); if let Some(ws) = self.workspace.get() { @@ -822,7 +853,7 @@ impl Node for OutputNode { } fn node_visible(&self) -> bool { - true + self.state.root_visible() } fn node_absolute_position(&self) -> Rect { diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index ee6ecdfc..5f9ccbaf 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -435,7 +435,6 @@ impl ToplevelData { .tl_into_node() .node_do_focus(&seat, Direction::Unspecified); } - state.damage(); } pub fn unset_fullscreen(&self, state: &Rc, node: Rc) { @@ -480,7 +479,6 @@ impl ToplevelData { fd.placeholder .node_seat_state() .destroy_node(fd.placeholder.deref()); - state.damage(); } pub fn set_visible(&self, node: &dyn Node, visible: bool) { @@ -496,7 +494,6 @@ impl ToplevelData { if let Some(parent) = self.parent.get() { parent.cnode_child_attention_request_changed(node, false); } - self.state.damage(); } pub fn request_attention(&self, node: &dyn Node) { @@ -510,6 +507,5 @@ impl ToplevelData { if let Some(parent) = self.parent.get() { parent.cnode_child_attention_request_changed(node, true); } - self.state.damage(); } } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index ed2835a5..7e8c1647 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -11,6 +11,7 @@ use { }, rect::Rect, renderer::Renderer, + state::State, text::TextTexture, tree::{ container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction, @@ -38,6 +39,7 @@ tree_id!(WorkspaceNodeId); pub struct WorkspaceNode { pub id: WorkspaceNodeId, + pub state: Rc, pub is_dummy: bool, pub output: CloneCell>, pub position: Cell, @@ -85,6 +87,7 @@ impl WorkspaceNode { } if self.has_capture.replace(has_capture) != has_capture { output.schedule_update_render_data(); + output.state.damage(output.global.pos.get()); } } @@ -117,6 +120,7 @@ impl WorkspaceNode { container.tl_set_parent(self.clone()); container.tl_set_visible(self.container_visible()); self.container.set(Some(container.clone())); + self.state.damage(self.position.get()); } pub fn is_empty(&self) -> bool { @@ -168,7 +172,7 @@ impl WorkspaceNode { } self.pull_child_properties(&**node); if self.visible.get() { - self.output.get().update_visible(); + self.output.get().fullscreen_changed(); } else { node.tl_set_visible(false); } @@ -183,7 +187,7 @@ impl WorkspaceNode { if let Some(node) = self.fullscreen.take() { self.discard_child_properties(&*node); if self.visible.get() { - self.output.get().update_visible(); + self.output.get().fullscreen_changed(); } if let Some(surface) = node.tl_scanout_surface() { if let Some(fb) = surface.client.state.drm_feedback.get() { @@ -324,6 +328,7 @@ impl ContainingNode for WorkspaceNode { if container.node_id() == child.node_id() { self.discard_child_properties(&*container); self.container.set(None); + self.state.damage(self.position.get()); return; } } @@ -387,4 +392,10 @@ pub fn move_ws_to_output( if !source.is_dummy { source.schedule_update_render_data(); } + if source.node_visible() { + target.state.damage(source.global.pos.get()); + } + if target.node_visible() { + target.state.damage(target.global.pos.get()); + } }