grim/gplugin

Merge in latest changes
develop
2020-02-25, Richard Laager
3feea9199e7e
Merge in latest changes
  • +12 -2
    ChangeLog
  • +1 -2
    gplugin-gtk-viewer/gplugin-gtk-viewer-window.c
  • +1 -2
    gplugin-gtk-viewer/gplugin-gtk-viewer-window.h
  • +1 -2
    gplugin-gtk-viewer/gplugin-gtk-viewer.c
  • +9 -9
    gplugin-gtk/gplugin-gtk-plugin-info.c
  • +1 -2
    gplugin-gtk/gplugin-gtk-plugin-info.h
  • +12 -13
    gplugin-gtk/gplugin-gtk-store.c
  • +2 -3
    gplugin-gtk/gplugin-gtk-store.h
  • +7 -8
    gplugin-gtk/gplugin-gtk-view.c
  • +1 -2
    gplugin-gtk/gplugin-gtk-view.h
  • +1 -2
    gplugin-gtk/gplugin-gtk.h.in
  • +1 -1
    gplugin-gtk/gplugin-gtk.xml.in
  • +1 -1
    gplugin-gtk/meson.build
  • +11 -14
    gplugin-gtk/reference/gplugin-gtk-docs.xml
  • +2 -1
    gplugin-gtk/reference/meson.build
  • +5 -7
    gplugin/gplugin-core.c
  • +2 -3
    gplugin/gplugin-core.h
  • +2 -3
    gplugin/gplugin-file-tree.c
  • +1 -2
    gplugin/gplugin-file-tree.h
  • +1 -3
    gplugin/gplugin-loader-tests.c
  • +1 -2
    gplugin/gplugin-loader-tests.h
  • +44 -39
    gplugin/gplugin-loader.c
  • +3 -3
    gplugin/gplugin-loader.h
  • +315 -243
    gplugin/gplugin-manager.c
  • +3 -3
    gplugin/gplugin-manager.h
  • +6 -14
    gplugin/gplugin-native-loader.c
  • +1 -2
    gplugin/gplugin-native-loader.h
  • +2 -12
    gplugin/gplugin-native-plugin.c
  • +1 -2
    gplugin/gplugin-native-plugin.h
  • +1 -2
    gplugin/gplugin-native-private.h
  • +1 -1
    gplugin/gplugin-native.h.in
  • +4 -4
    gplugin/gplugin-options.c
  • +1 -3
    gplugin/gplugin-options.h
  • +64 -133
    gplugin/gplugin-plugin-info.c
  • +2 -4
    gplugin/gplugin-plugin-info.h
  • +25 -24
    gplugin/gplugin-plugin.c
  • +1 -2
    gplugin/gplugin-plugin.h
  • +1 -2
    gplugin/gplugin-private.c
  • +1 -2
    gplugin/gplugin-private.h
  • +2 -3
    gplugin/gplugin-query.c
  • +61 -33
    gplugin/gplugin-version.c
  • +2 -3
    gplugin/gplugin-version.h.in
  • +1 -3
    gplugin/gplugin.h.in
  • +4 -5
    gplugin/reference/embedding.xml
  • +70 -0
    gplugin/reference/genie.xml
  • +17 -22
    gplugin/reference/gplugin-docs.xml
  • +26 -25
    gplugin/reference/lua.xml
  • +6 -0
    gplugin/reference/meson.build
  • +7 -7
    gplugin/reference/native-plugins.xml
  • +60 -0
    gplugin/reference/perl.xml
  • +24 -24
    gplugin/reference/python.xml
  • +36 -35
    gplugin/reference/vala.xml
  • +1 -2
    gplugin/tests/bad-plugins/query-error.c
  • +1 -2
    gplugin/tests/bind-local/bind-local.c
  • +1 -0
    gplugin/tests/dynamic-type/dynamic-test.h
  • +1 -1
    gplugin/tests/dynamic-type/dynamic-type-provider.c
  • +1 -1
    gplugin/tests/dynamic-type/dynamic-type-user.c
  • +1 -2
    gplugin/tests/id-collision/id-collision1.c
  • +1 -2
    gplugin/tests/id-collision/id-collision2.c
  • +1 -2
    gplugin/tests/load-on-query-fail/load-on-query-fail.c
  • +1 -2
    gplugin/tests/load-on-query-pass/load-on-query-pass.c
  • +4 -0
    gplugin/tests/meson.build
  • +1 -2
    gplugin/tests/plugins/basic-plugin.c
  • +1 -2
    gplugin/tests/plugins/broken-dependent-plugin.c
  • +1 -2
    gplugin/tests/plugins/dependent-plugin.c
  • +1 -2
    gplugin/tests/plugins/load-exception.c
  • +1 -2
    gplugin/tests/plugins/load-failed.c
  • +1 -2
    gplugin/tests/plugins/unload-failed.c
  • +1 -1
    gplugin/tests/test-bind-local.c
  • +1 -1
    gplugin/tests/test-id-collision.c
  • +179 -0
    gplugin/tests/test-loader-registration.c
  • +12 -18
    gplugin/tests/test-plugin-info.c
  • +41 -41
    gplugin/tests/test-version-compare.c
  • +1 -2
    gplugin/tests/unresolved-symbol/unresolved-symbol.c
  • +1 -2
    gplugin/tests/versioned-dependencies/bar.c
  • +1 -2
    gplugin/tests/versioned-dependencies/baz.c
  • +1 -2
    gplugin/tests/versioned-dependencies/exact1.c
  • +1 -2
    gplugin/tests/versioned-dependencies/exact2.c
  • +1 -2
    gplugin/tests/versioned-dependencies/fez.c
  • +1 -2
    gplugin/tests/versioned-dependencies/greater-equal.c
  • +1 -2
    gplugin/tests/versioned-dependencies/greater.c
  • +1 -2
    gplugin/tests/versioned-dependencies/less-equal.c
  • +1 -2
    gplugin/tests/versioned-dependencies/less.c
  • +1 -2
    gplugin/tests/versioned-dependencies/no-version.c
  • +1 -2
    gplugin/tests/versioned-dependencies/super-dependent.c
  • +3 -8
    lua/gplugin-lua-core.c
  • +4 -4
    lua/gplugin-lua-loader.c
  • +1 -2
    lua/gplugin-lua-loader.h
  • +1 -1
    lua/gplugin-lua-plugin.c
  • +1 -2
    lua/gplugin-lua-plugin.h
  • +1 -2
    lua/gplugin-lua-test-lgi.c
  • +22 -17
    lua/meson.build
  • +1 -3
    lua/tests/plugins/basic.lua
  • +1 -2
    lua/tests/plugins/dependent.lua
  • +1 -2
    lua/tests/plugins/load-exception.lua
  • +1 -2
    lua/tests/plugins/load-failed.lua
  • +1 -2
    lua/tests/plugins/unload-failed.lua
  • +1 -2
    lua/tests/test-lua-loader.c
  • +0 -2
    meson.build
  • +2 -2
    meson_options.txt
  • +15 -2
    packaging/debian/control
  • +1 -0
    packaging/debian/libgplugin-perl.install
  • +0 -1
    packaging/debian/libgplugin-python.install
  • +1 -0
    packaging/debian/libgplugin-python3.install
  • +44 -56
    packaging/gplugin.spec.in
  • +13 -6
    perl/gplugin-perl-core.c
  • +221 -55
    perl/gplugin-perl-loader.c
  • +1 -2
    perl/gplugin-perl-loader.h
  • +1 -7
    perl/gplugin-perl-plugin.c
  • +6 -3
    perl/gplugin-perl-plugin.h
  • +20 -9
    perl/meson.build
  • +12 -0
    perl/tests/meson.build
  • +8 -14
    perl/tests/plugins/basic.pl
  • +35 -0
    perl/tests/plugins/dependent.pl
  • +36 -0
    perl/tests/plugins/load-exception.pl
  • +34 -0
    perl/tests/plugins/load-failed.pl
  • +34 -0
    perl/tests/plugins/unload-failed.pl
  • +30 -0
    perl/tests/test-perl-loader.c
  • +5 -6
    po/POTFILES
  • +0 -77
    python/gplugin-python-core.c
  • +0 -377
    python/gplugin-python-loader.c
  • +0 -34
    python/gplugin-python-loader.h
  • +0 -277
    python/gplugin-python-plugin.c
  • +0 -34
    python/gplugin-python-plugin.h
  • +0 -66
    python/gplugin-python-test-pygobject.c
  • +0 -112
    python/gplugin-python-utils.c
  • +0 -36
    python/gplugin-python-utils.h
  • +74 -0
    python/gplugin-python3-core.c
  • +376 -0
    python/gplugin-python3-loader.c
  • +33 -0
    python/gplugin-python3-loader.h
  • +276 -0
    python/gplugin-python3-plugin.c
  • +33 -0
    python/gplugin-python3-plugin.h
  • +65 -0
    python/gplugin-python3-test-pygobject.c
  • +111 -0
    python/gplugin-python3-utils.c
  • +33 -0
    python/gplugin-python3-utils.h
  • +28 -28
    python/meson.build
  • +9 -13
    python/tests/meson.build
  • +4 -3
    python/tests/plugins/basic.py
  • +4 -3
    python/tests/plugins/dependent.py
  • +3 -4
    python/tests/plugins/load-exception.py
  • +3 -4
    python/tests/plugins/load-failed.py
  • +3 -4
    python/tests/plugins/unload-failed.py
  • +0 -34
    python/tests/test-python-loader.c
  • +0 -76
    python/tests/test-python-utils.c
  • +33 -0
    python/tests/test-python3-loader.c
  • +75 -0
    python/tests/test-python3-utils.c
  • +3 -8
    tcc/gplugin-tcc-core.c
  • +5 -9
    tcc/gplugin-tcc-loader.c
  • +1 -2
    tcc/gplugin-tcc-loader.h
  • +1 -2
    tcc/gplugin-tcc-plugin.c
  • +1 -2
    tcc/gplugin-tcc-plugin.h
  • +1 -2
    tcc/tests/plugins/basic-plugin.c
  • +1 -2
    tcc/tests/plugins/dependent.c
  • +1 -2
    tcc/tests/plugins/load-exception.c
  • +1 -2
    tcc/tests/plugins/load-failed.c
  • +1 -2
    tcc/tests/plugins/unload-failed.c
  • +1 -2
    tcc/tests/test-tcc-loader.c
  • +49 -0
    vala/tests/genie-plugins/basic.gs
  • +41 -0
    vala/tests/genie-plugins/dependent.gs
  • +38 -0
    vala/tests/genie-plugins/load-exception.gs
  • +38 -0
    vala/tests/genie-plugins/load-failed.gs
  • +17 -0
    vala/tests/genie-plugins/meson.build
  • +38 -0
    vala/tests/genie-plugins/unload-failed.gs
  • +10 -0
    vala/tests/meson.build
  • +1 -1
    vala/tests/plugins/basic.vala
  • +1 -1
    vala/tests/plugins/dependent.vala
  • +1 -1
    vala/tests/plugins/load-exception.vala
  • +1 -1
    vala/tests/plugins/load-failed.vala
  • +1 -1
    vala/tests/plugins/unload-failed.vala
  • +33 -0
    vala/tests/test-genie-loading.c
  • +1 -2
    vala/tests/test-vala-loading.c
  • --- a/ChangeLog Sat Feb 15 21:14:07 2020 -0600
    +++ b/ChangeLog Tue Feb 25 22:30:11 2020 -0600
    @@ -1,4 +1,5 @@
    0.29.1:
    + General
    * Changed the website to the repository on keep.imfreedom.org.
    (Gary Kramlich)
    * Removed an unnecessary meson version check. (Gary Kramlich)
    @@ -8,11 +9,20 @@
    * Removed GPluginVersionCompareFunc and the GPluginPluginInfo::version-func
    property as they aren't necessary with semantic versioning. (Gary Kramlich)
    * Fixed licenses throughout the codecase. (Richard Laager)
    +
    + Lua Loader
    + * Removed the moonscript support from the Lua loader. (Gary Kramlich)
    + * Add support for Lua 5.3. (Richard Laager)
    +
    + Perl Loader
    + * After years of on again, off again work, the Perl loader finally works!
    + (Gary Kramlich, Richard Laager)
    +
    + Python Loader
    * Bumped the Python 3 dependency to 3.5. (Gary Kramlich)
    * Replaced mbstowcs with Py_DecodeLocale. (Gary Kramlich)
    * Renamed the python loader from gplugin-python to gplugin-python3.
    (Gary Kramlich)
    - * Removed the moonscript support from the Lua loader. (Gary Kramlich)
    0.29.0: 2019/11/07
    * Synchronize GPluginGtkStore with the plugin manager, make the enabled
    @@ -167,7 +177,7 @@
    * Moved the native loader to the loader-testing static library
    * Overhauled the native loader tests
    * Fixed a bug where in certain conditions a load-on-query plugin that failed
    - to load, would get it's info tracked twice.
    + to load, would get its info tracked twice.
    0.0.11: 2013/12/02
    * Added gplugin_get_option_group which implements options for adding paths
    --- a/gplugin-gtk-viewer/gplugin-gtk-viewer-window.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk-viewer/gplugin-gtk-viewer-window.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,6 +1,5 @@
    /*
    - * talkatu
    - * Copyright (C) 2017-2018 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/gplugin-gtk-viewer/gplugin-gtk-viewer-window.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk-viewer/gplugin-gtk-viewer-window.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,6 +1,5 @@
    /*
    - * talkatu
    - * Copyright (C) 2017-2018 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/gplugin-gtk-viewer/gplugin-gtk-viewer.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk-viewer/gplugin-gtk-viewer.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -214,4 +214,3 @@
    return 0;
    }
    -
    --- a/gplugin-gtk/gplugin-gtk-plugin-info.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-plugin-info.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -21,10 +21,10 @@
    /**
    * SECTION:gplugin-gtk-plugin-info
    - * @Title: Plugin Info Gtk+ Widgets
    - * @Short_description: Gtk+ Widgets for plugins
    + * @title: Plugin Info Gtk Widgets
    + * @short_description: Gtk Widgets for plugins
    *
    - * #GPluginGtkPluginInfo is a Gtk+ widget that shows information about plugins.
    + * #GPluginGtkPluginInfo is a Gtk widget that shows information about plugins.
    */
    /**
    @@ -306,12 +306,12 @@
    /**
    * gplugin_gtk_plugin_info_set_plugin:
    - * @info: The #GPluginGtkPluginInfo instance
    - * @plugin: The #GPluginPlugin instance
    + * @info: The #GPluginGtkPluginInfo instance.
    + * @plugin: The #GPluginPlugin instance.
    *
    * Sets the #GPluginPlugin that should be displayed.
    *
    - * A @plugin value of NULL will clear the widget.
    + * A @plugin value of %NULL will clear the widget.
    */
    void
    gplugin_gtk_plugin_info_set_plugin(GPluginGtkPluginInfo *info,
    @@ -326,12 +326,12 @@
    /**
    * gplugin_gtk_plugin_info_get_plugin:
    - * @info: The #GPluginGtkPluginInfo instance
    + * @info: The #GPluginGtkPluginInfo instance.
    *
    * Returns the #GPluginPlugin that's being displayed.
    *
    * Return Value: (transfer full): The #GPluginPlugin that's being
    - * displayed.
    + * displayed.
    */
    GPluginPlugin *
    gplugin_gtk_plugin_info_get_plugin(GPluginGtkPluginInfo *info) {
    --- a/gplugin-gtk/gplugin-gtk-plugin-info.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-plugin-info.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_GTK_PLUGIN_INFO_H */
    -
    --- a/gplugin-gtk/gplugin-gtk-store.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-store.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -21,10 +21,10 @@
    /**
    * SECTION:gplugin-gtk-store
    - * @Title: GtkTreeModelStore for gplugins
    - * @Short_description: A store for plugins
    + * @title: GtkTreeModelStore for plugins
    + * @short_description: A store for plugins
    *
    - * #GPluginGtkStore is a GtkTreeModel populated with gplugins.
    + * #GPluginGtkStore is a GtkTreeModel populated with plugins.
    */
    /**
    @@ -110,7 +110,7 @@
    plugins = gplugin_manager_find_plugins(id);
    for(l = plugins; l; l = l->next)
    gplugin_gtk_store_add_plugin(store, GPLUGIN_PLUGIN(l->data));
    - gplugin_manager_free_plugin_list(plugins);
    + g_slist_free_full(plugins, g_object_unref);
    }
    static gboolean
    @@ -203,7 +203,7 @@
    static void
    gplugin_gtk_store_init(GPluginGtkStore *store) {
    - GType *types = (GType *)gplugin_gtk_get_store_column_types();
    + GType *types = (GType *)gplugin_gtk_store_get_column_types();
    gtk_list_store_set_column_types(GTK_LIST_STORE(store),
    GPLUGIN_GTK_STORE_N_COLUMNS,
    @@ -225,10 +225,10 @@
    /**
    * gplugin_gtk_store_new:
    *
    - * Create a new GPluginGtkStore which is a prepopulated #GtkTreeStore.
    + * Create a new #GPluginGtkStore which is a prepopulated #GtkTreeStore.
    *
    - * Returns: (transfer full): A new GtkTreeModel prepopulated with all of the
    - * gplugins.
    + * Returns: (transfer full): A new #GtkTreeModel prepopulated with all of the
    + * plugins.
    */
    GPluginGtkStore *
    gplugin_gtk_store_new(void) {
    @@ -236,15 +236,14 @@
    }
    /**
    - * gplugin_gtk_get_store_column_types:
    + * gplugin_gtk_store_get_column_types:
    *
    * Returns the columns that #GPluginGtkStore's will use.
    *
    - * Returns: (transfer none): A list of #GTypes for the columes that the store
    + * Returns: (transfer none): A list of #GType's for the columns that the store
    * will use.
    */
    const GType *
    -gplugin_gtk_get_store_column_types(void) {
    +gplugin_gtk_store_get_column_types(void) {
    return column_types;
    }
    -
    --- a/gplugin-gtk/gplugin-gtk-store.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-store.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -43,9 +43,8 @@
    GPluginGtkStore *gplugin_gtk_store_new(void);
    -const GType *gplugin_gtk_get_store_column_types(void);
    +const GType *gplugin_gtk_store_get_column_types(void);
    G_END_DECLS
    #endif /* GPLUGIN_GTK_STORE_H */
    -
    --- a/gplugin-gtk/gplugin-gtk-view.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-view.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -22,10 +22,10 @@
    /**
    * SECTION:gplugin-gtk-view
    - * @Title: Gtk+ View for gplugins
    - * @Short_description: A view for plugins
    + * @title: GtkTreeView for plugins
    + * @short_description: A view for plugins
    *
    - * #GPluginGtkView is a display widget for a list of GPlugins.
    + * #GPluginGtkView is a display widget for a list of plugins.
    */
    /**
    @@ -232,7 +232,7 @@
    /**
    * gplugin_gtk_view_new:
    *
    - * Creates a new GPluginGtkView.
    + * Creates a new #GPluginGtkView.
    *
    * Returns: (transfer full): The new view.
    */
    @@ -253,7 +253,7 @@
    /**
    * gplugin_gtk_view_set_show_internal:
    - * @view: The #GPluginGtkView instance
    + * @view: The #GPluginGtkView instance.
    * @show_internal: Whether or not to show internal plugins.
    *
    * This function will toggle whether or not the widget will show internal
    @@ -272,7 +272,7 @@
    /**
    * gplugin_gtk_view_get_show_internal:
    - * @view: The #GPluginGtkView instance
    + * @view: The #GPluginGtkView instance.
    *
    * Returns whether or not @view is showing internal plugins.
    */
    @@ -282,4 +282,3 @@
    return view->show_internal;
    }
    -
    --- a/gplugin-gtk/gplugin-gtk-view.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk-view.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_GTK_VIEW_H */
    -
    --- a/gplugin-gtk/gplugin-gtk.h.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk.h.in Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -26,4 +26,3 @@
    #undef GPLUGIN_GTK_GLOBAL_HEADER_INSIDE
    #endif /* GPLUGIN_GTK_H */
    -
    --- a/gplugin-gtk/gplugin-gtk.xml.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/gplugin-gtk.xml.in Tue Feb 25 22:30:11 2020 -0600
    @@ -28,7 +28,7 @@
    toplevel="True"/>
    </glade-widget-classes>
    - <glade-widget-group name="gplugin-gtk" title="GPlugin Gtk+ Widgets">
    + <glade-widget-group name="gplugin-gtk" title="GPlugin Gtk Widgets">
    <glade-widget-class-ref name="GPluginGtkPluginInfo"/>
    <glade-widget-class-ref name="GPluginGtkView"/>
    <glade-widget-class-ref name="GPluginGtkStore"/>
    --- a/gplugin-gtk/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -97,7 +97,7 @@
    pkgconfig.generate(
    name : 'libgplugin-gtk',
    - description : 'Gtk+ widgets for GPlugin',
    + description : 'Gtk widgets for GPlugin',
    version : meson.project_version(),
    filebase : 'gplugin-gtk',
    subdirs : 'gplugin-1.0',
    --- a/gplugin-gtk/reference/gplugin-gtk-docs.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/reference/gplugin-gtk-docs.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -1,20 +1,21 @@
    <?xml version="1.0"?>
    -<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
    - "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
    -
    +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
    + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
    <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
    <!ENTITY version SYSTEM "version.xml">
    ]>
    <book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
    <bookinfo>
    - <title>GPlugin Gtk+ Reference Manual</title>
    + <title>GPlugin Gtk Reference Manual</title>
    <abstract>
    - <title>GPlugin Gtk+ &version;</title>
    + <title>GPluginGtk &version;</title>
    <para>
    - GPlugin Gtk+ is a GObject based library that implements a reusable plugin system that
    - supports loading plugins in other languages
    - via loaders. GPlugin also implements
    - dependencies among the plugins.
    + GPluginGtk is a collection of Gtk widgets to make it easier to
    + integrate GPlugin into applications.
    + </para>
    + <para>
    + The latest version of this documentation can be found on-line at
    + <ulink role="online-location" url="https://docs.pidgin.im/gplugin-gtk/latest">https://docs.pidgin.im/gplugin-gtk/latest/</ulink>.
    </para>
    </abstract>
    </bookinfo>
    @@ -41,10 +42,6 @@
    <title>Index of deprecated symbols</title>
    <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
    </index>
    -<!-- <index id="api-index-2.11.0" role="2.11.0">
    - <title>Index of new symbols in 2.11.0</title>
    - <xi:include href="xml/api-index-2.11.0.xml"><xi:fallback /></xi:include>
    - </index>
    - -->
    +
    <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
    </book>
    --- a/gplugin-gtk/reference/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin-gtk/reference/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -15,7 +15,7 @@
    ]
    fixxref_args = [
    - '--extra-dir', join_paths(meson.current_build_dir(), '../../gplugin/reference')
    + '--extra-dir=' + join_paths(meson.current_build_dir(), '../../gplugin/reference/html')
    ]
    gplugin_gtk_version_xml = configure_file(
    @@ -28,6 +28,7 @@
    gnome.gtkdoc(DOC_MODULE,
    main_xml : DOC_MODULE + '-docs.xml',
    + namespace : 'gplugin_gtk',
    src_dir : gplugin_gtk_inc,
    dependencies : gplugin_gtk_dep,
    ignore_headers : ignore_hfiles,
    --- a/gplugin/gplugin-core.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-core.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -16,7 +16,7 @@
    */
    #include <glib.h>
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <glib-object.h>
    #include <gplugin/gplugin-core.h>
    @@ -25,11 +25,10 @@
    /**
    * SECTION:gplugin-core
    - * @Title: Core API
    - * @Short_description: the core api
    + * @title: Core API
    + * @short_description: the core API
    *
    - * This section contains the core api of gplugin, which includes #gplugin_init
    - * and #gplugin_uninit.
    + * This section contains the core API of GPlugin.
    */
    /******************************************************************************
    @@ -76,4 +75,3 @@
    gplugin_uninit(void) {
    gplugin_manager_private_uninit();
    }
    -
    --- a/gplugin/gplugin-core.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-core.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -25,7 +25,7 @@
    #include <glib.h>
    #include <glib-object.h>
    -#define GPLUGIN_DOMAIN (g_quark_from_static_string("gplugin"))
    +#define GPLUGIN_DOMAIN (g_quark_from_static_string("gplugin"))
    G_BEGIN_DECLS
    @@ -35,4 +35,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_CORE_H */
    -
    --- a/gplugin/gplugin-file-tree.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-file-tree.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -17,7 +17,7 @@
    #include "gplugin-file-tree.h"
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    /******************************************************************************
    * FileTreeEntry API
    @@ -145,4 +145,3 @@
    g_node_destroy(root);
    }
    -
    --- a/gplugin/gplugin-file-tree.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-file-tree.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -33,4 +33,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_FILE_TREE_H */
    -
    --- a/gplugin/gplugin-loader-tests.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-loader-tests.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -19,7 +19,6 @@
    #include <stdlib.h>
    #include <glib.h>
    -#include <glib/gi18n.h>
    #include <gplugin.h>
    @@ -224,4 +223,3 @@
    gplugin_loader_tests_add_tests(short_name);
    }
    -
    --- a/gplugin/gplugin-loader-tests.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-loader-tests.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -29,4 +29,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_OPTIONS_H */
    -
    --- a/gplugin/gplugin-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -20,24 +20,23 @@
    #include <gplugin/gplugin-core.h>
    /**
    + * SECTION:gplugin-loader
    + * @title: Plugin Loader
    + * @short_description: Abstract class for loading plugins
    + *
    + * GPluginLoader defines the base behavior for loaders of all languages.
    + */
    +
    +/**
    * GPLUGIN_TYPE_LOADER:
    *
    * The standard _get_type macro for #GPluginLoader.
    */
    /**
    - * SECTION:gplugin-loader
    - * @Title: Plugin Loader Interface
    - * @Short_description: interface for loading plugins
    - *
    - * A PluginLoader has to implement the interface described here for GPlugin to
    - * be able to use it to load plugins.
    - */
    -
    -/**
    * GPluginLoader:
    *
    - * An abstract data type that should not be accessed directly.
    + * An abstract class that should not be accessed directly.
    */
    /**
    @@ -52,11 +51,11 @@
    * @unload: The unload vfunc is called when the plugin manager wants to unload
    * a previously loaded plugin from this loader.
    *
    - * #GPluginLoader class defines the behavior for loading plugins.
    + * #GPluginLoaderClass defines the behavior for loading plugins.
    */
    /******************************************************************************
    - * GObject Stuff
    + * GObject Implementation
    *****************************************************************************/
    G_DEFINE_ABSTRACT_TYPE(GPluginLoader, gplugin_loader, G_TYPE_OBJECT);
    @@ -74,14 +73,15 @@
    /**
    * gplugin_loader_query_plugin:
    - * @loader: #GPluginLoader instance performing the query
    - * @filename: filename to query
    - * @error: return location for a GError, or NULL
    + * @loader: The #GPluginLoader instance performing the query.
    + * @filename: The filename to query.
    + * @error: (nullable): The return location for a #GError, or %NULL.
    *
    - * This function is called by the plugin manager to ask a loader to query the
    - * given file and determine if it's a usable plugin.
    + * This function is called by the plugin manager to ask @loader to query
    + * @filename and determine if it's a usable plugin.
    *
    - * Return value: (transfer full): A #GPluginPlugin instance or NULL on failure
    + * Return value: (transfer full): A #GPluginPlugin instance or %NULL on
    + * failure.
    */
    GPluginPlugin *
    gplugin_loader_query_plugin(GPluginLoader *loader,
    @@ -104,14 +104,14 @@
    /**
    * gplugin_loader_load_plugin:
    - * @loader: #GPluginLoader instance performing the load
    - * @plugin: #GPluginPlugin instance to load
    - * @error: return location for a GError, or NULL
    + * @loader: The #GPluginLoader instance performing the load.
    + * @plugin: The #GPluginPlugin instance to load.
    + * @error: (nullable): The return location for a #GError, or %NULL.
    *
    - * This function is called by the plugin manager to ask a loader to load the
    - * given plugin.
    + * This function is called by the plugin manager to ask @loader to load
    + * @plugin.
    *
    - * Return value: TRUE if @plugin was loaded successfully, FALSE otherwise
    + * Return value: %TRUE if @plugin was loaded successfully, %FALSE otherwise.
    */
    gboolean
    gplugin_loader_load_plugin(GPluginLoader *loader,
    @@ -139,14 +139,14 @@
    /**
    * gplugin_loader_unload_plugin:
    - * @loader: #GPluginLoader instance performing the unload
    - * @plugin: #GPluginPlugin instance to unload
    - * @error: return location for a GError, or NULL
    + * @loader: The #GPluginLoader instance performing the unload.
    + * @plugin: The #GPluginPlugin instance to unload.
    + * @error: (nullable): The return location for a #GError, or %NULL.
    *
    - * This function is called by the plugin manager to ask a loader to unload the
    - * given plugin.
    + * This function is called by the plugin manager to ask @loader to unload
    + * @plugin.
    *
    - * Return value: TRUE if @plugin was unloaded successfully, FALSE otherwise
    + * Return value: %TRUE if @plugin was unloaded successfully, %FALSE otherwise.
    */
    gboolean
    gplugin_loader_unload_plugin(GPluginLoader *loader,
    @@ -173,21 +173,26 @@
    }
    /**
    - * gplugin_loader_class_get_supported_extensions:
    - * @klass: #GPluginLoader instance
    + * gplugin_loader_get_supported_extensions:
    + * @loader: The #GPluginLoader instance.
    *
    - * Returns a #GSList of string for which extensions the loader supports.
    + * Returns a #GSList of strings containing the extensions that the loader
    + * supports. Each extension should not include the dot. For example: so,
    + * dll, py, etc.
    *
    * Return value: (element-type utf8) (transfer container): A #GSList of
    * extensions that the loader supports.
    */
    GSList *
    -gplugin_loader_class_get_supported_extensions(GPluginLoaderClass *klass) {
    - g_return_val_if_fail(GPLUGIN_IS_LOADER_CLASS(klass), NULL);
    +gplugin_loader_get_supported_extensions(GPluginLoader *loader) {
    + GPluginLoaderClass *klass = NULL;
    +
    + g_return_val_if_fail(GPLUGIN_IS_LOADER(loader), NULL);
    - if(klass->supported_extensions)
    - return klass->supported_extensions(klass);
    + klass = GPLUGIN_LOADER_GET_CLASS(loader);
    + if(klass != NULL && klass->supported_extensions) {
    + return klass->supported_extensions(loader);
    + }
    return NULL;
    }
    -
    --- a/gplugin/gplugin-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -38,7 +38,7 @@
    GObjectClass gparent;
    /*< public >*/
    - GSList *(*supported_extensions)(GPluginLoaderClass *klass);
    + GSList *(*supported_extensions)(GPluginLoader *loader);
    GPluginPlugin *(*query)(GPluginLoader *loader, const gchar *filename, GError **error);
    @@ -49,7 +49,7 @@
    gpointer reserved[4];
    };
    -GSList *gplugin_loader_class_get_supported_extensions(GPluginLoaderClass *klass);
    +GSList *gplugin_loader_get_supported_extensions(GPluginLoader *loader);
    GPluginPlugin *gplugin_loader_query_plugin(GPluginLoader *loader, const gchar *filename, GError **error);
    --- a/gplugin/gplugin-manager.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-manager.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -19,7 +19,7 @@
    #include <string.h>
    #include <glib.h>
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <gplugin/gplugin-manager.h>
    #include <gplugin/gplugin-core.h>
    @@ -61,7 +61,8 @@
    GHashTable *plugins;
    GHashTable *plugins_filename_view;
    - GHashTable *loaders;
    + GSList *loaders;
    + GHashTable *loaders_by_extension;
    gboolean refresh_needed;
    } GPluginManager;
    @@ -76,8 +77,8 @@
    GList *(*get_paths)(GPluginManager *manager);
    - void (*register_loader)(GPluginManager *manager, GType type);
    - void (*unregister_loader)(GPluginManager *manager, GType type);
    + gboolean (*register_loader)(GPluginManager *manager, GType type, GError **error);
    + gboolean (*unregister_loader)(GPluginManager *manager, GType type, GError **error);
    void (*refresh)(GPluginManager *manager);
    @@ -156,6 +157,31 @@
    return TRUE;
    }
    +/*
    + * gplugin_manager_find_loader_by_type:
    + * @manager: The #GPluginManager instance.
    + * @type: The #GType of the loader to find.
    + *
    + * Looks up a #GPluginLoader instance by its type.
    + *
    + * Returns: (transfer none): The #GPluginLoader instance or %NULL.
    + */
    +static GPluginLoader *
    +gplugin_manager_find_loader_by_type(GPluginManager *manager, GType type) {
    + GSList *l = NULL;
    +
    + g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
    + g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), NULL);
    +
    + for(l = manager->loaders; l; l = l->next) {
    + if(G_OBJECT_TYPE(l->data) == type) {
    + return GPLUGIN_LOADER(l->data);
    + }
    + }
    +
    + return NULL;
    +}
    +
    /******************************************************************************
    * Manager implementation
    *****************************************************************************/
    @@ -219,37 +245,37 @@
    return manager->paths->head;
    }
    -static void
    -gplugin_manager_real_register_loader(GPluginManager *manager,
    - GType type)
    +static gboolean
    +gplugin_manager_real_register_loader(GPluginManager *manager, GType type,
    + GError **error)
    {
    GPluginLoader *loader = NULL;
    - GPluginLoaderClass *lo_class = NULL;
    - GSList *l = NULL;
    + GSList *l = NULL, *exts = NULL;
    +
    + g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), FALSE);
    - g_return_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER));
    + loader = gplugin_manager_find_loader_by_type(manager, type);
    + if(GPLUGIN_IS_LOADER(loader)) {
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + _("loader %s was already registered"), g_type_name(type));
    + return FALSE;
    + }
    /* Create the loader instance first. If we can't create it, we bail */
    loader = g_object_new(type, NULL);
    if(!GPLUGIN_IS_LOADER(loader)) {
    - g_warning(_("failed to create loader instance for %s"),
    - g_type_name(type));
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + _("failed to create loader instance for %s"),
    + g_type_name(type));
    - return;
    + return FALSE;
    }
    - /* grab the class of the loader */
    - lo_class = GPLUGIN_LOADER_GET_CLASS(loader);
    - if(!lo_class) {
    - g_warning(_("failed to get the loader class for %s"), g_type_name(type));
    - g_object_unref(G_OBJECT(loader));
    + manager->loaders = g_slist_prepend(manager->loaders,
    + g_object_ref(G_OBJECT(loader)));
    - return;
    - }
    -
    - for(l = gplugin_loader_class_get_supported_extensions(lo_class);
    - l; l = l->next)
    - {
    + exts = gplugin_loader_get_supported_extensions(loader);
    + for(l = exts; l; l = l->next) {
    GSList *existing = NULL, *ll = NULL;
    const gchar *ext = (const gchar *)l->data;
    @@ -257,7 +283,7 @@
    * we can prepend our loader. But before we add ours, we remove any
    * old copies we might have of ours.
    */
    - existing = g_hash_table_lookup(manager->loaders, ext);
    + existing = g_hash_table_lookup(manager->loaders_by_extension, ext);
    for(ll = existing; ll; ll = ll->next) {
    if(G_OBJECT_TYPE(ll->data) == type) {
    GPluginLoader *old = GPLUGIN_LOADER(ll->data);
    @@ -273,40 +299,48 @@
    existing = g_slist_prepend(existing, g_object_ref(G_OBJECT(loader)));
    /* Now insert the updated slist back into the hash table */
    - g_hash_table_insert(manager->loaders, g_strdup(ext), existing);
    + g_hash_table_insert(manager->loaders_by_extension, g_strdup(ext),
    + existing);
    }
    + g_slist_free(exts);
    /* make a note that we need to refresh */
    manager->refresh_needed = TRUE;
    /* we remove our initial reference from the loader now to avoid a leak */
    g_object_unref(G_OBJECT(loader));
    +
    + return TRUE;
    }
    -static void
    -gplugin_manager_real_unregister_loader(GPluginManager *manager,
    - GType type)
    +static gboolean
    +gplugin_manager_real_unregister_loader(GPluginManager *manager, GType type,
    + GError **error)
    {
    - GPluginLoaderClass *klass = NULL;
    - GSList *exts = NULL;
    + GPluginLoader *loader = NULL;
    + GSList *l = NULL, *exts = NULL;
    - g_return_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER));
    + g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), FALSE);
    - klass = g_type_class_ref(type);
    - if(!klass)
    - return;
    + loader = gplugin_manager_find_loader_by_type(manager, type);
    + if(!GPLUGIN_IS_LOADER(loader)) {
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + _("loader %s is not registered"), g_type_name(type));
    - for(exts = gplugin_loader_class_get_supported_extensions(klass);
    - exts; exts = exts->next) {
    + return FALSE;
    + }
    +
    + exts = gplugin_loader_get_supported_extensions(loader);
    + for(l = exts; l; l = l->next) {
    GSList *los = NULL;
    - GSList *l = NULL;
    + GSList *ll = NULL;
    const gchar *ext = NULL;
    ext = (const gchar *)exts->data;
    - los = g_hash_table_lookup(manager->loaders, ext);
    + los = g_hash_table_lookup(manager->loaders_by_extension, ext);
    - for(l = los; l; l = l->next) {
    - GPluginLoader *lo = GPLUGIN_LOADER(l->data);
    + for(ll = los; ll; ll = ll->next) {
    + GPluginLoader *lo = GPLUGIN_LOADER(ll->data);
    /* check if this is not the loader we're looking for */
    if(G_OBJECT_TYPE(lo) != type)
    @@ -318,10 +352,12 @@
    * just update it.
    */
    los = g_slist_remove(los, lo);
    - if(los)
    - g_hash_table_insert(manager->loaders, g_strdup(ext), los);
    - else
    - g_hash_table_remove(manager->loaders, ext);
    + if(los) {
    + g_hash_table_insert(manager->loaders_by_extension,
    + g_strdup(ext), los);
    + } else {
    + g_hash_table_remove(manager->loaders_by_extension, ext);
    + }
    /* kill our ref to the loader */
    g_object_unref(G_OBJECT(lo));
    @@ -330,8 +366,12 @@
    break;
    }
    }
    + g_slist_free(exts);
    - g_type_class_unref(klass);
    + manager->loaders = g_slist_remove(manager->loaders, loader);
    + g_object_unref(G_OBJECT(loader));
    +
    + return TRUE;
    }
    static void
    @@ -380,7 +420,7 @@
    GPluginPluginState state =
    gplugin_plugin_get_state(plugin);
    - /* The plugin is in our "view", check it's state. If it's
    + /* The plugin is in our "view", check its state. If it's
    * queried or loaded, move on to the next one.
    */
    if(state == GPLUGIN_PLUGIN_STATE_QUERIED ||
    @@ -392,9 +432,8 @@
    }
    /* grab the list of loaders for this extension */
    - for(l = g_hash_table_lookup(manager->loaders, e->extension); l;
    - l = l->next)
    - {
    + l = g_hash_table_lookup(manager->loaders_by_extension, e->extension);
    + for(; l; l = l->next) {
    if(!l->data)
    continue;
    @@ -409,7 +448,7 @@
    filename,
    &error);
    - /* Check the GError, if it's set, output it's message and
    + /* Check the GError, if it's set, output its message and
    * try the next loader.
    */
    if(plugin == NULL || error) {
    @@ -605,124 +644,32 @@
    GPluginPluginInfo *info,
    GError **error)
    {
    - const gchar * const *dependencies = NULL;
    - gint i = 0;
    -
    - /* now walk through any dependencies the plugin has and load them. If they
    - * fail to load we need to fail as well.
    - */
    - dependencies = gplugin_plugin_info_get_dependencies(info);
    - if(dependencies != NULL) {
    - gboolean all_found = TRUE;
    -
    - for(i = 0; dependencies[i]; i++) {
    - gboolean found = FALSE;
    - gchar **ors = NULL;
    - gint o = 0;
    -
    - ors = g_strsplit(dependencies[i], "|", 0);
    - for(o = 0; ors[o]; o++) {
    - GMatchInfo *match = NULL;
    - GSList *matches = NULL, *m = NULL;
    - gchar *oid = NULL, *oop = NULL, *over = NULL;
    + GSList *dependencies = NULL, *l = NULL;
    + GError *ourerror = NULL;
    + gboolean all_loaded = TRUE;
    - if(!g_regex_match(dependency_regex, ors[o], 0, &match)) {
    - continue;
    - }
    -
    - /* grab the or'd id, op, and version */
    - oid = g_match_info_fetch_named(match, "id");
    - oop = g_match_info_fetch_named(match, "op");
    - over = g_match_info_fetch_named(match, "version");
    -
    - /* free the match info */
    - g_match_info_free(match);
    -
    - /* now look for a plugin matching the id */
    - matches = gplugin_manager_find_plugins(oid);
    - if(matches == NULL) {
    - g_free(oid);
    - g_free(oop);
    - g_free(over);
    - continue;
    - }
    -
    - /* now iterate the matches and check if we need to check their
    - * version.
    - */
    - for(m = matches; m; m = m->next) {
    - GPluginPlugin *dplugin = GPLUGIN_PLUGIN(g_object_ref(G_OBJECT(m->data)));
    - gboolean ret = FALSE;
    + dependencies = gplugin_manager_get_plugin_dependencies(plugin, &ourerror);
    + if(ourerror != NULL) {
    + g_propagate_error(error, ourerror);
    - if(oop && over) {
    - /* we need to check the version, so grab the info to
    - * get the version and check it.
    - */
    - GPluginPluginInfo *dinfo = NULL;
    - const gchar *dver = NULL;
    - gboolean satisfied = FALSE;
    - gint res = 0;
    -
    - dinfo = gplugin_plugin_get_info(dplugin);
    - dver = gplugin_plugin_info_get_version(dinfo);
    -
    - res = gplugin_version_compare(dver, over, error);
    - g_object_unref(G_OBJECT(dinfo));
    -
    - if(res < 0) {
    - /* dver is greather than over */
    - if(g_strcmp0(oop, ">") == 0)
    - satisfied = TRUE;
    - } else if(res == 0) {
    - /* dver is equal to over */
    - if(g_strcmp0(oop, ">=") == 0 ||
    - g_strcmp0(oop, "<=") == 0 ||
    - g_strcmp0(oop, "=") == 0 ||
    - g_strcmp0(oop, "==") == 0)
    - {
    - satisfied = TRUE;
    - }
    - } else if(res > 0) {
    - if(g_strcmp0(oop, "<") == 0)
    - satisfied = TRUE;
    - }
    + return FALSE;
    + }
    - if(satisfied)
    - found = TRUE;
    - }
    -
    - ret = gplugin_manager_load_plugin(dplugin, error);
    -
    -# warning need to figure out dependencies
    -// gplugin_plugin_add_dependent_plugin(dplugin, plugin);
    -
    - g_object_unref(G_OBJECT(dplugin));
    + for(l = dependencies; l != NULL; l = l->next) {
    + GPluginPlugin *dependency = GPLUGIN_PLUGIN(l->data);
    + gboolean loaded = FALSE;
    - if(ret) {
    - found = TRUE;
    - break;
    - }
    - }
    -
    - g_free(oid);
    - g_free(oop);
    - g_free(over);
    + loaded = gplugin_manager_load_plugin(dependency, error);
    - if(found)
    - break;
    - }
    - g_strfreev(ors);
    -
    - if(!found)
    - all_found = FALSE;
    - }
    -
    - if(!all_found) {
    - return FALSE;
    + if(!loaded) {
    + all_loaded = FALSE;
    + break;
    }
    }
    - return TRUE;
    + g_slist_free_full(dependencies, g_object_unref);
    +
    + return all_loaded;
    }
    static GSList *
    @@ -733,7 +680,7 @@
    GPluginPluginInfo *info = NULL;
    GSList *ret = NULL;
    const gchar * const *dependencies = NULL;
    - gint idx = 0;
    + gint i = 0;
    info = gplugin_plugin_get_info(plugin);
    dependencies = gplugin_plugin_info_get_dependencies(info);
    @@ -742,26 +689,64 @@
    return NULL;
    }
    - for(idx = 0; dependencies[idx] != NULL; idx++) {
    - GPluginPlugin *dep = gplugin_manager_find_plugin(dependencies[idx]);
    + for(i = 0; dependencies[i] != NULL; i++) {
    + gboolean found = FALSE;
    + gchar **ors = NULL;
    + gint o = 0;
    +
    + ors = g_strsplit(dependencies[i], "|", 0);
    + for(o = 0; ors[o]; o++) {
    + GMatchInfo *match = NULL;
    + GSList *matches = NULL;
    + gchar *oid = NULL, *oop = NULL, *over = NULL;
    +
    + if(!g_regex_match(dependency_regex, ors[o], 0, &match)) {
    + continue;
    + }
    +
    + /* grab the or'd id, op, and version */
    + oid = g_match_info_fetch_named(match, "id");
    + oop = g_match_info_fetch_named(match, "op");
    + over = g_match_info_fetch_named(match, "version");
    +
    + /* free the match info */
    + g_match_info_free(match);
    - if(!GPLUGIN_IS_PLUGIN(dep)) {
    - if(error) {
    - *error = g_error_new(
    - GPLUGIN_DOMAIN,
    - 0,
    - "failed to find plugin dependency '%s'",
    - dependencies[idx]
    - );
    + /* now look for a plugin matching the id */
    + matches = gplugin_manager_find_plugins_with_version(oid, oop,
    + over);
    + g_free(oid);
    + g_free(oop);
    + g_free(over);
    +
    + if(matches == NULL) {
    + continue;
    + }
    +
    + /* prepend the first found match to our return value */
    + ret = g_slist_prepend(ret, g_object_ref(matches->data));
    + g_slist_free_full(matches, g_object_unref);
    +
    + found = TRUE;
    - g_slist_free_full(ret, g_object_unref);
    + break;
    + }
    + g_strfreev(ors);
    +
    + if(!found) {
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + _("failed to find dependency %s for %s"),
    + dependencies[i], gplugin_plugin_info_get_id(info));
    - return NULL;
    - }
    + g_slist_free_full(ret, g_object_unref);
    +
    + g_object_unref(G_OBJECT(info));
    +
    + return NULL;
    }
    + }
    - ret = g_slist_prepend(ret, dep);
    - }
    + g_object_unref(G_OBJECT(info));
    return ret;
    }
    @@ -904,11 +889,15 @@
    /* destroy the filename view */
    g_clear_pointer(&manager->plugins_filename_view, g_hash_table_destroy);
    + /* clean up our list of loaders */
    + g_slist_free_full(manager->loaders, g_object_unref);
    + manager->loaders = NULL;
    +
    /* free all the data in the loaders hash table and destroy it */
    - g_hash_table_foreach_remove(manager->loaders,
    + g_hash_table_foreach_remove(manager->loaders_by_extension,
    gplugin_manager_remove_list_value,
    NULL);
    - g_clear_pointer(&manager->loaders, g_hash_table_destroy);
    + g_clear_pointer(&manager->loaders_by_extension, g_hash_table_destroy);
    /* call the base class's destructor */
    G_OBJECT_CLASS(gplugin_manager_parent_class)->finalize(obj);
    @@ -1065,18 +1054,18 @@
    manager->plugins_filename_view =
    g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
    - /* The loaders hash table is keyed on the supported extensions of the
    - * loader. Which means that a loader that supports multiple extensions
    - * will be in the table multiple times.
    + /* The loaders_by_extension hash table is keyed on the supported extensions
    + * of the loader. Which means that a loader that supports multiple
    + * extensions will be in the table multiple times.
    *
    * We deal with collisions by using a GSList for the value which will hold
    * references to instances of the actual loaders.
    *
    - * Storing this in this method allows up to quickly figure out which loader
    + * Storing this in this method allows us to quickly figure out which loader
    * to use by the filename and helps us to avoid iterating the loaders table
    * again and again.
    */
    - manager->loaders =
    + manager->loaders_by_extension =
    g_hash_table_new_full(gplugin_manager_str_hash, g_str_equal,
    g_free, NULL);
    }
    @@ -1086,13 +1075,22 @@
    *****************************************************************************/
    void
    gplugin_manager_private_init(void) {
    + GError *error = NULL;
    +
    if (instance != NULL) {
    return;
    }
    instance = g_object_new(GPLUGIN_TYPE_MANAGER, NULL);
    - gplugin_manager_register_loader(GPLUGIN_TYPE_NATIVE_LOADER);
    + if(!gplugin_manager_register_loader(GPLUGIN_TYPE_NATIVE_LOADER, &error)) {
    + if(error != NULL) {
    + g_error("failed to register loader: %s", error->message);
    + g_error_free(error);
    + } else {
    + g_error("failed to register loader: unknown failure");
    + }
    + }
    dependency_regex = g_regex_new(dependency_pattern, 0, 0, NULL);
    }
    @@ -1110,7 +1108,7 @@
    /**
    * gplugin_manager_append_path:
    - * @path: A path to add to the end of the plugin search paths
    + * @path: A path to add to the end of the plugin search paths.
    *
    * Adds @path to the end of the list of paths to search for plugins.
    */
    @@ -1129,7 +1127,7 @@
    /**
    * gplugin_manager_prepend_path:
    - * @path: A path to add to the beginning of the plugin search paths
    + * @path: A path to add to the beginning of the plugin search paths.
    *
    * Adds @path to the beginning of the list of paths to search for plugins.
    */
    @@ -1148,7 +1146,7 @@
    /**
    * gplugin_manager_remove_path:
    - * @path: A path to remove from the plugin search paths
    + * @path: A path to remove from the plugin search paths.
    *
    * Removes @path from the list of paths to search for plugins.
    */
    @@ -1187,7 +1185,8 @@
    * gplugin_manager_add_default_paths:
    *
    * Adds the path that GPlugin was installed to to the plugin search path, as
    - * well as ${XDG_CONFIG_HOME}/gplugin.
    + * well as `${XDG_CONFIG_HOME}/gplugin` so users can install additional loaders
    + * themselves.
    */
    void
    gplugin_manager_add_default_paths(void) {
    @@ -1209,8 +1208,8 @@
    * @appname: The name of the application whose paths to add.
    *
    * Adds the application installation path for @appname. This will add
    - * $prefix/@appname/plugins to the list as well as
    - * ${XDG_CONFIG_HOME}/@appname/plugins.
    + * `@prefix/@appname/plugins` to the list as well as
    + * `${XDG_CONFIG_HOME}/@appname/plugins`.
    */
    void
    gplugin_manager_add_app_paths(const gchar *prefix,
    @@ -1232,10 +1231,10 @@
    /**
    * gplugin_manager_get_paths:
    *
    - * Gets the list of paths which will be search for plugins.
    + * Gets the list of paths which will be searched for plugins.
    *
    - * Return value: (element-type utf8) (transfer none): list of paths which will
    - * be searched for plugins.
    + * Returns: (element-type utf8) (transfer none): The list of paths which will
    + * be searched for plugins.
    */
    GList *
    gplugin_manager_get_paths(void) {
    @@ -1253,38 +1252,56 @@
    /**
    * gplugin_manager_register_loader:
    - * @type: #GType of a #GPluginLoader
    + * @type: #GType of a #GPluginLoader.
    + * @error: (out) (nullable): The return address for a #GError.
    *
    * Registers @type as an available loader.
    + *
    + * Returns: %TRUE if the loader was successfully register, %FALSE otherwise
    + * with @error set.
    */
    -void
    -gplugin_manager_register_loader(GType type) {
    +gboolean
    +gplugin_manager_register_loader(GType type, GError **error) {
    GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
    GPluginManagerClass *klass = NULL;
    - g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
    + g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
    klass = GPLUGIN_MANAGER_GET_CLASS(manager);
    if(klass && klass->register_loader)
    - klass->register_loader(manager, type);
    + return klass->register_loader(manager, type, error);
    +
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + "register_loader method not implemented");
    +
    + return FALSE;
    }
    /**
    * gplugin_manager_unregister_loader:
    - * @type: #GType of a #GPluginLoader
    + * @type: #GType of a #GPluginLoader.
    + * @error: (out) (nullable): The return address for a #GError.
    *
    * Unregisters @type as an available loader.
    + *
    + * Returns: %TRUE if the loader was successfully unregistered, %FALSE
    + * otherwise with @error set.
    */
    -void
    -gplugin_manager_unregister_loader(GType type) {
    +gboolean
    +gplugin_manager_unregister_loader(GType type, GError **error) {
    GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
    GPluginManagerClass *klass = NULL;
    - g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
    + g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
    klass = GPLUGIN_MANAGER_GET_CLASS(manager);
    if(klass && klass->unregister_loader)
    - klass->unregister_loader(manager, type);
    + return klass->unregister_loader(manager, type, error);
    +
    + g_set_error(error, GPLUGIN_DOMAIN, 0,
    + "unregister_loader method not implemented");
    +
    + return FALSE;
    }
    /**
    @@ -1306,14 +1323,14 @@
    /**
    * gplugin_manager_find_plugins:
    - * @id: id string of the plugin to find
    + * @id: id string of the plugin to find.
    *
    * Finds all plugins matching @id.
    *
    - * Return value: (element-type GPlugin.Plugin) (transfer full): A #GSList of
    - * referenced #GPluginPlugin's matching @id. Call
    - * #gplugin_manager_free_plugin_list on the returned value
    - * when you're done with it.
    + * Returns: (element-type GPlugin.Plugin) (transfer full): A #GSList of
    + * referenced #GPluginPlugin's matching @id. Call
    + * g_slist_free_full() with a `DestroyNotify` of g_object_unref() on
    + * the returned value when you're done with it.
    */
    GSList *
    gplugin_manager_find_plugins(const gchar *id) {
    @@ -1330,30 +1347,84 @@
    }
    /**
    - * gplugin_manager_free_plugin_list:
    - * @plugins_list: (element-type GPlugin.Plugin): Returned value from
    - * #gplugin_manager_find_plugins
    + * gplugin_manager_find_plugins_with_version:
    + * @id: The ID of the plugin to find.
    + * @op: one of <, <=, =, ==, >=, >.
    + * @version: The version to compare against.
    + *
    + * Similar to gplugin_manager_find_plugins() but only returns plugins whose
    + * versions match @op and @version. This is primarily used for dependency
    + * loading where a plugin may depend on a specific range of versions of another
    + * plugin.
    *
    - * Frees the return value of #gplugin_manager_find_plugins.
    + * Returns: (element-type GPlugin.Plugin) (transfer full): A #GSList of
    + * referenced #GPluginPlugin's matching @id. Call
    + * g_slist_free_full() with a `DestroyNotify` of g_object_unref() on
    + * the returned value when you're done with it.
    */
    -void
    -gplugin_manager_free_plugin_list(GSList *plugins_list) {
    - GSList *l = NULL;
    +GSList *
    +gplugin_manager_find_plugins_with_version(const gchar *id, const gchar *op,
    + const gchar *version)
    +{
    + GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
    + GSList *plugins = NULL, *filtered = NULL, *l = NULL;
    - g_return_if_fail(plugins_list != NULL);
    + g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
    - for(l = plugins_list; l; l = l->next) {
    - GPluginPlugin *plugin = NULL;
    + plugins = gplugin_manager_find_plugins(id);
    - if(l->data == NULL)
    - continue;
    -
    - plugin = GPLUGIN_PLUGIN(l->data);
    -
    - g_object_unref(G_OBJECT(plugin));
    + if(op == NULL && version == NULL) {
    + /* we weren't actually passed an operator and a version so just return
    + * the list we have based on the id.
    + */
    + return plugins;
    }
    - g_slist_free(plugins_list);
    + for(l = plugins; l; l = l->next) {
    + GPluginPlugin *plugin = GPLUGIN_PLUGIN(l->data);
    + GPluginPluginInfo *info = NULL;
    + GError *error = NULL;
    + const gchar *found_version = NULL;
    + gint result = 0;
    + gboolean keep = FALSE;
    +
    + /* get the plugin's version from it's info */
    + info = gplugin_plugin_get_info(plugin);
    + found_version = gplugin_plugin_info_get_version(info);
    + g_object_unref(G_OBJECT(info));
    +
    + /* now compare the version of the plugin to passed in version. This
    + * should be done in this order so it's easier to track the operators.
    + * IE: we want to keep the inequality the same.
    + */
    + result = gplugin_version_compare(found_version, version, &error);
    + if(error != NULL) {
    + g_warning("failed to compare versions for %s: %s",
    + id,
    + error->message ? error->message : _("unknown error"));
    +
    + g_clear_error(&error);
    +
    + continue;
    + }
    +
    + if(result < 0) {
    + keep = (g_strcmp0(op, "<") == 0 || g_strcmp0(op, "<=") == 0);
    + } else if(result == 0) {
    + keep = (g_strcmp0(op, "=") == 0 || g_strcmp0(op, "==") == 0 ||
    + g_strcmp0(op, "<=") == 0 || g_strcmp0(op, ">=") == 0);
    + } else if(result > 0) {
    + keep = (g_strcmp0(op, ">") == 0 || g_strcmp0(op, ">=") == 0);
    + }
    +
    + if(keep) {
    + filtered = g_slist_prepend(filtered, g_object_ref(G_OBJECT(plugin)));
    + }
    + }
    +
    + g_slist_free_full(plugins, g_object_unref);
    +
    + return g_slist_reverse(filtered);
    }
    /**
    @@ -1380,7 +1451,7 @@
    plugin = GPLUGIN_PLUGIN(g_object_ref(G_OBJECT(plugins_list->data)));
    - gplugin_manager_free_plugin_list(plugins_list);
    + g_slist_free_full(plugins_list, g_object_unref);
    return plugin;
    }
    @@ -1388,13 +1459,15 @@
    /**
    * gplugin_manager_get_plugin_dependencies:
    * @plugin: The #GPluginPlugin whose dependencies to get.
    - * @error: Return address for a #GError.
    + * @error: (out) (nullable): Return address for a #GError.
    *
    * Returns a list of all the #GPluginPlugin's that @plugin depends on.
    *
    * Return value: (element-type GPlugin.Plugin) (transfer full): A #GSList of
    - * #GPluginPlugin's that @plugin depends on, or NULL on error
    - * with @error set.
    + * #GPluginPlugin's that @plugin depends on, or %NULL on error
    + * with @error set. Call g_slist_free_full() with a
    + * `DestroyNotify` of g_object_unref() on the returned value when
    + * you're done with it.
    */
    GSList *
    gplugin_manager_get_plugin_dependencies(GPluginPlugin *plugin, GError **error) {
    @@ -1416,15 +1489,15 @@
    /**
    * gplugin_manager_load_plugin:
    - * @plugin: #GPluginPlugin instance
    - * @error: (out): return location for a #GError or null
    + * @plugin: #GPluginPlugin instance.
    + * @error: (out) (nullable): return location for a #GError or %NULL.
    *
    - * Loads @plugin and all of it's dependencies. If a dependency can not be
    + * Loads @plugin and all of its dependencies. If a dependency can not be
    * loaded, @plugin will not be loaded either. However, any other plugins that
    * @plugin depends on that were loaded from this call, will not be unloaded.
    *
    - * Return value: TRUE if @plugin was loaded successfully or already loaded,
    - * FALSE otherwise.
    + * Return value: %TRUE if @plugin was loaded successfully or already loaded,
    + * %FALSE otherwise.
    */
    gboolean
    gplugin_manager_load_plugin(GPluginPlugin *plugin, GError **error) {
    @@ -1445,13 +1518,13 @@
    /**
    * gplugin_manager_unload_plugin:
    - * @plugin: #GPluginPlugin instance
    - * @error: (out): return location for a #GError or null
    + * @plugin: #GPluginPlugin instance.
    + * @error: (out) (nullable): Return location for a #GError or %NULL.
    *
    * Unloads @plugin. If @plugin has dependencies, they are not unloaded.
    *
    - * Return value: TRUE if @plugin was unloaded successfully or not loaded,
    - * FALSE otherwise.
    + * Returns: %TRUE if @plugin was unloaded successfully or not loaded, %FALSE
    + * otherwise.
    */
    gboolean
    gplugin_manager_unload_plugin(GPluginPlugin *plugin, GError **error) {
    @@ -1503,8 +1576,8 @@
    * This is provided so that signals can be connected and should not be tinkered
    * with in any way.
    *
    - * Return Value: (transfer none): The #GObject that is the instance of the
    - * plugin manager.
    + * Returns: (transfer none): The #GObject that is the instance of the plugin
    + * manager.
    */
    GObject *
    gplugin_manager_get_instance(void) {
    @@ -1513,4 +1586,3 @@
    return NULL;
    }
    -
    --- a/gplugin/gplugin-manager.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-manager.h Tue Feb 25 22:30:11 2020 -0600
    @@ -39,13 +39,13 @@
    GList *gplugin_manager_get_paths(void);
    -void gplugin_manager_register_loader(GType type);
    -void gplugin_manager_unregister_loader(GType type);
    +gboolean gplugin_manager_register_loader(GType type, GError **error);
    +gboolean gplugin_manager_unregister_loader(GType type, GError **error);
    void gplugin_manager_refresh(void);
    GSList *gplugin_manager_find_plugins(const gchar *id);
    -void gplugin_manager_free_plugin_list(GSList *plugins_list);
    +GSList *gplugin_manager_find_plugins_with_version(const gchar *id, const gchar *op, const gchar *version);
    GPluginPlugin *gplugin_manager_find_plugin(const gchar *id);
    --- a/gplugin/gplugin-native-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -22,7 +22,7 @@
    #include <gplugin/gplugin-native-plugin.h>
    #include <gplugin/gplugin-native-private.h>
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <gmodule.h>
    @@ -31,14 +31,6 @@
    #define GPLUGIN_UNLOAD_SYMBOL "gplugin_unload"
    /**
    - * SECTION:gplugin-native-loader
    - * @Title: Native Loader API
    - * @Short_description: API for the native plugin loader
    - *
    - * Basic API for the native plugin loader.
    - */
    -
    -/**
    * GPLUGIN_TYPE_NATIVE_LOADER:
    *
    * The standard _get_type macro for #GPluginNativeLoader.
    @@ -109,12 +101,12 @@
    * GPluginLoaderInterface API
    *****************************************************************************/
    static GSList *
    -gplugin_native_loader_class_supported_extensions(G_GNUC_UNUSED GPluginLoaderClass *klass) {
    +gplugin_native_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *l) {
    GSList *exts = g_slist_append(NULL, G_MODULE_SUFFIX);
    #if defined(__APPLE__) || defined(__MACH__)
    - /* G_MODULE_SUFFIX only requests so on not windows, and both .so and
    - * .dylib are use on macos, so add dylib if we're on macos.
    + /* G_MODULE_SUFFIX only requests `.so` on not windows, and both .so and
    + * .dylib are used on macos, so add dylib if we're on macos.
    * See: https://gitlab.gnome.org/GNOME/glib/issues/1413
    */
    exts = g_slist_append(exts, "dylib");
    @@ -315,7 +307,7 @@
    GPLUGIN_LOADER_CLASS(klass);
    loader_class->supported_extensions =
    - gplugin_native_loader_class_supported_extensions;
    + gplugin_native_loader_supported_extensions;
    loader_class->query = gplugin_native_loader_query;
    loader_class->load = gplugin_native_loader_load;
    loader_class->unload = gplugin_native_loader_unload;
    --- a/gplugin/gplugin-native-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -37,4 +37,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_NATIVE_LOADER_H */
    -
    --- a/gplugin/gplugin-native-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -24,16 +24,7 @@
    #include <gplugin/gplugin-core.h>
    -#include <glib/gi18n.h>
    -
    -/**
    - * SECTION:gplugin-native-plugin
    - * @Title: Native Plugin API
    - * @Short_description: API for native plugins
    - *
    - * API for use by native plugins. That is plugins written in a compiled
    - * language.
    - */
    +#include <glib/gi18n-lib.h>
    /**
    * GPLUGIN_TYPE_NATIVE_PLUGIN:
    @@ -274,4 +265,3 @@
    return plugin->module;
    }
    -
    --- a/gplugin/gplugin-native-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -36,4 +36,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_NATIVE_PLUGIN_H */
    -
    --- a/gplugin/gplugin-native-private.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native-private.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -35,4 +35,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_PRIVATE_H */
    -
    --- a/gplugin/gplugin-native.h.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-native.h.in Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/gplugin/gplugin-options.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-options.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -16,7 +16,7 @@
    */
    #include <glib.h>
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <glib-object.h>
    #include <gplugin/gplugin-options.h>
    @@ -99,7 +99,8 @@
    * g_option_context_add_group(), if you are using g_option_context_parse() to
    * parse your commandline arguments.
    *
    - * GPlugin must be initialized before you call this function.
    + * If gplugin_init() has yet to be called before g_option_context_parse() is
    + * called, gplugin_init() will be called automatically.
    *
    * Return Value: (transfer full): a #GOptionGroup for the commandline arguments
    * recognized by GPlugin.
    @@ -118,4 +119,3 @@
    return group;
    }
    -
    --- a/gplugin/gplugin-options.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-options.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -23,7 +23,6 @@
    #define GPLUGIN_OPTIONS_H
    #include <glib.h>
    -#include <glib-object.h>
    G_BEGIN_DECLS
    @@ -32,4 +31,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_OPTIONS_H */
    -
    --- a/gplugin/gplugin-plugin-info.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-plugin-info.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -24,7 +24,7 @@
    /**
    * SECTION:gplugin-plugin-info
    * @Title: Plugin Info Objects
    - * @Short_description: information about plugins
    + * @Short_description: information about plugins.
    *
    * #GPluginPluginInfo holds metadata for plugins.
    */
    @@ -57,19 +57,17 @@
    gchar *name;
    gchar *version;
    - GPluginVersionCompareFunc version_func;
    gchar *license_id;
    gchar *license_text;
    gchar *license_url;
    - gchar *icon;
    + gchar *icon_name;
    gchar *summary;
    gchar *description;
    gchar *category;
    gchar **authors;
    - gchar *help;
    gchar *website;
    gchar **dependencies;
    @@ -96,12 +94,11 @@
    PROP_LICENSE_ID,
    PROP_LICENSE_TEXT,
    PROP_LICENSE_URL,
    - PROP_ICON,
    + PROP_ICON_NAME,
    PROP_SUMMARY,
    PROP_DESCRIPTION,
    PROP_CATEGORY,
    PROP_AUTHORS,
    - PROP_HELP,
    PROP_WEBSITE,
    PROP_DEPENDENCIES,
    N_PROPERTIES,
    @@ -204,11 +201,13 @@
    }
    static void
    -gplugin_plugin_info_set_icon(GPluginPluginInfo *info, const gchar *icon) {
    +gplugin_plugin_info_set_icon_name(GPluginPluginInfo *info,
    + const gchar *icon_name)
    +{
    GPluginPluginInfoPrivate *priv = gplugin_plugin_info_get_instance_private(info);
    - g_free(priv->icon);
    - priv->icon = g_strdup(icon);
    + g_free(priv->icon_name);
    + priv->icon_name = g_strdup(icon_name);
    }
    static void
    @@ -252,15 +251,6 @@
    }
    static void
    -gplugin_plugin_info_set_help(GPluginPluginInfo *info, const gchar *help)
    -{
    - GPluginPluginInfoPrivate *priv = gplugin_plugin_info_get_instance_private(info);
    -
    - g_free(priv->help);
    - priv->help = g_strdup(help);
    -}
    -
    -static void
    gplugin_plugin_info_set_website(GPluginPluginInfo *info,
    const gchar *website)
    {
    @@ -326,8 +316,8 @@
    g_value_set_string(value,
    gplugin_plugin_info_get_license_url(info));
    break;
    - case PROP_ICON:
    - g_value_set_string(value, gplugin_plugin_info_get_icon(info));
    + case PROP_ICON_NAME:
    + g_value_set_string(value, gplugin_plugin_info_get_icon_name(info));
    break;
    case PROP_SUMMARY:
    g_value_set_string(value, gplugin_plugin_info_get_summary(info));
    @@ -342,9 +332,6 @@
    case PROP_AUTHORS:
    g_value_set_boxed(value, gplugin_plugin_info_get_authors(info));
    break;
    - case PROP_HELP:
    - g_value_set_string(value, gplugin_plugin_info_get_help(info));
    - break;
    case PROP_WEBSITE:
    g_value_set_string(value, gplugin_plugin_info_get_website(info));
    break;
    @@ -400,8 +387,8 @@
    gplugin_plugin_info_set_license_url(info,
    g_value_get_string(value));
    break;
    - case PROP_ICON:
    - gplugin_plugin_info_set_icon(info, g_value_get_string(value));
    + case PROP_ICON_NAME:
    + gplugin_plugin_info_set_icon_name(info, g_value_get_string(value));
    break;
    case PROP_SUMMARY:
    gplugin_plugin_info_set_summary(info, g_value_get_string(value));
    @@ -416,9 +403,6 @@
    case PROP_AUTHORS:
    gplugin_plugin_info_set_authors(info, g_value_get_boxed(value));
    break;
    - case PROP_HELP:
    - gplugin_plugin_info_set_help(info, g_value_get_string(value));
    - break;
    case PROP_WEBSITE:
    gplugin_plugin_info_set_website(info, g_value_get_string(value));
    break;
    @@ -442,11 +426,10 @@
    g_clear_pointer(&priv->license_id, g_free);
    g_clear_pointer(&priv->license_text, g_free);
    g_clear_pointer(&priv->license_url, g_free);
    - g_clear_pointer(&priv->icon, g_free);
    + g_clear_pointer(&priv->icon_name, g_free);
    g_clear_pointer(&priv->summary, g_free);
    g_clear_pointer(&priv->description, g_free);
    g_clear_pointer(&priv->authors, g_strfreev);
    - g_clear_pointer(&priv->help, g_free);
    g_clear_pointer(&priv->website, g_free);
    g_clear_pointer(&priv->dependencies, g_strfreev);
    @@ -474,8 +457,8 @@
    * While not required, the recommended convention is to use the following
    * format: &lt;application or library&gt;/&lt;name of the plugin&gt;.
    *
    - * For example, the python loader in GPlugin has an id of
    - * "gplugin/python-plugin-loader".
    + * For example, the Python3 loader in GPlugin has an id of
    + * "gplugin/python3-loader".
    */
    properties[PROP_ID] = g_param_spec_string(
    "id", "id",
    @@ -490,14 +473,16 @@
    *
    * The GPlugin ABI version that the plugin was compiled against.
    *
    - * GPlugin only uses the first byte (0xff000000) of this value. The
    + * GPlugin only uses the first byte (`0xff000000`) of this value. The
    * remaining 3 bytes are available for the application to use.
    *
    * Take the following example from an application:
    *
    + * |[<!-- language="C" -->
    * #define ABI_VERSION (GPLUGIN_NATIVE_ABI_VERSION |
    * (APPLICATION_MAJOR_VERSION << 8) |
    * (APPLICATION_MINOR_VERSION))
    + * ]|
    *
    * The application here uses the thrid and fourth bytes, but could use
    * the second as well.
    @@ -514,7 +499,7 @@
    *
    * Whether or not the plugin is considered an "internal" plugin.
    *
    - * Defaults to FALSE.
    + * Defaults to %FALSE.
    */
    properties[PROP_INTERNAL] = g_param_spec_boolean(
    "internal", "internal",
    @@ -531,7 +516,7 @@
    * This is used by the loaders and may be useful to your application as
    * well.
    *
    - * Defaults to FALSE.
    + * Defaults to %FALSE.
    */
    properties[PROP_LOQ] = g_param_spec_boolean(
    "load-on-query", "load-on-query",
    @@ -543,7 +528,7 @@
    /**
    * GPluginPluginInfo:bind-local:
    *
    - * Determines whether the plugin should be have it's symbols bound locally.
    + * Determines whether the plugin should be have its symbols bound locally.
    *
    * Note: This should only be used by the native plugin loader.
    */
    @@ -569,7 +554,7 @@
    /**
    * GPluginPluginInfo:version:
    *
    - * The version of the plugin.
    + * The version of the plugin. Preferably a semantic version.
    */
    properties[PROP_VERSION] = g_param_spec_string(
    "version", "version",
    @@ -584,8 +569,8 @@
    * The short name of the license.
    *
    * It is recommended to use the identifier of the license from
    - * http://dep.debian.net/deps/dep5/#license-specification and should be
    - * "Other" for licenses that are not mentioned in DEP5.
    + * https://spdx.org/licenses/ and should be "Other" for licenses that are
    + * not listed.
    *
    * If a plugin has multiple license, they should be separated by a pipe
    * (|). In the odd case that you have multiple licenses that are used at
    @@ -602,7 +587,7 @@
    * GPluginPluginInfo:license-text:
    *
    * The text of the license for this plugin. This should only be used when
    - * the plugin is licensed under a license that is not listed in DEP5.
    + * the plugin is licensed under a license that is not listed at spdx.org.
    */
    properties[PROP_LICENSE_TEXT] = g_param_spec_string(
    "license-text", "license text",
    @@ -615,7 +600,7 @@
    * GPluginPluginInfo:license-url:
    *
    * The url to the text of the license. This should primarily only be used
    - * for licenses not listed in DEP5.
    + * for licenses not listed at spdx.org.
    */
    properties[PROP_LICENSE_URL] = g_param_spec_string(
    "license-url", "license url",
    @@ -625,14 +610,14 @@
    );
    /**
    - * GPluginPluginInfo:icon:
    + * GPluginPluginInfo:icon-name:
    *
    - * A string representing an icon for the plugin. The actual use of this
    - * is determined by the application/library using GPlugin.
    + * A XDG icon name for the plugin. The actual use of this is determined by
    + * the application/library using GPlugin.
    */
    - properties[PROP_ICON] = g_param_spec_string(
    - "icon", "icon",
    - "The file path of the icon for the plugin",
    + properties[PROP_ICON_NAME] = g_param_spec_string(
    + "icon-name", "icon-name",
    + "The XDG icon name for the plugin",
    NULL,
    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY
    );
    @@ -683,11 +668,10 @@
    /**
    * GPluginPluginInfo:authors:
    *
    - * A gchar ** of the names and email addresses of the authors.
    + * A list of the names and email addresses of the authors.
    *
    * It is recommended to use the RFC 822, 2822 format of:
    - * "First Last <user@domain.com>" with additional authors separated by a
    - * comma.
    + * `"First Last <user@domain.com>"`.
    */
    properties[PROP_AUTHORS] = g_param_spec_boxed(
    "authors", "authors",
    @@ -697,18 +681,6 @@
    );
    /**
    - * GPluginPluginInfo:help:
    - *
    - * The url of the plugin that can be represented in a user interface.
    - */
    - properties[PROP_HELP] = g_param_spec_string(
    - "help", "help",
    - "The help string for the plugin",
    - NULL,
    - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY
    - );
    -
    - /**
    * GPluginPluginInfo:website:
    *
    * The url of the plugin that can be represented in a user interface.
    @@ -741,18 +713,18 @@
    /**
    * gplugin_plugin_info_new: (skip)
    - * @id: The id of the plugin
    - * @abi_version: The GPlugin ABI version that the plugin uses
    + * @id: The id of the plugin.
    + * @abi_version: The GPlugin ABI version that the plugin uses.
    * @...: name/value pairs of properties to set, followed by %NULL.
    *
    - * Creates a new GPluginPluginInfo instance.
    + * Creates a new #GPluginPluginInfo instance.
    *
    - * Returns: The new GPluginPluginInfo instance.
    + * Returns: (transfer full): The new #GPluginPluginInfo instance.
    */
    /**
    * gplugin_plugin_info_get_id:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the id that the plugin identifies itself as.
    *
    @@ -771,7 +743,7 @@
    /**
    * gplugin_plugin_info_get_abi_version:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the ABI or Application Binary Interface version that the plugin
    * is supposed to work against.
    @@ -791,13 +763,13 @@
    /**
    * gplugin_plugin_info_get_internal:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns where or not this plugin is is considered an internal plugin. An
    * internal plugin would be something like a plugin loader or another plugin
    * that should not be shown to users.
    *
    - * Returns: TRUE if the plugin is internal, FALSE otherwise.
    + * Returns: %TRUE if the plugin is internal, %FALSE otherwise.
    */
    gboolean
    gplugin_plugin_info_get_internal(GPluginPluginInfo *info) {
    @@ -812,14 +784,14 @@
    /**
    * gplugin_plugin_info_get_load_on_query:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns whether or not this plugin should be loaded when queried. This is
    * useful for internal plugins that are adding functionality and should always
    * be turned on. The plugin loaders use this to make sure all plugins can
    * always be loaded.
    *
    - * Returns: TRUE if the plugin should be loaded on query, FALSE otherwise.
    + * Returns: %TRUE if the plugin should be loaded on query, %FALSE otherwise.
    */
    gboolean
    gplugin_plugin_info_get_load_on_query(GPluginPluginInfo *info) {
    @@ -834,7 +806,7 @@
    /**
    * gplugin_plugin_info_get_name:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the name of the plugin as specified in @info.
    *
    @@ -853,7 +825,7 @@
    /**
    * gplugin_plugin_info_get_version:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the version of the plugin as specified in @info.
    *
    @@ -871,29 +843,8 @@
    }
    /**
    - * gplugin_plugin_info_get_version_func: (skip)
    - * @info: #GPluginPluginInfo instance
    - *
    - * Returns the #GPluginVersionCompareFunc used to compare versions of the
    - * plugin.
    - *
    - * Returns: The #GPluginVersionCompareFunc that can compare versions of this
    - * plugins.
    - */
    -GPluginVersionCompareFunc
    -gplugin_plugin_info_get_version_func(GPluginPluginInfo *info) {
    - GPluginPluginInfoPrivate *priv = NULL;
    -
    - g_return_val_if_fail(GPLUGIN_IS_PLUGIN_INFO(info), NULL);
    -
    - priv = gplugin_plugin_info_get_instance_private(info);
    -
    - return priv->version_func;
    -}
    -
    -/**
    * gplugin_plugin_info_get_license_id:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the liences id for the plugin as specified in @info.
    *
    @@ -912,7 +863,7 @@
    /**
    * gplugin_plugin_info_get_license_text:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the license text for the plugin as specified in @info.
    *
    @@ -931,7 +882,7 @@
    /**
    * gplugin_plugin_info_get_license_url:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the url of the license for the plugin as specified in @info
    *
    @@ -949,27 +900,27 @@
    }
    /**
    - * gplugin_plugin_info_get_icon:
    - * @info: #GPluginPluginInfo instance
    + * gplugin_plugin_info_get_icon_name:
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the name of the icon for the plugin as specified in @info.
    *
    - * Returns: The icon from @info.
    + * Returns: The icon name from @info.
    */
    const gchar *
    -gplugin_plugin_info_get_icon(GPluginPluginInfo *info) {
    +gplugin_plugin_info_get_icon_name(GPluginPluginInfo *info) {
    GPluginPluginInfoPrivate *priv = NULL;
    g_return_val_if_fail(GPLUGIN_IS_PLUGIN_INFO(info), NULL);
    priv = gplugin_plugin_info_get_instance_private(info);
    - return priv->icon;
    + return priv->icon_name;
    }
    /**
    * gplugin_plugin_info_get_summary:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the summery for the plugin as specified in @info.
    *
    @@ -988,7 +939,7 @@
    /**
    * gplugin_plugin_info_get_description:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the description for the plugin as specified in @info.
    *
    @@ -1007,7 +958,7 @@
    /**
    * gplugin_plugin_info_get_category:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the category of the plugin as specified in @info.
    *
    @@ -1026,7 +977,7 @@
    /**
    * gplugin_plugin_info_get_authors:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the authors of the plugin as specified in @info.
    *
    @@ -1044,27 +995,8 @@
    }
    /**
    - * gplugin_plugin_info_get_help:
    - * @info: #GPluginPluginInfo instance
    - *
    - * Returns the help text for the plugin as specified in @info.
    - *
    - * Returns: The help from @info.
    - */
    -const gchar *
    -gplugin_plugin_info_get_help(GPluginPluginInfo *info) {
    - GPluginPluginInfoPrivate *priv = NULL;
    -
    - g_return_val_if_fail(GPLUGIN_IS_PLUGIN_INFO(info), NULL);
    -
    - priv = gplugin_plugin_info_get_instance_private(info);
    -
    - return priv->help;
    -}
    -
    -/**
    * gplugin_plugin_info_get_website:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the website for the plugin as specified in @info.
    *
    @@ -1083,7 +1015,7 @@
    /**
    * gplugin_plugin_info_get_dependencies:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * Returns the dependencies of the plugins as specified in @info.
    *
    @@ -1103,12 +1035,12 @@
    /**
    * gplugin_plugin_info_get_bind_local:
    - * @info: #GPluginPluginInfo instance
    + * @info: The #GPluginPluginInfo instance.
    *
    * This function is only used by the native plugin loader.
    *
    - * Returns: TRUE if the plugin has requested to be loaded with it's symbols
    - * bound locally, FALSE if they should bind be bound globally.
    + * Returns: %TRUE if the plugin has requested to be loaded with its symbols
    + * bound locally, %FALSE if they should be bound globally.
    */
    gboolean
    gplugin_plugin_info_get_bind_local(GPluginPluginInfo *info) {
    @@ -1120,4 +1052,3 @@
    return priv->bind_local;
    }
    -
    --- a/gplugin/gplugin-plugin-info.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-plugin-info.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -58,16 +58,14 @@
    const gchar *gplugin_plugin_info_get_license_id(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_license_text(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_license_url(GPluginPluginInfo *info);
    -const gchar *gplugin_plugin_info_get_icon(GPluginPluginInfo *info);
    +const gchar *gplugin_plugin_info_get_icon_name(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_summary(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_description(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_category(GPluginPluginInfo *info);
    const gchar * const *gplugin_plugin_info_get_authors(GPluginPluginInfo *info);
    const gchar *gplugin_plugin_info_get_website(GPluginPluginInfo *info);
    const gchar * const *gplugin_plugin_info_get_dependencies(GPluginPluginInfo *info);
    -const gchar *gplugin_plugin_info_get_help(GPluginPluginInfo *info);
    G_END_DECLS
    #endif /* GPLUGIN_PLUGIN_INFO_H */
    -
    --- a/gplugin/gplugin-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -22,25 +22,25 @@
    /**
    * SECTION:gplugin-plugin
    - * @Title: Plugin Objects
    - * @Short_description: abstract plugin implementation
    + * @Title: Plugin Interface
    + * @Short_description: The plugin interface that all plugins must implement.
    *
    - * #GPluginPlugin is an abstract class that tracks the state of a plugin. It
    - * is subclassed by each loader for them to add additional data for their
    + * #GPluginPlugin is an interface that defines the behavior of plugins. It
    + * is implemented by each loader which add additional data for their
    * implementation.
    */
    /**
    * GPluginPluginState:
    - * @GPLUGIN_PLUGIN_STATE_UNKNOWN: The state of the plugin is unknown
    + * @GPLUGIN_PLUGIN_STATE_UNKNOWN: The state of the plugin is unknown.
    * @GPLUGIN_PLUGIN_STATE_ERROR: There was an error loading or unloading the
    - * plugin
    - * @GPLUGIN_PLUGIN_STATE_QUERIED: The plugin has been queried but not loaded
    - * @GPLUGIN_PLUGIN_STATE_REQUERY: The plugin should be requeried
    - * @GPLUGIN_PLUGIN_STATE_LOADED: The plugin is loaded
    - * @GPLUGIN_PLUGIN_STATE_LOAD_FAILED: The plugin failed to load
    + * plugin.
    + * @GPLUGIN_PLUGIN_STATE_QUERIED: The plugin has been queried but not loaded.
    + * @GPLUGIN_PLUGIN_STATE_REQUERY: The plugin should be requeried.
    + * @GPLUGIN_PLUGIN_STATE_LOADED: The plugin is loaded.
    + * @GPLUGIN_PLUGIN_STATE_LOAD_FAILED: The plugin failed to load.
    *
    - * The expected states of a plugin.
    + * The known states of a plugin.
    */
    /**
    @@ -57,7 +57,8 @@
    /**
    * GPluginPluginInterface:
    - * @state_changed: The class closure for the #GPluginPlugin::state-changed signal.
    + * @state_changed: The class closure for the #GPluginPlugin::state-changed
    + * signal.
    *
    * The interface that defines the behavior of plugins, including properties and
    * signals.
    @@ -162,11 +163,11 @@
    /**
    * gplugin_plugin_get_filename:
    - * @plugin: #GPluginPlugin instance
    + * @plugin: The #GPluginPlugin instance.
    *
    * Returns the filename that @plugin was loaded from.
    *
    - * Returns: (transfer full): The filename of @plugin
    + * Returns: (transfer full): The filename of @plugin.
    */
    gchar *
    gplugin_plugin_get_filename(GPluginPlugin *plugin) {
    @@ -181,11 +182,11 @@
    /**
    * gplugin_plugin_get_loader:
    - * @plugin: #GPluginPlugin instance
    + * @plugin: The #GPluginPlugin instance.
    *
    * Returns the #GPluginLoader that loaded @plugin.
    *
    - * Returns: (transfer full): The #GPluginLoader that loaded @plugin
    + * Returns: (transfer full): The #GPluginLoader that loaded @plugin.
    */
    GPluginLoader *
    gplugin_plugin_get_loader(GPluginPlugin *plugin) {
    @@ -200,11 +201,11 @@
    /**
    * gplugin_plugin_get_info:
    - * @plugin: #GPluginPlugin instance
    + * @plugin: The #GPluginPlugin instance.
    *
    * Returns the #GPluginPluginInfo for @plugin.
    *
    - * Returns: (transfer full): The #GPluginPluginInfo instance for @plugin
    + * Returns: (transfer full): The #GPluginPluginInfo instance for @plugin.
    */
    GPluginPluginInfo *
    gplugin_plugin_get_info(GPluginPlugin *plugin) {
    @@ -219,11 +220,11 @@
    /**
    * gplugin_plugin_get_state:
    - * @plugin: #GPluginPlugin instance
    + * @plugin: The #GPluginPlugin instance.
    *
    - * Gets the current state of @plugin
    + * Gets the current state of @plugin.
    *
    - * Returns: (transfer full): The current state of @plugin
    + * Returns: (transfer full): The current state of @plugin.
    */
    GPluginPluginState
    gplugin_plugin_get_state(GPluginPlugin *plugin) {
    @@ -238,8 +239,8 @@
    /**
    * gplugin_plugin_set_state:
    - * @plugin: #GPluginPlugin instance
    - * @state: new #GPluginPluginState for @plugin
    + * @plugin: The #GPluginPlugin instance.
    + * @state: A new #GPluginPluginState for @plugin.
    *
    * Changes the state of @plugin to @state. This function should only be called
    * by loaders.
    --- a/gplugin/gplugin-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -67,4 +67,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_PLUGIN_H */
    -
    --- a/gplugin/gplugin-private.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-private.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -33,4 +33,3 @@
    return continue_emission;
    }
    -
    --- a/gplugin/gplugin-private.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-private.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_PRIVATE_H */
    -
    --- a/gplugin/gplugin-query.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-query.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -222,7 +222,7 @@
    first = FALSE;
    }
    - gplugin_manager_free_plugin_list(plugins);
    + g_slist_free_full(plugins, g_object_unref);
    return TRUE;
    }
    @@ -352,4 +352,3 @@
    return ret;
    }
    -
    --- a/gplugin/gplugin-version.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-version.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -18,11 +18,11 @@
    /**
    * SECTION:gplugin-version
    * @Title: Version Information
    - * @Short_description: variables and functions to check the GPlugin version
    + * @Short_description: variables and functions to check the version of GPlugin.
    *
    - * GPlugin provides version information, primarily useful in configure
    - * checks for builds that have a configure script. Applications will
    - * not typically use the features described here.
    + * GPlugin provided version information, primarily useful when building against
    + * GPlugin. Applications will not typically use the features described here
    + * unless checking for new versions during builds.
    */
    #include <gplugin/gplugin-core.h>
    @@ -30,7 +30,7 @@
    #include <stdlib.h>
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    /******************************************************************************
    * Globals
    @@ -148,42 +148,71 @@
    /**
    * GPLUGIN_VERSION_CHECK:
    - * @major: the major version to compare for
    - * @minor: the minor version to compare for
    - * @micro: the micro version to compare for
    + * @major: The major version to compare for.
    + * @minor: The minor version to compare for.
    + * @micro: The micro version to compare for.
    *
    * Checks the version of the GPlugin library that is being compiled
    - * against. See gplugin_version_compare() for a runtime check.
    + * against.
    *
    * Returns: %TRUE if the version of the GPlugin header files
    * is the same as or newer than the passed-in version.
    */
    /**
    - * GPluginVersionCompareFunc:
    - * @v1: The first version to compare
    - * @v2: The second version to compare
    - * @error: A #GError return address if there are any errors.
    + * gplugin_version_check:
    + * @major: The required major version.
    + * @minor: The required minor version.
    + * @micro: The required micro version.
    + *
    + * Checks that the GPlugin library in use is compatible with the given version.
    + * Generally you would pass in the constants #GPLUGIN_MAJOR_VERSION,
    + * #GPLUGIN_MINOR_VERSION, #GPLUGIN_MICRO_VERSION as the three arguments to
    + * this function; that produces a check that the library in use is compatible
    + * with the version of GPlugin the application or module was compiled against.
    + *
    + * Compatibility is defined by two things: first the version of the running
    + * library is newer than the version @major.@minor.@micro. Second the running
    + * library must be binary compatible with the version @major.@minor.@micro
    + * (same major version).
    *
    - * #GPluginVersionCompareFunc is used to compare two versions of a plugin. It
    - * should return -1 if @v1 is greater than @v2, 0 if @v1 is equal to @v2, and
    - * 1 if @v1 is less than @v2.
    - *
    - * Returns: -1 if @v1 is greater than @v2, 0 if @v1 is equal to @v1, and 1 if
    - * @v1 is less than @v2.
    + * Returns: %NULL if the GPlugin library is compatible with the given version,
    + * or a string describing the version mismatch. The returned string
    + * is owned by GPlugin and must not be modified or freed.
    */
    +const gchar *
    +gplugin_version_check(guint major, guint minor, guint micro) {
    + if(major > GPLUGIN_MAJOR_VERSION) {
    + return "gplugin version too old (major mismatch)";
    + }
    +
    + /* disable the pvs-studio suppression after 1.0.0 is released */
    + if(major < GPLUGIN_MAJOR_VERSION) { //-V547
    + return "gplugin version too new (major mismatch)";
    + }
    +
    + if(minor > GPLUGIN_MINOR_VERSION) {
    + return "gplugin version too old (minor mismatch)";
    + }
    +
    + if(minor == GPLUGIN_MINOR_VERSION && micro > GPLUGIN_MICRO_VERSION) {
    + return "gplugin version too old (micro mismatch)";
    + }
    +
    + return NULL;
    +}
    /**
    * gplugin_version_compare:
    - * @v1: The first version to compare
    - * @v2: The second version to compare
    - * @error: A #GError return address if there are any errors.
    + * @v1: The first version to compare.
    + * @v2: The second version to compare.
    + * @error: (out) (nullable): A #GError return address if there are any errors.
    *
    - * The default #GPluginVersionCompareFunc. It handles the typical
    - * MAJOR.MINOR.MICRO format and ignore any characters after the micro version.
    + * A semantic version checker which ignores any characters after the micro
    + * version.
    *
    - * Returns: -1 if @v1 is greater than @v2, 0 if @v1 is equal to @v1, and 1 if
    - * @v1 is less than @v2.
    + * Returns: less than 0 if @v1 is less than @v2, 0 if @v1 is equal to @v1, and
    + * greater than 0 if @v1 is greater than @v2.
    */
    gint
    gplugin_version_compare(const gchar *v1, const gchar *v2, GError **error) {
    @@ -199,21 +228,20 @@
    /* make sure v1 matches the regex */
    if(!gplugin_version_parser(v1, &v1_maj, &v1_min, &v1_mic, NULL, error))
    - return 1;
    + return -1;
    /* make sure v2 matches the regex */
    if(!gplugin_version_parser(v2, &v2_maj, &v2_min, &v2_mic, NULL, error))
    - return -1;
    + return 1;
    /* now figure out if they match */
    - t = v2_maj - v1_maj;
    + t = v1_maj - v2_maj;
    if(t != 0)
    return t;
    - t = v2_min - v1_min;
    + t = v1_min - v2_min;
    if(t != 0)
    return t;
    - return v2_mic - v1_mic;
    + return v1_mic - v2_mic;
    }
    -
    --- a/gplugin/gplugin-version.h.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin-version.h.in Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -38,11 +38,10 @@
    G_BEGIN_DECLS
    -typedef gint (*GPluginVersionCompareFunc)(const gchar *v1, const gchar *v2, GError **error);
    +const gchar *gplugin_version_check(guint major, guint minor, guint micro);
    gint gplugin_version_compare(const gchar *v1, const gchar *v2, GError **error);
    G_END_DECLS
    #endif /* GPLUGIN_VERSION_H */
    -
    --- a/gplugin/gplugin.h.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/gplugin.h.in Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -26,5 +26,3 @@
    #undef GPLUGIN_GLOBAL_HEADER_INSIDE
    #endif /* GPLUGIN_H */
    -
    -
    --- a/gplugin/reference/embedding.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/embedding.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -13,8 +13,8 @@
    </para>
    <para>
    - Using GPlugin is pretty simple and I'd like to think straight forward
    - since that's the way I designed it.
    + GPlugin was designed to be simple to implement and use. Initialization
    + and teardown examples can be found below.
    </para>
    </simplesect>
    @@ -30,14 +30,13 @@
    gplugin_manager_add_default_paths();
    /* Optionally tell GPlugin to look for plugins in application specific
    - * paths.
    + * paths. This will add `$PREFIX/lib/application`.
    */
    gplugin_manager_add_app_paths(PREFIX, "application");
    /* Once you're ready to find/load plugins call g_plugin_manager_refresh.
    */
    gplugin_manager_refresh();
    -
    </programlisting></informalexample>
    </para>
    </simplesect>
    @@ -51,4 +50,4 @@
    </programlisting></informalexample>
    </para>
    </simplesect>
    -</chapter>
    \ No newline at end of file
    +</chapter>
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gplugin/reference/genie.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,70 @@
    +<?xml version='1.0' encoding="UTF-8"?>
    +<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
    + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
    +]>
    +<chapter id="chapter-genie">
    + <title>Genie Plugins</title>
    +
    + <warning>
    + <para>
    + You <emphasis role="strong">MUST</emphasis> have the Vala bindings
    + installed on your system for this to work. They are built by the
    + default GPlugin build.
    + </para>
    + </warning>
    +
    + <simplesect>
    + <title>Example Genie Plugin</title>
    +
    + <para>
    + Like all plugins in GPlugin, Genie plugins must also implement
    + the <code>gplugin_query</code>, <code>gplugin_load</code>, and
    + <code>gplugin_unload</code> functions.
    + </para>
    +
    + <para>
    + Due to the way <code>GPlugin.PluginInfo</code> info works, you must
    + subclass it and set your values in the new constructor.
    + </para>
    +
    + <para>
    + The following is a basic Genie plugin.
    + </para>
    +
    + <informalexample><programlisting>
    + uses GPlugin
    +
    + class BasicPluginInfo : GPlugin.PluginInfo
    + construct()
    + authors : array of string = {"author1"}
    +
    + Object(
    + id: "gplugin/genie-basic-plugin",
    + abi_version: 0x01020304,
    + name: "basic plugin",
    + authors: authors,
    + category: "test",
    + version: "version",
    + license_id: "license",
    + summary: "summary",
    + website: "website",
    + description: "description"
    + )
    +
    + def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new BasicPluginInfo()
    +
    + def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    +
    + def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    + </programlisting></informalexample>
    + </simplesect>
    +</chapter>
    --- a/gplugin/reference/gplugin-docs.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/gplugin-docs.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -11,10 +11,13 @@
    <abstract>
    <title>GPlugin &version;</title>
    <para>
    - GPlugin is a GObject based library that implements a reusable plugin system that
    - supports loading plugins in other languages
    - via loaders. GPlugin also implements
    - dependencies among the plugins.
    + GPlugin is a GObject based library that implements a reusable plugin
    + system that supports loading plugins in other languages via loaders.
    + GPlugin also implements dependencies among the plugins.
    + </para>
    + <para>
    + The latest version of this documentation can be found on-line at
    + <ulink role="online-location" url="https://docs.pidgin.im/gplugin/latest">https://docs.pidgin.im/gplugin/latest/</ulink>.
    </para>
    </abstract>
    </bookinfo>
    @@ -23,8 +26,10 @@
    <title>Tutorials</title>
    <xi:include href="embedding.xml"/>
    + <xi:include href="genie.xml"/>
    + <xi:include href="lua.xml"/>
    <xi:include href="native-plugins.xml"/>
    - <xi:include href="lua.xml"/>
    + <xi:include href="perl.xml"/>
    <xi:include href="python.xml"/>
    <xi:include href="vala.xml"/>
    </part>
    @@ -38,23 +43,13 @@
    <part id="API">
    <title>API Reference</title>
    - <chapter id="coreapi">
    - <title>Core API</title>
    - <xi:include href="xml/gplugin-core.xml"/>
    - <xi:include href="xml/gplugin-loader.xml"/>
    - <xi:include href="xml/gplugin-manager.xml"/>
    - <xi:include href="xml/gplugin-options.xml"/>
    - <xi:include href="xml/gplugin-plugin-info.xml"/>
    - <xi:include href="xml/gplugin-plugin.xml"/>
    - <xi:include href="xml/gplugin-version.xml"/>
    - </chapter>
    -
    - <chapter id="nativeapi">
    - <title>Native API</title>
    -
    - <xi:include href="xml/gplugin-native-plugin.xml"/>
    - <xi:include href="xml/gplugin-native-loader.xml"/>
    - </chapter>
    + <xi:include href="xml/gplugin-core.xml"/>
    + <xi:include href="xml/gplugin-loader.xml"/>
    + <xi:include href="xml/gplugin-manager.xml"/>
    + <xi:include href="xml/gplugin-options.xml"/>
    + <xi:include href="xml/gplugin-plugin-info.xml"/>
    + <xi:include href="xml/gplugin-plugin.xml"/>
    + <xi:include href="xml/gplugin-version.xml"/>
    </part>
    <index id="api-index-full">
    --- a/gplugin/reference/lua.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/lua.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -24,33 +24,34 @@
    <para>
    The following is a basic Lua plugin.
    - <informalexample><programlisting>
    - local lgi = require 'lgi'
    - local GPlugin = lgi.GPlugin
    + </para>
    +
    + <informalexample><programlisting>
    + local lgi = require "lgi"
    + local GPlugin = lgi.GPlugin
    - function gplugin_query()
    - return GPlugin.PluginInfo {
    - id = "gplugin-lua/basic-plugin",
    - abi_version = 0x01020304,
    - name = "basic plugin",
    - category = "test",
    - version = "0.0.10",
    - license_id = "license-id",
    - summary = "basic lua plugin",
    - description = "description of the basic lua plugin",
    - authors = { "Gary Kramlich &lt;grim@reaperworld.com&gt;" },
    - website = "https://bitbucket.org/gplugin/gplugin/"
    - }
    - end
    + function gplugin_query()
    + return GPlugin.PluginInfo {
    + id = "gplugin-lua/basic-plugin",
    + abi_version = 0x01020304,
    + name = "basic plugin",
    + category = "test",
    + version = "0.0.10",
    + license_id = "license-id",
    + summary = "basic lua plugin",
    + description = "description of the basic lua plugin",
    + authors = { "Gary Kramlich &lt;grim@reaperworld.com&gt;" },
    + website = "https://bitbucket.org/gplugin/gplugin/"
    + }
    + end
    - function gplugin_load(plugin)
    - return true
    - end
    + function gplugin_load(plugin)
    + return true
    + end
    - function gplugin_unload(plugin)
    - return true
    - end
    - </programlisting></informalexample>
    - </para>
    + function gplugin_unload(plugin)
    + return true
    + end
    + </programlisting></informalexample>
    </simplesect>
    </chapter>
    --- a/gplugin/reference/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -7,6 +7,8 @@
    'gplugin-loader-tests.h',
    'gplugin-native-private.h',
    'gplugin-native.h',
    + 'gplugin-native-loader.h',
    + 'gplugin-native-plugin.h',
    'gplugin-private.h',
    'gplugin.h',
    ]
    @@ -26,13 +28,17 @@
    content_files = [
    'embedding.xml',
    + 'genie.xml',
    'lua.xml',
    'native-plugins.xml',
    + 'perl.xml',
    'python.xml',
    + 'vala.xml',
    ]
    gnome.gtkdoc(DOC_MODULE,
    main_xml : DOC_MODULE + '-docs.xml',
    + namespace : 'gplugin',
    src_dir : gplugin_inc,
    dependencies : gplugin_dep,
    ignore_headers : ignore_hfiles,
    --- a/gplugin/reference/native-plugins.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/native-plugins.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -7,7 +7,7 @@
    <simplesect id="intro">
    <para>
    - Writing Native plugins is very simple, but since it's C/C++ it's a bit
    + Writing Native plugins is pretty simple, but since it's C/C++ it's a bit
    more complicated.
    </para>
    @@ -25,17 +25,17 @@
    /* gplugin_plugin_query is called by the native loader to determine if
    * the plugin is loadable. It must have this signature and should
    - * return a valid GPluginPluginInfo if everything is fine. If something
    - * went wrong, error should be set to a valid GError and NULL should be
    - * returned.
    + * return a valid GPluginPluginInfo instance if everything is fine. If
    + * something went wrong, error should be set to a valid GError and NULL
    + * should be returned.
    */
    G_MODULE_EXPORT GPluginPluginInfo *
    gplugin_plugin_query(GError **error) {
    - /* Authors is a list of authors of the plugin. Generally these are
    - * in the "Name Surname &lt;user@domain.com&gt;" format.
    + /* Authors is a list of authors who worked on the plugin. Generally
    + * these are in the "Name Surname &lt;user@domain.com&gt;" format.
    */
    const gchar * const authors[] = {
    - "author",
    + "Author O &lt;author@example.com&gt;",
    NULL
    };
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gplugin/reference/perl.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,60 @@
    +<?xml version='1.0' encoding="UTF-8"?>
    +<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
    + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
    +]>
    +<chapter id="chapter-perl">
    + <title>Perl Plugins</title>
    +
    + <warning>
    + <para>
    + You <emphasis role="strong">MUST</emphasis> have the Perl loader
    + plugin installed and working as well as the gobject-introspection
    + package for GPlugin installed to use Perl plugins.
    + </para>
    + </warning>
    +
    + <simplesect>
    + <title>Example Perl Plugin</title>
    +
    + <para>
    + Like all plugins in GPlugin, Perl plugins must also implement
    + the <code>gplugin_query</code>, <code>gplugin_load</code>, and
    + <code>gplugin_unload</code> functions.
    + </para>
    +
    + <para>
    + The following is a basic Perl plugin.
    + </para>
    +
    + <informalexample><programlisting>
    + use strict;
    +
    + use Glib::Object::Introspection;
    +
    + Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    +
    + sub gplugin_query {
    + return GPlugin::PluginInfo->new(
    + id => "gplugin/perl-basic-plugin",
    + abi_version => 0x01020304,
    + name => "basic plugin",
    + authors => ("author1"),
    + category => "test",
    + version => "version",
    + license_id => "license",
    + summary => "summary",
    + website => "website",
    + description => "description",
    + );
    + }
    +
    + sub gplugin_load {
    + return 0;
    + }
    +
    + sub gplugin_unload {
    + return 0;
    + }
    + </programlisting></informalexample>
    + </simplesect>
    +</chapter>
    --- a/gplugin/reference/python.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/python.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -24,33 +24,33 @@
    <para>
    The following is a basic Python plugin.
    - <informalexample><programlisting>
    - import gi
    + </para>
    - gi.require_version('GPlugin', '0.0')
    - from gi.repository import GPlugin
    + <informalexample><programlisting>
    + import gi
    +
    + gi.require_version("GPlugin", "0.0")
    + from gi.repository import GPlugin
    - def gplugin_plugin_query():
    - return GPlugin.PluginInfo(
    - id='gplugin-python/basic-plugin',
    - abi_version=0x01020304,
    - name='basic plugin',
    - authors=['author1'],
    - category='test',
    - version='version',
    - license_id='license',
    - summary='summary',
    - website='website',
    - description='description',
    - )
    + def gplugin_plugin_query():
    + return GPlugin.PluginInfo(
    + id="gplugin-python/basic-plugin",
    + abi_version=0x01020304,
    + name="basic plugin",
    + authors=["author1"],
    + category="test",
    + version="version",
    + license_id="license",
    + summary="summary",
    + website="website",
    + description="description",
    + )
    - def gplugin_plugin_load(plugin):
    - return True
    -
    + def gplugin_plugin_load(plugin):
    + return True
    - def gplugin_plugin_unload(plugin):
    - return True
    - </programlisting></informalexample>
    - </para>
    + def gplugin_plugin_unload(plugin):
    + return True
    + </programlisting></informalexample>
    </simplesect>
    </chapter>
    --- a/gplugin/reference/vala.xml Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/reference/vala.xml Tue Feb 25 22:30:11 2020 -0600
    @@ -8,8 +8,8 @@
    <warning>
    <para>
    You <emphasis role="strong">MUST</emphasis> have the Vala bindings
    - installed on your system for this to work. They are built by the
    - default GPlugin build.
    + installed on your system for this to work. They are built by
    + default.
    </para>
    </warning>
    @@ -29,46 +29,47 @@
    <para>
    The following is a basic Vala plugin.
    - <informalexample><programlisting>
    - using GPlugin;
    + </para>
    - public class BasicPluginInfo : GPlugin.PluginInfo {
    - public BasicPluginInfo() {
    - string[] authors = {"author1"};
    + <informalexample><programlisting>
    + using GPlugin;
    +
    + public class BasicPluginInfo : GPlugin.PluginInfo {
    + public BasicPluginInfo() {
    + string[] authors = {"author1"};
    - Object(
    - id: "gplugin/vala-basic-plugin",
    - abi_version: 0x01020304,
    - name: "basic plugin",
    - authors: authors,
    - category: "test",
    - version: "version",
    - license_id: "license",
    - summary: "summary",
    - website: "website",
    - description: "description"
    - );
    - }
    + Object(
    + id: "gplugin/vala-basic-plugin",
    + abi_version: 0x01020304,
    + name: "basic plugin",
    + authors: authors,
    + category: "test",
    + version: "version",
    + license_id: "license",
    + summary: "summary",
    + website: "website",
    + description: "description"
    + );
    }
    -
    - public GPlugin.PluginInfo gplugin_query(out Error error) {
    - error = null;
    + }
    - return new BasicPluginInfo();
    - }
    + public GPlugin.PluginInfo gplugin_query(out Error error) {
    + error = null;
    - public bool gplugin_load(GPlugin.Plugin plugin, out Error error) {
    - error = null;
    + return new BasicPluginInfo();
    + }
    - return true;
    - }
    + public bool gplugin_load(GPlugin.Plugin plugin, out Error error) {
    + error = null;
    +
    + return true;
    + }
    - public bool gplugin_unload(GPlugin.Plugin plugin, out Error error) {
    - error = null;
    + public bool gplugin_unload(GPlugin.Plugin plugin, out Error error) {
    + error = null;
    - return true;
    - }
    - </programlisting></informalexample>
    - </para>
    + return true;
    + }
    + </programlisting></informalexample>
    </simplesect>
    </chapter>
    --- a/gplugin/tests/bad-plugins/query-error.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/bad-plugins/query-error.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -38,4 +38,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/bind-local/bind-local.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/bind-local/bind-local.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/dynamic-type/dynamic-test.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/dynamic-type/dynamic-test.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,4 +1,5 @@
    /*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    * Copyright (C) 2013 Ankit Vani <a@nevitus.org>
    *
    * This library is free software; you can redistribute it and/or
    --- a/gplugin/tests/dynamic-type/dynamic-type-provider.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/dynamic-type/dynamic-type-provider.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,4 +1,5 @@
    /*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    * Copyright (C) 2013 Ankit Vani <a@nevitus.org>
    *
    * This library is free software; you can redistribute it and/or
    @@ -58,4 +59,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/dynamic-type/dynamic-type-user.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/dynamic-type/dynamic-type-user.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,4 +1,5 @@
    /*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    * Copyright (C) 2013 Ankit Vani <a@nevitus.org>
    *
    * This library is free software; you can redistribute it and/or
    @@ -67,4 +68,3 @@
    return TRUE;
    }
    -
    --- a/gplugin/tests/id-collision/id-collision1.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/id-collision/id-collision1.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/id-collision/id-collision2.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/id-collision/id-collision2.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/load-on-query-fail/load-on-query-fail.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/load-on-query-fail/load-on-query-fail.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -44,4 +44,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/load-on-query-pass/load-on-query-pass.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/load-on-query-pass/load-on-query-pass.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -28,6 +28,10 @@
    dependencies : [gplugin_dep, GLIB, GOBJECT])
    test('Core', e)
    +e = executable('test-loader-registration', 'test-loader-registration.c',
    + dependencies : [gplugin_dep, GLIB, GOBJECT])
    +test('Loader Registration', e)
    +
    e = executable('test-option-group', 'test-option-group.c',
    dependencies : [gplugin_dep, GLIB, GOBJECT])
    test('Option Group', e)
    --- a/gplugin/tests/plugins/basic-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/basic-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -54,4 +54,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/plugins/broken-dependent-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/broken-dependent-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -47,4 +47,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/plugins/dependent-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/dependent-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -48,4 +48,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/plugins/load-exception.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/load-exception.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -39,4 +39,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/plugins/load-failed.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/load-failed.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/plugins/unload-failed.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/plugins/unload-failed.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    return FALSE;
    }
    -
    --- a/gplugin/tests/test-bind-local.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/test-bind-local.c Tue Feb 25 22:30:11 2020 -0600
    @@ -27,7 +27,7 @@
    *****************************************************************************/
    /* This test kind of sucks because theres no way for us to lookup whether or
    - * not a module handle has had it's symbols bound locally. Therefore, right
    + * not a module handle has had its symbols bound locally. Therefore, right
    * now we have to settle to see if it was loaded correctly.
    */
    static void
    --- a/gplugin/tests/test-id-collision.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/test-id-collision.c Tue Feb 25 22:30:11 2020 -0600
    @@ -39,7 +39,7 @@
    g_assert_cmpuint(g_slist_length(plugins), ==, 2);
    - gplugin_manager_free_plugin_list(plugins);
    + g_slist_free_full(plugins, g_object_unref);
    }
    /******************************************************************************
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gplugin/tests/test-loader-registration.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,179 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <gplugin.h>
    +
    +/* it's getting dirty in here... */
    +#define GPLUGIN_COMPILATION
    +#include "../gplugin-private.h"
    +#undef GPLUGIN_COMPILATION
    +
    +#include <glib.h>
    +
    +/******************************************************************************
    + * GObject Implementation
    + *****************************************************************************/
    +#define TEST_GPLUGIN_TYPE_LOADER (test_gplugin_loader_get_type())
    +G_DECLARE_FINAL_TYPE(TestGPluginLoader, test_gplugin_loader, TEST_GPLUGIN, LOADER, GPluginLoader)
    +
    +struct _TestGPluginLoader {
    + GPluginLoader parent;
    +};
    +
    +static GSList *
    +test_gplugin_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *loader) {
    + return NULL;
    +}
    +
    +static GPluginPlugin *
    +test_gplugin_loader_query(G_GNUC_UNUSED GPluginLoader *loader,
    + G_GNUC_UNUSED const gchar *filename,
    + G_GNUC_UNUSED GError **error)
    +{
    + return NULL;
    +}
    +
    +static gboolean
    +test_gplugin_loader_load(G_GNUC_UNUSED GPluginLoader *loader,
    + G_GNUC_UNUSED GPluginPlugin *plugin,
    + G_GNUC_UNUSED GError **error)
    +{
    + return FALSE;
    +}
    +
    +static gboolean
    +test_gplugin_loader_unload(G_GNUC_UNUSED GPluginLoader *loader,
    + G_GNUC_UNUSED GPluginPlugin *plugin,
    + G_GNUC_UNUSED GError **error)
    +{
    + return FALSE;
    +}
    +
    +G_DEFINE_TYPE(TestGPluginLoader, test_gplugin_loader, GPLUGIN_TYPE_LOADER)
    +
    +static void
    +test_gplugin_loader_init(TestGPluginLoader *loader) {
    +}
    +
    +static void
    +test_gplugin_loader_class_init(TestGPluginLoaderClass *klass) {
    + GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    +
    + loader_class->supported_extensions =
    + test_gplugin_loader_supported_extensions;
    + loader_class->query = test_gplugin_loader_query;
    + loader_class->load = test_gplugin_loader_load;
    + loader_class->unload = test_gplugin_loader_unload;
    +}
    +
    +/******************************************************************************
    + * Tests
    + *****************************************************************************/
    +static void
    +test_gplugin_manager_loader_register(void) {
    + GError *error = NULL;
    + gboolean ret;
    +
    + gplugin_manager_private_uninit();
    + gplugin_manager_private_init();
    +
    + ret = gplugin_manager_register_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    +
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +}
    +
    +static void
    +test_gplugin_manager_loader_register_twice(void) {
    + GError *error = NULL;
    + gboolean ret;
    +
    + gplugin_manager_private_uninit();
    + gplugin_manager_private_init();
    +
    + ret = gplugin_manager_register_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +
    + ret = gplugin_manager_register_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    + g_assert_false(ret);
    + g_assert_error(error, GPLUGIN_DOMAIN, 0);
    +}
    +
    +static void
    +test_gplugin_manager_loader_unregister(void) {
    + GError *error = NULL;
    + gboolean ret;
    +
    + gplugin_manager_private_uninit();
    + gplugin_manager_private_init();
    +
    + ret = gplugin_manager_register_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    +
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +
    + ret = gplugin_manager_unregister_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    +
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +}
    +
    +static void
    +test_gplugin_manager_loader_unregister_twice(void) {
    + GError *error = NULL;
    + gboolean ret;
    +
    + gplugin_manager_private_uninit();
    + gplugin_manager_private_init();
    +
    + ret = gplugin_manager_register_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +
    + ret = gplugin_manager_unregister_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    + g_assert_true(ret);
    + g_assert_no_error(error);
    +
    + ret = gplugin_manager_unregister_loader(TEST_GPLUGIN_TYPE_LOADER, &error);
    + g_assert_false(ret);
    + g_assert_error(error, GPLUGIN_DOMAIN, 0);
    +}
    +
    +/******************************************************************************
    + * Main
    + *****************************************************************************/
    +gint
    +main(gint argc, gchar **argv) {
    +
    + g_test_init(&argc, &argv, NULL);
    +
    + gplugin_init();
    +
    + g_test_add_func("/manager/loader/register",
    + test_gplugin_manager_loader_register);
    + g_test_add_func("/manager/loader/register-twice",
    + test_gplugin_manager_loader_register_twice);
    +
    + g_test_add_func("/manager/loader/unregister",
    + test_gplugin_manager_loader_unregister);
    + g_test_add_func("/manager/loader/unregister-twice",
    + test_gplugin_manager_loader_unregister_twice);
    +
    + return g_test_run();
    +}
    +
    --- a/gplugin/tests/test-plugin-info.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/test-plugin-info.c Tue Feb 25 22:30:11 2020 -0600
    @@ -76,10 +76,10 @@
    static void
    test_gplugin_plugin_info_construction(void) {
    GPluginPluginInfo *info = NULL;
    - gchar *id = NULL, *name = NULL, *version = NULL;
    + gchar *id = NULL, *name = NULL, *version = NULL, *icon_name = NULL;
    gchar *license_id = NULL, *license_text = NULL, *license_url = NULL;
    - gchar *icon = NULL, *summary = NULL, *description = NULL, *category = NULL;
    - gchar *help = NULL, *website = NULL;
    + gchar *summary = NULL, *description = NULL, *category = NULL;
    + gchar *website = NULL;
    gchar **authors = NULL, **dependencies = NULL;
    guint abi_version = 0;
    gboolean internal = FALSE, load_on_query = FALSE;
    @@ -98,12 +98,11 @@
    "license-id", "license-id",
    "license-text", "license-text",
    "license-url", "license-url",
    - "icon", "icon",
    + "icon-name", "icon-name",
    "summary", "summary",
    "description", "description",
    "category", "category",
    "authors", r_authors,
    - "help", "help",
    "website", "website",
    "dependencies", r_dependencies,
    NULL);
    @@ -123,12 +122,11 @@
    "license-id", &license_id,
    "license-text", &license_text,
    "license-url", &license_url,
    - "icon", &icon,
    + "icon-name", &icon_name,
    "summary", &summary,
    "description", &description,
    "category", &category,
    "authors", &authors,
    - "help", &help,
    "website", &website,
    "dependencies", &dependencies,
    NULL);
    @@ -143,13 +141,12 @@
    test_string(license_id, "license-id");
    test_string(license_text, "license-text");
    test_string(license_url, "license-url");
    - test_string(icon, "icon");
    + test_string(icon_name, "icon-name");
    test_string(summary, "summary");
    test_string(description, "description");
    test_string(category, "category");
    test_stringv(authors, r_authors,
    (TestStringVFunc)gplugin_plugin_info_get_authors, info);
    - test_string(help, "help");
    test_string(website, "website");
    test_stringv(dependencies, r_dependencies,
    (TestStringVFunc)gplugin_plugin_info_get_dependencies, info);
    @@ -182,10 +179,10 @@
    static void
    test_gplugin_plugin_info_new_full(void) {
    GPluginPluginInfo *info = NULL;
    - gchar *id = NULL, *name = NULL, *version = NULL;
    + gchar *id = NULL, *name = NULL, *version = NULL, *icon_name = NULL;
    gchar *license_id = NULL, *license_text = NULL, *license_url = NULL;
    - gchar *icon = NULL, *summary = NULL, *description = NULL, *category = NULL;
    - gchar *website = NULL, *help = NULL;
    + gchar *summary = NULL, *description = NULL, *category = NULL;
    + gchar *website = NULL;
    gchar **authors = NULL, **dependencies = NULL;
    guint abi_version = 0;
    gboolean internal = FALSE, load_on_query = FALSE;
    @@ -203,12 +200,11 @@
    "license-id", "license-id",
    "license-text", "license-text",
    "license-url", "license-url",
    - "icon", "icon",
    + "icon-name", "icon-name",
    "summary", "summary",
    "description", "description",
    "category", "category",
    "authors", r_authors,
    - "help", "help",
    "website", "website",
    "dependencies", r_dependencies,
    NULL);
    @@ -228,12 +224,11 @@
    "license-id", &license_id,
    "license-text", &license_text,
    "license-url", &license_url,
    - "icon", &icon,
    + "icon-name", &icon_name,
    "summary", &summary,
    "description", &description,
    "category", &category,
    "authors", &authors,
    - "help", &help,
    "website", &website,
    "dependencies", &dependencies,
    NULL);
    @@ -248,13 +243,12 @@
    test_string(license_id, "license-id");
    test_string(license_text, "license-text");
    test_string(license_url, "license-url");
    - test_string(icon, "icon");
    + test_string(icon_name, "icon-name");
    test_string(summary, "summary");
    test_string(description, "description");
    test_string(category, "category");
    test_stringv(authors, r_authors,
    (TestStringVFunc)gplugin_plugin_info_get_authors, info);
    - test_string(help, "help");
    test_string(website, "website");
    test_stringv(dependencies, r_dependencies,
    (TestStringVFunc)gplugin_plugin_info_get_dependencies, info);
    --- a/gplugin/tests/test-version-compare.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/test-version-compare.c Tue Feb 25 22:30:11 2020 -0600
    @@ -28,7 +28,7 @@
    static void
    test_gplugin_version_null__null(void) {
    - g_test_trap_subprocess("/version-check/null__null/subprocess", 0, 0);
    + g_test_trap_subprocess("/version-compare/null__null/subprocess", 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("*gplugin_version_compare*assertion*");
    @@ -41,7 +41,7 @@
    static void
    test_gplugin_version_null__1_2_3(void) {
    - g_test_trap_subprocess("/version-check/null__1_2_3/subprocess", 0, 0);
    + g_test_trap_subprocess("/version-compare/null__1_2_3/subprocess", 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("*gplugin_version_compare*assertion*");
    @@ -54,7 +54,7 @@
    static void
    test_gplugin_version_1_2_3__null(void) {
    - g_test_trap_subprocess("/version-check/1_2_3__null/subprocess", 0, 0);
    + g_test_trap_subprocess("/version-compare/1_2_3__null/subprocess", 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("*gplugin_version_compare*assertion*");
    @@ -73,7 +73,7 @@
    static void
    test_gplugin_version_abc__1_2_3(void) {
    - g_test_trap_subprocess("/version-check/abc__1_2_3/subprocess", 0, 0);
    + g_test_trap_subprocess("/version-compare/abc__1_2_3/subprocess", 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("*assertion*");
    @@ -92,7 +92,7 @@
    static void
    test_gplugin_version_1_2_3__abc(void) {
    - g_test_trap_subprocess("/version-check/1_2_3__abc/subprocess", 0, 0);
    + g_test_trap_subprocess("/version-compare/1_2_3__abc/subprocess", 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("*assertion*");
    @@ -101,7 +101,7 @@
    /* major version tests */
    static void
    test_gplugin_version_1_0_0__0_0_0(void) {
    - g_assert_cmpint(gplugin_version_compare("1.0.0", "0.0.0", NULL), ==, -1);
    + g_assert_cmpint(gplugin_version_compare("1.0.0", "0.0.0", NULL), >, 0);
    }
    static void
    @@ -111,13 +111,13 @@
    static void
    test_gplugin_version_0_0_0__1_0_0(void) {
    - g_assert_cmpint(gplugin_version_compare("0.0.0", "1.0.0", NULL), ==, 1);
    + g_assert_cmpint(gplugin_version_compare("0.0.0", "1.0.0", NULL), <, 0);
    }
    /* minor version tests */
    static void
    test_gplugin_version_0_1_0__0_0_0(void) {
    - g_assert_cmpint(gplugin_version_compare("0.1.0", "0.0.0", NULL), ==, -1);
    + g_assert_cmpint(gplugin_version_compare("0.1.0", "0.0.0", NULL), >, 0);
    }
    static void
    @@ -127,28 +127,28 @@
    static void
    test_gplugin_version_0_0_0__0_1_0(void) {
    - g_assert_cmpint(gplugin_version_compare("0.0.0", "0.1.0", NULL), ==, 1);
    + g_assert_cmpint(gplugin_version_compare("0.0.0", "0.1.0", NULL), <, 0);
    }
    /* micro version tests */
    static void
    test_gplugin_version_0_0_1__0_0_0(void) {
    - g_assert_cmpint(gplugin_version_compare("0.1.0", "0.0.0", NULL), ==, -1);
    + g_assert_cmpint(gplugin_version_compare("0.0.1", "0.0.0", NULL), >, 0);
    }
    static void
    test_gplugin_version_0_0_1__0_0_1(void) {
    - g_assert_cmpint(gplugin_version_compare("0.1.0", "0.1.0", NULL), ==, 0);
    + g_assert_cmpint(gplugin_version_compare("0.0.1", "0.0.1", NULL), ==, 0);
    }
    static void
    test_gplugin_version_0_0_0__0_0_1(void) {
    - g_assert_cmpint(gplugin_version_compare("0.0.0", "0.1.0", NULL), ==, 1);
    + g_assert_cmpint(gplugin_version_compare("0.0.0", "0.0.1", NULL), <, 0);
    }
    /* major-minor tests */
    static void test_gplugin_version_1_0__0_1(void) {
    - g_assert_cmpint(gplugin_version_compare("1.0", "0.1", NULL), ==, -1);
    + g_assert_cmpint(gplugin_version_compare("1.0", "0.1", NULL), >, 0);
    }
    static void test_gplugin_version_1_0__1_0(void) {
    @@ -156,12 +156,12 @@
    }
    static void test_gplugin_version_0_1__1_0(void) {
    - g_assert_cmpint(gplugin_version_compare("0.1", "1.0", NULL), ==, 1);
    + g_assert_cmpint(gplugin_version_compare("0.1", "1.0", NULL), <, 0);
    }
    /* major tests */
    static void test_gplugin_version_1__0(void) {
    - g_assert_cmpint(gplugin_version_compare("1", "0", NULL), ==, -1);
    + g_assert_cmpint(gplugin_version_compare("1", "0", NULL), >, 0);
    }
    static void test_gplugin_version_1__1(void) {
    @@ -169,7 +169,7 @@
    }
    static void test_gplugin_version_0__1(void) {
    - g_assert_cmpint(gplugin_version_compare("0", "1", NULL), ==, 1);
    + g_assert_cmpint(gplugin_version_compare("0", "1", NULL), <, 0);
    }
    /******************************************************************************
    @@ -182,65 +182,65 @@
    gplugin_init();
    /* bad versions */
    - g_test_add_func("/version-check/null__null",
    + g_test_add_func("/version-compare/null__null",
    test_gplugin_version_null__null);
    - g_test_add_func("/version-check/null__1_2_3",
    + g_test_add_func("/version-compare/null__1_2_3",
    test_gplugin_version_null__1_2_3);
    - g_test_add_func("/version-check/1_2_3__null",
    + g_test_add_func("/version-compare/1_2_3__null",
    test_gplugin_version_1_2_3__null);
    - g_test_add_func("/version-check/abc__1_2_3",
    + g_test_add_func("/version-compare/abc__1_2_3",
    test_gplugin_version_abc__1_2_3);
    - g_test_add_func("/version-check/1_2_3__abc",
    + g_test_add_func("/version-compare/1_2_3__abc",
    test_gplugin_version_1_2_3__abc);
    - g_test_add_func("/version-check/null__null/subprocess",
    + g_test_add_func("/version-compare/null__null/subprocess",
    test_gplugin_version_null__null_subprocess);
    - g_test_add_func("/version-check/null__1_2_3/subprocess",
    + g_test_add_func("/version-compare/null__1_2_3/subprocess",
    test_gplugin_version_null__1_2_3_subprocess);
    - g_test_add_func("/version-check/1_2_3__null/subprocess",
    + g_test_add_func("/version-compare/1_2_3__null/subprocess",
    test_gplugin_version_1_2_3__null_subprocess);
    - g_test_add_func("/version-check/abc__1_2_3/subprocess",
    + g_test_add_func("/version-compare/abc__1_2_3/subprocess",
    test_gplugin_version_abc__1_2_3_subprocess);
    - g_test_add_func("/version-check/1_2_3__abc/subprocess",
    + g_test_add_func("/version-compare/1_2_3__abc/subprocess",
    test_gplugin_version_1_2_3__abc_subprocess);
    /* major version */
    - g_test_add_func("/version-check/1_0_0__0_0_0",
    + g_test_add_func("/version-compare/1_0_0__0_0_0",
    test_gplugin_version_1_0_0__0_0_0);
    - g_test_add_func("/version-check/1_0_0__1_0_0",
    + g_test_add_func("/version-compare/1_0_0__1_0_0",
    test_gplugin_version_1_0_0__1_0_0);
    - g_test_add_func("/version-check/0_0_0__1_0_0",
    + g_test_add_func("/version-compare/0_0_0__1_0_0",
    test_gplugin_version_0_0_0__1_0_0);
    /* minor version */
    - g_test_add_func("/version-check/0_1_0__0_0_0",
    + g_test_add_func("/version-compare/0_1_0__0_0_0",
    test_gplugin_version_0_1_0__0_0_0);
    - g_test_add_func("/version-check/0_1_0__0_1_0",
    + g_test_add_func("/version-compare/0_1_0__0_1_0",
    test_gplugin_version_0_1_0__0_1_0);
    - g_test_add_func("/version-check/0_0_0__0_1_0",
    + g_test_add_func("/version-compare/0_0_0__0_1_0",
    test_gplugin_version_0_0_0__0_1_0);
    /* micro version */
    - g_test_add_func("/version-check/0_0_1__0_0_0",
    + g_test_add_func("/version-compare/0_0_1__0_0_0",
    test_gplugin_version_0_0_1__0_0_0);
    - g_test_add_func("/version-check/0_0_1__0_0_1",
    + g_test_add_func("/version-compare/0_0_1__0_0_1",
    test_gplugin_version_0_0_1__0_0_1);
    - g_test_add_func("/version-check/0_0_0__0_0_1",
    + g_test_add_func("/version-compare/0_0_0__0_0_1",
    test_gplugin_version_0_0_0__0_0_1);
    /* major-minor */
    - g_test_add_func("/version-check/1_0__0_1",
    + g_test_add_func("/version-compare/1_0__0_1",
    test_gplugin_version_1_0__0_1);
    - g_test_add_func("/version-check/1_0__1_0",
    + g_test_add_func("/version-compare/1_0__1_0",
    test_gplugin_version_1_0__1_0);
    - g_test_add_func("/version-check/0_1__1_0",
    + g_test_add_func("/version-compare/0_1__1_0",
    test_gplugin_version_0_1__1_0);
    /* major */
    - g_test_add_func("/version-check/1__0",
    + g_test_add_func("/version-compare/1__0",
    test_gplugin_version_1__0);
    - g_test_add_func("/version-check/1__1",
    + g_test_add_func("/version-compare/1__1",
    test_gplugin_version_1__1);
    - g_test_add_func("/version-check/0__1",
    + g_test_add_func("/version-compare/0__1",
    test_gplugin_version_0__1);
    return g_test_run();
    --- a/gplugin/tests/unresolved-symbol/unresolved-symbol.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/unresolved-symbol/unresolved-symbol.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2016 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/bar.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/bar.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/baz.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/baz.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/exact1.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/exact1.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/exact2.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/exact2.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/fez.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/fez.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/greater-equal.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/greater-equal.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/greater.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/greater.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/less-equal.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/less-equal.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/less.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/less.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -42,4 +42,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/no-version.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/no-version.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -40,4 +40,3 @@
    {
    return TRUE;
    }
    -
    --- a/gplugin/tests/versioned-dependencies/super-dependent.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/gplugin/tests/versioned-dependencies/super-dependent.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -57,4 +57,3 @@
    {
    return TRUE;
    }
    -
    --- a/lua/gplugin-lua-core.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-core.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -51,15 +51,11 @@
    }
    G_MODULE_EXPORT gboolean
    -gplugin_load(GPluginNativePlugin *plugin,
    - G_GNUC_UNUSED GError **error)
    -{
    +gplugin_load(GPluginNativePlugin *plugin, GError **error) {
    gplugin_lua_loader_register(plugin);
    gplugin_lua_plugin_register(plugin);
    - gplugin_manager_register_loader(GPLUGIN_LUA_TYPE_LOADER);
    -
    - return TRUE;
    + return gplugin_manager_register_loader(GPLUGIN_LUA_TYPE_LOADER, error);
    }
    G_MODULE_EXPORT gboolean
    @@ -75,4 +71,3 @@
    return FALSE;
    }
    -
    --- a/lua/gplugin-lua-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -19,7 +19,7 @@
    #include "gplugin-lua-plugin.h"
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <lua.h>
    #include <lauxlib.h>
    @@ -81,7 +81,7 @@
    * GPluginLoaderInterface API
    *****************************************************************************/
    static GSList *
    -gplugin_lua_loader_class_supported_extensions(G_GNUC_UNUSED GPluginLoaderClass *klass) {
    +gplugin_lua_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *l) {
    return g_slist_append(NULL, "lua");
    }
    @@ -177,7 +177,7 @@
    GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    loader_class->supported_extensions =
    - gplugin_lua_loader_class_supported_extensions;
    + gplugin_lua_loader_supported_extensions;
    loader_class->query = gplugin_lua_loader_query;
    loader_class->load = gplugin_lua_loader_load;
    loader_class->unload = gplugin_lua_loader_unload;
    --- a/lua/gplugin-lua-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_LUA_LOADER_H */
    -
    --- a/lua/gplugin-lua-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/lua/gplugin-lua-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -35,4 +35,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_LUA_PLUGIN_H */
    -
    --- a/lua/gplugin-lua-test-lgi.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/gplugin-lua-test-lgi.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -63,4 +63,3 @@
    return ret;
    }
    -
    --- a/lua/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -14,17 +14,30 @@
    'gplugin-lua-plugin.h',
    ]
    - _LUAS = [['lua', '>=5.1.0'],
    - ['lua5.1', '>=5.1.0'],
    - ['luajit', '>=2.0.0'],
    - ['lua52', '>=5.2.0'],
    - ['lua5.2', '>=5.2.0'],
    - ['lua-5.2', '>=5.2.0']]
    + # These are ordered from most to least preferred, which would normally
    + # be from the highest to lowest version.
    + _LUAS = [
    + ['lua5.3', '>=5.3.0'],
    + ['lua-5.3', '>=5.3.0'],
    + ['lua5.2', '>=5.2.0'],
    + ['lua-5.2', '>=5.2.0'],
    + ['luajit', '>=2.0.0'],
    + ['lua5.1', '>=5.1.0'],
    + ['lua-5.1', '>=5.1.0'],
    + ['lua', '>=5.1.0'],
    + ]
    LUA_FOUND = false
    foreach _LUA : _LUAS
    - if not LUA_FOUND
    - LUA = dependency(_LUA[0], version : _LUA[1], required : false)
    - LUA_FOUND = LUA.found()
    + LUA = dependency(_LUA[0], version : _LUA[1], required : false)
    + if LUA.found()
    + # Compile and run our lua-lgi test program
    + lua_lgi_test = compiler.run(files('gplugin-lua-test-lgi.c'),
    + dependencies : LUA,
    + name : 'lua "lgi" module')
    + if lua_lgi_test.compiled() and lua_lgi_test.returncode() == 0
    + LUA_FOUND = true
    + break
    + endif
    endif
    endforeach
    @@ -32,14 +45,6 @@
    error('No usable Lua library was found')
    endif
    - # Compile and run our lua-lgi test program
    - lua_lgi_test = compiler.run(files('gplugin-lua-test-lgi.c'),
    - dependencies : LUA,
    - name : 'lua "lgi" module')
    - if not lua_lgi_test.compiled() or lua_lgi_test.returncode() != 0
    - error('Failed to find the "lgi" lua module')
    - endif
    -
    # now add the library
    shared_library('gplugin-lua',
    GPLUGIN_LUA_SOURCES,
    --- a/lua/tests/plugins/basic.lua Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/plugins/basic.lua Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    --[[
    - Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    @@ -40,5 +40,3 @@
    function gplugin_unload(plugin)
    return true
    end
    -
    -print(gplugin_query())
    --- a/lua/tests/plugins/dependent.lua Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/plugins/dependent.lua Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    --[[
    - Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    @@ -32,4 +32,3 @@
    function gplugin_unload(plugin)
    return false
    end
    -
    --- a/lua/tests/plugins/load-exception.lua Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/plugins/load-exception.lua Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    --[[
    - Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    function gplugin_unload(plugin)
    return false
    end
    -
    --- a/lua/tests/plugins/load-failed.lua Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/plugins/load-failed.lua Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    --[[
    - Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    function gplugin_unload(plugin)
    return true
    end
    -
    --- a/lua/tests/plugins/unload-failed.lua Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/plugins/unload-failed.lua Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    --[[
    - Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    function gplugin_unload(plugin)
    return false
    end
    -
    --- a/lua/tests/test-lua-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/lua/tests/test-lua-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -28,4 +28,3 @@
    return g_test_run();
    }
    -
    --- a/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -74,7 +74,6 @@
    toplevel_inc = include_directories('.')
    -
    ###############################################################################
    # gtk-doc
    ###############################################################################
    @@ -101,4 +100,3 @@
    # documentation
    install_data('ChangeLog', 'INSTALL.md', 'README.md', 'HACKING.md',
    install_dir : join_paths(get_option('datadir'), 'doc', 'gplugin'))
    -
    --- a/meson_options.txt Sat Feb 15 21:14:07 2020 -0600
    +++ b/meson_options.txt Tue Feb 25 22:30:11 2020 -0600
    @@ -39,12 +39,12 @@
    option(
    'perl',
    - type : 'boolean', value : false,
    + type : 'boolean', value : true,
    description : 'Whether or not to build the Perl plugin loader'
    )
    option(
    - 'python',
    + 'python3',
    type : 'boolean', value : true,
    description : 'Whether or not to build the Python 3.x plugin loader'
    )
    --- a/packaging/debian/control Sat Feb 15 21:14:07 2020 -0600
    +++ b/packaging/debian/control Tue Feb 25 22:30:11 2020 -0600
    @@ -51,6 +51,7 @@
    Architecture: all
    Depends: ${misc:Depends}, ${shlibs:Depends},
    libgplugin-lua,
    + libgplugin-perl,
    libgplugin-python
    Description: metapackage for all gplugin loaders
    GPlugin is a GObject based library that implements a reusable plugin system
    @@ -115,7 +116,7 @@
    This package allows GPlugin to load plugins written in the Lua programming
    language.
    -Package: libgplugin-python
    +Package: libgplugin-python3
    Architecture: any
    Depends: ${misc:Depends}, ${shlibs:Depends}, python3, python3-gi,
    libgplugin0, gir1.2-gplugin-0.0
    @@ -124,6 +125,18 @@
    which supports loading plugins in other languages via loaders. It relies
    heavily on GObjectIntrospection to expose its API to the other languages.
    .
    - This package allows GPlugin to load plugin written in the Python programming
    + This package allows GPlugin to load plugins written in the Python programming
    language.
    +Package: libgplugin-perl
    +Architecture: any
    +Depends: ${misc:Depends}, ${shlibs:Depends}, perl, libglib-perl,
    + libglib-object-introspection-perl, libgplugin0, gir1.2-gplugin-0.0
    +Description: GPlugin Perl Loader
    + GPlugin is a GObject based library that implements a reusable plugin system
    + which supports loading plugins in other languages via loaders. It relies
    + heavily on GObjectIntrospection to expose its API to the other languages.
    + .
    + This package allows GPlugin to load plugins written in the Perl programming
    + language.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/packaging/debian/libgplugin-perl.install Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,1 @@
    +debian/tmp/usr/lib/*/gplugin/gplugin-perl.so
    --- a/packaging/debian/libgplugin-python.install Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,1 +0,0 @@
    -debian/tmp/usr/lib/*/gplugin/gplugin-python.so
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/packaging/debian/libgplugin-python3.install Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,1 @@
    +debian/tmp/usr/lib/*/gplugin/gplugin-python3.so
    --- a/packaging/gplugin.spec.in Sat Feb 15 21:14:07 2020 -0600
    +++ b/packaging/gplugin.spec.in Tue Feb 25 22:30:11 2020 -0600
    @@ -23,7 +23,14 @@
    BuildRequires: python3-devel
    BuildRequires: python3-gobject
    BuildRequires: vala
    -BuildRequires: /usr/lib64/pkgconfig/pygobject-3.0.pc
    +BuildRequires: perl-Glib
    +BuildRequires: perl-Glib-Object-Introspection
    +%if 0%{?fedora}
    +BuildRequires: perl-devel
    +%endif
    +%if 0%{?opensuse}
    +BuildRequires: perl-ExtUtils-Config
    +%endif
    %package -n libgplugin0
    Summary: A GObject based library that implements a reusable plugin system
    @@ -54,6 +61,10 @@
    Summary: A GObject based library that implements a reusable plugin system
    Group: Development/Libraries
    +%package perl
    +Summary: A GObject based library that implements a reusable plugin system
    +Group: Development/Libraries
    +
    %package python3
    Summary: A GObject based library that implements a reusable plugin system
    Group: Development/Libraries
    @@ -68,31 +79,16 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    %description -n libgplugin0
    GPlugin is a GObject based library that implements a reusable plugin system
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    %description devel
    GPlugin is a GObject based library that implements a reusable plugin system
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains all necessary include files and libraries needed
    to develop applications that require these.
    @@ -101,11 +97,6 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains additional GTK3 dependencies for %{name} library.
    %description gtk3-devel
    @@ -113,11 +104,6 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains all necessary include files and libraries needed
    to develop GTK3 applications that require these.
    @@ -126,11 +112,6 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains the vapi bindings allowing GPluginGtk to be used from
    vala.
    @@ -139,23 +120,20 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    +This package contains the Lua Loader for %{name} library.
    -This package contains the Lua Loader for %{name} library.
    +%description perl
    +GPlugin is a GObject based library that implements a reusable plugin system
    +that supports loading plugins in other languages via loaders. GPlugin also
    +implements dependencies among the plugins.
    +
    +This package contains Perl loader for %{name} library.
    %description python3
    GPlugin is a GObject based library that implements a reusable plugin system
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains Python 3 loader for %{name} library.
    %description vala
    @@ -163,18 +141,11 @@
    that supports loading plugins in other languages via loaders. GPlugin also
    implements dependencies among the plugins.
    -It was started due to the infamous "Plugin Problem" for Guifications 3, which
    -was that I needed plugins that could depend on each other, be able to be
    -written in other languages, have plugins that are loaded before the main load
    -phase, and allow plugins to register types into the GObject type system.
    -
    This package contains the vapi bindings allowing GPlugin to be used from vala.
    %build
    CFLAGS="%{optflags}" meson \
    --prefix=%{_prefix} \
    - -Dlua=true \
    - -Dpython=true \
    build
    ninja -C build %{?_smp_mflags}
    @@ -196,17 +167,21 @@
    %files
    %defattr(-,root,root)
    %doc README.md ChangeLog
    +%license COPYING
    %{_bindir}/gplugin-query
    %{_libdir}/gplugin/
    %{_mandir}/man1/gplugin-query.1*
    %files -n libgplugin0
    %defattr(-,root,root)
    +%doc README.md
    +%license COPYING
    %{_libdir}/libgplugin.so.0.1.0
    %files devel
    %defattr(-,root,root)
    %doc HACKING.md README.md
    +%license COPYING
    %{_datadir}/gir-1.0/GPlugin-0.0.gir
    %{_datadir}/gtk-doc/html/gplugin/
    %{_includedir}/gplugin-1.0/
    @@ -219,6 +194,7 @@
    %files gtk3
    %defattr(-,root,root)
    %doc README.md
    +%license COPYING
    %{_bindir}/gplugin-gtk-viewer
    %{_libdir}/libgplugin-gtk.so.0.1.0
    %{_mandir}/man1/gplugin-gtk-viewer.1*
    @@ -227,6 +203,7 @@
    %defattr(-,root,root)
    %dir %{_datadir}/glade/catalogs/
    %doc README.md
    +%license COPYING
    %{_datadir}/gir-1.0/GPluginGtk-0.0.gir
    %{_datadir}/glade/catalogs/gplugin-gtk.xml
    %{_datadir}/gtk-doc/html/gplugin-gtk/
    @@ -234,16 +211,30 @@
    %{_libdir}/libgplugin-gtk.so
    %{_libdir}/libgplugin-gtk.so.0
    +%files gtk3-vala
    +%defattr(-,root,root)
    +%doc README.md
    +%license COPYING
    +%{_datadir}/vala/vapi/gplugin-gtk.vapi
    +%{_datadir}/vala/vapi/gplugin-gtk.deps
    +
    %files lua
    %defattr(-,root,root)
    %doc README.md
    %license COPYING
    %{_libdir}/gplugin/gplugin-lua.so
    +%files perl
    +%defattr(-,root,root)
    +%doc README.md
    +%license COPYING
    +%{_libdir}/gplugin/gplugin-perl.so
    +
    %files python3
    %defattr(-,root,root)
    %doc README.md
    -%{_libdir}/gplugin/gplugin-python.so
    +%license COPYING
    +%{_libdir}/gplugin/gplugin-python3.so
    %files vala
    %defattr(-,root,root)
    @@ -252,14 +243,11 @@
    %{_datadir}/vala/vapi/gplugin.vapi
    %{_datadir}/vala/vapi/gplugin.deps
    -%files gtk3-vala
    -%defattr(-,root,root)
    -%doc README.md
    -%license COPYING
    -%{_datadir}/vala/vapi/gplugin-gtk.vapi
    -%{_datadir}/vala/vapi/gplugin-gtk.deps
    +%changelog
    +* Tue Feb 18 2020 Gary Kramlich <grim@reaperworld.com>
    +- Add the perl package
    +- Updated the python files to reference the new name for gplugin-python3
    -%changelog
    * Thu Oct 31 2019 Gary Kramlich <grim@reaperworld.com>
    - Fixed an include bug in gplugin-gtk
    - Fixed building again python3.8
    --- a/perl/gplugin-perl-core.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/gplugin-perl-core.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -21,6 +21,9 @@
    #include "gplugin-perl-loader.h"
    #include "gplugin-perl-plugin.h"
    +#undef _
    +#include <glib/gi18n-lib.h>
    +
    G_MODULE_EXPORT GPluginPluginInfo *
    gplugin_query(GError **error) {
    const gchar * const authors[] = {
    @@ -52,13 +55,17 @@
    gplugin_perl_plugin_register(plugin);
    gplugin_perl_loader_register(plugin);
    - gplugin_manager_register_loader(GPLUGIN_PERL_TYPE_LOADER);
    -
    - return TRUE;
    + return gplugin_manager_register_loader(GPLUGIN_PERL_TYPE_LOADER, error);
    }
    G_MODULE_EXPORT gboolean
    gplugin_unload(GPluginNativePlugin *plugin, GError **error) {
    - return TRUE;
    + g_set_error_literal(
    + error,
    + GPLUGIN_DOMAIN,
    + 0,
    + _("The Perl loader can not be unloaded")
    + );
    +
    + return FALSE;
    }
    -
    --- a/perl/gplugin-perl-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/gplugin-perl-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -16,15 +16,9 @@
    */
    #include "gplugin-perl-loader.h"
    -
    -#include <EXTERN.h>
    -#include <perl.h>
    +#include "gplugin-perl-plugin.h"
    -/* perl define's _() to something completely different that we don't use. So
    - * we undef it so that we can use it for gettext.
    - */
    -#undef _
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    struct _GPluginPerlLoader {
    GPluginLoader parent;
    @@ -32,52 +26,20 @@
    G_DEFINE_DYNAMIC_TYPE(GPluginPerlLoader, gplugin_perl_loader, GPLUGIN_TYPE_LOADER);
    -/* I can't believe I have to use this variable name... */
    static PerlInterpreter *my_perl = NULL;
    /******************************************************************************
    - * GPluginLoaderInterface API
    - *****************************************************************************/
    -static GSList *
    -gplugin_perl_loader_class_supported_extensions(const GPluginLoaderClass *klass) {
    - return g_slist_append(NULL, "pl");
    -}
    -
    -static GPluginPlugin *
    -gplugin_perl_loader_query(GPluginLoader *loader,
    - const gchar *filename,
    - GError **error)
    -{
    - const gchar *args[] = { "", filename };
    - gchar **argv = (gchar **)args;
    - gint argc = 2;
    -
    - perl_parse(my_perl, NULL, argc, argv, NULL);
    -
    - call_argv("gplugin_plugin_query", G_DISCARD | G_NOARGS, argv);
    -
    - return NULL;
    -}
    -
    -static gboolean
    -gplugin_perl_loader_load(GPluginLoader *loader,
    - GPluginPlugin *plugin,
    - GError **error)
    -{
    - return FALSE;
    -}
    -
    -static gboolean
    -gplugin_perl_loader_unload(GPluginLoader *loader,
    - GPluginPlugin *plugin,
    - GError **error)
    -{
    - return FALSE;
    -}
    -
    -/******************************************************************************
    * Perl Stuff
    *****************************************************************************/
    +extern void boot_DynaLoader(pTHX_ CV* cv);
    +
    +static void
    +gplugin_perl_loader_xs_init(pTHX) {
    + dXSUB_SYS;
    +
    + newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__);
    +}
    +
    static void
    gplugin_perl_loader_init_perl(void) {
    gchar *args[] = { "", };
    @@ -88,17 +50,221 @@
    my_perl = perl_alloc();
    PERL_SET_CONTEXT(my_perl);
    - PL_perl_destruct_level = 1;
    + PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
    perl_construct(my_perl);
    }
    static void
    gplugin_perl_loader_uninit_perl(void) {
    - PERL_SYS_TERM();
    -
    + PERL_SET_CONTEXT(my_perl);
    perl_destruct(my_perl);
    perl_free(my_perl);
    - my_perl = NULL;
    + PERL_SYS_TERM();
    +}
    +
    +static GPluginPluginInfo *
    +gplugin_perl_loader_call_gplugin_query(PerlInterpreter *interpreter, GError **error) {
    + GPluginPluginInfo *info = NULL;
    + PerlInterpreter *old = NULL;
    + gint ret = 0;
    +
    + dSP;
    +
    + old = my_perl;
    + my_perl = interpreter;
    + PERL_SET_CONTEXT(interpreter);
    +
    + ENTER;
    + SAVETMPS;
    +
    + PUSHMARK(SP);
    + PUTBACK;
    +
    + ret = call_pv("gplugin_query", G_EVAL | G_NOARGS);
    +
    + SPAGAIN;
    +
    + if(ret != 1) {
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0,
    + "gplugin_query did not return a GPluginPluginInfo");
    + } else {
    + if(SvTRUE(ERRSV)) {
    + const gchar *errmsg = SvPVutf8_nolen(ERRSV);
    +
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0, errmsg);
    + } else {
    + info = (GPluginPluginInfo *)gperl_get_object(POPs);
    +
    + /* if we did get a real GPluginPluginInfo ref it because the perl
    + * code below will take it out of scope and delete it if its
    + * reference count is zero.
    + */
    + if(GPLUGIN_IS_PLUGIN_INFO(info)) {
    + g_object_ref(G_OBJECT(info));
    + }
    + }
    + }
    +
    + PUTBACK;
    + FREETMPS;
    + LEAVE;
    +
    + my_perl = old;
    +
    + return info;
    +}
    +
    +static gboolean
    +gplugin_perl_loader_call_boolean(PerlInterpreter *interpreter,
    + const gchar *func, GError **error)
    +{
    + PerlInterpreter *old = NULL;
    + gboolean r = FALSE;
    + gint count = 0;
    +
    + dSP;
    +
    + old = my_perl;
    + my_perl = interpreter;
    + PERL_SET_CONTEXT(interpreter);
    +
    + ENTER;
    + SAVETMPS;
    + PUSHMARK(SP);
    + PUTBACK;
    +
    + count = call_pv(func, G_EVAL | G_SCALAR);
    +
    + SPAGAIN;
    +
    + if(count != 1) {
    + g_set_error(error, GPLUGIN_DOMAIN, 0, "%s did not return a value",
    + func);
    + } else {
    + SV *err = ERRSV;
    +
    + if(SvTRUE(err)) {
    + const gchar *errmsg = SvPVutf8_nolen(err);
    +
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0, errmsg);
    + } else {
    + if(POPi == 0) {
    + r = TRUE;
    + }
    + }
    + }
    +
    + PUTBACK;
    + FREETMPS;
    + LEAVE;
    +
    + my_perl = old;
    +
    + return r;
    +}
    +
    +/******************************************************************************
    + * GPluginLoaderInterface API
    + *****************************************************************************/
    +static GSList *
    +gplugin_perl_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *l) {
    + return g_slist_append(NULL, "pl");
    +}
    +
    +static GPluginPlugin *
    +gplugin_perl_loader_query(GPluginLoader *loader,
    + const gchar *filename,
    + GError **error)
    +{
    + GPluginPlugin *plugin = NULL;
    + GPluginPluginInfo *info = NULL;
    + PerlInterpreter *interpreter = NULL;
    + const gchar *args[] = { "", filename };
    + gchar **argv = (gchar **)args;
    + gint argc = 2, ret = 0;
    +
    + interpreter = perl_alloc();
    + PERL_SET_CONTEXT(interpreter);
    + PL_perl_destruct_level = 1;
    + perl_construct(interpreter);
    +
    + ret = perl_parse(interpreter, gplugin_perl_loader_xs_init, argc, argv, NULL);
    + if(ret != 0) {
    + const gchar *errmsg = "unknown error";
    +
    + if(SvTRUE(ERRSV)) {
    + errmsg = SvPVutf8_nolen(ERRSV);
    + }
    +
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0, errmsg);
    +
    + perl_destruct(interpreter);
    + perl_free(interpreter);
    +
    + return NULL;
    + }
    +
    + ret = perl_run(interpreter);
    + if(ret != 0) {
    + const gchar *errmsg = "unknown error";
    +
    + if(SvTRUE(ERRSV)) {
    + errmsg = SvPVutf8_nolen(ERRSV);
    + }
    +
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0, errmsg);
    +
    + perl_destruct(interpreter);
    + perl_free(interpreter);
    +
    + return NULL;
    + }
    +
    + info = gplugin_perl_loader_call_gplugin_query(interpreter, error);
    + if(!GPLUGIN_IS_PLUGIN_INFO(info)) {
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0, "failed to query");
    +
    + return NULL;
    + }
    +
    + plugin = g_object_new(
    + GPLUGIN_PERL_TYPE_PLUGIN,
    + "interpreter", interpreter,
    + "filename", filename,
    + "info", info,
    + "loader", g_object_ref(loader),
    + NULL
    + );
    +
    + return plugin;
    +}
    +
    +static gboolean
    +gplugin_perl_loader_load(G_GNUC_UNUSED GPluginLoader *loader,
    + GPluginPlugin *plugin,
    + GError **error)
    +{
    + GPluginPerlPlugin *pplugin = GPLUGIN_PERL_PLUGIN(plugin);
    + PerlInterpreter *interpreter = NULL;
    +
    + interpreter = gplugin_perl_plugin_get_interpreter(pplugin);
    +
    + return gplugin_perl_loader_call_boolean(interpreter, "gplugin_load",
    + error);
    +}
    +
    +static gboolean
    +gplugin_perl_loader_unload(G_GNUC_UNUSED GPluginLoader *loader,
    + GPluginPlugin *plugin,
    + GError **error)
    +{
    + GPluginPerlPlugin *pplugin = GPLUGIN_PERL_PLUGIN(plugin);
    + PerlInterpreter *interpreter = NULL;
    +
    + interpreter = gplugin_perl_plugin_get_interpreter(pplugin);
    +
    + return gplugin_perl_loader_call_boolean(interpreter, "gplugin_unload",
    + error);
    }
    /******************************************************************************
    @@ -113,7 +279,7 @@
    GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    loader_class->supported_extensions =
    - gplugin_perl_loader_class_supported_extensions;
    + gplugin_perl_loader_supported_extensions;
    loader_class->query = gplugin_perl_loader_query;
    loader_class->load = gplugin_perl_loader_load;
    loader_class->unload = gplugin_perl_loader_unload;
    --- a/perl/gplugin-perl-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/gplugin-perl-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_PERL_PLUGIN_LOADER_H */
    -
    --- a/perl/gplugin-perl-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/gplugin-perl-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -20,12 +20,6 @@
    #include <EXTERN.h>
    #include <perl.h>
    -/* perl define's _() to something completely different that we don't use. So
    - * we undef it so that we can use it for gettext.
    - */
    -#undef _
    -#include <glib/gi18n.h>
    -
    /******************************************************************************
    * Typedefs
    *****************************************************************************/
    --- a/perl/gplugin-perl-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/gplugin-perl-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -21,8 +21,11 @@
    #include <gplugin.h>
    #include <gplugin-native.h>
    -#include <EXTERN.h>
    -#include <perl.h>
    +#define PERL_NO_GET_CONTEXT
    +#include <gperl.h>
    +/* perl define's _() to something completely different that we don't use. So
    + * we undef it so that we can use it for gettext.
    + */
    #undef _
    G_BEGIN_DECLS
    --- a/perl/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -14,24 +14,35 @@
    'gplugin-perl-plugin.h',
    ]
    + # make sure we have the perl executable, we need it to figure out the
    + # build arguments.
    PERL = find_program('perl')
    - PERL_CFLAGS = run_command(PERL, '-MExtUtils::Embed', '-e', 'ccopts').stdout()
    - PERL_LDFLAGS = run_command(PERL, '-MExtUtils::Embed', '-e', 'ldopts').stdout()
    - message('PERL_CFLAGS ' + PERL_CFLAGS)
    - message('PERL_LDFLAGS ' + PERL_LDFLAGS)
    + # make sure we have the gobject introspection perl module.
    + run_command(
    + PERL,
    + '-e use Glib::Object::Introspection;',
    + check : true,
    + )
    - PERL_CFLAGS = PERL_CFLAGS.split()
    - PERL_LDFLAGS = PERL_LDFLAGS.split()
    + perl_dep = declare_dependency(
    + compile_args : run_command(PERL, '-MExtUtils::Embed', '-e', 'ccopts', check : true).stdout().split(),
    + link_args : run_command(PERL, '-MExtUtils::Embed', '-e', 'ldopts', check : true).stdout().split(),
    + )
    +
    + glib_perl_dep = declare_dependency(
    + compile_args : run_command(PERL, '-MConfig', '-e', 'print "-I$Config{vendorarch}/Glib/Install"', check : true).stdout().split(),
    + link_args : run_command(PERL, '-MConfig', '-e', 'print "$Config{vendorarch}/auto/Glib/Glib.so"', check : true).stdout().split(),
    + )
    shared_library('gplugin-perl',
    GPLUGIN_PERL_SOURCES,
    GPLUGIN_PERL_HEADERS,
    - c_args : PERL_CFLAGS,
    - link_args : PERL_LDFLAGS,
    name_prefix : '',
    - dependencies : [gplugin_dep],
    + dependencies : [GMODULE, gplugin_dep, perl_dep, glib_perl_dep],
    install : true,
    install_dir : join_paths(get_option('libdir'), 'gplugin')
    )
    endif # perl
    +
    +subdir('tests')
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,12 @@
    +if get_option('perl')
    +
    +e = executable('test-perl-loader', 'test-perl-loader.c',
    + c_args : [
    + '-DPERL_LOADER_DIR="@0@"'.format(join_paths(meson.current_build_dir(), '..')),
    + '-DPERL_PLUGIN_DIR="@0@/plugins"'.format(meson.current_source_dir()),
    + ],
    + link_with : gplugin_loader_tests,
    + dependencies : [GLIB, GOBJECT, gplugin_dep, perl_dep])
    +test('Perl Loader', e)
    +
    +endif # perl
    --- a/perl/tests/plugins/basic.pl Sat Feb 15 21:14:07 2020 -0600
    +++ b/perl/tests/plugins/basic.pl Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,4 @@
    -# vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -20,30 +19,25 @@
    Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    -sub gplugin_plugin_query() {
    +sub gplugin_query {
    return GPlugin::PluginInfo->new(
    - id => "gplugin-perl/basic-plugin",
    - abi_version => 0x01000001,
    + id => "gplugin/perl-basic-plugin",
    + abi_version => 0x01020304,
    name => "basic plugin",
    - authors => ("Gary Kramlich <grim\@reaperworld.com>"),
    + authors => ("author1"),
    category => "test",
    version => "version",
    - license_id => "license id",
    + license_id => "license",
    summary => "summary",
    website => "website",
    description => "description",
    );
    }
    -sub gplugin_plugin_load() {
    - my $plugin = shift;
    -
    +sub gplugin_load {
    return 0;
    }
    -sub gplugin_plugin_unload() {
    - my $plugin = shift;
    -
    +sub gplugin_unload {
    return 0;
    }
    -
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/plugins/dependent.pl Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,35 @@
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    +#
    +# This library is free software; you can redistribute it and/or
    +# modify it under the terms of the GNU Lesser General Public
    +# License as published by the Free Software Foundation; either
    +# version 2 of the License, or (at your option) any later version.
    +#
    +# This library is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    +# Lesser General Public License for more details.
    +#
    +# You should have received a copy of the GNU Lesser General Public
    +# License along with this library; if not, see <http://www.gnu.org/licenses/>.
    +
    +use strict;
    +
    +use Glib::Object::Introspection;
    +
    +Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    +
    +sub gplugin_query() {
    + return GPlugin::PluginInfo->new(
    + id => "gplugin/perl-dependent-plugin",
    + dependencies => ['dependency1', 'dependency2'],
    + );
    +}
    +
    +sub gplugin_load() {
    + return 1;
    +}
    +
    +sub gplugin_unload() {
    + return 1;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/plugins/load-exception.pl Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,36 @@
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    +#
    +# This library is free software; you can redistribute it and/or
    +# modify it under the terms of the GNU Lesser General Public
    +# License as published by the Free Software Foundation; either
    +# version 2 of the License, or (at your option) any later version.
    +#
    +# This library is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    +# Lesser General Public License for more details.
    +#
    +# You should have received a copy of the GNU Lesser General Public
    +# License along with this library; if not, see <http://www.gnu.org/licenses/>.
    +
    +use strict;
    +
    +use Glib::Object::Introspection;
    +
    +Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    +
    +sub gplugin_query() {
    + return GPlugin::PluginInfo->new(
    + id => "gplugin/perl-load-exception",
    + );
    +}
    +
    +sub gplugin_load() {
    + die("you be dead");
    +
    + return 0;
    +}
    +
    +sub gplugin_unload() {
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/plugins/load-failed.pl Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,34 @@
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    +#
    +# This library is free software; you can redistribute it and/or
    +# modify it under the terms of the GNU Lesser General Public
    +# License as published by the Free Software Foundation; either
    +# version 2 of the License, or (at your option) any later version.
    +#
    +# This library is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    +# Lesser General Public License for more details.
    +#
    +# You should have received a copy of the GNU Lesser General Public
    +# License along with this library; if not, see <http://www.gnu.org/licenses/>.
    +
    +use strict;
    +
    +use Glib::Object::Introspection;
    +
    +Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    +
    +sub gplugin_query {
    + return GPlugin::PluginInfo->new(
    + id => "gplugin/perl-load-failed",
    + );
    +}
    +
    +sub gplugin_load {
    + return 1;
    +}
    +
    +sub gplugin_unload {
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/plugins/unload-failed.pl Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,34 @@
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    +#
    +# This library is free software; you can redistribute it and/or
    +# modify it under the terms of the GNU Lesser General Public
    +# License as published by the Free Software Foundation; either
    +# version 2 of the License, or (at your option) any later version.
    +#
    +# This library is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    +# Lesser General Public License for more details.
    +#
    +# You should have received a copy of the GNU Lesser General Public
    +# License along with this library; if not, see <http://www.gnu.org/licenses/>.
    +
    +use strict;
    +
    +use Glib::Object::Introspection;
    +
    +Glib::Object::Introspection->setup(basename => "GPlugin", version => "0.0", package=> "GPlugin");
    +
    +sub gplugin_query() {
    + return GPlugin::PluginInfo->new(
    + id => "gplugin/perl-unload-failed",
    + );
    +}
    +
    +sub gplugin_load() {
    + return 0;
    +}
    +
    +sub gplugin_unload() {
    + return 1;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/perl/tests/test-perl-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,30 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <glib.h>
    +#include <gplugin.h>
    +
    +#include <gplugin/gplugin-loader-tests.h>
    +
    +gint
    +main(gint argc, gchar **argv) {
    + g_test_init(&argc, &argv, NULL);
    +
    + gplugin_loader_tests_main(PERL_LOADER_DIR, PERL_PLUGIN_DIR, "perl");
    +
    + return g_test_run();
    +}
    --- a/po/POTFILES Sat Feb 15 21:14:07 2020 -0600
    +++ b/po/POTFILES Tue Feb 25 22:30:11 2020 -0600
    @@ -24,12 +24,11 @@
    perl/gplugin-perl-core.c
    perl/gplugin-perl-loader.c
    perl/gplugin-perl-plugin.c
    -python/gplugin-python-core.c
    -python/gplugin-python-loader.c
    -python/gplugin-python-plugin.c
    -python/gplugin-python-test-pygobject.c
    -python/gplugin-python-utils.c
    -ruby/gplugin-ruby-protect.c
    +python/gplugin-python3-core.c
    +python/gplugin-python3-loader.c
    +python/gplugin-python3-plugin.c
    +python/gplugin-python3-test-pygobject.c
    +python/gplugin-python3-utils.c
    tcc/gplugin-tcc-core.c
    tcc/gplugin-tcc-loader.c
    tcc/gplugin-tcc-plugin.c
    --- a/python/gplugin-python-core.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,77 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <gplugin.h>
    -#include <gplugin-native.h>
    -
    -#include <glib/gi18n-lib.h>
    -
    -#include "gplugin-python-loader.h"
    -#include "gplugin-python-plugin.h"
    -
    -G_MODULE_EXPORT GPluginPluginInfo *
    -gplugin_query(G_GNUC_UNUSED GError **error) {
    - const gchar * const authors[] = {
    - "Gary Kramlich <grim@reaperworld.com>",
    - NULL
    - };
    -
    - /* clang-format off */
    - return gplugin_plugin_info_new(
    - "gplugin/python-loader",
    - GPLUGIN_NATIVE_PLUGIN_ABI_VERSION,
    - "internal", TRUE,
    - "load-on-query", TRUE,
    - "name", "Python Plugin Loader",
    - "version", GPLUGIN_VERSION,
    - "license-id", "LGPL-2.0-or-later",
    - "summary", "A plugin that can load python plugins",
    - "description", "This plugin allows the loading of plugins written in "
    - "the python programming language.",
    - "authors", authors,
    - "website", GPLUGIN_WEBSITE,
    - "category", "loaders",
    - NULL);
    - /* clang-format on */
    -}
    -
    -G_MODULE_EXPORT gboolean
    -gplugin_load(GPluginNativePlugin *plugin,
    - G_GNUC_UNUSED GError **error)
    -{
    - gplugin_python_plugin_register(plugin);
    - gplugin_python_loader_register(plugin);
    -
    - gplugin_manager_register_loader(GPLUGIN_PYTHON_TYPE_LOADER);
    -
    - return TRUE;
    -}
    -
    -G_MODULE_EXPORT gboolean
    -gplugin_unload(G_GNUC_UNUSED GPluginNativePlugin *plugin,
    - GError **error)
    -{
    - g_set_error_literal(
    - error,
    - GPLUGIN_DOMAIN,
    - 0,
    - _("The Python loader can not be unloaded")
    - );
    -
    - return FALSE;
    -}
    -
    --- a/python/gplugin-python-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,377 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <Python.h>
    -
    -#include <stdlib.h>
    -
    -#include "gplugin-python-loader.h"
    -
    -#include "gplugin-python-plugin.h"
    -#include "gplugin-python-utils.h"
    -
    -#include <glib/gi18n.h>
    -
    -#include <pygobject.h>
    -
    -struct _GPluginPythonLoader {
    - GPluginLoader parent;
    -
    - PyThreadState *py_thread_state;
    - guint gc_id;
    -};
    -
    -G_DEFINE_DYNAMIC_TYPE(GPluginPythonLoader, gplugin_python_loader, GPLUGIN_TYPE_LOADER);
    -
    -/******************************************************************************
    - * GPluginLoader Implementation
    - *****************************************************************************/
    -static GSList *
    -gplugin_python_loader_class_supported_extensions(G_GNUC_UNUSED GPluginLoaderClass *klass) {
    - return g_slist_append(NULL, "py");
    -}
    -
    -static GPluginPlugin *
    -gplugin_python_loader_query(GPluginLoader *loader,
    - const gchar *filename,
    - G_GNUC_UNUSED GError **error)
    -{
    - GPluginPlugin *plugin = NULL;
    - GObject *info = NULL;
    - PyObject *pyinfo = NULL, *args = NULL;
    - PyObject *module = NULL, *package_list = NULL, *module_dict = NULL;
    - PyObject *query = NULL, *load = NULL, *unload = NULL;
    - PyGILState_STATE state;
    - gchar *module_name = NULL, *dir_name = NULL;
    -
    - /* lock the gil */
    - state = pyg_gil_state_ensure();
    -
    - /* create package_list as a tuple to handle 'import foo.bar' */
    - package_list = PyTuple_New(0);
    -
    - /* now figure out the module name from the filename */
    - module_name = gplugin_python_filename_to_module(filename);
    -
    - /* grab the dirname since we need it on sys.path to import the module */
    - dir_name = g_path_get_dirname(filename);
    - gplugin_python_add_module_path(dir_name);
    - g_free(dir_name);
    -
    - /* import the module */
    - module = PyImport_ImportModuleEx(module_name, NULL, NULL, package_list);
    - if(PyErr_Occurred()) {
    - g_warning(_("Failed to query %s"), filename);
    - PyErr_Print();
    -
    - /* clean some stuff up */
    - g_free(module_name);
    - Py_DECREF(package_list);
    -
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    -
    - /* clean some stuff up */
    - g_free(module_name);
    - Py_DECREF(package_list);
    -
    - /* at this point we have the module, lets find the query, load, and unload
    - * functions.
    - */
    - module_dict = PyModule_GetDict(module);
    -
    - query = PyDict_GetItemString(module_dict, "gplugin_query");
    - if(query == NULL) {
    - g_warning(_("Failed to find the gplugin_query function in %s"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    - if(!PyCallable_Check(query)) {
    - g_warning(_("Found gplugin_query in %s but it is not a "
    - "function"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    -
    - load = PyDict_GetItemString(module_dict, "gplugin_load");
    - if(load == NULL) {
    - g_warning(_("Failed to find the gplugin_load function in %s"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    - if(!PyCallable_Check(load)) {
    - g_warning(_("Found gplugin_load in %s but it is not a "
    - "function"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    -
    - unload = PyDict_GetItemString(module_dict, "gplugin_unload");
    - if(unload == NULL) {
    - g_warning(_("Failed to find the gplugin_unload function in %s"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    - if(!PyCallable_Check(unload)) {
    - g_warning(_("Found gplugin_unload in %s but it is not a "
    - "function"),
    - filename);
    -
    - Py_DECREF(module);
    - pyg_gil_state_release(state);
    -
    - return NULL;
    - }
    -
    - /* now that we have everything, call the query method and get the plugin's
    - * info.
    - */
    - args = PyTuple_New(0);
    - pyinfo = PyObject_Call(query, args, NULL);
    - Py_DECREF(args);
    -
    - info = pygobject_get(pyinfo);
    -
    - /* now that we have everything, create the plugin */
    - /* clang-format off */
    - plugin = g_object_new(
    - GPLUGIN_PYTHON_TYPE_PLUGIN,
    - "filename", filename,
    - "loader", loader,
    - "module", module,
    - "info", info,
    - "load-func", load,
    - "unload-func", unload,
    - NULL);
    - /* clang-format on */
    -
    - Py_DECREF(pyinfo);
    - Py_DECREF(module);
    -
    - /* unlock the gil */
    - pyg_gil_state_release(state);
    -
    - return plugin;
    -}
    -
    -static gboolean
    -gplugin_python_loader_load(G_GNUC_UNUSED GPluginLoader *loader,
    - GPluginPlugin *plugin,
    - GError **error)
    -{
    - PyObject *load = NULL, *pyplugin = NULL, *result = NULL;
    - gboolean ret = FALSE;
    -
    - g_object_get(G_OBJECT(plugin), "load-func", &load, NULL);
    -
    - pyplugin = pygobject_new(G_OBJECT(plugin));
    -
    - result = PyObject_CallFunctionObjArgs(load, pyplugin, NULL);
    - Py_DECREF(pyplugin);
    -
    - if (PyErr_Occurred()) {
    - Py_XDECREF(result);
    -
    - if (error) {
    - *error = gplugin_python_exception_to_gerror();
    - }
    -
    - return FALSE;
    - }
    -
    - ret = PyObject_IsTrue(result);
    - Py_DECREF(result);
    -
    - if(!ret) {
    - g_set_error_literal(error, GPLUGIN_DOMAIN, 0,
    - _("Failed to load plugin"));
    - }
    -
    - return ret;
    -}
    -
    -static gboolean
    -gplugin_python_loader_unload(G_GNUC_UNUSED GPluginLoader *loader,
    - GPluginPlugin *plugin,
    - GError **error)
    -{
    - PyObject *unload = NULL, *pyplugin = NULL, *result = NULL;
    - gboolean ret = FALSE;
    -
    - g_object_get(G_OBJECT(plugin), "unload-func", &unload, NULL);
    -
    - pyplugin = pygobject_new(G_OBJECT(plugin));
    -
    - result = PyObject_CallFunctionObjArgs(unload, pyplugin, NULL);
    - Py_DECREF(pyplugin);
    -
    - if(PyErr_Occurred()) {
    - PyErr_Print();
    -
    - Py_XDECREF(result);
    -
    - return FALSE;
    - }
    -
    - ret = PyObject_IsTrue(result);
    - Py_DECREF(result);
    -
    - if(!ret) {
    - g_set_error_literal(error, GPLUGIN_DOMAIN, 0,
    - _("Failed to unload plugin"));
    - }
    -
    - return ret;
    -}
    -
    -/******************************************************************************
    - * Python Stuff
    - *****************************************************************************/
    -static gboolean
    -gplugin_python_loader_init_pygobject(void) {
    - pygobject_init(3, 0, 0);
    - if(PyErr_Occurred()) {
    - PyObject *type = NULL, *value = NULL, *tb = NULL, *obj = NULL;
    -
    - PyErr_Fetch(&type, &value, &tb);
    - Py_DECREF(type);
    - Py_XDECREF(tb);
    -
    - obj = PyUnicode_AsUTF8String(value);
    - Py_DECREF(value);
    -
    - g_warning("Failed to initialize PyGObject : %s", PyBytes_AsString(obj));
    - Py_DECREF(obj);
    -
    - return FALSE;
    - }
    -
    - /* enable threads */
    - pyg_enable_threads();
    -
    - /* disable g_log redirections */
    - pyg_disable_warning_redirections();
    -
    - return TRUE;
    -}
    -
    -static gboolean
    -gplugin_python_loader_init_gettext(void) {
    - PyObject *module_dict = NULL, *install = NULL;
    - PyObject *gettext = NULL, *result = NULL;
    -
    - gettext = PyImport_ImportModule("gettext");
    - if(gettext == NULL) {
    - g_warning("Failed to import gettext");
    -
    - return FALSE;
    - }
    -
    - module_dict = PyModule_GetDict(gettext);
    - install = PyDict_GetItemString(module_dict, "install");
    - result = PyObject_CallFunction(install, "ss", GETTEXT_PACKAGE, LOCALEDIR);
    - Py_XDECREF(result);
    - Py_DECREF(gettext);
    -
    - return TRUE;
    -}
    -
    -static gboolean
    -gplugin_python_loader_init_python(void) {
    - wchar_t *argv[] = { NULL, NULL };
    -
    - /* Initializes Python */
    - if(!Py_IsInitialized())
    - Py_InitializeEx(FALSE);
    -
    - argv[0] = Py_DecodeLocale(g_get_prgname(), NULL);
    - if(argv[0] == NULL) {
    - g_warning("Could not convert program name to wchar_t string.");
    - return FALSE;
    - }
    -
    - /* setup sys.path according to
    - * https://docs.python.org/3/c-api/init.html#PySys_SetArgvEx
    - */
    - PySys_SetArgvEx(1, argv, 0);
    - PyMem_RawFree(argv[0]);
    -
    - /* initialize pygobject */
    - if(gplugin_python_loader_init_pygobject()) {
    - if(gplugin_python_loader_init_gettext()) {
    - return TRUE;
    - }
    - }
    -
    - return FALSE;
    -}
    -
    -/******************************************************************************
    - * Object Stuff
    - *****************************************************************************/
    -static void
    -gplugin_python_loader_init(G_GNUC_UNUSED GPluginPythonLoader *loader) {
    -}
    -
    -static void
    -gplugin_python_loader_class_finalize(G_GNUC_UNUSED GPluginPythonLoaderClass *klass)
    -{
    -}
    -
    -static void
    -gplugin_python_loader_class_init(GPluginPythonLoaderClass *klass) {
    - GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    -
    - loader_class->supported_extensions =
    - gplugin_python_loader_class_supported_extensions;
    - loader_class->query = gplugin_python_loader_query;
    - loader_class->load = gplugin_python_loader_load;
    - loader_class->unload = gplugin_python_loader_unload;
    -}
    -
    -/******************************************************************************
    - * API
    - *****************************************************************************/
    -void
    -gplugin_python_loader_register(GPluginNativePlugin *native) {
    - gplugin_python_loader_register_type(G_TYPE_MODULE(native));
    -
    - gplugin_python_loader_init_python();
    -}
    -
    --- a/python/gplugin-python-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,34 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#ifndef GPLUGIN_PYTHON_LOADER_H
    -#define GPLUGIN_PYTHON_LOADER_H
    -
    -#include <gplugin.h>
    -#include <gplugin-native.h>
    -
    -G_BEGIN_DECLS
    -
    -#define GPLUGIN_PYTHON_TYPE_LOADER (gplugin_python_loader_get_type())
    -G_DECLARE_FINAL_TYPE(GPluginPythonLoader, gplugin_python_loader, GPLUGIN_PYTHON, LOADER, GPluginLoader)
    -
    -void gplugin_python_loader_register(GPluginNativePlugin *native);
    -
    -G_END_DECLS
    -
    -#endif /* GPLUGIN_PYTHON_LOADER_H */
    -
    --- a/python/gplugin-python-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,277 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <Python.h>
    -
    -#include "gplugin-python-plugin.h"
    -
    -#include <pygobject.h>
    -
    -/******************************************************************************
    - * Typedefs
    - *****************************************************************************/
    -struct _GPluginPythonPlugin {
    - GObject parent;
    -
    - PyObject *module;
    - PyObject *query;
    - PyObject *load;
    - PyObject *unload;
    -
    - /* overrides */
    - gchar *filename;
    - GPluginLoader *loader;
    - GPluginPluginInfo *info;
    - GPluginPluginState state;
    -};
    -
    -/******************************************************************************
    - * Enums
    - *****************************************************************************/
    -enum {
    - PROP_ZERO,
    - PROP_MODULE,
    - PROP_LOAD_FUNC,
    - PROP_UNLOAD_FUNC,
    - N_PROPERTIES,
    - /* overrides */
    - PROP_FILENAME = N_PROPERTIES,
    - PROP_LOADER,
    - PROP_INFO,
    - PROP_STATE
    -};
    -static GParamSpec *properties[N_PROPERTIES] = {NULL,};
    -
    -/* I hate forward declarations... */
    -static void gplugin_python_plugin_iface_init(GPluginPluginInterface *iface);
    -
    -G_DEFINE_DYNAMIC_TYPE_EXTENDED(
    - GPluginPythonPlugin,
    - gplugin_python_plugin,
    - G_TYPE_OBJECT,
    - 0,
    - G_IMPLEMENT_INTERFACE(GPLUGIN_TYPE_PLUGIN, gplugin_python_plugin_iface_init)
    -);
    -
    -/******************************************************************************
    - * GPluginPlugin Implementation
    - *****************************************************************************/
    -static void
    -gplugin_python_plugin_iface_init(G_GNUC_UNUSED GPluginPluginInterface *iface) {
    -}
    -
    -/******************************************************************************
    - * Private Stuff
    - *****************************************************************************/
    -static void
    -gplugin_python_plugin_set_module(GPluginPythonPlugin *plugin,
    - PyObject *module)
    -{
    - g_return_if_fail(GPLUGIN_IS_PLUGIN(plugin));
    - g_return_if_fail(module);
    -
    - Py_XINCREF(module);
    - Py_CLEAR(plugin->module);
    - plugin->module = module;
    -}
    -
    -static gpointer
    -gplugin_python_plugin_get_load_func(GPluginPythonPlugin *plugin) {
    - g_return_val_if_fail(GPLUGIN_PYTHON_IS_PLUGIN(plugin), NULL);
    -
    - return plugin->load;
    -}
    -
    -static void
    -gplugin_python_plugin_set_load_func(GPluginPythonPlugin *plugin,
    - PyObject *func)
    -{
    - g_return_if_fail(GPLUGIN_PYTHON_IS_PLUGIN(plugin));
    - g_return_if_fail(func != NULL);
    -
    - Py_XINCREF(func);
    - Py_CLEAR(plugin->load);
    - plugin->load = func;
    -}
    -
    -static gpointer
    -gplugin_python_plugin_get_unload_func(GPluginPythonPlugin *plugin) {
    - g_return_val_if_fail(GPLUGIN_PYTHON_IS_PLUGIN(plugin), NULL);
    -
    - return plugin->unload;
    -}
    -
    -static void
    -gplugin_python_plugin_set_unload_func(GPluginPythonPlugin *plugin,
    - PyObject *func)
    -{
    - g_return_if_fail(GPLUGIN_PYTHON_IS_PLUGIN(plugin));
    - g_return_if_fail(func != NULL);
    -
    - Py_XINCREF(func);
    - Py_CLEAR(plugin->unload);
    - plugin->unload = func;
    -}
    -
    -/******************************************************************************
    - * Object Stuff
    - *****************************************************************************/
    -static void
    -gplugin_python_plugin_get_property(GObject *obj, guint param_id, GValue *value,
    - GParamSpec *pspec)
    -{
    - GPluginPythonPlugin *plugin = GPLUGIN_PYTHON_PLUGIN(obj);
    -
    - switch(param_id) {
    - case PROP_MODULE:
    - g_value_set_pointer(value, plugin->module);
    - break;
    - case PROP_LOAD_FUNC:
    - g_value_set_pointer(value,
    - gplugin_python_plugin_get_load_func(plugin));
    - break;
    - case PROP_UNLOAD_FUNC:
    - g_value_set_pointer(value,
    - gplugin_python_plugin_get_unload_func(plugin));
    - break;
    -
    - /* overrides */
    - case PROP_FILENAME:
    - g_value_set_string(value, plugin->filename);
    - break;
    - case PROP_LOADER:
    - g_value_set_object(value, plugin->loader);
    - break;
    - case PROP_INFO:
    - g_value_set_object(value, plugin->info);
    - break;
    - case PROP_STATE:
    - g_value_set_enum(value, plugin->state);
    - break;
    -
    - default:
    - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
    - break;
    - }
    -}
    -
    -static void
    -gplugin_python_plugin_set_property(GObject *obj, guint param_id,
    - const GValue *value, GParamSpec *pspec)
    -{
    - GPluginPythonPlugin *plugin = GPLUGIN_PYTHON_PLUGIN(obj);
    -
    - switch(param_id) {
    - case PROP_MODULE:
    - gplugin_python_plugin_set_module(plugin,
    - g_value_get_pointer(value));
    - break;
    - case PROP_LOAD_FUNC:
    - gplugin_python_plugin_set_load_func(plugin,
    - g_value_get_pointer(value));
    - break;
    - case PROP_UNLOAD_FUNC:
    - gplugin_python_plugin_set_unload_func(plugin,
    - g_value_get_pointer(value));
    - break;
    -
    - /* overrides */
    - case PROP_FILENAME:
    - plugin->filename = g_value_dup_string(value);
    - break;
    - case PROP_LOADER:
    - plugin->loader = g_value_dup_object(value);
    - break;
    - case PROP_INFO:
    - plugin->info = g_value_dup_object(value);
    - break;
    - case PROP_STATE:
    - plugin->state = g_value_get_enum(value);
    - break;
    -
    - default:
    - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
    - break;
    - }
    -}
    -
    -static void
    -gplugin_python_plugin_finalize(GObject *obj) {
    - GPluginPythonPlugin *plugin = GPLUGIN_PYTHON_PLUGIN(obj);
    -
    - Py_CLEAR(plugin->module);
    - Py_CLEAR(plugin->load);
    - Py_CLEAR(plugin->unload);
    -
    - g_clear_pointer(&plugin->filename, g_free);
    - g_clear_object(&plugin->loader);
    - g_clear_object(&plugin->info);
    -
    - G_OBJECT_CLASS(gplugin_python_plugin_parent_class)->finalize(obj);
    -}
    -
    -static void
    -gplugin_python_plugin_init(G_GNUC_UNUSED GPluginPythonPlugin *plugin) {
    -}
    -
    -static void
    -gplugin_python_plugin_class_finalize(G_GNUC_UNUSED GPluginPythonPluginClass *klass)
    -{
    -}
    -
    -static void
    -gplugin_python_plugin_class_init(GPluginPythonPluginClass *klass) {
    - GObjectClass *obj_class = G_OBJECT_CLASS(klass);
    -
    - obj_class->get_property = gplugin_python_plugin_get_property;
    - obj_class->set_property = gplugin_python_plugin_set_property;
    - obj_class->finalize = gplugin_python_plugin_finalize;
    -
    - properties[PROP_MODULE] = g_param_spec_pointer(
    - "module", "module",
    - "The python module object",
    - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    - );
    -
    - properties[PROP_LOAD_FUNC] = g_param_spec_pointer(
    - "load-func", "load-func",
    - "The python load function",
    - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    - );
    -
    - properties[PROP_UNLOAD_FUNC] = g_param_spec_pointer(
    - "unload-func", "unload-func",
    - "The python unload function",
    - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    - );
    -
    - g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
    -
    - /* add our overrides */
    - g_object_class_override_property(obj_class, PROP_FILENAME, "filename");
    - g_object_class_override_property(obj_class, PROP_LOADER, "loader");
    - g_object_class_override_property(obj_class, PROP_INFO, "info");
    - g_object_class_override_property(obj_class, PROP_STATE, "state");
    -}
    -
    -/******************************************************************************
    - * API
    - *****************************************************************************/
    -void
    -gplugin_python_plugin_register(GPluginNativePlugin *native) {
    - gplugin_python_plugin_register_type(G_TYPE_MODULE(native));
    -}
    --- a/python/gplugin-python-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,34 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#ifndef GPLUGIN_PYTHON_PLUGIN_H
    -#define GPLUGIN_PYTHON_PLUGIN_H
    -
    -#include <gplugin.h>
    -#include <gplugin-native.h>
    -
    -G_BEGIN_DECLS
    -
    -#define GPLUGIN_PYTHON_TYPE_PLUGIN (gplugin_python_plugin_get_type())
    -G_DECLARE_FINAL_TYPE(GPluginPythonPlugin, gplugin_python_plugin, GPLUGIN_PYTHON, PLUGIN, GObject)
    -
    -void gplugin_python_plugin_register(GPluginNativePlugin *native);
    -
    -G_END_DECLS
    -
    -#endif /* GPLUGIN_PYTHON_PLUGIN_H */
    -
    --- a/python/gplugin-python-test-pygobject.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,66 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <stdio.h>
    -
    -#include <glib.h>
    -
    -#include <Python.h>
    -
    -#include <pygobject.h>
    -
    -gint
    -main(gint argc, gchar *argv[]) {
    - wchar_t *wargv[] = { NULL, NULL };
    - size_t len;
    -
    - /* initialize python */
    - if(!Py_IsInitialized())
    - Py_InitializeEx(FALSE);
    -
    - /* setup wargv */
    - len = mbstowcs(NULL, argv[0], 0);
    - if(len == (size_t)-1)
    - return -1;
    -
    - wargv[0] = g_new0(wchar_t, len + 1);
    - len = mbstowcs(wargv[0], argv[0], len + 1);
    - if(len == (size_t)-1) {
    - g_free(wargv[0]);
    - return -1;
    - }
    -
    - /* setup sys.path */
    -#if PY_VERSION_HEX < 0x03010300
    - PySys_SetArgv(1, wargv);
    - PyRun_SimpleString("import sys; sys.path.pop(0)\n");
    -#else
    - PySys_SetArgvEx(1, wargv, 0);
    -#endif
    -
    - g_free(wargv[0]);
    -
    - /* initialize pygobject */
    - pygobject_init(3, 0, 0);
    - if(PyErr_Occurred()) {
    - PyErr_Print();
    - return -1;
    - }
    -
    - return 0;
    -}
    -
    --- a/python/gplugin-python-utils.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,112 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <Python.h>
    -
    -#include <gplugin/gplugin.h>
    -
    -#include "gplugin-python-utils.h"
    -
    -#include <string.h>
    -
    -gchar *
    -gplugin_python_filename_to_module(const gchar *filename) {
    - gchar *base = NULL;
    - gchar *e = NULL, *r = NULL;
    -
    - g_return_val_if_fail(filename != NULL, NULL);
    -
    - /* first make sure we just have a filename */
    - base = g_path_get_basename(filename);
    -
    - /* now find the last . for the extension */
    - e = g_utf8_strrchr(base, g_utf8_strlen(base, -1), g_utf8_get_char("."));
    - if(e == NULL) {
    - return base;
    - }
    -
    - /* now copy the module name into r */
    - r = g_malloc(e - base + 1);
    - memcpy(r, base, e - base);
    - r[e - base] = 0;
    -
    - /* free the basename */
    - g_free(base);
    -
    - return r;
    -}
    -
    -gboolean
    -gplugin_python_add_module_path(const gchar *module_path) {
    - PyObject *sys_path = NULL, *path = NULL;
    - gboolean ret = FALSE;
    -
    - sys_path = PySys_GetObject("path");
    -
    - path = PyUnicode_FromString(module_path);
    -
    - if(PySequence_Contains(sys_path, path) == 0) {
    - PyList_Insert(sys_path, 0, path);
    - ret = TRUE;
    - }
    -
    - Py_DECREF(path);
    -
    - return ret;
    -}
    -
    -GError *
    -gplugin_python_exception_to_gerror(void) {
    - GError *error = NULL;
    - PyObject *type = NULL, *value = NULL, *trace = NULL;
    - PyObject *type_name = NULL, *value_str = NULL, *obj = NULL;
    -
    - if(!PyErr_Occurred())
    - return NULL;
    -
    - PyErr_Fetch(&type, &value, &trace);
    - if(type == NULL)
    - return NULL;
    -
    - PyErr_NormalizeException(&type, &value, &trace);
    - Py_XDECREF(trace);
    -
    - type_name = PyObject_GetAttrString(type, "__name__");
    - Py_DECREF(type);
    -
    - value_str = PyObject_Str(value);
    - Py_DECREF(value);
    -
    - /* now decode the utf8 into a string we can use */
    - obj = PyUnicode_AsUTF8String(type_name);
    - Py_DECREF(type_name);
    - type_name = obj;
    -
    - obj = PyUnicode_AsUTF8String(value_str);
    - Py_DECREF(value_str);
    - value_str = obj;
    -
    - error = g_error_new(GPLUGIN_DOMAIN, 0, "%s: %s",
    - PyBytes_AsString(type_name),
    - PyBytes_AsString(value_str));
    -
    - Py_DECREF(type_name);
    - Py_DECREF(value_str);
    -
    - return error;
    -}
    -
    --- a/python/gplugin-python-utils.h Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,36 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#ifndef GPLUGIN_PYTHON_UTILS_H
    -#define GPLUGIN_PYTHON_UTILS_H
    -
    -#include <glib.h>
    -
    -#define GPLUGIN_PYTHON_DOMAIN (g_quark_from_static_string("gplugin-python"))
    -
    -G_BEGIN_DECLS
    -
    -gchar *gplugin_python_filename_to_module(const gchar *filename);
    -
    -gboolean gplugin_python_add_module_path(const gchar *module_path);
    -
    -GError *gplugin_python_exception_to_gerror(void);
    -
    -G_END_DECLS
    -
    -#endif /* GPLUGIN_PYTHON_UTILS_H */
    -
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-core.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,74 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <gplugin.h>
    +#include <gplugin-native.h>
    +
    +#include <glib/gi18n-lib.h>
    +
    +#include "gplugin-python3-loader.h"
    +#include "gplugin-python3-plugin.h"
    +
    +G_MODULE_EXPORT GPluginPluginInfo *
    +gplugin_query(G_GNUC_UNUSED GError **error) {
    + const gchar * const authors[] = {
    + "Gary Kramlich <grim@reaperworld.com>",
    + NULL
    + };
    +
    + /* clang-format off */
    + return gplugin_plugin_info_new(
    + "gplugin/python3-loader",
    + GPLUGIN_NATIVE_PLUGIN_ABI_VERSION,
    + "internal", TRUE,
    + "load-on-query", TRUE,
    + "name", "Python Plugin Loader",
    + "version", GPLUGIN_VERSION,
    + "license-id", "LGPL-2.0-or-later",
    + "summary", "A plugin that can load python plugins",
    + "description", "This plugin allows the loading of plugins written in "
    + "the python programming language.",
    + "authors", authors,
    + "website", GPLUGIN_WEBSITE,
    + "category", "loaders",
    + NULL);
    + /* clang-format on */
    +}
    +
    +G_MODULE_EXPORT gboolean
    +gplugin_load(GPluginNativePlugin *plugin,
    + G_GNUC_UNUSED GError **error)
    +{
    + gplugin_python3_plugin_register(plugin);
    + gplugin_python3_loader_register(plugin);
    +
    + return gplugin_manager_register_loader(GPLUGIN_PYTHON3_TYPE_LOADER, error);
    +}
    +
    +G_MODULE_EXPORT gboolean
    +gplugin_unload(G_GNUC_UNUSED GPluginNativePlugin *plugin,
    + GError **error)
    +{
    + g_set_error_literal(
    + error,
    + GPLUGIN_DOMAIN,
    + 0,
    + _("The Python loader can not be unloaded")
    + );
    +
    + return FALSE;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,376 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <Python.h>
    +
    +#include <stdlib.h>
    +
    +#include "gplugin-python3-loader.h"
    +
    +#include "gplugin-python3-plugin.h"
    +#include "gplugin-python3-utils.h"
    +
    +#include <glib/gi18n-lib.h>
    +
    +#include <pygobject.h>
    +
    +struct _GPluginPython3Loader {
    + GPluginLoader parent;
    +
    + PyThreadState *py_thread_state;
    + guint gc_id;
    +};
    +
    +G_DEFINE_DYNAMIC_TYPE(GPluginPython3Loader, gplugin_python3_loader, GPLUGIN_TYPE_LOADER);
    +
    +/******************************************************************************
    + * GPluginLoader Implementation
    + *****************************************************************************/
    +static GSList *
    +gplugin_python3_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *l) {
    + return g_slist_append(NULL, "py");
    +}
    +
    +static GPluginPlugin *
    +gplugin_python3_loader_query(GPluginLoader *loader,
    + const gchar *filename,
    + G_GNUC_UNUSED GError **error)
    +{
    + GPluginPlugin *plugin = NULL;
    + GObject *info = NULL;
    + PyObject *pyinfo = NULL, *args = NULL;
    + PyObject *module = NULL, *package_list = NULL, *module_dict = NULL;
    + PyObject *query = NULL, *load = NULL, *unload = NULL;
    + PyGILState_STATE state;
    + gchar *module_name = NULL, *dir_name = NULL;
    +
    + /* lock the gil */
    + state = pyg_gil_state_ensure();
    +
    + /* create package_list as a tuple to handle 'import foo.bar' */
    + package_list = PyTuple_New(0);
    +
    + /* now figure out the module name from the filename */
    + module_name = gplugin_python3_filename_to_module(filename);
    +
    + /* grab the dirname since we need it on sys.path to import the module */
    + dir_name = g_path_get_dirname(filename);
    + gplugin_python3_add_module_path(dir_name);
    + g_free(dir_name);
    +
    + /* import the module */
    + module = PyImport_ImportModuleEx(module_name, NULL, NULL, package_list);
    + if(PyErr_Occurred()) {
    + g_warning(_("Failed to query %s"), filename);
    + PyErr_Print();
    +
    + /* clean some stuff up */
    + g_free(module_name);
    + Py_DECREF(package_list);
    +
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    +
    + /* clean some stuff up */
    + g_free(module_name);
    + Py_DECREF(package_list);
    +
    + /* at this point we have the module, lets find the query, load, and unload
    + * functions.
    + */
    + module_dict = PyModule_GetDict(module);
    +
    + query = PyDict_GetItemString(module_dict, "gplugin_query");
    + if(query == NULL) {
    + g_warning(_("Failed to find the gplugin_query function in %s"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    + if(!PyCallable_Check(query)) {
    + g_warning(_("Found gplugin_query in %s but it is not a "
    + "function"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    +
    + load = PyDict_GetItemString(module_dict, "gplugin_load");
    + if(load == NULL) {
    + g_warning(_("Failed to find the gplugin_load function in %s"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    + if(!PyCallable_Check(load)) {
    + g_warning(_("Found gplugin_load in %s but it is not a "
    + "function"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    +
    + unload = PyDict_GetItemString(module_dict, "gplugin_unload");
    + if(unload == NULL) {
    + g_warning(_("Failed to find the gplugin_unload function in %s"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    + if(!PyCallable_Check(unload)) {
    + g_warning(_("Found gplugin_unload in %s but it is not a "
    + "function"),
    + filename);
    +
    + Py_DECREF(module);
    + pyg_gil_state_release(state);
    +
    + return NULL;
    + }
    +
    + /* now that we have everything, call the query method and get the plugin's
    + * info.
    + */
    + args = PyTuple_New(0);
    + pyinfo = PyObject_Call(query, args, NULL);
    + Py_DECREF(args);
    +
    + info = pygobject_get(pyinfo);
    +
    + /* now that we have everything, create the plugin */
    + /* clang-format off */
    + plugin = g_object_new(
    + GPLUGIN_PYTHON3_TYPE_PLUGIN,
    + "filename", filename,
    + "loader", loader,
    + "module", module,
    + "info", info,
    + "load-func", load,
    + "unload-func", unload,
    + NULL);
    + /* clang-format on */
    +
    + Py_DECREF(pyinfo);
    + Py_DECREF(module);
    +
    + /* unlock the gil */
    + pyg_gil_state_release(state);
    +
    + return plugin;
    +}
    +
    +static gboolean
    +gplugin_python3_loader_load(G_GNUC_UNUSED GPluginLoader *loader,
    + GPluginPlugin *plugin,
    + GError **error)
    +{
    + PyObject *load = NULL, *pyplugin = NULL, *result = NULL;
    + gboolean ret = FALSE;
    +
    + g_object_get(G_OBJECT(plugin), "load-func", &load, NULL);
    +
    + pyplugin = pygobject_new(G_OBJECT(plugin));
    +
    + result = PyObject_CallFunctionObjArgs(load, pyplugin, NULL);
    + Py_DECREF(pyplugin);
    +
    + if (PyErr_Occurred()) {
    + Py_XDECREF(result);
    +
    + if (error) {
    + *error = gplugin_python3_exception_to_gerror();
    + }
    +
    + return FALSE;
    + }
    +
    + ret = PyObject_IsTrue(result);
    + Py_DECREF(result);
    +
    + if(!ret) {
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0,
    + _("Failed to load plugin"));
    + }
    +
    + return ret;
    +}
    +
    +static gboolean
    +gplugin_python3_loader_unload(G_GNUC_UNUSED GPluginLoader *loader,
    + GPluginPlugin *plugin,
    + GError **error)
    +{
    + PyObject *unload = NULL, *pyplugin = NULL, *result = NULL;
    + gboolean ret = FALSE;
    +
    + g_object_get(G_OBJECT(plugin), "unload-func", &unload, NULL);
    +
    + pyplugin = pygobject_new(G_OBJECT(plugin));
    +
    + result = PyObject_CallFunctionObjArgs(unload, pyplugin, NULL);
    + Py_DECREF(pyplugin);
    +
    + if(PyErr_Occurred()) {
    + PyErr_Print();
    +
    + Py_XDECREF(result);
    +
    + return FALSE;
    + }
    +
    + ret = PyObject_IsTrue(result);
    + Py_DECREF(result);
    +
    + if(!ret) {
    + g_set_error_literal(error, GPLUGIN_DOMAIN, 0,
    + _("Failed to unload plugin"));
    + }
    +
    + return ret;
    +}
    +
    +/******************************************************************************
    + * Python3 Stuff
    + *****************************************************************************/
    +static gboolean
    +gplugin_python3_loader_init_pygobject(void) {
    + pygobject_init(3, 0, 0);
    + if(PyErr_Occurred()) {
    + PyObject *type = NULL, *value = NULL, *tb = NULL, *obj = NULL;
    +
    + PyErr_Fetch(&type, &value, &tb);
    + Py_DECREF(type);
    + Py_XDECREF(tb);
    +
    + obj = PyUnicode_AsUTF8String(value);
    + Py_DECREF(value);
    +
    + g_warning("Failed to initialize PyGObject : %s", PyBytes_AsString(obj));
    + Py_DECREF(obj);
    +
    + return FALSE;
    + }
    +
    + /* enable threads */
    + pyg_enable_threads();
    +
    + /* disable g_log redirections */
    + pyg_disable_warning_redirections();
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +gplugin_python3_loader_init_gettext(void) {
    + PyObject *module_dict = NULL, *install = NULL;
    + PyObject *gettext = NULL, *result = NULL;
    +
    + gettext = PyImport_ImportModule("gettext");
    + if(gettext == NULL) {
    + g_warning("Failed to import gettext");
    +
    + return FALSE;
    + }
    +
    + module_dict = PyModule_GetDict(gettext);
    + install = PyDict_GetItemString(module_dict, "install");
    + result = PyObject_CallFunction(install, "ss", GETTEXT_PACKAGE, LOCALEDIR);
    + Py_XDECREF(result);
    + Py_DECREF(gettext);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +gplugin_python3_loader_init_python(void) {
    + wchar_t *argv[] = { NULL, NULL };
    +
    + /* Initializes Python */
    + if(!Py_IsInitialized())
    + Py_InitializeEx(FALSE);
    +
    + argv[0] = Py_DecodeLocale(g_get_prgname(), NULL);
    + if(argv[0] == NULL) {
    + g_warning("Could not convert program name to wchar_t string.");
    + return FALSE;
    + }
    +
    + /* setup sys.path according to
    + * https://docs.python.org/3/c-api/init.html#PySys_SetArgvEx
    + */
    + PySys_SetArgvEx(1, argv, 0);
    + PyMem_RawFree(argv[0]);
    +
    + /* initialize pygobject */
    + if(gplugin_python3_loader_init_pygobject()) {
    + if(gplugin_python3_loader_init_gettext()) {
    + return TRUE;
    + }
    + }
    +
    + return FALSE;
    +}
    +
    +/******************************************************************************
    + * GObject Implementation
    + *****************************************************************************/
    +static void
    +gplugin_python3_loader_init(G_GNUC_UNUSED GPluginPython3Loader *loader) {
    +}
    +
    +static void
    +gplugin_python3_loader_class_finalize(G_GNUC_UNUSED GPluginPython3LoaderClass *klass)
    +{
    +}
    +
    +static void
    +gplugin_python3_loader_class_init(GPluginPython3LoaderClass *klass) {
    + GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    +
    + loader_class->supported_extensions =
    + gplugin_python3_loader_supported_extensions;
    + loader_class->query = gplugin_python3_loader_query;
    + loader_class->load = gplugin_python3_loader_load;
    + loader_class->unload = gplugin_python3_loader_unload;
    +}
    +
    +/******************************************************************************
    + * API
    + *****************************************************************************/
    +void
    +gplugin_python3_loader_register(GPluginNativePlugin *native) {
    + gplugin_python3_loader_register_type(G_TYPE_MODULE(native));
    +
    + gplugin_python3_loader_init_python();
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,33 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#ifndef GPLUGIN_PYTHON3_LOADER_H
    +#define GPLUGIN_PYTHON3_LOADER_H
    +
    +#include <gplugin.h>
    +#include <gplugin-native.h>
    +
    +G_BEGIN_DECLS
    +
    +#define GPLUGIN_PYTHON3_TYPE_LOADER (gplugin_python3_loader_get_type())
    +G_DECLARE_FINAL_TYPE(GPluginPython3Loader, gplugin_python3_loader, GPLUGIN_PYTHON3, LOADER, GPluginLoader)
    +
    +void gplugin_python3_loader_register(GPluginNativePlugin *native);
    +
    +G_END_DECLS
    +
    +#endif /* GPLUGIN_PYTHON3_LOADER_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,276 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <Python.h>
    +
    +#include "gplugin-python3-plugin.h"
    +
    +/******************************************************************************
    + * Typedefs
    + *****************************************************************************/
    +struct _GPluginPython3Plugin {
    + GObject parent;
    +
    + PyObject *module;
    + PyObject *query;
    + PyObject *load;
    + PyObject *unload;
    +
    + /* overrides */
    + gchar *filename;
    + GPluginLoader *loader;
    + GPluginPluginInfo *info;
    + GPluginPluginState state;
    +};
    +
    +/******************************************************************************
    + * Enums
    + *****************************************************************************/
    +enum {
    + PROP_ZERO,
    + PROP_MODULE,
    + PROP_LOAD_FUNC,
    + PROP_UNLOAD_FUNC,
    + N_PROPERTIES,
    + /* overrides */
    + PROP_FILENAME = N_PROPERTIES,
    + PROP_LOADER,
    + PROP_INFO,
    + PROP_STATE
    +};
    +static GParamSpec *properties[N_PROPERTIES] = {NULL,};
    +
    +/* I hate forward declarations... */
    +static void gplugin_python3_plugin_iface_init(GPluginPluginInterface *iface);
    +
    +G_DEFINE_DYNAMIC_TYPE_EXTENDED(
    + GPluginPython3Plugin,
    + gplugin_python3_plugin,
    + G_TYPE_OBJECT,
    + 0,
    + G_IMPLEMENT_INTERFACE(GPLUGIN_TYPE_PLUGIN, gplugin_python3_plugin_iface_init)
    +);
    +
    +/******************************************************************************
    + * GPluginPlugin Implementation
    + *****************************************************************************/
    +static void
    +gplugin_python3_plugin_iface_init(G_GNUC_UNUSED GPluginPluginInterface *iface) {
    +}
    +
    +/******************************************************************************
    + * Private Stuff
    + *****************************************************************************/
    +static void
    +gplugin_python3_plugin_set_module(GPluginPython3Plugin *plugin,
    + PyObject *module)
    +{
    + g_return_if_fail(GPLUGIN_IS_PLUGIN(plugin));
    + g_return_if_fail(module);
    +
    + Py_XINCREF(module);
    + Py_CLEAR(plugin->module);
    + plugin->module = module;
    +}
    +
    +static gpointer
    +gplugin_python3_plugin_get_load_func(GPluginPython3Plugin *plugin) {
    + g_return_val_if_fail(GPLUGIN_PYTHON3_IS_PLUGIN(plugin), NULL);
    +
    + return plugin->load;
    +}
    +
    +static void
    +gplugin_python3_plugin_set_load_func(GPluginPython3Plugin *plugin,
    + PyObject *func)
    +{
    + g_return_if_fail(GPLUGIN_PYTHON3_IS_PLUGIN(plugin));
    + g_return_if_fail(func != NULL);
    +
    + Py_XINCREF(func);
    + Py_CLEAR(plugin->load);
    + plugin->load = func;
    +}
    +
    +static gpointer
    +gplugin_python3_plugin_get_unload_func(GPluginPython3Plugin *plugin) {
    + g_return_val_if_fail(GPLUGIN_PYTHON3_IS_PLUGIN(plugin), NULL);
    +
    + return plugin->unload;
    +}
    +
    +static void
    +gplugin_python3_plugin_set_unload_func(GPluginPython3Plugin *plugin,
    + PyObject *func)
    +{
    + g_return_if_fail(GPLUGIN_PYTHON3_IS_PLUGIN(plugin));
    + g_return_if_fail(func != NULL);
    +
    + Py_XINCREF(func);
    + Py_CLEAR(plugin->unload);
    + plugin->unload = func;
    +}
    +
    +/******************************************************************************
    + * GObject Implementation
    + *****************************************************************************/
    +static void
    +gplugin_python3_plugin_get_property(GObject *obj, guint param_id, GValue *value,
    + GParamSpec *pspec)
    +{
    + GPluginPython3Plugin *plugin = GPLUGIN_PYTHON3_PLUGIN(obj);
    +
    + switch(param_id) {
    + case PROP_MODULE:
    + g_value_set_pointer(value, plugin->module);
    + break;
    + case PROP_LOAD_FUNC:
    + g_value_set_pointer(value,
    + gplugin_python3_plugin_get_load_func(plugin));
    + break;
    + case PROP_UNLOAD_FUNC:
    + g_value_set_pointer(value,
    + gplugin_python3_plugin_get_unload_func(plugin));
    + break;
    +
    + /* overrides */
    + case PROP_FILENAME:
    + g_value_set_string(value, plugin->filename);
    + break;
    + case PROP_LOADER:
    + g_value_set_object(value, plugin->loader);
    + break;
    + case PROP_INFO:
    + g_value_set_object(value, plugin->info);
    + break;
    + case PROP_STATE:
    + g_value_set_enum(value, plugin->state);
    + break;
    +
    + default:
    + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
    + break;
    + }
    +}
    +
    +static void
    +gplugin_python3_plugin_set_property(GObject *obj, guint param_id,
    + const GValue *value, GParamSpec *pspec)
    +{
    + GPluginPython3Plugin *plugin = GPLUGIN_PYTHON3_PLUGIN(obj);
    +
    + switch(param_id) {
    + case PROP_MODULE:
    + gplugin_python3_plugin_set_module(plugin,
    + g_value_get_pointer(value));
    + break;
    + case PROP_LOAD_FUNC:
    + gplugin_python3_plugin_set_load_func(plugin,
    + g_value_get_pointer(value));
    + break;
    + case PROP_UNLOAD_FUNC:
    + gplugin_python3_plugin_set_unload_func(plugin,
    + g_value_get_pointer(value));
    + break;
    +
    + /* overrides */
    + case PROP_FILENAME:
    + plugin->filename = g_value_dup_string(value);
    + break;
    + case PROP_LOADER:
    + plugin->loader = g_value_dup_object(value);
    + break;
    + case PROP_INFO:
    + plugin->info = g_value_dup_object(value);
    + break;
    + case PROP_STATE:
    + plugin->state = g_value_get_enum(value);
    + break;
    +
    + default:
    + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
    + break;
    + }
    +}
    +
    +static void
    +gplugin_python3_plugin_finalize(GObject *obj) {
    + GPluginPython3Plugin *plugin = GPLUGIN_PYTHON3_PLUGIN(obj);
    +
    + Py_CLEAR(plugin->module);
    + Py_CLEAR(plugin->load);
    + Py_CLEAR(plugin->unload);
    +
    + g_clear_pointer(&plugin->filename, g_free);
    + g_clear_object(&plugin->loader);
    + g_clear_object(&plugin->info);
    +
    + G_OBJECT_CLASS(gplugin_python3_plugin_parent_class)->finalize(obj);
    +}
    +
    +static void
    +gplugin_python3_plugin_init(G_GNUC_UNUSED GPluginPython3Plugin *plugin) {
    +}
    +
    +static void
    +gplugin_python3_plugin_class_finalize(G_GNUC_UNUSED GPluginPython3PluginClass *klass)
    +{
    +}
    +
    +static void
    +gplugin_python3_plugin_class_init(GPluginPython3PluginClass *klass) {
    + GObjectClass *obj_class = G_OBJECT_CLASS(klass);
    +
    + obj_class->get_property = gplugin_python3_plugin_get_property;
    + obj_class->set_property = gplugin_python3_plugin_set_property;
    + obj_class->finalize = gplugin_python3_plugin_finalize;
    +
    + properties[PROP_MODULE] = g_param_spec_pointer(
    + "module", "module",
    + "The python module object",
    + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    + );
    +
    + properties[PROP_LOAD_FUNC] = g_param_spec_pointer(
    + "load-func", "load-func",
    + "The python load function",
    + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    + );
    +
    + properties[PROP_UNLOAD_FUNC] = g_param_spec_pointer(
    + "unload-func", "unload-func",
    + "The python unload function",
    + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
    + );
    +
    + g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
    +
    + /* add our overrides */
    + g_object_class_override_property(obj_class, PROP_FILENAME, "filename");
    + g_object_class_override_property(obj_class, PROP_LOADER, "loader");
    + g_object_class_override_property(obj_class, PROP_INFO, "info");
    + g_object_class_override_property(obj_class, PROP_STATE, "state");
    +}
    +
    +/******************************************************************************
    + * API
    + *****************************************************************************/
    +void
    +gplugin_python3_plugin_register(GPluginNativePlugin *native) {
    + gplugin_python3_plugin_register_type(G_TYPE_MODULE(native));
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,33 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#ifndef GPLUGIN_PYTHON3_PLUGIN_H
    +#define GPLUGIN_PYTHON3_PLUGIN_H
    +
    +#include <gplugin.h>
    +#include <gplugin-native.h>
    +
    +G_BEGIN_DECLS
    +
    +#define GPLUGIN_PYTHON3_TYPE_PLUGIN (gplugin_python3_plugin_get_type())
    +G_DECLARE_FINAL_TYPE(GPluginPython3Plugin, gplugin_python3_plugin, GPLUGIN_PYTHON3, PLUGIN, GObject)
    +
    +void gplugin_python3_plugin_register(GPluginNativePlugin *native);
    +
    +G_END_DECLS
    +
    +#endif /* GPLUGIN_PYTHON3_PLUGIN_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-test-pygobject.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,65 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <stdio.h>
    +
    +#include <glib.h>
    +
    +#include <Python.h>
    +
    +#include <pygobject.h>
    +
    +gint
    +main(gint argc, gchar *argv[]) {
    + wchar_t *wargv[] = { NULL, NULL };
    + size_t len;
    +
    + /* initialize python */
    + if(!Py_IsInitialized())
    + Py_InitializeEx(FALSE);
    +
    + /* setup wargv */
    + len = mbstowcs(NULL, argv[0], 0);
    + if(len == (size_t)-1)
    + return -1;
    +
    + wargv[0] = g_new0(wchar_t, len + 1);
    + len = mbstowcs(wargv[0], argv[0], len + 1);
    + if(len == (size_t)-1) {
    + g_free(wargv[0]);
    + return -1;
    + }
    +
    + /* setup sys.path */
    +#if PY_VERSION_HEX < 0x03010300
    + PySys_SetArgv(1, wargv);
    + PyRun_SimpleString("import sys; sys.path.pop(0)\n");
    +#else
    + PySys_SetArgvEx(1, wargv, 0);
    +#endif
    +
    + g_free(wargv[0]);
    +
    + /* initialize pygobject */
    + pygobject_init(3, 0, 0);
    + if(PyErr_Occurred()) {
    + PyErr_Print();
    + return -1;
    + }
    +
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-utils.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,111 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <Python.h>
    +
    +#include <gplugin/gplugin.h>
    +
    +#include "gplugin-python3-utils.h"
    +
    +#include <string.h>
    +
    +gchar *
    +gplugin_python3_filename_to_module(const gchar *filename) {
    + gchar *base = NULL;
    + gchar *e = NULL, *r = NULL;
    +
    + g_return_val_if_fail(filename != NULL, NULL);
    +
    + /* first make sure we just have a filename */
    + base = g_path_get_basename(filename);
    +
    + /* now find the last . for the extension */
    + e = g_utf8_strrchr(base, g_utf8_strlen(base, -1), g_utf8_get_char("."));
    + if(e == NULL) {
    + return base;
    + }
    +
    + /* now copy the module name into r */
    + r = g_malloc(e - base + 1);
    + memcpy(r, base, e - base);
    + r[e - base] = 0;
    +
    + /* free the basename */
    + g_free(base);
    +
    + return r;
    +}
    +
    +gboolean
    +gplugin_python3_add_module_path(const gchar *module_path) {
    + PyObject *sys_path = NULL, *path = NULL;
    + gboolean ret = FALSE;
    +
    + sys_path = PySys_GetObject("path");
    +
    + path = PyUnicode_FromString(module_path);
    +
    + if(PySequence_Contains(sys_path, path) == 0) {
    + PyList_Insert(sys_path, 0, path);
    + ret = TRUE;
    + }
    +
    + Py_DECREF(path);
    +
    + return ret;
    +}
    +
    +GError *
    +gplugin_python3_exception_to_gerror(void) {
    + GError *error = NULL;
    + PyObject *type = NULL, *value = NULL, *trace = NULL;
    + PyObject *type_name = NULL, *value_str = NULL, *obj = NULL;
    +
    + if(!PyErr_Occurred())
    + return NULL;
    +
    + PyErr_Fetch(&type, &value, &trace);
    + if(type == NULL)
    + return NULL;
    +
    + PyErr_NormalizeException(&type, &value, &trace);
    + Py_XDECREF(trace);
    +
    + type_name = PyObject_GetAttrString(type, "__name__");
    + Py_DECREF(type);
    +
    + value_str = PyObject_Str(value);
    + Py_DECREF(value);
    +
    + /* now decode the utf8 into a string we can use */
    + obj = PyUnicode_AsUTF8String(type_name);
    + Py_DECREF(type_name);
    + type_name = obj;
    +
    + obj = PyUnicode_AsUTF8String(value_str);
    + Py_DECREF(value_str);
    + value_str = obj;
    +
    + error = g_error_new(GPLUGIN_DOMAIN, 0, "%s: %s",
    + PyBytes_AsString(type_name),
    + PyBytes_AsString(value_str));
    +
    + Py_DECREF(type_name);
    + Py_DECREF(value_str);
    +
    + return error;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/gplugin-python3-utils.h Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,33 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#ifndef GPLUGIN_PYTHON3_UTILS_H
    +#define GPLUGIN_PYTHON3_UTILS_H
    +
    +#include <glib.h>
    +
    +G_BEGIN_DECLS
    +
    +gchar *gplugin_python3_filename_to_module(const gchar *filename);
    +
    +gboolean gplugin_python3_add_module_path(const gchar *module_path);
    +
    +GError *gplugin_python3_exception_to_gerror(void);
    +
    +G_END_DECLS
    +
    +#endif /* GPLUGIN_PYTHON3_UTILS_H */
    --- a/python/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -1,19 +1,19 @@
    -if get_option('python')
    +if get_option('python3')
    if not get_option('gobject-introspection')
    error('Python plugin requires GObject Introspection.')
    endif
    - GPLUGIN_PYTHON_SOURCES = [
    - 'gplugin-python-core.c',
    - 'gplugin-python-loader.c',
    - 'gplugin-python-plugin.c',
    - 'gplugin-python-utils.c',
    + GPLUGIN_PYTHON3_SOURCES = [
    + 'gplugin-python3-core.c',
    + 'gplugin-python3-loader.c',
    + 'gplugin-python3-plugin.c',
    + 'gplugin-python3-utils.c',
    ]
    - GPLUGIN_PYTHON_HEADERS = [
    - 'gplugin-python-loader.h',
    - 'gplugin-python-plugin.h',
    - 'gplugin-python-utils.h',
    + GPLUGIN_PYTHON3_HEADERS = [
    + 'gplugin-python3-loader.h',
    + 'gplugin-python3-plugin.h',
    + 'gplugin-python3-utils.h',
    ]
    PYTHON3 = dependency('python3-embed', version: '>=3.5.0', required: false)
    @@ -24,37 +24,37 @@
    PYGOBJECT = dependency('pygobject-3.0', version: '>=3.0.0')
    # Compile and run our python-gi test program
    - python_gi_test = compiler.run(files('gplugin-python-test-pygobject.c'),
    + python3_gi_test = compiler.run(files('gplugin-python3-test-pygobject.c'),
    dependencies : [GLIB, PYTHON3, PYGOBJECT],
    - name : 'Python GI')
    - if not python_gi_test.compiled() or python_gi_test.returncode() != 0
    - error('pygobject does not work with python3')
    + name : 'Python3 GI')
    + if not python3_gi_test.compiled() or python3_gi_test.returncode() != 0
    + error('pygobject does not work with Python3')
    endif
    # Now add our libraries
    - gplugin_python_inc = include_directories('.')
    - gplugin_python = shared_library('gplugin-python3',
    - GPLUGIN_PYTHON_SOURCES,
    - GPLUGIN_PYTHON_HEADERS,
    + gplugin_python3_inc = include_directories('.')
    + gplugin_python3 = shared_library('gplugin-python3',
    + GPLUGIN_PYTHON3_SOURCES,
    + GPLUGIN_PYTHON3_HEADERS,
    name_prefix : '',
    dependencies : [PYTHON3, PYGOBJECT, gplugin_dep],
    install : true,
    install_dir : join_paths(get_option('libdir'), 'gplugin')
    )
    - gplugin_python_dep = declare_dependency(
    - include_directories : gplugin_python_inc,
    - link_with : gplugin_python,
    + gplugin_python3_dep = declare_dependency(
    + include_directories : gplugin_python3_inc,
    + link_with : gplugin_python3,
    )
    - gplugin_python_static = static_library('gplugin-python-static',
    - GPLUGIN_PYTHON_SOURCES,
    - GPLUGIN_PYTHON_HEADERS,
    + gplugin_python3_static = static_library('gplugin-python3-static',
    + GPLUGIN_PYTHON3_SOURCES,
    + GPLUGIN_PYTHON3_HEADERS,
    dependencies : [PYTHON3, PYGOBJECT, gplugin_dep],
    )
    - gplugin_python_static_dep = declare_dependency(
    - include_directories : gplugin_python_inc,
    - link_with : gplugin_python_static,
    + gplugin_python3_static_dep = declare_dependency(
    + include_directories : gplugin_python3_inc,
    + link_with : gplugin_python3_static,
    )
    -endif # python
    +endif # python3
    subdir('tests')
    --- a/python/tests/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -1,26 +1,22 @@
    -if get_option('python')
    +if get_option('python3')
    -e = executable('test-python-loader', 'test-python-loader.c',
    +e = executable('test-python3-loader', 'test-python3-loader.c',
    include_directories : include_directories('.'),
    c_args : [
    - '-DPYTHON_LOADER_DIR="@0@"'.format(join_paths(meson.current_build_dir(), '..')),
    - '-DPYTHON_PLUGIN_DIR="@0@/plugins"'.format(meson.current_source_dir()),
    + '-DPYTHON3_LOADER_DIR="@0@"'.format(join_paths(meson.current_build_dir(), '..')),
    + '-DPYTHON3_PLUGIN_DIR="@0@/plugins"'.format(meson.current_source_dir()),
    ],
    link_with : gplugin_loader_tests,
    dependencies : [GLIB, GOBJECT, PYTHON3, PYGOBJECT, gplugin_dep])
    -test('Python loader', e)
    +test('Python3 loader', e)
    # we can't see the symbols in gplugin-python externally, so use the static
    # version for testing
    -e = executable('test-python-utils', 'test-python-utils.c',
    +e = executable('test-python3-utils', 'test-python3-utils.c',
    include_directories : include_directories('.'),
    - c_args : [
    - '-DPYTHON_LOADER_DIR="@0@"'.format(join_paths(meson.current_build_dir(), '..')),
    - '-DPYTHON_PLUGIN_DIR="@0@/plugins"'.format(meson.current_source_dir()),
    - ],
    link_with : [gplugin_loader_tests],
    dependencies : [GLIB, GOBJECT, PYTHON3, PYGOBJECT,
    - gplugin_python_static_dep])
    -test('Python utils', e)
    + gplugin_python3_static_dep])
    +test('Python3 utils', e)
    -endif # python
    +endif # python3
    --- a/python/tests/plugins/basic.py Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/plugins/basic.py Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    # vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -16,9 +16,10 @@
    from gi.repository import GPlugin
    +
    def gplugin_query():
    return GPlugin.PluginInfo(
    - id='gplugin/python-basic-plugin',
    + id='gplugin/python3-basic-plugin',
    abi_version=0x01020304,
    name='basic plugin',
    authors=['author1'],
    @@ -30,10 +31,10 @@
    description='description',
    )
    +
    def gplugin_load(plugin):
    return True
    def gplugin_unload(plugin):
    return True
    -
    --- a/python/tests/plugins/dependent.py Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/plugins/dependent.py Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    # vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -16,16 +16,17 @@
    from gi.repository import GPlugin
    +
    def gplugin_query():
    return GPlugin.PluginInfo(
    - id='gplugin/python-dependent-plugin',
    + id='gplugin/python3-dependent-plugin',
    dependencies=['dependency1', 'dependency2'],
    )
    +
    def gplugin_load(plugin):
    return False
    def gplugin_unload(plugin):
    return False
    -
    --- a/python/tests/plugins/load-exception.py Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/plugins/load-exception.py Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    # vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -16,9 +16,10 @@
    from gi.repository import GPlugin
    +
    def gplugin_query():
    return GPlugin.PluginInfo(
    - id="gplugin/python-load-exception",
    + id="gplugin/python3-load-exception",
    )
    @@ -28,5 +29,3 @@
    def gplugin_unload(plugin):
    return True
    -
    -
    --- a/python/tests/plugins/load-failed.py Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/plugins/load-failed.py Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    # vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -16,9 +16,10 @@
    from gi.repository import GPlugin
    +
    def gplugin_query():
    return GPlugin.PluginInfo(
    - id="gplugin/python-load-failed",
    + id="gplugin/python3-load-failed",
    )
    @@ -28,5 +29,3 @@
    def gplugin_unload(plugin):
    return True
    -
    -
    --- a/python/tests/plugins/unload-failed.py Sat Feb 15 21:14:07 2020 -0600
    +++ b/python/tests/plugins/unload-failed.py Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    # vi:et:ts=4 sw=4 sts=4
    -# Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    #
    # This library is free software; you can redistribute it and/or
    # modify it under the terms of the GNU Lesser General Public
    @@ -16,9 +16,10 @@
    from gi.repository import GPlugin
    +
    def gplugin_query():
    return GPlugin.PluginInfo(
    - id="gplugin/python-unload-failed",
    + id="gplugin/python3-unload-failed",
    )
    @@ -28,5 +29,3 @@
    def gplugin_unload(plugin):
    return False
    -
    -
    --- a/python/tests/test-python-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,34 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <glib.h>
    -#include <gplugin.h>
    -
    -#include <gplugin/gplugin-loader-tests.h>
    -
    -/******************************************************************************
    - * Main
    - *****************************************************************************/
    -gint
    -main(gint argc, gchar **argv) {
    - g_test_init(&argc, &argv, NULL);
    -
    - gplugin_loader_tests_main(PYTHON_LOADER_DIR, PYTHON_PLUGIN_DIR, "python");
    -
    - return g_test_run();
    -}
    -
    --- a/python/tests/test-python-utils.c Sat Feb 15 21:14:07 2020 -0600
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,76 +0,0 @@
    -/*
    - * Copyright (C) 2011-2014 Gary Kramlich <grim@reaperworld.com>
    - *
    - * This library is free software; you can redistribute it and/or
    - * modify it under the terms of the GNU Lesser General Public
    - * License as published by the Free Software Foundation; either
    - * version 2 of the License, or (at your option) any later version.
    - *
    - * This library is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    - * Lesser General Public License for more details.
    - *
    - * You should have received a copy of the GNU Lesser General Public
    - * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    - */
    -
    -#include <Python.h>
    -
    -#include "gplugin-python-utils.h"
    -
    -#include <glib.h>
    -
    -/******************************************************************************
    - * filename to module tests
    - *****************************************************************************/
    -static void
    -test_filename_to_module_NULL_subprocess(void) {
    - gplugin_python_filename_to_module(NULL);
    -}
    -
    -static void
    -test_filename_to_module_NULL(void) {
    - g_test_trap_subprocess("/loaders/python/utils/filename_to_module/NULL/subprocess", 0, 0);
    -
    - g_test_trap_assert_failed();
    -}
    -
    -static void
    -test_filename_to_module_empty(void) {
    - gchar *module = gplugin_python_filename_to_module("");
    -
    - g_assert_cmpstr(module, ==, "");
    -
    - g_free(module);
    -}
    -
    -static void
    -test_filename_to_module_no_extension(void) {
    - gchar *module = gplugin_python_filename_to_module("foo");
    -
    - g_assert_cmpstr(module, ==, "foo");
    -
    - g_free(module);
    -}
    -
    -/******************************************************************************
    - * Main
    - *****************************************************************************/
    -gint
    -main(gint argc, gchar **argv) {
    - g_test_init(&argc, &argv, NULL);
    -
    - /* tests */
    - g_test_add_func("/loaders/python/utils/filename_to_module/NULL",
    - test_filename_to_module_NULL);
    - g_test_add_func("/loaders/python/utils/filename_to_module/NULL/subprocess",
    - test_filename_to_module_NULL_subprocess);
    - g_test_add_func("/loaders/python/utils/filename_to_module/empty",
    - test_filename_to_module_empty);
    - g_test_add_func("/loaders/python/utils/filename_to_module/no-extension",
    - test_filename_to_module_no_extension);
    -
    - return g_test_run();
    -}
    -
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/tests/test-python3-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,33 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <glib.h>
    +#include <gplugin.h>
    +
    +#include <gplugin/gplugin-loader-tests.h>
    +
    +/******************************************************************************
    + * Main
    + *****************************************************************************/
    +gint
    +main(gint argc, gchar **argv) {
    + g_test_init(&argc, &argv, NULL);
    +
    + gplugin_loader_tests_main(PYTHON3_LOADER_DIR, PYTHON3_PLUGIN_DIR, "python3");
    +
    + return g_test_run();
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/python/tests/test-python3-utils.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,75 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <Python.h>
    +
    +#include "gplugin-python3-utils.h"
    +
    +#include <glib.h>
    +
    +/******************************************************************************
    + * filename to module tests
    + *****************************************************************************/
    +static void
    +test_filename_to_module_NULL_subprocess(void) {
    + gplugin_python3_filename_to_module(NULL);
    +}
    +
    +static void
    +test_filename_to_module_NULL(void) {
    + g_test_trap_subprocess("/loaders/python/utils/filename_to_module/NULL/subprocess", 0, 0);
    +
    + g_test_trap_assert_failed();
    +}
    +
    +static void
    +test_filename_to_module_empty(void) {
    + gchar *module = gplugin_python3_filename_to_module("");
    +
    + g_assert_cmpstr(module, ==, "");
    +
    + g_free(module);
    +}
    +
    +static void
    +test_filename_to_module_no_extension(void) {
    + gchar *module = gplugin_python3_filename_to_module("foo");
    +
    + g_assert_cmpstr(module, ==, "foo");
    +
    + g_free(module);
    +}
    +
    +/******************************************************************************
    + * Main
    + *****************************************************************************/
    +gint
    +main(gint argc, gchar **argv) {
    + g_test_init(&argc, &argv, NULL);
    +
    + /* tests */
    + g_test_add_func("/loaders/python/utils/filename_to_module/NULL",
    + test_filename_to_module_NULL);
    + g_test_add_func("/loaders/python/utils/filename_to_module/NULL/subprocess",
    + test_filename_to_module_NULL_subprocess);
    + g_test_add_func("/loaders/python/utils/filename_to_module/empty",
    + test_filename_to_module_empty);
    + g_test_add_func("/loaders/python/utils/filename_to_module/no-extension",
    + test_filename_to_module_no_extension);
    +
    + return g_test_run();
    +}
    --- a/tcc/gplugin-tcc-core.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/gplugin-tcc-core.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -51,15 +51,11 @@
    }
    G_MODULE_EXPORT gboolean
    -gplugin_load(GPluginNativePlugin *plugin,
    - G_GNUC_UNUSED GError **error)
    -{
    +gplugin_load(GPluginNativePlugin *plugin, GError **error) {
    gplugin_tcc_loader_register(plugin);
    gplugin_tcc_plugin_register(plugin);
    - gplugin_manager_register_loader(GPLUGIN_TCC_TYPE_LOADER);
    -
    - return TRUE;
    + return gplugin_manager_register_loader(GPLUGIN_TCC_TYPE_LOADER, error);
    }
    G_MODULE_EXPORT gboolean
    @@ -75,4 +71,3 @@
    return FALSE;
    }
    -
    --- a/tcc/gplugin-tcc-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/gplugin-tcc-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -19,7 +19,7 @@
    #include "gplugin-tcc-plugin.h"
    -#include <glib/gi18n.h>
    +#include <glib/gi18n-lib.h>
    #include <libtcc.h>
    @@ -33,12 +33,8 @@
    * GPluginLoaderInterface API
    *****************************************************************************/
    static GSList *
    -gplugin_tcc_loader_class_supported_extensions(G_GNUC_UNUSED const GPluginLoaderClass *klass) {
    - GSList *exts = NULL;
    -
    - exts = g_slist_append(exts, "c");
    -
    - return exts;
    +gplugin_tcc_loader_supported_extensions(G_GNUC_UNUSED GPluginLoader *l) {
    + return g_list_append(NULL, "c");
    }
    static GPluginPlugin *
    @@ -164,7 +160,7 @@
    GPluginLoaderClass *loader_class = GPLUGIN_LOADER_CLASS(klass);
    loader_class->supported_extensions =
    - gplugin_tcc_loader_class_supported_extensions;
    + gplugin_tcc_loader_supported_extensions;
    loader_class->query = gplugin_tcc_loader_query;
    loader_class->load = gplugin_tcc_loader_load;
    loader_class->unload = gplugin_tcc_loader_unload;
    --- a/tcc/gplugin-tcc-loader.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/gplugin-tcc-loader.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -30,4 +30,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_TCC_LOADER_H */
    -
    --- a/tcc/gplugin-tcc-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/gplugin-tcc-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -204,4 +204,3 @@
    return plugin->s;
    }
    -
    --- a/tcc/gplugin-tcc-plugin.h Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/gplugin-tcc-plugin.h Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -38,4 +38,3 @@
    G_END_DECLS
    #endif /* GPLUGIN_TCC_PLUGIN_H */
    -
    --- a/tcc/tests/plugins/basic-plugin.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/plugins/basic-plugin.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -53,4 +53,3 @@
    {
    return TRUE;
    }
    -
    --- a/tcc/tests/plugins/dependent.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/plugins/dependent.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -47,4 +47,3 @@
    {
    return TRUE;
    }
    -
    --- a/tcc/tests/plugins/load-exception.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/plugins/load-exception.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -39,4 +39,3 @@
    {
    return TRUE;
    }
    -
    --- a/tcc/tests/plugins/load-failed.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/plugins/load-failed.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -39,4 +39,3 @@
    {
    return TRUE;
    }
    -
    --- a/tcc/tests/plugins/unload-failed.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/plugins/unload-failed.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -39,4 +39,3 @@
    return FALSE;
    }
    -
    --- a/tcc/tests/test-tcc-loader.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/tcc/tests/test-tcc-loader.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2013 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -28,4 +28,3 @@
    return g_test_run();
    }
    -
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/basic.gs Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,49 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +uses GPlugin
    +
    +class BasicPluginInfo : GPlugin.PluginInfo
    + construct()
    + authors : array of string = {"author1"}
    +
    + Object(
    + id: "gplugin/genie-basic-plugin",
    + abi_version: 0x01020304,
    + name: "basic plugin",
    + authors: authors,
    + category: "test",
    + version: "version",
    + license_id: "license",
    + summary: "summary",
    + website: "website",
    + description: "description"
    + )
    +
    +def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new BasicPluginInfo()
    +
    +def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    +
    +def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/dependent.gs Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,41 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +uses GPlugin;
    +
    +class DependentPluginInfo : GPlugin.PluginInfo
    + construct()
    + dependencies : array of string = {"dependency1", "dependency2"}
    +
    + Object(
    + id: "gplugin/genie-dependent-plugin",
    + dependencies: dependencies
    + )
    +
    +def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new DependentPluginInfo()
    +
    +def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    +
    +def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return false
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/load-exception.gs Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,38 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +uses GPlugin
    +
    +class LoadExceptionPluginInfo : GPlugin.PluginInfo
    + construct ()
    + Object(
    + id: "gplugin/genie-load-exception"
    + )
    +
    +def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new LoadExceptionPluginInfo()
    +
    +def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = new Error(Quark.from_string("gplugin"), 0, "explode")
    +
    + return false
    +
    +def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/load-failed.gs Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,38 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +uses GPlugin
    +
    +class LoadFailedPluginInfo : GPlugin.PluginInfo
    + construct ()
    + Object(
    + id: "gplugin/genie-load-failed"
    + )
    +
    +def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new LoadFailedPluginInfo()
    +
    +def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return false
    +
    +def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,17 @@
    +if get_option('vapi')
    + shared_library('genie-basic-plugin', 'basic.gs',
    + name_prefix : '',
    + dependencies : [gplugin_dep, gplugin_vapi])
    + shared_library('genie-dependent-plugin', 'dependent.gs',
    + name_prefix : '',
    + dependencies : [gplugin_dep, gplugin_vapi])
    + shared_library('load-exception-plugin', 'load-exception.gs',
    + name_prefix : '',
    + dependencies : [gplugin_dep, gplugin_vapi])
    + shared_library('load-failed-plugin', 'load-failed.gs',
    + name_prefix : '',
    + dependencies : [gplugin_dep, gplugin_vapi])
    + shared_library('unload-failed-plugin', 'unload-failed.gs',
    + name_prefix : '',
    + dependencies : [gplugin_dep, gplugin_vapi])
    +endif # vapi
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/genie-plugins/unload-failed.gs Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,38 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +uses GPlugin
    +
    +class UnloadFailedPluginInfo : GPlugin.PluginInfo
    + construct()
    + Object(
    + id: "gplugin/genie-unload-failed"
    + )
    +
    +def gplugin_query(out error : Error) : GPlugin.PluginInfo
    + error = null
    +
    + return new UnloadFailedPluginInfo()
    +
    +def gplugin_load(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return true
    +
    +def gplugin_unload(plugin : GPlugin.Plugin, out error : Error) : bool
    + error = null
    +
    + return false
    --- a/vala/tests/meson.build Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/meson.build Tue Feb 25 22:30:11 2020 -0600
    @@ -9,6 +9,16 @@
    dependencies : [GLIB, GOBJECT, gplugin_dep])
    test('Vala loading', e)
    +e = executable('test-genie-loading', 'test-genie-loading.c',
    + include_directories : include_directories('.'),
    + c_args : [
    + '-DGENIE_PLUGIN_DIR="@0@/genie-plugins"'.format(meson.current_build_dir()),
    + ],
    + link_with : gplugin_loader_tests,
    + dependencies : [GLIB, GOBJECT, gplugin_dep])
    +test('Genie loading', e)
    +
    +subdir('genie-plugins')
    subdir('plugins')
    endif # vapi
    --- a/vala/tests/plugins/basic.vala Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/plugins/basic.vala Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/vala/tests/plugins/dependent.vala Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/plugins/dependent.vala Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/vala/tests/plugins/load-exception.vala Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/plugins/load-exception.vala Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/vala/tests/plugins/load-failed.vala Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/plugins/load-failed.vala Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- a/vala/tests/plugins/unload-failed.vala Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/plugins/unload-failed.vala Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/vala/tests/test-genie-loading.c Tue Feb 25 22:30:11 2020 -0600
    @@ -0,0 +1,33 @@
    +/*
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    + *
    + * This library is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU Lesser General Public
    + * License as published by the Free Software Foundation; either
    + * version 2 of the License, or (at your option) any later version.
    + *
    + * This library is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * Lesser General Public License for more details.
    + *
    + * You should have received a copy of the GNU Lesser General Public
    + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +#include <glib.h>
    +#include <gplugin.h>
    +
    +#include <gplugin/gplugin-loader-tests.h>
    +
    +/******************************************************************************
    + * Main
    + *****************************************************************************/
    +gint
    +main(gint argc, gchar **argv) {
    + g_test_init(&argc, &argv, NULL);
    +
    + gplugin_loader_tests_main(NULL, GENIE_PLUGIN_DIR, "genie");
    +
    + return g_test_run();
    +}
    --- a/vala/tests/test-vala-loading.c Sat Feb 15 21:14:07 2020 -0600
    +++ b/vala/tests/test-vala-loading.c Tue Feb 25 22:30:11 2020 -0600
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2011-2019 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
    *
    * This library is free software; you can redistribute it and/or
    * modify it under the terms of the GNU Lesser General Public
    @@ -31,4 +31,3 @@
    return g_test_run();
    }
    -