diff --git a/ChangeLog b/ChangeLog index 0eaa76fe2..7a21e0058 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2011-03-30 Colomban Wendling + + * src/plugindata.h, src/pluginprivate.h, src/plugins.c, + src/pluginutils.c, src/pluginutils.h plugins/geanyfunctions.h: + Add plugin_idle_add(), plugin_timeout_add() and + plugin_timeout_add_seconds() to the plugin API. These are + convenience wrappers to ensure the added timeouts are properly + removed when unloading the plugin, preventing possible crashes. + + 2011-03-29 Nick Treleaven * doc/geany.txt, doc/geany.html: diff --git a/plugins/geanyfunctions.h b/plugins/geanyfunctions.h index 2febb83cc..4cc1c8501 100644 --- a/plugins/geanyfunctions.h +++ b/plugins/geanyfunctions.h @@ -26,6 +26,12 @@ geany_functions->p_plugin->plugin_set_key_group #define plugin_show_configure \ geany_functions->p_plugin->plugin_show_configure +#define plugin_timeout_add \ + geany_functions->p_plugin->plugin_timeout_add +#define plugin_timeout_add_seconds \ + geany_functions->p_plugin->plugin_timeout_add_seconds +#define plugin_idle_add \ + geany_functions->p_plugin->plugin_idle_add #define document_new_file \ geany_functions->p_document->document_new_file #define document_get_current \ diff --git a/src/plugindata.h b/src/plugindata.h index 730b4f8c7..0c4a84368 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -54,7 +54,7 @@ * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 204 +#define GEANY_API_VERSION 205 /** The Application Binary Interface (ABI) version, incremented whenever * existing fields in the plugin data types have to be changed or reordered. @@ -645,6 +645,11 @@ typedef struct PluginFuncs struct GeanyKeyGroup* (*plugin_set_key_group)(GeanyPlugin *plugin, const gchar *section_name, gsize count, _GeanyKeyGroupCallback callback); void (*plugin_show_configure)(GeanyPlugin *plugin); + guint (*plugin_timeout_add) (GeanyPlugin *plugin, guint interval, GSourceFunc function, + gpointer data); + guint (*plugin_timeout_add_seconds) (GeanyPlugin *plugin, guint interval, + GSourceFunc function, gpointer data); + guint (*plugin_idle_add) (GeanyPlugin *plugin, GSourceFunc function, gpointer data); } PluginFuncs; diff --git a/src/pluginprivate.h b/src/pluginprivate.h index 8b9d73a5f..b10cb7f8d 100644 --- a/src/pluginprivate.h +++ b/src/pluginprivate.h @@ -57,6 +57,7 @@ typedef struct GeanyPluginPrivate GeanyKeyGroup *key_group; GeanyAutoSeparator toolbar_separator; GArray *signal_ids; /* SignalConnection's to disconnect when unloading */ + GList *sources; /* GSources to destroy when unloading */ } GeanyPluginPrivate; diff --git a/src/plugins.c b/src/plugins.c index ace82a5f8..436a0ba87 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -85,7 +85,10 @@ static PluginFuncs plugin_funcs = { &plugin_module_make_resident, &plugin_signal_connect, &plugin_set_key_group, - &plugin_show_configure + &plugin_show_configure, + &plugin_timeout_add, + &plugin_timeout_add_seconds, + &plugin_idle_add }; static DocumentFuncs doc_funcs = { @@ -760,6 +763,22 @@ static void remove_callbacks(Plugin *plugin) } +static void remove_sources(Plugin *plugin) +{ + GList *item; + + item = plugin->sources; + while (item != NULL) + { + GList *next = item->next; /* cache the next pointer because current item will be freed */ + + g_source_destroy(item->data); + item = next; + } + /* don't free the list here, it is allocated inside each source's data */ +} + + static gboolean is_active_plugin(Plugin *plugin) { return (g_list_find(active_plugin_list, plugin) != NULL); @@ -776,6 +795,7 @@ plugin_cleanup(Plugin *plugin) plugin->cleanup(); remove_callbacks(plugin); + remove_sources(plugin); if (plugin->key_group) keybindings_free_group(plugin->key_group); diff --git a/src/pluginutils.c b/src/pluginutils.c index 5a5de9688..5007f519d 100644 --- a/src/pluginutils.c +++ b/src/pluginutils.c @@ -131,6 +131,143 @@ void plugin_signal_connect(GeanyPlugin *plugin, } +typedef struct PluginSourceData +{ + Plugin *plugin; + GList list_link; /* element of plugin->sources cointaining this GSource */ + GSourceFunc function; + gpointer user_data; +} PluginSourceData; + + +/* use GSlice if available */ +#if GLIB_CHECK_VERSION(2,10,0) +# define PSD_ALLOC() (g_slice_alloc(sizeof(PluginSourceData))) +# define PSD_FREE(psd) (g_slice_free1(sizeof(PluginSourceData), (psd))) +#else +# define PSD_ALLOC() (g_malloc(sizeof(PluginSourceData))) +# define PSD_FREE(psd) (g_free(psd)) +#endif + + +/* prepend psd->list_link to psd->plugin->sources */ +static void psd_register(PluginSourceData *psd, GSource *source) +{ + psd->list_link.data = source; + psd->list_link.prev = NULL; + psd->list_link.next = psd->plugin->sources; + if (psd->list_link.next) + psd->list_link.next->prev = &psd->list_link; + psd->plugin->sources = &psd->list_link; +} + + +/* removes psd->list_link from psd->plugin->sources */ +static void psd_unregister(PluginSourceData *psd) +{ + if (psd->list_link.next) + psd->list_link.next->prev = psd->list_link.prev; + if (psd->list_link.prev) + psd->list_link.prev->next = psd->list_link.next; + else /* we were the first of the list, update the plugin->sources pointer */ + psd->plugin->sources = psd->list_link.next; +} + + +static void on_plugin_source_destroy(gpointer data) +{ + PluginSourceData *psd = data; + + psd_unregister(psd); + PSD_FREE(psd); +} + + +static gboolean on_plugin_source_callback(gpointer data) +{ + PluginSourceData *psd = data; + + return psd->function(psd->user_data); +} + + +/* adds the given source to the default GMainContext and to the list of sources to remove at plugin + * unloading time */ +static guint plugin_source_add(GeanyPlugin *plugin, GSource *source, GSourceFunc func, gpointer data) +{ + guint id; + PluginSourceData *psd = PSD_ALLOC(); + + psd->plugin = plugin->priv; + psd->function = func; + psd->user_data = data; + + g_source_set_callback(source, on_plugin_source_callback, psd, on_plugin_source_destroy); + psd_register(psd, source); + id = g_source_attach(source, NULL); + g_source_unref(source); + + return id; +} + + +/** Adds a GLib main loop timeout callback that will be removed when unloading the plugin, + * preventing it to run after the plugin has been unloaded (which may lead to a segfault). + * + * @param plugin Must be @ref geany_plugin. + * @param interval The time between calls to the function, in milliseconds. + * @param function The function to call after the given timeout. + * @param data The user data passed to the function. + * @return the ID of the event source (you generally won't need it, or better use g_timeout_add() + * directly if you want to manage this event source manually). + * + * @see g_timeout_add() + * @since 0.21, plugin API 205. + */ +guint plugin_timeout_add(GeanyPlugin *plugin, guint interval, GSourceFunc function, gpointer data) +{ + return plugin_source_add(plugin, g_timeout_source_new(interval), function, data); +} + + +/** Adds a GLib main loop timeout callback that will be removed when unloading the plugin, + * preventing it to run after the plugin has been unloaded (which may lead to a segfault). + * + * @param plugin Must be @ref geany_plugin. + * @param interval The time between calls to the function, in seconds. + * @param function The function to call after the given timeout. + * @param data The user data passed to the function. + * @return the ID of the event source (you generally won't need it, or better use + * g_timeout_add_seconds() directly if you want to manage this event source manually). + * + * @see g_timeout_add_seconds() + * @since 0.21, plugin API 205. + */ +guint plugin_timeout_add_seconds(GeanyPlugin *plugin, guint interval, GSourceFunc function, + gpointer data) +{ + return plugin_source_add(plugin, g_timeout_source_new_seconds(interval), function, data); +} + + +/** Adds a GLib main loop IDLE callback that will be removed when unloading the plugin, preventing + * it to run after the plugin has been unloaded (which may lead to a segfault). + * + * @param plugin Must be @ref geany_plugin. + * @param function The function to call in IDLE time. + * @param data The user data passed to the function. + * @return the ID of the event source (you generally won't need it, or better use g_idle_add() + * directly if you want to manage this event source manually). + * + * @see g_idle_add() + * @since 0.21, plugin API 205. + */ +guint plugin_idle_add(GeanyPlugin *plugin, GSourceFunc function, gpointer data) +{ + return plugin_source_add(plugin, g_idle_source_new(), function, data); +} + + /** Sets up or resizes a keybinding group for the plugin. * You should then call keybindings_set_item() for each keybinding in the group. * @param plugin Must be @ref geany_plugin. diff --git a/src/pluginutils.h b/src/pluginutils.h index a62760a7c..8a449bbf6 100644 --- a/src/pluginutils.h +++ b/src/pluginutils.h @@ -43,6 +43,14 @@ void plugin_signal_connect(struct GeanyPlugin *plugin, GObject *object, const gchar *signal_name, gboolean after, GCallback callback, gpointer user_data); +guint plugin_timeout_add(struct GeanyPlugin *plugin, guint interval, GSourceFunc function, + gpointer data); + +guint plugin_timeout_add_seconds(struct GeanyPlugin *plugin, guint interval, GSourceFunc function, + gpointer data); + +guint plugin_idle_add(struct GeanyPlugin *plugin, GSourceFunc function, gpointer data); + struct GeanyKeyGroup *plugin_set_key_group(struct GeanyPlugin *plugin, const gchar *section_name, gsize count, GeanyKeyGroupCallback callback);