<NME> legacy.rs
<BEF> use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, PageFlipFlags};

use std::collections::HashSet;
use std::os::unix::io::AsRawFd;
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc, RwLock,
};

use crate::backend::drm::{
    device::legacy::set_connector_state,
    device::{DevPath, DrmDeviceInternal},
    error::Error,
};

use slog::{debug, info, o, trace};

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
    pub mode: Mode,
    pub connectors: HashSet<connector::Handle>,
}

impl State {
    fn current_state<A: AsRawFd + ControlDevice>(fd: &A, crtc: crtc::Handle) -> Result<Self, Error> {
        // Try to enumarate the current state to set the initial state variable correctly.
        // We need an accurate state to handle `commit_pending`.
        let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access {
            errmsg: "Error loading crtc info",
            dev: fd.dev_path(),
            source,
        })?;

        let current_mode = crtc_info.mode();

        let mut current_connectors = HashSet::new();
        let res_handles = fd.resource_handles().map_err(|source| Error::Access {
            errmsg: "Error loading drm resources",
            dev: fd.dev_path(),
            source,
        })?;
        for &con in res_handles.connectors() {
            let con_info = fd.get_connector(con, false).map_err(|source| Error::Access {
                errmsg: "Error loading connector info",
                dev: fd.dev_path(),
                source,
            })?;
            if let Some(enc) = con_info.current_encoder() {
                let enc_info = fd.get_encoder(enc).map_err(|source| Error::Access {
                    errmsg: "Error loading encoder info",
                    dev: fd.dev_path(),
                    source,
                })?;
                if let Some(current_crtc) = enc_info.crtc() {
                    if crtc == current_crtc {
                        current_connectors.insert(con);
                    }
                }
            }
        }

        // If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below).
        // A better fix would probably be making mode an `Option`, but that would mean
        // we need to be sure, we require a mode to always be set without relying on the compiler.
        // So we cheat, because it works and is easier to handle later.
        Ok(State {
            mode: current_mode.unwrap_or_else(|| unsafe { std::mem::zeroed() }),
            connectors: current_connectors,
        })
    }
}

#[derive(Debug)]
pub struct LegacyDrmSurface<A: AsRawFd + 'static> {
    pub(super) fd: Arc<DrmDeviceInternal<A>>,
    pub(super) active: Arc<AtomicBool>,
    crtc: crtc::Handle,
    state: RwLock<State>,
    pending: RwLock<State>,
    pub(crate) logger: ::slog::Logger,
}

impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
    pub fn new(
        fd: Arc<DrmDeviceInternal<A>>,
        active: Arc<AtomicBool>,
        crtc: crtc::Handle,
        mode: Mode,
        connectors: &[connector::Handle],
        logger: ::slog::Logger,
    ) -> Result<Self, Error> {
        let logger = logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "surface"));
        info!(
            logger,
            "Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors
        );

        let state = State::current_state(&*fd, crtc)?;
        let pending = State {
            mode,
            connectors: connectors.iter().copied().collect(),
        };

        let surface = LegacyDrmSurface {
            fd,
            active,
            crtc,
            state: RwLock::new(state),
            pending: RwLock::new(pending),
            logger,
        };

        Ok(surface)
    }

    pub fn current_connectors(&self) -> HashSet<connector::Handle> {
        self.state.read().unwrap().connectors.clone()
    }

    pub fn pending_connectors(&self) -> HashSet<connector::Handle> {
        self.pending.read().unwrap().connectors.clone()
    }

    pub fn current_mode(&self) -> Mode {
        self.state.read().unwrap().mode
    }

    pub fn pending_mode(&self) -> Mode {
        self.pending.read().unwrap().mode
    }

    pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        if self.check_connector(conn, &pending.mode)? {
            pending.connectors.insert(conn);
        }

        Ok(())
    }

    pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
        let mut pending = self.pending.write().unwrap();

        if pending.connectors.contains(&connector) && pending.connectors.len() == 1 {
            return Err(Error::SurfaceWithoutConnectors(self.crtc));
        }

        pending.connectors.remove(&connector);
        Ok(())
    }

    pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
        if connectors.is_empty() {
            return Err(Error::SurfaceWithoutConnectors(self.crtc));
        }

        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        if connectors
            .iter()
            .map(|conn| self.check_connector(*conn, &pending.mode))
            .collect::<Result<Vec<bool>, _>>()?
            .iter()
            .all(|v| *v)
        {
            pending.connectors = connectors.iter().cloned().collect();
        }

        Ok(())
    }

    pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        // check the connectors to see if this mode is supported
        for connector in &pending.connectors {
            if !self
                .fd
                .get_connector(*connector, false)
                .map_err(|source| Error::Access {
                    errmsg: "Error loading connector info",
                    dev: self.fd.dev_path(),
                    source,
                })?
                .modes()
                .contains(&mode)
            {
                return Err(Error::ModeNotSuitable(mode));
            }
        }

        pending.mode = mode;

        Ok(())
    }

    pub fn commit_pending(&self) -> bool {
        *self.pending.read().unwrap() != *self.state.read().unwrap()
    }

    pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut current = self.state.write().unwrap();
        let pending = self.pending.read().unwrap();

        {
            let removed = current.connectors.difference(&pending.connectors);
            let added = pending.connectors.difference(&current.connectors);

            let mut conn_removed = false;
            for conn in removed.clone() {
                if let Ok(info) = self.fd.get_connector(*conn, false) {
                    info!(self.logger, "Removing connector: {:?}", info.interface());
                } else {
                    info!(self.logger, "Removing unknown connector");
                }
                // if the connector was mapped to our crtc, we need to ack the disconnect.
                // the graphics pipeline will not be freed otherwise
                conn_removed = true;
            }
            set_connector_state(&*self.fd, removed.copied(), false)?;

            if conn_removed {
                // null commit (necessary to trigger removal on the kernel side with the legacy api.)
                self.fd
                    .set_crtc(self.crtc, None, (0, 0), &[], None)
                    .map_err(|source| Error::Access {
                        errmsg: "Error setting crtc",
                        dev: self.fd.dev_path(),
                        source,
                    })?;
            }

            for conn in added.clone() {
                if let Ok(info) = self.fd.get_connector(*conn, false) {
                    info!(self.logger, "Adding connector: {:?}", info.interface());
                } else {
                    info!(self.logger, "Adding unknown connector");
                }
            }
            set_connector_state(&*self.fd, added.copied(), true)?;

            if current.mode != pending.mode {
                info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
            }
        }

        debug!(self.logger, "Setting screen");
        // do a modeset and attach the given framebuffer
        self.fd
            .set_crtc(
                self.crtc,
                Some(framebuffer),
                (0, 0),
                &pending
                    .connectors
                    .iter()
                    .copied()
                    .collect::<Vec<connector::Handle>>(),
                Some(pending.mode),
            )
            .map_err(|source| Error::Access {
                errmsg: "Error setting crtc",
                dev: self.fd.dev_path(),
                source,
            })?;

        *current = pending.clone();

        if event {
            // set crtc does not trigger page_flip events, so we immediately queue a flip
            // with the same framebuffer.
            // this will result in wasting a frame, because this flip will need to wait
            // for `set_crtc`, but is necessary to drive the event loop and thus provide
            // a more consistent api.
            ControlDevice::page_flip(
                &*self.fd,
                self.crtc,
                framebuffer,
                &[PageFlipFlags::PageFlipEvent],
                None,
            )
            .map_err(|source| Error::Access {
                errmsg: "Failed to queue page flip",
                dev: self.fd.dev_path(),
                source,
            })?;
        }

        Ok(())
    }

    pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
        trace!(self.logger, "Queueing Page flip");

        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        ControlDevice::page_flip(
            &*self.fd,
            self.crtc,
            framebuffer,
            if event {
                &[PageFlipFlags::PageFlipEvent]
            } else {
                &[]
            },
            None,
        )
        .map_err(|source| Error::Access {
            errmsg: "Failed to page flip",
            dev: self.fd.dev_path(),
            source,
        })
    }

    pub fn test_buffer(&self, fb: framebuffer::Handle, mode: &Mode) -> Result<bool, Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let pending = self.pending.read().unwrap();

        debug!(self.logger, "Setting screen for buffer *testing*");
        Ok(self
            .fd
            .set_crtc(
                self.crtc,
                Some(fb),
                (0, 0),
                &pending
                    .connectors
                    .iter()
                    .copied()
                    .collect::<Vec<connector::Handle>>(),
                Some(*mode),
            )
            .is_ok())
    }

    // we use this function to verify, if a certain connector/mode combination
    // is valid on our crtc. We do this with the most basic information we have:
    // - is there a matching encoder
    // - does the connector support the provided Mode.
    //
    // Better would be some kind of test commit to ask the driver,
    // but that only exists for the atomic api.
    fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
        let info = self
            .fd
            .get_connector(conn, false)
            .map_err(|source| Error::Access {
                errmsg: "Error loading connector info",
                dev: self.fd.dev_path(),
                source,
            })?;

        // check if the connector can handle the current mode
        if info.modes().contains(mode) {
            // check if there is a valid encoder
            let encoders = info
                .encoders()
                .iter()
                .map(|encoder| {
                    self.fd.get_encoder(*encoder).map_err(|source| Error::Access {
                        errmsg: "Error loading encoder info",
                        dev: self.fd.dev_path(),
                        source,
                    })
                })
                .collect::<Result<Vec<encoder::Info>, _>>()?;

            // and if any encoder supports the selected crtc
            let resource_handles = self.fd.resource_handles().map_err(|source| Error::Access {
                errmsg: "Error loading resources",
                dev: self.fd.dev_path(),
                source,
            })?;
            if !encoders
                .iter()
                .map(|encoder| encoder.possible_crtcs())
                .all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc))
            {
                Ok(false)
            } else {
                Ok(true)
            }
        } else {
            Ok(false)
        }
    }

    pub(crate) fn reset_state<B: AsRawFd + ControlDevice + 'static>(
        &self,
        fd: Option<&B>,
    ) -> Result<(), Error> {
        *self.state.write().unwrap() = if let Some(fd) = fd {
            State::current_state(fd, self.crtc)?
        } else {
            State::current_state(&*self.fd, self.crtc)?
        };
        Ok(())
    }
}

impl<A: AsRawFd + 'static> Drop for LegacyDrmSurface<A> {
    fn drop(&mut self) {
        // ignore failure at this point

        if !self.active.load(Ordering::SeqCst) {
            // the device is gone or we are on another tty
            // old state has been restored, we shouldn't touch it.
            // if we are on another tty the connectors will get disabled
            // by the device, when switching back
            return;
        }

        // disable connectors again
        let current = self.state.read().unwrap();
        if set_connector_state(&*self.fd, current.connectors.iter().copied(), false).is_ok() {
            // null commit
            let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None);
        }
    }
}

#[cfg(test)]
mod test {
    use super::LegacyDrmSurface;
    use std::fs::File;

    fn is_send<S: Send>() {}

    #[test]
    fn surface_is_send() {
        is_send::<LegacyDrmSurface<File>>();
    }
}

<MSG> deps: Update drm-rs and gbm-rs

<DFF> @@ -289,18 +289,13 @@ impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
             // this will result in wasting a frame, because this flip will need to wait
             // for `set_crtc`, but is necessary to drive the event loop and thus provide
             // a more consistent api.
-            ControlDevice::page_flip(
-                &*self.fd,
-                self.crtc,
-                framebuffer,
-                &[PageFlipFlags::PageFlipEvent],
-                None,
-            )
-            .map_err(|source| Error::Access {
-                errmsg: "Failed to queue page flip",
-                dev: self.fd.dev_path(),
-                source,
-            })?;
+            ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err(
+                |source| Error::Access {
+                    errmsg: "Failed to queue page flip",
+                    dev: self.fd.dev_path(),
+                    source,
+                },
+            )?;
         }
 
         Ok(())
@@ -318,9 +313,9 @@ impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
             self.crtc,
             framebuffer,
             if event {
-                &[PageFlipFlags::PageFlipEvent]
+                PageFlipFlags::EVENT
             } else {
-                &[]
+                PageFlipFlags::empty()
             },
             None,
         )

deps: Update drm-rs and gbm-rs