pipewire/properties/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Properties are used to pass around arbitrary key/value pairs.
5//!
6//! This module contains wrappers for [`pw_properties`](pw_sys::pw_properties) and related items.
7
8use std::{ffi::CString, fmt, ptr};
9
10mod box_;
11pub use box_::*;
12
13/// Transparent wrapper around a [properties](self) object.
14///
15/// This does not own the underlying object and is usually seen behind a `&` reference.
16///
17/// For an owning wrapper that can construct properties, see [`PropertiesBox`].
18///
19/// For an explanation of these, see [Smart pointers to PipeWire
20/// objects](crate#smart-pointers-to-pipewire-objects).
21#[repr(transparent)]
22pub struct Properties(pw_sys::pw_properties);
23
24impl Properties {
25    pub fn as_raw(&self) -> &pw_sys::pw_properties {
26        &self.0
27    }
28
29    /// Obtain a pointer to the underlying `pw_properties` struct.
30    ///
31    /// The pointer is only valid for the lifetime of the [`Properties`] struct the pointer was obtained from,
32    /// and must not be dereferenced after it is dropped.
33    ///
34    /// Ownership of the `pw_properties` struct is not transferred to the caller and must not be manually freed.
35    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_properties {
36        std::ptr::addr_of!(self.0).cast_mut()
37    }
38
39    pub fn dict(&self) -> &spa::utils::dict::DictRef {
40        unsafe { &*(&self.0.dict as *const spa_sys::spa_dict as *const spa::utils::dict::DictRef) }
41    }
42
43    // TODO: Impl as trait?
44    pub fn to_owned(&self) -> PropertiesBox {
45        unsafe {
46            let ptr = pw_sys::pw_properties_copy(self.as_raw_ptr());
47            PropertiesBox::from_raw(ptr::NonNull::new_unchecked(ptr))
48        }
49    }
50
51    pub fn get(&self, key: &str) -> Option<&str> {
52        let key = CString::new(key).expect("key contains null byte");
53
54        let res =
55            unsafe { pw_sys::pw_properties_get(self.as_raw_ptr().cast_const(), key.as_ptr()) };
56
57        let res = if !res.is_null() {
58            unsafe { Some(std::ffi::CStr::from_ptr(res)) }
59        } else {
60            None
61        };
62
63        // FIXME: Don't return `None` if result is non-utf8
64        res.and_then(|res| res.to_str().ok())
65    }
66
67    pub fn insert<K, V>(&mut self, key: K, value: V)
68    where
69        K: Into<Vec<u8>>,
70        V: Into<Vec<u8>>,
71    {
72        let k = CString::new(key).unwrap();
73        let v = CString::new(value).unwrap();
74        unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), k.as_ptr(), v.as_ptr()) };
75    }
76
77    pub fn remove<T>(&mut self, key: T)
78    where
79        T: Into<Vec<u8>>,
80    {
81        let key = CString::new(key).unwrap();
82        unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), key.as_ptr(), std::ptr::null()) };
83    }
84
85    pub fn clear(&mut self) {
86        unsafe { pw_sys::pw_properties_clear(self.as_raw_ptr()) }
87    }
88}
89
90impl AsRef<spa::utils::dict::DictRef> for Properties {
91    fn as_ref(&self) -> &spa::utils::dict::DictRef {
92        self.dict()
93    }
94}
95
96impl fmt::Debug for Properties {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        // FIXME: Debug-print dict key and values directly
99        f.debug_tuple("Properties").field(self.as_ref()).finish()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn new() {
109        let props = properties! {
110            "K0" => "V0"
111        };
112
113        let mut iter = props.dict().iter();
114        assert_eq!(("K0", "V0"), iter.next().unwrap());
115        assert_eq!(None, iter.next());
116    }
117
118    #[test]
119    fn remove() {
120        let mut props = properties! {
121            "K0" => "V0"
122        };
123
124        assert_eq!(Some("V0"), props.dict().get("K0"));
125        props.remove("K0");
126        assert_eq!(None, props.dict().get("K0"));
127    }
128
129    #[test]
130    fn insert() {
131        let mut props = properties! {
132            "K0" => "V0"
133        };
134
135        assert_eq!(None, props.dict().get("K1"));
136        props.insert("K1", "V1");
137        assert_eq!(Some("V1"), props.dict().get("K1"));
138    }
139
140    #[test]
141    fn clone() {
142        let props1 = properties! {
143            "K0" => "V0"
144        };
145        let mut props2 = props1.clone();
146
147        props2.insert("K1", "V1");
148
149        // Now, props2 should contain ("K1", "V1"), but props1 should not.
150
151        assert_eq!(None, props1.dict().get("K1"));
152        assert_eq!(Some("V1"), props2.dict().get("K1"));
153    }
154
155    #[test]
156    fn from_dict() {
157        use spa::static_dict;
158
159        let mut props = {
160            let dict = static_dict! { "K0" => "V0" };
161
162            PropertiesBox::from_dict(&dict)
163        };
164
165        assert_eq!(props.dict().len(), 1);
166        assert_eq!(props.dict().get("K0"), Some("V0"));
167
168        props.insert("K1", "V1");
169        assert_eq!(props.dict().len(), 2);
170        assert_eq!(props.dict().get("K1"), Some("V1"));
171    }
172
173    #[test]
174    fn properties_ref() {
175        use std::ops::Deref;
176
177        let props = properties! {
178            "K0" => "V0"
179        };
180        println!("{:?}", &props);
181        let props_ref: &Properties = props.deref();
182
183        assert_eq!(props_ref.dict().len(), 1);
184        assert_eq!(props_ref.dict().get("K0"), Some("V0"));
185        dbg!(&props_ref);
186
187        let props_copy = props_ref.to_owned();
188        assert_eq!(props_copy.dict().len(), 1);
189        assert_eq!(props_copy.dict().get("K0"), Some("V0"));
190    }
191}