diff --git a/.travis.yml b/.travis.yml index 65c23e2..70fc95b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,15 +30,15 @@ branches: before_script: - export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH - - which rustfmt || cargo install rustfmt + - pip install 'travis-cargo<0.2' --user + - which rustfmt || travis-cargo --only nightly install rustfmt-nightly - which cargo-install-update || cargo install cargo-update - cargo install-update -a - - pip install 'travis-cargo<0.2' --user - mkdir $(pwd)/socket - export XDG_RUNTIME_DIR="$(pwd)/socket" script: - - cargo fmt -- --write-mode=diff + - travis-cargo --only nightly fmt -- -- --write-mode=diff - travis-cargo --skip nightly build - travis-cargo --only nightly build -- --features "clippy" - travis-cargo --only stable doc -- --no-deps diff --git a/Cargo.toml b/Cargo.toml index 855f6a8..35127d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ glium = { version = "0.16.0", optional = true, default-features = false } input = { version = "0.2.0", optional = true } clippy = { version = "*", optional = true } rental = "0.4.11" +wayland-protocols = { version = "0.9.9", features = ["unstable_protocols", "server"] } [build-dependencies] gl_generator = "0.5" @@ -25,6 +26,7 @@ gl_generator = "0.5" [dev-dependencies] slog-term = "2.0" slog-async = "2.0" +rand = "0.3" [features] default = ["backend_winit", "backend_libinput", "renderer_glium"] diff --git a/examples/helpers/glium.rs b/examples/helpers/glium.rs index 23a8278..d407b46 100644 --- a/examples/helpers/glium.rs +++ b/examples/helpers/glium.rs @@ -19,7 +19,6 @@ pub struct GliumDrawer<'a, F: 'a> { impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { pub fn new(display: &'a F) -> GliumDrawer<'a, F> { - // building the vertex buffer, which contains all the vertices that we will draw let vertex_buffer = glium::VertexBuffer::new( display, @@ -87,7 +86,6 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { pub fn draw(&self, target: &mut glium::Frame, contents: &[u8], surface_dimensions: (u32, u32), surface_location: (i32, i32), screen_size: (u32, u32)) { - let image = glium::texture::RawImage2d { data: contents.into(), width: surface_dimensions.0, @@ -102,8 +100,7 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { let x = 2.0 * (surface_location.0 as f32) / (screen_size.0 as f32) - 1.0; let y = 1.0 - 2.0 * (surface_location.1 as f32) / (screen_size.1 as f32); - let uniforms = - uniform! { + let uniforms = uniform! { matrix: [ [xscale, 0.0 , 0.0, 0.0], [ 0.0 , yscale , 0.0, 0.0], @@ -122,6 +119,5 @@ impl<'a, F: glium::backend::Facade + 'a> GliumDrawer<'a, F> { &Default::default(), ) .unwrap(); - } } diff --git a/examples/helpers/mod.rs b/examples/helpers/mod.rs index 6964d2b..407fec4 100644 --- a/examples/helpers/mod.rs +++ b/examples/helpers/mod.rs @@ -1,5 +1,3 @@ -mod shell; mod glium; pub use self::glium::GliumDrawer; -pub use self::shell::{ShellSurfaceRole, WlShellStubHandler}; diff --git a/examples/helpers/shell.rs b/examples/helpers/shell.rs deleted file mode 100644 index e640b0d..0000000 --- a/examples/helpers/shell.rs +++ /dev/null @@ -1,104 +0,0 @@ - - -use smithay::compositor::{CompositorToken, Handler as CompositorHandler}; -use smithay::compositor::roles::{Role, RoleType}; -use wayland_server::{Client, EventLoopHandle, GlobalHandler, Init, Resource}; -use wayland_server::protocol::{wl_shell, wl_shell_surface, wl_surface}; - -/// A very basic handler for wl_shell -/// -/// All it does is track which wl_shell_surface exist and which do not, -/// as well as the roles associated to them. -/// -/// That's it. -pub struct WlShellStubHandler { - my_id: Option, - token: CompositorToken, - surfaces: Vec<(wl_shell_surface::WlShellSurface, wl_surface::WlSurface)>, -} - -#[derive(Default)] -pub struct ShellSurfaceRole; - -impl WlShellStubHandler { - pub fn new(compositor_token: CompositorToken) -> WlShellStubHandler { - WlShellStubHandler { - my_id: None, - token: compositor_token, - surfaces: Vec::new(), - } - } - - pub fn surfaces(&self) -> &[(wl_shell_surface::WlShellSurface, wl_surface::WlSurface)] { - &self.surfaces - } -} - -impl Init for WlShellStubHandler { - fn init(&mut self, evqh: &mut EventLoopHandle, index: usize) { - self.my_id = Some(index) - } -} - - -impl GlobalHandler for WlShellStubHandler -where - U: Send + 'static, - R: RoleType - + Role - + Send - + 'static, - H: CompositorHandler - + Send - + 'static, -{ - fn bind(&mut self, evqh: &mut EventLoopHandle, client: &Client, global: wl_shell::WlShell) { - evqh.register::<_, Self>( - &global, - self.my_id.expect( - "WlShellStubHandler was not properly initialized.", - ), - ); - } -} - -impl wl_shell::Handler for WlShellStubHandler -where - U: Send + 'static, - R: RoleType - + Role - + Send - + 'static, - H: CompositorHandler + Send + 'static, -{ - fn get_shell_surface(&mut self, evqh: &mut EventLoopHandle, client: &Client, - resource: &wl_shell::WlShell, id: wl_shell_surface::WlShellSurface, - surface: &wl_surface::WlSurface) { - let surface = surface.clone().expect( - "WlShellStubHandler can only manage surfaces managed by Smithay's CompositorHandler.", - ); - if self.token.give_role::(&surface).is_err() { - // This surface already has a role, and thus cannot be given one! - resource.post_error( - wl_shell::Error::Role as u32, - "Surface already has a role.".into(), - ); - return; - } - evqh.register::<_, Self>(&id, self.my_id.unwrap()); - self.surfaces.push((id, surface)) - } -} - -server_declare_handler!(WlShellStubHandler, Send], H: [CompositorHandler, Send]>, wl_shell::Handler, wl_shell::WlShell); - -impl wl_shell_surface::Handler for WlShellStubHandler -where - U: Send + 'static, - H: CompositorHandler - + Send - + 'static, -{ -} - -server_declare_handler!(WlShellStubHandler, Send]>, wl_shell_surface::Handler, wl_shell_surface::WlShellSurface); diff --git a/examples/simple.rs b/examples/simple.rs index ea8723a..b3dd58a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,20 +1,20 @@ -#[macro_use(server_declare_handler)] -extern crate wayland_server; -#[macro_use(define_roles)] -extern crate smithay; #[macro_use] extern crate glium; - +extern crate rand; #[macro_use] extern crate slog; extern crate slog_async; extern crate slog_term; +#[macro_use(define_roles)] +extern crate smithay; +extern crate wayland_protocols; +extern crate wayland_server; mod helpers; use glium::Surface; -use helpers::{GliumDrawer, ShellSurfaceRole, WlShellStubHandler}; +use helpers::GliumDrawer; use slog::{Drain, Logger}; use smithay::backend::graphics::glium::IntoGlium; @@ -22,10 +22,15 @@ use smithay::backend::input::InputBackend; use smithay::backend::winit; use smithay::compositor::{self, CompositorHandler, CompositorToken, SubsurfaceRole, TraversalAction}; use smithay::compositor::roles::Role; -use smithay::shm::{BufferData, ShmGlobal, ShmToken}; -use wayland_server::{Client, EventLoopHandle, Liveness, Resource}; +use smithay::shell::{self, PopupConfigure, PopupSurface, ShellClient, ShellHandler, ShellSurfaceRole, + ToplevelConfigure, ToplevelSurface}; +use smithay::shm::{ShmGlobal, ShmToken}; -use wayland_server::protocol::{wl_compositor, wl_shell, wl_shm, wl_subcompositor, wl_surface}; +use wayland_protocols::unstable::xdg_shell::server::{zxdg_shell_v6, zxdg_toplevel_v6}; + +use wayland_server::{Client, EventLoopHandle}; +use wayland_server::protocol::{wl_compositor, wl_output, wl_seat, wl_shell, wl_shm, wl_subcompositor, + wl_surface}; define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); @@ -36,30 +41,33 @@ struct SurfaceHandler { #[derive(Default)] struct SurfaceData { buffer: Option<(Vec, (u32, u32))>, + location: Option<(i32, i32)>, } impl compositor::Handler for SurfaceHandler { - fn commit(&mut self, evlh: &mut EventLoopHandle, client: &Client, surface: &wl_surface::WlSurface, + fn commit(&mut self, _evlh: &mut EventLoopHandle, _client: &Client, surface: &wl_surface::WlSurface, token: CompositorToken) { // we retrieve the contents of the associated buffer and copy it token.with_surface_data(surface, |attributes| { match attributes.buffer.take() { - Some(Some((buffer, (x, y)))) => { - self.shm_token.with_buffer_contents(&buffer, |slice, data| { - let offset = data.offset as usize; - let stride = data.stride as usize; - let width = data.width as usize; - let height = data.height as usize; - let mut new_vec = Vec::with_capacity(width * height * 4); - for i in 0..height { - new_vec.extend( - &slice[(offset + i * stride)..(offset + i * stride + width * 4)], - ); - } - attributes.user_data.buffer = - Some((new_vec, (data.width as u32, data.height as u32))); - }); - + Some(Some((buffer, (_x, _y)))) => { + // we ignore hotspot coordinates in this simple example + self.shm_token + .with_buffer_contents(&buffer, |slice, data| { + let offset = data.offset as usize; + let stride = data.stride as usize; + let width = data.width as usize; + let height = data.height as usize; + let mut new_vec = Vec::with_capacity(width * height * 4); + for i in 0..height { + new_vec.extend( + &slice[(offset + i * stride)..(offset + i * stride + width * 4)], + ); + } + attributes.user_data.buffer = + Some((new_vec, (data.width as u32, data.height as u32))); + }) + .unwrap(); } Some(None) => { // erase the contents @@ -71,7 +79,79 @@ impl compositor::Handler for SurfaceHandler { } } +struct ShellSurfaceHandler { + token: CompositorToken, +} + +impl ShellSurfaceHandler { + fn new(token: CompositorToken) -> ShellSurfaceHandler { + ShellSurfaceHandler { token } + } +} + +impl shell::Handler for ShellSurfaceHandler { + fn new_client(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} + fn client_pong(&mut self, _evlh: &mut EventLoopHandle, _client: ShellClient<()>) {} + fn new_toplevel(&mut self, _evlh: &mut EventLoopHandle, + surface: ToplevelSurface) + -> ToplevelConfigure { + let wl_surface = surface.get_surface().unwrap(); + self.token.with_surface_data(wl_surface, |data| { + // place the window at a random location in the [0;300]x[0;300] square + use rand::distributions::{IndependentSample, Range}; + let range = Range::new(0, 300); + let mut rng = rand::thread_rng(); + let x = range.ind_sample(&mut rng); + let y = range.ind_sample(&mut rng); + data.user_data.location = Some((x, y)) + }); + ToplevelConfigure { + size: None, + states: vec![], + serial: 42, + } + } + fn new_popup(&mut self, _evlh: &mut EventLoopHandle, + _surface: PopupSurface) + -> PopupConfigure { + PopupConfigure { + size: (10, 10), + position: (10, 10), + serial: 42, + } + } + fn move_(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, + _serial: u32) { + } + fn resize(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, _seat: &wl_seat::WlSeat, + _serial: u32, _edges: zxdg_toplevel_v6::ResizeEdge) { + } + fn grab(&mut self, _evlh: &mut EventLoopHandle, + _surface: PopupSurface, _seat: &wl_seat::WlSeat, + _serial: u32) { + } + fn change_display_state(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, + _maximized: Option, _minimized: Option, _fullscreen: Option, + _output: Option<&wl_output::WlOutput>) + -> ToplevelConfigure { + ToplevelConfigure { + size: None, + states: vec![], + serial: 42, + } + } + fn show_window_menu(&mut self, _evlh: &mut EventLoopHandle, + _surface: ToplevelSurface, + _seat: &wl_seat::WlSeat, _serial: u32, _x: i32, _y: i32) { + } +} + + type MyCompositorHandler = CompositorHandler; +type MyShellHandler = ShellHandler; fn main() { // A logger facility, here we use the terminal for this example @@ -104,7 +184,9 @@ fn main() { * Initialize the compositor global */ let compositor_handler_id = event_loop.add_handler_with_init(MyCompositorHandler::new( - SurfaceHandler { shm_token: shm_token.clone() }, + SurfaceHandler { + shm_token: shm_token.clone(), + }, log.clone(), )); // register it to handle wl_compositor and wl_subcompositor @@ -120,14 +202,15 @@ fn main() { }; /* - * Initialize the shell stub global + * Initialize the shell global */ - let shell_handler_id = - event_loop.add_handler_with_init(WlShellStubHandler::new(compositor_token.clone())); - event_loop.register_global::>( - shell_handler_id, - 1, - ); + let shell_handler_id = event_loop.add_handler_with_init(MyShellHandler::new( + ShellSurfaceHandler::new(compositor_token), + compositor_token, + log.clone(), + )); + event_loop.register_global::(shell_handler_id, 1); + event_loop.register_global::(shell_handler_id, 1); /* * Initialize glium @@ -151,32 +234,35 @@ fn main() { { let screen_dimensions = context.get_framebuffer_dimensions(); let state = event_loop.state(); - for &(_, ref surface) in - state - .get_handler::>(shell_handler_id) - .surfaces() + for toplevel_surface in state + .get_handler::(shell_handler_id) + .toplevel_surfaces() { - if surface.status() != Liveness::Alive { - continue; + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + let initial_place = compositor_token + .with_surface_data(wl_surface, |data| data.user_data.location.unwrap_or((0, 0))); + compositor_token + .with_surface_tree( + wl_surface, + initial_place, + |_surface, attributes, role, &(mut x, mut y)| { + if let Some((ref contents, (w, h))) = attributes.user_data.buffer { + // there is actually something to draw ! + if let Ok(subdata) = Role::::data(role) { + x += subdata.x; + y += subdata.y; + } + drawer.draw(&mut frame, contents, (w, h), (x, y), screen_dimensions); + TraversalAction::DoChildren((x, y)) + } else { + // we are not display, so our children are neither + TraversalAction::SkipChildren + } + }, + ) + .unwrap(); } - // this surface is a root of a subsurface tree that needs to be drawn - compositor_token.with_surface_tree(surface, (100, 100), |surface, - attributes, - role, - &(mut x, mut y)| { - if let Some((ref contents, (w, h))) = attributes.user_data.buffer { - // there is actually something to draw ! - if let Ok(subdata) = Role::::data(role) { - x += subdata.x; - y += subdata.y; - } - drawer.draw(&mut frame, contents, (w, h), (x, y), screen_dimensions); - TraversalAction::DoChildren((x, y)) - } else { - // we are not display, so our children are neither - TraversalAction::SkipChildren - } - }); } } frame.finish().unwrap(); diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index bfd33ad..b6c5e51 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -116,11 +116,11 @@ impl error::Error for CreationError { CreationError::OsError(ref text) => text, CreationError::OpenGlVersionNotSupported => { "The requested OpenGL version is not \ - supported." + supported." } CreationError::NoAvailablePixelFormat => { "Couldn't find any pixel format that matches \ - the criterias." + the criterias." } CreationError::NonMatchingSurfaceType => "Surface type does not match the context type.", CreationError::NotSupported => "Context creation is not supported on the current window system", @@ -244,31 +244,36 @@ impl EGLContext { let display = match native { NativeDisplay::X11(display) - if has_dp_extension("EGL_KHR_platform_x11") && egl.GetPlatformDisplay.is_loaded() => { + if has_dp_extension("EGL_KHR_platform_x11") && egl.GetPlatformDisplay.is_loaded() => + { trace!(log, "EGL Display Initialization via EGL_KHR_platform_x11"); egl.GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) } NativeDisplay::X11(display) - if has_dp_extension("EGL_EXT_platform_x11") && egl.GetPlatformDisplayEXT.is_loaded() => { + if has_dp_extension("EGL_EXT_platform_x11") && egl.GetPlatformDisplayEXT.is_loaded() => + { trace!(log, "EGL Display Initialization via EGL_EXT_platform_x11"); egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) } NativeDisplay::Gbm(display) - if has_dp_extension("EGL_KHR_platform_gbm") && egl.GetPlatformDisplay.is_loaded() => { + if has_dp_extension("EGL_KHR_platform_gbm") && egl.GetPlatformDisplay.is_loaded() => + { trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) } NativeDisplay::Gbm(display) - if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplayEXT.is_loaded() => { + if has_dp_extension("EGL_MESA_platform_gbm") && egl.GetPlatformDisplayEXT.is_loaded() => + { trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) } NativeDisplay::Wayland(display) - if has_dp_extension("EGL_KHR_platform_wayland") && egl.GetPlatformDisplay.is_loaded() => { + if has_dp_extension("EGL_KHR_platform_wayland") && egl.GetPlatformDisplay.is_loaded() => + { trace!( log, "EGL Display Initialization via EGL_KHR_platform_wayland" @@ -281,7 +286,8 @@ impl EGLContext { } NativeDisplay::Wayland(display) - if has_dp_extension("EGL_EXT_platform_wayland") && egl.GetPlatformDisplayEXT.is_loaded() => { + if has_dp_extension("EGL_EXT_platform_wayland") && egl.GetPlatformDisplayEXT.is_loaded() => + { trace!( log, "EGL Display Initialization via EGL_EXT_platform_wayland" @@ -293,9 +299,7 @@ impl EGLContext { ) } - NativeDisplay::X11(display) | - NativeDisplay::Gbm(display) | - NativeDisplay::Wayland(display) => { + NativeDisplay::X11(display) | NativeDisplay::Gbm(display) | NativeDisplay::Wayland(display) => { trace!(log, "Default EGL Display Initialization via GetDisplay"); egl.GetDisplay(display as *mut _) } @@ -522,7 +526,6 @@ impl EGLContext { context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); context_attributes.push(0); - } else if egl_version >= (1, 3) { trace!(log, "Setting CONTEXT_CLIENT_VERSION to {}", version.0); context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32); @@ -602,14 +605,12 @@ impl EGLContext { let surface = match (native, self.backend_type) { (NativeSurface::X11(window), NativeType::X11) | (NativeSurface::Wayland(window), NativeType::Wayland) | - (NativeSurface::Gbm(window), NativeType::Gbm) => { - self.egl.CreateWindowSurface( - self.display, - self.config_id, - window, - self.surface_attributes.as_ptr(), - ) - } + (NativeSurface::Gbm(window), NativeType::Gbm) => self.egl.CreateWindowSurface( + self.display, + self.config_id, + window, + self.surface_attributes.as_ptr(), + ), _ => return Err(CreationError::NonMatchingSurfaceType), }; @@ -654,10 +655,9 @@ impl<'a> EGLSurface<'a> { /// Swaps buffers at the end of a frame. pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> { let ret = unsafe { - self.context.egl.SwapBuffers( - self.context.display as *const _, - self.surface as *const _, - ) + self.context + .egl + .SwapBuffers(self.context.display as *const _, self.surface as *const _) }; if ret == 0 { @@ -705,10 +705,8 @@ impl Drop for EGLContext { unsafe { // we don't call MakeCurrent(0, 0) because we are not sure that the context // is still the current one - self.egl.DestroyContext( - self.display as *const _, - self.context as *const _, - ); + self.egl + .DestroyContext(self.display as *const _, self.context as *const _); self.egl.Terminate(self.display as *const _); } } @@ -717,10 +715,9 @@ impl Drop for EGLContext { impl<'a> Drop for EGLSurface<'a> { fn drop(&mut self) { unsafe { - self.context.egl.DestroySurface( - self.context.display as *const _, - self.surface as *const _, - ); + self.context + .egl + .DestroySurface(self.context.display as *const _, self.surface as *const _); } } } diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index 65ceb4e..4cbad35 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -87,10 +87,10 @@ impl<'a> backend::PointerAxisEvent for PointerAxisEvent { fn amount(&self) -> f64 { match self.source() { - backend::AxisSource::Finger | - backend::AxisSource::Continuous => self.event.axis_value(self.axis), - backend::AxisSource::Wheel | - backend::AxisSource::WheelTilt => self.event.axis_value_discrete(self.axis).unwrap(), + backend::AxisSource::Finger | backend::AxisSource::Continuous => self.event.axis_value(self.axis), + backend::AxisSource::Wheel | backend::AxisSource::WheelTilt => { + self.event.axis_value_discrete(self.axis).unwrap() + } } } } @@ -271,9 +271,9 @@ impl backend::InputBackend for LibinputInputBackend { } fn get_handler(&mut self) -> Option<&mut backend::InputHandler> { - self.handler.as_mut().map(|handler| { - handler as &mut backend::InputHandler - }) + self.handler + .as_mut() + .map(|handler| handler as &mut backend::InputHandler) } fn clear_handler(&mut self) { @@ -349,21 +349,18 @@ impl backend::InputBackend for LibinputInputBackend { // update capabilities, so they appear correctly on `on_seat_changed` and `on_seat_destroyed`. if let Some(seat) = self.seats.get_mut(&device_seat) { let caps = seat.capabilities_mut(); - caps.pointer = - self.devices - .iter() - .filter(|x| x.seat() == device_seat) - .any(|x| x.has_capability(libinput::DeviceCapability::Pointer)); - caps.keyboard = - self.devices - .iter() - .filter(|x| x.seat() == device_seat) - .any(|x| x.has_capability(libinput::DeviceCapability::Keyboard)); - caps.touch = - self.devices - .iter() - .filter(|x| x.seat() == device_seat) - .any(|x| x.has_capability(libinput::DeviceCapability::Touch)); + caps.pointer = self.devices + .iter() + .filter(|x| x.seat() == device_seat) + .any(|x| x.has_capability(libinput::DeviceCapability::Pointer)); + caps.keyboard = self.devices + .iter() + .filter(|x| x.seat() == device_seat) + .any(|x| x.has_capability(libinput::DeviceCapability::Keyboard)); + caps.touch = self.devices + .iter() + .filter(|x| x.seat() == device_seat) + .any(|x| x.has_capability(libinput::DeviceCapability::Touch)); } else { panic!("Seat changed that was never created") } @@ -395,9 +392,9 @@ impl backend::InputBackend for LibinputInputBackend { use input::event::touch::*; if let Some(ref mut handler) = self.handler { let device_seat = touch_event.device().seat(); - let seat = &self.seats.get(&device_seat).expect( - "Recieved touch event of non existing Seat", - ); + let seat = &self.seats + .get(&device_seat) + .expect("Recieved touch event of non existing Seat"); match touch_event { TouchEvent::Down(down_event) => { trace!(self.logger, "Calling on_touch_down with {:?}", down_event); @@ -433,25 +430,23 @@ impl backend::InputBackend for LibinputInputBackend { libinput::Event::Keyboard(keyboard_event) => { use input::event::keyboard::*; match keyboard_event { - KeyboardEvent::Key(key_event) => { - if let Some(ref mut handler) = self.handler { - let device_seat = key_event.device().seat(); - let seat = &self.seats.get(&device_seat).expect( - "Recieved key event of non existing Seat", - ); - trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event); - handler.on_keyboard_key(seat, key_event); - } - } + KeyboardEvent::Key(key_event) => if let Some(ref mut handler) = self.handler { + let device_seat = key_event.device().seat(); + let seat = &self.seats + .get(&device_seat) + .expect("Recieved key event of non existing Seat"); + trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event); + handler.on_keyboard_key(seat, key_event); + }, } } libinput::Event::Pointer(pointer_event) => { use input::event::pointer::*; if let Some(ref mut handler) = self.handler { let device_seat = pointer_event.device().seat(); - let seat = &self.seats.get(&device_seat).expect( - "Recieved pointer event of non existing Seat", - ); + let seat = &self.seats + .get(&device_seat) + .expect("Recieved pointer event of non existing Seat"); match pointer_event { PointerEvent::Motion(motion_event) => { trace!( diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 5fdb921..3cf23d2 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -214,9 +214,9 @@ impl EGLGraphicsBackend for WinitGraphicsBackend { } fn get_framebuffer_dimensions(&self) -> (u32, u32) { - self.window.get_inner_size_pixels().expect( - "Window does not exist anymore", - ) + self.window + .get_inner_size_pixels() + .expect("Window does not exist anymore") } fn is_current(&self) -> bool { @@ -310,14 +310,16 @@ impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { fn x_transformed(&self, width: u32) -> u32 { cmp::min( - (self.x * width as f64 / self.window.get_inner_size_points().unwrap_or((width, 0)).0 as f64) as u32, + (self.x * width as f64 / self.window.get_inner_size_points().unwrap_or((width, 0)).0 as f64) as + u32, 0, ) } fn y_transformed(&self, height: u32) -> u32 { cmp::min( - (self.y * height as f64 / self.window.get_inner_size_points().unwrap_or((0, height)).1 as f64) as u32, + (self.y * height as f64 / self.window.get_inner_size_points().unwrap_or((0, height)).1 as f64) as + u32, 0, ) } @@ -529,9 +531,9 @@ impl InputBackend for WinitInputBackend { } fn get_handler(&mut self) -> Option<&mut InputHandler> { - self.handler.as_mut().map(|handler| { - handler as &mut InputHandler - }) + self.handler + .as_mut() + .map(|handler| handler as &mut InputHandler) } fn clear_handler(&mut self) { @@ -590,10 +592,15 @@ impl InputBackend for WinitInputBackend { wl_egl_surface.resize(x as i32, y as i32, 0, 0); } } - (WindowEvent::KeyboardInput { - input: KeyboardInput { scancode, state, .. }, .. - }, - Some(handler)) => { + ( + WindowEvent::KeyboardInput { + input: KeyboardInput { + scancode, state, .. + }, + .. + }, + Some(handler), + ) => { match state { ElementState::Pressed => *key_counter += 1, ElementState::Released => { @@ -615,7 +622,12 @@ impl InputBackend for WinitInputBackend { }, ) } - (WindowEvent::MouseMoved { position: (x, y), .. }, Some(handler)) => { + ( + WindowEvent::MouseMoved { + position: (x, y), .. + }, + Some(handler), + ) => { trace!(logger, "Calling on_pointer_move_absolute with {:?}", (x, y)); handler.on_pointer_move_absolute( seat, @@ -627,39 +639,36 @@ impl InputBackend for WinitInputBackend { }, ) } - (WindowEvent::MouseWheel { delta, .. }, Some(handler)) => { - match delta { - MouseScrollDelta::LineDelta(x, y) | - MouseScrollDelta::PixelDelta(x, y) => { - if x != 0.0 { - let event = WinitMouseWheelEvent { - axis: Axis::Horizontal, - time: *time_counter, - delta: delta, - }; - trace!( - logger, - "Calling on_pointer_axis for Axis::Horizontal with {:?}", - x - ); - handler.on_pointer_axis(seat, event); - } - if y != 0.0 { - let event = WinitMouseWheelEvent { - axis: Axis::Vertical, - time: *time_counter, - delta: delta, - }; - trace!( - logger, - "Calling on_pointer_axis for Axis::Vertical with {:?}", - y - ); - handler.on_pointer_axis(seat, event); - } + (WindowEvent::MouseWheel { delta, .. }, Some(handler)) => match delta { + MouseScrollDelta::LineDelta(x, y) | MouseScrollDelta::PixelDelta(x, y) => { + if x != 0.0 { + let event = WinitMouseWheelEvent { + axis: Axis::Horizontal, + time: *time_counter, + delta: delta, + }; + trace!( + logger, + "Calling on_pointer_axis for Axis::Horizontal with {:?}", + x + ); + handler.on_pointer_axis(seat, event); + } + if y != 0.0 { + let event = WinitMouseWheelEvent { + axis: Axis::Vertical, + time: *time_counter, + delta: delta, + }; + trace!( + logger, + "Calling on_pointer_axis for Axis::Vertical with {:?}", + y + ); + handler.on_pointer_axis(seat, event); } } - } + }, (WindowEvent::MouseInput { state, button, .. }, Some(handler)) => { trace!( logger, @@ -675,13 +684,15 @@ impl InputBackend for WinitInputBackend { }, ) } - (WindowEvent::Touch(Touch { - phase: TouchPhase::Started, - location: (x, y), - id, - .. - }), - Some(handler)) => { + ( + WindowEvent::Touch(Touch { + phase: TouchPhase::Started, + location: (x, y), + id, + .. + }), + Some(handler), + ) => { trace!(logger, "Calling on_touch_down at {:?}", (x, y)); handler.on_touch_down( seat, @@ -693,13 +704,15 @@ impl InputBackend for WinitInputBackend { }, ) } - (WindowEvent::Touch(Touch { - phase: TouchPhase::Moved, - location: (x, y), - id, - .. - }), - Some(handler)) => { + ( + WindowEvent::Touch(Touch { + phase: TouchPhase::Moved, + location: (x, y), + id, + .. + }), + Some(handler), + ) => { trace!(logger, "Calling on_touch_motion at {:?}", (x, y)); handler.on_touch_motion( seat, @@ -711,13 +724,15 @@ impl InputBackend for WinitInputBackend { }, ) } - (WindowEvent::Touch(Touch { - phase: TouchPhase::Ended, - location: (x, y), - id, - .. - }), - Some(handler)) => { + ( + WindowEvent::Touch(Touch { + phase: TouchPhase::Ended, + location: (x, y), + id, + .. + }), + Some(handler), + ) => { trace!(logger, "Calling on_touch_motion at {:?}", (x, y)); handler.on_touch_motion( seat, @@ -737,12 +752,14 @@ impl InputBackend for WinitInputBackend { }, ); } - (WindowEvent::Touch(Touch { - phase: TouchPhase::Cancelled, - id, - .. - }), - Some(handler)) => { + ( + WindowEvent::Touch(Touch { + phase: TouchPhase::Cancelled, + id, + .. + }), + Some(handler), + ) => { trace!(logger, "Calling on_touch_cancel"); handler.on_touch_cancel( seat, diff --git a/src/compositor/global.rs b/src/compositor/global.rs index 80609e2..db03be3 100644 --- a/src/compositor/global.rs +++ b/src/compositor/global.rs @@ -5,15 +5,9 @@ use wayland_server::protocol::{wl_compositor, wl_subcompositor}; impl GlobalHandler for CompositorHandler where - U: Default - + Send - + 'static, - R: Default - + Send - + 'static, - H: UserHandler - + Send - + 'static, + U: Default + Send + 'static, + R: Default + Send + 'static, + H: UserHandler + Send + 'static, { fn bind(&mut self, evlh: &mut EventLoopHandle, _: &Client, global: wl_compositor::WlCompositor) { debug!(self.log, "New compositor global binded."); diff --git a/src/compositor/handlers.rs b/src/compositor/handlers.rs index 9a4add9..b8acb67 100644 --- a/src/compositor/handlers.rs +++ b/src/compositor/handlers.rs @@ -193,10 +193,7 @@ impl Destroy for CompositorDestructor { impl wl_subcompositor::Handler for CompositorHandler where U: Send + 'static, - R: RoleType - + Role - + Send - + 'static, + R: RoleType + Role + Send + 'static, H: Send + 'static, { fn get_subsurface(&mut self, evqh: &mut EventLoopHandle, _: &Client, @@ -210,9 +207,9 @@ where ); return; } - id.set_user_data(Box::into_raw( - Box::new(unsafe { surface.clone_unchecked() }), - ) as *mut _); + id.set_user_data( + Box::into_raw(Box::new(unsafe { surface.clone_unchecked() })) as *mut _, + ); evqh.register_with_destructor::<_, CompositorHandler, CompositorDestructor>( &id, self.my_id, @@ -259,7 +256,10 @@ where let ptr = subsurface.get_user_data(); let surface = &*(ptr as *mut wl_surface::WlSurface); if let Err(()) = SurfaceData::::reorder(surface, Location::After, sibling) { - subsurface.post_error(wl_subsurface::Error::BadSurface as u32, "Provided surface is not a sibling or parent.".into()); + subsurface.post_error( + wl_subsurface::Error::BadSurface as u32, + "Provided surface is not a sibling or parent.".into(), + ); } } } @@ -270,7 +270,10 @@ where let ptr = subsurface.get_user_data(); let surface = &*(ptr as *mut wl_surface::WlSurface); if let Err(()) = SurfaceData::::reorder(surface, Location::Before, sibling) { - subsurface.post_error(wl_subsurface::Error::BadSurface as u32, "Provided surface is not a sibling or parent.".into()); + subsurface.post_error( + wl_subsurface::Error::BadSurface as u32, + "Provided surface is not a sibling or parent.".into(), + ); } } } @@ -291,7 +294,8 @@ where server_declare_handler!(CompositorHandler], H: []>, wl_subsurface::Handler, wl_subsurface::WlSubsurface); impl Destroy for CompositorDestructor - where R: RoleType + Role +where + R: RoleType + Role, { fn destroy(subsurface: &wl_subsurface::WlSubsurface) { let ptr = subsurface.get_user_data(); diff --git a/src/compositor/mod.rs b/src/compositor/mod.rs index a1529bb..77d11b0 100644 --- a/src/compositor/mod.rs +++ b/src/compositor/mod.rs @@ -107,7 +107,7 @@ use self::region::RegionData; use self::roles::{Role, RoleType, WrongRole}; use self::tree::SurfaceData; pub use self::tree::TraversalAction; -use wayland_server::{Client, EventLoopHandle, Init, resource_is_registered}; +use wayland_server::{resource_is_registered, Client, EventLoopHandle, Init}; use wayland_server::protocol::{wl_buffer, wl_callback, wl_output, wl_region, wl_surface}; @@ -287,17 +287,15 @@ impl + Send + 'static> Co /// /// If the surface is not managed by the CompositorGlobal that provided this token, this /// will panic (having more than one compositor is not supported). - pub fn with_surface_data(&self, surface: &wl_surface::WlSurface, f: F) + pub fn with_surface_data(&self, surface: &wl_surface::WlSurface, f: F) -> T where - F: FnOnce(&mut SurfaceAttributes), + F: FnOnce(&mut SurfaceAttributes) -> T, { assert!( resource_is_registered::<_, CompositorHandler>(surface, self.hid), "Accessing the data of foreign surfaces is not supported." ); - unsafe { - SurfaceData::::with_data(surface, f); - } + unsafe { SurfaceData::::with_data(surface, f) } } } @@ -321,14 +319,11 @@ where /// - a custom value that is passer in a fold-like maneer, but only from the output of a parent /// to its children. See `TraversalAction` for details. /// - /// If the surface is not managed by the CompositorGlobal that provided this token, this + /// If the surface not managed by the CompositorGlobal that provided this token, this /// will panic (having more than one compositor is not supported). pub fn with_surface_tree(&self, surface: &wl_surface::WlSurface, initial: T, f: F) -> Result<(), ()> where - F: FnMut(&wl_surface::WlSurface, - &mut SurfaceAttributes, - &mut R, - &T) + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { assert!( @@ -434,6 +429,24 @@ impl + Send + unsafe { SurfaceData::::give_role_with::(surface, data) } } + /// Access the role data of a surface + /// + /// Fails and don't call the closure if the surface doesn't have this role + /// + /// If the surface is not managed by the CompositorGlobal that provided this token, this + /// will panic (having more than one compositor is not supported). + pub fn with_role_data(&self, surface: &wl_surface::WlSurface, f: F) + -> Result + where + R: Role, + F: FnOnce(&mut RoleData) -> T, + { + assert!( + resource_is_registered::<_, CompositorHandler>(surface, self.hid), + "Accessing the data of foreign surfaces is not supported." + ); + unsafe { SurfaceData::::with_role_data::(surface, f) } + } /// Register that this surface does not have a role any longer and retrieve the data /// diff --git a/src/compositor/region.rs b/src/compositor/region.rs index 7f56d1c..d6a7a22 100644 --- a/src/compositor/region.rs +++ b/src/compositor/region.rs @@ -13,9 +13,9 @@ pub struct RegionData { impl RegionData { /// Initialize the user_data of a region, must be called right when the surface is created pub unsafe fn init(region: &wl_region::WlRegion) { - region.set_user_data(Box::into_raw( - Box::new(Mutex::new(RegionData::default())), - ) as *mut _) + region.set_user_data( + Box::into_raw(Box::new(Mutex::new(RegionData::default()))) as *mut _, + ) } /// Cleans the user_data of that surface, must be called when it is destroyed diff --git a/src/compositor/tree.rs b/src/compositor/tree.rs index c3c1e9b..ccd0de4 100644 --- a/src/compositor/tree.rs +++ b/src/compositor/tree.rs @@ -58,9 +58,9 @@ impl SurfaceData { /// Initialize the user_data of a surface, must be called right when the surface is created pub unsafe fn init(surface: &wl_surface::WlSurface) { - surface.set_user_data(Box::into_raw( - Box::new(Mutex::new(SurfaceData::::new())), - ) as *mut _) + surface.set_user_data( + Box::into_raw(Box::new(Mutex::new(SurfaceData::::new()))) as *mut _, + ) } } @@ -315,21 +315,15 @@ impl SurfaceData { /// false will cause an early-stopping. pub unsafe fn map_tree(root: &wl_surface::WlSurface, initial: T, mut f: F) where - F: FnMut(&wl_surface::WlSurface, - &mut SurfaceAttributes, - &mut R, - &T) + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { // helper function for recursion - unsafe fn map(surface: &wl_surface::WlSurface, root: &wl_surface::WlSurface, initial: &T, - f: &mut F) + unsafe fn map(surface: &wl_surface::WlSurface, root: &wl_surface::WlSurface, + initial: &T, f: &mut F) -> bool where - F: FnMut(&wl_surface::WlSurface, - &mut SurfaceAttributes, - &mut R, - &T) + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { // stop if we met the root, so to not deadlock/inifinte loop diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index cf957a9..0b444de 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -29,7 +29,7 @@ use wayland_server::protocol::{wl_keyboard, wl_surface}; use xkbcommon::xkb; -pub use xkbcommon::xkb::{Keysym, keysyms}; +pub use xkbcommon::xkb::{keysyms, Keysym}; /// Represents the current state of the keyboard modifiers /// @@ -178,19 +178,18 @@ where "rules" => rules, "model" => model, "layout" => layout, "variant" => variant, "options" => &options ); - let internal = KbdInternal::new(rules, model, layout, variant, options) - .map_err(|_| { - debug!(log, "Loading keymap failed"); - Error::BadKeymap - })?; + let internal = KbdInternal::new(rules, model, layout, variant, options).map_err(|_| { + debug!(log, "Loading keymap failed"); + Error::BadKeymap + })?; // prepare a tempfile with the keymap, to send it to clients let mut keymap_file = tempfile().map_err(Error::IoError)?; let keymap_data = internal.keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); - keymap_file.write_all(keymap_data.as_bytes()).map_err( - Error::IoError, - )?; + keymap_file + .write_all(keymap_data.as_bytes()) + .map_err(Error::IoError)?; keymap_file.flush().map_err(Error::IoError)?; trace!(log, "Keymap loaded and copied to tempfile."; diff --git a/src/lib.rs b/src/lib.rs index d706968..d6c17a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,24 +4,24 @@ //! Most entry points in the modules can take an optionnal `slog::Logger` as argument //! that will be used as a drain for logging. If `None` is provided, they'll log to `slog-stdlog`. - #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] -#[macro_use] -extern crate wayland_server; extern crate nix; -extern crate xkbcommon; -extern crate tempfile; #[macro_use] extern crate rental; +extern crate tempfile; +extern crate wayland_protocols; +#[macro_use] +extern crate wayland_server; +extern crate xkbcommon; -#[cfg(feature = "backend_winit")] -extern crate winit; -#[cfg(feature = "backend_winit")] -extern crate wayland_client; #[cfg(feature = "backend_libinput")] extern crate input; +#[cfg(feature = "backend_winit")] +extern crate wayland_client; +#[cfg(feature = "backend_winit")] +extern crate winit; extern crate libloading; @@ -36,13 +36,14 @@ pub mod backend; pub mod compositor; pub mod shm; pub mod keyboard; +pub mod shell; fn slog_or_stdlog(logger: L) -> ::slog::Logger where L: Into>, { use slog::Drain; - logger.into().unwrap_or_else(|| { - ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), o!()) - }) + logger + .into() + .unwrap_or_else(|| ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), o!())) } diff --git a/src/shell/global.rs b/src/shell/global.rs new file mode 100644 index 0000000..07e13a3 --- /dev/null +++ b/src/shell/global.rs @@ -0,0 +1,54 @@ +use super::{Handler as UserHandler, ShellClientData, ShellHandler, ShellSurfaceRole}; +use super::wl_handlers::WlShellDestructor; +use super::xdg_handlers::XdgShellDestructor; + +use compositor::Handler as CompositorHandler; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::zxdg_shell_v6; +use wayland_server::{Client, EventLoopHandle, GlobalHandler, Resource}; +use wayland_server::protocol::{wl_shell, wl_shell_surface}; + +fn shell_client_data() -> ShellClientData { + ShellClientData { + pending_ping: 0, + data: Default::default(), + } +} + +impl GlobalHandler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Default + Send + 'static, +{ + fn bind(&mut self, evlh: &mut EventLoopHandle, _: &Client, global: wl_shell::WlShell) { + debug!(self.log, "New wl_shell global binded."); + global.set_user_data(Box::into_raw(Box::new(Mutex::new(( + shell_client_data::(), + Vec::::new(), + )))) as *mut _); + evlh.register_with_destructor::<_, Self, WlShellDestructor>(&global, self.my_id); + } +} + +impl GlobalHandler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Default + Send + 'static, +{ + fn bind(&mut self, evlh: &mut EventLoopHandle, _: &Client, global: zxdg_shell_v6::ZxdgShellV6) { + debug!(self.log, "New xdg_shell global binded."); + global.set_user_data( + Box::into_raw(Box::new(Mutex::new(shell_client_data::()))) as *mut _, + ); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&global, self.my_id); + } +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs new file mode 100644 index 0000000..7d58480 --- /dev/null +++ b/src/shell/mod.rs @@ -0,0 +1,874 @@ +//! Utilities for handling shell surfaces, toplevel and popups +//! +//! This module provides the `ShellHandler` type, which implements automatic handling of +//! shell surfaces objects, by being registered as a global handler for `wl_shell` and +//! `xdg_shell`. +//! +//! ## Why use this handler +//! +//! This handler can track for you the various shell surfaces defined by the clients by +//! handling the `xdg_shell` protocol. It also includes a compatibility layer for the +//! deprecated `wl_shell` global. +//! +//! It allows you to easily access a list of all shell surfaces defined by your clients +//! access their associated metadata and underlying `wl_surface`s. +//! +//! This handler only handles the protocol exchanges with the client to present you the +//! information in a coherent and relatively easy to use maneer. All the actual drawing +//! and positioning logic of windows is out of its scope. +//! +//! ## How to use it +//! +//! ### Initialization +//! +//! To initialize this handler, simply instanciate it and register it to the event loop +//! as a global handler for xdg_shell and wl_shell. You will need to provide it the +//! `CompositorToken` you retrieved from an instanciation of the `CompositorHandler` +//! provided by smithay. +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! # extern crate wayland_protocols; +//! # +//! use smithay::compositor::roles::*; +//! use smithay::compositor::CompositorToken; +//! use smithay::shell::{ShellHandler, Handler as ShellHandlerTrait, ShellSurfaceRole}; +//! use wayland_server::protocol::wl_shell::WlShell; +//! use wayland_protocols::unstable::xdg_shell::server::zxdg_shell_v6::ZxdgShellV6; +//! use wayland_server::{EventLoop, EventLoopHandle}; +//! # use smithay::shell::*; +//! # use wayland_server::protocol::{wl_seat, wl_output}; +//! # use wayland_protocols::unstable::xdg_shell::server::zxdg_toplevel_v6; +//! # #[derive(Default)] struct MySurfaceData; +//! # struct MyHandlerForCompositor; +//! # impl ::smithay::compositor::Handler for MyHandlerForCompositor {} +//! +//! // define the roles type. You need to integrate the ShellSurface role: +//! define_roles!(MyRoles => +//! [ShellSurface, ShellSurfaceRole] +//! ); +//! +//! // define the metadata you want associated with the shell clients +//! #[derive(Default)] +//! struct MyShellData { +//! /* ... */ +//! } +//! +//! // define a sub-handler for the shell::Handler trait +//! struct MyHandlerForShell { +//! /* ... */ +//! } +//! +//! # type MyToplevelSurface = ToplevelSurface; +//! # type MyPopupSurface = PopupSurface; +//! +//! impl ShellHandlerTrait for MyHandlerForShell { +//! /* ... a few methods to implement, see shell::Handler +//! documentation for details ... */ +//! # fn new_client(&mut self, evlh: &mut EventLoopHandle, client: ShellClient) { unimplemented!() } +//! # fn client_pong(&mut self, evlh: &mut EventLoopHandle, client: ShellClient) { unimplemented!() } +//! # fn new_toplevel(&mut self, evlh: &mut EventLoopHandle, surface: MyToplevelSurface) +//! # -> ToplevelConfigure { unimplemented!() } +//! # fn new_popup(&mut self, evlh: &mut EventLoopHandle, surface: MyPopupSurface) +//! # -> PopupConfigure { unimplemented!() } +//! # fn move_(&mut self, evlh: &mut EventLoopHandle, surface: MyToplevelSurface, +//! # seat: &wl_seat::WlSeat, serial: u32) { unimplemented!() } +//! # fn resize(&mut self, evlh: &mut EventLoopHandle, surface: MyToplevelSurface, +//! # seat: &wl_seat::WlSeat, serial: u32, edges: zxdg_toplevel_v6::ResizeEdge) { unimplemented!() } +//! # fn grab(&mut self, evlh: &mut EventLoopHandle, surface: MyPopupSurface, +//! # seat: &wl_seat::WlSeat, serial: u32) { unimplemented!() } +//! # fn change_display_state(&mut self, evlh: &mut EventLoopHandle, surface: MyToplevelSurface, +//! # maximized: Option, minimized: Option, fullscreen: Option, +//! # output: Option<&wl_output::WlOutput>) +//! # -> ToplevelConfigure { unimplemented!() } +//! # fn show_window_menu(&mut self, evlh: &mut EventLoopHandle, surface: MyToplevelSurface, +//! # seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32) { unimplemented!() } +//! } +//! +//! # type MyCompositorHandler = smithay::compositor::CompositorHandler; +//! // A type alias for brevety. ShellHandler has many type parameters: +//! type MyShellHandler = ShellHandler< +//! MySurfaceData, // the surface data you defined for the CompositorHandler +//! MyRoles, // the roles type +//! MyHandlerForCompositor, // the sub-handler type you defined for the CompositorHandler +//! MyHandlerForShell, // the sub-handler type you defined for this ShellHandler +//! MyShellData // the client data you defined for this ShellHandler +//! >; +//! # fn main() { +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! # let compositor_hid = event_loop.add_handler_with_init( +//! # MyCompositorHandler::new(MyHandlerForCompositor{ /* ... */ }, None /* put a logger here */) +//! # ); +//! # let compositor_token = { +//! # let state = event_loop.state(); +//! # state.get_handler::(compositor_hid).get_token() +//! # }; +//! +//! let shell_hid = event_loop.add_handler_with_init( +//! MyShellHandler::new( +//! MyHandlerForShell{ /* ... */ }, +//! compositor_token, // the composior token you retrieved from the CompositorHandler +//! None /* put a logger here */ +//! ) +//! ); +//! +//! event_loop.register_global::(shell_hid, 1); +//! event_loop.register_global::(shell_hid, 1); +//! +//! // You're now ready to go! +//! # } +//! ``` +//! +//! ### Access to shell surface and clients data +//! +//! There are mainly 3 kind of objects that you'll manipulate from this handler: +//! +//! - `ShellClient`: This is a handle representing an isntanciation of a shell global +//! you can associate client-wise metadata to it (this is the `MyShellData` type in +//! the example above). +//! - `ToplevelSurface`: This is a handle representing a toplevel surface, you can +//! retrive a list of all currently alive toplevel surface from the `Shellhandler`. +//! - `PopupSurface`: This is a handle representing a popup/tooltip surface. Similarly, +//! you can get a list of all currently alive popup surface from the `ShellHandler`. +//! +//! You'll obtain these objects though two means: either via the callback methods of +//! the subhandler you provided, or via methods on the `ShellHandler` that you can +//! access from the `state()` of the event loop. + +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::Role; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_popup_v6, zxdg_positioner_v6 as xdg_positioner, + zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6}; + +use wayland_server::{EventLoopHandle, EventResult, Init, Liveness, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}; + +mod global; +mod wl_handlers; +mod xdg_handlers; + +/// Metadata associated with the `shell_surface` role +pub struct ShellSurfaceRole { + /// Pending state as requested by the client + /// + /// The data in this field are double-buffered, you should + /// apply them on a surface commit. + pub pending_state: ShellSurfacePendingState, + /// Geometry of the surface + /// + /// Defines, in surface relative coordinates, what should + /// be considered as "the surface itself", regarding focus, + /// window alignment, etc... + /// + /// By default, you should consider the full contents of the + /// buffers of this surface and its subsurfaces. + pub window_geometry: Option, + /// List of non-acked configures pending + /// + /// Whenever a configure is acked by the client, all configure + /// older than it are discarded as well. As such, this vec contains + /// the serials of all the configure send to this surface that are + /// newer than the last ack received. + pub pending_configures: Vec, + /// Has this surface acked at least one configure? + /// + /// xdg_shell defines it as illegal to commit on a surface that has + /// not yet acked a configure. + pub configured: bool, +} + +#[derive(Copy, Clone, Debug)] +/// The state of a positioner, as set by the client +pub struct PositionerState { + /// Size of the rectangle that needs to be positioned + pub rect_size: (i32, i32), + /// Anchor rectangle in the parent surface coordinates + /// relative to which the surface must be positioned + pub anchor_rect: Rectangle, + /// Edges defining the anchor point + pub anchor_edges: xdg_positioner::Anchor, + /// Gravity direction for positioning the child surface + /// relative to its anchor point + pub gravity: xdg_positioner::Gravity, + /// Adjustments to do if previous criterias constraint the + /// surface + pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, + /// Offset placement relative to the anchor point + pub offset: (i32, i32), +} + +/// Contents of the pending state of a shell surface, depending on its role +pub enum ShellSurfacePendingState { + /// This a regular, toplevel surface + /// + /// This corresponds to either the `xdg_toplevel` role from the + /// `xdg_shell` protocol, or the result of `set_toplevel` using the + /// `wl_shell` protocol. + /// + /// This is what you'll generaly interpret as "a window". + Toplevel(ToplevelState), + /// This is a popup surface + /// + /// This corresponds to either the `xdg_popup` role from the + /// `xdg_shell` protocol, or the result of `set_popup` using the + /// `wl_shell` protocol. + /// + /// This are mostly for small tooltips and similar short-lived + /// surfaces. + Popup(PopupState), + /// This surface was not yet assigned a kind + None, +} + +/// State of a regular toplevel surface +pub struct ToplevelState { + /// Parent of this surface + /// + /// If this surface has a parent, it should be hidden + /// or displayed, brought up at the same time as it. + pub parent: Option, + /// Title of this shell surface + pub title: String, + /// App id for this shell surface + /// + /// This identifier can be used to group surface together + /// as being several instance of the same app. This can + /// also be used as the D-Bus name for the app. + pub app_id: String, + /// Minimum size requested for this surface + /// + /// A value of 0 on an axis means this axis is not constrained + pub min_size: (i32, i32), + /// Maximum size requested for this surface + /// + /// A value of 0 on an axis means this axis is not constrained + pub max_size: (i32, i32), +} + +impl ToplevelState { + /// Clone this ToplevelState + /// + /// If the parent surface refers to a surface that no longer + /// exists, it is replaced by `None` in the process. + pub fn clone(&self) -> ToplevelState { + ToplevelState { + parent: self.parent.as_ref().and_then(|p| p.clone()), + title: self.title.clone(), + app_id: self.app_id.clone(), + min_size: self.min_size, + max_size: self.max_size, + } + } +} + +/// The pending state of a popup surface +pub struct PopupState { + /// Parent of this popup surface + pub parent: wl_surface::WlSurface, + /// The positioner specifying how this tooltip should + /// be placed relative to its parent. + pub positioner: PositionerState, +} + +impl PopupState { + /// Clone this PopupState + /// + /// If the parent surface refers to a surface that no longer + /// exists, this will return `None`, as the popup can no + /// longer be meaningfully displayed. + pub fn clone(&self) -> Option { + if let Some(p) = self.parent.clone() { + Some(PopupState { + parent: p, + positioner: self.positioner.clone(), + }) + } else { + // the parent surface does no exist any longer, + // this popup does not make any sense now + None + } + } +} + +impl Default for ShellSurfacePendingState { + fn default() -> ShellSurfacePendingState { + ShellSurfacePendingState::None + } +} + +/// The handler for the shell globals +/// +/// See module-level documentation for its use. +pub struct ShellHandler { + my_id: usize, + log: ::slog::Logger, + token: CompositorToken, + handler: SH, + known_toplevels: Vec>, + known_popups: Vec>, + _shell_data: ::std::marker::PhantomData, +} + +impl Init for ShellHandler { + fn init(&mut self, _evqh: &mut EventLoopHandle, index: usize) { + self.my_id = index; + debug!(self.log, "Init finished") + } +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + /// Create a new CompositorHandler + pub fn new(handler: SH, token: CompositorToken, logger: L) -> ShellHandler + where + L: Into>, + { + let log = ::slog_or_stdlog(logger); + ShellHandler { + my_id: ::std::usize::MAX, + log: log.new(o!("smithay_module" => "shell_handler")), + token: token, + handler: handler, + known_toplevels: Vec::new(), + known_popups: Vec::new(), + _shell_data: ::std::marker::PhantomData, + } + } + + /// Access the inner handler of this CompositorHandler + pub fn get_handler(&mut self) -> &mut SH { + &mut self.handler + } + + /// Cleans the internal surface storage by removing all dead surfaces + pub fn cleanup_surfaces(&mut self) { + self.known_toplevels.retain(|s| s.alive()); + self.known_popups.retain(|s| s.alive()); + } + + /// Access all the shell surfaces known by this handler + pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] { + &self.known_toplevels[..] + } + + /// Access all the popup surfaces known by this handler + pub fn popup_surfaces(&self) -> &[PopupSurface] { + &self.known_popups[..] + } +} + +/* + * User interaction + */ + +enum ShellClientKind { + Wl(wl_shell::WlShell), + Xdg(zxdg_shell_v6::ZxdgShellV6), +} + +struct ShellClientData { + pending_ping: u32, + data: SD, +} + +/// A shell client +/// +/// This represents an instanciation of a shell +/// global (be it `wl_shell` or `xdg_shell`). +/// +/// Most of the time, you can consider that a +/// wayland client will be a single shell client. +/// +/// You can use this handle to access a storage for any +/// client-specific data you wish to associate with it. +pub struct ShellClient { + kind: ShellClientKind, + _data: ::std::marker::PhantomData<*mut SD>, +} + +impl ShellClient { + /// Is the shell client represented by this handle still connected? + pub fn alive(&self) -> bool { + match self.kind { + ShellClientKind::Wl(ref s) => s.status() == Liveness::Alive, + ShellClientKind::Xdg(ref s) => s.status() == Liveness::Alive, + } + } + + /// Checks if this handle and the other one actually refer to the + /// same shell client + pub fn equals(&self, other: &Self) -> bool { + match (&self.kind, &other.kind) { + (&ShellClientKind::Wl(ref s1), &ShellClientKind::Wl(ref s2)) => s1.equals(s2), + (&ShellClientKind::Xdg(ref s1), &ShellClientKind::Xdg(ref s2)) => s1.equals(s2), + _ => false, + } + } + + /// Send a ping request to this shell client + /// + /// You'll receive the reply in the `Handler::cient_pong()` method. + /// + /// A typical use is to start a timer at the same time you send this ping + /// request, and cancel it when you receive the pong. If the timer runs + /// down to 0 before a pong is received, mark the client as unresponsive. + /// + /// Fails if this shell client already has a pending ping or is already dead. + pub fn send_ping(&self, serial: u32) -> Result<(), ()> { + if !self.alive() { + return Err(()); + } + match self.kind { + ShellClientKind::Wl(ref shell) => { + let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.0.pending_ping == 0 { + return Err(()); + } + guard.0.pending_ping = serial; + if let Some(surface) = guard.1.first() { + // there is at least one surface, send the ping + // if there is no surface, the ping will remain pending + // and will be sent when the client creates a surface + surface.ping(serial); + } + } + ShellClientKind::Xdg(ref shell) => { + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.pending_ping == 0 { + return Err(()); + } + guard.pending_ping = serial; + shell.ping(serial); + } + } + Ok(()) + } + + /// Access the user data associated with this shell client + pub fn with_data(&self, f: F) -> Result + where + F: FnOnce(&mut SD) -> T, + { + if !self.alive() { + return Err(()); + } + match self.kind { + ShellClientKind::Wl(ref shell) => { + let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + Ok(f(&mut guard.0.data)) + } + ShellClientKind::Xdg(ref shell) => { + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + Ok(f(&mut guard.data)) + } + } + } +} + +enum SurfaceKind { + Wl(wl_shell_surface::WlShellSurface), + XdgToplevel(zxdg_toplevel_v6::ZxdgToplevelV6), + XdgPopup(zxdg_popup_v6::ZxdgPopupV6), +} + +/// A handle to a toplevel surface +/// +/// This is an unified abstraction over the toplevel surfaces +/// of both `wl_shell` and `xdg_shell`. +pub struct ToplevelSurface { + wl_surface: wl_surface::WlSurface, + shell_surface: SurfaceKind, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl ToplevelSurface +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + /// Is the toplevel surface refered by this handle still alive? + pub fn alive(&self) -> bool { + let shell_surface_alive = match self.shell_surface { + SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive, + SurfaceKind::XdgToplevel(ref s) => s.status() == Liveness::Alive, + SurfaceKind::XdgPopup(_) => unreachable!(), + }; + shell_surface_alive && self.wl_surface.status() == Liveness::Alive + } + + /// Do this handle and the other one actually refer to the same toplevel surface? + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + /// Retrieve the shell client owning this toplevel surface + /// + /// Returns `None` if the surface does actually no longer exist. + pub fn client(&self) -> Option> { + if !self.alive() { + return None; + } + match self.shell_surface { + SurfaceKind::Wl(ref s) => { + let &(_, ref shell) = + unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; + Some(ShellClient { + kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + }) + } + SurfaceKind::XdgToplevel(ref s) => { + let &(_, ref shell, _) = + unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) }; + Some(ShellClient { + kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + }) + } + SurfaceKind::XdgPopup(_) => unreachable!(), + } + } + + /// Send a configure event to this toplevel surface to suggest it a new configuration + /// + /// The serial of this configure will be tracked waiting for the client to ACK it. + pub fn send_configure(&self, cfg: ToplevelConfigure) -> EventResult<()> { + if !self.alive() { + return EventResult::Destroyed; + } + match self.shell_surface { + SurfaceKind::Wl(ref s) => self::wl_handlers::send_toplevel_configure(s, cfg), + SurfaceKind::XdgToplevel(ref s) => { + self::xdg_handlers::send_toplevel_configure(self.token, s, cfg) + } + SurfaceKind::XdgPopup(_) => unreachable!(), + } + EventResult::Sent(()) + } + + /// Make sure this surface was configured + /// + /// Returns `true` if it was, if not, returns `false` and raise + /// a protocol error to the associated client. Also returns `false` + /// if the surface is already destroyed. + /// + /// xdg_shell mandates that a client acks a configure before commiting + /// anything. + pub fn ensure_configured(&self) -> bool { + if !self.alive() { + return false; + } + let configured = self.token + .with_role_data::(&self.wl_surface, |data| data.configured) + .expect( + "A shell surface object exists but the surface does not have the shell_surface role ?!", + ); + if !configured { + if let SurfaceKind::XdgToplevel(ref s) = self.shell_surface { + let ptr = s.get_user_data(); + let &(_, _, ref xdg_surface) = + unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; + xdg_surface.post_error( + zxdg_surface_v6::Error::NotConstructed as u32, + "Surface has not been confgured yet.".into(), + ); + } else { + unreachable!(); + } + } + configured + } + + /// Access the underlying `wl_surface` of this toplevel surface + /// + /// Returns `None` if the toplevel surface actually no longer exists. + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + /// Retrieve a copy of the pending state of this toplevel surface + /// + /// Returns `None` of the toplevel surface actually no longer exists. + pub fn get_pending_state(&self) -> Option { + if !self.alive() { + return None; + } + self.token + .with_role_data::(&self.wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref state) => Some(state.clone()), + _ => None, + }) + .ok() + .and_then(|x| x) + } +} + +/// A handle to a popup surface +/// +/// This is an unified abstraction over the popup surfaces +/// of both `wl_shell` and `xdg_shell`. +pub struct PopupSurface { + wl_surface: wl_surface::WlSurface, + shell_surface: SurfaceKind, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl PopupSurface +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + /// Is the popup surface refered by this handle still alive? + pub fn alive(&self) -> bool { + let shell_surface_alive = match self.shell_surface { + SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive, + SurfaceKind::XdgPopup(ref s) => s.status() == Liveness::Alive, + SurfaceKind::XdgToplevel(_) => unreachable!(), + }; + shell_surface_alive && self.wl_surface.status() == Liveness::Alive + } + + /// Do this handle and the other one actually refer to the same popup surface? + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + /// Retrieve the shell client owning this popup surface + /// + /// Returns `None` if the surface does actually no longer exist. + pub fn client(&self) -> Option> { + if !self.alive() { + return None; + } + match self.shell_surface { + SurfaceKind::Wl(ref s) => { + let &(_, ref shell) = + unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; + Some(ShellClient { + kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + }) + } + SurfaceKind::XdgPopup(ref s) => { + let &(_, ref shell, _) = + unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) }; + Some(ShellClient { + kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + }) + } + SurfaceKind::XdgToplevel(_) => unreachable!(), + } + } + + /// Send a configure event to this toplevel surface to suggest it a new configuration + /// + /// The serial of this configure will be tracked waiting for the client to ACK it. + pub fn send_configure(&self, cfg: PopupConfigure) -> EventResult<()> { + if !self.alive() { + return EventResult::Destroyed; + } + match self.shell_surface { + SurfaceKind::Wl(ref s) => self::wl_handlers::send_popup_configure(s, cfg), + SurfaceKind::XdgPopup(ref s) => self::xdg_handlers::send_popup_configure(self.token, s, cfg), + SurfaceKind::XdgToplevel(_) => unreachable!(), + } + EventResult::Sent(()) + } + + /// Make sure this surface was configured + /// + /// Returns `true` if it was, if not, returns `false` and raise + /// a protocol error to the associated client. Also returns `false` + /// if the surface is already destroyed. + /// + /// xdg_shell mandates that a client acks a configure before commiting + /// anything. + pub fn ensure_configured(&self) -> bool { + if !self.alive() { + return false; + } + let configured = self.token + .with_role_data::(&self.wl_surface, |data| data.configured) + .expect( + "A shell surface object exists but the surface does not have the shell_surface role ?!", + ); + if !configured { + if let SurfaceKind::XdgPopup(ref s) = self.shell_surface { + let ptr = s.get_user_data(); + let &(_, _, ref xdg_surface) = + unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; + xdg_surface.post_error( + zxdg_surface_v6::Error::NotConstructed as u32, + "Surface has not been confgured yet.".into(), + ); + } else { + unreachable!(); + } + } + configured + } + + /// Send a 'popup_done' event to the popup surface + /// + /// It means that the use has dismissed the popup surface, or that + /// the pointer has left the area of popup grab if there was a grab. + pub fn send_popup_done(&self) -> EventResult<()> { + if !self.alive() { + return EventResult::Destroyed; + } + match self.shell_surface { + SurfaceKind::Wl(ref s) => s.popup_done(), + SurfaceKind::XdgPopup(ref s) => s.popup_done(), + SurfaceKind::XdgToplevel(_) => unreachable!(), + } + } + + /// Access the underlying `wl_surface` of this toplevel surface + /// + /// Returns `None` if the toplevel surface actually no longer exists. + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + /// Retrieve a copy of the pending state of this popup surface + /// + /// Returns `None` of the popup surface actually no longer exists. + pub fn get_pending_state(&self) -> Option { + if !self.alive() { + return None; + } + self.token + .with_role_data::(&self.wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Popup(ref state) => state.clone(), + _ => None, + }) + .ok() + .and_then(|x| x) + } +} + +/// A configure message for toplevel surfaces +pub struct ToplevelConfigure { + /// A suggestion for a new size for the surface + pub size: Option<(i32, i32)>, + /// A notification of what are the current states of this surface + /// + /// A surface can be any combination of these possible states + /// at the same time. + pub states: Vec, + /// A serial number to track ACK from the client + /// + /// This should be an ever increasing number, as the ACK-ing + /// from a client for a serial will validate all pending lower + /// serials. + pub serial: u32, +} + +/// A configure message for popup surface +pub struct PopupConfigure { + /// The position chosen for this popup relative to + /// its parent + pub position: (i32, i32), + /// A suggested size for the popup + pub size: (i32, i32), + /// A serial number to track ACK from the client + /// + /// This should be an ever increasing number, as the ACK-ing + /// from a client for a serial will validate all pending lower + /// serials. + pub serial: u32, +} + +/// A trait for the sub-handler provided to the ShellHandler +/// +/// You need to implement this trait to handle events that the ShellHandler +/// cannot process for you directly. +/// +/// Depending on what you want to do, you might implement some of these methods +/// as doing nothing. +pub trait Handler { + /// A new shell client was instanciated + fn new_client(&mut self, evlh: &mut EventLoopHandle, client: ShellClient); + /// The pong for a pending ping of this shell client was received + /// + /// The ShellHandler already checked for you that the serial matches the one + /// from the pending ping. + fn client_pong(&mut self, evlh: &mut EventLoopHandle, client: ShellClient); + /// A new toplevel surface was created + /// + /// You need to return a `ToplevelConfigure` from this method, which will be sent + /// to the client to configure this surface + fn new_toplevel(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface) + -> ToplevelConfigure; + /// A new popup surface was created + /// + /// You need to return a `PopupConfigure` from this method, which will be sent + /// to the client to configure this surface + fn new_popup(&mut self, evlh: &mut EventLoopHandle, surface: PopupSurface) + -> PopupConfigure; + /// The client requested the start of an interactive move for this surface + fn move_(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32); + /// The client requested the start of an interactive resize for this surface + /// + /// The `edges` argument specifies which part of the window's border is being dragged. + fn resize(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32, edges: zxdg_toplevel_v6::ResizeEdge); + /// This popup requests a grab of the pointer + /// + /// This means it requests to be sent a `popup_done` event when the pointer leaves + /// the grab area. + fn grab(&mut self, evlh: &mut EventLoopHandle, surface: PopupSurface, + seat: &wl_seat::WlSeat, serial: u32); + /// A toplevel surface requested its display state to be changed + /// + /// Each field represents the request of the client for a specific property: + /// + /// - `None`: no request is made to change this property + /// - `Some(true)`: this property should be enabled + /// - `Some(false)`: this property should be disabled + /// + /// For fullscreen/maximization, the client can also optionnaly request a specific + /// output. + /// + /// You are to answer with a `ToplevelConfigure` that will be sent to the client in + /// response. + fn change_display_state(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + maximized: Option, minimized: Option, fullscreen: Option, + output: Option<&wl_output::WlOutput>) + -> ToplevelConfigure; + /// The client requests the window menu to be displayed on this surface at this location + /// + /// This menu belongs to the compositor. It is typically expected to contain options for + /// control of the window (maximize/minimize/close/move/etc...). + fn show_window_menu(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32); +} diff --git a/src/shell/wl_handlers.rs b/src/shell/wl_handlers.rs new file mode 100644 index 0000000..e536a51 --- /dev/null +++ b/src/shell/wl_handlers.rs @@ -0,0 +1,370 @@ +use super::{Handler as UserHandler, PopupConfigure, PopupState, PositionerState, ShellClient, + ShellClientData, ShellHandler, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, + ToplevelState}; + +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_positioner_v6 as xdg_positioner, zxdg_toplevel_v6}; + +use wayland_server::{Client, Destroy, EventLoopHandle, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}; + +pub struct WlShellDestructor { + _data: ::std::marker::PhantomData, +} + +/* + * wl_shell + */ + +pub type ShellUserData = Mutex<(ShellClientData, Vec)>; + +impl Destroy for WlShellDestructor { + fn destroy(shell: &wl_shell::WlShell) { + let ptr = shell.get_user_data(); + shell.set_user_data(::std::ptr::null_mut()); + let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; + // explicitly call drop to not forget what we're doing here + ::std::mem::drop(data); + } +} + +pub fn make_shell_client(resource: &wl_shell::WlShell) -> ShellClient { + ShellClient { + kind: super::ShellClientKind::Wl(unsafe { resource.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } +} + +impl wl_shell::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn get_shell_surface(&mut self, evlh: &mut EventLoopHandle, _: &Client, resource: &wl_shell::WlShell, + id: wl_shell_surface::WlShellSurface, surface: &wl_surface::WlSurface) { + trace!(self.log, "Creating new wl_shell_surface."); + let role_data = ShellSurfaceRole { + pending_state: ShellSurfacePendingState::None, + window_geometry: None, + pending_configures: Vec::new(), + configured: true, + }; + if let Err(_) = self.token.give_role_with(surface, role_data) { + resource.post_error( + wl_shell::Error::Role as u32, + "Surface already has a role.".into(), + ); + return; + } + id.set_user_data( + Box::into_raw(Box::new(unsafe { surface.clone_unchecked() })) as *mut _, + ); + evlh.register_with_destructor::<_, Self, WlShellDestructor>(&id, self.my_id); + + // register ourselves to the wl_shell for ping handling + let mutex = unsafe { &*(resource.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.1.len() == 0 && guard.0.pending_ping != 0 { + // there is a pending ping that no surface could receive yet, send it + // note this is not possible that it was received and then a wl_shell_surface was + // destroyed, because wl_shell_surface has no destructor! + id.ping(guard.0.pending_ping); + } + guard.1.push(id); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH:[UserHandler, Send], SD: [Send]>, + wl_shell::Handler, + wl_shell::WlShell +); + +/* + * wl_shell_surface + */ + +pub type ShellSurfaceUserData = (wl_surface::WlSurface, wl_shell::WlShell); + +impl Destroy for WlShellDestructor { + fn destroy(shell_surface: &wl_shell_surface::WlShellSurface) { + let ptr = shell_surface.get_user_data(); + shell_surface.set_user_data(::std::ptr::null_mut()); + // drop the WlSurface object + let surface = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + // explicitly call drop to not forget what we're doing here + ::std::mem::drop(surface); + } +} + +fn make_toplevel_handle(token: CompositorToken, + resource: &wl_shell_surface::WlShellSurface) + -> super::ToplevelSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::ToplevelSurface { + wl_surface: unsafe { wl_surface.clone_unchecked() }, + shell_surface: super::SurfaceKind::Wl(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +fn make_popup_handle(token: CompositorToken, + resource: &wl_shell_surface::WlShellSurface) + -> super::PopupSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::PopupSurface { + wl_surface: unsafe { wl_surface.clone_unchecked() }, + shell_surface: super::SurfaceKind::Wl(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +pub fn send_toplevel_configure(resource: &wl_shell_surface::WlShellSurface, configure: ToplevelConfigure) { + let (w, h) = configure.size.unwrap_or((0, 0)); + resource.configure(wl_shell_surface::Resize::empty(), w, h); +} + +pub fn send_popup_configure(resource: &wl_shell_surface::WlShellSurface, configure: PopupConfigure) { + let (w, h) = configure.size; + resource.configure(wl_shell_surface::Resize::empty(), w, h); +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn wl_handle_display_state_change(&mut self, evlh: &mut EventLoopHandle, + resource: &wl_shell_surface::WlShellSurface, + maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>) { + let handle = make_toplevel_handle(self.token, resource); + // handler callback + let configure = + self.handler + .change_display_state(evlh, handle, maximized, minimized, fullscreen, output); + // send the configure response to client + let (w, h) = configure.size.unwrap_or((0, 0)); + resource.configure(wl_shell_surface::None, w, h); + } + + fn wl_ensure_toplevel(&mut self, evlh: &mut EventLoopHandle, + resource: &wl_shell_surface::WlShellSurface) { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + // copy token to make borrow checker happy + let token = self.token; + let need_send = token + .with_role_data::(wl_surface, |data| { + match data.pending_state { + ShellSurfacePendingState::Toplevel(_) => { + return false; + } + ShellSurfacePendingState::Popup(_) => { + // this is no longer a popup, deregister it + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + } + ShellSurfacePendingState::None => {} + } + // This was not previously toplevel, need to make it toplevel + data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState { + parent: None, + title: String::new(), + app_id: String::new(), + min_size: (0, 0), + max_size: (0, 0), + }); + return true; + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + // we need to notify about this new toplevel surface + if need_send { + let handle = make_toplevel_handle(self.token, resource); + let configure = self.handler.new_toplevel(evlh, handle); + send_toplevel_configure(resource, configure); + } + } +} + +impl wl_shell_surface::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn pong(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, serial: u32) { + let &(_, ref shell) = unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let valid = { + let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.0.pending_ping == serial { + guard.0.pending_ping = 0; + true + } else { + false + } + }; + if valid { + self.handler.client_pong(evlh, make_shell_client(shell)); + } + } + + fn move_(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler.move_(evlh, handle, seat, serial); + } + + fn resize(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32, + edges: wl_shell_surface::Resize) { + let edges = zxdg_toplevel_v6::ResizeEdge::from_raw(edges.bits()) + .unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); + let handle = make_toplevel_handle(self.token, resource); + self.handler.resize(evlh, handle, seat, serial, edges); + } + + fn set_toplevel(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface) { + self.wl_ensure_toplevel(evlh, resource); + self.wl_handle_display_state_change(evlh, resource, Some(false), Some(false), Some(false), None) + } + + fn set_transient(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, parent: &wl_surface::WlSurface, _x: i32, + _y: i32, _flags: wl_shell_surface::Transient) { + self.wl_ensure_toplevel(evlh, resource); + // set the parent + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data::(wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.parent = Some(unsafe { parent.clone_unchecked() }); + } + _ => unreachable!(), + }) + .unwrap(); + // set as regular surface + self.wl_handle_display_state_change(evlh, resource, Some(false), Some(false), Some(false), None) + } + + fn set_fullscreen(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, + _method: wl_shell_surface::FullscreenMethod, _framerate: u32, + output: Option<&wl_output::WlOutput>) { + self.wl_ensure_toplevel(evlh, resource); + self.wl_handle_display_state_change(evlh, resource, Some(false), Some(false), Some(true), output) + } + + fn set_popup(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32, + parent: &wl_surface::WlSurface, x: i32, y: i32, _: wl_shell_surface::Transient) { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + // we are reseting the popup state, so remove this surface from everywhere + self.known_toplevels.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + self.token + .with_role_data(wl_surface, |data| { + data.pending_state = ShellSurfacePendingState::Popup(PopupState { + parent: unsafe { parent.clone_unchecked() }, + positioner: PositionerState { + rect_size: (1, 1), + anchor_rect: Rectangle { + x, + y, + width: 1, + height: 1, + }, + anchor_edges: xdg_positioner::Anchor::empty(), + gravity: xdg_positioner::Gravity::empty(), + constraint_adjustment: xdg_positioner::ConstraintAdjustment::empty(), + offset: (0, 0), + }, + }); + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + + // notify the handler about this new popup + let handle = make_popup_handle(self.token, resource); + let configure = self.handler.new_popup(evlh, handle); + send_popup_configure(resource, configure); + self.handler + .grab(evlh, make_popup_handle(self.token, resource), seat, serial); + } + + fn set_maximized(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, output: Option<&wl_output::WlOutput>) { + self.wl_ensure_toplevel(evlh, resource); + self.wl_handle_display_state_change(evlh, resource, Some(true), Some(false), Some(false), output) + } + + fn set_title(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, title: String) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.title = title; + } + _ => {} + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + } + + fn set_class(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &wl_shell_surface::WlShellSurface, class_: String) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.app_id = class_; + } + _ => {} + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + wl_shell_surface::Handler, + wl_shell_surface::WlShellSurface +); diff --git a/src/shell/xdg_handlers.rs b/src/shell/xdg_handlers.rs new file mode 100644 index 0000000..5b7fe53 --- /dev/null +++ b/src/shell/xdg_handlers.rs @@ -0,0 +1,734 @@ +use super::{Handler as UserHandler, PopupConfigure, PopupState, PositionerState, ShellClient, + ShellClientData, ShellHandler, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, + ToplevelState}; + +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6, + zxdg_surface_v6, zxdg_toplevel_v6}; +use wayland_server::{Client, Destroy, EventLoopHandle, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_surface}; + +pub struct XdgShellDestructor { + _data: ::std::marker::PhantomData, +} + +/* + * xdg_shell + */ + +pub type ShellUserData = Mutex>; + +impl Destroy for XdgShellDestructor { + fn destroy(shell: &zxdg_shell_v6::ZxdgShellV6) { + let ptr = shell.get_user_data(); + shell.set_user_data(::std::ptr::null_mut()); + let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; + // explicit call to drop to not forget what we're doing here + ::std::mem::drop(data); + } +} + +pub fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient { + ShellClient { + kind: super::ShellClientKind::Xdg(unsafe { resource.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } +} + +impl zxdg_shell_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, _: &mut EventLoopHandle, _: &Client, _: &zxdg_shell_v6::ZxdgShellV6) {} + fn create_positioner(&mut self, evlh: &mut EventLoopHandle, _: &Client, + _: &zxdg_shell_v6::ZxdgShellV6, id: zxdg_positioner_v6::ZxdgPositionerV6) { + trace!(self.log, "Creating new xdg_positioner."); + id.set_user_data(Box::into_raw(Box::new(PositionerState { + rect_size: (0, 0), + anchor_rect: Rectangle { + x: 0, + y: 0, + width: 0, + height: 0, + }, + anchor_edges: zxdg_positioner_v6::Anchor::empty(), + gravity: zxdg_positioner_v6::Gravity::empty(), + constraint_adjustment: zxdg_positioner_v6::ConstraintAdjustment::empty(), + offset: (0, 0), + })) as *mut _); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + } + fn get_xdg_surface(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_shell_v6::ZxdgShellV6, id: zxdg_surface_v6::ZxdgSurfaceV6, + surface: &wl_surface::WlSurface) { + trace!(self.log, "Creating new wl_shell_surface."); + let role_data = ShellSurfaceRole { + pending_state: ShellSurfacePendingState::None, + window_geometry: None, + pending_configures: Vec::new(), + configured: false, + }; + if let Err(_) = self.token.give_role_with(surface, role_data) { + resource.post_error( + zxdg_shell_v6::Error::Role as u32, + "Surface already has a role.".into(), + ); + return; + } + id.set_user_data( + Box::into_raw(Box::new((unsafe { surface.clone_unchecked() }, unsafe { + resource.clone_unchecked() + }))) as *mut _, + ); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + } + + fn pong(&mut self, evlh: &mut EventLoopHandle, _: &Client, resource: &zxdg_shell_v6::ZxdgShellV6, + serial: u32) { + let valid = { + let mutex = unsafe { &*(resource.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.pending_ping == serial { + guard.pending_ping = 0; + true + } else { + false + } + }; + if valid { + self.handler.client_pong(evlh, make_shell_client(resource)); + } + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_shell_v6::Handler, + zxdg_shell_v6::ZxdgShellV6 +); + +/* + * xdg_positioner + */ + +impl Destroy for XdgShellDestructor { + fn destroy(positioner: &zxdg_positioner_v6::ZxdgPositionerV6) { + let ptr = positioner.get_user_data(); + positioner.set_user_data(::std::ptr::null_mut()); + // drop the PositionerState + let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) }; + // explicit call to drop to not forget what we're doing here + ::std::mem::drop(surface); + } +} + +impl zxdg_positioner_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, _: &mut EventLoopHandle, _: &Client, _: &zxdg_positioner_v6::ZxdgPositionerV6) {} + + fn set_size(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, width: i32, height: i32) { + if width < 1 || height < 1 { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.rect_size = (width, height); + } + } + + fn set_anchor_rect(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, x: i32, y: i32, width: i32, + height: i32) { + if width < 1 || height < 1 { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner's anchor rectangle.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.anchor_rect = Rectangle { + x, + y, + width, + height, + }; + } + } + + fn set_anchor(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, anchor: zxdg_positioner_v6::Anchor) { + use self::zxdg_positioner_v6::{AnchorBottom, AnchorLeft, AnchorRight, AnchorTop}; + if anchor.contains(AnchorLeft | AnchorRight) || anchor.contains(AnchorTop | AnchorBottom) { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid anchor for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.anchor_edges = anchor; + } + } + + fn set_gravity(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, gravity: zxdg_positioner_v6::Gravity) { + use self::zxdg_positioner_v6::{GravityBottom, GravityLeft, GravityRight, GravityTop}; + if gravity.contains(GravityLeft | GravityRight) || gravity.contains(GravityTop | GravityBottom) { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid gravity for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.gravity = gravity; + } + } + + fn set_constraint_adjustment(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, + constraint_adjustment: u32) { + let constraint_adjustment = + zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.constraint_adjustment = constraint_adjustment; + } + + fn set_offset(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, x: i32, y: i32) { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.offset = (x, y); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_positioner_v6::Handler, + zxdg_positioner_v6::ZxdgPositionerV6 +); + +/* + * xdg_surface + */ + +impl Destroy for XdgShellDestructor { + fn destroy(surface: &zxdg_surface_v6::ZxdgSurfaceV6) { + let ptr = surface.get_user_data(); + surface.set_user_data(::std::ptr::null_mut()); + // drop the PositionerState + let data = unsafe { + Box::from_raw( + ptr as *mut (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6), + ) + }; + // explicit call to drop to not forget what we're doing here + ::std::mem::drop(data); + } +} + +impl zxdg_surface_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, _: &mut EventLoopHandle, _: &Client, resource: &zxdg_surface_v6::ZxdgSurfaceV6) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.token + .with_role_data::(surface, |data| { + if let ShellSurfacePendingState::None = data.pending_state { + // all is good + } else { + shell.post_error( + zxdg_shell_v6::Error::Role as u32, + "xdg_surface was destroyed before its role object".into(), + ); + } + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + } + + fn get_toplevel(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, id: zxdg_toplevel_v6::ZxdgToplevelV6) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState { + parent: None, + title: String::new(), + app_id: String::new(), + min_size: (0, 0), + max_size: (0, 0), + }); + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + + id.set_user_data(Box::into_raw(Box::new(unsafe { + ( + surface.clone_unchecked(), + shell.clone_unchecked(), + resource.clone_unchecked(), + ) + })) as *mut _); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + + // register to self + self.known_toplevels + .push(make_toplevel_handle(self.token, &id)); + + // intial configure event + let handle = make_toplevel_handle(self.token, &id); + let configure = self.handler.new_toplevel(evlh, handle); + send_toplevel_configure(self.token, &id, configure); + } + + fn get_popup(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, id: zxdg_popup_v6::ZxdgPopupV6, + parent: &zxdg_surface_v6::ZxdgSurfaceV6, + positioner: &zxdg_positioner_v6::ZxdgPositionerV6) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + + let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) }; + + let parent_ptr = parent.get_user_data(); + let &(ref parent_surface, _) = + unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + + self.token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::Popup(PopupState { + parent: unsafe { parent_surface.clone_unchecked() }, + positioner: positioner_data.clone(), + }); + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + + id.set_user_data(Box::into_raw(Box::new(unsafe { + ( + surface.clone_unchecked(), + shell.clone_unchecked(), + resource.clone_unchecked(), + ) + })) as *mut _); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + + // register to self + self.known_popups.push(make_popup_handle(self.token, &id)); + + // intial configure event + let handle = make_popup_handle(self.token, &id); + let configure = self.handler.new_popup(evlh, handle); + send_popup_configure(self.token, &id, configure); + } + + fn set_window_geometry(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, x: i32, y: i32, width: i32, + height: i32) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.token + .with_role_data::(surface, |data| { + data.window_geometry = Some(Rectangle { + x, + y, + width, + height, + }); + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + } + + fn ack_configure(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, serial: u32) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.token + .with_role_data::(surface, |data| { + let mut found = false; + data.pending_configures.retain(|&s| { + if s == serial { + found = true; + } + s > serial + }); + if !found { + // client responded to a non-existing configure + shell.post_error( + zxdg_shell_v6::Error::InvalidSurfaceState as u32, + format!("Wrong configure serial: {}", serial), + ); + } + data.configured = true; + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_surface_v6::Handler, + zxdg_surface_v6::ZxdgSurfaceV6 +); + +/* + * xdg_toplevel + */ + +pub type ShellSurfaceUserData = ( + wl_surface::WlSurface, + zxdg_shell_v6::ZxdgShellV6, + zxdg_surface_v6::ZxdgSurfaceV6, +); + +impl Destroy for XdgShellDestructor { + fn destroy(surface: &zxdg_toplevel_v6::ZxdgToplevelV6) { + let ptr = surface.get_user_data(); + surface.set_user_data(::std::ptr::null_mut()); + // drop the PositionerState + let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + // explicit call to drop to not forget what we're doing there + ::std::mem::drop(data); + } +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + // Utility function allowing to factor out a lot of the upcoming logic + fn with_surface_toplevel_data(&self, resource: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) + where + F: FnOnce(&mut ToplevelState), + { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data::(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), + _ => unreachable!(), + }) + .expect( + "xdg_toplevel exists but surface has not shell_surface role?!", + ); + } + + fn xdg_handle_display_state_change(&mut self, evlh: &mut EventLoopHandle, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>) { + let handle = make_toplevel_handle(self.token, resource); + // handler callback + let configure = + self.handler + .change_display_state(evlh, handle, maximized, minimized, fullscreen, output); + // send the configure response to client + send_toplevel_configure(self.token, resource, configure); + } +} + +pub fn send_toplevel_configure(token: CompositorToken, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + configure: ToplevelConfigure) +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + let &(ref surface, _, ref shell_surface) = + unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let (w, h) = configure.size.unwrap_or((0, 0)); + // convert the Vec (which is really a Vec) into Vec + let states = { + let mut states = configure.states; + let ptr = states.as_mut_ptr(); + let len = states.len(); + let cap = states.capacity(); + ::std::mem::forget(states); + unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } + }; + let serial = configure.serial; + resource.configure(w, h, states); + shell_surface.configure(serial); + // Add the configure as pending + token + .with_role_data::(surface, |data| data.pending_configures.push(serial)) + .expect( + "xdg_toplevel exists but surface has not shell_surface role?!", + ); +} + +fn make_toplevel_handle(token: CompositorToken, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) + -> super::ToplevelSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::ToplevelSurface { + wl_surface: unsafe { wl_surface.clone_unchecked() }, + shell_surface: super::SurfaceKind::XdgToplevel(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +impl zxdg_toplevel_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, _: &mut EventLoopHandle, _: &Client, resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::None; + data.configured = false; + }) + .expect( + "xdg_toplevel exists but surface has not shell_surface role?!", + ); + // remove this surface from the known ones (as well as any leftover dead surface) + self.known_toplevels.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(surface)) + .unwrap_or(false) + }); + } + + fn set_parent(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + parent: Option<&zxdg_toplevel_v6::ZxdgToplevelV6>) { + self.with_surface_toplevel_data(resource, |toplevel_data| { + toplevel_data.parent = parent.map(|toplevel_surface_parent| { + let parent_ptr = toplevel_surface_parent.get_user_data(); + let &(ref parent_surface, _) = + unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + unsafe { parent_surface.clone_unchecked() } + }) + }); + } + + fn set_title(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, title: String) { + self.with_surface_toplevel_data(resource, |toplevel_data| { toplevel_data.title = title; }); + } + + fn set_app_id(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, app_id: String) { + self.with_surface_toplevel_data(resource, |toplevel_data| { toplevel_data.app_id = app_id; }); + } + + fn show_window_menu(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32, + x: i32, y: i32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler + .show_window_menu(evlh, handle, seat, serial, x, y); + } + + fn move_(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler.move_(evlh, handle, seat, serial); + } + + fn resize(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32, edges: u32) { + let edges = + zxdg_toplevel_v6::ResizeEdge::from_raw(edges).unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); + let handle = make_toplevel_handle(self.token, resource); + self.handler.resize(evlh, handle, seat, serial, edges); + } + + fn set_max_size(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, width: i32, height: i32) { + self.with_surface_toplevel_data(resource, |toplevel_data| { + toplevel_data.max_size = (width, height); + }); + } + + fn set_min_size(&mut self, _: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, width: i32, height: i32) { + self.with_surface_toplevel_data(resource, |toplevel_data| { + toplevel_data.min_size = (width, height); + }); + } + + fn set_maximized(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evlh, resource, Some(true), None, None, None); + } + + fn unset_maximized(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evlh, resource, Some(false), None, None, None); + } + + fn set_fullscreen(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, output: Option<&wl_output::WlOutput>) { + self.xdg_handle_display_state_change(evlh, resource, None, None, Some(true), output); + } + + fn unset_fullscreen(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evlh, resource, None, None, Some(false), None); + } + + fn set_minimized(&mut self, evlh: &mut EventLoopHandle, _: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evlh, resource, None, Some(true), None, None); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_toplevel_v6::Handler, + zxdg_toplevel_v6::ZxdgToplevelV6 +); + +/* + * xdg_popup + */ + + + +impl Destroy for XdgShellDestructor { + fn destroy(surface: &zxdg_popup_v6::ZxdgPopupV6) { + let ptr = surface.get_user_data(); + surface.set_user_data(::std::ptr::null_mut()); + // drop the PositionerState + let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + // explicit call to drop to not forget what we're doing + ::std::mem::drop(data); + } +} + +pub fn send_popup_configure(token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6, + configure: PopupConfigure) +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + let &(ref surface, _, ref shell_surface) = + unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let (x, y) = configure.position; + let (w, h) = configure.size; + let serial = configure.serial; + resource.configure(x, y, w, h); + shell_surface.configure(serial); + // Add the configure as pending + token + .with_role_data::(surface, |data| data.pending_configures.push(serial)) + .expect( + "xdg_toplevel exists but surface has not shell_surface role?!", + ); +} + +fn make_popup_handle(token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6) + -> super::PopupSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::PopupSurface { + wl_surface: unsafe { wl_surface.clone_unchecked() }, + shell_surface: super::SurfaceKind::XdgPopup(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +impl zxdg_popup_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, _: &mut EventLoopHandle, _: &Client, resource: &zxdg_popup_v6::ZxdgPopupV6) { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { + &*(ptr as + *mut ( + wl_surface::WlSurface, + zxdg_shell_v6::ZxdgShellV6, + zxdg_surface_v6::ZxdgSurfaceV6, + )) + }; + self.token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::None; + data.configured = false; + }) + .expect( + "xdg_toplevel exists but surface has not shell_surface role?!", + ); + // remove this surface from the known ones (as well as any leftover dead surface) + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(surface)) + .unwrap_or(false) + }); + } + + fn grab(&mut self, evlh: &mut EventLoopHandle, _: &Client, resource: &zxdg_popup_v6::ZxdgPopupV6, + seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_popup_handle(self.token, resource); + self.handler.grab(evlh, handle, seat, serial); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_popup_v6::Handler, + zxdg_popup_v6::ZxdgPopupV6 +); diff --git a/src/shm/mod.rs b/src/shm/mod.rs index 9dba511..c0e05d5 100644 --- a/src/shm/mod.rs +++ b/src/shm/mod.rs @@ -67,7 +67,7 @@ use self::pool::{Pool, ResizeError}; use std::os::unix::io::RawFd; use std::sync::Arc; -use wayland_server::{Client, Destroy, EventLoopHandle, GlobalHandler, Init, Resource, resource_is_registered}; +use wayland_server::{resource_is_registered, Client, Destroy, EventLoopHandle, GlobalHandler, Init, Resource}; use wayland_server::protocol::{wl_buffer, wl_shm, wl_shm_pool}; mod pool; @@ -111,7 +111,9 @@ impl ShmGlobal { /// /// This is needed to retrieve the contents of the shm pools and buffers. pub fn get_token(&self) -> ShmToken { - ShmToken { hid: self.handler_id.expect("ShmGlobal was not initialized.") } + ShmToken { + hid: self.handler_id.expect("ShmGlobal was not initialized."), + } } } @@ -125,6 +127,7 @@ pub struct ShmToken { } /// Error that can occur when accessing an SHM buffer +#[derive(Debug)] pub enum BufferAccessError { /// This buffer is not managed by the SHM handler NotManaged, @@ -218,8 +221,11 @@ impl wl_shm::Handler for ShmHandler { let mmap_pool = match Pool::new(fd, size as usize, self.log.clone()) { Ok(p) => p, Err(()) => { - shm.post_error(wl_shm::Error::InvalidFd as u32, format!("Failed mmap of fd {}.", fd)); - return + shm.post_error( + wl_shm::Error::InvalidFd as u32, + format!("Failed mmap of fd {}.", fd), + ); + return; } }; let arc_pool = Box::new(Arc::new(mmap_pool)); diff --git a/src/shm/pool.rs b/src/shm/pool.rs index 4a814f0..227f669 100644 --- a/src/shm/pool.rs +++ b/src/shm/pool.rs @@ -6,7 +6,7 @@ use nix::sys::signal::{self, SigAction, SigHandler, Signal}; use std::cell::Cell; use std::os::unix::io::RawFd; use std::ptr; -use std::sync::{ONCE_INIT, Once, RwLock}; +use std::sync::{Once, RwLock, ONCE_INIT}; thread_local!(static SIGBUS_GUARD: Cell<(*const MemMap, bool)> = Cell::new((ptr::null_mut(), false)));