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