Skip to content

Commit

Permalink
Merge branch 'hugopl:main' into fix-#48
Browse files Browse the repository at this point in the history
  • Loading branch information
BlobCodes authored Jul 7, 2023
2 parents cbd8ecc + ba45aa2 commit d7560b6
Show file tree
Hide file tree
Showing 93 changed files with 3,680 additions and 1,458 deletions.
3 changes: 2 additions & 1 deletion .ameba.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
Excluded:
- src/auto

Lint/NotNil:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false

Performance/AnyInsteadOfEmpty:
Enabled: false
80 changes: 68 additions & 12 deletions BINDING_YML.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
94 changes: 91 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.

Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 60 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
9 changes: 0 additions & 9 deletions ecr/cast_methods.ecr

This file was deleted.

6 changes: 2 additions & 4 deletions ecr/gobject_constructor.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions ecr/heap_struct.ecr
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit d7560b6

Please sign in to comment.