Skip to main content

libobs_wrapper\display/
mod.rs

1//! This module is used to create a `OBS display` which you can use to preview the
2//! output of your recording.
3
4mod creation_data;
5mod enums;
6mod window_manager;
7
8pub use window_manager::{MiscDisplayTrait, ShowHideTrait, WindowPositionTrait};
9
10pub use creation_data::*;
11pub use enums::*;
12use libobs::obs_video_info;
13
14use crate::unsafe_send::SmartPointerSendable;
15use crate::utils::{ObsDropGuard, ObsError};
16use crate::{impl_obs_drop, run_with_obs, runtime::ObsRuntime, unsafe_send::Sendable};
17use std::mem::MaybeUninit;
18use std::{
19    ffi::c_void,
20    marker::PhantomPinned,
21    sync::{atomic::AtomicUsize, Arc, RwLock},
22};
23
24static ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
25#[derive(Debug, Clone)]
26/// You can use the `ObsContext` to create this struct. This struct is stored in the
27/// `ObsContext` itself and the display is removed if every instance of this struct is dropped
28/// (and you have called `remove_display` on the `ObsContext`).
29pub struct ObsDisplayRef {
30    id: usize,
31    render_state: Arc<DisplayRenderState>,
32
33    /// Keep for window, manager is accessed by render thread as well so Arc and RwLock
34    ///
35    /// This is mostly used on windows to handle the size and position of the child window.
36    #[cfg(windows)]
37    #[allow(dead_code)]
38    child_window_handler:
39        Option<Arc<RwLock<window_manager::windows::WindowsPreviewChildWindowHandler>>>,
40
41    /// Stored so the obs context is not dropped while this is alive
42    runtime: ObsRuntime,
43    display: SmartPointerSendable<*mut libobs::obs_display_t>,
44}
45
46#[derive(Debug)]
47pub(super) struct DisplayRenderState {
48    pub(super) position: RwLock<(i32, i32)>,
49}
50
51#[allow(unknown_lints)]
52#[allow(ensure_obs_call_in_runtime)]
53/// # Safety
54/// Always call this function in the graphics/rendering thread of OBS, never call this function directly!
55unsafe extern "C" fn render_display(data: *mut c_void, width: u32, height: u32) {
56    let Some(state) = (data as *const DisplayRenderState).as_ref() else {
57        log::error!("Display render callback received null userdata");
58        return;
59    };
60    let pos = *state
61        .position
62        .read()
63        .unwrap_or_else(|poisoned| poisoned.into_inner());
64
65    let mut ovi = MaybeUninit::<obs_video_info>::uninit();
66    let was_ok = libobs::obs_get_video_info(ovi.as_mut_ptr());
67    if !was_ok {
68        log::error!("Failed to get video info in display render callback");
69        return;
70    }
71
72    let ovi = unsafe {
73        // Safety: was_ok checked that the video info was properly initialized
74        ovi.assume_init()
75    };
76
77    libobs::gs_viewport_push();
78    libobs::gs_projection_push();
79
80    libobs::gs_ortho(
81        0.0f32,
82        ovi.base_width as f32,
83        0.0f32,
84        ovi.base_height as f32,
85        -100.0f32,
86        100.0f32,
87    );
88    libobs::gs_set_viewport(pos.0, pos.1, width as i32, height as i32);
89    //draw_backdrop(&s.buffers, ovi.base_width as f32, ovi.base_height as f32);
90
91    libobs::obs_render_main_texture_src_color_only();
92
93    libobs::gs_projection_pop();
94    libobs::gs_viewport_pop();
95}
96
97pub struct LockedPosition {
98    pub x: i32,
99    pub y: i32,
100    /// This must not be moved in memory as the draw callback is a raw pointer to this struct
101    _fixed_in_heap: PhantomPinned,
102}
103
104#[derive(Clone, Debug)]
105pub struct ObsWindowHandle {
106    pub(crate) window: Sendable<libobs::gs_window>,
107    #[allow(dead_code)]
108    pub(crate) is_wayland: bool,
109}
110
111impl ObsWindowHandle {
112    /// Construct from an HWND supplied by an external windowing toolkit.
113    ///
114    /// # Safety
115    /// `handle` must be a live HWND for the complete lifetime of any OBS display created
116    /// from this value, and all toolkit thread-affinity requirements must be respected.
117    #[cfg(windows)]
118    pub unsafe fn new_from_handle(handle: *mut std::os::raw::c_void) -> Self {
119        Self {
120            window: Sendable(libobs::gs_window { hwnd: handle }),
121            is_wayland: false,
122        }
123    }
124
125    #[cfg(windows)]
126    pub fn get_hwnd(&self) -> windows::Win32::Foundation::HWND {
127        windows::Win32::Foundation::HWND(self.window.0.hwnd)
128    }
129
130    /// Construct from a Wayland surface supplied by an external windowing toolkit.
131    ///
132    /// # Safety
133    /// `surface` must be a live `wl_surface` belonging to the configured Wayland display
134    /// and must outlive any OBS display created from this handle.
135    #[cfg(target_os = "linux")]
136    pub unsafe fn new_from_wayland(surface: *mut c_void) -> Self {
137        Self {
138            window: Sendable(libobs::gs_window {
139                display: surface,
140                id: 0,
141            }),
142            is_wayland: true,
143        }
144    }
145
146    #[cfg(target_os = "linux")]
147    pub fn new_from_x11(runtime: &ObsRuntime, id: u32) -> Result<Self, ObsError> {
148        let runtime = runtime.clone();
149        let display = run_with_obs!(runtime, (), move || unsafe {
150            // Safety: We are just getting a pointer and we are in the runtime
151            Sendable(libobs::obs_get_nix_platform_display())
152        })?;
153
154        Ok(Self {
155            window: Sendable(libobs::gs_window {
156                display: display.0,
157                id,
158            }),
159            is_wayland: false,
160        })
161    }
162}
163
164impl ObsDisplayRef {
165    /// Call initialize to ObsDisplay#create the display
166    pub(crate) fn new(data: ObsDisplayCreationData, runtime: ObsRuntime) -> Result<Self, ObsError> {
167        use std::sync::atomic::Ordering;
168
169        use creation_data::ObsDisplayCreationData;
170
171        use crate::run_with_obs;
172
173        let ObsDisplayCreationData {
174            x,
175            y,
176            background_color,
177            create_child,
178            #[cfg(windows)]
179            height,
180            #[cfg(windows)]
181            width,
182            #[cfg(windows)]
183            window_handle,
184            ..
185        } = data.clone();
186
187        #[cfg(windows)]
188        let mut child_handler = if create_child {
189            Some(
190                window_manager::windows::WindowsPreviewChildWindowHandler::new_child(
191                    window_handle.clone(),
192                    x,
193                    y,
194                    width,
195                    height,
196                )?,
197            )
198        } else {
199            None
200        };
201
202        #[cfg(windows)]
203        let init_data = Sendable(data.build(child_handler.as_ref().map(|e| e.get_window_handle())));
204
205        #[cfg(not(windows))]
206        let init_data = Sendable(data.build(None));
207
208        log::trace!("Creating obs display...");
209        let display = run_with_obs!(runtime, (init_data), move || {
210            let display_ptr = unsafe {
211                // Safety: All pointers are valid because we are keeping them in this scope and because we are cloning init_data into this scope
212                libobs::obs_display_create(&init_data.0 .0, background_color)
213            };
214
215            if display_ptr.is_null() {
216                Err(ObsError::NullPointer(None))
217            } else {
218                Ok(Sendable(display_ptr))
219            }
220        })??;
221
222        let initial_pos = if create_child && cfg!(windows) {
223            (0, 0)
224        } else {
225            (x, y)
226        };
227        let render_state = Arc::new(DisplayRenderState {
228            position: RwLock::new(initial_pos),
229        });
230
231        let display = SmartPointerSendable::new(
232            display.0,
233            Arc::new(_ObsDisplayDropGuard {
234                display,
235                render_state: render_state.clone(),
236                runtime: runtime.clone(),
237            }),
238            runtime.native_registry(),
239        );
240
241        #[cfg(windows)]
242        if let Some(handler) = &mut child_handler {
243            handler.set_display_handle(display.clone());
244        }
245
246        let id = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
247        let instance = Self {
248            display: display.clone(),
249            id,
250            render_state: render_state.clone(),
251            runtime: runtime.clone(),
252
253            #[cfg(windows)]
254            child_window_handler: child_handler.map(|e| Arc::new(RwLock::new(e))),
255        };
256
257        log::trace!("Adding draw callback with display {:?}", instance.display);
258
259        let display_ptr = instance.as_ptr();
260        run_with_obs!(runtime, (display_ptr), move || {
261            unsafe {
262                // Safety: The pointer is valid because we are using a smart pointer
263                libobs::obs_display_add_draw_callback(
264                    display_ptr.get_ptr(),
265                    Some(render_display),
266                    Arc::as_ptr(&render_state) as *mut c_void,
267                );
268            }
269        })?;
270
271        Ok(instance)
272    }
273
274    pub fn id(&self) -> usize {
275        self.id
276    }
277
278    pub fn update_color_space(&self) -> Result<(), ObsError> {
279        let display_ptr = self.as_ptr();
280        run_with_obs!(self.runtime, (display_ptr), move || {
281            unsafe {
282                // Safety: The pointer is valid because we are using a smart pointer
283                libobs::obs_display_update_color_space(display_ptr.get_ptr())
284            }
285        })
286    }
287
288    pub(crate) fn as_ptr(&self) -> SmartPointerSendable<*mut libobs::obs_display_t> {
289        self.display.clone()
290    }
291}
292
293#[derive(Debug)]
294struct _ObsDisplayDropGuard {
295    display: Sendable<*mut libobs::obs_display_t>,
296    render_state: Arc<DisplayRenderState>,
297    runtime: ObsRuntime,
298}
299
300impl ObsDropGuard for _ObsDisplayDropGuard {}
301
302impl_obs_drop!(
303    _ObsDisplayDropGuard,
304    (display, render_state),
305    move || unsafe {
306        // SAFETY: `render_state` remains alive through callback deregistration and display
307        // destruction, and the native pointer is leased by this drop guard.
308        log::trace!("Removing callback of display {:?}...", display);
309        libobs::obs_display_remove_draw_callback(
310            display.0,
311            Some(render_display),
312            Arc::as_ptr(&render_state) as *mut c_void,
313        );
314        libobs::obs_display_destroy(display.0);
315    }
316);