diff --git a/anvil/src/buffer_utils.rs b/anvil/src/buffer_utils.rs index e784177..8509c11 100644 --- a/anvil/src/buffer_utils.rs +++ b/anvil/src/buffer_utils.rs @@ -1,13 +1,25 @@ +use glium::texture::Texture2d; +#[cfg(feature = "egl")] +use glium::{ + texture::{MipmapsOption, UncompressedFloatFormat}, + GlObject, +}; +use slog::Logger; +use std::collections::HashMap; +#[cfg(feature = "egl")] use std::{cell::RefCell, rc::Rc}; -use slog::Logger; - #[cfg(feature = "egl")] -use smithay::backend::egl::display::EGLBufferReader; -use smithay::{ - reexports::wayland_server::protocol::wl_buffer::WlBuffer, - wayland::shm::with_buffer_contents as shm_buffer_contents, +use smithay::backend::egl::{ + display::EGLBufferReader, BufferAccessError as EGLBufferAccessError, EGLImages, Format, }; +use smithay::{ + backend::graphics::gl::GLGraphicsBackend, + reexports::wayland_server::protocol::wl_buffer::WlBuffer, + wayland::shm::{with_buffer_contents as shm_buffer_contents, BufferAccessError}, +}; + +use crate::glium_drawer::GliumDrawer; /// Utilities for working with `WlBuffer`s. #[derive(Clone)] @@ -41,22 +53,182 @@ impl BufferUtils { .borrow() .as_ref() .and_then(|display| display.egl_buffer_dimensions(buffer)) - .or_else(|| self.shm_buffer_dimensions(buffer)) + .or_else(|| self.shm_buffer_dimensions(buffer).ok()) } /// Returns the dimensions of an image stored in the buffer. #[cfg(not(feature = "egl"))] pub fn dimensions(&self, buffer: &WlBuffer) -> Option<(i32, i32)> { - self.shm_buffer_dimensions(buffer) + self.shm_buffer_dimensions(buffer).ok() } /// Returns the dimensions of an image stored in the shm buffer. - fn shm_buffer_dimensions(&self, buffer: &WlBuffer) -> Option<(i32, i32)> { - shm_buffer_contents(buffer, |_, data| (data.width, data.height)) - .map_err(|err| { - warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); - err - }) - .ok() + fn shm_buffer_dimensions(&self, buffer: &WlBuffer) -> Result<(i32, i32), BufferAccessError> { + shm_buffer_contents(buffer, |_, data| (data.width, data.height)).map_err(|err| { + warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); + err + }) + } + + #[cfg(feature = "egl")] + pub fn load_buffer(&self, buffer: WlBuffer) -> Result { + // try to retrieve the egl contents of this buffer + let images = if let Some(display) = &self.egl_buffer_reader.borrow().as_ref() { + display.egl_buffer_contents(&buffer) + } else { + return Err(buffer); + }; + + match images { + Ok(images) => { + // we have an EGL buffer + Ok(BufferTextures { + buffer, + textures: HashMap::new(), + fragment: crate::shaders::BUFFER_RGBA, + y_inverted: images.y_inverted, + dimensions: (images.width, images.height), + images: Some(images), // I guess we need to keep this alive ? + logger: self.log.clone(), + }) + } + Err(EGLBufferAccessError::NotManaged(_)) => { + // this is not an EGL buffer, try SHM + self.load_shm_buffer(buffer) + } + Err(err) => { + error!(self.log, "EGL error"; "err" => format!("{:?}", err)); + Err(buffer) + } + } + } + + #[cfg(not(feature = "egl"))] + pub fn load_buffer(&self, buffer: WlBuffer) -> Result { + self.load_shm_buffer(buffer) + } + + fn load_shm_buffer(&self, buffer: WlBuffer) -> Result { + let (width, height, format) = + match shm_buffer_contents(&buffer, |_, data| (data.width, data.height, data.format)) { + Ok(x) => x, + Err(err) => { + warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); + return Err(buffer); + } + }; + let shader = match crate::shm_load::load_format(format) { + Ok(x) => x.1, + Err(format) => { + warn!(self.log, "Unable to load buffer format: {:?}", format); + return Err(buffer); + } + }; + Ok(BufferTextures { + buffer, + textures: HashMap::new(), + fragment: shader, + y_inverted: false, + dimensions: (width as u32, height as u32), + #[cfg(feature = "egl")] + images: None, + logger: self.log.clone(), + }) + } +} + +pub struct BufferTextures { + buffer: WlBuffer, + pub textures: HashMap, + pub fragment: usize, + pub y_inverted: bool, + pub dimensions: (u32, u32), + #[cfg(feature = "egl")] + images: Option, + logger: slog::Logger, +} + +impl BufferTextures { + #[cfg(feature = "egl")] + pub fn load_texture<'a, F: GLGraphicsBackend + 'static>( + &'a mut self, + drawer: &GliumDrawer, + ) -> Result<&'a Texture2d, ()> { + if self.textures.contains_key(&drawer.id) { + return Ok(&self.textures[&drawer.id]); + } + + if let Some(images) = self.images.as_ref() { + //EGL buffer + let format = match images.format { + Format::RGB => UncompressedFloatFormat::U8U8U8, + Format::RGBA => UncompressedFloatFormat::U8U8U8U8, + _ => { + warn!(self.logger, "Unsupported EGL buffer format"; "format" => format!("{:?}", images.format)); + return Err(()); + } + }; + + let opengl_texture = Texture2d::empty_with_format( + &drawer.display, + format, + MipmapsOption::NoMipmap, + images.width, + images.height, + ) + .unwrap(); + + unsafe { + images + .bind_to_texture(0, opengl_texture.get_id(), &*drawer.display.borrow()) + .expect("Failed to bind to texture"); + } + + self.textures.insert(drawer.id, opengl_texture); + Ok(&self.textures[&drawer.id]) + } else { + self.load_shm_texture(drawer) + } + } + + #[cfg(not(feature = "egl"))] + pub fn load_texture<'a, F: GLGraphicsBackend + 'static>( + &'a mut self, + drawer: &GliumDrawer, + ) -> Result<&'a Texture2d, ()> { + if self.textures.contains_key(&drawer.id) { + return Ok(&self.textures[&drawer.id]); + } + + self.load_shm_texture(drawer) + } + + fn load_shm_texture<'a, F: GLGraphicsBackend + 'static>( + &'a mut self, + drawer: &GliumDrawer, + ) -> Result<&'a Texture2d, ()> { + match shm_buffer_contents(&self.buffer, |slice, data| { + crate::shm_load::load_shm_buffer(data, slice) + .map(|(image, _kind)| Texture2d::new(&drawer.display, image).unwrap()) + }) { + Ok(Ok(texture)) => { + self.textures.insert(drawer.id, texture); + Ok(&self.textures[&drawer.id]) + } + Ok(Err(format)) => { + warn!(self.logger, "Unsupported SHM buffer format"; "format" => format!("{:?}", format)); + Err(()) + } + Err(err) => { + warn!(self.logger, "Unable to load buffer contents"; "err" => format!("{:?}", err)); + Err(()) + } + } + } +} + +impl Drop for BufferTextures { + fn drop(&mut self) { + self.buffer.release() } } diff --git a/anvil/src/glium_drawer.rs b/anvil/src/glium_drawer.rs index 49b7301..bcf2a76 100644 --- a/anvil/src/glium_drawer.rs +++ b/anvil/src/glium_drawer.rs @@ -1,42 +1,33 @@ use std::{ cell::{Ref, RefCell}, rc::Rc, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, }; -use glium::{ - self, - index::PrimitiveType, - texture::{MipmapsOption, Texture2d, UncompressedFloatFormat}, - GlObject, Surface, -}; +use glium::{self, index::PrimitiveType, texture::Texture2d, Surface}; use slog::Logger; -#[cfg(feature = "egl")] -use smithay::backend::egl::display::EGLBufferReader; use smithay::{ - backend::{ - egl::{BufferAccessError, EGLImages, Format}, - graphics::{ - gl::GLGraphicsBackend, - glium::{Frame, GliumGraphicsBackend}, - SwapBuffersError, - }, - }, - reexports::{ - calloop::LoopHandle, - wayland_server::protocol::{wl_buffer, wl_surface}, + backend::graphics::{ + gl::GLGraphicsBackend, + glium::{Frame, GliumGraphicsBackend}, + CursorBackend, SwapBuffersError, }, + reexports::{calloop::LoopHandle, wayland_server::protocol::wl_surface}, + utils::Rectangle, wayland::{ compositor::{roles::Role, SubsurfaceRole, TraversalAction}, data_device::DnDIconRole, seat::CursorImageRole, - shm::with_buffer_contents as shm_buffer_contents, }, }; +use crate::buffer_utils::BufferUtils; use crate::shaders; use crate::shell::{MyCompositorToken, MyWindowMap, SurfaceData}; +pub static BACKEND_COUNTER: AtomicUsize = AtomicUsize::new(0); + #[derive(Copy, Clone)] struct Vertex { position: [f32; 2], @@ -51,12 +42,13 @@ mod implement_vertex { } pub struct GliumDrawer { - display: GliumGraphicsBackend, + pub id: usize, + pub display: GliumGraphicsBackend, vertex_buffer: glium::VertexBuffer, index_buffer: glium::IndexBuffer, programs: [glium::Program; shaders::FRAGMENT_COUNT], - #[cfg(feature = "egl")] - egl_buffer_reader: Rc>>, + buffer_loader: BufferUtils, + pub hardware_cursor: AtomicBool, log: Logger, } @@ -67,56 +59,7 @@ impl GliumDrawer { } impl> + GLGraphicsBackend + 'static> GliumDrawer { - #[cfg(feature = "egl")] - pub fn init( - backend: T, - egl_buffer_reader: Rc>>, - log: Logger, - ) -> GliumDrawer { - let display = backend.into(); - - // building the vertex buffer, which contains all the vertices that we will draw - let vertex_buffer = glium::VertexBuffer::new( - &display, - &[ - Vertex { - position: [0.0, 0.0], - tex_coords: [0.0, 0.0], - }, - Vertex { - position: [0.0, 1.0], - tex_coords: [0.0, 1.0], - }, - Vertex { - position: [1.0, 1.0], - tex_coords: [1.0, 1.0], - }, - Vertex { - position: [1.0, 0.0], - tex_coords: [1.0, 0.0], - }, - ], - ) - .unwrap(); - - // building the index buffer - let index_buffer = - glium::IndexBuffer::new(&display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap(); - - let programs = opengl_programs!(&display); - - GliumDrawer { - display, - vertex_buffer, - index_buffer, - programs, - egl_buffer_reader, - log, - } - } - - #[cfg(not(feature = "egl"))] - pub fn init(backend: T, log: Logger) -> GliumDrawer { + pub fn init(backend: T, buffer_loader: BufferUtils, log: Logger) -> GliumDrawer { let display = backend.into(); // building the vertex buffer, which contains all the vertices that we will draw @@ -150,99 +93,98 @@ impl> + GLGraphicsBackend + 'static> GliumDrawer let programs = opengl_programs!(&display); GliumDrawer { + id: BACKEND_COUNTER.fetch_add(1, Ordering::AcqRel), display, vertex_buffer, index_buffer, programs, + buffer_loader, + hardware_cursor: AtomicBool::new(false), log, } } } -impl GliumDrawer { - #[cfg(feature = "egl")] - pub fn texture_from_buffer(&self, buffer: wl_buffer::WlBuffer) -> Result { - // try to retrieve the egl contents of this buffer - let images = if let Some(display) = &self.egl_buffer_reader.borrow().as_ref() { - display.egl_buffer_contents(buffer) - } else { - Err(BufferAccessError::NotManaged( - buffer, - smithay::backend::egl::EGLError::BadDisplay, - )) +impl GliumDrawer { + pub fn draw_hardware_cursor( + &self, + cursor: &::CursorFormat, + hotspot: (u32, u32), + position: (i32, i32), + ) { + let (x, y) = position; + let _ = self.display.borrow().set_cursor_position(x as u32, y as u32); + if !self.hardware_cursor.swap(true, Ordering::SeqCst) + && self + .display + .borrow() + .set_cursor_representation(cursor, hotspot) + .is_err() + { + warn!(self.log, "Failed to upload hardware cursor",); + } + } + + pub fn draw_software_cursor( + &self, + frame: &mut Frame, + surface: &wl_surface::WlSurface, + (x, y): (i32, i32), + token: MyCompositorToken, + ) { + let (dx, dy) = match token.with_role_data::(surface, |data| data.hotspot) { + Ok(h) => h, + Err(_) => { + warn!( + self.log, + "Trying to display as a cursor a surface that does not have the CursorImage role." + ); + (0, 0) + } }; - match images { - Ok(images) => { - // we have an EGL buffer - let format = match images.format { - Format::RGB => UncompressedFloatFormat::U8U8U8, - Format::RGBA => UncompressedFloatFormat::U8U8U8U8, - _ => { - warn!(self.log, "Unsupported EGL buffer format"; "format" => format!("{:?}", images.format)); - return Err(()); - } - }; - let opengl_texture = Texture2d::empty_with_format( - &self.display, - format, - MipmapsOption::NoMipmap, - images.width, - images.height, - ) - .unwrap(); - unsafe { - images - .bind_to_texture(0, opengl_texture.get_id(), &*self.display.borrow()) - .expect("Failed to bind to texture"); - } - Ok(TextureMetadata { - texture: opengl_texture, - fragment: crate::shaders::BUFFER_RGBA, - y_inverted: images.y_inverted, - dimensions: (images.width, images.height), - images: Some(images), // I guess we need to keep this alive ? - }) - } - Err(BufferAccessError::NotManaged(buffer, _)) => { - // this is not an EGL buffer, try SHM - self.texture_from_shm_buffer(buffer) - } - Err(err) => { - error!(self.log, "EGL error"; "err" => format!("{:?}", err)); - Err(()) - } + let screen_dimensions = self.borrow().get_framebuffer_dimensions(); + self.draw_surface_tree(frame, surface, (x - dx, y - dy), token, screen_dimensions); + self.clear_cursor() + } + + pub fn clear_cursor(&self) { + if self.hardware_cursor.swap(false, Ordering::SeqCst) + && self.display.borrow().clear_cursor_representation().is_err() + { + warn!(self.log, "Failed to clear cursor"); } } +} - #[cfg(not(feature = "egl"))] - pub fn texture_from_buffer(&self, buffer: wl_buffer::WlBuffer) -> Result { - self.texture_from_shm_buffer(buffer) - } - - fn texture_from_shm_buffer(&self, buffer: wl_buffer::WlBuffer) -> Result { - match shm_buffer_contents(&buffer, |slice, data| { - crate::shm_load::load_shm_buffer(data, slice) - .map(|(image, kind)| (Texture2d::new(&self.display, image).unwrap(), kind, data)) - }) { - Ok(Ok((texture, kind, data))) => Ok(TextureMetadata { - texture, - fragment: kind, - y_inverted: false, - dimensions: (data.width as u32, data.height as u32), - #[cfg(feature = "egl")] - images: None, - }), - Ok(Err(format)) => { - warn!(self.log, "Unsupported SHM buffer format"; "format" => format!("{:?}", format)); - Err(()) +// I would love to do this (check on !CursorBackend), but this is essentially specialization... +// And since this is just an example compositor, it seems we require now, +// that for the use of software cursors we need the hardware cursor trait (to do automatic cleanup..) +/* +impl GliumDrawer { + pub fn draw_software_cursor( + &self, + frame: &mut Frame, + surface: &wl_surface::WlSurface, + (x, y): (i32, i32), + token: MyCompositorToken, + ) { + let (dx, dy) = match token.with_role_data::(surface, |data| data.hotspot) { + Ok(h) => h, + Err(_) => { + warn!( + self.log, + "Trying to display as a cursor a surface that does not have the CursorImage role." + ); + (0, 0) } - Err(err) => { - warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err)); - Err(()) - } - } + }; + let screen_dimensions = self.borrow().get_framebuffer_dimensions(); + self.draw_surface_tree(frame, surface, (x - dx, y - dy), token, screen_dimensions); } +} +*/ +impl GliumDrawer { pub fn render_texture(&self, target: &mut Frame, spec: RenderTextureSpec<'_>) { let xscale = 2.0 * (spec.surface_dimensions.0 as f32) / (spec.screen_size.0 as f32); let mut yscale = -2.0 * (spec.surface_dimensions.1 as f32) / (spec.screen_size.1 as f32); @@ -295,15 +237,6 @@ pub struct RenderTextureSpec<'a> { blending: glium::Blend, } -pub struct TextureMetadata { - pub texture: Texture2d, - pub fragment: usize, - pub y_inverted: bool, - pub dimensions: (u32, u32), - #[cfg(feature = "egl")] - images: Option, -} - impl GliumDrawer { fn draw_surface_tree( &self, @@ -322,25 +255,12 @@ impl GliumDrawer { let mut data = data.borrow_mut(); if data.texture.is_none() { if let Some(buffer) = data.current_state.buffer.take() { - if let Ok(m) = self.texture_from_buffer(buffer.clone()) { - // release the buffer if it was an SHM buffer - #[cfg(feature = "egl")] - { - if m.images.is_none() { - buffer.release(); - } - } - #[cfg(not(feature = "egl"))] - { - buffer.release(); - } - - data.texture = Some(m); - } else { + match self.buffer_loader.load_buffer(buffer) { + Ok(m) => data.texture = Some(m), // there was an error reading the buffer, release it, we // already logged the error - buffer.release(); - } + Err(buffer) => buffer.release(), + }; } } // Now, should we be drawn ? @@ -362,36 +282,42 @@ impl GliumDrawer { }, |_surface, attributes, role, &(mut x, mut y)| { if let Some(ref data) = attributes.user_data.get::>() { - let data = data.borrow(); - if let Some(ref metadata) = data.texture { - // we need to re-extract the subsurface offset, as the previous closure - // only passes it to our children - if Role::::has(role) { - x += data.current_state.sub_location.0; - y += data.current_state.sub_location.1; - } - self.render_texture( - frame, - RenderTextureSpec { - texture: &metadata.texture, - texture_kind: metadata.fragment, - y_inverted: metadata.y_inverted, - surface_dimensions: metadata.dimensions, - surface_location: (x, y), - screen_size: screen_dimensions, - blending: ::glium::Blend { - color: ::glium::BlendingFunction::Addition { - source: ::glium::LinearBlendingFactor::One, - destination: ::glium::LinearBlendingFactor::OneMinusSourceAlpha, + let mut data = data.borrow_mut(); + let (sub_x, sub_y) = data.current_state.sub_location; + if let Some(buffer_textures) = data.texture.as_mut() { + let texture_kind = buffer_textures.fragment; + let y_inverted = buffer_textures.y_inverted; + let surface_dimensions = buffer_textures.dimensions; + if let Ok(ref texture) = buffer_textures.load_texture(&self) { + // we need to re-extract the subsurface offset, as the previous closure + // only passes it to our children + if Role::::has(role) { + x += sub_x; + y += sub_y; + } + self.render_texture( + frame, + RenderTextureSpec { + texture: &texture, + texture_kind, + y_inverted, + surface_dimensions, + surface_location: (x, y), + screen_size: screen_dimensions, + blending: ::glium::Blend { + color: ::glium::BlendingFunction::Addition { + source: ::glium::LinearBlendingFactor::One, + destination: ::glium::LinearBlendingFactor::OneMinusSourceAlpha, + }, + alpha: ::glium::BlendingFunction::Addition { + source: ::glium::LinearBlendingFactor::One, + destination: ::glium::LinearBlendingFactor::OneMinusSourceAlpha, + }, + ..Default::default() }, - alpha: ::glium::BlendingFunction::Addition { - source: ::glium::LinearBlendingFactor::One, - destination: ::glium::LinearBlendingFactor::OneMinusSourceAlpha, - }, - ..Default::default() }, - }, - ); + ); + } } } }, @@ -403,47 +329,36 @@ impl GliumDrawer { &self, frame: &mut Frame, window_map: &MyWindowMap, + output_rect: Option, compositor_token: MyCompositorToken, ) { // redraw the frame, in a simple but inneficient way { let screen_dimensions = self.borrow().get_framebuffer_dimensions(); - window_map.with_windows_from_bottom_to_top(|toplevel_surface, initial_place| { - if let Some(wl_surface) = toplevel_surface.get_surface() { - // this surface is a root of a subsurface tree that needs to be drawn - self.draw_surface_tree( - frame, - &wl_surface, - initial_place, - compositor_token, - screen_dimensions, - ); - } - }); + window_map.with_windows_from_bottom_to_top( + |toplevel_surface, mut initial_place, bounding_box| { + // skip windows that do not overlap with a given output + if let Some(output) = output_rect { + if !output.overlaps(bounding_box) { + return; + } + initial_place.0 -= output.x; + } + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + self.draw_surface_tree( + frame, + &wl_surface, + initial_place, + compositor_token, + screen_dimensions, + ); + } + }, + ); } } - pub fn draw_cursor( - &self, - frame: &mut Frame, - surface: &wl_surface::WlSurface, - (x, y): (i32, i32), - token: MyCompositorToken, - ) { - let (dx, dy) = match token.with_role_data::(surface, |data| data.hotspot) { - Ok(h) => h, - Err(_) => { - warn!( - self.log, - "Trying to display as a cursor a surface that does not have the CursorImage role." - ); - (0, 0) - } - }; - let screen_dimensions = self.borrow().get_framebuffer_dimensions(); - self.draw_surface_tree(frame, surface, (x - dx, y - dy), token, screen_dimensions); - } - pub fn draw_dnd_icon( &self, frame: &mut Frame, diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 2657982..9d288e6 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -82,6 +82,20 @@ impl AnvilState { ); } } + #[cfg(feature = "udev")] + KeyAction::Screen(num) => { + let output_map = self.output_map.as_ref().unwrap(); + let outputs = output_map.borrow(); + if let Some(output) = outputs.get(num) { + let x = outputs + .iter() + .take(num) + .fold(0, |acc, output| acc + output.size.0) as f64 + + (output.size.0 as f64 / 2.0); + let y = output.size.1 as f64 / 2.0; + *self.pointer_location.borrow_mut() = (x as f64, y as f64) + } + } _ => (), } } @@ -92,10 +106,14 @@ impl AnvilState { let mut location = self.pointer_location.borrow_mut(); location.0 += x as f64; location.1 += y as f64; - // clamp to screen limits - // this event is never generated by winit so self.screen_size is relevant - location.0 = (location.0).max(0.0).min(self.screen_size.0 as f64); - location.1 = (location.1).max(0.0).min(self.screen_size.1 as f64); + + #[cfg(feature = "udev")] + { + // clamp to screen limits + // this event is never generated by winit + *location = self.clamp_coords(*location); + } + let under = self .window_map .borrow() @@ -110,8 +128,11 @@ impl AnvilState { { if self.session.is_some() { // we are started on a tty - let (ux, uy) = evt.position_transformed(self.screen_size); - (ux as f64, uy as f64) + let x = self.pointer_location.borrow().0; + let screen_size = self.current_output_size(x); + // monitor coordinates + let (ux, uy) = evt.position_transformed(screen_size); + ((ux + self.current_output_offset(x)) as f64, uy as f64) } else { // we are started in winit evt.position() @@ -128,6 +149,64 @@ impl AnvilState { self.pointer.motion((x, y), under, serial, evt.time()); } + #[cfg(feature = "udev")] + fn clamp_coords(&self, pos: (f64, f64)) -> (f64, f64) { + let output_map = self.output_map.as_ref().unwrap(); + let outputs = output_map.borrow(); + + if outputs.len() == 0 { + return pos; + } + + let (mut x, mut y) = pos; + // max_x is the sum of the width of all outputs + let max_x = outputs.iter().fold(0u32, |acc, output| acc + output.size.0); + x = x.max(0.0).min(max_x as f64); + + // max y depends on the current output + let max_y = self.current_output_size(x).1; + y = y.max(0.0).min(max_y as f64); + + (x, y) + } + + #[cfg(feature = "udev")] + fn current_output_idx(&self, x: f64) -> usize { + let output_map = self.output_map.as_ref().unwrap(); + let outputs = output_map.borrow(); + + outputs + .iter() + // map each output to their x position + .scan(0u32, |acc, output| { + let curr_x = *acc; + *acc += output.size.0; + Some(curr_x) + }) + // get an index + .enumerate() + // find the first one with a greater x + .find(|(_idx, x_pos)| *x_pos as f64 > x) + // the previous output is the one we are on + .map(|(idx, _)| idx - 1) + .unwrap_or(outputs.len() - 1) + } + #[cfg(feature = "udev")] + fn current_output_size(&self, x: f64) -> (u32, u32) { + let output_map = self.output_map.as_ref().unwrap(); + let outputs = output_map.borrow(); + outputs[self.current_output_idx(x)].size + } + #[cfg(feature = "udev")] + fn current_output_offset(&self, x: f64) -> u32 { + let output_map = self.output_map.as_ref().unwrap(); + let outputs = output_map.borrow(); + outputs + .iter() + .take(self.current_output_idx(x)) + .fold(0u32, |acc, output| acc + output.size.0) + } + fn on_pointer_button(&mut self, evt: B::PointerButtonEvent) { let serial = SCOUNTER.next_serial(); let button = match evt.button() { @@ -200,6 +279,8 @@ enum KeyAction { VtSwitch(i32), /// run a command Run(String), + /// Switch the current screen + Screen(usize), /// Forward the key to the client Forward, /// Do nothing more @@ -219,6 +300,8 @@ fn process_keyboard_shortcut(modifiers: ModifiersState, keysym: Keysym) -> KeyAc } else if modifiers.logo && keysym == xkb::KEY_Return { // run terminal KeyAction::Run("weston-terminal".into()) + } else if modifiers.logo && keysym >= xkb::KEY_1 && keysym <= xkb::KEY_9 { + KeyAction::Screen((keysym - xkb::KEY_1) as usize) } else { KeyAction::Forward } diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index d960393..fe297cf 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -654,7 +654,7 @@ pub struct CommitedState { #[derive(Default)] pub struct SurfaceData { - pub texture: Option, + pub texture: Option, pub geometry: Option, pub resize_state: ResizeState, /// Minimum width and height, as requested by the surface. @@ -673,7 +673,7 @@ impl SurfaceData { /// Apply a next state into the surface current state pub fn apply_state(&mut self, next_state: CommitedState) { if Self::merge_state(&mut self.current_state, next_state) { - self.texture = None; + let _ = self.texture.take(); } } diff --git a/anvil/src/shm_load.rs b/anvil/src/shm_load.rs index 1a9f26e..abb41c6 100644 --- a/anvil/src/shm_load.rs +++ b/anvil/src/shm_load.rs @@ -32,13 +32,7 @@ pub fn load_shm_buffer(data: BufferData, pool: &[u8]) -> Result<(RawImage2d<'_, }; // sharders format need to be reversed to account for endianness - let (client_format, fragment) = match data.format { - Format::Argb8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_BGRA), - Format::Xrgb8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_BGRX), - Format::Rgba8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_ABGR), - Format::Rgbx8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_XBGR), - _ => return Err(data.format), - }; + let (client_format, fragment) = load_format(data.format)?; Ok(( RawImage2d { data: slice, @@ -49,3 +43,13 @@ pub fn load_shm_buffer(data: BufferData, pool: &[u8]) -> Result<(RawImage2d<'_, fragment, )) } + +pub fn load_format(format: Format) -> Result<(ClientFormat, usize), Format> { + Ok(match format { + Format::Argb8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_BGRA), + Format::Xrgb8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_BGRX), + Format::Rgba8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_ABGR), + Format::Rgbx8888 => (ClientFormat::U8U8U8U8, crate::shaders::BUFFER_XBGR), + _ => return Err(format), + }) +} diff --git a/anvil/src/state.rs b/anvil/src/state.rs index 671601b..48bc6ce 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -8,7 +8,6 @@ use std::{ }; use smithay::{ - backend::session::auto::AutoSession, reexports::{ calloop::{ generic::{Fd, Generic}, @@ -25,8 +24,10 @@ use smithay::{ }; #[cfg(feature = "udev")] -use smithay::backend::session::Session; +use smithay::backend::session::{auto::AutoSession, Session}; +#[cfg(feature = "udev")] +use crate::udev::MyOutput; use crate::{buffer_utils::BufferUtils, shell::init_shell}; pub struct AnvilState { @@ -43,7 +44,8 @@ pub struct AnvilState { pub keyboard: KeyboardHandle, pub pointer_location: Rc>, pub cursor_status: Arc>, - pub screen_size: (u32, u32), + #[cfg(feature = "udev")] + pub output_map: Option>>>, pub seat_name: String, #[cfg(feature = "udev")] pub session: Option, @@ -58,6 +60,8 @@ impl AnvilState { buffer_utils: BufferUtils, #[cfg(feature = "udev")] session: Option, #[cfg(not(feature = "udev"))] _session: Option<()>, + #[cfg(feature = "udev")] output_map: Option>>>, + #[cfg(not(feature = "udev"))] _output_map: Option<()>, log: slog::Logger, ) -> AnvilState { // init the wayland connection @@ -126,7 +130,7 @@ impl AnvilState { "anvil".into() }; #[cfg(not(feature = "udev"))] - let seat_name = "anvil".into(); + let seat_name: String = "anvil".into(); let (mut seat, _) = Seat::new( &mut display.borrow_mut(), @@ -162,7 +166,8 @@ impl AnvilState { keyboard, cursor_status, pointer_location: Rc::new(RefCell::new((0.0, 0.0))), - screen_size: (1920, 1080), + #[cfg(feature = "udev")] + output_map, seat_name, #[cfg(feature = "udev")] session, diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index dad9b04..0f33b28 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -50,10 +50,11 @@ use smithay::{ nix::{fcntl::OFlag, sys::stat::dev_t}, wayland_server::{ protocol::{wl_output, wl_surface}, - Display, + Display, Global, }, }, signaling::{Linkable, SignalToken, Signaler}, + utils::Rectangle, wayland::{ compositor::CompositorToken, output::{Mode, Output, PhysicalProperties}, @@ -112,6 +113,8 @@ pub fn run_udev( #[cfg(not(feature = "egl"))] let buffer_utils = BufferUtils::new(log.clone()); + let output_map = Rc::new(RefCell::new(Vec::new())); + /* * Initialize session */ @@ -124,8 +127,9 @@ pub fn run_udev( let mut state = AnvilState::init( display.clone(), event_loop.handle(), - buffer_utils, + buffer_utils.clone(), Some(session), + Some(output_map.clone()), log.clone(), ); @@ -139,10 +143,12 @@ pub fn run_udev( let mut udev_handler = UdevHandlerImpl { compositor_token: state.ctoken, + buffer_utils, #[cfg(feature = "egl")] egl_buffer_reader, session: state.session.clone().unwrap(), backends: HashMap::new(), + output_map, display: display.clone(), primary_gpu, window_map: state.window_map.clone(), @@ -158,34 +164,6 @@ pub fn run_udev( /* * Initialize a fake output (we render one screen to every device in this example) */ - let (output, _output_global) = Output::new( - &mut display.borrow_mut(), - "Drm".into(), - PhysicalProperties { - width: 0, - height: 0, - subpixel: wl_output::Subpixel::Unknown, - make: "Smithay".into(), - model: "Generic DRM".into(), - }, - log.clone(), - ); - - let (w, h) = (1920, 1080); // Hardcode full-hd res - output.change_current_state( - Some(Mode { - width: w as i32, - height: h as i32, - refresh: 60_000, - }), - None, - None, - ); - output.set_preferred(Mode { - width: w as i32, - height: h as i32, - refresh: 60_000, - }); /* * Initialize libinput backend @@ -250,6 +228,68 @@ pub fn run_udev( Ok(()) } +pub struct MyOutput { + pub device_id: dev_t, + pub crtc: crtc::Handle, + pub size: (u32, u32), + _wl: Output, + global: Option>, +} + +impl MyOutput { + fn new( + display: &mut Display, + device_id: dev_t, + crtc: crtc::Handle, + conn: ConnectorInfo, + logger: ::slog::Logger, + ) -> MyOutput { + let (output, global) = Output::new( + display, + format!("{:?}", conn.interface()), + PhysicalProperties { + width: conn.size().unwrap_or((0, 0)).0 as i32, + height: conn.size().unwrap_or((0, 0)).1 as i32, + subpixel: wl_output::Subpixel::Unknown, + make: "Smithay".into(), + model: "Generic DRM".into(), + }, + logger, + ); + + let mode = conn.modes()[0]; + let (w, h) = mode.size(); + output.change_current_state( + Some(Mode { + width: w as i32, + height: h as i32, + refresh: (mode.vrefresh() * 1000) as i32, + }), + None, + None, + ); + output.set_preferred(Mode { + width: w as i32, + height: h as i32, + refresh: (mode.vrefresh() * 1000) as i32, + }); + + MyOutput { + device_id, + crtc, + size: (w as u32, h as u32), + _wl: output, + global: Some(global), + } + } +} + +impl Drop for MyOutput { + fn drop(&mut self) { + self.global.take().unwrap().destroy(); + } +} + struct BackendData { _restart_token: SignalToken, event_source: Source>, @@ -258,6 +298,7 @@ struct BackendData { struct UdevHandlerImpl { compositor_token: CompositorToken, + buffer_utils: BufferUtils, #[cfg(feature = "egl")] egl_buffer_reader: Rc>>, session: AutoSession, @@ -265,6 +306,7 @@ struct UdevHandlerImpl { display: Rc>, primary_gpu: Option, window_map: Rc>, + output_map: Rc>>, pointer_location: Rc>, pointer_image: ImageBuffer, Vec>, cursor_status: Arc>, @@ -275,10 +317,11 @@ struct UdevHandlerImpl { } impl UdevHandlerImpl { - #[cfg(feature = "egl")] pub fn scan_connectors( device: &mut RenderDevice, - egl_buffer_reader: Rc>>, + buffer_utils: &BufferUtils, + display: &mut Display, + output_map: &mut Vec, logger: &::slog::Logger, ) -> HashMap>> { // Get a set of all modesetting resource handles (excluding planes): @@ -310,9 +353,16 @@ impl UdevHandlerImpl { device .create_surface(crtc, connector_info.modes()[0], &[connector_info.handle()]) .unwrap(), - egl_buffer_reader.clone(), + buffer_utils.clone(), logger.clone(), ); + output_map.push(MyOutput::new( + display, + device.device_id(), + crtc, + connector_info, + logger.clone(), + )); entry.insert(Rc::new(renderer)); break 'outer; @@ -323,53 +373,10 @@ impl UdevHandlerImpl { backends } - - #[cfg(not(feature = "egl"))] - pub fn scan_connectors( - device: &mut RenderDevice, - logger: &::slog::Logger, - ) -> HashMap>> { - // Get a set of all modesetting resource handles (excluding planes): - let res_handles = device.resource_handles().unwrap(); - - // Use first connected connector - let connector_infos: Vec = res_handles - .connectors() - .iter() - .map(|conn| device.get_connector_info(*conn).unwrap()) - .filter(|conn| conn.state() == ConnectorState::Connected) - .inspect(|conn| info!(logger, "Connected: {:?}", conn.interface())) - .collect(); - - let mut backends = HashMap::new(); - - // very naive way of finding good crtc/encoder/connector combinations. This problem is np-complete - for connector_info in connector_infos { - let encoder_infos = connector_info - .encoders() - .iter() - .filter_map(|e| *e) - .flat_map(|encoder_handle| device.get_encoder_info(encoder_handle)) - .collect::>(); - 'outer: for encoder_info in encoder_infos { - for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { - if !backends.contains_key(&crtc) { - let renderer = - GliumDrawer::init(device.create_surface(crtc).unwrap(), logger.clone()); - - backends.insert(crtc, Rc::new(renderer)); - break 'outer; - } - } - } - } - - backends - } } impl UdevHandlerImpl { - fn device_added(&mut self, _device: dev_t, path: PathBuf) { + fn device_added(&mut self, device_id: dev_t, path: PathBuf) { // Try to open the device if let Some(mut device) = self .session @@ -412,21 +419,20 @@ impl UdevHandlerImpl { #[cfg(feature = "egl")] { if path.canonicalize().ok() == self.primary_gpu { + info!( + self.logger, + "Initializing EGL Hardware Acceleration via {:?}", path + ); *self.egl_buffer_reader.borrow_mut() = device.bind_wl_display(&*self.display.borrow()).ok(); } } - #[cfg(feature = "egl")] - let backends = Rc::new(RefCell::new(UdevHandlerImpl::::scan_connectors( - &mut device, - self.egl_buffer_reader.clone(), - &self.logger, - ))); - - #[cfg(not(feature = "egl"))] let backends = Rc::new(RefCell::new(UdevHandlerImpl::::scan_connectors( &mut device, + &self.buffer_utils, + &mut *self.display.borrow_mut(), + &mut *self.output_map.borrow_mut(), &self.logger, ))); @@ -434,10 +440,13 @@ impl UdevHandlerImpl { // Note: if you replicate this (very simple) structure, it is rather easy // to introduce reference cycles with Rc. Be sure about your drop order let renderer = Rc::new(DrmRenderer { + device_id, compositor_token: self.compositor_token, backends: backends.clone(), window_map: self.window_map.clone(), + output_map: self.output_map.clone(), pointer_location: self.pointer_location.clone(), + pointer_image: self.pointer_image.clone(), cursor_status: self.cursor_status.clone(), dnd_icon: self.dnd_icon.clone(), logger: self.logger.clone(), @@ -462,12 +471,6 @@ impl UdevHandlerImpl { .unwrap(); for renderer in backends.borrow_mut().values() { - // create cursor - renderer - .borrow() - .set_cursor_representation(&self.pointer_image, (2, 2)) - .unwrap(); - // render first frame schedule_initial_render(renderer.clone(), &self.loop_handle); } @@ -485,28 +488,25 @@ impl UdevHandlerImpl { fn device_changed(&mut self, device: dev_t) { //quick and dirty, just re-init all backends + let buffer_utils = &self.buffer_utils; if let Some(ref mut backend_data) = self.backends.get_mut(&device) { let logger = &self.logger; - let pointer_image = &self.pointer_image; - let egl_buffer_reader = self.egl_buffer_reader.clone(); let loop_handle = self.loop_handle.clone(); + let mut display = self.display.borrow_mut(); + let mut output_map = self.output_map.borrow_mut(); + output_map.retain(|output| output.device_id != device); self.loop_handle .with_source(&backend_data.event_source, |source| { let mut backends = backend_data.surfaces.borrow_mut(); - #[cfg(feature = "egl")] - let new_backends = - UdevHandlerImpl::::scan_connectors(&mut source.file, egl_buffer_reader, logger); - #[cfg(not(feature = "egl"))] - let new_backends = UdevHandlerImpl::::scan_connectors(&mut source.file, logger); - *backends = new_backends; + *backends = UdevHandlerImpl::::scan_connectors( + &mut source.file, + buffer_utils, + &mut *display, + &mut *output_map, + logger, + ); for renderer in backends.values() { - // create cursor - renderer - .borrow() - .set_cursor_representation(pointer_image, (2, 2)) - .unwrap(); - // render first frame schedule_initial_render(renderer.clone(), &loop_handle); } @@ -520,6 +520,10 @@ impl UdevHandlerImpl { // drop surfaces backend_data.surfaces.borrow_mut().clear(); debug!(self.logger, "Surfaces dropped"); + // clear outputs + self.output_map + .borrow_mut() + .retain(|output| output.device_id != device); let device = self.loop_handle.remove(backend_data.event_source).unwrap(); @@ -568,10 +572,13 @@ impl DrmRendererSessionListener { } pub struct DrmRenderer { + device_id: dev_t, compositor_token: CompositorToken, backends: Rc>>>>, window_map: Rc>, + output_map: Rc>>, pointer_location: Rc>, + pointer_image: ImageBuffer, Vec>, cursor_status: Arc>, dnd_icon: Arc>>, logger: ::slog::Logger, @@ -590,56 +597,82 @@ impl DrmRenderer { evt_handle: Option<&LoopHandle>, ) { if let Some(drawer) = self.backends.borrow().get(&crtc) { - { - let (x, y) = *self.pointer_location.borrow(); - let _ = drawer - .borrow() - .set_cursor_position(x.trunc().abs() as u32, y.trunc().abs() as u32); - } + // get output coordinates + let (x, y) = self + .output_map + .borrow() + .iter() + .take_while(|output| output.device_id != self.device_id || output.crtc != crtc) + .fold((0u32, 0u32), |pos, output| (pos.0 + output.size.0, pos.1)); + let (width, height) = self + .output_map + .borrow() + .iter() + .find(|output| output.device_id == self.device_id && output.crtc == crtc) + .map(|output| output.size) + .unwrap_or((0, 0)); // in this case the output will be removed. // and draw in sync with our monitor let mut frame = drawer.draw(); frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None); // draw the surfaces - drawer.draw_windows(&mut frame, &*self.window_map.borrow(), self.compositor_token); - let (x, y) = *self.pointer_location.borrow(); - // draw the dnd icon if applicable - { - let guard = self.dnd_icon.lock().unwrap(); - if let Some(ref surface) = *guard { - if surface.as_ref().is_alive() { - drawer.draw_dnd_icon( - &mut frame, - surface, - (x as i32, y as i32), - self.compositor_token, - ); + drawer.draw_windows( + &mut frame, + &*self.window_map.borrow(), + Some(Rectangle { + x: x as i32, + y: y as i32, + width: width as i32, + height: height as i32, + }), + self.compositor_token, + ); + + // get pointer coordinates + let (ptr_x, ptr_y) = *self.pointer_location.borrow(); + let ptr_x = ptr_x.trunc().abs() as i32 - x as i32; + let ptr_y = ptr_y.trunc().abs() as i32 - y as i32; + + // set cursor + if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 { + let _ = drawer.borrow().set_cursor_position(ptr_x as u32, ptr_y as u32); + + // draw the dnd icon if applicable + { + let guard = self.dnd_icon.lock().unwrap(); + if let Some(ref surface) = *guard { + if surface.as_ref().is_alive() { + drawer.draw_dnd_icon(&mut frame, surface, (ptr_x, ptr_y), self.compositor_token); + } } } - } - // draw the cursor as relevant - { - let mut guard = self.cursor_status.lock().unwrap(); - // reset the cursor if the surface is no longer alive - let mut reset = false; - if let CursorImageStatus::Image(ref surface) = *guard { - reset = !surface.as_ref().is_alive(); - } - if reset { - *guard = CursorImageStatus::Default; - } - if let CursorImageStatus::Image(ref surface) = *guard { - drawer.draw_cursor(&mut frame, surface, (x as i32, y as i32), self.compositor_token); + // draw the cursor as relevant + { + let mut guard = self.cursor_status.lock().unwrap(); + // reset the cursor if the surface is no longer alive + let mut reset = false; + if let CursorImageStatus::Image(ref surface) = *guard { + reset = !surface.as_ref().is_alive(); + } + if reset { + *guard = CursorImageStatus::Default; + } + if let CursorImageStatus::Image(ref surface) = *guard { + drawer.draw_software_cursor( + &mut frame, + surface, + (ptr_x, ptr_y), + self.compositor_token, + ); + } else { + drawer.draw_hardware_cursor(&self.pointer_image, (2, 2), (ptr_x, ptr_y)); + } } + } else { + drawer.clear_cursor(); } - let result = frame.finish(); - if result.is_ok() { - // Send frame events so that client start drawing their next frame - self.window_map.borrow().send_frames(SCOUNTER.next_serial()); - } - - if let Err(err) = result { + if let Err(err) = frame.finish() { warn!(self.logger, "Error during rendering: {:?}", err); let reschedule = match err { SwapBuffersError::AlreadySwapped => false, @@ -692,6 +725,7 @@ impl DrmRenderer { } } } else { + // TODO: only send drawn windows the frames callback // Send frame events so that client start drawing their next frame self.window_map.borrow().send_frames(SCOUNTER.next_serial()); } diff --git a/anvil/src/window_map.rs b/anvil/src/window_map.rs index fdfdc19..9eeec69 100644 --- a/anvil/src/window_map.rs +++ b/anvil/src/window_map.rs @@ -248,10 +248,10 @@ where pub fn with_windows_from_bottom_to_top(&self, mut f: Func) where - Func: FnMut(&Kind, (i32, i32)), + Func: FnMut(&Kind, (i32, i32), &Rectangle), { for w in self.windows.iter().rev() { - f(&w.toplevel, w.location) + f(&w.toplevel, w.location, &w.bbox) } } diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index bd2d549..7c876b0 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -1,10 +1,13 @@ use std::{cell::RefCell, rc::Rc, sync::atomic::Ordering, time::Duration}; +#[cfg(feature = "egl")] +use smithay::backend::egl::EGLGraphicsBackend; use smithay::{ - backend::{egl::EGLGraphicsBackend, graphics::gl::GLGraphicsBackend, input::InputBackend, winit}, + backend::{graphics::gl::GLGraphicsBackend, input::InputBackend, winit}, reexports::{ calloop::EventLoop, wayland_server::{protocol::wl_output, Display}, + winit::window::CursorIcon, }, wayland::{ output::{Mode, Output, PhysicalProperties}, @@ -38,17 +41,14 @@ pub fn run_winit( }, )); - let (w, h) = renderer.get_framebuffer_dimensions(); - #[cfg(feature = "egl")] - let drawer = GliumDrawer::init(renderer, egl_buffer_reader.clone(), log.clone()); - #[cfg(not(feature = "egl"))] - let drawer = GliumDrawer::init(renderer, log.clone()); - #[cfg(feature = "egl")] let buffer_utils = BufferUtils::new(egl_buffer_reader, log.clone()); #[cfg(not(feature = "egl"))] let buffer_utils = BufferUtils::new(log.clone()); + let (w, h) = renderer.get_framebuffer_dimensions(); + let drawer = GliumDrawer::init(renderer, buffer_utils.clone(), log.clone()); + /* * Initialize the globals */ @@ -58,6 +58,7 @@ pub fn run_winit( event_loop.handle(), buffer_utils, None, + None, log.clone(), ); @@ -103,7 +104,7 @@ pub fn run_winit( frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None); // draw the windows - drawer.draw_windows(&mut frame, &*state.window_map.borrow(), state.ctoken); + drawer.draw_windows(&mut frame, &*state.window_map.borrow(), None, state.ctoken); let (x, y) = *state.pointer_location.borrow(); // draw the dnd icon if any @@ -128,7 +129,9 @@ pub fn run_winit( } // draw as relevant if let CursorImageStatus::Image(ref surface) = *guard { - drawer.draw_cursor(&mut frame, surface, (x as i32, y as i32), state.ctoken); + drawer.draw_software_cursor(&mut frame, surface, (x as i32, y as i32), state.ctoken); + } else { + drawer.draw_hardware_cursor(&CursorIcon::Default, (0, 0), (x as i32, y as i32)); } } diff --git a/src/backend/drm/atomic/surface.rs b/src/backend/drm/atomic/surface.rs index 82bf2da..96c08ef 100644 --- a/src/backend/drm/atomic/surface.rs +++ b/src/backend/drm/atomic/surface.rs @@ -646,6 +646,15 @@ impl CursorBackend for AtomicDrmSurfaceInternal { Ok(()) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + let mut cursor = self.cursor.lock().unwrap(); + if let Some(fb) = cursor.framebuffer.take() { + let _ = self.destroy_framebuffer(fb); + } + + self.clear_plane(self.planes.cursor) + } } impl AtomicDrmSurfaceInternal { @@ -1022,6 +1031,10 @@ impl CursorBackend for AtomicDrmSurface { ) -> Result<(), Error> { self.0.set_cursor_representation(buffer, hotspot) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.0.clear_cursor_representation() + } } impl Surface for AtomicDrmSurface { diff --git a/src/backend/drm/common/fallback.rs b/src/backend/drm/common/fallback.rs index c1ddef7..bfc55d1 100644 --- a/src/backend/drm/common/fallback.rs +++ b/src/backend/drm/common/fallback.rs @@ -552,6 +552,7 @@ where fallback_surface_err_impl!(set_cursor_position, &Self, Result<(), EitherError>, x: u32, y: u32); fallback_surface_err_impl!(set_cursor_representation, &Self, Result<(), EitherError>, buffer: &Self::CursorFormat, hotspot: (u32, u32)); + fallback_surface_err_impl!(clear_cursor_representation, &Self, Result<(), EitherError>,); } #[cfg(feature = "renderer_gl")] diff --git a/src/backend/drm/egl/surface.rs b/src/backend/drm/egl/surface.rs index 4fabceb..2b852fb 100644 --- a/src/backend/drm/egl/surface.rs +++ b/src/backend/drm/egl/surface.rs @@ -92,6 +92,10 @@ where ) -> ::std::result::Result<(), Self::Error> { self.0.surface.set_cursor_representation(buffer, hotspot) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.0.surface.clear_cursor_representation() + } } #[cfg(feature = "renderer_gl")] diff --git a/src/backend/drm/eglstream/surface.rs b/src/backend/drm/eglstream/surface.rs index 3d70284..0d26d88 100644 --- a/src/backend/drm/eglstream/surface.rs +++ b/src/backend/drm/eglstream/surface.rs @@ -190,6 +190,18 @@ impl CursorBackend for EglStreamSurfaceInternal { Ok(()) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.crtc + .set_cursor(self.crtc.crtc(), Option::<&DumbBuffer>::None) + .compat() + .map_err(|source| DrmError::Access { + errmsg: "Failed to clear cursor", + dev: self.crtc.dev_path(), + source, + }) + .map_err(Error::Underlying) + } } /// egl stream surface for rendering @@ -565,6 +577,10 @@ impl CursorBackend for EglStreamSurface { ) -> Result<(), Self::Error> { self.0.set_cursor_representation(buffer, hotspot) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.0.clear_cursor_representation() + } } #[cfg(test)] diff --git a/src/backend/drm/gbm/surface.rs b/src/backend/drm/gbm/surface.rs index c3add63..56e500d 100644 --- a/src/backend/drm/gbm/surface.rs +++ b/src/backend/drm/gbm/surface.rs @@ -305,6 +305,23 @@ where *self.cursor.lock().unwrap() = (cursor, hotspot); Ok(()) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + *self.cursor.lock().unwrap() = ( + self.dev + .lock() + .unwrap() + .create_buffer_object( + 1, + 1, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ) + .map_err(Error::BufferCreationFailed)?, + (0, 0), + ); + self.crtc.clear_cursor_representation().map_err(Error::Underlying) + } } impl Drop for GbmSurfaceInternal { @@ -415,6 +432,10 @@ where ) -> Result<(), Self::Error> { self.0.set_cursor_representation(buffer, hotspot) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.0.clear_cursor_representation() + } } #[cfg(test)] diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/legacy/surface.rs index 383e12e..04cbe49 100644 --- a/src/backend/drm/legacy/surface.rs +++ b/src/backend/drm/legacy/surface.rs @@ -87,6 +87,16 @@ impl CursorBackend for LegacyDrmSurfaceInternal { Ok(()) } + + fn clear_cursor_representation(&self) -> Result<(), Error> { + self.set_cursor(self.crtc, Option::<&DumbBuffer>::None) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to set cursor", + dev: self.dev_path(), + source, + }) + } } impl Surface for LegacyDrmSurfaceInternal { @@ -498,6 +508,10 @@ impl CursorBackend for LegacyDrmSurface { ) -> Result<(), Error> { self.0.set_cursor_representation(buffer, hotspot) } + + fn clear_cursor_representation(&self) -> Result<(), Self::Error> { + self.0.clear_cursor_representation() + } } impl Surface for LegacyDrmSurface { diff --git a/src/backend/egl/display.rs b/src/backend/egl/display.rs index 79233d8..d5c733a 100644 --- a/src/backend/egl/display.rs +++ b/src/backend/egl/display.rs @@ -475,7 +475,7 @@ impl EGLBufferReader { /// to render it another way. pub fn egl_buffer_contents( &self, - buffer: WlBuffer, + buffer: &WlBuffer, ) -> ::std::result::Result { let mut format: i32 = 0; let query = wrap_egl_call(|| unsafe { @@ -486,9 +486,9 @@ impl EGLBufferReader { &mut format, ) }) - .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?; + .map_err(BufferAccessError::NotManaged)?; if query == ffi::egl::FALSE { - return Err(BufferAccessError::NotManaged(buffer, EGLError::BadParameter)); + return Err(BufferAccessError::NotManaged(EGLError::BadParameter)); } let format = match format { @@ -510,7 +510,7 @@ impl EGLBufferReader { &mut width, ) }) - .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?; + .map_err(BufferAccessError::NotManaged)?; let mut height: i32 = 0; wrap_egl_call(|| unsafe { @@ -521,7 +521,7 @@ impl EGLBufferReader { &mut height, ) }) - .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?; + .map_err(BufferAccessError::NotManaged)?; let mut inverted: i32 = 0; wrap_egl_call(|| unsafe { @@ -532,7 +532,7 @@ impl EGLBufferReader { &mut inverted, ) }) - .map_err(|source| BufferAccessError::NotManaged(buffer.clone(), source))?; + .map_err(BufferAccessError::NotManaged)?; let mut images = Vec::with_capacity(format.num_planes()); for i in 0..format.num_planes() { @@ -562,7 +562,6 @@ impl EGLBufferReader { y_inverted: inverted != 0, format, images, - buffer, #[cfg(feature = "renderer_gl")] gl: self.gl.clone(), }) diff --git a/src/backend/egl/mod.rs b/src/backend/egl/mod.rs index 7942e98..047bd64 100644 --- a/src/backend/egl/mod.rs +++ b/src/backend/egl/mod.rs @@ -26,7 +26,7 @@ use crate::backend::graphics::{ use nix::libc::c_uint; use std::fmt; #[cfg(feature = "wayland_frontend")] -use wayland_server::{protocol::wl_buffer::WlBuffer, Display}; +use wayland_server::Display; pub mod context; pub use self::context::EGLContext; @@ -87,8 +87,8 @@ pub enum BufferAccessError { #[error("The corresponding context was lost")] ContextLost, /// This buffer is not managed by the EGL buffer - #[error("This buffer is not managed by EGL. Err: {1:}")] - NotManaged(WlBuffer, #[source] EGLError), + #[error("This buffer is not managed by EGL. Err: {0:}")] + NotManaged(#[source] EGLError), /// Failed to create `EGLImages` from the buffer #[error("Failed to create EGLImages from the buffer. Err: {0:}")] EGLImageCreationFailed(#[source] EGLError), @@ -102,7 +102,7 @@ impl fmt::Debug for BufferAccessError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> { match *self { BufferAccessError::ContextLost => write!(formatter, "BufferAccessError::ContextLost"), - BufferAccessError::NotManaged(_, _) => write!(formatter, "BufferAccessError::NotManaged"), + BufferAccessError::NotManaged(_) => write!(formatter, "BufferAccessError::NotManaged"), BufferAccessError::EGLImageCreationFailed(_) => { write!(formatter, "BufferAccessError::EGLImageCreationFailed") } @@ -265,7 +265,6 @@ pub struct EGLImages { /// Format of these images pub format: Format, images: Vec, - buffer: WlBuffer, #[cfg(feature = "renderer_gl")] gl: gl_ffi::Gles2, } @@ -339,7 +338,6 @@ impl Drop for EGLImages { ffi::egl::DestroyImageKHR(**self.display, image); } } - self.buffer.release(); } } diff --git a/src/backend/graphics/cursor.rs b/src/backend/graphics/cursor.rs index f1ff46c..c0550dc 100644 --- a/src/backend/graphics/cursor.rs +++ b/src/backend/graphics/cursor.rs @@ -34,4 +34,7 @@ pub trait CursorBackend { cursor: &Self::CursorFormat, hotspot: (u32, u32), ) -> Result<(), Self::Error>; + + /// Clear the current cursor image drawn on the [`CursorBackend`]. + fn clear_cursor_representation(&self) -> Result<(), Self::Error>; } diff --git a/src/backend/session/mod.rs b/src/backend/session/mod.rs index 231081d..5c9206d 100644 --- a/src/backend/session/mod.rs +++ b/src/backend/session/mod.rs @@ -160,6 +160,9 @@ impl AsErrno for () { } pub mod auto; -mod dbus; pub mod direct; + +#[cfg(feature = "backend_session_logind")] +mod dbus; +#[cfg(feature = "backend_session_logind")] pub use self::dbus::*; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 239effd..2d90a24 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -270,6 +270,12 @@ impl CursorBackend for WinitGraphicsBackend { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); self.window.window().set_cursor_icon(*cursor); + self.window.window().set_cursor_visible(true); + Ok(()) + } + + fn clear_cursor_representation(&self) -> ::std::result::Result<(), ()> { + self.window.window().set_cursor_visible(false); Ok(()) } } diff --git a/src/reexports.rs b/src/reexports.rs index d5a4706..6c9aaee 100644 --- a/src/reexports.rs +++ b/src/reexports.rs @@ -21,3 +21,5 @@ pub use wayland_commons; pub use wayland_protocols; #[cfg(feature = "wayland_frontend")] pub use wayland_server; +#[cfg(feature = "backend_winit")] +pub use winit; diff --git a/src/utils/rectangle.rs b/src/utils/rectangle.rs index 5cd8357..575c565 100644 --- a/src/utils/rectangle.rs +++ b/src/utils/rectangle.rs @@ -17,4 +17,20 @@ impl Rectangle { let (x, y) = point; (x >= self.x) && (x < self.x + self.width) && (y >= self.y) && (y < self.y + self.height) } + + /// Checks whether a given rectangle overlaps with this one + pub fn overlaps(&self, other: &Rectangle) -> bool { + // if the rectangle is not outside of the other + // they must overlap + !( + // self is left of other + self.x + self.width < other.x + // self is right of other + || self.x > other.x + other.width + // self is above of other + || self.y + self.height < other.y + // self is below of other + || self.y > other.y + other.height + ) + } }