gio/
settings.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use glib::{prelude::*, translate::*, BoolError, StrV, Variant};
4
5use crate::{ffi, prelude::*, Settings, SettingsBindFlags};
6
7#[must_use = "The builder must be built to be used"]
8pub struct BindingBuilder<'a> {
9    settings: &'a Settings,
10    key: &'a str,
11    object: &'a glib::Object,
12    property: &'a str,
13    flags: SettingsBindFlags,
14    #[allow(clippy::type_complexity)]
15    get_mapping: Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
16    #[allow(clippy::type_complexity)]
17    set_mapping: Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
18}
19
20impl BindingBuilder<'_> {
21    pub fn flags(mut self, flags: SettingsBindFlags) -> Self {
22        self.flags = flags;
23        self
24    }
25
26    // rustdoc-stripper-ignore-next
27    /// Set the binding flags to [`GET`][crate::SettingsBindFlags::GET].
28    pub fn get(mut self) -> Self {
29        self.flags |= SettingsBindFlags::GET;
30        self
31    }
32
33    // rustdoc-stripper-ignore-next
34    /// Set the binding flags to [`SET`][crate::SettingsBindFlags::SET].
35    pub fn set(mut self) -> Self {
36        self.flags |= SettingsBindFlags::SET;
37        self
38    }
39
40    // rustdoc-stripper-ignore-next
41    /// Unsets the default [`GET`][crate::SettingsBindFlags::GET] flag.
42    pub fn set_only(mut self) -> Self {
43        self.flags = (self.flags - SettingsBindFlags::GET) | SettingsBindFlags::SET;
44        self
45    }
46
47    // rustdoc-stripper-ignore-next
48    /// Unsets the default [`SET`][crate::SettingsBindFlags::SET] flag.
49    pub fn get_only(mut self) -> Self {
50        self.flags = (self.flags - SettingsBindFlags::SET) | SettingsBindFlags::GET;
51        self
52    }
53
54    // rustdoc-stripper-ignore-next
55    /// Set the binding flags to [`NO_SENSITIVITY`][crate::SettingsBindFlags::NO_SENSITIVITY].
56    pub fn no_sensitivity(mut self) -> Self {
57        self.flags |= SettingsBindFlags::NO_SENSITIVITY;
58        self
59    }
60
61    // rustdoc-stripper-ignore-next
62    /// Set the binding flags to [`GET_NO_CHANGES`][crate::SettingsBindFlags::GET_NO_CHANGES].
63    pub fn get_no_changes(mut self) -> Self {
64        self.flags |= SettingsBindFlags::GET_NO_CHANGES;
65        self
66    }
67
68    // rustdoc-stripper-ignore-next
69    /// Set the binding flags to [`INVERT_BOOLEAN`][crate::SettingsBindFlags::INVERT_BOOLEAN].
70    pub fn invert_boolean(mut self) -> Self {
71        self.flags |= SettingsBindFlags::INVERT_BOOLEAN;
72        self
73    }
74
75    #[doc(alias = "get_mapping")]
76    pub fn mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>(
77        mut self,
78        f: F,
79    ) -> Self {
80        self.get_mapping = Some(Box::new(f));
81        self
82    }
83
84    pub fn set_mapping<
85        F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static,
86    >(
87        mut self,
88        f: F,
89    ) -> Self {
90        self.set_mapping = Some(Box::new(f));
91        self
92    }
93
94    pub fn build(self) {
95        type Mappings = (
96            Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
97            Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
98        );
99        unsafe extern "C" fn bind_with_mapping_get_trampoline(
100            value: *mut glib::gobject_ffi::GValue,
101            variant: *mut glib::ffi::GVariant,
102            user_data: glib::ffi::gpointer,
103        ) -> glib::ffi::gboolean {
104            let user_data = &*(user_data as *const Mappings);
105            let f = user_data.0.as_ref().unwrap();
106            let value = &mut *(value as *mut glib::Value);
107            if let Some(v) = f(&from_glib_borrow(variant), value.type_()) {
108                *value = v;
109                true
110            } else {
111                false
112            }
113            .into_glib()
114        }
115        unsafe extern "C" fn bind_with_mapping_set_trampoline(
116            value: *const glib::gobject_ffi::GValue,
117            variant_type: *const glib::ffi::GVariantType,
118            user_data: glib::ffi::gpointer,
119        ) -> *mut glib::ffi::GVariant {
120            let user_data = &*(user_data as *const Mappings);
121            let f = user_data.1.as_ref().unwrap();
122            let value = &*(value as *const glib::Value);
123            f(value, from_glib_none(variant_type)).into_glib_ptr()
124        }
125        unsafe extern "C" fn destroy_closure(ptr: *mut libc::c_void) {
126            let _ = Box::<Mappings>::from_raw(ptr as *mut _);
127        }
128
129        if self.get_mapping.is_none() && self.set_mapping.is_none() {
130            unsafe {
131                ffi::g_settings_bind(
132                    self.settings.to_glib_none().0,
133                    self.key.to_glib_none().0,
134                    self.object.to_glib_none().0,
135                    self.property.to_glib_none().0,
136                    self.flags.into_glib(),
137                );
138            }
139        } else {
140            let get_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
141                if self.get_mapping.is_none() {
142                    None
143                } else {
144                    Some(bind_with_mapping_get_trampoline)
145                };
146            let set_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
147                if self.set_mapping.is_none() {
148                    None
149                } else {
150                    Some(bind_with_mapping_set_trampoline)
151                };
152            let mappings: Mappings = (self.get_mapping, self.set_mapping);
153            unsafe {
154                ffi::g_settings_bind_with_mapping(
155                    self.settings.to_glib_none().0,
156                    self.key.to_glib_none().0,
157                    self.object.to_glib_none().0,
158                    self.property.to_glib_none().0,
159                    self.flags.into_glib(),
160                    get_trampoline,
161                    set_trampoline,
162                    Box::into_raw(Box::new(mappings)) as *mut libc::c_void,
163                    Some(destroy_closure),
164                )
165            }
166        }
167    }
168}
169
170pub trait SettingsExtManual: IsA<Settings> {
171    fn get<U: FromVariant>(&self, key: &str) -> U {
172        let val = self.value(key);
173        FromVariant::from_variant(&val).unwrap_or_else(|| {
174            panic!(
175                "Type mismatch: Expected '{}' got '{}'",
176                U::static_variant_type().as_str(),
177                val.type_()
178            )
179        })
180    }
181
182    fn set(&self, key: &str, value: impl Into<Variant>) -> Result<(), BoolError> {
183        self.set_value(key, &value.into())
184    }
185
186    #[doc(alias = "g_settings_get_strv")]
187    #[doc(alias = "get_strv")]
188    fn strv(&self, key: &str) -> StrV {
189        unsafe {
190            FromGlibPtrContainer::from_glib_full(ffi::g_settings_get_strv(
191                self.as_ref().to_glib_none().0,
192                key.to_glib_none().0,
193            ))
194        }
195    }
196
197    #[doc(alias = "g_settings_set_strv")]
198    fn set_strv(&self, key: &str, value: impl IntoStrV) -> Result<(), glib::error::BoolError> {
199        unsafe {
200            value.run_with_strv(|value| {
201                glib::result_from_gboolean!(
202                    ffi::g_settings_set_strv(
203                        self.as_ref().to_glib_none().0,
204                        key.to_glib_none().0,
205                        value.as_ptr() as *mut _,
206                    ),
207                    "Can't set readonly key"
208                )
209            })
210        }
211    }
212
213    #[doc(alias = "g_settings_bind")]
214    #[doc(alias = "g_settings_bind_with_mapping")]
215    fn bind<'a, P: IsA<glib::Object>>(
216        &'a self,
217        key: &'a str,
218        object: &'a P,
219        property: &'a str,
220    ) -> BindingBuilder<'a> {
221        BindingBuilder {
222            settings: self.upcast_ref(),
223            key,
224            object: object.upcast_ref(),
225            property,
226            flags: SettingsBindFlags::DEFAULT,
227            get_mapping: None,
228            set_mapping: None,
229        }
230    }
231}
232
233impl<O: IsA<Settings>> SettingsExtManual for O {}
234
235#[cfg(test)]
236mod test {
237    use std::{env::set_var, process::Command, str::from_utf8, sync::Once};
238
239    use super::*;
240
241    static INIT: Once = Once::new();
242
243    fn set_env() {
244        INIT.call_once(|| {
245            let tmp_dir = glib::mkdtemp("gio-rs-test-schemas-XXXXXX").unwrap();
246
247            let output = Command::new("glib-compile-schemas")
248                .args([
249                    &format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
250                    "--targetdir",
251                    tmp_dir.to_str().unwrap(),
252                ])
253                .output()
254                .unwrap();
255
256            if !output.status.success() {
257                println!("Failed to generate GSchema!");
258                println!(
259                    "glib-compile-schemas stdout: {}",
260                    from_utf8(&output.stdout).unwrap()
261                );
262                println!(
263                    "glib-compile-schemas stderr: {}",
264                    from_utf8(&output.stderr).unwrap()
265                );
266                panic!("Can't test without GSchemas!");
267            }
268
269            set_var("GSETTINGS_SCHEMA_DIR", tmp_dir);
270            set_var("GSETTINGS_BACKEND", "memory");
271        });
272    }
273
274    #[test]
275    #[serial_test::serial]
276    fn string_get() {
277        set_env();
278        let settings = Settings::new("com.github.gtk-rs.test");
279        assert_eq!(settings.get::<String>("test-string").as_str(), "Good");
280    }
281
282    #[test]
283    #[serial_test::serial]
284    fn bool_set_get() {
285        set_env();
286        let settings = Settings::new("com.github.gtk-rs.test");
287        settings.set("test-bool", false).unwrap();
288        assert!(!settings.get::<bool>("test-bool"));
289    }
290
291    #[test]
292    #[should_panic]
293    #[serial_test::serial]
294    fn wrong_type() {
295        set_env();
296        let settings = Settings::new("com.github.gtk-rs.test");
297        settings.get::<u8>("test-string");
298    }
299}