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    Closure, SignalFlags, Type, Value, ffi, gobject_ffi, prelude::*, translate::*,
7    utils::is_canonical_pspec_name,
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        unsafe {
268            debug_assert_ne!(signal_id, 0);
269            Self::new(NonZeroU32::new_unchecked(signal_id))
270        }
271    }
272}
273
274#[doc(hidden)]
275impl IntoGlib for SignalId {
276    type GlibType = u32;
277
278    #[inline]
279    fn into_glib(self) -> u32 {
280        self.0.into()
281    }
282}
283
284#[derive(Copy, Clone, Hash)]
285#[repr(transparent)]
286pub struct SignalType(ffi::GType);
287
288impl SignalType {
289    #[inline]
290    pub fn with_static_scope(type_: Type) -> Self {
291        Self(type_.into_glib() | gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT)
292    }
293
294    #[inline]
295    pub fn static_scope(&self) -> bool {
296        (self.0 & gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT) != 0
297    }
298
299    #[inline]
300    pub fn type_(&self) -> Type {
301        (*self).into()
302    }
303}
304
305impl From<Type> for SignalType {
306    #[inline]
307    fn from(type_: Type) -> Self {
308        Self(type_.into_glib())
309    }
310}
311
312impl From<SignalType> for Type {
313    #[inline]
314    fn from(type_: SignalType) -> Self {
315        // Remove the extra-bit used for G_SIGNAL_TYPE_STATIC_SCOPE
316        let type_ = type_.0 & (!gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT);
317        unsafe { from_glib(type_) }
318    }
319}
320
321impl PartialEq<Type> for SignalType {
322    #[inline]
323    fn eq(&self, other: &Type) -> bool {
324        let type_: Type = (*self).into();
325        type_.eq(other)
326    }
327}
328
329impl std::fmt::Debug for SignalType {
330    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
331        let type_: Type = (*self).into();
332        f.debug_struct("SignalType")
333            .field("name", &type_.name())
334            .field("static_scope", &self.static_scope())
335            .finish()
336    }
337}
338
339#[doc(hidden)]
340impl FromGlib<ffi::GType> for SignalType {
341    #[inline]
342    unsafe fn from_glib(type_: ffi::GType) -> Self {
343        Self(type_)
344    }
345}
346
347#[doc(hidden)]
348impl IntoGlib for SignalType {
349    type GlibType = ffi::GType;
350
351    #[inline]
352    fn into_glib(self) -> ffi::GType {
353        self.0
354    }
355}
356
357#[allow(clippy::type_complexity)]
358enum SignalRegistration {
359    Unregistered {
360        class_handler: Option<Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>>,
361        accumulator: Option<
362            Box<
363                dyn Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
364                    + Send
365                    + Sync
366                    + 'static,
367            >,
368        >,
369    },
370    Registered {
371        type_: Type,
372        signal_id: SignalId,
373    },
374}
375
376impl SignalBuilder {
377    // rustdoc-stripper-ignore-next
378    /// The signal's parameters.
379    pub fn param_types(
380        mut self,
381        param_types: impl IntoIterator<Item = impl Into<SignalType>>,
382    ) -> Self {
383        self.param_types = param_types
384            .into_iter()
385            .map(|t| t.into())
386            .collect::<Vec<_>>();
387        self
388    }
389
390    // rustdoc-stripper-ignore-next
391    /// The signal's returned value type.
392    pub fn return_type<T: StaticType>(mut self) -> Self {
393        self.return_type = T::static_type().into();
394        self
395    }
396
397    // rustdoc-stripper-ignore-next
398    /// The signal's returned value type.
399    pub fn return_type_from(mut self, type_: impl Into<SignalType>) -> Self {
400        self.return_type = type_.into();
401        self
402    }
403
404    // rustdoc-stripper-ignore-next
405    /// Run the signal class handler in the first emission stage.
406    pub fn run_first(mut self) -> Self {
407        self.flags |= SignalFlags::RUN_FIRST;
408        self
409    }
410
411    // rustdoc-stripper-ignore-next
412    /// Run the signal class handler in the third emission stage.
413    pub fn run_last(mut self) -> Self {
414        self.flags |= SignalFlags::RUN_LAST;
415        self
416    }
417
418    // rustdoc-stripper-ignore-next
419    /// Run the signal class handler in the last emission stage.
420    pub fn run_cleanup(mut self) -> Self {
421        self.flags |= SignalFlags::RUN_CLEANUP;
422        self
423    }
424
425    // rustdoc-stripper-ignore-next
426    /// Signals being emitted for an object while currently being in emission for this very object
427    /// will not be emitted recursively, but instead cause the first emission to be restarted.
428    pub fn no_recurse(mut self) -> Self {
429        self.flags |= SignalFlags::NO_RECURSE;
430        self
431    }
432
433    // rustdoc-stripper-ignore-next
434    /// This signal supports "::detail" appendices to the signal name upon handler connections and
435    /// emissions.
436    pub fn detailed(mut self) -> Self {
437        self.flags |= SignalFlags::DETAILED;
438        self
439    }
440
441    // rustdoc-stripper-ignore-next
442    /// Action signals are signals that may freely be emitted on alive objects from user code.
443    pub fn action(mut self) -> Self {
444        self.flags |= SignalFlags::ACTION;
445        self
446    }
447
448    // rustdoc-stripper-ignore-next
449    /// No emissions hooks are supported for this signal.
450    pub fn no_hooks(mut self) -> Self {
451        self.flags |= SignalFlags::NO_HOOKS;
452        self
453    }
454
455    // rustdoc-stripper-ignore-next
456    /// Varargs signal emission will always collect the arguments, even if there are no signal
457    /// handlers connected.
458    pub fn must_collect(mut self) -> Self {
459        self.flags |= SignalFlags::MUST_COLLECT;
460        self
461    }
462
463    // rustdoc-stripper-ignore-next
464    /// The signal is deprecated and will be removed in a future version.
465    pub fn deprecated(mut self) -> Self {
466        self.flags |= SignalFlags::DEPRECATED;
467        self
468    }
469
470    // rustdoc-stripper-ignore-next
471    /// Explicitly set all flags.
472    ///
473    /// This overrides previously set flags on this builder.
474    pub fn flags(mut self, flags: SignalFlags) -> Self {
475        self.flags = flags;
476        self
477    }
478
479    // rustdoc-stripper-ignore-next
480    /// Class handler for this signal.
481    pub fn class_handler<F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static>(
482        mut self,
483        func: F,
484    ) -> Self {
485        self.class_handler = Some(Box::new(func));
486        self
487    }
488
489    // rustdoc-stripper-ignore-next
490    /// Accumulator for the return values of the signal.
491    ///
492    /// This is called if multiple signal handlers are connected to the signal for accumulating the
493    /// return values into a single value.
494    pub fn accumulator<
495        F: Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
496            + Send
497            + Sync
498            + 'static,
499    >(
500        mut self,
501        func: F,
502    ) -> Self {
503        self.accumulator = Some(Box::new(func));
504        self
505    }
506
507    // rustdoc-stripper-ignore-next
508    /// Build the signal.
509    ///
510    /// This does not register the signal yet, which only happens as part of object type
511    /// registration.
512    #[must_use = "Signal returned from the builder must be used for it to be registered"]
513    pub fn build(self) -> Signal {
514        let flags = if self.flags
515            & (SignalFlags::RUN_FIRST | SignalFlags::RUN_LAST | SignalFlags::RUN_CLEANUP)
516            == SignalFlags::empty()
517        {
518            self.flags | SignalFlags::RUN_LAST
519        } else {
520            self.flags
521        };
522
523        Signal {
524            name: self.name,
525            flags,
526            param_types: self.param_types.to_vec(),
527            return_type: self.return_type,
528            registration: Mutex::new(SignalRegistration::Unregistered {
529                class_handler: self.class_handler,
530                accumulator: self.accumulator,
531            }),
532        }
533    }
534}
535
536impl Signal {
537    // rustdoc-stripper-ignore-next
538    /// Create a new builder for a signal.
539    pub fn builder(name: &str) -> SignalBuilder {
540        assert!(
541            is_canonical_pspec_name(name),
542            "{name} is not a valid canonical signal name",
543        );
544        SignalBuilder {
545            name: name.to_owned(),
546            param_types: Vec::default(),
547            return_type: <()>::static_type().into(),
548            flags: SignalFlags::empty(),
549            class_handler: None,
550            accumulator: None,
551        }
552    }
553
554    // rustdoc-stripper-ignore-next
555    /// Name of the signal.
556    #[inline]
557    pub fn name(&self) -> &str {
558        &self.name
559    }
560
561    // rustdoc-stripper-ignore-next
562    /// Flags of the signal.
563    #[inline]
564    pub fn flags(&self) -> SignalFlags {
565        self.flags
566    }
567
568    // rustdoc-stripper-ignore-next
569    /// Parameter types of the signal.
570    #[inline]
571    pub fn param_types(&self) -> &[SignalType] {
572        &self.param_types
573    }
574
575    // rustdoc-stripper-ignore-next
576    /// Return type of the signal.
577    #[inline]
578    pub fn return_type(&self) -> SignalType {
579        self.return_type
580    }
581
582    // rustdoc-stripper-ignore-next
583    /// Signal ID.
584    ///
585    /// This will panic if called before the signal was registered.
586    #[inline]
587    pub fn signal_id(&self) -> SignalId {
588        match &*self.registration.lock().unwrap() {
589            SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
590            SignalRegistration::Registered { signal_id, .. } => *signal_id,
591        }
592    }
593
594    // rustdoc-stripper-ignore-next
595    /// Type this signal was registered for.
596    ///
597    /// This will panic if called before the signal was registered.
598    #[inline]
599    pub fn type_(&self) -> Type {
600        match &*self.registration.lock().unwrap() {
601            SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
602            SignalRegistration::Registered { type_, .. } => *type_,
603        }
604    }
605
606    pub(super) fn register(&self, type_: Type) {
607        let mut registration = self.registration.lock().unwrap();
608
609        let (class_handler, accumulator) = match &mut *registration {
610            SignalRegistration::Unregistered {
611                class_handler,
612                accumulator,
613            } => (class_handler.take(), accumulator.take()),
614            SignalRegistration::Registered { .. } => unreachable!(),
615        };
616
617        let return_type = self.return_type;
618
619        let class_handler = class_handler.map(|class_handler| {
620            Closure::new(move |values| {
621                let res = class_handler(values);
622
623                if return_type == Type::UNIT {
624                    if let Some(ref v) = res {
625                        panic!("Signal has no return value but class handler returned a value of type {}", v.type_());
626                    }
627                } else {
628                    match res {
629                        None => {
630                            panic!("Signal has a return value but class handler returned none");
631                        }
632                        Some(ref v) => {
633                            assert!(v.type_().is_a(return_type.into()), "Signal has a return type of {} but class handler returned {}", Type::from(return_type), v.type_());
634                        }
635                    }
636                }
637
638                res
639            })
640        });
641
642        unsafe extern "C" fn accumulator_trampoline(
643            ihint: *mut gobject_ffi::GSignalInvocationHint,
644            return_accu: *mut gobject_ffi::GValue,
645            handler_return: *const gobject_ffi::GValue,
646            data: ffi::gpointer,
647        ) -> ffi::gboolean {
648            unsafe {
649                let accumulator = &*(data as *const (
650                    SignalType,
651                    Box<
652                        dyn Fn(&SignalInvocationHint, Value, &Value) -> ControlFlow<Value, Value>
653                            + Send
654                            + Sync
655                            + 'static,
656                    >,
657                ));
658
659                let return_accu = &mut *(return_accu as *mut Value);
660                let handler_return = &*(handler_return as *const Value);
661                let return_type = accumulator.0;
662
663                assert!(
664                    handler_return.type_().is_a(return_type.into()),
665                    "Signal has a return type of {} but handler returned {}",
666                    Type::from(return_type),
667                    handler_return.type_()
668                );
669
670                let control_flow = (accumulator.1)(
671                    &SignalInvocationHint(*ihint),
672                    std::mem::replace(return_accu, Value::uninitialized()),
673                    handler_return,
674                );
675
676                let res = match control_flow {
677                    ControlFlow::Continue(val) => {
678                        *return_accu = val;
679                        true.into_glib()
680                    }
681
682                    ControlFlow::Break(val) => {
683                        *return_accu = val;
684                        false.into_glib()
685                    }
686                };
687
688                assert!(
689                    return_accu.type_().is_a(return_type.into()),
690                    "Signal has a return type of {} but accumulator returned {}",
691                    Type::from(return_type),
692                    return_accu.type_()
693                );
694
695                res
696            }
697        }
698
699        let (accumulator, accumulator_trampoline) = match (accumulator, return_type != Type::UNIT) {
700            (Some(accumulator), true) => (
701                Box::into_raw(Box::new((return_type, accumulator))),
702                Some::<unsafe extern "C" fn(_, _, _, _) -> _>(accumulator_trampoline),
703            ),
704            _ => (ptr::null_mut(), None),
705        };
706
707        unsafe {
708            let signal_id = gobject_ffi::g_signal_newv(
709                self.name.to_glib_none().0,
710                type_.into_glib(),
711                self.flags.into_glib(),
712                class_handler.to_glib_none().0,
713                accumulator_trampoline,
714                accumulator as ffi::gpointer,
715                None,
716                return_type.into_glib(),
717                self.param_types.len() as u32,
718                self.param_types.as_ptr() as *mut _,
719            );
720            *registration = SignalRegistration::Registered {
721                type_,
722                signal_id: SignalId::from_glib(signal_id),
723            };
724        }
725    }
726}