diff --git a/glib-macros/src/class_handler.rs b/glib-macros/src/class_handler.rs new file mode 100644 index 000000000000..35bd2cc35909 --- /dev/null +++ b/glib-macros/src/class_handler.rs @@ -0,0 +1,58 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::utils::crate_ident_new; +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span}; +use quote::quote_spanned; +use syn::spanned::Spanned; + +pub(crate) fn class_handler_inner(input: TokenStream, has_token: bool) -> TokenStream { + let crate_ident = crate_ident_new(); + let closure = syn::parse_macro_input!(input as syn::ExprClosure); + let closure_ident = Ident::new("____closure", Span::mixed_site()); + let token_ident = has_token.then(|| Ident::new("____token", Span::mixed_site())); + let values_ident = Ident::new("____values", Span::mixed_site()); + let offset = if has_token { 1 } else { 0 }; + let arg_names = closure + .inputs + .iter() + .skip(offset) + .enumerate() + .map(|(index, _)| Ident::new(&format!("____arg{}", index), Span::mixed_site())); + let arg_names = if let Some(token) = token_ident.as_ref().cloned() { + std::iter::once(token).chain(arg_names).collect::>() + } else { + arg_names.collect::>() + }; + let arg_values = closure + .inputs + .iter() + .skip(offset) + .enumerate() + .map(|(index, pat)| { + let err_msg = format!("Wrong type for argument {}: {{:?}}", index); + let name = &arg_names[index + offset]; + quote_spanned! { pat.span() => + let #name = #crate_ident::Value::get(&#values_ident[#index]) + .unwrap_or_else(|e| ::std::panic!(#err_msg, e)); + } + }); + let args_len = closure.inputs.len().saturating_sub(offset); + let token_arg = token_ident.map(|t| { + quote_spanned! { t.span() => + #t: #crate_ident::subclass::SignalClassOverrideToken, + } + }); + let output = quote_spanned! { closure.span() => { + let #closure_ident = #closure; + move + |#token_arg #values_ident: &[#crate_ident::Value]| -> ::std::option::Option<#crate_ident::Value> { + assert_eq!(#values_ident.len(), #args_len); + #(#arg_values)* + #crate_ident::closure::ToClosureReturnValue::to_closure_return_value( + &#closure_ident(#(#arg_names),*) + ) + } + } }; + output.into() +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 1a7f88e881ab..a8ddc0815908 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1,6 +1,7 @@ // Take a look at the license at the top of the repository in the LICENSE file. mod boxed_derive; +mod class_handler; mod clone; mod closure; mod downgrade_derive; @@ -418,6 +419,190 @@ pub fn closure_local(item: TokenStream) -> TokenStream { closure::closure_inner(item, "new_local") } +/// Macro for creating signal class handlers. +/// +/// This is only meant to be used with [`SignalBuilder::class_handler`] for automatic conversion of +/// its types to and from [`Value`]s. This macro takes a closure expression with any number of +/// arguments, and returns a closure that returns `Option` and takes a single `&[Value]` +/// argument. +/// +/// Similar to [`closure!`], the returned function takes [`Value`] objects as inputs and output and +/// automatically converts the inputs to Rust types when invoking its callback, and then will +/// convert the output back to a `Value`. All inputs must implement the [`FromValue`] trait, and +/// outputs must either implement the [`ToValue`] trait or be the unit type `()`. Type-checking of +/// inputs is done at run-time; if the argument types do not match the type of the signal handler +/// then the closure will panic. Note that when passing input types derived from [`Object`] or +/// [`Interface`], you must take care to upcast to the exact object or interface type that is being +/// received. +/// +/// [`SignalBuilder::class_handler`]: ../glib/subclass/signal/struct.SignalBuilder.html#method.class_handler +/// [`Value`]: ../glib/value/struct.Value.html +/// [`FromValue`]: ../glib/value/trait.FromValue.html +/// [`ToValue`]: ../glib/value/trait.ToValue.html +/// [`Interface`]: ../glib/object/struct.Interface.html +/// [`Object`]: ../glib/object/struct.Object.html +/// ```no_run +/// # use glib; +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// use glib::class_handler; +/// # use glib::once_cell; +/// # +/// # glib::wrapper! { +/// # pub struct MyObject(ObjectSubclass); +/// # } +/// # mod imp { +/// # use super::*; +/// # +/// # #[derive(Default)] +/// # pub struct MyObject; +/// # +/// # #[glib::object_subclass] +/// # impl ObjectSubclass for MyObject { +/// # const NAME: &'static str = "MyObject"; +/// # type Type = super::MyObject; +/// # } +/// +/// impl ObjectImpl for MyObject { +/// fn signals() -> &'static [glib::subclass::Signal] { +/// use once_cell::sync::Lazy; +/// use glib::subclass::Signal; +/// static SIGNALS: Lazy> = Lazy::new(|| { +/// vec![ +/// Signal::builder( +/// "my-signal", +/// &[u32::static_type().into()], +/// String::static_type().into(), +/// ) +/// .class_handler(class_handler!( +/// |_this: &super::MyObject, num: u32| -> String { +/// format!("{}", num) +/// })) +/// .build(), +/// ] +/// }); +/// SIGNALS.as_ref() +/// } +/// } +/// # } +/// # fn main() {} +/// ``` +#[proc_macro] +#[proc_macro_error] +pub fn class_handler(item: TokenStream) -> TokenStream { + class_handler::class_handler_inner(item, false) +} + +/// Macro for creating overridden signal class handlers. +/// +/// This is only meant to be used with [`ObjectClassSubclassExt::override_signal_class_handler`] +/// for automatic conversion of its types to and from [`Value`]s. This macro takes a closure +/// expression with any number of arguments and returns a closure that returns `Option` and +/// takes two arguments: a [`SignalClassOverrideToken`] as the first and `&[Value]` as the second. +/// If any arguments are provided to the input closure, the `SignalClassOverrideToken` will always +/// be passed as the first argument. +/// +/// Similar to [`closure!`], the returned function takes [`Value`] objects as inputs and output and +/// automatically converts the inputs to Rust types when invoking its callback, and then will +/// convert the output back to a `Value`. All inputs must implement the [`FromValue`] trait, and +/// outputs must either implement the [`ToValue`] trait or be the unit type `()`. Type-checking of +/// inputs is done at run-time; if the argument types do not match the type of the signal handler +/// then the closure will panic. Note that when passing input types derived from [`Object`] or +/// [`Interface`], you must take care to upcast to the exact object or interface type that is being +/// received. +/// +/// [`ObjectClassSubclassExt::override_signal_class_handler`]: ../glib/subclass/object/trait.ObjectClassSubclassExt.html#method.override_signal_class_handler +/// [`SignalClassOverrideToken`]: ../glib/subclass/object/struct.SignalClassOverrideToken.html +/// [`Value`]: ../glib/value/struct.Value.html +/// [`FromValue`]: ../glib/value/trait.FromValue.html +/// [`ToValue`]: ../glib/value/trait.ToValue.html +/// [`Interface`]: ../glib/object/struct.Interface.html +/// [`Object`]: ../glib/object/struct.Object.html +/// ```no_run +/// # use glib; +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// use glib::subclass::SignalClassOverrideToken; +/// use glib::{class_handler, override_handler}; +/// # use glib::once_cell; +/// # +/// # glib::wrapper! { +/// # pub struct MyBase(ObjectSubclass); +/// # } +/// # unsafe impl IsSubclassable for MyBase {} +/// # glib::wrapper! { +/// # pub struct MyDerived(ObjectSubclass) @extends MyBase; +/// # } +/// # mod imp { +/// # use super::*; +/// # +/// # #[derive(Default)] +/// # pub struct MyBase; +/// # +/// # #[glib::object_subclass] +/// # impl ObjectSubclass for MyBase { +/// # const NAME: &'static str = "MyBase"; +/// # type Type = super::MyBase; +/// # } +/// +/// impl ObjectImpl for MyBase { +/// fn signals() -> &'static [glib::subclass::Signal] { +/// use once_cell::sync::Lazy; +/// use glib::subclass::Signal; +/// static SIGNALS: Lazy> = Lazy::new(|| { +/// vec![ +/// Signal::builder( +/// "my-signal", +/// &[String::static_type().into(), u32::static_type().into()], +/// String::static_type().into(), +/// ) +/// .class_handler(class_handler!( +/// |this: &super::MyBase, myarg1: Option<&str>, myarg2: u32| -> String { +/// format!("len: {}", myarg1.unwrap_or_default().len() + myarg2 as usize) +/// })) +/// .build(), +/// ] +/// }); +/// SIGNALS.as_ref() +/// } +/// } +/// # +/// # pub trait MyBaseImpl: ObjectImpl {} +/// # +/// # #[derive(Default)] +/// # pub struct MyDerived; +/// +/// #[glib::object_subclass] +/// impl ObjectSubclass for MyDerived { +/// const NAME: &'static str = "MyDerived"; +/// type Type = super::MyDerived; +/// type ParentType = super::MyBase; +/// fn class_init(class: &mut Self::Class) { +/// class.override_signal_class_handler( +/// "my-signal", +/// override_handler!(|token: SignalClassOverrideToken, +/// this: &super::MyDerived, +/// myarg1: Option<&str>, +/// myarg2: u32| +/// -> String { +/// let s = format!("str: {} num: {}", myarg1.unwrap_or("nothing"), myarg2); +/// let ret: String = token.chain(&[&this, &Some(&s), &myarg2]); +/// format!("ret: {}", ret) +/// }) +/// ); +/// } +/// } +/// # impl ObjectImpl for MyDerived {} +/// # impl MyBaseImpl for MyDerived {} +/// # } +/// # fn main() {} +/// ``` +#[proc_macro] +#[proc_macro_error] +pub fn override_handler(item: TokenStream) -> TokenStream { + class_handler::class_handler_inner(item, true) +} + /// Derive macro for register a rust enum in the glib type system and derive the /// the [`glib::Value`] traits. /// diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 1038bb3dcf1d..e6df9b4f22f8 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -17,8 +17,8 @@ pub use bitflags; pub use once_cell; pub use glib_macros::{ - clone, closure, closure_local, flags, object_interface, object_subclass, Boxed, Downgrade, - Enum, ErrorDomain, SharedBoxed, Variant, + class_handler, clone, closure, closure_local, flags, object_interface, object_subclass, + override_handler, Boxed, Downgrade, Enum, ErrorDomain, SharedBoxed, Variant, }; #[doc(hidden)] diff --git a/glib/src/subclass/mod.rs b/glib/src/subclass/mod.rs index 359bf9ed567d..7d40450832cb 100644 --- a/glib/src/subclass/mod.rs +++ b/glib/src/subclass/mod.rs @@ -276,7 +276,6 @@ pub mod prelude { pub use self::boxed::register_boxed_type; pub use self::interface::register_interface; -pub use self::signal::{ - Signal, SignalClassHandlerToken, SignalId, SignalInvocationHint, SignalQuery, SignalType, -}; +pub use self::object::SignalClassOverrideToken; +pub use self::signal::{Signal, SignalId, SignalInvocationHint, SignalQuery, SignalType}; pub use self::types::{register_type, InitializingObject, InitializingType, TypeData}; diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index 1f39630219d6..6c9fd52eecda 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -6,8 +6,10 @@ use super::prelude::*; use super::Signal; +use super::SignalQuery; +use crate::closure::TryFromClosureReturnValue; use crate::translate::*; -use crate::{Cast, Object, ObjectType, ParamSpec, Value}; +use crate::{Cast, Closure, Object, ParamSpec, ToValue, Type, Value}; use std::mem; use std::ptr; @@ -133,20 +135,173 @@ unsafe extern "C" fn dispose(obj: *mut gobject_ffi::GObject) { } } +// rustdoc-stripper-ignore-next +/// A token representing a single invocation of an overridden class handler. Used for chaining to +/// the original class handler. +/// +/// The token can only be used to chain up to the parent handler once, and is consumed by calling +/// any of its `chain` methods. +/// +/// # Thread-safety +/// +/// Consuming the token is thread-safe, but can result in logic errors if multiple signals are +/// emitting concurrently on the same object across threads. If your object is `Send+Sync` and +/// consumes any `SignalClassOverrideToken`s in any override handlers, you must wrap every signal +/// emission on that object with a mutex lock. Note this restriction applies to **all** signal +/// emissions on that object, not just overridden signals. A lock such as [`ReentrantMutex`] can be +/// used to prevent deadlocks in the case of recursive signal emissions. +/// +/// [`ReentrantMutex`]: https://docs.rs/lock_api/latest/lock_api/struct.ReentrantMutex.html +pub struct SignalClassOverrideToken<'a> { + values: &'a [Value], + query: &'a SignalQuery, +} + +impl<'a> SignalClassOverrideToken<'a> { + // rustdoc-stripper-ignore-next + /// Queries more information about the currently emitted signal. + pub fn query(&self) -> &'a SignalQuery { + self.query + } + + // rustdoc-stripper-ignore-next + /// Calls the original class handler of a signal with `values`, converting each to a `Value` + /// and consuming the token. The first value must be the `Object` instance the signal was + /// emitted on. Calling this function with the wrong `Object` instance or an incorrect number + /// of values or incorrect types for the values will panic. + #[doc(alias = "g_signal_chain_from_overridden")] + pub fn chain(self, values: &[&dyn ToValue]) -> R { + let values = values + .iter() + .copied() + .map(ToValue::to_value) + .collect::>(); + let ret = self.chain_values(&values); + R::try_from_closure_return_value(ret).expect("Invalid return value") + } + + // rustdoc-stripper-ignore-next + /// Calls the original class handler of a signal with `values`, consuming the token. The first + /// value must be the `Object` instance the signal was emitted on. Calling this function with + /// the wrong `Object` instance or an incorrect number of values or incorrect types for the + /// values will panic. + /// + /// This function can avoid type checking if the original + #[doc(alias = "g_signal_chain_from_overridden")] + pub fn chain_values(self, values: &[Value]) -> Option { + // Optimize out the type checks if this is the same slice as the original + if values.as_ptr() != self.values.as_ptr() { + assert_eq!(values[0].get::<&Object>(), self.values[0].get::<&Object>()); + + let n_args = self.query.n_params() as usize + 1; + if n_args != values.len() { + panic!( + "Expected {} values when chaining signal `{}`, got {}", + n_args, + self.query.signal_name(), + values.len(), + ); + } + for (index, arg_type) in self.query.param_types().iter().enumerate() { + let arg_type = arg_type.type_(); + let index = index + 1; + let value = &values[index]; + if !value.is_type(arg_type) { + panic!( + "Wrong type for argument {} when chaining signal `{}`: Actual {:?}, requested {:?}", + index, + self.query.signal_name(), + value.type_(), + arg_type, + ); + } + } + } + + unsafe { self.chain_values_unsafe(values) } + } + + // rustdoc-stripper-ignore-next + /// Calls the original class handler of a signal with `values`. The first value must be the + /// `Object` instance the signal was emitted on. + /// + /// # Safety + /// + /// The types and number of argument values are not checked, and `return_type` must match the + /// return type of the signal. It is undefined behavior to call this function with types that do + /// not match the type of the currently running class closure, or with the incorrect object as + /// the first argument. + #[doc(alias = "g_signal_chain_from_overridden")] + pub unsafe fn chain_values_unsafe(self, values: &[Value]) -> Option { + debug_assert_eq!(values[0].get::<&Object>(), self.values[0].get::<&Object>()); + let return_type = self.query.return_type().type_(); + let mut result = Value::from_type(return_type); + gobject_ffi::g_signal_chain_from_overridden( + values.as_ptr() as *mut Value as *mut gobject_ffi::GValue, + result.to_glib_none_mut().0, + ); + Some(result).filter(|r| r.type_().is_valid() && r.type_() != Type::UNIT) + } +} + // rustdoc-stripper-ignore-next /// Extension trait for `glib::Object`'s class struct. /// /// This contains various class methods and allows subclasses to override signal class handlers. pub unsafe trait ObjectClassSubclassExt: Sized + 'static { + // rustdoc-stripper-ignore-next + /// Overrides the class handler for a signal. The signal `name` must be installed on the parent + /// class or this method will panic. The parent class handler can be invoked by calling one of + /// the `chain` methods on the [`SignalClassOverrideToken`]. This can be used with the + /// [`override_handler`](crate::override_handler) macro to perform automatic conversion to and + /// from the [`Value`](crate::Value) arguments and return value. See the documentation of that + /// macro for more information. fn override_signal_class_handler(&mut self, name: &str, class_handler: F) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, + F: Fn(SignalClassOverrideToken, &[Value]) -> Option + Send + Sync + 'static, { + let type_ = unsafe { from_glib(*(self as *mut _ as *mut ffi::GType)) }; + let (signal_id, _) = super::SignalId::parse_name(name, type_, false) + .unwrap_or_else(|| panic!("Signal '{}' not found", name)); + let query = signal_id.query(); + let return_type = query.return_type().type_(); + let closure = Closure::new(move |values| { + let token = SignalClassOverrideToken { + values, + query: &query, + }; + let res = class_handler(token, values); + + if return_type == Type::UNIT { + if let Some(ref v) = res { + panic!( + "Signal has no return value but class handler returned a value of type {}", + v.type_() + ); + } + } else { + match res { + None => { + panic!("Signal has a return value but class handler returned none"); + } + Some(ref v) => { + assert!( + v.type_().is_a(return_type), + "Signal has a return type of {} but class handler returned {}", + return_type, + v.type_() + ); + } + } + } + + res + }); unsafe { - super::types::signal_override_class_handler( - name, - *(self as *mut _ as *mut ffi::GType), - class_handler, + gobject_ffi::g_signal_override_class_closure( + signal_id.into_glib(), + type_.into_glib(), + closure.to_glib_none().0, ); } } @@ -195,14 +350,6 @@ pub trait ObjectImplExt: ObjectSubclass { // rustdoc-stripper-ignore-next /// Chain up to the parent class' implementation of `glib::Object::constructed()`. fn parent_constructed(&self, obj: &Self::Type); - - // rustdoc-stripper-ignore-next - /// Chain up to parent class signal handler. - fn signal_chain_from_overridden( - &self, - token: &super::SignalClassHandlerToken, - values: &[Value], - ) -> Option; } impl ObjectImplExt for T { @@ -216,34 +363,21 @@ impl ObjectImplExt for T { } } } - - fn signal_chain_from_overridden( - &self, - token: &super::SignalClassHandlerToken, - values: &[Value], - ) -> Option { - unsafe { - super::types::signal_chain_from_overridden( - self.instance().as_ptr() as *mut _, - token, - values, - ) - } - } } #[cfg(test)] mod test { use super::super::super::object::ObjectExt; - use super::super::super::value::{ToValue, Value}; use super::*; // We rename the current crate as glib, since the macros in glib-macros // generate the glib namespace through the crate_ident_new utility, // and that returns `glib` (and not `crate`) when called inside the glib crate use crate as glib; + use crate::ObjectType; use crate::StaticType; use std::cell::RefCell; + use std::rc::Rc; mod imp { use super::*; @@ -329,7 +463,7 @@ mod test { String::static_type().into(), ) .action() - .class_handler(|_, args| { + .class_handler(|args| { let obj = args[0] .get::() .expect("Failed to get Object from args[0]"); @@ -354,6 +488,18 @@ mod test { ChildObject::type_().into(), ) .build(), + super::Signal::builder( + "print-hex", + &[u32::static_type().into()], + String::static_type().into(), + ) + .run_first() + .class_handler(glib::class_handler!(|_: &super::SimpleObject, + n: u32| + -> String { + format!("0x{:x}", n) + })) + .build(), ] }); @@ -407,6 +553,58 @@ mod test { } } + pub trait SimpleObjectImpl: ObjectImpl {} + + macro_rules! derive_simple { + ($name:ident, $wrapper:ty, $class:ident, $class_init:expr) => { + #[derive(Default)] + pub struct $name; + + #[glib::object_subclass] + impl ObjectSubclass for $name { + const NAME: &'static str = stringify!($name); + type Type = $wrapper; + type ParentType = super::SimpleObject; + fn class_init($class: &mut Self::Class) { + $class_init; + } + } + + impl ObjectImpl for $name {} + impl SimpleObjectImpl for $name {} + }; + } + + derive_simple!( + DerivedObject, + super::DerivedObject, + class, + class.override_signal_class_handler("change-name", |token, values| { + let name = token.chain_values(values).unwrap(); + let name = name + .get::>() + .unwrap() + .map(|n| format!("{}-return", n)); + Some(name.to_value()) + }) + ); + + derive_simple!( + DerivedObject2, + super::DerivedObject2, + class, + class.override_signal_class_handler( + "change-name", + glib::override_handler!(|token: SignalClassOverrideToken, + this: &super::DerivedObject2, + name: Option<&str>| + -> Option { + let name = name.map(|n| format!("{}-closure", n)); + token.chain(&[&this, &name]) + }) + ) + ); + #[derive(Clone, Copy)] #[repr(C)] pub struct DummyInterface { @@ -427,6 +625,16 @@ mod test { pub struct SimpleObject(ObjectSubclass); } + unsafe impl IsSubclassable for SimpleObject {} + + wrapper! { + pub struct DerivedObject(ObjectSubclass) @extends SimpleObject; + } + + wrapper! { + pub struct DerivedObject2(ObjectSubclass) @extends SimpleObject; + } + wrapper! { pub struct Dummy(ObjectInterface); } @@ -638,4 +846,62 @@ mod test { let value: glib::Object = obj.emit_by_name("create-child-object", &[]); assert!(value.type_().is_a(ChildObject::static_type())); } + + #[test] + fn test_signal_class_handler() { + let obj = Object::with_type(SimpleObject::static_type(), &[]).expect("Object::new failed"); + + let value = obj.emit_by_name::("print-hex", &[&55u32]); + assert_eq!(value, "0x37"); + obj.connect_closure( + "print-hex", + false, + glib::closure_local!(|_: &SimpleObject, n: u32| -> String { format!("0x{:08x}", n) }), + ); + let value = obj.emit_by_name::("print-hex", &[&56u32]); + assert_eq!(value, "0x00000038"); + } + + #[test] + fn test_signal_override_chain_values() { + let obj = Object::with_type(DerivedObject::static_type(), &[]).expect("Object::new failed"); + obj.emit_by_name::>("change-name", &[&"old-name"]); + + let current_name = Rc::new(RefCell::new(String::new())); + let current_name_clone = current_name.clone(); + + obj.connect_local("name-changed", false, move |args| { + let name = args[1].get::().expect("Failed to get args[1]"); + current_name_clone.replace(name); + None + }); + assert_eq!( + obj.emit_by_name::>("change-name", &[&"new-name"]) + .unwrap(), + "old-name-return" + ); + assert_eq!(*current_name.borrow(), "new-name"); + } + + #[test] + fn test_signal_override_chain() { + let obj = + Object::with_type(DerivedObject2::static_type(), &[]).expect("Object::new failed"); + obj.emit_by_name::>("change-name", &[&"old-name"]); + + let current_name = Rc::new(RefCell::new(String::new())); + let current_name_clone = current_name.clone(); + + obj.connect_local("name-changed", false, move |args| { + let name = args[1].get::().expect("Failed to get args[1]"); + current_name_clone.replace(name); + None + }); + assert_eq!( + obj.emit_by_name::>("change-name", &[&"new-name"]) + .unwrap(), + "old-name-closure" + ); + assert_eq!(*current_name.borrow(), "new-name-closure"); + } } diff --git a/glib/src/subclass/signal.rs b/glib/src/subclass/signal.rs index 8f77968dc382..da787381a678 100644 --- a/glib/src/subclass/signal.rs +++ b/glib/src/subclass/signal.rs @@ -2,15 +2,26 @@ use crate::translate::*; use crate::utils::is_canonical_pspec_name; +use crate::value::FromValue; use crate::Closure; +use crate::IsA; +use crate::Object; use crate::SignalFlags; +use crate::StaticType; +use crate::ToValue; use crate::Type; use crate::Value; +use std::ops::ControlFlow; use std::ptr; use std::sync::Mutex; use std::{fmt, num::NonZeroU32}; +type SignalClassHandler = Box Option + Send + Sync + 'static>; + +type SignalAccumulator = + Box bool + Send + Sync + 'static>; + // rustdoc-stripper-ignore-next /// Builder for signals. #[allow(clippy::type_complexity)] @@ -20,12 +31,8 @@ pub struct SignalBuilder<'a> { flags: SignalFlags, param_types: &'a [SignalType], return_type: SignalType, - class_handler: Option< - Box Option + Send + Sync + 'static>, - >, - accumulator: Option< - Box bool + Send + Sync + 'static>, - >, + class_handler: Option, + accumulator: Option, } // rustdoc-stripper-ignore-next @@ -38,36 +45,38 @@ pub struct Signal { registration: Mutex, } -// rustdoc-stripper-ignore-next -/// Token passed to signal class handlers. -pub struct SignalClassHandlerToken( - // rustdoc-stripper-ignore-next - /// Instance for which the signal is emitted. - pub(super) *mut gobject_ffi::GTypeInstance, - // rustdoc-stripper-ignore-next - /// Return type. - pub(super) Type, - // rustdoc-stripper-ignore-next - /// Arguments value array. - pub(super) *const Value, -); - -impl fmt::Debug for SignalClassHandlerToken { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.debug_struct("SignalClassHandlerToken") - .field("type", &unsafe { - crate::Object::from_glib_borrow(self.0 as *mut gobject_ffi::GObject) - }) - .finish() - } -} - // rustdoc-stripper-ignore-next /// Signal invocation hint passed to signal accumulators. #[repr(transparent)] pub struct SignalInvocationHint(gobject_ffi::GSignalInvocationHint); impl SignalInvocationHint { + // rustdoc-stripper-ignore-next + /// Gets the hint of the innermost signal emitting on `instance`. Returns `None` if no signal + /// is being emitted. + /// + /// # Thread-safety + /// + /// This section only applies when `instance` implements `Send+Sync`. Retreiving the hint is + /// thread-safe, but can result in logic errors if multiple signals are emitting concurrently on + /// the same object across threads. If you call this function on an object that is `Send+Sync`, + /// you must wrap every signal emission on that object with a mutex lock. Note this restriction + /// applies to **all** signal emissions on that object, not just overridden signals. A lock + /// such as [`ReentrantMutex`] can be used to prevent deadlocks in the case of recursive signal + /// emissions. + /// + /// [`ReentrantMutex`]: https://docs.rs/lock_api/latest/lock_api/struct.ReentrantMutex.html + #[doc(alias = "g_signal_get_invocation_hint")] + pub fn for_object>(instance: &T) -> Option { + unsafe { + from_glib_none(gobject_ffi::g_signal_get_invocation_hint( + instance.as_ref().to_glib_none().0, + )) + } + } + pub fn signal_id(&self) -> SignalId { + unsafe { from_glib(self.0.signal_id) } + } pub fn detail(&self) -> Option { unsafe { try_from_glib(self.0.detail).ok() } } @@ -77,6 +86,13 @@ impl SignalInvocationHint { } } +impl FromGlibPtrNone<*mut gobject_ffi::GSignalInvocationHint> for SignalInvocationHint { + unsafe fn from_glib_none(hint: *mut gobject_ffi::GSignalInvocationHint) -> Self { + assert!(!hint.is_null()); + Self(*hint) + } +} + impl fmt::Debug for SignalInvocationHint { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { f.debug_struct("SignalInvocationHint") @@ -362,14 +378,8 @@ impl FromGlibContainerAsVec for SignalType { #[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>, - >, + class_handler: Option, + accumulator: Option, }, Registered { type_: Type, @@ -455,13 +465,53 @@ impl<'a> SignalBuilder<'a> { // rustdoc-stripper-ignore-next /// Class handler for this signal. - pub fn class_handler< - F: Fn(&SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, + /// + /// This can be used with the [`class_handler`](crate::class_handler) macro to perform + /// automatic conversion to and from the [`Value`](crate::Value) arguments and return value. + /// See the documentation of that macro for more information. + pub fn class_handler(mut self, func: F) -> Self + where + F: Fn(&[Value]) -> Option + Send + Sync + 'static, + { + self.class_handler = Some(Box::new(func)); + self + } + + // rustdoc-stripper-ignore-next + /// 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. Panics if `T` and `R` do not return the same `Type` from + /// [`crate::StaticType::static_type`]. The first `T` is the currently accumulated value and + /// the second `T` is the return value from the current signal emission. If a `Some` value is + /// returned then that value will be used to replace the current accumulator. Retuning + /// `ControlFlow::Break` will abort the current signal emission. + /// + /// Either call this or [`Self::accumulator_with_values`], but not both. + pub fn accumulator< + T: for<'v> FromValue<'v> + StaticType, + R: ToValue + StaticType, + F: Fn(&SignalInvocationHint, T, T) -> ControlFlow, Option> + + Send + + Sync + + 'static, >( mut self, func: F, ) -> Self { - self.class_handler = Some(Box::new(func)); + assert_eq!(T::static_type(), R::static_type()); + self.accumulator = Some(Box::new(move |hint, accu, value| { + let curr_accu = accu.get::().unwrap(); + let value = value.get::().unwrap(); + let (next, ret) = match func(hint, curr_accu, value) { + ControlFlow::Continue(next) => (next, true), + ControlFlow::Break(next) => (next, false), + }; + if let Some(next) = next { + *accu = ToValue::to_value(&next); + } + ret + })); self } @@ -469,8 +519,13 @@ impl<'a> SignalBuilder<'a> { /// 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< + /// return values into a single value.. The first [`Value`] is the currently accumulated value and + /// the second [`Value`] is the return value from the current signal emission. If a `Some` + /// value is returned then that value will be used to replace the current accumulator. Retuning + /// `false` will abort the current signal emission. + /// + /// Either call this or [`Self::accumulator`], but not both. + pub fn accumulator_with_values< F: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, >( mut self, @@ -592,9 +647,8 @@ impl Signal { let return_type = self.return_type; 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); - let res = class_handler(&SignalClassHandlerToken(instance as *mut _, return_type.into(), values.as_ptr()), values); + Closure::new(move |values| { + let res = class_handler(values); if return_type == Type::UNIT { if let Some(ref v) = res { @@ -642,8 +696,8 @@ impl Signal { handler_return.type_() ); - let res = (accumulator.1)(&SignalInvocationHint(*ihint), return_accu, handler_return) - .into_glib(); + let res = + (accumulator.1)(&from_glib_none(ihint), return_accu, handler_return).into_glib(); assert!( return_accu.type_().is_a(return_type.into()), diff --git a/glib/src/subclass/types.rs b/glib/src/subclass/types.rs index 628b9e786a3d..529e26105fa5 100644 --- a/glib/src/subclass/types.rs +++ b/glib/src/subclass/types.rs @@ -5,14 +5,12 @@ use crate::object::{Cast, IsClass, IsInterface, ObjectSubclassIs, ObjectType, ParentClassIs}; use crate::translate::*; -use crate::{Closure, Object, StaticType, Type, Value}; +use crate::{Object, StaticType, Type}; use std::marker; use std::mem; use std::ptr; use std::{any::Any, collections::HashMap}; -use super::SignalId; - // rustdoc-stripper-ignore-next /// A newly registered `glib::Type` that is currently still being initialized. /// @@ -951,80 +949,3 @@ pub fn register_type() -> Type { type_ } } - -pub(crate) unsafe fn signal_override_class_handler( - name: &str, - type_: ffi::GType, - class_handler: F, -) where - F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option + Send + Sync + 'static, -{ - let (signal_id, _) = SignalId::parse_name(name, from_glib(type_), false) - .unwrap_or_else(|| panic!("Signal '{}' not found", name)); - - let query = signal_id.query(); - let return_type = query.return_type(); - - let class_handler = Closure::new(move |values| { - let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0); - let res = class_handler( - &super::SignalClassHandlerToken( - instance as *mut _, - return_type.into(), - values.as_ptr(), - ), - values, - ); - - if return_type == Type::UNIT { - if let Some(ref v) = res { - panic!( - "Signal has no return value but class handler returned a value of type {}", - v.type_() - ); - } - } else { - match res { - None => { - panic!("Signal has a return value but class handler returned none"); - } - Some(ref v) => { - assert!( - v.type_().is_a(return_type.into()), - "Signal has a return type of {} but class handler returned {}", - Type::from(return_type), - v.type_() - ); - } - } - } - - res - }); - - gobject_ffi::g_signal_override_class_closure( - signal_id.into_glib(), - type_, - class_handler.to_glib_none().0, - ); -} - -pub(crate) unsafe fn signal_chain_from_overridden( - instance: *mut gobject_ffi::GTypeInstance, - token: &super::SignalClassHandlerToken, - values: &[Value], -) -> Option { - assert_eq!(instance, token.0); - assert_eq!( - values.as_ptr(), - token.2, - "Arguments must be forwarded without changes when chaining up" - ); - - let mut result = Value::from_type(token.1); - gobject_ffi::g_signal_chain_from_overridden( - values.as_ptr() as *mut Value as *mut gobject_ffi::GValue, - result.to_glib_none_mut().0, - ); - Some(result).filter(|r| r.type_().is_valid() && r.type_() != Type::UNIT) -}