1 // Take a look at the license at the top of the repository in the LICENSE file. 2 3 use crate::prelude::*; 4 use crate::{Settings, SettingsBindFlags}; 5 use glib::translate::{from_glib_borrow, from_glib_none, IntoGlib, ToGlibPtr}; 6 use glib::variant::FromVariant; 7 use glib::{BoolError, IsA, ToVariant}; 8 9 #[must_use] 10 pub struct BindingBuilder<'a> { 11 settings: &'a Settings, 12 key: &'a str, 13 object: &'a glib::Object, 14 property: &'a str, 15 flags: SettingsBindFlags, 16 get_mapping: Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>, 17 set_mapping: Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>, 18 } 19 20 impl<'a> BindingBuilder<'a> { flags(mut self, flags: SettingsBindFlags) -> Self21 pub fn flags(mut self, flags: SettingsBindFlags) -> Self { 22 self.flags = flags; 23 self 24 } 25 26 #[doc(alias = "get_mapping")] mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>( mut self, f: F, ) -> Self27 pub fn mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>( 28 mut self, 29 f: F, 30 ) -> Self { 31 self.get_mapping = Some(Box::new(f)); 32 self 33 } 34 set_mapping< F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static, >( mut self, f: F, ) -> Self35 pub fn set_mapping< 36 F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static, 37 >( 38 mut self, 39 f: F, 40 ) -> Self { 41 self.set_mapping = Some(Box::new(f)); 42 self 43 } 44 build(self)45 pub fn build(self) { 46 type Mappings = ( 47 Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>, 48 Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>, 49 ); 50 unsafe extern "C" fn bind_with_mapping_get_trampoline( 51 value: *mut glib::gobject_ffi::GValue, 52 variant: *mut glib::ffi::GVariant, 53 user_data: glib::ffi::gpointer, 54 ) -> glib::ffi::gboolean { 55 let user_data = &*(user_data as *const Mappings); 56 let f = user_data.0.as_ref().unwrap(); 57 let value = &mut *(value as *mut glib::Value); 58 if let Some(v) = f(&*from_glib_borrow(variant), value.type_()) { 59 *value = v; 60 true 61 } else { 62 false 63 } 64 .into_glib() 65 } 66 unsafe extern "C" fn bind_with_mapping_set_trampoline( 67 value: *const glib::gobject_ffi::GValue, 68 variant_type: *const glib::ffi::GVariantType, 69 user_data: glib::ffi::gpointer, 70 ) -> *mut glib::ffi::GVariant { 71 let user_data = &*(user_data as *const Mappings); 72 let f = user_data.1.as_ref().unwrap(); 73 let value = &*(value as *const glib::Value); 74 f(value, from_glib_none(variant_type)).to_glib_full() 75 } 76 unsafe extern "C" fn destroy_closure(ptr: *mut libc::c_void) { 77 Box::<Mappings>::from_raw(ptr as *mut _); 78 } 79 80 if self.get_mapping.is_none() && self.set_mapping.is_none() { 81 unsafe { 82 ffi::g_settings_bind( 83 self.settings.to_glib_none().0, 84 self.key.to_glib_none().0, 85 self.object.to_glib_none().0, 86 self.property.to_glib_none().0, 87 self.flags.into_glib(), 88 ); 89 } 90 } else { 91 let get_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> = 92 if self.get_mapping.is_none() { 93 None 94 } else { 95 Some(bind_with_mapping_get_trampoline) 96 }; 97 let set_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> = 98 if self.set_mapping.is_none() { 99 None 100 } else { 101 Some(bind_with_mapping_set_trampoline) 102 }; 103 let mappings: Mappings = (self.get_mapping, self.set_mapping); 104 unsafe { 105 ffi::g_settings_bind_with_mapping( 106 self.settings.to_glib_none().0, 107 self.key.to_glib_none().0, 108 self.object.to_glib_none().0, 109 self.property.to_glib_none().0, 110 self.flags.into_glib(), 111 get_trampoline, 112 set_trampoline, 113 Box::into_raw(Box::new(mappings)) as *mut libc::c_void, 114 Some(destroy_closure), 115 ) 116 } 117 } 118 } 119 } 120 121 pub trait SettingsExtManual { get<U: FromVariant>(&self, key: &str) -> U122 fn get<U: FromVariant>(&self, key: &str) -> U; 123 set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>124 fn set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>; 125 126 #[doc(alias = "g_settings_bind")] 127 #[doc(alias = "g_settings_bind_with_mapping")] bind<'a, P: IsA<glib::Object>>( &'a self, key: &'a str, object: &'a P, property: &'a str, ) -> BindingBuilder<'a>128 fn bind<'a, P: IsA<glib::Object>>( 129 &'a self, 130 key: &'a str, 131 object: &'a P, 132 property: &'a str, 133 ) -> BindingBuilder<'a>; 134 } 135 136 impl<O: IsA<Settings>> SettingsExtManual for O { get<U: FromVariant>(&self, key: &str) -> U137 fn get<U: FromVariant>(&self, key: &str) -> U { 138 let val = self.value(key); 139 FromVariant::from_variant(&val).unwrap_or_else(|| { 140 panic!( 141 "Type mismatch: Expected '{}' got '{}'", 142 U::static_variant_type().to_str(), 143 val.type_() 144 ) 145 }) 146 } 147 set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>148 fn set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError> { 149 self.set_value(key, &value.to_variant()) 150 } 151 bind<'a, P: IsA<glib::Object>>( &'a self, key: &'a str, object: &'a P, property: &'a str, ) -> BindingBuilder<'a>152 fn bind<'a, P: IsA<glib::Object>>( 153 &'a self, 154 key: &'a str, 155 object: &'a P, 156 property: &'a str, 157 ) -> BindingBuilder<'a> { 158 BindingBuilder { 159 settings: self.upcast_ref(), 160 key, 161 object: object.upcast_ref(), 162 property, 163 flags: SettingsBindFlags::DEFAULT, 164 get_mapping: None, 165 set_mapping: None, 166 } 167 } 168 } 169 170 #[cfg(test)] 171 mod test { 172 use super::*; 173 use std::env::set_var; 174 use std::process::Command; 175 use std::str::from_utf8; 176 use std::sync::Once; 177 178 static INIT: Once = Once::new(); 179 set_env()180 fn set_env() { 181 INIT.call_once(|| { 182 let output = Command::new("glib-compile-schemas") 183 .args(&[ 184 &format!("{}/tests", env!("CARGO_MANIFEST_DIR")), 185 "--targetdir", 186 env!("OUT_DIR"), 187 ]) 188 .output() 189 .unwrap(); 190 191 if !output.status.success() { 192 println!("Failed to generate GSchema!"); 193 println!( 194 "glib-compile-schemas stdout: {}", 195 from_utf8(&output.stdout).unwrap() 196 ); 197 println!( 198 "glib-compile-schemas stderr: {}", 199 from_utf8(&output.stderr).unwrap() 200 ); 201 panic!("Can't test without GSchemas!"); 202 } 203 204 set_var("GSETTINGS_SCHEMA_DIR", env!("OUT_DIR")); 205 set_var("GSETTINGS_BACKEND", "memory"); 206 }); 207 } 208 209 #[test] 210 #[serial_test::serial] string_get()211 fn string_get() { 212 set_env(); 213 let settings = Settings::new("com.github.gtk-rs.test"); 214 assert_eq!(settings.get::<String>("test-string").as_str(), "Good"); 215 } 216 217 #[test] 218 #[serial_test::serial] bool_set_get()219 fn bool_set_get() { 220 set_env(); 221 let settings = Settings::new("com.github.gtk-rs.test"); 222 settings.set("test-bool", &false).unwrap(); 223 assert!(!settings.get::<bool>("test-bool")); 224 } 225 226 #[test] 227 #[should_panic] 228 #[serial_test::serial] wrong_type()229 fn wrong_type() { 230 set_env(); 231 let settings = Settings::new("com.github.gtk-rs.test"); 232 settings.get::<u8>("test-string"); 233 } 234 } 235