Merge branch 'Smithay:master' into master

This commit is contained in:
Victor Timofei 2022-01-29 00:31:44 +02:00 committed by GitHub
commit bf515da51a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1918 additions and 714 deletions

View File

@ -13,6 +13,7 @@
- `PointerButtonEvent::button` now returns an `Option<MouseButton>`. - `PointerButtonEvent::button` now returns an `Option<MouseButton>`.
- `MouseButton` is now non-exhaustive. - `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`. - 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 #### Backends
@ -49,6 +50,7 @@
- Support for `xdg_wm_base` protocol version 3 - Support for `xdg_wm_base` protocol version 3
- Added the option to initialize the dmabuf global with a client filter - 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 - `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 #### Backends

View File

@ -25,9 +25,9 @@ cgmath = "0.18.0"
dbus = { version = "0.9.0", optional = true } dbus = { version = "0.9.0", optional = true }
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
drm-fourcc = "^2.1.1" drm-fourcc = "^2.1.1"
drm = { version = "0.5.0", optional = true } drm = { version = "0.6.1", optional = true }
drm-ffi = { version = "0.2.0", optional = true } drm-ffi = { version = "0.2.1", optional = true }
gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] } 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 } input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true }
indexmap = { version = "1.7", optional = true } indexmap = { version = "1.7", optional = true }
lazy_static = "1" lazy_static = "1"

View File

@ -7,9 +7,11 @@ use image::{ImageBuffer, Rgba};
use slog::Logger; use slog::Logger;
#[cfg(feature = "image")] #[cfg(feature = "image")]
use smithay::backend::renderer::gles2::{Gles2Error, Gles2Renderer, Gles2Texture}; use smithay::backend::renderer::gles2::{Gles2Error, Gles2Renderer, Gles2Texture};
#[cfg(feature = "debug")]
use smithay::utils::Transform;
use smithay::{ use smithay::{
backend::{ backend::{
renderer::{buffer_type, BufferType, Frame, ImportAll, Renderer, Texture, Transform}, renderer::{buffer_type, BufferType, Frame, ImportAll, Renderer, Texture},
SwapBuffersError, SwapBuffersError,
}, },
reexports::wayland_server::protocol::{wl_buffer, wl_surface}, reexports::wayland_server::protocol::{wl_buffer, wl_surface},
@ -111,7 +113,11 @@ where
.map(|dmg| match dmg { .map(|dmg| match dmg {
Damage::Buffer(rect) => *rect, Damage::Buffer(rect) => *rect,
// TODO also apply transformations // 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::<Vec<_>>(); .collect::<Vec<_>>();
@ -161,6 +167,7 @@ where
if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() { if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
let buffer_scale = data.buffer_scale; let buffer_scale = data.buffer_scale;
let buffer_transform = data.buffer_transform;
if let Some(texture) = data if let Some(texture) = data
.texture .texture
.as_mut() .as_mut()
@ -177,7 +184,7 @@ where
location.to_f64().to_physical(output_scale as f64).to_i32_round(), location.to_f64().to_physical(output_scale as f64).to_i32_round(),
buffer_scale, buffer_scale,
output_scale as f64, output_scale as f64,
Transform::Normal, /* TODO */ buffer_transform,
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
1.0, 1.0,
) { ) {

View File

@ -13,13 +13,13 @@ use smithay::{
Display, Display,
}, },
}, },
utils::{Logical, Physical, Point, Rectangle, Size}, utils::{Buffer, Logical, Point, Rectangle, Size, Transform},
wayland::{ wayland::{
compositor::{ compositor::{
compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment, compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment,
SurfaceAttributes, TraversalAction, SurfaceAttributes, TraversalAction,
}, },
seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat},
shell::{ shell::{
legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind}, legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind},
wlr_layer::{LayerShellRequest, LayerSurfaceAttributes}, wlr_layer::{LayerShellRequest, LayerSurfaceAttributes},
@ -39,7 +39,7 @@ use crate::{
}; };
struct MoveSurfaceGrab { struct MoveSurfaceGrab {
start_data: GrabStartData, start_data: PointerGrabStartData,
window_map: Rc<RefCell<WindowMap>>, window_map: Rc<RefCell<WindowMap>>,
toplevel: SurfaceKind, toplevel: SurfaceKind,
initial_window_location: Point<i32, Logical>, initial_window_location: Point<i32, Logical>,
@ -82,7 +82,7 @@ impl PointerGrab for MoveSurfaceGrab {
handle.axis(details) handle.axis(details)
} }
fn start_data(&self) -> &GrabStartData { fn start_data(&self) -> &PointerGrabStartData {
&self.start_data &self.start_data
} }
} }
@ -130,7 +130,7 @@ impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
} }
struct ResizeSurfaceGrab { struct ResizeSurfaceGrab {
start_data: GrabStartData, start_data: PointerGrabStartData,
toplevel: SurfaceKind, toplevel: SurfaceKind,
edges: ResizeEdge, edges: ResizeEdge,
initial_window_size: Size<i32, Logical>, initial_window_size: Size<i32, Logical>,
@ -280,7 +280,7 @@ impl PointerGrab for ResizeSurfaceGrab {
handle.axis(details) handle.axis(details)
} }
fn start_data(&self) -> &GrabStartData { fn start_data(&self) -> &PointerGrabStartData {
&self.start_data &self.start_data
} }
} }
@ -947,8 +947,9 @@ pub struct SurfaceData {
pub texture: Option<Box<dyn std::any::Any + 'static>>, pub texture: Option<Box<dyn std::any::Any + 'static>>,
pub geometry: Option<Rectangle<i32, Logical>>, pub geometry: Option<Rectangle<i32, Logical>>,
pub resize_state: ResizeState, pub resize_state: ResizeState,
pub buffer_dimensions: Option<Size<i32, Physical>>, pub buffer_dimensions: Option<Size<i32, Buffer>>,
pub buffer_scale: i32, pub buffer_scale: i32,
pub buffer_transform: Transform,
} }
impl SurfaceData { impl SurfaceData {
@ -958,6 +959,7 @@ impl SurfaceData {
// new contents // new contents
self.buffer_dimensions = buffer_dimensions(&buffer); self.buffer_dimensions = buffer_dimensions(&buffer);
self.buffer_scale = attrs.buffer_scale; 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 let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) {
old_buffer.release(); old_buffer.release();
} }
@ -976,7 +978,7 @@ impl SurfaceData {
/// Returns the size of the surface. /// Returns the size of the surface.
pub fn size(&self) -> Option<Size<i32, Logical>> { pub fn size(&self) -> Option<Size<i32, Logical>> {
self.buffer_dimensions 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. /// Checks if the surface's input region contains the point.

View File

@ -20,7 +20,7 @@ use smithay::{
libinput::{LibinputInputBackend, LibinputSessionInterface}, libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{ renderer::{
gles2::{Gles2Renderer, Gles2Texture}, gles2::{Gles2Renderer, Gles2Texture},
Bind, Frame, Renderer, Transform, Bind, Frame, Renderer,
}, },
session::{auto::AutoSession, Session, Signal as SessionSignal}, session::{auto::AutoSession, Session, Signal as SessionSignal},
udev::{UdevBackend, UdevEvent}, udev::{UdevBackend, UdevEvent},
@ -50,7 +50,7 @@ use smithay::{
}, },
utils::{ utils::{
signaling::{Linkable, SignalToken, Signaler}, signaling::{Linkable, SignalToken, Signaler},
Logical, Point, Rectangle, Logical, Point, Rectangle, Transform,
}, },
wayland::{ wayland::{
output::{Mode, PhysicalProperties}, output::{Mode, PhysicalProperties},
@ -264,7 +264,7 @@ pub fn run_udev(log: Logger) {
event_loop.handle().remove(udev_event_source); event_loop.handle().remove(udev_event_source);
} }
pub type RenderSurface = GbmBufferedSurface<SessionFd>; pub type RenderSurface = GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>;
struct SurfaceData { struct SurfaceData {
surface: RenderSurface, surface: RenderSurface,
@ -279,7 +279,7 @@ struct BackendData {
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
fps_texture: Gles2Texture, fps_texture: Gles2Texture,
renderer: Rc<RefCell<Gles2Renderer>>, renderer: Rc<RefCell<Gles2Renderer>>,
gbm: GbmDevice<SessionFd>, gbm: Rc<RefCell<GbmDevice<SessionFd>>>,
registration_token: RegistrationToken, registration_token: RegistrationToken,
event_dispatcher: Dispatcher<'static, DrmDevice<SessionFd>, AnvilState<UdevData>>, event_dispatcher: Dispatcher<'static, DrmDevice<SessionFd>, AnvilState<UdevData>>,
dev_id: u64, dev_id: u64,
@ -287,7 +287,7 @@ struct BackendData {
fn scan_connectors( fn scan_connectors(
device: &mut DrmDevice<SessionFd>, device: &mut DrmDevice<SessionFd>,
gbm: &GbmDevice<SessionFd>, gbm: &Rc<RefCell<GbmDevice<SessionFd>>>,
renderer: &mut Gles2Renderer, renderer: &mut Gles2Renderer,
output_map: &mut crate::output_map::OutputMap, output_map: &mut crate::output_map::OutputMap,
signaler: &Signaler<SessionSignal>, signaler: &Signaler<SessionSignal>,
@ -483,6 +483,7 @@ impl AnvilState<UdevData> {
} }
} }
let gbm = Rc::new(RefCell::new(gbm));
let backends = Rc::new(RefCell::new(scan_connectors( let backends = Rc::new(RefCell::new(scan_connectors(
&mut device, &mut device,
&gbm, &gbm,

View File

@ -9,7 +9,7 @@ use smithay::{
}; };
use smithay::{ use smithay::{
backend::{ backend::{
renderer::{Renderer, Transform}, renderer::Renderer,
winit::{self, WinitEvent}, winit::{self, WinitEvent},
SwapBuffersError, SwapBuffersError,
}, },
@ -17,6 +17,7 @@ use smithay::{
calloop::EventLoop, calloop::EventLoop,
wayland_server::{protocol::wl_output, Display}, wayland_server::{protocol::wl_output, Display},
}, },
utils::Transform,
wayland::{ wayland::{
output::{Mode, PhysicalProperties}, output::{Mode, PhysicalProperties},
seat::CursorImageStatus, seat::CursorImageStatus,

View File

@ -11,7 +11,7 @@ use smithay::{backend::renderer::ImportDma, wayland::dmabuf::init_dmabuf_global}
use smithay::{ use smithay::{
backend::{ backend::{
egl::{EGLContext, EGLDisplay}, egl::{EGLContext, EGLDisplay},
renderer::{gles2::Gles2Renderer, Bind, ImportEgl, Renderer, Transform, Unbind}, renderer::{gles2::Gles2Renderer, Bind, ImportEgl, Renderer, Unbind},
x11::{WindowBuilder, X11Backend, X11Event, X11Surface}, x11::{WindowBuilder, X11Backend, X11Event, X11Surface},
SwapBuffersError, SwapBuffersError,
}, },
@ -20,6 +20,7 @@ use smithay::{
gbm, gbm,
wayland_server::{protocol::wl_output, Display}, wayland_server::{protocol::wl_output, Display},
}, },
utils::Transform,
wayland::{ wayland::{
output::{Mode, PhysicalProperties}, output::{Mode, PhysicalProperties},
seat::CursorImageStatus, seat::CursorImageStatus,

View File

@ -154,11 +154,13 @@ where
/// Mark a given buffer as submitted. /// Mark a given buffer as submitted.
/// ///
/// This might effect internal data (e.g. buffer age) and may only be called, /// 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 /// 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). /// (e.g. by not tracking the buffer age).
pub fn submitted(&self, slot: Slot<B>) { pub fn submitted(&self, slot: &Slot<B>) {
// don't mess up the state, if the user submitted and old buffer, after e.g. a resize // 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)) { if !self.slots.iter().any(|other| Arc::ptr_eq(&slot.0, other)) {
return; return;
@ -167,7 +169,7 @@ where
slot.0.age.store(1, Ordering::SeqCst); slot.0.age.store(1, Ordering::SeqCst);
for other_slot in &self.slots { for other_slot in &self.slots {
if !Arc::ptr_eq(other_slot, &slot.0) && other_slot.buffer.is_some() { if !Arc::ptr_eq(other_slot, &slot.0) && other_slot.buffer.is_some() {
assert!(other_slot let res = other_slot
.age .age
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |age| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |age| {
if age > 0 { if age > 0 {
@ -175,8 +177,8 @@ where
} else { } else {
Some(0) Some(0)
} }
}) });
.is_ok()); assert!(res.is_ok());
} }
} }
} }

View File

@ -242,7 +242,7 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
req.add_property(*crtc, *mode_prop, property::Value::Unknown(0)); req.add_property(*crtc, *mode_prop, property::Value::Unknown(0));
} }
self.fd self.fd
.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) .atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
errmsg: "Failed to disable connectors", errmsg: "Failed to disable connectors",
dev: self.fd.dev_path(), dev: self.fd.dev_path(),
@ -282,7 +282,7 @@ impl<A: AsRawFd + 'static> Drop for AtomicDrmDevice<A> {
add_multiple_props(&mut req, &self.old_state.2); add_multiple_props(&mut req, &self.old_state.2);
add_multiple_props(&mut req, &self.old_state.3); 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); error!(self.logger, "Failed to restore previous state. Error: {}", err);
} }
} }

View File

@ -249,7 +249,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
)?; )?;
self.fd self.fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.map_err(|_| Error::TestFailed(self.crtc))?; .map_err(|_| Error::TestFailed(self.crtc))?;
@ -287,7 +287,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
)?; )?;
self.fd self.fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.map_err(|_| Error::TestFailed(self.crtc))?; .map_err(|_| Error::TestFailed(self.crtc))?;
@ -327,7 +327,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
self.fd self.fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.map_err(|_| Error::TestFailed(self.crtc))?; .map_err(|_| Error::TestFailed(self.crtc))?;
@ -367,7 +367,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
if let Err(err) = self if let Err(err) = self
.fd .fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.map_err(|_| Error::TestFailed(self.crtc)) .map_err(|_| Error::TestFailed(self.crtc))
@ -428,7 +428,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
)?; )?;
self.fd self.fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.map_err(|_| Error::TestFailed(self.crtc))?; .map_err(|_| Error::TestFailed(self.crtc))?;
@ -502,7 +502,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
if let Err(err) = self if let Err(err) = self
.fd .fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req.clone(), req.clone(),
) )
.map_err(|_| Error::TestFailed(self.crtc)) .map_err(|_| Error::TestFailed(self.crtc))
@ -530,10 +530,8 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
.fd .fd
.atomic_commit( .atomic_commit(
if event { if event {
&[
// on the atomic api we can modeset and trigger a page_flip event on the same call! // on the atomic api we can modeset and trigger a page_flip event on the same call!
AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::ALLOW_MODESET
AtomicCommitFlags::AllowModeset,
// we also *should* not need to wait for completion, like with `set_crtc`, // 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. // because we have tested this exact commit already, so we do not expect any errors later down the line.
// //
@ -542,9 +540,8 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
// //
// so we skip this flag: // so we skip this flag:
// AtomicCommitFlags::Nonblock, // AtomicCommitFlags::Nonblock,
]
} else { } else {
&[AtomicCommitFlags::AllowModeset] AtomicCommitFlags::ALLOW_MODESET
}, },
req, req,
) )
@ -588,9 +585,9 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
self.fd self.fd
.atomic_commit( .atomic_commit(
if event { if event {
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock] AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK
} else { } else {
&[AtomicCommitFlags::Nonblock] AtomicCommitFlags::NONBLOCK
}, },
req, req,
) )
@ -638,7 +635,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
let result = self let result = self
.fd .fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.is_ok(); .is_ok();
@ -676,7 +673,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
let result = self let result = self
.fd .fd
.atomic_commit( .atomic_commit(
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], AtomicCommitFlags::ALLOW_MODESET | AtomicCommitFlags::TEST_ONLY,
req, req,
) )
.is_ok(); .is_ok();
@ -937,7 +934,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurface<A> {
let result = self let result = self
.fd .fd
.atomic_commit(&[AtomicCommitFlags::Nonblock], req) .atomic_commit(AtomicCommitFlags::NONBLOCK, req)
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
errmsg: "Failed to commit on clear_plane", errmsg: "Failed to commit on clear_plane",
dev: self.fd.dev_path(), dev: self.fd.dev_path(),
@ -1029,7 +1026,7 @@ impl<A: AsRawFd + 'static> Drop for AtomicDrmSurface<A> {
req.add_property(self.crtc, *active_prop, property::Value::Boolean(false)); req.add_property(self.crtc, *active_prop, property::Value::Boolean(false));
req.add_property(self.crtc, *mode_prop, property::Value::Unknown(0)); 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); warn!(self.logger, "Unable to disable connectors: {}", err);
} }
} }

View File

@ -4,12 +4,12 @@ use std::sync::Arc;
use drm::buffer::PlanarBuffer; use drm::buffer::PlanarBuffer;
use drm::control::{connector, crtc, framebuffer, plane, Device, Mode}; use drm::control::{connector, crtc, framebuffer, plane, Device, Mode};
use gbm::{BufferObject, Device as GbmDevice}; use gbm::BufferObject;
use crate::backend::allocator::{ use crate::backend::allocator::{
dmabuf::{AsDmabuf, Dmabuf}, dmabuf::{AsDmabuf, Dmabuf},
gbm::GbmConvertError, 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::drm::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
use crate::backend::SwapBuffersError; 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`]. /// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
#[derive(Debug)] #[derive(Debug)]
pub struct GbmBufferedSurface<D: AsRawFd + 'static> { pub struct GbmBufferedSurface<A: Allocator<BufferObject<()>> + 'static, D: AsRawFd + 'static> {
current_fb: Slot<BufferObject<()>>, current_fb: Slot<BufferObject<()>>,
pending_fb: Option<Slot<BufferObject<()>>>, pending_fb: Option<Slot<BufferObject<()>>>,
queued_fb: Option<Slot<BufferObject<()>>>, queued_fb: Option<Slot<BufferObject<()>>>,
next_fb: Option<Slot<BufferObject<()>>>, next_fb: Option<Slot<BufferObject<()>>>,
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>>, swapchain: Swapchain<A, BufferObject<()>>,
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
} }
impl<D> GbmBufferedSurface<D> impl<A, D> GbmBufferedSurface<A, D>
where where
A: Allocator<BufferObject<()>>,
A::Error: std::error::Error + Send + Sync,
D: AsRawFd + 'static, D: AsRawFd + 'static,
{ {
/// Create a new `GbmBufferedSurface` from a given compatible combination /// Create a new `GbmBufferedSurface` from a given compatible combination
@ -40,10 +42,10 @@ where
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn new<L>( pub fn new<L>(
drm: DrmSurface<D>, drm: DrmSurface<D>,
allocator: GbmDevice<D>, allocator: A,
mut renderer_formats: HashSet<Format>, mut renderer_formats: HashSet<Format>,
log: L, log: L,
) -> Result<GbmBufferedSurface<D>, Error> ) -> Result<GbmBufferedSurface<A, D>, Error<A::Error>>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -121,7 +123,7 @@ where
let mode = drm.pending_mode(); let mode = drm.pending_mode();
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>> = Swapchain::new( let mut swapchain: Swapchain<A, BufferObject<()>> = Swapchain::new(
allocator, allocator,
mode.size().0 as u32, mode.size().0 as u32,
mode.size().1 as u32, mode.size().1 as u32,
@ -130,7 +132,7 @@ where
); );
// Test format // Test format
let buffer = swapchain.acquire()?.unwrap(); let buffer = swapchain.acquire().map_err(Error::GbmError)?.unwrap();
let format = Format { let format = Format {
code, code,
modifier: buffer.modifier().unwrap(), // no guarantee modifier: buffer.modifier().unwrap(), // no guarantee
@ -171,9 +173,13 @@ where
/// ///
/// *Note*: This function can be called multiple times and /// *Note*: This function can be called multiple times and
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]). /// 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<A::Error>> {
if self.next_fb.is_none() { 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::<Dmabuf>().cloned(); let maybe_buffer = slot.userdata().get::<Dmabuf>().cloned();
if maybe_buffer.is_none() { if maybe_buffer.is_none() {
@ -197,7 +203,7 @@ where
/// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`] /// *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. /// when a vblank event is received, that denotes successful scanout of the buffer.
/// Otherwise the underlying swapchain will eventually run out of buffers. /// 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<A::Error>> {
self.queued_fb = self.next_fb.take(); self.queued_fb = self.next_fb.take();
if self.pending_fb.is_none() && self.queued_fb.is_some() { if self.pending_fb.is_none() && self.queued_fb.is_some() {
self.submit()?; self.submit()?;
@ -210,10 +216,9 @@ where
/// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::super::DrmDevice) /// *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. /// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
/// Otherwise the underlying swapchain will run out of buffers eventually. /// 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<A::Error>> {
if let Some(mut pending) = self.pending_fb.take() { if let Some(mut pending) = self.pending_fb.take() {
std::mem::swap(&mut pending, &mut self.current_fb); std::mem::swap(&mut pending, &mut self.current_fb);
self.swapchain.submitted(pending);
if self.queued_fb.is_some() { if self.queued_fb.is_some() {
self.submit()?; self.submit()?;
} }
@ -222,7 +227,7 @@ where
Ok(()) Ok(())
} }
fn submit(&mut self) -> Result<(), Error> { fn submit(&mut self) -> Result<(), Error<A::Error>> {
// yes it does not look like it, but both of these lines should be safe in all cases. // 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 slot = self.queued_fb.take().unwrap();
let fb = slot.userdata().get::<FbHandle<D>>().unwrap().fb; let fb = slot.userdata().get::<FbHandle<D>>().unwrap().fb;
@ -233,6 +238,7 @@ where
self.drm.page_flip([(fb, self.drm.plane())].iter(), true) self.drm.page_flip([(fb, self.drm.plane())].iter(), true)
}; };
if flip.is_ok() { if flip.is_ok() {
self.swapchain.submitted(&slot);
self.pending_fb = Some(slot); self.pending_fb = Some(slot);
} }
flip.map_err(Error::DrmError) flip.map_err(Error::DrmError)
@ -276,13 +282,13 @@ where
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending /// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode). /// [`Mode`](drm::control::Mode).
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<A::Error>> {
self.drm.add_connector(connector).map_err(Error::DrmError) self.drm.add_connector(connector).map_err(Error::DrmError)
} }
/// Tries to mark a [`connector`](drm::control::connector) /// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit. /// 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<A::Error>> {
self.drm.remove_connector(connector).map_err(Error::DrmError) 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) /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending /// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode). /// [`Mode`](drm::control::Mode).
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<A::Error>> {
self.drm.set_connectors(connectors).map_err(Error::DrmError) self.drm.set_connectors(connectors).map_err(Error::DrmError)
} }
@ -314,7 +320,7 @@ where
/// Fails if the mode is not compatible with the underlying /// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the /// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s. /// 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<A::Error>> {
self.drm.use_mode(mode).map_err(Error::DrmError)?; self.drm.use_mode(mode).map_err(Error::DrmError)?;
let (w, h) = mode.size(); let (w, h) = mode.size();
self.swapchain.resize(w as _, h as _); self.swapchain.resize(w as _, h as _);
@ -334,9 +340,10 @@ impl<A: AsRawFd + 'static> Drop for FbHandle<A> {
} }
} }
fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error> fn attach_framebuffer<E, D>(drm: &Arc<DrmSurface<D>>, bo: &BufferObject<()>) -> Result<FbHandle<D>, Error<E>>
where where
A: AsRawFd + 'static, E: std::error::Error + Send + Sync,
D: AsRawFd + 'static,
{ {
let modifier = match bo.modifier().unwrap() { let modifier = match bo.modifier().unwrap() {
Modifier::Invalid => None, Modifier::Invalid => None,
@ -385,7 +392,7 @@ where
/// Errors thrown by a [`GbmBufferedSurface`] /// Errors thrown by a [`GbmBufferedSurface`]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error<E: std::error::Error + Send + Sync + 'static> {
/// No supported pixel format for the given plane could be determined /// No supported pixel format for the given plane could be determined
#[error("No supported plane buffer format found")] #[error("No supported plane buffer format found")]
NoSupportedPlaneFormat, NoSupportedPlaneFormat,
@ -406,14 +413,14 @@ pub enum Error {
DrmError(#[from] DrmError), DrmError(#[from] DrmError),
/// Error importing the rendered buffer to libgbm for scan-out /// Error importing the rendered buffer to libgbm for scan-out
#[error("The underlying gbm device encounted an error: {0}")] #[error("The underlying gbm device encounted an error: {0}")]
GbmError(#[from] std::io::Error), GbmError(#[source] E),
/// Error exporting as Dmabuf /// Error exporting as Dmabuf
#[error("The allocated buffer could not be exported as a dmabuf: {0}")] #[error("The allocated buffer could not be exported as a dmabuf: {0}")]
AsDmabufError(#[from] GbmConvertError), AsDmabufError(#[from] GbmConvertError),
} }
impl From<Error> for SwapBuffersError { impl<E: std::error::Error + Send + Sync + 'static> From<Error<E>> for SwapBuffersError {
fn from(err: Error) -> SwapBuffersError { fn from(err: Error<E>) -> SwapBuffersError {
match err { match err {
x @ Error::NoSupportedPlaneFormat x @ Error::NoSupportedPlaneFormat
| x @ Error::NoSupportedRendererFormat | x @ Error::NoSupportedRendererFormat

View File

@ -289,18 +289,13 @@ impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
// this will result in wasting a frame, because this flip will need to wait // 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 // for `set_crtc`, but is necessary to drive the event loop and thus provide
// a more consistent api. // a more consistent api.
ControlDevice::page_flip( ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err(
&*self.fd, |source| Error::Access {
self.crtc,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to queue page flip", errmsg: "Failed to queue page flip",
dev: self.fd.dev_path(), dev: self.fd.dev_path(),
source, source,
})?; },
)?;
} }
Ok(()) Ok(())
@ -318,9 +313,9 @@ impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
self.crtc, self.crtc,
framebuffer, framebuffer,
if event { if event {
&[PageFlipFlags::PageFlipEvent] PageFlipFlags::EVENT
} else { } else {
&[] PageFlipFlags::empty()
}, },
None, None,
) )

View File

@ -839,7 +839,7 @@ impl EGLBufferReader {
pub fn egl_buffer_dimensions( pub fn egl_buffer_dimensions(
&self, &self,
buffer: &WlBuffer, buffer: &WlBuffer,
) -> Option<crate::utils::Size<i32, crate::utils::Physical>> { ) -> Option<crate::utils::Size<i32, crate::utils::Buffer>> {
if !buffer.as_ref().is_alive() { if !buffer.as_ref().is_alive() {
debug!(self.logger, "Suplied buffer is no longer alive"); debug!(self.logger, "Suplied buffer is no longer alive");
return None; return None;

View File

@ -81,18 +81,28 @@ impl EGLSurface {
} }
/// Returns the buffer age of the underlying back buffer /// Returns the buffer age of the underlying back buffer
pub fn buffer_age(&self) -> i32 { pub fn buffer_age(&self) -> Option<i32> {
let surface = self.surface.load(Ordering::SeqCst); let surface = self.surface.load(Ordering::SeqCst);
let mut age = 0; let mut age = 0;
unsafe { let ret = unsafe {
ffi::egl::QuerySurface( ffi::egl::QuerySurface(
**self.display, **self.display,
surface as *const _, surface as *const _,
ffi::egl::BUFFER_AGE_EXT as i32, ffi::egl::BUFFER_AGE_EXT as i32,
&mut age as *mut _, &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. /// Swaps buffers at the end of a frame.

View File

@ -13,7 +13,7 @@ use cgmath::{prelude::*, Matrix3, Vector2, Vector3};
mod shaders; mod shaders;
mod version; mod version;
use super::{Bind, Frame, Renderer, Texture, TextureFilter, Transform, Unbind}; use super::{Bind, Frame, Renderer, Texture, TextureFilter, Unbind};
use crate::backend::allocator::{ use crate::backend::allocator::{
dmabuf::{Dmabuf, WeakDmabuf}, dmabuf::{Dmabuf, WeakDmabuf},
Format, Format,
@ -23,7 +23,7 @@ use crate::backend::egl::{
EGLContext, EGLSurface, MakeCurrentError, EGLContext, EGLSurface, MakeCurrentError,
}; };
use crate::backend::SwapBuffersError; 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"))] #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use super::ImportEgl; use super::ImportEgl;
@ -874,6 +874,7 @@ impl Bind<Rc<EGLSurface>> for Gles2Renderer {
fn bind(&mut self, surface: Rc<EGLSurface>) -> Result<(), Gles2Error> { fn bind(&mut self, surface: Rc<EGLSurface>) -> Result<(), Gles2Error> {
self.unbind()?; self.unbind()?;
self.target_surface = Some(surface); self.target_surface = Some(surface);
self.make_current()?;
Ok(()) Ok(())
} }
} }
@ -881,9 +882,7 @@ impl Bind<Rc<EGLSurface>> for Gles2Renderer {
impl Bind<Dmabuf> for Gles2Renderer { impl Bind<Dmabuf> for Gles2Renderer {
fn bind(&mut self, dmabuf: Dmabuf) -> Result<(), Gles2Error> { fn bind(&mut self, dmabuf: Dmabuf) -> Result<(), Gles2Error> {
self.unbind()?; self.unbind()?;
unsafe { self.make_current()?;
self.egl.make_current()?;
}
// Free outdated buffer resources // Free outdated buffer resources
// TODO: Replace with `drain_filter` once it lands // TODO: Replace with `drain_filter` once it lands
@ -1250,7 +1249,7 @@ impl Frame for Gles2Frame {
texture: &Self::TextureId, texture: &Self::TextureId,
src: Rectangle<i32, Buffer>, src: Rectangle<i32, Buffer>,
dest: Rectangle<f64, Physical>, dest: Rectangle<f64, Physical>,
damage: &[Rectangle<i32, Physical>], damage: &[Rectangle<i32, Buffer>],
transform: Transform, transform: Transform,
alpha: f32, alpha: f32,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
@ -1266,7 +1265,7 @@ impl Frame for Gles2Frame {
assert_eq!(mat, mat * transform.invert().matrix()); assert_eq!(mat, mat * transform.invert().matrix());
assert_eq!(transform.matrix(), Matrix3::<f32>::identity()); assert_eq!(transform.matrix(), Matrix3::<f32>::identity());
} }
mat = mat * transform.invert().matrix(); mat = mat * transform.matrix();
mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5));
// this matrix should be regular, we can expect invert to succeed // this matrix should be regular, we can expect invert to succeed
@ -1290,18 +1289,22 @@ impl Frame for Gles2Frame {
let damage = damage let damage = damage
.iter() .iter()
.map(|rect| { .map(|rect| {
let src = src.size.to_f64();
let rect = rect.to_f64(); let rect = rect.to_f64();
let rect_constrained_loc = rect let rect_constrained_loc = rect
.loc .loc
.constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point())); .constrain(Rectangle::from_extemities((0f64, 0f64), src.to_point()));
let rect_clamped_size = rect.size.clamp((0f64, 0f64), dest.size); 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);
[ [
(rect_constrained_loc.x / dest.size.w) as f32, (rect.loc.x / src.w) as f32,
(rect_constrained_loc.y / dest.size.h) as f32, (rect.loc.y / src.h) as f32,
(rect_clamped_size.w / dest.size.w) as f32, (rect.size.w / src.w) as f32,
(rect_clamped_size.h / dest.size.h) as f32, (rect.size.h / src.h) as f32,
] ]
}) })
.flatten() .flatten()

View File

@ -10,7 +10,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::error::Error; 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")] #[cfg(feature = "wayland_frontend")]
use crate::wayland::compositor::SurfaceData; use crate::wayland::compositor::SurfaceData;
@ -35,27 +35,6 @@ use crate::backend::egl::{
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
pub mod utils; 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)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// Texture filtering methods /// Texture filtering methods
pub enum TextureFilter { 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::_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::_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::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::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<N: Coordinate, Kind>(&self, size: Size<N, Kind>) -> Size<N, Kind> {
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<N: Coordinate, Kind>(
&self,
rect: Rectangle<N, Kind>,
area: &Size<N, Kind>,
) -> Rectangle<N, Kind> {
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")] #[cfg(feature = "wayland_frontend")]
@ -202,6 +122,9 @@ pub trait Frame {
/// Clear the complete current target with a single given color. /// 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. /// 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. /// 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<i32, Physical>]) -> Result<(), Self::Error>; fn clear(&mut self, color: [f32; 4], at: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error>;
@ -217,7 +140,7 @@ pub trait Frame {
texture_scale: i32, texture_scale: i32,
output_scale: f64, output_scale: f64,
src_transform: Transform, src_transform: Transform,
damage: &[Rectangle<i32, Physical>], damage: &[Rectangle<i32, Buffer>],
alpha: f32, alpha: f32,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.render_texture_from_to( self.render_texture_from_to(
@ -227,7 +150,7 @@ pub trait Frame {
pos, pos,
texture texture
.size() .size()
.to_logical(texture_scale) .to_logical(texture_scale, src_transform)
.to_f64() .to_f64()
.to_physical(output_scale), .to_physical(output_scale),
), ),
@ -245,7 +168,7 @@ pub trait Frame {
texture: &Self::TextureId, texture: &Self::TextureId,
src: Rectangle<i32, Buffer>, src: Rectangle<i32, Buffer>,
dst: Rectangle<f64, Physical>, dst: Rectangle<f64, Physical>,
damage: &[Rectangle<i32, Physical>], damage: &[Rectangle<i32, Buffer>],
src_transform: Transform, src_transform: Transform,
alpha: f32, alpha: f32,
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;
@ -540,7 +463,7 @@ pub fn buffer_type(buffer: &wl_buffer::WlBuffer) -> Option<BufferType> {
/// ///
/// *Note*: This will only return dimensions for buffer types known to smithay (see [`buffer_type`]) /// *Note*: This will only return dimensions for buffer types known to smithay (see [`buffer_type`])
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<Size<i32, Physical>> { pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<Size<i32, Buffer>> {
use crate::backend::allocator::Buffer; use crate::backend::allocator::Buffer;
if let Some(buf) = buffer.as_ref().user_data().get::<Dmabuf>() { if let Some(buf) = buffer.as_ref().user_data().get::<Dmabuf>() {
@ -560,102 +483,3 @@ pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<Size<i32, Physi
crate::wayland::shm::with_buffer_contents(buffer, |_, data| (data.width, data.height).into()).ok() crate::wayland::shm::with_buffer_contents(buffer, |_, data| (data.width, data.height).into()).ok()
} }
#[cfg(test)]
mod tests {
use super::Transform;
use crate::utils::{Logical, Rectangle, Size};
#[test]
fn transform_rect_ident() {
let rect = Rectangle::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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)
)
}
}

View File

@ -2,7 +2,7 @@
use crate::{ use crate::{
backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture},
utils::{Logical, Physical, Point, Rectangle, Size}, utils::{Buffer, Logical, Point, Rectangle, Size, Transform},
wayland::compositor::{ wayland::compositor::{
is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState,
SurfaceAttributes, TraversalAction, SurfaceAttributes, TraversalAction,
@ -15,8 +15,9 @@ use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface};
#[derive(Default)] #[derive(Default)]
pub(crate) struct SurfaceState { pub(crate) struct SurfaceState {
pub(crate) buffer_dimensions: Option<Size<i32, Physical>>, pub(crate) buffer_dimensions: Option<Size<i32, Buffer>>,
pub(crate) buffer_scale: i32, pub(crate) buffer_scale: i32,
pub(crate) buffer_transform: Transform,
pub(crate) buffer: Option<WlBuffer>, pub(crate) buffer: Option<WlBuffer>,
pub(crate) texture: Option<Box<dyn std::any::Any + 'static>>, pub(crate) texture: Option<Box<dyn std::any::Any + 'static>>,
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
@ -30,6 +31,7 @@ impl SurfaceState {
// new contents // new contents
self.buffer_dimensions = buffer_dimensions(&buffer); self.buffer_dimensions = buffer_dimensions(&buffer);
self.buffer_scale = attrs.buffer_scale; 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 let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) {
if &old_buffer != self.buffer.as_ref().unwrap() { if &old_buffer != self.buffer.as_ref().unwrap() {
old_buffer.release(); old_buffer.release();
@ -52,6 +54,13 @@ impl SurfaceState {
None => {} None => {}
} }
} }
/// Returns the size of the surface.
pub fn surface_size(&self) -> Option<Size<i32, Logical>> {
self.buffer_dimensions
.as_ref()
.map(|dim| dim.to_logical(self.buffer_scale, self.buffer_transform))
}
} }
/// Handler to let smithay take over buffer management. /// Handler to let smithay take over buffer management.
@ -110,10 +119,6 @@ where
T: Texture + 'static, T: Texture + 'static,
{ {
let mut result = Ok(()); let mut result = Ok(());
let damage = damage
.iter()
.map(|geo| geo.to_f64().to_physical(scale).to_i32_up())
.collect::<Vec<_>>();
with_surface_tree_upward( with_surface_tree_upward(
surface, surface,
location, location,
@ -125,17 +130,20 @@ where
// Import a new buffer if necessary // Import a new buffer if necessary
if data.texture.is_none() { if data.texture.is_none() {
if let Some(buffer) = data.buffer.as_ref() { if let Some(buffer) = data.buffer.as_ref() {
let damage = attributes let buffer_damage = attributes
.damage .damage
.iter() .iter()
.map(|dmg| match dmg { .map(|dmg| match dmg {
Damage::Buffer(rect) => *rect, Damage::Buffer(rect) => *rect,
// TODO also apply transformations Damage::Surface(rect) => rect.to_buffer(
Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), attributes.buffer_scale,
attributes.buffer_transform.into(),
&data.surface_size().unwrap(),
),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match renderer.import_buffer(buffer, Some(states), &damage) { match renderer.import_buffer(buffer, Some(states), &buffer_damage) {
Some(Ok(m)) => { Some(Ok(m)) => {
data.texture = Some(Box::new(m)); data.texture = Some(Box::new(m));
} }
@ -169,10 +177,12 @@ where
let mut location = *location; let mut location = *location;
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() { if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
let dimensions = data.surface_size();
let buffer_scale = data.buffer_scale; let buffer_scale = data.buffer_scale;
let buffer_dimensions = data.buffer_dimensions; let buffer_transform = data.buffer_transform;
let attributes = states.cached_state.current::<SurfaceAttributes>(); let attributes = states.cached_state.current::<SurfaceAttributes>();
if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::<T>()) { if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::<T>()) {
let dimensions = dimensions.unwrap();
// we need to re-extract the subsurface offset, as the previous closure // we need to re-extract the subsurface offset, as the previous closure
// only passes it to our children // only passes it to our children
let mut surface_offset = (0, 0).into(); let mut surface_offset = (0, 0).into();
@ -182,23 +192,19 @@ where
location += current.location; location += current.location;
} }
let rect = Rectangle::<i32, Physical>::from_loc_and_size( let damage = damage
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
.iter() .iter()
.cloned() .cloned()
.flat_map(|geo| geo.intersection(rect)) // first move the damage by the surface offset in logical space
.map(|mut geo| { .map(|mut geo| {
geo.loc -= rect.loc; // make the damage relative to the surfaec
geo.loc -= surface_offset;
geo 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::<Vec<_>>(); .collect::<Vec<_>>();
// TODO: Take wp_viewporter into account // TODO: Take wp_viewporter into account
@ -208,7 +214,7 @@ where
buffer_scale, buffer_scale,
scale, scale,
attributes.buffer_transform.into(), attributes.buffer_transform.into(),
&new_damage, &damage,
1.0, 1.0,
) { ) {
result = Err(err); result = Err(err);

View File

@ -302,12 +302,19 @@ impl WinitGraphicsBackend {
Ok(()) Ok(())
} }
/// Retrieve the buffer age of the current backbuffer of the window /// Retrieve the buffer age of the current backbuffer of the window.
pub fn buffer_age(&self) -> usize { ///
/// This will only return a meaningful value, if this `WinitGraphicsBackend`
/// is currently bound (by previously calling [`WinitGraphicsBackend::bind`]).
///
/// 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<usize> {
if self.damage_tracking { if self.damage_tracking {
self.egl.buffer_age() as usize self.egl.buffer_age().map(|x| x as usize)
} else { } else {
0 Some(0)
} }
} }

View File

@ -123,7 +123,7 @@ impl X11Surface {
// Now present the current buffer // Now present the current buffer
let _ = pixmap.present(&*connection, window.as_ref())?; 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. // 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(); let _ = connection.flush();

View File

@ -3,7 +3,7 @@ use crate::{
desktop::{utils::*, PopupManager, Space}, desktop::{utils::*, PopupManager, Space},
utils::{user_data::UserDataMap, Logical, Point, Rectangle}, utils::{user_data::UserDataMap, Logical, Point, Rectangle},
wayland::{ wayland::{
compositor::with_states, compositor::{with_states, with_surface_tree_downward, TraversalAction},
output::{Inner as OutputInner, Output}, output::{Inner as OutputInner, Output},
shell::wlr_layer::{ shell::wlr_layer::{
Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface, Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
@ -21,6 +21,8 @@ use std::{
sync::{Arc, Mutex, Weak}, sync::{Arc, Mutex, Weak},
}; };
use super::WindowSurfaceType;
crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS); crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS);
/// Map of [`LayerSurface`]s on an [`Output`] /// Map of [`LayerSurface`]s on an [`Output`]
@ -29,6 +31,9 @@ pub struct LayerMap {
layers: IndexSet<LayerSurface>, layers: IndexSet<LayerSurface>,
output: Weak<(Mutex<OutputInner>, wayland_server::UserDataMap)>, output: Weak<(Mutex<OutputInner>, wayland_server::UserDataMap)>,
zone: Rectangle<i32, Logical>, zone: Rectangle<i32, Logical>,
// surfaces for tracking enter and leave events
surfaces: Vec<WlSurface>,
logger: ::slog::Logger,
} }
/// Retrieve a [`LayerMap`] for a given [`Output`]. /// Retrieve a [`LayerMap`] for a given [`Output`].
@ -53,6 +58,10 @@ pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> {
.map(|mode| mode.size.to_logical(o.current_scale())) .map(|mode| mode.size.to_logical(o.current_scale()))
.unwrap_or_else(|| (0, 0).into()), .unwrap_or_else(|| (0, 0).into()),
), ),
surfaces: Vec::new(),
logger: (*o.inner.0.lock().unwrap())
.log
.new(slog::o!("smithay_module" => "layer_map")),
}) })
}); });
userdata.get::<RefCell<LayerMap>>().unwrap().borrow_mut() userdata.get::<RefCell<LayerMap>>().unwrap().borrow_mut()
@ -90,6 +99,34 @@ impl LayerMap {
let _ = layer.user_data().get::<LayerUserdata>().take(); let _ = layer.user_data().get::<LayerUserdata>().take();
self.arrange(); self.arrange();
} }
if let (Some(output), Some(surface)) = (self.output(), layer.get_surface()) {
with_surface_tree_downward(
surface,
(),
|_, _, _| TraversalAction::DoChildren(()),
|wl_surface, _, _| {
output_leave(&output, &mut self.surfaces, wl_surface, &self.logger);
},
|_, _, _| 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,
)
}
}
}
} }
/// Return the area of this output, that is not exclusive to any [`LayerSurface`]s. /// Return the area of this output, that is not exclusive to any [`LayerSurface`]s.
@ -160,11 +197,7 @@ impl LayerMap {
.unwrap_or_else(|| (0, 0).into()), .unwrap_or_else(|| (0, 0).into()),
); );
let mut zone = output_rect; let mut zone = output_rect;
slog::debug!( slog::trace!(self.logger, "Arranging layers into {:?}", output_rect.size);
crate::slog_or_fallback(None),
"Arranging layers into {:?}",
output_rect.size
);
for layer in self.layers.iter() { for layer in self.layers.iter() {
let surface = if let Some(surface) = layer.get_surface() { let surface = if let Some(surface) = layer.get_surface() {
@ -173,6 +206,35 @@ impl LayerMap {
continue; continue;
}; };
let logger_ref = &self.logger;
let surfaces_ref = &mut self.surfaces;
with_surface_tree_downward(
surface,
(),
|_, _, _| TraversalAction::DoChildren(()),
|wl_surface, _, _| {
output_enter(&output, surfaces_ref, wl_surface, logger_ref);
},
|_, _, _| 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| { let data = with_states(surface, |states| {
*states.cached_state.current::<LayerSurfaceCachedState>() *states.cached_state.current::<LayerSurfaceCachedState>()
}) })
@ -233,8 +295,8 @@ impl LayerMap {
} }
} }
slog::debug!( slog::trace!(
crate::slog_or_fallback(None), self.logger,
"Setting layer to pos {:?} and size {:?}", "Setting layer to pos {:?} and size {:?}",
location, location,
size size
@ -253,7 +315,7 @@ impl LayerMap {
layer_state(layer).location = location; 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; self.zone = zone;
} }
} }
@ -267,7 +329,14 @@ impl LayerMap {
/// This function needs to be called periodically (though not necessarily frequently) /// This function needs to be called periodically (though not necessarily frequently)
/// to be able cleanup internally used resources. /// to be able cleanup internally used resources.
pub fn cleanup(&mut self) { 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());
}
/// 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()
} }
} }
@ -428,6 +497,7 @@ impl LayerSurface {
pub fn surface_under<P: Into<Point<f64, Logical>>>( pub fn surface_under<P: Into<Point<f64, Logical>>>(
&self, &self,
point: P, point: P,
surface_type: WindowSurfaceType,
) -> Option<(WlSurface, Point<i32, Logical>)> { ) -> Option<(WlSurface, Point<i32, Logical>)> {
let point = point.into(); let point = point.into();
if let Some(surface) = self.get_surface() { if let Some(surface) = self.get_surface() {
@ -438,13 +508,13 @@ impl LayerSurface {
{ {
if let Some(result) = popup if let Some(result) = popup
.get_surface() .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); return Some(result);
} }
} }
under_from_surface_tree(surface, point, (0, 0)) under_from_surface_tree(surface, point, (0, 0), surface_type)
} else { } else {
None None
} }
@ -485,7 +555,16 @@ impl LayerSurface {
/// window that requested it /// window that requested it
pub fn send_frame(&self, time: u32) { pub fn send_frame(&self, time: u32) {
if let Some(wl_surface) = self.0.surface.get_surface() { 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);
}
}
} }
} }

501
src/desktop/popup/grab.rs Normal file
View File

@ -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<Serial>,
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<Mutex<PopupGrabInternal>>,
}
impl PopupGrabInner {
pub(super) fn alive(&self) -> bool {
let guard = self.internal.lock().unwrap();
guard.alive()
}
fn current_grab(&self) -> Option<WlSurface> {
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<Option<Serial>, 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<WlSurface> {
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::<Vec<_>>();
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<Serial>,
toplevel_grab: PopupGrabInner,
keyboard_handle: Option<KeyboardHandle>,
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<Serial>,
keyboard_handle: Option<KeyboardHandle>,
) -> 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<Serial> {
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<WlSurface> {
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<WlSurface> {
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<f64, Logical>,
focus: Option<(WlSurface, Point<i32, Logical>)>,
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()
}
}

View File

@ -1,18 +1,23 @@
use crate::{ use crate::{
utils::{DeadResource, Logical, Point, Rectangle}, utils::{DeadResource, Logical, Point},
wayland::{ wayland::{
compositor::{get_role, with_states}, 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 std::sync::{Arc, Mutex};
use wayland_server::protocol::wl_surface::WlSurface; use wayland_server::protocol::wl_surface::WlSurface;
use super::{PopupGrab, PopupGrabError, PopupGrabInner, PopupKind};
/// Helper to track popups. /// Helper to track popups.
#[derive(Debug)] #[derive(Debug)]
pub struct PopupManager { pub struct PopupManager {
unmapped_popups: Vec<PopupKind>, unmapped_popups: Vec<PopupKind>,
popup_trees: Vec<PopupTree>, popup_trees: Vec<PopupTree>,
popup_grabs: Vec<PopupGrabInner>,
logger: ::slog::Logger, logger: ::slog::Logger,
} }
@ -22,6 +27,7 @@ impl PopupManager {
PopupManager { PopupManager {
unmapped_popups: Vec::new(), unmapped_popups: Vec::new(),
popup_trees: Vec::new(), popup_trees: Vec::new(),
popup_grabs: Vec::new(),
logger: crate::slog_or_fallback(logger), logger: crate::slog_or_fallback(logger),
} }
} }
@ -54,24 +60,89 @@ impl PopupManager {
} }
} }
fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { /// Take an explicit grab for the provided [`PopupKind`]
let mut parent = popup.parent().unwrap(); ///
while get_role(&parent) == Some(XDG_POPUP_ROLE) { /// Returns a [`PopupGrab`] on success or an [`PopupGrabError`]
parent = with_states(&parent, |states| { /// if the grab has been denied.
pub fn grab_popup(
&mut self,
popup: PopupKind,
seat: &Seat,
serial: Serial,
) -> Result<PopupGrab, PopupGrabError> {
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 states
.data_map .data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>() .get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap() .unwrap()
.lock() .lock()
.unwrap() .unwrap()
.parent .committed
.as_ref()
.cloned()
.unwrap()
})?; })?;
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::<PopupGrabInner>().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(); let tree = PopupTree::default();
if states.data_map.insert_if_missing(|| tree.clone()) { if states.data_map.insert_if_missing(|| tree.clone()) {
self.popup_trees.push(tree); 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 // if it previously had no popups, we likely removed it from our list already
self.popup_trees.push(tree.clone()); 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); 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::<PopupTree>();
if let Some(tree) = tree {
tree.dismiss_popup(popup);
}
})
}
/// Needs to be called periodically (but not necessarily frequently) /// Needs to be called periodically (but not necessarily frequently)
/// to cleanup internal resources. /// to cleanup internal resources.
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// retain_mut is sadly still unstable // 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.iter_mut().for_each(|tree| tree.cleanup());
self.popup_trees.retain(|tree| tree.alive()); self.popup_trees.retain(|tree| tree.alive());
self.unmapped_popups.retain(|surf| surf.alive()); self.unmapped_popups.retain(|surf| surf.alive());
} }
} }
fn find_popup_root_surface(popup: &PopupKind) -> Result<WlSurface, DeadResource> {
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::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.parent
.as_ref()
.cloned()
.unwrap()
})?;
}
Ok(parent)
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
struct PopupTree(Arc<Mutex<Vec<PopupNode>>>); struct PopupTree(Arc<Mutex<Vec<PopupNode>>>);
@ -157,6 +259,22 @@ impl PopupTree {
children.push(PopupNode::new(popup)); 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) { fn cleanup(&mut self) {
let mut children = self.0.lock().unwrap(); let mut children = self.0.lock().unwrap();
for child in children.iter_mut() { 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) { fn cleanup(&mut self) {
for child in &mut self.children { for child in &mut self.children {
child.cleanup(); 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()); 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<WlSurface> {
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<i32, Logical> {
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::<SurfaceCachedState>()
.geometry
.unwrap_or_default()
})
.unwrap()
}
fn location(&self) -> Point<i32, Logical> {
let wl_surface = match self.get_surface() {
Some(s) => s,
None => return (0, 0).into(),
};
with_states(wl_surface, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.current
.geometry
})
.unwrap_or_default()
.loc
}
}
impl From<PopupSurface> for PopupKind {
fn from(p: PopupSurface) -> PopupKind {
PopupKind::Xdg(p)
}
}

96
src/desktop/popup/mod.rs Normal file
View File

@ -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<WlSurface> {
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<i32, Logical> {
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::<SurfaceCachedState>()
.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<i32, Logical> {
let wl_surface = match self.get_surface() {
Some(s) => s,
None => return (0, 0).into(),
};
with_states(wl_surface, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.current
.geometry
})
.unwrap_or_default()
.loc
}
}
impl From<PopupSurface> for PopupKind {
fn from(p: PopupSurface) -> PopupKind {
PopupKind::Xdg(p)
}
}

View File

@ -10,6 +10,33 @@ use std::{
}; };
use wayland_server::protocol::wl_surface::WlSurface; use wayland_server::protocol::wl_surface::WlSurface;
/// Indicates default values for some zindexs inside smithay
#[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,
/// Default zindex for Windows
Shell = 30,
/// WlrLayer::Top default zindex
Top = 40,
/// Default zindex for Windows PopUps
Popups = 50,
/// Default Layer for RenderElements
Overlay = 60,
/// Default Layer for Overlay PopUp
PopupsOverlay = 70,
}
/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups.
pub type DynamicRenderElements<R> =
Box<dyn RenderElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>>;
pub(super) type SpaceElem<R> =
dyn SpaceElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>;
/// Trait for custom elements to be rendered during [`Space::render_output`]. /// Trait for custom elements to be rendered during [`Space::render_output`].
pub trait RenderElement<R, F, E, T> pub trait RenderElement<R, F, E, T>
where where
@ -28,6 +55,7 @@ where
/// Returns the bounding box of this element including its position in the space. /// Returns the bounding box of this element including its position in the space.
fn geometry(&self) -> Rectangle<i32, Logical>; fn geometry(&self) -> Rectangle<i32, Logical>;
/// Returns the damage of the element since it's last update. /// 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 /// If you receive `Some(_)` for `for_values` you may cache that you
/// send the damage for this `Space` and `Output` combination once /// send the damage for this `Space` and `Output` combination once
@ -43,6 +71,8 @@ where
/// Draws the element using the provided `Frame` and `Renderer`. /// Draws the element using the provided `Frame` and `Renderer`.
/// ///
/// - `scale` provides the current fractional scale value to render as /// - `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 /// - `damage` provides the regions you need to re-draw and *may* not
/// be equivalent to the damage returned by `accumulated_damage`. /// be equivalent to the damage returned by `accumulated_damage`.
/// Redrawing other parts of the element is not valid and may cause rendering artifacts. /// Redrawing other parts of the element is not valid and may cause rendering artifacts.
@ -51,9 +81,15 @@ where
renderer: &mut R, renderer: &mut R,
frame: &mut F, frame: &mut F,
scale: f64, scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>], damage: &[Rectangle<i32, Logical>],
log: &slog::Logger, log: &slog::Logger,
) -> Result<(), R::Error>; ) -> Result<(), R::Error>;
/// Returns z_index of RenderElement, reverf too [`RenderZindex`] for default values
fn z_index(&self) -> u8 {
RenderZindex::Overlay as u8
}
} }
pub(crate) trait SpaceElement<R, F, E, T> pub(crate) trait SpaceElement<R, F, E, T>
@ -81,6 +117,7 @@ where
damage: &[Rectangle<i32, Logical>], damage: &[Rectangle<i32, Logical>],
log: &slog::Logger, log: &slog::Logger,
) -> Result<(), R::Error>; ) -> Result<(), R::Error>;
fn z_index(&self) -> u8;
} }
impl<R, F, E, T> SpaceElement<R, F, E, T> for Box<dyn RenderElement<R, F, E, T>> impl<R, F, E, T> SpaceElement<R, F, E, T> for Box<dyn RenderElement<R, F, E, T>>
@ -108,11 +145,15 @@ where
renderer: &mut R, renderer: &mut R,
frame: &mut F, frame: &mut F,
scale: f64, scale: f64,
_location: Point<i32, Logical>, location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>], damage: &[Rectangle<i32, Logical>],
log: &slog::Logger, log: &slog::Logger,
) -> Result<(), R::Error> { ) -> Result<(), R::Error> {
(&**self as &dyn RenderElement<R, F, E, T>).draw(renderer, frame, scale, damage, log) (&**self as &dyn RenderElement<R, F, E, T>).draw(renderer, frame, scale, location, damage, log)
}
fn z_index(&self) -> u8 {
RenderElement::z_index(self.as_ref())
} }
} }
@ -161,6 +202,7 @@ where
renderer: &mut R, renderer: &mut R,
frame: &mut F, frame: &mut F,
scale: f64, scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>], damage: &[Rectangle<i32, Logical>],
log: &slog::Logger, log: &slog::Logger,
) -> Result<(), R::Error> { ) -> Result<(), R::Error> {
@ -169,7 +211,7 @@ where
frame, frame,
&self.surface, &self.surface,
scale, scale,
self.position, location,
damage, damage,
log, log,
) )

View File

@ -5,7 +5,7 @@ use crate::{
space::{Space, SpaceElement}, space::{Space, SpaceElement},
}, },
utils::{Logical, Point, Rectangle}, utils::{Logical, Point, Rectangle},
wayland::output::Output, wayland::{output::Output, shell::wlr_layer::Layer},
}; };
use std::{ use std::{
any::TypeId, any::TypeId,
@ -13,6 +13,8 @@ use std::{
collections::HashMap, collections::HashMap,
}; };
use super::RenderZindex;
#[derive(Default)] #[derive(Default)]
pub struct LayerState { pub struct LayerState {
pub drawn: bool, pub drawn: bool,
@ -69,4 +71,18 @@ where
} }
res 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 {
0
}
}
} }

View File

@ -2,23 +2,21 @@
//! rendering helpers to add custom elements or different clients to a space. //! rendering helpers to add custom elements or different clients to a space.
use crate::{ use crate::{
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, backend::renderer::{Frame, ImportAll, Renderer},
desktop::{ desktop::{
layer::{layer_map_for_output, LayerSurface}, layer::{layer_map_for_output, LayerSurface},
popup::PopupManager,
utils::{output_leave, output_update},
window::Window, window::Window,
}, },
utils::{Logical, Point, Rectangle}, utils::{Logical, Point, Rectangle, Transform},
wayland::{ wayland::{
compositor::{ compositor::{get_parent, is_sync_subsurface},
get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState,
TraversalAction,
},
output::Output, output::Output,
shell::wlr_layer::Layer as WlrLayer,
}, },
}; };
use indexmap::{IndexMap, IndexSet}; use indexmap::{IndexMap, IndexSet};
use std::{cell::RefCell, collections::VecDeque, fmt}; use std::{collections::VecDeque, fmt};
use wayland_server::protocol::wl_surface::WlSurface; use wayland_server::protocol::wl_surface::WlSurface;
mod element; mod element;
@ -44,10 +42,6 @@ pub struct Space {
logger: ::slog::Logger, logger: ::slog::Logger,
} }
/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups.
pub type DynamicRenderElements<R> =
Box<dyn RenderElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>>;
impl PartialEq for Space { impl PartialEq for Space {
fn eq(&self, other: &Space) -> bool { fn eq(&self, other: &Space) -> bool {
self.id == other.id self.id == other.id
@ -326,97 +320,39 @@ impl Space {
// the output. // the output.
if !output_geometry.overlaps(bbox) { if !output_geometry.overlaps(bbox) {
if let Some(surface) = kind.get_surface() { if let Some(surface) = kind.get_surface() {
with_surface_tree_downward( output_leave(output, &mut output_state.surfaces, surface, &self.logger);
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,
)
} }
continue; continue;
} }
if let Some(surface) = kind.get_surface() { if let Some(surface) = kind.get_surface() {
with_surface_tree_downward( output_update(
output,
output_geometry,
&mut output_state.surfaces,
surface, surface,
window_loc(window, &self.id), window_loc(window, &self.id),
|_, states, location| { &self.logger,
let mut location = *location;
let data = states.data_map.get::<RefCell<SurfaceState>>();
if data.is_some() {
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
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::<RefCell<SurfaceState>>();
if let Some(size) = data.and_then(|d| d.borrow().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()); for (popup, location) in PopupManager::popups_for_surface(surface)
} .ok()
} else { .into_iter()
// Surface does not match output, if we sent enter earlier .flatten()
// we should now send leave {
if output_state.surfaces.contains(wl_surface) { if let Some(surface) = popup.get_surface() {
slog::trace!( let location = window_loc(window, &self.id) + window.geometry().loc + location
self.logger, - popup.geometry().loc;
"surface ({:?}) leaving output {:?}", output_update(
wl_surface, output,
output.name() output_geometry,
&mut output_state.surfaces,
surface,
location,
&self.logger,
); );
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,
)
} }
} }
} }
@ -454,7 +390,8 @@ impl Space {
/// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered /// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered
/// after every other element. /// 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<R>( pub fn render_output<R>(
&mut self, &mut self,
renderer: &mut R, renderer: &mut R,
@ -473,9 +410,6 @@ impl Space {
return Err(RenderError::UnmappedOutput); return Err(RenderError::UnmappedOutput);
} }
type SpaceElem<R> =
dyn SpaceElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>;
let mut state = output_state(self.id, output); let mut state = output_state(self.id, output);
let output_size = output let output_size = output
.current_mode() .current_mode()
@ -497,23 +431,31 @@ impl Space {
.flat_map(|l| l.popup_elements::<R>(self.id)) .flat_map(|l| l.popup_elements::<R>(self.id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut render_elements: Vec<&SpaceElem<R>> = Vec::with_capacity(
custom_elements.len()
+ layer_map.len()
+ self.windows.len()
+ window_popups.len()
+ layer_popups.len(),
);
render_elements.extend(custom_elements.iter().map(|l| l as &SpaceElem<R>));
render_elements.extend(self.windows.iter().map(|l| l as &SpaceElem<R>));
render_elements.extend(window_popups.iter().map(|l| l as &SpaceElem<R>));
render_elements.extend(layer_map.layers().map(|l| l as &SpaceElem<R>));
render_elements.extend(layer_popups.iter().map(|l| l as &SpaceElem<R>));
render_elements.sort_by_key(|e| e.z_index());
// This will hold all the damage we need for this rendering step // This will hold all the damage we need for this rendering step
let mut damage = Vec::<Rectangle<i32, Logical>>::new(); let mut damage = Vec::<Rectangle<i32, Logical>>::new();
// First add damage for windows gone // First add damage for windows gone
for old_toplevel in state for old_toplevel in state
.last_state .last_state
.iter() .iter()
.filter_map(|(id, geo)| { .filter_map(|(id, geo)| {
if !self if !render_elements.iter().any(|e| ToplevelId::from(*e) == *id) {
.windows
.iter()
.map(|w| w as &SpaceElem<R>)
.chain(window_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(layer_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
.any(|e| ToplevelId::from(e) == *id)
{
Some(*geo) Some(*geo)
} else { } else {
None None
@ -526,17 +468,9 @@ impl Space {
} }
// lets iterate front to back and figure out, what new windows or unmoved windows we have // lets iterate front to back and figure out, what new windows or unmoved windows we have
for element in self for element in &render_elements {
.windows
.iter()
.map(|w| w as &SpaceElem<R>)
.chain(window_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(layer_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
{
let geo = element.geometry(self.id); 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 // window was moved or resized
if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) {
@ -572,12 +506,15 @@ impl Space {
damage.retain(|rect| rect.overlaps(output_geo)); damage.retain(|rect| rect.overlaps(output_geo));
damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0);
// merge overlapping rectangles // merge overlapping rectangles
damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| { damage = damage.into_iter().fold(Vec::new(), |new_damage, mut rect| {
if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) { // replace with drain_filter, when that becomes stable to reuse the original Vec's memory
*existing = existing.merge(rect); let (overlapping, mut new_damage): (Vec<_>, Vec<_>) =
} else { new_damage.into_iter().partition(|other| other.overlaps(rect));
new_damage.push(rect);
for overlap in overlapping {
rect = rect.merge(overlap);
} }
new_damage.push(rect);
new_damage new_damage
}); });
@ -600,37 +537,28 @@ impl Space {
clear_color, clear_color,
&damage &damage
.iter() .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()) .map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
)?; )?;
// Then re-draw all windows & layers overlapping with a damage rect. // Then re-draw all windows & layers overlapping with a damage rect.
for element in layer_map for element in &render_elements {
.layers_on(WlrLayer::Background)
.chain(layer_map.layers_on(WlrLayer::Bottom))
.map(|l| l as &SpaceElem<R>)
.chain(self.windows.iter().map(|w| w as &SpaceElem<R>))
.chain(
layer_map
.layers_on(WlrLayer::Top)
.chain(layer_map.layers_on(WlrLayer::Overlay))
.map(|l| l as &SpaceElem<R>),
)
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
{
let geo = element.geometry(self.id); let geo = element.geometry(self.id);
if damage.iter().any(|d| d.overlaps(geo)) { 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 let damage = damage
.iter() .iter()
.flat_map(|d| d.intersection(geo)) .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)) .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
slog::trace!( slog::trace!(
self.logger, self.logger,
"Rendering toplevel at {:?} with damage {:#?}", "Rendering toplevel at {:?} with damage {:#?}",
geo, Rectangle::from_loc_and_size(geo.loc - output_geo.loc, geo.size),
damage damage
); );
element.draw( element.draw(
@ -638,7 +566,7 @@ impl Space {
renderer, renderer,
frame, frame,
state.render_scale, state.render_scale,
loc, loc - output_geo.loc,
&damage, &damage,
&self.logger, &self.logger,
)?; )?;
@ -658,22 +586,24 @@ impl Space {
} }
// If rendering was successful capture the state and add the damage // If rendering was successful capture the state and add the damage
state.last_state = self state.last_state = render_elements
.windows
.iter() .iter()
.map(|w| w as &SpaceElem<R>)
.chain(window_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(layer_popups.iter().map(|p| p as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
.map(|elem| { .map(|elem| {
let geo = elem.geometry(self.id); let geo = elem.geometry(self.id);
(ToplevelId::from(elem), geo) (ToplevelId::from(*elem), geo)
}) })
.collect(); .collect();
state.old_damage.push_front(new_damage.clone()); 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. /// Sends the frame callback to mapped [`Window`]s and [`LayerSurface`]s.

View File

@ -8,14 +8,17 @@ use crate::{
window::Window, window::Window,
}, },
utils::{Logical, Point, Rectangle}, utils::{Logical, Point, Rectangle},
wayland::output::Output, wayland::{output::Output, shell::wlr_layer::Layer},
}; };
use std::any::TypeId; use std::any::TypeId;
use super::RenderZindex;
#[derive(Debug)] #[derive(Debug)]
pub struct RenderPopup { pub struct RenderPopup {
location: Point<i32, Logical>, location: Point<i32, Logical>,
popup: PopupKind, popup: PopupKind,
z_index: u8,
} }
impl Window { impl Window {
@ -39,6 +42,7 @@ impl Window {
RenderPopup { RenderPopup {
location: offset, location: offset,
popup, popup,
z_index: RenderZindex::Popups as u8,
} }
}) })
}) })
@ -48,7 +52,7 @@ impl Window {
} }
impl LayerSurface { impl LayerSurface {
pub(super) fn popup_elements<R>(&self, space_id: usize) -> impl Iterator<Item = RenderPopup> pub(super) fn popup_elements<R>(&self, space_id: usize) -> impl Iterator<Item = RenderPopup> + '_
where where
R: Renderer + ImportAll + 'static, R: Renderer + ImportAll + 'static,
R::TextureId: 'static, R::TextureId: 'static,
@ -67,9 +71,20 @@ impl LayerSurface {
.flatten() .flatten()
.map(move |(popup, location)| { .map(move |(popup, location)| {
let offset = loc + location - popup.geometry().loc; 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 { RenderPopup {
location: offset, location: offset,
popup, popup,
z_index,
} }
}) })
}) })
@ -126,4 +141,8 @@ where
// popups are special, we track them, but they render with their parents // popups are special, we track them, but they render with their parents
Ok(()) Ok(())
} }
fn z_index(&self) -> u8 {
self.z_index
}
} }

View File

@ -13,6 +13,8 @@ use std::{
collections::HashMap, collections::HashMap,
}; };
use super::RenderZindex;
#[derive(Default)] #[derive(Default)]
pub struct WindowState { pub struct WindowState {
pub location: Point<i32, Logical>, pub location: Point<i32, Logical>,
@ -103,4 +105,8 @@ where
} }
res res
} }
fn z_index(&self) -> u8 {
RenderZindex::Shell as u8
}
} }

View File

@ -3,7 +3,7 @@
use crate::{ use crate::{
backend::renderer::utils::SurfaceState, backend::renderer::utils::SurfaceState,
desktop::Space, desktop::Space,
utils::{Logical, Point, Rectangle, Size}, utils::{Logical, Point, Rectangle},
wayland::{ wayland::{
compositor::{ compositor::{
with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState,
@ -16,16 +16,12 @@ use wayland_server::protocol::wl_surface;
use std::cell::RefCell; use std::cell::RefCell;
impl SurfaceState { use super::WindowSurfaceType;
/// Returns the size of the surface.
pub fn size(&self) -> Option<Size<i32, Logical>> {
self.buffer_dimensions
.map(|dims| dims.to_logical(self.buffer_scale))
}
impl SurfaceState {
fn contains_point<P: Into<Point<f64, Logical>>>(&self, attrs: &SurfaceAttributes, point: P) -> bool { fn contains_point<P: Into<Point<f64, Logical>>>(&self, attrs: &SurfaceAttributes, point: P) -> bool {
let point = point.into(); 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. None => return false, // If the surface has no size, it can't have an input region.
Some(size) => size, Some(size) => size,
}; };
@ -71,7 +67,7 @@ where
let mut loc = *loc; let mut loc = *loc;
let data = states.data_map.get::<RefCell<SurfaceState>>(); let data = states.data_map.get::<RefCell<SurfaceState>>();
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") { if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>(); let current = states.cached_state.current::<SubsurfaceCachedState>();
loc += current.location; loc += current.location;
@ -148,7 +144,11 @@ where
damage.extend(attributes.damage.iter().map(|dmg| { damage.extend(attributes.damage.iter().map(|dmg| {
let mut rect = match 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, Damage::Surface(rect) => *rect,
}; };
rect.loc += location; rect.loc += location;
@ -174,6 +174,7 @@ pub fn under_from_surface_tree<P>(
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
point: Point<f64, Logical>, point: Point<f64, Logical>,
location: P, location: P,
surface_type: WindowSurfaceType,
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> ) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)>
where where
P: Into<Point<i32, Logical>>, P: Into<Point<i32, Logical>>,
@ -191,6 +192,7 @@ where
location += current.location; location += current.location;
} }
if states.role == Some("subsurface") || surface_type.contains(WindowSurfaceType::TOPLEVEL) {
let contains_the_point = data let contains_the_point = data
.map(|data| { .map(|data| {
data.borrow() data.borrow()
@ -200,8 +202,13 @@ where
if contains_the_point { if contains_the_point {
*found.borrow_mut() = Some((wl_surface.clone(), location)); *found.borrow_mut() = Some((wl_surface.clone(), location));
} }
}
if surface_type.contains(WindowSurfaceType::SUBSURFACE) {
TraversalAction::DoChildren(location) TraversalAction::DoChildren(location)
} else {
TraversalAction::SkipChildren
}
}, },
|_, _, _| {}, |_, _, _| {},
|_, _, _| { |_, _, _| {
@ -233,3 +240,89 @@ pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|_, _, &()| true, |_, _, &()| true,
); );
} }
pub(crate) fn output_update(
output: &Output,
output_geometry: Rectangle<i32, Logical>,
surface_list: &mut Vec<wl_surface::WlSurface>,
surface: &wl_surface::WlSurface,
location: Point<i32, Logical>,
logger: &slog::Logger,
) {
with_surface_tree_downward(
surface,
location,
|_, states, location| {
let mut location = *location;
let data = states.data_map.get::<RefCell<SurfaceState>>();
if data.is_some() {
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
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::<RefCell<SurfaceState>>();
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<wl_surface::WlSurface>,
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<wl_surface::WlSurface>,
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);
}
}

View File

@ -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 { impl Window {
/// Construct a new [`Window`] from a given compatible toplevel surface /// Construct a new [`Window`] from a given compatible toplevel surface
pub fn new(toplevel: Kind) -> Window { pub fn new(toplevel: Kind) -> Window {
@ -222,9 +237,11 @@ impl Window {
pub fn surface_under<P: Into<Point<f64, Logical>>>( pub fn surface_under<P: Into<Point<f64, Logical>>>(
&self, &self,
point: P, point: P,
surface_type: WindowSurfaceType,
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> { ) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> {
let point = point.into(); let point = point.into();
if let Some(surface) = self.0.toplevel.get_surface() { if let Some(surface) = self.0.toplevel.get_surface() {
if surface_type.contains(WindowSurfaceType::POPUP) {
for (popup, location) in PopupManager::popups_for_surface(surface) for (popup, location) in PopupManager::popups_for_surface(surface)
.ok() .ok()
.into_iter() .into_iter()
@ -233,13 +250,14 @@ impl Window {
let offset = self.geometry().loc + location - popup.geometry().loc; let offset = self.geometry().loc + location - popup.geometry().loc;
if let Some(result) = popup if let Some(result) = popup
.get_surface() .get_surface()
.and_then(|surface| under_from_surface_tree(surface, point, offset)) .and_then(|surface| under_from_surface_tree(surface, point, offset, surface_type))
{ {
return Some(result); return Some(result);
} }
} }
}
under_from_surface_tree(surface, point, (0, 0)) under_from_surface_tree(surface, point, (0, 0), surface_type)
} else { } else {
None None
} }

View File

@ -406,10 +406,11 @@ impl<N: Coordinate> Point<N, Logical> {
#[inline] #[inline]
/// Convert this logical point to buffer coordinate space according to given scale factor /// Convert this logical point to buffer coordinate space according to given scale factor
pub fn to_buffer(self, scale: N) -> Point<N, Buffer> { pub fn to_buffer(self, scale: N, transformation: Transform, area: &Size<N, Logical>) -> Point<N, Buffer> {
let point = transformation.transform_point_in(self, area);
Point { Point {
x: self.x.upscale(scale), x: point.x.upscale(scale),
y: self.y.upscale(scale), y: point.y.upscale(scale),
_kind: std::marker::PhantomData, _kind: std::marker::PhantomData,
} }
} }
@ -430,10 +431,11 @@ impl<N: Coordinate> Point<N, Physical> {
impl<N: Coordinate> Point<N, Buffer> { impl<N: Coordinate> Point<N, Buffer> {
#[inline] #[inline]
/// Convert this physical point to logical coordinate space according to given scale factor /// Convert this physical point to logical coordinate space according to given scale factor
pub fn to_logical(self, scale: N) -> Point<N, Logical> { pub fn to_logical(self, scale: N, transform: Transform, area: &Size<N, Buffer>) -> Point<N, Logical> {
let point = transform.invert().transform_point_in(self, area);
Point { Point {
x: self.x.downscale(scale), x: point.x.downscale(scale),
y: self.y.downscale(scale), y: point.y.downscale(scale),
_kind: std::marker::PhantomData, _kind: std::marker::PhantomData,
} }
} }
@ -667,12 +669,12 @@ impl<N: Coordinate> Size<N, Logical> {
#[inline] #[inline]
/// Convert this logical size to buffer coordinate space according to given scale factor /// Convert this logical size to buffer coordinate space according to given scale factor
pub fn to_buffer(self, scale: N) -> Size<N, Buffer> { pub fn to_buffer(self, scale: N, transformation: Transform) -> Size<N, Buffer> {
Size { transformation.transform_size(Size {
w: self.w.upscale(scale), w: self.w.upscale(scale),
h: self.h.upscale(scale), h: self.h.upscale(scale),
_kind: std::marker::PhantomData, _kind: std::marker::PhantomData,
} })
} }
} }
@ -691,12 +693,12 @@ impl<N: Coordinate> Size<N, Physical> {
impl<N: Coordinate> Size<N, Buffer> { impl<N: Coordinate> Size<N, Buffer> {
#[inline] #[inline]
/// Convert this physical point to logical coordinate space according to given scale factor /// Convert this physical point to logical coordinate space according to given scale factor
pub fn to_logical(self, scale: N) -> Size<N, Logical> { pub fn to_logical(self, scale: N, transformation: Transform) -> Size<N, Logical> {
Size { transformation.invert().transform_size(Size {
w: self.w.downscale(scale), w: self.w.downscale(scale),
h: self.h.downscale(scale), h: self.h.downscale(scale),
_kind: std::marker::PhantomData, _kind: std::marker::PhantomData,
} })
} }
} }
@ -969,10 +971,24 @@ impl<N: Coordinate> Rectangle<N, Logical> {
/// Convert this logical rectangle to buffer coordinate space according to given scale factor /// Convert this logical rectangle to buffer coordinate space according to given scale factor
#[inline] #[inline]
pub fn to_buffer(self, scale: N) -> Rectangle<N, Buffer> { pub fn to_buffer(
self,
scale: N,
transformation: Transform,
area: &Size<N, Logical>,
) -> Rectangle<N, Buffer> {
let rect = transformation.transform_rect_in(self, area);
Rectangle { Rectangle {
loc: self.loc.to_buffer(scale), loc: Point {
size: self.size.to_buffer(scale), 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<N: Coordinate> Rectangle<N, Physical> {
impl<N: Coordinate> Rectangle<N, Buffer> { impl<N: Coordinate> Rectangle<N, Buffer> {
/// Convert this physical rectangle to logical coordinate space according to given scale factor /// Convert this physical rectangle to logical coordinate space according to given scale factor
#[inline] #[inline]
pub fn to_logical(self, scale: N) -> Rectangle<N, Logical> { pub fn to_logical(
self,
scale: N,
transformation: Transform,
area: &Size<N, Buffer>,
) -> Rectangle<N, Logical> {
let rect = transformation.invert().transform_rect_in(self, area);
Rectangle { Rectangle {
loc: self.loc.to_logical(scale), loc: Point {
size: self.size.to_logical(scale), 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<N: Default, Kind> Default for Rectangle<N, Kind> {
} }
} }
} }
#[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<N: Coordinate, Kind>(
&self,
point: Point<N, Kind>,
area: &Size<N, Kind>,
) -> Point<N, Kind> {
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<N: Coordinate, Kind>(&self, size: Size<N, Kind>) -> Size<N, Kind> {
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<N: Coordinate, Kind>(
&self,
rect: Rectangle<N, Kind>,
area: &Size<N, Kind>,
) -> Rectangle<N, Kind> {
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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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::<i32, Logical>::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)
)
}
}

View File

@ -10,7 +10,7 @@ pub mod x11rb;
pub(crate) mod ids; pub(crate) mod ids;
pub mod user_data; 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 /// This resource is not managed by Smithay
#[derive(Debug)] #[derive(Debug)]

View File

@ -8,7 +8,7 @@ use wayland_server::{
use crate::{ use crate::{
utils::{Logical, Point}, utils::{Logical, Point},
wayland::{ wayland::{
seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat},
Serial, Serial,
}, },
}; };
@ -16,7 +16,7 @@ use crate::{
use super::{with_source_metadata, DataDeviceData, SeatData}; use super::{with_source_metadata, DataDeviceData, SeatData};
pub(crate) struct DnDGrab { pub(crate) struct DnDGrab {
start_data: GrabStartData, start_data: PointerGrabStartData,
data_source: Option<wl_data_source::WlDataSource>, data_source: Option<wl_data_source::WlDataSource>,
current_focus: Option<wl_surface::WlSurface>, current_focus: Option<wl_surface::WlSurface>,
pending_offers: Vec<wl_data_offer::WlDataOffer>, pending_offers: Vec<wl_data_offer::WlDataOffer>,
@ -29,7 +29,7 @@ pub(crate) struct DnDGrab {
impl DnDGrab { impl DnDGrab {
pub(crate) fn new( pub(crate) fn new(
start_data: GrabStartData, start_data: PointerGrabStartData,
source: Option<wl_data_source::WlDataSource>, source: Option<wl_data_source::WlDataSource>,
origin: wl_surface::WlSurface, origin: wl_surface::WlSurface,
seat: Seat, seat: Seat,
@ -222,7 +222,7 @@ impl PointerGrab for DnDGrab {
handle.axis(details); handle.axis(details);
} }
fn start_data(&self) -> &GrabStartData { fn start_data(&self) -> &PointerGrabStartData {
&self.start_data &self.start_data
} }
} }

View File

@ -60,7 +60,7 @@ use slog::{debug, error, o};
use crate::wayland::{ use crate::wayland::{
compositor, compositor,
seat::{GrabStartData, Seat}, seat::{PointerGrabStartData, Seat},
Serial, Serial,
}; };
@ -335,7 +335,7 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec<String>) {
pub fn start_dnd<C>( pub fn start_dnd<C>(
seat: &Seat, seat: &Seat,
serial: Serial, serial: Serial,
start_data: GrabStartData, start_data: PointerGrabStartData,
metadata: SourceMetadata, metadata: SourceMetadata,
callback: C, callback: C,
) where ) where

View File

@ -8,7 +8,7 @@ use wayland_server::{
use crate::{ use crate::{
utils::{Logical, Point}, utils::{Logical, Point},
wayland::{ wayland::{
seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat},
Serial, Serial,
}, },
}; };
@ -42,7 +42,7 @@ pub enum ServerDndEvent {
} }
pub(crate) struct ServerDnDGrab<C: 'static> { pub(crate) struct ServerDnDGrab<C: 'static> {
start_data: GrabStartData, start_data: PointerGrabStartData,
metadata: super::SourceMetadata, metadata: super::SourceMetadata,
current_focus: Option<wl_surface::WlSurface>, current_focus: Option<wl_surface::WlSurface>,
pending_offers: Vec<wl_data_offer::WlDataOffer>, pending_offers: Vec<wl_data_offer::WlDataOffer>,
@ -53,7 +53,7 @@ pub(crate) struct ServerDnDGrab<C: 'static> {
impl<C: 'static> ServerDnDGrab<C> { impl<C: 'static> ServerDnDGrab<C> {
pub(crate) fn new( pub(crate) fn new(
start_data: GrabStartData, start_data: PointerGrabStartData,
metadata: super::SourceMetadata, metadata: super::SourceMetadata,
seat: Seat, seat: Seat,
callback: Rc<RefCell<C>>, callback: Rc<RefCell<C>>,
@ -222,7 +222,7 @@ where
handle.axis(details); handle.axis(details);
} }
fn start_data(&self) -> &GrabStartData { fn start_data(&self) -> &PointerGrabStartData {
&self.start_data &self.start_data
} }
} }

View File

@ -102,7 +102,7 @@ pub struct PhysicalProperties {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Inner { pub(crate) struct Inner {
name: String, name: String,
log: ::slog::Logger, pub(crate) log: ::slog::Logger,
instances: Vec<WlOutput>, instances: Vec<WlOutput>,
physical: PhysicalProperties, physical: PhysicalProperties,
location: Point<i32, Logical>, location: Point<i32, Logical>,

View File

@ -86,9 +86,16 @@ pub struct XkbConfig<'a> {
pub options: Option<String>, pub options: Option<String>,
} }
enum GrabStatus {
None,
Active(Serial, Box<dyn KeyboardGrab>),
Borrowed,
}
struct KbdInternal { struct KbdInternal {
known_kbds: Vec<WlKeyboard>, known_kbds: Vec<WlKeyboard>,
focus: Option<WlSurface>, focus: Option<WlSurface>,
pending_focus: Option<WlSurface>,
pressed_keys: Vec<u32>, pressed_keys: Vec<u32>,
mods_state: ModifiersState, mods_state: ModifiersState,
keymap: xkb::Keymap, keymap: xkb::Keymap,
@ -96,6 +103,7 @@ struct KbdInternal {
repeat_rate: i32, repeat_rate: i32,
repeat_delay: i32, repeat_delay: i32,
focus_hook: Box<dyn FnMut(Option<&WlSurface>)>, focus_hook: Box<dyn FnMut(Option<&WlSurface>)>,
grab: GrabStatus,
} }
// focus_hook does not implement debug, so we have to impl Debug manually // focus_hook does not implement debug, so we have to impl Debug manually
@ -147,6 +155,7 @@ impl KbdInternal {
Ok(KbdInternal { Ok(KbdInternal {
known_kbds: Vec::new(), known_kbds: Vec::new(),
focus: None, focus: None,
pending_focus: None,
pressed_keys: Vec::new(), pressed_keys: Vec::new(),
mods_state: ModifiersState::default(), mods_state: ModifiersState::default(),
keymap, keymap,
@ -154,6 +163,7 @@ impl KbdInternal {
repeat_rate, repeat_rate,
repeat_delay, repeat_delay,
focus_hook, focus_hook,
grab: GrabStatus::None,
}) })
} }
@ -215,6 +225,35 @@ impl KbdInternal {
} }
} }
} }
fn with_grab<F>(&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 /// Errors that can be encountered when creating a keyboard handler
@ -320,6 +359,47 @@ pub enum FilterResult<T> {
Intercept(T), 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<WlSurface>,
}
/// 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 /// An handle to a keyboard handler
/// ///
/// It can be cloned and all clones manipulate the same internal state. /// It can be cloned and all clones manipulate the same internal state.
@ -337,6 +417,42 @@ pub struct KeyboardHandle {
} }
impl KeyboardHandle { impl KeyboardHandle {
/// Change the current grab on this keyboard to the provided grab
///
/// Overwrites any current grab.
pub fn set_grab<G: KeyboardGrab + 'static>(&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<GrabStartData> {
let guard = self.arc.internal.borrow();
match &guard.grab {
GrabStatus::Active(_, g) => Some(g.start_data().clone()),
_ => None,
}
}
/// Handle a keystroke /// Handle a keystroke
/// ///
/// All keystrokes from the input backend should be fed _in order_ to this method of the /// 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::Pressed => WlKeyState::Pressed,
KeyState::Released => WlKeyState::Released, KeyState::Released => WlKeyState::Released,
}; };
guard.with_focused_kbds(|kbd, _| { guard.with_grab(
// key event must be sent before modifers event for libxkbcommon move |mut handle, grab| {
// to process them correctly grab.input(&mut handle, keycode, wl_state, modifiers, serial, time);
kbd.key(serial.into(), time, keycode, wl_state); },
if let Some((dep, la, lo, gr)) = modifiers { self.arc.logger.clone(),
kbd.modifiers(serial.into(), dep, la, lo, gr); );
}
});
if guard.focus.is_some() { if guard.focus.is_some() {
trace!(self.arc.logger, "Input forwarded to client"); trace!(self.arc.logger, "Input forwarded to client");
} else { } else {
@ -417,44 +531,13 @@ impl KeyboardHandle {
/// a [`wl_keyboard::Event::Enter`](wayland_server::protocol::wl_keyboard::Event::Enter) event will be sent. /// 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) { pub fn set_focus(&self, focus: Option<&WlSurface>, serial: Serial) {
let mut guard = self.arc.internal.borrow_mut(); let mut guard = self.arc.internal.borrow_mut();
guard.pending_focus = focus.cloned();
let same = guard guard.with_grab(
.focus move |mut handle, grab| {
.as_ref() grab.set_focus(&mut handle, focus, serial);
.and_then(|f| focus.map(|s| s.as_ref().equals(f.as_ref()))) },
.unwrap_or(false); self.arc.logger.clone(),
);
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");
}
} }
/// Check if given client currently has keyboard focus /// Check if given client currently has keyboard focus
@ -543,3 +626,129 @@ pub(crate) fn implement_keyboard(keyboard: Main<WlKeyboard>, handle: Option<&Key
keyboard.deref().clone() 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<G: KeyboardGrab + 'static>(&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!()
}
}

View File

@ -41,12 +41,12 @@ mod pointer;
pub use self::{ pub use self::{
keyboard::{ keyboard::{
keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, KeysymHandle, ModifiersState, keysyms, Error as KeyboardError, FilterResult, GrabStartData as KeyboardGrabStartData, KeyboardGrab,
XkbConfig, KeyboardHandle, KeyboardInnerHandle, Keysym, KeysymHandle, ModifiersState, XkbConfig,
}, },
pointer::{ pointer::{
AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData as PointerGrabStartData,
PointerInnerHandle, PointerGrab, PointerHandle, PointerInnerHandle,
}, },
}; };

View File

@ -677,6 +677,7 @@ impl PointerGrab for DefaultGrab {
time: u32, time: u32,
) { ) {
handle.button(button, state, serial, time); handle.button(button, state, serial, time);
if state == ButtonState::Pressed {
handle.set_grab( handle.set_grab(
serial, serial,
ClickGrab { ClickGrab {
@ -688,6 +689,7 @@ impl PointerGrab for DefaultGrab {
}, },
); );
} }
}
fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) { fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) {
handle.axis(details); handle.axis(details);
} }

View File

@ -162,7 +162,7 @@ impl TryFrom<zwlr_layer_surface_v1::Anchor> for Anchor {
} }
/// Exclusive zone descriptor /// Exclusive zone descriptor
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExclusiveZone { pub enum ExclusiveZone {
/// Requests that the compositor avoids occluding an area with other surfaces. /// Requests that the compositor avoids occluding an area with other surfaces.
/// ///

View File

@ -383,6 +383,14 @@ xdg_role!(
/// It is a protocol error to call commit on a wl_surface with /// It is a protocol error to call commit on a wl_surface with
/// the xdg_popup role when no parent is set. /// the xdg_popup role when no parent is set.
pub parent: Option<wl_surface::WlSurface>, pub parent: Option<wl_surface::WlSurface>,
/// 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<xdg_popup::XdgPopup> popup_handle: Option<xdg_popup::XdgPopup>
} }
); );
@ -1169,15 +1177,14 @@ impl ToplevelSurface {
/// The parent must be another toplevel equivalent surface. /// The parent must be another toplevel equivalent surface.
/// ///
/// If the parent is `None`, the parent-child relationship is removed. /// If the parent is `None`, the parent-child relationship is removed.
pub fn set_parent(&self, parent: Option<wl_surface::WlSurface>) -> bool { pub fn set_parent(&self, parent: Option<&wl_surface::WlSurface>) -> bool {
if let Some(parent) = parent { if let Some(parent) = parent {
if !is_toplevel_equivalent(&parent) { if !is_toplevel_equivalent(parent) {
return false; return false;
} }
} }
// Unset the parent xdg_handlers::set_parent(&self.shell_surface, parent.cloned());
xdg_handlers::set_parent(&self.shell_surface, None);
true true
} }
@ -1399,6 +1406,7 @@ impl PopupSurface {
.unwrap() .unwrap()
.lock() .lock()
.unwrap(); .unwrap();
attributes.committed = true;
if attributes.initial_configure_sent { if attributes.initial_configure_sent {
if let Some(state) = attributes.last_acked { if let Some(state) = attributes.last_acked {
if state != attributes.current { if state != attributes.current {

View File

@ -447,7 +447,7 @@ fn imported_implementation(
.map(|export| export.surface.clone()) .map(|export| export.surface.clone())
.unwrap(); .unwrap();
toplevel_surface.set_parent(Some(imported_parent)); toplevel_surface.set_parent(Some(&imported_parent));
} }
_ => unreachable!(), _ => unreachable!(),

View File

@ -5,7 +5,7 @@ use std::{
use smithay::{ use smithay::{
backend::{ backend::{
renderer::{Frame, Renderer, Transform}, renderer::{Frame, Renderer},
SwapBuffersError, SwapBuffersError,
}, },
reexports::{ reexports::{
@ -18,7 +18,7 @@ use smithay::{
Client, Display, Client, Display,
}, },
}, },
utils::Rectangle, utils::{Rectangle, Transform},
wayland::{ wayland::{
output::{Mode, PhysicalProperties}, output::{Mode, PhysicalProperties},
seat::CursorImageStatus, seat::CursorImageStatus,

View File

@ -3,11 +3,11 @@ use std::cell::Cell;
use smithay::{ use smithay::{
backend::{ backend::{
allocator::dmabuf::Dmabuf, allocator::dmabuf::Dmabuf,
renderer::{Frame, ImportDma, ImportShm, Renderer, Texture, TextureFilter, Transform}, renderer::{Frame, ImportDma, ImportShm, Renderer, Texture, TextureFilter},
SwapBuffersError, SwapBuffersError,
}, },
reexports::wayland_server::protocol::wl_buffer, reexports::wayland_server::protocol::wl_buffer,
utils::{Buffer, Physical, Rectangle, Size}, utils::{Buffer, Physical, Rectangle, Size, Transform},
wayland::compositor::SurfaceData, wayland::compositor::SurfaceData,
}; };
@ -106,7 +106,7 @@ impl Frame for DummyFrame {
_texture: &Self::TextureId, _texture: &Self::TextureId,
_src: Rectangle<i32, Buffer>, _src: Rectangle<i32, Buffer>,
_dst: Rectangle<f64, Physical>, _dst: Rectangle<f64, Physical>,
_damage: &[Rectangle<i32, Physical>], _damage: &[Rectangle<i32, Buffer>],
_src_transform: Transform, _src_transform: Transform,
_alpha: f32, _alpha: f32,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {