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