Skip to main content

libdrmtap_sys/
lib.rs

1/*
2 * libdrmtap-sys — Raw FFI bindings for libdrmtap
3 * https://github.com/fxd0h/libdrmtap
4 *
5 * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
6 * SPDX-License-Identifier: MIT
7 *
8 * Hand-written FFI bindings matching include/drmtap.h
9 * These are intentionally minimal — use libdrmtap-rs for safe wrappers.
10 */
11
12#![allow(non_camel_case_types)]
13
14use std::os::raw::{c_char, c_int, c_void};
15
16/// Opaque capture context
17pub enum drmtap_ctx {}
18
19/// Configuration for opening a capture context
20#[repr(C)]
21pub struct drmtap_config {
22    pub device_path: *const c_char,
23    pub crtc_id: u32,
24    pub helper_path: *const c_char,
25    pub debug: c_int,
26}
27
28/// A capturable DRM device, as reported by `drmtap_list_devices`. Layout is
29/// frozen: it is written into caller-owned storage, so a future addition must
30/// come as a new accessor rather than a new field here.
31#[repr(C)]
32#[derive(Clone, Copy)]
33pub struct drmtap_device {
34    /// KMS card node, e.g. `/dev/dri/card1`
35    pub path: [c_char; 64],
36    /// Render node, or an empty string if the device has none
37    pub render_node: [c_char; 64],
38    /// Kernel driver, e.g. `i915`, `nvidia-drm`
39    pub driver: [c_char; 32],
40    /// CRTCs actively scanning out on this device
41    pub display_count: u32,
42}
43
44/// Information about a connected display
45#[repr(C)]
46#[derive(Clone, Copy)]
47pub struct drmtap_display {
48    pub crtc_id: u32,
49    pub connector_id: u32,
50    pub name: [c_char; 32],
51    pub x: u32,
52    pub y: u32,
53    pub width: u32,
54    pub height: u32,
55    pub refresh_hz: u32,
56    pub active: c_int,
57}
58
59/// Captured frame data
60#[repr(C)]
61pub struct drmtap_frame_info {
62    pub data: *mut c_void,
63    pub dma_buf_fd: c_int,
64    pub width: u32,
65    pub height: u32,
66    pub stride: u32,
67    pub format: u32,
68    pub modifier: u64,
69    pub fb_id: u32,
70    pub _priv: *mut c_void,
71}
72
73/// Cursor state
74#[repr(C)]
75pub struct drmtap_cursor_info {
76    pub x: i32,
77    pub y: i32,
78    pub hot_x: i32,
79    pub hot_y: i32,
80    pub width: u32,
81    pub height: u32,
82    pub pixels: *mut u32,
83    pub visible: c_int,
84    pub _priv: *mut c_void,
85}
86
87/// Dirty rectangle from frame differencing
88#[repr(C)]
89pub struct drmtap_rect {
90    pub x: u32,
91    pub y: u32,
92    pub w: u32,
93    pub h: u32,
94}
95
96/// Scanout EOTF (from the connector HDR_OUTPUT_METADATA, CTA-861 numbering)
97pub const DRMTAP_EOTF_SDR: u32 = 0;
98pub const DRMTAP_EOTF_PQ: u32 = 2;
99pub const DRMTAP_EOTF_HLG: u32 = 3;
100
101/// Descriptor of an externally-supplied scanout DMA-BUF (split capture):
102/// metadata from the privileged exporter shipped over IPC into
103/// drmtap_convert_dmabuf() on the unprivileged side.
104#[repr(C)]
105pub struct drmtap_dmabuf_desc {
106    pub dma_buf_fd: c_int,
107    pub width: u32,
108    pub height: u32,
109    pub format: u32,
110    pub modifier: u64,
111    pub fb_id: u32,
112    pub num_planes: u32,
113    pub offsets: [u32; 4],
114    pub pitches: [u32; 4],
115    pub hdr_eotf: u32,
116    pub hdr_max_nits: u32,
117}
118
119extern "C" {
120    // Version
121    pub fn drmtap_version() -> c_int;
122
123    // Context lifecycle
124    pub fn drmtap_open(config: *const drmtap_config) -> *mut drmtap_ctx;
125    pub fn drmtap_close(ctx: *mut drmtap_ctx);
126
127    // Split capture (privileged export + unprivileged convert)
128    pub fn drmtap_open_render(render_node: *const c_char) -> *mut drmtap_ctx;
129    /// Enumerate every DRM device with KMS resources. A context is bound to one
130    /// device, so this is how a multi-GPU consumer finds the other cards instead
131    /// of only ever advertising the displays of the first one.
132    pub fn drmtap_list_devices(out: *mut drmtap_device, max_count: c_int) -> c_int;
133    /// Render node of the device backing `ctx`, for handing to
134    /// `drmtap_open_render` on the converting side of a split so it binds to the
135    /// GPU that exported the frame. Owned by `ctx`; NULL if it has none.
136    pub fn drmtap_render_node(ctx: *mut drmtap_ctx) -> *const c_char;
137    pub fn drmtap_grab_desc(
138        ctx: *mut drmtap_ctx,
139        desc: *mut drmtap_dmabuf_desc,
140        frame: *mut drmtap_frame_info,
141    ) -> c_int;
142    pub fn drmtap_convert_dmabuf(
143        ctx: *mut drmtap_ctx,
144        desc: *const drmtap_dmabuf_desc,
145        frame: *mut drmtap_frame_info,
146    ) -> c_int;
147
148    // Display enumeration
149    pub fn drmtap_list_displays(
150        ctx: *mut drmtap_ctx,
151        displays: *mut drmtap_display,
152        max_count: c_int,
153    ) -> c_int;
154    pub fn drmtap_displays_changed(ctx: *mut drmtap_ctx) -> c_int;
155
156    // Frame capture
157    pub fn drmtap_grab(ctx: *mut drmtap_ctx, frame: *mut drmtap_frame_info) -> c_int;
158    pub fn drmtap_grab_mapped(ctx: *mut drmtap_ctx, frame: *mut drmtap_frame_info) -> c_int;
159    pub fn drmtap_frame_release(ctx: *mut drmtap_ctx, frame: *mut drmtap_frame_info);
160
161    /// Point the conversion paths at a caller-owned buffer instead of a
162    /// library-owned one, so a consumer that must end up with the pixels in its own
163    /// memory does not copy the frame.
164    ///
165    /// `dst` must stay valid for as long as it is set AND for as long as any frame
166    /// pointing into it is held; libdrmtap never frees or reallocates it. Pass a
167    /// null `dst` (with any `len`) to go back to the library buffer. A frame needing
168    /// more than `len` bytes is refused with `-ENOSPC` and `dst` is left untouched,
169    /// so a short frame can never be mistaken for a complete one. Size `dst` from a
170    /// real frame's `stride * height`, not `width * height * 4`: the CPU deswizzle
171    /// keeps the source stride, which on a padded scanout is wider than the visible
172    /// width.
173    pub fn drmtap_set_output_buffer(
174        ctx: *mut drmtap_ctx,
175        dst: *mut c_void,
176        len: usize,
177    ) -> c_int;
178
179    // Cursor
180    pub fn drmtap_get_cursor(ctx: *mut drmtap_ctx, cursor: *mut drmtap_cursor_info) -> c_int;
181    pub fn drmtap_cursor_release(ctx: *mut drmtap_ctx, cursor: *mut drmtap_cursor_info);
182
183    // Info
184    pub fn drmtap_error(ctx: *const drmtap_ctx) -> *const c_char;
185    pub fn drmtap_gpu_driver(ctx: *mut drmtap_ctx) -> *const c_char;
186
187    // Pixel conversion
188    pub fn drmtap_deswizzle(
189        src: *const c_void,
190        dst: *mut c_void,
191        width: u32,
192        height: u32,
193        src_stride: u32,
194        dst_stride: u32,
195        modifier: u64,
196        src_size: usize,
197    ) -> c_int;
198    pub fn drmtap_convert_format(
199        src: *const c_void,
200        dst: *mut c_void,
201        width: u32,
202        height: u32,
203        src_stride: u32,
204        dst_stride: u32,
205        src_format: u32,
206        dst_format: u32,
207    ) -> c_int;
208
209    // Frame differencing
210    pub fn drmtap_diff_frames(
211        frame_a: *const c_void,
212        frame_b: *const c_void,
213        width: u32,
214        height: u32,
215        stride: u32,
216        rects_out: *mut drmtap_rect,
217        max_rects: c_int,
218        tile_size: c_int,
219    ) -> c_int;
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_version() {
228        // The C library version (DRMTAP_VERSION_* in csrc/drmtap.h) is kept
229        // equal to this crate's version. Derive the expected packed value from
230        // CARGO_PKG_VERSION so the two can never silently drift apart.
231        let major: i32 = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap();
232        let minor: i32 = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap();
233        let patch: i32 = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap();
234        let expected = (major << 16) | (minor << 8) | patch;
235        let v = unsafe { drmtap_version() };
236        assert_eq!(v, expected);
237    }
238
239    #[test]
240    fn test_config_size() {
241        // Verify struct sizes are reasonable
242        assert!(std::mem::size_of::<drmtap_config>() > 0);
243        assert!(std::mem::size_of::<drmtap_display>() > 0);
244        assert!(std::mem::size_of::<drmtap_frame_info>() > 0);
245        assert!(std::mem::size_of::<drmtap_cursor_info>() > 0);
246    }
247}