diff --git a/examples/src/bin/basic_subclass.rs b/examples/src/bin/basic_subclass.rs index 9af082329ded..8214816bc70c 100644 --- a/examples/src/bin/basic_subclass.rs +++ b/examples/src/bin/basic_subclass.rs @@ -40,6 +40,7 @@ mod imp_win { const NAME: &'static str = "SimpleWindow"; type Type = super::SimpleWindow; type ParentType = gtk::ApplicationWindow; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; @@ -128,6 +129,7 @@ mod imp_app { const NAME: &'static str = "SimpleApplication"; type Type = super::SimpleApplication; type ParentType = gtk::Application; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; diff --git a/examples/src/bin/composite_template.rs b/examples/src/bin/composite_template.rs index aa5eb4dbe4b6..75f12aad0737 100644 --- a/examples/src/bin/composite_template.rs +++ b/examples/src/bin/composite_template.rs @@ -29,6 +29,7 @@ mod imp { const NAME: &'static str = "ExApplicationWindow"; type Type = super::ExApplicationWindow; type ParentType = gtk::ApplicationWindow; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; diff --git a/examples/src/bin/gio_task.rs b/examples/src/bin/gio_task.rs index db2a56e0dfb9..77e26778cc3c 100644 --- a/examples/src/bin/gio_task.rs +++ b/examples/src/bin/gio_task.rs @@ -27,6 +27,7 @@ mod imp { const NAME: &'static str = "FileSize"; type ParentType = glib::Object; type Instance = subclass::simple::InstanceStruct; + type Interfaces = (); type Class = subclass::simple::ClassStruct; type Type = super::FileSize; glib::object_subclass!(); diff --git a/examples/src/bin/listbox_model.rs b/examples/src/bin/listbox_model.rs index ad42467214c8..14018cb9a79b 100644 --- a/examples/src/bin/listbox_model.rs +++ b/examples/src/bin/listbox_model.rs @@ -34,17 +34,12 @@ mod model { const NAME: &'static str = "Model"; type Type = super::Model; type ParentType = glib::Object; + type Interfaces = (gio::ListModel,); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; glib::object_subclass!(); - // Called right before class_init and allows a GObject to specify - // which interfaces it implement, in this case gio::ListModel - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } - // Called once at the very beginning of instantiation fn new() -> Self { Self(RefCell::new(Vec::new())) @@ -306,48 +301,17 @@ mod row_data { count: RefCell, } - // GObject property definitions for our two values - static PROPERTIES: [subclass::Property; 2] = [ - subclass::Property("name", |name| { - glib::ParamSpec::string( - name, - "Name", - "Name", - None, // Default value - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("count", |name| { - glib::ParamSpec::uint( - name, - "Count", - "Count", - 0, - 100, - 0, // Allowed range and default value - glib::ParamFlags::READWRITE, - ) - }), - ]; - // Basic declaration of our type for the GObject type system impl ObjectSubclass for RowData { const NAME: &'static str = "RowData"; type Type = super::RowData; type ParentType = glib::Object; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; glib::object_subclass!(); - // Called exactly once before the first instantiation of an instance. This - // sets up any type-specific things, in this specific case it installs the - // properties so that GObject knows about their existence and they can be - // used on instances of our type - fn class_init(klass: &mut Self::Class) { - klass.install_properties(&PROPERTIES); - } - // Called once at the very beginning of instantiation of each instance and // creates the data structure that contains all our state fn new() -> Self { @@ -365,17 +329,47 @@ mod row_data { // This maps between the GObject properties and our internal storage of the // corresponding values of the properties. impl ObjectImpl for RowData { - fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::string( + "name", + "Name", + "Name", + None, // Default value + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::uint( + "count", + "Count", + "Count", + 0, + 100, + 0, // Allowed range and default value + glib::ParamFlags::READWRITE, + ), + ] + }); + + PROPERTIES.as_ref() + } - match *prop { - subclass::Property("name", ..) => { + fn set_property( + &self, + _obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.get_name() { + "name" => { let name = value .get() .expect("type conformity checked by `Object::set_property`"); self.name.replace(name); } - subclass::Property("count", ..) => { + "count" => { let count = value .get_some() .expect("type conformity checked by `Object::set_property`"); @@ -385,12 +379,15 @@ mod row_data { } } - fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("name", ..) => self.name.borrow().to_value(), - subclass::Property("count", ..) => self.count.borrow().to_value(), + fn get_property( + &self, + _obj: &Self::Type, + _id: usize, + pspec: &glib::ParamSpec, + ) -> glib::Value { + match pspec.get_name() { + "name" => self.name.borrow().to_value(), + "count" => self.count.borrow().to_value(), _ => unimplemented!(), } } diff --git a/gio/src/read_input_stream.rs b/gio/src/read_input_stream.rs index bbf65ceccd61..7114cfc3c200 100644 --- a/gio/src/read_input_stream.rs +++ b/gio/src/read_input_stream.rs @@ -25,6 +25,7 @@ mod imp { const NAME: &'static str = "ReadInputStream"; type Type = super::ReadInputStream; type ParentType = InputStream; + type Interfaces = (crate::Seekable,); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; @@ -35,10 +36,6 @@ mod imp { read: RefCell::new(None), } } - - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } } impl ObjectImpl for ReadInputStream {} diff --git a/gio/src/subclass/application.rs b/gio/src/subclass/application.rs index 87cb188bb1a7..f22e0bdee220 100644 --- a/gio/src/subclass/application.rs +++ b/gio/src/subclass/application.rs @@ -493,6 +493,7 @@ mod tests { const NAME: &'static str = "SimpleApplication"; type Type = super::SimpleApplication; type ParentType = Application; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; diff --git a/gio/src/subclass/input_stream.rs b/gio/src/subclass/input_stream.rs index 48c5988da24a..1e068cf4c2c2 100644 --- a/gio/src/subclass/input_stream.rs +++ b/gio/src/subclass/input_stream.rs @@ -269,6 +269,7 @@ mod tests { const NAME: &'static str = "SimpleInputStream"; type Type = super::SimpleInputStream; type ParentType = InputStream; + type Interfaces = (crate::Seekable,); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; @@ -279,10 +280,6 @@ mod tests { pos: RefCell::new(0), } } - - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } } impl ObjectImpl for SimpleInputStream {} diff --git a/gio/src/subclass/output_stream.rs b/gio/src/subclass/output_stream.rs index 9d95bbdbbe15..6f69ee35011c 100644 --- a/gio/src/subclass/output_stream.rs +++ b/gio/src/subclass/output_stream.rs @@ -331,6 +331,7 @@ mod tests { const NAME: &'static str = "SimpleOutputStream"; type Type = super::SimpleOutputStream; type ParentType = OutputStream; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; diff --git a/gio/src/task.rs b/gio/src/task.rs index 7832ba616dae..2f710baa5b43 100644 --- a/gio/src/task.rs +++ b/gio/src/task.rs @@ -129,6 +129,7 @@ mod test { const NAME: &'static str = "MySimpleObjectPrivate"; type ParentType = glib::Object; type Instance = subclass::simple::InstanceStruct; + type Interfaces = (); type Class = subclass::simple::ClassStruct; type Type = MySimpleObject; glib::object_subclass!(); diff --git a/gio/src/write_output_stream.rs b/gio/src/write_output_stream.rs index 22e3f8341940..dc5327463400 100644 --- a/gio/src/write_output_stream.rs +++ b/gio/src/write_output_stream.rs @@ -27,6 +27,7 @@ mod imp { const NAME: &'static str = "WriteOutputStream"; type Type = super::WriteOutputStream; type ParentType = OutputStream; + type Interfaces = (crate::Seekable,); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; @@ -37,10 +38,6 @@ mod imp { write: RefCell::new(None), } } - - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } } impl ObjectImpl for WriteOutputStream {} diff --git a/glib/Gir_GObject.toml b/glib/Gir_GObject.toml index 0dcf790de928..1849fc419fb1 100644 --- a/glib/Gir_GObject.toml +++ b/glib/Gir_GObject.toml @@ -12,7 +12,6 @@ trust_return_value_nullability = true generate = [ "GObject.BindingFlags", - "GObject.ParamFlags", "GObject.SignalFlags", ] @@ -22,6 +21,7 @@ ignore = [ manual = [ "GObject.Object", "GObject.Value", + "GObject.ParamFlags", ] [[object]] diff --git a/glib/src/gobject/auto/flags.rs b/glib/src/gobject/auto/flags.rs index 47b5811489cc..2f3b487b02d1 100644 --- a/glib/src/gobject/auto/flags.rs +++ b/glib/src/gobject/auto/flags.rs @@ -68,45 +68,6 @@ impl SetValue for BindingFlags { } } -bitflags! { - pub struct ParamFlags: u32 { - const READABLE = 1; - const WRITABLE = 2; - const READWRITE = 3; - const CONSTRUCT = 4; - const CONSTRUCT_ONLY = 8; - const LAX_VALIDATION = 16; - const STATIC_NAME = 32; - const PRIVATE = 32; - const STATIC_NICK = 64; - const STATIC_BLURB = 128; - const EXPLICIT_NOTIFY = 1073741824; - const DEPRECATED = 2147483648; - } -} - -impl fmt::Display for ParamFlags { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f) - } -} - -#[doc(hidden)] -impl ToGlib for ParamFlags { - type GlibType = gobject_ffi::GParamFlags; - - fn to_glib(&self) -> gobject_ffi::GParamFlags { - self.bits() - } -} - -#[doc(hidden)] -impl FromGlib for ParamFlags { - unsafe fn from_glib(value: gobject_ffi::GParamFlags) -> ParamFlags { - ParamFlags::from_bits_truncate(value) - } -} - bitflags! { pub struct SignalFlags: u32 { const RUN_FIRST = 1; diff --git a/glib/src/gobject/auto/mod.rs b/glib/src/gobject/auto/mod.rs index c9b4d7ac3b49..d34603edf550 100644 --- a/glib/src/gobject/auto/mod.rs +++ b/glib/src/gobject/auto/mod.rs @@ -7,7 +7,6 @@ pub use self::binding::Binding; mod flags; pub use self::flags::BindingFlags; -pub use self::flags::ParamFlags; pub use self::flags::SignalFlags; #[doc(hidden)] diff --git a/glib/src/gobject/flags.rs b/glib/src/gobject/flags.rs new file mode 100644 index 000000000000..f2f722beb201 --- /dev/null +++ b/glib/src/gobject/flags.rs @@ -0,0 +1,41 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::translate::*; + +bitflags::bitflags! { + pub struct ParamFlags: u32 { + const READABLE = 1; + const WRITABLE = 2; + const READWRITE = 3; + const CONSTRUCT = 4; + const CONSTRUCT_ONLY = 8; + const LAX_VALIDATION = 16; + const USER_0 = 128; + const USER_1 = 256; + const USER_2 = 1024; + const USER_3 = 2048; + const USER_4 = 4096; + const USER_5 = 8192; + const USER_6 = 16384; + const USER_7 = 32768; + const USER_8 = 65536; + const EXPLICIT_NOTIFY = 1073741824; + const DEPRECATED = 2147483648; + } +} + +#[doc(hidden)] +impl ToGlib for ParamFlags { + type GlibType = gobject_ffi::GParamFlags; + + fn to_glib(&self) -> gobject_ffi::GParamFlags { + self.bits() + } +} + +#[doc(hidden)] +impl FromGlib for ParamFlags { + unsafe fn from_glib(value: gobject_ffi::GParamFlags) -> ParamFlags { + ParamFlags::from_bits_truncate(value) + } +} diff --git a/glib/src/gobject/mod.rs b/glib/src/gobject/mod.rs index 4ed819256fa5..cf8f40c4479d 100644 --- a/glib/src/gobject/mod.rs +++ b/glib/src/gobject/mod.rs @@ -2,8 +2,10 @@ //! GObject bindings -pub mod auto; +mod auto; mod binding; +mod flags; pub use self::auto::*; +pub use self::flags::*; //pub use self::auto::functions::*; diff --git a/glib/src/subclass/boxed.rs b/glib/src/subclass/boxed.rs index c484234bd13c..0cd57a3a095d 100644 --- a/glib/src/subclass/boxed.rs +++ b/glib/src/subclass/boxed.rs @@ -3,8 +3,6 @@ //! Module for registering boxed types for Rust types. use crate::translate::*; -use crate::value::*; -use std::ops; /// Trait for defining boxed types. /// @@ -66,71 +64,12 @@ pub fn register_boxed_type() -> crate::Type { } } -/// Wrapper struct for storing any `BoxedType` in `glib::Value`. -/// -/// Instead of this the [`GBoxed!`] derive macro can be used to -/// directly implement the relevant traits on the type itself. -/// -/// [`GBoxed!`]: ../../derive.GBoxed.html -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Boxed(pub T); - -impl ops::Deref for Boxed { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl ops::DerefMut for Boxed { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl crate::StaticType for Boxed { - fn static_type() -> crate::Type { - T::get_type() - } -} - -impl SetValue for Boxed { - unsafe fn set_value(value: &mut Value, this: &Self) { - let ptr: *mut Boxed = Box::into_raw(Box::new(this.clone())); - gobject_ffi::g_value_take_boxed(value.to_glib_none_mut().0, ptr as *mut _); - } -} - -impl SetValueOptional for Boxed { - unsafe fn set_value_optional(value: &mut Value, this: Option<&Self>) { - let this = this.expect("None not allowed"); - let ptr: *mut Boxed = Box::into_raw(Box::new(this.clone())); - gobject_ffi::g_value_take_boxed(value.to_glib_none_mut().0, ptr as *mut _); - } -} - -impl<'a, T: BoxedType> FromValueOptional<'a> for &'a Boxed { - unsafe fn from_value_optional(value: &'a Value) -> Option { - let ptr = gobject_ffi::g_value_get_boxed(value.to_glib_none().0); - assert!(!ptr.is_null()); - Some(&*(ptr as *mut Boxed)) - } -} - -impl<'a, T: BoxedType> FromValue<'a> for &'a Boxed { - unsafe fn from_value(value: &'a Value) -> Self { - let ptr = gobject_ffi::g_value_get_boxed(value.to_glib_none().0); - assert!(!ptr.is_null()); - &*(ptr as *mut Boxed) - } -} - #[cfg(test)] mod test { use super::*; // GBoxed macro assumes 'glib' is in scope use crate as glib; + use crate::value::ToValue; #[derive(Clone, Debug, PartialEq, Eq, glib::GBoxed)] #[gboxed(type_name = "MyBoxed")] @@ -141,16 +80,6 @@ mod test { assert_ne!(crate::Type::Invalid, MyBoxed::get_type()); } - #[test] - fn test_value_boxed() { - assert_ne!(crate::Type::Invalid, MyBoxed::get_type()); - - let b = Boxed(MyBoxed(String::from("abc"))); - let v = b.to_value(); - let b2 = v.get_some::<&Boxed>().unwrap(); - assert_eq!(&b, b2); - } - #[test] fn test_value() { assert_ne!(crate::Type::Invalid, MyBoxed::get_type()); diff --git a/glib/src/subclass/interface.rs b/glib/src/subclass/interface.rs index 21ca61d539bc..dddb708a6457 100644 --- a/glib/src/subclass/interface.rs +++ b/glib/src/subclass/interface.rs @@ -1,9 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use super::{InitializingType, Property}; +use super::{InitializingType, Signal}; use crate::translate::*; -use crate::{IsA, Object, ObjectExt, SignalFlags, StaticType, Type, Value}; -use std::borrow::Borrow; +use crate::{IsA, Object, ObjectExt, ParamSpec, StaticType, Type}; use std::marker; use std::mem; @@ -86,12 +85,23 @@ pub trait ObjectInterface: Sized + 'static { /// /// This is called after `type_init` and before the first implementor /// of the interface is created. Interfaces can use this to do interface- - /// specific initialization, e.g. for installing properties or signals - /// on the interface, and for setting default implementations of interface - /// functions. + /// specific initialization, e.g. for installing signals on the interface, + /// and for setting default implementations of interface functions. /// /// Optional fn interface_init(&mut self) {} + + /// Properties installed for this interface. + /// + /// All implementors of the interface must provide these properties. + fn properties() -> &'static [ParamSpec] { + &[] + } + + /// Signals installed for this interface. + fn signals() -> &'static [Signal] { + &[] + } } pub trait ObjectInterfaceExt: ObjectInterface { @@ -109,137 +119,6 @@ pub trait ObjectInterfaceExt: ObjectInterface { &*(interface as *const Self) } } - - /// Install properties on the interface. - /// - /// All implementors of the interface must provide these properties. - fn install_properties<'a, T: Borrow>>(&mut self, properties: &[T]) { - if properties.is_empty() { - return; - } - - for property in properties { - let property = property.borrow(); - let pspec = (property.1)(property.0); - unsafe { - gobject_ffi::g_object_interface_install_property( - self as *mut Self as *mut _, - pspec.to_glib_none().0, - ); - } - } - } - - /// Add a new signal to the interface. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - fn add_signal(&mut self, name: &str, flags: SignalFlags, arg_types: &[Type], ret_type: Type) { - unsafe { - super::types::add_signal( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - ); - } - } - - /// Add a new signal with class handler to the interface. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The class handler will be called during the signal emission at the corresponding stage. - fn add_signal_with_class_handler( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, - ) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_class_handler( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - class_handler, - ); - } - } - - /// Add a new signal with accumulator to the interface. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The accumulator function is used for accumulating the return values of - /// multiple signal handlers. The new value is passed as second argument and - /// should be combined with the old value in the first argument. If no further - /// signal handlers should be called, `false` should be returned. - fn add_signal_with_accumulator( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - accumulator: F, - ) where - F: Fn(&super::SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_accumulator( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - accumulator, - ); - } - } - - /// Add a new signal with accumulator and class handler to the interface. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The accumulator function is used for accumulating the return values of - /// multiple signal handlers. The new value is passed as second argument and - /// should be combined with the old value in the first argument. If no further - /// signal handlers should be called, `false` should be returned. - /// - /// The class handler will be called during the signal emission at the corresponding stage. - fn add_signal_with_class_handler_and_accumulator( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, - accumulator: G, - ) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, - G: Fn(&super::SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_class_handler_and_accumulator( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - class_handler, - accumulator, - ); - } - } } impl ObjectInterfaceExt for T {} @@ -249,6 +128,21 @@ unsafe extern "C" fn interface_init( _klass_data: ffi::gpointer, ) { let iface = &mut *(klass as *mut T); + + let pspecs = ::properties(); + for pspec in pspecs { + gobject_ffi::g_object_interface_install_property( + iface as *mut T as *mut _, + pspec.to_glib_none().0, + ); + } + + let type_ = T::get_type(); + let signals = ::signals(); + for signal in signals { + signal.register(type_); + } + iface.interface_init(); } diff --git a/glib/src/subclass/mod.rs b/glib/src/subclass/mod.rs index bacc36e1f887..96fe17bc5323 100644 --- a/glib/src/subclass/mod.rs +++ b/glib/src/subclass/mod.rs @@ -56,39 +56,6 @@ //! mod imp { //! use super::*; //! -//! // Static array for defining the properties of the new type. -//! static PROPERTIES: [subclass::Property; 3] = [ -//! subclass::Property("name", |name| { -//! glib::ParamSpec::string( -//! name, -//! "Name", -//! "Name of this object", -//! None, -//! glib::ParamFlags::READWRITE, -//! ) -//! }), -//! subclass::Property("animal", |name| { -//! glib::ParamSpec::enum_( -//! name, -//! "Animal", -//! "Animal", -//! Animal::static_type(), -//! Animal::default() as i32, -//! glib::ParamFlags::READWRITE, -//! ) -//! }), -//! subclass::Property("flags", |name| { -//! glib::ParamSpec::flags( -//! name, -//! "Flags", -//! "Flags", -//! MyFlags::static_type(), -//! MyFlags::default().bits(), -//! glib::ParamFlags::READWRITE, -//! ) -//! }), -//! ]; -//! //! // This is the struct containing all state carried with //! // the new type. Generally this has to make use of //! // interior mutability. @@ -109,6 +76,9 @@ //! type Type = super::SimpleObject; //! type ParentType = glib::Object; //! +//! // Interfaces this type implements +//! type Interfaces = (); +//! //! // The C/FFI instance and class structs. The simple ones //! // are enough in most cases and more is only needed to //! // expose public instance fields to C APIs or to provide @@ -119,14 +89,6 @@ //! // This macro defines some boilerplate. //! glib::object_subclass!(); //! -//! // Called right before the first time an instance of the new -//! // type is created. Here class specific settings can be performed, -//! // including installation of properties and registration of signals -//! // for the new type. -//! fn class_init(klass: &mut Self::Class) { -//! klass.install_properties(&PROPERTIES); -//! } -//! //! // Called every time a new instance is created. This should return //! // a new instance of our type with its basic values. //! fn new() -> Self { @@ -140,25 +102,57 @@ //! //! // Trait that is used to override virtual methods of glib::Object. //! impl ObjectImpl for SimpleObject { +//! // Called once in the very beginning to list all properties of this class. +//! fn properties() -> &'static [glib::ParamSpec] { +//! use once_cell::sync::Lazy; +//! static PROPERTIES: Lazy> = Lazy::new(|| { +//! vec![ +//! glib::ParamSpec::string( +//! "name", +//! "Name", +//! "Name of this object", +//! None, +//! glib::ParamFlags::READWRITE, +//! ), +//! glib::ParamSpec::enum_( +//! "animal", +//! "Animal", +//! "Animal", +//! Animal::static_type(), +//! Animal::default() as i32, +//! glib::ParamFlags::READWRITE, +//! ), +//! glib::ParamSpec::flags( +//! "flags", +//! "Flags", +//! "Flags", +//! MyFlags::static_type(), +//! MyFlags::default().bits(), +//! glib::ParamFlags::READWRITE, +//! ), +//! ] +//! }); +//! +//! PROPERTIES.as_ref() +//! } +//! //! // Called whenever a property is set on this instance. The id //! // is the same as the index of the property in the PROPERTIES array. -//! fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) { -//! let prop = &PROPERTIES[id]; -//! -//! match *prop { -//! subclass::Property("name", ..) => { -//! let name = value +//! fn set_property(&self, _obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { +//! match pspec.get_name() { +//! "name" => { +//! let name = value //! .get() //! .expect("type conformity checked by `Object::set_property`"); //! self.name.replace(name); //! }, -//! subclass::Property("animal", ..) => { +//! "animal" => { //! let animal = value //! .get() //! .expect("type conformity checked by `Object::set_property`"); //! self.animal.replace(animal.unwrap()); //! }, -//! subclass::Property("flags", ..) => { +//! "flags" => { //! let flags = value //! .get() //! .expect("type conformity checked by `Object::set_property`"); @@ -170,20 +164,18 @@ //! //! // Called whenever a property is retrieved from this instance. The id //! // is the same as the index of the property in the PROPERTIES array. -//! fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value { -//! let prop = &PROPERTIES[id]; -//! -//! match *prop { -//! subclass::Property("name", ..) => self.name.borrow().to_value(), -//! subclass::Property("animal", ..) => self.animal.get().to_value(), -//! subclass::Property("flags", ..) => self.flags.get().to_value(), +//! fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { +//! match pspec.get_name() { +//! "name" => self.name.borrow().to_value(), +//! "animal" => self.animal.get().to_value(), +//! "flags" => self.flags.get().to_value(), //! _ => unimplemented!(), //! } //! } //! //! // Called right after construction of the instance. //! fn constructed(&self, obj: &Self::Type) { -//! // Chain up to the parent type's implementation of this virtual +//! // Chain up to the parent type's implementation of this virtual //! // method. //! self.parent_constructed(obj); //! @@ -262,6 +254,8 @@ pub mod object; #[macro_use] pub mod boxed; +pub mod signal; + pub mod prelude { //! Prelude that re-exports all important traits from this crate. pub use super::boxed::BoxedType; @@ -274,8 +268,5 @@ pub mod prelude { pub use self::boxed::register_boxed_type; pub use self::interface::register_interface; -pub use self::object::Property; -pub use self::types::{ - register_type, InitializingObject, InitializingType, SignalClassHandlerToken, - SignalInvocationHint, TypeData, -}; +pub use self::signal::{Signal, SignalClassHandlerToken, SignalId, SignalInvocationHint}; +pub use self::types::{register_type, InitializingObject, InitializingType, TypeData}; diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index 17f59d149a6e..ca2134412376 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -4,10 +4,9 @@ //! or implementing virtual methods of it. use super::prelude::*; +use super::Signal; use crate::translate::*; -use crate::{Cast, Object, ObjectType, SignalFlags, Type, Value}; -use std::borrow::Borrow; -use std::fmt; +use crate::{Cast, Object, ObjectExt, ObjectType, ParamSpec, StaticType, ToValue, Value}; use std::mem; use std::ptr; @@ -15,11 +14,21 @@ use std::ptr; /// /// This allows overriding the virtual methods of `glib::Object`. pub trait ObjectImpl: ObjectSubclass + ObjectImplExt { + /// Properties installed for this type. + fn properties() -> &'static [ParamSpec] { + &[] + } + + /// Signals installed for this type. + fn signals() -> &'static [Signal] { + &[] + } + /// Property setter. /// /// This is called whenever the property of this specific subclass with the /// given index is set. The new value is passed as `glib::Value`. - fn set_property(&self, _obj: &Self::Type, _id: usize, _value: &Value) { + fn set_property(&self, _obj: &Self::Type, _id: usize, _value: &Value, _pspec: &ParamSpec) { unimplemented!() } @@ -27,7 +36,7 @@ pub trait ObjectImpl: ObjectSubclass + ObjectImplExt { /// /// This is called whenever the property value of the specific subclass with the /// given index should be returned. - fn get_property(&self, _obj: &Self::Type, _id: usize) -> Value { + fn get_property(&self, _obj: &Self::Type, _id: usize, _pspec: &ParamSpec) -> Value { unimplemented!() } @@ -53,7 +62,7 @@ unsafe extern "C" fn get_property( obj: *mut gobject_ffi::GObject, id: u32, value: *mut gobject_ffi::GValue, - _pspec: *mut gobject_ffi::GParamSpec, + pspec: *mut gobject_ffi::GParamSpec, ) { let instance = &*(obj as *mut T::Instance); let imp = instance.get_impl(); @@ -61,6 +70,7 @@ unsafe extern "C" fn get_property( let v = imp.get_property( &from_glib_borrow::<_, Object>(obj).unsafe_cast_ref(), (id - 1) as usize, + &from_glib_borrow(pspec), ); // We first unset the value we get passed in, in case it contained @@ -80,7 +90,7 @@ unsafe extern "C" fn set_property( obj: *mut gobject_ffi::GObject, id: u32, value: *mut gobject_ffi::GValue, - _pspec: *mut gobject_ffi::GParamSpec, + pspec: *mut gobject_ffi::GParamSpec, ) { let instance = &*(obj as *mut T::Instance); let imp = instance.get_impl(); @@ -88,6 +98,7 @@ unsafe extern "C" fn set_property( &from_glib_borrow::<_, Object>(obj).unsafe_cast_ref(), (id - 1) as usize, &*(value as *mut Value), + &from_glib_borrow(pspec), ); } @@ -112,166 +123,10 @@ unsafe extern "C" fn dispose(obj: *mut gobject_ffi::GObject) { } } -/// Definition of a property. -#[derive(Clone)] -pub struct Property<'a>(pub &'a str, pub fn(&str) -> crate::ParamSpec); - -impl<'a> fmt::Debug for Property<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.debug_tuple("Property").field(&self.0).finish() - } -} - /// Extension trait for `glib::Object`'s class struct. /// -/// This contains various class methods and allows subclasses to override the virtual methods. +/// This contains various class methods and allows subclasses to override signal class handlers. pub unsafe trait ObjectClassSubclassExt: Sized + 'static { - /// Install properties on the subclass. - /// - /// The index in the properties array is going to be the index passed to the - /// property setters and getters. - #[doc(alias = "g_object_class_install_properties")] - fn install_properties<'a, T: Borrow>>(&mut self, properties: &[T]) { - if properties.is_empty() { - return; - } - - let mut pspecs = Vec::with_capacity(properties.len()); - - for property in properties { - let property = property.borrow(); - let pspec = (property.1)(property.0); - pspecs.push(pspec); - } - - unsafe { - let mut pspecs_ptrs = Vec::with_capacity(properties.len()); - - pspecs_ptrs.push(ptr::null_mut()); - - for pspec in &pspecs { - pspecs_ptrs.push(pspec.to_glib_none().0); - } - - gobject_ffi::g_object_class_install_properties( - self as *mut _ as *mut gobject_ffi::GObjectClass, - pspecs_ptrs.len() as u32, - pspecs_ptrs.as_mut_ptr(), - ); - } - } - - /// Add a new signal to the subclass. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - fn add_signal(&mut self, name: &str, flags: SignalFlags, arg_types: &[Type], ret_type: Type) { - unsafe { - super::types::add_signal( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - ); - } - } - - /// Add a new signal with class handler to the subclass. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The class handler will be called during the signal emission at the corresponding stage. - fn add_signal_with_class_handler( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, - ) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_class_handler( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - class_handler, - ); - } - } - - /// Add a new signal with accumulator to the subclass. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The accumulator function is used for accumulating the return values of - /// multiple signal handlers. The new value is passed as second argument and - /// should be combined with the old value in the first argument. If no further - /// signal handlers should be called, `false` should be returned. - fn add_signal_with_accumulator( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - accumulator: F, - ) where - F: Fn(&super::SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_accumulator( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - accumulator, - ); - } - } - - /// Add a new signal with accumulator and class handler to the subclass. - /// - /// This can be emitted later by `glib::Object::emit` and external code - /// can connect to the signal to get notified about emissions. - /// - /// The accumulator function is used for accumulating the return values of - /// multiple signal handlers. The new value is passed as second argument and - /// should be combined with the old value in the first argument. If no further - /// signal handlers should be called, `false` should be returned. - /// - /// The class handler will be called during the signal emission at the corresponding stage. - fn add_signal_with_class_handler_and_accumulator( - &mut self, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, - accumulator: G, - ) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, - G: Fn(&super::SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - { - unsafe { - super::types::add_signal_with_class_handler_and_accumulator( - *(self as *mut _ as *mut ffi::GType), - name, - flags, - arg_types, - ret_type, - class_handler, - accumulator, - ); - } - } - fn override_signal_class_handler(&mut self, name: &str, class_handler: F) where F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, @@ -295,6 +150,31 @@ unsafe impl IsSubclassable for Object { klass.get_property = Some(get_property::); klass.constructed = Some(constructed::); klass.dispose = Some(dispose::); + + let pspecs = ::properties(); + if !pspecs.is_empty() { + unsafe { + let mut pspecs_ptrs = Vec::with_capacity(pspecs.len() + 1); + + pspecs_ptrs.push(ptr::null_mut()); + + for pspec in pspecs { + pspecs_ptrs.push(pspec.to_glib_none().0); + } + + gobject_ffi::g_object_class_install_properties( + klass, + pspecs_ptrs.len() as u32, + pspecs_ptrs.as_mut_ptr(), + ); + } + } + + let type_ = T::get_type(); + let signals = ::signals(); + for signal in signals { + signal.register(type_); + } } } @@ -302,11 +182,27 @@ pub trait ObjectImplExt: ObjectSubclass { /// Chain up to the parent class' implementation of `glib::Object::constructed()`. fn parent_constructed(&self, obj: &Self::Type); + /// Chain up to parent class signal handler. fn signal_chain_from_overridden( &self, token: &super::SignalClassHandlerToken, values: &[Value], ) -> Option; + + /// Emit signal by signal id. + fn emit( + &self, + signal: &super::Signal, + args: &[&dyn ToValue], + ) -> Result, crate::BoolError>; + + /// Emit signal with details by signal id. + fn emit_with_details( + &self, + signal: &super::Signal, + details: crate::Quark, + args: &[&dyn ToValue], + ) -> Result, crate::BoolError>; } impl ObjectImplExt for T { @@ -334,6 +230,178 @@ impl ObjectImplExt for T { ) } } + + fn emit( + &self, + signal: &super::Signal, + args: &[&dyn ToValue], + ) -> Result, crate::BoolError> { + unsafe { + let type_ = Self::get_type(); + let instance = self.get_instance(); + + let signal_id = signal.signal_id(); + assert!(type_.is_a(&signal_id.0)); + + let self_v = { + let mut v = Value::uninitialized(); + gobject_ffi::g_value_init(v.to_glib_none_mut().0, Self::get_type().to_glib()); + gobject_ffi::g_value_set_object( + v.to_glib_none_mut().0, + instance.as_object_ref().to_glib_none().0, + ); + v + }; + + let mut args = Iterator::chain( + std::iter::once(self_v), + args.iter().copied().map(ToValue::to_value), + ) + .collect::>(); + + validate_signal_arguments(type_, &signal, &mut args)?; + + let mut return_value = Value::uninitialized(); + if signal.ret_type() != crate::Type::Unit { + gobject_ffi::g_value_init( + return_value.to_glib_none_mut().0, + signal.ret_type().to_glib(), + ); + } + + gobject_ffi::g_signal_emitv( + mut_override(args.as_ptr()) as *mut gobject_ffi::GValue, + signal_id.1, + 0, + return_value.to_glib_none_mut().0, + ); + + if return_value.type_() != crate::Type::Unit + && return_value.type_() != crate::Type::Invalid + { + Ok(Some(return_value)) + } else { + Ok(None) + } + } + } + + fn emit_with_details( + &self, + signal: &super::Signal, + details: crate::Quark, + args: &[&dyn ToValue], + ) -> Result, crate::BoolError> { + assert!(signal.flags().contains(crate::SignalFlags::DETAILED)); + + unsafe { + let type_ = Self::get_type(); + let instance = self.get_instance(); + + let signal_id = signal.signal_id(); + assert!(type_.is_a(&signal_id.0)); + + let self_v = { + let mut v = Value::uninitialized(); + gobject_ffi::g_value_init(v.to_glib_none_mut().0, Self::get_type().to_glib()); + gobject_ffi::g_value_set_object( + v.to_glib_none_mut().0, + instance.as_object_ref().to_glib_none().0, + ); + v + }; + + let mut args = Iterator::chain( + std::iter::once(self_v), + args.iter().copied().map(ToValue::to_value), + ) + .collect::>(); + + validate_signal_arguments(type_, &signal, &mut args)?; + + let mut return_value = Value::uninitialized(); + if signal.ret_type() != crate::Type::Unit { + gobject_ffi::g_value_init( + return_value.to_glib_none_mut().0, + signal.ret_type().to_glib(), + ); + } + + gobject_ffi::g_signal_emitv( + mut_override(args.as_ptr()) as *mut gobject_ffi::GValue, + signal_id.1, + details.to_glib(), + return_value.to_glib_none_mut().0, + ); + + if return_value.type_() != crate::Type::Unit + && return_value.type_() != crate::Type::Invalid + { + Ok(Some(return_value)) + } else { + Ok(None) + } + } + } +} + +fn validate_signal_arguments( + type_: crate::Type, + signal: &super::Signal, + args: &mut [Value], +) -> Result<(), crate::BoolError> { + let arg_types = signal.arg_types(); + + if arg_types.len() != args.len() { + return Err(bool_error!( + "Incompatible number of arguments for signal '{}' of type '{}' (expected {}, got {})", + signal.name(), + type_, + arg_types.len(), + args.len(), + )); + } + + for (i, (arg, param_type)) in Iterator::zip(args.iter_mut(), arg_types.iter()).enumerate() { + if arg.type_().is_a(&Object::static_type()) { + match arg.get::() { + Ok(Some(obj)) => { + if obj.get_type().is_a(¶m_type) { + arg.0.g_type = param_type.to_glib(); + } else { + return Err( + bool_error!( + "Incompatible argument type in argument {} for signal '{}' of type '{}' (expected {}, got {})", + i, + signal.name(), + type_, + param_type, + arg.type_(), + ) + ); + } + } + Ok(None) => { + // If the value is None then the type is compatible too + arg.0.g_type = param_type.to_glib(); + } + Err(_) => unreachable!("property_value type conformity already checked"), + } + } else if *param_type != arg.type_() { + return Err( + bool_error!( + "Incompatible argument type in argument {} for signal '{}' of type '{}' (expected {}, got {})", + i, + signal.name(), + type_, + param_type, + arg.type_(), + ) + ); + } + } + + Ok(()) } #[cfg(test)] @@ -342,7 +410,7 @@ mod test { use super::super::super::subclass; use super::super::super::value::{ToValue, Value}; use super::*; - use crate::prelude::*; + use crate::Type; use std::cell::RefCell; @@ -355,6 +423,7 @@ mod test { const NAME: &'static str = "ChildObject"; type Type = super::ChildObject; type ParentType = Object; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; @@ -377,66 +446,12 @@ mod test { const NAME: &'static str = "SimpleObject"; type Type = super::SimpleObject; type ParentType = Object; + type Interfaces = (DummyInterface,); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; object_subclass!(); - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } - - fn class_init(klass: &mut Self::Class) { - klass.install_properties(&PROPERTIES); - - klass.add_signal( - "name-changed", - SignalFlags::RUN_LAST, - &[String::static_type()], - crate::Type::Unit, - ); - - klass.add_signal_with_class_handler( - "change-name", - SignalFlags::RUN_LAST | SignalFlags::ACTION, - &[String::static_type()], - String::static_type(), - |_, args| { - let obj = args[0] - .get::() - .expect("Failed to get args[0]") - .expect("Failed to get Object from args[0]"); - let new_name = args[1] - .get::() - .expect("Failed to get args[1]") - .expect("Failed to get Object from args[1]"); - let imp = Self::from_instance(&obj); - - let old_name = imp.name.borrow_mut().take(); - *imp.name.borrow_mut() = Some(new_name); - - obj.emit("name-changed", &[&*imp.name.borrow()]) - .expect("Failed to borrow name"); - - Some(old_name.to_value()) - }, - ); - - klass.add_signal( - "create-string", - SignalFlags::RUN_LAST, - &[], - String::static_type(), - ); - - klass.add_signal( - "create-child-object", - SignalFlags::RUN_LAST, - &[], - ChildObject::get_type(), - ); - } - fn new() -> Self { Self { name: RefCell::new(None), @@ -447,11 +462,98 @@ mod test { } impl ObjectImpl for SimpleObject { - fn set_property(&self, obj: &Self::Type, id: usize, value: &Value) { - let prop = &PROPERTIES[id]; + fn signals() -> &'static [super::Signal] { + use once_cell::sync::Lazy; + static SIGNALS: Lazy> = Lazy::new(|| { + vec![ + super::Signal::builder( + "name-changed", + &[String::static_type()], + crate::Type::Unit, + ) + .build(), + super::Signal::builder( + "change-name", + &[String::static_type()], + String::static_type(), + ) + .action() + .class_handler(|_, args| { + let obj = args[0] + .get::() + .expect("Failed to get args[0]") + .expect("Failed to get Object from args[0]"); + let new_name = args[1] + .get::() + .expect("Failed to get args[1]") + .expect("Failed to get Object from args[1]"); + let imp = SimpleObject::from_instance(&obj); + + let old_name = imp.name.borrow_mut().take(); + *imp.name.borrow_mut() = Some(new_name); + + obj.emit("name-changed", &[&*imp.name.borrow()]) + .expect("Failed to borrow name"); + + Some(old_name.to_value()) + }) + .build(), + super::Signal::builder("create-string", &[], String::static_type()).build(), + super::Signal::builder("create-child-object", &[], ChildObject::get_type()) + .build(), + ] + }); + + SIGNALS.as_ref() + } - match *prop { - Property("name", ..) => { + fn properties() -> &'static [ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + crate::ParamSpec::string( + "name", + "Name", + "Name of this object", + None, + crate::ParamFlags::READWRITE, + ), + crate::ParamSpec::string( + "construct-name", + "Construct Name", + "Construct Name of this object", + None, + crate::ParamFlags::READWRITE | crate::ParamFlags::CONSTRUCT_ONLY, + ), + crate::ParamSpec::boolean( + "constructed", + "Constructed", + "True if the constructed() virtual method was called", + false, + crate::ParamFlags::READABLE, + ), + crate::ParamSpec::object( + "child", + "Child", + "Child object", + super::ChildObject::static_type(), + crate::ParamFlags::READWRITE, + ), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &Value, + pspec: &crate::ParamSpec, + ) { + match pspec.get_name() { + "name" => { let name = value .get() .expect("type conformity checked by 'Object::set_property'"); @@ -459,26 +561,29 @@ mod test { obj.emit("name-changed", &[&*self.name.borrow()]) .expect("Failed to borrow name"); } - Property("construct-name", ..) => { + "construct-name" => { let name = value .get() .expect("type conformity checked by 'Object::set_property'"); self.construct_name.replace(name); } - Property("child", ..) => { + "child" => { // not stored, only used to test `set_property` with `Objects` } _ => unimplemented!(), } } - fn get_property(&self, _obj: &Self::Type, id: usize) -> Value { - let prop = &PROPERTIES[id]; - - match *prop { - Property("name", ..) => self.name.borrow().to_value(), - Property("construct-name", ..) => self.construct_name.borrow().to_value(), - Property("constructed", ..) => self.constructed.borrow().to_value(), + fn get_property( + &self, + _obj: &Self::Type, + _id: usize, + pspec: &crate::ParamSpec, + ) -> Value { + match pspec.get_name() { + "name" => self.name.borrow().to_value(), + "construct-name" => self.construct_name.borrow().to_value(), + "constructed" => self.constructed.borrow().to_value(), _ => unimplemented!(), } } @@ -498,45 +603,6 @@ mod test { pub struct ChildObject(ObjectSubclass); } - static PROPERTIES: [Property; 4] = [ - Property("name", |name| { - crate::ParamSpec::string( - name, - "Name", - "Name of this object", - None, - crate::ParamFlags::READWRITE, - ) - }), - Property("construct-name", |name| { - crate::ParamSpec::string( - name, - "Construct Name", - "Construct Name of this object", - None, - crate::ParamFlags::READWRITE | crate::ParamFlags::CONSTRUCT_ONLY, - ) - }), - Property("constructed", |name| { - crate::ParamSpec::boolean( - name, - "Constructed", - "True if the constructed() virtual method was called", - false, - crate::ParamFlags::READABLE, - ) - }), - Property("child", |name| { - crate::ParamSpec::object( - name, - "Child", - "Child object", - ChildObject::static_type(), - crate::ParamFlags::READWRITE, - ) - }), - ]; - wrapper! { pub struct SimpleObject(ObjectSubclass); } diff --git a/glib/src/subclass/signal.rs b/glib/src/subclass/signal.rs new file mode 100644 index 000000000000..efdf8ba08da7 --- /dev/null +++ b/glib/src/subclass/signal.rs @@ -0,0 +1,333 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::translate::*; +use crate::Closure; +use crate::SignalFlags; +use crate::Type; +use crate::Value; + +use std::fmt; +use std::ptr; +use std::sync::Mutex; + +/// Builder for signals. +#[allow(clippy::type_complexity)] +pub struct SignalBuilder<'a> { + name: &'a str, + flags: SignalFlags, + arg_types: &'a [Type], + ret_type: Type, + class_handler: Option< + Box Option + Send + Sync + 'static>, + >, + accumulator: Option< + Box bool + Send + Sync + 'static>, + >, +} + +/// Signal metadata. +pub struct Signal { + name: String, + flags: SignalFlags, + arg_types: Vec, + ret_type: Type, + registration: Mutex, +} + +/// Token passed to signal class handlers. +pub struct SignalClassHandlerToken(pub(super) *mut gobject_ffi::GTypeInstance); + +impl fmt::Debug for SignalClassHandlerToken { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + f.debug_tuple("SignalClassHandlerToken") + .field(&unsafe { crate::Object::from_glib_borrow(self.0 as *mut gobject_ffi::GObject) }) + .finish() + } +} + +/// Signal invocation hint passed to signal accumulators. +#[repr(transparent)] +pub struct SignalInvocationHint(gobject_ffi::GSignalInvocationHint); + +impl SignalInvocationHint { + pub fn detail(&self) -> crate::Quark { + unsafe { from_glib(self.0.detail) } + } + + pub fn run_type(&self) -> SignalFlags { + unsafe { from_glib(self.0.run_type) } + } +} + +impl fmt::Debug for SignalInvocationHint { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + f.debug_struct("SignalInvocationHint") + .field("detail", &self.detail()) + .field("run_type", &self.run_type()) + .finish() + } +} + +/// Signal ID. +#[derive(Debug, Clone, Copy)] +pub struct SignalId(pub(super) Type, pub(super) u32); + +#[allow(clippy::type_complexity)] +enum SignalRegistration { + Unregistered { + class_handler: Option< + Box< + dyn Fn(&SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, + >, + >, + accumulator: Option< + Box bool + Send + Sync + 'static>, + >, + }, + Registered { + type_: Type, + signal_id: u32, + }, +} + +impl<'a> SignalBuilder<'a> { + /// Run the signal class handler in the first emission stage. + pub fn run_first(mut self) -> Self { + self.flags |= SignalFlags::RUN_FIRST; + self + } + + /// Run the signal class handler in the third emission stage. + pub fn run_last(mut self) -> Self { + self.flags |= SignalFlags::RUN_LAST; + self + } + + /// Run the signal class handler in the last emission stage. + pub fn run_cleanup(mut self) -> Self { + self.flags |= SignalFlags::RUN_CLEANUP; + self + } + + /// Signals being emitted for an object while currently being in emission for this very object + /// will not be emitted recursively, but instead cause the first emission to be restarted. + pub fn no_recurse(mut self) -> Self { + self.flags |= SignalFlags::NO_RECURSE; + self + } + + /// This signal supports "::detail" appendices to the signal name upon handler connections and + /// emissions. + pub fn detailed(mut self) -> Self { + self.flags |= SignalFlags::DETAILED; + self + } + + /// Action signals are signals that may freely be emitted on alive objects from user code. + pub fn action(mut self) -> Self { + self.flags |= SignalFlags::ACTION; + self + } + + /// No emissions hooks are supported for this signal. + pub fn no_hooks(mut self) -> Self { + self.flags |= SignalFlags::NO_HOOKS; + self + } + + /// Varargs signal emission will always collect the arguments, even if there are no signal + /// handlers connected. + pub fn must_collect(mut self) -> Self { + self.flags |= SignalFlags::MUST_COLLECT; + self + } + + /// The signal is deprecated and will be removed in a future version. + pub fn deprecated(mut self) -> Self { + self.flags |= SignalFlags::DEPRECATED; + self + } + + /// Explicitly set all flags. + /// + /// This overrides previously set flags on this builder. + pub fn flags(mut self, flags: SignalFlags) -> Self { + self.flags = flags; + self + } + + /// Class handler for this signal. + pub fn class_handler< + F: Fn(&SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, + >( + mut self, + func: F, + ) -> Self { + self.class_handler = Some(Box::new(func)); + self + } + + /// Accumulator for the return values of the signal. + /// + /// This is called if multiple signal handlers are connected to the signal for accumulating the + /// return values into a single value. + pub fn accumulator< + F: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, + >( + mut self, + func: F, + ) -> Self { + self.accumulator = Some(Box::new(func)); + self + } + + /// Build the signal. + /// + /// This does not register the signal yet, which only happens as part of object type + /// registration. + pub fn build(self) -> Signal { + let flags = if self.flags + & (SignalFlags::RUN_FIRST | SignalFlags::RUN_LAST | SignalFlags::RUN_CLEANUP) + == SignalFlags::empty() + { + self.flags | SignalFlags::RUN_LAST + } else { + self.flags + }; + + Signal { + name: String::from(self.name), + flags, + arg_types: Vec::from(self.arg_types), + ret_type: self.ret_type, + registration: Mutex::new(SignalRegistration::Unregistered { + class_handler: self.class_handler, + accumulator: self.accumulator, + }), + } + } +} + +impl Signal { + /// Create a new builder for a signal. + pub fn builder<'a>(name: &'a str, arg_types: &'a [Type], ret_type: Type) -> SignalBuilder<'a> { + SignalBuilder { + name, + arg_types, + ret_type, + flags: SignalFlags::empty(), + class_handler: None, + accumulator: None, + } + } + + /// Name of the signal. + pub fn name(&self) -> &str { + &self.name + } + + /// Flags of the signal. + pub fn flags(&self) -> SignalFlags { + self.flags + } + + /// Argument types of the signal. + pub fn arg_types(&self) -> &[Type] { + &self.arg_types + } + + /// Return type of the signal. + pub fn ret_type(&self) -> Type { + self.ret_type + } + + /// Signal ID. + /// + /// This will panic if called before the signal was registered. + pub fn signal_id(&self) -> SignalId { + match &*self.registration.lock().unwrap() { + SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"), + SignalRegistration::Registered { type_, signal_id } => SignalId(*type_, *signal_id), + } + } + + /// Type this signal was registered for. + /// + /// This will panic if called before the signal was registered. + pub fn type_(&self) -> Type { + match &*self.registration.lock().unwrap() { + SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"), + SignalRegistration::Registered { type_, .. } => *type_, + } + } + + pub(super) fn register(&self, type_: Type) { + let mut registration = self.registration.lock().unwrap(); + + let (class_handler, accumulator) = match &mut *registration { + SignalRegistration::Unregistered { + class_handler, + accumulator, + } => (class_handler.take(), accumulator.take()), + SignalRegistration::Registered { .. } => unreachable!(), + }; + + let arg_types = self + .arg_types + .iter() + .map(ToGlib::to_glib) + .collect::>(); + + let class_handler = class_handler.map(|class_handler| { + Closure::new(move |values| unsafe { + let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0); + class_handler(&SignalClassHandlerToken(instance as *mut _), values) + }) + }); + + unsafe extern "C" fn accumulator_trampoline( + ihint: *mut gobject_ffi::GSignalInvocationHint, + return_accu: *mut gobject_ffi::GValue, + handler_return: *const gobject_ffi::GValue, + data: ffi::gpointer, + ) -> ffi::gboolean { + let accumulator = &**(data + as *const *const (dyn Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + + Send + + Sync + + 'static)); + accumulator( + &SignalInvocationHint(*ihint), + &mut *(return_accu as *mut Value), + &*(handler_return as *const Value), + ) + .to_glib() + } + + let (accumulator, accumulator_trampoline) = if let Some(accumulator) = accumulator { + ( + Box::into_raw(Box::new(accumulator)), + Some:: _>(accumulator_trampoline), + ) + } else { + (ptr::null_mut(), None) + }; + + let signal_id = unsafe { + gobject_ffi::g_signal_newv( + self.name.to_glib_none().0, + type_.to_glib(), + self.flags.to_glib(), + class_handler.to_glib_none().0, + accumulator_trampoline, + accumulator as ffi::gpointer, + None, + self.ret_type.to_glib(), + arg_types.len() as u32, + arg_types.as_ptr() as *mut _, + ) + }; + + *registration = SignalRegistration::Registered { type_, signal_id }; + } +} diff --git a/glib/src/subclass/types.rs b/glib/src/subclass/types.rs index fc08cc13a67e..7af53a0882cf 100644 --- a/glib/src/subclass/types.rs +++ b/glib/src/subclass/types.rs @@ -4,38 +4,17 @@ use crate::object::{Cast, ObjectSubclassIs, ObjectType}; use crate::translate::*; -use crate::{Closure, Object, SignalFlags, StaticType, Type, Value}; -use std::fmt; +use crate::{Closure, Object, StaticType, Type, Value}; use std::marker; use std::mem; use std::ptr; /// A newly registered `glib::Type` that is currently still being initialized. /// -/// This allows running additional type-setup functions, e.g. for implementing -/// interfaces on the type. +/// This allows running additional type-setup functions. #[derive(Debug, PartialEq, Eq)] pub struct InitializingType(pub(crate) Type, pub(crate) marker::PhantomData<*const T>); -impl InitializingType { - /// Adds an interface implementation for `I` to the type. - #[doc(alias = "g_type_add_interface_static")] - pub fn add_interface>(&mut self) { - unsafe { - let iface_info = gobject_ffi::GInterfaceInfo { - interface_init: Some(I::interface_init), - interface_finalize: None, - interface_data: ptr::null_mut(), - }; - gobject_ffi::g_type_add_interface_static( - self.0.to_glib(), - I::static_type().to_glib(), - &iface_info, - ); - } - } -} - impl ToGlib for InitializingType { type GlibType = ffi::GType; @@ -128,6 +107,103 @@ pub unsafe trait IsImplementable: StaticType { unsafe extern "C" fn interface_init(iface: ffi::gpointer, _iface_data: ffi::gpointer); } +/// Trait for a type list of interfaces. +pub trait InterfaceList { + /// Returns the list of types and corresponding interface infos for this list. + fn iface_infos() -> Vec<(ffi::GType, gobject_ffi::GInterfaceInfo)>; +} + +impl InterfaceList for () { + fn iface_infos() -> Vec<(ffi::GType, gobject_ffi::GInterfaceInfo)> { + vec![] + } +} + +impl> InterfaceList for (A,) { + fn iface_infos() -> Vec<(ffi::GType, gobject_ffi::GInterfaceInfo)> { + vec![( + A::static_type().to_glib(), + gobject_ffi::GInterfaceInfo { + interface_init: Some(A::interface_init), + interface_finalize: None, + interface_data: ptr::null_mut(), + }, + )] + } +} + +// Generates all the InterfaceList impls for interface_lists of arbitrary sizes based on a list of type +// parameters like A B C. It would generate the impl then for (A, B) and (A, B, C). +macro_rules! interface_list_trait( + ($name1:ident, $name2: ident, $($name:ident),*) => ( + interface_list_trait!(__impl $name1, $name2; $($name),*); + ); + (__impl $($name:ident),+; $name1:ident, $($name2:ident),*) => ( + interface_list_trait_impl!($($name),+); + interface_list_trait!(__impl $($name),+ , $name1; $($name2),*); + ); + (__impl $($name:ident),+; $name1:ident) => ( + interface_list_trait_impl!($($name),+); + interface_list_trait_impl!($($name),+, $name1); + ); +); + +// Generates the impl block for InterfaceList on interface_lists or arbitrary sizes based on its +// arguments. Takes a list of type parameters as parameters, e.g. A B C +// and then implements the trait on (A, B, C). +macro_rules! interface_list_trait_impl( + ($($name:ident),+) => ( + impl),+> InterfaceList for ( $($name),+ ) { + fn iface_infos() -> Vec<(ffi::GType, gobject_ffi::GInterfaceInfo)> { + let mut types = Vec::new(); + interface_list_trait_inner!(types, $($name)+) + } + } + ); +); + +// Generates the inner part of the InterfaceList::types() implementation, which will +// basically look as follows: +// +// let mut types = Vec::new(); +// +// types.push((A::static_type().to_glib(), ...)); +// types.push((B::static_type().to_glib(), ...)); +// [...] +// types.push((Z::static_type().to_glib(), ...)); +// +// types +macro_rules! interface_list_trait_inner( + ($types:ident, $head:ident $($id:ident)+) => ({ + $types.push( + ( + $head::static_type().to_glib(), + gobject_ffi::GInterfaceInfo { + interface_init: Some($head::interface_init), + interface_finalize: None, + interface_data: ptr::null_mut(), + }, + ) + ); + interface_list_trait_inner!($types, $($id)+) + }); + ($types:ident, $head:ident) => ({ + $types.push( + ( + $head::static_type().to_glib(), + gobject_ffi::GInterfaceInfo { + interface_init: Some($head::interface_init), + interface_finalize: None, + interface_data: ptr::null_mut(), + }, + ) + ); + $types + }); +); + +interface_list_trait!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); + /// Type-specific data that is filled in during type creation. pub struct TypeData { #[doc(hidden)] @@ -257,6 +333,9 @@ pub trait ObjectSubclass: Sized + 'static { + FromGlibPtrBorrow<*mut ::GlibType> + FromGlibPtrNone<*mut ::GlibType>; + /// List of interfaces implemented by this type. + type Interfaces: InterfaceList; + /// The C instance struct. /// /// See [`simple::InstanceStruct`] for an basic instance struct that should be @@ -338,8 +417,8 @@ pub trait ObjectSubclass: Sized + 'static { /// /// This is called after `type_init` and before the first instance /// of the subclass is created. Subclasses can use this to do class- - /// specific initialization, e.g. for installing properties or signals - /// on the class or calling class methods. + /// specific initialization, e.g. for registering signals on the class + /// or calling class methods. /// /// Optional fn class_init(_klass: &mut Self::Class) {} @@ -529,193 +608,17 @@ where }; (*data.as_mut()).private_offset = private_offset as isize; + let iface_types = T::Interfaces::iface_infos(); + for (iface_type, iface_info) in iface_types { + gobject_ffi::g_type_add_interface_static(type_.to_glib(), iface_type, &iface_info); + } + T::type_init(&mut InitializingType::(type_, marker::PhantomData)); type_ } } -pub(crate) unsafe fn add_signal( - type_: ffi::GType, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, -) { - let arg_types = arg_types.iter().map(ToGlib::to_glib).collect::>(); - - gobject_ffi::g_signal_newv( - name.to_glib_none().0, - type_, - flags.to_glib(), - ptr::null_mut(), - None, - ptr::null_mut(), - None, - ret_type.to_glib(), - arg_types.len() as u32, - arg_types.as_ptr() as *mut _, - ); -} - -#[repr(transparent)] -pub struct SignalInvocationHint(gobject_ffi::GSignalInvocationHint); - -impl SignalInvocationHint { - pub fn detail(&self) -> crate::Quark { - unsafe { from_glib(self.0.detail) } - } - - pub fn run_type(&self) -> SignalFlags { - unsafe { from_glib(self.0.run_type) } - } -} - -impl fmt::Debug for SignalInvocationHint { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.debug_struct("SignalInvocationHint") - .field("detail", &self.detail()) - .field("run_type", &self.run_type()) - .finish() - } -} - -pub(crate) unsafe fn add_signal_with_accumulator( - type_: ffi::GType, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - accumulator: F, -) where - F: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, -{ - let arg_types = arg_types.iter().map(ToGlib::to_glib).collect::>(); - - let accumulator: Box = Box::new(accumulator); - - unsafe extern "C" fn accumulator_trampoline< - F: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - >( - ihint: *mut gobject_ffi::GSignalInvocationHint, - return_accu: *mut gobject_ffi::GValue, - handler_return: *const gobject_ffi::GValue, - data: ffi::gpointer, - ) -> ffi::gboolean { - let accumulator: &F = &*(data as *const &F); - accumulator( - &*(ihint as *const SignalInvocationHint), - &mut *(return_accu as *mut Value), - &*(handler_return as *const Value), - ) - .to_glib() - } - - gobject_ffi::g_signal_newv( - name.to_glib_none().0, - type_, - flags.to_glib(), - ptr::null_mut(), - Some(accumulator_trampoline::), - Box::into_raw(accumulator) as ffi::gpointer, - None, - ret_type.to_glib(), - arg_types.len() as u32, - arg_types.as_ptr() as *mut _, - ); -} - -pub struct SignalClassHandlerToken(*mut gobject_ffi::GTypeInstance); - -impl fmt::Debug for SignalClassHandlerToken { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.debug_tuple("SignalClassHandlerToken") - .field(&unsafe { crate::Object::from_glib_borrow(self.0 as *mut gobject_ffi::GObject) }) - .finish() - } -} - -pub(crate) unsafe fn add_signal_with_class_handler( - type_: ffi::GType, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, -) where - F: Fn(&SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, -{ - let arg_types = arg_types.iter().map(ToGlib::to_glib).collect::>(); - let class_handler = Closure::new(move |values| { - let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0); - class_handler(&SignalClassHandlerToken(instance as *mut _), values) - }); - - gobject_ffi::g_signal_newv( - name.to_glib_none().0, - type_, - flags.to_glib(), - class_handler.to_glib_none().0, - None, - ptr::null_mut(), - None, - ret_type.to_glib(), - arg_types.len() as u32, - arg_types.as_ptr() as *mut _, - ); -} - -pub(crate) unsafe fn add_signal_with_class_handler_and_accumulator( - type_: ffi::GType, - name: &str, - flags: SignalFlags, - arg_types: &[Type], - ret_type: Type, - class_handler: F, - accumulator: G, -) where - F: Fn(&SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, - G: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, -{ - let arg_types = arg_types.iter().map(ToGlib::to_glib).collect::>(); - - let class_handler = Closure::new(move |values| { - let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0); - class_handler(&SignalClassHandlerToken(instance as *mut _), values) - }); - let accumulator: Box = Box::new(accumulator); - - unsafe extern "C" fn accumulator_trampoline< - G: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, - >( - ihint: *mut gobject_ffi::GSignalInvocationHint, - return_accu: *mut gobject_ffi::GValue, - handler_return: *const gobject_ffi::GValue, - data: ffi::gpointer, - ) -> ffi::gboolean { - let accumulator: &G = &*(data as *const &G); - accumulator( - &SignalInvocationHint(*ihint), - &mut *(return_accu as *mut Value), - &*(handler_return as *const Value), - ) - .to_glib() - } - - gobject_ffi::g_signal_newv( - name.to_glib_none().0, - type_, - flags.to_glib(), - class_handler.to_glib_none().0, - Some(accumulator_trampoline::), - Box::into_raw(accumulator) as ffi::gpointer, - None, - ret_type.to_glib(), - arg_types.len() as u32, - arg_types.as_ptr() as *mut _, - ); -} - pub(crate) unsafe fn signal_override_class_handler( name: &str, type_: ffi::GType, @@ -725,7 +628,7 @@ pub(crate) unsafe fn signal_override_class_handler( { let class_handler = Closure::new(move |values| { let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0); - class_handler(&SignalClassHandlerToken(instance as *mut _), values) + class_handler(&super::SignalClassHandlerToken(instance as *mut _), values) }); let mut signal_id = 0; @@ -746,7 +649,7 @@ pub(crate) unsafe fn signal_override_class_handler( pub(crate) unsafe fn signal_chain_from_overridden( instance: *mut gobject_ffi::GTypeInstance, - token: &SignalClassHandlerToken, + token: &super::SignalClassHandlerToken, values: &[Value], ) -> Option { assert_eq!(instance, token.0);