gplugin/gplugin
Clone
Summary
Browse
Changes
Graph
Make sure gtk4 was found before trying to build vala bindings against it
default
tip
8 days ago, Gary Kramlich
7027845d098d
Make sure gtk4 was found before trying to build vala bindings against it
This error only showed up when gtk4 was enabled but not found.
Testing Done:
Built on haiku that doesn't have gtk without setting `-Dgtk4=false`.
Built on debian testing normally.
Reviewed at https://reviews.imfreedom.org/r/3069/
/*
* Copyright (C) 2011-2021 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 <https://www.gnu.org/licenses/>.
*/
#include
<stdio.h>
#include
<string.h>
#include
<glib.h>
#include
<glib/gi18n-lib.h>
#include
<gplugin/gplugin-core.h>
#include
<gplugin/gplugin-file-source.h>
#include
<gplugin/gplugin-file-tree.h>
#include
<gplugin/gplugin-manager.h>
#include
<gplugin/gplugin-native-loader.h>
#include
<gplugin/gplugin-private.h>
#include
<gplugin/gplugin-source.h>
/**
* GPluginManagerForeachFunc:
* @id: The id of the plugin.
* @plugins: (transfer none) (element-type GPlugin.Plugin): A
* [struct@GLib.SList] of each plugin that has the id @id.
* @data: User data passed to [method@GPlugin.Manager.foreach].
*
* A callback function for [method@GPlugin.Manager.foreach].
*/
/**
* GPluginManager:
*
* The manager is responsible for querying plugins as well as telling loaders
* when to load and unload plugins. It also keeps track of paths that should be
* searched for plugins.
*
* Since: 0.32
*/
/******************************************************************************
* Enums
*****************************************************************************/
enum
{
SIG_LOADING
,
SIG_LOADED
,
SIG_LOAD_FAILED
,
SIG_UNLOADING
,
SIG_UNLOADED
,
SIG_UNLOAD_FAILED
,
SIG_LOADER_REGISTERED
,
SIG_LOADER_UNREGISTERED
,
N_SIGNALS
,
};
/******************************************************************************
* Structs
*****************************************************************************/
struct
_GPluginManager
{
GObject
parent
;
GQueue
*
paths
;
GHashTable
*
plugins
;
GHashTable
*
loaders
;
gboolean
refresh_needed
;
GRegex
*
dependency_regex
;
};
G_DEFINE_FINAL_TYPE
(
GPluginManager
,
gplugin_manager
,
G_TYPE_OBJECT
)
/******************************************************************************
* Globals
*****************************************************************************/
GPluginManager
*
default_manager
=
NULL
;
GPluginLoader
*
native_loader
=
NULL
;
static
guint
signals
[
N_SIGNALS
]
=
{
0
,
};
const
gchar
*
dependency_pattern
=
"^(?P<id>.+?)((?P<op>
\\
<=|
\\
<|==|=|
\\
>=|
\\
>)(?P<version>.+))?$"
;
/******************************************************************************
* Helpers
*****************************************************************************/
static
gboolean
gplugin_manager_remove_list_value
(
G_GNUC_UNUSED
gpointer
k
,
gpointer
v
,
G_GNUC_UNUSED
gpointer
d
)
{
GSList
*
l
=
NULL
;
for
(
l
=
(
GSList
*
)
v
;
l
;
l
=
l
->
next
)
{
if
(
l
->
data
&&
G_IS_OBJECT
(
l
->
data
))
g_object_unref
(
l
->
data
);
}
g_slist_free
((
GSList
*
)
v
);
return
TRUE
;
}
static
void
gplugin_manager_change_paths_from_environment
(
GPluginManager
*
manager
,
const
gchar
*
name
,
gboolean
prepend
)
{
gchar
**
paths
;
gint
i
;
const
gchar
*
from_env
;
from_env
=
g_getenv
(
name
);
if
(
from_env
==
NULL
)
{
return
;
}
paths
=
g_strsplit
(
from_env
,
G_SEARCHPATH_SEPARATOR_S
,
0
);
for
(
i
=
0
;
paths
[
i
];
i
++
)
{
if
(
prepend
)
{
gplugin_manager_prepend_path
(
manager
,
paths
[
i
]);
}
else
{
gplugin_manager_append_path
(
manager
,
paths
[
i
]);
}
}
g_strfreev
(
paths
);
}
static
void
gplugin_manager_foreach_unload_plugin
(
gpointer
key
,
gpointer
value
,
G_GNUC_UNUSED
gpointer
data
)
{
GList
*
l
=
NULL
;
gchar
*
id
=
(
gchar
*
)
key
;
for
(
l
=
(
GList
*
)
value
;
l
;
l
=
l
->
next
)
{
GPluginPlugin
*
plugin
=
GPLUGIN_PLUGIN
(
l
->
data
);
GPluginLoader
*
loader
=
NULL
;
GError
*
error
=
NULL
;
if
(
gplugin_plugin_get_state
(
plugin
)
!=
GPLUGIN_PLUGIN_STATE_LOADED
)
{
continue
;
}
loader
=
gplugin_plugin_get_loader
(
plugin
);
if
(
!
gplugin_loader_unload_plugin
(
loader
,
plugin
,
TRUE
,
&
error
))
{
g_warning
(
"failed to unload plugin with id %s: %s"
,
id
,
error
?
error
->
message
:
"unknown"
);
g_clear_error
(
&
error
);
}
g_object_unref
(
loader
);
}
}
static
gchar
*
gplugin_manager_normalize_path
(
const
gchar
*
path
)
{
if
(
g_str_has_suffix
(
path
,
G_DIR_SEPARATOR_S
))
{
return
g_strdup
(
path
);
}
return
g_strdup_printf
(
"%s%s"
,
path
,
G_DIR_SEPARATOR_S
);
}
static
gint
gplugin_manager_compare_paths
(
gconstpointer
a
,
gconstpointer
b
)
{
gchar
*
keya
=
NULL
,
*
keyb
=
NULL
;
gint
r
=
0
;
keya
=
g_utf8_collate_key_for_filename
((
const
gchar
*
)
a
,
-1
);
keyb
=
g_utf8_collate_key_for_filename
((
const
gchar
*
)
b
,
-1
);
r
=
strcmp
(
keya
,
keyb
);
g_free
(
keya
);
g_free
(
keyb
);
return
r
;
}
static
gboolean
gplugin_manager_load_dependencies
(
GPluginManager
*
manager
,
GPluginPlugin
*
plugin
,
G_GNUC_UNUSED
GPluginPluginInfo
*
info
,
GError
**
error
)
{
GSList
*
dependencies
=
NULL
,
*
l
=
NULL
;
GError
*
ourerror
=
NULL
;
gboolean
all_loaded
=
TRUE
;
dependencies
=
gplugin_manager_get_plugin_dependencies
(
manager
,
plugin
,
&
ourerror
);
if
(
ourerror
!=
NULL
)
{
g_propagate_error
(
error
,
ourerror
);
return
FALSE
;
}
for
(
l
=
dependencies
;
l
!=
NULL
;
l
=
l
->
next
)
{
GPluginPlugin
*
dependency
=
GPLUGIN_PLUGIN
(
l
->
data
);
gboolean
loaded
=
FALSE
;
loaded
=
gplugin_manager_load_plugin
(
manager
,
dependency
,
&
ourerror
);
if
(
!
loaded
||
ourerror
!=
NULL
)
{
if
(
ourerror
!=
NULL
)
{
g_propagate_error
(
error
,
ourerror
);
}
all_loaded
=
FALSE
;
break
;
}
}
g_slist_free_full
(
dependencies
,
g_object_unref
);
return
all_loaded
;
}
/******************************************************************************
* Manager implementation
*****************************************************************************/
static
gboolean
gplugin_manager_loading_cb
(
G_GNUC_UNUSED
GPluginManager
*
manager
,
G_GNUC_UNUSED
GPluginPlugin
*
plugin
,
G_GNUC_UNUSED
GError
**
error
)
{
return
TRUE
;
}
static
gboolean
gplugin_manager_unloading_cb
(
G_GNUC_UNUSED
GPluginManager
*
manager
,
G_GNUC_UNUSED
GPluginPlugin
*
plugin
,
G_GNUC_UNUSED
GError
**
error
)
{
return
TRUE
;
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static
void
gplugin_manager_finalize
(
GObject
*
obj
)
{
GPluginManager
*
manager
=
GPLUGIN_MANAGER
(
obj
);
g_queue_free_full
(
manager
->
paths
,
g_free
);
manager
->
paths
=
NULL
;
g_clear_pointer
(
&
manager
->
dependency_regex
,
g_regex_unref
);
/* unload all of the loaded plugins */
g_hash_table_foreach
(
manager
->
plugins
,
gplugin_manager_foreach_unload_plugin
,
NULL
);
/* free all the data in the plugins hash table and destroy it */
g_hash_table_foreach_remove
(
manager
->
plugins
,
gplugin_manager_remove_list_value
,
NULL
);
g_clear_pointer
(
&
manager
->
plugins
,
g_hash_table_destroy
);
/* clean up our list of loaders */
g_clear_pointer
(
&
manager
->
loaders
,
g_hash_table_destroy
);
/* call the base class's destructor */
G_OBJECT_CLASS
(
gplugin_manager_parent_class
)
->
finalize
(
obj
);
}
static
void
gplugin_manager_class_init
(
GPluginManagerClass
*
klass
)
{
GObjectClass
*
obj_class
=
G_OBJECT_CLASS
(
klass
);
obj_class
->
finalize
=
gplugin_manager_finalize
;
/**
* GPluginManager::loading-plugin:
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] that's about to be loaded.
* @error: Return address for a [struct@GLib.Error].
*
* Emitted before @plugin is loaded.
*
* Returns: %TRUE to allow the plugin to load or %FALSE to stop it from
* being loaded.
*
* Since: 0.33
*/
signals
[
SIG_LOADING
]
=
g_signal_new_class_handler
(
"loading-plugin"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
G_CALLBACK
(
gplugin_manager_loading_cb
),
gplugin_boolean_accumulator
,
NULL
,
NULL
,
G_TYPE_BOOLEAN
,
2
,
G_TYPE_OBJECT
,
G_TYPE_POINTER
);
/**
* GPluginManager::loaded-plugin:
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] that's about to be loaded.
*
* Emitted after a plugin is loaded.
*
* Since: 0.33
*/
signals
[
SIG_LOADED
]
=
g_signal_new_class_handler
(
"loaded-plugin"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_OBJECT
);
/**
* GPluginManager::load-plugin-failed:
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] that failed to load.
* @error: The [struct@GLib.Error] of what went wrong.
*
* Emitted after a plugin fails to load.
*
* Since: 0.33
*/
signals
[
SIG_LOAD_FAILED
]
=
g_signal_new_class_handler
(
"load-plugin-failed"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
2
,
G_TYPE_OBJECT
,
G_TYPE_ERROR
);
/**
* GPluginManager::unloading-plugin
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] that's about to be unloaded.
* @error: Return address for a [struct@GLib.Error].
*
* Emitted before a plugin is unloaded.
*
* Returns: %TRUE to allow the plugin to be unloaded, or %FALSE to stop
* the plugin from being unloaded.
*
* Since: 0.33
*/
signals
[
SIG_UNLOADING
]
=
g_signal_new_class_handler
(
"unloading-plugin"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
G_CALLBACK
(
gplugin_manager_unloading_cb
),
gplugin_boolean_accumulator
,
NULL
,
NULL
,
G_TYPE_BOOLEAN
,
2
,
G_TYPE_OBJECT
,
G_TYPE_POINTER
);
/**
* GPluginManager::unloaded-plugin:
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] that's about to be loaded.
*
* emitted after a plugin is successfully unloaded.
*
* Since: 0.33
*/
signals
[
SIG_UNLOADED
]
=
g_signal_new_class_handler
(
"unloaded-plugin"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_OBJECT
);
/**
* GPluginManager::unload-plugin-failed:
* @manager: The [class@GPlugin.Manager] instance.
* @plugin: The [iface@GPlugin.Plugin] instance that failed to unload.
* @error: A [struct@GLib.Error] instance.
*
* Emitted when @manager was asked to unload @plugin, but @plugin returned
* %FALSE when its unload function was called.
*
* Since: 0.33
*/
signals
[
SIG_UNLOAD_FAILED
]
=
g_signal_new_class_handler
(
"unload-plugin-failed"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
2
,
G_TYPE_OBJECT
,
G_TYPE_ERROR
);
/**
* GPluginManager::loader-registered:
* @manager: The [class@GPlugin.Manager] instance.
* @loader: The [class@GPlugin.Loader] instance that was registered.
*
* Emitted when @loader has been registered with @manager via
* [method@GPlugin.Manager.register_loader].
*
* Since: 0.39
*/
signals
[
SIG_LOADER_REGISTERED
]
=
g_signal_new_class_handler
(
"loader-registered"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
GPLUGIN_TYPE_LOADER
);
/**
* GPluginManager::loader-unregistered:
* @manager: The [class@GPlugin.Manager] instance.
* @loader: The [class@GPlugin.Loader] instance that was unregistered.
*
* Emitted when @loader has been unregistered from @manager via
* [method@GPlugin.Manager.unregister_loader].
*
* Since: 0.39
*/
signals
[
SIG_LOADER_UNREGISTERED
]
=
g_signal_new_class_handler
(
"loader-unregistered"
,
G_OBJECT_CLASS_TYPE
(
klass
),
G_SIGNAL_RUN_LAST
,
NULL
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
GPLUGIN_TYPE_LOADER
);
}
static
void
gplugin_manager_init
(
GPluginManager
*
manager
)
{
manager
->
paths
=
g_queue_new
();
/* the plugins hashtable is keyed on a plugin id and holds a GSList of all
* plugins that share that id.
*/
manager
->
plugins
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
NULL
);
manager
->
loaders
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_object_unref
);
manager
->
dependency_regex
=
g_regex_new
(
dependency_pattern
,
0
,
0
,
NULL
);
}
/******************************************************************************
* Private API
*****************************************************************************/
void
gplugin_manager_private_init
(
gboolean
register_native_loader
)
{
GError
*
error
=
NULL
;
if
(
GPLUGIN_IS_MANAGER
(
default_manager
))
{
return
;
}
default_manager
=
g_object_new
(
GPLUGIN_TYPE_MANAGER
,
NULL
);
if
(
register_native_loader
)
{
native_loader
=
gplugin_native_loader_new
();
if
(
!
gplugin_manager_register_loader
(
default_manager
,
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"
);
}
}
}
}
void
gplugin_manager_private_uninit
(
void
)
{
g_clear_object
(
&
native_loader
);
/* g_clear_pointer (and therefore g_clear_object), clears the pointer
* before calling the destroy function. So we have to handle this ourself
* and clear the pointer after destruction since plugins are unloaded
* during destruction and may need to unregister a loader during their
* unload.
*/
if
(
default_manager
!=
NULL
)
{
g_object_unref
(
default_manager
);
default_manager
=
NULL
;
}
}
/******************************************************************************
* API
*****************************************************************************/
/**
* gplugin_manager_append_path:
* @manager: The manager instance.
* @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.
*/
void
gplugin_manager_append_path
(
GPluginManager
*
manager
,
const
gchar
*
path
)
{
GList
*
l
=
NULL
;
gchar
*
normalized
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
path
!=
NULL
);
normalized
=
gplugin_manager_normalize_path
(
path
);
l
=
g_queue_find_custom
(
manager
->
paths
,
normalized
,
gplugin_manager_compare_paths
);
if
(
l
==
NULL
)
{
g_queue_push_tail
(
manager
->
paths
,
normalized
);
}
else
{
g_free
(
normalized
);
}
}
/**
* gplugin_manager_prepend_path:
* @manager: The manager instance.
* @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.
*/
void
gplugin_manager_prepend_path
(
GPluginManager
*
manager
,
const
gchar
*
path
)
{
GList
*
l
=
NULL
;
gchar
*
normalized
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
path
!=
NULL
);
normalized
=
gplugin_manager_normalize_path
(
path
);
l
=
g_queue_find_custom
(
manager
->
paths
,
normalized
,
gplugin_manager_compare_paths
);
if
(
l
==
NULL
)
{
g_queue_push_head
(
manager
->
paths
,
normalized
);
}
else
{
g_free
(
normalized
);
}
}
/**
* gplugin_manager_remove_path:
* @manager: The manager instance.
* @path: A path to remove from the plugin search paths.
*
* Removes @path from the list of paths to search for plugins.
*/
void
gplugin_manager_remove_path
(
GPluginManager
*
manager
,
const
gchar
*
path
)
{
GList
*
l
=
NULL
;
gchar
*
normalized
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
path
!=
NULL
);
normalized
=
gplugin_manager_normalize_path
(
path
);
l
=
g_queue_find_custom
(
manager
->
paths
,
normalized
,
gplugin_manager_compare_paths
);
if
(
l
!=
NULL
)
{
g_free
(
l
->
data
);
g_queue_delete_link
(
manager
->
paths
,
l
);
}
g_free
(
normalized
);
}
/**
* gplugin_manager_remove_paths:
* @manager: The manager instance.
*
* Clears all paths that are set to search for plugins.
*/
void
gplugin_manager_remove_paths
(
GPluginManager
*
manager
)
{
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_queue_clear_full
(
manager
->
paths
,
g_free
);
}
/**
* gplugin_manager_add_default_paths:
* @manager: The manager instance.
*
* Adds the path that GPlugin was installed to to the plugin search path, as
* well as `${XDG_CONFIG_HOME}/gplugin` so users can install additional loaders
* themselves.
*/
void
gplugin_manager_add_default_paths
(
GPluginManager
*
manager
)
{
gchar
*
path
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
path
=
g_build_filename
(
PREFIX
,
LIBDIR
,
"gplugin"
,
NULL
);
gplugin_manager_prepend_path
(
manager
,
path
);
g_free
(
path
);
path
=
g_build_filename
(
g_get_user_config_dir
(),
"gplugin"
,
NULL
);
gplugin_manager_prepend_path
(
manager
,
path
);
g_free
(
path
);
}
/**
* gplugin_manager_add_app_paths:
* @manager: The manager instance.
* @prefix: The installation prefix for the application.
* @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`.
*/
void
gplugin_manager_add_app_paths
(
GPluginManager
*
manager
,
const
gchar
*
prefix
,
const
gchar
*
appname
)
{
gchar
*
path
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
appname
!=
NULL
);
path
=
g_build_filename
(
prefix
,
LIBDIR
,
appname
,
NULL
);
gplugin_manager_prepend_path
(
manager
,
path
);
g_free
(
path
);
path
=
g_build_filename
(
g_get_user_config_dir
(),
appname
,
"plugins"
,
NULL
);
gplugin_manager_prepend_path
(
manager
,
path
);
g_free
(
path
);
}
/**
* gplugin_manager_append_paths_from_environment:
* @manager: The manager instance.
* @name: The name of the environment variable containing the paths to add.
*
* Append the paths held in the environment variable @name to the list.
*
* Since: 0.37
*/
void
gplugin_manager_append_paths_from_environment
(
GPluginManager
*
manager
,
const
gchar
*
name
)
{
gplugin_manager_change_paths_from_environment
(
manager
,
name
,
FALSE
);
}
/**
* gplugin_manager_prepend_paths_from_environment:
* @manager: The manager instance.
* @name: The name of the environment variable containing the paths to add.
*
* Prepends the paths held in the environment variable @name to the list.
*
* Since: 0.37
*/
void
gplugin_manager_prepend_paths_from_environment
(
GPluginManager
*
manager
,
const
gchar
*
name
)
{
gplugin_manager_change_paths_from_environment
(
manager
,
name
,
TRUE
);
}
/**
* gplugin_manager_get_paths:
* @manager: The manager instance.
*
* Gets the list of paths which will be searched for plugins.
*
* Returns: (element-type utf8) (transfer none): The [type@GLib.List] of paths
* which will be searched for plugins.
*/
GList
*
gplugin_manager_get_paths
(
GPluginManager
*
manager
)
{
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
return
manager
->
paths
->
head
;
}
/**
* gplugin_manager_register_loader:
* @manager: The manager instance.
* @loader: The loader instance to register.
* @error: (out) (nullable): The return address for a [struct@GLib.Error].
*
* Registers @loader as an available loader.
*
* Returns: %TRUE if the loader was successfully register, %FALSE otherwise
* with @error set.
*/
gboolean
gplugin_manager_register_loader
(
GPluginManager
*
manager
,
GPluginLoader
*
loader
,
GError
**
error
)
{
GPluginLoader
*
found
=
NULL
;
const
gchar
*
id
=
NULL
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
FALSE
);
g_return_val_if_fail
(
GPLUGIN_IS_LOADER
(
loader
),
FALSE
);
id
=
gplugin_loader_get_id
(
loader
);
found
=
g_hash_table_lookup
(
manager
->
loaders
,
id
);
if
(
GPLUGIN_IS_LOADER
(
found
))
{
g_set_error
(
error
,
GPLUGIN_DOMAIN
,
0
,
_
(
"loader %s was already registered"
),
id
);
return
FALSE
;
}
g_hash_table_insert
(
manager
->
loaders
,
g_strdup
(
id
),
g_object_ref
(
loader
));
/* make a note that we need to refresh */
manager
->
refresh_needed
=
TRUE
;
g_signal_emit
(
manager
,
signals
[
SIG_LOADER_REGISTERED
],
0
,
loader
);
return
TRUE
;
}
/**
* gplugin_manager_unregister_loader:
* @manager: The manager instance.
* @loader: The loader instance to unregister.
* @error: (out) (nullable): The return address for a [struct@GLib.Error].
*
* Unregisters @loader as an available loader.
*
* Returns: %TRUE if the loader was successfully unregistered, %FALSE
* otherwise with @error set.
*/
gboolean
gplugin_manager_unregister_loader
(
GPluginManager
*
manager
,
GPluginLoader
*
loader
,
GError
**
error
)
{
const
gchar
*
id
=
NULL
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
FALSE
);
g_return_val_if_fail
(
GPLUGIN_IS_LOADER
(
loader
),
FALSE
);
id
=
gplugin_loader_get_id
(
loader
);
loader
=
g_hash_table_lookup
(
manager
->
loaders
,
id
);
if
(
!
GPLUGIN_IS_LOADER
(
loader
))
{
g_set_error
(
error
,
GPLUGIN_DOMAIN
,
0
,
_
(
"loader %s is not registered"
),
id
);
return
FALSE
;
}
/* Temporarily add a reference to loader so we can emit the signal if it
* was removed from our table correctly.
*/
g_object_ref
(
loader
);
if
(
g_hash_table_remove
(
manager
->
loaders
,
id
))
{
g_signal_emit
(
manager
,
signals
[
SIG_LOADER_UNREGISTERED
],
0
,
loader
);
}
g_clear_object
(
&
loader
);
return
TRUE
;
}
/**
* gplugin_manager_get_loaders:
* @manager: The manager instance.
*
* Returns a list of all registered loaders.
*
* Returns: (element-type GPlugin.Loader) (transfer container): Returns a list
* of all registered loaders.
*/
GList
*
gplugin_manager_get_loaders
(
GPluginManager
*
manager
)
{
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
FALSE
);
return
g_hash_table_get_values
(
manager
->
loaders
);
}
/**
* gplugin_manager_refresh:
* @manager: The manager instance.
*
* Forces a refresh of all plugins found in the search paths.
*/
void
gplugin_manager_refresh
(
GPluginManager
*
manager
)
{
GPluginSource
*
file_source
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
file_source
=
gplugin_file_source_new
(
manager
);
manager
->
refresh_needed
=
TRUE
;
while
(
manager
->
refresh_needed
)
{
manager
->
refresh_needed
=
FALSE
;
if
(
gplugin_source_scan
(
file_source
))
{
manager
->
refresh_needed
=
TRUE
;
}
}
g_clear_object
(
&
file_source
);
}
/**
* gplugin_manager_foreach:
* @manager: The manager instance.
* @func: (scope call): The function to call with each plugin.
* @data: User data to pass to @func.
*
* Calls @func for each plugin that is known.
*/
void
gplugin_manager_foreach
(
GPluginManager
*
manager
,
GPluginManagerForeachFunc
func
,
gpointer
data
)
{
GHashTableIter
iter
;
gpointer
id
=
NULL
,
plugins
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
func
!=
NULL
);
g_hash_table_iter_init
(
&
iter
,
manager
->
plugins
);
while
(
g_hash_table_iter_next
(
&
iter
,
&
id
,
&
plugins
))
{
func
((
gchar
*
)
id
,
(
GSList
*
)
plugins
,
data
);
}
}
/**
* gplugin_manager_find_plugins:
* @manager: The manager instance.
* @id: The ID of the plugin to find.
*
* Finds all plugins matching @id.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A [struct@GLib.SList]
* of plugins matching @id.
*/
GSList
*
gplugin_manager_find_plugins
(
GPluginManager
*
manager
,
const
gchar
*
id
)
{
GSList
*
plugins_list
=
NULL
,
*
l
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
g_return_val_if_fail
(
id
!=
NULL
,
NULL
);
l
=
g_hash_table_lookup
(
manager
->
plugins
,
id
);
plugins_list
=
g_slist_copy_deep
(
l
,
(
GCopyFunc
)
g_object_ref
,
NULL
);
return
plugins_list
;
}
/**
* gplugin_manager_find_plugins_with_version:
* @manager: The manager instance.
* @id: The ID of the plugin to find.
* @op: one of <, <=, =, ==, >=, >.
* @version: The version to compare against.
*
* Similar to [method@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.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A [struct@GLib.SList]
* of plugins matching @id and the version constraint.
*/
GSList
*
gplugin_manager_find_plugins_with_version
(
GPluginManager
*
manager
,
const
gchar
*
id
,
const
gchar
*
op
,
const
gchar
*
version
)
{
GSList
*
plugins
=
NULL
,
*
filtered
=
NULL
,
*
l
=
NULL
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
plugins
=
gplugin_manager_find_plugins
(
manager
,
id
);
if
((
op
==
NULL
||
*
op
==
'\0'
)
&&
(
version
==
NULL
||
*
version
==
'\0'
))
{
/* we weren't actually passed an operator and a version so just return
* the list we have based on the id.
*/
return
plugins
;
}
for
(
l
=
plugins
;
l
;
l
=
l
->
next
)
{
GPluginPlugin
*
plugin
=
GPLUGIN_PLUGIN
(
l
->
data
);
GPluginPluginInfo
*
info
=
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
);
/* 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
);
/* we need to keep info around until we're done using found_version */
g_object_unref
(
info
);
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
(
plugin
));
}
}
g_slist_free_full
(
plugins
,
g_object_unref
);
return
g_slist_reverse
(
filtered
);
}
/**
* gplugin_manager_find_plugins_with_state:
* @manager: The manager instance.
* @state: The state to look for.
*
* Finds all plugins that currently have a state of @state.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A [struct@GLib.SList]
* of plugins whose state is @state.
*/
GSList
*
gplugin_manager_find_plugins_with_state
(
GPluginManager
*
manager
,
GPluginPluginState
state
)
{
GSList
*
plugins
=
NULL
;
GHashTableIter
iter
;
gpointer
value
=
NULL
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
g_hash_table_iter_init
(
&
iter
,
manager
->
plugins
);
while
(
g_hash_table_iter_next
(
&
iter
,
NULL
,
&
value
))
{
GSList
*
l
=
NULL
;
for
(
l
=
(
GSList
*
)
value
;
l
!=
NULL
;
l
=
l
->
next
)
{
GPluginPlugin
*
plugin
=
GPLUGIN_PLUGIN
(
l
->
data
);
if
(
gplugin_plugin_get_state
(
plugin
)
==
state
)
{
plugins
=
g_slist_prepend
(
plugins
,
g_object_ref
(
plugin
));
}
}
}
return
plugins
;
}
/**
* gplugin_manager_find_plugin:
* @manager: The manager instance.
* @id: The ID of the plugin to find.
*
* Finds the first plugin matching @id.
*
* This function uses [method@GPlugin.Manager.find_plugins] and returns the
* first plugin in the list.
*
* Returns: (transfer full): A plugin instance or %NULL if no plugin
* matching @id was found.
*/
GPluginPlugin
*
gplugin_manager_find_plugin
(
GPluginManager
*
manager
,
const
gchar
*
id
)
{
GSList
*
plugins_list
=
NULL
;
GPluginPlugin
*
plugin
=
NULL
;
g_return_val_if_fail
(
id
!=
NULL
,
NULL
);
plugins_list
=
gplugin_manager_find_plugins
(
manager
,
id
);
if
(
plugins_list
==
NULL
)
{
return
NULL
;
}
plugin
=
g_object_ref
(
plugins_list
->
data
);
g_slist_free_full
(
plugins_list
,
g_object_unref
);
return
plugin
;
}
/**
* gplugin_manager_find_plugin_with_newest_version:
* @manager: The manager instance.
* @id: The ID of the plugin to find.
*
* Calls [method@GPlugin.Manager.find_plugins] with @id, and then returns the
* plugins with the highest version number or %NULL if no plugins with @id are
* found.
*
* Returns: (transfer full): The plugin with an ID of @id that has the highest
* version number, or %NULL if no plugins were found with @id.
*/
GPluginPlugin
*
gplugin_manager_find_plugin_with_newest_version
(
GPluginManager
*
manager
,
const
gchar
*
id
)
{
GPluginPlugin
*
plugin_a
=
NULL
;
GPluginPluginInfo
*
info_a
=
NULL
;
const
gchar
*
version_a
=
NULL
;
GSList
*
l
=
NULL
;
g_return_val_if_fail
(
id
!=
NULL
,
NULL
);
l
=
gplugin_manager_find_plugins
(
manager
,
id
);
for
(;
l
!=
NULL
;
l
=
g_slist_delete_link
(
l
,
l
))
{
GPluginPlugin
*
plugin_b
=
NULL
;
GPluginPluginInfo
*
info_b
=
NULL
;
const
gchar
*
version_b
=
NULL
;
gint
cmp
=
0
;
if
(
!
GPLUGIN_IS_PLUGIN
(
l
->
data
))
{
continue
;
}
plugin_b
=
GPLUGIN_PLUGIN
(
l
->
data
);
info_b
=
gplugin_plugin_get_info
(
plugin_b
);
/* If this is the first plugin we've found, set the plugin_a values and
* continue.
*/
if
(
!
GPLUGIN_IS_PLUGIN
(
plugin_a
))
{
plugin_a
=
plugin_b
;
info_a
=
info_b
;
version_a
=
gplugin_plugin_info_get_version
(
info_a
);
continue
;
}
/* At this point, we've seen another plugin, so we need to compare
* their versions.
*/
version_b
=
gplugin_plugin_info_get_version
(
info_b
);
cmp
=
gplugin_version_compare
(
version_a
,
version_b
);
if
(
cmp
<
0
)
{
/* plugin_b has a newer version, so set the plugin_a pointers to
* the plugin_b pointers as well as the version pointers.
*/
g_set_object
(
&
plugin_a
,
plugin_b
);
g_set_object
(
&
info_a
,
info_b
);
version_a
=
version_b
;
}
/* Clean up the plugin_b pointers. */
g_clear_object
(
&
plugin_b
);
g_clear_object
(
&
info_b
);
}
g_clear_object
(
&
info_a
);
return
plugin_a
;
}
/**
* gplugin_manager_get_plugin_dependencies:
* @manager: The manager instance.
* @plugin: The plugin whose dependencies to get.
* @error: (out) (nullable): Return address for a [struct@GLib.Error].
*
* Returns a list of all the plugins that @plugin depends on.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A [struct@GLib.SList]
* of plugins that @plugin depends on, or %NULL on error with @error
* set.
*/
GSList
*
gplugin_manager_get_plugin_dependencies
(
GPluginManager
*
manager
,
GPluginPlugin
*
plugin
,
GError
**
error
)
{
GPluginPluginInfo
*
info
=
NULL
;
GSList
*
ret
=
NULL
;
const
gchar
*
const
*
dependencies
=
NULL
;
gint
i
=
0
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
g_return_val_if_fail
(
GPLUGIN_IS_PLUGIN
(
plugin
),
NULL
);
info
=
gplugin_plugin_get_info
(
plugin
);
dependencies
=
gplugin_plugin_info_get_dependencies
(
info
);
g_object_unref
(
info
);
if
(
dependencies
==
NULL
)
{
return
NULL
;
}
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
(
manager
->
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_with_version
(
manager
,
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
;
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
));
g_slist_free_full
(
ret
,
g_object_unref
);
return
NULL
;
}
}
return
ret
;
}
/**
* gplugin_manager_load_plugin:
* @manager: The manager instance.
* @plugin: The plugin instance.
* @error: (out) (nullable): Return location for a [struct@GLib.Error] or %NULL.
*
* 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.
*
* Returns: %TRUE if @plugin was loaded successfully or already loaded, %FALSE
* otherwise.
*/
gboolean
gplugin_manager_load_plugin
(
GPluginManager
*
manager
,
GPluginPlugin
*
plugin
,
GError
**
error
)
{
GPluginPluginInfo
*
info
=
NULL
;
GPluginLoader
*
loader
=
NULL
;
GError
*
real_error
=
NULL
;
gboolean
ret
=
TRUE
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
FALSE
);
g_return_val_if_fail
(
GPLUGIN_IS_PLUGIN
(
plugin
),
FALSE
);
/* if the plugin is already loaded there's nothing for us to do */
if
(
gplugin_plugin_get_state
(
plugin
)
==
GPLUGIN_PLUGIN_STATE_LOADED
)
{
return
TRUE
;
}
/* now try to get the plugin info from the plugin */
info
=
gplugin_plugin_get_info
(
plugin
);
if
(
info
==
NULL
)
{
gchar
*
filename
=
gplugin_plugin_get_filename
(
plugin
);
g_set_error
(
error
,
GPLUGIN_DOMAIN
,
0
,
_
(
"Plugin %s did not return value plugin info"
),
filename
);
g_free
(
filename
);
gplugin_plugin_set_state
(
plugin
,
GPLUGIN_PLUGIN_STATE_LOAD_FAILED
);
return
FALSE
;
}
if
(
!
gplugin_manager_load_dependencies
(
manager
,
plugin
,
info
,
&
real_error
))
{
g_object_unref
(
info
);
g_propagate_error
(
error
,
real_error
);
return
FALSE
;
}
g_object_unref
(
info
);
/* now load the actual plugin */
loader
=
gplugin_plugin_get_loader
(
plugin
);
if
(
!
GPLUGIN_IS_LOADER
(
loader
))
{
gchar
*
filename
=
gplugin_plugin_get_filename
(
plugin
);
g_set_error
(
error
,
GPLUGIN_DOMAIN
,
0
,
_
(
"The loader for %s is not a loader. This "
"should not happened!"
),
filename
);
g_free
(
filename
);
gplugin_plugin_set_state
(
plugin
,
GPLUGIN_PLUGIN_STATE_LOAD_FAILED
);
g_object_unref
(
loader
);
return
FALSE
;
}
g_signal_emit
(
manager
,
signals
[
SIG_LOADING
],
0
,
plugin
,
&
real_error
,
&
ret
);
if
(
!
ret
)
{
/* Set the plugin's error. */
g_object_set
(
plugin
,
"error"
,
real_error
,
NULL
);
g_propagate_error
(
error
,
real_error
);
gplugin_plugin_set_state
(
plugin
,
GPLUGIN_PLUGIN_STATE_LOAD_FAILED
);
g_object_unref
(
loader
);
return
ret
;
}
ret
=
gplugin_loader_load_plugin
(
loader
,
plugin
,
&
real_error
);
if
(
ret
)
{
g_clear_error
(
&
real_error
);
g_signal_emit
(
manager
,
signals
[
SIG_LOADED
],
0
,
plugin
);
}
else
{
g_signal_emit
(
manager
,
signals
[
SIG_LOAD_FAILED
],
0
,
plugin
,
real_error
);
g_propagate_error
(
error
,
real_error
);
}
g_object_unref
(
loader
);
return
ret
;
}
/**
* gplugin_manager_unload_plugin:
* @manager: The manager instance.
* @plugin: The plugin instance.
* @error: (out) (nullable): Return location for a [struct@GLib.Error] or %NULL.
*
* Unloads @plugin.
*
* If @plugin has dependencies, they are not unloaded.
*
* Returns: %TRUE if @plugin was unloaded successfully or not loaded, %FALSE
* otherwise.
*/
gboolean
gplugin_manager_unload_plugin
(
GPluginManager
*
manager
,
GPluginPlugin
*
plugin
,
GError
**
error
)
{
GPluginLoader
*
loader
=
NULL
;
GError
*
real_error
=
NULL
;
gboolean
ret
=
TRUE
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
FALSE
);
g_return_val_if_fail
(
GPLUGIN_IS_PLUGIN
(
plugin
),
FALSE
);
if
(
gplugin_plugin_get_state
(
plugin
)
!=
GPLUGIN_PLUGIN_STATE_LOADED
)
{
return
TRUE
;
}
loader
=
gplugin_plugin_get_loader
(
plugin
);
if
(
!
GPLUGIN_IS_LOADER
(
loader
))
{
g_set_error_literal
(
error
,
GPLUGIN_DOMAIN
,
0
,
_
(
"Plugin loader is not a loader"
));
g_object_unref
(
loader
);
return
FALSE
;
}
g_signal_emit
(
manager
,
signals
[
SIG_UNLOADING
],
0
,
plugin
,
&
real_error
,
&
ret
);
if
(
!
ret
)
{
/* Set the plugin's error. */
g_object_set
(
plugin
,
"error"
,
real_error
,
NULL
);
g_propagate_error
(
error
,
real_error
);
gplugin_plugin_set_state
(
plugin
,
GPLUGIN_PLUGIN_STATE_LOAD_FAILED
);
g_object_unref
(
loader
);
return
ret
;
}
ret
=
gplugin_loader_unload_plugin
(
loader
,
plugin
,
FALSE
,
&
real_error
);
if
(
ret
)
{
g_clear_error
(
&
real_error
);
g_signal_emit
(
manager
,
signals
[
SIG_UNLOADED
],
0
,
plugin
);
}
else
{
g_signal_emit
(
manager
,
signals
[
SIG_UNLOAD_FAILED
],
0
,
plugin
,
real_error
);
g_propagate_error
(
error
,
real_error
);
}
g_object_unref
(
loader
);
return
ret
;
}
/**
* gplugin_manager_list_plugins:
* @manager: The manager instance.
*
* Returns a list of all plugin IDs.
*
* Each id should be queried directly for more information.
*
* Returns: (element-type utf8) (transfer container): A [struct@GLib.List] of
* each unique plugin ID.
*/
GList
*
gplugin_manager_list_plugins
(
GPluginManager
*
manager
)
{
GQueue
*
queue
=
NULL
;
GList
*
ret
=
NULL
;
GHashTableIter
iter
;
gpointer
key
=
NULL
;
g_return_val_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
),
NULL
);
queue
=
g_queue_new
();
g_hash_table_iter_init
(
&
iter
,
manager
->
plugins
);
while
(
g_hash_table_iter_next
(
&
iter
,
&
key
,
NULL
))
{
g_queue_push_tail
(
queue
,
(
gchar
*
)
key
);
}
ret
=
g_list_copy
(
queue
->
head
);
g_queue_free
(
queue
);
return
ret
;
}
/**
* gplugin_manager_add_plugin:
* @manager: The instance.
* @id: The id of the plugin to add.
* @plugin: The plugin to add.
*
* Adds @plugin to @manager with @id. This should only be called by
* [iface@GPlugin.Source] implementations.
*
* Since: 0.39
*/
void
gplugin_manager_add_plugin
(
GPluginManager
*
manager
,
const
gchar
*
id
,
GPluginPlugin
*
plugin
)
{
GSList
*
plugins
=
NULL
;
g_return_if_fail
(
GPLUGIN_IS_MANAGER
(
manager
));
g_return_if_fail
(
id
!=
NULL
);
g_return_if_fail
(
GPLUGIN_IS_PLUGIN
(
plugin
));
plugins
=
g_hash_table_lookup
(
manager
->
plugins
,
id
);
plugins
=
g_slist_prepend
(
plugins
,
g_object_ref
(
plugin
));
g_hash_table_insert
(
manager
->
plugins
,
g_strdup
(
id
),
plugins
);
}
/**
* gplugin_manager_get_default:
*
* Gets the default plugin manager in GPlugin.
*
* Returns: (transfer none): The default GPluginManager instance.
*
* Since: 0.33
*/
GPluginManager
*
gplugin_manager_get_default
(
void
)
{
return
default_manager
;
}