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