Skip to main content

glib/gobject/
signal_group.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem::transmute;
4
5use crate::{
6    Object, RustClosure, SignalGroup, Value, ffi, gobject_ffi,
7    prelude::*,
8    signal::{SignalHandlerId, connect_raw},
9    translate::*,
10};
11
12impl SignalGroup {
13    #[doc(alias = "g_signal_group_new")]
14    pub fn new<T: IsA<Object>>() -> Self {
15        Self::with_type(T::static_type())
16    }
17    /// Connects @closure to the signal @detailed_signal on #GSignalGroup:target.
18    ///
19    /// You cannot connect a signal handler after #GSignalGroup:target has been set.
20    /// ## `detailed_signal`
21    /// a string of the form `signal-name` with optional `::signal-detail`
22    /// ## `closure`
23    /// the closure to connect.
24    /// ## `after`
25    /// whether the handler should be called before or after the
26    ///  default handler of the signal.
27    // rustdoc-stripper-ignore-next-stop
28    /// Connects @closure to the signal @detailed_signal on #GSignalGroup:target.
29    ///
30    /// You cannot connect a signal handler after #GSignalGroup:target has been set.
31    /// ## `detailed_signal`
32    /// a string of the form `signal-name` with optional `::signal-detail`
33    /// ## `closure`
34    /// the closure to connect.
35    /// ## `after`
36    /// whether the handler should be called before or after the
37    ///  default handler of the signal.
38    #[doc(alias = "g_signal_group_connect_closure")]
39    pub fn connect_closure(&self, signal_name: &str, after: bool, closure: RustClosure) {
40        unsafe {
41            gobject_ffi::g_signal_group_connect_closure(
42                self.to_glib_none().0,
43                signal_name.to_glib_none().0,
44                closure.as_ref().to_glib_none().0,
45                after.into_glib(),
46            );
47        }
48    }
49
50    /// Connects @c_handler to the signal @detailed_signal
51    /// on the target instance of @self.
52    ///
53    /// You cannot connect a signal handler after #GSignalGroup:target has been set.
54    /// ## `detailed_signal`
55    /// a string of the form "signal-name::detail"
56    /// ## `c_handler`
57    /// the #GCallback to connect
58    // rustdoc-stripper-ignore-next-stop
59    /// Connects @c_handler to the signal @detailed_signal
60    /// on the target instance of @self.
61    ///
62    /// You cannot connect a signal handler after #GSignalGroup:target has been set.
63    /// ## `detailed_signal`
64    /// a string of the form "signal-name::detail"
65    /// ## `c_handler`
66    /// the #GCallback to connect
67    #[doc(alias = "g_signal_group_connect")]
68    #[inline]
69    pub fn connect<F>(&self, signal_name: &str, after: bool, callback: F)
70    where
71        F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
72    {
73        self.connect_closure(signal_name, after, RustClosure::new(callback));
74    }
75
76    // rustdoc-stripper-ignore-next
77    /// Like [`Self::connect`] but doesn't require a `Send+Sync` closure. Signal emission will
78    /// panic if the signal on the current target is emitted from a different thread from the
79    /// thread that connected the signal.
80    #[inline]
81    pub fn connect_local<F>(&self, signal_name: &str, after: bool, callback: F)
82    where
83        F: Fn(&[Value]) -> Option<Value> + 'static,
84    {
85        self.connect_closure(signal_name, after, RustClosure::new_local(callback));
86    }
87
88    #[inline]
89    pub fn connect_notify<F>(&self, name: Option<&str>, callback: F)
90    where
91        F: Fn(&crate::Object, &crate::ParamSpec) + Send + Sync + 'static,
92    {
93        let signal_name = if let Some(name) = name {
94            format!("notify::{name}")
95        } else {
96            "notify".into()
97        };
98
99        let closure = crate::RustClosure::new(move |values| {
100            let obj = values[0].get().unwrap();
101            let pspec = values[1].get().unwrap();
102            callback(obj, pspec);
103
104            None
105        });
106
107        self.connect_closure(&signal_name, false, closure);
108    }
109
110    #[inline]
111    pub fn connect_notify_local<F>(&self, name: Option<&str>, callback: F)
112    where
113        F: Fn(&crate::Object, &crate::ParamSpec) + 'static,
114    {
115        let signal_name = if let Some(name) = name {
116            format!("notify::{name}")
117        } else {
118            "notify".into()
119        };
120
121        let closure = crate::RustClosure::new_local(move |values| {
122            let obj = values[0].get().unwrap();
123            let pspec = values[1].get().unwrap();
124            callback(obj, pspec);
125
126            None
127        });
128
129        self.connect_closure(&signal_name, false, closure);
130    }
131
132    unsafe fn connect_bind_unsafe<F: Fn(&Self, &Object)>(&self, f: F) -> SignalHandlerId {
133        unsafe {
134            unsafe extern "C" fn bind_trampoline<F: Fn(&SignalGroup, &Object)>(
135                this: *mut crate::gobject_ffi::GSignalGroup,
136                instance: *mut crate::gobject_ffi::GObject,
137                f: ffi::gpointer,
138            ) {
139                unsafe {
140                    let f: &F = &*(f as *const F);
141                    f(&from_glib_borrow(this), &from_glib_borrow(instance))
142                }
143            }
144            let f: Box<F> = Box::new(f);
145            connect_raw(
146                self.as_ptr() as *mut _,
147                b"bind\0".as_ptr() as *const _,
148                Some(transmute::<*const (), unsafe extern "C" fn()>(
149                    bind_trampoline::<F> as *const (),
150                )),
151                Box::into_raw(f),
152            )
153        }
154    }
155
156    unsafe fn connect_unbind_unsafe<F: Fn(&Self)>(&self, f: F) -> SignalHandlerId {
157        unsafe {
158            unsafe extern "C" fn unbind_trampoline<F: Fn(&SignalGroup)>(
159                this: *mut crate::gobject_ffi::GSignalGroup,
160                f: ffi::gpointer,
161            ) {
162                unsafe {
163                    let f: &F = &*(f as *const F);
164                    f(&from_glib_borrow(this))
165                }
166            }
167            let f: Box<F> = Box::new(f);
168            connect_raw(
169                self.as_ptr() as *mut _,
170                b"unbind\0".as_ptr() as *const _,
171                Some(transmute::<*const (), unsafe extern "C" fn()>(
172                    unbind_trampoline::<F> as *const (),
173                )),
174                Box::into_raw(f),
175            )
176        }
177    }
178
179    /// This signal is emitted when #GSignalGroup:target is set to a new value
180    /// other than [`None`]. It is similar to #GObject::notify on `target` except it
181    /// will not emit when #GSignalGroup:target is [`None`] and also allows for
182    /// receiving the #GObject without a data-race.
183    /// ## `instance`
184    /// a #GObject containing the new value for #GSignalGroup:target
185    // rustdoc-stripper-ignore-next-stop
186    /// This signal is emitted when #GSignalGroup:target is set to a new value
187    /// other than [`None`]. It is similar to #GObject::notify on `target` except it
188    /// will not emit when #GSignalGroup:target is [`None`] and also allows for
189    /// receiving the #GObject without a data-race.
190    /// ## `instance`
191    /// a #GObject containing the new value for #GSignalGroup:target
192    #[doc(alias = "bind")]
193    pub fn connect_bind<F: Fn(&Self, &Object) + Send + Sync + 'static>(
194        &self,
195        f: F,
196    ) -> SignalHandlerId {
197        unsafe { self.connect_bind_unsafe(f) }
198    }
199
200    // rustdoc-stripper-ignore-next
201    /// Like [`Self::connect_bind`] but doesn't require a `Send+Sync` closure. Signal emission will
202    /// panic if the signal is emitted from a different thread from the thread that connected the
203    /// signal.
204    pub fn connect_bind_local<F: Fn(&Self, &Object) + 'static>(&self, f: F) -> SignalHandlerId {
205        let f = crate::thread_guard::ThreadGuard::new(f);
206
207        unsafe {
208            self.connect_bind_unsafe(move |s, o| {
209                (f.get_ref())(s, o);
210            })
211        }
212    }
213
214    /// This signal is emitted when the target instance of @self_ is set to a
215    /// new #GObject.
216    ///
217    /// This signal will only be emitted if the previous target of @self_ is
218    /// non-[`None`].
219    // rustdoc-stripper-ignore-next-stop
220    /// This signal is emitted when the target instance of @self_ is set to a
221    /// new #GObject.
222    ///
223    /// This signal will only be emitted if the previous target of @self_ is
224    /// non-[`None`].
225    #[doc(alias = "unbind")]
226    pub fn connect_unbind<F: Fn(&Self) + Send + Sync + 'static>(&self, f: F) -> SignalHandlerId {
227        unsafe { self.connect_unbind_unsafe(f) }
228    }
229
230    // rustdoc-stripper-ignore-next
231    /// Like [`Self::connect_unbind`] but doesn't require a `Send+Sync` closure. Signal emission
232    /// will panic if the signal is emitted from a different thread from the thread that connected
233    /// the signal.
234    pub fn connect_unbind_local<F: Fn(&Self) + 'static>(&self, f: F) -> SignalHandlerId {
235        let f = crate::thread_guard::ThreadGuard::new(f);
236
237        unsafe {
238            self.connect_unbind_unsafe(move |s| {
239                (f.get_ref())(s);
240            })
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use std::{cell::RefCell, rc::Rc, sync::OnceLock};
248
249    use super::*;
250    use crate as glib;
251
252    mod imp {
253        use super::*;
254        use crate::subclass::{Signal, prelude::*};
255
256        #[derive(Default)]
257        pub struct SignalObject {}
258
259        #[glib::object_subclass]
260        impl ObjectSubclass for SignalObject {
261            const NAME: &'static str = "SignalObject";
262            type Type = super::SignalObject;
263        }
264
265        impl ObjectImpl for SignalObject {
266            fn signals() -> &'static [Signal] {
267                static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
268                SIGNALS.get_or_init(|| {
269                    vec![
270                        Signal::builder("sig-with-args")
271                            .param_types([u32::static_type(), String::static_type()])
272                            .build(),
273                        Signal::builder("sig-with-ret")
274                            .return_type::<String>()
275                            .build(),
276                    ]
277                })
278            }
279        }
280    }
281
282    wrapper! {
283        pub struct SignalObject(ObjectSubclass<imp::SignalObject>);
284    }
285
286    #[test]
287    fn group_emit() {
288        let group = SignalGroup::new::<SignalObject>();
289
290        let obj = Object::new::<SignalObject>();
291        let store = Rc::new(RefCell::new(String::new()));
292        group.connect_closure(
293            "sig-with-args",
294            false,
295            glib::closure_local!(
296                #[watch]
297                obj,
298                #[strong]
299                store,
300                move |o: &SignalObject, a: u32, b: &str| {
301                    assert_eq!(o, obj);
302                    store.replace(format!("a {a} b {b}"));
303                }
304            ),
305        );
306        group.connect_closure(
307            "sig-with-ret",
308            false,
309            glib::closure_local!(
310                #[watch]
311                obj,
312                move |o: &SignalObject| -> &'static crate::GStr {
313                    assert_eq!(o, obj);
314                    crate::gstr!("Hello")
315                }
316            ),
317        );
318        group.set_target(Some(&obj));
319        obj.emit_by_name::<()>("sig-with-args", &[&5u32, &"World"]);
320        assert_eq!(*store.borrow(), "a 5 b World");
321        let ret = obj.emit_by_name::<crate::GString>("sig-with-ret", &[]);
322        assert_eq!(ret, "Hello");
323        group.set_target(Object::NONE);
324        let ret = obj.emit_by_name::<Option<String>>("sig-with-ret", &[]);
325        assert_eq!(ret, None);
326    }
327}