From a53d64053f8c4e6f48c1f2025cc7bc325b162ef7 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Fri, 14 Jan 2022 18:51:44 +0100 Subject: [PATCH 01/41] space: Handle multiple overlapping rects when merging damage --- src/desktop/space/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 47116b4..7908b1f 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -557,12 +557,15 @@ impl Space { damage.retain(|rect| rect.overlaps(output_geo)); damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); // merge overlapping rectangles - damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| { - if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) { - *existing = existing.merge(rect); - } else { - new_damage.push(rect); + damage = damage.into_iter().fold(Vec::new(), |new_damage, mut rect| { + // replace with drain_filter, when that becomes stable to reuse the original Vec's memory + let (overlapping, mut new_damage): (Vec<_>, Vec<_>) = + new_damage.into_iter().partition(|other| other.overlaps(rect)); + + for overlap in overlapping { + rect = rect.merge(overlap); } + new_damage.push(rect); new_damage }); From f76311227be0df854aa16ed2e1f006050e152557 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Fri, 14 Jan 2022 22:01:07 +0100 Subject: [PATCH 02/41] gles2: Apply output transformations for damage --- src/backend/renderer/gles2/mod.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index adad36f..c620dc5 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1295,13 +1295,19 @@ impl Frame for Gles2Frame { let rect_constrained_loc = rect .loc .constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point())); - let rect_clamped_size = rect.size.clamp((0f64, 0f64), dest.size); + let rect_clamped_size = rect.size.clamp( + (0f64, 0f64), + (dest.size.to_point() - rect_constrained_loc).to_size(), + ); + + let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); + let rect_transformed = self.transformation().transform_rect_in(rect, &dest.size); [ - (rect_constrained_loc.x / dest.size.w) as f32, - (rect_constrained_loc.y / dest.size.h) as f32, - (rect_clamped_size.w / dest.size.w) as f32, - (rect_clamped_size.h / dest.size.h) as f32, + (rect_transformed.loc.x / dest.size.w) as f32, + (rect_transformed.loc.y / dest.size.h) as f32, + (rect_transformed.size.w / dest.size.w) as f32, + (rect_transformed.size.h / dest.size.h) as f32, ] }) .flatten() From 75162e93c71cf897a0457bec6e1a987782087441 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 15 Jan 2022 20:23:08 +0100 Subject: [PATCH 03/41] fix setting the parent of a ToplevelSurface --- src/wayland/shell/xdg/mod.rs | 7 +++---- src/wayland/xdg_foreign/mod.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index cc6f4a3..ed7029c 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -1169,15 +1169,14 @@ impl ToplevelSurface { /// The parent must be another toplevel equivalent surface. /// /// If the parent is `None`, the parent-child relationship is removed. - pub fn set_parent(&self, parent: Option) -> bool { + pub fn set_parent(&self, parent: Option<&wl_surface::WlSurface>) -> bool { if let Some(parent) = parent { - if !is_toplevel_equivalent(&parent) { + if !is_toplevel_equivalent(parent) { return false; } } - // Unset the parent - xdg_handlers::set_parent(&self.shell_surface, None); + xdg_handlers::set_parent(&self.shell_surface, parent.cloned()); true } diff --git a/src/wayland/xdg_foreign/mod.rs b/src/wayland/xdg_foreign/mod.rs index 0e18289..9a83f6d 100644 --- a/src/wayland/xdg_foreign/mod.rs +++ b/src/wayland/xdg_foreign/mod.rs @@ -447,7 +447,7 @@ fn imported_implementation( .map(|export| export.surface.clone()) .unwrap(); - toplevel_surface.set_parent(Some(imported_parent)); + toplevel_surface.set_parent(Some(&imported_parent)); } _ => unreachable!(), From 8edcdf5cd04a89ddba7865866e25e74fda6afd48 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 8 Jan 2022 12:22:59 +0100 Subject: [PATCH 04/41] add a window surface type --- src/desktop/layer.rs | 7 +++++-- src/desktop/utils.rs | 27 ++++++++++++++++++--------- src/desktop/window.rs | 40 +++++++++++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 1dc4b9a..a01f8e9 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -21,6 +21,8 @@ use std::{ sync::{Arc, Mutex, Weak}, }; +use super::WindowSurfaceType; + crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS); /// Map of [`LayerSurface`]s on an [`Output`] @@ -428,6 +430,7 @@ impl LayerSurface { pub fn surface_under>>( &self, point: P, + surface_type: WindowSurfaceType, ) -> Option<(WlSurface, Point)> { let point = point.into(); if let Some(surface) = self.get_surface() { @@ -438,13 +441,13 @@ impl LayerSurface { { if let Some(result) = popup .get_surface() - .and_then(|surface| under_from_surface_tree(surface, point, location)) + .and_then(|surface| under_from_surface_tree(surface, point, location, surface_type)) { return Some(result); } } - under_from_surface_tree(surface, point, (0, 0)) + under_from_surface_tree(surface, point, (0, 0), surface_type) } else { None } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 380ae4e..f8d774c 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -16,6 +16,8 @@ use wayland_server::protocol::wl_surface; use std::cell::RefCell; +use super::WindowSurfaceType; + impl SurfaceState { /// Returns the size of the surface. pub fn size(&self) -> Option> { @@ -174,6 +176,7 @@ pub fn under_from_surface_tree

( surface: &wl_surface::WlSurface, point: Point, location: P, + surface_type: WindowSurfaceType, ) -> Option<(wl_surface::WlSurface, Point)> where P: Into>, @@ -191,17 +194,23 @@ where location += current.location; } - let contains_the_point = data - .map(|data| { - data.borrow() - .contains_point(&*states.cached_state.current(), point - location.to_f64()) - }) - .unwrap_or(false); - if contains_the_point { - *found.borrow_mut() = Some((wl_surface.clone(), location)); + if states.role == Some("subsurface") || surface_type.contains(WindowSurfaceType::TOPLEVEL) { + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), point - location.to_f64()) + }) + .unwrap_or(false); + if contains_the_point { + *found.borrow_mut() = Some((wl_surface.clone(), location)); + } } - TraversalAction::DoChildren(location) + if surface_type.contains(WindowSurfaceType::SUBSURFACE) { + TraversalAction::DoChildren(location) + } else { + TraversalAction::SkipChildren + } }, |_, _, _| {}, |_, _, _| { diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 1c57b1a..4e5761f 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -109,6 +109,21 @@ impl Hash for Window { } } +bitflags::bitflags! { + /// Defines the surface types that can be + /// queried with [`Window::surface_under`] + pub struct WindowSurfaceType: u32 { + /// Include the toplevel surface + const TOPLEVEL = 1; + /// Include all subsurfaces + const SUBSURFACE = 2; + /// Include all popup surfaces + const POPUP = 4; + /// Query all surfaces + const ALL = Self::TOPLEVEL.bits | Self::SUBSURFACE.bits | Self::POPUP.bits; + } +} + impl Window { /// Construct a new [`Window`] from a given compatible toplevel surface pub fn new(toplevel: Kind) -> Window { @@ -222,24 +237,27 @@ impl Window { pub fn surface_under>>( &self, point: P, + surface_type: WindowSurfaceType, ) -> Option<(wl_surface::WlSurface, Point)> { let point = point.into(); if let Some(surface) = self.0.toplevel.get_surface() { - for (popup, location) in PopupManager::popups_for_surface(surface) - .ok() - .into_iter() - .flatten() - { - let offset = self.geometry().loc + location - popup.geometry().loc; - if let Some(result) = popup - .get_surface() - .and_then(|surface| under_from_surface_tree(surface, point, offset)) + if surface_type.contains(WindowSurfaceType::POPUP) { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() { - return Some(result); + let offset = self.geometry().loc + location - popup.geometry().loc; + if let Some(result) = popup + .get_surface() + .and_then(|surface| under_from_surface_tree(surface, point, offset, surface_type)) + { + return Some(result); + } } } - under_from_surface_tree(surface, point, (0, 0)) + under_from_surface_tree(surface, point, (0, 0), surface_type) } else { None } From 070dc78c119cfbc74633da723e764d8525697531 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Sat, 8 Jan 2022 22:58:23 +0100 Subject: [PATCH 05/41] add the keyboardgrab and rename GrabStartData to PointerGrabStartData --- CHANGELOG.md | 2 + anvil/src/shell.rs | 10 +- src/wayland/data_device/dnd_grab.rs | 8 +- src/wayland/data_device/mod.rs | 4 +- src/wayland/data_device/server_dnd_grab.rs | 8 +- src/wayland/seat/keyboard.rs | 301 +++++++++++++++++---- src/wayland/seat/mod.rs | 8 +- 7 files changed, 276 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 110b268..b853f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - `PointerButtonEvent::button` now returns an `Option`. - `MouseButton` is now non-exhaustive. - Remove `Other` and add `Forward` and `Back` variants to `MouseButton`. Use the new `PointerButtonEvent::button_code` in place of `Other`. +- `GrabStartData` has been renamed to `PointerGrabStartData` #### Backends @@ -49,6 +50,7 @@ - Support for `xdg_wm_base` protocol version 3 - Added the option to initialize the dmabuf global with a client filter - `wayland::output::Output` now has user data attached to it and more functions to query its properties +- Added a `KeyboardGrab` similar to the existing `PointerGrab` #### Backends diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index 51c2d0d..bb0ee55 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -19,7 +19,7 @@ use smithay::{ compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment, SurfaceAttributes, TraversalAction, }, - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, shell::{ legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind}, wlr_layer::{LayerShellRequest, LayerSurfaceAttributes}, @@ -39,7 +39,7 @@ use crate::{ }; struct MoveSurfaceGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, window_map: Rc>, toplevel: SurfaceKind, initial_window_location: Point, @@ -82,7 +82,7 @@ impl PointerGrab for MoveSurfaceGrab { handle.axis(details) } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } @@ -130,7 +130,7 @@ impl From for xdg_toplevel::ResizeEdge { } struct ResizeSurfaceGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, toplevel: SurfaceKind, edges: ResizeEdge, initial_window_size: Size, @@ -280,7 +280,7 @@ impl PointerGrab for ResizeSurfaceGrab { handle.axis(details) } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index d8e02be..c15daff 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -8,7 +8,7 @@ use wayland_server::{ use crate::{ utils::{Logical, Point}, wayland::{ - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, Serial, }, }; @@ -16,7 +16,7 @@ use crate::{ use super::{with_source_metadata, DataDeviceData, SeatData}; pub(crate) struct DnDGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, data_source: Option, current_focus: Option, pending_offers: Vec, @@ -29,7 +29,7 @@ pub(crate) struct DnDGrab { impl DnDGrab { pub(crate) fn new( - start_data: GrabStartData, + start_data: PointerGrabStartData, source: Option, origin: wl_surface::WlSurface, seat: Seat, @@ -222,7 +222,7 @@ impl PointerGrab for DnDGrab { handle.axis(details); } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 125a840..d74d504 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -60,7 +60,7 @@ use slog::{debug, error, o}; use crate::wayland::{ compositor, - seat::{GrabStartData, Seat}, + seat::{PointerGrabStartData, Seat}, Serial, }; @@ -335,7 +335,7 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec) { pub fn start_dnd( seat: &Seat, serial: Serial, - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: SourceMetadata, callback: C, ) where diff --git a/src/wayland/data_device/server_dnd_grab.rs b/src/wayland/data_device/server_dnd_grab.rs index a1a554b..2f32c5d 100644 --- a/src/wayland/data_device/server_dnd_grab.rs +++ b/src/wayland/data_device/server_dnd_grab.rs @@ -8,7 +8,7 @@ use wayland_server::{ use crate::{ utils::{Logical, Point}, wayland::{ - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, Serial, }, }; @@ -42,7 +42,7 @@ pub enum ServerDndEvent { } pub(crate) struct ServerDnDGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: super::SourceMetadata, current_focus: Option, pending_offers: Vec, @@ -53,7 +53,7 @@ pub(crate) struct ServerDnDGrab { impl ServerDnDGrab { pub(crate) fn new( - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: super::SourceMetadata, seat: Seat, callback: Rc>, @@ -222,7 +222,7 @@ where handle.axis(details); } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 0872ce8..f64c1b1 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -86,9 +86,16 @@ pub struct XkbConfig<'a> { pub options: Option, } +enum GrabStatus { + None, + Active(Serial, Box), + Borrowed, +} + struct KbdInternal { known_kbds: Vec, focus: Option, + pending_focus: Option, pressed_keys: Vec, mods_state: ModifiersState, keymap: xkb::Keymap, @@ -96,6 +103,7 @@ struct KbdInternal { repeat_rate: i32, repeat_delay: i32, focus_hook: Box)>, + grab: GrabStatus, } // focus_hook does not implement debug, so we have to impl Debug manually @@ -147,6 +155,7 @@ impl KbdInternal { Ok(KbdInternal { known_kbds: Vec::new(), focus: None, + pending_focus: None, pressed_keys: Vec::new(), mods_state: ModifiersState::default(), keymap, @@ -154,6 +163,7 @@ impl KbdInternal { repeat_rate, repeat_delay, focus_hook, + grab: GrabStatus::None, }) } @@ -215,6 +225,35 @@ impl KbdInternal { } } } + + fn with_grab(&mut self, f: F, logger: ::slog::Logger) + where + F: FnOnce(KeyboardInnerHandle<'_>, &mut dyn KeyboardGrab), + { + let mut grab = ::std::mem::replace(&mut self.grab, GrabStatus::Borrowed); + match grab { + GrabStatus::Borrowed => panic!("Accessed a keyboard grab from within a keyboard grab access."), + GrabStatus::Active(_, ref mut handler) => { + // If this grab is associated with a surface that is no longer alive, discard it + if let Some(ref surface) = handler.start_data().focus { + if !surface.as_ref().is_alive() { + self.grab = GrabStatus::None; + f(KeyboardInnerHandle { inner: self, logger }, &mut DefaultGrab); + return; + } + } + f(KeyboardInnerHandle { inner: self, logger }, &mut **handler); + } + GrabStatus::None => { + f(KeyboardInnerHandle { inner: self, logger }, &mut DefaultGrab); + } + } + + if let GrabStatus::Borrowed = self.grab { + // the grab has not been ended nor replaced, put it back in place + self.grab = grab; + } + } } /// Errors that can be encountered when creating a keyboard handler @@ -320,6 +359,47 @@ pub enum FilterResult { Intercept(T), } +/// Data about the event that started the grab. +#[derive(Debug, Clone)] +pub struct GrabStartData { + /// The focused surface, if any, at the start of the grab. + pub focus: Option, +} + +/// A trait to implement a keyboard grab +/// +/// In some context, it is necessary to temporarily change the behavior of the keyboard. This is +/// typically known as a keyboard grab. A example would be, during a popup grab the keyboard focus +/// will not be changed and stay on the grabbed popup. +/// +/// This trait is the interface to intercept regular keyboard events and change them as needed, its +/// interface mimics the [`KeyboardHandle`] interface. +/// +/// If your logic decides that the grab should end, both [`KeyboardInnerHandle`] and [`KeyboardHandle`] have +/// a method to change it. +/// +/// When your grab ends (either as you requested it or if it was forcefully cancelled by the server), +/// the struct implementing this trait will be dropped. As such you should put clean-up logic in the destructor, +/// rather than trying to guess when the grab will end. +pub trait KeyboardGrab { + /// An input was reported + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ); + + /// A focus change was requested + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial); + + /// The data about the event that started the grab. + fn start_data(&self) -> &GrabStartData; +} + /// An handle to a keyboard handler /// /// It can be cloned and all clones manipulate the same internal state. @@ -337,6 +417,42 @@ pub struct KeyboardHandle { } impl KeyboardHandle { + /// Change the current grab on this keyboard to the provided grab + /// + /// Overwrites any current grab. + pub fn set_grab(&self, grab: G, serial: Serial) { + self.arc.internal.borrow_mut().grab = GrabStatus::Active(serial, Box::new(grab)); + } + + /// Remove any current grab on this keyboard, resetting it to the default behavior + pub fn unset_grab(&self) { + self.arc.internal.borrow_mut().grab = GrabStatus::None; + } + + /// Check if this keyboard is currently grabbed with this serial + pub fn has_grab(&self, serial: Serial) -> bool { + let guard = self.arc.internal.borrow_mut(); + match guard.grab { + GrabStatus::Active(s, _) => s == serial, + _ => false, + } + } + + /// Check if this keyboard is currently being grabbed + pub fn is_grabbed(&self) -> bool { + let guard = self.arc.internal.borrow_mut(); + !matches!(guard.grab, GrabStatus::None) + } + + /// Returns the start data for the grab, if any. + pub fn grab_start_data(&self) -> Option { + let guard = self.arc.internal.borrow(); + match &guard.grab { + GrabStatus::Active(_, g) => Some(g.start_data().clone()), + _ => None, + } + } + /// Handle a keystroke /// /// All keystrokes from the input backend should be fed _in order_ to this method of the @@ -392,14 +508,12 @@ impl KeyboardHandle { KeyState::Pressed => WlKeyState::Pressed, KeyState::Released => WlKeyState::Released, }; - guard.with_focused_kbds(|kbd, _| { - // key event must be sent before modifers event for libxkbcommon - // to process them correctly - kbd.key(serial.into(), time, keycode, wl_state); - if let Some((dep, la, lo, gr)) = modifiers { - kbd.modifiers(serial.into(), dep, la, lo, gr); - } - }); + guard.with_grab( + move |mut handle, grab| { + grab.input(&mut handle, keycode, wl_state, modifiers, serial, time); + }, + self.arc.logger.clone(), + ); if guard.focus.is_some() { trace!(self.arc.logger, "Input forwarded to client"); } else { @@ -417,44 +531,13 @@ impl KeyboardHandle { /// a [`wl_keyboard::Event::Enter`](wayland_server::protocol::wl_keyboard::Event::Enter) event will be sent. pub fn set_focus(&self, focus: Option<&WlSurface>, serial: Serial) { let mut guard = self.arc.internal.borrow_mut(); - - let same = guard - .focus - .as_ref() - .and_then(|f| focus.map(|s| s.as_ref().equals(f.as_ref()))) - .unwrap_or(false); - - if !same { - // unset old focus - guard.with_focused_kbds(|kbd, s| { - kbd.leave(serial.into(), s); - }); - - // set new focus - guard.focus = focus.cloned(); - let (dep, la, lo, gr) = guard.serialize_modifiers(); - let keys = guard.serialize_pressed_keys(); - guard.with_focused_kbds(|kbd, surface| { - kbd.enter(serial.into(), surface, keys.clone()); - // Modifiers must be send after enter event. - kbd.modifiers(serial.into(), dep, la, lo, gr); - }); - { - let KbdInternal { - ref focus, - ref mut focus_hook, - .. - } = *guard; - focus_hook(focus.as_ref()); - } - if guard.focus.is_some() { - trace!(self.arc.logger, "Focus set to new surface"); - } else { - trace!(self.arc.logger, "Focus unset"); - } - } else { - trace!(self.arc.logger, "Focus unchanged"); - } + guard.pending_focus = focus.cloned(); + guard.with_grab( + move |mut handle, grab| { + grab.set_focus(&mut handle, focus, serial); + }, + self.arc.logger.clone(), + ); } /// Check if given client currently has keyboard focus @@ -543,3 +626,129 @@ pub(crate) fn implement_keyboard(keyboard: Main, handle: Option<&Key keyboard.deref().clone() } + +/// This inner handle is accessed from inside a keyboard grab logic, and directly +/// sends event to the client +#[derive(Debug)] +pub struct KeyboardInnerHandle<'a> { + inner: &'a mut KbdInternal, + logger: ::slog::Logger, +} + +impl<'a> KeyboardInnerHandle<'a> { + /// Change the current grab on this keyboard to the provided grab + /// + /// Overwrites any current grab. + pub fn set_grab(&mut self, serial: Serial, grab: G) { + self.inner.grab = GrabStatus::Active(serial, Box::new(grab)); + } + + /// Remove any current grab on this keyboard, resetting it to the default behavior + /// + /// This will also restore the focus of the underlying keyboard if restore_focus + /// is [`true`] + pub fn unset_grab(&mut self, serial: Serial, restore_focus: bool) { + self.inner.grab = GrabStatus::None; + // restore the focus + if restore_focus { + let focus = self.inner.pending_focus.clone(); + self.set_focus(focus.as_ref(), serial); + } + } + + /// Access the current focus of this keyboard + pub fn current_focus(&self) -> Option<&WlSurface> { + self.inner.focus.as_ref() + } + + /// Send the input to the focused keyboards + pub fn input( + &mut self, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ) { + self.inner.with_focused_kbds(|kbd, _| { + // key event must be sent before modifers event for libxkbcommon + // to process them correctly + kbd.key(serial.into(), time, keycode, key_state); + if let Some((dep, la, lo, gr)) = modifiers { + kbd.modifiers(serial.into(), dep, la, lo, gr); + } + }); + } + + /// Set the current focus of this keyboard + /// + /// If the new focus is different from the previous one, any previous focus + /// will be sent a [`wl_keyboard::Event::Leave`](wayland_server::protocol::wl_keyboard::Event::Leave) + /// event, and if the new focus is not `None`, + /// a [`wl_keyboard::Event::Enter`](wayland_server::protocol::wl_keyboard::Event::Enter) event will be sent. + pub fn set_focus(&mut self, focus: Option<&WlSurface>, serial: Serial) { + let same = self + .inner + .focus + .as_ref() + .and_then(|f| focus.map(|s| s.as_ref().equals(f.as_ref()))) + .unwrap_or(false); + + if !same { + // unset old focus + self.inner.with_focused_kbds(|kbd, s| { + kbd.leave(serial.into(), s); + }); + + // set new focus + self.inner.focus = focus.cloned(); + let (dep, la, lo, gr) = self.inner.serialize_modifiers(); + let keys = self.inner.serialize_pressed_keys(); + self.inner.with_focused_kbds(|kbd, surface| { + kbd.enter(serial.into(), surface, keys.clone()); + // Modifiers must be send after enter event. + kbd.modifiers(serial.into(), dep, la, lo, gr); + }); + { + let KbdInternal { + ref focus, + ref mut focus_hook, + .. + } = *self.inner; + focus_hook(focus.as_ref()); + } + if self.inner.focus.is_some() { + trace!(self.logger, "Focus set to new surface"); + } else { + trace!(self.logger, "Focus unset"); + } + } else { + trace!(self.logger, "Focus unchanged"); + } + } +} + +// The default grab, the behavior when no particular grab is in progress +struct DefaultGrab; + +impl KeyboardGrab for DefaultGrab { + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ) { + handle.input(keycode, key_state, modifiers, serial, time) + } + + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial) { + handle.set_focus(focus, serial) + } + + fn start_data(&self) -> &GrabStartData { + unreachable!() + } +} diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 7055106..754dad5 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -41,12 +41,12 @@ mod pointer; pub use self::{ keyboard::{ - keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, KeysymHandle, ModifiersState, - XkbConfig, + keysyms, Error as KeyboardError, FilterResult, GrabStartData as KeyboardGrabStartData, KeyboardGrab, + KeyboardHandle, KeyboardInnerHandle, Keysym, KeysymHandle, ModifiersState, XkbConfig, }, pointer::{ - AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, - PointerInnerHandle, + AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData as PointerGrabStartData, + PointerGrab, PointerHandle, PointerInnerHandle, }, }; From 9cb64b9a7f9b4ad2b5c308f85b191078c1b8d031 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Thu, 13 Jan 2022 20:24:18 +0100 Subject: [PATCH 06/41] track if a popup has been committed --- src/wayland/shell/xdg/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index cc6f4a3..a70c928 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -383,6 +383,14 @@ xdg_role!( /// It is a protocol error to call commit on a wl_surface with /// the xdg_popup role when no parent is set. pub parent: Option, + + /// Defines if the surface has received at least one commit + /// + /// This can be used to check for protocol errors, like + /// checking if a popup requested a grab after it has been + /// mapped. + pub committed: bool, + popup_handle: Option } ); @@ -1399,6 +1407,7 @@ impl PopupSurface { .unwrap() .lock() .unwrap(); + attributes.committed = true; if attributes.initial_configure_sent { if let Some(state) = attributes.last_acked { if state != attributes.current { From 444a7f2be1ea753e10841ad3f449ce435060347b Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Wed, 12 Jan 2022 10:27:26 +0100 Subject: [PATCH 07/41] add support for popup grabs --- src/desktop/popup/grab.rs | 501 +++++++++++++++++++++ src/desktop/{popup.rs => popup/manager.rs} | 264 +++++++---- src/desktop/popup/mod.rs | 96 ++++ src/wayland/seat/pointer.rs | 20 +- 4 files changed, 783 insertions(+), 98 deletions(-) create mode 100644 src/desktop/popup/grab.rs rename src/desktop/{popup.rs => popup/manager.rs} (54%) create mode 100644 src/desktop/popup/mod.rs diff --git a/src/desktop/popup/grab.rs b/src/desktop/popup/grab.rs new file mode 100644 index 0000000..25f3c65 --- /dev/null +++ b/src/desktop/popup/grab.rs @@ -0,0 +1,501 @@ +use std::sync::{Arc, Mutex}; + +use wayland_server::protocol::{wl_keyboard::KeyState, wl_pointer::ButtonState, wl_surface::WlSurface}; + +use crate::{ + utils::{DeadResource, Logical, Point}, + wayland::{ + compositor::get_role, + seat::{ + AxisFrame, KeyboardGrab, KeyboardGrabStartData, KeyboardHandle, KeyboardInnerHandle, PointerGrab, + PointerGrabStartData, PointerInnerHandle, + }, + shell::xdg::XDG_POPUP_ROLE, + Serial, + }, +}; + +use thiserror::Error; + +use super::{PopupKind, PopupManager}; + +/// Defines the possible errors that +/// can be returned from [`PopupManager::grab_popup`] +#[derive(Debug, Error)] +pub enum PopupGrabError { + /// This resource has been destroyed and can no longer be used. + #[error(transparent)] + DeadResource(#[from] DeadResource), + /// The client tried to grab a popup after it's parent has been dismissed + #[error("the parent of the popup has been already dismissed")] + ParentDismissed, + /// The client tried to grab a popup after it has been mapped + #[error("tried to grab after being mapped")] + InvalidGrab, + /// The client tried to grab a popup which is not the topmost + #[error("popup was not created on the topmost popup")] + NotTheTopmostPopup, +} + +/// Defines the possibly strategies +/// for the [`PopupGrab::ungrab`] operation +#[derive(Debug, Copy, Clone)] +pub enum PopupUngrabStrategy { + /// Only ungrab the topmost popup + Topmost, + /// Ungrab all active popups + All, +} + +#[derive(Debug, Default)] +struct PopupGrabInternal { + serial: Option, + active_grabs: Vec<(WlSurface, PopupKind)>, + dismissed_grabs: Vec<(WlSurface, PopupKind)>, +} + +impl PopupGrabInternal { + fn alive(&self) -> bool { + !self.active_grabs.is_empty() || !self.dismissed_grabs.is_empty() + } + + fn current_grab(&self) -> Option<&WlSurface> { + self.active_grabs + .iter() + .rev() + .find(|(_, p)| p.alive()) + .map(|(s, _)| s) + } + + fn is_dismissed(&self, surface: &WlSurface) -> bool { + self.dismissed_grabs.iter().any(|(s, _)| s == surface) + } + + fn append_grab(&mut self, popup: &PopupKind) -> Result<(), DeadResource> { + let surface = popup.get_surface().ok_or(DeadResource)?; + self.active_grabs.push((surface.clone(), popup.clone())); + Ok(()) + } + + fn cleanup(&mut self) { + let mut i = 0; + while i < self.active_grabs.len() { + if !self.active_grabs[i].1.alive() { + let grab = self.active_grabs.remove(i); + self.dismissed_grabs.push(grab); + } else { + i += 1; + } + } + + self.dismissed_grabs.retain(|(s, _)| s.as_ref().is_alive()); + } +} + +#[derive(Debug, Clone, Default)] +pub(super) struct PopupGrabInner { + internal: Arc>, +} + +impl PopupGrabInner { + pub(super) fn alive(&self) -> bool { + let guard = self.internal.lock().unwrap(); + guard.alive() + } + + fn current_grab(&self) -> Option { + let guard = self.internal.lock().unwrap(); + guard + .active_grabs + .iter() + .rev() + .find(|(_, p)| p.alive()) + .map(|(s, _)| s) + .cloned() + } + + pub(super) fn cleanup(&self) { + let mut guard = self.internal.lock().unwrap(); + guard.cleanup(); + } + + pub(super) fn grab(&self, popup: &PopupKind, serial: Serial) -> Result, PopupGrabError> { + let parent = popup.parent().ok_or(DeadResource)?; + let parent_role = get_role(&parent); + + self.cleanup(); + + let mut guard = self.internal.lock().unwrap(); + + match guard.current_grab() { + Some(grab) => { + if grab != &parent { + // If the parent is a grabbing popup which has already been dismissed, this popup will be immediately dismissed. + if guard.is_dismissed(&parent) { + return Err(PopupGrabError::ParentDismissed); + } + + // If the parent is a popup that did not take an explicit grab, an error will be raised. + return Err(PopupGrabError::NotTheTopmostPopup); + } + } + None => { + if parent_role == Some(XDG_POPUP_ROLE) { + return Err(PopupGrabError::NotTheTopmostPopup); + } + } + } + + guard.append_grab(popup)?; + + Ok(guard.serial.replace(serial)) + } + + fn ungrab(&self, root: &WlSurface, strategy: PopupUngrabStrategy) -> Option { + let mut guard = self.internal.lock().unwrap(); + let dismissed = match strategy { + PopupUngrabStrategy::Topmost => { + if let Some(grab) = guard.active_grabs.pop() { + let dismissed = PopupManager::dismiss_popup(root, &grab.1); + + if dismissed.is_ok() { + guard.dismissed_grabs.push(grab); + } + + dismissed + } else { + Ok(()) + } + } + PopupUngrabStrategy::All => { + let grabs = guard.active_grabs.drain(..).collect::>(); + + if let Some(grab) = grabs.first() { + let dismissed = PopupManager::dismiss_popup(root, &grab.1); + + if dismissed.is_ok() { + guard.dismissed_grabs.push(grab.clone()); + guard.dismissed_grabs.extend(grabs); + } + + dismissed + } else { + Ok(()) + } + } + }; + + if dismissed.is_err() { + // If dismiss_popup returns Err(DeadResource) there is not much what + // can do about it here, we just remove all our grabs as they are dead now + // anyway. The pointer/keyboard grab will be unset automatically so we + // should be fine. + guard.active_grabs.drain(..); + } + + guard.current_grab().cloned() + } +} + +/// Represents the explicit grab a client requested for a popup +/// +/// An explicit grab can be used by a client to redirect all keyboard +/// input to a single popup. The focus of the keyboard will stay on +/// the popup for as long as the grab is valid, that is as long as the +/// compositor did not call [`ungrab`](PopupGrab::ungrab) or the client +/// did not destroy the popup. A grab can be nested by requesting a grab +/// on a popup who's parent is the currently grabbed popup. The grab will +/// be returned to the parent after the popup has been dismissed. +/// +/// This module also provides default implementations for [`KeyboardGrab`] and +/// [`PointerGrab`] that implement the behavior described in the [`xdg-shell`](https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab) +/// specification. See [`PopupKeyboardGrab`] and [`PopupPointerGrab`] for more +/// information on the default implementations. +/// +/// In case the implemented behavior is not suited for your use-case the grab can be +/// either decorated or a custom [`KeyboardGrab`]/[`PointerGrab`] can use the methods +/// on the [`PopupGrab`] to implement a custom behavior. +/// +/// One example would be to use a timer to automatically dismiss the popup after some +/// timeout. +/// +/// The grab is obtained by calling [`PopupManager::grap_popup`](super::PopupManager::grab_popup). +#[derive(Debug, Clone)] +pub struct PopupGrab { + root: WlSurface, + serial: Serial, + previous_serial: Option, + toplevel_grab: PopupGrabInner, + keyboard_handle: Option, + keyboard_grab_start_data: KeyboardGrabStartData, + pointer_grab_start_data: PointerGrabStartData, +} + +impl PopupGrab { + pub(super) fn new( + toplevel_popups: PopupGrabInner, + root: WlSurface, + serial: Serial, + previous_serial: Option, + keyboard_handle: Option, + ) -> Self { + PopupGrab { + root: root.clone(), + serial, + previous_serial, + toplevel_grab: toplevel_popups, + keyboard_handle, + keyboard_grab_start_data: KeyboardGrabStartData { + // We set the focus to root as this will make + // sure the grab will stay alive until the + // toplevel is destroyed or the grab is unset + focus: Some(root.clone()), + }, + pointer_grab_start_data: PointerGrabStartData { + button: 0, + // We set the focus to root as this will make + // sure the grab will stay alive until the + // toplevel is destroyed or the grab is unset + focus: Some((root, (0, 0).into())), + location: (0f64, 0f64).into(), + }, + } + } + + /// Returns the serial that was used to grab the popup + pub fn serial(&self) -> Serial { + self.serial + } + + /// Returns the previous serial that was used to grab + /// the parent popup in case of nested grabs + pub fn previous_serial(&self) -> Option { + self.previous_serial + } + + /// Check if this grab has ended + /// + /// A grab has ended if either all popups + /// associated with the grab have been dismissed + /// by the server with [`PopupGrab::ungrab`] or by the client + /// by destroying the popup. + /// + /// This will also return [`false`] if the root + /// of the grab has been destroyed. + pub fn has_ended(&self) -> bool { + !self.root.as_ref().is_alive() || !self.toplevel_grab.alive() + } + + /// Returns the current grabbed [`WlSurface`]. + /// + /// If the grab has ended this will return the root surface + /// so that the client expected focus can be restored + pub fn current_grab(&self) -> Option { + self.toplevel_grab + .current_grab() + .or_else(|| Some(self.root.clone())) + } + + /// Ungrab and dismiss a popup + /// + /// This will dismiss either the topmost or all popups + /// according to the specified [`PopupUngrabStrategy`] + /// + /// Returns the new topmost popup in case of nested popups + /// or if the grab has ended the root surface + pub fn ungrab(&mut self, strategy: PopupUngrabStrategy) -> Option { + self.toplevel_grab + .ungrab(&self.root, strategy) + .or_else(|| Some(self.root.clone())) + } + + /// Convenience method for getting a [`KeyboardGrabStartData`] for this grab. + /// + /// The focus of the [`KeyboardGrabStartData`] will always be the root + /// of the popup grab, e.g. the surface of the toplevel, to make sure + /// the grab is not automatically unset. + pub fn keyboard_grab_start_data(&self) -> &KeyboardGrabStartData { + &self.keyboard_grab_start_data + } + + /// Convenience method for getting a [`PointerGrabStartData`] for this grab. + /// + /// The focus of the [`PointerGrabStartData`] will always be the root + /// of the popup grab, e.g. the surface of the toplevel, to make sure + /// the grab is not automatically unset. + pub fn pointer_grab_start_data(&self) -> &PointerGrabStartData { + &self.pointer_grab_start_data + } + + fn unset_keyboard_grab(&self, serial: Serial) { + if let Some(keyboard) = self.keyboard_handle.as_ref() { + if keyboard.is_grabbed() + && (keyboard.has_grab(self.serial) + || keyboard.has_grab(self.previous_serial.unwrap_or(self.serial))) + { + keyboard.unset_grab(); + keyboard.set_focus(Some(&self.root), serial); + } + } + } +} + +/// Default implementation of a [`KeyboardGrab`] for [`PopupGrab`] +/// +/// The [`PopupKeyboardGrab`] will keep the focus of the keyboard +/// on the topmost popup until the grab has ended. If the +/// grab has ended it will restore the focus on the root of the grab +/// and unset the [`KeyboardGrab`] +#[derive(Debug)] +pub struct PopupKeyboardGrab { + popup_grab: PopupGrab, +} + +impl PopupKeyboardGrab { + /// Create a [`PopupKeyboardGrab`] for the provided [`PopupGrab`] + pub fn new(popup_grab: &PopupGrab) -> Self { + PopupKeyboardGrab { + popup_grab: popup_grab.clone(), + } + } +} + +impl KeyboardGrab for PopupKeyboardGrab { + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: KeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ) { + // Check if the grab changed and update the focus + // If the grab has ended this will return the root + // surface to restore the client expected focus. + if let Some(surface) = self.popup_grab.current_grab() { + handle.set_focus(Some(&surface), serial); + } + + if self.popup_grab.has_ended() { + handle.unset_grab(serial, false); + } + + handle.input(keycode, key_state, modifiers, serial, time) + } + + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial) { + // Ignore focus changes unless the grab has ended + if self.popup_grab.has_ended() { + handle.set_focus(focus, serial); + handle.unset_grab(serial, false); + return; + } + + // Allow to set the focus to the current grab, this can + // happen if the user initially sets the focus to + // popup instead of relying on the grab behavior + if self.popup_grab.current_grab().as_ref() == focus { + handle.set_focus(focus, serial); + } + } + + fn start_data(&self) -> &KeyboardGrabStartData { + self.popup_grab.keyboard_grab_start_data() + } +} + +/// Default implementation of a [`PointerGrab`] for [`PopupGrab`] +/// +/// The [`PopupPointerGrab`] will make sure that the pointer focus +/// stays on the same client as the grabbed popup (similar to an +/// "owner-events" grab in X11 parlance). If an input event happens +/// outside of the grabbed [`WlSurface`] the popup will be dismissed +/// and the grab ends. In case of a nested grab all parent grabs will +/// also be dismissed. +/// +/// If the grab has ended the pointer focus is restored and the +/// [`PointerGrab`] is unset. Additional it will unset an active +/// [`KeyboardGrab`] that matches the [`Serial`] of this grab and +/// restore the keyboard focus like described in [`PopupKeyboardGrab`] +#[derive(Debug)] +pub struct PopupPointerGrab { + popup_grab: PopupGrab, +} + +impl PopupPointerGrab { + /// Create a [`PopupPointerGrab`] for the provided [`PopupGrab`] + pub fn new(popup_grab: &PopupGrab) -> Self { + PopupPointerGrab { + popup_grab: popup_grab.clone(), + } + } +} + +impl PointerGrab for PopupPointerGrab { + fn motion( + &mut self, + handle: &mut PointerInnerHandle<'_>, + location: Point, + focus: Option<(WlSurface, Point)>, + serial: Serial, + time: u32, + ) { + if self.popup_grab.has_ended() { + handle.unset_grab(serial, time); + self.popup_grab.unset_keyboard_grab(serial); + return; + } + + // Check that the focus is of the same client as the grab + // If yes allow it, if not unset the focus. + let same_client = focus.as_ref().map(|(surface, _)| surface.as_ref().client()) + == self + .popup_grab + .current_grab() + .map(|surface| surface.as_ref().client()); + + if same_client { + handle.motion(location, focus, serial, time); + } else { + handle.motion(location, None, serial, time); + } + } + + fn button( + &mut self, + handle: &mut PointerInnerHandle<'_>, + button: u32, + state: ButtonState, + serial: Serial, + time: u32, + ) { + if self.popup_grab.has_ended() { + handle.unset_grab(serial, time); + handle.button(button, state, serial, time); + self.popup_grab.unset_keyboard_grab(serial); + return; + } + + // Check if the the focused surface is still on the current grabbed surface, + // if not the popup will be dismissed + if state == ButtonState::Pressed + && handle.current_focus().map(|(surface, _)| surface) != self.popup_grab.current_grab().as_ref() + { + let _ = self.popup_grab.ungrab(PopupUngrabStrategy::All); + handle.unset_grab(serial, time); + handle.button(button, state, serial, time); + self.popup_grab.unset_keyboard_grab(serial); + } + + handle.button(button, state, serial, time); + } + + fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) { + handle.axis(details); + } + + fn start_data(&self) -> &PointerGrabStartData { + self.popup_grab.pointer_grab_start_data() + } +} diff --git a/src/desktop/popup.rs b/src/desktop/popup/manager.rs similarity index 54% rename from src/desktop/popup.rs rename to src/desktop/popup/manager.rs index 2dd69c5..ab743f2 100644 --- a/src/desktop/popup.rs +++ b/src/desktop/popup/manager.rs @@ -1,18 +1,23 @@ use crate::{ - utils::{DeadResource, Logical, Point, Rectangle}, + utils::{DeadResource, Logical, Point}, wayland::{ compositor::{get_role, with_states}, - shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + seat::Seat, + shell::xdg::{XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + Serial, }, }; use std::sync::{Arc, Mutex}; use wayland_server::protocol::wl_surface::WlSurface; +use super::{PopupGrab, PopupGrabError, PopupGrabInner, PopupKind}; + /// Helper to track popups. #[derive(Debug)] pub struct PopupManager { unmapped_popups: Vec, popup_trees: Vec, + popup_grabs: Vec, logger: ::slog::Logger, } @@ -22,6 +27,7 @@ impl PopupManager { PopupManager { unmapped_popups: Vec::new(), popup_trees: Vec::new(), + popup_grabs: Vec::new(), logger: crate::slog_or_fallback(logger), } } @@ -54,24 +60,89 @@ impl PopupManager { } } - fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { - let mut parent = popup.parent().unwrap(); - while get_role(&parent) == Some(XDG_POPUP_ROLE) { - parent = with_states(&parent, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .parent - .as_ref() - .cloned() - .unwrap() - })?; + /// Take an explicit grab for the provided [`PopupKind`] + /// + /// Returns a [`PopupGrab`] on success or an [`PopupGrabError`] + /// if the grab has been denied. + pub fn grab_popup( + &mut self, + popup: PopupKind, + seat: &Seat, + serial: Serial, + ) -> Result { + let surface = popup.get_surface().ok_or(DeadResource)?; + let root = find_popup_root_surface(&popup)?; + + match popup { + PopupKind::Xdg(ref xdg) => { + let surface = xdg.get_surface().ok_or(DeadResource)?; + let committed = with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .committed + })?; + + if committed { + surface.as_ref().post_error( + wayland_protocols::xdg_shell::server::xdg_popup::Error::InvalidGrab as u32, + "xdg_popup already is mapped".to_string(), + ); + return Err(PopupGrabError::InvalidGrab); + } + } } - with_states(&parent, |states| { + // The primary store for the grab is the seat, additional we store it + // in the popupmanager for active cleanup + seat.user_data().insert_if_missing(PopupGrabInner::default); + let toplevel_popups = seat.user_data().get::().unwrap().clone(); + + // It the popup grab is not alive it is likely + // that it either is new and have never been + // added to the popupmanager or that it has been + // cleaned up. + if !toplevel_popups.alive() { + self.popup_grabs.push(toplevel_popups.clone()); + } + + let previous_serial = match toplevel_popups.grab(&popup, serial) { + Ok(serial) => serial, + Err(err) => { + match err { + PopupGrabError::ParentDismissed => { + let _ = PopupManager::dismiss_popup(&root, &popup); + } + PopupGrabError::NotTheTopmostPopup => { + surface.as_ref().post_error( + wayland_protocols::xdg_shell::server::xdg_wm_base::Error::NotTheTopmostPopup + as u32, + "xdg_popup was not created on the topmost popup".to_string(), + ); + } + _ => {} + } + + return Err(err); + } + }; + + Ok(PopupGrab::new( + toplevel_popups, + root, + serial, + previous_serial, + seat.get_keyboard(), + )) + } + + fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { + let root = find_popup_root_surface(&popup)?; + + with_states(&root, |states| { let tree = PopupTree::default(); if states.data_map.insert_if_missing(|| tree.clone()) { self.popup_trees.push(tree); @@ -81,7 +152,7 @@ impl PopupManager { // if it previously had no popups, we likely removed it from our list already self.popup_trees.push(tree.clone()); } - slog::trace!(self.logger, "Adding popup {:?} to parent {:?}", popup, parent); + slog::trace!(self.logger, "Adding popup {:?} to root {:?}", popup, root); tree.insert(popup); }) } @@ -116,16 +187,47 @@ impl PopupManager { }) } + pub(crate) fn dismiss_popup(surface: &WlSurface, popup: &PopupKind) -> Result<(), DeadResource> { + with_states(surface, |states| { + let tree = states.data_map.get::(); + + if let Some(tree) = tree { + tree.dismiss_popup(popup); + } + }) + } + /// Needs to be called periodically (but not necessarily frequently) /// to cleanup internal resources. pub fn cleanup(&mut self) { // retain_mut is sadly still unstable + self.popup_grabs.iter_mut().for_each(|grabs| grabs.cleanup()); + self.popup_grabs.retain(|grabs| grabs.alive()); self.popup_trees.iter_mut().for_each(|tree| tree.cleanup()); self.popup_trees.retain(|tree| tree.alive()); self.unmapped_popups.retain(|surf| surf.alive()); } } +fn find_popup_root_surface(popup: &PopupKind) -> Result { + let mut parent = popup.parent().ok_or(DeadResource)?; + while get_role(&parent) == Some(XDG_POPUP_ROLE) { + parent = with_states(&parent, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .parent + .as_ref() + .cloned() + .unwrap() + })?; + } + Ok(parent) +} + #[derive(Debug, Default, Clone)] struct PopupTree(Arc>>); @@ -157,6 +259,22 @@ impl PopupTree { children.push(PopupNode::new(popup)); } + fn dismiss_popup(&self, popup: &PopupKind) { + let mut children = self.0.lock().unwrap(); + + let mut i = 0; + while i < children.len() { + let child = &mut children[i]; + + if child.dismiss_popup(popup) { + let _ = children.remove(i); + break; + } else { + i += 1; + } + } + } + fn cleanup(&mut self) { let mut children = self.0.lock().unwrap(); for child in children.iter_mut() { @@ -209,80 +327,48 @@ impl PopupNode { } } + fn send_done(&self) { + for child in self.children.iter().rev() { + child.send_done(); + } + + self.surface.send_done(); + } + + fn dismiss_popup(&mut self, popup: &PopupKind) -> bool { + if self.surface.get_surface() == popup.get_surface() { + self.send_done(); + return true; + } + + let mut i = 0; + while i < self.children.len() { + let child = &mut self.children[i]; + + if child.dismiss_popup(popup) { + let _ = self.children.remove(i); + return false; + } else { + i += 1; + } + } + + false + } + fn cleanup(&mut self) { for child in &mut self.children { child.cleanup(); } + + if !self.surface.alive() && !self.children.is_empty() { + // TODO: The client destroyed a popup before + // destroying all children, this is a protocol + // error. As the surface is no longer alive we + // can not retrieve the client here to send + // the error. + } + self.children.retain(|n| n.surface.alive()); } } - -/// Represents a popup surface -#[derive(Debug, Clone)] -pub enum PopupKind { - /// xdg-shell [`PopupSurface`] - Xdg(PopupSurface), -} - -impl PopupKind { - fn alive(&self) -> bool { - match *self { - PopupKind::Xdg(ref t) => t.alive(), - } - } - - /// Retrieves the underlying [`WlSurface`] - pub fn get_surface(&self) -> Option<&WlSurface> { - match *self { - PopupKind::Xdg(ref t) => t.get_surface(), - } - } - - fn parent(&self) -> Option { - match *self { - PopupKind::Xdg(ref t) => t.get_parent_surface(), - } - } - - /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` - pub fn geometry(&self) -> Rectangle { - let wl_surface = match self.get_surface() { - Some(s) => s, - None => return Rectangle::from_loc_and_size((0, 0), (0, 0)), - }; - - with_states(wl_surface, |states| { - states - .cached_state - .current::() - .geometry - .unwrap_or_default() - }) - .unwrap() - } - - fn location(&self) -> Point { - let wl_surface = match self.get_surface() { - Some(s) => s, - None => return (0, 0).into(), - }; - with_states(wl_surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .current - .geometry - }) - .unwrap_or_default() - .loc - } -} - -impl From for PopupKind { - fn from(p: PopupSurface) -> PopupKind { - PopupKind::Xdg(p) - } -} diff --git a/src/desktop/popup/mod.rs b/src/desktop/popup/mod.rs new file mode 100644 index 0000000..c5ec0b4 --- /dev/null +++ b/src/desktop/popup/mod.rs @@ -0,0 +1,96 @@ +mod grab; +mod manager; + +use std::sync::Mutex; + +pub use grab::*; +pub use manager::*; +use wayland_server::protocol::wl_surface::WlSurface; + +use crate::{ + utils::{Logical, Point, Rectangle}, + wayland::{ + compositor::with_states, + shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes}, + }, +}; + +/// Represents a popup surface +#[derive(Debug, Clone)] +pub enum PopupKind { + /// xdg-shell [`PopupSurface`] + Xdg(PopupSurface), +} + +impl PopupKind { + fn alive(&self) -> bool { + match *self { + PopupKind::Xdg(ref t) => t.alive(), + } + } + + /// Retrieves the underlying [`WlSurface`] + pub fn get_surface(&self) -> Option<&WlSurface> { + match *self { + PopupKind::Xdg(ref t) => t.get_surface(), + } + } + + fn parent(&self) -> Option { + match *self { + PopupKind::Xdg(ref t) => t.get_parent_surface(), + } + } + + /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` + pub fn geometry(&self) -> Rectangle { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return Rectangle::from_loc_and_size((0, 0), (0, 0)), + }; + + with_states(wl_surface, |states| { + states + .cached_state + .current::() + .geometry + .unwrap_or_default() + }) + .unwrap() + } + + fn send_done(&self) { + if !self.alive() { + return; + } + + match *self { + PopupKind::Xdg(ref t) => t.send_popup_done(), + } + } + + fn location(&self) -> Point { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return (0, 0).into(), + }; + with_states(wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .current + .geometry + }) + .unwrap_or_default() + .loc + } +} + +impl From for PopupKind { + fn from(p: PopupSurface) -> PopupKind { + PopupKind::Xdg(p) + } +} diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index f7274eb..8d604b5 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -677,16 +677,18 @@ impl PointerGrab for DefaultGrab { time: u32, ) { handle.button(button, state, serial, time); - handle.set_grab( - serial, - ClickGrab { - start_data: GrabStartData { - focus: handle.current_focus().cloned(), - button, - location: handle.current_location(), + if state == ButtonState::Pressed { + handle.set_grab( + serial, + ClickGrab { + start_data: GrabStartData { + focus: handle.current_focus().cloned(), + button, + location: handle.current_location(), + }, }, - }, - ); + ); + } } fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) { handle.axis(details); From 439d5a78204c1d999e2cab060d9e5b9e6e2d640c Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 16 Jan 2022 19:28:28 +0100 Subject: [PATCH 08/41] Consistently use buffer coordinates - Moves `Transform` into utils::geometry - Changes conversion from and into buffer-coordinates to take `Transform` arguments. - `Renderer` take `Buffer`-space damage now - buffer_transform is taken into account everywhere --- src/backend/egl/display.rs | 2 +- src/backend/renderer/gles2/mod.rs | 28 ++-- src/backend/renderer/mod.rs | 193 +-------------------- src/backend/renderer/utils.rs | 54 +++--- src/desktop/space/mod.rs | 6 +- src/desktop/utils.rs | 18 +- src/utils/geometry.rs | 270 ++++++++++++++++++++++++++++-- src/utils/mod.rs | 2 +- 8 files changed, 316 insertions(+), 257 deletions(-) diff --git a/src/backend/egl/display.rs b/src/backend/egl/display.rs index da5a37d..1cf3e86 100644 --- a/src/backend/egl/display.rs +++ b/src/backend/egl/display.rs @@ -839,7 +839,7 @@ impl EGLBufferReader { pub fn egl_buffer_dimensions( &self, buffer: &WlBuffer, - ) -> Option> { + ) -> Option> { if !buffer.as_ref().is_alive() { debug!(self.logger, "Suplied buffer is no longer alive"); return None; diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index c620dc5..7026569 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -13,7 +13,7 @@ use cgmath::{prelude::*, Matrix3, Vector2, Vector3}; mod shaders; mod version; -use super::{Bind, Frame, Renderer, Texture, TextureFilter, Transform, Unbind}; +use super::{Bind, Frame, Renderer, Texture, TextureFilter, Unbind}; use crate::backend::allocator::{ dmabuf::{Dmabuf, WeakDmabuf}, Format, @@ -23,7 +23,7 @@ use crate::backend::egl::{ EGLContext, EGLSurface, MakeCurrentError, }; use crate::backend::SwapBuffersError; -use crate::utils::{Buffer, Physical, Rectangle, Size}; +use crate::utils::{Buffer, Physical, Rectangle, Size, Transform}; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use super::ImportEgl; @@ -1250,7 +1250,7 @@ impl Frame for Gles2Frame { texture: &Self::TextureId, src: Rectangle, dest: Rectangle, - damage: &[Rectangle], + damage: &[Rectangle], transform: Transform, alpha: f32, ) -> Result<(), Self::Error> { @@ -1266,7 +1266,7 @@ impl Frame for Gles2Frame { assert_eq!(mat, mat * transform.invert().matrix()); assert_eq!(transform.matrix(), Matrix3::::identity()); } - mat = mat * transform.invert().matrix(); + mat = mat * transform.matrix(); mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); // this matrix should be regular, we can expect invert to succeed @@ -1290,24 +1290,24 @@ impl Frame for Gles2Frame { let damage = damage .iter() .map(|rect| { + let src = src.size.to_f64(); let rect = rect.to_f64(); let rect_constrained_loc = rect .loc - .constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point())); - let rect_clamped_size = rect.size.clamp( - (0f64, 0f64), - (dest.size.to_point() - rect_constrained_loc).to_size(), - ); + .constrain(Rectangle::from_extemities((0f64, 0f64), src.to_point())); + let rect_clamped_size = rect + .size + .clamp((0f64, 0f64), (src.to_point() - rect_constrained_loc).to_size()); let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); - let rect_transformed = self.transformation().transform_rect_in(rect, &dest.size); + let rect_transformed = self.transformation().transform_rect_in(rect, &src); [ - (rect_transformed.loc.x / dest.size.w) as f32, - (rect_transformed.loc.y / dest.size.h) as f32, - (rect_transformed.size.w / dest.size.w) as f32, - (rect_transformed.size.h / dest.size.h) as f32, + (rect_transformed.loc.x / src.w) as f32, + (rect_transformed.loc.y / src.h) as f32, + (rect_transformed.size.w / src.w) as f32, + (rect_transformed.size.h / src.h) as f32, ] }) .flatten() diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index b624697..39c8f91 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -10,7 +10,7 @@ use std::collections::HashSet; use std::error::Error; -use crate::utils::{Buffer, Coordinate, Physical, Point, Rectangle, Size}; +use crate::utils::{Buffer, Physical, Point, Rectangle, Size, Transform}; #[cfg(feature = "wayland_frontend")] use crate::wayland::compositor::SurfaceData; @@ -35,27 +35,6 @@ use crate::backend::egl::{ #[cfg(feature = "wayland_frontend")] pub mod utils; -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -/// Possible transformations to two-dimensional planes -pub enum Transform { - /// Identity transformation (plane is unaltered when applied) - Normal, - /// Plane is rotated by 90 degrees - _90, - /// Plane is rotated by 180 degrees - _180, - /// Plane is rotated by 270 degrees - _270, - /// Plane is flipped vertically - Flipped, - /// Plane is flipped vertically and rotated by 90 degrees - Flipped90, - /// Plane is flipped vertically and rotated by 180 degrees - Flipped180, - /// Plane is flipped vertically and rotated by 270 degrees - Flipped270, -} - #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] /// Texture filtering methods pub enum TextureFilter { @@ -74,70 +53,11 @@ impl Transform { Transform::_180 => Matrix3::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0), Transform::_270 => Matrix3::new(0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0), Transform::Flipped => Matrix3::new(-1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0), - Transform::Flipped90 => Matrix3::new(0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0), + Transform::Flipped90 => Matrix3::new(0.0, -1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0), Transform::Flipped180 => Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0), - Transform::Flipped270 => Matrix3::new(0.0, -1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0), + Transform::Flipped270 => Matrix3::new(0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0), } } - - /// Inverts any 90-degree transformation into 270-degree transformations and vise versa. - /// - /// Flipping is preserved and 180/Normal transformation are uneffected. - pub fn invert(&self) -> Transform { - match self { - Transform::Normal => Transform::Normal, - Transform::Flipped => Transform::Flipped, - Transform::_90 => Transform::_270, - Transform::_180 => Transform::_180, - Transform::_270 => Transform::_90, - Transform::Flipped90 => Transform::Flipped270, - Transform::Flipped180 => Transform::Flipped180, - Transform::Flipped270 => Transform::Flipped90, - } - } - - /// Transformed size after applying this transformation. - pub fn transform_size(&self, size: Size) -> Size { - if *self == Transform::_90 - || *self == Transform::_270 - || *self == Transform::Flipped90 - || *self == Transform::Flipped270 - { - (size.h, size.w).into() - } else { - size - } - } - - /// Transforms a rectangle inside an area of a given size by applying this transformation - pub fn transform_rect_in( - &self, - rect: Rectangle, - area: &Size, - ) -> Rectangle { - let size = self.transform_size(rect.size); - - let loc = match *self { - Transform::Normal => rect.loc, - Transform::_90 => (area.h - rect.loc.y - rect.size.h, rect.loc.x).into(), - Transform::_180 => ( - area.w - rect.loc.x - rect.size.w, - area.h - rect.loc.y - rect.size.h, - ) - .into(), - Transform::_270 => (rect.loc.y, area.w - rect.loc.x - rect.size.w).into(), - Transform::Flipped => (area.w - rect.loc.x - rect.size.w, rect.loc.y).into(), - Transform::Flipped90 => (rect.loc.y, rect.loc.x).into(), - Transform::Flipped180 => (rect.loc.x, area.h - rect.loc.y - rect.size.h).into(), - Transform::Flipped270 => ( - area.h - rect.loc.y - rect.size.h, - area.w - rect.loc.x - rect.size.w, - ) - .into(), - }; - - Rectangle::from_loc_and_size(loc, size) - } } #[cfg(feature = "wayland_frontend")] @@ -217,7 +137,7 @@ pub trait Frame { texture_scale: i32, output_scale: f64, src_transform: Transform, - damage: &[Rectangle], + damage: &[Rectangle], alpha: f32, ) -> Result<(), Self::Error> { self.render_texture_from_to( @@ -227,7 +147,7 @@ pub trait Frame { pos, texture .size() - .to_logical(texture_scale) + .to_logical(texture_scale, src_transform) .to_f64() .to_physical(output_scale), ), @@ -245,7 +165,7 @@ pub trait Frame { texture: &Self::TextureId, src: Rectangle, dst: Rectangle, - damage: &[Rectangle], + damage: &[Rectangle], src_transform: Transform, alpha: f32, ) -> Result<(), Self::Error>; @@ -540,7 +460,7 @@ pub fn buffer_type(buffer: &wl_buffer::WlBuffer) -> Option { /// /// *Note*: This will only return dimensions for buffer types known to smithay (see [`buffer_type`]) #[cfg(feature = "wayland_frontend")] -pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option> { +pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option> { use crate::backend::allocator::Buffer; if let Some(buf) = buffer.as_ref().user_data().get::() { @@ -560,102 +480,3 @@ pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::Normal; - - assert_eq!(rect, transform.transform_rect_in(rect, &size)) - } - - #[test] - fn transform_rect_90() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::_90; - - assert_eq!( - Rectangle::from_loc_and_size((30, 10), (40, 30)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_180() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::_180; - - assert_eq!( - Rectangle::from_loc_and_size((30, 30), (30, 40)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_270() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::_270; - - assert_eq!( - Rectangle::from_loc_and_size((20, 30), (40, 30)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_f() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::Flipped; - - assert_eq!( - Rectangle::from_loc_and_size((30, 20), (30, 40)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_f90() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 80)); - let transform = Transform::Flipped90; - - assert_eq!( - Rectangle::from_loc_and_size((20, 10), (40, 30)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_f180() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::Flipped180; - - assert_eq!( - Rectangle::from_loc_and_size((10, 30), (30, 40)), - transform.transform_rect_in(rect, &size) - ) - } - - #[test] - fn transform_rect_f270() { - let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); - let size = Size::from((70, 90)); - let transform = Transform::Flipped270; - - assert_eq!( - Rectangle::from_loc_and_size((30, 30), (40, 30)), - transform.transform_rect_in(rect, &size) - ) - } -} diff --git a/src/backend/renderer/utils.rs b/src/backend/renderer/utils.rs index 1b38f01..e267916 100644 --- a/src/backend/renderer/utils.rs +++ b/src/backend/renderer/utils.rs @@ -2,7 +2,7 @@ use crate::{ backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, - utils::{Logical, Physical, Point, Rectangle, Size}, + utils::{Buffer, Logical, Point, Rectangle, Size, Transform}, wayland::compositor::{ is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, @@ -15,8 +15,9 @@ use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface}; #[derive(Default)] pub(crate) struct SurfaceState { - pub(crate) buffer_dimensions: Option>, + pub(crate) buffer_dimensions: Option>, pub(crate) buffer_scale: i32, + pub(crate) buffer_transform: Transform, pub(crate) buffer: Option, pub(crate) texture: Option>, #[cfg(feature = "desktop")] @@ -30,6 +31,7 @@ impl SurfaceState { // new contents self.buffer_dimensions = buffer_dimensions(&buffer); self.buffer_scale = attrs.buffer_scale; + self.buffer_transform = attrs.buffer_transform.into(); if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { if &old_buffer != self.buffer.as_ref().unwrap() { old_buffer.release(); @@ -52,6 +54,13 @@ impl SurfaceState { None => {} } } + + /// Returns the size of the surface. + pub fn surface_size(&self) -> Option> { + self.buffer_dimensions + .as_ref() + .map(|dim| dim.to_logical(self.buffer_scale, self.buffer_transform)) + } } /// Handler to let smithay take over buffer management. @@ -110,10 +119,6 @@ where T: Texture + 'static, { let mut result = Ok(()); - let damage = damage - .iter() - .map(|geo| geo.to_f64().to_physical(scale).to_i32_up()) - .collect::>(); with_surface_tree_upward( surface, location, @@ -125,17 +130,20 @@ where // Import a new buffer if necessary if data.texture.is_none() { if let Some(buffer) = data.buffer.as_ref() { - let damage = attributes + let buffer_damage = attributes .damage .iter() .map(|dmg| match dmg { Damage::Buffer(rect) => *rect, - // TODO also apply transformations - Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), + Damage::Surface(rect) => rect.to_buffer( + attributes.buffer_scale, + attributes.buffer_transform.into(), + &data.surface_size().unwrap(), + ), }) .collect::>(); - match renderer.import_buffer(buffer, Some(states), &damage) { + match renderer.import_buffer(buffer, Some(states), &buffer_damage) { Some(Ok(m)) => { data.texture = Some(Box::new(m)); } @@ -169,10 +177,12 @@ where let mut location = *location; if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); + let dimensions = data.surface_size(); let buffer_scale = data.buffer_scale; - let buffer_dimensions = data.buffer_dimensions; + let buffer_transform = data.buffer_transform; let attributes = states.cached_state.current::(); if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { + let dimensions = dimensions.unwrap(); // we need to re-extract the subsurface offset, as the previous closure // only passes it to our children let mut surface_offset = (0, 0).into(); @@ -182,23 +192,19 @@ where location += current.location; } - let rect = Rectangle::::from_loc_and_size( - surface_offset.to_f64().to_physical(scale).to_i32_round(), - buffer_dimensions - .unwrap_or_default() - .to_logical(buffer_scale) - .to_f64() - .to_physical(scale) - .to_i32_round(), - ); - let new_damage = damage + let damage = damage .iter() .cloned() - .flat_map(|geo| geo.intersection(rect)) + // first move the damage by the surface offset in logical space .map(|mut geo| { - geo.loc -= rect.loc; + // make the damage relative to the surfaec + geo.loc -= surface_offset; geo }) + // then clamp to surface size again in logical space + .flat_map(|geo| geo.intersection(Rectangle::from_loc_and_size((0, 0), dimensions))) + // lastly transform it into buffer space + .map(|geo| geo.to_buffer(buffer_scale, buffer_transform, &dimensions)) .collect::>(); // TODO: Take wp_viewporter into account @@ -208,7 +214,7 @@ where buffer_scale, scale, attributes.buffer_transform.into(), - &new_damage, + &damage, 1.0, ) { result = Err(err); diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 11bd6e8..934a47d 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -2,12 +2,12 @@ //! rendering helpers to add custom elements or different clients to a space. use crate::{ - backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, + backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer}, desktop::{ layer::{layer_map_for_output, LayerSurface}, window::Window, }, - utils::{Logical, Point, Rectangle}, + utils::{Logical, Point, Rectangle, Transform}, wayland::{ compositor::{ get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState, @@ -372,7 +372,7 @@ impl Space { |wl_surface, states, &loc| { let data = states.data_map.get::>(); - if let Some(size) = data.and_then(|d| d.borrow().size()) { + if let Some(size) = data.and_then(|d| d.borrow().surface_size()) { let surface_rectangle = Rectangle { loc, size }; if output_geometry.overlaps(surface_rectangle) { diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 380ae4e..b855691 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -3,7 +3,7 @@ use crate::{ backend::renderer::utils::SurfaceState, desktop::Space, - utils::{Logical, Point, Rectangle, Size}, + utils::{Logical, Point, Rectangle}, wayland::{ compositor::{ with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, @@ -17,15 +17,9 @@ use wayland_server::protocol::wl_surface; use std::cell::RefCell; impl SurfaceState { - /// Returns the size of the surface. - pub fn size(&self) -> Option> { - self.buffer_dimensions - .map(|dims| dims.to_logical(self.buffer_scale)) - } - fn contains_point>>(&self, attrs: &SurfaceAttributes, point: P) -> bool { let point = point.into(); - let size = match self.size() { + let size = match self.surface_size() { None => return false, // If the surface has no size, it can't have an input region. Some(size) => size, }; @@ -71,7 +65,7 @@ where let mut loc = *loc; let data = states.data_map.get::>(); - if let Some(size) = data.and_then(|d| d.borrow().size()) { + if let Some(size) = data.and_then(|d| d.borrow().surface_size()) { if states.role == Some("subsurface") { let current = states.cached_state.current::(); loc += current.location; @@ -148,7 +142,11 @@ where damage.extend(attributes.damage.iter().map(|dmg| { let mut rect = match dmg { - Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), + Damage::Buffer(rect) => rect.to_logical( + attributes.buffer_scale, + attributes.buffer_transform.into(), + &data.buffer_dimensions.unwrap(), + ), Damage::Surface(rect) => *rect, }; rect.loc += location; diff --git a/src/utils/geometry.rs b/src/utils/geometry.rs index b4a14a3..98c2848 100644 --- a/src/utils/geometry.rs +++ b/src/utils/geometry.rs @@ -406,10 +406,11 @@ impl Point { #[inline] /// Convert this logical point to buffer coordinate space according to given scale factor - pub fn to_buffer(self, scale: N) -> Point { + pub fn to_buffer(self, scale: N, transformation: Transform, area: &Size) -> Point { + let point = transformation.transform_point_in(self, area); Point { - x: self.x.upscale(scale), - y: self.y.upscale(scale), + x: point.x.upscale(scale), + y: point.y.upscale(scale), _kind: std::marker::PhantomData, } } @@ -430,10 +431,11 @@ impl Point { impl Point { #[inline] /// Convert this physical point to logical coordinate space according to given scale factor - pub fn to_logical(self, scale: N) -> Point { + pub fn to_logical(self, scale: N, transform: Transform, area: &Size) -> Point { + let point = transform.invert().transform_point_in(self, area); Point { - x: self.x.downscale(scale), - y: self.y.downscale(scale), + x: point.x.downscale(scale), + y: point.y.downscale(scale), _kind: std::marker::PhantomData, } } @@ -667,12 +669,12 @@ impl Size { #[inline] /// Convert this logical size to buffer coordinate space according to given scale factor - pub fn to_buffer(self, scale: N) -> Size { - Size { + pub fn to_buffer(self, scale: N, transformation: Transform) -> Size { + transformation.transform_size(Size { w: self.w.upscale(scale), h: self.h.upscale(scale), _kind: std::marker::PhantomData, - } + }) } } @@ -691,12 +693,12 @@ impl Size { impl Size { #[inline] /// Convert this physical point to logical coordinate space according to given scale factor - pub fn to_logical(self, scale: N) -> Size { - Size { + pub fn to_logical(self, scale: N, transformation: Transform) -> Size { + transformation.invert().transform_size(Size { w: self.w.downscale(scale), h: self.h.downscale(scale), _kind: std::marker::PhantomData, - } + }) } } @@ -969,10 +971,24 @@ impl Rectangle { /// Convert this logical rectangle to buffer coordinate space according to given scale factor #[inline] - pub fn to_buffer(self, scale: N) -> Rectangle { + pub fn to_buffer( + self, + scale: N, + transformation: Transform, + area: &Size, + ) -> Rectangle { + let rect = transformation.transform_rect_in(self, area); Rectangle { - loc: self.loc.to_buffer(scale), - size: self.size.to_buffer(scale), + loc: Point { + x: rect.loc.x.upscale(scale), + y: rect.loc.y.upscale(scale), + _kind: std::marker::PhantomData, + }, + size: Size { + w: rect.size.w.upscale(scale), + h: rect.size.h.upscale(scale), + _kind: std::marker::PhantomData, + }, } } } @@ -991,10 +1007,24 @@ impl Rectangle { impl Rectangle { /// Convert this physical rectangle to logical coordinate space according to given scale factor #[inline] - pub fn to_logical(self, scale: N) -> Rectangle { + pub fn to_logical( + self, + scale: N, + transformation: Transform, + area: &Size, + ) -> Rectangle { + let rect = transformation.invert().transform_rect_in(self, area); Rectangle { - loc: self.loc.to_logical(scale), - size: self.size.to_logical(scale), + loc: Point { + x: rect.loc.x.downscale(scale), + y: rect.loc.y.downscale(scale), + _kind: std::marker::PhantomData, + }, + size: Size { + w: rect.size.w.downscale(scale), + h: rect.size.h.downscale(scale), + _kind: std::marker::PhantomData, + }, } } } @@ -1071,3 +1101,207 @@ impl Default for Rectangle { } } } + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +/// Possible transformations to two-dimensional planes +pub enum Transform { + /// Identity transformation (plane is unaltered when applied) + Normal, + /// Plane is rotated by 90 degrees + _90, + /// Plane is rotated by 180 degrees + _180, + /// Plane is rotated by 270 degrees + _270, + /// Plane is flipped vertically + Flipped, + /// Plane is flipped vertically and rotated by 90 degrees + Flipped90, + /// Plane is flipped vertically and rotated by 180 degrees + Flipped180, + /// Plane is flipped vertically and rotated by 270 degrees + Flipped270, +} + +impl Default for Transform { + fn default() -> Transform { + Transform::Normal + } +} + +impl Transform { + /// Inverts any 90-degree transformation into 270-degree transformations and vise versa. + /// + /// Flipping is preserved and 180/Normal transformation are uneffected. + pub fn invert(&self) -> Transform { + match self { + Transform::Normal => Transform::Normal, + Transform::Flipped => Transform::Flipped, + Transform::_90 => Transform::_270, + Transform::_180 => Transform::_180, + Transform::_270 => Transform::_90, + Transform::Flipped90 => Transform::Flipped270, + Transform::Flipped180 => Transform::Flipped180, + Transform::Flipped270 => Transform::Flipped90, + } + } + + /// Transforms a point inside an area of a given size by applying this transformation. + pub fn transform_point_in( + &self, + point: Point, + area: &Size, + ) -> Point { + match *self { + Transform::Normal => point, + Transform::_90 => (area.h - point.y, point.x).into(), + Transform::_180 => (area.w - point.x, area.h - point.y).into(), + Transform::_270 => (point.y, area.w - point.x).into(), + Transform::Flipped => (area.w - point.x, point.y).into(), + Transform::Flipped90 => (point.y, point.x).into(), + Transform::Flipped180 => (point.x, area.h - point.y).into(), + Transform::Flipped270 => (area.h - point.y, area.w - point.x).into(), + } + } + + /// Transformed size after applying this transformation. + pub fn transform_size(&self, size: Size) -> Size { + if *self == Transform::_90 + || *self == Transform::_270 + || *self == Transform::Flipped90 + || *self == Transform::Flipped270 + { + (size.h, size.w).into() + } else { + size + } + } + + /// Transforms a rectangle inside an area of a given size by applying this transformation. + pub fn transform_rect_in( + &self, + rect: Rectangle, + area: &Size, + ) -> Rectangle { + let size = self.transform_size(rect.size); + + let loc = match *self { + Transform::Normal => rect.loc, + Transform::_90 => (area.h - rect.loc.y - rect.size.h, rect.loc.x).into(), + Transform::_180 => ( + area.w - rect.loc.x - rect.size.w, + area.h - rect.loc.y - rect.size.h, + ) + .into(), + Transform::_270 => (rect.loc.y, area.w - rect.loc.x - rect.size.w).into(), + Transform::Flipped => (area.w - rect.loc.x - rect.size.w, rect.loc.y).into(), + Transform::Flipped90 => (rect.loc.y, rect.loc.x).into(), + Transform::Flipped180 => (rect.loc.x, area.h - rect.loc.y - rect.size.h).into(), + Transform::Flipped270 => ( + area.h - rect.loc.y - rect.size.h, + area.w - rect.loc.x - rect.size.w, + ) + .into(), + }; + + Rectangle::from_loc_and_size(loc, size) + } +} + +#[cfg(test)] +mod tests { + use super::{Logical, Rectangle, Size, Transform}; + + #[test] + fn transform_rect_ident() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Normal; + + assert_eq!(rect, transform.transform_rect_in(rect, &size)) + } + + #[test] + fn transform_rect_90() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_90; + + assert_eq!( + Rectangle::from_loc_and_size((30, 10), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_180() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_180; + + assert_eq!( + Rectangle::from_loc_and_size((30, 30), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_270() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_270; + + assert_eq!( + Rectangle::from_loc_and_size((20, 30), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped; + + assert_eq!( + Rectangle::from_loc_and_size((30, 20), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f90() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 80)); + let transform = Transform::Flipped90; + + assert_eq!( + Rectangle::from_loc_and_size((20, 10), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f180() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped180; + + assert_eq!( + Rectangle::from_loc_and_size((10, 30), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f270() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped270; + + assert_eq!( + Rectangle::from_loc_and_size((30, 30), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index de3d678..bbe8e55 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -10,7 +10,7 @@ pub mod x11rb; pub(crate) mod ids; pub mod user_data; -pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size}; +pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size, Transform}; /// This resource is not managed by Smithay #[derive(Debug)] From 4b22624e745c347073c07c222cc75bddd3ac7895 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sun, 16 Jan 2022 20:31:59 +0100 Subject: [PATCH 09/41] anvil: Use buffer transformations --- anvil/src/drawing.rs | 13 ++++++++++--- anvil/src/shell.rs | 8 +++++--- anvil/src/udev.rs | 4 ++-- anvil/src/winit.rs | 3 ++- anvil/src/x11.rs | 3 ++- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index cb03ac5..4ad9340 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -7,9 +7,11 @@ use image::{ImageBuffer, Rgba}; use slog::Logger; #[cfg(feature = "image")] use smithay::backend::renderer::gles2::{Gles2Error, Gles2Renderer, Gles2Texture}; +#[cfg(feature = "debug")] +use smithay::utils::Transform; use smithay::{ backend::{ - renderer::{buffer_type, BufferType, Frame, ImportAll, Renderer, Texture, Transform}, + renderer::{buffer_type, BufferType, Frame, ImportAll, Renderer, Texture}, SwapBuffersError, }, reexports::wayland_server::protocol::{wl_buffer, wl_surface}, @@ -111,7 +113,11 @@ where .map(|dmg| match dmg { Damage::Buffer(rect) => *rect, // TODO also apply transformations - Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), + Damage::Surface(rect) => rect.to_buffer( + attributes.buffer_scale, + attributes.buffer_transform.into(), + &data.size().unwrap(), + ), }) .collect::>(); @@ -161,6 +167,7 @@ where if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); let buffer_scale = data.buffer_scale; + let buffer_transform = data.buffer_transform; if let Some(texture) = data .texture .as_mut() @@ -177,7 +184,7 @@ where location.to_f64().to_physical(output_scale as f64).to_i32_round(), buffer_scale, output_scale as f64, - Transform::Normal, /* TODO */ + buffer_transform, &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], 1.0, ) { diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index 51c2d0d..d295d4f 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -13,7 +13,7 @@ use smithay::{ Display, }, }, - utils::{Logical, Physical, Point, Rectangle, Size}, + utils::{Buffer, Logical, Point, Rectangle, Size, Transform}, wayland::{ compositor::{ compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment, @@ -947,8 +947,9 @@ pub struct SurfaceData { pub texture: Option>, pub geometry: Option>, pub resize_state: ResizeState, - pub buffer_dimensions: Option>, + pub buffer_dimensions: Option>, pub buffer_scale: i32, + pub buffer_transform: Transform, } impl SurfaceData { @@ -958,6 +959,7 @@ impl SurfaceData { // new contents self.buffer_dimensions = buffer_dimensions(&buffer); self.buffer_scale = attrs.buffer_scale; + self.buffer_transform = attrs.buffer_transform.into(); if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { old_buffer.release(); } @@ -976,7 +978,7 @@ impl SurfaceData { /// Returns the size of the surface. pub fn size(&self) -> Option> { self.buffer_dimensions - .map(|dims| dims.to_logical(self.buffer_scale)) + .map(|dims| dims.to_logical(self.buffer_scale, self.buffer_transform)) } /// Checks if the surface's input region contains the point. diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 9987d32..3b4d3ef 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -20,7 +20,7 @@ use smithay::{ libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ gles2::{Gles2Renderer, Gles2Texture}, - Bind, Frame, Renderer, Transform, + Bind, Frame, Renderer, }, session::{auto::AutoSession, Session, Signal as SessionSignal}, udev::{UdevBackend, UdevEvent}, @@ -50,7 +50,7 @@ use smithay::{ }, utils::{ signaling::{Linkable, SignalToken, Signaler}, - Logical, Point, Rectangle, + Logical, Point, Rectangle, Transform, }, wayland::{ output::{Mode, PhysicalProperties}, diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 2552e04..429e9c1 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -9,7 +9,7 @@ use smithay::{ }; use smithay::{ backend::{ - renderer::{Renderer, Transform}, + renderer::Renderer, winit::{self, WinitEvent}, SwapBuffersError, }, @@ -17,6 +17,7 @@ use smithay::{ calloop::EventLoop, wayland_server::{protocol::wl_output, Display}, }, + utils::Transform, wayland::{ output::{Mode, PhysicalProperties}, seat::CursorImageStatus, diff --git a/anvil/src/x11.rs b/anvil/src/x11.rs index 69766c8..e40d302 100644 --- a/anvil/src/x11.rs +++ b/anvil/src/x11.rs @@ -11,7 +11,7 @@ use smithay::{backend::renderer::ImportDma, wayland::dmabuf::init_dmabuf_global} use smithay::{ backend::{ egl::{EGLContext, EGLDisplay}, - renderer::{gles2::Gles2Renderer, Bind, ImportEgl, Renderer, Transform, Unbind}, + renderer::{gles2::Gles2Renderer, Bind, ImportEgl, Renderer, Unbind}, x11::{WindowBuilder, X11Backend, X11Event, X11Surface}, SwapBuffersError, }, @@ -20,6 +20,7 @@ use smithay::{ gbm, wayland_server::{protocol::wl_output, Display}, }, + utils::Transform, wayland::{ output::{Mode, PhysicalProperties}, seat::CursorImageStatus, From 4161bc30ec0ae444e20066d1233726c033ea2625 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 17 Jan 2022 13:52:07 +0100 Subject: [PATCH 10/41] wlcs_anvil: Use buffer transformations --- wlcs_anvil/src/main_loop.rs | 4 ++-- wlcs_anvil/src/renderer.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wlcs_anvil/src/main_loop.rs b/wlcs_anvil/src/main_loop.rs index cc1108e..2b5ad8d 100644 --- a/wlcs_anvil/src/main_loop.rs +++ b/wlcs_anvil/src/main_loop.rs @@ -5,7 +5,7 @@ use std::{ use smithay::{ backend::{ - renderer::{Frame, Renderer, Transform}, + renderer::{Frame, Renderer}, SwapBuffersError, }, reexports::{ @@ -18,7 +18,7 @@ use smithay::{ Client, Display, }, }, - utils::Rectangle, + utils::{Rectangle, Transform}, wayland::{ output::{Mode, PhysicalProperties}, seat::CursorImageStatus, diff --git a/wlcs_anvil/src/renderer.rs b/wlcs_anvil/src/renderer.rs index bd75ec1..6454d7d 100644 --- a/wlcs_anvil/src/renderer.rs +++ b/wlcs_anvil/src/renderer.rs @@ -3,11 +3,11 @@ use std::cell::Cell; use smithay::{ backend::{ allocator::dmabuf::Dmabuf, - renderer::{Frame, ImportDma, ImportShm, Renderer, Texture, TextureFilter, Transform}, + renderer::{Frame, ImportDma, ImportShm, Renderer, Texture, TextureFilter}, SwapBuffersError, }, reexports::wayland_server::protocol::wl_buffer, - utils::{Buffer, Physical, Rectangle, Size}, + utils::{Buffer, Physical, Rectangle, Size, Transform}, wayland::compositor::SurfaceData, }; @@ -106,7 +106,7 @@ impl Frame for DummyFrame { _texture: &Self::TextureId, _src: Rectangle, _dst: Rectangle, - _damage: &[Rectangle], + _damage: &[Rectangle], _src_transform: Transform, _alpha: f32, ) -> Result<(), Self::Error> { From d5bfc6f613e3bdeddb93b1d44468b3d110e35ed5 Mon Sep 17 00:00:00 2001 From: Poly Date: Sun, 16 Jan 2022 21:15:21 +0100 Subject: [PATCH 11/41] desktop.space: Fix multioutput rendering --- src/desktop/space/element.rs | 1 + src/desktop/space/mod.rs | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index ac013c9..d162060 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -28,6 +28,7 @@ where /// Returns the bounding box of this element including its position in the space. fn geometry(&self) -> Rectangle; /// Returns the damage of the element since it's last update. + /// It should be relative to the elements coordinates. /// /// If you receive `Some(_)` for `for_values` you may cache that you /// send the damage for this `Space` and `Output` combination once diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 56b1119..ed45ad9 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -454,7 +454,8 @@ impl Space { /// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered /// after every other element. /// - /// Returns a list of updated regions (or `None` if that list would be empty) in case of success. + /// Returns a list of updated regions relative to the rendered output + /// (or `None` if that list would be empty) in case of success. pub fn render_output( &mut self, renderer: &mut R, @@ -603,6 +604,9 @@ impl Space { clear_color, &damage .iter() + // Map from global space to output space + .map(|geo| Rectangle::from_loc_and_size(geo.loc - output_geo.loc, geo.size)) + // Map from logical to physical .map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round()) .collect::>(), )?; @@ -624,16 +628,17 @@ impl Space { { let geo = element.geometry(self.id); if damage.iter().any(|d| d.overlaps(geo)) { - let loc = element.location(self.id) - output_geo.loc; + let loc = element.location(self.id); let damage = damage .iter() .flat_map(|d| d.intersection(geo)) + // Map from output space to surface-relative coordinates .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size)) .collect::>(); slog::trace!( self.logger, "Rendering toplevel at {:?} with damage {:#?}", - geo, + Rectangle::from_loc_and_size(geo.loc - output_geo.loc, geo.size), damage ); element.draw( @@ -641,7 +646,7 @@ impl Space { renderer, frame, state.render_scale, - loc, + loc - output_geo.loc, &damage, &self.logger, )?; @@ -676,7 +681,15 @@ impl Space { .collect(); state.old_damage.push_front(new_damage.clone()); - Ok(Some(new_damage)) + Ok(Some( + new_damage + .into_iter() + .map(|mut geo| { + geo.loc -= output_geo.loc; + geo + }) + .collect(), + )) } /// Sends the frame callback to mapped [`Window`]s and [`LayerSurface`]s. From 21a78f9858e54745752c5ad6d4189c69c6f09a84 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 17 Jan 2022 21:15:59 +0100 Subject: [PATCH 12/41] gles: Don't apply output transformations to buffer damage --- src/backend/renderer/gles2/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 7026569..9df9829 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1301,13 +1301,11 @@ impl Frame for Gles2Frame { .clamp((0f64, 0f64), (src.to_point() - rect_constrained_loc).to_size()); let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); - let rect_transformed = self.transformation().transform_rect_in(rect, &src); - [ - (rect_transformed.loc.x / src.w) as f32, - (rect_transformed.loc.y / src.h) as f32, - (rect_transformed.size.w / src.w) as f32, - (rect_transformed.size.h / src.h) as f32, + (rect.loc.x / src.w) as f32, + (rect.loc.y / src.h) as f32, + (rect.size.w / src.w) as f32, + (rect.size.h / src.h) as f32, ] }) .flatten() From 8e1263ce9cab5c6591d5796d4a3f01a1dd4c92ac Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 18 Jan 2022 19:04:34 +0100 Subject: [PATCH 13/41] Add PartialEq/Eq implementation for ExclusiveZone --- src/wayland/shell/wlr_layer/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wayland/shell/wlr_layer/types.rs b/src/wayland/shell/wlr_layer/types.rs index ba3282e..1e736cc 100644 --- a/src/wayland/shell/wlr_layer/types.rs +++ b/src/wayland/shell/wlr_layer/types.rs @@ -162,7 +162,7 @@ impl TryFrom for Anchor { } /// Exclusive zone descriptor -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExclusiveZone { /// Requests that the compositor avoids occluding an area with other surfaces. /// From b9ecc3c2d418e98fcb374040602abb7ce5f4047c Mon Sep 17 00:00:00 2001 From: dragonn Date: Tue, 18 Jan 2022 22:12:48 +0100 Subject: [PATCH 14/41] add layer definition to RenderElement --- src/desktop/space/element.rs | 22 ++++++++++++++++ src/desktop/space/mod.rs | 50 +++++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index d162060..287337e 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -10,6 +10,23 @@ use std::{ }; use wayland_server::protocol::wl_surface::WlSurface; +/// Enum for indicating on with layer a render element schould be draw +#[derive(Debug, PartialEq, Eq)] +pub enum RenderLayer { + /// Bellow every other elements + Bottom, + /// Above WlrLayer::Background but bellow WlrLayer::Bottom + AboveBackground, + /// Right before programs windows are draw + BeforeWindows, + /// Right after programs windows are draw + AfterWindows, + /// Above WlrLayer::Top but bellow WlrLayer::Overlay + BeforeOverlay, + /// Above anything else + Top, +} + /// Trait for custom elements to be rendered during [`Space::render_output`]. pub trait RenderElement where @@ -55,6 +72,11 @@ where damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error>; + + /// Returns they layer the elements schould be draw on, defaults to Top + fn layer(&self) -> RenderLayer { + RenderLayer::Top + } } pub(crate) trait SpaceElement diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index ed45ad9..2946570 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -501,6 +501,7 @@ impl Space { // This will hold all the damage we need for this rendering step let mut damage = Vec::>::new(); // First add damage for windows gone + for old_toplevel in state .last_state .iter() @@ -613,18 +614,53 @@ impl Space { // Then re-draw all windows & layers overlapping with a damage rect. - for element in layer_map - .layers_on(WlrLayer::Background) - .chain(layer_map.layers_on(WlrLayer::Bottom)) - .map(|l| l as &SpaceElem) + for element in custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::Bottom) + .map(|p| p as &SpaceElem) + .chain( + layer_map + .layers_on(WlrLayer::Background) + .map(|l| l as &SpaceElem) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::AboveBackground) + .map(|c| c as &SpaceElem), + ) + .chain(layer_map.layers_on(WlrLayer::Bottom).map(|l| l as &SpaceElem)), + ) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::BeforeWindows) + .map(|c| c as &SpaceElem), + ) .chain(self.windows.iter().map(|w| w as &SpaceElem)) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::AfterWindows) + .map(|c| c as &SpaceElem), + ) .chain( layer_map .layers_on(WlrLayer::Top) - .chain(layer_map.layers_on(WlrLayer::Overlay)) - .map(|l| l as &SpaceElem), + .map(|l| l as &SpaceElem) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::BeforeOverlay) + .map(|c| c as &SpaceElem), + ) + .chain(layer_map.layers_on(WlrLayer::Overlay).map(|l| l as &SpaceElem)), + ) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::Top) + .map(|c| c as &SpaceElem), ) - .chain(custom_elements.iter().map(|c| c as &SpaceElem)) { let geo = element.geometry(self.id); if damage.iter().any(|d| d.overlaps(geo)) { From d7dfe2e3b691267231e9f3cf992cda05ef630bfc Mon Sep 17 00:00:00 2001 From: Mateusz Date: Wed, 19 Jan 2022 16:49:03 +0100 Subject: [PATCH 15/41] Move chain to upper chain in render loop Co-authored-by: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> --- src/desktop/space/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 2946570..f6881e5 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -622,14 +622,14 @@ impl Space { layer_map .layers_on(WlrLayer::Background) .map(|l| l as &SpaceElem) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::AboveBackground) - .map(|c| c as &SpaceElem), - ) - .chain(layer_map.layers_on(WlrLayer::Bottom).map(|l| l as &SpaceElem)), ) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::AboveBackground) + .map(|c| c as &SpaceElem), + ) + .chain(layer_map.layers_on(WlrLayer::Bottom).map(|l| l as &SpaceElem)) .chain( custom_elements .iter() From 478fe6280828cfa603a84146234326b553a94f11 Mon Sep 17 00:00:00 2001 From: Mateusz Date: Wed, 19 Jan 2022 16:49:15 +0100 Subject: [PATCH 16/41] Move chain to upper chain in loop Co-authored-by: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> --- src/desktop/space/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index f6881e5..38a245c 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -647,14 +647,14 @@ impl Space { layer_map .layers_on(WlrLayer::Top) .map(|l| l as &SpaceElem) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::BeforeOverlay) - .map(|c| c as &SpaceElem), - ) - .chain(layer_map.layers_on(WlrLayer::Overlay).map(|l| l as &SpaceElem)), ) + .chain( + custom_elements + .iter() + .filter(|c| c.layer() == RenderLayer::BeforeOverlay) + .map(|c| c as &SpaceElem), + ) + .chain(layer_map.layers_on(WlrLayer::Overlay).map(|l| l as &SpaceElem)) .chain( custom_elements .iter() From 3ae387e991b2c1f104819a747a0d6eace801e308 Mon Sep 17 00:00:00 2001 From: dragonn Date: Wed, 19 Jan 2022 17:35:30 +0100 Subject: [PATCH 17/41] DynamicRenderElementMap as a helper for iter on custom_elements --- src/desktop/space/mod.rs | 98 +++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 38a245c..c193192 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -48,6 +48,52 @@ pub struct Space { pub type DynamicRenderElements = Box::Frame, ::Error, ::TextureId>>; +type SpaceElem = + dyn SpaceElement::Frame, ::Error, ::TextureId>; + +struct DynamicRenderElementMap<'a, R: Renderer>(&'a [DynamicRenderElements]); + +impl<'a, R> DynamicRenderElementMap<'a, R> +where + R: Renderer + ImportAll + 'static, + R::TextureId: 'static, + R::Error: 'static, + R::Frame: 'static, +{ + pub fn iter_bottom(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::Bottom) + } + + pub fn iter_above_background(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::AboveBackground) + } + + pub fn iter_before_windows(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::BeforeWindows) + } + + pub fn iter_after_windows(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::AfterWindows) + } + + pub fn iter_before_overlay(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::BeforeOverlay) + } + + pub fn iter_top(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::Top) + } + + pub fn iter_layer(&'a self, layer: RenderLayer) -> Box> + 'a> { + Box::new( + self.0 + .iter() + .filter(move |c| c.layer() == layer) + .map(|c| c as &SpaceElem), + ) + } +} + impl PartialEq for Space { fn eq(&self, other: &Space) -> bool { self.id == other.id @@ -474,9 +520,6 @@ impl Space { return Err(RenderError::UnmappedOutput); } - type SpaceElem = - dyn SpaceElement::Frame, ::Error, ::TextureId>; - let mut state = output_state(self.id, output); let output_size = output .current_mode() @@ -612,55 +655,26 @@ impl Space { .collect::>(), )?; + let custom_elements = DynamicRenderElementMap(custom_elements); + // Then re-draw all windows & layers overlapping with a damage rect. for element in custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::Bottom) - .map(|p| p as &SpaceElem) + .iter_bottom() .chain( layer_map .layers_on(WlrLayer::Background) - .map(|l| l as &SpaceElem) - ) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::AboveBackground) - .map(|c| c as &SpaceElem), + .map(|l| l as &SpaceElem), ) + .chain(custom_elements.iter_above_background()) .chain(layer_map.layers_on(WlrLayer::Bottom).map(|l| l as &SpaceElem)) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::BeforeWindows) - .map(|c| c as &SpaceElem), - ) + .chain(custom_elements.iter_before_windows()) .chain(self.windows.iter().map(|w| w as &SpaceElem)) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::AfterWindows) - .map(|c| c as &SpaceElem), - ) - .chain( - layer_map - .layers_on(WlrLayer::Top) - .map(|l| l as &SpaceElem) - ) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::BeforeOverlay) - .map(|c| c as &SpaceElem), - ) + .chain(custom_elements.iter_after_windows()) + .chain(layer_map.layers_on(WlrLayer::Top).map(|l| l as &SpaceElem)) + .chain(custom_elements.iter_before_overlay()) .chain(layer_map.layers_on(WlrLayer::Overlay).map(|l| l as &SpaceElem)) - .chain( - custom_elements - .iter() - .filter(|c| c.layer() == RenderLayer::Top) - .map(|c| c as &SpaceElem), - ) + .chain(custom_elements.iter_top()) { let geo = element.geometry(self.id); if damage.iter().any(|d| d.overlaps(geo)) { From 017146304ed112f7c66203e58a3542c904448afa Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Jan 2022 20:40:46 +0100 Subject: [PATCH 18/41] desktop.layer: Send frames to popups --- src/desktop/layer.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 1dc4b9a..a36c323 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -485,7 +485,16 @@ impl LayerSurface { /// window that requested it pub fn send_frame(&self, time: u32) { if let Some(wl_surface) = self.0.surface.get_surface() { - send_frames_surface_tree(wl_surface, time) + send_frames_surface_tree(wl_surface, time); + for (popup, _) in PopupManager::popups_for_surface(wl_surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + send_frames_surface_tree(surface, time); + } + } } } From a099ccbb2ef07dbde5af39c102ee5f86b68ed84a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Jan 2022 19:35:03 +0100 Subject: [PATCH 19/41] desktop.layer: Use a proper logger --- src/desktop/layer.rs | 16 ++++++++-------- src/wayland/output/mod.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 1dc4b9a..e2f0ed5 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -29,6 +29,7 @@ pub struct LayerMap { layers: IndexSet, output: Weak<(Mutex, wayland_server::UserDataMap)>, zone: Rectangle, + logger: ::slog::Logger, } /// Retrieve a [`LayerMap`] for a given [`Output`]. @@ -53,6 +54,9 @@ pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> { .map(|mode| mode.size.to_logical(o.current_scale())) .unwrap_or_else(|| (0, 0).into()), ), + logger: (*o.inner.0.lock().unwrap()) + .log + .new(slog::o!("smithay_module" => "layer_map")), }) }); userdata.get::>().unwrap().borrow_mut() @@ -160,11 +164,7 @@ impl LayerMap { .unwrap_or_else(|| (0, 0).into()), ); let mut zone = output_rect; - slog::debug!( - crate::slog_or_fallback(None), - "Arranging layers into {:?}", - output_rect.size - ); + slog::trace!(self.logger, "Arranging layers into {:?}", output_rect.size); for layer in self.layers.iter() { let surface = if let Some(surface) = layer.get_surface() { @@ -233,8 +233,8 @@ impl LayerMap { } } - slog::debug!( - crate::slog_or_fallback(None), + slog::trace!( + self.logger, "Setting layer to pos {:?} and size {:?}", location, size @@ -253,7 +253,7 @@ impl LayerMap { layer_state(layer).location = location; } - slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone); + slog::trace!(self.logger, "Remaining zone {:?}", zone); self.zone = zone; } } diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index fdfba5e..30620e5 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -102,7 +102,7 @@ pub struct PhysicalProperties { #[derive(Debug)] pub(crate) struct Inner { name: String, - log: ::slog::Logger, + pub(crate) log: ::slog::Logger, instances: Vec, physical: PhysicalProperties, location: Point, From 3b99a2c9dd32c94bca3b2c73056112222085f16b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Jan 2022 19:36:26 +0100 Subject: [PATCH 20/41] desktop.layer: Send output enter/leave events --- src/desktop/layer.rs | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index e2f0ed5..11ae791 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -3,7 +3,7 @@ use crate::{ desktop::{utils::*, PopupManager, Space}, utils::{user_data::UserDataMap, Logical, Point, Rectangle}, wayland::{ - compositor::with_states, + compositor::{with_states, with_surface_tree_downward, TraversalAction}, output::{Inner as OutputInner, Output}, shell::wlr_layer::{ Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface, @@ -29,6 +29,8 @@ pub struct LayerMap { layers: IndexSet, output: Weak<(Mutex, wayland_server::UserDataMap)>, zone: Rectangle, + // surfaces for tracking enter and leave events + surfaces: Vec, logger: ::slog::Logger, } @@ -54,6 +56,7 @@ pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> { .map(|mode| mode.size.to_logical(o.current_scale())) .unwrap_or_else(|| (0, 0).into()), ), + surfaces: Vec::new(), logger: (*o.inner.0.lock().unwrap()) .log .new(slog::o!("smithay_module" => "layer_map")), @@ -94,6 +97,26 @@ impl LayerMap { let _ = layer.user_data().get::().take(); self.arrange(); } + if let (Some(output), Some(surface)) = (self.output(), layer.get_surface()) { + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + if self.surfaces.contains(wl_surface) { + slog::trace!( + self.logger, + "surface ({:?}) leaving output {:?}", + wl_surface, + output.name() + ); + output.leave(wl_surface); + self.surfaces.retain(|s| s != wl_surface); + } + }, + |_, _, _| true, + ); + } } /// Return the area of this output, that is not exclusive to any [`LayerSurface`]s. @@ -173,6 +196,27 @@ impl LayerMap { continue; }; + let logger_ref = &self.logger; + let surfaces_ref = &mut self.surfaces; + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + if !surfaces_ref.contains(wl_surface) { + slog::trace!( + logger_ref, + "surface ({:?}) entering output {:?}", + wl_surface, + output.name() + ); + output.enter(wl_surface); + surfaces_ref.push(wl_surface.clone()); + } + }, + |_, _, _| true, + ); + let data = with_states(surface, |states| { *states.cached_state.current::() }) @@ -267,7 +311,8 @@ impl LayerMap { /// This function needs to be called periodically (though not necessarily frequently) /// to be able cleanup internally used resources. pub fn cleanup(&mut self) { - self.layers.retain(|layer| layer.alive()) + self.layers.retain(|layer| layer.alive()); + self.surfaces.retain(|s| s.as_ref().is_alive()); } } From 98da5fe467358596556d02c11183ff3c5f0db4fe Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Jan 2022 20:41:34 +0100 Subject: [PATCH 21/41] desktop: Refactor output_{enter, leave} into utils --- src/desktop/layer.rs | 22 +------- src/desktop/space/mod.rs | 119 ++++++++++----------------------------- src/desktop/utils.rs | 86 ++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 109 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 11ae791..bf23a1f 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -103,16 +103,7 @@ impl LayerMap { (), |_, _, _| TraversalAction::DoChildren(()), |wl_surface, _, _| { - if self.surfaces.contains(wl_surface) { - slog::trace!( - self.logger, - "surface ({:?}) leaving output {:?}", - wl_surface, - output.name() - ); - output.leave(wl_surface); - self.surfaces.retain(|s| s != wl_surface); - } + output_leave(&output, &mut self.surfaces, wl_surface, &self.logger); }, |_, _, _| true, ); @@ -203,16 +194,7 @@ impl LayerMap { (), |_, _, _| TraversalAction::DoChildren(()), |wl_surface, _, _| { - if !surfaces_ref.contains(wl_surface) { - slog::trace!( - logger_ref, - "surface ({:?}) entering output {:?}", - wl_surface, - output.name() - ); - output.enter(wl_surface); - surfaces_ref.push(wl_surface.clone()); - } + output_enter(&output, surfaces_ref, wl_surface, logger_ref); }, |_, _, _| true, ); diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index ed45ad9..2f91365 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -2,23 +2,22 @@ //! rendering helpers to add custom elements or different clients to a space. use crate::{ - backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer}, + backend::renderer::{Frame, ImportAll, Renderer}, desktop::{ layer::{layer_map_for_output, LayerSurface}, + popup::PopupManager, + utils::{output_leave, output_update}, window::Window, }, utils::{Logical, Point, Rectangle, Transform}, wayland::{ - compositor::{ - get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState, - TraversalAction, - }, + compositor::{get_parent, is_sync_subsurface}, output::Output, shell::wlr_layer::Layer as WlrLayer, }, }; use indexmap::{IndexMap, IndexSet}; -use std::{cell::RefCell, collections::VecDeque, fmt}; +use std::{collections::VecDeque, fmt}; use wayland_server::protocol::wl_surface::WlSurface; mod element; @@ -326,97 +325,39 @@ impl Space { // the output. if !output_geometry.overlaps(bbox) { if let Some(surface) = kind.get_surface() { - with_surface_tree_downward( - surface, - (), - |_, _, _| TraversalAction::DoChildren(()), - |wl_surface, _, _| { - if output_state.surfaces.contains(wl_surface) { - slog::trace!( - self.logger, - "surface ({:?}) leaving output {:?}", - wl_surface, - output.name() - ); - output.leave(wl_surface); - output_state.surfaces.retain(|s| s != wl_surface); - } - }, - |_, _, _| true, - ) + output_leave(output, &mut output_state.surfaces, surface, &self.logger); } continue; } if let Some(surface) = kind.get_surface() { - with_surface_tree_downward( + output_update( + output, + output_geometry, + &mut output_state.surfaces, surface, window_loc(window, &self.id), - |_, states, location| { - let mut location = *location; - let data = states.data_map.get::>(); + &self.logger, + ); - if data.is_some() { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - - TraversalAction::DoChildren(location) - } else { - // If the parent surface is unmapped, then the child surfaces are hidden as - // well, no need to consider them here. - TraversalAction::SkipChildren - } - }, - |wl_surface, states, &loc| { - let data = states.data_map.get::>(); - - if let Some(size) = data.and_then(|d| d.borrow().surface_size()) { - let surface_rectangle = Rectangle { loc, size }; - - if output_geometry.overlaps(surface_rectangle) { - // We found a matching output, check if we already sent enter - if !output_state.surfaces.contains(wl_surface) { - slog::trace!( - self.logger, - "surface ({:?}) entering output {:?}", - wl_surface, - output.name() - ); - output.enter(wl_surface); - output_state.surfaces.push(wl_surface.clone()); - } - } else { - // Surface does not match output, if we sent enter earlier - // we should now send leave - if output_state.surfaces.contains(wl_surface) { - slog::trace!( - self.logger, - "surface ({:?}) leaving output {:?}", - wl_surface, - output.name() - ); - output.leave(wl_surface); - output_state.surfaces.retain(|s| s != wl_surface); - } - } - } else { - // Maybe the the surface got unmapped, send leave on output - if output_state.surfaces.contains(wl_surface) { - slog::trace!( - self.logger, - "surface ({:?}) leaving output {:?}", - wl_surface, - output.name() - ); - output.leave(wl_surface); - output_state.surfaces.retain(|s| s != wl_surface); - } - } - }, - |_, _, _| true, - ) + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + let location = window_loc(window, &self.id) + window.geometry().loc + location + - popup.geometry().loc; + output_update( + output, + output_geometry, + &mut output_state.surfaces, + surface, + location, + &self.logger, + ); + } + } } } } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index b855691..c726633 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -231,3 +231,89 @@ pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) { |_, _, &()| true, ); } + +pub(crate) fn output_update( + output: &Output, + output_geometry: Rectangle, + surface_list: &mut Vec, + surface: &wl_surface::WlSurface, + location: Point, + logger: &slog::Logger, +) { + with_surface_tree_downward( + surface, + location, + |_, states, location| { + let mut location = *location; + let data = states.data_map.get::>(); + + if data.is_some() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + TraversalAction::DoChildren(location) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |wl_surface, states, &loc| { + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().surface_size()) { + let surface_rectangle = Rectangle { loc, size }; + if output_geometry.overlaps(surface_rectangle) { + // We found a matching output, check if we already sent enter + output_enter(output, surface_list, wl_surface, logger); + } else { + // Surface does not match output, if we sent enter earlier + // we should now send leave + output_leave(output, surface_list, wl_surface, logger); + } + } else { + // Maybe the the surface got unmapped, send leave on output + output_leave(output, surface_list, wl_surface, logger); + } + }, + |_, _, _| true, + ); +} + +pub(crate) fn output_enter( + output: &Output, + surface_list: &mut Vec, + surface: &wl_surface::WlSurface, + logger: &slog::Logger, +) { + if !surface_list.contains(surface) { + slog::debug!( + logger, + "surface ({:?}) entering output {:?}", + surface, + output.name() + ); + output.enter(surface); + surface_list.push(surface.clone()); + } +} + +pub(crate) fn output_leave( + output: &Output, + surface_list: &mut Vec, + surface: &wl_surface::WlSurface, + logger: &slog::Logger, +) { + if surface_list.contains(surface) { + slog::debug!( + logger, + "surface ({:?}) leaving output {:?}", + surface, + output.name() + ); + output.leave(surface); + surface_list.retain(|s| s != surface); + } +} From f18ebd64052b3301d1c578ecb16590e20f1e9a29 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Jan 2022 20:43:21 +0100 Subject: [PATCH 22/41] desktop.layer: Send output enter/leave events to popups --- src/desktop/layer.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index bf23a1f..4780bf9 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -107,6 +107,23 @@ impl LayerMap { }, |_, _, _| true, ); + for (popup, _) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + output_leave(&output, &mut self.surfaces, wl_surface, &self.logger); + }, + |_, _, _| true, + ) + } + } } } @@ -198,6 +215,23 @@ impl LayerMap { }, |_, _, _| true, ); + for (popup, _) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + output_enter(&output, surfaces_ref, wl_surface, logger_ref); + }, + |_, _, _| true, + ) + } + } let data = with_states(surface, |states| { *states.cached_state.current::() From 2b1e3894950af5c4cd66580079734effb4a81dc3 Mon Sep 17 00:00:00 2001 From: dragonn Date: Wed, 19 Jan 2022 21:12:54 +0100 Subject: [PATCH 23/41] move DynamicRenderElementMap and it's friends to elements.rs --- src/desktop/space/element.rs | 51 ++++++++++++++++++++++++++++++++++++ src/desktop/space/mod.rs | 50 ----------------------------------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 287337e..d623e6a 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -12,6 +12,7 @@ use wayland_server::protocol::wl_surface::WlSurface; /// Enum for indicating on with layer a render element schould be draw #[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum RenderLayer { /// Bellow every other elements Bottom, @@ -27,6 +28,56 @@ pub enum RenderLayer { Top, } +/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. +pub type DynamicRenderElements = + Box::Frame, ::Error, ::TextureId>>; + +pub(super) type SpaceElem = + dyn SpaceElement::Frame, ::Error, ::TextureId>; + +pub(super) struct DynamicRenderElementMap<'a, R: Renderer>(pub(super) &'a [DynamicRenderElements]); + +impl<'a, R> DynamicRenderElementMap<'a, R> +where + R: Renderer + ImportAll + 'static, + R::TextureId: 'static, + R::Error: 'static, + R::Frame: 'static, +{ + pub fn iter_bottom(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::Bottom) + } + + pub fn iter_above_background(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::AboveBackground) + } + + pub fn iter_before_windows(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::BeforeWindows) + } + + pub fn iter_after_windows(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::AfterWindows) + } + + pub fn iter_before_overlay(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::BeforeOverlay) + } + + pub fn iter_top(&'a self) -> Box> + 'a> { + self.iter_layer(RenderLayer::Top) + } + + pub fn iter_layer(&'a self, layer: RenderLayer) -> Box> + 'a> { + Box::new( + self.0 + .iter() + .filter(move |c| c.layer() == layer) + .map(|c| c as &SpaceElem), + ) + } +} + /// Trait for custom elements to be rendered during [`Space::render_output`]. pub trait RenderElement where diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index c193192..93b0ed9 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -44,56 +44,6 @@ pub struct Space { logger: ::slog::Logger, } -/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. -pub type DynamicRenderElements = - Box::Frame, ::Error, ::TextureId>>; - -type SpaceElem = - dyn SpaceElement::Frame, ::Error, ::TextureId>; - -struct DynamicRenderElementMap<'a, R: Renderer>(&'a [DynamicRenderElements]); - -impl<'a, R> DynamicRenderElementMap<'a, R> -where - R: Renderer + ImportAll + 'static, - R::TextureId: 'static, - R::Error: 'static, - R::Frame: 'static, -{ - pub fn iter_bottom(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::Bottom) - } - - pub fn iter_above_background(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::AboveBackground) - } - - pub fn iter_before_windows(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::BeforeWindows) - } - - pub fn iter_after_windows(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::AfterWindows) - } - - pub fn iter_before_overlay(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::BeforeOverlay) - } - - pub fn iter_top(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::Top) - } - - pub fn iter_layer(&'a self, layer: RenderLayer) -> Box> + 'a> { - Box::new( - self.0 - .iter() - .filter(move |c| c.layer() == layer) - .map(|c| c as &SpaceElem), - ) - } -} - impl PartialEq for Space { fn eq(&self, other: &Space) -> bool { self.id == other.id From dea000921b897902c67bd4630fdb5eb72ffce948 Mon Sep 17 00:00:00 2001 From: dragonn Date: Wed, 19 Jan 2022 22:02:35 +0100 Subject: [PATCH 24/41] add comments to DynamicRenderElementMap --- src/desktop/space/element.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index d623e6a..6752af9 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -35,6 +35,7 @@ pub type DynamicRenderElements = pub(super) type SpaceElem = dyn SpaceElement::Frame, ::Error, ::TextureId>; +/// Helper struct for iterating over diffrent layers of `DynamicRenderElements` pub(super) struct DynamicRenderElementMap<'a, R: Renderer>(pub(super) &'a [DynamicRenderElements]); impl<'a, R> DynamicRenderElementMap<'a, R> @@ -44,30 +45,37 @@ where R::Error: 'static, R::Frame: 'static, { + /// Iterate over `DynamicRenderElements` with layer `RenderLayer::Bottom` pub fn iter_bottom(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::Bottom) } + /// Iterate over `DynamicRenderElements with layer `RenderLayer::AboveBackground` pub fn iter_above_background(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::AboveBackground) } + /// Iterate over `DynamicRenderElements` with layer `RenderLayer::BeforeWindows` pub fn iter_before_windows(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::BeforeWindows) } + /// Iterate over `DynamicRenderElements` with layer `RenderLayer::AfterWindows` pub fn iter_after_windows(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::AfterWindows) } + /// Iterate over `DynamicRenderElements` with layer `RenderLayer::BeforeOverlay` pub fn iter_before_overlay(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::BeforeOverlay) } + /// Iterate over `DynamicRenderElements` with layer `RenderLayer::Top` pub fn iter_top(&'a self) -> Box> + 'a> { self.iter_layer(RenderLayer::Top) } + /// Iterate over `DynamicRenderElements` with provided `layer` pub fn iter_layer(&'a self, layer: RenderLayer) -> Box> + 'a> { Box::new( self.0 From 957f1c522b5a486ce16c9b01b34dfa287ea3c45c Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 19:11:58 +0100 Subject: [PATCH 25/41] rework renderlayer to zindex solution --- src/desktop/layer.rs | 5 ++ src/desktop/space/element.rs | 96 ++++++++++-------------------------- src/desktop/space/layer.rs | 19 ++++++- src/desktop/space/mod.rs | 28 ++++------- src/desktop/space/popup.rs | 6 +++ src/desktop/space/window.rs | 6 +++ 6 files changed, 70 insertions(+), 90 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 1dc4b9a..b6e2c06 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -269,6 +269,11 @@ impl LayerMap { pub fn cleanup(&mut self) { self.layers.retain(|layer| layer.alive()) } + + /// Returns layers count + pub fn len(&self) -> usize { + self.layers.len() + } } #[derive(Debug, Default)] diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 6752af9..4f915af 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -10,22 +10,22 @@ use std::{ }; use wayland_server::protocol::wl_surface::WlSurface; -/// Enum for indicating on with layer a render element schould be draw -#[derive(Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum RenderLayer { - /// Bellow every other elements - Bottom, - /// Above WlrLayer::Background but bellow WlrLayer::Bottom - AboveBackground, - /// Right before programs windows are draw - BeforeWindows, - /// Right after programs windows are draw - AfterWindows, - /// Above WlrLayer::Top but bellow WlrLayer::Overlay - BeforeOverlay, - /// Above anything else - Top, +/// Indicates default values for some zindexs inside smithay +#[derive(Debug)] +#[repr(u8)] +pub enum RenderZindex { + /// WlrLayer::Background default zindex + Background = 10, + /// WlrLayer::Bottom default zindex + Bottom = 20, + /// Not used yet? + Shell = 30, + /// Default zindex for Windows + Top = 40, + /// Default Layer for RenderElements + Overlay = 50, + /// Default Layer for PopUps? + PopUp = 60, } /// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. @@ -35,57 +35,6 @@ pub type DynamicRenderElements = pub(super) type SpaceElem = dyn SpaceElement::Frame, ::Error, ::TextureId>; -/// Helper struct for iterating over diffrent layers of `DynamicRenderElements` -pub(super) struct DynamicRenderElementMap<'a, R: Renderer>(pub(super) &'a [DynamicRenderElements]); - -impl<'a, R> DynamicRenderElementMap<'a, R> -where - R: Renderer + ImportAll + 'static, - R::TextureId: 'static, - R::Error: 'static, - R::Frame: 'static, -{ - /// Iterate over `DynamicRenderElements` with layer `RenderLayer::Bottom` - pub fn iter_bottom(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::Bottom) - } - - /// Iterate over `DynamicRenderElements with layer `RenderLayer::AboveBackground` - pub fn iter_above_background(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::AboveBackground) - } - - /// Iterate over `DynamicRenderElements` with layer `RenderLayer::BeforeWindows` - pub fn iter_before_windows(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::BeforeWindows) - } - - /// Iterate over `DynamicRenderElements` with layer `RenderLayer::AfterWindows` - pub fn iter_after_windows(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::AfterWindows) - } - - /// Iterate over `DynamicRenderElements` with layer `RenderLayer::BeforeOverlay` - pub fn iter_before_overlay(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::BeforeOverlay) - } - - /// Iterate over `DynamicRenderElements` with layer `RenderLayer::Top` - pub fn iter_top(&'a self) -> Box> + 'a> { - self.iter_layer(RenderLayer::Top) - } - - /// Iterate over `DynamicRenderElements` with provided `layer` - pub fn iter_layer(&'a self, layer: RenderLayer) -> Box> + 'a> { - Box::new( - self.0 - .iter() - .filter(move |c| c.layer() == layer) - .map(|c| c as &SpaceElem), - ) - } -} - /// Trait for custom elements to be rendered during [`Space::render_output`]. pub trait RenderElement where @@ -132,9 +81,9 @@ where log: &slog::Logger, ) -> Result<(), R::Error>; - /// Returns they layer the elements schould be draw on, defaults to Top - fn layer(&self) -> RenderLayer { - RenderLayer::Top + /// Returns z_index of RenderElement, reverf too [`RenderZindex`] for default values + fn z_index(&self) -> u8 { + RenderZindex::Overlay as u8 } } @@ -163,6 +112,9 @@ where damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error>; + fn z_index(&self) -> u8; //{ + // 0 + //} } impl SpaceElement for Box> @@ -196,6 +148,10 @@ where ) -> Result<(), R::Error> { (&**self as &dyn RenderElement).draw(renderer, frame, scale, damage, log) } + + fn z_index(&self) -> u8 { + RenderElement::z_index(self.as_ref()) + } } /// Generic helper for drawing [`WlSurface`]s and their subsurfaces diff --git a/src/desktop/space/layer.rs b/src/desktop/space/layer.rs index 89a4799..e6f397e 100644 --- a/src/desktop/space/layer.rs +++ b/src/desktop/space/layer.rs @@ -5,7 +5,7 @@ use crate::{ space::{Space, SpaceElement}, }, utils::{Logical, Point, Rectangle}, - wayland::output::Output, + wayland::{output::Output, shell::wlr_layer::Layer}, }; use std::{ any::TypeId, @@ -13,6 +13,8 @@ use std::{ collections::HashMap, }; +use super::RenderZindex; + #[derive(Default)] pub struct LayerState { pub drawn: bool, @@ -69,4 +71,19 @@ where } res } + + fn z_index(&self) -> u8 { + if let Some(layer) = self.layer() { + let z_index = match layer { + Layer::Background => RenderZindex::Background, + Layer::Bottom => RenderZindex::Bottom, + Layer::Top => RenderZindex::Top, + Layer::Overlay => RenderZindex::Overlay, + }; + z_index as u8 + } else { + //TODO: what to do when layersurface doesn't have a layer? + 0 + } + } } diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 93b0ed9..36e817c 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -14,7 +14,6 @@ use crate::{ TraversalAction, }, output::Output, - shell::wlr_layer::Layer as WlrLayer, }, }; use indexmap::{IndexMap, IndexSet}; @@ -605,27 +604,18 @@ impl Space { .collect::>(), )?; - let custom_elements = DynamicRenderElementMap(custom_elements); + let mut render_elements: Vec<&SpaceElem> = + Vec::with_capacity(custom_elements.len() + layer_map.len() + self.windows.len()); + + render_elements.append(&mut custom_elements.iter().map(|l| l as &SpaceElem).collect()); + render_elements.append(&mut self.windows.iter().map(|l| l as &SpaceElem).collect()); + render_elements.append(&mut layer_map.layers().map(|l| l as &SpaceElem).collect()); + + render_elements.sort_by_key(|e| e.z_index()); // Then re-draw all windows & layers overlapping with a damage rect. - for element in custom_elements - .iter_bottom() - .chain( - layer_map - .layers_on(WlrLayer::Background) - .map(|l| l as &SpaceElem), - ) - .chain(custom_elements.iter_above_background()) - .chain(layer_map.layers_on(WlrLayer::Bottom).map(|l| l as &SpaceElem)) - .chain(custom_elements.iter_before_windows()) - .chain(self.windows.iter().map(|w| w as &SpaceElem)) - .chain(custom_elements.iter_after_windows()) - .chain(layer_map.layers_on(WlrLayer::Top).map(|l| l as &SpaceElem)) - .chain(custom_elements.iter_before_overlay()) - .chain(layer_map.layers_on(WlrLayer::Overlay).map(|l| l as &SpaceElem)) - .chain(custom_elements.iter_top()) - { + for element in render_elements { let geo = element.geometry(self.id); if damage.iter().any(|d| d.overlaps(geo)) { let loc = element.location(self.id); diff --git a/src/desktop/space/popup.rs b/src/desktop/space/popup.rs index dbef156..6a0c423 100644 --- a/src/desktop/space/popup.rs +++ b/src/desktop/space/popup.rs @@ -12,6 +12,8 @@ use crate::{ }; use std::any::TypeId; +use super::RenderZindex; + #[derive(Debug)] pub struct RenderPopup { location: Point, @@ -126,4 +128,8 @@ where // popups are special, we track them, but they render with their parents Ok(()) } + + fn z_index(&self) -> u8 { + RenderZindex::PopUp as u8 + } } diff --git a/src/desktop/space/window.rs b/src/desktop/space/window.rs index f1ece6b..c000f04 100644 --- a/src/desktop/space/window.rs +++ b/src/desktop/space/window.rs @@ -13,6 +13,8 @@ use std::{ collections::HashMap, }; +use super::RenderZindex; + #[derive(Default)] pub struct WindowState { pub location: Point, @@ -103,4 +105,8 @@ where } res } + + fn z_index(&self) -> u8 { + RenderZindex::Top as u8 + } } From 53c6bf003f3e2d175ae788dd4e9fe7faa8e1fc00 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 19:18:00 +0100 Subject: [PATCH 26/41] suppres clippy len_without_is_empty for LayerMap --- src/desktop/layer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index b6e2c06..fe63ed9 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -271,6 +271,7 @@ impl LayerMap { } /// Returns layers count + #[allow(clippy::len_without_is_empty)] //we don't need is_empty on that struct for now, mark as allow pub fn len(&self) -> usize { self.layers.len() } From 6477942122f439eca567e2b608c23bf2445788e6 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 19:45:30 +0100 Subject: [PATCH 27/41] add extra derives to RenderZindex --- src/desktop/space/element.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 4f915af..93fdced 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -11,21 +11,23 @@ use std::{ use wayland_server::protocol::wl_surface::WlSurface; /// Indicates default values for some zindexs inside smithay -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum RenderZindex { /// WlrLayer::Background default zindex Background = 10, /// WlrLayer::Bottom default zindex Bottom = 20, - /// Not used yet? - Shell = 30, /// Default zindex for Windows - Top = 40, + Shell = 30, + /// Default zindex for Windows PopUps + PopUpsShell = 40, + /// WlrLayer::Top default zindex + Top = 50, /// Default Layer for RenderElements - Overlay = 50, - /// Default Layer for PopUps? - PopUp = 60, + Overlay = 80, + /// Default Layer for Overlay PopUp + PopUpsOverlay = 100, } /// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. From cbb2c1e54180cafc4eb6345a1c130acd202f5d38 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 19:45:59 +0100 Subject: [PATCH 28/41] fine to return 0 when LayerSurface doesn't have layer --- src/desktop/space/layer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/desktop/space/layer.rs b/src/desktop/space/layer.rs index e6f397e..93087dd 100644 --- a/src/desktop/space/layer.rs +++ b/src/desktop/space/layer.rs @@ -82,7 +82,6 @@ where }; z_index as u8 } else { - //TODO: what to do when layersurface doesn't have a layer? 0 } } From d7415e6fa9fb88e47dbb351edd76fb63d80aead9 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 19:51:41 +0100 Subject: [PATCH 29/41] remove left over comment from default implemantion for z_index --- src/desktop/space/element.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 93fdced..52075a9 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -114,9 +114,7 @@ where damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error>; - fn z_index(&self) -> u8; //{ - // 0 - //} + fn z_index(&self) -> u8; } impl SpaceElement for Box> From f0564ebad666b0740f8aa70bca61c9723e564890 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 20:03:33 +0100 Subject: [PATCH 30/41] store parent_layer in RenderPopup for z_index implemantion --- src/desktop/space/popup.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/desktop/space/popup.rs b/src/desktop/space/popup.rs index 6a0c423..718d0d2 100644 --- a/src/desktop/space/popup.rs +++ b/src/desktop/space/popup.rs @@ -18,6 +18,7 @@ use super::RenderZindex; pub struct RenderPopup { location: Point, popup: PopupKind, + parent_layer: RenderZindex, } impl Window { @@ -41,6 +42,7 @@ impl Window { RenderPopup { location: offset, popup, + parent_layer: RenderZindex::Shell, } }) }) @@ -72,6 +74,7 @@ impl LayerSurface { RenderPopup { location: offset, popup, + parent_layer: RenderZindex::Overlay, } }) }) @@ -130,6 +133,10 @@ where } fn z_index(&self) -> u8 { - RenderZindex::PopUp as u8 + match self.parent_layer { + RenderZindex::Shell => RenderZindex::PopUpsShell as u8, + RenderZindex::Overlay => RenderZindex::PopUpsOverlay as u8, + _ => 0, //Maybe better panic here? Or return u8::MAX? + } } } From 97c831acbce033078136744bf245106e2f8f0b44 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 20:04:05 +0100 Subject: [PATCH 31/41] rework damage tracking to render_elements and use extend --- src/desktop/space/mod.rs | 61 +++++++++++++++------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 36e817c..401330a 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -490,6 +490,22 @@ impl Space { .flat_map(|l| l.popup_elements::(self.id)) .collect::>(); + let mut render_elements: Vec<&SpaceElem> = Vec::with_capacity( + custom_elements.len() + + layer_map.len() + + self.windows.len() + + window_popups.len() + + layer_popups.len(), + ); + + render_elements.extend(&mut custom_elements.iter().map(|l| l as &SpaceElem)); + render_elements.extend(&mut self.windows.iter().map(|l| l as &SpaceElem)); + render_elements.extend(&mut window_popups.iter().map(|l| l as &SpaceElem)); + render_elements.extend(&mut layer_map.layers().map(|l| l as &SpaceElem)); + render_elements.extend(&mut layer_popups.iter().map(|l| l as &SpaceElem)); + + render_elements.sort_by_key(|e| e.z_index()); + // This will hold all the damage we need for this rendering step let mut damage = Vec::>::new(); // First add damage for windows gone @@ -498,16 +514,7 @@ impl Space { .last_state .iter() .filter_map(|(id, geo)| { - if !self - .windows - .iter() - .map(|w| w as &SpaceElem) - .chain(window_popups.iter().map(|p| p as &SpaceElem)) - .chain(layer_map.layers().map(|l| l as &SpaceElem)) - .chain(layer_popups.iter().map(|p| p as &SpaceElem)) - .chain(custom_elements.iter().map(|c| c as &SpaceElem)) - .any(|e| ToplevelId::from(e) == *id) - { + if !render_elements.iter().any(|e| ToplevelId::from(*e) == *id) { Some(*geo) } else { None @@ -520,17 +527,9 @@ impl Space { } // lets iterate front to back and figure out, what new windows or unmoved windows we have - for element in self - .windows - .iter() - .map(|w| w as &SpaceElem) - .chain(window_popups.iter().map(|p| p as &SpaceElem)) - .chain(layer_map.layers().map(|l| l as &SpaceElem)) - .chain(layer_popups.iter().map(|p| p as &SpaceElem)) - .chain(custom_elements.iter().map(|c| c as &SpaceElem)) - { + for element in &render_elements { let geo = element.geometry(self.id); - let old_geo = state.last_state.get(&ToplevelId::from(element)).cloned(); + let old_geo = state.last_state.get(&ToplevelId::from(*element)).cloned(); // window was moved or resized if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { @@ -603,19 +602,9 @@ impl Space { .map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round()) .collect::>(), )?; - - let mut render_elements: Vec<&SpaceElem> = - Vec::with_capacity(custom_elements.len() + layer_map.len() + self.windows.len()); - - render_elements.append(&mut custom_elements.iter().map(|l| l as &SpaceElem).collect()); - render_elements.append(&mut self.windows.iter().map(|l| l as &SpaceElem).collect()); - render_elements.append(&mut layer_map.layers().map(|l| l as &SpaceElem).collect()); - - render_elements.sort_by_key(|e| e.z_index()); - // Then re-draw all windows & layers overlapping with a damage rect. - for element in render_elements { + for element in &render_elements { let geo = element.geometry(self.id); if damage.iter().any(|d| d.overlaps(geo)) { let loc = element.location(self.id); @@ -656,17 +645,11 @@ impl Space { } // If rendering was successful capture the state and add the damage - state.last_state = self - .windows + state.last_state = render_elements .iter() - .map(|w| w as &SpaceElem) - .chain(window_popups.iter().map(|p| p as &SpaceElem)) - .chain(layer_map.layers().map(|l| l as &SpaceElem)) - .chain(layer_popups.iter().map(|p| p as &SpaceElem)) - .chain(custom_elements.iter().map(|c| c as &SpaceElem)) .map(|elem| { let geo = elem.geometry(self.id); - (ToplevelId::from(elem), geo) + (ToplevelId::from(*elem), geo) }) .collect(); state.old_damage.push_front(new_damage.clone()); From 15c9e9adf0385e87997b6cca07a1e3fd7b84e2c1 Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 20:04:22 +0100 Subject: [PATCH 32/41] change default layer for Windows to Shell --- src/desktop/space/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop/space/window.rs b/src/desktop/space/window.rs index c000f04..9b0c045 100644 --- a/src/desktop/space/window.rs +++ b/src/desktop/space/window.rs @@ -107,6 +107,6 @@ where } fn z_index(&self) -> u8 { - RenderZindex::Top as u8 + RenderZindex::Shell as u8 } } From 3f86c5b94d222bcaf403d1e3cd74ba044604ef1a Mon Sep 17 00:00:00 2001 From: dragonn Date: Thu, 20 Jan 2022 20:43:04 +0100 Subject: [PATCH 33/41] small reorder in RenderZindex and change z_index logic in popups --- src/desktop/space/element.rs | 10 +++++----- src/desktop/space/popup.rs | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 52075a9..ebb7a7b 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -20,14 +20,14 @@ pub enum RenderZindex { Bottom = 20, /// Default zindex for Windows Shell = 30, - /// Default zindex for Windows PopUps - PopUpsShell = 40, /// WlrLayer::Top default zindex - Top = 50, + Top = 40, + /// Default zindex for Windows PopUps + Popups = 50, /// Default Layer for RenderElements - Overlay = 80, + Overlay = 60, /// Default Layer for Overlay PopUp - PopUpsOverlay = 100, + PopupsOverlay = 70, } /// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. diff --git a/src/desktop/space/popup.rs b/src/desktop/space/popup.rs index 718d0d2..dd8cc47 100644 --- a/src/desktop/space/popup.rs +++ b/src/desktop/space/popup.rs @@ -8,7 +8,7 @@ use crate::{ window::Window, }, utils::{Logical, Point, Rectangle}, - wayland::output::Output, + wayland::{output::Output, shell::wlr_layer::Layer}, }; use std::any::TypeId; @@ -18,7 +18,7 @@ use super::RenderZindex; pub struct RenderPopup { location: Point, popup: PopupKind, - parent_layer: RenderZindex, + z_index: u8, } impl Window { @@ -42,7 +42,7 @@ impl Window { RenderPopup { location: offset, popup, - parent_layer: RenderZindex::Shell, + z_index: RenderZindex::Popups as u8, } }) }) @@ -52,7 +52,7 @@ impl Window { } impl LayerSurface { - pub(super) fn popup_elements(&self, space_id: usize) -> impl Iterator + pub(super) fn popup_elements(&self, space_id: usize) -> impl Iterator + '_ where R: Renderer + ImportAll + 'static, R::TextureId: 'static, @@ -71,10 +71,20 @@ impl LayerSurface { .flatten() .map(move |(popup, location)| { let offset = loc + location - popup.geometry().loc; + let z_index = if let Some(layer) = self.layer() { + if layer == Layer::Overlay { + RenderZindex::PopupsOverlay as u8 + } else { + RenderZindex::Popups as u8 + } + } else { + 0 + }; + RenderPopup { location: offset, popup, - parent_layer: RenderZindex::Overlay, + z_index, } }) }) @@ -133,10 +143,6 @@ where } fn z_index(&self) -> u8 { - match self.parent_layer { - RenderZindex::Shell => RenderZindex::PopUpsShell as u8, - RenderZindex::Overlay => RenderZindex::PopUpsOverlay as u8, - _ => 0, //Maybe better panic here? Or return u8::MAX? - } + self.z_index } } From ff81fce7866be007bdf29f060243b5c6439bc801 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 21 Jan 2022 18:50:52 +0100 Subject: [PATCH 34/41] space: Pass a location to `RenderElement::draw` --- src/desktop/space/element.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index d162060..b2294bc 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -44,6 +44,8 @@ where /// Draws the element using the provided `Frame` and `Renderer`. /// /// - `scale` provides the current fractional scale value to render as + /// - `location` refers to the relative position in the bound buffer the element should be drawn at, + /// so that it matches with the space-relative coordinates returned by [`RenderElement::geometry`]. /// - `damage` provides the regions you need to re-draw and *may* not /// be equivalent to the damage returned by `accumulated_damage`. /// Redrawing other parts of the element is not valid and may cause rendering artifacts. @@ -52,6 +54,7 @@ where renderer: &mut R, frame: &mut F, scale: f64, + location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error>; @@ -109,11 +112,11 @@ where renderer: &mut R, frame: &mut F, scale: f64, - _location: Point, + location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { - (&**self as &dyn RenderElement).draw(renderer, frame, scale, damage, log) + (&**self as &dyn RenderElement).draw(renderer, frame, scale, location, damage, log) } } @@ -162,6 +165,7 @@ where renderer: &mut R, frame: &mut F, scale: f64, + location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { @@ -170,7 +174,7 @@ where frame, &self.surface, scale, - self.position, + location, damage, log, ) From 0077a7abf60af3b437969376effce4def9e978ee Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sat, 22 Jan 2022 21:14:36 +0100 Subject: [PATCH 35/41] swapchain: Don't force release of submitted buffers --- src/backend/allocator/swapchain.rs | 14 ++++++++------ src/backend/drm/surface/gbm.rs | 2 +- src/backend/x11/surface.rs | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/backend/allocator/swapchain.rs b/src/backend/allocator/swapchain.rs index 60783bf..1a385cf 100644 --- a/src/backend/allocator/swapchain.rs +++ b/src/backend/allocator/swapchain.rs @@ -154,11 +154,13 @@ where /// Mark a given buffer as submitted. /// /// This might effect internal data (e.g. buffer age) and may only be called, - /// if the buffer was actually used for display. + /// the buffer may not be used for rendering anymore. + /// You may hold on to it, if you require keeping it alive. + /// /// Buffers can always just be safely discarded by dropping them, but not - /// calling this function may affect performance characteristics + /// calling this function before may affect performance characteristics /// (e.g. by not tracking the buffer age). - pub fn submitted(&self, slot: Slot) { + pub fn submitted(&self, slot: &Slot) { // don't mess up the state, if the user submitted and old buffer, after e.g. a resize if !self.slots.iter().any(|other| Arc::ptr_eq(&slot.0, other)) { return; @@ -167,7 +169,7 @@ where slot.0.age.store(1, Ordering::SeqCst); for other_slot in &self.slots { if !Arc::ptr_eq(other_slot, &slot.0) && other_slot.buffer.is_some() { - assert!(other_slot + let res = other_slot .age .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |age| { if age > 0 { @@ -175,8 +177,8 @@ where } else { Some(0) } - }) - .is_ok()); + }); + assert!(res.is_ok()); } } } diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index d5d646e..7fc16c5 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -213,7 +213,6 @@ where pub fn frame_submitted(&mut self) -> Result<(), Error> { if let Some(mut pending) = self.pending_fb.take() { std::mem::swap(&mut pending, &mut self.current_fb); - self.swapchain.submitted(pending); if self.queued_fb.is_some() { self.submit()?; } @@ -233,6 +232,7 @@ where self.drm.page_flip([(fb, self.drm.plane())].iter(), true) }; if flip.is_ok() { + self.swapchain.submitted(&slot); self.pending_fb = Some(slot); } flip.map_err(Error::DrmError) diff --git a/src/backend/x11/surface.rs b/src/backend/x11/surface.rs index 5a3a2a8..4d8f9ba 100644 --- a/src/backend/x11/surface.rs +++ b/src/backend/x11/surface.rs @@ -123,7 +123,7 @@ impl X11Surface { // Now present the current buffer let _ = pixmap.present(&*connection, window.as_ref())?; - self.swapchain.submitted(next); + self.swapchain.submitted(&next); // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. let _ = connection.flush(); From c5f24fe574a7f6accbf9b358d44079b22e82bb9b Mon Sep 17 00:00:00 2001 From: i509VCB Date: Sat, 22 Jan 2022 14:31:46 -0600 Subject: [PATCH 36/41] renderer: describe at parameter in Frame::clear --- src/backend/renderer/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 39c8f91..454b6fc 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -122,6 +122,9 @@ pub trait Frame { /// Clear the complete current target with a single given color. /// + /// The `at` parameter specifies a set of rectangles to clear in the current target. This allows partially + /// clearing the target which may be useful for damaged rendering. + /// /// This operation is only valid in between a `begin` and `finish`-call. /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error>; From 6ae0a3510ac66052e64b2d5edbbeccb267c70d09 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sat, 22 Jan 2022 22:11:57 +0100 Subject: [PATCH 37/41] winit: Fix returned buffer age --- src/backend/renderer/gles2/mod.rs | 5 ++--- src/backend/winit/mod.rs | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 9df9829..0d0bce2 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -874,6 +874,7 @@ impl Bind> for Gles2Renderer { fn bind(&mut self, surface: Rc) -> Result<(), Gles2Error> { self.unbind()?; self.target_surface = Some(surface); + self.make_current()?; Ok(()) } } @@ -881,9 +882,7 @@ impl Bind> for Gles2Renderer { impl Bind for Gles2Renderer { fn bind(&mut self, dmabuf: Dmabuf) -> Result<(), Gles2Error> { self.unbind()?; - unsafe { - self.egl.make_current()?; - } + self.make_current()?; // Free outdated buffer resources // TODO: Replace with `drain_filter` once it lands diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index d78560a..6223c90 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -302,7 +302,11 @@ impl WinitGraphicsBackend { Ok(()) } - /// Retrieve the buffer age of the current backbuffer of the window + /// Retrieve the buffer age of the current backbuffer of the window. + /// + /// This will only return a meaningful value, if this `WinitGraphicsBackend` + /// is currently bound (by previously calling [`WinitGraphicsBackend::bind`]). + /// Otherwise the contents of the return value are undefined. pub fn buffer_age(&self) -> usize { if self.damage_tracking { self.egl.buffer_age() as usize From b11fef8a90c8a2d7a2144174fd1aeca2dfca3926 Mon Sep 17 00:00:00 2001 From: dragonn Date: Sun, 23 Jan 2022 19:58:11 +0100 Subject: [PATCH 38/41] remove not need &mut in render_elements extend --- src/desktop/space/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 401330a..e35f0bf 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -498,11 +498,11 @@ impl Space { + layer_popups.len(), ); - render_elements.extend(&mut custom_elements.iter().map(|l| l as &SpaceElem)); - render_elements.extend(&mut self.windows.iter().map(|l| l as &SpaceElem)); - render_elements.extend(&mut window_popups.iter().map(|l| l as &SpaceElem)); - render_elements.extend(&mut layer_map.layers().map(|l| l as &SpaceElem)); - render_elements.extend(&mut layer_popups.iter().map(|l| l as &SpaceElem)); + render_elements.extend(custom_elements.iter().map(|l| l as &SpaceElem)); + render_elements.extend(self.windows.iter().map(|l| l as &SpaceElem)); + render_elements.extend(window_popups.iter().map(|l| l as &SpaceElem)); + render_elements.extend(layer_map.layers().map(|l| l as &SpaceElem)); + render_elements.extend(layer_popups.iter().map(|l| l as &SpaceElem)); render_elements.sort_by_key(|e| e.z_index()); From cd26ac1507584979160e3731195f39be5f49dae8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sat, 22 Jan 2022 23:19:28 +0100 Subject: [PATCH 39/41] egl: Handle buffer age query errors --- src/backend/egl/surface.rs | 16 +++++++++++++--- src/backend/winit/mod.rs | 11 +++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/backend/egl/surface.rs b/src/backend/egl/surface.rs index a4a2c24..fa4bead 100644 --- a/src/backend/egl/surface.rs +++ b/src/backend/egl/surface.rs @@ -81,18 +81,28 @@ impl EGLSurface { } /// Returns the buffer age of the underlying back buffer - pub fn buffer_age(&self) -> i32 { + pub fn buffer_age(&self) -> Option { let surface = self.surface.load(Ordering::SeqCst); let mut age = 0; - unsafe { + let ret = unsafe { ffi::egl::QuerySurface( **self.display, surface as *const _, ffi::egl::BUFFER_AGE_EXT as i32, &mut age as *mut _, + ) + }; + if ret == ffi::egl::FALSE { + slog::debug!( + self.logger, + "Failed to query buffer age value for surface {:?}: {}", + self, + EGLError::from_last_call().unwrap_err() ); + None + } else { + Some(age) } - age } /// Swaps buffers at the end of a frame. diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index 6223c90..a93f3e7 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -306,12 +306,15 @@ impl WinitGraphicsBackend { /// /// This will only return a meaningful value, if this `WinitGraphicsBackend` /// is currently bound (by previously calling [`WinitGraphicsBackend::bind`]). - /// Otherwise the contents of the return value are undefined. - pub fn buffer_age(&self) -> usize { + /// + /// Otherwise and on error this function returns `None`. + /// If you are using this value actively e.g. for damage-tracking you should + /// likely interpret an error just as if "0" was returned. + pub fn buffer_age(&self) -> Option { if self.damage_tracking { - self.egl.buffer_age() as usize + self.egl.buffer_age().map(|x| x as usize) } else { - 0 + Some(0) } } From 3454146c922cd2329401fde344042c493a2856b7 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Jan 2022 14:26:10 +0100 Subject: [PATCH 40/41] gbm: Allow usage of other allocators for GbmBufferedSurface --- anvil/src/udev.rs | 7 +++-- src/backend/drm/surface/gbm.rs | 55 +++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 3b4d3ef..d61c392 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -264,7 +264,7 @@ pub fn run_udev(log: Logger) { event_loop.handle().remove(udev_event_source); } -pub type RenderSurface = GbmBufferedSurface; +pub type RenderSurface = GbmBufferedSurface>>, SessionFd>; struct SurfaceData { surface: RenderSurface, @@ -279,7 +279,7 @@ struct BackendData { #[cfg(feature = "debug")] fps_texture: Gles2Texture, renderer: Rc>, - gbm: GbmDevice, + gbm: Rc>>, registration_token: RegistrationToken, event_dispatcher: Dispatcher<'static, DrmDevice, AnvilState>, dev_id: u64, @@ -287,7 +287,7 @@ struct BackendData { fn scan_connectors( device: &mut DrmDevice, - gbm: &GbmDevice, + gbm: &Rc>>, renderer: &mut Gles2Renderer, output_map: &mut crate::output_map::OutputMap, signaler: &Signaler, @@ -483,6 +483,7 @@ impl AnvilState { } } + let gbm = Rc::new(RefCell::new(gbm)); let backends = Rc::new(RefCell::new(scan_connectors( &mut device, &gbm, diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index 7fc16c5..9ec9414 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -4,12 +4,12 @@ use std::sync::Arc; use drm::buffer::PlanarBuffer; use drm::control::{connector, crtc, framebuffer, plane, Device, Mode}; -use gbm::{BufferObject, Device as GbmDevice}; +use gbm::BufferObject; use crate::backend::allocator::{ dmabuf::{AsDmabuf, Dmabuf}, gbm::GbmConvertError, - Format, Fourcc, Modifier, Slot, Swapchain, + Allocator, Format, Fourcc, Modifier, Slot, Swapchain, }; use crate::backend::drm::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface}; use crate::backend::SwapBuffersError; @@ -18,17 +18,19 @@ use slog::{debug, error, o, trace, warn}; /// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`]. #[derive(Debug)] -pub struct GbmBufferedSurface { +pub struct GbmBufferedSurface> + 'static, D: AsRawFd + 'static> { current_fb: Slot>, pending_fb: Option>>, queued_fb: Option>>, next_fb: Option>>, - swapchain: Swapchain, BufferObject<()>>, + swapchain: Swapchain>, drm: Arc>, } -impl GbmBufferedSurface +impl GbmBufferedSurface where + A: Allocator>, + A::Error: std::error::Error + Send + Sync, D: AsRawFd + 'static, { /// Create a new `GbmBufferedSurface` from a given compatible combination @@ -40,10 +42,10 @@ where #[allow(clippy::type_complexity)] pub fn new( drm: DrmSurface, - allocator: GbmDevice, + allocator: A, mut renderer_formats: HashSet, log: L, - ) -> Result, Error> + ) -> Result, Error> where L: Into>, { @@ -121,7 +123,7 @@ where let mode = drm.pending_mode(); - let mut swapchain: Swapchain, BufferObject<()>> = Swapchain::new( + let mut swapchain: Swapchain> = Swapchain::new( allocator, mode.size().0 as u32, mode.size().1 as u32, @@ -130,7 +132,7 @@ where ); // Test format - let buffer = swapchain.acquire()?.unwrap(); + let buffer = swapchain.acquire().map_err(Error::GbmError)?.unwrap(); let format = Format { code, modifier: buffer.modifier().unwrap(), // no guarantee @@ -171,9 +173,13 @@ where /// /// *Note*: This function can be called multiple times and /// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]). - pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error> { + pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error> { if self.next_fb.is_none() { - let slot = self.swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?; + let slot = self + .swapchain + .acquire() + .map_err(Error::GbmError)? + .ok_or(Error::NoFreeSlotsError)?; let maybe_buffer = slot.userdata().get::().cloned(); if maybe_buffer.is_none() { @@ -197,7 +203,7 @@ where /// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`] /// when a vblank event is received, that denotes successful scanout of the buffer. /// Otherwise the underlying swapchain will eventually run out of buffers. - pub fn queue_buffer(&mut self) -> Result<(), Error> { + pub fn queue_buffer(&mut self) -> Result<(), Error> { self.queued_fb = self.next_fb.take(); if self.pending_fb.is_none() && self.queued_fb.is_some() { self.submit()?; @@ -210,7 +216,7 @@ where /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::super::DrmDevice) /// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface. /// Otherwise the underlying swapchain will run out of buffers eventually. - pub fn frame_submitted(&mut self) -> Result<(), Error> { + pub fn frame_submitted(&mut self) -> Result<(), Error> { if let Some(mut pending) = self.pending_fb.take() { std::mem::swap(&mut pending, &mut self.current_fb); if self.queued_fb.is_some() { @@ -221,7 +227,7 @@ where Ok(()) } - fn submit(&mut self) -> Result<(), Error> { + fn submit(&mut self) -> Result<(), Error> { // yes it does not look like it, but both of these lines should be safe in all cases. let slot = self.queued_fb.take().unwrap(); let fb = slot.userdata().get::>().unwrap().fb; @@ -276,13 +282,13 @@ where /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// or is not compatible with the currently pending /// [`Mode`](drm::control::Mode). - pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { + pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { self.drm.add_connector(connector).map_err(Error::DrmError) } /// Tries to mark a [`connector`](drm::control::connector) /// for removal on the next commit. - pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { self.drm.remove_connector(connector).map_err(Error::DrmError) } @@ -292,7 +298,7 @@ where /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// or is not compatible with the currently pending /// [`Mode`](drm::control::Mode). - pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { self.drm.set_connectors(connectors).map_err(Error::DrmError) } @@ -314,7 +320,7 @@ where /// Fails if the mode is not compatible with the underlying /// [`crtc`](drm::control::crtc) or any of the /// pending [`connector`](drm::control::connector)s. - pub fn use_mode(&mut self, mode: Mode) -> Result<(), Error> { + pub fn use_mode(&mut self, mode: Mode) -> Result<(), Error> { self.drm.use_mode(mode).map_err(Error::DrmError)?; let (w, h) = mode.size(); self.swapchain.resize(w as _, h as _); @@ -334,9 +340,10 @@ impl Drop for FbHandle { } } -fn attach_framebuffer(drm: &Arc>, bo: &BufferObject<()>) -> Result, Error> +fn attach_framebuffer(drm: &Arc>, bo: &BufferObject<()>) -> Result, Error> where - A: AsRawFd + 'static, + E: std::error::Error + Send + Sync, + D: AsRawFd + 'static, { let modifier = match bo.modifier().unwrap() { Modifier::Invalid => None, @@ -385,7 +392,7 @@ where /// Errors thrown by a [`GbmBufferedSurface`] #[derive(Debug, thiserror::Error)] -pub enum Error { +pub enum Error { /// No supported pixel format for the given plane could be determined #[error("No supported plane buffer format found")] NoSupportedPlaneFormat, @@ -406,14 +413,14 @@ pub enum Error { DrmError(#[from] DrmError), /// Error importing the rendered buffer to libgbm for scan-out #[error("The underlying gbm device encounted an error: {0}")] - GbmError(#[from] std::io::Error), + GbmError(#[source] E), /// Error exporting as Dmabuf #[error("The allocated buffer could not be exported as a dmabuf: {0}")] AsDmabufError(#[from] GbmConvertError), } -impl From for SwapBuffersError { - fn from(err: Error) -> SwapBuffersError { +impl From> for SwapBuffersError { + fn from(err: Error) -> SwapBuffersError { match err { x @ Error::NoSupportedPlaneFormat | x @ Error::NoSupportedRendererFormat From ac265a3d68cd089eea086365a8d5d7bd3aaca612 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 26 Jan 2022 20:29:34 +0100 Subject: [PATCH 41/41] deps: Update drm-rs and gbm-rs --- Cargo.toml | 6 ++-- src/backend/drm/device/atomic.rs | 4 +-- src/backend/drm/surface/atomic.rs | 49 +++++++++++++++---------------- src/backend/drm/surface/legacy.rs | 23 ++++++--------- 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b3a592..886c4dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,9 @@ cgmath = "0.18.0" dbus = { version = "0.9.0", optional = true } downcast-rs = "1.2.0" drm-fourcc = "^2.1.1" -drm = { version = "0.5.0", optional = true } -drm-ffi = { version = "0.2.0", optional = true } -gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] } +drm = { version = "0.6.1", optional = true } +drm-ffi = { version = "0.2.1", optional = true } +gbm = { version = "0.8.0", optional = true, default-features = false, features = ["drm-support"] } input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true } indexmap = { version = "1.7", optional = true } lazy_static = "1" diff --git a/src/backend/drm/device/atomic.rs b/src/backend/drm/device/atomic.rs index 24ab6bb..9763d27 100644 --- a/src/backend/drm/device/atomic.rs +++ b/src/backend/drm/device/atomic.rs @@ -242,7 +242,7 @@ impl AtomicDrmDevice { req.add_property(*crtc, *mode_prop, property::Value::Unknown(0)); } self.fd - .atomic_commit(&[AtomicCommitFlags::AllowModeset], req) + .atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req) .map_err(|source| Error::Access { errmsg: "Failed to disable connectors", dev: self.fd.dev_path(), @@ -282,7 +282,7 @@ impl Drop for AtomicDrmDevice { add_multiple_props(&mut req, &self.old_state.2); add_multiple_props(&mut req, &self.old_state.3); - if let Err(err) = self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { + if let Err(err) = self.fd.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req) { error!(self.logger, "Failed to restore previous state. Error: {}", err); } } diff --git a/src/backend/drm/surface/atomic.rs b/src/backend/drm/surface/atomic.rs index f381b20..6c17408 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -249,7 +249,7 @@ impl AtomicDrmSurface { )?; self.fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .map_err(|_| Error::TestFailed(self.crtc))?; @@ -287,7 +287,7 @@ impl AtomicDrmSurface { )?; self.fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .map_err(|_| Error::TestFailed(self.crtc))?; @@ -327,7 +327,7 @@ impl AtomicDrmSurface { self.fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .map_err(|_| Error::TestFailed(self.crtc))?; @@ -367,7 +367,7 @@ impl AtomicDrmSurface { if let Err(err) = self .fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .map_err(|_| Error::TestFailed(self.crtc)) @@ -428,7 +428,7 @@ impl AtomicDrmSurface { )?; self.fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .map_err(|_| Error::TestFailed(self.crtc))?; @@ -502,7 +502,7 @@ impl AtomicDrmSurface { if let Err(err) = self .fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req.clone(), ) .map_err(|_| Error::TestFailed(self.crtc)) @@ -530,21 +530,18 @@ impl AtomicDrmSurface { .fd .atomic_commit( if event { - &[ - // on the atomic api we can modeset and trigger a page_flip event on the same call! - AtomicCommitFlags::PageFlipEvent, - AtomicCommitFlags::AllowModeset, - // we also *should* not need to wait for completion, like with `set_crtc`, - // because we have tested this exact commit already, so we do not expect any errors later down the line. - // - // but there is always an exception and `amdgpu` can fail in interesting ways with this flag set... - // https://gitlab.freedesktop.org/drm/amd/-/issues?scope=all&utf8=%E2%9C%93&state=opened&search=drm_atomic_helper_wait_for_flip_done - // - // so we skip this flag: - // AtomicCommitFlags::Nonblock, - ] + // on the atomic api we can modeset and trigger a page_flip event on the same call! + AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::ALLOW_MODESET + // we also *should* not need to wait for completion, like with `set_crtc`, + // because we have tested this exact commit already, so we do not expect any errors later down the line. + // + // but there is always an exception and `amdgpu` can fail in interesting ways with this flag set... + // https://gitlab.freedesktop.org/drm/amd/-/issues?scope=all&utf8=%E2%9C%93&state=opened&search=drm_atomic_helper_wait_for_flip_done + // + // so we skip this flag: + // AtomicCommitFlags::Nonblock, } else { - &[AtomicCommitFlags::AllowModeset] + AtomicCommitFlags::ALLOW_MODESET }, req, ) @@ -588,9 +585,9 @@ impl AtomicDrmSurface { self.fd .atomic_commit( if event { - &[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock] + AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK } else { - &[AtomicCommitFlags::Nonblock] + AtomicCommitFlags::NONBLOCK }, req, ) @@ -638,7 +635,7 @@ impl AtomicDrmSurface { let result = self .fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .is_ok(); @@ -676,7 +673,7 @@ impl AtomicDrmSurface { let result = self .fd .atomic_commit( - &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY, req, ) .is_ok(); @@ -937,7 +934,7 @@ impl AtomicDrmSurface { let result = self .fd - .atomic_commit(&[AtomicCommitFlags::Nonblock], req) + .atomic_commit(AtomicCommitFlags::NONBLOCK, req) .map_err(|source| Error::Access { errmsg: "Failed to commit on clear_plane", dev: self.fd.dev_path(), @@ -1029,7 +1026,7 @@ impl Drop for AtomicDrmSurface { req.add_property(self.crtc, *active_prop, property::Value::Boolean(false)); req.add_property(self.crtc, *mode_prop, property::Value::Unknown(0)); - if let Err(err) = self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { + if let Err(err) = self.fd.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req) { warn!(self.logger, "Unable to disable connectors: {}", err); } } diff --git a/src/backend/drm/surface/legacy.rs b/src/backend/drm/surface/legacy.rs index fcf37ce..abfd2bb 100644 --- a/src/backend/drm/surface/legacy.rs +++ b/src/backend/drm/surface/legacy.rs @@ -289,18 +289,13 @@ impl LegacyDrmSurface { // this will result in wasting a frame, because this flip will need to wait // for `set_crtc`, but is necessary to drive the event loop and thus provide // a more consistent api. - ControlDevice::page_flip( - &*self.fd, - self.crtc, - framebuffer, - &[PageFlipFlags::PageFlipEvent], - None, - ) - .map_err(|source| Error::Access { - errmsg: "Failed to queue page flip", - dev: self.fd.dev_path(), - source, - })?; + ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err( + |source| Error::Access { + errmsg: "Failed to queue page flip", + dev: self.fd.dev_path(), + source, + }, + )?; } Ok(()) @@ -318,9 +313,9 @@ impl LegacyDrmSurface { self.crtc, framebuffer, if event { - &[PageFlipFlags::PageFlipEvent] + PageFlipFlags::EVENT } else { - &[] + PageFlipFlags::empty() }, None, )