smithay/backend/egl/
device.rs

1use std::{ffi::CStr, os::raw::c_void, path::PathBuf, ptr};
2
3use super::{
4    ffi::{self, egl::types::EGLDeviceEXT},
5    wrap_egl_call_bool, wrap_egl_call_ptr, EGLDisplay, EGLError, Error,
6};
7
8#[cfg(feature = "backend_drm")]
9use crate::backend::drm::{DrmNode, NodeType};
10
11/// safe EGLDevice wrapper
12#[derive(Debug, Clone)]
13pub struct EGLDevice {
14    pub(super) inner: EGLDeviceEXT,
15    device_extensions: Vec<String>,
16}
17
18unsafe impl Send for EGLDevice {}
19
20impl EGLDevice {
21    /// Returns an iterator which enumerates over the available [`EGLDevices`](EGLDevice) on the system.
22    ///
23    /// This function will return an error if the following extensions are not available:
24    /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
25    /// - [`EGL_EXT_device_enumeration`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_enumeration.txt)
26    /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
27    pub fn enumerate() -> Result<impl Iterator<Item = EGLDevice>, Error> {
28        // Check the required extensions are present:
29        let extensions = ffi::make_sure_egl_is_loaded()?;
30
31        if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
32            return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
33        }
34
35        if !extensions.iter().any(|s| s == "EGL_EXT_device_enumeration") {
36            return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_enumeration"]));
37        }
38
39        if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
40            return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
41        }
42
43        // Yes, this is marked as `mut` even though the value is never mutated. EGL expects a mutable pointer
44        // for num_devices and will not modify the value if we are asking for pointers to some EGLDeviceEXT.
45        let mut device_amount = 0;
46        wrap_egl_call_bool(|| unsafe {
47            // Passing 0 for max devices and a null-pointer for devices is safe because we indicate we only
48            // want the number of devices.
49            ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut device_amount)
50        })
51        .map_err(Error::QueryDevices)?;
52
53        let mut devices = Vec::with_capacity(device_amount as usize);
54
55        wrap_egl_call_bool(|| unsafe {
56            // SAFETY:
57            // - Vector used as pointer is correct size.
58            // - Device amount will accommodate all available devices because we have checked the size earlier.
59            ffi::egl::QueryDevicesEXT(device_amount, devices.as_mut_ptr(), &mut device_amount)
60        })
61        .map_err(Error::QueryDevices)?;
62
63        // Set the length of the vec so that rust does not think it is still empty.
64
65        // SAFETY:
66        // 1) the vector is pre-allocated to the same size as the amount of returned devices.
67        // 2) EGL has initialized every value in the vector.
68        unsafe { devices.set_len(device_amount as usize) };
69
70        Ok(devices
71            .into_iter()
72            .map(|device| {
73                // SAFETY: We have queried that the extensions are valid and the device pointer is valid.
74                let device_extensions = unsafe { device_extensions(device) }?;
75                Ok(EGLDevice {
76                    inner: device,
77                    device_extensions,
78                })
79            })
80            .collect::<Result<Vec<_>, EGLError>>()
81            .map_err(Error::QueryDevices)?
82            .into_iter())
83    }
84
85    /// Returns the [`EGLDevices`](EGLDevice) related to the given `EGLDisplay`.
86    ///
87    /// This function will return an error if the following extensions are not available:
88    /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
89    /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
90    pub fn device_for_display(display: &EGLDisplay) -> Result<EGLDevice, Error> {
91        // Check the required extensions are present:
92        let extensions = ffi::make_sure_egl_is_loaded()?;
93
94        if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
95            return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
96        }
97
98        if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
99            return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
100        }
101
102        let mut device: ffi::egl::types::EGLAttrib = 0;
103        if unsafe {
104            ffi::egl::QueryDisplayAttribEXT(
105                display.get_display_handle().handle,
106                ffi::egl::DEVICE_EXT as i32,
107                &mut device as *mut _,
108            )
109        } != ffi::egl::TRUE
110        {
111            return Err(Error::DisplayNotSupported);
112        }
113
114        let device = device as EGLDeviceEXT;
115
116        // Per the EGL specification:
117        //
118        // > Functions with a return type of EGLDeviceEXT will return this value on failure: EGL_NO_DEVICE_EXT
119        if device == ffi::egl::NO_DEVICE_EXT {
120            return Err(Error::DisplayNotSupported);
121        }
122
123        // SAFETY: We have queried that the extensions are valid and the device pointer is valid.
124        let device_extensions = unsafe { device_extensions(device) }.map_err(Error::QueryDevices)?;
125        Ok(EGLDevice {
126            inner: device,
127            device_extensions,
128        })
129    }
130
131    /// Returns a list of extensions the device supports.
132    pub fn extensions(&self) -> Vec<String> {
133        self.device_extensions.clone()
134    }
135
136    /// Returns the path to the drm node of this EGLDevice.
137    ///
138    /// This function will return an error if the following extensions are not available:
139    /// - [`EGL_EXT_device_drm`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt)
140    pub fn drm_device_path(&self) -> Result<PathBuf, Error> {
141        if !self.extensions().contains(&"EGL_EXT_device_drm".to_owned()) {
142            Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_drm"]))
143        } else {
144            let raw_path = wrap_egl_call_ptr(|| unsafe {
145                ffi::egl::QueryDeviceStringEXT(
146                    self.inner,
147                    ffi::egl::DRM_DEVICE_FILE_EXT as ffi::egl::types::EGLint,
148                )
149            })
150            .map_err(Error::QueryDeviceProperty)?;
151            if raw_path.is_null() {
152                return Err(Error::EmptyDeviceProperty);
153            }
154
155            // This is safe because of the following:
156            // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
157            //    as the EGLDisplay is valid. Since the pointer is only used in this function, the
158            //    lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
159            // 2) The string returned by EGL is null terminated.
160            let device_path = unsafe { CStr::from_ptr(raw_path) }
161                .to_str()
162                // EGL ensures the string is valid UTF-8
163                .expect("Non-UTF8 device path name");
164
165            Ok(PathBuf::from(device_path))
166        }
167    }
168
169    /// Returns the path to the render node of this EGLDevice.
170    ///
171    /// This function will return an error if the following extensions are not available:
172    /// - [`EGL_EXT_device_drm_render_node`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt)
173    pub fn render_device_path(&self) -> Result<PathBuf, Error> {
174        if !self
175            .extensions()
176            .contains(&"EGL_EXT_device_drm_render_node".to_owned())
177        {
178            Err(Error::EglExtensionNotSupported(&[
179                "EGL_EXT_device_drm_render_node",
180            ]))
181        } else {
182            let raw_path = wrap_egl_call_ptr(|| unsafe {
183                ffi::egl::QueryDeviceStringEXT(
184                    self.inner,
185                    ffi::egl::DRM_RENDER_NODE_FILE_EXT as ffi::egl::types::EGLint,
186                )
187            })
188            .map_err(Error::QueryDeviceProperty)?;
189            if raw_path.is_null() {
190                return Err(Error::EmptyDeviceProperty);
191            }
192
193            // This is safe because of the following:
194            // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
195            //    as the EGLDisplay is valid. Since the pointer is only used in this function, the
196            //    lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
197            // 2) The string returned by EGL is null terminated.
198            let device_path = unsafe { CStr::from_ptr(raw_path) }
199                .to_str()
200                // EGL ensures the string is valid UTF-8
201                .expect("Non-UTF8 device path name");
202
203            Ok(PathBuf::from(device_path))
204        }
205    }
206
207    /// Returns the drm node beloging to this device.
208    /// Tries to optain a render_node first through `EGL_EXT_device_drm_render_node`
209    /// (see also [`EGLDevice::render_device_path`]) and then falls back to
210    /// get a render_node from `EGL_EXT_device_drm` (see also [`EGLDevice::drm_device_path`]).
211    /// If both fail to produce a render node, whichever device returned by
212    /// `EGL_EXT_device_drm` is returned.
213    #[cfg(feature = "backend_drm")]
214    pub fn try_get_render_node(&self) -> Result<Option<DrmNode>, Error> {
215        // first lets try to get a render_node directly
216        match self
217            .render_device_path()
218            .ok()
219            .and_then(|path| DrmNode::from_path(path).ok())
220        {
221            Some(node) => Ok(Some(node)),
222            // else we take a drm_path
223            None => {
224                let path = self.drm_device_path()?;
225                let node = DrmNode::from_path(path).ok();
226                // and try to convert it to a render_node
227                Ok(node.map(|node| {
228                    node.node_with_type(NodeType::Render)
229                        .and_then(Result::ok)
230                        // and otherwise go with whatever we got initially
231                        .unwrap_or(node)
232                }))
233            }
234        }
235    }
236
237    /// Queries if device is not backed by any actual device node and simply renders into client memory.
238    ///
239    /// Note: This simply tests presence of the [`EGL_MESA_device_software`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/docs/_extra/specs/EGL_MESA_device_software.txt) extension.
240    pub fn is_software(&self) -> bool {
241        // Note: EGL_MESA_device_software requires EGL_EXT_device_query which we already test for initializing the device,
242        // so there is no need to re-test it here again.
243        self.extensions().contains(&"EGL_MESA_device_software".to_owned())
244    }
245
246    /// Returns the pointer to the raw [`EGLDevice`].
247    ///
248    /// The pointer will become invalid, when this struct is destroyed.
249    pub fn get_device_handle(&self) -> *const c_void {
250        self.inner
251    }
252}
253
254/// Returns all device extensions a device supports.
255///
256/// # Safety
257///
258/// - The `device` must be a valid pointer to an `EGLDeviceEXT`.
259/// - The following extensions must be supported by the display which provides the device:
260///     - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
261///     - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
262unsafe fn device_extensions(device: EGLDeviceEXT) -> Result<Vec<String>, EGLError> {
263    let raw_extensions = wrap_egl_call_ptr(|| {
264        ffi::egl::QueryDeviceStringEXT(device, ffi::egl::EXTENSIONS as ffi::egl::types::EGLint)
265    })?;
266
267    // SAFETY:
268    // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
269    //    as the EGLDisplay is valid. Safety requirements for the function ensure this.
270    // 2) The string returned by EGL is null terminated.
271    let c_extensions = CStr::from_ptr(raw_extensions);
272
273    Ok(c_extensions
274        .to_str()
275        // EGL ensures the string is valid UTF-8
276        .expect("Non-UTF8 device extension name")
277        // Each extension is space separated (0x20) in the pointer, so strlen cannot return an improper length.
278        .split_whitespace()
279        // Take an owned copy so we do not point to garbage if EGL somehow vanishes.
280        .map(ToOwned::to_owned)
281        .collect::<Vec<_>>())
282}