gstreamer/
gobject.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::marker::PhantomData;
4
5use glib::{object::IsClass, prelude::*, Type};
6
7use crate::{value::GstValueExt, IdStr};
8
9impl crate::Object {
10    // rustdoc-stripper-ignore-next
11    /// Builds a `GObjectBuilder` targeting type `O`.
12    #[inline]
13    pub fn builder<'a, O>() -> GObjectBuilder<'a, O>
14    where
15        O: IsA<crate::Object> + IsClass,
16    {
17        assert_initialized_main_thread!();
18        GObjectBuilder {
19            type_: Some(O::static_type()),
20            properties: smallvec::SmallVec::new(),
21            phantom: PhantomData,
22        }
23    }
24
25    // rustdoc-stripper-ignore-next
26    /// Builds a `GObjectBuilder` targeting base class of type `O` and concrete `type_`.
27    #[inline]
28    pub fn builder_for<'a, O>(type_: Type) -> GObjectBuilder<'a, O>
29    where
30        O: IsA<crate::Object> + IsClass,
31    {
32        assert_initialized_main_thread!();
33        GObjectBuilder {
34            type_: Some(type_),
35            properties: smallvec::SmallVec::new(),
36            phantom: PhantomData,
37        }
38    }
39
40    // rustdoc-stripper-ignore-next
41    /// Builds a `GObjectBuilder` targeting base class of type `O`
42    /// and a concrete `Type` that will be specified later.
43    ///
44    /// This is useful when the concrete type of the object is dynamically determined
45    /// when calling the `build()` method of a wrapping builder.
46    #[inline]
47    pub fn builder_for_deferred_type<'a, O>() -> GObjectBuilder<'a, O>
48    where
49        O: IsA<crate::Object> + IsClass,
50    {
51        assert_initialized_main_thread!();
52        GObjectBuilder {
53            type_: None,
54            properties: smallvec::SmallVec::new(),
55            phantom: PhantomData,
56        }
57    }
58}
59
60#[derive(Debug, Eq, PartialEq, thiserror::Error)]
61pub enum GObjectError {
62    #[error("property {property} for type {type_} not found")]
63    PropertyNotFound { type_: Type, property: IdStr },
64
65    #[error("property {property} for type {type_} can't be set from string {value}")]
66    PropertyFromStr {
67        type_: Type,
68        property: IdStr,
69        value: IdStr,
70    },
71}
72
73fn value_from_property_str(
74    pspec: glib::ParamSpec,
75    value: &str,
76) -> Result<glib::Value, GObjectError> {
77    skip_assert_initialized!(); // Already checked transitively by caller
78
79    if pspec.value_type() == crate::Structure::static_type() && value == "NULL" {
80        Ok(None::<crate::Structure>.to_value())
81    } else {
82        cfg_if::cfg_if! {
83            if #[cfg(feature = "v1_20")] {
84                let res = glib::Value::deserialize_with_pspec(value, &pspec);
85            } else {
86                let res = glib::Value::deserialize(value, pspec.value_type());
87            }
88        }
89        res.map_err(|_| GObjectError::PropertyFromStr {
90            type_: pspec.owner_type(),
91            property: pspec.name().into(),
92            value: value.into(),
93        })
94    }
95}
96
97pub trait GObjectExtManualGst: IsA<glib::Object> + 'static {
98    #[doc(alias = "gst_util_set_object_arg")]
99    #[track_caller]
100    fn set_property_from_str(&self, name: &str, value: &str) {
101        let pspec = self.find_property(name).unwrap_or_else(|| {
102            panic!("property '{}' of type '{}' not found", name, self.type_());
103        });
104
105        self.set_property(name, value_from_property_str(pspec, value).unwrap())
106    }
107}
108
109impl<O: IsA<glib::Object>> GObjectExtManualGst for O {}
110
111// rustdoc-stripper-ignore-next
112/// Builder for `GObject`s.
113#[must_use = "The builder must be built to be used"]
114pub struct GObjectBuilder<'a, O> {
115    type_: Option<Type>,
116    properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>,
117    phantom: PhantomData<O>,
118}
119
120enum ValueOrStr<'a> {
121    Value(glib::Value),
122    Str(&'a str),
123}
124
125impl<'a, O: IsA<crate::Object> + IsClass> GObjectBuilder<'a, O> {
126    // rustdoc-stripper-ignore-next
127    /// Sets the concrete `Type`.
128    ///
129    /// This should be used on an `GObjectBuilder` created with
130    /// [`GObjectBuilder::for_deferred_type`].
131    #[inline]
132    pub fn type_(mut self, type_: Type) -> Self {
133        self.type_ = Some(type_);
134        self
135    }
136
137    // rustdoc-stripper-ignore-next
138    /// Sets property `name` to the given value `value`.
139    ///
140    /// Overrides any default or previously defined value for `name`.
141    #[inline]
142    pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
143        Self {
144            properties: {
145                let mut properties = self.properties;
146                properties.push((name, ValueOrStr::Value(value.into())));
147                properties
148            },
149            ..self
150        }
151    }
152
153    // rustdoc-stripper-ignore-next
154    /// Sets property `name` to the given string value `value`.
155    #[inline]
156    pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
157        Self {
158            properties: {
159                let mut properties = self.properties;
160                properties.push((name, ValueOrStr::Str(value)));
161                properties
162            },
163            ..self
164        }
165    }
166
167    impl_builder_gvalue_extra_setters!(property_and_name);
168
169    // rustdoc-stripper-ignore-next
170    /// Builds the [`Object`] with the provided properties.
171    ///
172    /// This fails if there is no such element factory or the element factory can't be loaded.
173    ///
174    /// # Panics
175    ///
176    /// This panics if:
177    ///
178    /// * The [`Object`] is not instantiable, doesn't have all the given properties or
179    ///   property values of the wrong type are provided.
180    /// * The [`GObjectBuilder`] was created for a deferred concrete `Type` but
181    ///   the `Type` was not set.
182    ///
183    /// [`Object`]: crate::Object
184    #[track_caller]
185    #[must_use = "Building the element without using it has no effect"]
186    pub fn build(self) -> Result<O, GObjectError> {
187        let type_ = self.type_.expect("Deferred Type must be set");
188
189        let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len());
190        let klass = glib::Class::<O>::from_type(type_).unwrap();
191        for (name, value) in self.properties {
192            let pspec =
193                klass
194                    .find_property(name)
195                    .ok_or_else(|| GObjectError::PropertyNotFound {
196                        type_,
197                        property: name.into(),
198                    })?;
199
200            match value {
201                ValueOrStr::Value(value) => properties.push((name, value)),
202                ValueOrStr::Str(value) => {
203                    properties.push((name, value_from_property_str(pspec, value)?));
204                }
205            }
206        }
207
208        let object =
209            unsafe { glib::Object::with_mut_values(type_, &mut properties).unsafe_cast::<O>() };
210
211        Ok(object)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::{prelude::*, Bin, Element, ElementFactory, Object};
219
220    #[test]
221    fn test_set_property_from_str() {
222        crate::init().unwrap();
223
224        let fakesink = ElementFactory::make("fakesink").build().unwrap();
225        fakesink.set_property_from_str("state-error", "ready-to-paused");
226        let v = fakesink.property_value("state-error");
227        let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
228        assert_eq!(e.nick(), "ready-to-paused");
229    }
230
231    #[test]
232    fn builder() {
233        crate::init().unwrap();
234
235        let msg_fwd = "message-forward";
236        let bin = Object::builder::<Bin>()
237            .name("test-bin")
238            .property("async-handling", true)
239            .property_from_str(msg_fwd, "True")
240            .build()
241            .unwrap();
242
243        assert_eq!(bin.name(), "test-bin");
244        assert!(bin.property::<bool>("async-handling"));
245        assert!(bin.property::<bool>("message-forward"));
246    }
247
248    #[test]
249    fn builder_err() {
250        crate::init().unwrap();
251
252        assert_eq!(
253            Object::builder::<Bin>()
254                .property("not-a-prop", true)
255                .build(),
256            Err(GObjectError::PropertyNotFound {
257                type_: Bin::static_type(),
258                property: idstr!("not-a-prop")
259            })
260        );
261
262        assert_eq!(
263            Object::builder::<Bin>()
264                .property_from_str("async-handling", "not-a-bool")
265                .build(),
266            Err(GObjectError::PropertyFromStr {
267                type_: Bin::static_type(),
268                property: idstr!("async-handling"),
269                value: idstr!("not-a-bool")
270            })
271        );
272    }
273
274    #[test]
275    fn builder_for() {
276        crate::init().unwrap();
277
278        let fakesink = ElementFactory::make("fakesink").build().unwrap();
279
280        let fakesink = Object::builder_for::<Element>(fakesink.type_())
281            .name("test-fakesink")
282            .property("can-activate-pull", true)
283            .property_from_str("state-error", "ready-to-paused")
284            .build()
285            .unwrap();
286
287        assert_eq!(fakesink.name(), "test-fakesink");
288        assert!(fakesink.property::<bool>("can-activate-pull"));
289        let v = fakesink.property_value("state-error");
290        let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
291        assert_eq!(e.nick(), "ready-to-paused");
292    }
293}