diff --git a/Cargo.toml b/Cargo.toml index 3527288..49f4eac 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 = { version = "~0.6.4", 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", "wayland-client/dlopen"] renderer_glium = ["glium"] backend_libinput = ["input"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d846dc8 --- /dev/null +++ b/build.rs @@ -0,0 +1,30 @@ +extern crate gl_generator; + +use gl_generator::{Registry, Api, Profile, Fallbacks}; + +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/src/backend/glium.rs b/src/backend/glium.rs index 7814443..26eefd9 100644 --- a/src/backend/glium.rs +++ b/src/backend/glium.rs @@ -1,6 +1,4 @@ - - -use backend::graphics::opengl::{OpenglGraphicsBackend, SwapBuffersError}; +use backend::graphics::egl::{EGLGraphicsBackend, SwapBuffersError}; use glium::SwapBuffersError as GliumSwapBuffersError; use glium::backend::Backend; @@ -15,19 +13,19 @@ impl From for GliumSwapBuffersError { } } -pub struct GliumGraphicBackend(T); +pub struct GliumGraphicBackend(T); -pub trait IntoGlium: OpenglGraphicsBackend + Sized { +pub trait IntoGlium: EGLGraphicsBackend + Sized { fn into_glium(self) -> GliumGraphicBackend; } -impl IntoGlium for T { +impl IntoGlium for T { fn into_glium(self) -> GliumGraphicBackend { GliumGraphicBackend(self) } } -unsafe impl Backend for GliumGraphicBackend { +unsafe impl Backend for GliumGraphicBackend { fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> { self.0.swap_buffers().map_err(Into::into) } @@ -45,6 +43,6 @@ unsafe impl Backend for GliumGraphicBackend { } unsafe fn make_current(&self) { - self.0.make_current() + self.0.make_current().expect("Context was lost") } } diff --git a/src/backend/glutin.rs b/src/backend/glutin.rs index d09401a..fe1ba84 100644 --- a/src/backend/glutin.rs +++ b/src/backend/glutin.rs @@ -21,7 +21,7 @@ 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)) + init_headless_renderer_from_builder(HeadlessRendererBuilder::new(1024, 600) } /// Create a new `GlutinHeadlessRenderer`, which implements the `OpenglRenderer` graphics diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs new file mode 100644 index 0000000..694a1d0 --- /dev/null +++ b/src/backend/graphics/egl.rs @@ -0,0 +1,662 @@ +//! Common traits and types for opengl rendering on graphics backends + +/// Large parts of the following file are taken from +/// https://github.com/tomaka/glutin/tree/master/src/api/egl at commit +/// `044e651edf67a2029eecc650dd42546af1501414` +/// +/// It therefor falls under glutin's Apache 2.0 license +/// (see https://github.com/tomaka/glutin/blob/master/LICENSE) + +use super::GraphicsBackend; + +use libloading::Library; +use nix::{c_int, c_void}; + +use std::ffi::{CStr, CString}; +use std::error::{self, Error}; +use std::fmt; +use std::io; +use std::ptr; +use std::mem; + +mod ffi { + use nix::c_void; + use nix::libc::{uint64_t, int32_t, c_long}; + + 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")); + } +} + +#[derive(Clone, Copy)] +pub enum Native { + X11(ffi::NativeDisplayType, ffi::NativeWindowType), + Wayland(ffi::NativeDisplayType, ffi::NativeWindowType), + Gbm(ffi::NativeDisplayType, ffi::NativeWindowType), +} + +#[derive(Debug)] +pub enum CreationError { + IoError(io::Error), + OsError(String), + RobustnessNotSupported, + OpenGlVersionNotSupported, + NoAvailablePixelFormat, + 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::RobustnessNotSupported => "You requested robustness, but it is \ + not supported.", + 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 + } + } +} + +pub struct EGLContext { + context: *const c_void, + display: *const c_void, + egl: ffi::egl::Egl, + surface: *const c_void, + pixel_format: PixelFormat, +} + +impl EGLContext { + 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 sym = CString::new(sym).unwrap(); + unsafe { &*lib.get(sym.as_bytes()).unwrap() as *const _ } + }); + + // 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,x)) => { + //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 = unsafe { + 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(|_| format!("")); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + + let has_dp_extension = |e: &str| dp_extensions.iter().find(|s| s == &e).is_some(); + + let display = match native { + Native::X11(display, _) if has_dp_extension("EGL_KHR_platform_x11") && + egl.GetPlatformDisplay.is_loaded() => + { + unsafe { 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() => + { + unsafe { 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() => + { + unsafe { 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() => + { + unsafe { 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() => + { + unsafe { 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() => + { + unsafe { egl.GetPlatformDisplayEXT(ffi::egl::PLATFORM_WAYLAND_EXT, display as *mut _, + ptr::null()) } + }, + + Native::X11(display, _) | Native::Gbm(display, _) | + Native::Wayland(display, _) => { + unsafe { egl.GetDisplay(display as *mut _) } + } + }; + + let egl_version = unsafe { + 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(format!("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 = unsafe { CStr::from_ptr(egl.QueryString(display, ffi::egl::EXTENSIONS as i32)) }; + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| format!("")); + list.split(' ').map(|e| e.to_string()).collect::>() + + } else { + vec![] + }; + + if egl_version >= (1, 2) { + if 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(format!("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(format!("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 surface = unsafe { + let surface = match native { + Native::X11(_, window) => egl.CreateWindowSurface(display, config_id, window, ptr::null()), + Native::Wayland(_, window) => egl.CreateWindowSurface(display, config_id, window, ptr::null()), + Native::Gbm(_, window) => egl.CreateWindowSurface(display, config_id, window, ptr::null()), + }; + + if surface.is_null() { + return Err(CreationError::OsError(format!("eglCreateWindowSurface failed"))) + } + surface + }; + + let mut context_attributes = Vec::with_capacity(10); + let mut flags = 0; + + if egl_version >= (1, 5) || extensions.iter().find(|s| s == &"EGL_KHR_create_context") + .is_some() + { + 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); + + // handling robustness + let supports_robustness = egl_version >= (1, 5) || + extensions.iter() + .find(|s| s == &"EGL_EXT_create_context_robustness") + .is_some(); + + match attributes.robustness { + Robustness::NotRobust => (), + + Robustness::NoError => { + if extensions.iter().find(|s| s == &"EGL_KHR_create_context_no_error").is_some() { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_NO_ERROR_KHR as c_int); + context_attributes.push(1); + } + }, + + Robustness::RobustNoResetNotification => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } else { + return Err(CreationError::RobustnessNotSupported); + } + }, + + Robustness::TryRobustNoResetNotification => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } + }, + + Robustness::RobustLoseContextOnReset => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } else { + return Err(CreationError::RobustnessNotSupported); + } + }, + + Robustness::TryRobustLoseContextOnReset => { + if supports_robustness { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY + as c_int); + context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int); + flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int; + } + }, + } + + if attributes.debug { + if egl_version >= (1, 5) { + context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32); + context_attributes.push(ffi::egl::TRUE as i32); + } + + // TODO: using this flag sometimes generates an error + // there was a change in the specs that added this flag, so it may not be + // supported everywhere ; however it is not possible to know whether it is + // supported or not + //flags = flags | ffi::egl::CONTEXT_OPENGL_DEBUG_BIT_KHR as i32; + } + + context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32); + context_attributes.push(flags); + + } else if egl_version >= (1, 3) { + // robustness is not supported + match attributes.robustness { + Robustness::RobustNoResetNotification | Robustness::RobustLoseContextOnReset => { + return Err(CreationError::RobustnessNotSupported); + }, + _ => () + } + + 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), + } + } + + Ok(EGLContext { + context: context as *const _, + display: display as *const _, + egl: egl, + surface: surface as *const _, + pixel_format: desc, + }) + } + + 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 => return Err(SwapBuffersError::ContextLost), + err => panic!("eglSwapBuffers failed (eglGetError returned 0x{:x})", err) + } + } else { + Ok(()) + } + } + + 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(); + unsafe { + self.egl.GetProcAddress(addr) as *const _ + } + } + + pub fn is_current(&self) -> bool { + unsafe { self.egl.GetCurrentContext() == self.context as *const _ } + } + + 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 => return Err(SwapBuffersError::ContextLost), + err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err) + } + } else { + Ok(()) + } + } + + 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, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct GlAttributes { + pub version: Option<(u8, u8)>, + pub profile: Option, + pub debug: bool, + pub robustness: Robustness, + pub vsync: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Robustness { + NotRobust, + NoError, + RobustNoResetNotification, + TryRobustNoResetNotification, + RobustLoseContextOnReset, + TryRobustLoseContextOnReset, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GlProfile { + Compatibility, + Core, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct PixelFormatRequirements { + pub hardware_accelerated: Option, + pub color_bits: Option, + pub float_color_buffer: bool, + pub alpha_bits: Option, + pub depth_bits: Option, + pub stencil_bits: Option, + pub double_buffer: Option, + pub multisampling: Option, + 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. + /// + /// 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/mod.rs b/src/backend/graphics/mod.rs index 4ad08f8..35855a2 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -30,4 +30,4 @@ pub trait GraphicsBackend { } pub mod software; -pub mod opengl; +pub mod egl; 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/mod.rs b/src/backend/mod.rs index c91fc91..3d1ef0a 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -6,17 +6,18 @@ //! //! 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; diff --git a/src/backend/winit.rs b/src/backend/winit.rs new file mode 100644 index 0000000..468e969 --- /dev/null +++ b/src/backend/winit.rs @@ -0,0 +1,655 @@ +use backend::{SeatInternal, TouchSlotInternal}; +use backend::graphics::GraphicsBackend; +use backend::graphics::egl::{CreationError, EGLContext, EGLGraphicsBackend, GlAttributes, Native, Robustness, PixelFormatRequirements, 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 nix::c_void; +use wayland_client::egl as wegl; +use winit::{CreationError as WinitCreationError, ElementState, Event, EventsLoop, MouseButton as WinitMouseButton, MouseCursor, MouseScrollDelta, Touch, TouchPhase, Window, WindowBuilder, WindowEvent}; +use winit::os::unix::WindowExt; + +use std::cmp; +use std::error::Error; +use std::fmt; +use std::rc::Rc; + +/// 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 `process_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 +pub fn init() -> Result<(WinitGraphicsBackend, WinitInputBackend), CreationError> { + init_from_builder(WindowBuilder::new()) +} + +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), + robustness: Robustness::TryRobustLoseContextOnReset, + vsync: true, + }) +} + +/// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` +/// graphics backend trait, with a given already configured `WindowBuilder` for +/// customization. +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(display), Some(window)) = (window.get_xlib_display(), window.get_xlib_window()) { + (Native::X11(display, 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 { EGLContext::new(native, attributes, + PixelFormatRequirements { + hardware_accelerated: Some(true), + color_bits: Some(24), + alpha_bits: Some(8), + double_buffer: Some(true), + ..Default::default() + } + )? }; + + 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::process_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: u8, + 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 as u32 + } + + 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: i32, + y: i32, +} + +impl BackendEvent for WinitMouseMovedEvent { + fn time(&self) -> u32 { + self.time + } +} + +impl PointerMotionAbsoluteEvent for WinitMouseMovedEvent { + 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(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 + /// `process_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| { + if let Some(ref mut handler) = handler { + let Event::WindowEvent{ event, window_id: _ } = event; + match event { + WindowEvent::Resized(x, y) => { + window.set_inner_size(x, y); + if let Some(wl_surface) = surface.as_ref() { + wl_surface.resize(x as i32, y as i32, 0, 0); + } + } + WindowEvent::KeyboardInput(state, key_code, _, _) => { + 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: key_code, + count: *key_counter, + state: state, + }) + } + WindowEvent::MouseMoved(x, y) => { + handler.on_pointer_move_absolute(seat, + WinitMouseMovedEvent { + window: window.clone(), + time: *time_counter, + x: x, + y: y, + }) + } + WindowEvent::MouseWheel(delta, _) => { + 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.clone()); + } + if y != 0.0 { + handler.on_pointer_axis(seat, event); + } + } + } + } + WindowEvent::MouseInput(state, button) => { + handler.on_pointer_button(seat, + WinitMouseInputEvent { + time: *time_counter, + button: button, + state: state, + }) + } + WindowEvent::Touch(Touch { + phase: TouchPhase::Started, + location: (x, y), + id, + }) => { + 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, + }) => { + 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, + }) => { + 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, + .. + }) => { + handler.on_touch_cancel(seat, + WinitTouchCancelledEvent { + time: *time_counter, + id: id, + }) + } + WindowEvent::Closed => *closed_ptr = true, + _ => {} + } + *time_counter += 1; + } + }); + } + + 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/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;