wayrs_utils/
dmabuf_feedback.rs

1//! linux-dmabuf feedback event helper
2//!
3//! See <https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/linux-dmabuf/feedback.rst>
4//! to learn what dmabuf feedback is and how it is used.
5//!
6//! To use this helper, implement [`DmabufFeedbackHandler`] for your state and create an
7//! instance/instances of [`DmabufFeedback`]. When the feedback is received or updated, you will be
8//! notified via [`DmabufFeedbackHandler::feedback_done`] callback.
9
10use libc::dev_t;
11use std::fmt;
12
13use wayrs_client::protocol::WlSurface;
14use wayrs_client::{Connection, EventCtx};
15use wayrs_protocols::linux_dmabuf_v1::*;
16
17#[derive(Debug)]
18pub struct DmabufFeedback {
19    wl: ZwpLinuxDmabufFeedbackV1,
20    main_device: Option<dev_t>,
21    format_table: Option<memmap2::Mmap>,
22    tranches: Vec<DmabufTranche>,
23    pending_tranche: DmabufTranche,
24    tranches_done: bool,
25}
26
27#[derive(Debug, Default)]
28pub struct DmabufTranche {
29    pub target_device: Option<dev_t>,
30    pub formats: Option<Vec<u16>>,
31    pub flags: zwp_linux_dmabuf_feedback_v1::TrancheFlags,
32}
33
34#[derive(Clone, Copy, Default)]
35#[repr(C)]
36pub struct FormatTableEntry {
37    pub fourcc: u32,
38    _padding: u32,
39    pub modifier: u64,
40}
41
42pub trait DmabufFeedbackHandler: Sized + 'static {
43    /// Get a reference to a [`DmabufFeedback`] associated with `wl`.
44    ///
45    /// Returning a reference to a wrong object may cause [`Connection::dispatch_events`] to panic.
46    fn get_dmabuf_feedback(&mut self, wl: ZwpLinuxDmabufFeedbackV1) -> &mut DmabufFeedback;
47
48    /// A feedback for `wl` is received/updated.
49    fn feedback_done(&mut self, conn: &mut Connection<Self>, wl: ZwpLinuxDmabufFeedbackV1);
50}
51
52impl DmabufFeedback {
53    pub fn get_default<D: DmabufFeedbackHandler>(
54        conn: &mut Connection<D>,
55        linux_dmabuf: ZwpLinuxDmabufV1,
56    ) -> Self {
57        Self {
58            wl: linux_dmabuf.get_default_feedback_with_cb(conn, dmabuf_feedback_cb),
59            main_device: None,
60            format_table: None,
61            tranches: Vec::new(),
62            pending_tranche: DmabufTranche::default(),
63            tranches_done: false,
64        }
65    }
66
67    pub fn get_for_surface<D: DmabufFeedbackHandler>(
68        conn: &mut Connection<D>,
69        linux_dmabuf: ZwpLinuxDmabufV1,
70        surface: WlSurface,
71    ) -> Self {
72        Self {
73            wl: linux_dmabuf.get_surface_feedback_with_cb(conn, surface, dmabuf_feedback_cb),
74            main_device: None,
75            format_table: None,
76            tranches: Vec::new(),
77            pending_tranche: DmabufTranche::default(),
78            tranches_done: false,
79        }
80    }
81
82    #[must_use]
83    pub fn wl(&self) -> ZwpLinuxDmabufFeedbackV1 {
84        self.wl
85    }
86
87    #[must_use]
88    pub fn main_device(&self) -> Option<dev_t> {
89        self.main_device
90    }
91
92    #[must_use]
93    pub fn format_table(&self) -> &[FormatTableEntry] {
94        match &self.format_table {
95            Some(mmap) => unsafe {
96                std::slice::from_raw_parts(
97                    mmap.as_ptr().cast(),
98                    mmap.len() / std::mem::size_of::<FormatTableEntry>(),
99                )
100            },
101            None => &[],
102        }
103    }
104
105    #[must_use]
106    pub fn tranches(&self) -> &[DmabufTranche] {
107        &self.tranches
108    }
109
110    pub fn destroy<D>(self, conn: &mut Connection<D>) {
111        self.wl.destroy(conn);
112    }
113}
114
115fn dmabuf_feedback_cb<D: DmabufFeedbackHandler>(ctx: EventCtx<D, ZwpLinuxDmabufFeedbackV1>) {
116    let feedback = ctx.state.get_dmabuf_feedback(ctx.proxy);
117    assert_eq!(
118        feedback.wl, ctx.proxy,
119        "invalid DmabufFeedbackHandler::get_dmabuf_feedback() implementation"
120    );
121
122    use zwp_linux_dmabuf_feedback_v1::Event;
123    match ctx.event {
124        Event::Done => {
125            feedback.tranches_done = true;
126            ctx.state.feedback_done(ctx.conn, ctx.proxy);
127        }
128        Event::FormatTable(args) => {
129            let mmap = unsafe {
130                memmap2::MmapOptions::new()
131                    .len(args.size as usize)
132                    .map_copy_read_only(&args.fd)
133                    .expect("mmap failed")
134            };
135            assert!(
136                mmap.as_ptr().cast::<FormatTableEntry>().is_aligned(),
137                "memory map is not alligned"
138            );
139            feedback.format_table = Some(mmap);
140        }
141        Event::MainDevice(main_dev) => {
142            feedback.main_device = Some(dev_t::from_ne_bytes(
143                main_dev.try_into().expect("invalid main_device size"),
144            ));
145        }
146        Event::TrancheDone => {
147            let tranche = std::mem::take(&mut feedback.pending_tranche);
148            feedback.tranches.push(tranche);
149        }
150        Event::TrancheTargetDevice(target_dev) => {
151            if feedback.tranches_done {
152                feedback.tranches.clear();
153                feedback.tranches_done = false;
154            }
155            feedback.pending_tranche.target_device = Some(dev_t::from_ne_bytes(
156                target_dev
157                    .try_into()
158                    .expect("invalid tranche_target_device size"),
159            ));
160        }
161        Event::TrancheFormats(indices) => {
162            if feedback.tranches_done {
163                feedback.tranches.clear();
164                feedback.tranches_done = false;
165            }
166            // TODO: check alignment and do Vec::into_raw_parts + Vec::from_raw_parts to avoid unnecessary allocation
167            let mut formats = Vec::with_capacity(indices.len() / 2);
168            for index in indices.chunks_exact(2) {
169                let index = u16::from_ne_bytes(index.try_into().unwrap());
170                formats.push(index);
171            }
172            feedback.pending_tranche.formats = Some(formats);
173        }
174        Event::TrancheFlags(flags) => {
175            if feedback.tranches_done {
176                feedback.tranches.clear();
177                feedback.tranches_done = false;
178            }
179            feedback.pending_tranche.flags = flags;
180        }
181        _ => (),
182    }
183}
184
185impl fmt::Debug for FormatTableEntry {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        let [a, b, c, d] = self.fourcc.to_le_bytes();
188        write!(
189            f,
190            "{}{}{}{}:{}",
191            a.escape_ascii(),
192            b.escape_ascii(),
193            c.escape_ascii(),
194            d.escape_ascii(),
195            self.modifier
196        )
197    }
198}