diff --git a/.ameba.yml b/.ameba.yml index 55493f3..79f0aea 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -1,8 +1,9 @@ Excluded: - src/auto +Lint/NotNil: + Enabled: false Metrics/CyclomaticComplexity: Enabled: false - Performance/AnyInsteadOfEmpty: Enabled: false diff --git a/BINDING_YML.md b/BINDING_YML.md index fbe3d86..0750324 100644 --- a/BINDING_YML.md +++ b/BINDING_YML.md @@ -10,30 +10,86 @@ Examples on how to define a binding can be found at [GLib](src/bindings/g_lib/bi While gi-crystal remains in 0.x version this spec can change from one version to another. -## namespace +## namespace (string) GObject namespace this file describes. -## version +## version (string) Version of the GObject namespace this file describes. -## include +## include_before (list of strings) + +List of extra files that must be included in the binding, the generator will add an `require` call for each of then at the end of module definition *before* the inclusion of the wrappers. + +## require_after (list of strings) List of extra files that must be included in the binding, the generator will add an `require` call for each of then at the end of module definition. -## include_before +## lib_ignore (list of strings) -List of extra files that must be included in the binding, the generator will add an `require` call for each of then at the end of module definition *before* the inclusion of the wrappers. +List of C functions that the generator will complete ignore, this means that it does not even generate +the C signature in the `lib` declaration. + +This entry was created as a workaround for [issues](https://github.com/hugopl/gi-crystal/issues/53) generating bindings for +the HarfBuzz library, if you just want to not generate a binding for a specific function see _ignore_methods_ entry. + +## execute_callback (list of strings) + +List of C functions that may execute a callback, so the generator adds a `@[Raises]` annotation to it. + +## ignore_constants (list of strings) + +List of C `#define`s that the generator will ignore. + +## types (list of BindingTypes) + +List of types that require extra configuration, like be removed from generation, have methods removed, etc. Note that the +module name is considered a type, e.g. if you want to remove the C function `foo_bar` from module `Foo` bindings, you should +write: + +```YAML +types: + Foo: + ignore_methods: + - bar +``` + +# BindingTypes + +## ignore (boolean) + +If true, the type is ignored and no binding is generated for it, if the type is used in some method a warn is raised and the +method isn't generated. + +## handmade (boolean) + +If true, the type is handmade, the generator doesn't generate any code for it, methods using this type in the signature get +no type restrictions. This was create as an attempt to have flexible GValue and GVariant bindings. + +## ignore_methods (list of strings) + +List of methods the generator will not generate for this type, **you must use the function binding name, not the C symbol**, +so `g_object_ref` is just `ref` and if the GIR has a rename annotation, the renamed name must be used. + +Even if the function is ignored here, their C signature still declared in the lib declaration, so you can write custom code that calls it. + +## ignore fields (list of strings) + +This is valid only for structs. -## handmade +A list of fields in the structs that you don't want to create access for. -List of types that are going to be handmade. The generator does not generate implementation for handmade types since they are - handmade by you 😉️. +## binding_strategy (auto | stack_struct | heap_struct | heap_wrapper) -## ignore +This is valid only for structs. -List of types and/or functions the generator will ignore. For types it does not generate any implementation and emit warnings -when some other function uses them. For functions the function/method is just not generated in bindings. +Plain structs are hard to bind properly, since the GIR information sometimes doesn't give a clear information about how the +structs must be used, so this flag allows the binding author to fine tune how the struct binding must be done, the possible +values are: -Functions must use the full C function name without arguments, Types must use the type name without the namespace prefix, i.e. `GPrivate` is just `Private`. +- `auto`: Let the generator choose what's better. +- `stack_struct`: Bind this as a Crystal struct, that is always allocated on stack. +- `heap_struct`: Bind this as a Crystal class with the C struct as attribute, so the struct is allocated on the heap and the + memory is always copied to Crystal. +- `heap_wrapper`: Bind this as a Crystal class with a pointer to the C struct, like it's done got GObject types. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3880b6c..4dfe86a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Changes that change the generated API have a ⚠️. +## [0.16.0] 2023-06-16 +### Added +- Add test helper methods: `ClosureDataManager.count`, `ClosureDataManager.info` and `ClosureDataManager.deregister_all` (#92). +- Added bindings for `GLib.real_name` (#93). +- Added option to ignore constants in binding generation, thanks @charitybell (#95) + +### Fixed +- Fix compilation with `-Ddebugmemory` for some struct bindings (#91). +- Convert boolean return values in virtual functions (#96). + +### Changed +- Removed a lot of constants from GLib and GObject bindings (#97). + +## [0.15.3] 2023-05-31 +### Fixed +- Let struct bindings allocated on help to obey `-Ddebugmemory` flag. +- Translate GTK doc parameter markup to crystal doc style. + +### Changed +- Removed version_from_shard dependency. + +## [0.15.2] 2023-04-16 +### Added +- Added declaration of `LibGLib.g_bytes_new_static`, used internally by other modules. + +## [0.15.1] 2023-03-10 +### Added +- Show Crystal version on generator logs. +- Support `Path` objects in signals, they are exposed as C strings to C code. + +### Fixed +- Fix `GLib::SList` of modules (GObject interfaces) (#68). +- Fix `GLib::SList` of `GObject::Object`. +- Fix `GLib::List` of modules. +- Fix `GLib::List` of `GObject::Object`. +- Fix signals with modules (GObject interfaces) parameters. +- Do not use relative paths on `GiCrystal.require` macro (#70). +- Fix binding generation for modules with no errors that doesn't depend on GLib (#79). + +## [0.15.0] 2023-01-15 +### Fixed +- Fix callback generation of callbacks without user_data paramenter as last parameter (#63). +- Fix signal boolean parameters (#66). +- Allow chaining up unsafe vfuncs, thanks @BlobCodes. + +## [0.14.0] 2022-09-02 +### Added +- Added support to declare GObject properties in Crystal classes 🎉️, thanks @BlobCodes (#44). +- Added possibility to ignore some struct fields in binding generation (#58). +- Crystalize even more docs, thanks @GeopJr (#62). + +### Fixed +- Enum and flags `#g_type` method now works (#56). +- Convert return values of vfuncs (#60). + +### Changed +- Changed format of `binding.yml` file for better flexibility. +- Generate bindings for POD structs as Crystal structs (#58). +- Doc comments generation is now disabled by default (#59). + +## [0.13.1] 2022-07-04 +### Added +- Added option to complete ignore functions (i.e. ignore new added HarfBuzz functions that broke the generator). + +## Fixed +- Fixed naming of virtual functions, now they can have any name. +- Correct generate code for structs with static array of structs, thanks @BlobCodes. + +## [0.13.0] 2022-06-30 +### Added +- Property constructors can be called in constructor super calls from user classes, e.g. `super(property: value)`. +- Show if return value is nullable in generated code, helping debug. +- Implement virtual functions and allow including interfaces, thanks @BlobCodes (#26). +- Implement unsafe virtual functions, thanks @BlobCodes (#41). +- Implement user-transparent GObject enums, thanks @BlobCodes + +### Changed +- Use `GC.malloc_atomic` to allocate wrappers to reduce GC work, thanks @BlobCodes (#18). +- Move struct wrappers data inside struct, reducing 1 malloc call, thanks @BlobCodes (#19). +- Translate GError in return values or signal parameters to exception objects. (#25) +- Raise a compile error when using `GObject::ParamSpec.g_type`. (#24) +- Print info about ClosureDataManager when compiling using -Ddebugmemory. +- `GObject::GeneratedWrapper` annotation is now `GICrystal::GeneratedWrapper`. + +### Fixed +- Don't use invalid characters when registering GObject types, thanks @BlobCodes (#30) +- Allow binding objects with functions named initialize/finalize, thanks @BlobCodes (#42) + ## [0.12.0] 2022-06-04 ### Added - Signals can now be disconnected, `connect` method returns a `GObject::SignalConnection` object. @@ -46,7 +134,7 @@ Changes that change the generated API have a ⚠️. - Methods with nilable handmade types (GValue/GVariant) in parameters now works. - Strings in structs now works. - Signals with return types now works. -- Methods named `initialize` are now correctly binded to `_initialize` (thanks @GeopJr). +- Methods named `initialize` are now correctly binded to `_initialize`, thanks @GeopJr. - Fixed parameters of fixed size arrays. - Fix `make doc`. @@ -84,8 +172,8 @@ Changes that change the generated API have a ⚠️. ## [0.7.0] 2022-04-03 ### Added -- Better code blocks in documentation (thanks @GeopJr). -- It's possible to add `@[Raises]` annotation on functiosn that can executa a callback (thanks @BlobCodes). +- Better code blocks in documentation, thanks @GeopJr. +- It's possible to add `@[Raises]` annotation on functiosn that can executa a callback, thanks @BlobCodes. ### Fixed - Structs with non pointers struct attributes works as expected. diff --git a/HACKING.md b/HACKING.md index 6224fbb..65e98f6 100644 --- a/HACKING.md +++ b/HACKING.md @@ -89,9 +89,9 @@ object pointer into every wrapper it creates, so it can restore the wraper objec When the wrapper is collected by the GC it reset this pointer to NULL, so if it appear again in the Crystal world we create another wrapper for it. See `Crystal::INSTANCE_QDATA_KEY` constant. -For user made objects inheriting GObject we need another flag, since if the GC collect it all the Crystal data is removed as -well, so we raise an exception when the user try to restore the Crystal object doing a cast, see -`Crystal::GC_COLLECTED_QDATA_KEY`. +For user made objects inheriting GObject a [toggle ref](https://docs.gtk.org/gobject/method.Object.add_toggle_ref.html) is +added to the GObject, so the object it's garbage collected only if there's no references for it in C world. This is done at +`src/bindings/g_object/object.cr`, look for `_add_toggle_ref`. ## How Struct wrappers works diff --git a/Makefile b/Makefile index 889c9ad..d4c4011 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ generator: shards build --error-trace test-binding: libtest generator - GI_TYPELIB_PATH="./spec/build" LIBRARY_PATH="./spec/build" LD_LIBRARY_PATH="./spec/build" ./bin/gi-crystal spec/libtest_binding.yml -o src/auto + GI_TYPELIB_PATH="./spec/build" LIBRARY_PATH="./spec/build" LD_LIBRARY_PATH="./spec/build" ./bin/gi-crystal spec/libtest_binding.yml -o src/auto --doc libtest: +make --quiet -C ./spec/libtest diff --git a/README.md b/README.md index 9ca4b4a..32f7719 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ where `GLib` and `Gtk` are modules. GObject interfaces are mapped to Crystal modules + a dummy class that only implements this module, used when there's some function returning the interface. -Currently isn't possible to include a GObject interface on your objects, see [issue 10](https://github.com/hugopl/gi-crystal/issues/10). - ### Down Casts If the object was created by Crystal code you can cast it like you do with any Crystal object instance, using `.as?` and `.as`. @@ -125,7 +123,17 @@ implementation that will probably change in the future to just ignore the return ### After signals -Instead of `widget.focus_signal.connect`, use `widget.focus_signal.connect_after`. +Use the after keyword argument: + +```Crystal +# Connect to a slot without the sender +widget.focus_signal.connect(->slot_without_sender(Gtk::Direction), after: true) + +# Connect to a block (always without sender parameter) +widget.focus_signal.connect(after: true) do |direction| + # ... +end +``` ### Signals with details @@ -184,11 +192,45 @@ Crystal as a `const char*` pointer. This may change in the future. ## Declaring GObject properties -- TBD +GObject Properties are declared using the `GObject::Property` annotation on the instance variable. ### Virtual Methods -- TBD +Virtual methods must have the `GObject::Virtual` annotation, currently only virtual methods from interfaces are supported. + +```Crystal +class Widget0 < Gtk::Widget + # GObject virtual method name is guessed from Crystal method name, that can start with `do_`. + @[GObject::Virtual] + def do_snapshot(snapshot : Gtk::Snapshot) + end +emd + +class Widget1 < Gtk::Widget + # If the `do_` prefix annoyes you, just use the same GObject virtual method name. + @[GObject::Virtual] + def snapshot(snapshot : Gtk::Snapshot) + end +end + +class Widget2 < Gtk::Widget + # Or you can use whatever name and inform the GObject virtual method name in the annotation. + @[GObject::Virtual(name: "snapshot")] + def heyho(snapshot : Gtk::Snapshot) + end +end +``` + +If for some reason (peformance or GICrystal bugs 🙊️) you don't want wrappers, you can create an unsafe virtual method: + +```Crystal +class Widget3 < Gtk::Widget + @[GObject::Virtual(unsafe: true)] + def snapshot(snapshot : Pointer(Void)) + # User is responsible for memory management here, like in C. + end +end +``` ## GLib GError @@ -217,6 +259,19 @@ So if you want to rescue from this specific error you must `rescue e : GLib::Fil error in this domain you must `rescue e : GLib::FileError`, and finally if you want to rescue from any GLib errors you do `rescue e : GLib::GLibError`. +## Raw C Structs + +At [binding.yml](BINDING_YML.md) file you can define the strategy used to bind the structs, if set to `auto`it will behave +like lsited bellow: + +- If the struct have no pointer attributes it's mapped to a Crystal struct with the same memory layout of the C struct + (`stack_struct` binding strategy). +- If the struct have pointer attributes it's mapped to a Crystal class with the same memory layout of the C struct, so a + `finalize` method can be implemented to free the resources. Not that no setters are generated to pointer attributes, since + we can't guess how this memory must be handled (`heap_struct` binding strategy). +- If the struct is a opaque pointer it's mapped to a Crystal class with a pointer to the C object, it's assumed that the + object is a GObject Box, so the `g_boxed_*` family of functions are used to handle the memory (`heap_wrapper_struct` + binding strategy). ## Contributing diff --git a/ecr/cast_methods.ecr b/ecr/cast_methods.ecr deleted file mode 100644 index 4df9a65..0000000 --- a/ecr/cast_methods.ecr +++ /dev/null @@ -1,9 +0,0 @@ -# Cast a `GObject::Object` to `<%= type_name %>`, throw `TypeCastError` if cast can't be made. -def self.cast(obj : GObject::Object) : self - cast?(obj) || raise TypeCastError.new("can't cast #{typeof(obj).name} to <%= type_name %>") -end - -# Cast a `GObject::Object` to `<%= type_name %>`, returns nil if cast can't be made. -def self.cast?(obj : GObject::Object) : self? - new(obj.to_unsafe, GICrystal::Transfer::None) unless LibGObject.g_type_check_instance_is_a(obj, g_type).zero? -end diff --git a/ecr/gobject_constructor.ecr b/ecr/gobject_constructor.ecr index 5860005..80d8e02 100644 --- a/ecr/gobject_constructor.ecr +++ b/ecr/gobject_constructor.ecr @@ -11,10 +11,8 @@ def initialize(<%= gobject_constructor_parameter_declaration %>) end <% end %> - @pointer = LibGObject.g_object_new_with_properties(self.class.g_type, _n, _names, _values) - <% if object.initially_unowned? -%> - LibGObject.g_object_ref_sink(self) if LibGObject.g_object_is_floating(self) == 1 - <% end %> + ptr = LibGObject.g_object_new_with_properties(self.class.g_type, _n, _names, _values) + super(ptr, :full) _n.times do |i| LibGObject.g_value_unset(_values.to_unsafe + i) diff --git a/ecr/heap_struct.ecr b/ecr/heap_struct.ecr new file mode 100644 index 0000000..fad19be --- /dev/null +++ b/ecr/heap_struct.ecr @@ -0,0 +1,31 @@ +module <%= namespace_name %> + <% render_doc(object) -%> + class <%= type_name %> + @data : <%= to_lib_type(object) %> + + def initialize(@data, transfer : GICrystal::Transfer) + end + + def initialize(pointer : Pointer(Void), transfer : GICrystal::Transfer) + raise ArgumentError.new("Tried to generate struct with a NULL pointer") if pointer.null? + + # Raw structs are always moved to Crystal memory. + @data = pointer.as(Pointer(<%= to_lib_type(object) %>)).value + LibGLib.g_free(pointer) if transfer.full? + end + + <%= struct_new_method %> + + def ==(other : self) : Bool + LibC.memcmp(self, other.to_unsafe, sizeof(<%= to_lib_type(object) %>)).zero? + end + + <% render_getters_and_setters(io) %> + <% render "ecr/g_type_method.ecr" %> + <% render_methods %> + + def to_unsafe + pointerof(@data).as(Void*) + end + end +end diff --git a/ecr/heap_wrapper_struct.ecr b/ecr/heap_wrapper_struct.ecr new file mode 100644 index 0000000..1a91904 --- /dev/null +++ b/ecr/heap_wrapper_struct.ecr @@ -0,0 +1,47 @@ +module <%= namespace_name %> + <% render_doc(object) -%> + class <%= type_name %> + @pointer : Pointer(Void) + + def initialize(pointer : Pointer(Void), transfer : GICrystal::Transfer) + raise ArgumentError.new("Tried to generate struct with a NULL pointer") if pointer.null? + + <% if object.boxed? %> + @pointer = if transfer.none? + LibGObject.g_boxed_copy(<%= to_crystal_type(object, false) %>.g_type, pointer) + else + pointer + end + <% else %> + @pointer = pointer + <% end %> + end + + # :nodoc: + # Code copied from crystal/src/weak_ref.cr + # Allocates this object using malloc_atomic, allowing the GC to run more efficiently. + # As GObjects memory is managed using reference counting, we do not need to scan its pointers. + def self.allocate + ptr = GC.malloc_atomic(instance_sizeof(self)).as(self) + set_crystal_type_id(ptr) + ptr + end + + <% if object.boxed? %> + def finalize + {% if flag?(:debugmemory) %} + LibC.printf("~%s at %p\n", self.class.name, self) + {% end %} + LibGObject.g_boxed_free(<%= type_name %>.g_type, self) + end + <% end %> + + <% render_getters_and_setters(io) %> + <% render "ecr/g_type_method.ecr" %> + <% render_methods %> + + def to_unsafe + @pointer + end + end +end diff --git a/ecr/interface.ecr b/ecr/interface.ecr index bf1e119..e90e818 100644 --- a/ecr/interface.ecr +++ b/ecr/interface.ecr @@ -9,7 +9,7 @@ module <%= namespace_name %> include <%= namespace_name %>::<%= type_name %> end - {% unless @type.annotation(GObject::GeneratedWrapper) %} + {% unless @type.annotation(GICrystal::GeneratedWrapper) %} def self._install_iface_<%= namespace_name %>__<%= type_name %>(interface : Pointer(LibGObject::TypeInterface)) : Nil end {% end %} @@ -28,7 +28,7 @@ module <%= namespace_name %> end # :nodoc: - @[GObject::GeneratedWrapper] + @[GICrystal::GeneratedWrapper] class <%= abstract_interface_name(object, false) %> < GObject::Object include <%= type_name %> @@ -39,6 +39,5 @@ module <%= namespace_name %> end <% render "ecr/g_type_method.ecr" %> - <% render "ecr/cast_methods.ecr" %> end end diff --git a/ecr/module.ecr b/ecr/module.ecr index 9bd2b21..f072f96 100644 --- a/ecr/module.ecr +++ b/ecr/module.ecr @@ -6,7 +6,7 @@ require "../<%= dep.module_dir %>/<%= dep.filename%>" # C lib declaration require "./<%= @lib.filename %>" -<% config.includes_before.each do |inc| -%> +<% config.require_before.each do |inc| -%> require "<%= inc.relative_to("#{output_dir}/#{module_dir}") %>" <% end %> @@ -17,8 +17,9 @@ require "./<%= inc %>" module <%= namespace_name %> <% constants.each do |const| -%> + <% next if config.ignore_constants.includes?(const.name) %> <% render_doc(const) -%> - <%= const.name %> = <%= const.literal %> + <%= const.name %> = <%= const.literal -%> <% end %> # Callbacks @@ -27,13 +28,17 @@ module <%= namespace_name %> alias <%= to_type_name(callback.name) %> = <%= callable_to_crystal_proc(callback) %> <% end %> + <% if declare_error? %> # Base class for all errors in this module. class <%= namespace_name %>Error < GLib::Error end + <% end %> # Enums <% enums.each do |enum_| %> - <%= next if enum_.error_domain %> + <% next if config.type_config(enum_.name).ignore? %> + <% next if enum_.error_domain %> + <% render_doc(enum_) -%> enum <%= to_type_name(enum_.name) %> : <%= to_crystal_type(enum_.storage_type) %> <% enum_.values.each do |value| -%> @@ -46,6 +51,8 @@ module <%= namespace_name %> # Flags <% flags.each do |flag| -%> + <% next if config.type_config(flag.name).ignore? %> + <% render_doc(flag) -%> <%= "@[Flags]\n" unless empty_flag?(flag) -%> enum <%= to_type_name(flag.name) %> : <%= to_crystal_type(flag.storage_type) %> @@ -102,7 +109,6 @@ module <%= namespace_name %> extend self end -# Extra includes -<% config.includes.each do |inc| -%> +<% config.require_after.each do |inc| -%> require "<%= inc.relative_to("#{output_dir}/#{module_dir}") %>" <% end %> diff --git a/ecr/object.ecr b/ecr/object.ecr index f85609a..444510d 100644 --- a/ecr/object.ecr +++ b/ecr/object.ecr @@ -5,18 +5,20 @@ module <%= namespace_name %> <% render_doc(object) -%> - @[GObject::GeneratedWrapper] + @[GICrystal::GeneratedWrapper] class <%= type_name %> <%= parent_class %> <% object.interfaces.each do |interface| -%> include <%= to_crystal_type(interface, namespace != interface.namespace) %> <% end %> + <% if object.parent.nil? %> @pointer : Pointer(Void) + <% end %> <% class_struct = object.class_struct -%> <% if class_struct.nil? || object.fundamental? %> macro inherited - {{ raise "Cannot inherit from #{@type.superclass}" unless @type.annotation(GObject::GeneratedWrapper) }} + {{ raise "Cannot inherit from #{@type.superclass}" unless @type.annotation(GICrystal::GeneratedWrapper) }} end <% elsif class_struct %> # :nodoc: @@ -27,27 +29,20 @@ module <%= namespace_name %> end <% end %> - GICrystal.declare_new_method(<%= type_name %>, <%= object.qdata_get_func %>, <%= object.qdata_set_func %>) + <% render_qdata_optimized_new_method %> + <% if object.parent %> # Initialize a new `<%= type_name %>`. def initialize - @pointer = LibGObject.g_object_newv(self.class.g_type, 0, Pointer(Void).null) - LibGObject.g_object_ref_sink(self) if LibGObject.g_object_is_floating(self) == 1 - LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id)) + super end # :nodoc: - def initialize(@pointer, transfer : GICrystal::Transfer) - <% if object.parent.nil? %> - <% if g_object_type? %> - LibGObject.<%= object.ref_function %>(self) if transfer.none? || LibGObject.g_object_is_floating(self) == 1 - <% else %> - LibGObject.<%= object.ref_function %>(self) if transfer.none? - <% end %> - <% else %> - super - <% end %> + def initialize(pointer, transfer : GICrystal::Transfer) + super end + <% end %> + # :nodoc: # Code copied from crystal/src/weak_ref.cr @@ -68,7 +63,7 @@ module <%= namespace_name %> # (i.e. its memory is freed). def finalize {% if flag?(:debugmemory) %} - LibC.printf("~%s at %p - ref count: %d\n", self.class.name.to_unsafe, self, ref_count) + LibC.printf("~%s at %p\n", self.class.name.to_unsafe, self) {% end %} GICrystal.finalize_instance(self) end @@ -78,7 +73,8 @@ module <%= namespace_name %> @pointer end - <% render "ecr/cast_methods.ecr" %> + private def _after_init : Nil + end <% end %> <% render "ecr/g_type_method.ecr" %> diff --git a/ecr/stack_struct.ecr b/ecr/stack_struct.ecr new file mode 100644 index 0000000..e05a89b --- /dev/null +++ b/ecr/stack_struct.ecr @@ -0,0 +1,32 @@ +module <%= namespace_name %> + <% render_doc(object) -%> + struct <%= type_name %> + @data : <%= to_lib_type(object) %> + + def initialize(@data, _transfer : GICrystal::Transfer) + end + + def initialize(pointer : Pointer(Void), transfer : GICrystal::Transfer) + raise ArgumentError.new("Tried to generate struct with a NULL pointer") if pointer.null? + + # Raw structs are always moved to Crystal memory. + @data = pointer.as(Pointer(<%= to_lib_type(object) %>)).value + LibGLib.g_free(pointer) if transfer.full? + end + + <% render_initialize %> + + def ==(other : self) : Bool + LibC.memcmp(self, other.to_unsafe, sizeof(<%= to_lib_type(object) %>)).zero? + end + + <% render_getters_and_setters(io) %> + <% render "ecr/g_type_method.ecr" %> + <% render_methods %> + + # :nodoc: + def to_unsafe + pointerof(@data).as(Void*) + end + end +end diff --git a/ecr/struct.ecr b/ecr/struct.ecr deleted file mode 100644 index 995810e..0000000 --- a/ecr/struct.ecr +++ /dev/null @@ -1,72 +0,0 @@ -module <%= namespace_name %> - <% render_doc(object) -%> - class <%= type_name %> - <% if object.copyable? %> - @data : <%= to_lib_type(object) %> - <% else %> - @pointer : Pointer(Void) - <% end %> - - def initialize(pointer : Pointer(Void), transfer : GICrystal::Transfer) - raise ArgumentError.new("Tried to generate struct with a NULL pointer") if pointer.null? - - <% if object.boxed? %> - @pointer = if transfer.none? - LibGObject.g_boxed_copy(<%= to_crystal_type(object, false) %>.g_type, pointer) - else - pointer - end - <% elsif object.copyable? %> - # Raw structs are always moved to Crystal memory. - @data = pointer.as(Pointer(<%= to_lib_type(object) %>)).value - LibGLib.g_free(pointer) if transfer.full? - <% else %> - @pointer = pointer - <% end %> - end - - <% if object.copyable? %> - <%= struct_new_method %> - <% end %> - - <% if !object.copyable? %> - # :nodoc: - # Code copied from crystal/src/weak_ref.cr - # Allocates this object using malloc_atomic, allowing the GC to run more efficiently. - # As GObjects memory is managed using reference counting, we do not need to scan its pointers. - def self.allocate - ptr = GC.malloc_atomic(instance_sizeof(self)).as(self) - set_crystal_type_id(ptr) - ptr - end - <% end %> - - <% if object.boxed? %> - def finalize - LibGObject.g_boxed_free(<%= type_name %>.g_type, self) - end - <% end %> - - def ==(other : self) : Bool - LibC.memcmp(self, other.to_unsafe, sizeof(<%= to_lib_type(object) %>)).zero? - end - - <% if object.copyable? %> - <% foreach_field do |field| %> - <% generate_getter(io, field) -%> - <% generate_setter(io, field) -%> - <% end %> - <% end %> - - <% render "ecr/g_type_method.ecr" %> - <% render_methods %> - - def to_unsafe - <% if object.copyable? %> - pointerof(@data).as(Void*) - <% else %> - @pointer - <% end %> - end - end -end diff --git a/ecr/v_func.ecr b/ecr/v_func.ecr index e06109d..7e60f04 100644 --- a/ecr/v_func.ecr +++ b/ecr/v_func.ecr @@ -1,14 +1,18 @@ - private macro _register_do_<%= vfunc.name %> + <%= method_gi_annotations -%> + private macro _register_<%= vfunc.name %>_vfunc(impl_method_name) private def self._vfunc_<%= vfunc.name %>(%this : Pointer(Void), <% generate_lib_args(io, vfunc) %>) : <%= return_type %> <%= vfunc_gi_annotations %> <%- write_implementations(io) -%> <%-= '\n' unless vfunc.args.empty? -%> - %gc_collected = !LibGObject.g_object_get_qdata(%this, GICrystal::GC_COLLECTED_QDATA_KEY).null? %instance = LibGObject.g_object_get_qdata(%this, GICrystal::INSTANCE_QDATA_KEY) - raise GICrystal::ObjectCollectedError.new if %gc_collected || %instance.null? + raise GICrystal::ObjectCollectedError.new if %instance.null? - %instance.as(self).do_<%= vfunc.name %>(<% call_user_method(io) %>) + %retval = %instance.as(self).{{ impl_method_name.id }}(<% call_user_method(io) %>) + <% if vfunc.caller_owns.full? && vfunc.return_type.object? %> + LibGObject.g_object_ref(%retval) if %retval + <% end %> + <%= convert_to_lib("%retval", vfunc.return_type, vfunc.caller_owns, vfunc.may_return_null?) %> end <%- if object.is_a?(InterfaceInfo) -%> @@ -22,14 +26,14 @@ end end - private macro _register_unsafe_do_<%= vfunc.name %> + <%= method_gi_annotations -%> + private macro _register_unsafe_<%= vfunc.name %>_vfunc(impl_method_name) private def self._vfunc_unsafe_<%= vfunc.name %>(%this : Pointer(Void), <% generate_lib_args(io, vfunc) %>) : <%= return_type %> <%-= vfunc_gi_annotations %> - %gc_collected = !LibGObject.g_object_get_qdata(%this, GICrystal::GC_COLLECTED_QDATA_KEY).null? %instance = LibGObject.g_object_get_qdata(%this, GICrystal::INSTANCE_QDATA_KEY) - raise GICrystal::ObjectCollectedError.new if %gc_collected || %instance.null? + raise GICrystal::ObjectCollectedError.new if %instance.null? - %instance.as(self).unsafe_do_<%= vfunc.name %>(<% call_user_method_with_lib_args(io) %>) + %instance.as(self).{{ impl_method_name.id }}(<% call_user_method_with_lib_args(io) %>) end <%- if object.is_a?(InterfaceInfo) -%> @@ -38,7 +42,10 @@ def self._class_init(type_struct : Pointer(LibGObject::TypeClass), user_data : Pointer(Void)) : Nil <%- end -%> vfunc_ptr = (type_struct.as(Pointer(Void)) + <%= @byte_offset %>).as(Pointer(Pointer(Void))) + @@_gi_parent_vfunc_<%= vfunc.name %> = Proc(Pointer(Void), <% generate_lib_types(io, vfunc) %><%= return_type %>).new(vfunc_ptr.value, Pointer(Void).null) unless vfunc_ptr.value.null? vfunc_ptr.value = (->_vfunc_unsafe_<%= vfunc.name %>(Pointer(Void)<% proc_args(io) %>)).pointer previous_def end + + @@_gi_parent_vfunc_<%= vfunc.name %> : Proc(Pointer(Void), <% generate_lib_types(io, vfunc) %><%= return_type %>)? = nil end diff --git a/shard.yml b/shard.yml index 745b7f4..d654a78 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: gi-crystal -version: 0.12.0 +version: 0.16.0 authors: - Hugo Parente Lima @@ -8,11 +8,6 @@ targets: gi-crystal: main: src/generator/main.cr -dependencies: - version_from_shard: - github: hugopl/version_from_shard - version: ~> 1.2 - libraries: libgirepository: ~> 1.70 diff --git a/spec/basic_spec.cr b/spec/basic_spec.cr index 24f6ca9..815a88b 100644 --- a/spec/basic_spec.cr +++ b/spec/basic_spec.cr @@ -125,72 +125,6 @@ describe "GObject Binding" do end end - describe "GList" do - it "works on transfer full" do - subject = Test::Subject.new - list = subject.return_list_of_strings_transfer_full - list.size.should eq(2) - list.first?.should eq("one") - list.last?.should eq("two") - list[0].should eq("one") - list[1].should eq("two") - end - - it "works on transfer none" do - subject = Test::Subject.new - list = subject.return_list_of_strings_transfer_container - list.size.should eq(2) - list.first?.should eq("one") - list.last?.should eq("two") - end - - it "can be converted to an array" do - subject = Test::Subject.new - list = subject.return_list_of_strings_transfer_container - list.to_a.should eq(%w(one two)) - end - - it "has .each method" do - subject = Test::Subject.new - list = subject.return_list_of_strings_transfer_container - res = [] of String - list.each { |s| res << s } - res.should eq(%w(one two)) - end - end - - describe "GSList" do - it "works on transfer full" do - subject = Test::Subject.new - list = subject.return_slist_of_strings_transfer_full - list.size.should eq(2) - list[0].should eq("one") - list[1].should eq("two") - end - - it "works on transfer none" do - subject = Test::Subject.new - list = subject.return_slist_of_strings_transfer_container - list.size.should eq(2) - list[0].should eq("one") - list[1].should eq("two") - end - - it "can be converted to an array" do - subject = Test::Subject.new - list = subject.return_slist_of_strings_transfer_container - list.to_a.should eq(%w(one two)) - end - - it "has .each method" do - subject = Test::Subject.new - list = subject.return_list_of_strings_transfer_container - res = [] of String - list.each { |s| res << s } - res.should eq(%w(one two)) - end - end - describe "constants" do it "can have a value annotation" do Test::CONSTANT_WITH_VALUE_ANNOTATION.should eq(100) diff --git a/spec/boxed_struct_spec.cr b/spec/boxed_struct_spec.cr index 1c60673..96aba5a 100644 --- a/spec/boxed_struct_spec.cr +++ b/spec/boxed_struct_spec.cr @@ -1,4 +1,13 @@ require "./spec_helper" +@[NoInline] +private def create_boxed : Nil + Test::BoxedStruct.return_boxed_struct("hey") +end + describe "Boxed Struct bindings" do + it "doesn't crash on finalize method" do + create_boxed + GC.collect + end end diff --git a/spec/calllback_spec.cr b/spec/calllback_spec.cr index 0c5593e..5c33e52 100644 --- a/spec/calllback_spec.cr +++ b/spec/calllback_spec.cr @@ -5,6 +5,10 @@ def simple_callback(obj, value) : Nil end describe "Callback parameters" do + it "do not remove last callback argument unless it is a pointer" do + Test::SubjectCallbackWithPointer.should eq(Proc(Pointer(Void), GObject::Object, Bool, Nil)) + end + it "can be set" do subject = Test::Subject.new subject.simple_func = ->simple_callback(Test::Subject, Int32) diff --git a/spec/closure_manager_spec.cr b/spec/closure_manager_spec.cr new file mode 100644 index 0000000..abd3f26 --- /dev/null +++ b/spec/closure_manager_spec.cr @@ -0,0 +1,30 @@ +require "./spec_helper" +describe GICrystal::ClosureDataManager do + it "can print info about registered pointers" do + GC.collect + buffer = IO::Memory.new + GICrystal::ClosureDataManager.info(buffer) + buffer.to_s.should eq("total closures on hold: 0\n") + + buffer.clear + ptr = "some value".to_unsafe.as(Pointer(Void)) + GICrystal::ClosureDataManager.register(ptr) + GICrystal::ClosureDataManager.info(buffer) + buffer.to_s.should end_with("total closures on hold: 1\n") + + buffer.clear + GICrystal::ClosureDataManager.register(ptr) + GICrystal::ClosureDataManager.info(buffer) + buffer.to_s.should end_with("total closures on hold: 1\n") + + buffer.clear + GICrystal::ClosureDataManager.deregister(ptr) + GICrystal::ClosureDataManager.info(buffer) + buffer.to_s.should end_with("total closures on hold: 1\n") + + buffer.clear + GICrystal::ClosureDataManager.deregister(ptr) + GICrystal::ClosureDataManager.info(buffer) + buffer.to_s.should end_with("total closures on hold: 0\n") + end +end diff --git a/spec/const_spec.cr b/spec/const_spec.cr new file mode 100644 index 0000000..c44e080 --- /dev/null +++ b/spec/const_spec.cr @@ -0,0 +1,27 @@ +require "./spec_helper" + +describe "constant bindings" do + it "ignores constants according to binding.yml" do + responds = true + {% unless Test.has_constant? :IGNORED_CONSTANT %} + responds = false + {% end %} + responds.should eq(false) + end + + it "doesn't ignore all constants" do + responds = false + {% if Test.has_constant? :NON_IGNORED_CONSTANT %} + responds = true + {% end %} + responds.should eq(true) + end + + it "bind false Boolean constants" do + Test::BOOLEAN_FALSE_CONSTANT.should eq(false) + end + + it "bind true Boolean constants" do + Test::BOOLEAN_TRUE_CONSTANT.should eq(true) + end +end diff --git a/spec/docs_spec.cr b/spec/docs_spec.cr index 1399cef..269f5fe 100644 --- a/spec/docs_spec.cr +++ b/spec/docs_spec.cr @@ -2,13 +2,18 @@ require "./spec_helper" describe "Docs conversion" do it "converted the docs to crystal" do - expected_results = [ + expected_results = { "# getter: `Test::Subject#out_param`", "# setter: `Test::Subject#str_list=`", "# is: `Test::Subject#is_bool?`", "# initializer: `Test::Subject.new`", "# WARNING: **⚠️ The following code is in c ⚠️**", - ] + "# `nil` `true` `false`", + "# `Gdk::VulkanContext` Adw::ComboRow", + "# parameter: *parameter_42*", + "# email_is_not_a_parameter: foo@example.com", + } + test_subject = File.read("./src/auto/test-1.0/subject.cr") expected_results.each do |doc| diff --git a/spec/enum_spec.cr b/spec/enum_spec.cr index e8133e3..88922dd 100644 --- a/spec/enum_spec.cr +++ b/spec/enum_spec.cr @@ -1,26 +1,23 @@ -@[Flags] -enum TestFlags - A = 1 - B = 2 - C = 4 - D = 8 - BC = 6 -end - -enum TestEnum - X - Y - Z - Odd_Välue = Int32::MAX -end +require "./spec_helper" describe "Enums" do - it "registers valid gtypes" do + it "registers valid gtypes for user enum/flags" do TestFlags.g_type.should_not eq(0) TestEnum.g_type.should_not eq(0) TestEnum.g_type.should_not eq(TestFlags.g_type) end + it "registers valid gtypes for generated enum/flags" do + Test::RegularEnum.g_type.should_not eq(0) + Test::FlagFlags.g_type.should_not eq(0) + end + + it "can be ignored in binding.yml" do + {% if parse_type("Test::IgnoredEnum").resolve? %} + true.should eq(false), "Test::IgnoredEnum was not ignored." + {% end %} + end + it "allows retrieving values by name" do enum_class : LibGObject::EnumClass* = LibGObject.g_type_class_ref(TestEnum.g_type).as(LibGObject::EnumClass*) flags_class : LibGObject::FlagsClass* = LibGObject.g_type_class_ref(TestFlags.g_type).as(LibGObject::FlagsClass*) diff --git a/spec/flags_spec.cr b/spec/flags_spec.cr index ad37a01..5f658d3 100644 --- a/spec/flags_spec.cr +++ b/spec/flags_spec.cr @@ -23,4 +23,10 @@ describe "GObject flags" do ret.none?.should eq(false) ret.to_i.should eq(17) end + + it "can be ignored in binding.yml" do + {% if parse_type("Test::IgnoredFlags").resolve? %} + true.should eq(false), "Test::IgnoredFlags was not ignored." + {% end %} + end end diff --git a/spec/gc_spec.cr b/spec/gc_spec.cr new file mode 100644 index 0000000..8117c7d --- /dev/null +++ b/spec/gc_spec.cr @@ -0,0 +1,30 @@ +require "./spec_helper" + +private class GCResistantObj < GObject::Object + property moto : String + + def initialize(@moto) + super() + end +end + +@[NoInline] +private def create_gc_resistant_object : Test::Subject + subject = Test::Subject.new + gc_resistance = GCResistantObj.new("viva la resistance!") + # subject.gobj will hold a reference to `gc_resistance`, so if GC collect it the contents of `GCResistantObj#moto` + # will be collected as well. + subject.gobj = gc_resistance + subject +end + +describe "GC resistant GObject subclasses" do + it "don't get collected by GC" do + subject = create_gc_resistant_object + GC.collect + + gc_resistance = subject.gobj.as(GCResistantObj) + gc_resistance.moto.should eq("viva la resistance!") + gc_resistance.ref_count.should eq(2) + end +end diff --git a/spec/inheritance_spec.cr b/spec/inheritance_spec.cr index 4ea5b22..fd65cb1 100644 --- a/spec/inheritance_spec.cr +++ b/spec/inheritance_spec.cr @@ -18,6 +18,35 @@ private class UserSubject < Test::Subject end end +private class UserObjectWithGProperties < GObject::Object + @[GObject::Property(nick: "STRING", blurb: "A string without meaning", default: "default")] + property str_ing : String = "default" + + @[GObject::Property(nick: "INTEGER", blurb: "An Int32", default: 42, min: 40, max: 50)] + property int : Int32 = 42 + + @[GObject::Property(default: TestFlags::BC)] + property flags = TestFlags::BC + + @[GObject::Property] + property object : UserObject + + @[GObject::Property] + property signal_test : Bool = true + + # This declaration tests if the property is created as readonly if it only have getter? + @[GObject::Property] + getter? readonly_bool : Bool = true + + @[GObject::Property] + property nilable_object : UserObject? + + def initialize + super + @object = UserObject.new + end +end + class User::Class::With::Colons < GObject::Object end @@ -36,21 +65,6 @@ describe "Classes inheriting GObject::Object" do casted_obj.object_id.should eq(obj.object_id) end - it "raises on cast of deleted crystal objects" do - subject = Test::Subject.new - user_obj = UserObject.new - subject.gobj = user_obj - - # Pretend the GC collected the object, but add a ref to it, so the - # unref made by the artificial `finalize` call wont have any effect. - LibGObject.g_object_ref(user_obj) - user_obj.finalize - - expect_raises(GICrystal::ObjectCollectedError) do - UserObject.cast(subject.gobj.not_nil!) - end - end - it "create a crystal instance if the object was born on C world" do raw_gobj = LibGObject.g_object_newv(UserObject.g_type, 0, nil) wrapper = GObject::Object.new(raw_gobj, :full) @@ -90,4 +104,66 @@ describe "Classes inheriting GObject::Object" do LibGObject.g_type_check_instance_is_a(obj, UserSubject.g_type).should eq(1) obj.string.should eq("hey") end + + it "can set GObject properties" do + obj = UserObjectWithGProperties.new + + LibGObject.g_object_set(obj, "str-ing", "test value", Pointer(Void).null) + obj.str_ing.should eq("test value") + + LibGObject.g_object_set(obj, "int", 50, Pointer(Void).null) + obj.int.should eq(50) + + LibGObject.g_object_set(obj, "flags", TestFlags::C, Pointer(Void).null) + obj.flags.should eq(TestFlags::C) + + object = UserObject.new + LibGObject.g_object_set(obj, "object", object, Pointer(Void).null) + obj.object.should eq(object) + + LibGObject.g_object_set(obj, "nilable-object", Pointer(Void).null, Pointer(Void).null) + obj.nilable_object.should eq(nil) + end + + it "can get GObject properties" do + obj = UserObjectWithGProperties.new + + out_string = uninitialized Pointer(LibC::Char) + LibGObject.g_object_get(obj, "str-ing", pointerof(out_string), Pointer(Void).null) + String.new(out_string).should eq("default") + obj.str_ing = "test value" + LibGObject.g_object_get(obj, "str-ing", pointerof(out_string), Pointer(Void).null) + String.new(out_string).should eq("test value") + + out_int = uninitialized Int32 + LibGObject.g_object_get(obj, "int", pointerof(out_int), Pointer(Void).null) + out_int.should eq(42) + obj.int = 50 + LibGObject.g_object_get(obj, "int", pointerof(out_int), Pointer(Void).null) + out_int.should eq(50) + + obj.flags = TestFlags::A + out_flags = uninitialized TestFlags + LibGObject.g_object_get(obj, "flags", pointerof(out_flags), Pointer(Void).null) + out_flags.should eq(TestFlags::A) + + obj.object = user_obj = UserObject.new + out_object = uninitialized Pointer(Void) + LibGObject.g_object_get(obj, "object", pointerof(out_object), Pointer(Void).null) + out_object.should eq(user_obj.to_unsafe) + end + + it "emits notify signal on GObject properties access" do + test_var = 0 + obj = UserObjectWithGProperties.new + signal = obj.notify_signal["signal-test"].connect { test_var += 1 } + + test_var.should eq(0) + obj.signal_test = false + test_var.should eq(1) + obj.signal_test = true + test_var.should eq(2) + + signal.disconnect + end end diff --git a/spec/libtest/test_const.h b/spec/libtest/test_const.h new file mode 100644 index 0000000..ba5b0cd --- /dev/null +++ b/spec/libtest/test_const.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +/** + * TEST_IGNORED_CONSTANT: + * A constant ignored in the binding config. + */ +#define TEST_IGNORED_CONSTANT 123 + +/** + * TEST_NON_IGNORED_CONSTANT: + * A constant. + */ +#define TEST_NON_IGNORED_CONSTANT 123 + +/** + * TEST_BOOLEAN_TRUE_CONSTANT: + * A boolean constant with true value + */ +#define TEST_BOOLEAN_TRUE_CONSTANT TRUE + +/** + * TEST_BOOLEAN_FALSE_CONSTANT: + * A boolean constant with false value + */ +#define TEST_BOOLEAN_FALSE_CONSTANT FALSE + +G_END_DECLS diff --git a/spec/libtest/test_flags.c b/spec/libtest/test_flags.c index a4b552c..5fd288e 100644 --- a/spec/libtest/test_flags.c +++ b/spec/libtest/test_flags.c @@ -27,3 +27,16 @@ GType test_empty_flags_get_type() { return static_g_define_type_id; } + +GType test_ignored_flags_get_type() { + static gsize static_g_define_type_id = 0; + + if (g_once_init_enter(&static_g_define_type_id)) { + static const GFlagsValue values[] + = { { TEST_IGNORED_FLAGS_VALUE, "TEST_IGNORED_FLAGS_VALUE", "value" }, { 0, NULL, NULL } }; + GType g_define_type_id = g_flags_register_static(g_intern_static_string("TestIgnoredFlags"), values); + g_once_init_leave(&static_g_define_type_id, g_define_type_id); + } + + return static_g_define_type_id; +} diff --git a/spec/libtest/test_flags.h b/spec/libtest/test_flags.h index fed8d12..0bdf7a9 100644 --- a/spec/libtest/test_flags.h +++ b/spec/libtest/test_flags.h @@ -27,8 +27,22 @@ GType test_flag_flags_get_type(); * * Used to test flags that have only the NONE entry. */ -typedef enum { TEST_EMPTY_NONE = 0 } TestEmptyFlags; +typedef enum { + TEST_EMPTY_NONE = 0 +} TestEmptyFlags; GType test_empty_flags_get_type(); +/** + * TestIgnoredFlags: + * @TEST_IGNORED_FLAGS_VALUE: + * + * Used to test ignored flags + */ +typedef enum { + TEST_IGNORED_FLAGS_VALUE = 1 +} TestIgnoredFlags; + +GType test_ignored_flags_get_type(); + G_END_DECLS diff --git a/spec/libtest/test_iface_vfuncs.c b/spec/libtest/test_iface_vfuncs.c index 3ef851e..a03e1a2 100644 --- a/spec/libtest/test_iface_vfuncs.c +++ b/spec/libtest/test_iface_vfuncs.c @@ -5,18 +5,80 @@ G_DEFINE_INTERFACE(TestIfaceVFuncs, test_iface_vfuncs, G_TYPE_OBJECT) +static guint32 test_iface_vfuncs_default_vfunc_bubble_up(TestIfaceVFuncs* iface) { + return 0xDEADBEEF; +} + +static guint32 test_iface_vfuncs_default_vfunc_bubble_up_with_args(TestIfaceVFuncs* iface, guint32 a) { + return a + 1; +} + static void test_iface_vfuncs_default_init(TestIfaceVFuncsInterface* iface) { + iface->vfunc_bubble_up = test_iface_vfuncs_default_vfunc_bubble_up; + iface->vfunc_bubble_up_with_args = test_iface_vfuncs_default_vfunc_bubble_up_with_args; } -void test_iface_vfuncs_call_vfunc(TestIfaceVFuncs* self, const char* name) { - g_return_if_fail(TEST_IS_IFACE_VFUNCS(self)); +gchar* test_iface_vfuncs_call_vfunc(TestIfaceVFuncs* self, const char* name) { + gchar* buffer = NULL; + + g_return_val_if_fail(TEST_IS_IFACE_VFUNCS(self), NULL); TestIfaceVFuncsInterface* iface = TEST_IFACE_VFUNCS_GET_IFACE(self); - g_return_if_fail(iface); + g_return_val_if_fail(iface, NULL); if (!strcmp(name, "vfunc_basic")) { - g_return_if_fail(iface->vfunc_basic); + g_return_val_if_fail(iface->vfunc_basic, NULL); + iface->vfunc_basic(self, 1, 2.2, 3.3, "string", G_OBJECT(test_subject_new_from_string("hey"))); + buffer = g_strdup("void"); + } else if (!strcmp(name, "vfunc_return_string")) { + g_return_val_if_fail(iface->vfunc_return_string, NULL); + + buffer = g_strdup(iface->vfunc_return_string(self)); + } else if (!strcmp(name, "vfunc_return_bool")) { + g_return_val_if_fail(iface->vfunc_return_bool, NULL); + + gboolean bool_retval = iface->vfunc_return_bool(self); + buffer = bool_retval ? g_strdup("true") : g_strdup("false"); + } else if (!strcmp(name, "vfunc_return_enum")) { + g_return_val_if_fail(iface->vfunc_return_enum, NULL); + + TestRegularEnum enum_retval = iface->vfunc_return_enum(self); + buffer = g_enum_to_string(TEST_TYPE_REGULAR_ENUM, enum_retval); + } else if (!strcmp(name, "vfunc_bubble_up")) { + g_return_val_if_fail(iface->vfunc_bubble_up, NULL); + + iface->vfunc_bubble_up(self); + buffer = g_strdup("success"); + } else if (!strcmp(name, "vfunc_bubble_up_with_args")) { + g_return_val_if_fail(iface->vfunc_bubble_up_with_args, NULL); + + iface->vfunc_bubble_up_with_args(self, 5); + buffer = g_strdup("success"); + } else if (!strcmp(name, "vfunc_return_nullable_string")) { + g_return_val_if_fail(iface->vfunc_return_nullable_string, NULL); + + buffer = iface->vfunc_return_nullable_string(self); + if (!buffer) + buffer = "NULL"; + buffer = g_strdup(buffer); + } else if (!strcmp(name, "vfunc_return_nullable_obj")) { + g_return_val_if_fail(iface->vfunc_return_nullable_obj, NULL); + + TestSubject* obj = iface->vfunc_return_nullable_obj(self); + buffer = g_strdup(obj ? "Obj" : "NULL"); + } else if (!strcmp(name, "vfunc_return_transfer_full_obj")) { + g_return_val_if_fail(iface->vfunc_return_transfer_full_obj, NULL); + + GObject* obj = G_OBJECT(iface->vfunc_return_transfer_full_obj(self)); + if (obj) { + int ref_value = obj->ref_count; + g_object_unref(obj); + buffer = g_strdup_printf("%i", ref_value); + } else + buffer = g_strdup("NULL"); } else g_warning("bad vfunc name: %s", name); + + return buffer; } diff --git a/spec/libtest/test_iface_vfuncs.h b/spec/libtest/test_iface_vfuncs.h index ecd0875..3769fbf 100644 --- a/spec/libtest/test_iface_vfuncs.h +++ b/spec/libtest/test_iface_vfuncs.h @@ -2,6 +2,9 @@ #include +#include "test_regular_enum.h" +#include "test_subject.h" + G_BEGIN_DECLS #define TEST_TYPE_IFACE_VFUNCS test_iface_vfuncs_get_type() @@ -21,15 +24,68 @@ struct _TestIfaceVFuncsInterface { * @o: (transfer full): An Test::Subject object. */ void (*vfunc_basic)(TestIfaceVFuncs* self, int i, float f, double d, const char* s, GObject* o); + + /** + * TestIfaceVFuncsInterface::vfunc_return_string + * @self: Self + */ + char* (*vfunc_return_string)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_return_bool + * @self: Self + */ + gboolean (*vfunc_return_bool)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_bubble_up + * @self: Self + */ + guint32 (*vfunc_bubble_up)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_bubble_up_with_args + * @self: Self + * @a: A uint32 + */ + guint32 (*vfunc_bubble_up_with_args)(TestIfaceVFuncs* self, guint32 a); + + /** + * TestIfaceVFuncsInterface::vfunc_return_enum + * @self: Self + */ + TestRegularEnum (*vfunc_return_enum)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_return_nullable_string + * @self: Self. + * Returns: (nullable): A nullable GObject + */ + char* (*vfunc_return_nullable_string)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_return_nullable_obj + * @self: Self. + * Returns: (type TestSubject) (transfer none) (nullable): A nullable GObject + */ + TestSubject* (*vfunc_return_nullable_obj)(TestIfaceVFuncs* self); + + /** + * TestIfaceVFuncsInterface::vfunc_return_transfer_full_obj + * @self: Self. + * Returns: (type TestSubject) (transfer full) (nullable): A GObject + */ + TestSubject* (*vfunc_return_transfer_full_obj)(TestIfaceVFuncs* self); }; /** * test_iface_vfuncs_call_vfunc: * @self: Self. * @name: Name of vfunc to call. + * Returns: (nullable): A String representing the value returned by the vfunc * - * Used to test vfunc call of vfuncs without activators. + * Used to test vfunc call of vfuncs without activators, see the implementation for possible values for @name. */ -void test_iface_vfuncs_call_vfunc(TestIfaceVFuncs* self, const char* name); +gchar* test_iface_vfuncs_call_vfunc(TestIfaceVFuncs* self, const char* name); G_END_DECLS diff --git a/spec/libtest/test_regular_enum.c b/spec/libtest/test_regular_enum.c index 9776466..d4810a7 100644 --- a/spec/libtest/test_regular_enum.c +++ b/spec/libtest/test_regular_enum.c @@ -13,4 +13,16 @@ GType test_regular_enum_get_type() { } return static_g_define_type_id; -} \ No newline at end of file +} + +GType test_ignored_enum_get_type() { + static gsize static_g_define_type_id = 0; + + if (g_once_init_enter(&static_g_define_type_id)) { + static const GEnumValue values[] = { { TEST_IGNORED_VALUE, "TEST_IGNORED_VALUE", "value" }, { 0, NULL, NULL } }; + GType g_define_type_id = g_enum_register_static(g_intern_static_string("TestIgnoredEnum"), values); + g_once_init_leave(&static_g_define_type_id, g_define_type_id); + } + + return static_g_define_type_id; +} diff --git a/spec/libtest/test_regular_enum.h b/spec/libtest/test_regular_enum.h index ae54f60..2bc75fd 100644 --- a/spec/libtest/test_regular_enum.h +++ b/spec/libtest/test_regular_enum.h @@ -21,4 +21,17 @@ typedef enum { GType test_regular_enum_get_type(); #define TEST_TYPE_REGULAR_ENUM test_regular_enum_get_type() +/** + * TestIgnoredEnum: + * @TEST_IGNORED_VALUE: + * + * Used to test ignored enums. + */ +typedef enum { + TEST_IGNORED_VALUE +} TestIgnoredEnum; + +GType test_ignored_enum_get_type(); +#define TEST_TYPE_IGNORED_ENUM test_ignored_enum_get_type() + G_END_DECLS diff --git a/spec/libtest/test_struct.c b/spec/libtest/test_struct.c index 7a6dfac..848446a 100644 --- a/spec/libtest/test_struct.c +++ b/spec/libtest/test_struct.c @@ -1,6 +1,7 @@ #include "test_struct.h" void test_struct_initialize(TestStruct* self) { + self->string = "hey"; } void test_struct_finalize(TestStruct* self) { diff --git a/spec/libtest/test_struct.h b/spec/libtest/test_struct.h index b817bf7..f689e9f 100644 --- a/spec/libtest/test_struct.h +++ b/spec/libtest/test_struct.h @@ -16,6 +16,26 @@ typedef struct _TestPoint { int y; } TestPoint; +/** + * TestRect + * @origin: Rect origin + * @width: + * @height: + */ +typedef struct _TestRect { + TestPoint origin; + int width; + int height; +} TestRect; + +/** + * TestTwoPoints + * @points: two points + */ +typedef struct _TestTwoPoints { + TestPoint points[2]; +} TestTwoPoints; + /** * TestStruct: * @in: A attribute using a invalid Crystal keyword. @@ -31,6 +51,7 @@ typedef struct _TestStruct { TestPoint* point_ptr; TestPoint point; const char* string; + int ignored_field; } TestStruct; /** diff --git a/spec/libtest/test_subject.c b/spec/libtest/test_subject.c index 46f361e..0adc22c 100644 --- a/spec/libtest/test_subject.c +++ b/spec/libtest/test_subject.c @@ -208,6 +208,21 @@ static void test_subject_class_init(TestSubjectClass* klass) { G_TYPE_INT, // return_type 0, // n_params NULL); + /** + * TestSubject::return-bool: + * @subject: the subject + * @boolean: A boolean value + * + * Used to test bool return values and parameters. + */ + g_signal_new("return-bool", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, // class_offset + NULL, // accumulator + NULL, // accumulator data + NULL, // C marshaller + G_TYPE_BOOLEAN, // return_type + 1, // n_params + G_TYPE_BOOLEAN, NULL); /** * TestSubject::array-of-gobj: * @self: the subject who sent the signal. @@ -240,6 +255,23 @@ static void test_subject_class_init(TestSubjectClass* klass) { G_TYPE_NONE, // return_type 2, // n_params G_TYPE_POINTER, G_TYPE_INT, NULL); + + /** + * TestSubject::iface: + * @self: the subject who sent the signal. + * @obj: a TestIface. + * + * Used to test signals with interfaces. + */ + g_signal_new("iface", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, // class_offset + NULL, // accumulator + NULL, // accumulator data + NULL, // C marshaller + G_TYPE_NONE, // return_type + 1, // n_params + TEST_TYPE_IFACE, NULL); + /** * TestSubject::enum: * @self: the subject who sent the signal. @@ -471,6 +503,18 @@ TestIface* test_subject_return_myself_as_interface(TestIface* self) { return self; } +GList* test_subject_return_list_of_iface_transfer_full(TestSubject* self) { + GList* list = NULL; + g_object_ref(self); + list = g_list_append(list, self); + list = g_list_append(list, test_subject_new_from_string("Subject from C")); + return list; +} + +GList* test_subject_return_list_of_gobject_transfer_full(TestSubject* self) { + return test_subject_return_list_of_iface_transfer_full(self); +} + GList* test_subject_return_list_of_strings_transfer_full(TestSubject* self) { GList* list = NULL; list = g_list_append(list, g_strdup("one")); @@ -485,6 +529,18 @@ GList* test_subject_return_list_of_strings_transfer_container(TestSubject* self) return list; } +GSList* test_subject_return_slist_of_iface_transfer_full(TestSubject* self) { + GSList* list = NULL; + g_object_ref(self); + list = g_slist_append(list, self); + list = g_slist_append(list, test_subject_new_from_string("Subject from C")); + return list; +} + +GSList* test_subject_return_slist_of_gobject_transfer_full(TestSubject* self) { + return test_subject_return_slist_of_iface_transfer_full(self); +} + GSList* test_subject_return_slist_of_strings_transfer_full(TestSubject* self) { GSList* list = NULL; list = g_slist_append(list, g_strdup("one")); diff --git a/spec/libtest/test_subject.h b/spec/libtest/test_subject.h index deeebb1..1c5c18a 100644 --- a/spec/libtest/test_subject.h +++ b/spec/libtest/test_subject.h @@ -37,6 +37,10 @@ G_BEGIN_DECLS * * initializer: [ctor@Test.Subject.new] * + * parameter: @parameter_42 + * + * email_is_not_a_parameter: foo@example.com + * * code block: * ```c * #include @@ -46,6 +50,10 @@ G_BEGIN_DECLS * } * ``` * + * Others: + * %NULL %TRUE %FALSE + * `GdkVulkanContext` AdwComboRow + * */ #define TEST_TYPE_SUBJECT test_subject_get_type() G_DECLARE_DERIVABLE_TYPE(TestSubject, test_subject, TEST, SUBJECT, GObject) @@ -66,6 +74,13 @@ typedef struct _TestSubjectClass { */ typedef void (*TestSubjectSimpleFunc)(TestSubject* subject, int number, gpointer user_data); +/** + * TestSubjectCallbackWithPointer: + * + * Used to test callbacks with pointer parameters + */ +typedef void (*TestSubjectCallbackWithPointer)(gpointer data, GObject* object, gboolean boolean); + /** * test_subject_new_from_whatever: * @value: @@ -264,6 +279,24 @@ gchar** test_subject_return_array_transfer_container(TestSubject* self, int* len */ int* test_subject_return_int32_array_transfer_full(TestSubject* self, int* length); +/** + * test_subject_return_list_of_iface_transfer_full: + * Returns: (transfer full) (element-type TestIface): + * + * Used to test GList transfer full conversions, first element will be self, the second one + * a new created `TestSubject`. + */ +GList* test_subject_return_list_of_iface_transfer_full(TestSubject* self); + +/** + * test_subject_return_list_of_gobject_transfer_full: + * Returns: (transfer full) (element-type GObject): + * + * Used to test GList transfer full conversions, first element will be self, the second one + * a new created `TestSubject`. + */ +GList* test_subject_return_list_of_gobject_transfer_full(TestSubject* self); + /** * test_subject_return_list_of_strings_transfer_full: * Returns: (transfer full) (element-type utf8): `["one", "two"]` @@ -280,6 +313,24 @@ GList* test_subject_return_list_of_strings_transfer_full(TestSubject* self); */ GList* test_subject_return_list_of_strings_transfer_container(TestSubject* self); +/** + * test_subject_return_slist_of_iface_transfer_full: + * Returns: (transfer full) (element-type TestIface): + * + * Used to test GSList transfer full conversions, first element will be self, the second one + * a new created `TestSubject`. + */ +GSList* test_subject_return_slist_of_iface_transfer_full(TestSubject* self); + +/** + * test_subject_return_slist_of_gobject_transfer_full: + * Returns: (transfer full) (element-type TestSubject): + * + * Used to test GSList transfer full conversions, first element will be self, the second one + * a new created `TestSubject`. + */ +GSList* test_subject_return_slist_of_gobject_transfer_full(TestSubject* self); + /** * test_subject_return_slist_of_strings_transfer_full: * Returns: (transfer full) (element-type utf8): `["one", "two"]` diff --git a/spec/libtest_binding.yml b/spec/libtest_binding.yml index 2dd6d4d..5b1b112 100644 --- a/spec/libtest_binding.yml +++ b/spec/libtest_binding.yml @@ -1,2 +1,14 @@ namespace: Test version: "1.0" + +ignore_constants: + - IGNORED_CONSTANT + +types: + Struct: + ignore_fields: + - ignored_field + IgnoredEnum: + ignore: true + IgnoredFlags: + ignore: true diff --git a/spec/list_spec.cr b/spec/list_spec.cr new file mode 100644 index 0000000..6f33d97 --- /dev/null +++ b/spec/list_spec.cr @@ -0,0 +1,59 @@ +require "./spec_helper" + +describe "GList" do + it "works with interfaces" do + subject = Test::Subject.new(string: "Subject from Crystal") + + list = subject.return_list_of_iface_transfer_full + list.size.should eq(2) + + subject_from_c = list[0] + subject_from_c.object_id.should eq(subject.object_id) + Test::Subject.cast(subject_from_c).string.should eq("Subject from Crystal") + Test::Subject.cast(list[1]).string.should eq("Subject from C") + end + + it "works with GObjects" do + subject = Test::Subject.new(string: "Subject from Crystal") + + list = subject.return_list_of_gobject_transfer_full + list.size.should eq(2) + + subject_from_c = list[0] + subject_from_c.object_id.should eq(subject.object_id) + Test::Subject.cast(subject_from_c).string.should eq("Subject from Crystal") + Test::Subject.cast(list[1]).string.should eq("Subject from C") + end + + it "works on transfer full" do + subject = Test::Subject.new + list = subject.return_list_of_strings_transfer_full + list.size.should eq(2) + list.first?.should eq("one") + list.last?.should eq("two") + list[0].should eq("one") + list[1].should eq("two") + end + + it "works on transfer none" do + subject = Test::Subject.new + list = subject.return_list_of_strings_transfer_container + list.size.should eq(2) + list.first?.should eq("one") + list.last?.should eq("two") + end + + it "can be converted to an array" do + subject = Test::Subject.new + list = subject.return_list_of_strings_transfer_container + list.to_a.should eq(%w(one two)) + end + + it "has .each method" do + subject = Test::Subject.new + list = subject.return_list_of_strings_transfer_container + res = [] of String + list.each { |s| res << s } + res.should eq(%w(one two)) + end +end diff --git a/spec/s_list_spec.cr b/spec/s_list_spec.cr new file mode 100644 index 0000000..e98fdc1 --- /dev/null +++ b/spec/s_list_spec.cr @@ -0,0 +1,57 @@ +require "./spec_helper" + +describe "GSList" do + it "works with interfaces" do + subject = Test::Subject.new(string: "Subject from Crystal") + + list = subject.return_slist_of_iface_transfer_full + list.size.should eq(2) + + subject_from_c = list[0] + subject_from_c.object_id.should eq(subject.object_id) + Test::Subject.cast(subject_from_c).string.should eq("Subject from Crystal") + Test::Subject.cast(list[1]).string.should eq("Subject from C") + end + + it "works with GObjects" do + subject = Test::Subject.new(string: "Subject from Crystal") + + list = subject.return_slist_of_gobject_transfer_full + list.size.should eq(2) + + subject_from_c = list[0] + subject_from_c.object_id.should eq(subject.object_id) + Test::Subject.cast(subject_from_c).string.should eq("Subject from Crystal") + Test::Subject.cast(list[1]).string.should eq("Subject from C") + end + + it "works on transfer full" do + subject = Test::Subject.new + list = subject.return_slist_of_strings_transfer_full + list.size.should eq(2) + list[0].should eq("one") + list[1].should eq("two") + end + + it "works on transfer none" do + subject = Test::Subject.new + list = subject.return_slist_of_strings_transfer_container + list.size.should eq(2) + list[0].should eq("one") + list[1].should eq("two") + end + + it "can be converted to an array" do + subject = Test::Subject.new + list = subject.return_slist_of_strings_transfer_container + list.to_a.should eq(%w(one two)) + end + + it "has .each method" do + subject = Test::Subject.new + list = subject.return_list_of_strings_transfer_container + res = [] of String + list.each { |s| res << s } + res.should eq(%w(one two)) + end +end diff --git a/spec/signals_spec.cr b/spec/signals_spec.cr index 92e3459..5657c69 100644 --- a/spec/signals_spec.cr +++ b/spec/signals_spec.cr @@ -6,6 +6,7 @@ private class UserSignalObj < GObject::Object signal int64(int64 : Int64, uint64 : UInt64) signal floats(float : Float32, double : Float64) signal string(str : String) + signal path(path : Path) signal bool(value : Bool) end @@ -81,6 +82,14 @@ describe "GObject signals" do # subject.return_int_signal.emit end + it "can have boolean parameters and return value" do + subject = Test::Subject.new + subject.return_bool_signal.connect do |value| + typeof(value).should be_a(Bool) + value + end + end + pending "test emit signals with return values" it "can have array GObject as parameter" do @@ -96,6 +105,19 @@ describe "GObject signals" do received_objs.should eq([obj1, obj2]) end + it "can have Interface as parameter" do + subject = Test::Subject.new + + received_obj : Test::Iface? = nil + subject.iface_signal.connect do |obj| + received_obj = obj + end + + obj = Test::Subject.new + subject.iface_signal.emit(obj) + received_obj.should eq(obj) + end + it "can have array Interface as parameter" do subject = Test::Subject.new obj1 = Test::Subject.new @@ -199,7 +221,7 @@ describe "GObject signals" do called.should eq(true) end - it "works with 32 bits integer parameters" do + it "works with (U)Int32 parameters" do obj = UserSignalObj.new value = 0 obj.uint32_signal.connect do |v| @@ -210,10 +232,10 @@ describe "GObject signals" do value.should eq(32) end - it "works with 64 bits integer parameters" do + it "works with (U)Int64 parameters" do obj = UserSignalObj.new received_i64 = 0_i64 - received_u64 = 0_i64 + received_u64 = 0_u64 obj.int64_signal.connect do |i64, u64| received_i64 = i64 received_u64 = u64 @@ -224,7 +246,7 @@ describe "GObject signals" do received_u64.should eq(128_u64) end - it "works with float parameters" do + it "works with Float32 parameters" do obj = UserSignalObj.new received_f32 = 0.0_f32 received_f64 = 0.0 @@ -238,7 +260,7 @@ describe "GObject signals" do received_f64.should eq(6.28_f64) end - it "works with string parameters" do + it "works with String parameters" do obj = UserSignalObj.new received_str = "" obj.string_signal.connect do |str| @@ -249,7 +271,18 @@ describe "GObject signals" do received_str.should eq("Hello") end - it "works with boolean parameters" do + it "works with Path parameters" do + obj = UserSignalObj.new + received_path = "" + obj.path_signal.connect do |path| + received_path = path + end + + obj.path_signal.emit(Path.new("Hello")) + received_path.should eq(Path.new("Hello")) + end + + it "works with Bool parameters" do obj = UserSignalObj.new received_bool = false obj.bool_signal.connect do |bool| diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index f6defe1..a378e55 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -19,3 +19,21 @@ Spec.after_each do # Why 5 times? No idea... I was just unsure of doing just a single call. 5.times { GC.collect } end + +# This flag is used by different tests, so it's here +@[Flags] +enum TestFlags + A = 1 + B = 2 + C = 4 + D = 8 + BC = 6 +end + +# This enum is used by different tests, so it's here +enum TestEnum + X + Y + Z + Odd_Välue = Int32::MAX +end diff --git a/spec/struct_spec.cr b/spec/struct_spec.cr index 03740e4..f35705d 100644 --- a/spec/struct_spec.cr +++ b/spec/struct_spec.cr @@ -1,14 +1,9 @@ require "./spec_helper" describe "Struct bindings" do - it "can have struct pointers as attributes" do + it "do not generate setters for struct pointer attributes" do truct = Test::Struct.new(begin: 42) - truct.point_ptr.should eq(nil) - truct.point_ptr = Test::Point.new(1, 2) - truct.point_ptr.should be_a(Test::Point) - truct.point_ptr!.x.should eq(1) - truct.point_ptr!.y.should eq(2) - truct.point_ptr = nil + truct.responds_to?(:point_ptr=).should eq(false) truct.point_ptr.should eq(nil) end @@ -21,8 +16,24 @@ describe "Struct bindings" do truct.point.y.should eq(2) end + it "binds POD structs with other POD structs as a Crystal struct" do + rect = Test::Rect.new + rect.is_a?(Value).should eq(true) + + two_points = Test::TwoPoints.new + two_points.is_a?(Value).should eq(true) + end + + it "ignore fields according to binding.yml" do + truct = Test::Struct.new + truct.responds_to?(:ignored_field).should eq(false) + truct.responds_to?(:ignored_field=).should eq(false) + end + it "can have nullable string attributes" do - truct = Test::Struct.new(string: "hey") + truct = Test::Struct.new + truct.string.should eq(nil) + truct._initialize truct.string.should eq("hey") end diff --git a/spec/timeout_spec.cr b/spec/timeout_spec.cr index f33d9b8..8a0b3cb 100644 --- a/spec/timeout_spec.cr +++ b/spec/timeout_spec.cr @@ -9,5 +9,9 @@ describe "Glib timeout & idle_add" do GLib.idle_add do false end + + # We still with 2 refs here because no main loop was run to free them + GICrystal::ClosureDataManager.count.should eq(2) + GICrystal::ClosureDataManager.deregister_all end end diff --git a/spec/vfunc_spec.cr b/spec/vfunc_spec.cr index d353f1d..cec6561 100644 --- a/spec/vfunc_spec.cr +++ b/spec/vfunc_spec.cr @@ -8,8 +8,42 @@ private class IfaceVFuncImpl < GObject::Object getter float64 = 0.0 getter string : String? getter obj : GObject::Object? + property bool_return_value = false + property nullable_string_return_value : String? + property nullable_obj_return_value : Test::Subject? - def do_vfunc_basic(@int32, @float32, @float64, @string, @obj) + @[GObject::Virtual] + def vfunc_basic(@int32, @float32, @float64, @string, @obj) + end + + @[GObject::Virtual] + def vfunc_return_string + "string returned from vfunc!" + end + + @[GObject::Virtual] + def vfunc_return_bool : Bool + @bool_return_value + end + + @[GObject::Virtual] + def vfunc_return_enum + Test::RegularEnum::Value2 + end + + @[GObject::Virtual] + def vfunc_return_nullable_string : String? + @nullable_string_return_value + end + + @[GObject::Virtual] + def vfunc_return_nullable_obj : Test::Subject? + @nullable_obj_return_value + end + + @[GObject::Virtual] + def vfunc_return_transfer_full_obj : Test::Subject? + @nullable_obj_return_value end end @@ -22,10 +56,25 @@ private class UnsafeIfaceVFuncImpl < GObject::Object getter string : String? getter obj : GObject::Object? - def unsafe_do_vfunc_basic(@int32, @float32, @float64, c_string : Pointer(UInt8), obj : Pointer(Void)) + @[GObject::Virtual(unsafe: true, name: "vfunc_basic")] + def do_it(@int32, @float32, @float64, c_string : Pointer(UInt8), obj : Pointer(Void)) @string = String.new(c_string) if c_string @obj = GObject::Object.new(obj, :full) end + + @[GObject::Virtual(unsafe: true, name: "vfunc_bubble_up")] + def vfunc_bubble_up : UInt32 + ret = previous_vfunc! + raise "Funny number not found" unless ret == 0xDEADBEEF + ret + end + + @[GObject::Virtual(unsafe: true, name: "vfunc_bubble_up_with_args")] + def vfunc_bubble_up_with_args(a : UInt32) : UInt32 + ret = previous_vfunc!(a + 1) + raise "Wrong number returned" unless ret == 7 + ret + end end describe "GObject vfuncs" do @@ -53,6 +102,67 @@ describe "GObject vfuncs" do subject.string.should eq("hey") end - pending "can have return values" + it "can return a string" do + obj = IfaceVFuncImpl.new + obj.call_vfunc("vfunc_return_string").should eq("string returned from vfunc!") + end + + it "can return a boolean" do + obj = IfaceVFuncImpl.new + obj.bool_return_value = true + obj.call_vfunc("vfunc_return_bool").should eq("true") + obj.bool_return_value = false + obj.call_vfunc("vfunc_return_bool").should eq("false") + end + + it "can return an enum" do + obj = IfaceVFuncImpl.new + obj.call_vfunc("vfunc_return_enum").should eq("TEST_VALUE2") + end + + it "can return nil on a String? return type restriction" do + obj = IfaceVFuncImpl.new + obj.call_vfunc("vfunc_return_nullable_string").should eq("NULL") + end + + it "can return an String on a String? return type restriction" do + obj = IfaceVFuncImpl.new + obj.nullable_string_return_value = "hey" + obj.call_vfunc("vfunc_return_nullable_string").should eq("hey") + end + + it "can return nil on a Object? return type restriction" do + obj = IfaceVFuncImpl.new + obj.call_vfunc("vfunc_return_nullable_string").should eq("NULL") + end + + it "can return an object on a Object? return type restriction" do + obj = IfaceVFuncImpl.new + obj.nullable_obj_return_value = Test::Subject.new + obj.call_vfunc("vfunc_return_nullable_obj").should eq("Obj") + end + + it "can return 'transfer full' objects" do + obj = IfaceVFuncImpl.new + obj.nullable_obj_return_value = Test::Subject.new + # this call returns obj ref count. + obj.call_vfunc("vfunc_return_transfer_full_obj").should eq("2") + end + + it "can return 'transfer full' nullable objects" do + obj = IfaceVFuncImpl.new + obj.nullable_obj_return_value = nil + obj.call_vfunc("vfunc_return_transfer_full_obj").should eq("NULL") + end + + it "can chain vfuncs up" do + obj = UnsafeIfaceVFuncImpl.new + ret = obj.call_vfunc("vfunc_bubble_up") + ret.should eq("success") + + ret = obj.call_vfunc("vfunc_bubble_up_with_args") + ret.should eq("success") + end + pending "can be from objects, not interfaces" end diff --git a/src/bindings/g_lib/binding.yml b/src/bindings/g_lib/binding.yml index f90e9c9..988044d 100644 --- a/src/bindings/g_lib/binding.yml +++ b/src/bindings/g_lib/binding.yml @@ -1,627 +1,843 @@ namespace: GLib version: "2.0" -include_before: - - error.cr - - lib_g_lib.cr +require_before: +- error.cr +- lib_g_lib.cr -include: - - bytes.cr - - list.cr - - slist.cr - - timeout.cr - - variant.cr - - variant_dict.cr +require_after: +- bytes.cr +- list.cr +- slist.cr +- timeout.cr +- variant.cr +- variant_dict.cr -handmade: - - Variant +ignore_constants: +- ANALYZER_ANALYZING +- ASCII_DTOSTR_BUF_SIZE +- BIG_ENDIAN +- CSET_a_2_z +- CSET_A_2_Z +- CSET_DIGITS +- C_STD_VERSION +- DATALIST_FLAGS_MASK +- DATE_BAD_DAY +- DATE_BAD_JULIAN +- DATE_BAD_YEAR +- DIR_SEPARATOR +- DIR_SEPARATOR_S +- E +- GINT16_FORMAT +- GINT16_MODIFIER +- GINT32_FORMAT +- GINT32_MODIFIER +- GINT64_FORMAT +- GINT64_MODIFIER +- GINTPTR_FORMAT +- GINTPTR_MODIFIER +- GNUC_FUNCTION +- GNUC_PRETTY_FUNCTION +- GSIZE_FORMAT +- GSIZE_MODIFIER +- GSSIZE_FORMAT +- GSSIZE_MODIFIER +- GUINT16_FORMAT +- GUINT32_FORMAT +- GUINT64_FORMAT +- GUINTPTR_FORMAT +- HAVE_GINT64 +- HAVE_GNUC_VARARGS +- HAVE_GNUC_VISIBILITY +- HAVE_GROWING_STACK +- HAVE_ISO_VARARGS +- HOOK_FLAG_USER_SHIFT +- IEEE754_DOUBLE_BIAS +- IEEE754_FLOAT_BIAS +- KEY_FILE_DESKTOP_GROUP +- KEY_FILE_DESKTOP_KEY_ACTIONS +- KEY_FILE_DESKTOP_KEY_CATEGORIES +- KEY_FILE_DESKTOP_KEY_COMMENT +- KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE +- KEY_FILE_DESKTOP_KEY_EXEC +- KEY_FILE_DESKTOP_KEY_GENERIC_NAME +- KEY_FILE_DESKTOP_KEY_HIDDEN +- KEY_FILE_DESKTOP_KEY_ICON +- KEY_FILE_DESKTOP_KEY_MIME_TYPE +- KEY_FILE_DESKTOP_KEY_NAME +- KEY_FILE_DESKTOP_KEY_NO_DISPLAY +- KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN +- KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN +- KEY_FILE_DESKTOP_KEY_PATH +- KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY +- KEY_FILE_DESKTOP_KEY_STARTUP_WM_CLASS +- KEY_FILE_DESKTOP_KEY_TERMINAL +- KEY_FILE_DESKTOP_KEY_TRY_EXEC +- KEY_FILE_DESKTOP_KEY_TYPE +- KEY_FILE_DESKTOP_KEY_URL +- KEY_FILE_DESKTOP_KEY_VERSION +- KEY_FILE_DESKTOP_TYPE_APPLICATION +- KEY_FILE_DESKTOP_TYPE_DIRECTORY +- KEY_FILE_DESKTOP_TYPE_LINK +- LITTLE_ENDIAN +- LN10 +- LN2 +- LOG_2_BASE_10 +- LOG_DOMAIN +- LOG_FATAL_MASK +- LOG_LEVEL_USER_SHIFT +- macro__has_attribute___noreturn__ +- MAXINT16 +- MAXINT32 +- MAXINT64 +- MAXINT8 +- MAXUINT16 +- MAXUINT32 +- MAXUINT64 +- MAXUINT8 +- MININT16 +- MININT32 +- MININT64 +- MININT8 +- PDP_ENDIAN +- OPTION_REMAINING +- MODULE_SUFFIX +- PI +- PI_2 +- PI_4 +- PID_FORMAT +- POLLFD_FORMAT +- SEARCHPATH_SEPARATOR +- SEARCHPATH_SEPARATOR_S +- SIZEOF_LONG +- SIZEOF_SIZE_T +- SIZEOF_SSIZE_T +- SIZEOF_VOID_P +- SQRT2 +- STR_DELIMITERS +- SYSDEF_AF_INET +- SYSDEF_AF_INET6 +- SYSDEF_AF_UNIX +- SYSDEF_MSG_DONTROUTE +- SYSDEF_MSG_OOB +- SYSDEF_MSG_PEEK +- TEST_OPTION_ISOLATE_DIRS +- TIME_SPAN_DAY +- TIME_SPAN_HOUR +- TIME_SPAN_MILLISECOND +- TIME_SPAN_MINUTE +- TIME_SPAN_SECOND +- UNICHAR_MAX_DECOMPOSITION_LENGTH +- URI_RESERVED_CHARS_GENERIC_DELIMITERS +- URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS +- USEC_PER_SEC +- VA_COPY_AS_ARRAY +- VERSION_MIN_REQUIRED +- WIN32_MSG_HANDLE -# All GLib class and functions that have Crystal versions in the stdlib must be removed from bindings -ignore: - - ErrorCopyFunc - - Array - - AsyncQueue - - BookmarkFile - - ByteArray - - Checksum - - ChecksumType - - Cond - - Data - - Date - - DateTime - - DebugKey - - Dir - - g_access - - g_aligned_alloc - - g_aligned_alloc0 - - g_aligned_free - - g_ascii_digit_value - - g_ascii_dtostr - - g_ascii_formatd - - g_ascii_strcasecmp - - g_ascii_strdown - - g_ascii_string_to_signed - - g_ascii_string_to_unsigned - - g_ascii_strncasecmp - - g_ascii_strtod - - g_ascii_strtoll - - g_ascii_strtoull - - g_ascii_strup - - g_ascii_tolower - - g_ascii_toupper - - g_ascii_xdigit_value - - g_assertion_message - - g_assertion_message_cmpstr - - g_assertion_message_cmpstrv - - g_assertion_message_error - - g_assert_warning - - g_atexit - - g_atomic_int_add - - g_atomic_int_and - - g_atomic_int_compare_and_exchange - - g_atomic_int_dec_and_test - - g_atomic_int_exchange_and_add - - g_atomic_int_get - - g_atomic_int_inc - - g_atomic_int_or - - g_atomic_int_set - - g_atomic_int_xor - - g_atomic_pointer_add - - g_atomic_pointer_and - - g_atomic_pointer_compare_and_exchange - - g_atomic_pointer_get - - g_atomic_pointer_or - - g_atomic_pointer_set - - g_atomic_pointer_xor - - g_atomic_rc_box_acquire - - g_atomic_rc_box_alloc - - g_atomic_rc_box_alloc0 - - g_atomic_rc_box_dup - - g_atomic_rc_box_get_size - - g_atomic_rc_box_release - - g_atomic_rc_box_release_full - - g_atomic_ref_count_compare - - g_atomic_ref_count_dec - - g_atomic_ref_count_inc - - g_atomic_ref_count_init - - g_base64_decode - - g_base64_decode_inplace - - g_base64_encode - - g_base64_encode_close - - g_base64_encode_step - - g_basename - - g_bit_lock - - g_bit_trylock - - g_bit_unlock - - g_bookmark_file_error_quark - - g_build_filenamev - - g_build_pathv - - g_byte_array_free - - g_byte_array_free_to_bytes - - g_byte_array_new - - g_byte_array_new_take - - g_byte_array_steal - - g_byte_array_unref - - g_bytes_new - - g_bytes_new_take - - g_bytes_ref - - g_bytes_unref - - g_bytes_unref_to_array - - g_bytes_unref_to_data - - g_canonicalize_filename - - g_chdir - - g_checksum_type_get_length - - g_child_watch_add_full - - g_child_watch_source_new - - g_clear_error - - g_close - - g_compute_checksum_for_bytes - - g_compute_checksum_for_data - - g_compute_checksum_for_string - - g_compute_hmac_for_bytes - - g_compute_hmac_for_data - - g_compute_hmac_for_string - - g_convert - - g_convert_error_quark - - g_convert_with_fallback - - g_datalist_foreach - - g_datalist_get_data - - g_datalist_get_flags - - g_datalist_id_get_data - - g_datalist_set_flags - - g_datalist_unset_flags - - g_dataset_destroy - - g_dataset_foreach - - g_dataset_id_get_data - - g_date_get_days_in_month - - g_date_get_monday_weeks_in_year - - g_date_get_sunday_weeks_in_year - - g_date_is_leap_year - - g_date_strftime - - g_date_valid_day - - g_date_valid_dmy - - g_date_valid_julian - - g_date_valid_month - - g_date_valid_weekday - - g_date_valid_year - - g_dcgettext - - g_dgettext - - g_direct_equal - - g_direct_hash - - g_dir_make_tmp - - g_dngettext - - g_double_equal - - g_double_hash - - g_dpgettext - - g_dpgettext2 - - g_environ_getenv - - g_environ_setenv - - g_environ_unsetenv - - g_error_copy - - g_file_error_from_errno - - g_file_error_quark - - g_file_get_contents - - g_filename_display_basename - - g_filename_display_name - - g_filename_from_uri - - g_filename_from_utf8 - - g_filename_to_uri - - g_filename_to_utf8 - - g_file_open_tmp - - g_file_read_link - - g_file_set_contents - - g_file_set_contents_full - - g_file_test - - g_find_program_in_path - - g_format_size - - g_format_size_full - - g_free - - g_get_charset - - g_get_codeset - - g_get_console_charset - - g_get_current_dir - - g_get_current_time - - g_getenv - - g_get_environ - - g_get_filename_charsets - - g_get_home_dir - - g_get_host_name - - g_get_language_names - - g_get_language_names_with_category - - g_get_locale_variants - - g_get_monotonic_time - - g_get_num_processors - - g_get_real_name - - g_get_real_time - - g_get_tmp_dir - - g_get_user_name - - g_hash_table_add - - g_hash_table_contains - - g_hash_table_destroy - - g_hash_table_insert - - g_hash_table_lookup - - g_hash_table_lookup_extended - - g_hash_table_new_similar - - g_hash_table_remove - - g_hash_table_remove_all - - g_hash_table_replace - - g_hash_table_size - - g_hash_table_steal - - g_hash_table_steal_all - - g_hash_table_steal_extended - - g_hash_table_unref - - g_hook_destroy - - g_hook_destroy_link - - g_hook_free - - g_hook_insert_before - - g_hook_prepend - - g_hook_unref - - g_hostname_is_ascii_encoded - - g_hostname_is_ip_address - - g_hostname_is_non_ascii - - g_hostname_to_ascii - - g_hostname_to_unicode - - g_idle_add_full - - g_int64_equal - - g_int64_hash - - g_int_equal - - g_intern_static_string - - g_intern_string - - g_int_hash - - g_io_add_watch_full - - g_io_channel_error_from_errno - - g_io_channel_error_quark - - g_io_create_watch - - g_key_file_error_quark - - g_key_file_load_from_bytes - - g_listenv - - g_locale_from_utf8 - - g_locale_to_utf8 - - g_log_default_handler - - g_log_get_debug_enabled - - g_log_remove_handler - - g_log_set_always_fatal - - g_log_set_debug_enabled - - g_log_set_fatal_mask - - g_log_set_handler_full - - g_log_set_writer_func - - g_log_structured_array - - g_log_variant - - g_log_writer_default - - g_log_writer_default_set_use_stderr - - g_log_writer_default_would_drop - - g_log_writer_format_fields - - g_log_writer_is_journald - - g_log_writer_journald - - g_log_writer_standard_streams - - g_log_writer_supports_color - - g_main_context_add_poll - - g_main_context_remove_poll - - g_malloc - - g_malloc0 - - g_malloc0_n - - g_malloc_n - - g_markup_error_quark - - g_memdup - - g_memdup2 - - g_mem_is_system_malloc - - g_mem_profile - - g_mem_set_vtable - - g_mkdir_with_parents - - g_nullify_pointer - - g_number_parser_error_quark - - g_once_init_enter - - g_once_init_leave - - g_option_error_quark - - g_parse_debug_string - - g_path_get_basename - - g_path_get_dirname - - g_path_is_absolute - - g_path_skip_root - - g_pattern_match - - g_pattern_match_simple - - g_pattern_match_string - - g_pattern_spec_free - - g_pointer_bit_lock - - g_pointer_bit_trylock - - g_pointer_bit_unlock - - g_poll - - g_prefix_error_literal - - g_propagate_error - - g_quark_from_static_string - - g_random_double - - g_random_double_range - - g_random_int - - g_random_int_range - - g_random_set_seed - - g_rc_box_acquire - - g_rc_box_alloc - - g_rc_box_alloc0 - - g_rc_box_dup - - g_rc_box_get_size - - g_rc_box_release - - g_rc_box_release_full - - g_realloc - - g_realloc_n - - g_ref_count_compare - - g_ref_count_dec - - g_ref_count_inc - - g_ref_count_init - - g_ref_string_acquire - - g_ref_string_length - - g_ref_string_new - - g_ref_string_new_intern - - g_ref_string_new_len - - g_ref_string_release - - g_regex_check_replacement - - g_regex_error_quark - - g_regex_escape_nul - - g_regex_escape_string - - g_regex_match_simple - - g_regex_split_simple - - g_rmdir - - g_sequence_get - - g_sequence_insert_before - - g_sequence_move - - g_sequence_move_range - - g_sequence_range_get_midpoint - - g_sequence_remove - - g_sequence_remove_range - - g_sequence_set - - g_sequence_swap - - g_setenv - - g_set_error_literal - - g_shell_error_quark - - g_shell_parse_argv - - g_shell_quote - - g_shell_unquote - - g_slice_alloc - - g_slice_alloc0 - - g_slice_copy - - g_slice_free1 - - g_slice_free_chain_with_offset - - g_slice_get_config - - g_slice_get_config_state - - g_slice_set_config - - g_source_add_poll - - g_source_remove_by_funcs_user_data - - g_source_remove_by_user_data - - g_source_remove_poll - - g_spawn_async - - g_spawn_async_with_fds - - g_spawn_async_with_pipes - - g_spawn_async_with_pipes_and_fds - - g_spawn_check_exit_status - - g_spawn_check_wait_status - - g_spawn_close_pid - - g_spawn_command_line_async - - g_spawn_command_line_sync - - g_spawn_error_quark - - g_spawn_exit_error_quark - - g_spawn_sync - - g_stpcpy - - g_strcanon - - g_strcasecmp - - g_strchomp - - g_strchug - - g_strcmp0 - - g_strcompress - - g_strdelimit - - g_strdown - - g_strdup - - g_str_equal - - g_strerror - - g_strescape - - g_strfreev - - g_str_hash - - g_str_has_prefix - - g_str_has_suffix - - g_strip_context - - g_str_is_ascii - - g_strjoinv - - g_strlcat - - g_strlcpy - - g_str_match_string - - g_strncasecmp - - g_strndup - - g_strnfill - - g_strreverse - - g_strrstr - - g_strrstr_len - - g_strsignal - - g_strstr_len - - g_str_to_ascii - - g_strtod - - g_str_tokenize_and_fold - - g_strup - - g_strv_contains - - g_strv_equal - - g_strv_get_type - - g_strv_length - - g_test_add_data_func - - g_test_add_data_func_full - - g_test_add_func - - g_test_assert_expected_messages_internal - - g_test_bug - - g_test_bug_base - - g_test_expect_message - - g_test_fail - - g_test_failed - - g_test_get_dir - - g_test_get_path - - g_test_incomplete - - g_test_log_type_name - - g_test_queue_destroy - - g_test_queue_free - - g_test_rand_double - - g_test_rand_double_range - - g_test_rand_int - - g_test_rand_int_range - - g_test_run - - g_test_run_suite - - g_test_set_nonfatal_assertions - - g_test_skip - - g_test_subprocess - - g_test_summary - - g_test_timer_elapsed - - g_test_timer_last - - g_test_timer_start - - g_test_trap_assertions - - g_test_trap_fork - - g_test_trap_has_passed - - g_test_trap_reached_timeout - - g_test_trap_subprocess - - g_thread_error_quark - - g_thread_exit - - g_thread_pool_get_max_idle_time - - g_thread_pool_get_max_unused_threads - - g_thread_pool_get_num_unused_threads - - g_thread_pool_set_max_idle_time - - g_thread_pool_set_max_unused_threads - - g_thread_pool_stop_unused_threads - - g_thread_self - - g_thread_yield - - g_timeout_add_full - - g_timeout_add_seconds_full - - g_time_val_from_iso8601 - - g_trash_stack_height - - g_trash_stack_peek - - g_trash_stack_pop - - g_trash_stack_push - - g_try_malloc - - g_try_malloc0 - - g_try_malloc0_n - - g_try_malloc_n - - g_try_realloc - - g_try_realloc_n - - g_ucs4_to_utf16 - - g_ucs4_to_utf8 - - g_unichar_break_type - - g_unichar_combining_class - - g_unichar_compose - - g_unichar_decompose - - g_unichar_digit_value - - g_unichar_fully_decompose - - g_unichar_get_mirror_char - - g_unichar_get_script - - g_unichar_isalnum - - g_unichar_isalpha - - g_unichar_iscntrl - - g_unichar_isdefined - - g_unichar_isdigit - - g_unichar_isgraph - - g_unichar_islower - - g_unichar_ismark - - g_unichar_isprint - - g_unichar_ispunct - - g_unichar_isspace - - g_unichar_istitle - - g_unichar_isupper - - g_unichar_iswide - - g_unichar_iswide_cjk - - g_unichar_isxdigit - - g_unichar_iszerowidth - - g_unichar_tolower - - g_unichar_totitle - - g_unichar_toupper - - g_unichar_to_utf8 - - g_unichar_type - - g_unichar_validate - - g_unichar_xdigit_value - - g_unicode_canonical_decomposition - - g_unicode_canonical_ordering - - g_unicode_script_from_iso15924 - - g_unicode_script_to_iso15924 - - g_unix_error_quark - - g_unix_fd_add_full - - g_unix_fd_source_new - - g_unix_get_passwd_entry - - g_unix_open_pipe - - g_unix_set_fd_nonblocking - - g_unix_signal_add_full - - g_unix_signal_source_new - - g_unlink - - g_unsetenv - - g_uri_build - - g_uri_build_with_user - - g_uri_error_quark - - g_uri_escape_bytes - - g_uri_escape_string - - g_uri_is_valid - - g_uri_join - - g_uri_join_with_user - - g_uri_list_extract_uris - - g_uri_parse - - g_uri_parse_params - - g_uri_parse_scheme - - g_uri_peek_scheme - - g_uri_resolve_relative - - g_uri_split - - g_uri_split_network - - g_uri_split_with_user - - g_uri_unescape_bytes - - g_uri_unescape_segment - - g_uri_unescape_string - - g_usleep - - g_utf16_to_ucs4 - - g_utf16_to_utf8 - - g_utf8_casefold - - g_utf8_collate - - g_utf8_collate_key - - g_utf8_collate_key_for_filename - - g_utf8_find_next_char - - g_utf8_find_prev_char - - g_utf8_get_char - - g_utf8_get_char_validated - - g_utf8_make_valid - - g_utf8_normalize - - g_utf8_offset_to_pointer - - g_utf8_pointer_to_offset - - g_utf8_prev_char - - g_utf8_strchr - - g_utf8_strdown - - g_utf8_strlen - - g_utf8_strncpy - - g_utf8_strrchr - - g_utf8_strreverse - - g_utf8_strup - - g_utf8_substring - - g_utf8_to_ucs4 - - g_utf8_to_ucs4_fast - - g_utf8_to_utf16 - - g_utf8_validate - - g_utf8_validate_len - - g_uuid_string_is_valid - - g_uuid_string_random - - HashTable - - HashTableIter - - Hmac - - Hook - - HookList - - IOChannel - - IOFuncs - - KeyFile - - List - - LogField - - MatchInfo - - MemVTable - - Node - - Once - - OptionContext - - OptionEntry - - OptionGroup - - PatternSpec - - PollFD - - Private - - PtrArray - - Queue - - Rand - - RecMutex - - Regex - - RWLock - - Scanner - - ScannerConfig - - Sequence - - SequenceIter - - SList - - SourcePrivate - - StatBuf - - String - - StringChunk - - StrvBuilder - - TestCase - - TestConfig - - TestLogBuffer - - TestLogMsg - - TestSuite - - Thread - - ThreadPool - - Timer - - TimeVal - - TimeZone - - TrashStack - - Tree - - TreeNode - - Uri - - UriParamsIter - - HookCheckMarshaller - - HookCompareFunc - - HookFinalizeFunc - - HookFindFunc - - HookMarshaller - - IOFunc - - LogFunc - - LogWriterFunc - - NodeForeachFunc - - OptionErrorFunc - - RegexEvalCallback - - ScannerMsgFunc - - SequenceIterCompareFunc - - OptionParseFunc - - PollFunc - - TraverseNodeFunc - - NodeTraverseFunc - - HookFunc +types: + Array: + ignore: true + AsyncQueue: + ignore: true + Variant: + handmade: true + BookmarkFile: + ignore: true + ByteArray: + ignore: true + Bytes: + ignore_methods: + - new + - new_take + - ref + - unref + - unref_to_array + - unref_to_data + Checksum: + ignore: true + ChecksumType: + ignore: true + Cond: + ignore: true + Data: + ignore: true + Date: + ignore: true + DateTime: + ignore: true + DebugKey: + ignore: true + Dir: + ignore: true + ErrorCopyFunc: + ignore: true + GLib: + ignore_methods: + - access + - aligned_alloc + - aligned_alloc0 + - aligned_free + - ascii_digit_value + - ascii_dtostr + - ascii_formatd + - ascii_strcasecmp + - ascii_strdown + - ascii_string_to_signed + - ascii_string_to_unsigned + - ascii_strncasecmp + - ascii_strtod + - ascii_strtoll + - ascii_strtoull + - ascii_strup + - ascii_tolower + - ascii_toupper + - ascii_xdigit_value + - assertion_message + - assertion_message_cmpstr + - assertion_message_cmpstrv + - assertion_message_error + - assert_warning + - atexit + - atomic_int_add + - atomic_int_and + - atomic_int_compare_and_exchange + - atomic_int_dec_and_test + - atomic_int_exchange_and_add + - atomic_int_get + - atomic_int_inc + - atomic_int_or + - atomic_int_set + - atomic_int_xor + - atomic_pointer_add + - atomic_pointer_and + - atomic_pointer_compare_and_exchange + - atomic_pointer_get + - atomic_pointer_or + - atomic_pointer_set + - atomic_pointer_xor + - atomic_rc_box_acquire + - atomic_rc_box_alloc + - atomic_rc_box_alloc0 + - atomic_rc_box_dup + - atomic_rc_box_get_size + - atomic_rc_box_release + - atomic_rc_box_release_full + - atomic_ref_count_compare + - atomic_ref_count_dec + - atomic_ref_count_inc + - atomic_ref_count_init + - base64_decode + - base64_decode_inplace + - base64_encode + - base64_encode_close + - base64_encode_step + - basename + - bit_lock + - bit_trylock + - bit_unlock + - bookmark_file_error_quark + - build_filenamev + - build_pathv + - byte_array_free + - byte_array_free_to_bytes + - byte_array_new + - byte_array_new_take + - byte_array_steal + - byte_array_unref + - canonicalize_filename + - chdir + - checksum_type_get_length + - child_watch_add + - child_watch_source_new + - clear_error + - close + - compute_checksum_for_bytes + - compute_checksum_for_data + - compute_checksum_for_string + - compute_hmac_for_bytes + - compute_hmac_for_data + - compute_hmac_for_string + - convert + - convert_error_quark + - convert_with_fallback + - datalist_foreach + - datalist_get_data + - datalist_get_flags + - datalist_id_get_data + - datalist_set_flags + - datalist_unset_flags + - dataset_destroy + - dataset_foreach + - dataset_id_get_data + - date_get_days_in_month + - date_get_monday_weeks_in_year + - date_get_sunday_weeks_in_year + - date_is_leap_year + - date_strftime + - date_valid_day + - date_valid_dmy + - date_valid_julian + - date_valid_month + - date_valid_weekday + - date_valid_year + - dcgettext + - dgettext + - direct_equal + - direct_hash + - dir_make_tmp + - dngettext + - double_equal + - double_hash + - dpgettext + - dpgettext2 + - environ_getenv + - environ_setenv + - environ_unsetenv + - error_copy + - file_error_from_errno + - file_error_quark + - file_get_contents + - filename_display_basename + - filename_display_name + - filename_from_uri + - filename_from_utf8 + - filename_to_uri + - filename_to_utf8 + - file_open_tmp + - file_read_link + - file_set_contents + - file_set_contents_full + - file_test + - find_program_in_path + - format_size + - format_size_full + - free + - get_charset + - get_codeset + - get_console_charset + - get_current_dir + - get_current_time + - getenv + - get_environ + - get_filename_charsets + - get_home_dir + - get_host_name + - get_language_names + - get_language_names_with_category + - get_locale_variants + - get_monotonic_time + - get_num_processors + - get_real_name + - get_real_time + - get_tmp_dir + - get_user_name + - hash_table_add + - hash_table_contains + - hash_table_destroy + - hash_table_insert + - hash_table_lookup + - hash_table_lookup_extended + - hash_table_new_similar + - hash_table_remove + - hash_table_remove_all + - hash_table_replace + - hash_table_size + - hash_table_steal + - hash_table_steal_all + - hash_table_steal_extended + - hash_table_unref + - hook_destroy + - hook_destroy_link + - hook_free + - hook_insert_before + - hook_prepend + - hook_unref + - hostname_is_ascii_encoded + - hostname_is_ip_address + - hostname_is_non_ascii + - hostname_to_ascii + - hostname_to_unicode + - idle_add + - int64_equal + - int64_hash + - int_equal + - intern_static_string + - intern_string + - int_hash + - io_add_watch + - io_channel_error_from_errno + - io_channel_error_quark + - io_create_watch + - key_file_error_quark + - key_file_load_from_bytes + - listenv + - locale_from_utf8 + - locale_to_utf8 + - log_default_handler + - log_get_debug_enabled + - log_remove_handler + - log_set_always_fatal + - log_set_debug_enabled + - log_set_fatal_mask + - log_set_handler + - log_set_writer_func + - log_structured_array + - log_variant + - log_writer_default + - log_writer_default_set_use_stderr + - log_writer_default_would_drop + - log_writer_format_fields + - log_writer_is_journald + - log_writer_journald + - log_writer_standard_streams + - log_writer_supports_color + - main_context_add_poll + - main_context_remove_poll + - malloc + - malloc0 + - malloc0_n + - malloc_n + - markup_error_quark + - memdup + - memdup2 + - mem_is_system_malloc + - mem_profile + - mem_set_vtable + - mkdir_with_parents + - nullify_pointer + - number_parser_error_quark + - once_init_enter + - once_init_leave + - option_error_quark + - parse_debug_string + - path_get_basename + - path_get_dirname + - path_is_absolute + - path_skip_root + - pattern_match + - pattern_match_simple + - pattern_match_string + - pattern_spec_free + - pointer_bit_lock + - pointer_bit_trylock + - pointer_bit_unlock + - poll + - prefix_error_literal + - propagate_error + - quark_from_static_string + - random_double + - random_double_range + - random_int + - random_int_range + - random_set_seed + - rc_box_acquire + - rc_box_alloc + - rc_box_alloc0 + - rc_box_dup + - rc_box_get_size + - rc_box_release + - rc_box_release_full + - realloc + - realloc_n + - ref_count_compare + - ref_count_dec + - ref_count_inc + - ref_count_init + - ref_string_acquire + - ref_string_length + - ref_string_new + - ref_string_new_intern + - ref_string_new_len + - ref_string_release + - regex_check_replacement + - regex_error_quark + - regex_escape_nul + - regex_escape_string + - regex_match_simple + - regex_split_simple + - rmdir + - sequence_get + - sequence_insert_before + - sequence_move + - sequence_move_range + - sequence_range_get_midpoint + - sequence_remove + - sequence_remove_range + - sequence_set + - sequence_swap + - setenv + - set_error_literal + - shell_error_quark + - shell_parse_argv + - shell_quote + - shell_unquote + - slice_alloc + - slice_alloc0 + - slice_copy + - slice_free1 + - slice_free_chain_with_offset + - slice_get_config + - slice_get_config_state + - slice_set_config + - source_add_poll + - source_remove_by_funcs_user_data + - source_remove_by_user_data + - source_remove_poll + - spawn_async + - spawn_async_with_fds + - spawn_async_with_pipes + - spawn_async_with_pipes_and_fds + - spawn_check_exit_status + - spawn_check_wait_status + - spawn_close_pid + - spawn_command_line_async + - spawn_command_line_sync + - spawn_error_quark + - spawn_exit_error_quark + - spawn_sync + - stpcpy + - strcanon + - strcasecmp + - strchomp + - strchug + - strcmp0 + - strcompress + - strdelimit + - strdown + - strdup + - str_equal + - strerror + - strescape + - strfreev + - str_hash + - str_has_prefix + - str_has_suffix + - strip_context + - str_is_ascii + - strjoinv + - strlcat + - strlcpy + - str_match_string + - strncasecmp + - strndup + - strnfill + - strreverse + - strrstr + - strrstr_len + - strsignal + - strstr_len + - str_to_ascii + - strtod + - str_tokenize_and_fold + - strup + - strv_contains + - strv_equal + - strv_get_type + - strv_length + - test_add_data_func + - test_add_data_func_full + - test_add_func + - test_assert_expected_messages_internal + - test_bug + - test_bug_base + - test_expect_message + - test_fail + - test_failed + - test_get_dir + - test_get_path + - test_incomplete + - test_log_type_name + - test_queue_destroy + - test_queue_free + - test_rand_double + - test_rand_double_range + - test_rand_int + - test_rand_int_range + - test_run + - test_run_suite + - test_set_nonfatal_assertions + - test_skip + - test_subprocess + - test_summary + - test_timer_elapsed + - test_timer_last + - test_timer_start + - test_trap_assertions + - test_trap_fork + - test_trap_has_passed + - test_trap_reached_timeout + - test_trap_subprocess + - thread_error_quark + - thread_exit + - thread_pool_get_max_idle_time + - thread_pool_get_max_unused_threads + - thread_pool_get_num_unused_threads + - thread_pool_set_max_idle_time + - thread_pool_set_max_unused_threads + - thread_pool_stop_unused_threads + - thread_self + - thread_yield + - timeout_add + - timeout_add_seconds + - time_val_from_iso8601 + - trash_stack_height + - trash_stack_peek + - trash_stack_pop + - trash_stack_push + - try_malloc + - try_malloc0 + - try_malloc0_n + - try_malloc_n + - try_realloc + - try_realloc_n + - ucs4_to_utf16 + - ucs4_to_utf8 + - unichar_break_type + - unichar_combining_class + - unichar_compose + - unichar_decompose + - unichar_digit_value + - unichar_fully_decompose + - unichar_get_mirror_char + - unichar_get_script + - unichar_isalnum + - unichar_isalpha + - unichar_iscntrl + - unichar_isdefined + - unichar_isdigit + - unichar_isgraph + - unichar_islower + - unichar_ismark + - unichar_isprint + - unichar_ispunct + - unichar_isspace + - unichar_istitle + - unichar_isupper + - unichar_iswide + - unichar_iswide_cjk + - unichar_isxdigit + - unichar_iszerowidth + - unichar_tolower + - unichar_totitle + - unichar_toupper + - unichar_to_utf8 + - unichar_type + - unichar_validate + - unichar_xdigit_value + - unicode_canonical_decomposition + - unicode_canonical_ordering + - unicode_script_from_iso15924 + - unicode_script_to_iso15924 + - unix_error_quark + - unix_fd_add_full + - unix_fd_source_new + - unix_get_passwd_entry + - unix_open_pipe + - unix_set_fd_nonblocking + - unix_signal_add + - unix_signal_source_new + - unlink + - unsetenv + - uri_build + - uri_build_with_user + - uri_error_quark + - uri_escape_bytes + - uri_escape_string + - uri_is_valid + - uri_join + - uri_join_with_user + - uri_list_extract_uris + - uri_parse + - uri_parse_params + - uri_parse_scheme + - uri_peek_scheme + - uri_resolve_relative + - uri_split + - uri_split_network + - uri_split_with_user + - uri_unescape_bytes + - uri_unescape_segment + - uri_unescape_string + - usleep + - utf16_to_ucs4 + - utf16_to_utf8 + - utf8_casefold + - utf8_collate + - utf8_collate_key + - utf8_collate_key_for_filename + - utf8_find_next_char + - utf8_find_prev_char + - utf8_get_char + - utf8_get_char_validated + - utf8_make_valid + - utf8_normalize + - utf8_offset_to_pointer + - utf8_pointer_to_offset + - utf8_prev_char + - utf8_strchr + - utf8_strdown + - utf8_strlen + - utf8_strncpy + - utf8_strrchr + - utf8_strreverse + - utf8_strup + - utf8_substring + - utf8_to_ucs4 + - utf8_to_ucs4_fast + - utf8_to_utf16 + - utf8_validate + - utf8_validate_len + - uuid_string_is_valid + - uuid_string_random + HashTable: + ignore: true + HashTableIter: + ignore: true + Hmac: + ignore: true + Hook: + ignore: true + HookList: + ignore: true + IOChannel: + ignore: true + IOFuncs: + ignore: true + KeyFile: + ignore: true + List: + ignore: true + LogField: + ignore: true + MainContext: + ignore_methods: + - add_poll + - remove_poll + MatchInfo: + ignore: true + MemVTable: + ignore: true + Node: + ignore: true + Once: + ignore: true + OptionContext: + ignore: true + OptionEntry: + ignore: true + OptionGroup: + ignore: true + PatternSpec: + ignore: true + PollFD: + ignore: true + Private: + ignore: true + PtrArray: + ignore: true + Queue: + ignore: true + Rand: + ignore: true + RecMutex: + ignore: true + Regex: + ignore: true + RWLock: + ignore: true + Scanner: + ignore: true + ScannerConfig: + ignore: true + Sequence: + ignore: true + SequenceIter: + ignore: true + SList: + ignore: true + Source: + ignore_methods: + - add_poll + - remove_poll + - remove_by_funcs_user_data + - remove_by_user_data + SourcePrivate: + ignore: true + StatBuf: + ignore: true + String: + ignore: true + StringChunk: + ignore: true + StrvBuilder: + ignore: true + TestCase: + ignore: true + TestConfig: + ignore: true + TestLogBuffer: + ignore: true + TestLogMsg: + ignore: true + TestSuite: + ignore: true + Thread: + ignore: true + ThreadPool: + ignore: true + Timer: + ignore: true + TimeVal: + ignore: true + TimeZone: + ignore: true + TrashStack: + ignore: true + Tree: + ignore: true + TreeNode: + ignore: true + Uri: + ignore: true + UriParamsIter: + ignore: true + HookCheckMarshaller: + ignore: true + HookCompareFunc: + ignore: true + HookFinalizeFunc: + ignore: true + HookFindFunc: + ignore: true + HookMarshaller: + ignore: true + IOFunc: + ignore: true + LogFunc: + ignore: true + LogWriterFunc: + ignore: true + NodeForeachFunc: + ignore: true + OptionErrorFunc: + ignore: true + RegexEvalCallback: + ignore: true + ScannerMsgFunc: + ignore: true + SequenceIterCompareFunc: + ignore: true + OptionParseFunc: + ignore: true + PollFunc: + ignore: true + TraverseNodeFunc: + ignore: true + NodeTraverseFunc: + ignore: true + HookFunc: + ignore: true execute_callback: - g_main_loop_run diff --git a/src/bindings/g_lib/g_lib.cr b/src/bindings/g_lib/g_lib.cr new file mode 100644 index 0000000..2dc04d8 --- /dev/null +++ b/src/bindings/g_lib/g_lib.cr @@ -0,0 +1,9 @@ +module GLib + # Gets the real name of the user. This usually comes from the user’s entry in the passwd file. + # If the real user name cannot be determined, the string “Unknown” is returned. + # The real user name is always interpreted as a UTF-8 string with invalid bytes removed. + def real_name : String + _retval = LibGLib.g_get_real_name + ::String.new(_retval).scrub + end +end diff --git a/src/bindings/g_lib/lib_g_lib.cr b/src/bindings/g_lib/lib_g_lib.cr index db62791..d5a3a99 100644 --- a/src/bindings/g_lib/lib_g_lib.cr +++ b/src/bindings/g_lib/lib_g_lib.cr @@ -18,4 +18,9 @@ lib LibGLib fun g_slist_nth(list : SList*, n : UInt32) : SList* fun g_slist_free(list : SList*) fun g_slist_free_full(list : SList*, free_func : Proc(Void*, Nil)) + + # GBytes + # This function is used by some bindings of other modules like + # GTK widget template and Gio GResource. + fun g_bytes_new_static(data : Void*, size : LibC::SizeT) : Void* end diff --git a/src/bindings/g_lib/list.cr b/src/bindings/g_lib/list.cr index 128fd92..329657d 100644 --- a/src/bindings/g_lib/list.cr +++ b/src/bindings/g_lib/list.cr @@ -59,15 +59,17 @@ module GLib {% if T == String %} T.new(data.as(Pointer(LibC::Char))) + {% elsif T.module? %} + {{ T.name.stringify.gsub(/(.*)(::)([^:]*)\z/, "\\1::Abstract\\3").id }}.new(data, :none) {% else %} - T.new(data) + T.new(data, :none) {% end %} end def finalize if @transfer.full? - {% if T > GObject::Object %} - LibGLib.g_list_free_full(self, ->LibGLib.g_object_unref) + {% if T <= GObject::Object || T.module? %} + LibGLib.g_list_free_full(self, ->LibGObject.g_object_unref) {% else %} LibGLib.g_list_free_full(self, ->LibGLib.g_free) {% end %} diff --git a/src/bindings/g_lib/slist.cr b/src/bindings/g_lib/slist.cr index 19db88f..50b7b49 100644 --- a/src/bindings/g_lib/slist.cr +++ b/src/bindings/g_lib/slist.cr @@ -37,15 +37,17 @@ module GLib {% if T == String %} T.new(data.as(Pointer(LibC::Char))) + {% elsif T.module? %} + {{ T.name.stringify.gsub(/(.*)(::)([^:]*)\z/, "\\1::Abstract\\3").id }}.new(data, :none) {% else %} - T.new(data) + T.new(data, :none) {% end %} end def finalize if @transfer.full? - {% if T > GObject::Object %} - LibGLib.g_slist_free_full(self, ->LibGLib.g_object_unref) + {% if T <= GObject::Object || T.module? %} + LibGLib.g_slist_free_full(self, ->LibGObject.g_object_unref) {% else %} LibGLib.g_slist_free_full(self, ->LibGLib.g_free) {% end %} diff --git a/src/bindings/g_object/binding.yml b/src/bindings/g_object/binding.yml index aa3b48a..adf2e75 100644 --- a/src/bindings/g_object/binding.yml +++ b/src/bindings/g_object/binding.yml @@ -1,187 +1,272 @@ namespace: GObject version: "2.0" -include_before: +require_before: + - gc.cr - enum.cr - lib_g_object.cr - type.cr - signal.cr - - object.cr -include: +require_after: - param_spec.cr + - object.cr - value.cr - signal_connection.cr - gi_crystal.cr -handmade: - - Value +ignore_constants: +- PARAM_MASK +- PARAM_STATIC_STRINGS +- PARAM_USER_SHIFT +- SIGNAL_FLAGS_MASK +- SIGNAL_MATCH_MASK +- TYPE_FLAG_RESERVED_ID_BIT +- TYPE_FUNDAMENTAL_MAX +- TYPE_FUNDAMENTAL_SHIFT +- TYPE_RESERVED_BSE_FIRST +- TYPE_RESERVED_BSE_LAST +- TYPE_RESERVED_GLIB_FIRST +- TYPE_RESERVED_GLIB_LAST +- TYPE_RESERVED_USER_FIRST +- VALUE_INTERNED_STRING +- VALUE_NOCOPY_CONTENTS -ignore: - - g_boxed_copy - - g_boxed_free - - g_cclosure_marshal_BOOLEAN__BOXED_BOXED - - g_cclosure_marshal_BOOLEAN__FLAGS - - g_cclosure_marshal_generic - - g_cclosure_marshal_STRING__OBJECT_POINTER - - g_cclosure_marshal_VOID__BOOLEAN - - g_cclosure_marshal_VOID__BOXED - - g_cclosure_marshal_VOID__CHAR - - g_cclosure_marshal_VOID__DOUBLE - - g_cclosure_marshal_VOID__ENUM - - g_cclosure_marshal_VOID__FLAGS - - g_cclosure_marshal_VOID__FLOAT - - g_cclosure_marshal_VOID__INT - - g_cclosure_marshal_VOID__LONG - - g_cclosure_marshal_VOID__OBJECT - - g_cclosure_marshal_VOID__PARAM - - g_cclosure_marshal_VOID__POINTER - - g_cclosure_marshal_VOID__STRING - - g_cclosure_marshal_VOID__UCHAR - - g_cclosure_marshal_VOID__UINT - - g_cclosure_marshal_VOID__UINT_POINTER - - g_cclosure_marshal_VOID__ULONG - - g_cclosure_marshal_VOID__VARIANT - - g_cclosure_marshal_VOID__VOID - - g_clear_signal_handler - - g_enum_complete_type_info - - g_enum_get_value - - g_enum_get_value_by_name - - g_enum_get_value_by_nick - - g_enum_register_static - - g_enum_to_string - - g_flags_complete_type_info - - g_flags_get_first_value - - g_flags_get_value_by_name - - g_flags_get_value_by_nick - - g_flags_register_static - - g_flags_to_string - - g_gtype_get_type - - g_object_interface_install_property - - g_object_ref - - g_object_is_floating - - g_object_ref_sink - - g_object_force_floating - - g_object_unref - - g_param_spec_boxed - - g_param_type_register_static - - g_param_value_convert - - g_param_value_defaults - - g_param_values_cmp - - g_param_value_set_default - - g_param_value_validate - - g_pointer_type_register_static - - g_signal_accumulator_first_wins - - g_signal_accumulator_true_handled - - g_signal_add_emission_hook - - g_signal_chain_from_overridden - - g_signal_connect_closure - - g_signal_connect_closure_by_id - - g_signal_emitv - - g_signal_get_invocation_hint - - g_signal_handler_block - - g_signal_handler_disconnect - - g_signal_handler_find - - g_signal_handler_is_connected - - g_signal_handlers_block_matched - - g_signal_handlers_destroy - - g_signal_handlers_disconnect_matched - - g_signal_handlers_unblock_matched - - g_signal_handler_unblock - - g_signal_has_handler_pending - - g_signal_is_valid_name - - g_signal_list_ids - - g_signal_lookup - - g_signal_name - - g_signal_override_class_closure - - g_signal_parse_name - - g_signal_query - - g_signal_remove_emission_hook - - g_signal_set_va_marshaller - - g_signal_stop_emission - - g_signal_stop_emission_by_name - - g_signal_type_cclosure_new - - g_source_set_closure - - g_source_set_dummy_callback - - g_strdup_value_contents - - g_type_add_class_private - - g_type_add_instance_private - - g_type_add_interface_dynamic - - g_type_add_interface_static - - g_type_check_class_is_a - - g_type_check_instance - - g_type_check_instance_is_a - - g_type_check_instance_is_fundamentally_a - - g_type_check_is_value_type - - g_type_check_value - - g_type_check_value_holds - - g_type_children - - g_type_class_adjust_private_offset - - g_type_class_peek - - g_type_class_peek_static - - g_type_class_ref - - g_type_default_interface_peek - - g_type_default_interface_ref - - g_type_default_interface_unref - - g_type_depth - - g_type_ensure - - g_type_free_instance - - g_type_from_name - - g_type_fundamental - - g_type_fundamental_next - - g_type_get_instance_count - - g_type_get_plugin - - g_type_get_qdata - - g_type_get_type_registration_serial - - g_type_init - - g_type_init_with_debug_flags - - g_type_interface_add_prerequisite - - g_type_interface_get_plugin - - g_type_interface_instantiatable_prerequisite - - g_type_interface_peek - - g_type_interface_prerequisites - - g_type_interfaces - - g_type_is_a - - g_type_name - - g_type_name_from_class - - g_type_name_from_instance - - g_type_next_base - - g_type_parent - - g_type_qname - - g_type_query - - g_type_register_dynamic - - g_type_register_fundamental - - g_type_register_static - - g_type_set_qdata - - g_type_test_flags - - g_value_type_compatible - - g_value_type_transformable - - ParamSpecBoolean - - ParamSpecBoxed - - ParamSpecChar - - ParamSpecDouble - - ParamSpecEnum - - ParamSpecFlags - - ParamSpecFloat - - ParamSpecGType - - ParamSpecInt - - ParamSpecInt64 - - ParamSpecLong - - ParamSpecObject - - ParamSpecOverride - - ParamSpecParam - - ParamSpecPointer - - ParamSpecString - - ParamSpecUChar - - ParamSpecUInt - - ParamSpecUInt64 - - ParamSpecULong - - ParamSpecUnichar - - ParamSpecValueArray - - ParamSpecVariant - - ParamSpecTypeInfo - - SignalQuery - - WeakRef +types: + CClosure: + ignore_methods: + - marshal_BOOLEAN__BOXED_BOXED + - marshal_BOOLEAN__FLAGS + - marshal_generic + - marshal_STRING__OBJECT_POINTER + - marshal_VOID__BOOLEAN + - marshal_VOID__BOXED + - marshal_VOID__CHAR + - marshal_VOID__DOUBLE + - marshal_VOID__ENUM + - marshal_VOID__FLAGS + - marshal_VOID__FLOAT + - marshal_VOID__INT + - marshal_VOID__LONG + - marshal_VOID__OBJECT + - marshal_VOID__PARAM + - marshal_VOID__POINTER + - marshal_VOID__STRING + - marshal_VOID__UCHAR + - marshal_VOID__UINT + - marshal_VOID__UINT_POINTER + - marshal_VOID__ULONG + - marshal_VOID__VARIANT + - marshal_VOID__VOID + GObject: + ignore_methods: + - boxed_copy + - boxed_free + - cclosure_marshal_BOOLEAN__BOXED_BOXED + - cclosure_marshal_BOOLEAN__FLAGS + - cclosure_marshal_generic + - cclosure_marshal_STRING__OBJECT_POINTER + - cclosure_marshal_VOID__BOOLEAN + - cclosure_marshal_VOID__BOXED + - cclosure_marshal_VOID__CHAR + - cclosure_marshal_VOID__DOUBLE + - cclosure_marshal_VOID__ENUM + - cclosure_marshal_VOID__FLAGS + - cclosure_marshal_VOID__FLOAT + - cclosure_marshal_VOID__INT + - cclosure_marshal_VOID__LONG + - cclosure_marshal_VOID__OBJECT + - cclosure_marshal_VOID__PARAM + - cclosure_marshal_VOID__POINTER + - cclosure_marshal_VOID__STRING + - cclosure_marshal_VOID__UCHAR + - cclosure_marshal_VOID__UINT + - cclosure_marshal_VOID__UINT_POINTER + - cclosure_marshal_VOID__ULONG + - cclosure_marshal_VOID__VARIANT + - cclosure_marshal_VOID__VOID + - clear_signal_handler + - enum_complete_type_info + - enum_get_value + - enum_get_value_by_name + - enum_get_value_by_nick + - enum_register_static + - enum_to_string + - flags_complete_type_info + - flags_get_first_value + - flags_get_value_by_name + - flags_get_value_by_nick + - flags_register_static + - flags_to_string + - gtype_get_type + - param_spec_boxed + - param_type_register_static + - param_value_convert + - param_value_defaults + - param_values_cmp + - param_value_set_default + - param_value_validate + - pointer_type_register_static + - signal_accumulator_first_wins + - signal_accumulator_true_handled + - signal_add_emission_hook + - signal_chain_from_overridden + - signal_connect_closure + - signal_connect_closure_by_id + - signal_emitv + - signal_get_invocation_hint + - signal_handler_block + - signal_handler_disconnect + - signal_handler_find + - signal_handler_is_connected + - signal_handlers_block_matched + - signal_handlers_destroy + - signal_handlers_disconnect_matched + - signal_handlers_unblock_matched + - signal_handler_unblock + - signal_has_handler_pending + - signal_is_valid_name + - signal_list_ids + - signal_lookup + - signal_name + - signal_override_class_closure + - signal_parse_name + - signal_query + - signal_remove_emission_hook + - signal_set_va_marshaller + - signal_stop_emission + - signal_stop_emission_by_name + - signal_type_cclosure_new + - source_set_closure + - source_set_dummy_callback + - strdup_value_contents + - type_add_class_private + - type_add_instance_private + - type_add_interface_dynamic + - type_add_interface_static + - type_check_class_is_a + - type_check_instance + - type_check_instance_is_a + - type_check_instance_is_fundamentally_a + - type_check_is_value_type + - type_check_value + - type_check_value_holds + - type_children + - type_class_adjust_private_offset + - type_class_peek + - type_class_peek_static + - type_class_ref + - type_default_interface_peek + - type_default_interface_ref + - type_default_interface_unref + - type_depth + - type_ensure + - type_free_instance + - type_from_name + - type_fundamental + - type_fundamental_next + - type_get_instance_count + - type_get_plugin + - type_get_qdata + - type_get_type_registration_serial + - type_init + - type_init_with_debug_flags + - type_interface_add_prerequisite + - type_interface_get_plugin + - type_interface_instantiatable_prerequisite + - type_interface_peek + - type_interface_prerequisites + - type_interfaces + - type_is_a + - type_name + - type_name_from_class + - type_name_from_instance + - type_next_base + - type_parent + - type_qname + - type_query + - type_register_dynamic + - type_register_fundamental + - type_register_static + - type_set_qdata + - type_test_flags + - value_type_compatible + - value_type_transformable + Object: + ignore_methods: + - interface_install_property + - ref + - is_floating + - ref_sink + - force_floating + - unref + ParamSpecBoolean: + ignore: true + ParamSpecBoxed: + ignore: true + ParamSpecChar: + ignore: true + ParamSpecDouble: + ignore: true + ParamSpecEnum: + ignore: true + ParamSpecFlags: + ignore: true + ParamSpecFloat: + ignore: true + ParamSpecGType: + ignore: true + ParamSpecInt: + ignore: true + ParamSpecInt64: + ignore: true + ParamSpecLong: + ignore: true + ParamSpecObject: + ignore: true + ParamSpecOverride: + ignore: true + ParamSpecParam: + ignore: true + ParamSpecPointer: + ignore: true + ParamSpecString: + ignore: true + ParamSpecUChar: + ignore: true + ParamSpecUInt: + ignore: true + ParamSpecUInt64: + ignore: true + ParamSpecULong: + ignore: true + ParamSpecUnichar: + ignore: true + ParamSpecValueArray: + ignore: true + ParamSpecVariant: + ignore: true + ParamSpecTypeInfo: + ignore: true + SignalQuery: + ignore: true + TypeClass: + ignore_methods: + - adjust_private_offset + - peek + - peek_static + - ref + TypeInterface: + ignore_methods: + - add_prerequisite + - get_plugin + - instantiatable_prerequisite + - peek + - prerequisites + Value: + handmade: true + WeakRef: + ignore: true execute_callback: - g_signal_emitv diff --git a/src/bindings/g_object/enum.cr b/src/bindings/g_object/enum.cr index ef1b970..7894fd1 100644 --- a/src/bindings/g_object/enum.cr +++ b/src/bindings/g_object/enum.cr @@ -35,8 +35,13 @@ abstract struct Enum @@_g_type end + # :nodoc: + def self._is_flags_enum? : Bool + {{ !!@type.annotation(Flags) }} + end + # :nodoc: def self._raise_invalid_enum_type(i : T) forall T - {% raise "Enums used with GLib must use Int32 as their datatype, but #{@type} uses #{T}" unless T == Int32 %} + {% raise "Enums used with GLib must use Int32 or UInt32 as their datatype, but #{@type} uses #{T}" unless T == Int32 || T == UInt32 %} end end diff --git a/src/bindings/g_object/gc.cr b/src/bindings/g_object/gc.cr new file mode 100644 index 0000000..f14575f --- /dev/null +++ b/src/bindings/g_object/gc.cr @@ -0,0 +1,35 @@ +# This file includes the function GC_malloc_uncollectable from bdwgc. +# It enables us to allocate a block of memory which will not be freed, +# but which will be scanned for pointers to collectible objects. +# This allows storing pointers to crystal objects in GObject subclasses. + +{% if flag?(:gc_none) || flag?(:wasm32) %} + module GC + # :nodoc: + def self.malloc_uncollectable(size : LibC::SizeT) : Void* + LibC.malloc(size) + end + end +{% else %} + lib GICrystal_LibGC + fun malloc_uncollectable = GC_malloc_uncollectable(size : LibC::SizeT) : Void* + end + + module GC + # :nodoc: + def self.malloc_uncollectable(size : LibC::SizeT) : Void* + GICrystal_LibGC.malloc_uncollectable(size) + end + end +{% end %} + +module GC + # Allocates *size* bytes of uncollectable memory. + # + # The resulting object may contain pointers and they will be tracked by the GC. + # + # The memory will not be automatically deallocated when unreferenced. + def self.malloc_uncollectable(size : Int) : Void* + malloc_uncollectable(LibC::SizeT.new(size)) + end +end diff --git a/src/bindings/g_object/gi_crystal.cr b/src/bindings/g_object/gi_crystal.cr index fe56e4d..a267f4b 100644 --- a/src/bindings/g_object/gi_crystal.cr +++ b/src/bindings/g_object/gi_crystal.cr @@ -1,9 +1,4 @@ module GICrystal - # Return true if GC already collected the original wrapper of this object. - def gc_collected?(object : GObject::Object) : Bool - !LibGObject.g_object_get_qdata(object, GC_COLLECTED_QDATA_KEY).null? - end - # Return a pointer to the original wrapper for this object. def instance_pointer(object : GObject::Object) : Pointer(Void) LibGObject.g_object_get_qdata(object, INSTANCE_QDATA_KEY) @@ -12,14 +7,12 @@ module GICrystal # Finalize this object, called by `GObject::Object#finalize` def finalize_instance(object : GObject::Object) LibGObject.g_object_set_qdata(object, INSTANCE_QDATA_KEY, Pointer(Void).null) - LibGObject.g_object_set_qdata(object, GC_COLLECTED_QDATA_KEY, Pointer(Void).new(0x1)) LibGObject.g_object_unref(object) end # Finalize this object, called by `GObject::ParamSpec#finalize` def finalize_instance(object : GObject::ParamSpec) LibGObject.g_param_spec_set_qdata(object, INSTANCE_QDATA_KEY, Pointer(Void).null) - LibGObject.g_param_spec_set_qdata(object, GC_COLLECTED_QDATA_KEY, Pointer(Void).new(0x1)) LibGObject.g_param_spec_unref(object) end end diff --git a/src/bindings/g_object/lib_g_object.cr b/src/bindings/g_object/lib_g_object.cr index 3b0dad7..429bb53 100644 --- a/src/bindings/g_object/lib_g_object.cr +++ b/src/bindings/g_object/lib_g_object.cr @@ -48,4 +48,7 @@ lib LibGObject # Null terminated strings GType, used by GValue fun g_strv_get_type : UInt64 + + fun g_object_add_toggle_ref(object : Void*, notify : (Void*, Void*, Int32 ->), data : Void*) + fun g_object_remove_toggle_ref(object : Void*, notify : (Void*, Void*, Int32 ->), data : Void*) end diff --git a/src/bindings/g_object/object.cr b/src/bindings/g_object/object.cr index 0bf8124..266f49c 100644 --- a/src/bindings/g_object/object.cr +++ b/src/bindings/g_object/object.cr @@ -1,16 +1,53 @@ module GObject - # :nodoc: - # This annotation is used to identify user types that inherit from GObject from binding types that does the same. - annotation GeneratedWrapper + # Virtual functions must be annotated with this. + # + # The annotation supports the following attributes: + # + # - unsafe: true/false. + # - name: The name of the virtual function, if not present it's guessed from methods name. + # + # All the method declaration bellow are equivalent and implement the _snapshot_ virtual method. + # + # ```Crystal + # @[GObject::Virtual] + # def do_snapshot(snapshot : Gtk::Snapshot) + # end + # + # @[GObject::Virtual] + # def snapshot(snapshot : Gtk::Snapshot) + # end + # + # @[GObject::Virtual(name: "snapshot")] + # def heyho(snapshot : Gtk::Snapshot) + # end + # ``` + # + # If for some reason you want to go bare metal and not have any wrappers involved in your virtual method implementation + # you can use the *unsafe* annotation flag, then the implementation will receive the pointers for the Objects and structs + # instead of GI::Crystal wrappers. It's up to you to handle the memory and reference count. + # + # ```Crystal + # @[GObject::Virtual(unsafe: true)] + # def snapshot(snapshot : Pointer(Void)) + # end + # ``` + annotation Virtual + end + + annotation Property end class Object macro inherited - {% unless @type.annotation(GObject::GeneratedWrapper) %} + {% unless @type.annotation(GICrystal::GeneratedWrapper) %} macro method_added(method) {% verbatim do %} - {% if method.name.starts_with?("do_") || method.name.starts_with?("unsafe_do_") %} - _register_{{method.name}} + {% if method.annotation(GObject::Virtual) %} + {% vfunc_name = method.annotation(GObject::Virtual)[:name] || method.name.gsub(/^do_/, "") %} + {% if method.annotation(GObject::Virtual)[:unsafe] %} + {% vfunc_name = "unsafe_#{vfunc_name.id}" %} + {% end %} + _register_{{ vfunc_name.id }}_vfunc({{ method.name }}) {% end %} {% end %} end @@ -18,6 +55,11 @@ module GObject # GType for the new created type @@_g_type : UInt64 = 0 + # ParamSpec pointers for the GObject properties in the object + @@_g_param_specs = Pointer(LibGObject::ParamSpec*).null + + @_g_retainer : Void* = Pointer(Void).null + def self.g_type : UInt64 if LibGLib.g_once_init_enter(pointerof(@@_g_type)) != 0 g_type = {{ @type.superclass.id }}._register_derived_type("{{ @type.name.gsub(/::/, "-") }}", @@ -33,6 +75,329 @@ module GObject # :nodoc: def self._class_init(klass : Pointer(LibGObject::TypeClass), user_data : Pointer(Void)) : Nil + {% verbatim do %} + {% begin %} + {% instance_vars = @type.instance_vars.select(&.annotation(GObject::Property)) %} + + @@_g_param_specs = Pointer(LibGObject::ParamSpec*).malloc({{ instance_vars.size }}) + {% for var, i in instance_vars %} + {% property = var.annotation(GObject::Property) %} + name = {{ var.name.gsub(/\_/, "-").stringify }}.to_unsafe + nick = {{ property["nick"] }}.try(&.to_unsafe) || Pointer(LibC::Char).null + blurb = {{ property["blurb"] }}.try(&.to_unsafe) || Pointer(LibC::Char).null + {% other_args = property.named_args.to_a.reject { |arg| ["nick", "blurb"].includes?(arg[0].stringify) } %} + + {% has_getter = @type.has_method?(var.name.stringify) || @type.has_method?(var.name.stringify + "?") %} + {% has_setter = @type.has_method?("#{var.name}=") %} + {% raise "GObject properties need to have a getter and/or a setter" unless has_getter || has_setter %} + + flags = GObject::ParamFlags::StaticName | GObject::ParamFlags::StaticBlurb | GObject::ParamFlags::ExplicitNotify + flags |= GObject::ParamFlags::Deprecated unless {{ !!var.annotation(Deprecated) }} + flags |= GObject::ParamFlags::Readable if {{ has_getter }} + flags |= GObject::ParamFlags::Writable if {{ has_setter }} + + # Finally register the type to GLib. + # The given varible name has its underscores converted to dashes. + pspec = GObject.create_param_spec({{ var.type }}, name, nick, blurb, flags, {{ other_args.map { |tuple| "#{tuple[0]}: #{tuple[1]}".id }.splat }}) + @@_g_param_specs[{{ i }}] = pspec.as(LibGObject::ParamSpec*) + LibGObject.g_object_class_install_property(klass, {{ i + 1 }}, pspec) + {% end %} + {% end %} + {% end %} + end + + # :nodoc: + def self._g_toggle_notify(object : Void*, _gobject : Void*, is_last_ref : Int32) : Nil + return if object.null? + is_last_ref = GICrystal.to_bool(is_last_ref) + + if is_last_ref + # This branch is entered once there are no additional references to this GObject left. + # Remove the toggle ref so it can be garbage collected if there are no references to this object in crystal. + + GICrystal::ToggleRefManager.deregister(object.as(self).@_g_retainer) + else + # This branch is entered once at least one additional reference to this GObject has been established. + # It must now be protected from being garbage collected. + + (object + offsetof(self, @_g_retainer)).as(Void**).value = GICrystal::ToggleRefManager.register(object) + end + end + private G_TOGGLE_NOTIFY__ = ->_g_toggle_notify(Void*, Void*, Int32) + + # :nodoc: + @[GObject::Virtual(unsafe: true, name: "get_property")] + def _get_property(property_id : UInt32, gvalue : Void*, param_spec : Void*) : Nil + {% verbatim do %} + {% begin %} + {% instance_vars = @type.instance_vars.select(&.annotation(GObject::Property)) %} + + case property_id + {% for var, i in instance_vars %} + {% if @type.has_method?(var.name.stringify) %} + when {{ i + 1 }} + GObject::Value.set_g_value(gvalue.as(LibGObject::Value*), self.{{ var }}) + {% end %} + {% end %} + end + {% end %} + {% end %} + end + + # :nodoc: + @[GObject::Virtual(unsafe: true, name: "set_property")] + def _set_property(property_id : UInt32, gvalue : Void*, param_spec : Void*) : Nil + {% verbatim do %} + {% begin %} + {% instance_vars = @type.instance_vars.select(&.annotation(GObject::Property)) %} + + case property_id + {% for var, i in instance_vars %} + {% if @type.has_method?("#{var.name}=") %} + when {{ i + 1 }} + {% if var.type.nilable? && var.type.union_types.size > 2 %} + {% raise "Union types are not supported in GObject properties" %} + {% end %} + + {% var_type = var.type.union_types.reject { |t| t == Nil }.first %} + + {% if var_type < GObject::Object %} + raw = GObject::Value.raw(GObject::TYPE_OBJECT, gvalue) + {% if var.type.nilable? %} + raw_obj = raw.as?(GObject::Object) + self.{{ var }} = raw_obj.nil? ? nil : {{ var_type }}.cast(raw_obj) + {% else %} + self.{{ var }} = {{ var_type }}.cast(raw.as(GObject::Object)) + {% end %} + {% elsif var_type < Enum %} + {% if var_type.annotation(Flags) %} + raw = GObject::Value.raw(GObject::TYPE_FLAGS, gvalue) + self.{{ var }} = raw.as(UInt32).unsafe_as({{ var_type }}) + {% else %} + raw = GObject::Value.raw(GObject::TYPE_ENUM, gvalue) + self.{{ var }} = raw.as(Int32).unsafe_as({{ var_type }}) + {% end %} + {% else %} + raw = GObject::Value.raw({{ var_type }}.g_type, gvalue) + self.{{ var }} = raw.as({{ var_type }}) + {% end %} + {% end %} + {% end %} + end + {% end %} + {% end %} + end + + private macro _emit_notify_signal(arg) + {% verbatim do %} + {% begin %} + {% instance_vars = @type.instance_vars.select(&.annotation(GObject::Property)) %} + + {% for var, i in instance_vars %} + {% if var.name == arg.id %} + LibGObject.g_object_notify_by_pspec(self, @@_g_param_specs[{{ i }}]) + {% end %} + {% end %} + {% end %} + {% end %} + end + + # :nodoc: + # Mostly copied from crystal source + macro setter(*names) + {% verbatim do %} + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + @{{name}} + + def {{name.var.id}}=(@{{name.var.id}} : {{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% elsif name.is_a?(Assign) %} + @{{name}} + + def {{name.target.id}}=(@{{name.target.id}}) + _emit_notify_signal({{name.target.id}}) + end + {% else %} + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% end %} + {% end %} + end + + # :nodoc: + # Mostly copied from crystal source + macro property(*names, &block) + {% verbatim do %} + {% if block %} + {% if names.size != 1 %} + {{ raise "Only one argument can be passed to `property` with a block" }} + {% end %} + + {% name = names[0] %} + + {% if name.is_a?(TypeDeclaration) %} + @{{name.var.id}} : {{name.type}}? + + def {{name.var.id}} : {{name.type}} + if (value = @{{name.var.id}}).nil? + @{{name.var.id}} = {{yield}} + _emit_notify_signal({{name.var.id}}) + else + value + end + end + + def {{name.var.id}}=(@{{name.var.id}} : {{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% else %} + def {{name.id}} + if (value = @{{name.id}}).nil? + @{{name.id}} = {{yield}} + _emit_notify_signal({{name.id}}) + else + value + end + end + + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% else %} + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + @{{name}} + + def {{name.var.id}} : {{name.type}} + @{{name.var.id}} + end + + def {{name.var.id}}=(@{{name.var.id}} : {{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% elsif name.is_a?(Assign) %} + @{{name}} + + def {{name.target.id}} + @{{name.target.id}} + end + + def {{name.target.id}}=(@{{name.target.id}}) + _emit_notify_signal({{name.target.id}}) + end + {% else %} + def {{name.id}} + @{{name.id}} + end + + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% end %} + {% end %} + {% end %} + end + + # :nodoc: + # Mostly copied from crystal source + macro property!(*names) + {% verbatim do %} + getter! {{*names}} + + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + def {{name.var.id}}=(@{{name.var.id}} : {{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% else %} + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% end %} + {% end %} + end + + # :nodoc: + # Mostly copied from crystal source + macro property?(*names, &block) + {% verbatim do %} + {% if block %} + {% if names.size != 1 %} + {{ raise "Only one argument can be passed to `property?` with a block" }} + {% end %} + + {% name = names[0] %} + + {% if name.is_a?(TypeDeclaration) %} + @{{name.var.id}} : {{name.type}}? + + def {{name.var.id}}? : {{name.type}} + if (value = @{{name.var.id}}).nil? + @{{name.var.id}} = {{yield}} + _emit_notify_signal({{name.var.id}}) + else + value + end + end + + def {{name.var.id}}=(@{{name.var.id}} : \{{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% else %} + def {{name.id}}? + if (value = @{{name.id}}).nil? + @{{name.id}} = {{yield}} + _emit_notify_signal({{name.id}}) + else + value + end + end + + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% else %} + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + @{{name}} + + def {{name.var.id}}? : {{name.type}} + @{{name.var.id}} + end + + def {{name.var.id}}=(@{{name.var.id}} : {{name.type}}) + _emit_notify_signal({{name.var.id}}) + end + {% elsif name.is_a?(Assign) %} + @{{name}} + + def {{name.target.id}}? + @{{name.target.id}} + end + + def {{name.target.id}}=(@{{name.target.id}}) + _emit_notify_signal({{name.target.id}}) + end + {% else %} + def {{name.id}}? + @{{name.id}} + end + + def {{name.id}}=(@{{name.id}}) + _emit_notify_signal({{name.id}}) + end + {% end %} + {% end %} + {% end %} + {% end %} end # :nodoc: @@ -59,17 +424,78 @@ module GObject def self.cast?(obj : GObject::Object) : self? return if LibGObject.g_type_check_instance_is_a(obj, g_type).zero? - # If the object was collected by Crystal GC but still alive in C world we can't bring - # the crystal object form the dead. - gc_collected = GICrystal.gc_collected?(obj) instance = GICrystal.instance_pointer(obj) - raise GICrystal::ObjectCollectedError.new if gc_collected || instance.null? + # This should never happen with GC resistant objects + raise GICrystal::ObjectCollectedError.new if instance.null? + + instance.as(self) + end + + # A hook to be executed after the underlying gobject has been initialized. + # + # This specific implementation turns a normal reference into a toggle reference. + private def _after_init : Nil + # Set toggle ref to protect the crystal object from the garbage collector while in C. + + self.class._g_toggle_notify(self.as(Void*), @pointer, 0) + LibGObject.g_object_add_toggle_ref(@pointer, G_TOGGLE_NOTIFY__, self.as(Void*)) + LibGObject.g_object_unref(@pointer) + end + + # :nodoc: + def finalize + {% if flag?(:debugmemory) %} + LibC.printf("~%s at %p - ref count: %d\n", self.class.name.to_unsafe, self, ref_count) + {% end %} + + LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).null) + LibGObject.g_object_remove_toggle_ref(self, G_TOGGLE_NOTIFY__, self.as(Void*)) + end + + # :nodoc: + def self.new(pointer, transfer : GICrystal::Transfer) : self + # This overrides the `initialize(pointer, transfer : GICrystal::Transfer)` + # method because we must make sure there is never a second crystal object for this gobject. + + instance = LibGObject.g_object_get_qdata(pointer, GICrystal::INSTANCE_QDATA_KEY) + raise "Could not retrieve crystal instance!" if instance.null? + + LibGObject.g_object_ref_sink(pointer) if transfer.none? || LibGObject.g_object_is_floating(pointer) == 1 + LibGObject.g_object_unref(pointer) instance.as(self) end {% end %} end + # To be used inside a vfunc (re)-implementation. Has the same meaning of `super`, but works with GObject virtual functions. + # + # Do nothing is the current vfunc doesn't have any previous implementation. + macro previous_vfunc(*args) + \{% begin %} + %func = @@_gi_parent_vfunc_\{{ (@def.annotation(GObject::Virtual)[:name] || @def.name.gsub(/^do_/, "")).id }} + {% if args.empty? %} + %func.try &.call(self.to_unsafe, \{{ @def.args.map { |arg| arg.internal_name || arg.name }.splat }}) + {% else %} + %func.try &.call(self.to_unsafe, {{ *args }}) + {% end %} + \{% end %} + end + + # To be used inside a vfunc (re)-implementation. Has the same meaning of `super`, but works with GObject virtual functions. + # + # Raises NilAssertionError if the current vfunc doesn't have any previous implementation. + macro previous_vfunc!(*args) + \{% begin %} + %func = @@_gi_parent_vfunc_\{{ (@def.annotation(GObject::Virtual)[:name] || @def.name.gsub(/^do_/, "")).id }}.not_nil! + {% if args.empty? %} + %func.call(self.to_unsafe, \{{ @def.args.map { |arg| arg.internal_name || arg.name }.splat }}) + {% else %} + %func.call(self.to_unsafe, {{ *args }}) + {% end %} + \{% end %} + end + # Declares a GObject signal. # # Supported signal parameter types are: @@ -83,7 +509,7 @@ module GObject raise "Signal signature #{signature} can't have a block argument" if signature.block_arg %} - struct {{ signature.name.titleize }}Signal < GObject::Signal + struct {{ signature.name.camelcase }}Signal < GObject::Signal def name : String @detail ? "{{ signature.name }}::#{@detail}" : {{ signature.name.stringify }} end @@ -103,7 +529,7 @@ module GObject {% for arg in signature.args %} {% resolved_type = arg.type.resolve - if resolved_type == String + if resolved_type == String || resolved_type == Path type = ::Pointer(UInt8) elsif resolved_type == Bool type = ::Int32 @@ -117,9 +543,11 @@ module GObject {% for arg in signature.args %} {% resolved_type = arg.type.resolve %} - {% if arg.type.resolve == String %} + {% if resolved_type == String %} {{ arg.var }} = String.new({{ arg.var }}) - {% elsif arg.type.resolve == Bool %} + {% elsif resolved_type == Path %} + {{ arg.var }} = Path.new(String.new({{ arg.var }})) + {% elsif resolved_type == Bool %} {{ arg.var }} = {{ arg.var }} != 0 {% end %} {% end %} @@ -139,7 +567,7 @@ module GObject end def {{ signature.name }}_signal - {{ signature.name.titleize }}Signal.new(self) + {{ signature.name.camelcase }}Signal.new(self) end def self._class_init(klass : Pointer(LibGObject::TypeClass), user_data : Pointer(Void)) : Nil @@ -159,9 +587,32 @@ module GObject end end + def initialize + @pointer = LibGObject.g_object_newv(self.class.g_type, 0, Pointer(LibGObject::Parameter).null) + LibGObject.g_object_ref_sink(self) if LibGObject.g_object_is_floating(self) == 1 + LibGObject.g_object_set_qdata(self, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(object_id)) + self._after_init + end + + def initialize(pointer, transfer : GICrystal::Transfer) + @pointer = pointer + LibGObject.g_object_ref_sink(self) if transfer.none? || LibGObject.g_object_is_floating(self) == 1 + self._after_init + end + # Returns GObject reference counter. def ref_count : UInt32 to_unsafe.as(Pointer(LibGObject::Object)).value.ref_count end + + # Cast a `GObject::Object` to `self`, throws a `TypeCastError` if the cast can't be made. + def self.cast(obj : GObject::Object) : self + cast?(obj) || raise TypeCastError.new("can't cast #{typeof(obj).name} to #{self}") + end + + # Cast a `GObject::Object` to `self`, returns nil if the cast can't be made. + def self.cast?(obj : GObject::Object) : self? + new(obj.to_unsafe, GICrystal::Transfer::None) unless LibGObject.g_type_check_instance_is_a(obj, g_type).zero? + end end end diff --git a/src/bindings/g_object/param_spec.cr b/src/bindings/g_object/param_spec.cr index 1ce79ee..83e9e6e 100644 --- a/src/bindings/g_object/param_spec.cr +++ b/src/bindings/g_object/param_spec.cr @@ -1,5 +1,9 @@ module GObject class ParamSpec + def initialize(@pointer : Pointer(Void), transfer : GICrystal::Transfer) + LibGObject.g_param_spec_ref(@pointer) if transfer.none? + end + # Returns ParamSpec reference counter. def ref_count : UInt32 to_unsafe.as(Pointer(LibGObject::ParamSpec)).value.ref_count diff --git a/src/bindings/g_object/type.cr b/src/bindings/g_object/type.cr index 6b78809..8ab900a 100644 --- a/src/bindings/g_object/type.cr +++ b/src/bindings/g_object/type.cr @@ -22,6 +22,80 @@ module GObject TYPE_OBJECT = 0x50_u64 TYPE_VARIANT = 0x54_u64 TYPE_STRV = LibGObject.g_strv_get_type + + # :nodoc: + def self.create_param_spec(klass : String.class, name, nick, blurb, flags, *, default : String = "") : Void* + LibGObject.g_param_spec_string(name, nick, blurb, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Bool.class, name, nick, blurb, flags, *, default : Bool = false) : Void* + LibGObject.g_param_spec_boolean(name, nick, blurb, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Int8.class, name, nick, blurb, flags, *, min : Int8 = Int8::MIN, max : Int8 = Int8::MAX, default : Int8 = 0i8) : Void* + LibGObject.g_param_spec_char(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : UInt8.class, name, nick, blurb, flags, *, min : UInt8 = UInt8::MIN, max : UInt8 = UInt8::MAX, default : UInt8 = 0u8) : Void* + LibGObject.g_param_spec_uchar(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Int32.class, name, nick, blurb, flags, *, min : Int32 = Int32::MIN, max : Int32 = Int32::MAX, default : Int32 = 0) : Void* + LibGObject.g_param_spec_int(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : UInt32.class, name, nick, blurb, flags, *, min : UInt32 = UInt32::MIN, max : UInt32 = UInt32::MAX, default : UInt32 = 0u32) : Void* + LibGObject.g_param_spec_uint(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Int64.class, name, nick, blurb, flags, *, min : Int64 = Int64::MIN, max : Int64 = Int64::MAX, default : Int64 = 0i64) : Void* + LibGObject.g_param_spec_int64(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : UInt64.class, name, nick, blurb, flags, *, min : UInt64 = UInt64::MIN, max : UInt64 = UInt64::MAX, default : UInt64 = 0u64) : Void* + LibGObject.g_param_spec_uint64(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Float32.class, name, nick, blurb, flags, *, min : Float32 = Float32::MIN, max : Float32 = Float32::MAX, default : Float32 = 0f32) : Void* + LibGObject.g_param_spec_float(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Float64.class, name, nick, blurb, flags, *, min : Float64 = Float64::MIN, max : Float64 = Float64::MAX, default : Float64 = 0.0) : Void* + LibGObject.g_param_spec_double(name, nick, blurb, min, max, default, flags) + end + + # :nodoc: + def self.create_param_spec(klass : GObject::Object?.class, name, nick, blurb, flags) : Void* + LibGObject.g_param_spec_object(name, nick, blurb, type_not_nil!(klass).g_type, flags) + end + + # :nodoc: + def self.create_param_spec(klass : Enum.class, name, nick, blurb, flags, *, default : Enum? = nil) : Void* + if klass._is_flags_enum? + LibGObject.g_param_spec_flags(name, nick, blurb, klass.g_type, default || 0, flags) + else + LibGObject.g_param_spec_enum(name, nick, blurb, klass.g_type, default || 0, flags) + end + end + + # :nodoc: + def self.type_not_nil!(klass : T?.class) : T.class forall T + T + end + + # :nodoc: + def self.type_not_nil!(klass) + klass + end end class String @@ -31,6 +105,15 @@ class String end end +struct Path + # Returns the GObject GType for Path. + def self.g_type + GObject::TYPE_STRING + end + + delegate to_unsafe, to: to_s +end + struct Bool # Returns the GObject GType for Bool. def self.g_type diff --git a/src/bindings/g_object/value.cr b/src/bindings/g_object/value.cr index d2b11ff..b87deee 100644 --- a/src/bindings/g_object/value.cr +++ b/src/bindings/g_object/value.cr @@ -22,10 +22,13 @@ module GObject # :nodoc: def self.init_g_value(ptr : Pointer(LibGObject::Value), value) : Nil LibGObject.g_value_init(ptr, Value.g_type_for(value)) + self.set_g_value(ptr, value) + end + # :nodoc: + def self.set_g_value(ptr : Pointer(LibGObject::Value), value) : Nil case value when Bool then LibGObject.g_value_set_boolean(ptr, value) - when Enum then LibGObject.g_value_set_enum(ptr, value) when Float32 then LibGObject.g_value_set_float(ptr, value) when Float64 then LibGObject.g_value_set_double(ptr, value) when Int32 then LibGObject.g_value_set_int(ptr, value) @@ -38,6 +41,12 @@ module GObject when UInt8 then LibGObject.g_value_set_uchar(ptr, value) when GLib::Variant then LibGObject.g_value_set_variant(ptr, value) when ParamSpec then LibGObject.g_value_set_param(ptr, value) + when Enum + if value.class._is_flags_enum? + LibGObject.g_value_set_flags(ptr, value) + else + LibGObject.g_value_set_enum(ptr, value) + end when Enumerable(String) array = value.map(&.to_unsafe).to_a << Pointer(UInt8).null LibGObject.g_value_set_boxed(ptr, array) @@ -50,7 +59,6 @@ module GObject def self.g_type_for(value) case value when Bool then TYPE_BOOL - when Enum then TYPE_ENUM when Float32 then TYPE_FLOAT when Float64 then TYPE_DOUBLE when Int32 then TYPE_INT @@ -64,6 +72,12 @@ module GObject when Enumerable(String) then TYPE_STRV when GLib::Variant then TYPE_VARIANT when ParamSpec then TYPE_PARAM + when Enum + if value.class._is_flags_enum? + TYPE_FLAGS + else + TYPE_ENUM + end else raise ArgumentError.new("Unable to wrap a #{value.class} into a GValue, probably not implemented.") end @@ -78,26 +92,34 @@ module GObject @g_value.g_type end - def raw + def self.raw(g_type : UInt64, ptr : Void*) case g_type - when TYPE_BOOL then GICrystal.to_bool(LibGObject.g_value_get_boolean(self)) - when TYPE_CHAR then LibGObject.g_value_get_schar(self) - when TYPE_DOUBLE then LibGObject.g_value_get_double(self) - when TYPE_FLOAT then LibGObject.g_value_get_float(self) - when TYPE_INT then LibGObject.g_value_get_int(self) - when TYPE_INT64 then LibGObject.g_value_get_int64(self) - when TYPE_OBJECT then GObject::Object.new(LibGObject.g_value_get_object(self), :none) - when TYPE_STRING then String.new(LibGObject.g_value_get_string(self)) - when TYPE_UCHAR then LibGObject.g_value_get_uchar(self) - when TYPE_UINT then LibGObject.g_value_get_uint(self) - when TYPE_UINT64 then LibGObject.g_value_get_uint64(self) - when TYPE_VARIANT then GLib::Variant.new(LibGObject.g_value_get_variant(self), :none) - when TYPE_PARAM then ParamSpec.new(LibGObject.g_value_get_param(self), :none) + when TYPE_BOOL then GICrystal.to_bool(LibGObject.g_value_get_boolean(ptr)) + when TYPE_CHAR then LibGObject.g_value_get_schar(ptr) + when TYPE_DOUBLE then LibGObject.g_value_get_double(ptr) + when TYPE_FLOAT then LibGObject.g_value_get_float(ptr) + when TYPE_INT then LibGObject.g_value_get_int(ptr) + when TYPE_INT64 then LibGObject.g_value_get_int64(ptr) + when TYPE_STRING then String.new(LibGObject.g_value_get_string(ptr)) + when TYPE_UCHAR then LibGObject.g_value_get_uchar(ptr) + when TYPE_UINT then LibGObject.g_value_get_uint(ptr) + when TYPE_UINT64 then LibGObject.g_value_get_uint64(ptr) + when TYPE_VARIANT then GLib::Variant.new(LibGObject.g_value_get_variant(ptr), :none) + when TYPE_PARAM then ParamSpec.new(LibGObject.g_value_get_param(ptr), :none) + when TYPE_ENUM then LibGObject.g_value_get_enum(ptr) + when TYPE_FLAGS then LibGObject.g_value_get_flags(ptr) + when TYPE_OBJECT + object_ptr = LibGObject.g_value_get_object(ptr) + object_ptr.null? ? nil : GObject::Object.new(object_ptr, :none) else raise ArgumentError.new("Cannot obtain raw value for g_type #{g_type}") end end + def raw + GObject::Value.raw(g_type, to_unsafe) + end + {% for name, type in { "i8" => Int8, "u8" => UInt8, diff --git a/src/generator/arg_strategy.cr b/src/generator/arg_strategy.cr index 6fd1ad7..c53752d 100644 --- a/src/generator/arg_strategy.cr +++ b/src/generator/arg_strategy.cr @@ -191,7 +191,7 @@ module Generator return false unless arg.nullable? arg_type = arg.type_info - return false if BindingConfig.handmade?(arg_type) + return false if handmade_type?(arg_type) true end @@ -276,7 +276,7 @@ module Generator return false if strategy.remove_from_declaration? arg_type = strategy.arg_type - return false unless BindingConfig.handmade?(arg_type) + return false unless handmade_type?(arg_type) true end @@ -366,10 +366,10 @@ module Generator return false if strategy.remove_from_declaration? arg_type = strategy.arg.type_info - return false if BindingConfig.handmade?(arg_type) + return false if handmade_type?(arg_type) case arg_type.tag - when .interface?, .utf8?, .filename? + when .interface?, .utf8?, .filename?, .boolean? true else false @@ -384,22 +384,8 @@ module Generator arg_name = to_identifier(arg.name) type_info = arg.type_info - io << arg_name << '=' - - tag = type_info.tag - if tag.interface? - if type_info.interface.class.in?(ObjectInfo, InterfaceInfo, StructInfo) - io << to_crystal_type(type_info) << ".new(lib_" << arg_name << ", GICrystal::Transfer::" << arg.ownership_transfer << ")" - else - io << to_crystal_type(type_info) << ".new(lib_" << arg_name << ")" - end - elsif tag.utf8? || tag.filename? - io << convert_to_crystal("lib_#{arg_name}", type_info, nil, arg.ownership_transfer) - end - - if arg.nullable? - io << " unless lib_" << arg_name << ".null?" - end + io << arg_name << '=' << convert_to_crystal("lib_#{arg_name}", type_info, nil, arg.ownership_transfer) + io << " unless lib_" << arg_name << ".null?" if arg.nullable? io << LF end end @@ -430,7 +416,7 @@ module Generator arg = strategy.arg type_info = arg.type_info callback = type_info.interface.as(CallbackInfo) - idx = strategies.index(strategy).not_nil! + idx = strategies.index!(strategy) user_data_arg = strategies[idx + 1].arg destroy_notify_arg = strategies[idx + 2].arg diff --git a/src/generator/binding_config.cr b/src/generator/binding_config.cr index 0a3ddc5..c56def0 100644 --- a/src/generator/binding_config.cr +++ b/src/generator/binding_config.cr @@ -1,100 +1,102 @@ require "yaml" +require "./error" +require "./generator" + +# This exists due to https://github.com/crystal-lang/crystal/issues/12290 module Generator - class BindingConfig - getter namespace : String - getter version : String - getter includes : Set(Path) - getter includes_before : Set(Path) - getter handmade : Set(String) - getter ignore : Set(String) - getter execute_callback : Set(String) + alias BindingConfig = GeneratorNamespaceRenamedDueToACrystalBug::BindingConfig +end - getter base_path : Path +module GeneratorNamespaceRenamedDueToACrystalBug + alias Error = ::Generator::Error + alias Namespace = ::Generator::Namespace + + enum BindingStrategy + Auto + # Bind the type as a Crystal struct + StackStruct + # Bind the type as a Crystal class with the lib struct as attribute. + HeapStruct + # Bind the type as a Crystal class with a pointer to the object. + HeapWrapperStruct + end - class_getter loaded_configs = Hash(String, BindingConfig).new + class TypeConfig + include YAML::Serializable + include YAML::Serializable::Strict - # Constructs a binding config object from a yaml file - def initialize(file : Path) - file = file.expand - data = YAML.parse(File.read(file)) - @base_path = file.parent - @namespace = data["namespace"].as_s? || raise Error.new("namespace must be a string.") - @version = data["version"].as_s? || raise Error.new("version must be a string.") - - @includes = Set(Path).new - read_list(data, "include").each do |i| - @includes << @base_path.join(i) - end + def initialize + end - @includes_before = Set(Path).new - read_list(data, "include_before").each do |i| - @includes_before << @base_path.join(i) - end + getter binding_strategy : BindingStrategy = BindingStrategy::Auto + @ignore_methods : Set(String)? + @ignore_fields : Set(String)? + getter? handmade = false + getter? ignore = false - @handmade = read_list(data, "handmade") - @ignore = read_list(data, "ignore") - @execute_callback = read_list(data, "execute_callback") - end + {% for attr in %w(ignore_method ignore_field) %} + def {{ attr.id }}?(name : String) : Bool + {{ attr.id }} = @{{ attr.id }}s + return false if {{ attr.id }}.nil? - # Constructs an empty binding config - def initialize(@namespace, @version) - @includes_before = @includes = Set(Path).new - @execute_callback = @handmade = @ignore = Set(String).new - @base_path = Path.new(".") + {{ attr.id }}.includes?(name) end + {% end %} + end - private def read_list(data : YAML::Any, key : String) : Nil - value = data[key]? - return Set(String).new if value.nil? - raise Error.new("'#{key}' value must be a list of strings.") unless value.as_a? + class BindingConfig + include YAML::Serializable + include YAML::Serializable::Strict - value.as_a.each do |item| - value = item.to_s - yield(value) - end - end + getter namespace : String + getter version : String + getter require_before = Set(Path).new + getter require_after = Set(Path).new + @types = Hash(String, TypeConfig).new + getter lib_ignore = Set(String).new + getter execute_callback = Set(String).new + getter ignore_constants = Set(String).new - private def read_list(data : YAML::Any, key : String) : Set(String) - list = Set(String).new - read_list(data, key) do |value| - raise Error.new("duplicated item '#{value}' on '#{key}'") if list.includes?(value) + class_getter loaded_configs = Hash(String, BindingConfig).new - list << value - end - list - end + @@empty_type_config = TypeConfig.new - def ignore?(name : String) : Bool - @ignore.includes?(name) + # Constructs an empty binding config + def initialize(@namespace, @version) end - def handmade?(name : String) : Bool - @handmade.includes?(name) + def adjust_paths(path : Path) + {% for ivar in %w(@require_before @require_after) %} + tmp = {{ ivar.id }} + {{ ivar.id }} = Set(Path).new + tmp.each do |p| + {{ ivar.id }} << path.join(p) + end + {% end %} end - def self.handmade?(type_info : TypeInfo) : Bool - return false unless type_info.tag.interface? - - iface = type_info.interface - return false if iface.nil? - - namespace = iface.namespace - for(namespace.name, namespace.version).handmade?(iface.name) + def type_config(type : String) : TypeConfig + @types[type]? || @@empty_type_config end - def self.load(files : Enumerable(String)) - files.each { |file| load(file) } + def lib_ignore?(symbol : String) : Bool + @lib_ignore.includes?(symbol) end def self.load(file : String) - conf = BindingConfig.new(Path.new(file)) - namespace = conf.namespace - raise Error.new("binding config already loaded for #{namespace} namespace.") if @@loaded_configs.has_key?(namespace) - @@loaded_configs[namespace] = conf - rescue e : Error - raise Error.new("Error loading #{file}: #{e.message}") - rescue e : KeyError + load(Path.new(file)) + end + + def self.load(file : Path) + File.open(file) do |fp| + conf = from_yaml(fp) + conf.adjust_paths(file.parent) + namespace = conf.namespace + raise Error.new("Binding config already loaded for #{namespace} namespace.") if @@loaded_configs.has_key?(namespace) + @@loaded_configs[namespace] = conf + end + rescue e : YAML::ParseException raise Error.new("Error loading #{file}: #{e.message}") end diff --git a/src/generator/box_helper.cr b/src/generator/box_helper.cr index befae78..c971a3d 100644 --- a/src/generator/box_helper.cr +++ b/src/generator/box_helper.cr @@ -27,6 +27,7 @@ module Generator if arg_strategy.has_implementation? arg_strategy.write_implementation(io) elsif !arg_strategy.remove_from_declaration? + io << "# NoStrategy\n" arg_name = arg_strategy.arg.name io << to_identifier(arg_name) << '=' << "lib_" << arg_name << LF end @@ -50,6 +51,18 @@ module Generator end end + def generate_lib_types(io : IO, callable : CallableInfo) + is_signal = callable.is_a?(SignalInfo) + + callable.args.each do |arg| + # If arg_type is Void, it's probably a struct, GObjIntrospection doesn't inform that signal args are pointer when + # they are structs + arg_type = to_lib_type(arg.type_info, structs_as_void: true) + arg_type = "Pointer(#{arg_type})" if is_signal && arg_type == "Void" + io << arg_type << ", " + end + end + def arg_strategies_to_proc_param_string(io : IO, callable : CallableInfo, strategies : Array(ArgStrategy)) : Nil strategies.each do |arg_strategy| next if arg_strategy.remove_from_declaration? diff --git a/src/generator/callable_gen.cr b/src/generator/callable_gen.cr new file mode 100644 index 0000000..f4286a5 --- /dev/null +++ b/src/generator/callable_gen.cr @@ -0,0 +1,22 @@ +module Generator + abstract class CallableGen < Generator + abstract def callable : CallableInfo + + def method_gi_annotations : String + args = callable.args + String.build do |io| + io << "# " + callable.to_s(io) << ": (" << callable.flags.to_s << ")\n" + args_gi_annotations(io, args) + + io << "# Returns: (transfer " << callable.caller_owns.to_s.downcase + return_type = callable.return_type + io << ") (filename" if return_type.tag.filename? + io << ") (nullable" if callable.may_return_null? + io << ") " + type_info_gi_annotations(io, callable.return_type, args) + io << LF + end + end + end +end diff --git a/src/generator/doc_repo.cr b/src/generator/doc_repo.cr index 8797079..a82f75a 100644 --- a/src/generator/doc_repo.cr +++ b/src/generator/doc_repo.cr @@ -124,6 +124,15 @@ module Generator def crystallize_doc(doc : String) : String crystallized_doc = doc crystallized_doc = crystallized_doc.gsub(/```([a-zA-Z]+)\n/, "\n\nWARNING: **⚠️ The following code is in \\1 ⚠️**\n```\\1\n") + crystallized_doc = crystallized_doc.gsub(/\s@(\w[\w\d_]*)\b/, " *\\1*") + crystallized_doc = crystallized_doc.gsub(/%(TRUE|FALSE|NULL)/) do |_, match| + case match[1] + when "TRUE" then "`true`" + when "FALSE" then "`false`" + when "NULL" then "`nil`" + end + end + crystallized_doc = crystallized_doc.gsub(/\[([a-zA-Z]+)@([a-zA-Z\.:_]+)\]/) do |_, match| split_reference = match[-1].split(/\.|:/) identifier = split_reference[-1] @@ -136,11 +145,17 @@ module Generator elsif identifier.starts_with?("set_") && identifier.size > 4 "##{identifier[4..]}=" else - "##{identifier}" + "#{split_reference.size == 2 ? "::" : "#"}#{identifier}" end "`#{split_reference[0..-2].join("::")}#{identifier}`" end + + # FIXME: Get this list of modules form the generator itself instead of hardcode them + crystallized_doc = crystallized_doc.gsub(/(G[dts]k|Pango|cairo_|graphene_|Adw|Hdy|GtkSource)(\w+\b)/) do |_, match| + match.captures.join("::") + end + crystallized_doc end diff --git a/src/generator/generator.cr b/src/generator/generator.cr index 54493c2..bcedb93 100644 --- a/src/generator/generator.cr +++ b/src/generator/generator.cr @@ -60,10 +60,6 @@ module Generator Generator.output_dir end - def skip?(key : String = subject) : Bool - config.ignore?(key) || config.handmade?(key) - end - def module_dir(namespace : Namespace = @namespace) "#{namespace.name.underscore}-#{namespace.version}" end @@ -88,11 +84,15 @@ module Generator Generator.pop_log_scope end - def generate(io : IO) - with_log_scope do - # Generator::ModuleGen class needs `module.ecr` and so on. - ECR.embed({{ "ecr/" + @type.name[11..-4].underscore.stringify + ".ecr" }}, io) + macro inherited + {% unless @type.abstract? %} + def generate(io : IO) + with_log_scope do + # Generator::ModuleGen class needs `module.ecr` and so on. + ECR.embed({{ "ecr/" + @type.name[11..-4].underscore.stringify + ".ecr" }}, io) + end end + {% end %} end macro render(filename, object = object) diff --git a/src/generator/heap_struct_gen.cr b/src/generator/heap_struct_gen.cr new file mode 100644 index 0000000..820ac56 --- /dev/null +++ b/src/generator/heap_struct_gen.cr @@ -0,0 +1,9 @@ +require "./struct_gen" + +module Generator + class HeapStructGen < StructGen + def initialize(info : StructInfo) + super(info) + end + end +end diff --git a/src/generator/heap_wrapper_struct_gen.cr b/src/generator/heap_wrapper_struct_gen.cr new file mode 100644 index 0000000..5380c8f --- /dev/null +++ b/src/generator/heap_wrapper_struct_gen.cr @@ -0,0 +1,9 @@ +require "./struct_gen" + +module Generator + class HeapWrapperStructGen < StructGen + def initialize(info : StructInfo) + super(info) + end + end +end diff --git a/src/generator/helpers.cr b/src/generator/helpers.cr index 936a466..fe7ddfe 100644 --- a/src/generator/helpers.cr +++ b/src/generator/helpers.cr @@ -114,7 +114,7 @@ module Generator::Helpers raise Error.new("Unknown lib representation for #{iface.class.name}.") end elsif tag.array? - array_type_name = to_lib_type(type.param_type, include_namespace) + array_type_name = to_lib_type(type.param_type, include_namespace: include_namespace) if is_arg array_type_name else @@ -140,16 +140,53 @@ module Generator::Helpers end end - def convert_to_lib(var : String, type : TypeInfo, _transfer : Transfer) : String + def convert_to_lib(var : String, type : TypeInfo, _transfer : Transfer, nullable : Bool) : String tag = type.tag case tag - when .interface?, .utf8?, .filename? - "#{var}.to_unsafe" + when .utf8?, .filename? + if nullable + "#{var}.nil? ? Pointer(UInt8).null : #{var}.to_unsafe" + else + "#{var}.to_unsafe" + end + when .boolean? + "GICrystal.to_c_bool(#{var})" + when .interface? + iface = type.interface.not_nil! + if iface.is_a?(EnumInfo) + "#{var}.#{tag_conversion_function(iface.storage_type)}" + else + if nullable + "#{var}.nil? ? Pointer(Void).null : #{var}.to_unsafe" + else + "#{var}.to_unsafe" + end + end else var end end + def tag_conversion_function(tag : TypeTag) + case tag + when .boolean? then "to_i" + when .int8? then "to_i8" + when .u_int8? then "to_u8" + when .int16? then "to_i16" + when .u_int16? then "to_u16" + when .int32? then "to_i32" + when .u_int32? then "to_u32" + when .int64? then "to_i64" + when .u_int64? then "to_u64" + when .float? then "to_f32" + when .double? then "to_f64" + when .gtype? then "to_u64" + when .unichar? then "to_u32" + else + "to_unsafe" + end + end + def to_crystal_arg_decl(name : String) if ID_KEYWORDS.includes?(name) "#{name} _#{name}" @@ -166,14 +203,23 @@ module Generator::Helpers end end + def remove_callable_last_parameter?(info : CallableInfo) : Bool + return false unless info.is_a?(CallbackInfo) + + last_arg = info.args.last? + return false if last_arg.nil? + + last_arg.type_info.tag.void? + end + def callable_to_crystal_types(io : IO, info : CallableInfo) : Nil # He must hide the user_data arg from CallbackInfo - stop_at = info.is_a?(CallbackInfo) ? info.args.size - 1 : -1 + stop_at = remove_callable_last_parameter?(info) ? info.args.size - 1 : -1 info.args.each_with_index do |arg, i| break if i == stop_at arg_type_info = arg.type_info - nullmark = '?' if arg.nullable? + nullmark = '?' if arg.nullable? && !arg_type_info.tag.void? io << to_crystal_type(arg_type_info, include_namespace: true) << nullmark << ',' end io << to_crystal_type(info.return_type, include_namespace: true) @@ -206,9 +252,19 @@ module Generator::Helpers end end + def handmade_type?(type : TypeInfo) : Bool + return false unless type.tag.interface? + + iface = type.interface + return false if iface.nil? + + BindingConfig.for(iface.namespace).type_config(iface.name).handmade? + end + # @is_arg: The type is means to be used in a argument list for some method def to_crystal_type(type : TypeInfo, include_namespace : Bool = true, is_arg : Bool = false) : String - return "_" if is_arg && BindingConfig.handmade?(type) + # Check if the type is handmade used in a argument + return "_" if is_arg && handmade_type?(type) tag = type.tag case tag diff --git a/src/generator/lib_gen.cr b/src/generator/lib_gen.cr index dea6482..3853a97 100644 --- a/src/generator/lib_gen.cr +++ b/src/generator/lib_gen.cr @@ -21,13 +21,20 @@ module Generator {% for attr in %w(objects interfaces structs) %} namespace.{{ attr.id }}.each do |obj| obj.methods.each do |func| - all << generate_c_function(func) + all << generate_c_function(func) unless config.lib_ignore?(func.symbol) end + end + {% end %} + + # Type init functions + {% for attr in %w(objects interfaces structs enums flags) %} + namespace.{{ attr.id }}.each do |obj| all << type_init_func(obj) if obj.type_init end {% end %} + namespace.functions.each do |func| - all << generate_c_function(func) + all << generate_c_function(func) unless config.lib_ignore?(func.symbol) end all.sort_by!(&.lines.last) end diff --git a/src/generator/main.cr b/src/generator/main.cr index f47e254..727bfaa 100644 --- a/src/generator/main.cr +++ b/src/generator/main.cr @@ -1,13 +1,12 @@ require "colorize" require "log" require "option_parser" -require "version_from_shard" -require "./module_gen" require "./binding_config" require "./error" +require "./module_gen" -VersionFromShard.declare +VERSION = {{ `shards version #{__DIR__}`.strip.stringify }} private def project_dir exe_path = Process.executable_path @@ -18,7 +17,7 @@ end private def parse_options(argv) output_dir = nil - doc_gen = true + doc_gen = false OptionParser.parse(argv) do |parser| parser.banner = "Usage: generator [binding-config]" @@ -33,7 +32,11 @@ private def parse_options(argv) parser.on("-o=DIRECTORY", "Output directory, default: \"lib/gi-crystal/src/auto\"") do |dir| output_dir = Path.new(dir).expand.to_s end - parser.on("--no-doc", "Disable documentation generation on generated code") { doc_gen = false } + parser.on("--doc", "Disable documentation generation on generated code") { doc_gen = true } + parser.on("--no-doc", "Disable documentation generation on generated code") do + STDERR.puts("⚠️ --no-doc is DEPRECATED and set by default.".colorize.yellow) + doc_gen = false + end parser.invalid_option do |flag| abort("#{flag} is not a valid option.\n\n#{parser}") @@ -94,15 +97,17 @@ private def main(argv) options = parse_options(argv) Log.info { "Starting at #{Time.local}, project dir: #{project_dir}" } + Log.info { "Gi-Crystal version #{VERSION}, built with Crystal #{Crystal::VERSION}." } Generator::Generator.output_dir = options[:output_dir].to_s Log.info { "Generating bindings at #{options[:output_dir]}" } Generator::DocRepo.disable! unless options[:doc_gen] binding_yamls = find_bindings.concat(options[:extra_bindings]) - binding_yamls.each { |file| Log.info { "Using binding config at #{file}" } } - - Generator::BindingConfig.load(binding_yamls) + binding_yamls.each do |file| + Log.info { "Using binding config at #{file}" } + Generator::BindingConfig.load(file) + end generate_all rescue e : Generator::Error | GObjectIntrospection::Error | File::NotFoundError diff --git a/src/generator/method_gen.cr b/src/generator/method_gen.cr index 071ea9d..833d721 100644 --- a/src/generator/method_gen.cr +++ b/src/generator/method_gen.cr @@ -1,7 +1,8 @@ require "./arg_strategy" +require "./callable_gen" module Generator - class MethodGen < Generator + class MethodGen < CallableGen include WrapperUtil alias MethodReturnType = TypeInfo | ArgInfo @@ -22,10 +23,6 @@ module Generator @crystal_arg_count = @args_strategies.size - @args_strategies.count(&.remove_from_declaration?) end - def skip? : Bool - config.ignore?(@method.symbol) || (@method.flags.constructor? && @method.args.empty? && object.is_a?(StructInfo)) - end - def scope @method.symbol end @@ -34,6 +31,10 @@ module Generator @method.flags.throws? end + def callable : CallableInfo + @method + end + private def method_identifier : String identifier = to_call(@method.name) method_flags = @method.flags diff --git a/src/generator/method_holder.cr b/src/generator/method_holder.cr index c3efeac..2084eeb 100644 --- a/src/generator/method_holder.cr +++ b/src/generator/method_holder.cr @@ -4,8 +4,11 @@ module Generator module MethodHolder macro render_methods each_object_method do |method| - gen = MethodGen.new(object, method) - gen.generate(io) unless gen.skip? + next if config.type_config(object.name).ignore_method?(method.name) + next if method.flags.constructor? && method.args.empty? && object.is_a?(StructInfo) + next if config.lib_ignore?(method.symbol) + + MethodGen.new(object, method).generate(io) end end diff --git a/src/generator/module_gen.cr b/src/generator/module_gen.cr index b2e5e04..ea5c07b 100644 --- a/src/generator/module_gen.cr +++ b/src/generator/module_gen.cr @@ -3,7 +3,9 @@ require "file_utils" require "./file_gen" require "./wrapper_util" require "./object_gen" -require "./struct_gen" +require "./heap_struct_gen" +require "./heap_wrapper_struct_gen" +require "./stack_struct_gen" require "./interface_gen" require "./lib_gen" @@ -26,12 +28,30 @@ module Generator protected def initialize(@config : BindingConfig) @namespace = GObjectIntrospection::Repository.default.require(@config.namespace, @config.version) - @objects = @namespace.objects.map { |info| ObjectGen.new(info) }.reject(&.skip?) - @structs = @namespace.structs.map { |info| StructGen.new(info) }.reject(&.skip?) - @interfaces = @namespace.interfaces.map { |info| InterfaceGen.new(info) }.reject(&.skip?) + @objects = @namespace.objects.compact_map do |info| + ObjectGen.new(info) if should_generate_code?(info) + end + @structs = @namespace.structs.compact_map do |info| + StructGen.new(info) if should_generate_code?(info) + end + @interfaces = @namespace.interfaces.compact_map do |info| + InterfaceGen.new(info) if should_generate_code?(info) + end @lib = LibGen.new(@namespace) end + private def should_generate_code?(info : RegisteredTypeInfo) : Bool + type_config = config.type_config(info.name) + !(type_config.handmade? || type_config.ignore?) + end + + private def should_generate_code?(info : StructInfo) : Bool + return false if info.g_type_struct? || info.g_error? + + type_config = config.type_config(info.name) + !(type_config.handmade? || type_config.ignore?) + end + delegate version, to: @namespace def object @@ -52,7 +72,7 @@ module Generator def each_callback @namespace.callbacks.each do |callback| - yield(callback) unless config.ignore?(callback.name) + yield(callback) unless config.type_config(callback.name).ignore? end end @@ -75,7 +95,7 @@ module Generator requires = [] of String {% for collection in %w(@objects @structs @interfaces) %} {{ collection.id }}.each do |gen| - requires << gen.filename unless gen.skip? + requires << gen.filename end {% end %} requires.sort! @@ -142,5 +162,9 @@ module Generator values.first.value.zero? end + + private def declare_error? : Bool + enums.any?(&.error_domain) + end end end diff --git a/src/generator/object_gen.cr b/src/generator/object_gen.cr index da1b3d7..7227b47 100644 --- a/src/generator/object_gen.cr +++ b/src/generator/object_gen.cr @@ -27,10 +27,6 @@ module Generator @object.name end - def generate - generate(filename) unless skip? - end - private def parent_class parent = @object.parent return if parent.nil? @@ -42,10 +38,6 @@ module Generator to_crystal_type(@object, false) end - def g_object_type? : Bool - object.parent.nil? && type_name == "Object" # loose check that works - end - private def all_properties : Array(PropertyInfo) @all_properties = begin obj = @object @@ -80,5 +72,15 @@ module Generator s << '/' << info.name.underscore << "\"\n" end end + + macro render_qdata_optimized_new_method + render_qdata_optimized_new_method(io) + end + + def render_qdata_optimized_new_method(io : IO) + return if !object.inherits?("GObject") && !object.inherits?("GParam") + + io << "GICrystal.declare_new_method(" << type_name << ',' << object.qdata_get_func << ',' << object.qdata_set_func << ")\n" + end end end diff --git a/src/generator/signal_holder.cr b/src/generator/signal_holder.cr index 8bb2bf6..4207b55 100644 --- a/src/generator/signal_holder.cr +++ b/src/generator/signal_holder.cr @@ -4,7 +4,7 @@ module Generator module SignalHolder private macro render_signals object.signals.each do |signal| - SignalGen.new(object, signal).generate(io) + SignalGen.new(object, signal).generate(io) end end end diff --git a/src/generator/stack_struct_gen.cr b/src/generator/stack_struct_gen.cr new file mode 100644 index 0000000..f9b7ade --- /dev/null +++ b/src/generator/stack_struct_gen.cr @@ -0,0 +1,36 @@ +require "./struct_gen" + +module Generator + class StackStructGen < StructGen + def initialize(info : StructInfo) + super(info) + end + + private def generate_getter(io : IO, field : FieldInfo) + field_name = field.name + io << "delegate :" << to_call(field_name) << ", to: @data\n" + end + + private def generate_setter(io : IO, field : FieldInfo) + return if field.type_info.pointer? + + field_name = field.name + io << "delegate :" << to_call(field_name) << "=, to: @data\n" + end + + macro render_initialize + render_initialize(io) + end + + def render_initialize(io : IO) + io << "def initialize(" + io << @struct.fields.map do |field| + "#{to_crystal_arg_decl(field.name)} : #{to_crystal_type(field.type_info)}? = nil" + end.join(", ") + io << ")\n" + io << "@data = " << to_lib_type(@struct) << ".new\n" + generate_ctor_fields_assignment(io, "@data") + io << "end\n" + end + end +end diff --git a/src/generator/struct_gen.cr b/src/generator/struct_gen.cr index 10a40f2..d298442 100644 --- a/src/generator/struct_gen.cr +++ b/src/generator/struct_gen.cr @@ -1,5 +1,5 @@ module Generator - class StructGen < FileGen + abstract class StructGen < FileGen include WrapperUtil include MethodHolder @@ -9,6 +9,28 @@ module Generator super(@struct.namespace) end + def self.new(info : StructInfo) + type_config = BindingConfig.for(info.namespace).type_config(info.name) + case type_config.binding_strategy + in .auto? + if info.copyable? + if info.pod_type? + StackStructGen.new(info) + else + HeapStructGen.new(info) + end + else + HeapWrapperStructGen.new(info) + end + in .stack_struct? + StackStructGen.new(info) + in .heap_struct? + HeapStructGen.new(info) + in .heap_wrapper_struct? + HeapWrapperStructGen.new(info) + end + end + def filename : String "#{@struct.name.underscore}.cr" end @@ -21,10 +43,6 @@ module Generator @struct.name end - def skip?(key : String = subject) : Bool - super || @struct.g_type_struct? || @struct.g_error? - end - def type_name to_crystal_type(@struct, false) end @@ -36,9 +54,12 @@ module Generator def struct_new_method : String String.build do |s| s << "def self.new(" - s << @struct.fields.map do |field| - "#{to_crystal_arg_decl(field.name)} : #{to_crystal_type(field.type_info)}? = nil" - end.join(", ") + @struct.fields.each do |field| + next if ignore_field?(field) + next if field.type_info.pointer? + + s << "#{to_crystal_arg_decl(field.name)} : #{to_crystal_type(field.type_info)}? = nil, " + end s << ")\n" s << "_instance = allocate\n" @@ -48,10 +69,13 @@ module Generator end end - private def generate_ctor_fields_assignment(io) + private def generate_ctor_fields_assignment(io : IO, var : String = "_instance") @struct.fields.each do |field| + next if ignore_field?(field) + next if field.type_info.pointer? + field_name = to_identifier(field.name) - io << "_instance." << field.name << " = " << field_name << " unless " << field_name << ".nil?\n" + io << var << '.' << field.name << " = " << field_name << " unless " << field_name << ".nil?\n" end end @@ -70,6 +94,19 @@ module Generator io << "?" if is_pointer end + private def ignore_field?(field : FieldInfo) : Bool + config.type_config(@struct.name).ignore_field?(field.name) + end + + private def render_getters_and_setters(io : IO) + foreach_field do |field| + next if ignore_field?(field) + + generate_getter(io, field) + generate_setter(io, field) + end + end + private def generate_getter(io : IO, field : FieldInfo) field_name = field.name field_type = field.type_info @@ -85,23 +122,27 @@ module Generator field_type_name(io, field) io << LF - io << "_var = (to_unsafe + " << field.byteoffset << ").as(Pointer(" << to_lib_type(field_type, structs_as_void: true) << "))\n" + io << "value = to_unsafe.as(Pointer(" << to_lib_type(@struct) << ")).value." << to_identifier(field_name) << LF if is_pointer - io << "return if _var.value.null?\n" + io << "return if value.null?\n" + type = case field_type.tag + when .utf8?, .filename? then "UInt8" + else + "Void" + end + io << "value = value.as(Pointer(" << type << "))\n" end - # Bindinged objects ctors expect a pointer to the object, if the same behavior would be used for - # stdlib String class a constructor like `String.new(ptr : Pointer(Pointer(Void))` would need to exists, - # but it doesn't (of course). - obj_ptr_expr = !is_pointer && field_type.tag.interface? ? "_var" : "_var.value" - io << convert_to_crystal(obj_ptr_expr, field.type_info, @struct.fields, :none) << LF + io << convert_to_crystal("value", field.type_info, @struct.fields, :none) << LF io << "\nend\n" end private def generate_setter(io : IO, field : FieldInfo) - field_name = field.name field_type = field.type_info is_pointer = field_type.pointer? + return if is_pointer + + field_name = field.name field_lib_type = to_lib_type(field_type, structs_as_void: true) io << "def " << to_call(field_name) << "=(value : " @@ -109,7 +150,7 @@ module Generator io << ")\n" io << "_var = (to_unsafe + " << field.byteoffset << ").as(Pointer(" << field_lib_type << "))" - if !is_pointer && field_type.tag.interface? + if field_type.tag.interface? iface = field_type.interface if iface.is_a?(StructInfo) && iface.boxed? Log.warn { "Struct with non pointer boxed struct as parameter" } @@ -118,8 +159,7 @@ module Generator end else io << ".value = " - io << "value.nil? ? " << field_lib_type << ".null : " if is_pointer - io << convert_to_lib("value", field_type, :none) + io << convert_to_lib("value", field_type, :none, false) end io << "\nvalue\n" io << "end\n" diff --git a/src/generator/vfunc_gen.cr b/src/generator/vfunc_gen.cr index da8e15e..a2d0fa1 100644 --- a/src/generator/vfunc_gen.cr +++ b/src/generator/vfunc_gen.cr @@ -1,8 +1,9 @@ require "./arg_strategy" +require "./callable_gen" require "./box_helper" module Generator - class VFuncGen < Generator + class VFuncGen < CallableGen include BoxHelper include Helpers @@ -33,6 +34,10 @@ module Generator end end + def callable : CallableInfo + @vfunc + end + private def call_user_method(io) vfunc.args.join(io, ", ") { |param| io << to_identifier(param.name) } end diff --git a/src/generator/wrapper_util.cr b/src/generator/wrapper_util.cr index bddd07f..8a9218b 100644 --- a/src/generator/wrapper_util.cr +++ b/src/generator/wrapper_util.cr @@ -19,7 +19,7 @@ module Generator tag = param_type.tag io << identifier << ".to_a" - if BindingConfig.handmade?(param_type) + if handmade_type?(param_type) ptype_name = to_crystal_type(param_type) if param_type.pointer? io << ".map { |_i| " << ptype_name << ".new(_i).to_unsafe }" diff --git a/src/gi-crystal.cr b/src/gi-crystal.cr index ff2dc84..19ad712 100644 --- a/src/gi-crystal.cr +++ b/src/gi-crystal.cr @@ -1,6 +1,8 @@ -require "./gi_crystal/closure_data_manager" -require "./gi_crystal/util" +require "./gi-crystal/closure_data_manager" +require "./gi-crystal/toggle_ref_manager" +# This module have types, functions and macros used by the generated bindings, if you are just using the bindings you +# should never deal with any of this. module GICrystal # A macro to check if a binding was generated and require it macro require(namespace, version) @@ -8,6 +10,130 @@ module GICrystal {% unless file_exists?(req_path) %} {{ raise "Bindings for #{namespace.id}-#{version.id} not yet generated, run ./bin/gi-crystal first." }} {% end %} - require {{ "../lib/gi-crystal/src/auto/#{namespace.underscore.id}-#{version.id}/#{namespace.underscore.id}" }} + require {{ "gi-crystal/src/auto/#{namespace.underscore.id}-#{version.id}/#{namespace.underscore.id}" }} end + + # This annotation is used to identify types generated by the generator from user types. + annotation GeneratedWrapper + end + + # How the memory ownership is transfered (or not) from C to Crystal and vice-versa. + enum Transfer + # Transfer nothing from the callee (function or the type instance the property belongs to) to the caller. + None + # Transfer the container (list, array, hash table) from the callee to the caller. + Container + # Transfer everything, e.g. the container and its contents from the callee to the caller. + Full + end + + # See `declare_new_method`. + INSTANCE_QDATA_KEY = LibGLib.g_quark_from_static_string("gi-crystal::instance") + + # Raised when trying to cast an object that was already collected by GC. + class ObjectCollectedError < RuntimeError + end + + # :nodoc: + def instance_pointer(object) : Pointer(Void) + {% raise "Implement GICrystal.instance_pointer(object) for your fundamental type." %} + end + + # :nodoc: + def finalize_instance(object) + {% raise "Implement GICrystal.finalize_instance(object) for your fundamental type." %} + end + + # :nodoc: + @[AlwaysInline] + def to_bool(value : Int32) : Bool + value != 0 + end + + # :nodoc: + @[AlwaysInline] + def to_c_bool(value : Bool) : Int32 + value ? 1 : 0 + end + + # :nodoc: + def transfer_null_ended_array(ptr : Pointer(Pointer(UInt8)), transfer : Transfer) : Array(String) + res = Array(String).new + return res if ptr.null? + + item_ptr = ptr + while !item_ptr.value.null? + res << String.new(item_ptr.value) + LibGLib.g_free(item_ptr.value) if transfer.full? + item_ptr += 1 + end + LibGLib.g_free(ptr) unless transfer.none? + res + end + + # :nodoc: + def transfer_array(ptr : Pointer(Pointer(UInt8)), length : Int, transfer : Transfer) : Array(String) + res = Array(String).new(length) + return res if ptr.null? + + length.times do |i| + item_ptr = (ptr + i).value + res << String.new(item_ptr) + LibGLib.g_free(item_ptr) if transfer.full? + end + LibGLib.g_free(ptr) unless transfer.none? + res + end + + # :nodoc: + def transfer_array(ptr : Pointer(UInt8), length : Int, transfer : Transfer) : Slice(UInt8) + slice = Slice(UInt8).new(ptr, length, read_only: true) + if transfer.full? + slice = slice.clone + LibGLib.g_free(ptr) + end + slice + end + + # :nodoc: + def transfer_array(ptr : Pointer(T), length : Int, transfer : Transfer) : Array(T) forall T + Array(T).build(length) do |buffer| + ptr.copy_to(buffer, length) + length + end + ensure + LibGLib.g_free(ptr) if transfer.full? + end + + # :nodoc: + def transfer_full(str : Pointer(UInt8)) : String + String.new(str).tap do + LibGLib.g_free(str) + end + end + + # This declare the `new` method on a instance of type *type*, *qdata_get_func* and *qdata_set_func* are functions used + # to set/get qdata on objects, e.g. `g_object_get_qdata`/`g_object_set_qdata` for GObjects. + # + # GICrystal stores the Crystal object instance in the GObject, so when it appears from C world we try to re-use the + # old Crystal instance if it wasn't garbage collected. The key used for this is `INSTANCE_QDATA_KEY`. + # + # This is mainly used for `GObject::Object`, since `GObject::ParamSpec` doesn't support casts on GICrystal. + macro declare_new_method(type, qdata_get_func, qdata_set_func) + # :nodoc: + def self.new(pointer : Pointer(Void), transfer : GICrystal::Transfer) : self + # Try to recover the Crystal instance if any + instance = LibGObject.{{ qdata_get_func }}(pointer, GICrystal::INSTANCE_QDATA_KEY) + return instance.as({{ type }}) if instance + + # This object never meet Crystal land, so we allocate memory and initialize it. + instance = {{ type }}.allocate + LibGObject.{{ qdata_set_func }}(pointer, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(instance.object_id)) + instance.initialize(pointer, transfer) + GC.add_finalizer(instance) + instance + end + end + + extend self end diff --git a/src/gi-crystal/closure_data_manager.cr b/src/gi-crystal/closure_data_manager.cr new file mode 100644 index 0000000..9dc1dff --- /dev/null +++ b/src/gi-crystal/closure_data_manager.cr @@ -0,0 +1,80 @@ +module GICrystal + # :nodoc: + # ClosureDataManager stores a table with pointers to counters. + # + # When connecting signals we store Crystall callbacks in `::Box` objects, if there's no reference to these box objects the + # Garbage Collector will collect them and the program will crash when the signal gets emitted. + # + # ClosureDataManager is used to hold a reference for these objects and avoid them to be collected while they can still + # be used. + # + # People writing GTK programs must never need to use this function. + class ClosureDataManager + def self.instance + @@instance ||= new + end + + # De-register a pointer, if the count equals to zero, the pointer is removed. + def self.deregister(data : Pointer(Void)) : Nil + instance.deregister(data) + end + + # Register a pointer, if it's already registered the counter will be increased. + def self.register(data : Pointer(Void)) : Pointer(Void) + instance.register(data) + end + + # Returns number of references hold. + def self.count : Int32 + instance.count + end + + # Print all pointers registered on ClosureDataManager + def self.info(io : IO = STDOUT) + instance.info(io) + end + + # Deregister all references hold, no matter their counts. + def self.deregister_all + instance.deregister_all + end + + private def initialize + @closure_data = Hash(Pointer(Void), Int32).new { |h, k| h[k] = 0 } + end + + def deregister_all + @closure_data.clear + end + + def count : Int32 + @closure_data.each_value.sum + end + + def info(io : IO) + @closure_data.each do |ptr, count| + io.puts "0x#{ptr.address.to_s(16)} -> #{count}" + end + io.puts "total closures on hold: #{@closure_data.size}" + end + + def register(data : Pointer(Void)) : Pointer(Void) + {% if flag?(:debugmemory) %} + puts "Registering #{data.address.to_s(16)} on ClosureDataManager, count: #{@closure_data[data]? || 0}." + {% end %} + @closure_data[data] += 1 if data + data + end + + def deregister(data : Pointer(Void)) : Nil + {% if flag?(:debugmemory) %} + puts "Deregistering #{data.address.to_s(16)} from ClosureDataManager, count: #{@closure_data[data]}." + {% end %} + + @closure_data[data] -= 1 + if @closure_data[data] <= 0 + @closure_data.delete(data) + end + end + end +end diff --git a/src/gi-crystal/toggle_ref_manager.cr b/src/gi-crystal/toggle_ref_manager.cr new file mode 100644 index 0000000..964262f --- /dev/null +++ b/src/gi-crystal/toggle_ref_manager.cr @@ -0,0 +1,24 @@ +module GICrystal + # :nodoc: + # This class is used to toggle GObject subclasses' references between a strong and weak state. + module ToggleRefManager + def self.register(data : Void*) : Void* + node = GC.malloc_uncollectable(sizeof(Void*)) + node.as(Void**).value = data + + {% if flag?(:debugmemory) %} + puts "Registering #{data} on ToggleRefManager as #{node}" + {% end %} + + node + end + + def self.deregister(node : Void*) : Nil + {% if flag?(:debugmemory) %} + puts "Deregistering #{node} on ToggleRefManager" + {% end %} + + GC.free(node) + end + end +end diff --git a/src/gi_crystal/closure_data_manager.cr b/src/gi_crystal/closure_data_manager.cr deleted file mode 100644 index 6079aea..0000000 --- a/src/gi_crystal/closure_data_manager.cr +++ /dev/null @@ -1,40 +0,0 @@ -module GICrystal - # :nodoc: - # Code kindly stolen from crystal-gobject shard - class ClosureDataManager - def self.instance - @@instance ||= new - end - - def self.deregister(data : Pointer(Void)) : Nil - instance.deregister(data) - end - - def self.register(data : Pointer(Void)) : Pointer(Void) - instance.register(data) - end - - private def initialize - @closure_data = Hash(Pointer(Void), Int32).new { |h, k| h[k] = 0 } - end - - def register(data : Pointer(Void)) : Pointer(Void) - {% if flag?(:debugmemory) %} - puts "Registering #{data} on ClosureDataManager, count: #{@closure_data[data]? || 0}." - {% end %} - @closure_data[data] += 1 if data - data - end - - def deregister(data : Pointer(Void)) : Nil - {% if flag?(:debugmemory) %} - puts "Deregistering #{data} from ClosureDataManager, count: #{@closure_data[data]}." - {% end %} - - @closure_data[data] -= 1 - if @closure_data[data] <= 0 - @closure_data.delete(data) - end - end - end -end diff --git a/src/gi_crystal/util.cr b/src/gi_crystal/util.cr deleted file mode 100644 index e63e11c..0000000 --- a/src/gi_crystal/util.cr +++ /dev/null @@ -1,136 +0,0 @@ -# This module have types, functions and macros used by the generated bindings, if you are just using the bindings you -# should never deal with any of this. -module GICrystal - # How the memory ownership is transfered (or not) from C to Crystal and vice-versa. - enum Transfer - # Transfer nothing from the callee (function or the type instance the property belongs to) to the caller. - None - # Transfer the container (list, array, hash table) from the callee to the caller. - Container - # Transfer everything, e.g. the container and its contents from the callee to the caller. - Full - end - - # See `declare_new_method`. - INSTANCE_QDATA_KEY = LibGLib.g_quark_from_static_string("gi-crystal::instance") - # See `declare_new_method`. - GC_COLLECTED_QDATA_KEY = LibGLib.g_quark_from_static_string("gi-crystal::gc-collected") - - # Raised when trying to cast an object that was already collected by GC. - class ObjectCollectedError < RuntimeError - end - - # :nodoc: - def gc_collected?(object) : Bool - {% raise "Implement GICrystal.gc_collected?(object) for your fundamental type." %} - end - - # :nodoc: - def instance_pointer(object) : Pointer(Void) - {% raise "Implement GICrystal.instance_pointer(object) for your fundamental type." %} - end - - # :nodoc: - def finalize_instance(object) - {% raise "Implement GICrystal.finalize_instance(object) for your fundamental type." %} - end - - # :nodoc: - @[AlwaysInline] - def to_unsafe(value : String?) - value ? value.to_unsafe : Pointer(UInt8).null - end - - # :nodoc: - @[AlwaysInline] - def to_bool(value : Int32) : Bool - value != 0 - end - - # :nodoc: - def transfer_null_ended_array(ptr : Pointer(Pointer(UInt8)), transfer : Transfer) : Array(String) - res = Array(String).new - return res if ptr.null? - - item_ptr = ptr - while !item_ptr.value.null? - res << String.new(item_ptr.value) - LibGLib.g_free(item_ptr.value) if transfer.full? - item_ptr += 1 - end - LibGLib.g_free(ptr) unless transfer.none? - res - end - - # :nodoc: - def transfer_array(ptr : Pointer(Pointer(UInt8)), length : Int, transfer : Transfer) : Array(String) - res = Array(String).new(length) - return res if ptr.null? - - length.times do |i| - item_ptr = (ptr + i).value - res << String.new(item_ptr) - LibGLib.g_free(item_ptr) if transfer.full? - end - LibGLib.g_free(ptr) unless transfer.none? - res - end - - # :nodoc: - def transfer_array(ptr : Pointer(UInt8), length : Int, transfer : Transfer) : Slice(UInt8) - slice = Slice(UInt8).new(ptr, length, read_only: true) - if transfer.full? - slice = slice.clone - LibGLib.g_free(ptr) - end - slice - end - - # :nodoc: - def transfer_array(ptr : Pointer(T), length : Int, transfer : Transfer) : Array(T) forall T - Array(T).build(length) do |buffer| - ptr.copy_to(buffer, length) - length - end - ensure - LibGLib.g_free(ptr) if transfer.full? - end - - # :nodoc: - def transfer_full(str : Pointer(UInt8)) : String - String.new(str).tap do - LibGLib.g_free(str) - end - end - - # This declare the `new` method on a instance of type *type*, *qdata_get_func* and *qdata_set_func* are functions used - # to set/get qdata on objects, e.g. `g_object_get_qdata`/`g_object_set_qdata` for GObjects. - # - # GICrystal stores two qdatas in objects on following keys: - # - # - INSTANCE_QDATA_KEY: Store the pointer of Crystal wrapper for this C object. - # - GC_COLLECTED_QDATA_KEY: Store 1 if the GC was called for the Crystal wrapper, 0 otherwise. - # - # `INSTANCE_QDATA_KEY` is used when a object comes from a C function and instead of allocate a new wrapper for it - # we just restore the old one. - # `GC_COLLECTED_QDATA_KEY` is used to avoid to restore a wrapper that was already collected by GC. - # - # This is mainly used for `GObject::Object`, since `GObject::ParamSpec` doesn't support casts on GICrystal. - macro declare_new_method(type, qdata_get_func, qdata_set_func) - # :nodoc: - def self.new(pointer : Pointer(Void), transfer : GICrystal::Transfer) : self - # Try to recover the Crystal instance if any - instance = LibGObject.{{ qdata_get_func }}(pointer, GICrystal::INSTANCE_QDATA_KEY) - return instance.as({{ type }}) if instance - - # This object never meet Crystal land, so we allocate memory and initialize it. - instance = {{ type }}.allocate - LibGObject.{{ qdata_set_func }}(pointer, GICrystal::INSTANCE_QDATA_KEY, Pointer(Void).new(instance.object_id)) - instance.initialize(pointer, transfer) - GC.add_finalizer(instance) - instance - end - end - - extend self -end diff --git a/src/gobject_introspection/callable_info.cr b/src/gobject_introspection/callable_info.cr index cab80a0..e71e811 100644 --- a/src/gobject_introspection/callable_info.cr +++ b/src/gobject_introspection/callable_info.cr @@ -27,5 +27,9 @@ module GObjectIntrospection def caller_owns Transfer.from_value(LibGIRepository.g_callable_info_get_caller_owns(self)) end + + def to_s(io : IO) + io << (name? || "?") + end end end diff --git a/src/gobject_introspection/constant_info.cr b/src/gobject_introspection/constant_info.cr index 20404a9..550df70 100644 --- a/src/gobject_introspection/constant_info.cr +++ b/src/gobject_introspection/constant_info.cr @@ -12,7 +12,7 @@ module GObjectIntrospection tag = type_info.tag case tag when .boolean? - value.v_boolean ? "true" : "false" + value.v_boolean == 1 ? "true" : "false" when .int8? "#{value.v_int8}_i8" when .u_int8? diff --git a/src/gobject_introspection/function_info.cr b/src/gobject_introspection/function_info.cr index 5b7fd84..e929316 100644 --- a/src/gobject_introspection/function_info.cr +++ b/src/gobject_introspection/function_info.cr @@ -41,5 +41,9 @@ module GObjectIntrospection def flags Flags.from_value(LibGIRepository.g_function_info_get_flags(self)) end + + def to_s(io : IO) + io << symbol + end end end diff --git a/src/gobject_introspection/gobject_introspection.cr b/src/gobject_introspection/gobject_introspection.cr index c14cfd2..0e6a792 100644 --- a/src/gobject_introspection/gobject_introspection.cr +++ b/src/gobject_introspection/gobject_introspection.cr @@ -2,7 +2,7 @@ require "./lib_glib" require "./lib_gobject" require "./lib_gobject_introspection" -require "../gi_crystal/util" +require "../gi-crystal" lib LibGIRepository fun g_base_info_unref(info : Pointer(LibGIRepository::BaseInfo)) diff --git a/src/gobject_introspection/namespace.cr b/src/gobject_introspection/namespace.cr index 8955c27..a190fce 100644 --- a/src/gobject_introspection/namespace.cr +++ b/src/gobject_introspection/namespace.cr @@ -20,7 +20,8 @@ module GObjectIntrospection protected def initialize(@name, version : String? = nil) @pointer = LibGIRepository.g_irepository_get_default error = Pointer(LibGLib::Error).new(0) - ptr = LibGIRepository.g_irepository_require(@pointer, @name, GICrystal.to_unsafe(version), 0, pointerof(error)) + version_ptr = version ? version.to_unsafe : Pointer(UInt8).null + ptr = LibGIRepository.g_irepository_require(@pointer, @name, version_ptr, 0, pointerof(error)) raise Error.new(String.new(error.value.message)) if ptr.null? @version = String.new(LibGIRepository.g_irepository_get_version(@pointer, @name)) diff --git a/src/gobject_introspection/object_info.cr b/src/gobject_introspection/object_info.cr index 65b6c58..f37c9c9 100644 --- a/src/gobject_introspection/object_info.cr +++ b/src/gobject_introspection/object_info.cr @@ -58,13 +58,13 @@ module GObjectIntrospection StructInfo.new(ptr) if ptr end - def initially_unowned? : Bool - parent = LibGIRepository.g_object_info_get_parent(self) - return false if parent.null? + def inherits?(c_type_name : String) : Bool + parent = to_unsafe + LibGIRepository.g_base_info_ref(parent) while !parent.null? type_name = LibGIRepository.g_object_info_get_type_name(parent) - if LibC.strcmp(type_name, "GInitiallyUnowned").zero? + if LibC.strcmp(type_name, c_type_name).zero? return true else new_parent = LibGIRepository.g_object_info_get_parent(parent) diff --git a/src/gobject_introspection/struct_info.cr b/src/gobject_introspection/struct_info.cr index c5d5558..f17cc11 100644 --- a/src/gobject_introspection/struct_info.cr +++ b/src/gobject_introspection/struct_info.cr @@ -27,6 +27,28 @@ module GObjectIntrospection bytesize.zero? && !type_init.nil? end + def pod_type? : Bool + fields.each do |field| + type_info = field.type_info + tag = type_info.tag + return false if type_info.pointer? + + is_pod = if (tag.boolean? || tag.int8? || tag.u_int8? || tag.int16? || tag.u_int16? || tag.int32? || tag.u_int32? || + tag.int64? || tag.u_int64? || tag.float? || tag.double? || tag.gtype? || tag.unichar?) + true + elsif tag.interface? + iface = type_info.interface.not_nil! + iface.is_a?(StructInfo) && iface.pod_type? + elsif tag.array? && type_info.array_type.c? && type_info.array_fixed_size > 0 + true + else + false + end + return false unless is_pod + end + true + end + def g_type_struct? GICrystal.to_bool(LibGIRepository.g_struct_info_is_gtype_struct(self)) end diff --git a/src/gobject_introspection/type_info.cr b/src/gobject_introspection/type_info.cr index 01ff9ab..06b31bf 100644 --- a/src/gobject_introspection/type_info.cr +++ b/src/gobject_introspection/type_info.cr @@ -46,6 +46,11 @@ module GObjectIntrospection delegate array?, to: tag + def object? : Bool + iface = interface + !iface.nil? && !iface.is_a?(EnumInfo) + end + def interface : BaseInfo? ptr = LibGIRepository.g_type_info_get_interface(self) BaseInfo.build(ptr) if ptr