glib/subclass/
signal.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{fmt, num::NonZeroU32, ops::ControlFlow, ptr, sync::Mutex};
4
5use crate::{
6    ffi, gobject_ffi, prelude::*, translate::*, utils::is_canonical_pspec_name, Closure,
7    SignalFlags, Type, Value,
8};
9
10// rustdoc-stripper-ignore-next
11/// Builder for signals.
12#[allow(clippy::type_complexity)]
13#[must_use = "The builder must be built to be used"]
14pub struct SignalBuilder {
15    name: String,
16    flags: SignalFlags,
17    param_types: Vec<SignalType>,
18    return_type: SignalType,
19    class_handler: Option<Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>>,
20    accumulator: Option<
21        Box<
22            dyn Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
23                + Send
24                + Sync
25                + 'static,
26        >,
27    >,
28}
29
30// rustdoc-stripper-ignore-next
31/// Signal metadata.
32pub struct Signal {
33    name: String,
34    flags: SignalFlags,
35    param_types: Vec<SignalType>,
36    return_type: SignalType,
37    registration: Mutex<SignalRegistration>,
38}
39
40// rustdoc-stripper-ignore-next
41/// Token passed to signal class handlers.
42pub struct SignalClassHandlerToken(
43    // rustdoc-stripper-ignore-next
44    /// Instance for which the signal is emitted.
45    pub(super) *mut gobject_ffi::GTypeInstance,
46    // rustdoc-stripper-ignore-next
47    /// Return type.
48    pub(super) Type,
49    // rustdoc-stripper-ignore-next
50    /// Arguments value array.
51    pub(super) *const Value,
52);
53
54impl fmt::Debug for SignalClassHandlerToken {
55    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
56        f.debug_struct("SignalClassHandlerToken")
57            .field("type", &unsafe {
58                crate::Object::from_glib_borrow(self.0 as *mut gobject_ffi::GObject)
59            })
60            .finish()
61    }
62}
63
64// rustdoc-stripper-ignore-next
65/// Signal invocation hint passed to signal accumulators.
66#[repr(transparent)]
67pub struct SignalInvocationHint(gobject_ffi::GSignalInvocationHint);
68
69impl SignalInvocationHint {
70    #[inline]
71    pub fn detail(&self) -> Option<crate::Quark> {
72        unsafe { try_from_glib(self.0.detail).ok() }
73    }
74
75    #[inline]
76    pub fn run_type(&self) -> SignalFlags {
77        unsafe { from_glib(self.0.run_type) }
78    }
79}
80
81impl fmt::Debug for SignalInvocationHint {
82    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
83        f.debug_struct("SignalInvocationHint")
84            .field("detail", &self.detail())
85            .field("run_type", &self.run_type())
86            .finish()
87    }
88}
89
90// rustdoc-stripper-ignore-next
91/// In-depth information of a specific signal
92pub struct SignalQuery(gobject_ffi::GSignalQuery);
93
94unsafe impl Send for SignalQuery {}
95unsafe impl Sync for SignalQuery {}
96
97impl SignalQuery {
98    // rustdoc-stripper-ignore-next
99    /// The name of the signal.
100    #[inline]
101    pub fn signal_name<'a>(&self) -> &'a str {
102        unsafe {
103            let ptr = self.0.signal_name;
104            std::ffi::CStr::from_ptr(ptr).to_str().unwrap()
105        }
106    }
107
108    // rustdoc-stripper-ignore-next
109    /// The ID of the signal.
110    #[inline]
111    pub fn signal_id(&self) -> SignalId {
112        unsafe { SignalId::from_glib(self.0.signal_id) }
113    }
114
115    // rustdoc-stripper-ignore-next
116    /// The instance type this signal can be emitted for.
117    #[inline]
118    pub fn type_(&self) -> Type {
119        unsafe { from_glib(self.0.itype) }
120    }
121
122    // rustdoc-stripper-ignore-next
123    /// The signal flags.
124    #[inline]
125    pub fn flags(&self) -> SignalFlags {
126        unsafe { from_glib(self.0.signal_flags) }
127    }
128
129    // rustdoc-stripper-ignore-next
130    /// The return type for the user callback.
131    #[inline]
132    pub fn return_type(&self) -> SignalType {
133        unsafe { from_glib(self.0.return_type) }
134    }
135
136    // rustdoc-stripper-ignore-next
137    /// The number of parameters the user callback takes.
138    #[inline]
139    pub fn n_params(&self) -> u32 {
140        self.0.n_params
141    }
142
143    // rustdoc-stripper-ignore-next
144    /// The parameters for the user callback.
145    #[inline]
146    pub fn param_types(&self) -> &[SignalType] {
147        if self.n_params() == 0 {
148            return &[];
149        }
150
151        unsafe {
152            std::slice::from_raw_parts(
153                self.0.param_types as *const SignalType,
154                self.0.n_params as usize,
155            )
156        }
157    }
158}
159
160impl fmt::Debug for SignalQuery {
161    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
162        f.debug_struct("SignalQuery")
163            .field("signal_name", &self.signal_name())
164            .field("type", &self.type_())
165            .field("flags", &self.flags())
166            .field("return_type", &self.return_type())
167            .field("param_types", &self.param_types())
168            .finish()
169    }
170}
171
172// rustdoc-stripper-ignore-next
173/// Signal ID.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
175pub struct SignalId(NonZeroU32);
176
177impl SignalId {
178    // rustdoc-stripper-ignore-next
179    /// Create a new Signal Identifier.
180    ///
181    /// # Safety
182    ///
183    /// The caller has to ensure it's a valid signal identifier.
184    #[inline]
185    pub unsafe fn new(id: NonZeroU32) -> Self {
186        Self(id)
187    }
188
189    #[doc(alias = "g_signal_parse_name")]
190    #[inline]
191    pub fn parse_name(
192        name: &str,
193        type_: Type,
194        force_detail: bool,
195    ) -> Option<(Self, Option<crate::Quark>)> {
196        let mut signal_id = std::mem::MaybeUninit::uninit();
197        let mut detail_quark = std::mem::MaybeUninit::uninit();
198        unsafe {
199            let found: bool = name.run_with_gstr(|name| {
200                from_glib(gobject_ffi::g_signal_parse_name(
201                    name.as_ptr(),
202                    type_.into_glib(),
203                    signal_id.as_mut_ptr(),
204                    detail_quark.as_mut_ptr(),
205                    force_detail.into_glib(),
206                ))
207            });
208
209            if found {
210                Some((
211                    from_glib(signal_id.assume_init()),
212                    crate::Quark::try_from_glib(detail_quark.assume_init()).ok(),
213                ))
214            } else {
215                None
216            }
217        }
218    }
219
220    // rustdoc-stripper-ignore-next
221    /// Find a SignalId by its `name`, and the `type` it connects to.
222    #[doc(alias = "g_signal_lookup")]
223    #[inline]
224    pub fn lookup(name: &str, type_: Type) -> Option<Self> {
225        unsafe {
226            let signal_id = name.run_with_gstr(|name| {
227                gobject_ffi::g_signal_lookup(name.as_ptr(), type_.into_glib())
228            });
229            if signal_id == 0 {
230                None
231            } else {
232                Some(Self::new(NonZeroU32::new_unchecked(signal_id)))
233            }
234        }
235    }
236
237    // rustdoc-stripper-ignore-next
238    /// Queries more in-depth information about the current signal.
239    #[doc(alias = "g_signal_query")]
240    #[inline]
241    pub fn query(&self) -> SignalQuery {
242        unsafe {
243            let mut query_ptr = std::mem::MaybeUninit::uninit();
244            gobject_ffi::g_signal_query(self.into_glib(), query_ptr.as_mut_ptr());
245            let query = query_ptr.assume_init();
246            debug_assert_ne!(query.signal_id, 0);
247            SignalQuery(query)
248        }
249    }
250
251    // rustdoc-stripper-ignore-next
252    /// Find the signal name.
253    #[doc(alias = "g_signal_name")]
254    #[inline]
255    pub fn name<'a>(&self) -> &'a str {
256        unsafe {
257            let ptr = gobject_ffi::g_signal_name(self.into_glib());
258            std::ffi::CStr::from_ptr(ptr).to_str().unwrap()
259        }
260    }
261}
262
263#[doc(hidden)]
264impl FromGlib<u32> for SignalId {
265    #[inline]
266    unsafe fn from_glib(signal_id: u32) -> Self {
267        debug_assert_ne!(signal_id, 0);
268        Self::new(NonZeroU32::new_unchecked(signal_id))
269    }
270}
271
272#[doc(hidden)]
273impl IntoGlib for SignalId {
274    type GlibType = u32;
275
276    #[inline]
277    fn into_glib(self) -> u32 {
278        self.0.into()
279    }
280}
281
282#[derive(Copy, Clone, Hash)]
283#[repr(transparent)]
284pub struct SignalType(ffi::GType);
285
286impl SignalType {
287    #[inline]
288    pub fn with_static_scope(type_: Type) -> Self {
289        Self(type_.into_glib() | gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT)
290    }
291
292    #[inline]
293    pub fn static_scope(&self) -> bool {
294        (self.0 & gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT) != 0
295    }
296
297    #[inline]
298    pub fn type_(&self) -> Type {
299        (*self).into()
300    }
301}
302
303impl From<Type> for SignalType {
304    #[inline]
305    fn from(type_: Type) -> Self {
306        Self(type_.into_glib())
307    }
308}
309
310impl From<SignalType> for Type {
311    #[inline]
312    fn from(type_: SignalType) -> Self {
313        // Remove the extra-bit used for G_SIGNAL_TYPE_STATIC_SCOPE
314        let type_ = type_.0 & (!gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT);
315        unsafe { from_glib(type_) }
316    }
317}
318
319impl PartialEq<Type> for SignalType {
320    #[inline]
321    fn eq(&self, other: &Type) -> bool {
322        let type_: Type = (*self).into();
323        type_.eq(other)
324    }
325}
326
327impl std::fmt::Debug for SignalType {
328    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
329        let type_: Type = (*self).into();
330        f.debug_struct("SignalType")
331            .field("name", &type_.name())
332            .field("static_scope", &self.static_scope())
333            .finish()
334    }
335}
336
337#[doc(hidden)]
338impl FromGlib<ffi::GType> for SignalType {
339    #[inline]
340    unsafe fn from_glib(type_: ffi::GType) -> Self {
341        Self(type_)
342    }
343}
344
345#[doc(hidden)]
346impl IntoGlib for SignalType {
347    type GlibType = ffi::GType;
348
349    #[inline]
350    fn into_glib(self) -> ffi::GType {
351        self.0
352    }
353}
354
355#[allow(clippy::type_complexity)]
356enum SignalRegistration {
357    Unregistered {
358        class_handler: Option<Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>>,
359        accumulator: Option<
360            Box<
361                dyn Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
362                    + Send
363                    + Sync
364                    + 'static,
365            >,
366        >,
367    },
368    Registered {
369        type_: Type,
370        signal_id: SignalId,
371    },
372}
373
374impl SignalBuilder {
375    // rustdoc-stripper-ignore-next
376    /// The signal's parameters.
377    pub fn param_types(
378        mut self,
379        param_types: impl IntoIterator<Item = impl Into<SignalType>>,
380    ) -> Self {
381        self.param_types = param_types
382            .into_iter()
383            .map(|t| t.into())
384            .collect::<Vec<_>>();
385        self
386    }
387
388    // rustdoc-stripper-ignore-next
389    /// The signal's returned value type.
390    pub fn return_type<T: StaticType>(mut self) -> Self {
391        self.return_type = T::static_type().into();
392        self
393    }
394
395    // rustdoc-stripper-ignore-next
396    /// The signal's returned value type.
397    pub fn return_type_from(mut self, type_: impl Into<SignalType>) -> Self {
398        self.return_type = type_.into();
399        self
400    }
401
402    // rustdoc-stripper-ignore-next
403    /// Run the signal class handler in the first emission stage.
404    pub fn run_first(mut self) -> Self {
405        self.flags |= SignalFlags::RUN_FIRST;
406        self
407    }
408
409    // rustdoc-stripper-ignore-next
410    /// Run the signal class handler in the third emission stage.
411    pub fn run_last(mut self) -> Self {
412        self.flags |= SignalFlags::RUN_LAST;
413        self
414    }
415
416    // rustdoc-stripper-ignore-next
417    /// Run the signal class handler in the last emission stage.
418    pub fn run_cleanup(mut self) -> Self {
419        self.flags |= SignalFlags::RUN_CLEANUP;
420        self
421    }
422
423    // rustdoc-stripper-ignore-next
424    /// Signals being emitted for an object while currently being in emission for this very object
425    /// will not be emitted recursively, but instead cause the first emission to be restarted.
426    pub fn no_recurse(mut self) -> Self {
427        self.flags |= SignalFlags::NO_RECURSE;
428        self
429    }
430
431    // rustdoc-stripper-ignore-next
432    /// This signal supports "::detail" appendices to the signal name upon handler connections and
433    /// emissions.
434    pub fn detailed(mut self) -> Self {
435        self.flags |= SignalFlags::DETAILED;
436        self
437    }
438
439    // rustdoc-stripper-ignore-next
440    /// Action signals are signals that may freely be emitted on alive objects from user code.
441    pub fn action(mut self) -> Self {
442        self.flags |= SignalFlags::ACTION;
443        self
444    }
445
446    // rustdoc-stripper-ignore-next
447    /// No emissions hooks are supported for this signal.
448    pub fn no_hooks(mut self) -> Self {
449        self.flags |= SignalFlags::NO_HOOKS;
450        self
451    }
452
453    // rustdoc-stripper-ignore-next
454    /// Varargs signal emission will always collect the arguments, even if there are no signal
455    /// handlers connected.
456    pub fn must_collect(mut self) -> Self {
457        self.flags |= SignalFlags::MUST_COLLECT;
458        self
459    }
460
461    // rustdoc-stripper-ignore-next
462    /// The signal is deprecated and will be removed in a future version.
463    pub fn deprecated(mut self) -> Self {
464        self.flags |= SignalFlags::DEPRECATED;
465        self
466    }
467
468    // rustdoc-stripper-ignore-next
469    /// Explicitly set all flags.
470    ///
471    /// This overrides previously set flags on this builder.
472    pub fn flags(mut self, flags: SignalFlags) -> Self {
473        self.flags = flags;
474        self
475    }
476
477    // rustdoc-stripper-ignore-next
478    /// Class handler for this signal.
479    pub fn class_handler<F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>(
480        mut self,
481        func: F,
482    ) -> Self {
483        self.class_handler = Some(Box::new(func));
484        self
485    }
486
487    // rustdoc-stripper-ignore-next
488    /// Accumulator for the return values of the signal.
489    ///
490    /// This is called if multiple signal handlers are connected to the signal for accumulating the
491    /// return values into a single value.
492    pub fn accumulator<
493        F: Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
494            + Send
495            + Sync
496            + 'static,
497    >(
498        mut self,
499        func: F,
500    ) -> Self {
501        self.accumulator = Some(Box::new(func));
502        self
503    }
504
505    // rustdoc-stripper-ignore-next
506    /// Build the signal.
507    ///
508    /// This does not register the signal yet, which only happens as part of object type
509    /// registration.
510    #[must_use = "Signal returned from the builder must be used for it to be registered"]
511    pub fn build(self) -> Signal {
512        let flags = if self.flags
513            & (SignalFlags::RUN_FIRST | SignalFlags::RUN_LAST | SignalFlags::RUN_CLEANUP)
514            == SignalFlags::empty()
515        {
516            self.flags | SignalFlags::RUN_LAST
517        } else {
518            self.flags
519        };
520
521        Signal {
522            name: self.name,
523            flags,
524            param_types: self.param_types.to_vec(),
525            return_type: self.return_type,
526            registration: Mutex::new(SignalRegistration::Unregistered {
527                class_handler: self.class_handler,
528                accumulator: self.accumulator,
529            }),
530        }
531    }
532}
533
534impl Signal {
535    // rustdoc-stripper-ignore-next
536    /// Create a new builder for a signal.
537    pub fn builder(name: &str) -> SignalBuilder {
538        assert!(
539            is_canonical_pspec_name(name),
540            "{name} is not a valid canonical signal name",
541        );
542        SignalBuilder {
543            name: name.to_owned(),
544            param_types: Vec::default(),
545            return_type: <()>::static_type().into(),
546            flags: SignalFlags::empty(),
547            class_handler: None,
548            accumulator: None,
549        }
550    }
551
552    // rustdoc-stripper-ignore-next
553    /// Name of the signal.
554    #[inline]
555    pub fn name(&self) -> &str {
556        &self.name
557    }
558
559    // rustdoc-stripper-ignore-next
560    /// Flags of the signal.
561    #[inline]
562    pub fn flags(&self) -> SignalFlags {
563        self.flags
564    }
565
566    // rustdoc-stripper-ignore-next
567    /// Parameter types of the signal.
568    #[inline]
569    pub fn param_types(&self) -> &[SignalType] {
570        &self.param_types
571    }
572
573    // rustdoc-stripper-ignore-next
574    /// Return type of the signal.
575    #[inline]
576    pub fn return_type(&self) -> SignalType {
577        self.return_type
578    }
579
580    // rustdoc-stripper-ignore-next
581    /// Signal ID.
582    ///
583    /// This will panic if called before the signal was registered.
584    #[inline]
585    pub fn signal_id(&self) -> SignalId {
586        match &*self.registration.lock().unwrap() {
587            SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
588            SignalRegistration::Registered { signal_id, .. } => *signal_id,
589        }
590    }
591
592    // rustdoc-stripper-ignore-next
593    /// Type this signal was registered for.
594    ///
595    /// This will panic if called before the signal was registered.
596    #[inline]
597    pub fn type_(&self) -> Type {
598        match &*self.registration.lock().unwrap() {
599            SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
600            SignalRegistration::Registered { type_, .. } => *type_,
601        }
602    }
603
604    pub(super) fn register(&self, type_: Type) {
605        let mut registration = self.registration.lock().unwrap();
606
607        let (class_handler, accumulator) = match &mut *registration {
608            SignalRegistration::Unregistered {
609                class_handler,
610                accumulator,
611            } => (class_handler.take(), accumulator.take()),
612            SignalRegistration::Registered { .. } => unreachable!(),
613        };
614
615        let return_type = self.return_type;
616
617        let class_handler = class_handler.map(|class_handler| {
618            Closure::new(move |values| {
619                let res = class_handler(values);
620
621                if return_type == Type::UNIT {
622                    if let Some(ref v) = res {
623                        panic!("Signal has no return value but class handler returned a value of type {}", v.type_());
624                    }
625                } else {
626                    match res {
627                        None => {
628                            panic!("Signal has a return value but class handler returned none");
629                        }
630                        Some(ref v) => {
631                            assert!(v.type_().is_a(return_type.into()), "Signal has a return type of {} but class handler returned {}", Type::from(return_type), v.type_());
632                        }
633                    }
634                }
635
636                res
637            })
638        });
639
640        unsafe extern "C" fn accumulator_trampoline(
641            ihint: *mut gobject_ffi::GSignalInvocationHint,
642            return_accu: *mut gobject_ffi::GValue,
643            handler_return: *const gobject_ffi::GValue,
644            data: ffi::gpointer,
645        ) -> ffi::gboolean {
646            let accumulator = &*(data as *const (
647                SignalType,
648                Box<
649                    dyn Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
650                        + Send
651                        + Sync
652                        + 'static,
653                >,
654            ));
655
656            let return_accu = &mut *(return_accu as *mut Value);
657            let handler_return = &*(handler_return as *const Value);
658            let return_type = accumulator.0;
659
660            assert!(
661                handler_return.type_().is_a(return_type.into()),
662                "Signal has a return type of {} but handler returned {}",
663                Type::from(return_type),
664                handler_return.type_()
665            );
666
667            let control_flow = (accumulator.1)(
668                &SignalInvocationHint(*ihint),
669                std::mem::replace(return_accu, Value::uninitialized()),
670                handler_return,
671            );
672
673            let res = match control_flow {
674                ControlFlow::Continue(val) => {
675                    *return_accu = val;
676                    true.into_glib()
677                }
678
679                ControlFlow::Break(val) => {
680                    *return_accu = val;
681                    false.into_glib()
682                }
683            };
684
685            assert!(
686                return_accu.type_().is_a(return_type.into()),
687                "Signal has a return type of {} but accumulator returned {}",
688                Type::from(return_type),
689                return_accu.type_()
690            );
691
692            res
693        }
694
695        let (accumulator, accumulator_trampoline) =
696            if let (Some(accumulator), true) = (accumulator, return_type != Type::UNIT) {
697                (
698                    Box::into_raw(Box::new((return_type, accumulator))),
699                    Some::<unsafe extern "C" fn(_, _, _, _) -> _>(accumulator_trampoline),
700                )
701            } else {
702                (ptr::null_mut(), None)
703            };
704
705        unsafe {
706            let signal_id = gobject_ffi::g_signal_newv(
707                self.name.to_glib_none().0,
708                type_.into_glib(),
709                self.flags.into_glib(),
710                class_handler.to_glib_none().0,
711                accumulator_trampoline,
712                accumulator as ffi::gpointer,
713                None,
714                return_type.into_glib(),
715                self.param_types.len() as u32,
716                self.param_types.as_ptr() as *mut _,
717            );
718            *registration = SignalRegistration::Registered {
719                type_,
720                signal_id: SignalId::from_glib(signal_id),
721            };
722        }
723    }
724}