obs_wrapper/source/
mod.rs

1#![allow(non_upper_case_globals)]
2
3use paste::item;
4
5pub mod audio;
6pub mod context;
7mod ffi;
8pub mod media;
9pub mod traits;
10pub mod video;
11
12pub use context::*;
13pub use media::*;
14pub use traits::*;
15
16use obs_sys::{
17    obs_filter_get_target, obs_icon_type, obs_icon_type_OBS_ICON_TYPE_AUDIO_INPUT,
18    obs_icon_type_OBS_ICON_TYPE_AUDIO_OUTPUT, obs_icon_type_OBS_ICON_TYPE_BROWSER,
19    obs_icon_type_OBS_ICON_TYPE_CAMERA, obs_icon_type_OBS_ICON_TYPE_COLOR,
20    obs_icon_type_OBS_ICON_TYPE_CUSTOM, obs_icon_type_OBS_ICON_TYPE_DESKTOP_CAPTURE,
21    obs_icon_type_OBS_ICON_TYPE_GAME_CAPTURE, obs_icon_type_OBS_ICON_TYPE_IMAGE,
22    obs_icon_type_OBS_ICON_TYPE_MEDIA, obs_icon_type_OBS_ICON_TYPE_SLIDESHOW,
23    obs_icon_type_OBS_ICON_TYPE_TEXT, obs_icon_type_OBS_ICON_TYPE_UNKNOWN,
24    obs_icon_type_OBS_ICON_TYPE_WINDOW_CAPTURE, obs_mouse_button_type,
25    obs_mouse_button_type_MOUSE_LEFT, obs_mouse_button_type_MOUSE_MIDDLE,
26    obs_mouse_button_type_MOUSE_RIGHT, obs_source_active, obs_source_enabled,
27    obs_source_get_base_height, obs_source_get_base_width, obs_source_get_height,
28    obs_source_get_id, obs_source_get_name, obs_source_get_ref, obs_source_get_type,
29    obs_source_get_width, obs_source_info, obs_source_media_ended, obs_source_media_get_duration,
30    obs_source_media_get_state, obs_source_media_get_time, obs_source_media_next,
31    obs_source_media_play_pause, obs_source_media_previous, obs_source_media_restart,
32    obs_source_media_set_time, obs_source_media_started, obs_source_media_stop,
33    obs_source_process_filter_begin, obs_source_process_filter_end,
34    obs_source_process_filter_tech_end, obs_source_release, obs_source_set_enabled,
35    obs_source_set_name, obs_source_showing, obs_source_skip_video_filter, obs_source_t,
36    obs_source_type, obs_source_type_OBS_SOURCE_TYPE_FILTER, obs_source_type_OBS_SOURCE_TYPE_INPUT,
37    obs_source_type_OBS_SOURCE_TYPE_SCENE, obs_source_type_OBS_SOURCE_TYPE_TRANSITION,
38    obs_source_update, obs_text_type, OBS_SOURCE_AUDIO, OBS_SOURCE_CONTROLLABLE_MEDIA,
39    OBS_SOURCE_INTERACTION, OBS_SOURCE_VIDEO,
40};
41
42use super::{
43    graphics::{
44        GraphicsAllowDirectRendering, GraphicsColorFormat, GraphicsEffect, GraphicsEffectContext,
45    },
46    string::ObsString,
47};
48use crate::{data::DataObj, native_enum, wrapper::PtrWrapper};
49
50use std::{
51    ffi::{CStr, CString},
52    marker::PhantomData,
53};
54
55native_enum!(MouseButton, obs_mouse_button_type {
56    Left => MOUSE_LEFT,
57    Middle => MOUSE_MIDDLE,
58    Right => MOUSE_RIGHT
59});
60
61native_enum!(Icon, obs_icon_type {
62    Unknown => OBS_ICON_TYPE_UNKNOWN,
63    Image => OBS_ICON_TYPE_IMAGE,
64    Color => OBS_ICON_TYPE_COLOR,
65    Slideshow => OBS_ICON_TYPE_SLIDESHOW,
66    AudioInput => OBS_ICON_TYPE_AUDIO_INPUT,
67    AudioOutput => OBS_ICON_TYPE_AUDIO_OUTPUT,
68    DesktopCapture => OBS_ICON_TYPE_DESKTOP_CAPTURE,
69    WindowCapture => OBS_ICON_TYPE_WINDOW_CAPTURE,
70    GameCapture => OBS_ICON_TYPE_GAME_CAPTURE,
71    Camera => OBS_ICON_TYPE_CAMERA,
72    Text => OBS_ICON_TYPE_TEXT,
73    Media => OBS_ICON_TYPE_MEDIA,
74    Browser => OBS_ICON_TYPE_BROWSER,
75    Custom => OBS_ICON_TYPE_CUSTOM
76});
77
78/// OBS source type
79///
80/// See [OBS documentation](https://obsproject.com/docs/reference-sources.html#c.obs_source_get_type)
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub enum SourceType {
83    INPUT,
84    SCENE,
85    FILTER,
86    TRANSITION,
87}
88
89impl SourceType {
90    pub(crate) fn from_native(source_type: obs_source_type) -> Option<SourceType> {
91        match source_type {
92            obs_source_type_OBS_SOURCE_TYPE_INPUT => Some(SourceType::INPUT),
93            obs_source_type_OBS_SOURCE_TYPE_SCENE => Some(SourceType::SCENE),
94            obs_source_type_OBS_SOURCE_TYPE_FILTER => Some(SourceType::FILTER),
95            obs_source_type_OBS_SOURCE_TYPE_TRANSITION => Some(SourceType::TRANSITION),
96            _ => None,
97        }
98    }
99
100    pub(crate) fn to_native(self) -> obs_source_type {
101        match self {
102            SourceType::INPUT => obs_source_type_OBS_SOURCE_TYPE_INPUT,
103            SourceType::SCENE => obs_source_type_OBS_SOURCE_TYPE_SCENE,
104            SourceType::FILTER => obs_source_type_OBS_SOURCE_TYPE_FILTER,
105            SourceType::TRANSITION => obs_source_type_OBS_SOURCE_TYPE_TRANSITION,
106        }
107    }
108}
109
110/// Context wrapping an OBS source - video / audio elements which are displayed
111/// to the screen.
112///
113/// See [OBS documentation](https://obsproject.com/docs/reference-sources.html#c.obs_source_t)
114pub struct SourceContext {
115    inner: *mut obs_source_t,
116}
117
118impl SourceContext {
119    /// # Safety
120    ///
121    /// Must call with a valid pointer.
122    pub unsafe fn from_raw(source: *mut obs_source_t) -> Self {
123        Self {
124            inner: obs_source_get_ref(source),
125        }
126    }
127}
128
129impl Clone for SourceContext {
130    fn clone(&self) -> Self {
131        unsafe { Self::from_raw(self.inner) }
132    }
133}
134
135impl Drop for SourceContext {
136    fn drop(&mut self) {
137        unsafe { obs_source_release(self.inner) }
138    }
139}
140
141impl SourceContext {
142    /// Run a function on the next source in the filter chain.
143    ///
144    /// Note: only works with sources that are filters.
145    pub fn do_with_target<F: FnOnce(&mut SourceContext)>(&mut self, func: F) {
146        unsafe {
147            if let Some(SourceType::FILTER) =
148                SourceType::from_native(obs_source_get_type(self.inner))
149            {
150                let target = obs_filter_get_target(self.inner);
151                let mut context = SourceContext::from_raw(target);
152                func(&mut context);
153            }
154        }
155    }
156
157    /// Return a unique id for the filter
158    pub fn id(&self) -> usize {
159        self.inner as usize
160    }
161
162    pub fn get_base_width(&self) -> u32 {
163        unsafe { obs_source_get_base_width(self.inner) }
164    }
165
166    pub fn get_base_height(&self) -> u32 {
167        unsafe { obs_source_get_base_height(self.inner) }
168    }
169
170    pub fn showing(&self) -> bool {
171        unsafe { obs_source_showing(self.inner) }
172    }
173
174    pub fn active(&self) -> bool {
175        unsafe { obs_source_active(self.inner) }
176    }
177
178    pub fn enabled(&self) -> bool {
179        unsafe { obs_source_enabled(self.inner) }
180    }
181
182    pub fn set_enabled(&mut self, enabled: bool) {
183        unsafe { obs_source_set_enabled(self.inner, enabled) }
184    }
185
186    pub fn source_id(&self) -> Option<&str> {
187        unsafe {
188            let ptr = obs_source_get_id(self.inner);
189            if ptr.is_null() {
190                None
191            } else {
192                Some(CStr::from_ptr(ptr).to_str().unwrap())
193            }
194        }
195    }
196
197    pub fn name(&self) -> Option<&str> {
198        unsafe {
199            let ptr = obs_source_get_name(self.inner);
200            if ptr.is_null() {
201                None
202            } else {
203                Some(CStr::from_ptr(ptr).to_str().unwrap())
204            }
205        }
206    }
207
208    pub fn set_name(&mut self, name: &str) {
209        let cstr = CString::new(name).unwrap();
210        unsafe {
211            obs_source_set_name(self.inner, cstr.as_ptr());
212        }
213    }
214
215    pub fn width(&self) -> u32 {
216        unsafe { obs_source_get_width(self.inner) }
217    }
218
219    pub fn height(&self) -> u32 {
220        unsafe { obs_source_get_height(self.inner) }
221    }
222
223    pub fn media_play_pause(&mut self, pause: bool) {
224        unsafe {
225            obs_source_media_play_pause(self.inner, pause);
226        }
227    }
228
229    pub fn media_restart(&mut self) {
230        unsafe {
231            obs_source_media_restart(self.inner);
232        }
233    }
234
235    pub fn media_stop(&mut self) {
236        unsafe {
237            obs_source_media_stop(self.inner);
238        }
239    }
240
241    pub fn media_next(&mut self) {
242        unsafe {
243            obs_source_media_next(self.inner);
244        }
245    }
246
247    pub fn media_previous(&mut self) {
248        unsafe {
249            obs_source_media_previous(self.inner);
250        }
251    }
252
253    pub fn media_duration(&self) -> i64 {
254        unsafe { obs_source_media_get_duration(self.inner) }
255    }
256
257    pub fn media_time(&self) -> i64 {
258        unsafe { obs_source_media_get_time(self.inner) }
259    }
260
261    pub fn media_set_time(&mut self, ms: i64) {
262        unsafe { obs_source_media_set_time(self.inner, ms) }
263    }
264
265    pub fn media_state(&self) -> MediaState {
266        let ret = unsafe { obs_source_media_get_state(self.inner) };
267        MediaState::from_native(ret).expect("Invalid media state value")
268    }
269
270    pub fn media_started(&mut self) {
271        unsafe {
272            obs_source_media_started(self.inner);
273        }
274    }
275
276    pub fn media_ended(&mut self) {
277        unsafe {
278            obs_source_media_ended(self.inner);
279        }
280    }
281
282    /// Skips the video filter if it's invalid
283    pub fn skip_video_filter(&mut self) {
284        unsafe {
285            obs_source_skip_video_filter(self.inner);
286        }
287    }
288
289    /// Run a function to do drawing - if the source is a filter.
290    /// This function is wrapped by calls that automatically handle effect-based
291    /// filter processing.
292    ///
293    /// See [OBS documentation](https://obsproject.com/docs/reference-sources.html#c.obs_source_process_filter_begin)
294    ///
295    /// Note: only works with sources that are filters.
296    pub fn process_filter<F: FnOnce(&mut GraphicsEffectContext, &mut GraphicsEffect)>(
297        &mut self,
298        _render: &mut VideoRenderContext,
299        effect: &mut GraphicsEffect,
300        (cx, cy): (u32, u32),
301        format: GraphicsColorFormat,
302        direct: GraphicsAllowDirectRendering,
303        func: F,
304    ) {
305        unsafe {
306            if let Some(SourceType::FILTER) =
307                SourceType::from_native(obs_source_get_type(self.inner))
308            {
309                if obs_source_process_filter_begin(self.inner, format.as_raw(), direct.as_raw()) {
310                    let mut context = GraphicsEffectContext::new();
311                    func(&mut context, effect);
312                    obs_source_process_filter_end(self.inner, effect.as_ptr(), cx, cy);
313                }
314            }
315        }
316    }
317
318    #[allow(clippy::too_many_arguments)]
319    pub fn process_filter_tech<F: FnOnce(&mut GraphicsEffectContext, &mut GraphicsEffect)>(
320        &mut self,
321        _render: &mut VideoRenderContext,
322        effect: &mut GraphicsEffect,
323        (cx, cy): (u32, u32),
324        format: GraphicsColorFormat,
325        direct: GraphicsAllowDirectRendering,
326        technique: ObsString,
327        func: F,
328    ) {
329        unsafe {
330            if let Some(SourceType::FILTER) =
331                SourceType::from_native(obs_source_get_type(self.inner))
332            {
333                if obs_source_process_filter_begin(self.inner, format.as_raw(), direct.as_raw()) {
334                    let mut context = GraphicsEffectContext::new();
335                    func(&mut context, effect);
336                    obs_source_process_filter_tech_end(
337                        self.inner,
338                        effect.as_ptr(),
339                        cx,
340                        cy,
341                        technique.as_ptr(),
342                    );
343                }
344            }
345        }
346    }
347
348    /// Update the source settings based on a settings context.
349    pub fn update_source_settings(&mut self, settings: &mut DataObj) {
350        unsafe {
351            obs_source_update(self.inner, settings.as_ptr_mut());
352        }
353    }
354}
355
356pub struct EnumActiveContext {}
357
358pub struct EnumAllContext {}
359
360pub struct SourceInfo {
361    info: Box<obs_source_info>,
362}
363
364impl SourceInfo {
365    pub fn into_raw(self) -> *mut obs_source_info {
366        Box::into_raw(self.info)
367    }
368
369    pub fn set_icon(&mut self, icon: Icon) {
370        self.info.icon_type = icon.into();
371    }
372}
373
374impl AsRef<obs_source_info> for SourceInfo {
375    fn as_ref(&self) -> &obs_source_info {
376        self.info.as_ref()
377    }
378}
379
380impl AsMut<obs_source_info> for SourceInfo {
381    fn as_mut(&mut self) -> &mut obs_source_info {
382        self.info.as_mut()
383    }
384}
385
386/// The SourceInfoBuilder that handles creating the [SourceInfo](https://obsproject.com/docs/reference-sources.html#c.obs_source_info) object.
387///
388/// For each trait that is implemented for the Source, it needs to be enabled
389/// using this builder. If an struct called `FocusFilter` implements
390/// `CreateSource` and `GetNameSource` it would need to enable those features.
391///
392/// ```rs
393/// let source = load_context
394///  .create_source_builder::<FocusFilter, ()>()
395///  .enable_get_name()
396///  .enable_create()
397///  .build();
398/// ```
399pub struct SourceInfoBuilder<D: Sourceable> {
400    __data: PhantomData<D>,
401    info: obs_source_info,
402}
403
404impl<D: Sourceable> SourceInfoBuilder<D> {
405    pub(crate) fn new() -> Self {
406        Self {
407            __data: PhantomData,
408            info: obs_source_info {
409                id: D::get_id().as_ptr(),
410                type_: D::get_type().to_native(),
411                create: Some(ffi::create::<D>),
412                destroy: Some(ffi::destroy::<D>),
413                type_data: std::ptr::null_mut(),
414                ..Default::default()
415            },
416        }
417    }
418
419    pub fn build(mut self) -> SourceInfo {
420        if self.info.video_render.is_some() {
421            self.info.output_flags |= OBS_SOURCE_VIDEO;
422        }
423
424        if self.info.audio_render.is_some() || self.info.filter_audio.is_some() {
425            self.info.output_flags |= OBS_SOURCE_AUDIO;
426        }
427
428        if self.info.media_get_state.is_some() || self.info.media_play_pause.is_some() {
429            self.info.output_flags |= OBS_SOURCE_CONTROLLABLE_MEDIA;
430        }
431
432        if self.info.mouse_click.is_some()
433            || self.info.mouse_move.is_some()
434            || self.info.mouse_wheel.is_some()
435            || self.info.focus.is_some()
436            || self.info.key_click.is_some()
437        {
438            self.info.output_flags |= OBS_SOURCE_INTERACTION;
439        }
440
441        SourceInfo {
442            info: Box::new(self.info),
443        }
444    }
445
446    pub fn with_icon(mut self, icon: Icon) -> Self {
447        self.info.icon_type = icon.into();
448        self
449    }
450}
451
452macro_rules! impl_source_builder {
453    ($($f:ident => $t:ident)*) => ($(
454        item! {
455            impl<D: Sourceable + [<$t>]> SourceInfoBuilder<D> {
456                pub fn [<enable_$f>](mut self) -> Self {
457                    self.info.[<$f>] = Some(ffi::[<$f>]::<D>);
458                    self
459                }
460            }
461        }
462    )*)
463}
464
465impl_source_builder! {
466    get_name => GetNameSource
467    get_width => GetWidthSource
468    get_height => GetHeightSource
469    activate => ActivateSource
470    deactivate => DeactivateSource
471    update => UpdateSource
472    video_render => VideoRenderSource
473    audio_render => AudioRenderSource
474    get_properties => GetPropertiesSource
475    enum_active_sources => EnumActiveSource
476    enum_all_sources => EnumAllSource
477    transition_start => TransitionStartSource
478    transition_stop => TransitionStopSource
479    video_tick => VideoTickSource
480    filter_audio => FilterAudioSource
481    filter_video => FilterVideoSource
482    get_defaults => GetDefaultsSource
483    media_play_pause => MediaPlayPauseSource
484    media_restart => MediaRestartSource
485    media_stop => MediaStopSource
486    media_next => MediaNextSource
487    media_previous => MediaPreviousSource
488    media_get_duration => MediaGetDurationSource
489    media_get_time => MediaGetTimeSource
490    media_set_time => MediaSetTimeSource
491    media_get_state => MediaGetStateSource
492    mouse_wheel => MouseWheelSource
493    mouse_click => MouseClickSource
494    mouse_move => MouseMoveSource
495    key_click => KeyClickSource
496    focus => FocusSource
497}