diff --git a/.travis.yml b/.travis.yml index bcb5e89..7669879 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ branches: before_script: - export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH - pip install 'travis-cargo<0.2' --user - - which rustfmt || travis-cargo --only nightly install rustfmt-nightly + - which rustfmt || travis-cargo --only nightly install rustfmt-nightly -- --force - which cargo-install-update || cargo install cargo-update - cargo install-update -a - mkdir $(pwd)/socket diff --git a/Cargo.toml b/Cargo.toml index 41d31b0..062db94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Victor Berger "] license = "MIT" [dependencies] -wayland-server = "0.10.1" +wayland-server = "0.10.2" nix = "0.7.0" xkbcommon = "0.2.1" tempfile = "2.1.5" @@ -13,14 +13,14 @@ slog = { version = "2.0.0" } slog-stdlog = "2.0.0-0.2" libloading = "0.4.0" wayland-client = { version = "0.9.9", optional = true } -winit = { version = "0.7.5", optional = true } +winit = { version = "0.8.0", optional = true } drm = { version = "0.2.1", optional = true } gbm = { version = "0.2.1", optional = true } glium = { version = "0.17.1", optional = true, default-features = false } input = { version = "0.2.0", optional = true } clippy = { version = "*", optional = true } rental = "0.4.11" -wayland-protocols = { version = "0.10.1", features = ["unstable_protocols", "server"] } +wayland-protocols = { version = "0.10.2", features = ["unstable_protocols", "server"] } image = "0.15.0" error-chain = "0.11.0" diff --git a/examples/drm.rs b/examples/drm.rs index 3c1d4eb..bd93763 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -17,16 +17,18 @@ use drm::control::{Device as ControlDevice, ResourceInfo}; use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; use drm::control::encoder::Info as EncoderInfo; use glium::Surface; -use helpers::{shell_implementation, surface_implementation, GliumDrawer, Roles, SurfaceData}; +use helpers::{init_shell, GliumDrawer, MyWindowMap, Roles, SurfaceData}; use slog::{Drain, Logger}; use smithay::backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}; use smithay::backend::graphics::egl::EGLGraphicsBackend; -use smithay::compositor::{compositor_init, CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::compositor::{CompositorToken, SubsurfaceRole, TraversalAction}; use smithay::compositor::roles::Role; -use smithay::shell::{shell_init, ShellState}; +use smithay::shell::ShellState; use smithay::shm::init_shm_global; +use std::cell::RefCell; use std::fs::OpenOptions; use std::io::Error as IoError; +use std::rc::Rc; use std::time::Duration; use wayland_server::{EventLoopHandle, StateToken}; @@ -89,16 +91,7 @@ fn main() { init_shm_global(&mut event_loop, vec![], log.clone()); - let (compositor_token, _, _) = - compositor_init(&mut event_loop, surface_implementation(), (), log.clone()); - - let (shell_state_token, _, _) = shell_init( - &mut event_loop, - compositor_token, - shell_implementation(), - compositor_token, - log.clone(), - ); + let (compositor_token, shell_state_token, window_map) = init_shell(&mut event_loop, log.clone()); /* * Initialize glium @@ -125,16 +118,23 @@ fn main() { DrmHandlerImpl { shell_state_token, compositor_token, + window_map: window_map.clone(), logger: log, }, ).unwrap(); - event_loop.run().unwrap(); + loop { + event_loop.dispatch(Some(16)).unwrap(); + display.flush_clients(); + + window_map.borrow_mut().refresh(); + } } pub struct DrmHandlerImpl { shell_state_token: StateToken>, compositor_token: CompositorToken, + window_map: Rc>, logger: ::slog::Logger, } @@ -148,33 +148,39 @@ impl DrmHandler> for DrmHandlerImpl { // redraw the frame, in a simple but inneficient way { let screen_dimensions = drawer.get_framebuffer_dimensions(); - for toplevel_surface in state.get(&self.shell_state_token).toplevel_surfaces() { - 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 = self.compositor_token - .with_surface_data(wl_surface, |data| data.user_data.location.unwrap_or((0, 0))); - self.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; + self.window_map + .borrow() + .with_windows_from_bottom_to_top(|toplevel_surface, initial_place| { + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + self.compositor_token + .with_surface_tree_upward( + 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.render( + &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 } - drawer.render(&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(); - } - } + }, + ) + .unwrap(); + } + }); } frame.finish().unwrap(); } diff --git a/examples/helpers/implementations.rs b/examples/helpers/implementations.rs index e89874d..4fc3ff5 100644 --- a/examples/helpers/implementations.rs +++ b/examples/helpers/implementations.rs @@ -1,14 +1,18 @@ +use super::WindowMap; use rand; -use smithay::compositor::{CompositorToken, SurfaceUserImplementation}; -use smithay::shell::{PopupConfigure, ShellSurfaceRole, ShellSurfaceUserImplementation, ToplevelConfigure}; +use smithay::compositor::{compositor_init, CompositorToken, SurfaceAttributes, SurfaceUserImplementation}; +use smithay::shell::{shell_init, PopupConfigure, ShellState, ShellSurfaceRole, + ShellSurfaceUserImplementation, ToplevelConfigure}; use smithay::shm::with_buffer_contents; +use std::cell::RefCell; +use std::rc::Rc; +use wayland_server::{EventLoop, StateToken}; define_roles!(Roles => [ ShellSurface, ShellSurfaceRole ] ); #[derive(Default)] pub struct SurfaceData { pub buffer: Option<(Vec, (u32, u32))>, - pub location: Option<(i32, i32)>, } pub fn surface_implementation() -> SurfaceUserImplementation { @@ -48,24 +52,26 @@ pub fn surface_implementation() -> SurfaceUserImplementation ShellSurfaceUserImplementation, ()> +pub struct ShellIData { + pub token: CompositorToken, + pub window_map: Rc>>, +} + +pub fn shell_implementation() -> ShellSurfaceUserImplementation, ()> +where + F: Fn(&SurfaceAttributes) -> Option<(i32, i32)>, { ShellSurfaceUserImplementation { new_client: |_, _, _| {}, client_pong: |_, _, _| {}, - new_toplevel: |_, token, toplevel| { - let wl_surface = toplevel.get_surface().unwrap(); - 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)) - }); + new_toplevel: |_, idata, toplevel| { + // 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); + idata.window_map.borrow_mut().insert(toplevel, (x, y)); ToplevelConfigure { size: None, states: vec![], @@ -92,3 +98,47 @@ pub fn shell_implementation( show_window_menu: |_, _, _, _, _, _, _| {}, } } + +fn get_size(attrs: &SurfaceAttributes) -> Option<(i32, i32)> { + attrs + .user_data + .buffer + .as_ref() + .map(|&(_, (w, h))| (w as i32, h as i32)) +} + +pub type MyWindowMap = WindowMap< + SurfaceData, + Roles, + (), + (), + fn(&SurfaceAttributes) -> Option<(i32, i32)>, +>; + +pub fn init_shell( + evl: &mut EventLoop, log: ::slog::Logger) + -> ( + CompositorToken, + StateToken>, + Rc>, + ) { + let (compositor_token, _, _) = compositor_init(evl, surface_implementation(), (), log.clone()); + + let window_map = Rc::new(RefCell::new(WindowMap::<_, _, _, (), _>::new( + compositor_token, + get_size as _, + ))); + + let (shell_state_token, _, _) = shell_init( + evl, + compositor_token, + shell_implementation(), + ShellIData { + token: compositor_token, + window_map: window_map.clone(), + }, + log.clone(), + ); + + (compositor_token, shell_state_token, window_map) +} diff --git a/examples/helpers/mod.rs b/examples/helpers/mod.rs index 36e6945..6659931 100644 --- a/examples/helpers/mod.rs +++ b/examples/helpers/mod.rs @@ -1,5 +1,7 @@ mod glium; mod implementations; +mod window_map; pub use self::glium::GliumDrawer; pub use self::implementations::*; +pub use self::window_map::WindowMap; diff --git a/examples/helpers/window_map.rs b/examples/helpers/window_map.rs new file mode 100644 index 0000000..9122846 --- /dev/null +++ b/examples/helpers/window_map.rs @@ -0,0 +1,191 @@ +use smithay::compositor::{CompositorToken, SubsurfaceRole, SurfaceAttributes, TraversalAction}; +use smithay::compositor::roles::Role; +use smithay::shell::{ShellSurfaceRole, ToplevelSurface}; +use smithay::utils::Rectangle; +use wayland_server::Resource; +use wayland_server::protocol::wl_surface; + +struct Window { + location: (i32, i32), + surface: Rectangle, + toplevel: ToplevelSurface, +} + +impl Window +where + U: 'static, + R: Role + Role + 'static, + CID: 'static, + SD: 'static, +{ + // Find the topmost surface under this point if any and the location of this point in the surface + fn matching(&self, point: (f64, f64), ctoken: CompositorToken, get_size: F) + -> Option<(wl_surface::WlSurface, (f64, f64))> + where + F: Fn(&SurfaceAttributes) -> Option<(i32, i32)>, + { + if !self.surface.contains((point.0 as i32, point.1 as i32)) { + return None; + } + // need to check more carefully + let mut found = None; + if let Some(wl_surface) = self.toplevel.get_surface() { + let _ = ctoken.with_surface_tree_downward( + wl_surface, + self.location, + |wl_surface, attributes, role, &(mut x, mut y)| if let Some((w, h)) = get_size(attributes) { + if let Ok(subdata) = Role::::data(role) { + x += subdata.x; + y += subdata.y; + } + let my_rect = Rectangle { + x, + y, + width: w, + height: h, + }; + if my_rect.contains((point.0 as i32, point.1 as i32)) { + found = wl_surface.clone().map(|s| { + (s, (point.0 - my_rect.x as f64, point.1 - my_rect.y as f64)) + }); + TraversalAction::Break + } else { + TraversalAction::DoChildren((x, y)) + } + } else { + TraversalAction::SkipChildren + }, + ); + } + found + } + + fn self_update(&mut self, ctoken: CompositorToken, get_size: F) + where + F: Fn(&SurfaceAttributes) -> Option<(i32, i32)>, + { + let (base_x, base_y) = self.location; + let (mut min_x, mut min_y, mut max_x, mut max_y) = (base_x, base_y, base_x, base_y); + if let Some(wl_surface) = self.toplevel.get_surface() { + let _ = ctoken.with_surface_tree_downward( + wl_surface, + (base_x, base_y), + |_, attributes, role, &(mut x, mut y)| { + if let Some((w, h)) = get_size(attributes) { + if let Ok(subdata) = Role::::data(role) { + x += subdata.x; + y += subdata.y; + } + // update the bounding box + if x < min_x { + min_x = x; + } + if y < min_y { + min_y = y; + } + if x + w > max_x { + max_x = x + w; + } + if y + h > max_y { + max_y = y + w; + } + TraversalAction::DoChildren((x, y)) + } else { + TraversalAction::SkipChildren + } + }, + ); + } + self.surface = Rectangle { + x: min_x, + y: min_y, + width: max_x - min_x, + height: max_y - min_y, + }; + } +} + +pub struct WindowMap { + ctoken: CompositorToken, + windows: Vec>, + get_size: F, +} + +impl WindowMap +where + F: Fn(&SurfaceAttributes) -> Option<(i32, i32)>, + U: 'static, + R: Role + Role + 'static, + CID: 'static, + SD: 'static, +{ + pub fn new(ctoken: CompositorToken, get_size: F) -> WindowMap { + WindowMap { + ctoken: ctoken, + windows: Vec::new(), + get_size: get_size, + } + } + + pub fn insert(&mut self, toplevel: ToplevelSurface, location: (i32, i32)) { + let mut window = Window { + location: location, + surface: Rectangle { + x: 0, + y: 0, + width: 0, + height: 0, + }, + toplevel: toplevel, + }; + window.self_update(self.ctoken, &self.get_size); + self.windows.insert(0, window); + } + + pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> { + for w in &self.windows { + if let Some(surface) = w.matching(point, self.ctoken, &self.get_size) { + return Some(surface); + } + } + None + } + + pub fn get_surface_and_bring_to_top(&mut self, point: (f64, f64)) + -> Option<(wl_surface::WlSurface, (f64, f64))> { + let mut found = None; + for (i, w) in self.windows.iter().enumerate() { + if let Some(surface) = w.matching(point, self.ctoken, &self.get_size) { + found = Some((i, surface)); + break; + } + } + if let Some((i, surface)) = found { + let winner = self.windows.remove(i); + self.windows.insert(0, winner); + Some(surface) + } else { + None + } + } + + pub fn with_windows_from_bottom_to_top(&self, mut f: Func) + where + Func: FnMut(&ToplevelSurface, (i32, i32)), + { + for w in self.windows.iter().rev() { + f(&w.toplevel, w.location) + } + } + + pub fn refresh(&mut self) { + self.windows.retain(|w| w.toplevel.alive()); + for w in self.windows.iter_mut() { + w.self_update(self.ctoken, &self.get_size); + } + } + + pub fn clear(&mut self) { + self.windows.clear(); + } +} diff --git a/examples/winit.rs b/examples/winit.rs index 037ece2..5529e5f 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -12,15 +12,112 @@ extern crate wayland_server; mod helpers; use glium::Surface; -use helpers::{shell_implementation, surface_implementation, GliumDrawer}; +use helpers::{init_shell, GliumDrawer, MyWindowMap}; use slog::{Drain, Logger}; use smithay::backend::graphics::egl::EGLGraphicsBackend; -use smithay::backend::input::InputBackend; +use smithay::backend::input::{self, Event, InputBackend, InputHandler, KeyboardKeyEvent, PointerButtonEvent, + PointerMotionAbsoluteEvent}; use smithay::backend::winit; -use smithay::compositor::{compositor_init, SubsurfaceRole, TraversalAction}; +use smithay::compositor::{SubsurfaceRole, TraversalAction}; use smithay::compositor::roles::Role; -use smithay::shell::shell_init; +use smithay::seat::{KeyboardHandle, PointerHandle, Seat}; use smithay::shm::init_shm_global; +use std::cell::RefCell; +use std::rc::Rc; +use wayland_server::protocol::wl_pointer; + +struct WinitInputHandler { + log: Logger, + pointer: PointerHandle, + keyboard: KeyboardHandle, + window_map: Rc>, + pointer_location: (f64, f64), + serial: u32, +} + +impl WinitInputHandler { + fn next_serial(&mut self) -> u32 { + self.serial += 1; + self.serial + } +} + +impl InputHandler for WinitInputHandler { + fn on_seat_created(&mut self, _: &input::Seat) { + /* never happens with winit */ + } + fn on_seat_destroyed(&mut self, _: &input::Seat) { + /* never happens with winit */ + } + fn on_seat_changed(&mut self, _: &input::Seat) { + /* never happens with winit */ + } + fn on_keyboard_key(&mut self, _: &input::Seat, evt: winit::WinitKeyboardInputEvent) { + let keycode = evt.key_code() - 8; + let state = evt.state(); + debug!(self.log, "key"; "keycode" => keycode, "state" => format!("{:?}", state)); + let serial = self.next_serial(); + self.keyboard.input(keycode, state, serial, |_, _| true); + } + fn on_pointer_move(&mut self, _: &input::Seat, _: input::UnusedEvent) { + /* never happens with winit */ + } + fn on_pointer_move_absolute(&mut self, _: &input::Seat, evt: winit::WinitMouseMovedEvent) { + // on winit, mouse events are already in pixel coordinates + let (x, y) = evt.position(); + self.pointer_location = (x, y); + let serial = self.next_serial(); + let under = self.window_map.borrow().get_surface_under((x, y)); + self.pointer.motion( + under.as_ref().map(|&(ref s, (x, y))| (s, x, y)), + serial, + evt.time(), + ); + } + fn on_pointer_button(&mut self, _: &input::Seat, evt: winit::WinitMouseInputEvent) { + let serial = self.next_serial(); + let button = match evt.button() { + input::MouseButton::Left => 0x110, + input::MouseButton::Right => 0x111, + input::MouseButton::Middle => 0x112, + input::MouseButton::Other(b) => b as u32, + }; + let state = match evt.state() { + input::MouseButtonState::Pressed => { + // change the keyboard focus + let under = self.window_map + .borrow_mut() + .get_surface_and_bring_to_top(self.pointer_location); + self.keyboard + .set_focus(under.as_ref().map(|&(ref s, _)| s), serial); + wl_pointer::ButtonState::Pressed + } + input::MouseButtonState::Released => wl_pointer::ButtonState::Released, + }; + self.pointer.button(button, state, serial, evt.time()); + } + fn on_pointer_axis(&mut self, _: &input::Seat, _: winit::WinitMouseWheelEvent) { + /* not done in this example */ + } + fn on_touch_down(&mut self, _: &input::Seat, _: winit::WinitTouchStartedEvent) { + /* not done in this example */ + } + fn on_touch_motion(&mut self, _: &input::Seat, _: winit::WinitTouchMovedEvent) { + /* not done in this example */ + } + fn on_touch_up(&mut self, _: &input::Seat, _: winit::WinitTouchEndedEvent) { + /* not done in this example */ + } + fn on_touch_cancel(&mut self, _: &input::Seat, _: winit::WinitTouchCancelledEvent) { + /* not done in this example */ + } + fn on_touch_frame(&mut self, _: &input::Seat, _: input::UnusedEvent) { + /* never happens with winit */ + } + fn on_input_config_changed(&mut self, _: &mut ()) { + /* never happens with winit */ + } +} fn main() { // A logger facility, here we use the terminal for this example @@ -40,22 +137,31 @@ fn main() { init_shm_global(&mut event_loop, vec![], log.clone()); - let (compositor_token, _, _) = - compositor_init(&mut event_loop, surface_implementation(), (), log.clone()); + let (compositor_token, _shell_state_token, window_map) = init_shell(&mut event_loop, log.clone()); - let (shell_state_token, _, _) = shell_init( - &mut event_loop, - compositor_token, - shell_implementation(), - compositor_token, - log.clone(), - ); + let (seat_token, _) = Seat::new(&mut event_loop, "winit".into(), log.clone()); + + let pointer = event_loop.state().get_mut(&seat_token).add_pointer(); + let keyboard = event_loop + .state() + .get_mut(&seat_token) + .add_keyboard("", "fr", "oss", None, 1000, 500) + .expect("Failed to initialize the keyboard"); /* * Initialize glium */ let drawer = GliumDrawer::from(renderer); + input.set_handler(WinitInputHandler { + log: log.clone(), + pointer, + keyboard, + window_map: window_map.clone(), + pointer_location: (0.0, 0.0), + serial: 0, + }); + /* * Add a listening socket: */ @@ -70,38 +176,45 @@ fn main() { // redraw the frame, in a simple but inneficient way { let screen_dimensions = drawer.get_framebuffer_dimensions(); - let state = event_loop.state(); - for toplevel_surface in state.get(&shell_state_token).toplevel_surfaces() { - 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; + window_map + .borrow() + .with_windows_from_bottom_to_top(|toplevel_surface, initial_place| { + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + compositor_token + .with_surface_tree_upward( + 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.render( + &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 } - drawer.render(&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(); - } - } + }, + ) + .unwrap(); + } + }); } frame.finish().unwrap(); event_loop.dispatch(Some(16)).unwrap(); display.flush_clients(); + + window_map.borrow_mut().refresh(); } } diff --git a/src/compositor/mod.rs b/src/compositor/mod.rs index 68336c2..ec819ed 100644 --- a/src/compositor/mod.rs +++ b/src/compositor/mod.rs @@ -94,6 +94,7 @@ use self::region::RegionData; use self::roles::{Role, RoleType, WrongRole}; use self::tree::SurfaceData; pub use self::tree::TraversalAction; +use utils::Rectangle; use wayland_server::{resource_is_registered, EventLoop, EventLoopHandle, Global}; use wayland_server::protocol::{wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface}; @@ -217,19 +218,6 @@ pub enum RectangleKind { Subtract, } -/// A rectangle defined by its top-left corner and dimensions -#[derive(Copy, Clone, Debug)] -pub struct Rectangle { - /// horizontal position of the top-leftcorner of the rectangle, in surface coordinates - pub x: i32, - /// vertical position of the top-leftcorner of the rectangle, in surface coordinates - pub y: i32, - /// width of the rectangle - pub width: i32, - /// height of the rectangle - pub height: i32, -} - /// Description of the contents of a region /// /// A region is defined as an union and difference of rectangle. @@ -310,7 +298,7 @@ where R: RoleType + Role + 'static, ID: 'static, { - /// Access the data of a surface tree + /// Access the data of a surface tree from bottom to top /// /// The provided closure is called successively on the surface and all its child subsurfaces, /// in a depth-first order. This matches the order in which the surfaces are supposed to be @@ -326,7 +314,8 @@ where /// /// 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<(), ()> + pub fn with_surface_tree_upward(&self, surface: &wl_surface::WlSurface, initial: T, f: F) + -> Result<(), ()> where F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, @@ -339,7 +328,33 @@ where "Accessing the data of foreign surfaces is not supported." ); unsafe { - SurfaceData::::map_tree(surface, initial, f); + SurfaceData::::map_tree(surface, initial, f, false); + } + Ok(()) + } + + /// Access the data of a surface tree from top to bottom + /// + /// The provided closure is called successively on the surface and all its child subsurfaces, + /// in a depth-first order. This matches the reverse of the order in which the surfaces are + /// supposed to be drawn: top-most first. + /// + /// Behavior is the same as `with_surface_tree_upward`. + pub fn with_surface_tree_downward(&self, surface: &wl_surface::WlSurface, initial: T, f: F) + -> Result<(), ()> + where + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) + -> TraversalAction, + { + assert!( + resource_is_registered( + surface, + &self::handlers::surface_implementation::() + ), + "Accessing the data of foreign surfaces is not supported." + ); + unsafe { + SurfaceData::::map_tree(surface, initial, f, true); } Ok(()) } diff --git a/src/compositor/tree.rs b/src/compositor/tree.rs index 10b2a55..6d261f8 100644 --- a/src/compositor/tree.rs +++ b/src/compositor/tree.rs @@ -307,21 +307,22 @@ impl SurfaceData { } /// Access sequentially the attributes associated with a surface tree, - /// in a depth-first order + /// in a depth-first order. /// /// Note that an internal lock is taken during access of this data, /// so the tree cannot be manipulated at the same time. /// /// The callback returns wether the traversal should continue or not. Returning /// false will cause an early-stopping. - pub unsafe fn map_tree(root: &wl_surface::WlSurface, initial: T, mut f: F) + pub unsafe fn map_tree(root: &wl_surface::WlSurface, initial: T, mut f: F, reverse: bool) where 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) + root: &wl_surface::WlSurface, initial: &T, f: &mut F, + reverse: bool) -> bool where F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) @@ -344,9 +345,17 @@ impl SurfaceData { ) { TraversalAction::DoChildren(t) => { // loop over children - for c in &data_guard.children { - if !map::(c, root, &t, f) { - return false; + if reverse { + for c in data_guard.children.iter().rev() { + if !map::(c, root, &t, f, true) { + return false; + } + } + } else { + for c in &data_guard.children { + if !map::(c, root, &t, f, false) { + return false; + } } } true @@ -368,9 +377,17 @@ impl SurfaceData { ) { TraversalAction::DoChildren(t) => { // loop over children - for c in &data_guard.children { - if !map::(c, root, &t, &mut f) { - break; + if reverse { + for c in data_guard.children.iter().rev() { + if !map::(c, root, &t, &mut f, true) { + break; + } + } + } else { + for c in &data_guard.children { + if !map::(c, root, &t, &mut f, false) { + break; + } } } } diff --git a/src/lib.rs b/src/lib.rs index 2598c77..6f8f0a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,9 @@ extern crate error_chain; pub mod backend; pub mod compositor; pub mod shm; -pub mod keyboard; +pub mod seat; pub mod shell; +pub mod utils; fn slog_or_stdlog(logger: L) -> ::slog::Logger where diff --git a/src/keyboard/mod.rs b/src/seat/keyboard.rs similarity index 62% rename from src/keyboard/mod.rs rename to src/seat/keyboard.rs index abb44e5..12cb235 100644 --- a/src/keyboard/mod.rs +++ b/src/seat/keyboard.rs @@ -1,22 +1,3 @@ -//! Utilities for keyboard handling -//! -//! This module provides utilities for keyboardand keymap handling: keymap interpretation -//! and forwarding keystrokes to clients using xkbcommon. -//! -//! You can first create a `KbdHandle` using the `create_keyboard_handler` function in this module. -//! The handle you obtained can be cloned to access this keyboard state from different places. It is -//! expected that such a context is created for each keyboard the compositor has access to. -//! -//! This handle gives you 3 main ways to interact with the keymap handling: -//! -//! - send the keymap information to a client using the `KbdHandle::send_keymap` method. -//! - set the current focus for this keyboard: designing the client that will receive the key inputs -//! using the `KbdHandle::set_focus` method. -//! - process key inputs from the input backend, allowing them to be catched at the compositor-level -//! or forwarded to the client. See the documentation of the `KbdHandle::input` method for -//! details. - - use backend::input::KeyState; use std::io::{Error as IoError, Write}; use std::os::unix::io::AsRawFd; @@ -74,15 +55,19 @@ impl ModifiersState { } struct KbdInternal { - focus: Option<(wl_surface::WlSurface, wl_keyboard::WlKeyboard)>, + known_kbds: Vec, + focus: Option, pressed_keys: Vec, mods_state: ModifiersState, keymap: xkb::Keymap, state: xkb::State, + repeat_rate: i32, + repeat_delay: i32, } impl KbdInternal { - fn new(rules: &str, model: &str, layout: &str, variant: &str, options: Option) + fn new(rules: &str, model: &str, layout: &str, variant: &str, options: Option, + repeat_rate: i32, repeat_delay: i32) -> Result { // we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe // so confining it inside the KbdInternal allows us to use Rusts mutability rules to make @@ -102,11 +87,14 @@ impl KbdInternal { ).ok_or(())?; let state = xkb::State::new(&keymap); Ok(KbdInternal { + known_kbds: Vec::new(), focus: None, pressed_keys: Vec::new(), mods_state: ModifiersState::new(), keymap: keymap, state: state, + repeat_rate: repeat_rate, + repeat_delay: repeat_delay, }) } @@ -125,7 +113,13 @@ impl KbdInternal { }; // update state - let state_components = self.state.update_key(keycode, direction); + // Offset the keycode by 8, as the evdev XKB rules reflect X's + // broken keycode system, which starts at 8. + let state_components = self.state.update_key(keycode + 8, direction); + + println!("KEYCODE: {}", keycode); + println!("MODS: {:b}", state_components); + if state_components != 0 { self.mods_state.update_with(&self.state); true @@ -152,9 +146,23 @@ impl KbdInternal { }; serialized.into() } + + fn with_focused_kbds(&self, mut f: F) + where + F: FnMut(&wl_keyboard::WlKeyboard, &wl_surface::WlSurface), + { + if let Some(ref surface) = self.focus { + for kbd in &self.known_kbds { + if kbd.same_client_as(surface) { + f(kbd, surface); + } + } + } + } } /// Errors that can be encountered when creating a keyboard handler +#[derive(Debug)] pub enum Error { /// libxkbcommon could not load the specified keymap BadKeymap, @@ -163,22 +171,30 @@ pub enum Error { } /// Create a keyboard handler from a set of RMLVO rules -pub fn create_keyboard_handler(rules: &str, model: &str, layout: &str, variant: &str, - options: Option, logger: L) - -> Result -where - L: Into>, -{ - let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "xkbcommon_handler")); - info!(log, "Initializing a xkbcommon handler with keymap"; +pub(crate) fn create_keyboard_handler(rules: &str, model: &str, layout: &str, variant: &str, + options: Option, repeat_delay: i32, repeat_rate: i32, + logger: ::slog::Logger) + -> Result { + let log = logger.new(o!("smithay_module" => "xkbcommon_handler")); + info!(log, "Initializing a xkbcommon handler with keymap query"; "rules" => rules, "model" => model, "layout" => layout, "variant" => variant, "options" => &options ); - let internal = KbdInternal::new(rules, model, layout, variant, options).map_err(|_| { + let internal = KbdInternal::new( + rules, + model, + layout, + variant, + options, + repeat_rate, + repeat_delay, + ).map_err(|_| { debug!(log, "Loading keymap failed"); Error::BadKeymap })?; + info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next()); + // prepare a tempfile with the keymap, to send it to clients let mut keymap_file = tempfile().map_err(Error::IoError)?; @@ -192,7 +208,7 @@ where "fd" => keymap_file.as_raw_fd(), "len" => keymap_data.as_bytes().len() ); - Ok(KbdHandle { + Ok(KeyboardHandle { arc: Arc::new(KbdArc { internal: Mutex::new(internal), keymap_file: keymap_file, @@ -214,20 +230,26 @@ struct KbdArc { /// It can be cloned and all clones manipulate the same internal state. Clones /// can also be sent across threads. /// -/// See module-level documentation for details of its use. +/// This handle gives you 2 main ways to interact with the keyboard handling: +/// +/// - set the current focus for this keyboard: designing the surface that will receive the key inputs +/// using the `KeyboardHandle::set_focus` method. +/// - process key inputs from the input backend, allowing them to be catched at the compositor-level +/// or forwarded to the client. See the documentation of the `KeyboardHandle::input` method for +/// details. #[derive(Clone)] -pub struct KbdHandle { +pub struct KeyboardHandle { arc: Arc, } -impl KbdHandle { +impl KeyboardHandle { /// Handle a keystroke /// /// All keystrokes from the input backend should be fed _in order_ to this method of the /// keyboard handler. It will internally track the state of the keymap. /// /// The `filter` argument is expected to be a closure which will peek at the generated input - /// as interpreted by the keymap befor it is forwarded to the focused client. If this closure + /// as interpreted by the keymap before it is forwarded to the focused client. If this closure /// returns false, the input will not be sent to the client. This mechanism can be used to /// implement compositor-level key bindings for example. /// @@ -239,9 +261,12 @@ impl KbdHandle { { trace!(self.arc.logger, "Handling keystroke"; "keycode" => keycode, "state" => format_args!("{:?}", state)); let mut guard = self.arc.internal.lock().unwrap(); - let mods_changed = guard.key_input(keycode, state); - let sym = guard.state.key_get_one_sym(keycode); + // Offset the keycode by 8, as the evdev XKB rules reflect X's + // broken keycode system, which starts at 8. + let sym = guard.state.key_get_one_sym(keycode + 8); + + let mods_changed = guard.key_input(keycode, state); trace!(self.arc.logger, "Calling input filter"; "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(sym) @@ -254,16 +279,22 @@ impl KbdHandle { } // forward to client if no keybinding is triggered - if let Some((_, ref kbd)) = guard.focus { - if mods_changed { - let (dep, la, lo, gr) = guard.serialize_modifiers(); + let modifiers = if mods_changed { + Some(guard.serialize_modifiers()) + } else { + None + }; + let wl_state = match state { + KeyState::Pressed => wl_keyboard::KeyState::Pressed, + KeyState::Released => wl_keyboard::KeyState::Released, + }; + guard.with_focused_kbds(|kbd, _| { + if let Some((dep, la, lo, gr)) = modifiers { kbd.modifiers(serial, dep, la, lo, gr); } - let wl_state = match state { - KeyState::Pressed => wl_keyboard::KeyState::Pressed, - KeyState::Released => wl_keyboard::KeyState::Released, - }; kbd.key(serial, 0, keycode, wl_state); + }); + if guard.focus.is_some() { trace!(self.arc.logger, "Input forwarded to client"); } else { trace!(self.arc.logger, "No client currently focused"); @@ -272,53 +303,78 @@ impl KbdHandle { /// Set the current focus of this keyboard /// - /// Any previous focus will be sent a `wl_keyboard::leave` event, and if the new focus - /// is not `None`, a `wl_keyboard::enter` event will be sent. - pub fn set_focus(&self, focus: Option<(wl_surface::WlSurface, wl_keyboard::WlKeyboard)>, serial: u32) { - // TODO: check surface and keyboard are from the same client - + /// If the ne focus is different from the previous one, any previous focus + /// will be sent a `wl_keyboard::leave` event, and if the new focus is not `None`, + /// a `wl_keyboard::enter` event will be sent. + pub fn set_focus(&self, focus: Option<&wl_surface::WlSurface>, serial: u32) { let mut guard = self.arc.internal.lock().unwrap(); - // remove current focus - let old_kbd = if let Some((old_surface, old_kbd)) = guard.focus.take() { - if old_surface.status() != Liveness::Dead { - old_kbd.leave(serial, &old_surface); - } - Some(old_kbd) - } else { - None - }; + let same = guard + .focus + .as_ref() + .and_then(|f| focus.map(|s| s.equals(f))) + .unwrap_or(false); - // set new focus - if let Some((surface, kbd)) = focus { - if surface.status() != Liveness::Dead { - // send new mods status if client instance changed - match old_kbd { - Some(ref okbd) if okbd.equals(&kbd) => {} - _ => { - let (dep, la, lo, gr) = guard.serialize_modifiers(); - kbd.modifiers(serial, dep, la, lo, gr); - } - } - // send enter event - kbd.enter(serial, &surface, guard.serialize_pressed_keys()); + if !same { + // unset old focus + guard.with_focused_kbds(|kbd, s| { + kbd.leave(serial, s); + }); + + // set new focus + guard.focus = focus.and_then(|s| s.clone()); + let (dep, la, lo, gr) = guard.serialize_modifiers(); + let keys = guard.serialize_pressed_keys(); + guard.with_focused_kbds(|kbd, s| { + kbd.modifiers(serial, dep, la, lo, gr); + kbd.enter(serial, s, keys.clone()); + }); + if guard.focus.is_some() { + trace!(self.arc.logger, "Focus set to new surface"); + } else { + trace!(self.arc.logger, "Focus unset"); } - guard.focus = Some((surface, kbd)); - trace!(self.arc.logger, "Focus set to new surface"); } else { - trace!(self.arc.logger, "Focus unset"); + trace!(self.arc.logger, "Focus unchanged"); } } - /// Send the keymap to this keyboard + /// Register a new keyboard to this handler + /// + /// The keymap will automatically be sent to it /// /// This should be done first, before anything else is done with this keyboard. - pub fn send_keymap(&self, kbd: &wl_keyboard::WlKeyboard) { + pub(crate) fn new_kbd(&self, kbd: wl_keyboard::WlKeyboard) { trace!(self.arc.logger, "Sending keymap to client"); kbd.keymap( wl_keyboard::KeymapFormat::XkbV1, self.arc.keymap_file.as_raw_fd(), self.arc.keymap_len, ); + let mut guard = self.arc.internal.lock().unwrap(); + if kbd.version() >= 4 { + kbd.repeat_info(guard.repeat_rate, guard.repeat_delay); + } + guard.known_kbds.push(kbd); + } + + /// Change the repeat info configured for this keyboard + pub fn change_repeat_info(&self, rate: i32, delay: i32) { + let mut guard = self.arc.internal.lock().unwrap(); + guard.repeat_delay = delay; + guard.repeat_rate = rate; + for kbd in &guard.known_kbds { + kbd.repeat_info(rate, delay); + } + } + + /// Performs an internal cleanup of known kbds + /// + /// Drops any wl_keyboard that is no longer alive + pub(crate) fn cleanup_old_kbds(&self) { + let mut guard = self.arc.internal.lock().unwrap(); + guard + .known_kbds + .retain(|kbd| kbd.status() != Liveness::Dead); } } diff --git a/src/seat/mod.rs b/src/seat/mod.rs new file mode 100644 index 0000000..cdc2516 --- /dev/null +++ b/src/seat/mod.rs @@ -0,0 +1,278 @@ +//! Seat global utilities +//! +//! This module provides you with utilities for handling the seat globals +//! and the associated input wayland objects. +//! +//! ## How to use it +//! +//! ### Initialization +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! +//! use smithay::seat::Seat; +//! +//! # fn main(){ +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! // insert the seat: +//! let (seat_state_token, seat_global) = Seat::new( +//! &mut event_loop, +//! "seat-0".into(), // the name of the seat, will be advertize to clients +//! None /* insert a logger here*/ +//! ); +//! # } +//! ``` +//! +//! ### Run usage +//! +//! Once the seat is initialized, you can add capabilities to it. +//! +//! Currently, only pointer and keyboard capabilities are supported by +//! smithay. +//! +//! You can add these capabilities via methods of the `Seat` struct that was +//! inserted in the event loop, that you can retreive via its token: +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! # +//! # use smithay::seat::Seat; +//! # +//! # fn main(){ +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! # let (seat_state_token, seat_global) = Seat::new( +//! # &mut event_loop, +//! # "seat-0".into(), // the name of the seat, will be advertize to clients +//! # None /* insert a logger here*/ +//! # ); +//! let pointer_handle = event_loop.state().get_mut(&seat_state_token).add_pointer(); +//! # } +//! ``` +//! +//! These handles can be cloned and sent accross thread, so you can keep one around +//! in your event-handling code to forward inputs to your clients. + +mod keyboard; +mod pointer; + +pub use self::keyboard::{Error as KeyboardError, KeyboardHandle}; +pub use self::pointer::PointerHandle; +use wayland_server::{Client, EventLoop, EventLoopHandle, Global, Liveness, Resource, StateToken}; +use wayland_server::protocol::{wl_keyboard, wl_pointer, wl_seat}; + +/// Internal data of a seat global +/// +/// This struct gives you access to the control of the +/// capabilities of the associated seat. +/// +/// It is directly inserted in the event loop by its `new` method. +/// +/// See module-level documentation for details of use. +pub struct Seat { + log: ::slog::Logger, + name: String, + pointer: Option, + keyboard: Option, + known_seats: Vec, +} + +impl Seat { + /// Create a new seat global + /// + /// A new seat global is created with given name and inserted + /// into this event loop. + /// + /// You are provided with the state token to retrieve it (allowing + /// you to add or remove capabilities from it), and the global handle, + /// in case you want to remove it. + pub fn new(evl: &mut EventLoop, name: String, logger: L) + -> (StateToken, Global>) + where + L: Into>, + { + let log = ::slog_or_stdlog(logger); + let seat = Seat { + log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())), + name: name, + pointer: None, + keyboard: None, + known_seats: Vec::new(), + }; + let token = evl.state().insert(seat); + // TODO: support version 5 (axis) + let global = evl.register_global(4, seat_global_bind, token.clone()); + (token, global) + } + + /// Adds the pointer capability to this seat + /// + /// You are provided a `PointerHandle`, which allows you to send input events + /// to this keyboard. This handle can be cloned and sent accross threads. + /// + /// Calling this method on a seat that already has a pointer capability + /// will overwrite it, and will be seen by the clients as if the + /// mouse was unplugged and a new one was plugged. + pub fn add_pointer(&mut self) -> PointerHandle { + let pointer = self::pointer::create_pointer_handler(); + if self.pointer.is_some() { + // there is already a pointer, remove it and notify the clients + // of the change + self.pointer = None; + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + } + self.pointer = Some(pointer.clone()); + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + pointer + } + + /// Remove the pointer capability from this seat + /// + /// Clients will be appropriately notified. + pub fn remove_pointer(&mut self) { + if self.pointer.is_some() { + self.pointer = None; + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + } + } + + /// Adds the keyboard capability to this seat + /// + /// You are provided a `KbdHandle`, which allows you to send input events + /// to this keyboard. This handle can be cloned and sent accross threads. + /// + /// You also provide a Model/Layout/Variant/Options specification of the + /// keymap to be used for this keyboard, as well as any repeat-info that + /// will be forwarded to the clients. + /// + /// Calling this method on a seat that already has a keyboard capability + /// will overwrite it, and will be seen by the clients as if the + /// keyboard was unplugged and a new one was plugged. + pub fn add_keyboard(&mut self, model: &str, layout: &str, variant: &str, options: Option, + repeat_delay: i32, repeat_rate: i32) + -> Result { + let keyboard = self::keyboard::create_keyboard_handler( + "evdev", // we need this one + model, + layout, + variant, + options, + repeat_delay, + repeat_rate, + self.log.clone(), + )?; + if self.keyboard.is_some() { + // there is already a keyboard, remove it and notify the clients + // of the change + self.keyboard = None; + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + } + self.keyboard = Some(keyboard.clone()); + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + Ok(keyboard) + } + + /// Remove the keyboard capability from this seat + /// + /// Clients will be appropriately notified. + pub fn remove_keyboard(&mut self) { + if self.keyboard.is_some() { + self.keyboard = None; + let caps = self.compute_caps(); + for seat in &self.known_seats { + seat.capabilities(caps); + } + } + } + + /// Cleanup internal states from old resources + /// + /// Deletes all remnnant of ressources from clients that + /// are now disconnected. + /// + /// It can be wise to run this from time to time. + pub fn cleanup(&mut self) { + if let Some(ref pointer) = self.pointer { + pointer.cleanup_old_pointers(); + } + if let Some(ref kbd) = self.keyboard { + kbd.cleanup_old_kbds(); + } + self.known_seats.retain(|s| s.status() == Liveness::Alive); + } + + fn compute_caps(&self) -> wl_seat::Capability { + let mut caps = wl_seat::Capability::empty(); + if self.pointer.is_some() { + caps |= wl_seat::Pointer; + } + if self.keyboard.is_some() { + caps |= wl_seat::Keyboard; + } + caps + } +} + +fn seat_global_bind(evlh: &mut EventLoopHandle, token: &mut StateToken, _: &Client, + seat: wl_seat::WlSeat) { + evlh.register(&seat, seat_implementation(), token.clone(), None); + let seat_mgr = evlh.state().get_mut(token); + seat.name(seat_mgr.name.clone()); + seat.capabilities(seat_mgr.compute_caps()); + seat_mgr.known_seats.push(seat); +} + +fn seat_implementation() -> wl_seat::Implementation> { + wl_seat::Implementation { + get_pointer: |evlh, token, _, _, pointer| { + evlh.register(&pointer, pointer_implementation(), (), None); + if let Some(ref ptr_handle) = evlh.state().get(token).pointer { + ptr_handle.new_pointer(pointer); + } else { + // we should send a protocol error... but the protocol does not allow + // us, so this pointer will just remain inactive ¯\_(ツ)_/¯ + } + }, + get_keyboard: |evlh, token, _, _, keyboard| { + evlh.register(&keyboard, keyboard_implementation(), (), None); + if let Some(ref kbd_handle) = evlh.state().get(token).keyboard { + kbd_handle.new_kbd(keyboard); + } else { + // same, should error but cant + } + }, + get_touch: |_evlh, _token, _, _, _touch| { + // TODO + }, + release: |_, _, _, _| {}, + } +} + +fn pointer_implementation() -> wl_pointer::Implementation<()> { + wl_pointer::Implementation { + set_cursor: |_, _, _, _, _, _, _, _| {}, + release: |_, _, _, _| {}, + } +} + +fn keyboard_implementation() -> wl_keyboard::Implementation<()> { + wl_keyboard::Implementation { + release: |_, _, _, _| {}, + } +} diff --git a/src/seat/pointer.rs b/src/seat/pointer.rs new file mode 100644 index 0000000..43b2599 --- /dev/null +++ b/src/seat/pointer.rs @@ -0,0 +1,121 @@ +use std::sync::{Arc, Mutex}; +use wayland_server::{Liveness, Resource}; +use wayland_server::protocol::{wl_pointer, wl_surface}; + +// TODO: handle pointer surface role + +struct PointerInternal { + known_pointers: Vec, + focus: Option, +} + +impl PointerInternal { + fn new() -> PointerInternal { + PointerInternal { + known_pointers: Vec::new(), + focus: None, + } + } + + fn with_focused_pointers(&self, mut f: F) + where + F: FnMut(&wl_pointer::WlPointer, &wl_surface::WlSurface), + { + if let Some(ref focus) = self.focus { + for ptr in &self.known_pointers { + if ptr.same_client_as(focus) { + f(ptr, focus) + } + } + } + } +} + +/// An handle to a keyboard handler +/// +/// It can be cloned and all clones manipulate the same internal state. Clones +/// can also be sent across threads. +/// +/// This handle gives you access to an interface to send pointer events to your +/// clients. +#[derive(Clone)] +pub struct PointerHandle { + inner: Arc>, +} + +impl PointerHandle { + pub(crate) fn new_pointer(&self, pointer: wl_pointer::WlPointer) { + let mut guard = self.inner.lock().unwrap(); + guard.known_pointers.push(pointer); + } + + /// Notify that the pointer moved + /// + /// You provide the new location of the pointer, in the form of: + /// + /// - `None` if the pointer is not on top of a client surface + /// - `Some(surface, x, y)` if the pointer is focusing surface `surface`, + /// at location `(x, y)` relative to this surface + /// + /// This will internally take care of notifying the appropriate client objects + /// of enter/motion/leave events. + pub fn motion(&self, location: Option<(&wl_surface::WlSurface, f64, f64)>, serial: u32, time: u32) { + let mut guard = self.inner.lock().unwrap(); + // do we leave a surface ? + let mut leave = true; + if let Some(ref focus) = guard.focus { + if let Some((ref surface, _, _)) = location { + if focus.equals(surface) { + leave = false; + } + } + } + if leave { + guard.with_focused_pointers(|pointer, surface| { + pointer.leave(serial, surface); + }); + guard.focus = None; + } + + // do we enter one ? + if let Some((surface, x, y)) = location { + if guard.focus.is_none() { + guard.focus = surface.clone(); + guard.with_focused_pointers(|pointer, surface| { + pointer.enter(serial, surface, x, y); + }) + } else { + // we were on top of a surface and remained on it + guard.with_focused_pointers(|pointer, _| { + pointer.motion(time, x, y); + }) + } + } + } + + /// Notify that a button was pressed + /// + /// This will internally send the appropriate button event to the client + /// objects matching with the currently focused surface. + pub fn button(&self, button: u32, state: wl_pointer::ButtonState, serial: u32, time: u32) { + let guard = self.inner.lock().unwrap(); + guard.with_focused_pointers(|pointer, _| { + pointer.button(serial, time, button, state); + }) + } + + // TODO: handle axis + + pub(crate) fn cleanup_old_pointers(&self) { + let mut guard = self.inner.lock().unwrap(); + guard + .known_pointers + .retain(|p| p.status() != Liveness::Dead); + } +} + +pub(crate) fn create_pointer_handler() -> PointerHandle { + PointerHandle { + inner: Arc::new(Mutex::new(PointerInternal::new())), + } +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index a8b7048..dce9124 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -105,10 +105,11 @@ //! access from the `state()` of the event loop and the token returned by the init //! function. -use compositor::{CompositorToken, Rectangle}; +use compositor::CompositorToken; use compositor::roles::Role; use std::cell::RefCell; use std::rc::Rc; +use utils::Rectangle; 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::{EventLoop, EventLoopHandle, EventResult, Global, Liveness, Resource, StateToken}; diff --git a/src/shell/wl_handlers.rs b/src/shell/wl_handlers.rs index d97297e..53c3102 100644 --- a/src/shell/wl_handlers.rs +++ b/src/shell/wl_handlers.rs @@ -1,9 +1,10 @@ use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient, ShellClientData, ShellSurfaceIData, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, ToplevelState}; -use compositor::{CompositorToken, Rectangle}; +use compositor::CompositorToken; use compositor::roles::*; use std::sync::Mutex; +use utils::Rectangle; use wayland_protocols::unstable::xdg_shell::server::{zxdg_positioner_v6 as xdg_positioner, zxdg_toplevel_v6}; use wayland_server::{Client, EventLoopHandle, Resource}; use wayland_server::protocol::{wl_output, wl_shell, wl_shell_surface, wl_surface}; diff --git a/src/shell/xdg_handlers.rs b/src/shell/xdg_handlers.rs index 7a568fe..15311b9 100644 --- a/src/shell/xdg_handlers.rs +++ b/src/shell/xdg_handlers.rs @@ -1,9 +1,10 @@ use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient, ShellClientData, ShellSurfaceIData, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, ToplevelState}; -use compositor::{CompositorToken, Rectangle}; +use compositor::CompositorToken; use compositor::roles::*; use std::sync::Mutex; +use utils::Rectangle; 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, EventLoopHandle, Resource}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..5e1bc70 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,5 @@ +//! Various utilities functions and types + +mod rectangle; + +pub use self::rectangle::Rectangle; diff --git a/src/utils/rectangle.rs b/src/utils/rectangle.rs new file mode 100644 index 0000000..f3953f5 --- /dev/null +++ b/src/utils/rectangle.rs @@ -0,0 +1,20 @@ +/// A rectangle defined by its top-left corner and dimensions +#[derive(Copy, Clone, Debug)] +pub struct Rectangle { + /// horizontal position of the top-leftcorner of the rectangle, in surface coordinates + pub x: i32, + /// vertical position of the top-leftcorner of the rectangle, in surface coordinates + pub y: i32, + /// width of the rectangle + pub width: i32, + /// height of the rectangle + pub height: i32, +} + +impl Rectangle { + /// Checks wether given point is inside a rectangle + pub fn contains(&self, point: (i32, i32)) -> bool { + let (x, y) = point; + (x >= self.x) && (x < self.x + self.width) && (y >= self.y) && (y < self.y + self.height) + } +}