# Source code for pyvips.gobject

    import logging

    import pyvips
    from pyvips import ffi, gobject_lib, _to_bytes, Error, type_name, \
        at_least_libvips

    logger = logging.getLogger(__name__)

    # the python marshalers for gobject signal handling
    # - we keep a ref to each callback to stop them being GCd
    # - I tried to make this less copy-paste, but failed -- check again

    if pyvips.API_mode:
        @ffi.def_extern()
        def _marshal_image_progress(vi, pointer, handle):
            gobject_lib.g_object_ref(vi)
            image = pyvips.Image(vi)
            callback = ffi.from_handle(handle)
            progress = ffi.cast('VipsProgress*', pointer)
            callback(image, progress)
        _marshal_image_progress_cb =  \
            ffi.cast('GCallback', gobject_lib._marshal_image_progress)
    else:
        @ffi.callback('void(VipsImage*, void*, void*)')
        def _marshal_image_progress(vi, pointer, handle):
            gobject_lib.g_object_ref(vi)
            image = pyvips.Image(vi)
            callback = ffi.from_handle(handle)
            progress = ffi.cast('VipsProgress*', pointer)
            callback(image, progress)
        _marshal_image_progress_cb = \
            ffi.cast('GCallback', _marshal_image_progress)

    _marshalers = {
        'preeval': _marshal_image_progress_cb,
        'eval': _marshal_image_progress_cb,
        'posteval': _marshal_image_progress_cb,
    }

    if at_least_libvips(8, 9):
        if pyvips.API_mode:
            @ffi.def_extern()
            def _marshal_read(gobject, pointer, length, handle):
                buf = ffi.buffer(pointer, length)
                callback = ffi.from_handle(handle)
                return callback(buf)
            _marshal_read_cb = ffi.cast('GCallback', gobject_lib._marshal_read)
        else:
            @ffi.callback('gint64(VipsSourceCustom*, void*, gint64, void*)')
            def _marshal_read(gobject, pointer, length, handle):
                buf = ffi.buffer(pointer, length)
                callback = ffi.from_handle(handle)
                return callback(buf)
            _marshal_read_cb = ffi.cast('GCallback', _marshal_read)
        _marshalers['read'] = _marshal_read_cb

        if pyvips.API_mode:
            @ffi.def_extern()
            def _marshal_seek(gobject, offset, whence, handle):
                callback = ffi.from_handle(handle)
                return callback(offset, whence)
            _marshal_seek_cb = \
                ffi.cast('GCallback', gobject_lib._marshal_seek)
        else:
            @ffi.callback('gint64(VipsSourceCustom*, gint64, int, void*)')
            def _marshal_seek(gobject, offset, whence, handle):
                callback = ffi.from_handle(handle)
                return callback(offset, whence)
            _marshal_seek_cb = ffi.cast('GCallback', _marshal_seek)
        _marshalers['seek'] = _marshal_seek_cb

        if pyvips.API_mode:
            @ffi.def_extern()
            def _marshal_write(gobject, pointer, length, handle):
                buf = ffi.buffer(pointer, length)
                callback = ffi.from_handle(handle)
                return callback(buf)
            _marshal_write_cb = ffi.cast('GCallback', gobject_lib._marshal_write)
        else:
            @ffi.callback('gint64(VipsTargetCustom*, void*, gint64, void*)')
            def _marshal_write(gobject, pointer, length, handle):
                buf = ffi.buffer(pointer, length)
                callback = ffi.from_handle(handle)
                return callback(buf)
            _marshal_write_cb = ffi.cast('GCallback', _marshal_write)
        _marshalers['write'] = _marshal_write_cb

        if pyvips.API_mode:
            @ffi.def_extern()
            def _marshal_finish(gobject, handle):
                callback = ffi.from_handle(handle)
                callback()
            _marshal_finish_cb = ffi.cast('GCallback', gobject_lib._marshal_finish)
        else:
            @ffi.callback('void(VipsTargetCustom*, void*)')
            def _marshal_finish(gobject, handle):
                callback = ffi.from_handle(handle)
                callback()
            _marshal_finish_cb = ffi.cast('GCallback', _marshal_finish)
        _marshalers['finish'] = _marshal_finish_cb

    if at_least_libvips(8, 13):
        if pyvips.API_mode:
            @ffi.def_extern()
            def _marshal_end(gobject, handle):
                callback = ffi.from_handle(handle)
                return callback()
            _marshal_end_cb = ffi.cast('GCallback', gobject_lib._marshal_end)
        else:
            @ffi.callback('int(VipsTargetCustom*, void*)')
            def _marshal_end(gobject, handle):
                callback = ffi.from_handle(handle)
                return callback()
            _marshal_end_cb = ffi.cast('GCallback', _marshal_end)
        _marshalers['end'] = _marshal_end_cb

    class GObject(object):
        """Manage GObject lifetime.

        """
        __slots__ = ('_handles', 'pointer')

        def __init__(self, pointer):
            """Wrap around a pointer.

            Wraps a GObject instance around an underlying pointer. When the
            instance is garbage-collected, the underlying object is unreferenced.

            """

            # we have to record all of the ffi.new_handle we make for callbacks on
            # this object to prevent them being GC'd
            self._handles = []

            # record the pointer we were given to manage
            # on GC, unref
            self.pointer = ffi.gc(pointer, gobject_lib.g_object_unref)

            # logger.debug('GObject.__init__: pointer = %s', str(self.pointer))

        @staticmethod
        def new_pointer_from_gtype(gtype):
            """Make a new GObject pointer from a gtype.

            This is useful for subclasses which need to control the construction
            process.

            You can pass the result pointer to the Python constructor for the
            object you are building. You will need to call VipsObject.build() to
            finish construction.

            Returns:
                A pointer to a new GObject.

            Raises:
                :class:`.Error`

            """

            pointer = gobject_lib.g_object_new(gtype, ffi.NULL)
            if pointer == ffi.NULL:
                raise Error(f"can't create {type_name(gtype)}")

            return pointer

        def signal_connect(self, name, callback):
            """Connect to a signal on this object.

            The callback will be triggered every time this signal is issued on this
            instance. It will be passed the image ('self' here), and a single
            `void *` pointer from libvips.

            The value of the pointer, if any, depends on the signal -- for
            example, ::eval passes a pointer to a `VipsProgress` struct.

            """

            if name not in _marshalers:
                raise Error(f'unsupported signal "{name}"')

            go = ffi.cast('GObject *', self.pointer)
            handle = ffi.new_handle(callback)
            # we need to keep refs to the ffi handle and the callback to prevent
            # them being GCed
            # the callback might be a bound method (a closure) rather than a simple
            # function, so it can vanish
            self._handles.append(handle)
            self._handles.append(callback)

            gobject_lib.g_signal_connect_data(go, _to_bytes(name),
                                              _marshalers[name],
                                              handle, ffi.NULL, 0)

    __all__ = ['GObject']