diff --git a/Cargo.lock b/Cargo.lock index 6e8486e..c77f3e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,6 +438,7 @@ dependencies = [ "num-traits", "obs-sys", "paste", + "thiserror", ] [[package]] @@ -633,18 +634,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 70149ee..54b736a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ obs-sys = { path = "./obs-sys", version = "0.3.0" } paste = "0.1.7" log = {version = "0.4.11", features = ["std"]} num-traits = "0.2.14" +thiserror = "1.0.58" diff --git a/README.md b/README.md index ce62a37..06f9bbe 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ use obs_wrapper::{ // The module that will handle creating the source. struct TestModule { - context: ModuleContext + context: ModuleRef } // The source that will be shown inside OBS. @@ -62,7 +62,7 @@ impl Sourceable for TestSource { } fn get_type() -> SourceType { - SourceType::FILTER + SourceType::Filter } fn create(create: &mut CreatableSourceContext, source: SourceContext) -> Self { @@ -80,11 +80,11 @@ impl GetNameSource for TestSource { // Implement the Module trait for TestModule. This will handle the creation of the source and // has some methods for telling OBS a bit about itself. impl Module for TestModule { - fn new(context: ModuleContext) -> Self { + fn new(context: ModuleRef) -> Self { Self { context } } - fn get_ctx(&self) -> &ModuleContext { + fn get_ctx(&self) -> &ModuleRef { &self.context } diff --git a/plugins/rnnoise-denoiser-filter/src/lib.rs b/plugins/rnnoise-denoiser-filter/src/lib.rs index 27bf327..932d185 100644 --- a/plugins/rnnoise-denoiser-filter/src/lib.rs +++ b/plugins/rnnoise-denoiser-filter/src/lib.rs @@ -29,7 +29,7 @@ struct RnnoiseDenoiserFilter { } struct TheModule { - context: ModuleContext, + context: ModuleRef, } impl Sourceable for RnnoiseDenoiserFilter { @@ -37,9 +37,9 @@ impl Sourceable for RnnoiseDenoiserFilter { obs_string!("rnnoise_noise_suppression_filter") } fn get_type() -> SourceType { - SourceType::FILTER + SourceType::Filter } - fn create(create: &mut CreatableSourceContext, _source: SourceContext) -> Self { + fn create(create: &mut CreatableSourceContext, _source: SourceRef) -> Self { let (sample_rate, channels) = create.with_audio(|audio| (audio.sample_rate(), audio.channels())); @@ -172,10 +172,10 @@ impl FilterAudioSource for RnnoiseDenoiserFilter { } impl Module for TheModule { - fn new(context: ModuleContext) -> Self { + fn new(context: ModuleRef) -> Self { Self { context } } - fn get_ctx(&self) -> &ModuleContext { + fn get_ctx(&self) -> &ModuleRef { &self.context } diff --git a/plugins/scroll-focus-filter/src/lib.rs b/plugins/scroll-focus-filter/src/lib.rs index 1744d05..f085de4 100644 --- a/plugins/scroll-focus-filter/src/lib.rs +++ b/plugins/scroll-focus-filter/src/lib.rs @@ -17,7 +17,7 @@ enum ServerMessage { } struct ScrollFocusFilter { - source: SourceContext, + source: SourceRef, effect: GraphicsEffect, base_dimension: GraphicsEffectVec2Param, @@ -60,7 +60,7 @@ impl Drop for ScrollFocusFilter { } struct TheModule { - context: ModuleContext, + context: ModuleRef, } impl Sourceable for ScrollFocusFilter { @@ -68,9 +68,9 @@ impl Sourceable for ScrollFocusFilter { obs_string!("scroll_focus_filter") } fn get_type() -> SourceType { - SourceType::FILTER + SourceType::Filter } - fn create(create: &mut CreatableSourceContext, mut source: SourceContext) -> Self { + fn create(create: &mut CreatableSourceContext, mut source: SourceRef) -> Self { let mut effect = GraphicsEffect::from_effect_string( obs_string!(include_str!("./crop_filter.effect")), obs_string!("crop_filter.effect"), @@ -382,10 +382,10 @@ impl UpdateSource for ScrollFocusFilter { } impl Module for TheModule { - fn new(context: ModuleContext) -> Self { + fn new(context: ModuleRef) -> Self { Self { context } } - fn get_ctx(&self) -> &ModuleContext { + fn get_ctx(&self) -> &ModuleRef { &self.context } diff --git a/src/data.rs b/src/data.rs index c02bdbe..a5e428d 100644 --- a/src/data.rs +++ b/src/data.rs @@ -20,7 +20,10 @@ use obs_sys::{ size_t, }; -use crate::{string::ObsString, wrapper::PtrWrapper}; +use crate::{ + string::{ObsString, TryIntoObsString}, + wrapper::PtrWrapper, +}; #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum DataType { @@ -57,12 +60,12 @@ impl DataType { } } -pub trait FromDataItem { +pub trait FromDataItem: Sized { fn typ() -> DataType; /// # Safety /// /// Pointer must be valid. - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self; + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option; /// # Safety /// @@ -74,9 +77,12 @@ impl FromDataItem for Cow<'_, str> { fn typ() -> DataType { DataType::String } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { let ptr = obs_data_item_get_string(item); - CStr::from_ptr(ptr).to_string_lossy() + if ptr.is_null() { + return None; + } + Some(CStr::from_ptr(ptr).to_string_lossy()) } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { let s = CString::new(val.as_ref()).unwrap(); @@ -88,9 +94,9 @@ impl FromDataItem for ObsString { fn typ() -> DataType { DataType::String } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { let ptr = obs_data_item_get_string(item); - ObsString::Dynamic(CStr::from_ptr(ptr).into()) + ptr.try_into_obs_string().ok() } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_string(obj, name.as_ptr(), val.as_ptr()); @@ -104,8 +110,8 @@ macro_rules! impl_get_int { fn typ() -> DataType { DataType::Int } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - obs_data_item_get_int(item) as $t + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + Some(obs_data_item_get_int(item) as $t) } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_int(obj, name.as_ptr(), val as i64) @@ -121,8 +127,8 @@ impl FromDataItem for f64 { fn typ() -> DataType { DataType::Double } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - obs_data_item_get_double(item) + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + Some(obs_data_item_get_double(item)) } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_double(obj, name.as_ptr(), val) @@ -133,8 +139,8 @@ impl FromDataItem for f32 { fn typ() -> DataType { DataType::Double } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - obs_data_item_get_double(item) as f32 + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + Some(obs_data_item_get_double(item) as f32) } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_double(obj, name.as_ptr(), val as f64) @@ -145,8 +151,8 @@ impl FromDataItem for bool { fn typ() -> DataType { DataType::Boolean } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - obs_data_item_get_bool(item) + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + Some(obs_data_item_get_bool(item)) } unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_bool(obj, name.as_ptr(), val) @@ -157,10 +163,12 @@ impl FromDataItem for DataObj<'_> { fn typ() -> DataType { DataType::Object } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - Self::from_raw(obs_data_item_get_obj(item)) + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + // https://github.com/obsproject/obs-studio/blob/01610d8c06edb08d0cc3155cb91b3e52e9a6473e/libobs/obs-data.c#L1798 + // `os_atomic_inc_long(&obj->ref);` + Self::from_raw_unchecked(obs_data_item_get_obj(item)) } - unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, mut val: Self) { + unsafe fn set_default_unchecked(obj: *mut obs_data_t, name: ObsString, val: Self) { obs_data_set_default_obj(obj, name.as_ptr(), val.as_ptr_mut()) } } @@ -169,8 +177,10 @@ impl FromDataItem for DataArray<'_> { fn typ() -> DataType { DataType::Array } - unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Self { - Self::from_raw(obs_data_item_get_array(item)) + unsafe fn from_item_unchecked(item: *mut obs_data_item_t) -> Option { + // https://github.com/obsproject/obs-studio/blob/01610d8c06edb08d0cc3155cb91b3e52e9a6473e/libobs/obs-data.c#L1811 + // `os_atomic_inc_long(&array->ref);` + Self::from_raw_unchecked(obs_data_item_get_array(item)) } unsafe fn set_default_unchecked(_obj: *mut obs_data_t, _name: ObsString, _val: Self) { unimplemented!("obs_data_set_default_array function doesn't exist") @@ -183,21 +193,22 @@ pub struct DataObj<'parent> { _parent: PhantomData<&'parent DataObj<'parent>>, } -impl PtrWrapper for DataObj<'_> { - type Pointer = obs_data_t; - - unsafe fn from_raw(raw: *mut Self::Pointer) -> Self { +impl crate::wrapper::PtrWrapperInternal for DataObj<'_> { + unsafe fn new_internal(ptr: *mut Self::Pointer) -> Self { Self { - raw, + raw: ptr, _parent: PhantomData, } } - fn as_ptr(&self) -> *const Self::Pointer { + unsafe fn get_internal(&self) -> *mut Self::Pointer { self.raw } } +// impl_ptr_wrapper!(DataObj, obs_data_t, @addref: obs_data_addref, obs_data_release); +impl_ptr_wrapper!(DataObj<'_>, obs_data_t, @identity, obs_data_release); + impl Default for DataObj<'_> { fn default() -> Self { DataObj::new() @@ -209,7 +220,7 @@ impl DataObj<'_> { pub fn new() -> Self { unsafe { let raw = obs_data_create(); - Self::from_raw(raw) + Self::from_raw_unchecked(raw).expect("obs_data_create") } } @@ -218,11 +229,7 @@ impl DataObj<'_> { let json_str = json_str.into(); unsafe { let raw = obs_data_create_from_json(json_str.as_ptr()); - if raw.is_null() { - None - } else { - Some(Self::from_raw(raw)) - } + Self::from_raw_unchecked(raw) } } @@ -241,11 +248,7 @@ impl DataObj<'_> { } else { obs_data_create_from_json_file(json_file.as_ptr()) }; - if raw.is_null() { - None - } else { - Some(Self::from_raw(raw)) - } + Self::from_raw_unchecked(raw) } } @@ -265,7 +268,7 @@ impl DataObj<'_> { let typ = unsafe { DataType::from_item(item_ptr) }; if typ == T::typ() { - Some(unsafe { T::from_item_unchecked(item_ptr) }) + unsafe { T::from_item_unchecked(item_ptr) } } else { None } @@ -288,12 +291,7 @@ impl DataObj<'_> { pub fn get_json(&self) -> Option { unsafe { let ptr = obs_data_get_json(self.raw); - if ptr.is_null() { - None - } else { - let slice = CStr::from_ptr(ptr); - Some(slice.to_string_lossy().into_owned()) - } + Some(ptr.try_into_obs_string().ok()?.as_str().to_string()) } } @@ -312,34 +310,26 @@ impl DataObj<'_> { } } -impl Drop for DataObj<'_> { - fn drop(&mut self) { - unsafe { - obs_data_release(self.raw); - } - } -} - pub struct DataArray<'parent> { raw: *mut obs_data_array_t, _parent: PhantomData<&'parent DataArray<'parent>>, } -impl PtrWrapper for DataArray<'_> { - type Pointer = obs_data_array_t; - - unsafe fn from_raw(raw: *mut Self::Pointer) -> Self { +impl crate::wrapper::PtrWrapperInternal for DataArray<'_> { + unsafe fn new_internal(raw: *mut Self::Pointer) -> Self { Self { raw, _parent: PhantomData, } } - fn as_ptr(&self) -> *const Self::Pointer { + unsafe fn get_internal(&self) -> *mut Self::Pointer { self.raw } } +impl_ptr_wrapper!(DataArray<'_>, obs_data_array_t, @identity, obs_data_array_release); + impl DataArray<'_> { pub fn len(&self) -> usize { unsafe { obs_data_array_count(self.raw) } @@ -350,19 +340,9 @@ impl DataArray<'_> { } pub fn get(&self, index: usize) -> Option { + // https://github.com/obsproject/obs-studio/blob/01610d8c06edb08d0cc3155cb91b3e52e9a6473e/libobs/obs-data.c#L1395 + // os_atomic_inc_long(&data->ref); let ptr = unsafe { obs_data_array_item(self.raw, index as size_t) }; - if ptr.is_null() { - None - } else { - Some(unsafe { DataObj::from_raw(ptr) }) - } - } -} - -impl Drop for DataArray<'_> { - fn drop(&mut self) { - unsafe { - obs_data_array_release(self.raw); - } + unsafe { DataObj::from_raw_unchecked(ptr) } } } diff --git a/src/graphics/display.rs b/src/graphics/display.rs new file mode 100644 index 0000000..7786cfc --- /dev/null +++ b/src/graphics/display.rs @@ -0,0 +1,208 @@ +use obs_sys::{ + obs_display_add_draw_callback, obs_display_destroy, obs_display_enabled, + obs_display_remove_draw_callback, obs_display_resize, obs_display_set_background_color, + obs_display_set_enabled, obs_display_size, obs_display_t, obs_render_main_texture, +}; + +use super::GraphicsColorFormat; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +fn srgb_nonlinear_to_linear(c: f32) -> f32 { + if c <= 0.04045 { + c / 12.92 + } else { + ((c + 0.055) / 1.055).powf(2.4) + } +} +fn srgb_linear_to_nonlinear(c: f32) -> f32 { + if c <= 0.0031308 { + c * 12.92 + } else { + 1.055 * c.powf(1.0 / 2.4) - 0.055 + } +} +impl Color { + pub const BLACK: Color = Color::new(0, 0, 0, 255); + pub const WHITE: Color = Color::new(255, 255, 255, 255); + pub const RED: Color = Color::new(255, 0, 0, 255); + pub const GREEN: Color = Color::new(0, 255, 0, 255); + pub const BLUE: Color = Color::new(0, 0, 255, 255); + + pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Color { r, g, b, a } + } + + pub fn as_format(self, format: GraphicsColorFormat) -> u32 { + match format { + GraphicsColorFormat::RGBA => self.as_rgba(), + GraphicsColorFormat::BGRA => self.as_bgra(), + _ => unimplemented!("unsupported color format"), + } + } + + pub fn as_rgba(self) -> u32 { + u32::from_ne_bytes([self.r, self.g, self.b, self.a]) + } + + pub fn as_bgra(self) -> u32 { + u32::from_ne_bytes([self.b, self.g, self.r, self.a]) + } + + /// gs_float3_srgb_nonlinear_to_linear + pub fn srgb_nonlinear_to_linear(self) -> Self { + let r = srgb_nonlinear_to_linear(self.r as f32 / 255.0); + let g = srgb_nonlinear_to_linear(self.g as f32 / 255.0); + let b = srgb_nonlinear_to_linear(self.b as f32 / 255.0); + Color { + r: (r * 255.0) as u8, + g: (g * 255.0) as u8, + b: (b * 255.0) as u8, + a: self.a, + } + } + + pub fn srgb_linear_to_nonlinear(self) -> Self { + let r = srgb_linear_to_nonlinear(self.r as f32 / 255.0); + let g = srgb_linear_to_nonlinear(self.g as f32 / 255.0); + let b = srgb_linear_to_nonlinear(self.b as f32 / 255.0); + Color { + r: (r * 255.0) as u8, + g: (g * 255.0) as u8, + b: (b * 255.0) as u8, + a: self.a, + } + } +} + +/// A reference to a display, inner pointer is not managed by reference count. +/// So no `Clone` is implemented. you might want to use `Arc` if you need to clone it. +pub struct DisplayRef { + inner: *mut obs_display_t, +} + +impl_ptr_wrapper!(@ptr: inner, DisplayRef, obs_display_t, @identity, obs_display_destroy); + +impl DisplayRef { + pub fn enabled(&self) -> bool { + unsafe { obs_display_enabled(self.inner) } + } + + pub fn set_enabled(&self, enabled: bool) { + unsafe { obs_display_set_enabled(self.inner, enabled) } + } + + pub fn size(&self) -> (u32, u32) { + let mut cx = 0; + let mut cy = 0; + unsafe { obs_display_size(self.inner, &mut cx, &mut cy) } + (cx, cy) + } + + pub fn set_size(&self, cx: u32, cy: u32) { + unsafe { obs_display_resize(self.inner, cx, cy) } + } + + pub fn set_background_color(&self, color: Color) { + unsafe { obs_display_set_background_color(self.inner, color.as_rgba()) } + } + + pub fn add_draw_callback(&self, callback: S) -> DrawCallbackId<'_, S> { + let data = Box::into_raw(Box::new(callback)); + // force the pointer to be a function pointer, since it is not garantueed to be the same pointer + // for different instance of generic functions + // see https://users.rust-lang.org/t/generic-functions-and-their-pointer-uniquness/36989 + // clippy: #[deny(clippy::fn_address_comparisons)] + let callback: unsafe extern "C" fn(*mut std::ffi::c_void, u32, u32) = draw_callback::; + unsafe { + obs_display_add_draw_callback(self.inner, Some(callback), data as *mut std::ffi::c_void) + } + DrawCallbackId::new(data, callback as *const _, self) + } + + pub fn remove_draw_callback(&self, data: DrawCallbackId) -> S { + data.take(self) + } +} + +pub struct RenderMainTexture; + +impl DrawCallback for RenderMainTexture { + fn draw(&self, _cx: u32, _cy: u32) { + unsafe { obs_render_main_texture() } + } +} + +pub trait DrawCallback { + fn draw(&self, cx: u32, cy: u32); +} + +/// # Safety +/// This function is called by OBS, and it is guaranteed that the pointer is valid. +pub unsafe extern "C" fn draw_callback( + data: *mut std::ffi::c_void, + cx: u32, + cy: u32, +) { + let callback = &*(data as *const S); + callback.draw(cx, cy); +} + +pub struct DrawCallbackId<'a, S> { + data: *mut S, + callback: *const std::ffi::c_void, + display: *mut obs_display_t, + _marker: std::marker::PhantomData<&'a S>, +} + +impl<'a, S> DrawCallbackId<'a, S> { + pub fn new(data: *mut S, callback: *const std::ffi::c_void, display: &'a DisplayRef) -> Self { + DrawCallbackId { + data, + callback, + display: display.inner, + _marker: std::marker::PhantomData, + } + } + + pub fn take(self, display: &DisplayRef) -> S { + assert_eq!(self.display, display.inner); + let ptr = self.data; + unsafe { + obs_display_add_draw_callback( + self.display, + Some(std::mem::transmute(self.callback)), + ptr as *mut std::ffi::c_void, + ) + } + std::mem::forget(self); + unsafe { *Box::from_raw(ptr) } + } + + /// Forget the callback and keep it alive forever. + /// As long as the underlying display is alive, the callback will be called. + /// If the display is destroyed, the callback will be also dropped. + pub fn forever(self) { + std::mem::forget(self); + } +} + +impl<'a, S> Drop for DrawCallbackId<'a, S> { + fn drop(&mut self) { + unsafe { + // we don't check validity of the display here + obs_display_remove_draw_callback( + self.display, + Some(std::mem::transmute(self.callback)), + self.data as *mut std::ffi::c_void, + ); + drop(Box::from_raw(self.data)); + } + } +} diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 7b6cfd8..e8076a4 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -1,4 +1,6 @@ -use crate::{Error, Result}; +pub mod display; + +use crate::{native_enum, Error, Result}; use core::convert::TryFrom; use core::ptr::null_mut; use obs_sys::{ @@ -70,62 +72,21 @@ impl Drop for GraphicsGuard { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ShaderParamType { - Unknown, - Bool, - Float, - Int, - String, - Vec2, - Vec3, - Vec4, - Int2, - Int3, - Int4, - Mat4, - Texture, -} - -impl ShaderParamType { - pub fn as_raw(&self) -> gs_shader_param_type { - match self { - ShaderParamType::Unknown => gs_shader_param_type_GS_SHADER_PARAM_UNKNOWN, - ShaderParamType::Bool => gs_shader_param_type_GS_SHADER_PARAM_BOOL, - ShaderParamType::Float => gs_shader_param_type_GS_SHADER_PARAM_FLOAT, - ShaderParamType::Int => gs_shader_param_type_GS_SHADER_PARAM_INT, - ShaderParamType::String => gs_shader_param_type_GS_SHADER_PARAM_STRING, - ShaderParamType::Vec2 => gs_shader_param_type_GS_SHADER_PARAM_VEC2, - ShaderParamType::Vec3 => gs_shader_param_type_GS_SHADER_PARAM_VEC3, - ShaderParamType::Vec4 => gs_shader_param_type_GS_SHADER_PARAM_VEC4, - ShaderParamType::Int2 => gs_shader_param_type_GS_SHADER_PARAM_INT2, - ShaderParamType::Int3 => gs_shader_param_type_GS_SHADER_PARAM_INT3, - ShaderParamType::Int4 => gs_shader_param_type_GS_SHADER_PARAM_INT4, - ShaderParamType::Mat4 => gs_shader_param_type_GS_SHADER_PARAM_MATRIX4X4, - ShaderParamType::Texture => gs_shader_param_type_GS_SHADER_PARAM_TEXTURE, - } - } - - #[allow(non_upper_case_globals)] - pub fn from_raw(param_type: gs_shader_param_type) -> Self { - match param_type { - gs_shader_param_type_GS_SHADER_PARAM_UNKNOWN => ShaderParamType::Unknown, - gs_shader_param_type_GS_SHADER_PARAM_BOOL => ShaderParamType::Bool, - gs_shader_param_type_GS_SHADER_PARAM_FLOAT => ShaderParamType::Float, - gs_shader_param_type_GS_SHADER_PARAM_INT => ShaderParamType::Int, - gs_shader_param_type_GS_SHADER_PARAM_STRING => ShaderParamType::String, - gs_shader_param_type_GS_SHADER_PARAM_VEC2 => ShaderParamType::Vec2, - gs_shader_param_type_GS_SHADER_PARAM_VEC3 => ShaderParamType::Vec3, - gs_shader_param_type_GS_SHADER_PARAM_VEC4 => ShaderParamType::Vec4, - gs_shader_param_type_GS_SHADER_PARAM_INT2 => ShaderParamType::Int2, - gs_shader_param_type_GS_SHADER_PARAM_INT3 => ShaderParamType::Int3, - gs_shader_param_type_GS_SHADER_PARAM_INT4 => ShaderParamType::Int4, - gs_shader_param_type_GS_SHADER_PARAM_MATRIX4X4 => ShaderParamType::Mat4, - gs_shader_param_type_GS_SHADER_PARAM_TEXTURE => ShaderParamType::Texture, - _ => panic!("Invalid param_type!"), - } - } -} +native_enum!(ShaderParamType, gs_shader_param_type { + Unknown => GS_SHADER_PARAM_UNKNOWN, + Bool => GS_SHADER_PARAM_BOOL, + Float => GS_SHADER_PARAM_FLOAT, + Int => GS_SHADER_PARAM_INT, + String => GS_SHADER_PARAM_STRING, + Vec2 => GS_SHADER_PARAM_VEC2, + Vec3 => GS_SHADER_PARAM_VEC3, + Vec4 => GS_SHADER_PARAM_VEC4, + Int2 => GS_SHADER_PARAM_INT2, + Int3 => GS_SHADER_PARAM_INT3, + Int4 => GS_SHADER_PARAM_INT4, + Mat4 => GS_SHADER_PARAM_MATRIX4X4, + Texture => GS_SHADER_PARAM_TEXTURE, +}); pub struct GraphicsEffect { raw: *mut gs_effect_t, @@ -190,7 +151,7 @@ impl GraphicsEffectParam { let mut info = gs_effect_param_info::default(); gs_effect_get_param_info(raw, &mut info); - let shader_type = ShaderParamType::from_raw(info.type_); + let shader_type = ShaderParamType::from_raw(info.type_).unwrap(); let name = CString::from(CStr::from_ptr(info.name)) .into_string() .unwrap_or_else(|_| String::from("{unknown-param-name}")); @@ -221,7 +182,7 @@ macro_rules! impl_graphics_effects { fn try_from(effect: GraphicsEffectParam) -> Result { match effect.shader_type { ShaderParamType::[<$t>] => Ok([] { effect }), - _ => Err(Error), + _ => Err(Error::EnumOutOfRange("ShaderParamType", effect.shader_type as _)), } } } @@ -261,65 +222,25 @@ impl GraphicsEffectTextureParam { } } -pub enum GraphicsAddressMode { - Clamp, - Wrap, - Mirror, - Border, - MirrorOnce, -} +native_enum!(GraphicsAddressMode, gs_address_mode { + Clamp => GS_ADDRESS_CLAMP, + Wrap => GS_ADDRESS_WRAP, + Mirror => GS_ADDRESS_MIRROR, + Border => GS_ADDRESS_BORDER, + MirrorOnce => GS_ADDRESS_MIRRORONCE, +}); -impl GraphicsAddressMode { - pub fn as_raw(&self) -> gs_address_mode { - match self { - GraphicsAddressMode::Clamp => gs_address_mode_GS_ADDRESS_CLAMP, - GraphicsAddressMode::Wrap => gs_address_mode_GS_ADDRESS_WRAP, - GraphicsAddressMode::Mirror => gs_address_mode_GS_ADDRESS_MIRROR, - GraphicsAddressMode::Border => gs_address_mode_GS_ADDRESS_BORDER, - GraphicsAddressMode::MirrorOnce => gs_address_mode_GS_ADDRESS_MIRRORONCE, - } - } -} - -pub enum GraphicsSampleFilter { - Point, - Linear, - Anisotropic, - MinMagPointMipLinear, - MinPointMagLinearMipPoint, - MinPointMagMipLinear, - MinLinearMapMipPoint, - MinLinearMagPointMipLinear, - MinMagLinearMipPoint, -} - -impl GraphicsSampleFilter { - fn as_raw(&self) -> gs_sample_filter { - match self { - GraphicsSampleFilter::Point => gs_sample_filter_GS_FILTER_POINT, - GraphicsSampleFilter::Linear => gs_sample_filter_GS_FILTER_LINEAR, - GraphicsSampleFilter::Anisotropic => gs_sample_filter_GS_FILTER_ANISOTROPIC, - GraphicsSampleFilter::MinMagPointMipLinear => { - gs_sample_filter_GS_FILTER_MIN_MAG_POINT_MIP_LINEAR - } - GraphicsSampleFilter::MinPointMagLinearMipPoint => { - gs_sample_filter_GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT - } - GraphicsSampleFilter::MinPointMagMipLinear => { - gs_sample_filter_GS_FILTER_MIN_POINT_MAG_MIP_LINEAR - } - GraphicsSampleFilter::MinLinearMapMipPoint => { - gs_sample_filter_GS_FILTER_MIN_LINEAR_MAG_MIP_POINT - } - GraphicsSampleFilter::MinLinearMagPointMipLinear => { - gs_sample_filter_GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR - } - GraphicsSampleFilter::MinMagLinearMipPoint => { - gs_sample_filter_GS_FILTER_MIN_MAG_LINEAR_MIP_POINT - } - } - } -} +native_enum!(GraphicsSampleFilter, gs_sample_filter { + Point => GS_FILTER_POINT, + Linear => GS_FILTER_LINEAR, + Anisotropic => GS_FILTER_ANISOTROPIC, + MinMagPointMipLinear => GS_FILTER_MIN_MAG_POINT_MIP_LINEAR, + MinPointMagLinearMipPoint => GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + MinPointMagMipLinear => GS_FILTER_MIN_POINT_MAG_MIP_LINEAR, + MinLinearMapMipPoint => GS_FILTER_MIN_LINEAR_MAG_MIP_POINT, + MinLinearMagPointMipLinear => GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + MinMagLinearMipPoint => GS_FILTER_MIN_MAG_LINEAR_MIP_POINT, +}); pub struct GraphicsSamplerInfo { info: gs_sampler_info, @@ -396,72 +317,32 @@ impl GraphicsEffectContext { } } -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum GraphicsColorFormat { - UNKNOWN, - A8, - R8, - RGBA, - BGRX, - BGRA, - R10G10B10A2, - RGBA16, - R16, - RGBA16F, - RGBA32F, - RG16F, - RG32F, - R16F, - R32F, - DXT1, - DXT3, - DXT5, - R8G8, -} - -impl GraphicsColorFormat { - pub fn as_raw(&self) -> gs_color_format { - match self { - GraphicsColorFormat::UNKNOWN => gs_color_format_GS_UNKNOWN, - GraphicsColorFormat::A8 => gs_color_format_GS_A8, - GraphicsColorFormat::R8 => gs_color_format_GS_R8, - GraphicsColorFormat::RGBA => gs_color_format_GS_RGBA, - GraphicsColorFormat::BGRX => gs_color_format_GS_BGRX, - GraphicsColorFormat::BGRA => gs_color_format_GS_BGRA, - GraphicsColorFormat::R10G10B10A2 => gs_color_format_GS_R10G10B10A2, - GraphicsColorFormat::RGBA16 => gs_color_format_GS_RGBA16, - GraphicsColorFormat::R16 => gs_color_format_GS_R16, - GraphicsColorFormat::RGBA16F => gs_color_format_GS_RGBA16F, - GraphicsColorFormat::RGBA32F => gs_color_format_GS_RGBA32F, - GraphicsColorFormat::RG16F => gs_color_format_GS_RG16F, - GraphicsColorFormat::RG32F => gs_color_format_GS_RG32F, - GraphicsColorFormat::R16F => gs_color_format_GS_R16F, - GraphicsColorFormat::R32F => gs_color_format_GS_R32F, - GraphicsColorFormat::DXT1 => gs_color_format_GS_DXT1, - GraphicsColorFormat::DXT3 => gs_color_format_GS_DXT3, - GraphicsColorFormat::DXT5 => gs_color_format_GS_DXT5, - GraphicsColorFormat::R8G8 => gs_color_format_GS_R8G8, - } - } -} - -pub enum GraphicsAllowDirectRendering { - NoDirectRendering, - AllowDirectRendering, -} - -impl GraphicsAllowDirectRendering { - pub fn as_raw(&self) -> obs_allow_direct_render { - match self { - GraphicsAllowDirectRendering::NoDirectRendering => { - obs_allow_direct_render_OBS_NO_DIRECT_RENDERING - } - GraphicsAllowDirectRendering::AllowDirectRendering => { - obs_allow_direct_render_OBS_ALLOW_DIRECT_RENDERING - } - } - } -} +native_enum!(GraphicsColorFormat, gs_color_format { + UNKNOWN => GS_UNKNOWN, + A8 => GS_A8, + R8 => GS_R8, + RGBA => GS_RGBA, + BGRX => GS_BGRX, + BGRA => GS_BGRA, + R10G10B10A2 => GS_R10G10B10A2, + RGBA16 => GS_RGBA16, + R16 => GS_R16, + RGBA16F => GS_RGBA16F, + RGBA32F => GS_RGBA32F, + RG16F => GS_RG16F, + RG32F => GS_RG32F, + R16F => GS_R16F, + R32F => GS_R32F, + DXT1 => GS_DXT1, + DXT3 => GS_DXT3, + DXT5 => GS_DXT5, + R8G8 => GS_R8G8, +}); + +native_enum!(GraphicsAllowDirectRendering, obs_allow_direct_render { + NoDirectRendering => OBS_NO_DIRECT_RENDERING, + AllowDirectRendering => OBS_ALLOW_DIRECT_RENDERING, +}); macro_rules! vector_impls { ($($rust_name: ident, $name:ident => $($component:ident)*,)*) => ( @@ -700,7 +581,7 @@ impl<'tex> MappedTexture<'tex> { gs_texture_map(tex.as_ptr(), &mut ptr, &mut linesize) }); if !map_result { - return Err(Error); + return Err(Error::ObsError(-1)); } let len = (linesize * tex.height()) as usize; Ok(Self { tex, ptr, len }) diff --git a/src/lib.rs b/src/lib.rs index ec374a5..9cb355f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! //! // The module that will handle creating the source. //! struct TestModule { -//! context: ModuleContext +//! context: ModuleRef //! }; //! //! // The source that will be shown inside OBS. @@ -52,12 +52,12 @@ //! } //! //! fn get_type() -> SourceType { -//! SourceType::FILTER +//! SourceType::Filter //! } //! //! fn create( //! create: &mut CreatableSourceContext, -//! _source: SourceContext +//! _source: SourceRef //! ) -> Self { //! Self //! } @@ -73,11 +73,11 @@ //! // Implement the Module trait for TestModule. This will handle the creation //! // of the source and has some methods for telling OBS a bit about itself. //! impl Module for TestModule { -//! fn new(context: ModuleContext) -> Self { +//! fn new(context: ModuleRef) -> Self { //! Self { context } //! } //! -//! fn get_ctx(&self) -> &ModuleContext { +//! fn get_ctx(&self) -> &ModuleRef { //! &self.context //! } //! @@ -123,6 +123,9 @@ /// Raw bindings of OBS C API pub use obs_sys; +/// FFI pointer wrapper +#[macro_use] +pub mod wrapper; /// `obs_data_t` handling pub mod data; /// Tools required for manipulating graphics in OBS @@ -138,12 +141,12 @@ pub mod module; pub mod output; /// Tools for creating properties pub mod properties; +/// Error handling +pub mod result; /// Tools for creating sources pub mod source; /// String macros pub mod string; -/// FFI pointer wrapper -pub mod wrapper; mod native_enum; @@ -155,15 +158,4 @@ pub mod prelude { pub use crate::string::*; } -#[derive(Debug)] -pub struct Error; - -impl std::error::Error for Error {} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "OBS Error") - } -} - -pub type Result = std::result::Result; +pub use result::{Error, Result}; diff --git a/src/media/state.rs b/src/media/state.rs index 90e1a2d..38375cb 100644 --- a/src/media/state.rs +++ b/src/media/state.rs @@ -6,45 +6,17 @@ use obs_sys::{ obs_media_state_OBS_MEDIA_STATE_STOPPED, }; -/// OBS media state -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum MediaState { - None, - Playing, - Opening, - Buffering, - Paused, - Stopped, - Ended, - Error, -} - -impl MediaState { - #[allow(non_upper_case_globals)] - pub fn from_native(state: obs_media_state) -> Option { - match state { - obs_media_state_OBS_MEDIA_STATE_NONE => Some(Self::None), - obs_media_state_OBS_MEDIA_STATE_PLAYING => Some(Self::Playing), - obs_media_state_OBS_MEDIA_STATE_OPENING => Some(Self::Opening), - obs_media_state_OBS_MEDIA_STATE_BUFFERING => Some(Self::Buffering), - obs_media_state_OBS_MEDIA_STATE_PAUSED => Some(Self::Paused), - obs_media_state_OBS_MEDIA_STATE_STOPPED => Some(Self::Stopped), - obs_media_state_OBS_MEDIA_STATE_ENDED => Some(Self::Ended), - obs_media_state_OBS_MEDIA_STATE_ERROR => Some(Self::Error), - _ => None, - } - } +use crate::native_enum; - pub fn to_native(self) -> obs_media_state { - match self { - Self::None => obs_media_state_OBS_MEDIA_STATE_NONE, - Self::Playing => obs_media_state_OBS_MEDIA_STATE_PLAYING, - Self::Opening => obs_media_state_OBS_MEDIA_STATE_OPENING, - Self::Buffering => obs_media_state_OBS_MEDIA_STATE_BUFFERING, - Self::Paused => obs_media_state_OBS_MEDIA_STATE_PAUSED, - Self::Stopped => obs_media_state_OBS_MEDIA_STATE_STOPPED, - Self::Ended => obs_media_state_OBS_MEDIA_STATE_ENDED, - Self::Error => obs_media_state_OBS_MEDIA_STATE_ERROR, - } - } -} +native_enum!( +/// OBS media state +MediaState, obs_media_state { + None => OBS_MEDIA_STATE_NONE, + Playing => OBS_MEDIA_STATE_PLAYING, + Opening => OBS_MEDIA_STATE_OPENING, + Buffering => OBS_MEDIA_STATE_BUFFERING, + Paused => OBS_MEDIA_STATE_PAUSED, + Stopped => OBS_MEDIA_STATE_STOPPED, + Ended => OBS_MEDIA_STATE_ENDED, + Error => OBS_MEDIA_STATE_ERROR, +}); diff --git a/src/media/video.rs b/src/media/video.rs index 849846a..2c28079 100644 --- a/src/media/video.rs +++ b/src/media/video.rs @@ -1,5 +1,3 @@ -use std::mem; - use obs_sys::{ obs_source_frame, video_data, video_format, video_format_VIDEO_FORMAT_AYUV, video_format_VIDEO_FORMAT_BGR3, video_format_VIDEO_FORMAT_BGRA, video_format_VIDEO_FORMAT_BGRX, @@ -13,98 +11,57 @@ use obs_sys::{ video_output_get_width, video_t, }; -#[derive(Debug, Clone, Copy, Eq)] -pub enum VideoFormat { - Unknown, - None, +use crate::native_enum; + +native_enum!(VideoFormat, video_format { + None => VIDEO_FORMAT_NONE, /// planar 4:2:0 formats, three-plane - I420, + I420 => VIDEO_FORMAT_I420, /// planar 4:2:0 formats, two-plane, luma and packed chroma - NV12, + NV12 => VIDEO_FORMAT_NV12, /// packed 4:2:2 formats - YVYU, + YVYU => VIDEO_FORMAT_YVYU, /// packed 4:2:2 formats, YUYV - YUY2, + YUY2 => VIDEO_FORMAT_YUY2, /// packed 4:2:2 formats - UYVY, + UYVY => VIDEO_FORMAT_UYVY, /// packed uncompressed formats - RGBA, + RGBA => VIDEO_FORMAT_RGBA, /// packed uncompressed formats - BGRA, + BGRA => VIDEO_FORMAT_BGRA, /// packed uncompressed formats - BGRX, + BGRX => VIDEO_FORMAT_BGRX, /// packed uncompressed formats, grayscale - Y800, + Y800 => VIDEO_FORMAT_Y800, /// planar 4:4:4 - I444, + I444 => VIDEO_FORMAT_I444, /// more packed uncompressed formats - BGR3, + BGR3 => VIDEO_FORMAT_BGR3, /// planar 4:2:2 - I422, + I422 => VIDEO_FORMAT_I422, /// planar 4:2:0 with alpha - I40A, + I40A => VIDEO_FORMAT_I40A, /// planar 4:2:2 with alpha - I42A, + I42A => VIDEO_FORMAT_I42A, /// planar 4:4:4 with alpha - YUVA, + YUVA => VIDEO_FORMAT_YUVA, /// packed 4:4:4 with alpha - AYUV, + AYUV => VIDEO_FORMAT_AYUV, /// planar 4:2:0 format, 10 bpp, three-plane - I010, + I010 => VIDEO_FORMAT_I010, /// planar 4:2:0 format, 10 bpp, two-plane, luma and packed chroma - P010, + P010 => VIDEO_FORMAT_P010, /// planar 4:2:2 10 bits, Little Endian - I210, + I210 => VIDEO_FORMAT_I210, /// planar 4:4:4 12 bits, Little Endian - I412, + I412 => VIDEO_FORMAT_I412, /// planar 4:4:4 12 bits with alpha, Little Endian - YA2L, -} - -impl PartialEq for VideoFormat { - fn eq(&self, other: &Self) -> bool { - if matches!(self, VideoFormat::Unknown) || matches!(other, VideoFormat::Unknown) { - false - } else { - mem::discriminant(self) == mem::discriminant(other) - } - } -} - -impl From for VideoFormat { - #[allow(non_upper_case_globals)] - fn from(raw: video_format) -> Self { - match raw { - video_format_VIDEO_FORMAT_NONE => VideoFormat::None, - video_format_VIDEO_FORMAT_I420 => VideoFormat::I420, - video_format_VIDEO_FORMAT_NV12 => VideoFormat::NV12, - video_format_VIDEO_FORMAT_YVYU => VideoFormat::YVYU, - video_format_VIDEO_FORMAT_YUY2 => VideoFormat::YUY2, - video_format_VIDEO_FORMAT_UYVY => VideoFormat::UYVY, - video_format_VIDEO_FORMAT_RGBA => VideoFormat::RGBA, - video_format_VIDEO_FORMAT_BGRA => VideoFormat::BGRA, - video_format_VIDEO_FORMAT_BGRX => VideoFormat::BGRX, - video_format_VIDEO_FORMAT_Y800 => VideoFormat::Y800, - video_format_VIDEO_FORMAT_I444 => VideoFormat::I444, - video_format_VIDEO_FORMAT_BGR3 => VideoFormat::BGR3, - video_format_VIDEO_FORMAT_I422 => VideoFormat::I422, - video_format_VIDEO_FORMAT_I40A => VideoFormat::I40A, - video_format_VIDEO_FORMAT_I42A => VideoFormat::I42A, - video_format_VIDEO_FORMAT_YUVA => VideoFormat::YUVA, - video_format_VIDEO_FORMAT_AYUV => VideoFormat::AYUV, - video_format_VIDEO_FORMAT_I010 => VideoFormat::I010, - video_format_VIDEO_FORMAT_P010 => VideoFormat::P010, - video_format_VIDEO_FORMAT_I210 => VideoFormat::I210, - video_format_VIDEO_FORMAT_I412 => VideoFormat::I412, - video_format_VIDEO_FORMAT_YA2L => VideoFormat::YA2L, - _ => VideoFormat::Unknown, - } - } -} + YA2L => VIDEO_FORMAT_YA2L, +}); pub struct VideoDataSourceContext { pointer: *mut obs_source_frame, @@ -115,10 +72,10 @@ impl VideoDataSourceContext { Self { pointer } } - pub fn format(&self) -> VideoFormat { + pub fn format(&self) -> Option { let raw = unsafe { (*self.pointer).format }; - VideoFormat::from(raw) + VideoFormat::from_raw(raw).ok() } pub fn width(&self) -> u32 { @@ -170,7 +127,7 @@ pub struct VideoInfo { pub width: u32, pub height: u32, pub frame_rate: f64, - pub format: VideoFormat, + pub format: Option, } pub enum FrameSize { @@ -193,10 +150,10 @@ impl VideoInfo { let full_size = width * height; let half_size = half_width * height; let quarter_size = half_width * half_height; - if self.format == VideoFormat::Unknown { + let Some(format) = self.format else { return FrameSize::Unknown; }; - match self.format { + match format { VideoFormat::None => FrameSize::Planes { size: 0, count: 0 }, I420 => FrameSize::ThreePlane(full_size, quarter_size, quarter_size), NV12 => FrameSize::TwoPlane(full_size, half_size * 2), @@ -226,7 +183,6 @@ impl VideoInfo { }, I010 => FrameSize::ThreePlane(full_size * 2, quarter_size * 2, quarter_size * 2), P010 => FrameSize::TwoPlane(full_size * 2, quarter_size * 4), - Unknown => FrameSize::Unknown, } } } @@ -263,9 +219,9 @@ impl VideoRef { unsafe { video_output_get_frame_rate(self.pointer) } } - pub fn format(&self) -> VideoFormat { + pub fn format(&self) -> Option { let raw = unsafe { video_output_get_format(self.pointer) }; - VideoFormat::from(raw) + VideoFormat::from_raw(raw).ok() } } diff --git a/src/module.rs b/src/module.rs index 0aa3ac9..7450959 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,9 +1,11 @@ use crate::output::{traits::Outputable, OutputInfo, OutputInfoBuilder}; use crate::source::{traits::Sourceable, SourceInfo, SourceInfoBuilder}; -use crate::string::ObsString; +use crate::string::{DisplayExt as _, ObsString, TryIntoObsString as _}; +use crate::{Error, Result}; use obs_sys::{ - obs_module_t, obs_output_info, obs_register_output_s, obs_register_source_s, obs_source_info, - size_t, + obs_get_module_author, obs_get_module_description, obs_get_module_file_name, + obs_get_module_name, obs_module_t, obs_output_info, obs_register_output_s, + obs_register_source_s, obs_source_info, size_t, }; use std::marker::PhantomData; @@ -65,8 +67,8 @@ impl Drop for LoadContext { } pub trait Module { - fn new(ctx: ModuleContext) -> Self; - fn get_ctx(&self) -> &ModuleContext; + fn new(ctx: ModuleRef) -> Self; + fn get_ctx(&self) -> &ModuleRef; fn load(&mut self, _load_context: &mut LoadContext) -> bool { true } @@ -86,7 +88,7 @@ macro_rules! obs_register_module { #[allow(missing_safety_doc)] #[no_mangle] pub unsafe extern "C" fn obs_module_set_pointer(raw: *mut $crate::obs_sys::obs_module_t) { - OBS_MODULE = Some(<$t>::new(ModuleContext::new(raw))); + OBS_MODULE = ModuleRef::from_raw(raw).ok().map(<$t>::new); } #[allow(missing_safety_doc)] @@ -150,16 +152,34 @@ macro_rules! obs_register_module { }; } -pub struct ModuleContext { +#[deprecated = "use `ModuleRef` instead"] +pub type ModuleContext = ModuleRef; + +pub struct ModuleRef { raw: *mut obs_module_t, } -impl ModuleContext { +impl std::fmt::Debug for ModuleRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModuleRef") + .field("name", &self.name().display()) + .field("description", &self.description().display()) + .field("author", &self.author().display()) + .field("file_name", &self.file_name().display()) + .finish() + } +} + +impl ModuleRef { /// # Safety - /// Creates a ModuleContext from a pointer to the raw obs_module data which + /// Creates a ModuleRef from a pointer to the raw obs_module data which /// if modified could cause UB. - pub unsafe fn new(raw: *mut obs_module_t) -> Self { - Self { raw } + pub fn from_raw(raw: *mut obs_module_t) -> Result { + if raw.is_null() { + Err(Error::NulPointer("obs_module_t")) + } else { + Ok(Self { raw }) + } } /// # Safety @@ -169,3 +189,21 @@ impl ModuleContext { self.raw } } + +impl ModuleRef { + pub fn name(&self) -> Result { + unsafe { obs_get_module_name(self.raw) }.try_into_obs_string() + } + + pub fn description(&self) -> Result { + unsafe { obs_get_module_description(self.raw) }.try_into_obs_string() + } + + pub fn author(&self) -> Result { + unsafe { obs_get_module_author(self.raw) }.try_into_obs_string() + } + + pub fn file_name(&self) -> Result { + unsafe { obs_get_module_file_name(self.raw) }.try_into_obs_string() + } +} diff --git a/src/native_enum.rs b/src/native_enum.rs index 08d8015..6f28c22 100644 --- a/src/native_enum.rs +++ b/src/native_enum.rs @@ -24,32 +24,46 @@ impl std::error::Error for NativeParsingError {} #[macro_export] macro_rules! native_enum { - ($name:ident,$native_name:ident { $($rust:ident => $native:ident),* }) => { + ($(#[$($attrs_enum:tt)*])* $name:ident,$native_name:ident { $($(#[$($attrss:tt)*])* $rust:ident => $native:ident,)* }) => { paste::item! { - #[derive(Debug, Clone, Copy, Eq, PartialEq)] + $(#[$($attrs_enum)*])* + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum $name { - $($rust),* + $( + $(#[$($attrss)*])* + $rust, + )* } - #[allow(clippy::from_over_into)] - impl Into<$native_name> for $name { - fn into(self) -> obs_text_type { + impl $name { + pub fn as_raw(&self) -> $native_name { match self { - $(Self::$rust => [<$native_name _ $native>]),* + $(Self::$rust => [<$native_name _ $native>],)* } } - } - impl std::convert::TryFrom<$native_name> for $name { - type Error = $crate::native_enum::NativeParsingError; #[allow(non_upper_case_globals)] - fn try_from(value: $native_name) -> Result { + pub fn from_raw(value: $native_name) -> Result { match value { $([<$native_name _ $native>] => Ok(Self::$rust)),*, _ => Err($crate::native_enum::NativeParsingError::new(stringify!($name), value as i64)) } } } + + #[allow(clippy::from_over_into)] + impl Into<$native_name> for $name { + fn into(self) -> $native_name { + self.as_raw() + } + } + + impl std::convert::TryFrom<$native_name> for $name { + type Error = $crate::native_enum::NativeParsingError; + fn try_from(value: $native_name) -> Result { + Self::from_raw(value) + } + } } }; } diff --git a/src/output/context.rs b/src/output/context.rs index cb3f570..e529587 100644 --- a/src/output/context.rs +++ b/src/output/context.rs @@ -14,38 +14,28 @@ use obs_sys::{ use crate::hotkey::HotkeyCallbacks; use crate::media::{audio::AudioRef, video::VideoRef}; +use crate::string::TryIntoObsString; use crate::{hotkey::Hotkey, prelude::DataObj, string::ObsString, wrapper::PtrWrapper}; +use crate::{Error, Result}; + +#[deprecated = "use `OutputRef` instead"] +pub type OutputContext = OutputRef; /// Context wrapping an OBS output - video / audio elements which are displayed /// to the screen. /// /// See [OBS documentation](https://obsproject.com/docs/reference-outputs.html#c.obs_output_t) -pub struct OutputContext { +pub struct OutputRef { pub(crate) inner: *mut obs_output_t, } -impl OutputContext { - /// # Safety - /// - /// Pointer must be valid. - pub unsafe fn from_raw(output: *mut obs_output_t) -> Self { - Self { - inner: obs_output_get_ref(output), - } - } -} - -impl Clone for OutputContext { - fn clone(&self) -> Self { - unsafe { Self::from_raw(self.inner) } - } -} - -impl Drop for OutputContext { - fn drop(&mut self) { - unsafe { obs_output_release(self.inner) } - } -} +impl_ptr_wrapper!( + @ptr: inner, + OutputRef, + obs_output_t, + obs_output_get_ref, + obs_output_release +); extern "C" fn enum_proc(params: *mut std::ffi::c_void, output: *mut obs_output_t) -> bool { let mut v = unsafe { Box::>::from_raw(params as *mut _) }; @@ -54,28 +44,29 @@ extern "C" fn enum_proc(params: *mut std::ffi::c_void, output: *mut obs_output_t true } -impl OutputContext { - pub fn new(id: ObsString, name: ObsString, settings: Option>) -> Self { +impl OutputRef { + pub fn new(id: ObsString, name: ObsString, settings: Option>) -> Result { let settings = match settings { - Some(mut data) => data.as_ptr_mut(), + Some(data) => unsafe { data.as_ptr_mut() }, None => std::ptr::null_mut(), }; let output = unsafe { obs_output_create(id.as_ptr(), name.as_ptr(), settings, std::ptr::null_mut()) }; - unsafe { Self::from_raw(output) } + unsafe { Self::from_raw_unchecked(output) }.ok_or(Error::NulPointer("obs_output_cretae")) } pub fn all_outputs() -> Vec { let outputs = Vec::<*mut obs_output_t>::new(); let params = Box::into_raw(Box::new(outputs)); unsafe { + // `obs_enum_outputs` would return `weak_ref`, so `get_ref` needed obs_enum_outputs(Some(enum_proc), params as *mut _); } let outputs = unsafe { Box::from_raw(params) }; outputs .into_iter() - .map(|i| unsafe { OutputContext::from_raw(i) }) + .filter_map(OutputRef::from_raw) .collect() } pub fn all_types() -> Vec { @@ -96,26 +87,12 @@ impl OutputContext { types } - pub fn output_id(&self) -> Option<&str> { - unsafe { - let ptr = obs_output_get_id(self.inner); - if ptr.is_null() { - None - } else { - Some(CStr::from_ptr(ptr).to_str().unwrap()) - } - } + pub fn output_id(&self) -> Result { + unsafe { obs_output_get_id(self.inner) }.try_into_obs_string() } - pub fn name(&self) -> Option<&str> { - unsafe { - let ptr = obs_output_get_name(self.inner); - if ptr.is_null() { - None - } else { - Some(CStr::from_ptr(ptr).to_str().unwrap()) - } - } + pub fn name(&self) -> Result { + unsafe { obs_output_get_name(self.inner) }.try_into_obs_string() } pub fn start(&mut self) -> bool { diff --git a/src/output/ffi.rs b/src/output/ffi.rs index 68975be..84ae395 100644 --- a/src/output/ffi.rs +++ b/src/output/ffi.rs @@ -1,4 +1,4 @@ -use super::{traits::*, CreatableOutputContext, OutputContext}; +use super::{traits::*, CreatableOutputContext, OutputRef}; use crate::hotkey::{Hotkey, HotkeyCallbacks}; use crate::{data::DataObj, wrapper::PtrWrapper}; use obs_sys::{ @@ -53,9 +53,10 @@ pub unsafe extern "C" fn create( settings: *mut obs_data_t, output: *mut obs_output_t, ) -> *mut c_void { - let settings = DataObj::from_raw(settings); + // this is later forgotten + let settings = DataObj::from_raw_unchecked(settings).unwrap(); let mut context = CreatableOutputContext::from_raw(settings); - let output_context = OutputContext::from_raw(output); + let output_context = OutputRef::from_raw(output).expect("create"); let data = D::create(&mut context, output_context); let wrapper = Box::new(DataWrapper::from(data)); @@ -129,13 +130,15 @@ pub unsafe extern "C" fn encoded_packet( pub unsafe extern "C" fn update(data: *mut c_void, settings: *mut obs_data_t) { let data: &mut DataWrapper = &mut *(data as *mut DataWrapper); - let mut settings = DataObj::from_raw(settings); + // this is later forgotten + let mut settings = DataObj::from_raw_unchecked(settings).unwrap(); D::update(&mut data.data, &mut settings); forget(settings); } pub unsafe extern "C" fn get_defaults(settings: *mut obs_data_t) { - let mut settings = DataObj::from_raw(settings); + // this is later forgotten + let mut settings = DataObj::from_raw_unchecked(settings).unwrap(); D::get_defaults(&mut settings); forget(settings); } diff --git a/src/output/traits.rs b/src/output/traits.rs index 4382833..1d0ef93 100644 --- a/src/output/traits.rs +++ b/src/output/traits.rs @@ -2,11 +2,11 @@ use obs_sys::{audio_data, encoder_packet, video_data}; use crate::{prelude::DataObj, properties::Properties, string::ObsString}; -use super::{CreatableOutputContext, OutputContext}; +use super::{CreatableOutputContext, OutputRef}; pub trait Outputable: Sized { fn get_id() -> ObsString; - fn create(context: &mut CreatableOutputContext<'_, Self>, output: OutputContext) -> Self; + fn create(context: &mut CreatableOutputContext<'_, Self>, output: OutputRef) -> Self; fn start(&mut self) -> bool { true diff --git a/src/properties.rs b/src/properties.rs index 9650650..9a2de81 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -29,32 +29,32 @@ use std::{marker::PhantomData, ops::RangeBounds, os::raw::c_int}; native_enum!(TextType, obs_text_type { Default => OBS_TEXT_DEFAULT, Password => OBS_TEXT_PASSWORD, - Multiline => OBS_TEXT_MULTILINE + Multiline => OBS_TEXT_MULTILINE, }); native_enum!(PathType, obs_path_type { File => OBS_PATH_FILE, FileSave => OBS_PATH_FILE_SAVE, - Directory => OBS_PATH_DIRECTORY + Directory => OBS_PATH_DIRECTORY, }); native_enum!(ComboFormat, obs_combo_format { Invalid => OBS_COMBO_FORMAT_INVALID, Int => OBS_COMBO_FORMAT_INT, Float => OBS_COMBO_FORMAT_FLOAT, - String => OBS_COMBO_FORMAT_STRING + String => OBS_COMBO_FORMAT_STRING, }); native_enum!(ComboType, obs_combo_type { Invalid => OBS_COMBO_TYPE_INVALID, Editable => OBS_COMBO_TYPE_EDITABLE, - List => OBS_COMBO_TYPE_LIST + List => OBS_COMBO_TYPE_LIST, }); native_enum!(EditableListType, obs_editable_list_type { Strings => OBS_EDITABLE_LIST_TYPE_STRINGS, Files => OBS_EDITABLE_LIST_TYPE_FILES, - FilesAndUrls => OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS + FilesAndUrls => OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS, }); /// Wrapper around [`obs_properties_t`], which is used by @@ -63,16 +63,12 @@ pub struct Properties { pointer: *mut obs_properties_t, } -impl PtrWrapper for Properties { - type Pointer = obs_properties_t; - - unsafe fn from_raw(raw: *mut Self::Pointer) -> Self { - Self { pointer: raw } - } - - fn as_ptr(&self) -> *const Self::Pointer { - self.pointer - } +impl_ptr_wrapper! { + @ptr: pointer, + Properties, + obs_properties_t, + @identity, + obs_properties_destroy } impl Default for Properties { @@ -85,7 +81,7 @@ impl Properties { pub fn new() -> Self { unsafe { let ptr = obs_properties_create(); - Self::from_raw(ptr) + Self::from_raw_unchecked(ptr).expect("obs_properties_create") } } @@ -120,17 +116,11 @@ impl Properties { .into(), T::format().into(), ); - ListProp::from_raw(raw) + ListProp::from_raw(raw).expect("obs_properties_add_list") } } } -impl Drop for Properties { - fn drop(&mut self) { - unsafe { obs_properties_destroy(self.pointer) } - } -} - /// Wrapper around [`obs_property_t`], which is a list of possible values for a /// property. pub struct ListProp<'props, T> { @@ -142,17 +132,27 @@ pub struct ListProp<'props, T> { impl PtrWrapper for ListProp<'_, T> { type Pointer = obs_property_t; - unsafe fn from_raw(raw: *mut Self::Pointer) -> Self { - Self { - raw, - _props: PhantomData, - _type: PhantomData, + unsafe fn from_raw_unchecked(raw: *mut Self::Pointer) -> Option { + if raw.is_null() { + None + } else { + Some(Self { + raw, + _props: PhantomData, + _type: PhantomData, + }) } } - fn as_ptr(&self) -> *const Self::Pointer { + unsafe fn as_ptr(&self) -> *const Self::Pointer { self.raw } + + unsafe fn get_ref(ptr: *mut Self::Pointer) -> *mut Self::Pointer { + ptr + } + + unsafe fn release(_ptr: *mut Self::Pointer) {} } impl ListProp<'_, T> { diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..1d8a49c --- /dev/null +++ b/src/result.rs @@ -0,0 +1,32 @@ +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Error from OBS API + #[error("OBS Error: {0}")] + ObsError(i32), + /// Null Pointer + #[error("Null Pointer Error: {0}")] + NulPointer(&'static str), + /// Null Error + #[error("Null String Error: {0}")] + NulError(#[from] std::ffi::NulError), + /// Error converting string + #[error("Utf8 Error: {0}")] + Utf8Error(#[from] std::str::Utf8Error), + /// Error converting enum + #[error("Enum Out of Range: {0} {1}")] + EnumOutOfRange(&'static str, i64), + /// Error converting path to str + #[error("Path Error: utf8")] + PathUtf8, +} + +pub trait OptionExt { + fn null_pointer(self, msg: &'static str) -> Result<()>; +} +impl OptionExt for Option<()> { + fn null_pointer(self, msg: &'static str) -> Result<()> { + self.ok_or(Error::NulPointer(msg)) + } +} diff --git a/src/source/ffi.rs b/src/source/ffi.rs index b40df7e..1e148e7 100644 --- a/src/source/ffi.rs +++ b/src/source/ffi.rs @@ -1,5 +1,5 @@ use super::context::{CreatableSourceContext, GlobalContext, VideoRenderContext}; -use super::{traits::*, SourceContext}; +use super::{traits::*, SourceRef}; use super::{EnumActiveContext, EnumAllContext}; use crate::media::{audio::AudioDataContext, video::VideoDataSourceContext}; use crate::{ @@ -87,9 +87,10 @@ pub unsafe extern "C" fn create( source: *mut obs_source_t, ) -> *mut c_void { let mut global = GlobalContext; - let settings = DataObj::from_raw(settings); + // this is later forgotten + let settings = DataObj::from_raw_unchecked(settings).unwrap(); let mut context = CreatableSourceContext::from_raw(settings, &mut global); - let source_context = SourceContext::from_raw(source); + let source_context = SourceRef::from_raw(source).expect("create"); let data = D::create(&mut context, source_context); @@ -115,7 +116,7 @@ pub unsafe extern "C" fn destroy(data: *mut c_void) { pub unsafe extern "C" fn update(data: *mut c_void, settings: *mut obs_data_t) { let mut global = GlobalContext; let data: &mut DataWrapper = &mut *(data as *mut DataWrapper); - let mut settings = DataObj::from_raw(settings); + let mut settings = DataObj::from_raw_unchecked(settings).unwrap(); D::update(&mut data.data, &mut settings, &mut global); forget(settings); } @@ -218,7 +219,7 @@ pub unsafe extern "C" fn media_get_state( data: *mut std::os::raw::c_void, ) -> obs_media_state { let wrapper = &mut *(data as *mut DataWrapper); - D::get_state(&mut wrapper.data).to_native() + D::get_state(&mut wrapper.data).as_raw() } pub unsafe extern "C" fn media_set_time( @@ -252,7 +253,8 @@ impl_media!( ); pub unsafe extern "C" fn get_defaults(settings: *mut obs_data_t) { - let mut settings = DataObj::from_raw(settings); + // this is later forgotten + let mut settings = DataObj::from_raw_unchecked(settings).unwrap(); D::get_defaults(&mut settings); forget(settings); } diff --git a/src/source/mod.rs b/src/source/mod.rs index 38a54e4..40f02dd 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -2,9 +2,14 @@ use paste::item; pub mod context; mod ffi; +pub mod scene; pub mod traits; -use crate::media::state::MediaState; +use crate::{ + media::state::MediaState, + string::{DisplayExt as _, TryIntoObsString}, + Result, +}; pub use context::*; pub use traits::*; @@ -31,8 +36,8 @@ use obs_sys::{ obs_source_set_name, obs_source_showing, obs_source_skip_video_filter, obs_source_t, obs_source_type, obs_source_type_OBS_SOURCE_TYPE_FILTER, obs_source_type_OBS_SOURCE_TYPE_INPUT, obs_source_type_OBS_SOURCE_TYPE_SCENE, obs_source_type_OBS_SOURCE_TYPE_TRANSITION, - obs_source_update, obs_text_type, OBS_SOURCE_AUDIO, OBS_SOURCE_CONTROLLABLE_MEDIA, - OBS_SOURCE_INTERACTION, OBS_SOURCE_VIDEO, + obs_source_update, OBS_SOURCE_AUDIO, OBS_SOURCE_CONTROLLABLE_MEDIA, OBS_SOURCE_INTERACTION, + OBS_SOURCE_VIDEO, }; use super::{ @@ -43,15 +48,12 @@ use super::{ }; use crate::{data::DataObj, native_enum, wrapper::PtrWrapper}; -use std::{ - ffi::{CStr, CString}, - marker::PhantomData, -}; +use std::{ffi::CString, marker::PhantomData}; native_enum!(MouseButton, obs_mouse_button_type { Left => MOUSE_LEFT, Middle => MOUSE_MIDDLE, - Right => MOUSE_RIGHT + Right => MOUSE_RIGHT, }); native_enum!(Icon, obs_icon_type { @@ -68,85 +70,66 @@ native_enum!(Icon, obs_icon_type { Text => OBS_ICON_TYPE_TEXT, Media => OBS_ICON_TYPE_MEDIA, Browser => OBS_ICON_TYPE_BROWSER, - Custom => OBS_ICON_TYPE_CUSTOM + Custom => OBS_ICON_TYPE_CUSTOM, }); +native_enum!( /// OBS source type /// /// See [OBS documentation](https://obsproject.com/docs/reference-sources.html#c.obs_source_get_type) -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SourceType { - INPUT, - SCENE, - FILTER, - TRANSITION, -} +SourceType, obs_source_type { + Input => OBS_SOURCE_TYPE_INPUT, + Scene => OBS_SOURCE_TYPE_SCENE, + Filter => OBS_SOURCE_TYPE_FILTER, + Transition => OBS_SOURCE_TYPE_TRANSITION, +}); -impl SourceType { - #[allow(non_upper_case_globals)] - pub(crate) fn from_native(source_type: obs_source_type) -> Option { - match source_type { - obs_source_type_OBS_SOURCE_TYPE_INPUT => Some(SourceType::INPUT), - obs_source_type_OBS_SOURCE_TYPE_SCENE => Some(SourceType::SCENE), - obs_source_type_OBS_SOURCE_TYPE_FILTER => Some(SourceType::FILTER), - obs_source_type_OBS_SOURCE_TYPE_TRANSITION => Some(SourceType::TRANSITION), - _ => None, - } - } - - pub(crate) fn to_native(self) -> obs_source_type { - match self { - SourceType::INPUT => obs_source_type_OBS_SOURCE_TYPE_INPUT, - SourceType::SCENE => obs_source_type_OBS_SOURCE_TYPE_SCENE, - SourceType::FILTER => obs_source_type_OBS_SOURCE_TYPE_FILTER, - SourceType::TRANSITION => obs_source_type_OBS_SOURCE_TYPE_TRANSITION, - } - } -} +#[deprecated = "use `SourceRef` instead"] +pub type SourceContext = SourceRef; /// Context wrapping an OBS source - video / audio elements which are displayed /// to the screen. /// /// See [OBS documentation](https://obsproject.com/docs/reference-sources.html#c.obs_source_t) -pub struct SourceContext { +pub struct SourceRef { inner: *mut obs_source_t, } -impl SourceContext { - /// # Safety - /// - /// Must call with a valid pointer. - pub unsafe fn from_raw(source: *mut obs_source_t) -> Self { - Self { - inner: obs_source_get_ref(source), - } +impl std::fmt::Debug for SourceRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SourceRef") + .field("id", &self.id()) + .field("name", &self.name().display()) + .field("source_id", &self.source_id().display()) + .field("width", &self.width()) + .field("height", &self.height()) + .field("showing", &self.showing()) + .field("active", &self.active()) + .field("enabled", &self.enabled()) + .finish() } } -impl Clone for SourceContext { - fn clone(&self) -> Self { - unsafe { Self::from_raw(self.inner) } - } -} - -impl Drop for SourceContext { - fn drop(&mut self) { - unsafe { obs_source_release(self.inner) } - } -} +impl_ptr_wrapper!( + @ptr: inner, + SourceRef, + obs_source_t, + obs_source_get_ref, + obs_source_release +); -impl SourceContext { +impl SourceRef { /// Run a function on the next source in the filter chain. /// /// Note: only works with sources that are filters. - pub fn do_with_target(&mut self, func: F) { + pub fn do_with_target(&mut self, func: F) { unsafe { - if let Some(SourceType::FILTER) = - SourceType::from_native(obs_source_get_type(self.inner)) - { + if let Ok(SourceType::Filter) = SourceType::from_raw(obs_source_get_type(self.inner)) { + // doc says "Does not increment the reference." let target = obs_filter_get_target(self.inner); - let mut context = SourceContext::from_raw(target); - func(&mut context); + if let Some(mut context) = SourceRef::from_raw(target) { + func(&mut context); + } } } } @@ -180,26 +163,12 @@ impl SourceContext { unsafe { obs_source_set_enabled(self.inner, enabled) } } - pub fn source_id(&self) -> Option<&str> { - unsafe { - let ptr = obs_source_get_id(self.inner); - if ptr.is_null() { - None - } else { - Some(CStr::from_ptr(ptr).to_str().unwrap()) - } - } + pub fn source_id(&self) -> Result { + unsafe { obs_source_get_id(self.inner) }.try_into_obs_string() } - pub fn name(&self) -> Option<&str> { - unsafe { - let ptr = obs_source_get_name(self.inner); - if ptr.is_null() { - None - } else { - Some(CStr::from_ptr(ptr).to_str().unwrap()) - } - } + pub fn name(&self) -> Result { + unsafe { obs_source_get_name(self.inner) }.try_into_obs_string() } pub fn set_name(&mut self, name: &str) { @@ -261,7 +230,7 @@ impl SourceContext { pub fn media_state(&self) -> MediaState { let ret = unsafe { obs_source_media_get_state(self.inner) }; - MediaState::from_native(ret).expect("Invalid media state value") + MediaState::from_raw(ret).expect("Invalid media state value") } pub fn media_started(&mut self) { @@ -300,9 +269,7 @@ impl SourceContext { func: F, ) { unsafe { - if let Some(SourceType::FILTER) = - SourceType::from_native(obs_source_get_type(self.inner)) - { + if let Ok(SourceType::Filter) = SourceType::from_raw(obs_source_get_type(self.inner)) { if obs_source_process_filter_begin(self.inner, format.as_raw(), direct.as_raw()) { let mut context = GraphicsEffectContext::new(); func(&mut context, effect); @@ -324,9 +291,7 @@ impl SourceContext { func: F, ) { unsafe { - if let Some(SourceType::FILTER) = - SourceType::from_native(obs_source_get_type(self.inner)) - { + if let Ok(SourceType::Filter) = SourceType::from_raw(obs_source_get_type(self.inner)) { if obs_source_process_filter_begin(self.inner, format.as_raw(), direct.as_raw()) { let mut context = GraphicsEffectContext::new(); func(&mut context, effect); @@ -404,7 +369,7 @@ impl SourceInfoBuilder { __data: PhantomData, info: obs_source_info { id: D::get_id().as_ptr(), - type_: D::get_type().to_native(), + type_: D::get_type().as_raw(), create: Some(ffi::create::), destroy: Some(ffi::destroy::), type_data: std::ptr::null_mut(), diff --git a/src/source/scene.rs b/src/source/scene.rs new file mode 100644 index 0000000..7597a3a --- /dev/null +++ b/src/source/scene.rs @@ -0,0 +1,68 @@ +use crate::{ + source::SourceRef, + string::{DisplayExt as _, ObsString}, + wrapper::PtrWrapper, +}; +use obs_sys::{ + obs_scene_add, obs_scene_get_ref, obs_scene_get_source, obs_scene_release, obs_scene_t, + obs_sceneitem_addref, obs_sceneitem_release, obs_sceneitem_t, obs_sceneitem_visible, +}; + +use super::Result; + +pub struct SceneRef { + inner: *mut obs_scene_t, +} + +impl std::fmt::Debug for SceneRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SceneRef") + .field(&self.name().display()) + .field(&self.inner) + .finish() + } +} + +impl_ptr_wrapper!(@ptr: inner, SceneRef, obs_scene_t, obs_scene_get_ref, obs_scene_release); + +impl SceneRef { + pub fn name(&self) -> Result { + self.as_source().name() + } + + pub fn as_source(&self) -> SourceRef { + let ptr = unsafe { + // as doc said "The scene’s source. Does not increment the reference" + // we should manually add_ref for it + obs_scene_get_source(self.inner) + }; + SourceRef::from_raw(ptr).expect("obs_scene_get_source") + } + + pub fn add_source(&self, source: SourceRef) -> SceneItemRef { + let ptr = unsafe { + // add ref for source, Docs said "A new scene item for a source within a scene. Does not + // increment the reference" + obs_scene_add(self.inner, source.as_ptr_mut()) + }; + SceneItemRef::from_raw(ptr).expect("obs_scene_add") + } +} + +pub struct SceneItemRef { + inner: *mut obs_sceneitem_t, +} + +impl_ptr_wrapper!( + @ptr: inner, + SceneItemRef, + obs_sceneitem_t, + @addref: obs_sceneitem_addref, + obs_sceneitem_release +); + +impl SceneItemRef { + pub fn visible(&self) -> bool { + unsafe { obs_sceneitem_visible(self.inner) } + } +} diff --git a/src/source/traits.rs b/src/source/traits.rs index ab83101..113ec12 100644 --- a/src/source/traits.rs +++ b/src/source/traits.rs @@ -1,7 +1,7 @@ use obs_sys::{obs_key_event, obs_mouse_event}; use super::context::{CreatableSourceContext, GlobalContext, VideoRenderContext}; -use super::{EnumActiveContext, EnumAllContext, SourceContext, SourceType}; +use super::{EnumActiveContext, EnumAllContext, SourceRef, SourceType}; use crate::data::DataObj; use crate::media::state::MediaState; use crate::media::{audio::AudioDataContext, video::VideoDataSourceContext}; @@ -11,7 +11,7 @@ use crate::string::ObsString; pub trait Sourceable: Sized { fn get_id() -> ObsString; fn get_type() -> SourceType; - fn create(create: &mut CreatableSourceContext, source: SourceContext) -> Self; + fn create(create: &mut CreatableSourceContext, source: SourceRef) -> Self; } macro_rules! simple_trait { diff --git a/src/string.rs b/src/string.rs index 0b9dad2..6d139ef 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,4 +1,10 @@ -use std::{ffi::CString, ptr::null}; +use std::{ + ffi::{CStr, CString}, + path::Path, + ptr::null, +}; + +use crate::{Error, Result}; #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum ObsString { @@ -45,3 +51,94 @@ macro_rules! obs_string { unsafe { $crate::string::ObsString::from_nul_terminted_str(concat!($e, "\0")) } }; } + +pub trait TryIntoObsString { + fn try_into_obs_string(self) -> Result; +} + +impl TryIntoObsString for &str { + fn try_into_obs_string(self) -> Result { + Ok(ObsString::Dynamic(CString::new(self)?)) + } +} +impl TryIntoObsString for String { + fn try_into_obs_string(self) -> Result { + Ok(ObsString::Dynamic(CString::new(self)?)) + } +} +impl TryIntoObsString for &Path { + fn try_into_obs_string(self) -> Result { + Ok(ObsString::Dynamic(CString::new( + self.to_str().ok_or(Error::PathUtf8)?, + )?)) + } +} +impl TryIntoObsString for *const std::os::raw::c_char { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn try_into_obs_string(self) -> Result { + if self.is_null() { + return Err(crate::Error::NulPointer("ObsString")); + } + Ok(ObsString::Dynamic( + unsafe { CStr::from_ptr(self) }.to_owned(), + )) + } +} + +pub struct DisplayStr<'a, T>(&'a T); +pub trait DisplayExt: Sized { + fn display(&self) -> DisplayStr<'_, Self> { + DisplayStr(self) + } +} +impl DisplayExt for ObsString {} +impl DisplayExt for Option {} +impl<'a> DisplayExt for Option<&'a ObsString> {} +impl DisplayExt for Result {} +impl std::fmt::Display for DisplayStr<'_, ObsString> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DisplayStr(ObsString::Static(s)) => write!(f, "{}", &s[..s.len() - 1]), + DisplayStr(ObsString::Dynamic(s)) => write!(f, "{}", s.to_string_lossy()), + } + } +} +impl std::fmt::Debug for DisplayStr<'_, ObsString> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.to_string()) + } +} + +impl<'a, T: DisplayExt> std::fmt::Display for DisplayStr<'a, Option> +where + DisplayStr<'a, T>: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Some(s) => write!(f, "{}", s.display()), + None => write!(f, ""), + } + } +} +impl<'a, T: DisplayExt> std::fmt::Debug for DisplayStr<'a, Option> +where + DisplayStr<'a, T>: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Some(s) => write!(f, "{:?}", s.display()), + None => write!(f, "None"), + } + } +} +impl<'a, T: DisplayExt, E> std::fmt::Debug for DisplayStr<'a, Result> +where + DisplayStr<'a, T>: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Ok(s) => write!(f, "{:?}", s.display()), + _ => write!(f, "Error"), + } + } +} diff --git a/src/wrapper.rs b/src/wrapper.rs index 0f492da..098afe4 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -1,28 +1,156 @@ use std::mem::forget; +pub trait PtrWrapperInternal: PtrWrapper { + /// # Safety + /// + /// This function should not be called directly, use `from_raw` and + /// `from_raw_unchecked` instead. + unsafe fn new_internal(ptr: *mut Self::Pointer) -> Self; + /// # Safety + /// + /// This function should not be called directly, use `as_ptr`, + /// `as_ptr_mut` and `into_raw` instead. + unsafe fn get_internal(&self) -> *mut Self::Pointer; +} + pub trait PtrWrapper: Sized { type Pointer; + + /// # Safety + /// + /// This function called extern C api, and should not be called directly. + unsafe fn get_ref(ptr: *mut Self::Pointer) -> *mut Self::Pointer; + + /// # Safety + /// + /// This function called extern C api, and should not be called directly. + unsafe fn release(ptr: *mut Self::Pointer); + /// Wraps the pointer into a **owned** wrapper. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn from_raw(raw: *mut Self::Pointer) -> Option { + unsafe { Self::from_raw_unchecked(Self::get_ref(raw)) } + } + + /// Wraps a **owned** pointer into wrapper. /// /// # Safety /// - /// Pointer must be valid. - unsafe fn from_raw(raw: *mut Self::Pointer) -> Self; + /// You have to make sure you owned the pointer + unsafe fn from_raw_unchecked(raw: *mut Self::Pointer) -> Option; /// Returns the inner pointer. - fn as_ptr(&self) -> *const Self::Pointer; + /// + /// # Safety + /// + /// This function would return a pointer not managed, should only called + /// when interacting with extern C api. + unsafe fn as_ptr(&self) -> *const Self::Pointer; /// Consumes the wrapper and transfers ownershop to the pointer /// /// This does **NOT** drop the wrapper internally. - fn into_raw(mut self) -> *mut Self::Pointer { - let raw = self.as_ptr_mut(); + fn into_raw(self) -> *mut Self::Pointer { + let raw = unsafe { self.as_ptr_mut() }; forget(self); raw } /// Returns the inner pointer (mutable version). - fn as_ptr_mut(&mut self) -> *mut Self::Pointer { + /// + /// # Safety + /// + /// This function would return a pointer not managed, should only called + /// when interacting with extern C api. + unsafe fn as_ptr_mut(&self) -> *mut Self::Pointer { self.as_ptr() as *mut _ } } + +macro_rules! impl_ptr_wrapper { + (@ptr: $field:ident, $ref:ty, $($tt:tt)*) => { + impl_ptr_wrapper!(@__impl.trait.internal $ref, $field); + impl_ptr_wrapper!($ref, $($tt)*); + }; + ($ref:ty, $ptr:ty, @identity, $release:expr) => { + impl_ptr_wrapper!(@__impl.trait.wrapper $ref, $ptr, impl_ptr_wrapper!{@__impl.fn.id}, $release); + // when get_ref is `@identity`, no `Clone implemented` + impl_ptr_wrapper!(@__impl.trait.drop $ref, $ptr); + }; + ($ref:ty, $ptr:ty, $get_ref:expr, $release:expr) => { + impl_ptr_wrapper!(@__impl.trait.wrapper $ref, $ptr, impl_ptr_wrapper!{@__impl.fn.get_ref $get_ref}, $release); + impl_ptr_wrapper!(@__impl.trait.clone $ref, $ptr); + impl_ptr_wrapper!(@__impl.trait.drop $ref, $ptr); + }; + + ($ref:ty, $ptr:ty, @addref: $add_ref:expr, $release:expr) => { + impl_ptr_wrapper!(@__impl.trait.wrapper $ref, $ptr, impl_ptr_wrapper!{@__impl.fn.add_ref $add_ref}, $release); + impl_ptr_wrapper!(@__impl.trait.clone $ref, $ptr); + impl_ptr_wrapper!(@__impl.trait.drop $ref, $ptr); + }; + (@__impl.fn.get_ref $get_ref:expr) => { + unsafe fn get_ref(ptr: *mut Self::Pointer) -> *mut Self::Pointer { + unsafe { $get_ref(ptr) } + } + }; + (@__impl.fn.add_ref $add_ref:expr) => { + unsafe fn get_ref(ptr: *mut Self::Pointer) -> *mut Self::Pointer { + unsafe { $add_ref(ptr); ptr } + } + }; + (@__impl.fn.id) => { + unsafe fn get_ref(ptr: *mut Self::Pointer) -> *mut Self::Pointer { + ptr + } + }; + (@__impl.trait.internal $ref:ty, $field:ident) => { + impl $crate::wrapper::PtrWrapperInternal for $ref { + unsafe fn new_internal(ptr: *mut Self::Pointer) -> Self { + Self { $field: ptr } + } + unsafe fn get_internal(&self) -> *mut Self::Pointer { + self.$field + } + } + }; + (@__impl.trait.wrapper $ref:ty, $ptr:ty, $get_ref:item, $release:expr) => { + impl $crate::wrapper::PtrWrapper for $ref { + type Pointer = $ptr; + + unsafe fn from_raw_unchecked(raw: *mut Self::Pointer) -> Option { + use $crate::wrapper::PtrWrapperInternal; + if raw.is_null() { + None + } else { + Some(Self::new_internal(raw)) + } + } + + $get_ref + + unsafe fn release(ptr: *mut Self::Pointer) { + unsafe { $release(ptr) } + } + + unsafe fn as_ptr(&self) -> *const Self::Pointer { + use $crate::wrapper::PtrWrapperInternal; + self.get_internal() + } + } + }; + (@__impl.trait.clone $ref:ty, $ptr:ty) => { + impl Clone for $ref { + fn clone(&self) -> Self { + Self::from_raw(unsafe { self.as_ptr_mut() }).expect("clone") + } + } + }; + (@__impl.trait.drop $ref:ty, $ptr:ty) => { + impl Drop for $ref { + fn drop(&mut self) { + use $crate::wrapper::PtrWrapper; + unsafe { Self::release(self.as_ptr_mut()) } + } + } + }; +}