1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![warn(missing_docs, missing_debug_implementations)]
#![forbid(improper_ctypes, unsafe_op_in_unsafe_fn)]

//! EGL utilities
//!
//! This module contains bindings to the `libwayland-egl.so` library.
//!
//! This library is used to interface with the OpenGL stack, and creating
//! EGL surfaces from a wayland surface.
//!
//! See [`WlEglSurface`] documentation for details.

use std::{fmt, os::raw::c_void};

use wayland_backend::client::ObjectId;
use wayland_sys::{client::wl_proxy, egl::*, ffi_dispatch};

/// Checks if the wayland-egl lib is available and can be used
///
/// Trying to create an [`WlEglSurface`] while this function returns
/// `false` will result in a panic.
pub fn is_available() -> bool {
    is_lib_available()
}

/// EGL surface
///
/// This object is a simple wrapper around a `wl_surface` to add the EGL
/// capabilities. Just use the [`ptr()`](WlEglSurface::ptr) method once this object
/// is created to get the window pointer your OpenGL library is needing to initialize
/// the EGL context (you'll most likely need the display ptr as well, that you can
/// get via the [`ObjectId::as_ptr()`] method on of the `wl_display` ID).
#[derive(Debug)]
pub struct WlEglSurface {
    ptr: *mut wl_egl_window,
}

impl WlEglSurface {
    /// Create an EGL surface from a wayland surface
    ///
    /// This method will check that the provided `ObjectId` is still alive and from the
    /// correct interface (`wl_surface`).
    ///
    /// You must always destroy the [`WlEglSurface`] *before* the underling `wl_surface`
    /// protocol object.
    pub fn new(surface: ObjectId, width: i32, height: i32) -> Result<Self, Error> {
        if surface.interface().name != "wl_surface" {
            return Err(Error::InvalidId);
        }

        let ptr = surface.as_ptr();
        if ptr.is_null() {
            // ObjectId::as_ptr() returns NULL if the surface is no longer alive
            Err(Error::InvalidId)
        } else {
            // SAFETY: We are sure the pointer is valid and the interface is correct.
            unsafe { Self::new_from_raw(ptr, width, height) }
        }
    }

    /// Create an EGL surface from a raw pointer to a wayland surface.
    ///
    /// # Safety
    ///
    /// The provided pointer must be a valid `wl_surface` pointer from `libwayland-client`.
    pub unsafe fn new_from_raw(
        surface: *mut wl_proxy,
        width: i32,
        height: i32,
    ) -> Result<Self, Error> {
        if width <= 0 || height <= 0 {
            return Err(Error::InvalidSize);
        }
        let ptr = ffi_dispatch!(wayland_egl_handle(), wl_egl_window_create, surface, width, height);
        if ptr.is_null() {
            panic!("egl window allocation failed");
        }
        Ok(Self { ptr })
    }

    /// Fetch current size of the EGL surface
    pub fn get_size(&self) -> (i32, i32) {
        let mut w = 0i32;
        let mut h = 0i32;
        unsafe {
            ffi_dispatch!(
                wayland_egl_handle(),
                wl_egl_window_get_attached_size,
                self.ptr,
                &mut w as *mut i32,
                &mut h as *mut i32
            );
        }
        (w, h)
    }

    /// Resize the EGL surface
    ///
    /// The two first arguments `(width, height)` are the new size of
    /// the surface, the two others `(dx, dy)` represent the displacement
    /// of the top-left corner of the surface. It allows you to control the
    /// direction of the resizing if necessary.
    pub fn resize(&self, width: i32, height: i32, dx: i32, dy: i32) {
        unsafe {
            ffi_dispatch!(
                wayland_egl_handle(),
                wl_egl_window_resize,
                self.ptr,
                width,
                height,
                dx,
                dy
            )
        }
    }

    /// Raw pointer to the EGL surface
    ///
    /// You'll need this pointer to initialize the EGL context in your
    /// favourite OpenGL lib.
    pub fn ptr(&self) -> *const c_void {
        self.ptr as *const c_void
    }
}

// SAFETY: We own the pointer to the wl_egl_window and can therefore be transferred to another thread.
unsafe impl Send for WlEglSurface {}
// Note that WlEglSurface is !Sync. This is because the pointer performs no internal synchronization.

impl Drop for WlEglSurface {
    fn drop(&mut self) {
        unsafe {
            ffi_dispatch!(wayland_egl_handle(), wl_egl_window_destroy, self.ptr);
        }
    }
}

/// EGL surface creation error.
#[derive(Debug)]
pub enum Error {
    /// Surface width or height are <= 0.
    InvalidSize,
    /// Passed surface object is not a surface.
    InvalidId,
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::InvalidSize => write!(f, "surface width or height is <= 0"),
            Error::InvalidId => write!(f, "object id is not a surface"),
        }
    }
}