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