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
103impl<K, V> Extend<(K, V)> for Properties
104where
105    K: Into<Vec<u8>>,
106    V: Into<Vec<u8>>,
107{
108    fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
109        for (k, v) in iter {
110            self.insert(k, v);
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn new() {
121        let props = properties! {
122            "K0" => "V0"
123        };
124
125        let mut iter = props.dict().iter();
126        assert_eq!(("K0", "V0"), iter.next().unwrap());
127        assert_eq!(None, iter.next());
128    }
129
130    #[test]
131    fn remove() {
132        let mut props = properties! {
133            "K0" => "V0"
134        };
135
136        assert_eq!(Some("V0"), props.dict().get("K0"));
137        props.remove("K0");
138        assert_eq!(None, props.dict().get("K0"));
139    }
140
141    #[test]
142    fn insert() {
143        let mut props = properties! {
144            "K0" => "V0"
145        };
146
147        assert_eq!(None, props.dict().get("K1"));
148        props.insert("K1", "V1");
149        assert_eq!(Some("V1"), props.dict().get("K1"));
150    }
151
152    #[test]
153    fn clone() {
154        let props1 = properties! {
155            "K0" => "V0"
156        };
157        let mut props2 = props1.clone();
158
159        props2.insert("K1", "V1");
160
161        // Now, props2 should contain ("K1", "V1"), but props1 should not.
162
163        assert_eq!(None, props1.dict().get("K1"));
164        assert_eq!(Some("V1"), props2.dict().get("K1"));
165    }
166
167    #[test]
168    fn from_dict() {
169        use spa::static_dict;
170
171        let mut props = {
172            let dict = static_dict! { "K0" => "V0" };
173
174            PropertiesBox::from_dict(&dict)
175        };
176
177        assert_eq!(props.dict().len(), 1);
178        assert_eq!(props.dict().get("K0"), Some("V0"));
179
180        props.insert("K1", "V1");
181        assert_eq!(props.dict().len(), 2);
182        assert_eq!(props.dict().get("K1"), Some("V1"));
183    }
184
185    #[test]
186    fn properties_ref() {
187        use std::ops::Deref;
188
189        let props = properties! {
190            "K0" => "V0"
191        };
192        println!("{:?}", &props);
193        let props_ref: &Properties = props.deref();
194
195        assert_eq!(props_ref.dict().len(), 1);
196        assert_eq!(props_ref.dict().get("K0"), Some("V0"));
197        dbg!(&props_ref);
198
199        let props_copy = props_ref.to_owned();
200        assert_eq!(props_copy.dict().len(), 1);
201        assert_eq!(props_copy.dict().get("K0"), Some("V0"));
202    }
203}