diff --git a/.rustfmt.toml b/.rustfmt.toml index 7e41aae..82fb65d 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -4,8 +4,8 @@ fn_args_layout = "Visual" fn_arg_intent = "Tabbed" reorder_imports = true reorder_imported_names = true -report_todo = "Always" -report_fixme = "Always" +report_todo = "Never" +report_fixme = "Never" normalize_comments = true use_try_shorthand = true max_width = 110 diff --git a/.travis.yml b/.travis.yml index cb938e1..885f206 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,8 @@ branches: before_script: - export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH - which rustfmt || cargo install rustfmt + - 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" diff --git a/Cargo.toml b/Cargo.toml index 025a343..e5adedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,18 @@ version = "0.1.0" authors = ["Victor Berger "] [dependencies] -wayland-server = "0.8.4" +wayland-server = "0.8.6" nix = "0.7.0" +glutin = { version = "~0.7.4", optional = true } +glium = { version = "~0.16.0", optional = true } slog = { version = "~1.5.2", features = ["max_level_trace", "release_max_level_info"] } slog-stdlog = "~1.1.0" clippy = { version = "*", optional = true } [dev-dependencies] slog-term = "~1.5" + +[features] +default = ["backend_glutin", "renderer_glium"] +backend_glutin = ["glutin", "wayland-server/dlopen"] +renderer_glium = ["glium"] diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..e435792 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,36 @@ +extern crate wayland_server; +extern crate smithay; + +use smithay::backend::glutin; +use smithay::backend::input::InputBackend; +use smithay::shm::ShmGlobal; +use wayland_server::protocol::wl_shm; + +fn main() { + let (_, mut event_loop) = wayland_server::create_display(); + + // Insert the ShmGlobal as a handler to your event loop + // Here, we specify tha the standard Argb8888 and Xrgb8888 is the only supported. + let handler_id = + event_loop.add_handler_with_init(ShmGlobal::new(vec![], + None /* we don't provide a logger here */)); + + // Register this handler to advertise a wl_shm global of version 1 + let shm_global = event_loop.register_global::(handler_id, 1); + + // Retrieve the shm token for later use to access the buffers + let shm_token = { + let state = event_loop.state(); + state.get_handler::(handler_id).get_token() + }; + + // Initialize a simple backend for testing + let (mut renderer, mut input) = glutin::init_windowed().unwrap(); + + // TODO render stuff + + // TODO put input handling on the event loop + input.dispatch_new_events().unwrap(); + + event_loop.run().unwrap(); +} diff --git a/src/backend/glium.rs b/src/backend/glium.rs new file mode 100644 index 0000000..7814443 --- /dev/null +++ b/src/backend/glium.rs @@ -0,0 +1,50 @@ + + +use backend::graphics::opengl::{OpenglGraphicsBackend, SwapBuffersError}; +use glium::SwapBuffersError as GliumSwapBuffersError; +use glium::backend::Backend; + +use std::os::raw::c_void; + +impl From for GliumSwapBuffersError { + fn from(error: SwapBuffersError) -> Self { + match error { + SwapBuffersError::ContextLost => GliumSwapBuffersError::ContextLost, + SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped, + } + } +} + +pub struct GliumGraphicBackend(T); + +pub trait IntoGlium: OpenglGraphicsBackend + Sized { + fn into_glium(self) -> GliumGraphicBackend; +} + +impl IntoGlium for T { + fn into_glium(self) -> GliumGraphicBackend { + GliumGraphicBackend(self) + } +} + +unsafe impl Backend for GliumGraphicBackend { + fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> { + self.0.swap_buffers().map_err(Into::into) + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.0.get_proc_address(symbol) as *const c_void + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + self.0.get_framebuffer_dimensions() + } + + fn is_current(&self) -> bool { + self.0.is_current() + } + + unsafe fn make_current(&self) { + self.0.make_current() + } +} diff --git a/src/backend/glutin.rs b/src/backend/glutin.rs new file mode 100644 index 0000000..ff97418 --- /dev/null +++ b/src/backend/glutin.rs @@ -0,0 +1,422 @@ +//! Implementation of backend traits for types provided by `glutin` + + +use backend::{SeatInternal, TouchSlotInternal}; +use backend::graphics::opengl::{Api, OpenglGraphicsBackend, PixelFormat, SwapBuffersError}; +use backend::input::{Axis, AxisSource, InputBackend, InputHandler, KeyState, MouseButton, MouseButtonState, + Seat, SeatCapabilities, TouchEvent, TouchSlot}; +use glutin::{Api as GlutinApi, MouseButton as GlutinMouseButton, PixelFormat as GlutinPixelFormat}; +use glutin::{ContextError, CreationError, ElementState, Event, GlContext, HeadlessContext, + HeadlessRendererBuilder, MouseScrollDelta, Touch, TouchPhase, Window, WindowBuilder}; +use nix::c_void; +use std::error::Error; +use std::fmt; +use std::rc::Rc; + +/// Create a new `GlutinHeadlessRenderer` which implements the `OpenglRenderer` graphics +/// backend trait +pub fn init_headless_renderer() -> Result { + init_headless_renderer_from_builder(HeadlessRendererBuilder::new(1024, 600)) +} + +/// Create a new `GlutinHeadlessRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait, with a given already configured `HeadlessRendererBuilder` for +/// customization +pub fn init_headless_renderer_from_builder(builder: HeadlessRendererBuilder) + -> Result { + let (w, h) = builder.dimensions; + let context = builder.build_strict()?; + + Ok(GlutinHeadlessRenderer::new(context, w, h)) +} + +/// Create a new `GlutinWindowedRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait +pub fn init_windowed_renderer() -> Result { + init_windowed_renderer_from_builder(WindowBuilder::new()) +} + +/// Create a new `GlutinWindowedRenderer`, which implements the `OpenglRenderer` graphics +/// backend trait, with a given already configured `WindowBuilder` for customization. +pub fn init_windowed_renderer_from_builder(builder: WindowBuilder) + -> Result { + let window = Rc::new(builder.build_strict()?); + Ok(GlutinWindowedRenderer::new(window)) +} + +/// Create a new `glutin` `Window`. Returns a `GlutinWindowedRenderer` implementing +/// the `OpenglRenderer` graphics backend trait and a `GlutinInputBackend` implementing +/// the `InputBackend` trait. +pub fn init_windowed() -> Result<(GlutinWindowedRenderer, GlutinInputBackend), CreationError> { + init_windowed_from_builder(WindowBuilder::new()) +} + +/// Create a new `glutin` `Window` with a given already configured `WindowBuilder` for +/// customization. Returns a `GlutinWindowedRenderer` implementing +/// the `OpenglRenderer` graphics backend trait and a `GlutinInputBackend` implementing +/// the `InputBackend` trait. +pub fn init_windowed_from_builder(builder: WindowBuilder) + -> Result<(GlutinWindowedRenderer, GlutinInputBackend), CreationError> { + let window = Rc::new(builder.build_strict()?); + Ok((GlutinWindowedRenderer::new(window.clone()), GlutinInputBackend::new(window))) +} + +/// Headless Opengl Context created by `glutin`. Implements the `OpenglGraphicsBackend` graphics +/// backend trait. +pub struct GlutinHeadlessRenderer { + context: HeadlessContext, + w: u32, + h: u32, +} + +impl GlutinHeadlessRenderer { + fn new(context: HeadlessContext, w: u32, h: u32) -> GlutinHeadlessRenderer { + GlutinHeadlessRenderer { + context: context, + w: w, + h: h, + } + } +} + +impl OpenglGraphicsBackend for GlutinHeadlessRenderer { + #[inline] + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + match self.context.swap_buffers() { + Ok(()) => Ok(()), + Err(ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e), + Err(ContextError::ContextLost) => Err(SwapBuffersError::ContextLost), + } + } + + #[inline] + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.context.get_proc_address(symbol) as *const _ + } + + #[inline] + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + (self.w, self.h) + } + + #[inline] + fn is_current(&self) -> bool { + self.context.is_current() + } + + #[inline] + unsafe fn make_current(&self) { + self.context.make_current().unwrap(); + } + + fn get_api(&self) -> Api { + self.context.get_api().into() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.context.get_pixel_format().into() + } +} + +/// Window with an active Opengl Context created by `glutin`. Implements the +/// `OpenglGraphicsBackend` graphics backend trait. +pub struct GlutinWindowedRenderer { + window: Rc, +} + +impl GlutinWindowedRenderer { + fn new(window: Rc) -> GlutinWindowedRenderer { + GlutinWindowedRenderer { window: window } + } +} + +impl OpenglGraphicsBackend for GlutinWindowedRenderer { + #[inline] + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + match self.window.swap_buffers() { + Ok(()) => Ok(()), + Err(ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e), + Err(ContextError::ContextLost) => Err(SwapBuffersError::ContextLost), + } + } + + #[inline] + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.window.get_proc_address(symbol) as *const _ + } + + #[inline] + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + let (width, height) = self.window.get_inner_size().unwrap_or((800, 600)); // TODO: 800x600 ? + let scale = self.window.hidpi_factor(); + ((width as f32 * scale) as u32, (height as f32 * scale) as u32) + } + + #[inline] + fn is_current(&self) -> bool { + self.window.is_current() + } + + #[inline] + unsafe fn make_current(&self) { + self.window.make_current().unwrap(); + } + + fn get_api(&self) -> Api { + self.window.get_api().into() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.window.get_pixel_format().into() + } +} + +/// Errors that may happen when driving the event loop of `GlutinInputBackend` +#[derive(Debug)] +pub enum GlutinInputError { + /// The underlying `glutin` `Window` was closed. No further events can be processed. + /// + /// See `GlutinInputBackend::process_new_events`. + WindowClosed, +} + +impl Error for GlutinInputError { + fn description(&self) -> &str { + match *self { + GlutinInputError::WindowClosed => "Glutin Window was closed", + } + } +} + +impl fmt::Display for GlutinInputError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +/// Abstracted event loop of a `glutin` `Window` implementing the `InputBackend` trait +/// +/// You need to call `process_new_events` periodically to receive any events. +pub struct GlutinInputBackend { + window: Rc, + time_counter: u32, + seat: Seat, + input_config: (), + handler: Option + 'static>>, +} + +impl InputBackend for GlutinInputBackend { + type InputConfig = (); + type EventError = GlutinInputError; + + fn set_handler + 'static>(&mut self, mut handler: H) { + if self.handler.is_some() { + self.clear_handler(); + } + handler.on_seat_created(&self.seat); + self.handler = Some(Box::new(handler)); + } + + fn get_handler(&mut self) -> Option<&mut InputHandler> { + self.handler.as_mut().map(|handler| handler as &mut InputHandler) + } + + fn clear_handler(&mut self) { + if let Some(mut handler) = self.handler.take() { + handler.on_seat_destroyed(&self.seat); + } + } + + fn input_config(&mut self) -> &mut Self::InputConfig { + &mut self.input_config + } + + fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()> { + if let Some((win_x, win_y)) = self.window.get_position() { + self.window.set_cursor_position(win_x + x as i32, win_y + y as i32) + } else { + Err(()) + } + } + + /// Processes new events of the underlying event loop to drive the set `InputHandler`. + /// + /// You need to periodically call this function to keep the underlying event loop and + /// `Window` active. Otherwise the window may no respond to user interaction and no + /// input events will be received by a set `InputHandler`. + /// + /// Returns an error if the `Window` the window has been closed. Calling + /// `process_new_events` again after the `Window` has been closed is considered an + /// application error and unspecified baviour may occur. + /// + /// The linked `GlutinWindowedRenderer` will error with a lost Context and should + /// not be used anymore as well. + fn dispatch_new_events(&mut self) -> Result<(), GlutinInputError> { + for event in self.window.poll_events() { + if let Some(ref mut handler) = self.handler { + match event { + Event::KeyboardInput(state, key_code, _) => { + handler.on_keyboard_key(&self.seat, + self.time_counter, + key_code as u32, + state.into(), + 1) + } + Event::MouseMoved(x, y) => { + handler.on_pointer_move(&self.seat, self.time_counter, (x as u32, y as u32)) + } + Event::MouseWheel(delta, _) => { + match delta { + MouseScrollDelta::LineDelta(x, y) => { + if x != 0.0 { + handler.on_pointer_scroll(&self.seat, + self.time_counter, + Axis::Horizontal, + AxisSource::Wheel, + x as f64); + } + if y != 0.0 { + handler.on_pointer_scroll(&self.seat, + self.time_counter, + Axis::Vertical, + AxisSource::Wheel, + y as f64); + } + } + MouseScrollDelta::PixelDelta(x, y) => { + if x != 0.0 { + handler.on_pointer_scroll(&self.seat, + self.time_counter, + Axis::Vertical, + AxisSource::Continous, + x as f64); + } + if y != 0.0 { + handler.on_pointer_scroll(&self.seat, + self.time_counter, + Axis::Horizontal, + AxisSource::Continous, + y as f64); + } + } + } + } + Event::MouseInput(state, button) => { + handler.on_pointer_button(&self.seat, self.time_counter, button.into(), state.into()) + } + Event::Touch(Touch { phase: TouchPhase::Started, location: (x, y), id }) => { + handler.on_touch(&self.seat, + self.time_counter, + TouchEvent::Down { + slot: Some(TouchSlot::new(id as u32)), + x: x, + y: y, + }) + } + Event::Touch(Touch { phase: TouchPhase::Moved, location: (x, y), id }) => { + handler.on_touch(&self.seat, + self.time_counter, + TouchEvent::Motion { + slot: Some(TouchSlot::new(id as u32)), + x: x, + y: y, + }) + } + Event::Touch(Touch { phase: TouchPhase::Ended, location: (x, y), id }) => { + handler.on_touch(&self.seat, + self.time_counter, + TouchEvent::Motion { + slot: Some(TouchSlot::new(id as u32)), + x: x, + y: y, + }); + handler.on_touch(&self.seat, + self.time_counter, + TouchEvent::Up { slot: Some(TouchSlot::new(id as u32)) }); + } + Event::Touch(Touch { phase: TouchPhase::Cancelled, id, .. }) => { + handler.on_touch(&self.seat, + self.time_counter, + TouchEvent::Cancel { slot: Some(TouchSlot::new(id as u32)) }) + } + Event::Closed => return Err(GlutinInputError::WindowClosed), + _ => {} + } + self.time_counter += 1; + } + } + Ok(()) + } +} + +impl GlutinInputBackend { + fn new(window: Rc) -> GlutinInputBackend { + GlutinInputBackend { + window: window, + time_counter: 0, + seat: Seat::new(0, + SeatCapabilities { + pointer: true, + keyboard: true, + touch: true, + }), + input_config: (), + handler: None, + } + } +} + +impl From for Api { + fn from(api: GlutinApi) -> Self { + match api { + GlutinApi::OpenGl => Api::OpenGl, + GlutinApi::OpenGlEs => Api::OpenGlEs, + GlutinApi::WebGl => Api::WebGl, + } + } +} + +impl From for PixelFormat { + fn from(format: GlutinPixelFormat) -> Self { + PixelFormat { + hardware_accelerated: format.hardware_accelerated, + color_bits: format.color_bits, + alpha_bits: format.alpha_bits, + depth_bits: format.depth_bits, + stencil_bits: format.stencil_bits, + stereoscopy: format.stereoscopy, + double_buffer: format.double_buffer, + multisampling: format.multisampling, + srgb: format.srgb, + } + } +} + +impl From for MouseButton { + fn from(button: GlutinMouseButton) -> MouseButton { + match button { + GlutinMouseButton::Left => MouseButton::Left, + GlutinMouseButton::Right => MouseButton::Right, + GlutinMouseButton::Middle => MouseButton::Middle, + GlutinMouseButton::Other(num) => MouseButton::Other(num), + } + } +} + +impl From for KeyState { + fn from(state: ElementState) -> Self { + match state { + ElementState::Pressed => KeyState::Pressed, + ElementState::Released => KeyState::Released, + } + } +} + +impl From for MouseButtonState { + fn from(state: ElementState) -> Self { + match state { + ElementState::Pressed => MouseButtonState::Pressed, + ElementState::Released => MouseButtonState::Released, + } + } +} diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs new file mode 100644 index 0000000..75b2ee6 --- /dev/null +++ b/src/backend/graphics/mod.rs @@ -0,0 +1,6 @@ +//! Common traits for various ways to renderer on a given graphics backend. +//! +//! Note: Not every api may be supported by every backend + +pub mod software; +pub mod opengl; diff --git a/src/backend/graphics/opengl.rs b/src/backend/graphics/opengl.rs new file mode 100644 index 0000000..a03f93b --- /dev/null +++ b/src/backend/graphics/opengl.rs @@ -0,0 +1,94 @@ +//! Common traits and types for opengl rendering on graphics backends + +use nix::c_void; + +/// Error that can happen when swapping buffers. +#[derive(Debug, Clone)] +pub enum SwapBuffersError { + /// The OpenGL context has been lost and needs to be recreated. + /// + /// All the objects associated to it (textures, buffers, programs, etc.) + /// need to be recreated from scratch. + /// + /// Operations will have no effect. Functions that read textures, buffers, etc. + /// from OpenGL will return uninitialized data instead. + /// + /// A context loss usually happens on mobile devices when the user puts the + /// application on sleep and wakes it up later. However any OpenGL implementation + /// can theoretically lose the context at any time. + ContextLost, + /// The buffers have already been swapped. + /// + /// This error can be returned when `swap_buffers` has been called multiple times + /// without any modification in between. + AlreadySwapped, +} + +/// All APIs related to OpenGL that you can possibly get +/// through OpenglRenderer implementations +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Api { + /// The classical OpenGL. Available on Windows, Linux, OS/X. + OpenGl, + /// OpenGL embedded system. Available on Linux, Android. + OpenGlEs, + /// OpenGL for the web. Very similar to OpenGL ES. + WebGl, +} + +/// Describes the pixel format of the main framebuffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PixelFormat { + /// is the format hardware accelerated + pub hardware_accelerated: bool, + /// number of bits used for colors + pub color_bits: u8, + /// number of bits used for alpha channel + pub alpha_bits: u8, + /// number of bits used for depth channel + pub depth_bits: u8, + /// number of bits used for stencil buffer + pub stencil_bits: u8, + /// is stereoscopy enabled + pub stereoscopy: bool, + /// is double buffering enabled + pub double_buffer: bool, + /// number of samples used for multisampling if enabled + pub multisampling: Option, + /// is srgb enabled + pub srgb: bool, +} + +/// Trait that describes objects that have an OpenGl context +/// and can be used to render upon +pub trait OpenglGraphicsBackend { + /// Swaps buffers at the end of a frame. + fn swap_buffers(&self) -> Result<(), SwapBuffersError>; + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void; + + /// Returns the dimensions of the window, or screen, etc in points. + /// + /// That are the scaled pixels of the underlying graphics backend. + /// For nested compositors this will respect the scaling of the root compositor. + /// For drawing directly onto hardware this unit will be equal to actual pixels. + fn get_framebuffer_dimensions(&self) -> (u32, u32); + + /// Returns true if the OpenGL context is the current one in the thread. + fn is_current(&self) -> bool; + + /// Makes the OpenGL context the current context in the current thread. + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + unsafe fn make_current(&self); + + /// Returns the OpenGL API being used. + fn get_api(&self) -> Api; + + /// Returns the pixel format of the main framebuffer of the context. + fn get_pixel_format(&self) -> PixelFormat; +} diff --git a/src/backend/graphics/software.rs b/src/backend/graphics/software.rs new file mode 100644 index 0000000..278885a --- /dev/null +++ b/src/backend/graphics/software.rs @@ -0,0 +1,17 @@ +//! Common traits and types used for software rendering on graphics backends + +use std::error::Error; +use wayland_server::protocol::wl_shm::Format; + +/// Trait that describes objects providing a software rendering implementation +pub trait CpuGraphicsBackend { + /// Render a given buffer of a given format at a specified place in the framebuffer + /// + /// # Error + /// Returns an error if the buffer size does not match the required amount of pixels + /// for the given size or if the position and size is out of scope of the framebuffer. + fn render(&mut self, buffer: &[u8], format: Format, at: (u32, u32), size: (u32, u32)) -> Result<(), E>; + + /// Returns the dimensions of the Framebuffer + fn get_framebuffer_dimensions(&self) -> (u32, u32); +} diff --git a/src/backend/input.rs b/src/backend/input.rs new file mode 100644 index 0000000..03d0ca5 --- /dev/null +++ b/src/backend/input.rs @@ -0,0 +1,329 @@ +//! Common traits for input backends to receive input from. + +use backend::{SeatInternal, TouchSlotInternal}; + +use std::error::Error; + +/// A seat describes a group of input devices and at least one +/// graphics device belonging together. +/// +/// By default only one seat exists for most systems and smithay backends +/// however multiseat configurations are possible and should be treated as +/// separated users, all with their own focus, input and cursor available. +/// +/// Seats can be checked for equality and hashed for differentiation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Seat { + id: u32, + capabilities: SeatCapabilities, +} + +impl SeatInternal for Seat { + fn new(id: u32, capabilities: SeatCapabilities) -> Seat { + Seat { + id: id, + capabilities: capabilities, + } + } +} + +impl Seat { + /// Get the currently capabilities of this `Seat` + pub fn capabilities(&self) -> &SeatCapabilities { + &self.capabilities + } +} + +/// Describes capabilities a `Seat` has. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SeatCapabilities { + /// `Seat` has a pointer + pub pointer: bool, + /// `Seat` has a keyboard + pub keyboard: bool, + /// `Seat` has a touchscreen + pub touch: bool, +} + +/// State of key on a keyboard. Either pressed or released +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum KeyState { + /// Key is released + Released, + /// Key is pressed + Pressed, +} + +/// A particular mouse button +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum MouseButton { + /// Left mouse button + Left, + /// Middle mouse button + Middle, + /// Right mouse button + Right, + /// Other mouse button with index + Other(u8), +} + +/// State of a button on a mouse. Either pressed or released +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum MouseButtonState { + /// Button is released + Released, + /// Button is pressed + Pressed, +} + +/// Axis when scrolling +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Axis { + /// Vertical axis + Vertical, + /// Horizonal axis + Horizontal, +} + +/// Source of an axis when scrolling +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum AxisSource { + /// Finger. Mostly used for trackpads. + /// + /// Guarantees that a scroll sequence is terminated with a scroll value of 0. + /// A caller may use this information to decide on whether kinetic scrolling should + /// be triggered on this scroll sequence. + /// + /// The coordinate system is identical to the + /// cursor movement, i.e. a scroll value of 1 represents the equivalent relative + /// motion of 1. + Finger, + /// Continous scrolling device. Almost identical to `Finger` + /// + /// No terminating event is guaranteed (though it may happen). + /// + /// The coordinate system is identical to + /// the cursor movement, i.e. a scroll value of 1 represents the equivalent relative + /// motion of 1. + Continous, + /// Scroll wheel. + /// + /// No terminating event is guaranteed (though it may happen). Scrolling is in + /// discrete steps. It is up to the caller how to interpret such different step sizes. + Wheel, + /// Scrolling through tilting the scroll wheel. + /// + /// No terminating event is guaranteed (though it may happen). Scrolling is in + /// discrete steps. It is up to the caller how to interpret such different step sizes. + WheelTilt, +} + +/// Slot of a different touch event. +/// +/// Touch events are groubed by slots, usually to identify different +/// fingers on a multi-touch enabled input device. Events should only +/// be interpreted in the context of other events on the same slot. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TouchSlot { + id: u32, +} + +impl TouchSlotInternal for TouchSlot { + fn new(id: u32) -> Self { + TouchSlot { id: id } + } +} + +/// Touch event +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum TouchEvent { + /// The start of an event at a given position (x, y). + /// + /// If the device has multi-touch capabilities a slot is given. + Down { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + /// Absolute x-coordinate of the touch position. + x: f64, + /// Absolute y-coordinate of the touch position. + y: f64, + }, + /// Movement of a touch on the device surface to a given position (x, y). + /// + /// If the device has multi-touch capabilities a slot is given. + Motion { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + /// Absolute x-coordinate of the final touch position after the motion. + x: f64, + /// Absolute y-coordinate of the final touch position after the motion. + y: f64, + }, + /// Stop of an event chain. + /// + /// If the device has multi-touch capabilities a slot is given. + Up { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + }, + /// Cancel of an event chain. All previous events in the chain should be ignored. + /// + /// If the device has multi-touch capabilities a slot is given. + Cancel { + /// `TouchSlot`, if the device has multi-touch capabilities + slot: Option, + }, + /// Signals the end of a set of touchpoints at one device sample time. + Frame, +} + +/// Trait that describes objects providing a source of input events. All input backends +/// need to implemenent this and provide the same base gurantees about the presicion of +/// given events. +pub trait InputBackend: Sized { + /// Type of input device associated with the backend + type InputConfig; + + /// Type representing errors that may be returned when processing events + type EventError: Error; + + /// Sets a new handler for this `InputBackend` + fn set_handler + 'static>(&mut self, handler: H); + /// Get a reference to the currently set handler, if any + fn get_handler(&mut self) -> Option<&mut InputHandler>; + /// Clears the currently handler, if one is set + fn clear_handler(&mut self); + + /// Get current `InputConfig` + fn input_config(&mut self) -> &mut Self::InputConfig; + + /// Processes new events of the underlying backend and drives the `InputHandler`. + fn dispatch_new_events(&mut self) -> Result<(), Self::EventError>; + + /// Sets the cursor position, useful for e.g. pointer wrapping. + /// + /// Not guaranteed to be supported on every backend. The result usually + /// depends on the capability of the backend, but may also fail for certain + /// specific actions. See the backends documentation. + fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()>; +} + +/// Implement to receive input events from any `InputBackend`. +pub trait InputHandler { + /// Called when a new `Seat` has been created + fn on_seat_created(&mut self, seat: &Seat); + /// Called when an existing `Seat` has been destroyed. + fn on_seat_destroyed(&mut self, seat: &Seat); + /// Called when a `Seat`'s properties have changed. + fn on_seat_changed(&mut self, seat: &Seat); + + /// Called when a new keyboard event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `key_code` - Code of the pressed key. See linux/input-event-codes.h + /// - `state` - `KeyState` of the event + /// - `count` - Total number of keys pressed on all devices on the associated `Seat` + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_keyboard_key(&mut self, seat: &Seat, time: u32, key_code: u32, state: KeyState, count: u32); + /// Called when a new pointer movement event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `to` - Absolute screen coordinates of the pointer moved to. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_move(&mut self, seat: &Seat, time: u32, to: (u32, u32)); + /// Called when a new pointer button event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `button` - Which button was pressed.. + /// - `state` - `MouseButtonState` of the event + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_button(&mut self, seat: &Seat, time: u32, button: MouseButton, state: MouseButtonState); + /// Called when a new pointer scroll event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `axis` - `Axis` this event was generated for. + /// - `source` - Source of the scroll event. Important for interpretation of `amount`. + /// - `amount` - Amount of scrolling on the given `Axis`. See `source` for interpretation. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_pointer_scroll(&mut self, seat: &Seat, time: u32, axis: Axis, source: AxisSource, amount: f64); + /// Called when a new touch event was received. + /// + /// # Arguments + /// + /// - `seat` - The `Seat` the event belongs to + /// - `time` - A upward counting variable useful for event ordering. Makes no gurantees about actual time passed between events. + /// - `event` - Touch event recieved. See `TouchEvent`. + /// + /// # TODO: + /// - check if events can arrive out of order. + /// - Make stronger time guarantees + fn on_touch(&mut self, seat: &Seat, time: u32, event: TouchEvent); + + /// Called when the `InputConfig` was changed through an external event. + /// + /// What kind of events can trigger this call is completely backend dependent. + /// E.g. an input devices was attached/detached or changed it's own configuration. + fn on_input_config_changed(&mut self, config: &mut B::InputConfig); +} + +impl InputHandler for Box> { + fn on_seat_created(&mut self, seat: &Seat) { + (**self).on_seat_created(seat) + } + + fn on_seat_destroyed(&mut self, seat: &Seat) { + (**self).on_seat_destroyed(seat) + } + + fn on_seat_changed(&mut self, seat: &Seat) { + (**self).on_seat_changed(seat) + } + + fn on_keyboard_key(&mut self, seat: &Seat, time: u32, key_code: u32, state: KeyState, count: u32) { + (**self).on_keyboard_key(seat, time, key_code, state, count) + } + + fn on_pointer_move(&mut self, seat: &Seat, time: u32, to: (u32, u32)) { + (**self).on_pointer_move(seat, time, to) + } + + fn on_pointer_button(&mut self, seat: &Seat, time: u32, button: MouseButton, state: MouseButtonState) { + (**self).on_pointer_button(seat, time, button, state) + } + + fn on_pointer_scroll(&mut self, seat: &Seat, time: u32, axis: Axis, source: AxisSource, amount: f64) { + (**self).on_pointer_scroll(seat, time, axis, source, amount) + } + + fn on_touch(&mut self, seat: &Seat, time: u32, event: TouchEvent) { + (**self).on_touch(seat, time, event) + } + + fn on_input_config_changed(&mut self, config: &mut B::InputConfig) { + (**self).on_input_config_changed(config) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..6e01f97 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,34 @@ +//! Backend (rendering/input) creation helpers +//! +//! Collection of common traits and implementation about +//! rendering onto various targets and receiving input +//! from various sources. +//! +//! Supported graphics backends: +//! +//! - glutin (headless/windowed) +//! +//! Supported input backends: +//! +//! - glutin (windowed) + +pub mod input; +pub mod graphics; + +#[cfg(feature = "backend_glutin")] +pub mod glutin; + +#[cfg(feature = "renderer_glium")] +mod glium; +#[cfg(feature = "renderer_glium")] +pub use glium::*; + +/// Internal functions that need to be accessible by the different backend implementations + +trait SeatInternal { + fn new(id: u32, capabilities: input::SeatCapabilities) -> Self; +} + +trait TouchSlotInternal { + fn new(id: u32) -> Self; +} diff --git a/src/lib.rs b/src/lib.rs index b4eed74..56498d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,15 @@ extern crate wayland_server; extern crate nix; +#[cfg(feature = "backend_glutin")] +extern crate glutin; + +#[cfg(feature = "renderer_glium")] +extern crate glium; + #[macro_use] extern crate slog; extern crate slog_stdlog; pub mod shm; +pub mod backend;