// Take a look at the license at the top of the repository in the LICENSE file. use std::{fmt, num::NonZeroU32, ptr, sync::Mutex}; use crate::{ prelude::*, translate::*, utils::is_canonical_pspec_name, Closure, IntoGStr, SignalFlags, Type, Value, }; // rustdoc-stripper-ignore-next /// Builder for signals. #[allow(clippy::type_complexity)] #[must_use = "The builder must be built to be used"] pub struct SignalBuilder { name: String, flags: SignalFlags, param_types: Vec, return_type: SignalType, class_handler: Option< Box Option + Send + Sync + 'static>, >, accumulator: Option< Box bool + Send + Sync + 'static>, >, } // rustdoc-stripper-ignore-next /// Signal metadata. pub struct Signal { name: String, flags: SignalFlags, param_types: Vec, return_type: SignalType, 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 { #[inline] pub fn detail(&self) -> Option { unsafe { try_from_glib(self.0.detail).ok() } } #[inline] 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() } } // rustdoc-stripper-ignore-next /// In-depth information of a specific signal pub struct SignalQuery(gobject_ffi::GSignalQuery); unsafe impl Send for SignalQuery {} unsafe impl Sync for SignalQuery {} impl SignalQuery { // rustdoc-stripper-ignore-next /// The name of the signal. #[inline] pub fn signal_name<'a>(&self) -> &'a str { unsafe { let ptr = self.0.signal_name; std::ffi::CStr::from_ptr(ptr).to_str().unwrap() } } // rustdoc-stripper-ignore-next /// The ID of the signal. #[inline] pub fn signal_id(&self) -> SignalId { unsafe { SignalId::from_glib(self.0.signal_id) } } // rustdoc-stripper-ignore-next /// The instance type this signal can be emitted for. #[inline] pub fn type_(&self) -> Type { unsafe { from_glib(self.0.itype) } } // rustdoc-stripper-ignore-next /// The signal flags. #[inline] pub fn flags(&self) -> SignalFlags { unsafe { from_glib(self.0.signal_flags) } } // rustdoc-stripper-ignore-next /// The return type for the user callback. #[inline] pub fn return_type(&self) -> SignalType { unsafe { from_glib(self.0.return_type) } } // rustdoc-stripper-ignore-next /// The number of parameters the user callback takes. #[inline] pub fn n_params(&self) -> u32 { self.0.n_params } // rustdoc-stripper-ignore-next /// The parameters for the user callback. #[inline] pub fn param_types(&self) -> &[SignalType] { if self.n_params() == 0 { return &[]; } unsafe { std::slice::from_raw_parts( self.0.param_types as *const SignalType, self.0.n_params as usize, ) } } } impl fmt::Debug for SignalQuery { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { f.debug_struct("SignalQuery") .field("signal_name", &self.signal_name()) .field("type", &self.type_()) .field("flags", &self.flags()) .field("return_type", &self.return_type()) .field("param_types", &self.param_types()) .finish() } } // rustdoc-stripper-ignore-next /// Signal ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SignalId(NonZeroU32); impl SignalId { // rustdoc-stripper-ignore-next /// Create a new Signal Identifier. /// /// # Safety /// /// The caller has to ensure it's a valid signal identifier. #[inline] pub unsafe fn new(id: NonZeroU32) -> Self { Self(id) } #[doc(alias = "g_signal_parse_name")] #[inline] pub fn parse_name( name: &str, type_: Type, force_detail: bool, ) -> Option<(Self, Option)> { let mut signal_id = std::mem::MaybeUninit::uninit(); let mut detail_quark = std::mem::MaybeUninit::uninit(); unsafe { let found: bool = name.run_with_gstr(|name| { from_glib(gobject_ffi::g_signal_parse_name( name.as_ptr(), type_.into_glib(), signal_id.as_mut_ptr(), detail_quark.as_mut_ptr(), force_detail.into_glib(), )) }); if found { Some(( from_glib(signal_id.assume_init()), crate::Quark::try_from_glib(detail_quark.assume_init()).ok(), )) } else { None } } } // rustdoc-stripper-ignore-next /// Find a SignalId by its `name`, and the `type` it connects to. #[doc(alias = "g_signal_lookup")] #[inline] pub fn lookup(name: &str, type_: Type) -> Option { unsafe { let signal_id = name.run_with_gstr(|name| { gobject_ffi::g_signal_lookup(name.as_ptr(), type_.into_glib()) }); if signal_id == 0 { None } else { Some(Self::new(NonZeroU32::new_unchecked(signal_id))) } } } // rustdoc-stripper-ignore-next /// Queries more in-depth information about the current signal. #[doc(alias = "g_signal_query")] #[inline] pub fn query(&self) -> SignalQuery { unsafe { let mut query_ptr = std::mem::MaybeUninit::uninit(); gobject_ffi::g_signal_query(self.into_glib(), query_ptr.as_mut_ptr()); let query = query_ptr.assume_init(); debug_assert_ne!(query.signal_id, 0); SignalQuery(query) } } // rustdoc-stripper-ignore-next /// Find the signal name. #[doc(alias = "g_signal_name")] #[inline] pub fn name<'a>(&self) -> &'a str { unsafe { let ptr = gobject_ffi::g_signal_name(self.into_glib()); std::ffi::CStr::from_ptr(ptr).to_str().unwrap() } } } #[doc(hidden)] impl FromGlib for SignalId { #[inline] unsafe fn from_glib(signal_id: u32) -> Self { debug_assert_ne!(signal_id, 0); Self::new(NonZeroU32::new_unchecked(signal_id)) } } #[doc(hidden)] impl IntoGlib for SignalId { type GlibType = u32; #[inline] fn into_glib(self) -> u32 { self.0.into() } } #[derive(Copy, Clone, Hash)] #[repr(transparent)] pub struct SignalType(ffi::GType); impl SignalType { #[inline] pub fn with_static_scope(type_: Type) -> Self { Self(type_.into_glib() | gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT) } #[inline] pub fn static_scope(&self) -> bool { (self.0 & gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT) != 0 } #[inline] pub fn type_(&self) -> Type { (*self).into() } } impl From for SignalType { #[inline] fn from(type_: Type) -> Self { Self(type_.into_glib()) } } impl From for Type { #[inline] fn from(type_: SignalType) -> Self { // Remove the extra-bit used for G_SIGNAL_TYPE_STATIC_SCOPE let type_ = type_.0 & (!gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT); unsafe { from_glib(type_) } } } impl PartialEq for SignalType { #[inline] fn eq(&self, other: &Type) -> bool { let type_: Type = (*self).into(); type_.eq(other) } } impl std::fmt::Debug for SignalType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let type_: Type = (*self).into(); f.debug_struct("SignalType") .field("name", &type_.name()) .field("static_scope", &self.static_scope()) .finish() } } impl std::fmt::Display for SignalType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let type_: Type = (*self).into(); f.debug_struct("SignalType") .field("name", &type_.name()) .field("static_scope", &self.static_scope()) .finish() } } #[doc(hidden)] impl FromGlib for SignalType { #[inline] unsafe fn from_glib(type_: ffi::GType) -> Self { Self(type_) } } #[doc(hidden)] impl IntoGlib for SignalType { type GlibType = ffi::GType; #[inline] fn into_glib(self) -> ffi::GType { self.0 } } #[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: SignalId, }, } impl SignalBuilder { // rustdoc-stripper-ignore-next /// The signal's parameters. pub fn param_types( mut self, param_types: impl IntoIterator>, ) -> Self { self.param_types = param_types .into_iter() .map(|t| t.into()) .collect::>(); self } // rustdoc-stripper-ignore-next /// The signal's returned value type. pub fn return_type(mut self) -> Self { self.return_type = T::static_type().into(); self } // rustdoc-stripper-ignore-next /// The signal's returned value type. pub fn return_type_from(mut self, type_: impl Into) -> Self { self.return_type = type_.into(); self } // rustdoc-stripper-ignore-next /// Run the signal class handler in the first emission stage. pub fn run_first(mut self) -> Self { self.flags |= SignalFlags::RUN_FIRST; self } // rustdoc-stripper-ignore-next /// Run the signal class handler in the third emission stage. pub fn run_last(mut self) -> Self { self.flags |= SignalFlags::RUN_LAST; self } // rustdoc-stripper-ignore-next /// Run the signal class handler in the last emission stage. pub fn run_cleanup(mut self) -> Self { self.flags |= SignalFlags::RUN_CLEANUP; self } // rustdoc-stripper-ignore-next /// 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 } // rustdoc-stripper-ignore-next /// 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 } // rustdoc-stripper-ignore-next /// 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 } // rustdoc-stripper-ignore-next /// No emissions hooks are supported for this signal. pub fn no_hooks(mut self) -> Self { self.flags |= SignalFlags::NO_HOOKS; self } // rustdoc-stripper-ignore-next /// 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 } // rustdoc-stripper-ignore-next /// The signal is deprecated and will be removed in a future version. pub fn deprecated(mut self) -> Self { self.flags |= SignalFlags::DEPRECATED; self } // rustdoc-stripper-ignore-next /// Explicitly set all flags. /// /// This overrides previously set flags on this builder. pub fn flags(mut self, flags: SignalFlags) -> Self { self.flags = flags; self } // rustdoc-stripper-ignore-next /// 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 } // 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. 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 } // rustdoc-stripper-ignore-next /// Build the signal. /// /// This does not register the signal yet, which only happens as part of object type /// registration. #[must_use = "Signal returned from the builder must be used for it to be registered"] 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: self.name, flags, param_types: self.param_types.to_vec(), return_type: self.return_type, registration: Mutex::new(SignalRegistration::Unregistered { class_handler: self.class_handler, accumulator: self.accumulator, }), } } } impl Signal { // rustdoc-stripper-ignore-next /// Create a new builder for a signal. pub fn builder(name: &str) -> SignalBuilder { assert!( is_canonical_pspec_name(name), "{name} is not a valid canonical signal name", ); SignalBuilder { name: name.to_owned(), param_types: Vec::default(), return_type: <()>::static_type().into(), flags: SignalFlags::empty(), class_handler: None, accumulator: None, } } // rustdoc-stripper-ignore-next /// Name of the signal. #[inline] pub fn name(&self) -> &str { &self.name } // rustdoc-stripper-ignore-next /// Flags of the signal. #[inline] pub fn flags(&self) -> SignalFlags { self.flags } // rustdoc-stripper-ignore-next /// Parameter types of the signal. #[inline] pub fn param_types(&self) -> &[SignalType] { &self.param_types } // rustdoc-stripper-ignore-next /// Return type of the signal. #[inline] pub fn return_type(&self) -> SignalType { self.return_type } // rustdoc-stripper-ignore-next /// Signal ID. /// /// This will panic if called before the signal was registered. #[inline] pub fn signal_id(&self) -> SignalId { match &*self.registration.lock().unwrap() { SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"), SignalRegistration::Registered { signal_id, .. } => *signal_id, } } // rustdoc-stripper-ignore-next /// Type this signal was registered for. /// /// This will panic if called before the signal was registered. #[inline] 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 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); 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 }) }); 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 ( SignalType, Box< dyn Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static, >, )); let return_accu = &mut *(return_accu as *mut Value); let handler_return = &*(handler_return as *const Value); let return_type = accumulator.0; assert!( handler_return.type_().is_a(return_type.into()), "Signal has a return type of {} but handler returned {}", Type::from(return_type), handler_return.type_() ); let res = (accumulator.1)(&SignalInvocationHint(*ihint), return_accu, handler_return) .into_glib(); assert!( return_accu.type_().is_a(return_type.into()), "Signal has a return type of {} but accumulator returned {}", Type::from(return_type), return_accu.type_() ); res } let (accumulator, accumulator_trampoline) = if let (Some(accumulator), true) = (accumulator, return_type != Type::UNIT) { ( Box::into_raw(Box::new((return_type, accumulator))), Some:: _>(accumulator_trampoline), ) } else { (ptr::null_mut(), None) }; unsafe { let signal_id = gobject_ffi::g_signal_newv( self.name.to_glib_none().0, type_.into_glib(), self.flags.into_glib(), class_handler.to_glib_none().0, accumulator_trampoline, accumulator as ffi::gpointer, None, return_type.into_glib(), self.param_types.len() as u32, self.param_types.as_ptr() as *mut _, ); *registration = SignalRegistration::Registered { type_, signal_id: SignalId::from_glib(signal_id), }; } } }