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