diff --git a/Cargo.toml b/Cargo.toml index 3527288..f78f43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,22 +5,27 @@ authors = ["Victor Berger "] license = "MIT" [dependencies] -wayland-server = "0.9.1" +wayland-server = "0.9.4" nix = "0.7.0" xkbcommon = "0.2.1" tempfile = "2.1.5" slog = { version = "2.0.0" } slog-stdlog = "2.0.0-0.2" -glutin = { version = "~0.7.4", optional = true } +libloading = "0.4.0" +wayland-client = { version = "~0.8.6", optional = true } +winit = { git = "https://github.com/tomaka/winit.git", optional = true } glium = { version = "~0.16.0", optional = true } input = { version = "~0.1.1", optional = true } clippy = { version = "*", optional = true } +[build-dependencies] +gl_generator = "0.5" + [dev-dependencies] slog-term = "~1.5" [features] -default = ["backend_glutin", "backend_libinput", "renderer_glium"] -backend_glutin = ["glutin", "wayland-server/dlopen"] +default = ["backend_winit", "backend_libinput", "renderer_glium"] +backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] renderer_glium = ["glium"] backend_libinput = ["input"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0295cf5 --- /dev/null +++ b/build.rs @@ -0,0 +1,33 @@ +extern crate gl_generator; + +use gl_generator::{Api, Fallbacks, Profile, Registry}; + +use std::env; +use std::fs::File; +use std::path::PathBuf; + +fn main() { + let dest = PathBuf::from(&env::var("OUT_DIR").unwrap()); + + println!("cargo:rerun-if-changed=build.rs"); + + let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap(); + Registry::new(Api::Egl, + (1, 5), + Profile::Core, + Fallbacks::All, + ["EGL_KHR_create_context", + "EGL_EXT_create_context_robustness", + "EGL_KHR_create_context_no_error", + "EGL_KHR_platform_x11", + "EGL_KHR_platform_android", + "EGL_KHR_platform_wayland", + "EGL_KHR_platform_gbm", + "EGL_EXT_platform_base", + "EGL_EXT_platform_x11", + "EGL_MESA_platform_gbm", + "EGL_EXT_platform_wayland", + "EGL_EXT_platform_device"]) + .write_bindings(gl_generator::StructGenerator, &mut file) + .unwrap(); +} diff --git a/examples/simple.rs b/examples/simple.rs index e435792..8ed83c9 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,13 +1,19 @@ extern crate wayland_server; extern crate smithay; +extern crate glium; -use smithay::backend::glutin; +use glium::Surface; +use smithay::backend::graphics::glium::IntoGlium; use smithay::backend::input::InputBackend; +use smithay::backend::winit; use smithay::shm::ShmGlobal; use wayland_server::protocol::wl_shm; fn main() { - let (_, mut event_loop) = wayland_server::create_display(); + // Initialize a simple backend for testing + let (renderer, mut input) = winit::init().unwrap(); + + let (_display, 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. @@ -24,13 +30,17 @@ fn main() { state.get_handler::(handler_id).get_token() }; - // Initialize a simple backend for testing - let (mut renderer, mut input) = glutin::init_windowed().unwrap(); + // Init glium + let context = renderer.into_glium(); - // TODO render stuff - // TODO put input handling on the event loop - input.dispatch_new_events().unwrap(); + loop { + input.dispatch_new_events().unwrap(); - event_loop.run().unwrap(); + let mut frame = context.draw(); + frame.clear(None, Some((0.0, 0.0, 0.0, 1.0)), false, None, None); + frame.finish().unwrap(); + + event_loop.dispatch(Some(16)).unwrap(); + } } diff --git a/src/backend/glium.rs b/src/backend/glium.rs deleted file mode 100644 index 7814443..0000000 --- a/src/backend/glium.rs +++ /dev/null @@ -1,50 +0,0 @@ - - -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 deleted file mode 100644 index d09401a..0000000 --- a/src/backend/glutin.rs +++ /dev/null @@ -1,745 +0,0 @@ -//! Implementation of backend traits for types provided by `glutin` - - -use backend::{SeatInternal, TouchSlotInternal}; -use backend::graphics::GraphicsBackend; -use backend::graphics::opengl::{Api, OpenglGraphicsBackend, PixelFormat, SwapBuffersError}; -use backend::input::{Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, - KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, - PointerMotionAbsoluteEvent, Seat, SeatCapabilities, TouchCancelEvent, TouchDownEvent, - TouchMotionEvent, TouchSlot, TouchUpEvent, UnusedEvent}; -use glutin::{Api as GlutinApi, MouseButton as GlutinMouseButton, MouseCursor, - PixelFormat as GlutinPixelFormat}; -use glutin::{ContextError, CreationError, ElementState, Event, GlContext, HeadlessContext, - HeadlessRendererBuilder, MouseScrollDelta, Touch, TouchPhase, Window, WindowBuilder}; -use nix::c_void; -use std::cmp; -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 GraphicsBackend for GlutinHeadlessRenderer { - type CursorFormat = (); - - fn set_cursor_position(&mut self, _x: u32, _y: u32) -> Result<(), ()> { - // FIXME: Maybe save position? Is it of any use? - Ok(()) - } - - fn set_cursor_representation(&mut self, _cursor: ()) {} -} - -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 GraphicsBackend for GlutinWindowedRenderer { - type CursorFormat = MouseCursor; - - 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(()) - } - } - - fn set_cursor_representation(&mut self, cursor: MouseCursor) { - self.window.set_cursor(cursor); - } -} - -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, - key_counter: u32, - seat: Seat, - input_config: (), - handler: Option + 'static>>, -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `KeyboardKeyEvent` -pub struct GlutinKeyboardInputEvent { - time: u32, - key: u8, - count: u32, - state: ElementState, -} - -impl BackendEvent for GlutinKeyboardInputEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl KeyboardKeyEvent for GlutinKeyboardInputEvent { - fn key_code(&self) -> u32 { - self.key as u32 - } - - fn state(&self) -> KeyState { - self.state.into() - } - - fn count(&self) -> u32 { - self.count - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `PointerMotionAbsoluteEvent` -pub struct GlutinMouseMovedEvent { - window: Rc, - time: u32, - x: i32, - y: i32, -} - -impl BackendEvent for GlutinMouseMovedEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl PointerMotionAbsoluteEvent for GlutinMouseMovedEvent { - fn x(&self) -> f64 { - self.x as f64 - } - - fn y(&self) -> f64 { - self.y as f64 - } - - fn x_transformed(&self, width: u32) -> u32 { - cmp::min(self.x * width as i32 / - self.window - .get_inner_size_points() - .unwrap_or((width, 0)) - .0 as i32, - 0) as u32 - } - - fn y_transformed(&self, height: u32) -> u32 { - cmp::min(self.y * height as i32 / - self.window - .get_inner_size_points() - .unwrap_or((0, height)) - .1 as i32, - 0) as u32 - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `PointerAxisEvent` -pub struct GlutinMouseWheelEvent { - axis: Axis, - time: u32, - delta: MouseScrollDelta, -} - -impl BackendEvent for GlutinMouseWheelEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl PointerAxisEvent for GlutinMouseWheelEvent { - fn axis(&self) -> Axis { - self.axis - } - - fn source(&self) -> AxisSource { - match self.delta { - MouseScrollDelta::LineDelta(_, _) => AxisSource::Wheel, - MouseScrollDelta::PixelDelta(_, _) => AxisSource::Continuous, - } - } - - fn amount(&self) -> f64 { - match (self.axis, self.delta) { - (Axis::Horizontal, MouseScrollDelta::LineDelta(x, _)) | - (Axis::Horizontal, MouseScrollDelta::PixelDelta(x, _)) => x as f64, - (Axis::Vertical, MouseScrollDelta::LineDelta(_, y)) | - (Axis::Vertical, MouseScrollDelta::PixelDelta(_, y)) => y as f64, - } - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `PointerButtonEvent` -pub struct GlutinMouseInputEvent { - time: u32, - button: GlutinMouseButton, - state: ElementState, -} - -impl BackendEvent for GlutinMouseInputEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl PointerButtonEvent for GlutinMouseInputEvent { - fn button(&self) -> MouseButton { - self.button.into() - } - - fn state(&self) -> MouseButtonState { - self.state.into() - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `TouchDownEvent` -pub struct GlutinTouchStartedEvent { - window: Rc, - time: u32, - location: (f64, f64), - id: u64, -} - -impl BackendEvent for GlutinTouchStartedEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl TouchDownEvent for GlutinTouchStartedEvent { - fn slot(&self) -> Option { - Some(TouchSlot::new(self.id)) - } - - fn x(&self) -> f64 { - self.location.0 - } - - fn y(&self) -> f64 { - self.location.1 - } - - fn x_transformed(&self, width: u32) -> u32 { - cmp::min(self.location.0 as i32 * width as i32 / - self.window - .get_inner_size_points() - .unwrap_or((width, 0)) - .0 as i32, - 0) as u32 - } - - fn y_transformed(&self, height: u32) -> u32 { - cmp::min(self.location.1 as i32 * height as i32 / - self.window - .get_inner_size_points() - .unwrap_or((0, height)) - .1 as i32, - 0) as u32 - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `TouchMotionEvent` -pub struct GlutinTouchMovedEvent { - window: Rc, - time: u32, - location: (f64, f64), - id: u64, -} - -impl BackendEvent for GlutinTouchMovedEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl TouchMotionEvent for GlutinTouchMovedEvent { - fn slot(&self) -> Option { - Some(TouchSlot::new(self.id)) - } - - fn x(&self) -> f64 { - self.location.0 - } - - fn y(&self) -> f64 { - self.location.1 - } - - fn x_transformed(&self, width: u32) -> u32 { - self.location.0 as u32 * width / - self.window - .get_inner_size_points() - .unwrap_or((width, 0)) - .0 - } - - fn y_transformed(&self, height: u32) -> u32 { - self.location.1 as u32 * height / - self.window - .get_inner_size_points() - .unwrap_or((0, height)) - .1 - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `TouchUpEvent` -pub struct GlutinTouchEndedEvent { - time: u32, - id: u64, -} - -impl BackendEvent for GlutinTouchEndedEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl TouchUpEvent for GlutinTouchEndedEvent { - fn slot(&self) -> Option { - Some(TouchSlot::new(self.id)) - } -} - -#[derive(Clone)] -/// Glutin-Backend internal event wrapping glutin's types into a `TouchCancelEvent` -pub struct GlutinTouchCancelledEvent { - time: u32, - id: u64, -} - -impl BackendEvent for GlutinTouchCancelledEvent { - fn time(&self) -> u32 { - self.time - } -} - -impl TouchCancelEvent for GlutinTouchCancelledEvent { - fn slot(&self) -> Option { - Some(TouchSlot::new(self.id)) - } -} - -impl InputBackend for GlutinInputBackend { - type InputConfig = (); - type EventError = GlutinInputError; - - type KeyboardKeyEvent = GlutinKeyboardInputEvent; - type PointerAxisEvent = GlutinMouseWheelEvent; - type PointerButtonEvent = GlutinMouseInputEvent; - type PointerMotionEvent = UnusedEvent; - type PointerMotionAbsoluteEvent = GlutinMouseMovedEvent; - type TouchDownEvent = GlutinTouchStartedEvent; - type TouchUpEvent = GlutinTouchEndedEvent; - type TouchMotionEvent = GlutinTouchMovedEvent; - type TouchCancelEvent = GlutinTouchCancelledEvent; - type TouchFrameEvent = UnusedEvent; - - 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 - } - - /// 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, _) => { - match state { - ElementState::Pressed => self.key_counter += 1, - ElementState::Released => { - self.key_counter = self.key_counter.checked_sub(1).unwrap_or(0) - } - }; - handler.on_keyboard_key(&self.seat, - GlutinKeyboardInputEvent { - time: self.time_counter, - key: key_code, - count: self.key_counter, - state: state, - }) - } - Event::MouseMoved(x, y) => { - handler.on_pointer_move_absolute(&self.seat, - GlutinMouseMovedEvent { - window: self.window.clone(), - time: self.time_counter, - x: x, - y: y, - }) - } - Event::MouseWheel(delta, _) => { - let event = GlutinMouseWheelEvent { - axis: Axis::Horizontal, - time: self.time_counter, - delta: delta, - }; - match delta { - MouseScrollDelta::LineDelta(x, y) | - MouseScrollDelta::PixelDelta(x, y) => { - if x != 0.0 { - handler.on_pointer_axis(&self.seat, event.clone()); - } - if y != 0.0 { - handler.on_pointer_axis(&self.seat, event); - } - } - } - } - Event::MouseInput(state, button) => { - handler.on_pointer_button(&self.seat, - GlutinMouseInputEvent { - time: self.time_counter, - button: button, - state: state, - }) - } - Event::Touch(Touch { - phase: TouchPhase::Started, - location: (x, y), - id, - }) => { - handler.on_touch_down(&self.seat, - GlutinTouchStartedEvent { - window: self.window.clone(), - time: self.time_counter, - location: (x, y), - id: id, - }) - } - Event::Touch(Touch { - phase: TouchPhase::Moved, - location: (x, y), - id, - }) => { - handler.on_touch_motion(&self.seat, - GlutinTouchMovedEvent { - window: self.window.clone(), - time: self.time_counter, - location: (x, y), - id: id, - }) - } - Event::Touch(Touch { - phase: TouchPhase::Ended, - location: (x, y), - id, - }) => { - handler.on_touch_motion(&self.seat, - GlutinTouchMovedEvent { - window: self.window.clone(), - time: self.time_counter, - location: (x, y), - id: id, - }); - handler.on_touch_up(&self.seat, - GlutinTouchEndedEvent { - time: self.time_counter, - id: id, - }); - } - Event::Touch(Touch { - phase: TouchPhase::Cancelled, - id, - .. - }) => { - handler.on_touch_cancel(&self.seat, - GlutinTouchCancelledEvent { - time: self.time_counter, - id: id, - }) - } - Event::Closed => return Err(GlutinInputError::WindowClosed), - _ => {} - } - self.time_counter += 1; - } - } - Ok(()) - } -} - -impl GlutinInputBackend { - fn new(window: Rc) -> GlutinInputBackend { - GlutinInputBackend { - window: window, - time_counter: 0, - key_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/egl.rs b/src/backend/graphics/egl.rs new file mode 100644 index 0000000..50c934e --- /dev/null +++ b/src/backend/graphics/egl.rs @@ -0,0 +1,647 @@ +//! Common traits and types for egl context creation and rendering + +/// Large parts of the following file are taken from +/// https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/ +/// +/// It therefore falls under glutin's Apache 2.0 license +/// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) + +use super::GraphicsBackend; + +use libloading::Library; +use nix::{c_int, c_void}; +use std::error::{self, Error}; + +use std::ffi::{CStr, CString}; +use std::fmt; +use std::io; +use std::mem; +use std::ptr; + +#[allow(non_camel_case_types, dead_code)] +mod ffi { + use nix::c_void; + use nix::libc::{c_long, int32_t, uint64_t}; + + pub type khronos_utime_nanoseconds_t = khronos_uint64_t; + pub type khronos_uint64_t = uint64_t; + pub type khronos_ssize_t = c_long; + pub type EGLint = int32_t; + pub type EGLNativeDisplayType = NativeDisplayType; + pub type EGLNativePixmapType = NativePixmapType; + pub type EGLNativeWindowType = NativeWindowType; + pub type NativeDisplayType = *const c_void; + pub type NativePixmapType = *const c_void; + pub type NativeWindowType = *const c_void; + + pub mod egl { + use super::*; + + include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); + } +} + +/// Native types to create an `EGLContext` from. +/// Currently supported providers are X11, Wayland and GBM. +#[derive(Clone, Copy)] +pub enum Native { + /// X11 Display and Window objects to create an `EGLContext` upon. + X11(ffi::NativeDisplayType, ffi::NativeWindowType), + /// Wayland Display and Surface objects to create an `EGLContext` upon. + Wayland(ffi::NativeDisplayType, ffi::NativeWindowType), + /// GBM Display + Gbm(ffi::NativeDisplayType, ffi::NativeWindowType), +} + +/// Error that can happen while creating an `EGLContext` +#[derive(Debug)] +pub enum CreationError { + /// I/O error from the underlying system + IoError(io::Error), + /// Operating System error + OsError(String), + /// The requested OpenGl version is not supported by the graphics system + OpenGlVersionNotSupported, + /// There is no pixel format available that fulfills all requirements + NoAvailablePixelFormat, + /// Context creation is not supported on this system + NotSupported, +} + +impl From for CreationError { + fn from(err: io::Error) -> Self { + CreationError::IoError(err) + } +} + +impl fmt::Display for CreationError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.write_str(self.description())?; + if let Some(err) = error::Error::cause(self) { + write!(formatter, ": {}", err)?; + } + Ok(()) + } +} + +impl error::Error for CreationError { + fn description(&self) -> &str { + match *self { + CreationError::IoError(ref err) => err.description(), + CreationError::OsError(ref text) => text, + CreationError::OpenGlVersionNotSupported => { + "The requested OpenGL version is not \ + supported." + } + CreationError::NoAvailablePixelFormat => { + "Couldn't find any pixel format that matches \ + the criterias." + } + CreationError::NotSupported => "Context creation is not supported on the current window system", + } + } + + fn cause(&self) -> Option<&error::Error> { + match *self { + CreationError::IoError(ref err) => Some(err), + _ => None, + } + } +} + +/// EGL context for rendering +pub struct EGLContext { + _lib: Library, + context: ffi::egl::types::EGLContext, + display: ffi::egl::types::EGLDisplay, + egl: ffi::egl::Egl, + surface: ffi::egl::types::EGLSurface, + pixel_format: PixelFormat, +} + +impl EGLContext { + /// Create a new EGL context + /// + /// # Unsafety + /// + /// This method is marked unsafe, because the contents of `Native` cannot be verified and msy + /// contain dangeling pointers are similar unsafe content + pub unsafe fn new(native: Native, mut attributes: GlAttributes, reqs: PixelFormatRequirements) + -> Result { + let lib = Library::new("libEGL.so.1")?; + let egl = ffi::egl::Egl::load_with(|sym| { + let name = CString::new(sym).unwrap(); + let symbol = lib.get::<*mut c_void>(name.as_bytes()); + match symbol { + Ok(x) => *x as *const _, + Err(_) => ptr::null(), + } + }); + + // If no version is given, try OpenGLES 3.0, if available, + // fallback to 2.0 otherwise + let version = match attributes.version { + Some((3, x)) => (3, x), + Some((2, x)) => (2, x), + None => { + attributes.version = Some((3, 0)); + match EGLContext::new(native, attributes, reqs) { + Ok(x) => return Ok(x), + Err(_) => { + // TODO log + attributes.version = Some((2, 0)); + return EGLContext::new(native, attributes, reqs); + } + } + } + Some((1, _)) => { + // TODO logging + error, 1.0 not supported + unimplemented!() + } + Some(_) => { + // TODO logging + error, version not supported + unimplemented!() + } + }; + + // the first step is to query the list of extensions without any display, if supported + let dp_extensions = { + let p = egl.QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32); + + // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise + // `eglQueryString` returns an error + if p.is_null() { + vec![] + } else { + let p = CStr::from_ptr(p); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + + let has_dp_extension = |e: &str| dp_extensions.iter().any(|s| s == e); + + let display = match native { + Native::X11(display, _) if has_dp_extension("EGL_KHR_platform_x11") && + egl.GetPlatformDisplay.is_loaded() => { + egl.GetPlatformDisplay(ffi::egl::PLATFORM_X11_KHR, display as *mut _, ptr::null()) + } + + Native::X11(display, _) if has_dp_extension("EGL_EXT_platform_x11") && + egl.GetPlatformDisplayEXT.is_loaded() => { + egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_X11_EXT, display as *mut _, ptr::null()) + } + + Native::Gbm(display, _) if has_dp_extension("EGL_KHR_platform_gbm") && + egl.GetPlatformDisplay.is_loaded() => { + egl.GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + } + + Native::Gbm(display, _) if has_dp_extension("EGL_MESA_platform_gbm") && + egl.GetPlatformDisplayEXT.is_loaded() => { + egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + } + + Native::Wayland(display, _) if has_dp_extension("EGL_KHR_platform_wayland") && + egl.GetPlatformDisplay.is_loaded() => { + egl.GetPlatformDisplay(ffi::egl::PLATFORM_WAYLAND_KHR, + display as *mut _, + ptr::null()) + } + + Native::Wayland(display, _) if has_dp_extension("EGL_EXT_platform_wayland") && + egl.GetPlatformDisplayEXT.is_loaded() => { + egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_WAYLAND_EXT, + display as *mut _, + ptr::null()) + } + + Native::X11(display, _) | + Native::Gbm(display, _) | + Native::Wayland(display, _) => egl.GetDisplay(display as *mut _), + }; + + let egl_version = { + let mut major: ffi::egl::types::EGLint = mem::uninitialized(); + let mut minor: ffi::egl::types::EGLint = mem::uninitialized(); + + if egl.Initialize(display, &mut major, &mut minor) == 0 { + return Err(CreationError::OsError(String::from("eglInitialize failed"))); + } + + (major, minor) + }; + + // the list of extensions supported by the client once initialized is different from the + // list of extensions obtained earlier + let extensions = if egl_version >= (1, 2) { + let p = CStr::from_ptr(egl.QueryString(display, ffi::egl::EXTENSIONS as i32)); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + + } else { + vec![] + }; + + if egl_version >= (1, 2) && egl.BindAPI(ffi::egl::OPENGL_ES_API) == 0 { + return Err(CreationError::OpenGlVersionNotSupported); + } + + let descriptor = { + let mut out: Vec = Vec::with_capacity(37); + + if egl_version >= (1, 2) { + out.push(ffi::egl::COLOR_BUFFER_TYPE as c_int); + out.push(ffi::egl::RGB_BUFFER as c_int); + } + + out.push(ffi::egl::SURFACE_TYPE as c_int); + // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error + // if we ask for PBUFFER_BIT as well as WINDOW_BIT + out.push((ffi::egl::WINDOW_BIT) as c_int); + + match version { + (3, _) => { + if egl_version < (1, 3) { + return Err(CreationError::NoAvailablePixelFormat); + } + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES3_BIT as c_int); + } + (2, _) => { + if egl_version < (1, 3) { + return Err(CreationError::NoAvailablePixelFormat); + } + out.push(ffi::egl::RENDERABLE_TYPE as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + out.push(ffi::egl::CONFORMANT as c_int); + out.push(ffi::egl::OPENGL_ES2_BIT as c_int); + } + (_, _) => unreachable!(), + }; + + if let Some(hardware_accelerated) = reqs.hardware_accelerated { + out.push(ffi::egl::CONFIG_CAVEAT as c_int); + out.push(if hardware_accelerated { + ffi::egl::NONE as c_int + } else { + ffi::egl::SLOW_CONFIG as c_int + }); + } + + if let Some(color) = reqs.color_bits { + out.push(ffi::egl::RED_SIZE as c_int); + out.push((color / 3) as c_int); + out.push(ffi::egl::GREEN_SIZE as c_int); + out.push((color / 3 + if color % 3 != 0 { 1 } else { 0 }) as c_int); + out.push(ffi::egl::BLUE_SIZE as c_int); + out.push((color / 3 + if color % 3 == 2 { 1 } else { 0 }) as c_int); + } + + if let Some(alpha) = reqs.alpha_bits { + out.push(ffi::egl::ALPHA_SIZE as c_int); + out.push(alpha as c_int); + } + + if let Some(depth) = reqs.depth_bits { + out.push(ffi::egl::DEPTH_SIZE as c_int); + out.push(depth as c_int); + } + + if let Some(stencil) = reqs.stencil_bits { + out.push(ffi::egl::STENCIL_SIZE as c_int); + out.push(stencil as c_int); + } + + if let Some(true) = reqs.double_buffer { + return Err(CreationError::NoAvailablePixelFormat); + } + + if let Some(multisampling) = reqs.multisampling { + out.push(ffi::egl::SAMPLES as c_int); + out.push(multisampling as c_int); + } + + if reqs.stereoscopy { + return Err(CreationError::NoAvailablePixelFormat); + } + + out.push(ffi::egl::NONE as c_int); + out + }; + + // calling `eglChooseConfig` + let mut config_id = mem::uninitialized(); + let mut num_configs = mem::uninitialized(); + if egl.ChooseConfig(display, + descriptor.as_ptr(), + &mut config_id, + 1, + &mut num_configs) == 0 { + return Err(CreationError::OsError(String::from("eglChooseConfig failed"))); + } + if num_configs == 0 { + return Err(CreationError::NoAvailablePixelFormat); + } + + // analyzing each config + macro_rules! attrib { + ($egl:expr, $display:expr, $config:expr, $attr:expr) => ( + { + let mut value = mem::uninitialized(); + let res = $egl.GetConfigAttrib($display, $config, + $attr as ffi::egl::types::EGLint, &mut value); + if res == 0 { + return Err(CreationError::OsError(String::from("eglGetConfigAttrib failed"))); + } + value + } + ) + }; + + let desc = PixelFormat { + hardware_accelerated: attrib!(egl, display, config_id, ffi::egl::CONFIG_CAVEAT) != + ffi::egl::SLOW_CONFIG as i32, + color_bits: attrib!(egl, display, config_id, ffi::egl::RED_SIZE) as u8 + + attrib!(egl, display, config_id, ffi::egl::BLUE_SIZE) as u8 + + attrib!(egl, display, config_id, ffi::egl::GREEN_SIZE) as u8, + alpha_bits: attrib!(egl, display, config_id, ffi::egl::ALPHA_SIZE) as u8, + depth_bits: attrib!(egl, display, config_id, ffi::egl::DEPTH_SIZE) as u8, + stencil_bits: attrib!(egl, display, config_id, ffi::egl::STENCIL_SIZE) as u8, + stereoscopy: false, + double_buffer: true, + multisampling: match attrib!(egl, display, config_id, ffi::egl::SAMPLES) { + 0 | 1 => None, + a => Some(a as u16), + }, + srgb: false, // TODO: use EGL_KHR_gl_colorspace to know that + }; + + let mut context_attributes = Vec::with_capacity(10); + + if egl_version >= (1, 5) || extensions.iter().any(|s| s == &"EGL_KHR_create_context") { + context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32); + context_attributes.push(version.0 as i32); + context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32); + context_attributes.push(version.1 as i32); + + if attributes.debug && egl_version >= (1, 5) { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32); + context_attributes.push(ffi::egl::TRUE as i32); + } + + context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); + context_attributes.push(0); + + } else if egl_version >= (1, 3) { + context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32); + context_attributes.push(version.0 as i32); + } + + context_attributes.push(ffi::egl::NONE as i32); + + let context = egl.CreateContext(display, config_id, ptr::null(), context_attributes.as_ptr()); + + if context.is_null() { + match egl.GetError() as u32 { + ffi::egl::BAD_ATTRIBUTE => return Err(CreationError::OpenGlVersionNotSupported), + e => panic!("eglCreateContext failed: 0x{:x}", e), + } + } + + let surface = { + let surface = match native { + Native::X11(_, window) | + Native::Wayland(_, window) | + Native::Gbm(_, window) => egl.CreateWindowSurface(display, config_id, window, ptr::null()), + }; + + if surface.is_null() { + return Err(CreationError::OsError(String::from("eglCreateWindowSurface failed"))); + } + surface + }; + + Ok(EGLContext { + _lib: lib, + context: context as *const _, + display: display as *const _, + egl: egl, + surface: surface as *const _, + pixel_format: desc, + }) + } + + /// Swaps buffers at the end of a frame. + pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + let ret = unsafe { + self.egl + .SwapBuffers(self.display as *const _, self.surface as *const _) + }; + + if ret == 0 { + match unsafe { self.egl.GetError() } as u32 { + ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), + err => panic!("eglSwapBuffers failed (eglGetError returned 0x{:x})", err), + } + } else { + Ok(()) + } + } + + /// Returns the address of an OpenGL function. + /// + /// Supposes that the context has been made current before this function is called. + pub unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + let addr = CString::new(symbol.as_bytes()).unwrap(); + let addr = addr.as_ptr(); + self.egl.GetProcAddress(addr) as *const _ + } + + /// Returns true if the OpenGL context is the current one in the thread. + pub fn is_current(&self) -> bool { + unsafe { self.egl.GetCurrentContext() == self.context as *const _ } + } + + /// Makes the OpenGL context the current context in the current thread. + /// + /// # Unsafety + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + pub unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + let ret = self.egl + .MakeCurrent(self.display as *const _, + self.surface as *const _, + self.surface as *const _, + self.context as *const _); + + if ret == 0 { + match self.egl.GetError() as u32 { + ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), + err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err), + } + } else { + Ok(()) + } + } + + /// Returns the pixel format of the main framebuffer of the context. + pub fn get_pixel_format(&self) -> PixelFormat { + self.pixel_format + } +} + +unsafe impl Send for EGLContext {} +unsafe impl Sync for EGLContext {} + +impl Drop for EGLContext { + fn drop(&mut self) { + 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 + .DestroySurface(self.display as *const _, self.surface as *const _); + self.egl.Terminate(self.display as *const _); + } + } +} + +/// 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, +} + +/// Attributes to use when creating an OpenGL context. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct GlAttributes { + /// Describes the OpenGL API and version that are being requested when a context is created. + /// + /// `Some(3, 0)` will request a OpenGL ES 3.0 context for example. + /// `None` means "don't care" (minimum will be 2.0). + pub version: Option<(u8, u8)>, + /// OpenGL profile to use + pub profile: Option, + /// Whether to enable the debug flag of the context. + /// + /// Debug contexts are usually slower but give better error reporting. + pub debug: bool, + /// Whether to use vsync. If vsync is enabled, calling swap_buffers will block until the screen refreshes. + /// This is typically used to prevent screen tearing. + pub vsync: bool, +} + +/// Describes the requested OpenGL context profiles. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GlProfile { + /// Include all the immediate more functions and definitions. + Compatibility, + /// Include all the future-compatible functions and definitions. + Core, +} + +/// Describes how the backend should choose a pixel format. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct PixelFormatRequirements { + /// If `true`, only hardware-accelerated formats will be conisdered. If `false`, only software renderers. + /// `None` means "don't care". Default is `None`. + pub hardware_accelerated: Option, + /// Minimum number of bits for the color buffer, excluding alpha. None means "don't care". The default is `None``. + pub color_bits: Option, + /// If `true`, the color buffer must be in a floating point format. Default is `false`. + /// + /// Using floating points allows you to write values outside of the `[0.0, 1.0]` range. + pub float_color_buffer: bool, + /// Minimum number of bits for the alpha in the color buffer. `None` means "don't care". The default is `None`. + pub alpha_bits: Option, + /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. + pub depth_bits: Option, + /// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`. + pub stencil_bits: Option, + /// If `true`, only double-buffered formats will be considered. If `false`, only single-buffer formats. + /// `None` means "don't care". The default is `None`. + pub double_buffer: Option, + /// Contains the minimum number of samples per pixel in the color, depth and stencil buffers. + /// `None` means "don't care". Default is `None`. A value of `Some(0)` indicates that multisampling must not be enabled. + pub multisampling: Option, + /// If `true`, only stereoscopic formats will be considered. If `false`, only non-stereoscopic formats. + /// The default is `false`. + pub stereoscopy: bool, +} + +/// 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 EGLGraphicsBackend: GraphicsBackend { + /// 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. + /// + /// # Unsafety + /// + /// This function is marked unsafe, because the context cannot be made current + /// on multiple threads. + unsafe fn make_current(&self) -> Result<(), SwapBuffersError>; + + /// Returns the pixel format of the main framebuffer of the context. + fn get_pixel_format(&self) -> PixelFormat; +} diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs new file mode 100644 index 0000000..2105eed --- /dev/null +++ b/src/backend/graphics/glium.rs @@ -0,0 +1,96 @@ +//! Glium compatibility module + +use backend::graphics::egl::{EGLGraphicsBackend, SwapBuffersError}; +use glium::Frame; +use glium::SwapBuffersError as GliumSwapBuffersError; +use glium::backend::{Backend, Context}; +use glium::debug::DebugCallbackBehavior; +use std::ops::Deref; +use std::os::raw::c_void; + +use std::rc::Rc; + +impl From for GliumSwapBuffersError { + fn from(error: SwapBuffersError) -> Self { + match error { + SwapBuffersError::ContextLost => GliumSwapBuffersError::ContextLost, + SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped, + } + } +} + +/// Wrapper to expose `glium` compatibility +pub struct GliumGraphicsBackend { + context: Rc, + backend: Rc>, +} + +struct InternalBackend(T); + +impl GliumGraphicsBackend { + fn new(backend: T) -> GliumGraphicsBackend { + let internal = Rc::new(InternalBackend(backend)); + + GliumGraphicsBackend { + // cannot fail + context: unsafe { + Context::new::<_, ()>(internal.clone(), false, DebugCallbackBehavior::default()).unwrap() + }, + backend: internal, + } + } + + /// Start drawing on the backbuffer. + /// + /// This function returns a `Frame`, which can be used to draw on it. When the `Frame` is + /// destroyed, the buffers are swapped. + /// + /// Note that destroying a `Frame` is immediate, even if vsync is enabled. + #[inline] + pub fn draw(&self) -> Frame { + Frame::new(self.context.clone(), + self.backend.get_framebuffer_dimensions()) + } +} + +impl Deref for GliumGraphicsBackend { + type Target = Context; + + fn deref(&self) -> &Context { + &self.context + } +} + +/// Converter trait to expose `glium` compatibility for all `EGLGraphicsBackend`s +pub trait IntoGlium: EGLGraphicsBackend + Sized { + /// Wrap the given `EGLGraphicsBackend` to a `GliumGraphicBackend` + fn into_glium(self) -> GliumGraphicsBackend; +} + +impl IntoGlium for T { + fn into_glium(self) -> GliumGraphicsBackend { + GliumGraphicsBackend::new(self) + } +} + +unsafe impl Backend for InternalBackend { + 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().expect("Context was lost") + } +} diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index 4ad08f8..cb04c21 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -30,4 +30,6 @@ pub trait GraphicsBackend { } pub mod software; -pub mod opengl; +pub mod egl; +#[cfg(feature = "renderer_glium")] +pub mod glium; diff --git a/src/backend/graphics/opengl.rs b/src/backend/graphics/opengl.rs deleted file mode 100644 index e5884f5..0000000 --- a/src/backend/graphics/opengl.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Common traits and types for opengl rendering on graphics backends - - -use super::GraphicsBackend; -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: GraphicsBackend { - /// 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/libinput.rs b/src/backend/libinput.rs index 62d46f5..0f8ab38 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -328,8 +328,8 @@ impl backend::InputBackend for LibinputInputBackend { Entry::Vacant(seat_entry) => { let mut hasher = DefaultHasher::default(); seat_entry.key().hash(&mut hasher); - let seat = seat_entry.insert(backend::Seat::new(hasher.finish(), - new_caps)); + let seat = seat_entry + .insert(backend::Seat::new(hasher.finish(), new_caps)); if let Some(ref mut handler) = self.handler { trace!(self.logger, "Calling on_seat_created with {:?}", seat); handler.on_seat_created(seat); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c91fc91..1102635 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -6,26 +6,22 @@ //! //! Supported graphics backends: //! -//! - glutin (headless/windowed) +//! - winit //! //! Supported input backends: //! -//! - glutin (windowed) +//! - winit +//! - libinput pub mod input; pub mod graphics; -#[cfg(feature = "backend_glutin")] -pub mod glutin; +#[cfg(feature = "backend_winit")] +pub mod winit; #[cfg(feature = "backend_libinput")] pub mod libinput; -#[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 +// Internal functions that need to be accessible by the different backend implementations trait SeatInternal { fn new(id: u64, capabilities: input::SeatCapabilities) -> Self; diff --git a/src/backend/winit.rs b/src/backend/winit.rs new file mode 100644 index 0000000..e5cfcef --- /dev/null +++ b/src/backend/winit.rs @@ -0,0 +1,674 @@ +//! Implementation of backend traits for types provided by `winit` + +use backend::{SeatInternal, TouchSlotInternal}; +use backend::graphics::GraphicsBackend; +use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, Native, + PixelFormat, PixelFormatRequirements, SwapBuffersError}; +use backend::input::{Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, + KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, + PointerMotionAbsoluteEvent, Seat, SeatCapabilities, TouchCancelEvent, TouchDownEvent, + TouchMotionEvent, TouchSlot, TouchUpEvent, UnusedEvent}; +use nix::c_void; + +use std::cmp; +use std::error::Error; +use std::fmt; +use std::rc::Rc; +use wayland_client::egl as wegl; +use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, KeyboardInput, + MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, Window, + WindowBuilder, WindowEvent}; +use winit::os::unix::{WindowExt, get_x11_xconnection}; + +/// Window with an active EGL Context created by `winit`. Implements the +/// `EGLGraphicsBackend` graphics backend trait +pub struct WinitGraphicsBackend { + window: Rc, + context: EGLContext, +} + +/// Abstracted event loop of a `winit` `Window` implementing the `InputBackend` trait +/// +/// You need to call `dispatch_new_events` periodically to receive any events. +pub struct WinitInputBackend { + events_loop: EventsLoop, + window: Rc, + surface: Option, + time_counter: u32, + key_counter: u32, + seat: Seat, + input_config: (), + handler: Option + 'static>>, +} + +/// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` +/// graphics backend trait and a corresponding `WinitInputBackend`, which implements +/// the `InputBackend` trait +pub fn init() -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> { + init_from_builder(WindowBuilder::new() + .with_dimensions(1280, 800) + .with_title("Smithay") + .with_visibility(true)) +} + +/// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` +/// graphics backend trait, from a given `WindowBuilder` struct and a corresponding +/// `WinitInputBackend`, which implements the `InputBackend` trait +pub fn init_from_builder(builder: WindowBuilder) + -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> { + init_from_builder_with_gl_attr(builder, + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }) +} + +/// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` +/// graphics backend trait, from a given `WindowBuilder` struct, as well as given +/// `GlAttributes` for further customization of the rendering pipeline and a +/// corresponding `WinitInputBackend`, which implements the `InputBackend` trait. +pub fn init_from_builder_with_gl_attr(builder: WindowBuilder, attributes: GlAttributes) + -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> { + let events_loop = EventsLoop::new(); + let window = Rc::new(builder.build(&events_loop)?); + + let (native, surface) = if let (Some(conn), Some(window)) = + (get_x11_xconnection(), window.get_xlib_window()) { + (Native::X11(conn.display as *const _, window), None) + } else if let (Some(display), Some(surface)) = + (window.get_wayland_display(), window.get_wayland_client_surface()) { + let (w, h) = window.get_inner_size().unwrap(); + let egl_surface = wegl::WlEglSurface::new(surface, w as i32, h as i32); + (Native::Wayland(display, egl_surface.ptr() as *const _), Some(egl_surface)) + } else { + return Err(CreationError::NotSupported); + }; + + let context = unsafe { + match EGLContext::new(native, + attributes, + PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + ..Default::default() + }) { + Ok(context) => context, + Err(err) => { + println!("EGLContext creation failed:\n{}", err); + return Err(err); + } + } + }; + + Ok((WinitGraphicsBackend { + window: window.clone(), + context: context, + }, + WinitInputBackend { + events_loop: events_loop, + window: window, + surface: surface, + time_counter: 0, + key_counter: 0, + seat: Seat::new(0, + SeatCapabilities { + pointer: true, + keyboard: true, + touch: true, + }), + input_config: (), + handler: None, + })) +} + +impl GraphicsBackend for WinitGraphicsBackend { + type CursorFormat = MouseCursor; + + fn set_cursor_position(&mut self, x: u32, y: u32) -> Result<(), ()> { + self.window.set_cursor_position(x as i32, y as i32) + } + + fn set_cursor_representation(&mut self, cursor: Self::CursorFormat) { + self.window.set_cursor(cursor) + } +} + +impl EGLGraphicsBackend for WinitGraphicsBackend { + fn swap_buffers(&self) -> Result<(), SwapBuffersError> { + self.context.swap_buffers() + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.context.get_proc_address(symbol) + } + + fn get_framebuffer_dimensions(&self) -> (u32, u32) { + self.window + .get_inner_size_pixels() + .expect("Window does not exist anymore") + } + + fn is_current(&self) -> bool { + self.context.is_current() + } + + unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + self.context.make_current() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.context.get_pixel_format() + } +} + +/// Errors that may happen when driving the event loop of `WinitInputBackend` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WinitInputError { + /// The underlying `winit` `Window` was closed. No further events can be processed. + /// + /// See `WinitInputBackend::dispatch_new_events`. + WindowClosed, +} + +impl Error for WinitInputError { + fn description(&self) -> &str { + match *self { + WinitInputError::WindowClosed => "Glutin Window was closed", + } + } +} + +impl fmt::Display for WinitInputError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Winit-Backend internal event wrapping winit's types into a `KeyboardKeyEvent` +pub struct WinitKeyboardInputEvent { + time: u32, + key: u32, + count: u32, + state: ElementState, +} + +impl BackendEvent for WinitKeyboardInputEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl KeyboardKeyEvent for WinitKeyboardInputEvent { + fn key_code(&self) -> u32 { + self.key + } + + fn state(&self) -> KeyState { + self.state.into() + } + + fn count(&self) -> u32 { + self.count + } +} + +#[derive(Clone)] +/// Winit-Backend internal event wrapping winit's types into a `PointerMotionAbsoluteEvent` +pub struct WinitMouseMovedEvent { + window: Rc, + time: u32, + x: f64, + y: f64, +} + +impl BackendEvent for WinitMouseMovedEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { + fn x(&self) -> f64 { + self.x + } + + fn y(&self) -> f64 { + self.y + } + + 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, + 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, + 0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +/// Winit-Backend internal event wrapping winit's types into a `PointerAxisEvent` +pub struct WinitMouseWheelEvent { + axis: Axis, + time: u32, + delta: MouseScrollDelta, +} + +impl BackendEvent for WinitMouseWheelEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl PointerAxisEvent for WinitMouseWheelEvent { + fn axis(&self) -> Axis { + self.axis + } + + fn source(&self) -> AxisSource { + match self.delta { + MouseScrollDelta::LineDelta(_, _) => AxisSource::Wheel, + MouseScrollDelta::PixelDelta(_, _) => AxisSource::Continuous, + } + } + + fn amount(&self) -> f64 { + match (self.axis, self.delta) { + (Axis::Horizontal, MouseScrollDelta::LineDelta(x, _)) | + (Axis::Horizontal, MouseScrollDelta::PixelDelta(x, _)) => x as f64, + (Axis::Vertical, MouseScrollDelta::LineDelta(_, y)) | + (Axis::Vertical, MouseScrollDelta::PixelDelta(_, y)) => y as f64, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Winit-Backend internal event wrapping winit's types into a `PointerButtonEvent` +pub struct WinitMouseInputEvent { + time: u32, + button: WinitMouseButton, + state: ElementState, +} + +impl BackendEvent for WinitMouseInputEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl PointerButtonEvent for WinitMouseInputEvent { + fn button(&self) -> MouseButton { + self.button.into() + } + + fn state(&self) -> MouseButtonState { + self.state.into() + } +} + +#[derive(Clone)] +/// Winit-Backend internal event wrapping winit's types into a `TouchDownEvent` +pub struct WinitTouchStartedEvent { + window: Rc, + time: u32, + location: (f64, f64), + id: u64, +} + +impl BackendEvent for WinitTouchStartedEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl TouchDownEvent for WinitTouchStartedEvent { + fn slot(&self) -> Option { + Some(TouchSlot::new(self.id)) + } + + fn x(&self) -> f64 { + self.location.0 + } + + fn y(&self) -> f64 { + self.location.1 + } + + fn x_transformed(&self, width: u32) -> u32 { + cmp::min(self.location.0 as i32 * width as i32 / + self.window.get_inner_size_points().unwrap_or((width, 0)).0 as i32, + 0) as u32 + } + + fn y_transformed(&self, height: u32) -> u32 { + cmp::min(self.location.1 as i32 * height as i32 / + self.window.get_inner_size_points().unwrap_or((0, height)).1 as i32, + 0) as u32 + } +} + +#[derive(Clone)] +/// Winit-Backend internal event wrapping winit's types into a `TouchMotionEvent` +pub struct WinitTouchMovedEvent { + window: Rc, + time: u32, + location: (f64, f64), + id: u64, +} + +impl BackendEvent for WinitTouchMovedEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl TouchMotionEvent for WinitTouchMovedEvent { + fn slot(&self) -> Option { + Some(TouchSlot::new(self.id)) + } + + fn x(&self) -> f64 { + self.location.0 + } + + fn y(&self) -> f64 { + self.location.1 + } + + fn x_transformed(&self, width: u32) -> u32 { + self.location.0 as u32 * width / self.window.get_inner_size_points().unwrap_or((width, 0)).0 + } + + fn y_transformed(&self, height: u32) -> u32 { + self.location.1 as u32 * height / self.window.get_inner_size_points().unwrap_or((0, height)).1 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Winit-Backend internal event wrapping winit's types into a `TouchUpEvent` +pub struct WinitTouchEndedEvent { + time: u32, + id: u64, +} + +impl BackendEvent for WinitTouchEndedEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl TouchUpEvent for WinitTouchEndedEvent { + fn slot(&self) -> Option { + Some(TouchSlot::new(self.id)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Winit-Backend internal event wrapping winit's types into a `TouchCancelEvent` +pub struct WinitTouchCancelledEvent { + time: u32, + id: u64, +} + +impl BackendEvent for WinitTouchCancelledEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl TouchCancelEvent for WinitTouchCancelledEvent { + fn slot(&self) -> Option { + Some(TouchSlot::new(self.id)) + } +} + +impl InputBackend for WinitInputBackend { + type InputConfig = (); + type EventError = WinitInputError; + + type KeyboardKeyEvent = WinitKeyboardInputEvent; + type PointerAxisEvent = WinitMouseWheelEvent; + type PointerButtonEvent = WinitMouseInputEvent; + type PointerMotionEvent = UnusedEvent; + type PointerMotionAbsoluteEvent = WinitMouseMovedEvent; + type TouchDownEvent = WinitTouchStartedEvent; + type TouchUpEvent = WinitTouchEndedEvent; + type TouchMotionEvent = WinitTouchMovedEvent; + type TouchCancelEvent = WinitTouchCancelledEvent; + type TouchFrameEvent = UnusedEvent; + + 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 + } + + /// 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 + /// `dispatch_new_events` again after the `Window` has been closed is considered an + /// application error and unspecified baviour may occur. + /// + /// The linked `WinitGraphicsBackend` will error with a lost Context and should + /// not be used anymore as well. + fn dispatch_new_events(&mut self) -> Result<(), WinitInputError> { + let mut closed = false; + + { + let mut closed_ptr = &mut closed; + let mut key_counter = &mut self.key_counter; + let mut time_counter = &mut self.time_counter; + let seat = &self.seat; + let window = &self.window; + let surface = &self.surface; + let mut handler = self.handler.as_mut(); + + self.events_loop + .poll_events(move |event| match event { + Event::WindowEvent { event, .. } => { + match (event, handler.as_mut()) { + (WindowEvent::Resized(x, y), _) => { + window.set_inner_size(x, y); + if let Some(wl_egl_surface) = surface.as_ref() { + wl_egl_surface.resize(x as i32, y as i32, 0, 0); + } + } + (WindowEvent::KeyboardInput { + input: KeyboardInput { scancode, state, .. }, .. + }, + Some(handler)) => { + match state { + ElementState::Pressed => *key_counter += 1, + ElementState::Released => { + *key_counter = key_counter.checked_sub(1).unwrap_or(0) + } + }; + handler.on_keyboard_key(seat, + WinitKeyboardInputEvent { + time: *time_counter, + key: scancode, + count: *key_counter, + state: state, + }) + } + (WindowEvent::MouseMoved { position: (x, y), .. }, + Some(handler)) => { + handler.on_pointer_move_absolute(seat, + WinitMouseMovedEvent { + window: window.clone(), + time: *time_counter, + x: x, + y: y, + }) + } + (WindowEvent::MouseWheel { delta, .. }, Some(handler)) => { + let event = WinitMouseWheelEvent { + axis: Axis::Horizontal, + time: *time_counter, + delta: delta, + }; + match delta { + MouseScrollDelta::LineDelta(x, y) | + MouseScrollDelta::PixelDelta(x, y) => { + if x != 0.0 { + handler.on_pointer_axis(seat, event); + } + if y != 0.0 { + handler.on_pointer_axis(seat, event); + } + } + } + } + (WindowEvent::MouseInput { state, button, .. }, Some(handler)) => { + handler.on_pointer_button(seat, + WinitMouseInputEvent { + time: *time_counter, + button: button, + state: state, + }) + } + (WindowEvent::Touch(Touch { + phase: TouchPhase::Started, + location: (x, y), + id, + .. + }), + Some(handler)) => { + handler.on_touch_down(seat, + WinitTouchStartedEvent { + window: window.clone(), + time: *time_counter, + location: (x, y), + id: id, + }) + } + (WindowEvent::Touch(Touch { + phase: TouchPhase::Moved, + location: (x, y), + id, + .. + }), + Some(handler)) => { + handler.on_touch_motion(seat, + WinitTouchMovedEvent { + window: window.clone(), + time: *time_counter, + location: (x, y), + id: id, + }) + } + (WindowEvent::Touch(Touch { + phase: TouchPhase::Ended, + location: (x, y), + id, + .. + }), + Some(handler)) => { + handler.on_touch_motion(seat, + WinitTouchMovedEvent { + window: window.clone(), + time: *time_counter, + location: (x, y), + id: id, + }); + handler.on_touch_up(seat, + WinitTouchEndedEvent { + time: *time_counter, + id: id, + }); + } + (WindowEvent::Touch(Touch { + phase: TouchPhase::Cancelled, + id, + .. + }), + Some(handler)) => { + handler.on_touch_cancel(seat, + WinitTouchCancelledEvent { + time: *time_counter, + id: id, + }) + } + (WindowEvent::Closed, _) => *closed_ptr = true, + _ => {} + } + *time_counter += 1; + } + Event::DeviceEvent { .. } => {} + }); + } + + if closed { + Err(WinitInputError::WindowClosed) + } else { + Ok(()) + } + } +} + +impl From for MouseButton { + fn from(button: WinitMouseButton) -> MouseButton { + match button { + WinitMouseButton::Left => MouseButton::Left, + WinitMouseButton::Right => MouseButton::Right, + WinitMouseButton::Middle => MouseButton::Middle, + WinitMouseButton::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, + } + } +} + +impl From for CreationError { + fn from(error: WinitCreationError) -> Self { + match error { + WinitCreationError::OsError(x) => CreationError::OsError(x), + WinitCreationError::NotSupported => CreationError::NotSupported, + } + } +} diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 475f4f7..883f5e8 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -143,7 +143,7 @@ impl KbdInternal { let mods_locked = self.state.serialize_mods(xkb::STATE_MODS_LOCKED); let layout_locked = self.state.serialize_layout(xkb::STATE_LAYOUT_LOCKED); - return (mods_depressed, mods_latched, mods_locked, layout_locked); + (mods_depressed, mods_latched, mods_locked, layout_locked) } fn serialize_pressed_keys(&self) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index 7ac03cd..d29f3c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,15 @@ extern crate nix; extern crate xkbcommon; extern crate tempfile; -#[cfg(feature = "backend_glutin")] -extern crate glutin; +#[cfg(feature = "backend_winit")] +extern crate winit; +#[cfg(feature = "backend_winit")] +extern crate wayland_client; #[cfg(feature = "backend_libinput")] extern crate input; +extern crate libloading; + #[cfg(feature = "renderer_glium")] extern crate glium;