{"id":17681259,"url":"https://github.com/hugopl/gi-crystal","last_synced_at":"2025-08-22T03:32:38.851Z","repository":{"id":36982275,"uuid":"393261557","full_name":"hugopl/gi-crystal","owner":"hugopl","description":"Tool to generate Crystal bindings for gobject-based libraries (i.e. GTK)","archived":false,"fork":false,"pushed_at":"2024-08-24T04:48:39.000Z","size":539,"stargazers_count":46,"open_issues_count":19,"forks_count":3,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-12-09T20:12:18.344Z","etag":null,"topics":["bindings","crystal","desktop","glib","gobject","gobject-introspection","gtk","gtk4","gui","linux"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hugopl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"hugopl"}},"created_at":"2021-08-06T05:27:23.000Z","updated_at":"2024-10-24T04:08:03.000Z","dependencies_parsed_at":"2023-11-29T19:25:55.021Z","dependency_job_id":"27eb49ca-f2dc-4120-8122-3f35851f12b0","html_url":"https://github.com/hugopl/gi-crystal","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugopl%2Fgi-crystal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugopl%2Fgi-crystal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugopl%2Fgi-crystal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugopl%2Fgi-crystal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hugopl","download_url":"https://codeload.github.com/hugopl/gi-crystal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230554330,"owners_count":18244234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bindings","crystal","desktop","glib","gobject","gobject-introspection","gtk","gtk4","gui","linux"],"created_at":"2024-10-24T09:10:39.052Z","updated_at":"2024-12-20T08:09:12.471Z","avatar_url":"https://github.com/hugopl.png","language":"Crystal","funding_links":["https://github.com/sponsors/hugopl"],"categories":[],"sub_categories":[],"readme":"![Build Status](https://github.com/hugopl/gi-crystal/actions/workflows/ci.yml/badge.svg?branch=main)\n\n# GI Crystal\n\nGI Crystal is a binding generator used to generate Crystal bindings for GObject based libraries using\n[GObject Introspection](https://gi.readthedocs.io/en/latest/).\n\nIf you are looking for GTK4 bindings for Crystal, go to [GTK4](https://github.com/hugopl/gtk4.cr)\n\nI wrote this while studying GObject Introspection to contribute with [crystal-gobject](https://github.com/jhass/crystal-gobject)\nbut at some point I decided to take a different approach on how to generate the bindings, so I started this.\n\nBesides the binding generator this shard provides bindings for GLib, GObject and Gio libraries.\n\n## Installation\n\nYou are probably looking for the [GTK4](https://github.com/hugopl/gtk4.cr) shard, not this one, since this shard is only\nuseful if you are creating a binding for a GObject based library.\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   developer_dependencies:\n     gtk:\n       github: hugopl/gi-crystal\n   ```\n\n2. Run `shards install`\n3. Run `./bin/gi-crystal` to generate the bindings.\n\n## Usage\n\nBindings are specified in [binding.yml](BINDING_YML.md) files. When you run the generator it will scan all `binding.yml`\nfiles under the project directory and generate the bindings at `lib/gi-crystal/src/auto/`.\n\nThe generator is compiled in a _post-install_ task and can be found at `bin/gi-crystal` after you run `shards install`.\n\nSee https://github.com/hugopl/gtk4.cr for an example of how to use it.\n\nIf you want to use just GLib, GObject or Gio bindings do:\n\n```Crystal\nrequire \"gi-crystal/glib\"    # Just GLib bindings\nrequire \"gi-crystal/gobject\" # GLib and GObject bindings\nrequire \"gi-crystal/gio\"     # GLib, GObject and Gio bindings\n```\n\n## Memory Management ❤️‍🔥️\n\nCrystal is a garbage collected language, you create objects and have faith that the GC will free them at some point in time,\nwhile on the other hand GLib uses reference count, the clash of these two approaches of how to deal with memory management\ncan't end up in something beautiful without corner cases, etc... but we try our best to reduce the mess.\n\nThe basic rules are:\n\n- All objects (except enums, flags and unions) are created in the heap (including non GObject C Structs).\n- Boxed structs (except GValue) are always allocated by GLib but owned by Crystal wrappers.\n- If the struct is passed from C to Crystal with \"transfer none\", the struct is copied anyway to ensure that every Crystal object\n  wrapper always points to valid memory. On \"transfer full\" no copy is needed.\n- All Crystal GObject wrappers have just a pointer to the C object (always allocated by GLib) and always hold a reference during their lifetime.\n\nIf you don't know what means `Transfer full`, `Transfer none` and few other terms about GOBject introspection, is worth to\n[read the docs](https://gi.readthedocs.io/en/latest/annotations/giannotations.html#memory-and-lifecycle-management).\n\n### Debugging\n\nTo help debug memory issues you can compile your code with `-Ddebugmemory`, this will print the object address and reference\ncounter to STDOUT when any wrapper object finalize method is called.\n\n## How GObject is mapped to Crystal world\n\nDespite of being written in a language that doesn't have object oriented features, GObject is an object oriented library by design so many things maps easily to OO languages. However each language has its way of doing things and some adaptation is always needed to have a better blending and let the bindings feels more native to the language.\n\n### Class names\n\nClass names do not have the module prefix, i.e. `GFile` from `GLib` module is mapped to `GLib::File`, `GtkLabel` is be mapped to `Gtk::Label`,\nwhere `GLib` and `Gtk` are modules.\n\n### Interfaces\n\nGObject interfaces are mapped to Crystal modules + a dummy class that only implements this module, used when there's some\nfunction returning the interface.\n\n### Down Casts\n\nIf the object was created by Crystal code you can cast it like you do with any Crystal object instance, using `.as?` and `.as`.\n\nIf the object was created by C code, e.g. `Gtk::Builder` where you get everything as a `GObject::Object` instance, Crystal type system doesn't knows the exact type of the object in GObject type system so you need to cast it using `ClassName.cast(instance)` or `ClassName.cast?(instance)`. `.cast` throws a `TypeCastError` if the cast can't be made while `.cast?` just returns `nil`.\n\n```Crystal\n  builder = Gtk::Builder.new_from_string(\"...\") # Returns a Gtk::Object\n  label = Gtk::Label.cast(builder[\"label\"])\n```\n\n## Signal Connections\n\nSuppose you want to connect the `Gtk::Widget` `focus` signal, the C signature is:\n\n```C\ngboolean\nuser_function (GtkWidget       *widget,\n               GtkDirectionType direction,\n               gpointer         user_data)\n```\n\nThe `user_data` parameter is used internally by bindings to pass closure data, so forget about it.\n\nAll signals are translated to a method named `#{signal_name}_signal`, that returns the signal object, the `_signal` suffix\nexists to solve name conflicts like `Gtk::Window` `destroy` method and `destroy` signal.\n\nSo there are 3 ways to connect this signal to a callback:\n\n```Crystal\ndef slot_with_sender(widget, direction)\n  # ...\nend\n# Connect to a slot with all arguments\nwidget.focus_signal.connect(-\u003eslot_with_sender(Gtk::Widget, Gtk::Direction)\n\ndef slot_without_sender(direction)\n  # ...\nend\n# Connect to a slot without the sender\nwidget.focus_signal.connect(-\u003eslot_without_sender(Gtk::Direction)\n\n# Connect to a block (always without sender parameter)\nwidget.focus_signal.connect do |direction|\n  # ...\nend\n```\n\nIf the signal requires a slot that returns nothing, a slot that returns nothing (Nil) must be used, this is a limitation of the current\nimplementation that will probably change in the future to just ignore the return value on those slots.\n\n### After signals\n\nUse the after keyword argument:\n\n```Crystal\n# Connect to a slot without the sender\nwidget.focus_signal.connect(-\u003eslot_without_sender(Gtk::Direction), after: true)\n\n# Connect to a block (always without sender parameter)\nwidget.focus_signal.connect(after: true) do |direction|\n  # ...\nend\n```\n\n### Signals with details\n\n```\n# To connect the equivalent in C to \"notify::my_property\" do\nwidget.notify_signal[\"my_property\"].connect do\n  # ...\nend\n```\n### Disconnecting signals\n\nWhen you connect a signal it returns a `GObject::SignalConnection` object, call the disconnect method on it and it's done.\n\n⚠️ Objects with signals connections will never be garbage collected, so remember to disconnect all signals from your object\nif you want to really free up that beloved memory.\n\n## GValue\n\nWhen returned by methods or as signal parameters they are represented by `GObject::Value` class, however if a method accepts a\nGValue as parameter you can pass any supported value. I.e. you can pass e.g. a plain Int32 to a method that in C expects a GValue.\n\n## GObject inheritance\n\nYou can inherit GObjects, when you do so a new type is registered in GObject type system.\n\nCrystal objects that inherit `GObject` returns the same object reference on casts, i.e. no memory allocation is done.\nFor more examples see the [inheritance tests](spec/inheritance_spec.cr).\n\n## Declaring GObject signals\n\nYou can declare signals in your `GObject::Object` derived class using the `signal` macro, e.g.:\n\n```Crystal\nclass Foo \u003c GObject::Object\n  signal my_signal_without_args\n  signal my_signal(number : Int32, some_float : Float32)\nend\n\n# Using the signal\nfoo = Foo.new\nfoo.my_signal_without_args_signal.connect { puts \"Got signal!\" }\nfoo.my_signal_signal.connect { |a, b| puts \"Got signal with #{a} and #{b}!\" }\n\n# emitting signals\nfoo.my_signal_without_args_signal.emit\nfoo.my_signal_signal.emit(42, 3.14)\n```\n\n⚠️ Meanwhile signals only support parameters of Integer, Float, String and Boolean types.\n\nAlso note that String parameters will be copied for each signal receiver, this is because the String goes to C, then back to\nCrystal as a `const char*` pointer. This may change in the future.\n\n## Declaring GObject properties\n\nGObject Properties are declared using the `GObject::Property` annotation on the instance variable.\n\n### Virtual Methods\n\nVirtual methods must have the `GObject::Virtual` annotation, currently only virtual methods from interfaces are supported.\n\n```Crystal\nclass Widget0 \u003c Gtk::Widget\n  @[GObject::Virtual]\n  def snapshot(snapshot : Gtk::Snapshot)\n  end\nend\n\nclass Widget2 \u003c Gtk::Widget\n  # If there's a name conflict you can name your method whatever you want and use the name annotation attribute.\n  @[GObject::Virtual(name: \"snapshot\")]\n  def heyho(snapshot : Gtk::Snapshot)\n  end\nend\n```\n\nIf for some reason (peformance or GICrystal bugs 🙊️) you don't want wrappers, you can create an unsafe virtual method:\n\n```Crystal\nclass Widget3 \u003c Gtk::Widget\n  @[GObject::Virtual(unsafe: true)]\n  def snapshot(snapshot : Pointer(Void))\n    # User is responsible for memory management here, like in C.\n  end\nend\n```\n\n## GLib GError\n\nGI-Crystal translates all GLib errors to different exceptions.\n\nExample: `G_FILE_ERROR_EXIST` is a GLib error from domain `FILE_ERROR` with the code name `EXIST`, GICrystal translates this\nin these the following exception classes:\n\n```Crystal\nmodule GLib\n  class GLibError \u003c RuntimeError\n  end\n\n  class FileError \u003c GLibError\n    class Exist \u003c FileError\n      def code : Int32\n        # ...\n      end\n    end\n    # ...\n  end\nend\n```\n\nSo if you want to rescue from this specific error you must `rescue e : GLib::FileError::Exist`, if you want to rescue from any\nerror in this domain you must `rescue e : GLib::FileError`, and finally if you want to rescue from any GLib errors you do\n`rescue e : GLib::GLibError`.\n\n## Gio Async Pattern\n\nAll `*_async` methods with a `*_finish` methods receive a block, the block works as the `Gio::AsyncReadyCallback` and you need\nto call the `*_finish` on the `result`, exceptions are raised by the `*_finish` functions on errors.\n\nExample:\n\n```Crystal\nfile = Gio::File.new_for_path(\"/my/nice/file\")\nfile.read_async(0, nil) do |obj, result|\n  obj.as(Gio::File).read_finish(result)\nend\n```\n\n## Raw C Structs\n\nAt [binding.yml](BINDING_YML.md) file you can define the strategy used to bind the structs, if set to `auto`it will behave\nlike lsited bellow:\n\n- If the struct have no pointer attributes it's mapped to a Crystal struct with the same memory layout of the C struct\n  (`stack_struct` binding strategy).\n- If the struct have pointer attributes it's mapped to a Crystal class with the same memory layout of the C struct, so a\n  `finalize` method can be implemented to free the resources. Not that no setters are generated to pointer attributes, since\n  we can't guess how this memory must be handled (`heap_struct` binding strategy).\n- 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\n  object is a GObject Box, so the `g_boxed_*` family of functions are used to handle the memory (`heap_wrapper_struct`\n  binding strategy).\n\n## Contributing\n\nSee [HACKING.md](HACKING.md) for details about how the generator works.\n\n1. Fork it (\u003chttps://github.com/hugopl/gi-crystal/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [Hugo Parente Lima](https://github.com/hugopl) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugopl%2Fgi-crystal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhugopl%2Fgi-crystal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugopl%2Fgi-crystal/lists"}