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#[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
110pub struct SourceContext {
115 inner: *mut obs_source_t,
116}
117
118impl SourceContext {
119 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 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 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 pub fn skip_video_filter(&mut self) {
284 unsafe {
285 obs_source_skip_video_filter(self.inner);
286 }
287 }
288
289 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 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
386pub 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}