Backport official GLib iterator safety fix for Linux desktop
This commit is contained in:
1 parent
43c43ebdc9
commit
8ef874c609
133 files changed
+55342
-2
No files matched your search
@@ -0,0 +1,72 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! This module contains basic instance and class structs to be used for
|
||||
//! `GObject` subclasses that don't require any additional data in these
|
||||
//! structs and don't provide any new virtual methods.
|
||||
|
||||
use std::{fmt, ops};
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// A basic instance struct that does not store any additional data.
|
||||
#[repr(C)]
|
||||
pub struct InstanceStruct<T: ObjectSubclass> {
|
||||
parent: <T::ParentType as ObjectType>::GlibType,
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> fmt::Debug for InstanceStruct<T>
|
||||
where
|
||||
<T::ParentType as ObjectType>::GlibType: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("InstanceStruct")
|
||||
.field("parent", &self.parent)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ObjectSubclass> super::types::InstanceStruct for InstanceStruct<T> {
|
||||
type Type = T;
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// A basic class struct that does not store any additional data
|
||||
/// or virtual methods.
|
||||
#[repr(C)]
|
||||
pub struct ClassStruct<T: ObjectSubclass> {
|
||||
parent_class: <T::ParentType as ObjectType>::GlibClassType,
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> fmt::Debug for ClassStruct<T>
|
||||
where
|
||||
<T::ParentType as ObjectType>::GlibClassType: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("InstanceStruct")
|
||||
.field("parent_class", &self.parent_class)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ObjectSubclass> super::types::ClassStruct for ClassStruct<T> {
|
||||
type Type = T;
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> ops::Deref for ClassStruct<T> {
|
||||
type Target = crate::Class<<T as ObjectSubclass>::Type>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(self as *const _ as *const Self::Target) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> ops::DerefMut for ClassStruct<T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *(self as *mut _ as *mut Self::Target) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! Module for registering boxed types for Rust types.
|
||||
|
||||
use crate::{prelude::*, translate::*};
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Trait for defining boxed types.
|
||||
///
|
||||
/// Links together the type name with the type itself.
|
||||
///
|
||||
/// See [`register_boxed_type`] for registering an implementation of this trait
|
||||
/// with the type system.
|
||||
///
|
||||
/// [`register_boxed_type`]: fn.register_boxed_type.html
|
||||
pub trait BoxedType: StaticType + Clone + Sized + 'static {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Boxed type name.
|
||||
///
|
||||
/// This must be unique in the whole process.
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Register a boxed `glib::Type` ID for `T`.
|
||||
///
|
||||
/// This must be called only once and will panic on a second call.
|
||||
///
|
||||
/// See [`Boxed!`] for defining a function that ensures that
|
||||
/// this is only called once and returns the type id.
|
||||
///
|
||||
/// [`Boxed!`]: ../../derive.Boxed.html
|
||||
pub fn register_boxed_type<T: BoxedType>() -> crate::Type {
|
||||
unsafe extern "C" fn boxed_copy<T: BoxedType>(v: ffi::gpointer) -> ffi::gpointer {
|
||||
let v = &*(v as *mut T);
|
||||
let copy = Box::new(v.clone());
|
||||
|
||||
Box::into_raw(copy) as ffi::gpointer
|
||||
}
|
||||
unsafe extern "C" fn boxed_free<T: BoxedType>(v: ffi::gpointer) {
|
||||
let v = v as *mut T;
|
||||
let _ = Box::from_raw(v);
|
||||
}
|
||||
unsafe {
|
||||
use std::ffi::CString;
|
||||
|
||||
let type_name = CString::new(T::NAME).unwrap();
|
||||
assert_eq!(
|
||||
gobject_ffi::g_type_from_name(type_name.as_ptr()),
|
||||
gobject_ffi::G_TYPE_INVALID,
|
||||
"Type {} has already been registered",
|
||||
type_name.to_str().unwrap()
|
||||
);
|
||||
|
||||
let type_ = crate::Type::from_glib(gobject_ffi::g_boxed_type_register_static(
|
||||
type_name.as_ptr(),
|
||||
Some(boxed_copy::<T>),
|
||||
Some(boxed_free::<T>),
|
||||
));
|
||||
assert!(type_.is_valid());
|
||||
|
||||
type_
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// We rename the current crate as glib, since the macros in glib-macros
|
||||
// generate the glib namespace through the crate_ident_new utility,
|
||||
// and that returns `glib` (and not `crate`) when called inside the glib crate
|
||||
use crate as glib;
|
||||
use crate::prelude::*;
|
||||
use crate::translate::{FromGlibPtrBorrow, FromGlibPtrFull, IntoGlibPtr};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
|
||||
#[boxed_type(name = "MyBoxed")]
|
||||
struct MyBoxed(String);
|
||||
|
||||
#[test]
|
||||
fn test_register() {
|
||||
assert!(MyBoxed::static_type().is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value() {
|
||||
assert!(MyBoxed::static_type().is_valid());
|
||||
|
||||
let b = MyBoxed(String::from("abc"));
|
||||
let v = b.to_value();
|
||||
let b2 = v.get::<&MyBoxed>().unwrap();
|
||||
assert_eq!(&b, b2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_glib_borrow() {
|
||||
assert!(MyBoxed::static_type().is_valid());
|
||||
|
||||
let b = MyBoxed(String::from("abc"));
|
||||
let raw_ptr = unsafe { MyBoxed::into_glib_ptr(b) };
|
||||
|
||||
// test that the from_glib_borrow does not take ownership of the raw_ptr
|
||||
let _ = unsafe { MyBoxed::from_glib_borrow(raw_ptr) };
|
||||
|
||||
let new_b = unsafe { MyBoxed::from_glib_full(raw_ptr) };
|
||||
|
||||
assert_eq!(new_b.0, "abc".to_string());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use std::{marker, mem};
|
||||
|
||||
use super::{InitializingType, Signal};
|
||||
use crate::{prelude::*, translate::*, Object, ParamSpec, Type};
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Trait for a type list of prerequisite object types.
|
||||
pub trait PrerequisiteList {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Returns the list of types for this list.
|
||||
fn types() -> Vec<ffi::GType>;
|
||||
}
|
||||
|
||||
impl PrerequisiteList for () {
|
||||
fn types() -> Vec<ffi::GType> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::ObjectType> PrerequisiteList for (T,) {
|
||||
fn types() -> Vec<ffi::GType> {
|
||||
vec![T::static_type().into_glib()]
|
||||
}
|
||||
}
|
||||
|
||||
// Generates all the PrerequisiteList impls for prerequisite_lists of arbitrary sizes based on a list of type
|
||||
// parameters like A B C. It would generate the impl then for (A, B) and (A, B, C).
|
||||
macro_rules! prerequisite_list_trait(
|
||||
($name1:ident, $name2: ident, $($name:ident),*) => (
|
||||
prerequisite_list_trait!(__impl $name1, $name2; $($name),*);
|
||||
);
|
||||
(__impl $($name:ident),+; $name1:ident, $($name2:ident),*) => (
|
||||
prerequisite_list_trait_impl!($($name),+);
|
||||
prerequisite_list_trait!(__impl $($name),+ , $name1; $($name2),*);
|
||||
);
|
||||
(__impl $($name:ident),+; $name1:ident) => (
|
||||
prerequisite_list_trait_impl!($($name),+);
|
||||
prerequisite_list_trait_impl!($($name),+, $name1);
|
||||
);
|
||||
);
|
||||
|
||||
// Generates the impl block for PrerequisiteList on prerequisite_lists or arbitrary sizes based on its
|
||||
// arguments. Takes a list of type parameters as parameters, e.g. A B C
|
||||
// and then implements the trait on (A, B, C).
|
||||
macro_rules! prerequisite_list_trait_impl(
|
||||
($($name:ident),+) => (
|
||||
impl<$($name: crate::ObjectType),+> PrerequisiteList for ( $($name),+ ) {
|
||||
fn types() -> Vec<ffi::GType> {
|
||||
vec![$($name::static_type().into_glib()),+]
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
prerequisite_list_trait!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
|
||||
/// Type methods required for an [`ObjectInterface`] implementation.
|
||||
///
|
||||
/// This is usually generated by the [`#[object_interface]`](crate::object_interface) attribute macro.
|
||||
pub unsafe trait ObjectInterfaceType {
|
||||
/// Returns the `glib::Type` ID of the interface.
|
||||
///
|
||||
/// This will register the type with the type system on the first call.
|
||||
#[doc(alias = "get_type")]
|
||||
fn type_() -> Type;
|
||||
}
|
||||
|
||||
/// The central trait for defining a `GObject` interface.
|
||||
///
|
||||
/// Links together the type name, and the interface struct for type registration and allows hooking
|
||||
/// into various steps of the type registration and initialization.
|
||||
///
|
||||
/// This must only be implemented on `#[repr(C)]` structs and have `gobject_ffi::GTypeInterface` as
|
||||
/// the first field.
|
||||
///
|
||||
/// See [`register_interface`] for registering an implementation of this trait
|
||||
/// with the type system.
|
||||
///
|
||||
/// [`register_interface`]: fn.register_interface.html
|
||||
pub unsafe trait ObjectInterface: ObjectInterfaceType + Sized + 'static {
|
||||
/// `GObject` type name.
|
||||
///
|
||||
/// This must be unique in the whole process.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Prerequisites for this interface.
|
||||
///
|
||||
/// Any implementer of the interface must be a subclass of the prerequisites or implement them
|
||||
/// in case of interfaces.
|
||||
type Prerequisites: PrerequisiteList;
|
||||
|
||||
/// Additional type initialization.
|
||||
///
|
||||
/// This is called right after the type was registered and allows
|
||||
/// interfaces to do additional type-specific initialization.
|
||||
///
|
||||
/// Optional
|
||||
fn type_init(_type_: &mut InitializingType<Self>) {}
|
||||
|
||||
/// Interface initialization.
|
||||
///
|
||||
/// This is called after `type_init` and before the first implementor
|
||||
/// of the interface is created. Interfaces can use this to do interface-
|
||||
/// specific initialization, e.g. for installing signals on the interface,
|
||||
/// and for setting default implementations of interface functions.
|
||||
///
|
||||
/// Optional
|
||||
fn interface_init(&mut self) {}
|
||||
|
||||
/// Properties installed for this interface.
|
||||
///
|
||||
/// All implementors of the interface must provide these properties.
|
||||
fn properties() -> &'static [ParamSpec] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Signals installed for this interface.
|
||||
fn signals() -> &'static [Signal] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ObjectInterfaceExt: ObjectInterface {
|
||||
/// Get interface from an instance.
|
||||
///
|
||||
/// This will panic if `obj` does not implement the interface.
|
||||
#[inline]
|
||||
#[deprecated = "Use from_obj() instead"]
|
||||
fn from_instance<T: IsA<Object>>(obj: &T) -> &Self {
|
||||
Self::from_obj(obj)
|
||||
}
|
||||
|
||||
/// Get interface from an instance.
|
||||
///
|
||||
/// This will panic if `obj` does not implement the interface.
|
||||
#[inline]
|
||||
fn from_obj<T: IsA<Object>>(obj: &T) -> &Self {
|
||||
assert!(obj.as_ref().type_().is_a(Self::type_()));
|
||||
|
||||
unsafe {
|
||||
let klass = (*(obj.as_ptr() as *const gobject_ffi::GTypeInstance)).g_class;
|
||||
let interface =
|
||||
gobject_ffi::g_type_interface_peek(klass as *mut _, Self::type_().into_glib());
|
||||
debug_assert!(!interface.is_null());
|
||||
&*(interface as *const Self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectInterface> ObjectInterfaceExt for T {}
|
||||
|
||||
unsafe extern "C" fn interface_init<T: ObjectInterface>(
|
||||
klass: ffi::gpointer,
|
||||
_klass_data: ffi::gpointer,
|
||||
) {
|
||||
let iface = &mut *(klass as *mut T);
|
||||
|
||||
let pspecs = <T as ObjectInterface>::properties();
|
||||
for pspec in pspecs {
|
||||
gobject_ffi::g_object_interface_install_property(
|
||||
iface as *mut T as *mut _,
|
||||
pspec.to_glib_none().0,
|
||||
);
|
||||
}
|
||||
|
||||
let type_ = T::type_();
|
||||
let signals = <T as ObjectInterface>::signals();
|
||||
for signal in signals {
|
||||
signal.register(type_);
|
||||
}
|
||||
|
||||
iface.interface_init();
|
||||
}
|
||||
|
||||
/// Register a `glib::Type` ID for `T`.
|
||||
///
|
||||
/// This must be called only once and will panic on a second call.
|
||||
///
|
||||
/// The [`object_interface!`] macro will create a `type_()` function around this, which will
|
||||
/// ensure that it's only ever called once.
|
||||
///
|
||||
/// [`object_interface!`]: ../../macro.object_interface.html
|
||||
pub fn register_interface<T: ObjectInterface>() -> Type {
|
||||
unsafe {
|
||||
use std::ffi::CString;
|
||||
|
||||
let type_name = CString::new(T::NAME).unwrap();
|
||||
assert_eq!(
|
||||
gobject_ffi::g_type_from_name(type_name.as_ptr()),
|
||||
gobject_ffi::G_TYPE_INVALID
|
||||
);
|
||||
|
||||
let type_ = gobject_ffi::g_type_register_static_simple(
|
||||
Type::INTERFACE.into_glib(),
|
||||
type_name.as_ptr(),
|
||||
mem::size_of::<T>() as u32,
|
||||
Some(interface_init::<T>),
|
||||
0,
|
||||
None,
|
||||
0,
|
||||
);
|
||||
|
||||
let prerequisites = T::Prerequisites::types();
|
||||
for prerequisite in prerequisites {
|
||||
gobject_ffi::g_type_interface_add_prerequisite(type_, prerequisite);
|
||||
}
|
||||
|
||||
let type_ = Type::from_glib(type_);
|
||||
assert!(type_.is_valid());
|
||||
|
||||
T::type_init(&mut InitializingType::<T>(type_, marker::PhantomData));
|
||||
|
||||
type_
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! Module containing infrastructure for subclassing `GObject`s and registering boxed types.
|
||||
//!
|
||||
//! # Example for registering a `glib::Object` subclass
|
||||
//!
|
||||
//! The following code implements a subclass of `glib::Object` with a
|
||||
//! string-typed "name" property.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use glib::prelude::*;
|
||||
//! use glib::subclass;
|
||||
//! use glib::subclass::prelude::*;
|
||||
//! use glib::{Variant, VariantType};
|
||||
//!
|
||||
//! use std::cell::{Cell, RefCell};
|
||||
//!
|
||||
//! #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::Enum)]
|
||||
//! #[repr(u32)]
|
||||
//! // type_name: GType name of the enum (mandatory)
|
||||
//! #[enum_type(name = "SimpleObjectAnimal")]
|
||||
//! enum Animal {
|
||||
//! Goat = 0,
|
||||
//! #[enum_value(name = "The Dog")]
|
||||
//! Dog = 1,
|
||||
//! // name: the name of the GEnumValue (optional), default to the enum name in CamelCase
|
||||
//! // nick: the nick of the GEnumValue (optional), default to the enum name in kebab-case
|
||||
//! #[enum_value(name = "The Cat", nick = "chat")]
|
||||
//! Cat = 2,
|
||||
//! }
|
||||
//!
|
||||
//! impl Default for Animal {
|
||||
//! fn default() -> Self {
|
||||
//! Animal::Goat
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[glib::flags(name = "MyFlags")]
|
||||
//! enum MyFlags {
|
||||
//! #[flags_value(name = "Flag A", nick = "nick-a")]
|
||||
//! A = 0b00000001,
|
||||
//! #[flags_value(name = "Flag B")]
|
||||
//! B = 0b00000010,
|
||||
//! #[flags_value(skip)]
|
||||
//! AB = Self::A.bits() | Self::B.bits(),
|
||||
//! C = 0b00000100,
|
||||
//! }
|
||||
//!
|
||||
//! impl Default for MyFlags {
|
||||
//! fn default() -> Self {
|
||||
//! MyFlags::A
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! mod imp {
|
||||
//! use super::*;
|
||||
//!
|
||||
//! // This is the struct containing all state carried with
|
||||
//! // the new type. Generally this has to make use of
|
||||
//! // interior mutability.
|
||||
//! // If it implements the `Default` trait, then `Self::default()`
|
||||
//! // will be called every time a new instance is created.
|
||||
//! #[derive(Default)]
|
||||
//! pub struct SimpleObject {
|
||||
//! name: RefCell<Option<String>>,
|
||||
//! animal: Cell<Animal>,
|
||||
//! flags: Cell<MyFlags>,
|
||||
//! variant: RefCell<Option<Variant>>,
|
||||
//! }
|
||||
//!
|
||||
//! // ObjectSubclass is the trait that defines the new type and
|
||||
//! // contains all information needed by the GObject type system,
|
||||
//! // including the new type's name, parent type, etc.
|
||||
//! // If you do not want to implement `Default`, you can provide
|
||||
//! // a `new()` method.
|
||||
//! #[glib::object_subclass]
|
||||
//! impl ObjectSubclass for SimpleObject {
|
||||
//! // This type name must be unique per process.
|
||||
//! const NAME: &'static str = "SimpleObject";
|
||||
//!
|
||||
//! type Type = super::SimpleObject;
|
||||
//!
|
||||
//! // The parent type this one is inheriting from.
|
||||
//! // Optional, if not specified it defaults to `glib::Object`
|
||||
//! type ParentType = glib::Object;
|
||||
//!
|
||||
//! // Interfaces this type implements.
|
||||
//! // Optional, if not specified it defaults to `()`
|
||||
//! type Interfaces = ();
|
||||
//! }
|
||||
//!
|
||||
//! // Trait that is used to override virtual methods of glib::Object.
|
||||
//! impl ObjectImpl for SimpleObject {
|
||||
//! // Called once in the very beginning to list all properties of this class.
|
||||
//! fn properties() -> &'static [glib::ParamSpec] {
|
||||
//! use once_cell::sync::Lazy;
|
||||
//! static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
//! vec![
|
||||
//! glib::ParamSpecString::builder("name")
|
||||
//! .build(),
|
||||
//! glib::ParamSpecEnum::builder::<Animal>("animal")
|
||||
//! .build(),
|
||||
//! glib::ParamSpecFlags::builder::<MyFlags>("flags")
|
||||
//! .build(),
|
||||
//! glib::ParamSpecVariant::builder("variant", glib::VariantTy::ANY)
|
||||
//! .build(),
|
||||
//! ]
|
||||
//! });
|
||||
//!
|
||||
//! PROPERTIES.as_ref()
|
||||
//! }
|
||||
//!
|
||||
//! // Called whenever a property is set on this instance. The id
|
||||
//! // is the same as the index of the property in the PROPERTIES array.
|
||||
//! fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
//! match pspec.name() {
|
||||
//! "name" => {
|
||||
//! let name = value
|
||||
//! .get()
|
||||
//! .expect("type conformity checked by `Object::set_property`");
|
||||
//! self.name.replace(name);
|
||||
//! },
|
||||
//! "animal" => {
|
||||
//! let animal = value
|
||||
//! .get()
|
||||
//! .expect("type conformity checked by `Object::set_property`");
|
||||
//! self.animal.replace(animal);
|
||||
//! },
|
||||
//! "flags" => {
|
||||
//! let flags = value
|
||||
//! .get()
|
||||
//! .expect("type conformity checked by `Object::set_property`");
|
||||
//! self.flags.replace(flags);
|
||||
//! },
|
||||
//! "variant" => {
|
||||
//! let variant = value
|
||||
//! .get()
|
||||
//! .expect("type conformity checked by `Object::set_property`");
|
||||
//! self.variant.replace(variant);
|
||||
//! },
|
||||
//! _ => unimplemented!(),
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Called whenever a property is retrieved from this instance. The id
|
||||
//! // is the same as the index of the property in the PROPERTIES array.
|
||||
//! fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
//! match pspec.name() {
|
||||
//! "name" => self.name.borrow().to_value(),
|
||||
//! "animal" => self.animal.get().to_value(),
|
||||
//! "flags" => self.flags.get().to_value(),
|
||||
//! "variant" => self.variant.borrow().to_value(),
|
||||
//! _ => unimplemented!(),
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Called right after construction of the instance.
|
||||
//! fn constructed(&self) {
|
||||
//! // Chain up to the parent type's implementation of this virtual
|
||||
//! // method.
|
||||
//! self.parent_constructed();
|
||||
//!
|
||||
//! // And here we could do our own initialization.
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Optionally, define a wrapper type to make it more ergonomic to use from Rust
|
||||
//! glib::wrapper! {
|
||||
//! pub struct SimpleObject(ObjectSubclass<imp::SimpleObject>);
|
||||
//! }
|
||||
//!
|
||||
//! impl SimpleObject {
|
||||
//! // Create an object instance of the new type.
|
||||
//! pub fn new() -> Self {
|
||||
//! glib::Object::new()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! pub fn main() {
|
||||
//! let obj = SimpleObject::new();
|
||||
//!
|
||||
//! // Get the name property and change its value.
|
||||
//! assert_eq!(obj.property::<Option<String>>("name"), None);
|
||||
//! obj.set_property("name", "test");
|
||||
//! assert_eq!(&obj.property::<String>("name"), "test");
|
||||
//!
|
||||
//! assert_eq!(obj.property::<Animal>("animal"), Animal::Goat);
|
||||
//! obj.set_property("animal", Animal::Cat);
|
||||
//! assert_eq!(obj.property::<Animal>("animal"), Animal::Cat);
|
||||
//!
|
||||
//! assert_eq!(obj.property::<MyFlags>("flags"), MyFlags::A);
|
||||
//! obj.set_property("flags", MyFlags::B);
|
||||
//! assert_eq!(obj.property::<MyFlags>("flags"), MyFlags::B);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Example for registering a boxed type for a Rust struct
|
||||
//!
|
||||
//! The following code boxed type for a tuple struct around `String` and uses it in combination
|
||||
//! with `glib::Value`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use glib::prelude::*;
|
||||
//! use glib::subclass;
|
||||
//! use glib::subclass::prelude::*;
|
||||
//!
|
||||
//! #[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
|
||||
//! #[boxed_type(name = "MyBoxed")]
|
||||
//! struct MyBoxed(String);
|
||||
//!
|
||||
//! pub fn main() {
|
||||
//! assert!(MyBoxed::static_type().is_valid());
|
||||
//!
|
||||
//! let b = MyBoxed(String::from("abc"));
|
||||
//! let v = b.to_value();
|
||||
//! let b2 = v.get::<&MyBoxed>().unwrap();
|
||||
//! assert_eq!(&b, b2);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub mod basic;
|
||||
#[macro_use]
|
||||
pub mod types;
|
||||
|
||||
#[macro_use]
|
||||
pub mod interface;
|
||||
|
||||
#[macro_use]
|
||||
pub mod object;
|
||||
|
||||
#[macro_use]
|
||||
pub mod boxed;
|
||||
|
||||
pub mod shared;
|
||||
|
||||
pub mod signal;
|
||||
|
||||
mod object_impl_ref;
|
||||
pub use object_impl_ref::{ObjectImplRef, ObjectImplWeakRef};
|
||||
|
||||
pub mod prelude {
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! Prelude that re-exports all important traits from this crate.
|
||||
pub use super::{
|
||||
boxed::BoxedType,
|
||||
interface::{ObjectInterface, ObjectInterfaceExt, ObjectInterfaceType},
|
||||
object::{DerivedObjectProperties, ObjectClassSubclassExt, ObjectImpl, ObjectImplExt},
|
||||
shared::{RefCounted, SharedType},
|
||||
types::{
|
||||
ClassStruct, InstanceStruct, InstanceStructExt, IsImplementable, IsSubclassable,
|
||||
IsSubclassableExt, ObjectSubclass, ObjectSubclassExt, ObjectSubclassIsExt,
|
||||
ObjectSubclassType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub use self::{
|
||||
boxed::register_boxed_type,
|
||||
interface::register_interface,
|
||||
signal::{
|
||||
Signal, SignalClassHandlerToken, SignalId, SignalInvocationHint, SignalQuery, SignalType,
|
||||
},
|
||||
types::{register_type, InitializingObject, InitializingType, TypeData},
|
||||
};
|
||||
@@ -0,0 +1,717 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! Module that contains all types needed for creating a direct subclass of `GObject`
|
||||
//! or implementing virtual methods of it.
|
||||
|
||||
use std::{mem, ptr};
|
||||
|
||||
use super::{prelude::*, Signal};
|
||||
use crate::{prelude::*, translate::*, Cast, Object, ParamSpec, Slice, Value};
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Trait for implementors of `glib::Object` subclasses.
|
||||
///
|
||||
/// This allows overriding the virtual methods of `glib::Object`. Except for
|
||||
/// `finalize` as implementing `Drop` would allow the same behavior.
|
||||
pub trait ObjectImpl: ObjectSubclass + ObjectImplExt {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Properties installed for this type.
|
||||
fn properties() -> &'static [ParamSpec] {
|
||||
&[]
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signals installed for this type.
|
||||
fn signals() -> &'static [Signal] {
|
||||
&[]
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Property setter.
|
||||
///
|
||||
/// This is called whenever the property of this specific subclass with the
|
||||
/// given index is set. The new value is passed as `glib::Value`.
|
||||
///
|
||||
/// `value` is guaranteed to be of the correct type for the given property.
|
||||
fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Property getter.
|
||||
///
|
||||
/// This is called whenever the property value of the specific subclass with the
|
||||
/// given index should be returned.
|
||||
///
|
||||
/// The returned `Value` must be of the correct type for the given property.
|
||||
#[doc(alias = "get_property")]
|
||||
fn property(&self, _id: usize, _pspec: &ParamSpec) -> Value {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Constructed.
|
||||
///
|
||||
/// This is called once construction of the instance is finished.
|
||||
///
|
||||
/// Should chain up to the parent class' implementation.
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Disposes of the object.
|
||||
///
|
||||
/// When `dispose()` ends, the object should not hold any reference to any other member object.
|
||||
/// The object is also expected to be able to answer client method invocations (with possibly an
|
||||
/// error code but no memory violation) until it is dropped. `dispose()` can be executed more
|
||||
/// than once.
|
||||
fn dispose(&self) {}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Function to be called when property change is notified for with
|
||||
/// `self.notify("property")`.
|
||||
fn notify(&self, pspec: &ParamSpec) {
|
||||
self.parent_notify(pspec)
|
||||
}
|
||||
|
||||
fn dispatch_properties_changed(&self, pspecs: &[ParamSpec]) {
|
||||
self.parent_dispatch_properties_changed(pspecs)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "get_property")]
|
||||
unsafe extern "C" fn property<T: ObjectImpl>(
|
||||
obj: *mut gobject_ffi::GObject,
|
||||
id: u32,
|
||||
value: *mut gobject_ffi::GValue,
|
||||
pspec: *mut gobject_ffi::GParamSpec,
|
||||
) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
|
||||
let v = imp.property(id as usize, &from_glib_borrow(pspec));
|
||||
|
||||
// We first unset the value we get passed in, in case it contained
|
||||
// any previous data. Then we directly overwrite it with our new
|
||||
// value, and pass ownership of the contained data to the C GValue
|
||||
// by forgetting it on the Rust side.
|
||||
//
|
||||
// Without this, by using the GValue API, we would have to create
|
||||
// a copy of the value when setting it on the destination just to
|
||||
// immediately free the original value afterwards.
|
||||
gobject_ffi::g_value_unset(value);
|
||||
let v = mem::ManuallyDrop::new(v);
|
||||
ptr::write(value, ptr::read(v.to_glib_none().0));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn set_property<T: ObjectImpl>(
|
||||
obj: *mut gobject_ffi::GObject,
|
||||
id: u32,
|
||||
value: *mut gobject_ffi::GValue,
|
||||
pspec: *mut gobject_ffi::GParamSpec,
|
||||
) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
imp.set_property(
|
||||
id as usize,
|
||||
&*(value as *mut Value),
|
||||
&from_glib_borrow(pspec),
|
||||
);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn constructed<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
|
||||
imp.constructed();
|
||||
}
|
||||
|
||||
unsafe extern "C" fn notify<T: ObjectImpl>(
|
||||
obj: *mut gobject_ffi::GObject,
|
||||
pspec: *mut gobject_ffi::GParamSpec,
|
||||
) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
imp.notify(&from_glib_borrow(pspec));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn dispatch_properties_changed<T: ObjectImpl>(
|
||||
obj: *mut gobject_ffi::GObject,
|
||||
n_pspecs: u32,
|
||||
pspecs: *mut *mut gobject_ffi::GParamSpec,
|
||||
) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
imp.dispatch_properties_changed(Slice::from_glib_borrow_num(pspecs, n_pspecs as _));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn dispose<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) {
|
||||
let instance = &*(obj as *mut T::Instance);
|
||||
let imp = instance.imp();
|
||||
|
||||
imp.dispose();
|
||||
|
||||
// Chain up to the parent's dispose.
|
||||
let data = T::type_data();
|
||||
let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
|
||||
if let Some(ref func) = (*parent_class).dispose {
|
||||
func(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Trait containing only the property related functions of `ObjectImpl`.
|
||||
/// Implemented by the `Props` macro.
|
||||
/// When implementing `ObjectImpl` you may want to delegate the function calls to this trait.
|
||||
pub trait DerivedObjectProperties: ObjectSubclass {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Properties installed for this type.
|
||||
fn derived_properties() -> &'static [ParamSpec] {
|
||||
&[]
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`] macro
|
||||
/// to allow handling more complex use-cases.
|
||||
fn derived_set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`] macro
|
||||
/// to allow handling more complex use-cases.
|
||||
fn derived_property(&self, _id: usize, _pspec: &ParamSpec) -> Value {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Extension trait for `glib::Object`'s class struct.
|
||||
///
|
||||
/// This contains various class methods and allows subclasses to override signal class handlers.
|
||||
pub unsafe trait ObjectClassSubclassExt: Sized + 'static {
|
||||
fn override_signal_class_handler<F>(&mut self, name: &str, class_handler: F)
|
||||
where
|
||||
F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
{
|
||||
unsafe {
|
||||
super::types::signal_override_class_handler(
|
||||
name,
|
||||
*(self as *mut _ as *mut ffi::GType),
|
||||
class_handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ObjectClassSubclassExt for crate::Class<Object> {}
|
||||
|
||||
unsafe impl<T: ObjectImpl> IsSubclassable<T> for Object {
|
||||
fn class_init(class: &mut crate::Class<Self>) {
|
||||
let klass = class.as_mut();
|
||||
klass.set_property = Some(set_property::<T>);
|
||||
klass.get_property = Some(property::<T>);
|
||||
klass.constructed = Some(constructed::<T>);
|
||||
klass.notify = Some(notify::<T>);
|
||||
klass.dispatch_properties_changed = Some(dispatch_properties_changed::<T>);
|
||||
klass.dispose = Some(dispose::<T>);
|
||||
|
||||
let pspecs = <T as ObjectImpl>::properties();
|
||||
if !pspecs.is_empty() {
|
||||
unsafe {
|
||||
let mut pspecs_ptrs = Vec::with_capacity(pspecs.len() + 1);
|
||||
|
||||
pspecs_ptrs.push(ptr::null_mut());
|
||||
|
||||
for pspec in pspecs {
|
||||
pspecs_ptrs.push(pspec.to_glib_none().0);
|
||||
}
|
||||
|
||||
gobject_ffi::g_object_class_install_properties(
|
||||
klass,
|
||||
pspecs_ptrs.len() as u32,
|
||||
pspecs_ptrs.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let type_ = T::type_();
|
||||
let signals = <T as ObjectImpl>::signals();
|
||||
for signal in signals {
|
||||
signal.register(type_);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn instance_init(_instance: &mut super::InitializingObject<T>) {}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<T: super::ObjectImplExt> Sealed for T {}
|
||||
}
|
||||
|
||||
pub trait ObjectImplExt: sealed::Sealed + ObjectSubclass {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Chain up to the parent class' implementation of `glib::Object::constructed()`.
|
||||
#[inline]
|
||||
fn parent_constructed(&self) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
|
||||
|
||||
if let Some(ref func) = (*parent_class).constructed {
|
||||
func(self.obj().unsafe_cast_ref::<Object>().to_glib_none().0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Chain up to the parent class' implementation of `glib::Object::notify()`.
|
||||
#[inline]
|
||||
fn parent_notify(&self, pspec: &ParamSpec) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
|
||||
|
||||
if let Some(ref func) = (*parent_class).notify {
|
||||
func(
|
||||
self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
|
||||
pspec.to_glib_none().0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Chain up to the parent class' implementation of `glib::Object::dispatch_properties_changed()`.
|
||||
#[inline]
|
||||
fn parent_dispatch_properties_changed(&self, pspecs: &[ParamSpec]) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
|
||||
|
||||
if let Some(ref func) = (*parent_class).dispatch_properties_changed {
|
||||
func(
|
||||
self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
|
||||
pspecs.len() as _,
|
||||
pspecs.as_ptr() as *mut _,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Chain up to parent class signal handler.
|
||||
fn signal_chain_from_overridden(
|
||||
&self,
|
||||
token: &super::SignalClassHandlerToken,
|
||||
values: &[Value],
|
||||
) -> Option<Value> {
|
||||
unsafe {
|
||||
super::types::signal_chain_from_overridden(self.obj().as_ptr() as *mut _, token, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectImpl> ObjectImplExt for T {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::cell::RefCell;
|
||||
|
||||
use super::*;
|
||||
// We rename the current crate as glib, since the macros in glib-macros
|
||||
// generate the glib namespace through the crate_ident_new utility,
|
||||
// and that returns `glib` (and not `crate`) when called inside the glib crate
|
||||
use crate as glib;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
// A dummy `Object` to test setting an `Object` property and returning an `Object` in signals
|
||||
#[derive(Default)]
|
||||
pub struct ChildObject;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ChildObject {
|
||||
const NAME: &'static str = "ChildObject";
|
||||
type Type = super::ChildObject;
|
||||
}
|
||||
|
||||
impl ObjectImpl for ChildObject {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SimpleObject {
|
||||
name: RefCell<Option<String>>,
|
||||
construct_name: RefCell<Option<String>>,
|
||||
constructed: RefCell<bool>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for SimpleObject {
|
||||
const NAME: &'static str = "SimpleObject";
|
||||
type Type = super::SimpleObject;
|
||||
type Interfaces = (super::Dummy,);
|
||||
}
|
||||
|
||||
impl ObjectImpl for SimpleObject {
|
||||
fn properties() -> &'static [ParamSpec] {
|
||||
use once_cell::sync::Lazy;
|
||||
static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
crate::ParamSpecString::builder("name").build(),
|
||||
crate::ParamSpecString::builder("construct-name")
|
||||
.construct_only()
|
||||
.build(),
|
||||
crate::ParamSpecBoolean::builder("constructed")
|
||||
.read_only()
|
||||
.build(),
|
||||
crate::ParamSpecObject::builder::<super::ChildObject>("child").build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn signals() -> &'static [super::Signal] {
|
||||
use once_cell::sync::Lazy;
|
||||
static SIGNALS: Lazy<Vec<super::Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
super::Signal::builder("name-changed")
|
||||
.param_types([String::static_type()])
|
||||
.build(),
|
||||
super::Signal::builder("change-name")
|
||||
.param_types([String::static_type()])
|
||||
.return_type::<String>()
|
||||
.action()
|
||||
.class_handler(|_, args| {
|
||||
let obj = args[0]
|
||||
.get::<super::SimpleObject>()
|
||||
.expect("Failed to get Object from args[0]");
|
||||
let new_name = args[1]
|
||||
.get::<String>()
|
||||
.expect("Failed to get Object from args[1]");
|
||||
let imp = obj.imp();
|
||||
|
||||
let old_name = imp.name.replace(Some(new_name));
|
||||
|
||||
obj.emit_by_name::<()>("name-changed", &[&*imp.name.borrow()]);
|
||||
|
||||
Some(old_name.to_value())
|
||||
})
|
||||
.build(),
|
||||
super::Signal::builder("create-string")
|
||||
.return_type::<String>()
|
||||
.build(),
|
||||
super::Signal::builder("create-child-object")
|
||||
.return_type::<super::ChildObject>()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &Value, pspec: &crate::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"name" => {
|
||||
let name = value
|
||||
.get()
|
||||
.expect("type conformity checked by 'Object::set_property'");
|
||||
self.name.replace(name);
|
||||
self.obj()
|
||||
.emit_by_name::<()>("name-changed", &[&*self.name.borrow()]);
|
||||
}
|
||||
"construct-name" => {
|
||||
let name = value
|
||||
.get()
|
||||
.expect("type conformity checked by 'Object::set_property'");
|
||||
self.construct_name.replace(name);
|
||||
}
|
||||
"child" => {
|
||||
// not stored, only used to test `set_property` with `Objects`
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> Value {
|
||||
match pspec.name() {
|
||||
"name" => self.name.borrow().to_value(),
|
||||
"construct-name" => self.construct_name.borrow().to_value(),
|
||||
"constructed" => self.constructed.borrow().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
debug_assert_eq!(self as *const _, self.obj().imp() as *const _);
|
||||
|
||||
*self.constructed.borrow_mut() = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct DummyInterface {
|
||||
parent: gobject_ffi::GTypeInterface,
|
||||
}
|
||||
|
||||
#[glib::object_interface]
|
||||
unsafe impl ObjectInterface for DummyInterface {
|
||||
const NAME: &'static str = "Dummy";
|
||||
}
|
||||
}
|
||||
|
||||
wrapper! {
|
||||
pub struct ChildObject(ObjectSubclass<imp::ChildObject>);
|
||||
}
|
||||
|
||||
wrapper! {
|
||||
pub struct SimpleObject(ObjectSubclass<imp::SimpleObject>);
|
||||
}
|
||||
|
||||
wrapper! {
|
||||
pub struct Dummy(ObjectInterface<imp::DummyInterface>);
|
||||
}
|
||||
|
||||
unsafe impl<T: ObjectSubclass> IsImplementable<T> for Dummy {}
|
||||
|
||||
#[test]
|
||||
fn test_create() {
|
||||
let type_ = SimpleObject::static_type();
|
||||
let obj = Object::with_type(type_);
|
||||
|
||||
assert!(obj.type_().is_a(Dummy::static_type()));
|
||||
|
||||
// Assert that the object representation is equivalent to the underlying C GObject pointer
|
||||
assert_eq!(
|
||||
mem::size_of::<SimpleObject>(),
|
||||
mem::size_of::<ffi::gpointer>()
|
||||
);
|
||||
assert_eq!(obj.as_ptr() as ffi::gpointer, unsafe {
|
||||
*(&obj as *const _ as *const ffi::gpointer)
|
||||
});
|
||||
|
||||
assert!(obj.property::<bool>("constructed"));
|
||||
|
||||
let weak = obj.downgrade();
|
||||
drop(obj);
|
||||
assert!(weak.upgrade().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_properties() {
|
||||
let type_ = SimpleObject::static_type();
|
||||
let obj = Object::with_type(type_);
|
||||
|
||||
assert!(obj.type_().is_a(Dummy::static_type()));
|
||||
|
||||
let properties = obj.list_properties();
|
||||
assert_eq!(properties.len(), 4);
|
||||
assert_eq!(properties[0].name(), "name");
|
||||
assert_eq!(properties[1].name(), "construct-name");
|
||||
assert_eq!(properties[2].name(), "constructed");
|
||||
assert_eq!(properties[3].name(), "child");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_child_object() {
|
||||
let obj: ChildObject = Object::new();
|
||||
|
||||
assert_eq!(&obj, obj.imp().obj().as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
obj.property::<String>("construct-name"),
|
||||
String::from("meh")
|
||||
);
|
||||
|
||||
assert_eq!(obj.property::<String>("name"), String::from("initial"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_property() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
obj.property::<String>("construct-name"),
|
||||
String::from("meh")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
obj.property::<String>("construct-name"),
|
||||
String::from("meh")
|
||||
);
|
||||
|
||||
assert_eq!(obj.property::<String>("name"), String::from("initial"));
|
||||
obj.set_property("name", "test");
|
||||
assert_eq!(obj.property::<String>("name"), String::from("test"));
|
||||
|
||||
let child = Object::with_type(ChildObject::static_type());
|
||||
obj.set_property("child", &child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "property 'construct-name' of type 'SimpleObject' is not writable"]
|
||||
fn test_set_property_non_writable() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
obj.set_property("construct-name", "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "property 'test' of type 'SimpleObject' not found"]
|
||||
fn test_set_property_not_found() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
obj.set_property("test", true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "property 'constructed' of type 'SimpleObject' is not writable"]
|
||||
fn test_set_property_not_writable() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
obj.set_property("constructed", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "property 'name' of type 'SimpleObject' can't be set from the given type (expected: 'gchararray', got: 'gboolean')"]
|
||||
fn test_set_property_wrong_type() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
obj.set_property("name", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "property 'child' of type 'SimpleObject' can't be set from the given type (expected: 'ChildObject', got: 'SimpleObject')"]
|
||||
fn test_set_property_wrong_type_2() {
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("name", "initial")
|
||||
.build();
|
||||
|
||||
let other_obj = Object::with_type(SimpleObject::static_type());
|
||||
|
||||
obj.set_property("child", &other_obj);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Can't set construct property 'construct-name' for type 'SimpleObject' twice"]
|
||||
fn test_construct_property_set_twice() {
|
||||
let _obj = Object::builder::<SimpleObject>()
|
||||
.property("construct-name", "meh")
|
||||
.property("construct-name", "meh2")
|
||||
.build();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signals() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("name", "old-name")
|
||||
.build();
|
||||
|
||||
let name_changed_triggered = Arc::new(AtomicBool::new(false));
|
||||
let name_changed_clone = name_changed_triggered.clone();
|
||||
obj.connect("name-changed", false, move |args| {
|
||||
let _obj = args[0].get::<Object>().expect("Failed to get args[0]");
|
||||
let name = args[1].get::<&str>().expect("Failed to get args[1]");
|
||||
|
||||
assert_eq!(name, "new-name");
|
||||
name_changed_clone.store(true, Ordering::Relaxed);
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
assert_eq!(obj.property::<String>("name"), String::from("old-name"));
|
||||
assert!(!name_changed_triggered.load(Ordering::Relaxed));
|
||||
|
||||
assert_eq!(
|
||||
obj.emit_by_name::<String>("change-name", &[&"new-name"]),
|
||||
"old-name"
|
||||
);
|
||||
assert!(name_changed_triggered.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signal_return_expected_type() {
|
||||
let obj = Object::with_type(SimpleObject::static_type());
|
||||
|
||||
obj.connect("create-string", false, move |_args| {
|
||||
Some("return value".to_value())
|
||||
});
|
||||
|
||||
let signal_id = imp::SimpleObject::signals()[2].signal_id();
|
||||
|
||||
let value = obj.emit::<String>(signal_id, &[]);
|
||||
assert_eq!(value, "return value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_callback_validity() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let obj = Object::builder::<SimpleObject>()
|
||||
.property("name", "old-name")
|
||||
.build();
|
||||
|
||||
let name_changed_triggered = Arc::new(AtomicBool::new(false));
|
||||
let name_changed_clone = name_changed_triggered.clone();
|
||||
|
||||
obj.connect_notify(Some("name"), move |_, _| {
|
||||
name_changed_clone.store(true, Ordering::Relaxed);
|
||||
});
|
||||
obj.notify("name");
|
||||
assert!(name_changed_triggered.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
// Note: can't test type mismatch in signals since panics accross FFI boundaries
|
||||
// are UB. See https://github.com/gtk-rs/glib/issues/518
|
||||
|
||||
#[test]
|
||||
fn test_signal_return_expected_object_type() {
|
||||
let obj = Object::with_type(SimpleObject::static_type());
|
||||
|
||||
obj.connect("create-child-object", false, move |_args| {
|
||||
Some(Object::with_type(ChildObject::static_type()).to_value())
|
||||
});
|
||||
let value: glib::Object = obj.emit_by_name("create-child-object", &[]);
|
||||
assert!(value.type_().is_a(ChildObject::static_type()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use std::{cmp, fmt, hash};
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::{
|
||||
clone::{Downgrade, Upgrade},
|
||||
prelude::*,
|
||||
WeakRef,
|
||||
};
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Reference-counted wrapper around an [`ObjectSubclass`] reference.
|
||||
///
|
||||
/// This can be used for passing into closures as strong or weak reference without manually going
|
||||
/// from the implementation type to the instance type and back.
|
||||
pub struct ObjectImplRef<T: ObjectSubclass>(T::Type);
|
||||
|
||||
unsafe impl<T: ObjectSubclass + Send + Sync> Send for ObjectImplRef<T> {}
|
||||
unsafe impl<T: ObjectSubclass + Send + Sync> Sync for ObjectImplRef<T> {}
|
||||
|
||||
impl<T: ObjectSubclass> ObjectImplRef<T> {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Create a new reference-counting wrapper around `imp`.
|
||||
#[inline]
|
||||
pub fn new(imp: &T) -> Self {
|
||||
Self(imp.obj().clone())
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Downgrade to a weak reference.
|
||||
///
|
||||
/// This can be upgraded to a strong reference again via [`ObjectImplWeakRef::upgrade`].
|
||||
#[inline]
|
||||
pub fn downgrade(&self) -> ObjectImplWeakRef<T> {
|
||||
ObjectImplWeakRef(self.0.downgrade())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Clone for ObjectImplRef<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> fmt::Debug for ObjectImplRef<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<T::Type as fmt::Debug>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> std::ops::Deref for ObjectImplRef<T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
T::from_obj(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Downgrade for ObjectImplRef<T> {
|
||||
type Weak = ObjectImplWeakRef<T>;
|
||||
|
||||
#[inline]
|
||||
fn downgrade(&self) -> Self::Weak {
|
||||
self.downgrade()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> PartialOrd for ObjectImplRef<T> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass, OT: crate::object::ObjectType> PartialOrd<OT> for ObjectImplRef<T>
|
||||
where
|
||||
T::Type: PartialOrd<OT>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &OT) -> Option<cmp::Ordering> {
|
||||
self.0.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Ord for ObjectImplRef<T> {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> PartialEq for ObjectImplRef<T> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass, OT: crate::object::ObjectType> PartialEq<OT> for ObjectImplRef<T>
|
||||
where
|
||||
T::Type: PartialEq<OT>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &OT) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Eq for ObjectImplRef<T> {}
|
||||
|
||||
impl<T: ObjectSubclass> hash::Hash for ObjectImplRef<T> {
|
||||
#[inline]
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: hash::Hasher,
|
||||
{
|
||||
self.0.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Weak reference to an [`ObjectSubclass`] reference.
|
||||
pub struct ObjectImplWeakRef<T: ObjectSubclass>(WeakRef<T::Type>);
|
||||
|
||||
unsafe impl<T: ObjectSubclass + Send + Sync> Send for ObjectImplWeakRef<T> {}
|
||||
unsafe impl<T: ObjectSubclass + Send + Sync> Sync for ObjectImplWeakRef<T> {}
|
||||
|
||||
impl<T: ObjectSubclass> ObjectImplWeakRef<T> {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Upgrade to a strong reference, if possible.
|
||||
///
|
||||
/// This will return `None` if the underlying object was freed in the meantime.
|
||||
#[inline]
|
||||
pub fn upgrade(&self) -> Option<ObjectImplRef<T>> {
|
||||
let obj = self.0.upgrade()?;
|
||||
Some(ObjectImplRef(obj))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Clone for ObjectImplWeakRef<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> fmt::Debug for ObjectImplWeakRef<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<crate::WeakRef<T::Type> as fmt::Debug>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ObjectSubclass> Upgrade for ObjectImplWeakRef<T> {
|
||||
type Strong = ObjectImplRef<T>;
|
||||
|
||||
#[inline]
|
||||
fn upgrade(&self) -> Option<Self::Strong> {
|
||||
self.upgrade()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
//! Module for registering shared types for Rust types.
|
||||
|
||||
use crate::{prelude::*, translate::*};
|
||||
|
||||
pub unsafe trait RefCounted: Clone + Sized + 'static {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The inner type
|
||||
type InnerType;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The function used to increment the inner type refcount
|
||||
unsafe fn ref_(this: *const Self::InnerType) -> *const Self::InnerType;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Provides access to a raw pointer to InnerType
|
||||
fn as_ptr(&self) -> *const Self::InnerType;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Converts the RefCounted object to a raw pointer to InnerType
|
||||
unsafe fn into_raw(self) -> *const Self::InnerType;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Converts a raw pointer to InnerType to a RefCounted object
|
||||
unsafe fn from_raw(this: *const Self::InnerType) -> Self;
|
||||
}
|
||||
|
||||
unsafe impl<T> RefCounted for std::sync::Arc<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type InnerType = T;
|
||||
|
||||
#[inline]
|
||||
unsafe fn ref_(this: *const Self::InnerType) -> *const Self::InnerType {
|
||||
std::sync::Arc::increment_strong_count(this);
|
||||
this
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const Self::InnerType {
|
||||
std::sync::Arc::as_ptr(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn into_raw(self) -> *const Self::InnerType {
|
||||
std::sync::Arc::into_raw(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_raw(this: *const Self::InnerType) -> Self {
|
||||
std::sync::Arc::from_raw(this)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> RefCounted for std::rc::Rc<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type InnerType = T;
|
||||
|
||||
#[inline]
|
||||
unsafe fn ref_(this: *const Self::InnerType) -> *const Self::InnerType {
|
||||
use std::mem::ManuallyDrop;
|
||||
let this_rc = ManuallyDrop::new(std::rc::Rc::from_raw(this));
|
||||
std::rc::Rc::into_raw(ManuallyDrop::take(&mut this_rc.clone()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const Self::InnerType {
|
||||
std::rc::Rc::as_ptr(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn into_raw(self) -> *const Self::InnerType {
|
||||
std::rc::Rc::into_raw(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_raw(this: *const Self::InnerType) -> Self {
|
||||
std::rc::Rc::from_raw(this)
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Trait for defining shared types.
|
||||
///
|
||||
/// Links together the type name with the type itself.
|
||||
///
|
||||
/// See [`register_shared_type`] for registering an implementation of this trait
|
||||
/// with the type system.
|
||||
///
|
||||
/// [`register_shared_type`]: fn.register_shared_type.html
|
||||
pub trait SharedType: StaticType + Clone + Sized + 'static {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Shared type name.
|
||||
///
|
||||
/// This must be unique in the whole process.
|
||||
const NAME: &'static str;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The inner refcounted type
|
||||
type RefCountedType: RefCounted;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Converts the SharedType into its inner RefCountedType
|
||||
fn into_refcounted(self) -> Self::RefCountedType;
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Constructs a SharedType from a RefCountedType
|
||||
fn from_refcounted(this: Self::RefCountedType) -> Self;
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Register a boxed `glib::Type` ID for `T`.
|
||||
///
|
||||
/// This must be called only once and will panic on a second call.
|
||||
///
|
||||
/// See [`Shared!`] for defining a function that ensures that
|
||||
/// this is only called once and returns the type id.
|
||||
///
|
||||
/// [`Shared!`]: ../../derive.Shared.html
|
||||
pub fn register_shared_type<T: SharedType>() -> crate::Type {
|
||||
unsafe {
|
||||
use std::ffi::CString;
|
||||
unsafe extern "C" fn shared_ref<T: SharedType>(v: ffi::gpointer) -> ffi::gpointer {
|
||||
T::RefCountedType::ref_(v as *const <T::RefCountedType as RefCounted>::InnerType)
|
||||
as ffi::gpointer
|
||||
}
|
||||
unsafe extern "C" fn shared_unref<T: SharedType>(v: ffi::gpointer) {
|
||||
let _ = T::RefCountedType::from_raw(
|
||||
v as *const <T::RefCountedType as RefCounted>::InnerType,
|
||||
);
|
||||
}
|
||||
|
||||
let type_name = CString::new(T::NAME).unwrap();
|
||||
assert_eq!(
|
||||
gobject_ffi::g_type_from_name(type_name.as_ptr()),
|
||||
gobject_ffi::G_TYPE_INVALID,
|
||||
"Type {} has already been registered",
|
||||
type_name.to_str().unwrap()
|
||||
);
|
||||
|
||||
let type_ = crate::Type::from_glib(gobject_ffi::g_boxed_type_register_static(
|
||||
type_name.as_ptr(),
|
||||
Some(shared_ref::<T>),
|
||||
Some(shared_unref::<T>),
|
||||
));
|
||||
assert!(type_.is_valid());
|
||||
|
||||
type_
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
// We rename the current crate as glib, since the macros in glib-macros
|
||||
// generate the glib namespace through the crate_ident_new utility,
|
||||
// and that returns `glib` (and not `crate`) when called inside the glib crate
|
||||
use crate as glib;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct MySharedInner {
|
||||
foo: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, glib::SharedBoxed)]
|
||||
#[shared_boxed_type(name = "MySharedArc")]
|
||||
struct MySharedArc(std::sync::Arc<MySharedInner>);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, glib::SharedBoxed)]
|
||||
#[shared_boxed_type(name = "MySharedRc")]
|
||||
struct MySharedRc(std::rc::Rc<MySharedInner>);
|
||||
|
||||
#[test]
|
||||
fn test_register() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedArc::static_type());
|
||||
assert_ne!(crate::Type::INVALID, MySharedRc::static_type());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_arc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedArc::static_type());
|
||||
|
||||
let b = MySharedArc::from_refcounted(std::sync::Arc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
let v = b.to_value();
|
||||
let b2 = v.get::<MySharedArc>().unwrap();
|
||||
assert!(std::sync::Arc::ptr_eq(&b.0, &b2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_rc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedRc::static_type());
|
||||
|
||||
let b = MySharedRc::from_refcounted(std::rc::Rc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
let v = b.to_value();
|
||||
let b2 = v.get::<MySharedRc>().unwrap();
|
||||
assert!(std::rc::Rc::ptr_eq(&b.0, &b2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_ffi_pointer_arc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedArc::static_type());
|
||||
|
||||
let b = MySharedArc::from_refcounted(std::sync::Arc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
|
||||
let inner_raw_ptr = std::sync::Arc::into_raw(b.clone().0);
|
||||
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 2);
|
||||
|
||||
let inner_raw_ptr_clone =
|
||||
unsafe { <MySharedArc as SharedType>::RefCountedType::ref_(inner_raw_ptr) };
|
||||
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 3);
|
||||
assert!(std::ptr::eq(inner_raw_ptr, inner_raw_ptr_clone));
|
||||
|
||||
let _ = unsafe { <MySharedArc as SharedType>::RefCountedType::from_raw(inner_raw_ptr) };
|
||||
let _ =
|
||||
unsafe { <MySharedArc as SharedType>::RefCountedType::from_raw(inner_raw_ptr_clone) };
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_ffi_pointer_rc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedRc::static_type());
|
||||
|
||||
let b = MySharedRc::from_refcounted(std::rc::Rc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
|
||||
let inner_raw_ptr = std::rc::Rc::into_raw(b.clone().0);
|
||||
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 2);
|
||||
|
||||
let inner_raw_ptr_clone =
|
||||
unsafe { <MySharedRc as SharedType>::RefCountedType::ref_(inner_raw_ptr) };
|
||||
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 3);
|
||||
assert!(std::ptr::eq(inner_raw_ptr, inner_raw_ptr_clone));
|
||||
|
||||
let _ = unsafe { <MySharedRc as SharedType>::RefCountedType::from_raw(inner_raw_ptr) };
|
||||
let _ =
|
||||
unsafe { <MySharedRc as SharedType>::RefCountedType::from_raw(inner_raw_ptr_clone) };
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_glib_borrow_arc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedRc::static_type());
|
||||
|
||||
let b = MySharedArc::from_refcounted(std::sync::Arc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
|
||||
let inner_raw_ptr = std::sync::Arc::into_raw(b.clone().0);
|
||||
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 2);
|
||||
|
||||
unsafe {
|
||||
let _ = MySharedArc::from_glib_borrow(inner_raw_ptr);
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 2);
|
||||
}
|
||||
|
||||
assert_eq!(std::sync::Arc::strong_count(&b.0), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_glib_borrow_rc() {
|
||||
assert_ne!(crate::Type::INVALID, MySharedRc::static_type());
|
||||
|
||||
let b = MySharedRc::from_refcounted(std::rc::Rc::new(MySharedInner {
|
||||
foo: String::from("abc"),
|
||||
}));
|
||||
|
||||
let inner_raw_ptr = std::rc::Rc::into_raw(b.clone().0);
|
||||
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 2);
|
||||
|
||||
unsafe {
|
||||
let _ = MySharedRc::from_glib_borrow(inner_raw_ptr);
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 2);
|
||||
}
|
||||
|
||||
assert_eq!(std::rc::Rc::strong_count(&b.0), 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,715 @@
|
||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use std::{fmt, num::NonZeroU32, ptr, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
prelude::*, translate::*, utils::is_canonical_pspec_name, Closure, IntoGStr, SignalFlags, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Builder for signals.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[must_use = "The builder must be built to be used"]
|
||||
pub struct SignalBuilder {
|
||||
name: String,
|
||||
flags: SignalFlags,
|
||||
param_types: Vec<SignalType>,
|
||||
return_type: SignalType,
|
||||
class_handler: Option<
|
||||
Box<dyn Fn(&SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static>,
|
||||
>,
|
||||
accumulator: Option<
|
||||
Box<dyn Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static>,
|
||||
>,
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signal metadata.
|
||||
pub struct Signal {
|
||||
name: String,
|
||||
flags: SignalFlags,
|
||||
param_types: Vec<SignalType>,
|
||||
return_type: SignalType,
|
||||
registration: Mutex<SignalRegistration>,
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Token passed to signal class handlers.
|
||||
pub struct SignalClassHandlerToken(
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Instance for which the signal is emitted.
|
||||
pub(super) *mut gobject_ffi::GTypeInstance,
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Return type.
|
||||
pub(super) Type,
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Arguments value array.
|
||||
pub(super) *const Value,
|
||||
);
|
||||
|
||||
impl fmt::Debug for SignalClassHandlerToken {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("SignalClassHandlerToken")
|
||||
.field("type", &unsafe {
|
||||
crate::Object::from_glib_borrow(self.0 as *mut gobject_ffi::GObject)
|
||||
})
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signal invocation hint passed to signal accumulators.
|
||||
#[repr(transparent)]
|
||||
pub struct SignalInvocationHint(gobject_ffi::GSignalInvocationHint);
|
||||
|
||||
impl SignalInvocationHint {
|
||||
#[inline]
|
||||
pub fn detail(&self) -> Option<crate::Quark> {
|
||||
unsafe { try_from_glib(self.0.detail).ok() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn run_type(&self) -> SignalFlags {
|
||||
unsafe { from_glib(self.0.run_type) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SignalInvocationHint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("SignalInvocationHint")
|
||||
.field("detail", &self.detail())
|
||||
.field("run_type", &self.run_type())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// In-depth information of a specific signal
|
||||
pub struct SignalQuery(gobject_ffi::GSignalQuery);
|
||||
|
||||
unsafe impl Send for SignalQuery {}
|
||||
unsafe impl Sync for SignalQuery {}
|
||||
|
||||
impl SignalQuery {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The name of the signal.
|
||||
#[inline]
|
||||
pub fn signal_name<'a>(&self) -> &'a str {
|
||||
unsafe {
|
||||
let ptr = self.0.signal_name;
|
||||
std::ffi::CStr::from_ptr(ptr).to_str().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The ID of the signal.
|
||||
#[inline]
|
||||
pub fn signal_id(&self) -> SignalId {
|
||||
unsafe { SignalId::from_glib(self.0.signal_id) }
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The instance type this signal can be emitted for.
|
||||
#[inline]
|
||||
pub fn type_(&self) -> Type {
|
||||
unsafe { from_glib(self.0.itype) }
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The signal flags.
|
||||
#[inline]
|
||||
pub fn flags(&self) -> SignalFlags {
|
||||
unsafe { from_glib(self.0.signal_flags) }
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The return type for the user callback.
|
||||
#[inline]
|
||||
pub fn return_type(&self) -> SignalType {
|
||||
unsafe { from_glib(self.0.return_type) }
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The number of parameters the user callback takes.
|
||||
#[inline]
|
||||
pub fn n_params(&self) -> u32 {
|
||||
self.0.n_params
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The parameters for the user callback.
|
||||
#[inline]
|
||||
pub fn param_types(&self) -> &[SignalType] {
|
||||
if self.n_params() == 0 {
|
||||
return &[];
|
||||
}
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
self.0.param_types as *const SignalType,
|
||||
self.0.n_params as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SignalQuery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("SignalQuery")
|
||||
.field("signal_name", &self.signal_name())
|
||||
.field("type", &self.type_())
|
||||
.field("flags", &self.flags())
|
||||
.field("return_type", &self.return_type())
|
||||
.field("param_types", &self.param_types())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signal ID.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SignalId(NonZeroU32);
|
||||
|
||||
impl SignalId {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Create a new Signal Identifier.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller has to ensure it's a valid signal identifier.
|
||||
#[inline]
|
||||
pub unsafe fn new(id: NonZeroU32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
#[doc(alias = "g_signal_parse_name")]
|
||||
#[inline]
|
||||
pub fn parse_name(
|
||||
name: &str,
|
||||
type_: Type,
|
||||
force_detail: bool,
|
||||
) -> Option<(Self, Option<crate::Quark>)> {
|
||||
let mut signal_id = std::mem::MaybeUninit::uninit();
|
||||
let mut detail_quark = std::mem::MaybeUninit::uninit();
|
||||
unsafe {
|
||||
let found: bool = name.run_with_gstr(|name| {
|
||||
from_glib(gobject_ffi::g_signal_parse_name(
|
||||
name.as_ptr(),
|
||||
type_.into_glib(),
|
||||
signal_id.as_mut_ptr(),
|
||||
detail_quark.as_mut_ptr(),
|
||||
force_detail.into_glib(),
|
||||
))
|
||||
});
|
||||
|
||||
if found {
|
||||
Some((
|
||||
from_glib(signal_id.assume_init()),
|
||||
crate::Quark::try_from_glib(detail_quark.assume_init()).ok(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Find a SignalId by its `name`, and the `type` it connects to.
|
||||
#[doc(alias = "g_signal_lookup")]
|
||||
#[inline]
|
||||
pub fn lookup(name: &str, type_: Type) -> Option<Self> {
|
||||
unsafe {
|
||||
let signal_id = name.run_with_gstr(|name| {
|
||||
gobject_ffi::g_signal_lookup(name.as_ptr(), type_.into_glib())
|
||||
});
|
||||
if signal_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Self::new(NonZeroU32::new_unchecked(signal_id)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Queries more in-depth information about the current signal.
|
||||
#[doc(alias = "g_signal_query")]
|
||||
#[inline]
|
||||
pub fn query(&self) -> SignalQuery {
|
||||
unsafe {
|
||||
let mut query_ptr = std::mem::MaybeUninit::uninit();
|
||||
gobject_ffi::g_signal_query(self.into_glib(), query_ptr.as_mut_ptr());
|
||||
let query = query_ptr.assume_init();
|
||||
debug_assert_ne!(query.signal_id, 0);
|
||||
SignalQuery(query)
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Find the signal name.
|
||||
#[doc(alias = "g_signal_name")]
|
||||
#[inline]
|
||||
pub fn name<'a>(&self) -> &'a str {
|
||||
unsafe {
|
||||
let ptr = gobject_ffi::g_signal_name(self.into_glib());
|
||||
std::ffi::CStr::from_ptr(ptr).to_str().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<u32> for SignalId {
|
||||
#[inline]
|
||||
unsafe fn from_glib(signal_id: u32) -> Self {
|
||||
debug_assert_ne!(signal_id, 0);
|
||||
Self::new(NonZeroU32::new_unchecked(signal_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for SignalId {
|
||||
type GlibType = u32;
|
||||
|
||||
#[inline]
|
||||
fn into_glib(self) -> u32 {
|
||||
self.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SignalType(ffi::GType);
|
||||
|
||||
impl SignalType {
|
||||
#[inline]
|
||||
pub fn with_static_scope(type_: Type) -> Self {
|
||||
Self(type_.into_glib() | gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn static_scope(&self) -> bool {
|
||||
(self.0 & gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT) != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_(&self) -> Type {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Type> for SignalType {
|
||||
#[inline]
|
||||
fn from(type_: Type) -> Self {
|
||||
Self(type_.into_glib())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignalType> for Type {
|
||||
#[inline]
|
||||
fn from(type_: SignalType) -> Self {
|
||||
// Remove the extra-bit used for G_SIGNAL_TYPE_STATIC_SCOPE
|
||||
let type_ = type_.0 & (!gobject_ffi::G_TYPE_FLAG_RESERVED_ID_BIT);
|
||||
unsafe { from_glib(type_) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Type> for SignalType {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Type) -> bool {
|
||||
let type_: Type = (*self).into();
|
||||
type_.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SignalType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let type_: Type = (*self).into();
|
||||
f.debug_struct("SignalType")
|
||||
.field("name", &type_.name())
|
||||
.field("static_scope", &self.static_scope())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SignalType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let type_: Type = (*self).into();
|
||||
f.debug_struct("SignalType")
|
||||
.field("name", &type_.name())
|
||||
.field("static_scope", &self.static_scope())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::GType> for SignalType {
|
||||
#[inline]
|
||||
unsafe fn from_glib(type_: ffi::GType) -> Self {
|
||||
Self(type_)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for SignalType {
|
||||
type GlibType = ffi::GType;
|
||||
|
||||
#[inline]
|
||||
fn into_glib(self) -> ffi::GType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
enum SignalRegistration {
|
||||
Unregistered {
|
||||
class_handler: Option<
|
||||
Box<
|
||||
dyn Fn(&SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
>,
|
||||
>,
|
||||
accumulator: Option<
|
||||
Box<dyn Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static>,
|
||||
>,
|
||||
},
|
||||
Registered {
|
||||
type_: Type,
|
||||
signal_id: SignalId,
|
||||
},
|
||||
}
|
||||
|
||||
impl SignalBuilder {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The signal's parameters.
|
||||
pub fn param_types(
|
||||
mut self,
|
||||
param_types: impl IntoIterator<Item = impl Into<SignalType>>,
|
||||
) -> Self {
|
||||
self.param_types = param_types
|
||||
.into_iter()
|
||||
.map(|t| t.into())
|
||||
.collect::<Vec<_>>();
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The signal's returned value type.
|
||||
pub fn return_type<T: StaticType>(mut self) -> Self {
|
||||
self.return_type = T::static_type().into();
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The signal's returned value type.
|
||||
pub fn return_type_from(mut self, type_: impl Into<SignalType>) -> Self {
|
||||
self.return_type = type_.into();
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Run the signal class handler in the first emission stage.
|
||||
pub fn run_first(mut self) -> Self {
|
||||
self.flags |= SignalFlags::RUN_FIRST;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Run the signal class handler in the third emission stage.
|
||||
pub fn run_last(mut self) -> Self {
|
||||
self.flags |= SignalFlags::RUN_LAST;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Run the signal class handler in the last emission stage.
|
||||
pub fn run_cleanup(mut self) -> Self {
|
||||
self.flags |= SignalFlags::RUN_CLEANUP;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signals being emitted for an object while currently being in emission for this very object
|
||||
/// will not be emitted recursively, but instead cause the first emission to be restarted.
|
||||
pub fn no_recurse(mut self) -> Self {
|
||||
self.flags |= SignalFlags::NO_RECURSE;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// This signal supports "::detail" appendices to the signal name upon handler connections and
|
||||
/// emissions.
|
||||
pub fn detailed(mut self) -> Self {
|
||||
self.flags |= SignalFlags::DETAILED;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Action signals are signals that may freely be emitted on alive objects from user code.
|
||||
pub fn action(mut self) -> Self {
|
||||
self.flags |= SignalFlags::ACTION;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// No emissions hooks are supported for this signal.
|
||||
pub fn no_hooks(mut self) -> Self {
|
||||
self.flags |= SignalFlags::NO_HOOKS;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Varargs signal emission will always collect the arguments, even if there are no signal
|
||||
/// handlers connected.
|
||||
pub fn must_collect(mut self) -> Self {
|
||||
self.flags |= SignalFlags::MUST_COLLECT;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// The signal is deprecated and will be removed in a future version.
|
||||
pub fn deprecated(mut self) -> Self {
|
||||
self.flags |= SignalFlags::DEPRECATED;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Explicitly set all flags.
|
||||
///
|
||||
/// This overrides previously set flags on this builder.
|
||||
pub fn flags(mut self, flags: SignalFlags) -> Self {
|
||||
self.flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Class handler for this signal.
|
||||
pub fn class_handler<
|
||||
F: Fn(&SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
func: F,
|
||||
) -> Self {
|
||||
self.class_handler = Some(Box::new(func));
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Accumulator for the return values of the signal.
|
||||
///
|
||||
/// This is called if multiple signal handlers are connected to the signal for accumulating the
|
||||
/// return values into a single value.
|
||||
pub fn accumulator<
|
||||
F: Fn(&SignalInvocationHint, &mut Value, &Value) -> bool + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
func: F,
|
||||
) -> Self {
|
||||
self.accumulator = Some(Box::new(func));
|
||||
self
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Build the signal.
|
||||
///
|
||||
/// This does not register the signal yet, which only happens as part of object type
|
||||
/// registration.
|
||||
#[must_use = "Signal returned from the builder must be used for it to be registered"]
|
||||
pub fn build(self) -> Signal {
|
||||
let flags = if self.flags
|
||||
& (SignalFlags::RUN_FIRST | SignalFlags::RUN_LAST | SignalFlags::RUN_CLEANUP)
|
||||
== SignalFlags::empty()
|
||||
{
|
||||
self.flags | SignalFlags::RUN_LAST
|
||||
} else {
|
||||
self.flags
|
||||
};
|
||||
|
||||
Signal {
|
||||
name: self.name,
|
||||
flags,
|
||||
param_types: self.param_types.to_vec(),
|
||||
return_type: self.return_type,
|
||||
registration: Mutex::new(SignalRegistration::Unregistered {
|
||||
class_handler: self.class_handler,
|
||||
accumulator: self.accumulator,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Signal {
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Create a new builder for a signal.
|
||||
pub fn builder(name: &str) -> SignalBuilder {
|
||||
assert!(
|
||||
is_canonical_pspec_name(name),
|
||||
"{name} is not a valid canonical signal name",
|
||||
);
|
||||
SignalBuilder {
|
||||
name: name.to_owned(),
|
||||
param_types: Vec::default(),
|
||||
return_type: <()>::static_type().into(),
|
||||
flags: SignalFlags::empty(),
|
||||
class_handler: None,
|
||||
accumulator: None,
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Name of the signal.
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Flags of the signal.
|
||||
#[inline]
|
||||
pub fn flags(&self) -> SignalFlags {
|
||||
self.flags
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Parameter types of the signal.
|
||||
#[inline]
|
||||
pub fn param_types(&self) -> &[SignalType] {
|
||||
&self.param_types
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Return type of the signal.
|
||||
#[inline]
|
||||
pub fn return_type(&self) -> SignalType {
|
||||
self.return_type
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Signal ID.
|
||||
///
|
||||
/// This will panic if called before the signal was registered.
|
||||
#[inline]
|
||||
pub fn signal_id(&self) -> SignalId {
|
||||
match &*self.registration.lock().unwrap() {
|
||||
SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
|
||||
SignalRegistration::Registered { signal_id, .. } => *signal_id,
|
||||
}
|
||||
}
|
||||
|
||||
// rustdoc-stripper-ignore-next
|
||||
/// Type this signal was registered for.
|
||||
///
|
||||
/// This will panic if called before the signal was registered.
|
||||
#[inline]
|
||||
pub fn type_(&self) -> Type {
|
||||
match &*self.registration.lock().unwrap() {
|
||||
SignalRegistration::Unregistered { .. } => panic!("Signal not registered yet"),
|
||||
SignalRegistration::Registered { type_, .. } => *type_,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn register(&self, type_: Type) {
|
||||
let mut registration = self.registration.lock().unwrap();
|
||||
|
||||
let (class_handler, accumulator) = match &mut *registration {
|
||||
SignalRegistration::Unregistered {
|
||||
class_handler,
|
||||
accumulator,
|
||||
} => (class_handler.take(), accumulator.take()),
|
||||
SignalRegistration::Registered { .. } => unreachable!(),
|
||||
};
|
||||
|
||||
let return_type = self.return_type;
|
||||
|
||||
let class_handler = class_handler.map(|class_handler| {
|
||||
Closure::new(move |values| unsafe {
|
||||
let instance = gobject_ffi::g_value_get_object(values[0].to_glib_none().0);
|
||||
let res = class_handler(&SignalClassHandlerToken(instance as *mut _, return_type.into(), values.as_ptr()), values);
|
||||
|
||||
if return_type == Type::UNIT {
|
||||
if let Some(ref v) = res {
|
||||
panic!("Signal has no return value but class handler returned a value of type {}", v.type_());
|
||||
}
|
||||
} else {
|
||||
match res {
|
||||
None => {
|
||||
panic!("Signal has a return value but class handler returned none");
|
||||
}
|
||||
Some(ref v) => {
|
||||
assert!(v.type_().is_a(return_type.into()), "Signal has a return type of {} but class handler returned {}", Type::from(return_type), v.type_());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
})
|
||||
});
|
||||
|
||||
unsafe extern "C" fn accumulator_trampoline(
|
||||
ihint: *mut gobject_ffi::GSignalInvocationHint,
|
||||
return_accu: *mut gobject_ffi::GValue,
|
||||
handler_return: *const gobject_ffi::GValue,
|
||||
data: ffi::gpointer,
|
||||
) -> ffi::gboolean {
|
||||
let accumulator = &*(data as *const (
|
||||
SignalType,
|
||||
Box<
|
||||
dyn Fn(&SignalInvocationHint, &mut Value, &Value) -> bool
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>,
|
||||
));
|
||||
|
||||
let return_accu = &mut *(return_accu as *mut Value);
|
||||
let handler_return = &*(handler_return as *const Value);
|
||||
let return_type = accumulator.0;
|
||||
|
||||
assert!(
|
||||
handler_return.type_().is_a(return_type.into()),
|
||||
"Signal has a return type of {} but handler returned {}",
|
||||
Type::from(return_type),
|
||||
handler_return.type_()
|
||||
);
|
||||
|
||||
let res = (accumulator.1)(&SignalInvocationHint(*ihint), return_accu, handler_return)
|
||||
.into_glib();
|
||||
|
||||
assert!(
|
||||
return_accu.type_().is_a(return_type.into()),
|
||||
"Signal has a return type of {} but accumulator returned {}",
|
||||
Type::from(return_type),
|
||||
return_accu.type_()
|
||||
);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
let (accumulator, accumulator_trampoline) =
|
||||
if let (Some(accumulator), true) = (accumulator, return_type != Type::UNIT) {
|
||||
(
|
||||
Box::into_raw(Box::new((return_type, accumulator))),
|
||||
Some::<unsafe extern "C" fn(_, _, _, _) -> _>(accumulator_trampoline),
|
||||
)
|
||||
} else {
|
||||
(ptr::null_mut(), None)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let signal_id = gobject_ffi::g_signal_newv(
|
||||
self.name.to_glib_none().0,
|
||||
type_.into_glib(),
|
||||
self.flags.into_glib(),
|
||||
class_handler.to_glib_none().0,
|
||||
accumulator_trampoline,
|
||||
accumulator as ffi::gpointer,
|
||||
None,
|
||||
return_type.into_glib(),
|
||||
self.param_types.len() as u32,
|
||||
self.param_types.as_ptr() as *mut _,
|
||||
);
|
||||
*registration = SignalRegistration::Registered {
|
||||
type_,
|
||||
signal_id: SignalId::from_glib(signal_id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user