<NME> mod.rs
<BEF> //! Drm/Kms types and backend implementations
//!
//! This module provide a `DrmDevice` which acts as a representation for any DRM
//! device and can be used to create the second provided structure a `DrmBackend`.
//!
//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/).
//!
//! Three entities are relevant for the initialization procedure.
//!
//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There responsibility
//! is to read the data of the framebuffer and export it into an "Encoder". The number of crtc's
//! represent the number of independent output devices the hardware may handle.
//!
//! An "Encoder" encodes the data of connected crtcs into a video signal for a fixed set
//! of connectors. E.g. you might have an analog encoder based on a DAG for VGA ports, but another
//! one for digital ones. Also not every encoder might be connected to every crtc.
//!
//! The last entity the "Connector" represents a port on your computer, possibly with a connected
//! monitor, TV, capture card, etc.
//!
//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to
//! and that feeds a given set of connectors, that can be manipulated at runtime.
//!
//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector,
//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the
//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown.
//!
//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm).
//!
//!
//! ## How to use it
//!
//! ### Initialization
//!
//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of
//! your DRM node. The `File` is recommended as it represents the save API.
//!
//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s.
//! You will need to use the `drm` crate to provide the required types to create
//! a backend.
//!
//! ```rust,no_run
//! extern crate drm;
//! extern crate smithay;
//! extern crate wayland_server;
//!
//! use drm::Device as BasicDevice;
//! use drm::control::{Device as ControlDevice, ResourceInfo};
//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
//! use drm::control::encoder::{Info as EncoderInfo};
//! use std::fs::{File, OpenOptions};
//! use std::os::unix::io::RawFd;
//! use std::os::unix::io::AsRawFd;
//! use smithay::backend::drm::{DrmDevice, DrmBackend};
//!
//! #[derive(Debug)]
//! pub struct Card(File);
//!
//! impl AsRawFd for Card {
//!     fn as_raw_fd(&self) -> RawFd {
//!         self.0.as_raw_fd()
//!     }
//! }
//!
//! impl BasicDevice for Card {}
//! impl ControlDevice for Card {}
//!
//! # fn main() {
//! // Open the drm device
//! let mut options = OpenOptions::new();
//! options.read(true);
//! options.write(true);
//! let mut device = DrmDevice::new(
//!     Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
//!     None /*put a logger here*/
//! ).unwrap();
//!
//! // Get a set of all modesetting resource handles
//! let res_handles = device.resource_handles().unwrap();
//!
//! // Use first connected connector for this example
//! let connector_info = res_handles.connectors().iter()
//!     .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
//!     .find(|conn| conn.connection_state() == ConnectorState::Connected)
//!     .unwrap();
//!
//! // Use the first encoder
//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap();
//!
//! // use the connected crtc if any
//! let crtc = encoder_info.current_crtc()
//!     // or use the first one that is compatible with the encoder
//!     .unwrap_or_else(||
//!         *res_handles.filter_crtcs(encoder_info.possible_crtcs())
//!           .iter()
//!           .next()
//!           .unwrap());
//!
//! // Use first mode (usually the highest resolution)
//! let mode = connector_info.modes()[0];
//!
//! // Create the backend
//! let backend = device.create_backend(
//!     crtc,
//!     mode,
//!     vec![connector_info.handle()]
//! ).unwrap();
//! # }
//! ```
//!
//! ### Page Flips / Tear-free video
//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a
//! `DrmBackend` works the same to finish the rendering, but will return
//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the
//! crtc has happened.
//!
//! You can monitor the page flips by registering the `DrmDevice` as and
//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified
//! whenever a page flip has happened, so you can render the next frame immediately
//! and get a tear-free representation on the display.
//!
//! You need to render at least once to successfully trigger the first event.
//!
//! ```rust,no_run
//! # extern crate drm;
//! # extern crate smithay;
//! # extern crate wayland_server;
//! #
//! # use drm::Device as BasicDevice;
//! # use drm::control::{Device as ControlDevice, ResourceInfo};
//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
//! use drm::control::crtc::{Handle as CrtcHandle};
//! use drm::result::Error as DrmError;
//! # use std::fs::{File, OpenOptions};
//! # use std::os::unix::io::RawFd;
//! # use std::os::unix::io::AsRawFd;
//! # use std::time::Duration;
//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind};
//! use smithay::backend::graphics::egl::EGLGraphicsBackend;
//! #
//! # #[derive(Debug)]
//! # pub struct Card(File);
//! # impl AsRawFd for Card {
//! #     fn as_raw_fd(&self) -> RawFd {
//! #         self.0.as_raw_fd()
//! #     }
//! # }
//! # impl BasicDevice for Card {}
//! # impl ControlDevice for Card {}
//! #
//! # fn main() {
//! #
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! #
//! # let mut options = OpenOptions::new();
//! # options.read(true);
//! # options.write(true);
//! # let mut device = DrmDevice::new(
//! #     Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
//! #     None /*put a logger here*/
//! # ).unwrap();
//! #
//! # let res_handles = device.resource_handles().unwrap();
//! # let connector_info = res_handles.connectors().iter()
//! #     .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
//! #     .find(|conn| conn.connection_state() == ConnectorState::Connected)
//! #     .unwrap();
//! # let crtc = res_handles.crtcs()[0];
//! # let mode = connector_info.modes()[0];
//! # let backend = device.create_backend(
//! #     crtc,
//! #     mode,
//! #     vec![connector_info.handle()]
//! # ).unwrap();
//!
//! struct MyDrmHandler(DrmBackend<Card>);
//!
//! impl DrmHandler<Card> for MyDrmHandler {
//!     fn ready(
//!         &mut self,
//!         _device: &mut DrmDevice<Card>,
//!         _crtc: CrtcHandle,
//!         _frame: u32,
//!         _duration: Duration)
//!     {
//!         // render surfaces and swap again
//!         self.0.swap_buffers().unwrap();
//!     }
//!     fn error(
//!         &mut self,
//!         device: &mut DrmDevice<Card>,
//!         error: DrmError)
//!     {
//!         panic!("DrmDevice errored: {}", error);
//!     }
//! }
//!
//! // render something (like clear_color)
//! backend.swap_buffers().unwrap();
//!
//! let (_source, _device_rc) = drm_device_bind(
//!     &event_loop.handle(),
//!     device,
//!     MyDrmHandler(backend)
//! ).map_err(|(err, _)| err).unwrap();
//!
//! /* And then run the event loop once all your setup is done */
//! # }
//! ```

use backend::graphics::egl::{
    context::{EGLContext, GlAttributes},
    error::Result as EGLResult,
    native::Gbm,
    wayland::{EGLDisplay, EGLWaylandExtensions},
};
#[cfg(feature = "backend_session")]
use backend::session::{AsSessionObserver, SessionObserver};
use drm::{
    control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo},
    result::Error as DrmError,
    Device as BasicDevice,
};
use gbm::{BufferObject, Device as GbmDevice};
use nix::{
    self,
    sys::stat::{self, dev_t, fstat},
};
use std::{
    cell::RefCell,
    collections::HashMap,
    hash::{Hash, Hasher},
    io::Error as IoError,
    os::unix::io::{AsRawFd, RawFd},
    path::PathBuf,
    rc::{Rc, Weak},
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, Once, ONCE_INIT,
    },
    time::Duration,
};

use wayland_server::{
    calloop::{
        generic::{EventedRawFd, Generic},
        mio::Ready,
        LoopHandle, Source,
    },
    Display,
};

mod backend;
pub mod error;

pub use self::backend::DrmBackend;
use self::{backend::DrmBackendInternal, error::*};

static LOAD: Once = ONCE_INIT;

/// Representation of an open DRM device node to create rendering backends
pub struct DrmDevice<A: ControlDevice + 'static> {
    context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
    old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
    device_id: dev_t,
    backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
    active: Arc<AtomicBool>,
    privileged: bool,
    logger: ::slog::Logger,
}

impl<A: ControlDevice + 'static> DrmDevice<A> {
    /// Create a new `DrmDevice` from an open DRM node
    ///
    /// Returns an error if the file is no valid DRM node or context creation was not
    /// successful.
    pub fn new<L>(dev: A, logger: L) -> Result<Self>
    where
        L: Into<Option<::slog::Logger>>,
    {
        DrmDevice::new_with_gl_attr(
            dev,
            GlAttributes {
                version: None,
                profile: None,
                debug: cfg!(debug_assertions),
                vsync: true,
            },
            logger,
        )
    }

    /// Create a new `DrmDevice` from an open DRM node and given `GlAttributes`
    ///
    /// Returns an error if the file is no valid DRM node or context creation was not
    /// successful.
    pub fn new_with_gl_attr<L>(dev: A, attributes: GlAttributes, logger: L) -> Result<Self>
    where
        L: Into<Option<::slog::Logger>>,
    {
        let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));

        /* GBM will load a dri driver, but even though they need symbols from
         * libglapi, in some version of Mesa they are not linked to it. Since
         * only the gl-renderer module links to it, these symbols won't be globally available,
         * and loading the DRI driver fails.
         * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
         */
        LOAD.call_once(|| unsafe {
            nix::libc::dlopen(
                "libglapi.so.0".as_ptr() as *const _,
                nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
            );
        });

        let device_id = fstat(dev.as_raw_fd())
            .chain_err(|| ErrorKind::UnableToGetDeviceId)?
            .st_rdev;

        let mut drm = DrmDevice {
            // Open the gbm device from the DRM device and create a context based on that
            context: Rc::new(
                EGLContext::new(
                    {
                        debug!(log, "Creating gbm device");
                        let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?;
                        debug!(log, "Creating egl context from gbm device");
                        gbm
                    },
                    attributes,
                    Default::default(),
                    log.clone(),
                ).map_err(Error::from)?,
            ),
            backends: Rc::new(RefCell::new(HashMap::new())),
            device_id,
            old_state: HashMap::new(),
            active: Arc::new(AtomicBool::new(true)),
            privileged: true,
            logger: log.clone(),
        };

        info!(log, "DrmDevice initializing");

        // we want to mode-set, so we better be the master, if we run via a tty session
        if drm.set_master().is_err() {
            warn!(log, "Unable to become drm master, assuming unprivileged mode");
            drm.privileged = false;
        };

        let res_handles = drm.resource_handles().chain_err(|| {
            ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path()))
        })?;
        for &con in res_handles.connectors() {
            let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| {
                ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path()))
            })?;
            if let Some(enc) = con_info.current_encoder() {
                let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| {
                    ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path()))
                })?;
                if let Some(crtc) = enc_info.current_crtc() {
                    let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| {
                        ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path()))
                    })?;
                    drm.old_state
                        .entry(crtc)
                        .or_insert((info, Vec::new()))
                        .1
                        .push(con);
                }
            }
        }

        Ok(drm)
    }

    /// Create a new backend on a given crtc with a given `Mode` for a given amount
    /// of `connectors` (mirroring).
    ///
    /// Errors if initialization fails or the mode is not available on all given
    /// connectors.
    pub fn create_backend<I>(
        &mut self,
        crtc: crtc::Handle,
        mode: Mode,
        connectors: I,
    ) -> Result<DrmBackend<A>>
    where
        I: Into<Vec<connector::Handle>>,
    {
        if self.backends.borrow().contains_key(&crtc) {
            bail!(ErrorKind::CrtcAlreadyInUse(crtc));
        }

        if !self.active.load(Ordering::SeqCst) {
            bail!(ErrorKind::DeviceInactive);
        }

        // check if the given connectors and crtc match
        let connectors = connectors.into();

        // check if we have an encoder for every connector and the mode mode
        for connector in &connectors {
            let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| {
                ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
            })?;

            // check the mode
            if !con_info.modes().contains(&mode) {
                bail!(ErrorKind::ModeNotSuitable(mode));
            }

            // check for every connector which encoders it does support
            let encoders = con_info
                .encoders()
                .iter()
                .map(|encoder| {
                    encoder::Info::load_from_device(self, *encoder).chain_err(|| {
                        ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
                    })
                }).collect::<Result<Vec<encoder::Info>>>()?;

            // and if any encoder supports the selected crtc
            let resource_handles = self.resource_handles().chain_err(|| {
                ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
            })?;
            if !encoders
                .iter()
                .map(|encoder| encoder.possible_crtcs())
                .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc))
            {
                bail!(ErrorKind::NoSuitableEncoder(con_info, crtc))
            }
        }

        // configuration is valid, the kernel will figure out the rest

        let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc)));
        let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?;
        self.backends.borrow_mut().insert(crtc, backend.weak());
        Ok(backend)
    }

    /// Returns an internal device id, that is unique per boot per system
    pub fn device_id(&self) -> u64 {
        self.device_id
    }
}

/// Trait for types representing open devices
pub trait DevPath {
    /// Returns the path of the open device if possible
pub(self) mod surface;

pub use device::{DevPath, DrmDevice, DrmEvent, EventMetadata as DrmEventMetadata, Time as DrmEventTime};
pub use error::Error as DrmError;
    fn dev_path(&self) -> Option<PathBuf> {
        use std::fs;

        fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
    }
}

impl<A: ControlDevice + 'static> PartialEq for DrmDevice<A> {
    fn eq(&self, other: &DrmDevice<A>) -> bool {
        self.device_id == other.device_id
    }
}
impl<A: ControlDevice + 'static> Eq for DrmDevice<A> {}

impl<A: ControlDevice + 'static> Hash for DrmDevice<A> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.device_id.hash(state);
    }
}

// for users convenience and FdEventSource registering
impl<A: ControlDevice + 'static> AsRawFd for DrmDevice<A> {
    fn as_raw_fd(&self) -> RawFd {
        self.context.as_raw_fd()
    }
}

impl<A: ControlDevice + 'static> BasicDevice for DrmDevice<A> {}
impl<A: ControlDevice + 'static> ControlDevice for DrmDevice<A> {}

impl<A: ControlDevice + 'static> EGLWaylandExtensions for DrmDevice<A> {
    fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
        self.context.bind_wl_display(display)
    }
}

impl<A: ControlDevice + 'static> Drop for DrmDevice<A> {
    fn drop(&mut self) {
        if Rc::strong_count(&self.context) > 1 {
            panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed");
        }
        for (handle, (info, connectors)) in self.old_state.drain() {
            if let Err(err) = crtc::set(
                &*self.context,
                handle,
                info.fb(),
                &connectors,
                info.position(),
                info.mode(),
            ) {
                error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
            }
        }
        if self.privileged {
            if let Err(err) = self.drop_master() {
                error!(self.logger, "Failed to drop drm master state. Error: {}", err);
            }
        }
    }
}

/// Handler for DRM node events
///
/// See module-level documentation for its use
pub trait DrmHandler<A: ControlDevice + 'static> {
    /// The `DrmBackend` of crtc has finished swapping buffers and new frame can now
    /// (and should be immediately) be rendered.
    fn ready(&mut self, device: &mut DrmDevice<A>, crtc: crtc::Handle, frame: u32, duration: Duration);
    /// The `DrmDevice` has thrown an error.
    ///
    /// The related backends are most likely *not* usable anymore and
    /// the whole stack has to be recreated..
    fn error(&mut self, device: &mut DrmDevice<A>, error: DrmError);
}

/// Bind a `DrmDevice` to an `EventLoop`,
///
/// This will cause it to recieve events and feed them into an `DrmHandler`
pub fn drm_device_bind<A, H, Data: 'static>(
    handle: &LoopHandle<Data>,
    device: DrmDevice<A>,
    mut handler: H,
) -> ::std::result::Result<(Source<Generic<EventedRawFd>>, Rc<RefCell<DrmDevice<A>>>), (IoError, DrmDevice<A>)>
where
    A: ControlDevice + 'static,
    H: DrmHandler<A> + 'static,
{
    let fd = device.as_raw_fd();
    let device = Rc::new(RefCell::new(device));

    let mut source = Generic::from_raw_fd(fd);
    source.set_interest(Ready::readable());

    match handle.insert_source(source, {
        let device = device.clone();
        move |_evt, _| {
            let mut device = device.borrow_mut();
            process_events(&mut *device, &mut handler);
        }
    }) {
        Ok(source) => Ok((source, device)),
        Err(e) => {
            let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!());
            Err((e.into(), device.into_inner()))
        }
    }
}

fn process_events<A, H>(device: &mut DrmDevice<A>, handler: &mut H)
where
    A: ControlDevice + 'static,
    H: DrmHandler<A> + 'static,
{
    match crtc::receive_events(&*device) {
        Ok(events) => for event in events {
            if let crtc::Event::PageFlip(event) = event {
                if device.active.load(Ordering::SeqCst) {
                    let backends = device.backends.borrow().clone();
                    if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() {
                        // we can now unlock the buffer
                        backend.unlock_buffer();
                        trace!(device.logger, "Handling event for backend {:?}", event.crtc);
                        // and then call the user to render the next frame
                        handler.ready(device, event.crtc, event.frame, event.duration);
                    } else {
                        device.backends.borrow_mut().remove(&event.crtc);
                    }
                }
            }
        },
        Err(err) => handler.error(device, err),
    }
}

/// `SessionObserver` linked to the `DrmDevice` it was created from.
pub struct DrmDeviceObserver<A: ControlDevice + 'static> {
    context: Weak<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
    device_id: dev_t,
    backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
    old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
    active: Arc<AtomicBool>,
    privileged: bool,
    logger: ::slog::Logger,
}

#[cfg(feature = "backend_session")]
impl<A: ControlDevice + 'static> AsSessionObserver<DrmDeviceObserver<A>> for DrmDevice<A> {
    fn observer(&mut self) -> DrmDeviceObserver<A> {
        DrmDeviceObserver {
            context: Rc::downgrade(&self.context),
            device_id: self.device_id,
            backends: self.backends.clone(),
            old_state: self.old_state.clone(),
            active: self.active.clone(),
            privileged: self.privileged,
            logger: self.logger.clone(),
        }
    }
}

#[cfg(feature = "backend_session")]
impl<A: ControlDevice + 'static> SessionObserver for DrmDeviceObserver<A> {
    fn pause(&mut self, devnum: Option<(u32, u32)>) {
        if let Some((major, minor)) = devnum {
            if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
                return;
            }
        }
        if let Some(device) = self.context.upgrade() {
            for (handle, &(ref info, ref connectors)) in &self.old_state {
                if let Err(err) = crtc::set(
                    &*device,
                    *handle,
                    info.fb(),
                    connectors,
                    info.position(),
                    info.mode(),
                ) {
                    error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
                }
            }
        }
        self.active.store(false, Ordering::SeqCst);
        if self.privileged {
            if let Some(device) = self.context.upgrade() {
                if let Err(err) = device.drop_master() {
                    error!(self.logger, "Failed to drop drm master state. Error: {}", err);
                }
            }
        }
    }

    fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
        if let Some((major, minor, fd)) = devnum {
            if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
                return;
            } else if let Some(fd) = fd {
                info!(self.logger, "Replacing fd");
                if let Some(device) = self.context.upgrade() {
                    nix::unistd::dup2(device.as_raw_fd(), fd)
                        .expect("Failed to replace file descriptor of drm device");
                }
            }
        }
        self.active.store(true, Ordering::SeqCst);
        if self.privileged {
            if let Some(device) = self.context.upgrade() {
                if let Err(err) = device.set_master() {
                    crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
                }
            }
        }
        let mut crtcs = Vec::new();
        for (crtc, backend) in self.backends.borrow().iter() {
            if let Some(backend) = backend.upgrade() {
                backend.unlock_buffer();
                if let Err(err) = backend.page_flip(None) {
                    error!(
                        self.logger,
                        "Failed to activate crtc ({:?}) again. Error: {}", crtc, err
                    );
                }
                // reset cursor
                {
                    let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
                        unsafe { &*backend.cursor.as_ptr() };
                    if crtc::set_cursor2(
                        &*backend.context,
                        *crtc,
                        cursor,
                        ((*hotspot).0 as i32, (*hotspot).1 as i32),
                    ).is_err()
                    {
                        if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) {
                            error!(self.logger, "Failed to reset cursor. Error: {}", err);
                        }
                    }
                }
            } else {
                crtcs.push(*crtc);
            }
        }
        for crtc in crtcs {
            self.backends.borrow_mut().remove(&crtc);
        }
    }
}
    let mut overlay = Vec::new();

    let planes = dev.plane_handles().map_err(|source| DrmError::Access {
        errmsg: "Error loading plane handles",
        dev: dev.dev_path(),
        source,
    })?;

    let resources = dev.resource_handles().map_err(|source| DrmError::Access {
        errmsg: "Error loading resource handles",
        dev: dev.dev_path(),
        source,
    })?;

    for plane in planes {
        let info = dev.get_plane(plane).map_err(|source| DrmError::Access {
            errmsg: "Failed to get plane information",
            dev: dev.dev_path(),
            source,
        })?;
        let filter = info.possible_crtcs();
        if resources.filter_crtcs(filter).contains(crtc) {
            match plane_type(dev, plane)? {
                PlaneType::Primary => {
                    primary = Some(plane);
                }
                PlaneType::Cursor => {
                    cursor = Some(plane);
                }
                PlaneType::Overlay => {
                    overlay.push(plane);
                }
            };
        }
    }

    Ok(Planes {
        primary: primary.expect("Crtc has no primary plane"),
        cursor: if has_universal_planes { cursor } else { None },
        overlay: if has_universal_planes { overlay } else { Vec::new() },
    })
}

fn plane_type(dev: &impl ControlDevice, plane: plane::Handle) -> Result<PlaneType, DrmError> {
    let props = dev.get_properties(plane).map_err(|source| DrmError::Access {
        errmsg: "Failed to get properties of plane",
        dev: dev.dev_path(),
        source,
    })?;
    let (ids, vals) = props.as_props_and_values();
    for (&id, &val) in ids.iter().zip(vals.iter()) {
        let info = dev.get_property(id).map_err(|source| DrmError::Access {
            errmsg: "Failed to get property info",
            dev: dev.dev_path(),
            source,
        })?;
        if info.name().to_str().map(|x| x == "type").unwrap_or(false) {
            return Ok(match val {
                x if x == (PlaneType::Primary as u64) => PlaneType::Primary,
                x if x == (PlaneType::Cursor as u64) => PlaneType::Cursor,
                _ => PlaneType::Overlay,
            });
        }
    }
    unreachable!()
}

<MSG> Refactor drm backend

- Split `DrmDevice` into `LegacyDrmDevice`, `GbmDevice` and `EglDevice`
- Provide common `Device` and `RawDevice` traits

- Change `DrmBackend` into `Surface` (and `RawSurface`) implementations of each `Device` (or `RawDevice`)

<DFF> @@ -1,454 +1,77 @@
-//! Drm/Kms types and backend implementations
-//!
-//! This module provide a `DrmDevice` which acts as a representation for any DRM
-//! device and can be used to create the second provided structure a `DrmBackend`.
-//!
-//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/).
-//!
-//! Three entities are relevant for the initialization procedure.
-//!
-//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There responsibility
-//! is to read the data of the framebuffer and export it into an "Encoder". The number of crtc's
-//! represent the number of independent output devices the hardware may handle.
-//!
-//! An "Encoder" encodes the data of connected crtcs into a video signal for a fixed set
-//! of connectors. E.g. you might have an analog encoder based on a DAG for VGA ports, but another
-//! one for digital ones. Also not every encoder might be connected to every crtc.
-//!
-//! The last entity the "Connector" represents a port on your computer, possibly with a connected
-//! monitor, TV, capture card, etc.
-//!
-//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to
-//! and that feeds a given set of connectors, that can be manipulated at runtime.
-//!
-//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector,
-//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the
-//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown.
-//!
-//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm).
-//!
-//!
-//! ## How to use it
-//!
-//! ### Initialization
-//!
-//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of
-//! your DRM node. The `File` is recommended as it represents the save API.
-//!
-//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s.
-//! You will need to use the `drm` crate to provide the required types to create
-//! a backend.
-//!
-//! ```rust,no_run
-//! extern crate drm;
-//! extern crate smithay;
-//! extern crate wayland_server;
-//!
-//! use drm::Device as BasicDevice;
-//! use drm::control::{Device as ControlDevice, ResourceInfo};
-//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
-//! use drm::control::encoder::{Info as EncoderInfo};
-//! use std::fs::{File, OpenOptions};
-//! use std::os::unix::io::RawFd;
-//! use std::os::unix::io::AsRawFd;
-//! use smithay::backend::drm::{DrmDevice, DrmBackend};
-//!
-//! #[derive(Debug)]
-//! pub struct Card(File);
-//!
-//! impl AsRawFd for Card {
-//!     fn as_raw_fd(&self) -> RawFd {
-//!         self.0.as_raw_fd()
-//!     }
-//! }
-//!
-//! impl BasicDevice for Card {}
-//! impl ControlDevice for Card {}
-//!
-//! # fn main() {
-//! // Open the drm device
-//! let mut options = OpenOptions::new();
-//! options.read(true);
-//! options.write(true);
-//! let mut device = DrmDevice::new(
-//!     Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
-//!     None /*put a logger here*/
-//! ).unwrap();
-//!
-//! // Get a set of all modesetting resource handles
-//! let res_handles = device.resource_handles().unwrap();
-//!
-//! // Use first connected connector for this example
-//! let connector_info = res_handles.connectors().iter()
-//!     .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
-//!     .find(|conn| conn.connection_state() == ConnectorState::Connected)
-//!     .unwrap();
-//!
-//! // Use the first encoder
-//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap();
-//!
-//! // use the connected crtc if any
-//! let crtc = encoder_info.current_crtc()
-//!     // or use the first one that is compatible with the encoder
-//!     .unwrap_or_else(||
-//!         *res_handles.filter_crtcs(encoder_info.possible_crtcs())
-//!           .iter()
-//!           .next()
-//!           .unwrap());
-//!
-//! // Use first mode (usually the highest resolution)
-//! let mode = connector_info.modes()[0];
-//!
-//! // Create the backend
-//! let backend = device.create_backend(
-//!     crtc,
-//!     mode,
-//!     vec![connector_info.handle()]
-//! ).unwrap();
-//! # }
-//! ```
-//!
-//! ### Page Flips / Tear-free video
-//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a
-//! `DrmBackend` works the same to finish the rendering, but will return
-//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the
-//! crtc has happened.
-//!
-//! You can monitor the page flips by registering the `DrmDevice` as and
-//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified
-//! whenever a page flip has happened, so you can render the next frame immediately
-//! and get a tear-free representation on the display.
-//!
-//! You need to render at least once to successfully trigger the first event.
-//!
-//! ```rust,no_run
-//! # extern crate drm;
-//! # extern crate smithay;
-//! # extern crate wayland_server;
-//! #
-//! # use drm::Device as BasicDevice;
-//! # use drm::control::{Device as ControlDevice, ResourceInfo};
-//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
-//! use drm::control::crtc::{Handle as CrtcHandle};
-//! use drm::result::Error as DrmError;
-//! # use std::fs::{File, OpenOptions};
-//! # use std::os::unix::io::RawFd;
-//! # use std::os::unix::io::AsRawFd;
-//! # use std::time::Duration;
-//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind};
-//! use smithay::backend::graphics::egl::EGLGraphicsBackend;
-//! #
-//! # #[derive(Debug)]
-//! # pub struct Card(File);
-//! # impl AsRawFd for Card {
-//! #     fn as_raw_fd(&self) -> RawFd {
-//! #         self.0.as_raw_fd()
-//! #     }
-//! # }
-//! # impl BasicDevice for Card {}
-//! # impl ControlDevice for Card {}
-//! #
-//! # fn main() {
-//! #
-//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
-//! # let mut display = wayland_server::Display::new(event_loop.handle());
-//! #
-//! # let mut options = OpenOptions::new();
-//! # options.read(true);
-//! # options.write(true);
-//! # let mut device = DrmDevice::new(
-//! #     Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
-//! #     None /*put a logger here*/
-//! # ).unwrap();
-//! #
-//! # let res_handles = device.resource_handles().unwrap();
-//! # let connector_info = res_handles.connectors().iter()
-//! #     .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
-//! #     .find(|conn| conn.connection_state() == ConnectorState::Connected)
-//! #     .unwrap();
-//! # let crtc = res_handles.crtcs()[0];
-//! # let mode = connector_info.modes()[0];
-//! # let backend = device.create_backend(
-//! #     crtc,
-//! #     mode,
-//! #     vec![connector_info.handle()]
-//! # ).unwrap();
-//!
-//! struct MyDrmHandler(DrmBackend<Card>);
-//!
-//! impl DrmHandler<Card> for MyDrmHandler {
-//!     fn ready(
-//!         &mut self,
-//!         _device: &mut DrmDevice<Card>,
-//!         _crtc: CrtcHandle,
-//!         _frame: u32,
-//!         _duration: Duration)
-//!     {
-//!         // render surfaces and swap again
-//!         self.0.swap_buffers().unwrap();
-//!     }
-//!     fn error(
-//!         &mut self,
-//!         device: &mut DrmDevice<Card>,
-//!         error: DrmError)
-//!     {
-//!         panic!("DrmDevice errored: {}", error);
-//!     }
-//! }
-//!
-//! // render something (like clear_color)
-//! backend.swap_buffers().unwrap();
-//!
-//! let (_source, _device_rc) = drm_device_bind(
-//!     &event_loop.handle(),
-//!     device,
-//!     MyDrmHandler(backend)
-//! ).map_err(|(err, _)| err).unwrap();
-//!
-//! /* And then run the event loop once all your setup is done */
-//! # }
-//! ```
-
-use backend::graphics::egl::{
-    context::{EGLContext, GlAttributes},
-    error::Result as EGLResult,
-    native::Gbm,
-    wayland::{EGLDisplay, EGLWaylandExtensions},
-};
-#[cfg(feature = "backend_session")]
-use backend::session::{AsSessionObserver, SessionObserver};
-use drm::{
-    control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo},
-    result::Error as DrmError,
-    Device as BasicDevice,
-};
-use gbm::{BufferObject, Device as GbmDevice};
-use nix::{
-    self,
-    sys::stat::{self, dev_t, fstat},
-};
-use std::{
-    cell::RefCell,
-    collections::HashMap,
-    hash::{Hash, Hasher},
-    io::Error as IoError,
-    os::unix::io::{AsRawFd, RawFd},
-    path::PathBuf,
-    rc::{Rc, Weak},
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc, Once, ONCE_INIT,
-    },
-    time::Duration,
-};
-
-use wayland_server::{
-    calloop::{
-        generic::{EventedRawFd, Generic},
-        mio::Ready,
-        LoopHandle, Source,
-    },
-    Display,
-};
-
-mod backend;
-pub mod error;
-
-pub use self::backend::DrmBackend;
-use self::{backend::DrmBackendInternal, error::*};
-
-static LOAD: Once = ONCE_INIT;
-
-/// Representation of an open DRM device node to create rendering backends
-pub struct DrmDevice<A: ControlDevice + 'static> {
-    context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
-    old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
-    device_id: dev_t,
-    backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
-    active: Arc<AtomicBool>,
-    privileged: bool,
-    logger: ::slog::Logger,
+use drm::Device as BasicDevice;
+use drm::control::Device as ControlDevice;
+pub use drm::control::crtc;
+pub use drm::control::connector;
+pub use drm::control::framebuffer;
+pub use drm::control::Mode;
+
+use std::borrow::Borrow;
+use std::error::Error;
+use std::path::PathBuf;
+use std::os::unix::io::AsRawFd;
+
+use wayland_server::calloop::generic::{EventedFd, Generic};
+use wayland_server::calloop::{LoopHandle, Source};
+use wayland_server::calloop::mio::Ready;
+pub use wayland_server::calloop::InsertError;
+
+use super::graphics::SwapBuffersError;
+
+#[cfg(feature = "backend_drm_legacy")]
+pub mod legacy;
+#[cfg(feature = "backend_drm_gbm")]
+pub mod gbm;
+#[cfg(feature = "backend_drm_egl")]
+pub mod egl;
+
+pub trait DeviceHandler {
+    type Device: Device + ?Sized;
+    fn vblank(&mut self, surface: &<<Self as DeviceHandler>::Device as Device>::Surface);
+    fn error(&mut self, error: <<<Self as DeviceHandler>::Device as Device>::Surface as Surface>::Error);
 }
 
-impl<A: ControlDevice + 'static> DrmDevice<A> {
-    /// Create a new `DrmDevice` from an open DRM node
-    ///
-    /// Returns an error if the file is no valid DRM node or context creation was not
-    /// successful.
-    pub fn new<L>(dev: A, logger: L) -> Result<Self>
-    where
-        L: Into<Option<::slog::Logger>>,
-    {
-        DrmDevice::new_with_gl_attr(
-            dev,
-            GlAttributes {
-                version: None,
-                profile: None,
-                debug: cfg!(debug_assertions),
-                vsync: true,
-            },
-            logger,
-        )
-    }
-
-    /// Create a new `DrmDevice` from an open DRM node and given `GlAttributes`
-    ///
-    /// Returns an error if the file is no valid DRM node or context creation was not
-    /// successful.
-    pub fn new_with_gl_attr<L>(dev: A, attributes: GlAttributes, logger: L) -> Result<Self>
-    where
-        L: Into<Option<::slog::Logger>>,
-    {
-        let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
-
-        /* GBM will load a dri driver, but even though they need symbols from
-         * libglapi, in some version of Mesa they are not linked to it. Since
-         * only the gl-renderer module links to it, these symbols won't be globally available,
-         * and loading the DRI driver fails.
-         * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
-         */
-        LOAD.call_once(|| unsafe {
-            nix::libc::dlopen(
-                "libglapi.so.0".as_ptr() as *const _,
-                nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
-            );
-        });
+pub trait Device: AsRawFd + DevPath {
+    type Surface: Surface;
+    type Return: Borrow<Self::Surface>;
 
-        let device_id = fstat(dev.as_raw_fd())
-            .chain_err(|| ErrorKind::UnableToGetDeviceId)?
-            .st_rdev;
-
-        let mut drm = DrmDevice {
-            // Open the gbm device from the DRM device and create a context based on that
-            context: Rc::new(
-                EGLContext::new(
-                    {
-                        debug!(log, "Creating gbm device");
-                        let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?;
-                        debug!(log, "Creating egl context from gbm device");
-                        gbm
-                    },
-                    attributes,
-                    Default::default(),
-                    log.clone(),
-                ).map_err(Error::from)?,
-            ),
-            backends: Rc::new(RefCell::new(HashMap::new())),
-            device_id,
-            old_state: HashMap::new(),
-            active: Arc::new(AtomicBool::new(true)),
-            privileged: true,
-            logger: log.clone(),
-        };
-
-        info!(log, "DrmDevice initializing");
-
-        // we want to mode-set, so we better be the master, if we run via a tty session
-        if drm.set_master().is_err() {
-            warn!(log, "Unable to become drm master, assuming unprivileged mode");
-            drm.privileged = false;
-        };
-
-        let res_handles = drm.resource_handles().chain_err(|| {
-            ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path()))
-        })?;
-        for &con in res_handles.connectors() {
-            let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| {
-                ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path()))
-            })?;
-            if let Some(enc) = con_info.current_encoder() {
-                let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| {
-                    ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path()))
-                })?;
-                if let Some(crtc) = enc_info.current_crtc() {
-                    let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| {
-                        ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path()))
-                    })?;
-                    drm.old_state
-                        .entry(crtc)
-                        .or_insert((info, Vec::new()))
-                        .1
-                        .push(con);
-                }
-            }
-        }
-
-        Ok(drm)
-    }
-
-    /// Create a new backend on a given crtc with a given `Mode` for a given amount
-    /// of `connectors` (mirroring).
-    ///
-    /// Errors if initialization fails or the mode is not available on all given
-    /// connectors.
-    pub fn create_backend<I>(
+    fn set_handler(&mut self, handler: impl DeviceHandler<Device=Self> + 'static);
+    fn clear_handler(&mut self);
+    fn create_surface(
         &mut self,
-        crtc: crtc::Handle,
+        ctrc: crtc::Handle,
         mode: Mode,
-        connectors: I,
-    ) -> Result<DrmBackend<A>>
-    where
-        I: Into<Vec<connector::Handle>>,
-    {
-        if self.backends.borrow().contains_key(&crtc) {
-            bail!(ErrorKind::CrtcAlreadyInUse(crtc));
-        }
-
-        if !self.active.load(Ordering::SeqCst) {
-            bail!(ErrorKind::DeviceInactive);
-        }
-
-        // check if the given connectors and crtc match
-        let connectors = connectors.into();
-
-        // check if we have an encoder for every connector and the mode mode
-        for connector in &connectors {
-            let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| {
-                ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
-            })?;
-
-            // check the mode
-            if !con_info.modes().contains(&mode) {
-                bail!(ErrorKind::ModeNotSuitable(mode));
-            }
-
-            // check for every connector which encoders it does support
-            let encoders = con_info
-                .encoders()
-                .iter()
-                .map(|encoder| {
-                    encoder::Info::load_from_device(self, *encoder).chain_err(|| {
-                        ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
-                    })
-                }).collect::<Result<Vec<encoder::Info>>>()?;
-
-            // and if any encoder supports the selected crtc
-            let resource_handles = self.resource_handles().chain_err(|| {
-                ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
-            })?;
-            if !encoders
-                .iter()
-                .map(|encoder| encoder.possible_crtcs())
-                .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc))
-            {
-                bail!(ErrorKind::NoSuitableEncoder(con_info, crtc))
-            }
-        }
-
-        // configuration is valid, the kernel will figure out the rest
+        connectors: impl Into<<Self::Surface as Surface>::Connectors>
+    ) -> Result<Self::Return, <Self::Surface as Surface>::Error>;
+    fn process_events(&mut self);
+}
 
-        let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc)));
-        let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?;
-        self.backends.borrow_mut().insert(crtc, backend.weak());
-        Ok(backend)
-    }
+pub trait RawDevice: Device<Surface=<Self as RawDevice>::Surface>
+where
+    <Self as Device>::Return: Borrow<<Self as RawDevice>::Surface>
+{
+    type Surface: RawSurface;
+}
 
-    /// Returns an internal device id, that is unique per boot per system
-    pub fn device_id(&self) -> u64 {
-        self.device_id
-    }
+pub trait Surface {
+    type Connectors: IntoIterator<Item=connector::Handle>;
+    type Error: Error + Send;
+
+    fn crtc(&self) -> crtc::Handle;
+    fn current_connectors(&self) -> Self::Connectors;
+    fn pending_connectors(&self) -> Self::Connectors;
+    fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
+    fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
+    fn current_mode(&self) -> Mode;
+    fn pending_mode(&self) -> Mode;
+    fn use_mode(&self, mode: Mode) -> Result<(), Self::Error>;
 }
 
+pub trait RawSurface: Surface + ControlDevice + BasicDevice {
+    fn commit_pending(&self) -> bool;
+    fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), <Self as Surface>::Error>;
+    fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>;
+} 
+
 /// Trait for types representing open devices
 pub trait DevPath {
     /// Returns the path of the open device if possible
@@ -459,247 +82,25 @@ impl<A: AsRawFd> DevPath for A {
     fn dev_path(&self) -> Option<PathBuf> {
         use std::fs;
 
-        fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
-    }
-}
-
-impl<A: ControlDevice + 'static> PartialEq for DrmDevice<A> {
-    fn eq(&self, other: &DrmDevice<A>) -> bool {
-        self.device_id == other.device_id
+       fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
     }
 }
-impl<A: ControlDevice + 'static> Eq for DrmDevice<A> {}
 
-impl<A: ControlDevice + 'static> Hash for DrmDevice<A> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.device_id.hash(state);
-    }
-}
-
-// for users convenience and FdEventSource registering
-impl<A: ControlDevice + 'static> AsRawFd for DrmDevice<A> {
-    fn as_raw_fd(&self) -> RawFd {
-        self.context.as_raw_fd()
-    }
-}
-
-impl<A: ControlDevice + 'static> BasicDevice for DrmDevice<A> {}
-impl<A: ControlDevice + 'static> ControlDevice for DrmDevice<A> {}
-
-impl<A: ControlDevice + 'static> EGLWaylandExtensions for DrmDevice<A> {
-    fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
-        self.context.bind_wl_display(display)
-    }
-}
-
-impl<A: ControlDevice + 'static> Drop for DrmDevice<A> {
-    fn drop(&mut self) {
-        if Rc::strong_count(&self.context) > 1 {
-            panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed");
-        }
-        for (handle, (info, connectors)) in self.old_state.drain() {
-            if let Err(err) = crtc::set(
-                &*self.context,
-                handle,
-                info.fb(),
-                &connectors,
-                info.position(),
-                info.mode(),
-            ) {
-                error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
-            }
-        }
-        if self.privileged {
-            if let Err(err) = self.drop_master() {
-                error!(self.logger, "Failed to drop drm master state. Error: {}", err);
-            }
-        }
-    }
-}
-
-/// Handler for DRM node events
+/// Bind a `Device` to an `EventLoop`,
 ///
-/// See module-level documentation for its use
-pub trait DrmHandler<A: ControlDevice + 'static> {
-    /// The `DrmBackend` of crtc has finished swapping buffers and new frame can now
-    /// (and should be immediately) be rendered.
-    fn ready(&mut self, device: &mut DrmDevice<A>, crtc: crtc::Handle, frame: u32, duration: Duration);
-    /// The `DrmDevice` has thrown an error.
-    ///
-    /// The related backends are most likely *not* usable anymore and
-    /// the whole stack has to be recreated..
-    fn error(&mut self, device: &mut DrmDevice<A>, error: DrmError);
-}
-
-/// Bind a `DrmDevice` to an `EventLoop`,
-///
-/// This will cause it to recieve events and feed them into an `DrmHandler`
-pub fn drm_device_bind<A, H, Data: 'static>(
+/// This will cause it to recieve events and feed them into an `DeviceHandler`
+pub fn device_bind<D: Device + 'static, Data>(
     handle: &LoopHandle<Data>,
-    device: DrmDevice<A>,
-    mut handler: H,
-) -> ::std::result::Result<(Source<Generic<EventedRawFd>>, Rc<RefCell<DrmDevice<A>>>), (IoError, DrmDevice<A>)>
+    device: D,
+) -> ::std::result::Result<Source<Generic<EventedFd<D>>>, InsertError<Generic<EventedFd<D>>>>
 where
-    A: ControlDevice + 'static,
-    H: DrmHandler<A> + 'static,
+    D: Device,
+    Data: 'static,
 {
-    let fd = device.as_raw_fd();
-    let device = Rc::new(RefCell::new(device));
-
-    let mut source = Generic::from_raw_fd(fd);
+    let mut source = Generic::from_fd_source(device);
     source.set_interest(Ready::readable());
 
-    match handle.insert_source(source, {
-        let device = device.clone();
-        move |_evt, _| {
-            let mut device = device.borrow_mut();
-            process_events(&mut *device, &mut handler);
-        }
-    }) {
-        Ok(source) => Ok((source, device)),
-        Err(e) => {
-            let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!());
-            Err((e.into(), device.into_inner()))
-        }
-    }
-}
-
-fn process_events<A, H>(device: &mut DrmDevice<A>, handler: &mut H)
-where
-    A: ControlDevice + 'static,
-    H: DrmHandler<A> + 'static,
-{
-    match crtc::receive_events(&*device) {
-        Ok(events) => for event in events {
-            if let crtc::Event::PageFlip(event) = event {
-                if device.active.load(Ordering::SeqCst) {
-                    let backends = device.backends.borrow().clone();
-                    if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() {
-                        // we can now unlock the buffer
-                        backend.unlock_buffer();
-                        trace!(device.logger, "Handling event for backend {:?}", event.crtc);
-                        // and then call the user to render the next frame
-                        handler.ready(device, event.crtc, event.frame, event.duration);
-                    } else {
-                        device.backends.borrow_mut().remove(&event.crtc);
-                    }
-                }
-            }
-        },
-        Err(err) => handler.error(device, err),
-    }
-}
-
-/// `SessionObserver` linked to the `DrmDevice` it was created from.
-pub struct DrmDeviceObserver<A: ControlDevice + 'static> {
-    context: Weak<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
-    device_id: dev_t,
-    backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
-    old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
-    active: Arc<AtomicBool>,
-    privileged: bool,
-    logger: ::slog::Logger,
-}
-
-#[cfg(feature = "backend_session")]
-impl<A: ControlDevice + 'static> AsSessionObserver<DrmDeviceObserver<A>> for DrmDevice<A> {
-    fn observer(&mut self) -> DrmDeviceObserver<A> {
-        DrmDeviceObserver {
-            context: Rc::downgrade(&self.context),
-            device_id: self.device_id,
-            backends: self.backends.clone(),
-            old_state: self.old_state.clone(),
-            active: self.active.clone(),
-            privileged: self.privileged,
-            logger: self.logger.clone(),
-        }
-    }
-}
-
-#[cfg(feature = "backend_session")]
-impl<A: ControlDevice + 'static> SessionObserver for DrmDeviceObserver<A> {
-    fn pause(&mut self, devnum: Option<(u32, u32)>) {
-        if let Some((major, minor)) = devnum {
-            if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
-                return;
-            }
-        }
-        if let Some(device) = self.context.upgrade() {
-            for (handle, &(ref info, ref connectors)) in &self.old_state {
-                if let Err(err) = crtc::set(
-                    &*device,
-                    *handle,
-                    info.fb(),
-                    connectors,
-                    info.position(),
-                    info.mode(),
-                ) {
-                    error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
-                }
-            }
-        }
-        self.active.store(false, Ordering::SeqCst);
-        if self.privileged {
-            if let Some(device) = self.context.upgrade() {
-                if let Err(err) = device.drop_master() {
-                    error!(self.logger, "Failed to drop drm master state. Error: {}", err);
-                }
-            }
-        }
-    }
-
-    fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
-        if let Some((major, minor, fd)) = devnum {
-            if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
-                return;
-            } else if let Some(fd) = fd {
-                info!(self.logger, "Replacing fd");
-                if let Some(device) = self.context.upgrade() {
-                    nix::unistd::dup2(device.as_raw_fd(), fd)
-                        .expect("Failed to replace file descriptor of drm device");
-                }
-            }
-        }
-        self.active.store(true, Ordering::SeqCst);
-        if self.privileged {
-            if let Some(device) = self.context.upgrade() {
-                if let Err(err) = device.set_master() {
-                    crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
-                }
-            }
-        }
-        let mut crtcs = Vec::new();
-        for (crtc, backend) in self.backends.borrow().iter() {
-            if let Some(backend) = backend.upgrade() {
-                backend.unlock_buffer();
-                if let Err(err) = backend.page_flip(None) {
-                    error!(
-                        self.logger,
-                        "Failed to activate crtc ({:?}) again. Error: {}", crtc, err
-                    );
-                }
-                // reset cursor
-                {
-                    let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
-                        unsafe { &*backend.cursor.as_ptr() };
-                    if crtc::set_cursor2(
-                        &*backend.context,
-                        *crtc,
-                        cursor,
-                        ((*hotspot).0 as i32, (*hotspot).1 as i32),
-                    ).is_err()
-                    {
-                        if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) {
-                            error!(self.logger, "Failed to reset cursor. Error: {}", err);
-                        }
-                    }
-                }
-            } else {
-                crtcs.push(*crtc);
-            }
-        }
-        for crtc in crtcs {
-            self.backends.borrow_mut().remove(&crtc);
-        }
-    }
+    handle.insert_source(source, |evt, _| {
+        evt.source.borrow_mut().0.process_events();
+    })
 }

Refactor drm backend