├── .gitignore ├── Cargo.toml ├── Ex-0.1.pc ├── Makefile ├── README.md ├── build.rs ├── include └── ex │ ├── bar.h │ ├── color.h │ ├── error.h │ ├── ex.h │ ├── flags.h │ ├── foo.h │ ├── nameable.h │ ├── rstring.h │ └── shared-rstring.h ├── src ├── bar │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── color │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── error │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── flags │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── foo │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── lib.rs ├── nameable │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── rstring │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs └── shared_rstring │ ├── ffi.rs │ ├── imp.rs │ └── mod.rs ├── test.c ├── test.js ├── test.py └── test.vala /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gobject-example" 3 | version = "0.1.0" 4 | authors = ["Sebastian Dröge "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | glib = { git = "https://github.com/gtk-rs/gtk-rs-core" } 9 | gio = { git = "https://github.com/gtk-rs/gtk-rs-core" } 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [features] 15 | bindings = [] 16 | -------------------------------------------------------------------------------- /Ex-0.1.pc: -------------------------------------------------------------------------------- 1 | Name: Ex 2 | Description: 3 | Version: 0.1 4 | Libs: -Ltarget/debug -lgobject_example 5 | Cflags: -Iinclude 6 | Requires: gobject-2.0 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HEADERS = \ 2 | include/ex/ex.h \ 3 | include/ex/error.h \ 4 | include/ex/flags.h \ 5 | include/ex/color.h \ 6 | include/ex/foo.h \ 7 | include/ex/bar.h \ 8 | include/ex/nameable.h \ 9 | include/ex/rstring.h \ 10 | include/ex/shared-rstring.h \ 11 | 12 | RUST_SOURCES = \ 13 | src/lib.rs \ 14 | src/color/ffi.rs \ 15 | src/color/imp.rs \ 16 | src/color/mod.rs \ 17 | src/error/ffi.rs \ 18 | src/error/imp.rs \ 19 | src/error/mod.rs \ 20 | src/flags/ffi.rs \ 21 | src/flags/imp.rs \ 22 | src/flags/mod.rs \ 23 | src/foo/ffi.rs \ 24 | src/foo/imp.rs \ 25 | src/foo/mod.rs \ 26 | src/bar/ffi.rs \ 27 | src/bar/imp.rs \ 28 | src/bar/mod.rs \ 29 | src/nameable/ffi.rs \ 30 | src/nameable/imp.rs \ 31 | src/nameable/mod.rs \ 32 | src/rstring/ffi.rs \ 33 | src/rstring/imp.rs \ 34 | src/rstring/mod.rs \ 35 | src/shared_rstring/ffi.rs \ 36 | src/shared_rstring/imp.rs \ 37 | src/shared_rstring/mod.rs 38 | 39 | all: Ex-0.1.gir Ex-0.1.typelib Ex-0.1.vapi 40 | 41 | export PKG_CONFIG_PATH=$(PWD) 42 | export GI_TYPELIB_PATH=$(PWD) 43 | export LD_LIBRARY_PATH=$(PWD)/target/debug 44 | 45 | target/debug/libgobject_example.so: $(RUST_SOURCES) 46 | cargo build 47 | 48 | Ex-0.1.gir: target/debug/libgobject_example.so $(HEADERS) 49 | g-ir-scanner -v --warn-all \ 50 | --namespace Ex --nsversion=0.1 \ 51 | -Iinclude --c-include "ex/ex.h" \ 52 | --library=gobject_example --library-path=target/debug \ 53 | --include GLib-2.0 --pkg glib-2.0 \ 54 | --include GObject-2.0 --pkg gobject-2.0 \ 55 | --include Gio-2.0 --pkg gio-2.0 \ 56 | --pkg-export Ex-0.1 \ 57 | --output $@ \ 58 | $(HEADERS) 59 | 60 | Ex-0.1.typelib: Ex-0.1.gir 61 | g-ir-compiler \ 62 | --includedir=include \ 63 | $< -o $@ 64 | 65 | Ex-0.1.vapi: Ex-0.1.gir 66 | vapigen \ 67 | --pkg gio-2.0 \ 68 | --library Ex-0.1 \ 69 | $< 70 | 71 | clean: 72 | rm -f Ex-0.1.typelib 73 | rm -f Ex-0.1.gir 74 | rm -f Ex-0.1.vapi test-vala 75 | rm -rf test-c 76 | cargo clean 77 | 78 | run-python: Ex-0.1.typelib 79 | python3 test.py 80 | 81 | run-gjs: Ex-0.1.typelib 82 | gjs test.js 83 | 84 | test-vala: test.vala Ex-0.1.vapi 85 | valac -v \ 86 | --vapidir=$(PWD) \ 87 | --pkg=Ex-0.1 \ 88 | $< -o $@ 89 | 90 | run-vala: test-vala 91 | ./test-vala 92 | 93 | test-c: test.c target/debug/libgobject_example.so Ex-0.1.pc $(HEADERS) 94 | $(CC) -Wall $< `pkg-config --cflags --libs Ex-0.1` -o $@ 95 | 96 | run-c: test-c 97 | ./test-c 98 | 99 | check: 100 | cargo test 101 | 102 | check-bindings: target/debug/libgobject_example.so 103 | cargo test --features=bindings 104 | 105 | check-all: check check-bindings run-c run-python run-gjs run-vala 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example for exporting a GObject/C API from Rust 2 | 3 | This repository contains an example [Rust](https://www.rust-lang.org/) crate 4 | that can compile to a C-compatible shared library and that exports a 5 | [GObject](https://developer.gnome.org/gobject/stable) C API. 6 | 7 | At the same time the API provided by this crate can be used directly from Rust 8 | by statically linking this crate as well as by dynamically linking against the 9 | C-compatible library. Both variants provide exactly the same Rust API. 10 | 11 | In addition it provides a way to generate [GObject-Introspection](https://gitlab.gnome.org/GNOME/gobject-introspection/) 12 | metadata that allows usage from other languages, like [Python](https://gitlab.gnome.org/GNOME/pygobject/) and 13 | [JavaScript](https://gitlab.gnome.org/GNOME/gjs). 14 | 15 | ## Implemented Example APIs 16 | 17 | ### General Structure 18 | 19 | Each type comes with 3 Rust modules 20 | 21 | * `mod.rs`: Contains a Rust wrapper of the API. This is basically the same 22 | as what [gir](https://github.com/gtk-rs/gir) would autogenerate, and 23 | follows the same patterns as the GLib, GTK, etc. bindings. 24 | * `imp.rs`: Contains the definition of the types together with the actual 25 | private implementation, plus an inline FFI module that exports 26 | C-compatible FFI functions. 27 | * `ffi.rs`: Contains Rust FFI definitions of the exported C types. This is 28 | only used in combination with the `bindings` cargo feature (see below). 29 | 30 | and a C header that is (for now, see [issue 6](https://github.com/sdroege/gobject-example-rs/pull/6)) 31 | manually written. 32 | 33 | ### Details 34 | 35 | Pending refactoring in [issue 10](https://github.com/sdroege/gobject-example-rs/issues/10). 36 | 37 | ## Usage 38 | 39 | ### Usage from Rust 40 | 41 | The API from the `mod.rs` can directly be used from Rust code and follows the 42 | same patterns as the GLib, GTK, etc. bindings. 43 | 44 | There is example usage in the inline tests inside the `mod.rs` of each type. 45 | 46 | #### Statically linked crate 47 | 48 | The crate can be directly added as a dependency in some other projects 49 | `Cargo.toml` and then the API from the individual `mod.rs` is available. 50 | 51 | This statically links the implementation into the application. 52 | 53 | Running `make check` would run tests in this mode. 54 | 55 | #### Dynamically linked C library (`bindings` feature) 56 | 57 | When adding the crate as a dependency and enabling the `bindings` cargo 58 | feature then the actual implementation of all the types is omitted. Instead 59 | dynamic linking against the implementation from the C-compatible shared 60 | library will happen. 61 | 62 | The API is otherwise exactly the same. 63 | 64 | Running `make check-bindings` would run tests in this mode. 65 | 66 | ### Usage from C 67 | 68 | Running `cargo build` will create a C-compatible shared library in 69 | `target/debug/libgobject_example.so`. The corresponding headers for the API 70 | can be found in the `include` directory. 71 | 72 | `test.c` contains some example usage of the API and `make run-c` compiles and 73 | runs this. 74 | 75 | ### Usage from Python 76 | 77 | Via [gobject-introspection](https://gitlab.gnome.org/GNOME/gobject-introspection/) 78 | a `Ex-0.1.typelib` file is created. This can be used by [pygobject](https://gitlab.gnome.org/GNOME/pygobject/) 79 | to expose the API directly to Python. 80 | 81 | `test.py` contains some example usage of the API and `make run-python` runs this. 82 | 83 | ### Usage from JavaScript/GJS 84 | 85 | Via [gobject-introspection](https://gitlab.gnome.org/GNOME/gobject-introspection/) 86 | a `Ex-0.1.typelib` file is created. This can be used by [gjs](https://gitlab.gnome.org/GNOME/gjs) 87 | to expose the API directly to JavaScript. An alternative for [node.js](https://nodejs.org) would 88 | be [node-gtk](https://github.com/romgrk/node-gtk). 89 | 90 | `test.js` contains some example usage of the API and `make run-gjs` runs this. 91 | 92 | ### Usage from Vala 93 | 94 | Via [gobject-introspection](https://gitlab.gnome.org/GNOME/gobject-introspection/) 95 | a `Ex-0.1.gir` file is created that contains an XML representation of the API. 96 | [Vala](https://wiki.gnome.org/Projects/Vala) can directly make use of this. 97 | 98 | `test.vala` contains some example usage of the API and `make run-vala` runs this. 99 | 100 | ### Usage from other languages 101 | 102 | The [gobject-introspection](https://gitlab.gnome.org/GNOME/gobject-introspection/) 103 | `.gir` and `.typelib` files can be used to autogenerate bindings for dozens of 104 | different languages. 105 | 106 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | if cfg!(feature = "bindings") { 5 | // assuming target dir isn't tweaked 6 | let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 7 | let profile = env::var("PROFILE").unwrap(); 8 | println!( 9 | "cargo:rustc-link-search=native={}/target/{}", 10 | manifest_dir, profile 11 | ); 12 | println!("cargo:rustc-link-lib=dylib=gobject_example"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /include/ex/bar.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_BAR_H__ 2 | #define __EX_BAR_H__ 3 | 4 | #include 5 | 6 | #include "foo.h" 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define EX_TYPE_BAR (ex_bar_get_type()) 11 | #define EX_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EX_TYPE_BAR,ExBar)) 12 | #define EX_IS_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),EX_TYPE_BAR)) 13 | #define EX_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,EX_TYPE_BAR,ExBarClass)) 14 | #define EX_IS_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,EX_TYPE_BAR)) 15 | #define EX_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,EX_TYPE_BAR,ExBarClass)) 16 | 17 | typedef struct _ExBar ExBar; 18 | typedef struct _ExBarClass ExBarClass; 19 | 20 | struct _ExBar { 21 | ExFoo parent; 22 | }; 23 | 24 | struct _ExBarClass { 25 | ExFooClass parent_class; 26 | }; 27 | 28 | GType ex_bar_get_type (void); 29 | 30 | ExBar * ex_bar_new (const gchar * name); 31 | 32 | gdouble ex_bar_get_number (ExBar *bar); 33 | void ex_bar_set_number (ExBar *bar, gdouble num); 34 | 35 | G_END_DECLS 36 | 37 | #endif /* __EX_BAR_H__ */ 38 | -------------------------------------------------------------------------------- /include/ex/color.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_COLOR_H__ 2 | #define __EX_COLOR_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | typedef enum ExColor 9 | { 10 | EX_COLOR_RED, 11 | EX_COLOR_GREEN, 12 | EX_COLOR_BLUE, 13 | } ExColor; 14 | 15 | #define EX_TYPE_COLOR (ex_color_get_type()) 16 | 17 | GType ex_color_get_type (void); 18 | 19 | G_END_DECLS 20 | 21 | #endif /* __EX_COLOR_H__ */ 22 | -------------------------------------------------------------------------------- /include/ex/error.h: -------------------------------------------------------------------------------- 1 | #ifndef __ERROR_H_ 2 | #define __ERROR_H_ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | typedef enum ExError 9 | { 10 | EX_ERROR_INVALID_ARGUMENT, 11 | EX_ERROR_FAILED, 12 | } ExError; 13 | 14 | #define EX_ERROR (ex_error_quark()) 15 | 16 | GQuark ex_error_quark (void); 17 | 18 | G_END_DECLS 19 | 20 | #endif /* __ERROR_H_ */ 21 | -------------------------------------------------------------------------------- /include/ex/ex.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_H__ 2 | #define __EX_H__ 3 | 4 | #include "error.h" 5 | #include "flags.h" 6 | #include "color.h" 7 | #include "foo.h" 8 | #include "bar.h" 9 | #include "nameable.h" 10 | #include "rstring.h" 11 | #include "shared-rstring.h" 12 | 13 | #endif /* __EX_H__ */ 14 | -------------------------------------------------------------------------------- /include/ex/flags.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_FLAGS_H__ 2 | #define __EX_FLAGS_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | typedef enum ExFlags 9 | { 10 | EX_FLAGS_SOME = 1 << 0, 11 | EX_FLAGS_ZING = 1 << 1, 12 | EX_FLAGS_BONG = 1 << 2, 13 | } ExFlags; 14 | 15 | #define EX_TYPE_FLAGS (ex_flags_get_type()) 16 | 17 | GType ex_flags_get_type (void); 18 | 19 | G_END_DECLS 20 | 21 | #endif /* __EX_FLAGS_H__ */ 22 | -------------------------------------------------------------------------------- /include/ex/foo.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_FOO_H__ 2 | #define __EX_FOO_H__ 3 | 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | 9 | #define EX_TYPE_FOO (ex_foo_get_type()) 10 | #define EX_FOO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EX_TYPE_FOO,ExFoo)) 11 | #define EX_IS_FOO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),EX_TYPE_FOO)) 12 | #define EX_FOO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,EX_TYPE_FOO,ExFooClass)) 13 | #define EX_IS_FOO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,EX_TYPE_FOO)) 14 | #define EX_FOO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,EX_TYPE_FOO,ExFooClass)) 15 | 16 | typedef struct _ExFoo ExFoo; 17 | typedef struct _ExFooClass ExFooClass; 18 | 19 | struct _ExFoo { 20 | GObject parent; 21 | }; 22 | 23 | struct _ExFooClass { 24 | GObjectClass parent_class; 25 | 26 | gint (*increment) (ExFoo * foo, gint inc); 27 | void (*incremented) (ExFoo * foo, gint val, gint inc); 28 | }; 29 | 30 | GType ex_foo_get_type (void); 31 | 32 | ExFoo * ex_foo_new (const gchar * name); 33 | 34 | gint ex_foo_increment (ExFoo * foo, gint inc); 35 | gint ex_foo_get_counter (ExFoo * foo); 36 | gchar * ex_foo_get_name (ExFoo * foo); 37 | 38 | void ex_foo_check_async (ExFoo * foo, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); 39 | gboolean ex_foo_check_finish (ExFoo * foo, GAsyncResult *res, GError **error); 40 | 41 | G_END_DECLS 42 | 43 | #endif /* __EX_FOO_H__ */ 44 | -------------------------------------------------------------------------------- /include/ex/nameable.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_NAMEABLE_H__ 2 | #define __EX_NAMEABLE_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #define EX_TYPE_NAMEABLE (ex_nameable_get_type()) 9 | #define EX_NAMEABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj) ,EX_TYPE_NAMEABLE,ExNameable)) 10 | #define EX_IS_NAMEABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj) ,EX_TYPE_NAMEABLE)) 11 | #define EX_NAMEABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj) ,EX_TYPE_NAMEABLE,ExNameableInterface)) 12 | 13 | typedef struct _ExNameable ExNameable; 14 | typedef struct _ExNameableInterface ExNameableInterface; 15 | 16 | struct _ExNameableInterface { 17 | GTypeInterface parent_interface; 18 | 19 | gchar * (*get_name) (ExNameable * nameable); 20 | }; 21 | 22 | GType ex_nameable_get_type (void); 23 | 24 | gchar * ex_nameable_get_name (ExNameable * nameable); 25 | 26 | G_END_DECLS 27 | 28 | #endif /* __EX_NAMEABLE_H__ */ 29 | -------------------------------------------------------------------------------- /include/ex/rstring.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_RSTRING_H__ 2 | #define __EX_RSTRING_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #define EX_TYPE_RSTRING (ex_rstring_get_type()) 9 | 10 | typedef struct _ExRString ExRString; 11 | 12 | GType ex_rstring_get_type (void); 13 | 14 | ExRString * ex_rstring_new (const gchar * s); 15 | ExRString * ex_rstring_copy (const ExRString * rstring); 16 | void ex_rstring_free (ExRString * rstring); 17 | gchar * ex_rstring_get (const ExRString * rstring); 18 | void ex_rstring_set (ExRString *rstring, const gchar *s); 19 | 20 | G_END_DECLS 21 | 22 | #endif /* __EX_RSTRING_H__ */ 23 | -------------------------------------------------------------------------------- /include/ex/shared-rstring.h: -------------------------------------------------------------------------------- 1 | #ifndef __EX_SHARED_RSTRING_H__ 2 | #define __EX_SHARED_RSTRING_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #define EX_TYPE_SHARED_RSTRING (ex_shared_rstring_get_type()) 9 | 10 | typedef struct _ExSharedRString ExSharedRString; 11 | 12 | GType ex_shared_rstring_get_type (void); 13 | 14 | ExSharedRString * ex_shared_rstring_new (const gchar * s); 15 | ExSharedRString * ex_shared_rstring_ref (ExSharedRString * shared_rstring); 16 | void ex_shared_rstring_unref (ExSharedRString * shared_rstring); 17 | gchar * ex_shared_rstring_get (ExSharedRString * shared_rstring); 18 | 19 | G_END_DECLS 20 | 21 | #endif /* __EX_SHARED_RSTRING_H__ */ 22 | -------------------------------------------------------------------------------- /src/bar/ffi.rs: -------------------------------------------------------------------------------- 1 | use crate::foo; 2 | use std::ffi::c_char; 3 | 4 | #[repr(C)] 5 | pub struct ExBar { 6 | pub parent: foo::ffi::ExFoo, 7 | } 8 | 9 | #[repr(C)] 10 | pub struct ExBarClass { 11 | pub parent_class: foo::ffi::ExFooClass, 12 | } 13 | 14 | extern "C" { 15 | pub fn ex_bar_new(name: *const c_char) -> *mut ExBar; 16 | pub fn ex_bar_get_type() -> glib::ffi::GType; 17 | 18 | pub fn ex_bar_set_number(this: *mut ExBar, num: f64); 19 | pub fn ex_bar_get_number(this: *mut ExBar) -> f64; 20 | } 21 | -------------------------------------------------------------------------------- /src/bar/imp.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::sync::OnceLock; 3 | 4 | use glib::prelude::*; 5 | use glib::subclass::prelude::*; 6 | 7 | use crate::foo::*; 8 | 9 | // We could put our data into the Bar struct above but that's discouraged nowadays so let's just 10 | // keep it all in Bar 11 | // 12 | // We use Cell/RefCell here for each field as GObject conceptually uses interior mutability everywhere. 13 | // If this was to be used from multiple threads, these would have to be mutexes or otherwise 14 | // Sync+Send 15 | #[derive(Debug, Default)] 16 | pub struct Bar { 17 | number: Cell, 18 | } 19 | 20 | #[glib::object_subclass] 21 | impl ObjectSubclass for Bar { 22 | const NAME: &'static str = "ExBar"; 23 | type ParentType = Foo; 24 | type Type = super::Bar; 25 | } 26 | 27 | impl ObjectImpl for Bar { 28 | fn properties() -> &'static [glib::ParamSpec] { 29 | static PROPERTIES: OnceLock> = OnceLock::new(); 30 | PROPERTIES.get_or_init(|| { 31 | vec![glib::ParamSpecDouble::builder("number") 32 | .nick("Number") 33 | .blurb("Some number") 34 | .default_value(0.0) 35 | .maximum(100.0) 36 | .minimum(0.0) 37 | .build()] 38 | }) 39 | } 40 | 41 | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { 42 | match pspec.name() { 43 | "number" => { 44 | let number = value.get().unwrap(); 45 | self.set_number(number); 46 | } 47 | _ => unimplemented!(), 48 | } 49 | } 50 | 51 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { 52 | match pspec.name() { 53 | "number" => self.number().to_value(), 54 | _ => unimplemented!(), 55 | } 56 | } 57 | } 58 | 59 | impl FooImpl for Bar { 60 | fn increment(&self, obj: &Foo, inc: i32) -> i32 { 61 | self.parent_increment(obj, 2 * inc) 62 | } 63 | } 64 | 65 | impl Bar { 66 | fn set_number(&self, num: f64) { 67 | self.number.set(num); 68 | self.obj().notify("number"); 69 | } 70 | 71 | fn number(&self) -> f64 { 72 | self.number.get() 73 | } 74 | } 75 | 76 | pub(crate) mod ffi { 77 | use super::*; 78 | use glib::translate::*; 79 | use std::ffi::c_char; 80 | 81 | pub type ExBar = ::Instance; 82 | 83 | /// # Safety 84 | /// 85 | /// Must be a BarInstance object. 86 | #[no_mangle] 87 | pub unsafe extern "C" fn ex_bar_get_number(this: *mut ExBar) -> f64 { 88 | let imp = (*this).imp(); 89 | imp.number() 90 | } 91 | 92 | /// # Safety 93 | /// 94 | /// Must be a BarInstance object. 95 | #[no_mangle] 96 | pub unsafe extern "C" fn ex_bar_set_number(this: *mut ExBar, num: f64) { 97 | let imp = (*this).imp(); 98 | imp.set_number(num); 99 | } 100 | 101 | // GObject glue 102 | /// # Safety 103 | /// 104 | /// Must be a valid C string, 0-terminated. 105 | #[no_mangle] 106 | pub unsafe extern "C" fn ex_bar_new(name: *const c_char) -> *mut ExBar { 107 | let obj = glib::Object::builder::() 108 | .property("name", &*glib::GString::from_glib_borrow(name)) 109 | .build(); 110 | obj.to_glib_full() 111 | } 112 | 113 | #[no_mangle] 114 | pub extern "C" fn ex_bar_get_type() -> glib::ffi::GType { 115 | ::static_type().into_glib() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/bar/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::prelude::*; 10 | use glib::signal::{connect_raw, SignalHandlerId}; 11 | use glib::translate::*; 12 | 13 | use std::mem; 14 | 15 | use crate::foo::Foo; 16 | use crate::nameable::Nameable; 17 | 18 | #[cfg(feature = "bindings")] 19 | glib::wrapper! { 20 | pub struct Bar(Object) @extends Foo, @implements Nameable; 21 | 22 | match fn { 23 | type_ => || ffi::ex_bar_get_type(), 24 | } 25 | } 26 | 27 | #[cfg(not(feature = "bindings"))] 28 | glib::wrapper! { 29 | pub struct Bar(ObjectSubclass) @extends Foo, @implements Nameable; 30 | } 31 | 32 | impl Bar { 33 | pub fn new(name: Option<&str>) -> Bar { 34 | unsafe { from_glib_full(ffi::ex_bar_new(name.to_glib_none().0)) } 35 | } 36 | 37 | pub fn set_number(&self, num: f64) { 38 | unsafe { ffi::ex_bar_set_number(self.to_glib_none().0, num) } 39 | } 40 | 41 | pub fn number(&self) -> f64 { 42 | unsafe { ffi::ex_bar_get_number(self.to_glib_none().0) } 43 | } 44 | 45 | pub fn property_number(&self) -> f64 { 46 | let mut value = glib::Value::from(&0.0f64); 47 | unsafe { 48 | glib::gobject_ffi::g_object_get_property( 49 | self.as_ptr() as *mut glib::gobject_ffi::GObject, 50 | b"number\0".as_ptr() as *const _, 51 | value.to_glib_none_mut().0, 52 | ); 53 | } 54 | value.get().unwrap() 55 | } 56 | 57 | pub fn set_property_number(&self, num: f64) { 58 | unsafe { 59 | glib::gobject_ffi::g_object_set_property( 60 | self.as_ptr() as *mut glib::gobject_ffi::GObject, 61 | b"number\0".as_ptr() as *const _, 62 | glib::Value::from(&num).to_glib_none().0, 63 | ); 64 | } 65 | } 66 | 67 | pub fn connect_property_number_notify(&self, f: F) -> SignalHandlerId { 68 | unsafe extern "C" fn notify_number_trampoline( 69 | this: *mut ffi::ExBar, 70 | _param_spec: glib::ffi::gpointer, 71 | f: glib::ffi::gpointer, 72 | ) where 73 | P: IsA, 74 | { 75 | let f = &*(f as *const F); 76 | f(Bar::from_glib_borrow(this).unsafe_cast_ref::

()) 77 | } 78 | unsafe { 79 | let f: Box = Box::new(f); 80 | connect_raw( 81 | self.as_ptr() as *mut glib::gobject_ffi::GObject, 82 | b"notify::number\0".as_ptr() as *const _, 83 | Some(mem::transmute(notify_number_trampoline:: as usize)), 84 | Box::into_raw(f), 85 | ) 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | use crate::foo::FooExt; 94 | use std::cell::RefCell; 95 | use std::rc::Rc; 96 | 97 | #[test] 98 | fn test_new() { 99 | let bar = Bar::new(Some("bar's name")); 100 | 101 | drop(bar); 102 | } 103 | 104 | #[test] 105 | fn test_counter() { 106 | let bar = Bar::new(Some("bar's name")); 107 | 108 | assert_eq!(bar.counter(), 0); 109 | assert_eq!(bar.increment(1), 2); 110 | assert_eq!(bar.counter(), 2); 111 | assert_eq!(bar.increment(10), 22); 112 | assert_eq!(bar.counter(), 22); 113 | } 114 | 115 | #[test] 116 | fn test_name() { 117 | let bar = Bar::new(Some("bar's name")); 118 | 119 | assert_eq!(bar.name(), Some("bar's name".into())); 120 | assert_eq!(bar.property_name(), Some("bar's name".into())); 121 | } 122 | 123 | #[test] 124 | fn test_number() { 125 | let bar = Bar::new(Some("bar's name")); 126 | 127 | let counter = Rc::new(RefCell::new(0i32)); 128 | let counter_clone = counter.clone(); 129 | bar.connect_property_number_notify(move |_| { 130 | *counter_clone.borrow_mut() += 1; 131 | }); 132 | 133 | assert_eq!(*counter.borrow(), 0); 134 | assert_eq!(bar.number(), 0.0); 135 | assert_eq!(bar.property_number(), 0.0); 136 | bar.set_number(10.0); 137 | assert_eq!(*counter.borrow(), 1); 138 | assert_eq!(bar.number(), 10.0); 139 | assert_eq!(bar.property_number(), 10.0); 140 | bar.set_property_number(20.0); 141 | assert_eq!(*counter.borrow(), 2); 142 | assert_eq!(bar.number(), 20.0); 143 | assert_eq!(bar.property_number(), 20.0); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/color/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_int; 2 | 3 | pub type ExColor = c_int; 4 | 5 | pub const EX_COLOR_RED: ExColor = 0; 6 | pub const EX_COLOR_GREEN: ExColor = 1; 7 | pub const EX_COLOR_BLUE: ExColor = 2; 8 | 9 | extern "C" { 10 | pub fn ex_color_get_type() -> glib::ffi::GType; 11 | } 12 | -------------------------------------------------------------------------------- /src/color/imp.rs: -------------------------------------------------------------------------------- 1 | use glib::{prelude::*, translate::*}; 2 | 3 | #[derive(Debug, Copy, Clone, glib::Enum)] 4 | #[enum_type(name = "ExColor")] 5 | pub enum Color { 6 | Red, 7 | Green, 8 | Blue, 9 | } 10 | 11 | pub(crate) mod ffi { 12 | use super::*; 13 | 14 | pub type ExColor = ::GlibType; 15 | 16 | pub const EX_COLOR_RED: ExColor = super::Color::Red as i32; 17 | pub const EX_COLOR_GREEN: ExColor = super::Color::Green as i32; 18 | pub const EX_COLOR_BLUE: ExColor = super::Color::Blue as i32; 19 | 20 | #[no_mangle] 21 | pub unsafe extern "C" fn ex_color_get_type() -> glib::ffi::GType { 22 | super::Color::static_type().into_glib() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/color/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::{prelude::*, translate::*, Type}; 10 | 11 | #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] 12 | #[non_exhaustive] 13 | pub enum Color { 14 | Red, 15 | Green, 16 | Blue, 17 | __Unknown(i32), 18 | } 19 | 20 | impl IntoGlib for Color { 21 | type GlibType = ffi::ExColor; 22 | 23 | fn into_glib(self) -> ffi::ExColor { 24 | match self { 25 | Color::Red => ffi::EX_COLOR_RED, 26 | Color::Green => ffi::EX_COLOR_GREEN, 27 | Color::Blue => ffi::EX_COLOR_BLUE, 28 | Color::__Unknown(value) => value, 29 | } 30 | } 31 | } 32 | 33 | impl FromGlib for Color { 34 | unsafe fn from_glib(value: ffi::ExColor) -> Self { 35 | match value { 36 | 0 => Color::Red, 37 | 1 => Color::Green, 38 | 2 => Color::Blue, 39 | value => Color::__Unknown(value), 40 | } 41 | } 42 | } 43 | 44 | impl StaticType for Color { 45 | fn static_type() -> Type { 46 | unsafe { from_glib(ffi::ex_color_get_type()) } 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn test_enum() { 56 | let c = Color::Blue; 57 | assert_eq!(c.into_glib(), 2); 58 | 59 | let t = Color::static_type(); 60 | assert!(t.is_a(glib::Type::ENUM)); 61 | assert_eq!(t.name(), "ExColor"); 62 | 63 | let e = glib::EnumClass::with_type(t).unwrap(); 64 | let v = e.value(1).unwrap(); 65 | assert_eq!(v.name(), "Green"); 66 | assert_eq!(v.nick(), "green"); 67 | 68 | assert_eq!(e.value(42), None); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/error/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_int; 2 | 3 | pub type ExError = c_int; 4 | 5 | pub const EX_ERROR_INVALID_ARGUMENT: ExError = 0; 6 | pub const EX_ERROR_FAILED: ExError = 1; 7 | 8 | extern "C" { 9 | pub fn ex_error_quark() -> glib::ffi::GQuark; 10 | } 11 | -------------------------------------------------------------------------------- /src/error/imp.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, PartialEq, Eq, glib::ErrorDomain)] 2 | #[error_domain(name = "ex-error")] 3 | pub enum Error { 4 | InvalidArgument, 5 | Failed, 6 | } 7 | 8 | pub(crate) mod ffi { 9 | use glib::{prelude::*, translate::*}; 10 | 11 | pub type ExError = i32; 12 | 13 | pub const EX_ERROR_INVALID_ARGUMENT: ExError = super::Error::InvalidArgument as i32; 14 | pub const EX_ERROR_FAILED: ExError = super::Error::Failed as i32; 15 | 16 | #[no_mangle] 17 | pub unsafe extern "C" fn ex_error_quark() -> glib::ffi::GQuark { 18 | ::domain().into_glib() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::translate::*; 10 | 11 | #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] 12 | #[non_exhaustive] 13 | pub enum Error { 14 | InvalidArgument, 15 | Failed, 16 | __Unknown(i32), 17 | } 18 | 19 | impl IntoGlib for Error { 20 | type GlibType = ffi::ExError; 21 | 22 | fn into_glib(self) -> ffi::ExError { 23 | match self { 24 | Error::InvalidArgument => ffi::EX_ERROR_INVALID_ARGUMENT, 25 | Error::Failed => ffi::EX_ERROR_FAILED, 26 | Error::__Unknown(value) => value, 27 | } 28 | } 29 | } 30 | 31 | impl FromGlib for Error { 32 | unsafe fn from_glib(value: ffi::ExError) -> Self { 33 | match value { 34 | ffi::EX_ERROR_INVALID_ARGUMENT => Error::InvalidArgument, 35 | ffi::EX_ERROR_FAILED => Error::Failed, 36 | value => Error::__Unknown(value), 37 | } 38 | } 39 | } 40 | 41 | impl glib::error::ErrorDomain for Error { 42 | fn domain() -> glib::Quark { 43 | unsafe { from_glib(ffi::ex_error_quark()) } 44 | } 45 | 46 | fn code(self) -> i32 { 47 | self.into_glib() 48 | } 49 | 50 | fn from(code: i32) -> Option { 51 | unsafe { Some(Self::from_glib(code)) } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn test_error() { 61 | let err = glib::Error::new(Error::Failed, "We are all mad here."); 62 | assert!(err.is::()); 63 | assert!(matches!(err.kind::(), Some(Error::Failed))); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/flags/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_uint; 2 | 3 | pub type ExFlags = c_uint; 4 | 5 | pub const EX_FLAGS_SOME: ExFlags = 1; 6 | pub const EX_FLAGS_ZING: ExFlags = 2; 7 | pub const EX_FLAGS_BONG: ExFlags = 4; 8 | 9 | extern "C" { 10 | pub fn ex_flags_get_type() -> glib::ffi::GType; 11 | } 12 | -------------------------------------------------------------------------------- /src/flags/imp.rs: -------------------------------------------------------------------------------- 1 | #[glib::flags(name = "ExFlags")] 2 | pub enum Flags { 3 | SOME = 0b00000001, 4 | ZING = 0b00000010, 5 | BONG = 0b00000100, 6 | } 7 | 8 | pub(crate) mod ffi { 9 | use glib::{prelude::*, translate::*}; 10 | 11 | pub type ExFlags = ::GlibType; 12 | 13 | pub const EX_FLAGS_SOME: ExFlags = super::Flags::SOME.bits(); 14 | pub const EX_FLAGS_ZING: ExFlags = super::Flags::ZING.bits(); 15 | pub const EX_FLAGS_BONG: ExFlags = super::Flags::BONG.bits(); 16 | 17 | #[no_mangle] 18 | pub unsafe extern "C" fn ex_flags_get_type() -> glib::ffi::GType { 19 | super::Flags::static_type().into_glib() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/flags/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::{bitflags::bitflags, prelude::*, translate::*, Type}; 10 | 11 | bitflags! { 12 | pub struct Flags: u32 { 13 | const SOME = ffi::EX_FLAGS_SOME; 14 | const ZING = ffi::EX_FLAGS_ZING; 15 | const BONG = ffi::EX_FLAGS_BONG; 16 | } 17 | } 18 | 19 | impl IntoGlib for Flags { 20 | type GlibType = ffi::ExFlags; 21 | 22 | fn into_glib(self) -> ffi::ExFlags { 23 | self.bits() 24 | } 25 | } 26 | 27 | impl FromGlib for Flags { 28 | unsafe fn from_glib(value: ffi::ExFlags) -> Self { 29 | Flags::from_bits_truncate(value) 30 | } 31 | } 32 | 33 | impl StaticType for Flags { 34 | fn static_type() -> Type { 35 | unsafe { from_glib(ffi::ex_flags_get_type()) } 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn test_flags() { 45 | assert_eq!(Flags::ZING.bits(), 2); 46 | let t = Flags::static_type(); 47 | assert!(t.is_a(glib::Type::FLAGS)); 48 | assert_eq!(t.name(), "ExFlags"); 49 | let e = glib::FlagsClass::with_type(t).unwrap(); 50 | let v = e.value(1).unwrap(); 51 | assert_eq!(v.name(), "Some"); 52 | assert_eq!(v.nick(), "some"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/foo/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | #[repr(C)] 4 | pub struct ExFoo { 5 | pub parent: glib::gobject_ffi::GObject, 6 | } 7 | 8 | #[repr(C)] 9 | pub struct ExFooClass { 10 | pub parent_class: glib::gobject_ffi::GObjectClass, 11 | pub increment: Option i32>, 12 | pub incremented: Option, 13 | } 14 | 15 | extern "C" { 16 | pub fn ex_foo_new(name: *const c_char) -> *mut ExFoo; 17 | pub fn ex_foo_get_type() -> glib::ffi::GType; 18 | 19 | pub fn ex_foo_check_async( 20 | this: *mut ExFoo, 21 | cancellable: *mut gio::ffi::GCancellable, 22 | callback: gio::ffi::GAsyncReadyCallback, 23 | user_data: *mut c_void, 24 | ); 25 | 26 | pub fn ex_foo_check_finish( 27 | this: *mut ExFoo, 28 | res: *mut gio::ffi::GAsyncResult, 29 | error: *mut *mut glib::ffi::GError, 30 | ) -> bool; 31 | 32 | pub fn ex_foo_increment(this: *mut ExFoo, inc: i32) -> i32; 33 | pub fn ex_foo_get_counter(this: *mut ExFoo) -> i32; 34 | pub fn ex_foo_get_name(this: *mut ExFoo) -> *mut c_char; 35 | } 36 | -------------------------------------------------------------------------------- /src/foo/imp.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::ops; 3 | use std::sync::OnceLock; 4 | 5 | use glib::prelude::*; 6 | use glib::subclass::{prelude::*, Signal}; 7 | 8 | use crate::nameable::*; 9 | 10 | // Class struct aka "vtable" 11 | // 12 | // Here we would store virtual methods and similar 13 | #[repr(C)] 14 | pub struct FooClass { 15 | pub parent_class: glib::gobject_ffi::GObjectClass, 16 | pub increment: Option i32>, 17 | pub incremented: Option, 18 | } 19 | 20 | unsafe impl ClassStruct for FooClass { 21 | type Type = Foo; 22 | } 23 | 24 | impl ops::Deref for FooClass { 25 | type Target = glib::Class; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | unsafe { &*(self as *const _ as *const Self::Target) } 29 | } 30 | } 31 | 32 | impl ops::DerefMut for FooClass { 33 | fn deref_mut(&mut self) -> &mut glib::Class { 34 | unsafe { &mut *(self as *mut _ as *mut glib::Class) } 35 | } 36 | } 37 | 38 | // Our private state for the class 39 | // 40 | // We use RefCell/Cells here for each field as GObject conceptually uses interior mutability everywhere. 41 | // If this was to be used from multiple threads, these would have to be mutexes or otherwise 42 | // Sync+Send 43 | #[derive(Debug, Default)] 44 | pub struct Foo { 45 | name: RefCell>, 46 | counter: Cell, 47 | } 48 | 49 | #[glib::object_subclass] 50 | impl ObjectSubclass for Foo { 51 | const NAME: &'static str = "ExFoo"; 52 | type ParentType = glib::Object; 53 | type Type = super::Foo; 54 | type Class = FooClass; 55 | type Interfaces = (Nameable,); 56 | 57 | fn class_init(klass: &mut Self::Class) { 58 | klass.increment = Some(increment_default_trampoline); 59 | klass.incremented = Some(incremented_default_trampoline); 60 | } 61 | } 62 | 63 | impl ObjectImpl for Foo { 64 | fn signals() -> &'static [Signal] { 65 | static SIGNALS: OnceLock> = OnceLock::new(); 66 | SIGNALS 67 | .get_or_init(|| { 68 | vec![Signal::builder("incremented") 69 | .param_types([i32::static_type(), i32::static_type()]) 70 | .class_handler(|_, args| { 71 | let obj = args[0].get::().unwrap(); 72 | let val = args[1].get::().unwrap(); 73 | let inc = args[2].get::().unwrap(); 74 | 75 | unsafe { 76 | let klass = &*(obj.object_class() as *const _ as *const FooClass); 77 | if let Some(ref func) = klass.incremented { 78 | func(obj.as_ptr() as *mut ffi::ExFoo, val, inc); 79 | } 80 | } 81 | 82 | None 83 | }) 84 | .build()] 85 | }) 86 | .as_ref() 87 | } 88 | 89 | fn properties() -> &'static [glib::ParamSpec] { 90 | static PROPERTIES: OnceLock> = OnceLock::new(); 91 | PROPERTIES.get_or_init(|| { 92 | vec![glib::ParamSpecString::builder("name") 93 | .nick("Name") 94 | .blurb("Name of this object") 95 | .build()] 96 | }) 97 | } 98 | 99 | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { 100 | match pspec.name() { 101 | "name" => { 102 | let name = value.get().unwrap(); 103 | self.set_name(name); 104 | } 105 | _ => unimplemented!(), 106 | } 107 | } 108 | 109 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { 110 | match pspec.name() { 111 | "name" => self.name().to_value(), 112 | _ => unimplemented!(), 113 | } 114 | } 115 | } 116 | 117 | impl NameableImpl for Foo { 118 | fn name(&self) -> Option { 119 | self.name() 120 | } 121 | } 122 | 123 | impl Foo { 124 | // 125 | // Safe implementations. These take the wrapper type, and not &Self, as first argument 126 | // 127 | fn increment(&self, inc: i32) -> i32 { 128 | let mut val = self.counter(); 129 | 130 | val += inc; 131 | 132 | self.counter.set(val); 133 | 134 | self.obj().emit_by_name::<()>("incremented", &[&val, &inc]); 135 | 136 | val 137 | } 138 | 139 | fn incremented(&self, _val: i32, _inc: i32) { 140 | // Could do something here. Default/class handler of the "incremented" 141 | // signal that could be overriden by subclasses 142 | } 143 | 144 | fn counter(&self) -> i32 { 145 | self.counter.get() 146 | } 147 | 148 | fn name(&self) -> Option { 149 | self.name.borrow().clone() 150 | } 151 | 152 | fn set_name(&self, name: Option) { 153 | *self.name.borrow_mut() = name; 154 | } 155 | 156 | async fn check_async(&self) -> Result<(), glib::Error> { 157 | Ok(()) 158 | } 159 | } 160 | 161 | pub(crate) mod ffi { 162 | use super::*; 163 | use glib::translate::*; 164 | use std::ffi::{c_char, c_void}; 165 | 166 | pub type ExFoo = ::Instance; 167 | pub type ExFooClass = super::FooClass; 168 | 169 | /// # Safety 170 | /// 171 | /// Must be a ExFoo object. 172 | #[no_mangle] 173 | pub unsafe extern "C" fn ex_foo_increment(this: *mut ExFoo, inc: i32) -> i32 { 174 | let klass = (*this).class(); 175 | 176 | (klass.increment.unwrap())(this, inc) 177 | } 178 | 179 | // Trampolines to safe Rust implementations 180 | /// # Safety 181 | /// 182 | /// Must be a FooInstance object. 183 | #[no_mangle] 184 | pub unsafe extern "C" fn ex_foo_get_counter(this: *mut ExFoo) -> i32 { 185 | let imp = (*this).imp(); 186 | imp.counter() 187 | } 188 | 189 | /// # Safety 190 | /// 191 | /// Must be a FooInstance object. 192 | #[no_mangle] 193 | pub unsafe extern "C" fn ex_foo_get_name(this: *mut ExFoo) -> *mut c_char { 194 | let imp = (*this).imp(); 195 | imp.name().to_glib_full() 196 | } 197 | 198 | // GObject glue 199 | /// # Safety 200 | /// 201 | /// Must be a valid C string, 0-terminated. 202 | #[no_mangle] 203 | pub unsafe extern "C" fn ex_foo_new(name: *const c_char) -> *mut ExFoo { 204 | glib::Object::builder::() 205 | .property("name", &*glib::GString::from_glib_borrow(name)) 206 | .build() 207 | .to_glib_full() 208 | } 209 | 210 | #[no_mangle] 211 | pub extern "C" fn ex_foo_get_type() -> glib::ffi::GType { 212 | ::static_type().into_glib() 213 | } 214 | 215 | #[no_mangle] 216 | pub unsafe extern "C" fn ex_foo_check_async( 217 | this: *mut ExFoo, 218 | cancellable: *mut gio::ffi::GCancellable, 219 | callback: gio::ffi::GAsyncReadyCallback, 220 | user_data: *mut c_void, 221 | ) { 222 | let imp = (*this).imp(); 223 | let obj = &super::super::Foo::from_glib_none(this); 224 | let cancellable = gio::Cancellable::from_glib_borrow(cancellable); 225 | let callback = callback.unwrap(); 226 | 227 | let closure = move |task: gio::LocalTask, _: Option<&super::super::Foo>| { 228 | let result: *mut gio::ffi::GAsyncResult = 229 | task.upcast_ref::().to_glib_none().0; 230 | callback(this as *mut _, result, user_data) 231 | }; 232 | 233 | let task = gio::LocalTask::new(Some(obj), Some(&*cancellable), closure); 234 | 235 | glib::MainContext::ref_thread_default().spawn_local(async move { 236 | let res = imp.check_async().await.map(|_| true); 237 | task.return_result(res); 238 | }); 239 | } 240 | 241 | #[no_mangle] 242 | pub unsafe extern "C" fn ex_foo_check_finish( 243 | _this: *mut ExFoo, 244 | res: *mut gio::ffi::GAsyncResult, 245 | error: *mut *mut glib::ffi::GError, 246 | ) -> bool { 247 | let task = gio::Task::::from_glib_none(res as *mut gio::ffi::GTask); 248 | 249 | match task.propagate() { 250 | Ok(_) => true, 251 | Err(e) => { 252 | if !error.is_null() { 253 | *error = e.into_glib_ptr(); 254 | } 255 | false 256 | } 257 | } 258 | } 259 | } 260 | 261 | // Virtual method default implementation trampolines 262 | unsafe extern "C" fn increment_default_trampoline(this: *mut ffi::ExFoo, inc: i32) -> i32 { 263 | let imp = (*this).imp(); 264 | imp.increment(inc) 265 | } 266 | 267 | unsafe extern "C" fn incremented_default_trampoline(this: *mut ffi::ExFoo, val: i32, inc: i32) { 268 | let imp = (*this).imp(); 269 | imp.incremented(val, inc); 270 | } 271 | -------------------------------------------------------------------------------- /src/foo/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | pub(crate) use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | pub(crate) mod ffi; 8 | 9 | use glib::prelude::*; 10 | use glib::signal::{connect_raw, SignalHandlerId}; 11 | use glib::subclass::prelude::*; 12 | use glib::translate::*; 13 | 14 | use std::mem; 15 | use std::pin::Pin; 16 | 17 | use crate::nameable::Nameable; 18 | 19 | #[cfg(feature = "bindings")] 20 | glib::wrapper! { 21 | pub struct Foo(Object) @implements Nameable; 22 | 23 | match fn { 24 | type_ => || ffi::ex_foo_get_type(), 25 | } 26 | } 27 | 28 | #[cfg(not(feature = "bindings"))] 29 | glib::wrapper! { 30 | pub struct Foo(ObjectSubclass) @implements Nameable; 31 | } 32 | 33 | impl Foo { 34 | pub fn new(name: Option<&str>) -> Foo { 35 | unsafe { from_glib_full(ffi::ex_foo_new(name.to_glib_none().0)) } 36 | } 37 | } 38 | 39 | pub trait FooExt { 40 | fn increment(&self, inc: i32) -> i32; 41 | fn counter(&self) -> i32; 42 | fn name(&self) -> Option; 43 | 44 | fn property_name(&self) -> Option; 45 | 46 | fn connect_incremented(&self, f: F) -> SignalHandlerId; 47 | 48 | fn check_future( 49 | &self, 50 | ) -> Pin> + 'static>>; 51 | } 52 | 53 | impl> FooExt for O { 54 | fn increment(&self, inc: i32) -> i32 { 55 | unsafe { ffi::ex_foo_increment(self.as_ref().to_glib_none().0, inc) } 56 | } 57 | 58 | fn counter(&self) -> i32 { 59 | unsafe { ffi::ex_foo_get_counter(self.as_ref().to_glib_none().0) } 60 | } 61 | 62 | fn name(&self) -> Option { 63 | unsafe { from_glib_full(ffi::ex_foo_get_name(self.as_ref().to_glib_none().0)) } 64 | } 65 | 66 | fn property_name(&self) -> Option { 67 | let mut value = glib::Value::for_value_type::(); 68 | unsafe { 69 | glib::gobject_ffi::g_object_get_property( 70 | self.as_ptr() as *mut glib::gobject_ffi::GObject, 71 | b"name\0".as_ptr() as *const _, 72 | value.to_glib_none_mut().0, 73 | ); 74 | } 75 | value.get().unwrap() 76 | } 77 | 78 | fn connect_incremented(&self, f: F) -> SignalHandlerId { 79 | unsafe extern "C" fn connect_incremented_trampoline( 80 | this: *mut ffi::ExFoo, 81 | val: i32, 82 | inc: i32, 83 | f: glib::ffi::gpointer, 84 | ) where 85 | P: IsA, 86 | { 87 | let f = &*(f as *const F); 88 | f(Foo::from_glib_borrow(this).unsafe_cast_ref::

(), val, inc) 89 | } 90 | unsafe { 91 | let f: Box = Box::new(f); 92 | connect_raw( 93 | self.as_ptr() as *mut glib::gobject_ffi::GObject, 94 | b"incremented\0".as_ptr() as *const _, 95 | Some(mem::transmute( 96 | connect_incremented_trampoline:: as usize, 97 | )), 98 | Box::into_raw(f), 99 | ) 100 | } 101 | } 102 | 103 | fn check_future( 104 | &self, 105 | ) -> Pin> + 'static>> { 106 | fn check_async, P: FnOnce(Result<(), glib::Error>) + 'static>( 107 | obj: &O, 108 | cancellable: Option<&impl IsA>, 109 | callback: P, 110 | ) { 111 | let main_context = glib::MainContext::ref_thread_default(); 112 | let is_main_context_owner = main_context.is_owner(); 113 | let has_acquired_main_context = (!is_main_context_owner) 114 | .then(|| main_context.acquire().ok()) 115 | .flatten(); 116 | assert!( 117 | is_main_context_owner || has_acquired_main_context.is_some(), 118 | "Async operations only allowed if the thread is owning the MainContext" 119 | ); 120 | 121 | let user_data: Box> = 122 | Box::new(glib::thread_guard::ThreadGuard::new(callback)); 123 | unsafe extern "C" fn check_trampoline) + 'static>( 124 | _source_object: *mut glib::gobject_ffi::GObject, 125 | res: *mut gio::ffi::GAsyncResult, 126 | user_data: glib::ffi::gpointer, 127 | ) { 128 | let mut error = std::ptr::null_mut(); 129 | let _ = ffi::ex_foo_check_finish(_source_object as *mut _, res, &mut error); 130 | let result = if error.is_null() { 131 | Ok(()) 132 | } else { 133 | Err(from_glib_full(error)) 134 | }; 135 | let callback: Box> = 136 | Box::from_raw(user_data as *mut _); 137 | let callback: P = callback.into_inner(); 138 | callback(result); 139 | } 140 | let callback = check_trampoline::

; 141 | unsafe { 142 | ffi::ex_foo_check_async( 143 | obj.as_ref().to_glib_none().0, 144 | cancellable.map(|p| p.as_ref()).to_glib_none().0, 145 | Some(callback), 146 | Box::into_raw(user_data) as *mut _, 147 | ); 148 | } 149 | } 150 | 151 | Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| { 152 | check_async(obj, Some(cancellable), move |res| { 153 | send.resolve(res); 154 | }); 155 | })) 156 | } 157 | } 158 | 159 | pub trait FooImpl: ObjectImpl + 'static { 160 | fn increment(&self, obj: &Foo, inc: i32) -> i32 { 161 | self.parent_increment(obj, inc) 162 | } 163 | 164 | fn incremented(&self, obj: &Foo, val: i32, inc: i32) { 165 | self.parent_incremented(obj, val, inc); 166 | } 167 | 168 | fn parent_increment(&self, obj: &Foo, inc: i32) -> i32 { 169 | unsafe { 170 | let data = Self::type_data(); 171 | let parent_class = data.as_ref().parent_class() as *mut ffi::ExFooClass; 172 | if let Some(ref f) = (*parent_class).increment { 173 | f(obj.to_glib_none().0, inc) 174 | } else { 175 | unimplemented!() 176 | } 177 | } 178 | } 179 | 180 | fn parent_incremented(&self, obj: &Foo, val: i32, inc: i32) { 181 | unsafe { 182 | let data = Self::type_data(); 183 | let parent_class = data.as_ref().parent_class() as *mut ffi::ExFooClass; 184 | if let Some(ref f) = (*parent_class).incremented { 185 | f(obj.to_glib_none().0, val, inc) 186 | } 187 | } 188 | } 189 | } 190 | 191 | unsafe impl IsSubclassable for Foo { 192 | fn class_init(class: &mut glib::Class) { 193 | >::class_init(class); 194 | 195 | let klass = class.as_mut(); 196 | klass.increment = Some(increment_trampoline::); 197 | klass.incremented = Some(incremented_trampoline::); 198 | } 199 | fn instance_init(instance: &mut glib::subclass::InitializingObject) { 200 | >::instance_init(instance); 201 | } 202 | } 203 | 204 | // Virtual method default implementation trampolines 205 | unsafe extern "C" fn increment_trampoline(this: *mut ffi::ExFoo, inc: i32) -> i32 206 | where 207 | T: FooImpl, 208 | { 209 | let instance = &*(this as *const T::Instance); 210 | let imp = instance.imp(); 211 | imp.increment(&from_glib_borrow(this), inc) 212 | } 213 | 214 | unsafe extern "C" fn incremented_trampoline( 215 | this: *mut ffi::ExFoo, 216 | val: i32, 217 | inc: i32, 218 | ) where 219 | T: FooImpl, 220 | { 221 | let instance = &*(this as *const T::Instance); 222 | let imp = instance.imp(); 223 | imp.incremented(&from_glib_borrow(this), val, inc); 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use super::*; 229 | use std::cell::RefCell; 230 | use std::rc::Rc; 231 | 232 | #[test] 233 | fn test_new() { 234 | let foo = Foo::new(Some("foo's name")); 235 | 236 | drop(foo); 237 | } 238 | 239 | #[test] 240 | fn test_counter() { 241 | let foo = Foo::new(Some("foo's name")); 242 | 243 | let incremented = Rc::new(RefCell::new((0i32, 0i32))); 244 | let incremented_clone = incremented.clone(); 245 | foo.connect_incremented(move |_, val, inc| { 246 | *incremented_clone.borrow_mut() = (val, inc); 247 | }); 248 | 249 | assert_eq!(foo.counter(), 0); 250 | assert_eq!(foo.increment(1), 1); 251 | assert_eq!(*incremented.borrow(), (1, 1)); 252 | assert_eq!(foo.counter(), 1); 253 | assert_eq!(foo.increment(10), 11); 254 | assert_eq!(*incremented.borrow(), (11, 10)); 255 | assert_eq!(foo.counter(), 11); 256 | } 257 | 258 | #[test] 259 | fn test_name() { 260 | let foo = Foo::new(Some("foo's name")); 261 | 262 | assert_eq!(foo.name(), Some("foo's name".into())); 263 | assert_eq!(foo.property_name(), Some("foo's name".into())); 264 | } 265 | 266 | #[test] 267 | fn test_async() { 268 | let foo = Foo::new(Some("foo's name")); 269 | 270 | let c = glib::MainContext::default(); 271 | let l = glib::MainLoop::new(Some(&c), false); 272 | 273 | let future = glib::clone!(@strong l => async move { 274 | assert!(foo.check_future().await.is_ok()); 275 | l.quit(); 276 | }); 277 | 278 | c.spawn_local(future); 279 | l.run(); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bar; 2 | pub mod color; 3 | pub mod error; 4 | pub mod flags; 5 | pub mod foo; 6 | pub mod nameable; 7 | pub mod rstring; 8 | pub mod shared_rstring; 9 | -------------------------------------------------------------------------------- /src/nameable/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | #[repr(C)] 4 | pub struct ExNameable(c_void); 5 | 6 | #[derive(Clone, Copy)] 7 | #[repr(C)] 8 | pub struct ExNameableInterface { 9 | pub parent_iface: glib::gobject_ffi::GTypeInterface, 10 | pub get_name: Option *mut c_char>, 11 | } 12 | 13 | extern "C" { 14 | pub fn ex_nameable_get_type() -> glib::ffi::GType; 15 | 16 | pub fn ex_nameable_get_name(nameable: *mut ExNameable) -> *mut c_char; 17 | } 18 | -------------------------------------------------------------------------------- /src/nameable/imp.rs: -------------------------------------------------------------------------------- 1 | use glib::subclass::prelude::*; 2 | use glib::translate::*; 3 | 4 | use std::ffi::c_char; 5 | 6 | // Type implementing ObjectInterface. Use a uninhabited enum to make the type uninstantiatable. 7 | pub enum Nameable {} 8 | 9 | // Default implementation of the interface methods (note: these are optional) 10 | impl Nameable { 11 | fn name_default() -> Option { 12 | None 13 | } 14 | } 15 | 16 | #[glib::object_interface] 17 | impl ObjectInterface for Nameable { 18 | const NAME: &'static str = "ExNameable"; 19 | type Instance = ffi::ExNameable; 20 | type Interface = ffi::ExNameableInterface; 21 | type Prerequisites = (glib::Object,); 22 | 23 | // Interface struct initialization, called from GObject 24 | fn interface_init(iface: &mut Self::Interface) { 25 | // Optionally set the default implementation 26 | iface.get_name = Some(get_name_default_trampoline); 27 | 28 | // TODO: Could also add signals here, and interface properties via 29 | // g_object_interface_install_property() 30 | } 31 | } 32 | 33 | // trampoline to safe implementation 34 | unsafe extern "C" fn get_name_default_trampoline(_this: *mut ffi::ExNameable) -> *mut c_char { 35 | Nameable::name_default().to_glib_full() 36 | } 37 | 38 | pub(crate) mod ffi { 39 | use super::*; 40 | use glib::object::ObjectExt; 41 | use std::ffi::c_char; 42 | use std::ptr; 43 | 44 | // Instance struct, to be used as pointer to "self" in ffi methods 45 | #[repr(C)] 46 | pub struct ExNameable(std::ffi::c_void); 47 | 48 | // Interface struct aka "vtable" 49 | // 50 | // Here we would store virtual methods and similar 51 | #[derive(Clone, Copy)] 52 | #[repr(C)] 53 | pub struct ExNameableInterface { 54 | pub parent_iface: glib::gobject_ffi::GTypeInterface, 55 | pub get_name: Option *mut c_char>, 56 | } 57 | 58 | unsafe impl InterfaceStruct for ExNameableInterface { 59 | type Type = super::Nameable; 60 | } 61 | 62 | #[no_mangle] 63 | pub extern "C" fn ex_nameable_get_type() -> glib::ffi::GType { 64 | ::type_().into_glib() 65 | } 66 | 67 | // Virtual method callers 68 | /// # Safety 69 | /// 70 | /// Must be a Nameable interface. 71 | #[no_mangle] 72 | pub unsafe extern "C" fn ex_nameable_get_name(this: *mut ExNameable) -> *mut c_char { 73 | let wrapper = from_glib_borrow::<_, super::super::Nameable>(this); 74 | let iface = wrapper.interface::().unwrap(); 75 | iface 76 | .as_ref() 77 | .get_name 78 | .map(|f| f(this)) 79 | .unwrap_or(ptr::null_mut()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/nameable/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::{prelude::*, subclass::prelude::*, translate::*}; 10 | 11 | #[cfg(feature = "bindings")] 12 | glib::wrapper! { 13 | pub struct Nameable(Interface); 14 | 15 | match fn { 16 | type_ => || ffi::ex_nameable_get_type(), 17 | } 18 | } 19 | 20 | #[cfg(not(feature = "bindings"))] 21 | glib::wrapper! { 22 | pub struct Nameable(ObjectInterface); 23 | } 24 | 25 | pub trait NameableExt: IsA { 26 | fn name(&self) -> Option { 27 | let iface = self.interface::().unwrap(); 28 | unsafe { 29 | from_glib_full(((iface.as_ref()).get_name.unwrap())( 30 | self.upcast_ref::().to_glib_none().0, 31 | )) 32 | } 33 | } 34 | } 35 | 36 | impl> NameableExt for O {} 37 | 38 | pub trait NameableImpl: ObjectImpl { 39 | fn name(&self) -> Option { 40 | self.parent_name() 41 | } 42 | } 43 | 44 | pub trait NameableImplExt: NameableImpl { 45 | fn parent_name(&self) -> Option { 46 | let data = Self::type_data(); 47 | let parent_iface = unsafe { 48 | &*(data.as_ref().parent_interface::() as *const ffi::ExNameableInterface) 49 | }; 50 | 51 | unsafe { 52 | from_glib_full((parent_iface.get_name.unwrap())( 53 | self.obj().unsafe_cast_ref::().to_glib_none().0, 54 | )) 55 | } 56 | } 57 | } 58 | 59 | impl NameableImplExt for T {} 60 | 61 | unsafe impl IsImplementable for Nameable { 62 | fn interface_init(iface: &mut glib::Interface) { 63 | let iface = iface.as_mut(); 64 | iface.get_name = Some(get_name_trampoline::); 65 | } 66 | fn instance_init(_instance: &mut glib::subclass::InitializingObject) {} 67 | } 68 | 69 | unsafe extern "C" fn get_name_trampoline( 70 | nameable: *mut ffi::ExNameable, 71 | ) -> *mut std::ffi::c_char 72 | where 73 | T: NameableImpl, 74 | { 75 | let instance = &*(nameable as *mut T::Instance); 76 | let imp = instance.imp(); 77 | 78 | imp.name().to_glib_full() 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | use crate::foo::Foo; 85 | use crate::nameable::{Nameable, NameableExt}; 86 | 87 | #[test] 88 | fn test_name() { 89 | let foo = Foo::new(Some("foo's name")); 90 | 91 | // We cast here because otherwise we would just use the get_name() of foo itself 92 | let nameable = foo.upcast::(); 93 | 94 | assert_eq!(nameable.name(), Some("foo's name".into())); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/rstring/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | // Opaque struct 4 | #[repr(C)] 5 | pub struct ExRString(c_void); 6 | 7 | extern "C" { 8 | pub fn ex_rstring_get_type() -> glib::ffi::GType; 9 | 10 | pub fn ex_rstring_new(s: *const c_char) -> *mut ExRString; 11 | pub fn ex_rstring_copy(rstring: *const ExRString) -> *mut ExRString; 12 | pub fn ex_rstring_free(rstring: *mut ExRString); 13 | 14 | pub fn ex_rstring_get(rstring: *const ExRString) -> *mut c_char; 15 | pub fn ex_rstring_set(rstring: *mut ExRString, s: *const c_char); 16 | } 17 | -------------------------------------------------------------------------------- /src/rstring/imp.rs: -------------------------------------------------------------------------------- 1 | // No #[repr(C)] here as we export it as an opaque struct 2 | // If it was not opaque, it must be #[repr(C)] 3 | #[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)] 4 | #[boxed_type(name = "ExRString")] 5 | pub struct RString(Option); 6 | 7 | impl RString { 8 | fn new(s: Option) -> RString { 9 | RString(s) 10 | } 11 | 12 | fn get(&self) -> Option { 13 | self.0.clone() 14 | } 15 | 16 | fn set(&mut self, s: Option) { 17 | self.0 = s; 18 | } 19 | } 20 | 21 | pub(crate) mod ffi { 22 | use glib::{prelude::*, translate::*}; 23 | use std::ffi::c_char; 24 | 25 | pub type ExRString = super::RString; 26 | 27 | /// # Safety 28 | /// 29 | /// Must be a valid C string, 0-terminated. 30 | #[no_mangle] 31 | pub unsafe extern "C" fn ex_rstring_new(s: *const c_char) -> *mut ExRString { 32 | let s = Box::new(super::RString::new(from_glib_none(s))); 33 | Box::into_raw(s) 34 | } 35 | 36 | /// # Safety 37 | /// 38 | /// Must be a valid RString pointer. 39 | #[no_mangle] 40 | pub unsafe extern "C" fn ex_rstring_copy(rstring: *const ExRString) -> *mut ExRString { 41 | let rstring = &*rstring; 42 | let s = Box::new(rstring.clone()); 43 | Box::into_raw(s) 44 | } 45 | 46 | /// # Safety 47 | /// 48 | /// Must be a valid RString pointer. 49 | #[no_mangle] 50 | pub unsafe extern "C" fn ex_rstring_free(rstring: *mut ExRString) { 51 | let _ = Box::from_raw(rstring); 52 | } 53 | 54 | /// # Safety 55 | /// 56 | /// Must be a valid RString pointer. 57 | #[no_mangle] 58 | pub unsafe extern "C" fn ex_rstring_get(rstring: *const ExRString) -> *mut c_char { 59 | let rstring = &*rstring; 60 | rstring.get().to_glib_full() 61 | } 62 | 63 | /// # Safety 64 | /// 65 | /// Must be a valid RString pointer, and a valid C string, 0-terminated. 66 | #[no_mangle] 67 | pub unsafe extern "C" fn ex_rstring_set(rstring: *mut ExRString, s: *const c_char) { 68 | let rstring = &mut *rstring; 69 | rstring.set(from_glib_none(s)); 70 | } 71 | 72 | // GObject glue 73 | #[no_mangle] 74 | pub extern "C" fn ex_rstring_get_type() -> glib::ffi::GType { 75 | ::static_type().into_glib() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/rstring/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::translate::*; 10 | 11 | glib::wrapper! { 12 | pub struct RString(Boxed); 13 | 14 | match fn { 15 | copy => |ptr| ffi::ex_rstring_copy(ptr), 16 | free => |ptr| ffi::ex_rstring_free(ptr), 17 | type_ => || ffi::ex_rstring_get_type(), 18 | } 19 | } 20 | 21 | impl RString { 22 | pub fn new(s: Option<&str>) -> RString { 23 | unsafe { from_glib_full(ffi::ex_rstring_new(s.to_glib_none().0)) } 24 | } 25 | 26 | pub fn get(&self) -> Option { 27 | unsafe { from_glib_full(ffi::ex_rstring_get(self.to_glib_none().0)) } 28 | } 29 | 30 | pub fn set(&mut self, s: Option<&str>) { 31 | unsafe { ffi::ex_rstring_set(self.to_glib_none_mut().0, s.to_glib_none().0) } 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn test_new() { 41 | let s = RString::new(Some("bla")); 42 | assert_eq!(s.get(), Some("bla".into())); 43 | 44 | let mut s2 = s.clone(); 45 | s2.set(Some("blabla")); 46 | assert_eq!(s.get(), Some("bla".into())); 47 | assert_eq!(s2.get(), Some("blabla".into())); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/shared_rstring/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | // Opaque struct 4 | #[repr(C)] 5 | pub struct ExSharedRString(c_void); 6 | 7 | extern "C" { 8 | pub fn ex_shared_rstring_get_type() -> glib::ffi::GType; 9 | 10 | pub fn ex_shared_rstring_new(s: *const c_char) -> *mut ExSharedRString; 11 | pub fn ex_shared_rstring_ref(shared_rstring: *const ExSharedRString) -> *mut ExSharedRString; 12 | pub fn ex_shared_rstring_unref(shared_rstring: *mut ExSharedRString); 13 | 14 | pub fn ex_shared_rstring_get(shared_rstring: *const ExSharedRString) -> *mut c_char; 15 | } 16 | -------------------------------------------------------------------------------- /src/shared_rstring/imp.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | // No #[repr(C)] here as we export it as an opaque struct 4 | // If it was not opaque, it must be #[repr(C)] 5 | #[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)] 6 | #[boxed_type(name = "ExSharedRString")] 7 | pub struct SharedRString(Arc>); 8 | 9 | impl SharedRString { 10 | fn new(s: Option) -> SharedRString { 11 | SharedRString(Arc::new(s)) 12 | } 13 | 14 | // FIXME: This could borrow the &str in theory! 15 | fn get(&self) -> Option { 16 | (*self.0).clone() 17 | } 18 | } 19 | 20 | pub(crate) mod ffi { 21 | use glib::{prelude::*, translate::*}; 22 | use std::ffi::c_char; 23 | 24 | pub type ExSharedRString = super::SharedRString; 25 | 26 | /// # Safety 27 | /// 28 | /// Must be a valid C string, 0-terminated. 29 | #[no_mangle] 30 | pub unsafe extern "C" fn ex_shared_rstring_new(s: *const c_char) -> *mut ExSharedRString { 31 | let s = Box::new(super::SharedRString::new(from_glib_none(s))); 32 | Box::into_raw(s) as *mut _ 33 | } 34 | 35 | /// # Safety 36 | /// 37 | /// Must be a valid SharedRString pointer. 38 | #[no_mangle] 39 | pub unsafe extern "C" fn ex_shared_rstring_ref( 40 | shared_rstring: *const ExSharedRString, 41 | ) -> *mut ExSharedRString { 42 | let shared_rstring = &*shared_rstring; 43 | let s = Box::new(shared_rstring.clone()); 44 | 45 | Box::into_raw(s) as *mut _ 46 | } 47 | 48 | /// # Safety 49 | /// 50 | /// Must be a valid SharedRString pointer. 51 | #[no_mangle] 52 | pub unsafe extern "C" fn ex_shared_rstring_unref(shared_rstring: *mut ExSharedRString) { 53 | let _ = Box::from_raw(shared_rstring); 54 | } 55 | 56 | /// # Safety 57 | /// 58 | /// Must be a valid SharedRString pointer. 59 | #[no_mangle] 60 | pub unsafe extern "C" fn ex_shared_rstring_get( 61 | shared_rstring: *const ExSharedRString, 62 | ) -> *mut c_char { 63 | let shared_rstring = &*shared_rstring; 64 | // FIXME: This could borrow the &str in theory! 65 | shared_rstring.get().to_glib_full() 66 | } 67 | 68 | // GObject glue 69 | #[no_mangle] 70 | pub extern "C" fn ex_shared_rstring_get_type() -> glib::ffi::GType { 71 | ::static_type().into_glib() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/shared_rstring/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "bindings"))] 2 | pub mod imp; 3 | #[cfg(not(feature = "bindings"))] 4 | use imp::ffi; 5 | 6 | #[cfg(feature = "bindings")] 7 | mod ffi; 8 | 9 | use glib::translate::*; 10 | 11 | // We use a Boxed with copy/free since imp::ref() returns a new Box* to hold an 12 | // Arc clone and handle refcounting. 13 | // 14 | // TODO: turn into a Shared and do the refcounting ourself. 15 | glib::wrapper! { 16 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub struct SharedRString(Boxed); 18 | 19 | match fn { 20 | copy => |ptr| ffi::ex_shared_rstring_ref(ptr), 21 | free => |ptr| ffi::ex_shared_rstring_unref(ptr), 22 | type_ => || ffi::ex_shared_rstring_get_type(), 23 | } 24 | } 25 | 26 | impl SharedRString { 27 | pub fn new(s: Option<&str>) -> SharedRString { 28 | unsafe { from_glib_full(ffi::ex_shared_rstring_new(s.to_glib_none().0)) } 29 | } 30 | 31 | // FIXME: This could borrow the &str in theory! 32 | pub fn get(&self) -> Option { 33 | unsafe { from_glib_full(ffi::ex_shared_rstring_get(self.to_glib_none().0)) } 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_new() { 43 | let s = SharedRString::new(Some("bla")); 44 | assert_eq!(s.get(), Some("bla".into())); 45 | 46 | let s2 = s.clone(); 47 | assert_eq!(s.get(), Some("bla".into())); 48 | assert_eq!(s2.get(), Some("bla".into())); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void 5 | on_incremented (ExFoo *foo, gint val, gint inc, void *user_data) 6 | { 7 | g_print ("incremented to %d by %d\n", val, inc); 8 | } 9 | 10 | int 11 | main (int argc, const char *argv[]) 12 | { 13 | char *str; 14 | ExFoo *foo; 15 | ExBar *bar; 16 | ExRString *s, *s2; 17 | ExSharedRString *ss, *ss2; 18 | gdouble number; 19 | 20 | foo = ex_foo_new ("foo's name"); 21 | g_signal_connect (foo, "incremented", G_CALLBACK (on_incremented), NULL); 22 | 23 | str = ex_foo_get_name (foo); 24 | g_print ("foo name: %s\n", str); 25 | g_free (str); 26 | g_print ("foo inc 1: %d\n", ex_foo_increment (foo, 1)); 27 | g_print ("foo inc 10: %d\n", ex_foo_increment (foo, 10)); 28 | g_print ("foo counter: %d\n", ex_foo_get_counter (foo)); 29 | g_object_unref (foo); 30 | 31 | bar = ex_bar_new ("bar's name"); 32 | g_signal_connect (bar, "incremented", G_CALLBACK (on_incremented), NULL); 33 | 34 | str = ex_foo_get_name (EX_FOO (bar)); 35 | g_print ("bar name: %s\n", str); 36 | g_free (str); 37 | g_print ("bar inc 1: %d\n", ex_foo_increment (EX_FOO (bar), 1)); 38 | g_print ("bar inc 10: %d\n", ex_foo_increment (EX_FOO (bar), 10)); 39 | g_print ("bar counter: %d\n", ex_foo_get_counter (EX_FOO (bar))); 40 | 41 | g_print ("bar number: %f\n", ex_bar_get_number (bar)); 42 | g_object_get (bar, "number", &number, NULL); 43 | g_print ("bar number (property): %f\n", number); 44 | ex_bar_set_number (bar, 10.0); 45 | g_print ("bar number: %f\n", ex_bar_get_number (bar)); 46 | g_object_get (bar, "number", &number, NULL); 47 | g_print ("bar number (property): %f\n", number); 48 | number = 20.0; 49 | g_object_set (bar, "number", number, NULL); 50 | g_print ("bar number: %f\n", ex_bar_get_number (bar)); 51 | g_object_get (bar, "number", &number, NULL); 52 | g_print ("bar number (property): %f\n", number); 53 | g_object_unref (bar); 54 | 55 | s = ex_rstring_new ("something"); 56 | 57 | str = ex_rstring_get (s); 58 | g_print ("rstring: %s\n", str); 59 | g_free (str); 60 | s2 = ex_rstring_copy (s); 61 | ex_rstring_set (s2, "something else"); 62 | str = ex_rstring_get (s2); 63 | g_print ("rstring 2: %s\n", str); 64 | g_free (str); 65 | ex_rstring_free (s2); 66 | ex_rstring_free (s); 67 | 68 | ss = ex_shared_rstring_new ("something"); 69 | str = ex_shared_rstring_get (ss); 70 | g_print ("shared rstring: %s\n", str); 71 | g_free (str); 72 | ss2 = ex_shared_rstring_ref (ss); 73 | str = ex_shared_rstring_get (ss2); 74 | g_print ("shared rstring 2: %s\n", str); 75 | g_free (str); 76 | ex_shared_rstring_unref (ss2); 77 | ex_shared_rstring_unref (ss); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gjs 2 | 3 | const Lang = imports.lang; 4 | const Ex = imports.gi.Ex; 5 | 6 | let foo = new Ex.Foo({name: "foo's name"}); 7 | foo.connect("incremented", function(obj, val, inc) { 8 | print("incremented to " + val + " by " + inc); 9 | }); 10 | 11 | print("foo name: " + foo.get_name()); 12 | print("foo inc 1: " + foo.increment(1)); 13 | print("foo inc 10: " + foo.increment(10)); 14 | print("foo counter: " + foo.get_counter()); 15 | 16 | let bar = new Ex.Bar({name: "bar's name"}); 17 | bar.connect("incremented", function(obj, val, inc) { 18 | print("incremented to " + val + " by " + inc); 19 | }); 20 | 21 | print("bar name: " + bar.get_name()); 22 | print("bar inc 1: " + bar.increment(1)); 23 | print("bar inc 10: " + bar.increment(10)); 24 | print("bar counter: " + bar.get_counter()); 25 | 26 | print("bar number: " + bar.get_number()); 27 | print("bar number (property): " + bar["number"]); 28 | bar.set_number(10.0) 29 | print("bar number: " + bar.get_number()); 30 | print("bar number (property): " + bar["number"]); 31 | bar["number"] = 20.0; 32 | print("bar number: " + bar.get_number()); 33 | print("bar number (property): " + bar["number"]); 34 | 35 | let s = new Ex.RString("something"); 36 | print("rstring: " + s.get()); 37 | let s2 = s.copy(); 38 | s2.set("something else"); 39 | print("rstring2: " + s2.get()); 40 | 41 | let t = new Ex.SharedRString("something"); 42 | print("shared rstring: " + t.get()); 43 | let t2 = t.ref(); 44 | print("shared rstring2: " + t2.get()); 45 | 46 | 47 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | import gi 4 | gi.require_version("Ex", "0.1") 5 | from gi.repository import Ex 6 | 7 | def on_incremented(obj, val, inc): 8 | print("incremented to {} by {}".format(val, inc)) 9 | 10 | foo = Ex.Foo.new("foo's name") 11 | foo.connect("incremented", on_incremented) 12 | 13 | print("foo name: " + str(foo.get_name())) 14 | print("foo inc 1: " + str(foo.increment(1))) 15 | print("foo inc 10: " + str(foo.increment(10))) 16 | print("foo counter: " + str(foo.get_counter())) 17 | 18 | bar = Ex.Bar.new("bar's name") 19 | bar.connect("incremented", on_incremented) 20 | 21 | print("bar name: " + str(bar.get_name())) 22 | print("bar inc 1: " + str(bar.increment(1))) 23 | print("bar inc 10: " + str(bar.increment(10))) 24 | print("bar counter: " + str(bar.get_counter())) 25 | 26 | print("bar number: " + str(bar.get_number())) 27 | print("bar number (property): " + str(bar.get_property("number"))) 28 | bar.set_number(10.0) 29 | print("bar number: " + str(bar.get_number())) 30 | print("bar number (property): " + str(bar.get_property("number"))) 31 | bar.set_property("number", 20.0) 32 | print("bar number: " + str(bar.get_number())) 33 | print("bar number (property): " + str(bar.get_property("number"))) 34 | 35 | s = Ex.RString.new("something") 36 | print("rstring: " + str(s.get())) 37 | s2 = s.copy() 38 | s2.set("something else") 39 | print("rstring 2: " + str(s2.get())) 40 | 41 | s = Ex.SharedRString.new("something") 42 | print("shared rstring: " + str(s.get())) 43 | s2 = s.ref() 44 | print("shared rstring 2: " + str(s2.get())) 45 | -------------------------------------------------------------------------------- /test.vala: -------------------------------------------------------------------------------- 1 | using Ex; 2 | 3 | void on_incremented (int val, int inc) { 4 | stdout.printf ("incremented to %d by %d\n", val, inc); 5 | } 6 | 7 | void throw_err() throws Ex.Error { 8 | throw new Ex.Error.FAILED ("something went wrong"); 9 | } 10 | 11 | public int main (string[] args) { 12 | var foo = new Ex.Foo ("foo's name"); 13 | foo.incremented.connect (on_incremented); 14 | 15 | stdout.printf ("foo name: %s\n", foo.get_name ()); 16 | stdout.printf ("foo inc 1: %d\n", foo.increment (1)); 17 | stdout.printf ("foo inc 10: %d\n", foo.increment (10)); 18 | stdout.printf ("foo counter: %d\n", foo.get_counter ()); 19 | 20 | var bar = new Ex.Bar ("bar's name"); 21 | bar.incremented.connect (on_incremented); 22 | 23 | stdout.printf ("bar name: %s\n", bar.get_name ()); 24 | stdout.printf ("bar inc 1: %d\n", bar.increment (1)); 25 | stdout.printf ("bar inc 10: %d\n", bar.increment (10)); 26 | stdout.printf ("bar counter: %d\n", bar.get_counter ()); 27 | 28 | stdout.printf ("bar number: %f\n", bar.get_number ()); 29 | stdout.printf ("bar number (property): %f\n", bar.number); 30 | bar.set_number (10.0); 31 | stdout.printf ("bar number: %f\n", bar.get_number ()); 32 | stdout.printf ("bar number (property): %f\n", bar.number); 33 | bar.number = 20.0; 34 | stdout.printf ("bar number: %f\n", bar.get_number ()); 35 | stdout.printf ("bar number (property): %f\n", bar.number); 36 | 37 | var s = new Ex.RString ("something"); 38 | stdout.printf ("rstring: %s\n", s.get ()); 39 | var s2 = s.copy (); 40 | s2.set ("something else"); 41 | stdout.printf ("rstring 2: %s\n", s2.get ()); 42 | 43 | var ss = new Ex.SharedRString ("something"); 44 | stdout.printf ("shared rstring: %s\n", ss.get ()); 45 | var ss2 = ss.ref (); 46 | stdout.printf ("shared rstring 2: %s\n", ss2.get ()); 47 | 48 | try { 49 | throw_err(); 50 | assert_not_reached(); 51 | } catch (Ex.Error e) { 52 | assert_error(e, Ex.Error.quark(), Ex.Error.FAILED); 53 | } 54 | 55 | var loop = new MainLoop(); 56 | var cancellable = new GLib.Cancellable(); 57 | foo.check_async.begin(cancellable, (obj, res) => { 58 | try { 59 | foo.check_async.end(res); 60 | } catch (GLib.Error e) { 61 | assert_not_reached(); 62 | } 63 | loop.quit(); 64 | }); 65 | loop.run(); 66 | return 0; 67 | } 68 | --------------------------------------------------------------------------------