2
* plugins.c - this file is part of Geany, a fast and lightweight IDE
4
* Copyright 2007-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5
* Copyright 2007-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22
* $Id: plugins.c 5886 2011-08-06 18:34:14Z colombanw $
25
/* Code to manage, load and unload plugins. */
33
#include "Scintilla.h"
34
#include "ScintillaWidget.h"
38
#include "plugindata.h"
42
#include "filetypes.h"
43
#include "templates.h"
44
#include "sciwrappers.h"
48
#include "msgwindow.h"
50
#include "geanywraplabel.h"
52
#include "encodings.h"
54
#include "highlighting.h"
55
#include "keybindings.h"
63
#include "pluginutils.h"
64
#include "pluginprivate.h"
67
GList *active_plugin_list = NULL; /* list of only actually loaded plugins, always valid */
70
static gboolean want_plugins = FALSE;
72
/* list of all available, loadable plugins, only valid as long as the plugin manager dialog is
73
* opened, afterwards it will be destroyed */
74
static GList *plugin_list = NULL;
75
static gchar **active_plugins_pref = NULL; /* list of plugin filenames to load at startup */
76
static GList *failed_plugins_list = NULL; /* plugins the user wants active but can't be used */
78
static GtkWidget *menu_separator = NULL;
80
static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data);
83
static PluginFuncs plugin_funcs = {
84
&plugin_add_toolbar_item,
85
&plugin_module_make_resident,
86
&plugin_signal_connect,
87
&plugin_set_key_group,
88
&plugin_show_configure,
90
&plugin_timeout_add_seconds,
94
static DocumentFuncs doc_funcs = {
96
&document_get_current,
97
&document_get_from_page,
98
&document_find_by_filename,
99
&document_find_by_real_path,
102
&document_open_files,
103
&document_remove_page,
104
&document_reload_file,
105
&document_set_encoding,
106
&document_set_text_changed,
107
&document_set_filetype,
110
&document_save_file_as,
111
&document_rename_file,
112
&document_get_status_color,
113
&document_get_basename_for_display,
114
&document_get_notebook_page,
115
&document_compare_by_display_name,
116
&document_compare_by_tab_order,
117
&document_compare_by_tab_order_reverse
120
static EditorFuncs editor_funcs = {
121
&editor_get_indent_prefs,
122
&editor_create_widget,
123
&editor_indicator_set_on_range,
124
&editor_indicator_set_on_line,
125
&editor_indicator_clear,
126
&editor_set_indent_type,
127
&editor_get_word_at_pos,
128
&editor_get_eol_char_name,
129
&editor_get_eol_char_len,
130
&editor_get_eol_char,
131
&editor_insert_text_block,
132
&editor_get_eol_char_mode,
134
&editor_find_snippet,
135
&editor_insert_snippet
138
static ScintillaFuncs scintilla_funcs = {
139
&scintilla_send_message,
143
/* Macro to prevent a duplicate macro being generated in geanyfunctions.h */
144
#define _scintilla_send_message_macro scintilla_send_message
146
static SciFuncs sci_funcs = {
147
&_scintilla_send_message_macro,
149
&sci_start_undo_action,
150
&sci_end_undo_action,
155
&sci_get_current_position,
156
&sci_set_current_position,
157
&sci_get_col_from_position,
158
&sci_get_line_from_position,
159
&sci_get_position_from_line,
161
&sci_get_selected_text,
162
&sci_get_selected_text_length,
163
&sci_get_selection_start,
164
&sci_get_selection_end,
165
&sci_get_selection_mode,
166
&sci_set_selection_mode,
167
&sci_set_selection_start,
168
&sci_set_selection_end,
171
&sci_get_line_length,
173
&sci_get_line_is_visible,
174
&sci_ensure_line_is_visible,
176
&sci_find_matching_brace,
179
&sci_get_current_line,
182
&sci_indicator_clear,
185
&sci_get_contents_range,
186
&sci_get_selection_contents,
188
&sci_get_line_end_position,
189
&sci_set_target_start,
192
&sci_set_marker_at_line,
193
&sci_delete_marker_at_line,
194
&sci_is_marker_set_at_line,
197
&sci_set_line_indentation,
198
&sci_get_line_indentation,
202
static TemplateFuncs template_funcs = {
203
&templates_get_template_fileheader
206
static UtilsFuncs utils_funcs = {
208
&utils_string_replace_all,
209
&utils_get_file_list,
211
&utils_get_locale_from_utf8,
212
&utils_get_utf8_from_locale,
213
&utils_remove_ext_from_filename,
215
&utils_get_setting_boolean,
216
&utils_get_setting_integer,
217
&utils_get_setting_string,
221
&utils_get_date_time,
223
&utils_string_replace_first,
224
&utils_str_middle_truncate,
225
&utils_str_remove_chars,
226
&utils_get_file_list_full,
227
&utils_copy_environment,
228
&utils_find_open_xml_tag,
229
&utils_find_open_xml_tag_pos
232
static UIUtilsFuncs uiutils_funcs = {
234
&ui_frame_new_with_alignment,
238
&ui_button_new_with_image,
239
&ui_add_document_sensitive,
240
&ui_widget_set_tooltip_text,
241
&ui_image_menu_item_new,
243
&ui_progress_bar_start,
244
&ui_progress_bar_stop,
245
&ui_entry_add_clear_icon,
246
&ui_menu_add_document_items,
247
&ui_widget_modify_font_from_string,
248
&ui_is_keyval_enter_or_return,
249
&ui_get_gtk_settings_integer,
250
&ui_combo_box_add_to_history,
251
&ui_menu_add_document_items_sorted
254
static DialogFuncs dialog_funcs = {
255
&dialogs_show_question,
256
&dialogs_show_msgbox,
257
&dialogs_show_save_as,
258
&dialogs_show_input_numeric,
262
/* Macro to prevent confusing macro being generated in geanyfunctions.h */
263
#define _lookup_widget_macro ui_lookup_widget
266
static SupportFuncs support_funcs = {
267
&_lookup_widget_macro
270
static MsgWinFuncs msgwin_funcs = {
272
&msgwin_compiler_add,
276
&msgwin_set_messages_dir
279
static EncodingFuncs encoding_funcs = {
280
&encodings_convert_to_utf8,
281
&encodings_convert_to_utf8_from_charset,
282
&encodings_get_charset_from_index
285
static KeybindingFuncs keybindings_funcs = {
286
&keybindings_send_command,
287
&keybindings_set_item,
288
&keybindings_get_item
291
static TagManagerFuncs tagmanager_funcs = {
294
&tm_workspace_add_object,
295
&tm_source_file_update,
296
&tm_work_object_free,
297
&tm_workspace_remove_object
300
static SearchFuncs search_funcs = {
301
&search_show_find_in_files_dialog
304
static HighlightingFuncs highlighting_funcs = {
305
&highlighting_get_style,
306
&highlighting_set_styles,
307
&highlighting_is_string_style,
308
&highlighting_is_comment_style,
309
&highlighting_is_code_style
312
static FiletypeFuncs filetype_funcs = {
313
&filetypes_detect_from_file,
314
&filetypes_lookup_by_name,
316
&filetypes_get_display_name,
317
&filetypes_get_sorted_by_name
320
static NavQueueFuncs navqueue_funcs = {
324
static MainFuncs main_funcs = {
325
&main_reload_configuration,
330
static StashFuncs stash_funcs = {
332
&stash_group_add_boolean,
333
&stash_group_add_integer,
334
&stash_group_add_string,
335
&stash_group_add_string_vector,
336
&stash_group_load_from_key_file,
337
&stash_group_save_to_key_file,
339
&stash_group_load_from_file,
340
&stash_group_save_to_file,
341
&stash_group_add_toggle_button,
342
&stash_group_add_radio_buttons,
343
&stash_group_add_spin_button_integer,
344
&stash_group_add_combo_box,
345
&stash_group_add_combo_box_entry,
346
&stash_group_add_entry,
347
&stash_group_add_widget_property,
348
&stash_group_display,
352
static SymbolsFuncs symbols_funcs = {
353
&symbols_get_context_separator
356
static GeanyFunctions geany_functions = {
381
static GeanyData geany_data;
385
geany_data_init(void)
408
/* Prevent the same plugin filename being loaded more than once.
409
* Note: g_module_name always returns the .so name, even when Plugin::filename is a .la file. */
411
plugin_loaded(GModule *module)
413
gchar *basename_module, *basename_loaded;
416
basename_module = g_path_get_basename(g_module_name(module));
417
for (item = plugin_list; item != NULL; item = g_list_next(item))
419
basename_loaded = g_path_get_basename(
420
g_module_name(((Plugin*)item->data)->module));
422
if (utils_str_equal(basename_module, basename_loaded))
424
g_free(basename_loaded);
425
g_free(basename_module);
428
g_free(basename_loaded);
430
/* Look also through the list of active plugins. This prevents problems when we have the same
431
* plugin in libdir/geany/ AND in configdir/plugins/ and the one in libdir/geany/ is loaded
432
* as active plugin. The plugin manager list would only take the one in configdir/geany/ and
433
* the plugin manager would list both plugins. Additionally, unloading the active plugin
434
* would cause a crash. */
435
for (item = active_plugin_list; item != NULL; item = g_list_next(item))
437
basename_loaded = g_path_get_basename(g_module_name(((Plugin*)item->data)->module));
439
if (utils_str_equal(basename_module, basename_loaded))
441
g_free(basename_loaded);
442
g_free(basename_module);
445
g_free(basename_loaded);
447
g_free(basename_module);
452
static Plugin *find_active_plugin_by_name(const gchar *filename)
456
g_return_val_if_fail(filename, FALSE);
458
for (item = active_plugin_list; item != NULL; item = g_list_next(item))
460
if (utils_str_equal(filename, ((Plugin*)item->data)->filename))
469
plugin_check_version(GModule *module)
471
gint (*version_check)(gint) = NULL;
473
g_module_symbol(module, "plugin_version_check", (void *) &version_check);
475
if (G_UNLIKELY(! version_check))
477
geany_debug("Plugin \"%s\" has no plugin_version_check() function - ignoring plugin!",
478
g_module_name(module));
483
gint result = version_check(GEANY_ABI_VERSION);
487
msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this "
488
"release of Geany - please recompile it."), g_module_name(module));
489
geany_debug("Plugin \"%s\" is not binary compatible with this "
490
"release of Geany - recompile it.", g_module_name(module));
493
if (result > GEANY_API_VERSION)
495
geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).",
496
g_module_name(module), result);
504
static void add_callbacks(Plugin *plugin, PluginCallback *callbacks)
511
cb = &callbacks[len];
512
if (!cb->signal_name || !cb->callback)
519
for (i = 0; i < len; i++)
523
plugin_signal_connect(&plugin->public, NULL, cb->signal_name, cb->after,
524
cb->callback, cb->user_data);
529
static void read_key_group(Plugin *plugin)
531
GeanyKeyGroupInfo *p_key_info;
532
GeanyKeyGroup **p_key_group;
534
g_module_symbol(plugin->module, "plugin_key_group_info", (void *) &p_key_info);
535
g_module_symbol(plugin->module, "plugin_key_group", (void *) &p_key_group);
536
if (p_key_info && p_key_group)
538
GeanyKeyGroupInfo *key_info = p_key_info;
541
geany_debug("Ignoring plugin_key_group symbol for plugin '%s' - "
542
"use plugin_set_key_group() instead to allocate keybindings dynamically.",
548
GeanyKeyGroup *key_group =
549
plugin_set_key_group(&plugin->public, key_info->name, key_info->count, NULL);
551
*p_key_group = key_group;
554
geany_debug("Ignoring plugin_key_group_info symbol for plugin '%s' - "
555
"count field is zero. Maybe use plugin_set_key_group() instead?",
559
else if (p_key_info || p_key_group)
560
geany_debug("Ignoring only one of plugin_key_group[_info] symbols defined for plugin '%s'. "
561
"Maybe use plugin_set_key_group() instead?",
566
static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
568
const Plugin *pa = a;
569
const Plugin *pb = b;
571
return strcmp(pa->info.name, pb->info.name);
576
plugin_init(Plugin *plugin)
578
GeanyPlugin **p_geany_plugin;
579
PluginCallback *callbacks;
581
PluginFields **plugin_fields;
583
/* set these symbols before plugin_init() is called
584
* we don't set geany_functions and geany_data since they are set directly by plugin_new() */
585
g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin);
587
*p_geany_plugin = &plugin->public;
588
g_module_symbol(plugin->module, "plugin_info", (void *) &p_info);
590
*p_info = &plugin->info;
591
g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields);
593
*plugin_fields = &plugin->fields;
594
read_key_group(plugin);
596
/* start the plugin */
597
g_return_if_fail(plugin->init);
598
plugin->init(&geany_data);
600
/* store some function pointers for later use */
601
g_module_symbol(plugin->module, "plugin_configure", (void *) &plugin->configure);
602
g_module_symbol(plugin->module, "plugin_configure_single", (void *) &plugin->configure_single);
603
if (app->debug_mode && plugin->configure && plugin->configure_single)
604
g_warning("Plugin '%s' implements plugin_configure_single() unnecessarily - "
605
"only plugin_configure() will be used!",
608
g_module_symbol(plugin->module, "plugin_help", (void *) &plugin->help);
609
g_module_symbol(plugin->module, "plugin_cleanup", (void *) &plugin->cleanup);
610
if (plugin->cleanup == NULL)
613
g_warning("Plugin '%s' has no plugin_cleanup() function - there may be memory leaks!",
617
/* now read any plugin-owned data that might have been set in plugin_init() */
619
if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
621
ui_add_document_sensitive(plugin->fields.menu_item);
624
g_module_symbol(plugin->module, "plugin_callbacks", (void *) &callbacks);
626
add_callbacks(plugin, callbacks);
628
/* remember which plugins are active.
629
* keep list sorted so tools menu items and plugin preference tabs are
630
* sorted by plugin name */
631
active_plugin_list = g_list_insert_sorted(active_plugin_list, plugin, cmp_plugin_names);
633
geany_debug("Loaded: %s (%s)", plugin->filename,
634
NVL(plugin->info.name, "<Unknown>"));
638
/* Load and optionally init a plugin.
639
* init_plugin decides whether the plugin's plugin_init() function should be called or not. If it is
640
* called, the plugin will be started, if not the plugin will be read only (for the list of
641
* available plugins in the plugin manager).
642
* When add_to_list is set, the plugin will be added to the plugin manager's plugin_list. */
644
plugin_new(const gchar *fname, gboolean init_plugin, gboolean add_to_list)
648
GeanyData **p_geany_data;
649
GeanyFunctions **p_geany_functions;
650
void (*plugin_set_info)(PluginInfo*);
652
g_return_val_if_fail(fname, NULL);
653
g_return_val_if_fail(g_module_supported(), NULL);
655
/* find the plugin in the list of already loaded, active plugins and use it, otherwise
657
plugin = find_active_plugin_by_name(fname);
660
geany_debug("Plugin \"%s\" already loaded.", fname);
663
/* do not add the to list twice */
664
if (g_list_find(plugin_list, plugin) != NULL)
667
plugin_list = g_list_prepend(plugin_list, plugin);
672
/* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime,
673
* causing a segfault. Without that flag the module will safely fail to load.
674
* G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would
675
* otherwise not be detected due to the shadowing of Geany's app variable.
676
* Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info()
677
* function from a plugin will be shadowed. */
678
module = g_module_open(fname, G_MODULE_BIND_LOCAL);
681
geany_debug("Can't load plugin: %s", g_module_error());
685
if (plugin_loaded(module))
687
geany_debug("Plugin \"%s\" already loaded.", fname);
689
if (! g_module_close(module))
690
g_warning("%s: %s", fname, g_module_error());
694
if (! plugin_check_version(module))
696
if (! g_module_close(module))
697
g_warning("%s: %s", fname, g_module_error());
701
g_module_symbol(module, "plugin_set_info", (void *) &plugin_set_info);
702
if (plugin_set_info == NULL)
704
geany_debug("No plugin_set_info() defined for \"%s\" - ignoring plugin!", fname);
706
if (! g_module_close(module))
707
g_warning("%s: %s", fname, g_module_error());
711
plugin = g_new0(Plugin, 1);
713
/* set basic fields here to allow plugins to call Geany functions in set_info() */
714
g_module_symbol(module, "geany_data", (void *) &p_geany_data);
716
*p_geany_data = &geany_data;
717
g_module_symbol(module, "geany_functions", (void *) &p_geany_functions);
718
if (p_geany_functions)
719
*p_geany_functions = &geany_functions;
721
/* read plugin name, etc. */
722
plugin_set_info(&plugin->info);
723
if (G_UNLIKELY(! NZV(plugin->info.name)))
725
geany_debug("No plugin name set in plugin_set_info() for \"%s\" - ignoring plugin!",
728
if (! g_module_close(module))
729
g_warning("%s: %s", fname, g_module_error());
734
g_module_symbol(module, "plugin_init", (void *) &plugin->init);
735
if (plugin->init == NULL)
737
geany_debug("Plugin '%s' has no plugin_init() function - ignoring plugin!",
740
if (! g_module_close(module))
741
g_warning("%s: %s", fname, g_module_error());
745
/*geany_debug("Initializing plugin '%s'", plugin->info.name);*/
747
plugin->filename = g_strdup(fname);
748
plugin->module = module;
749
plugin->public.info = &plugin->info;
750
plugin->public.priv = plugin;
756
plugin_list = g_list_prepend(plugin_list, plugin);
762
static void remove_callbacks(Plugin *plugin)
764
GArray *signal_ids = plugin->signal_ids;
765
SignalConnection *sc;
767
if (signal_ids == NULL)
770
foreach_array(SignalConnection, sc, signal_ids)
771
g_signal_handler_disconnect(sc->object, sc->handler_id);
773
g_array_free(signal_ids, TRUE);
777
static void remove_sources(Plugin *plugin)
781
item = plugin->sources;
784
GList *next = item->next; /* cache the next pointer because current item will be freed */
786
g_source_destroy(item->data);
789
/* don't free the list here, it is allocated inside each source's data */
793
static gboolean is_active_plugin(Plugin *plugin)
795
return (g_list_find(active_plugin_list, plugin) != NULL);
799
/* Clean up anything used by an active plugin */
801
plugin_cleanup(Plugin *plugin)
808
remove_callbacks(plugin);
809
remove_sources(plugin);
811
if (plugin->key_group)
812
keybindings_free_group(plugin->key_group);
814
widget = plugin->toolbar_separator.widget;
816
gtk_widget_destroy(widget);
818
geany_debug("Unloaded: %s", plugin->filename);
823
plugin_free(Plugin *plugin)
825
g_return_if_fail(plugin);
826
g_return_if_fail(plugin->module);
828
if (is_active_plugin(plugin))
829
plugin_cleanup(plugin);
831
active_plugin_list = g_list_remove(active_plugin_list, plugin);
833
if (! g_module_close(plugin->module))
834
g_warning("%s: %s", plugin->filename, g_module_error());
836
plugin_list = g_list_remove(plugin_list, plugin);
838
g_free(plugin->filename);
844
/* load active plugins at startup */
846
load_active_plugins(void)
850
if (active_plugins_pref == NULL || (len = g_strv_length(active_plugins_pref)) == 0)
853
for (i = 0; i < len; i++)
855
const gchar *fname = active_plugins_pref[i];
857
if (NZV(fname) && g_file_test(fname, G_FILE_TEST_EXISTS))
859
if (plugin_new(fname, TRUE, FALSE) == NULL)
860
failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname));
867
load_plugins_from_path(const gchar *path)
873
list = utils_get_file_list(path, NULL, NULL);
875
for (item = list; item != NULL; item = g_slist_next(item))
877
tmp = strrchr(item->data, '.');
878
if (tmp == NULL || utils_str_casecmp(tmp, "." G_MODULE_SUFFIX) != 0)
881
fname = g_strconcat(path, G_DIR_SEPARATOR_S, item->data, NULL);
882
if (plugin_new(fname, FALSE, TRUE))
887
g_slist_foreach(list, (GFunc) g_free, NULL);
891
geany_debug("Found %d plugin(s) in '%s'.", count, path);
895
static gchar *get_plugin_path(void)
899
gchar *install_dir = win32_get_installation_dir();
901
path = g_strconcat(install_dir, "\\lib", NULL);
906
return g_strconcat(GEANY_LIBDIR, G_DIR_SEPARATOR_S "geany", NULL);
911
static gboolean validate_custom_plugin_path(const gchar *plugin_path_custom,
912
const gchar *plugin_path_config,
913
const gchar *plugin_path_system)
915
/* check whether the custom plugin path is one of the system or user plugin paths
917
if (utils_str_equal(plugin_path_custom, plugin_path_config) ||
918
utils_str_equal(plugin_path_custom, plugin_path_system))
925
/* Load (but don't initialize) all plugins for the Plugin Manager dialog */
926
static void load_all_plugins(void)
928
gchar *plugin_path_config;
929
gchar *plugin_path_system;
931
plugin_path_config = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "plugins", NULL);
932
plugin_path_system = get_plugin_path();
934
/* first load plugins in ~/.config/geany/plugins/ */
935
load_plugins_from_path(plugin_path_config);
937
/* load plugins from a custom path */
938
if (NZV(prefs.custom_plugin_path))
940
gchar *plugin_path_custom = utils_get_locale_from_utf8(prefs.custom_plugin_path);
941
utils_tidy_path(plugin_path_custom);
943
if (validate_custom_plugin_path(plugin_path_custom, plugin_path_config, plugin_path_system))
944
load_plugins_from_path(plugin_path_custom);
946
g_free(plugin_path_custom);
949
/* finally load plugins from $prefix/lib/geany */
950
load_plugins_from_path(plugin_path_system);
952
g_free(plugin_path_config);
953
g_free(plugin_path_system);
957
static void on_tools_menu_show(GtkWidget *menu_item, G_GNUC_UNUSED gpointer user_data)
959
GList *item, *list = gtk_container_get_children(GTK_CONTAINER(menu_item));
961
gboolean have_plugin_menu_items = FALSE;
963
for (item = list; item != NULL; item = g_list_next(item))
965
if (item->data == menu_separator)
967
if (i < g_list_length(list) - 1)
969
have_plugin_menu_items = TRUE;
977
ui_widget_show_hide(menu_separator, have_plugin_menu_items);
981
/* Calling this starts up plugin support */
982
void plugins_load_active(void)
990
widget = gtk_separator_menu_item_new();
991
gtk_widget_show(widget);
992
gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), widget);
994
widget = gtk_menu_item_new_with_mnemonic(_("_Plugin Manager"));
995
gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), widget);
996
gtk_widget_show(widget);
997
g_signal_connect(widget, "activate", G_CALLBACK(pm_show_dialog), NULL);
999
menu_separator = gtk_separator_menu_item_new();
1000
gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), menu_separator);
1001
g_signal_connect(main_widgets.tools_menu, "show", G_CALLBACK(on_tools_menu_show), NULL);
1003
load_active_plugins();
1007
static void update_active_plugins_pref(void)
1011
gsize count = g_list_length(active_plugin_list) + g_list_length(failed_plugins_list);
1013
g_strfreev(active_plugins_pref);
1017
active_plugins_pref = NULL;
1021
active_plugins_pref = g_new0(gchar*, count + 1);
1023
for (list = g_list_first(active_plugin_list); list != NULL; list = list->next)
1025
Plugin *plugin = list->data;
1027
active_plugins_pref[i] = g_strdup(plugin->filename);
1030
for (list = g_list_first(failed_plugins_list); list != NULL; list = list->next)
1032
const gchar *fname = list->data;
1034
active_plugins_pref[i] = g_strdup(fname);
1037
active_plugins_pref[i] = NULL;
1041
static void on_save_settings(GKeyFile *config)
1043
/* if plugins are disabled, don't clear list of active plugins */
1045
update_active_plugins_pref();
1049
/* called even if plugin support is disabled */
1050
void plugins_init(void)
1054
group = stash_group_new("plugins");
1055
configuration_add_pref_group(group, TRUE);
1057
stash_group_add_toggle_button(group, &prefs.load_plugins,
1058
"load_plugins", TRUE, "check_plugins");
1059
stash_group_add_entry(group, &prefs.custom_plugin_path,
1060
"custom_plugin_path", "", "extra_plugin_path_entry");
1062
g_signal_connect(geany_object, "save-settings", G_CALLBACK(on_save_settings), NULL);
1063
stash_group_add_string_vector(group, &active_plugins_pref, "active_plugins", NULL);
1067
/* called even if plugin support is disabled */
1068
void plugins_finalize(void)
1070
if (failed_plugins_list != NULL)
1072
g_list_foreach(failed_plugins_list, (GFunc) g_free, NULL);
1073
g_list_free(failed_plugins_list);
1075
if (active_plugin_list != NULL)
1077
g_list_foreach(active_plugin_list, (GFunc) plugin_free, NULL);
1078
g_list_free(active_plugin_list);
1080
g_strfreev(active_plugins_pref);
1084
/* Check whether there are any plugins loaded which provide a configure symbol */
1085
gboolean plugins_have_preferences(void)
1089
if (active_plugin_list == NULL)
1092
foreach_list(item, active_plugin_list)
1094
Plugin *plugin = item->data;
1095
if (plugin->configure != NULL || plugin->configure_single != NULL)
1103
/* Plugin Manager */
1107
PLUGIN_COLUMN_CHECK = 0,
1110
PLUGIN_COLUMN_PLUGIN,
1112
PM_BUTTON_CONFIGURE,
1120
GtkListStore *store;
1121
GtkWidget *plugin_label;
1122
GtkWidget *description_label;
1123
GtkWidget *author_label;
1124
GtkWidget *configure_button;
1125
GtkWidget *help_button;
1127
PluginManagerWidgets;
1129
static PluginManagerWidgets pm_widgets;
1132
static void pm_update_buttons(Plugin *p)
1136
is_active = is_active_plugin(p);
1137
gtk_widget_set_sensitive(pm_widgets.configure_button,
1138
(p->configure || p->configure_single) && is_active);
1139
gtk_widget_set_sensitive(pm_widgets.help_button, p->help != NULL && is_active);
1143
static void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data)
1146
GtkTreeModel *model;
1149
if (gtk_tree_selection_get_selected(selection, &model, &iter))
1151
gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
1159
/* Translators: <plugin name> <plugin version> */
1160
text = g_strdup_printf(_("%s %s"), pi->name, pi->version);
1161
gtk_label_set_text(GTK_LABEL(pm_widgets.plugin_label), text);
1162
gtk_label_set_text(GTK_LABEL(pm_widgets.description_label), pi->description);
1163
gtk_label_set_text(GTK_LABEL(pm_widgets.author_label), pi->author);
1166
pm_update_buttons(p);
1172
static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
1174
gboolean old_state, state;
1177
GtkTreePath *path = gtk_tree_path_new_from_string(pth);
1180
gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &iter, path);
1181
gtk_tree_path_free(path);
1183
gtk_tree_model_get(GTK_TREE_MODEL(pm_widgets.store), &iter,
1184
PLUGIN_COLUMN_CHECK, &old_state, PLUGIN_COLUMN_PLUGIN, &p, -1);
1186
/* no plugins item */
1190
state = ! old_state; /* toggle the state */
1192
/* save the filename of the plugin */
1193
file_name = g_strdup(p->filename);
1195
/* unload plugin module */
1197
/* save shortcuts (only need this group, but it doesn't take long) */
1198
keybindings_write_to_file();
1202
/* reload plugin module and initialize it if item is checked */
1203
p = plugin_new(file_name, state, TRUE);
1206
/* plugin file may no longer be on disk, or is now incompatible */
1207
gtk_list_store_remove(pm_widgets.store, &iter);
1212
keybindings_load_keyfile(); /* load shortcuts */
1215
gtk_list_store_set(pm_widgets.store, &iter,
1216
PLUGIN_COLUMN_CHECK, state,
1217
PLUGIN_COLUMN_PLUGIN, p, -1);
1219
/* set again the sensitiveness of the configure and help buttons */
1220
pm_update_buttons(p);
1226
static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store)
1228
GtkCellRenderer *text_renderer, *checkbox_renderer;
1229
GtkTreeViewColumn *column;
1232
GtkTreeSelection *sel;
1234
checkbox_renderer = gtk_cell_renderer_toggle_new();
1235
column = gtk_tree_view_column_new_with_attributes(
1236
_("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL);
1237
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1238
g_signal_connect(checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL);
1240
text_renderer = gtk_cell_renderer_text_new();
1241
column = gtk_tree_view_column_new_with_attributes(
1242
_("Plugin"), text_renderer, "text", PLUGIN_COLUMN_NAME, NULL);
1243
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1245
text_renderer = gtk_cell_renderer_text_new();
1246
g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1247
column = gtk_tree_view_column_new_with_attributes(
1248
_("File"), text_renderer, "text", PLUGIN_COLUMN_FILE, NULL);
1249
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1251
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
1252
gtk_tree_view_set_enable_search(GTK_TREE_VIEW(tree), FALSE);
1253
gtk_tree_sortable_set_sort_column_id(
1254
GTK_TREE_SORTABLE(store), PLUGIN_COLUMN_NAME, GTK_SORT_ASCENDING);
1256
/* selection handling */
1257
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
1258
gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
1259
g_signal_connect(sel, "changed", G_CALLBACK(pm_selection_changed), NULL);
1261
list = g_list_first(plugin_list);
1264
gtk_list_store_append(store, &iter);
1265
gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE,
1266
PLUGIN_COLUMN_NAME, _("No plugins available."),
1267
PLUGIN_COLUMN_FILE, "", PLUGIN_COLUMN_PLUGIN, NULL, -1);
1272
for (; list != NULL; list = list->next)
1276
gtk_list_store_append(store, &iter);
1277
gtk_list_store_set(store, &iter,
1278
PLUGIN_COLUMN_CHECK, is_active_plugin(p),
1279
PLUGIN_COLUMN_NAME, p->info.name,
1280
PLUGIN_COLUMN_FILE, p->filename,
1281
PLUGIN_COLUMN_PLUGIN, p,
1285
gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
1286
g_object_unref(store);
1290
static void pm_on_plugin_button_clicked(GtkButton *button, gpointer user_data)
1292
GtkTreeModel *model;
1293
GtkTreeSelection *selection;
1297
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree));
1298
if (gtk_tree_selection_get_selected(selection, &model, &iter))
1300
gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
1304
if (GPOINTER_TO_INT(user_data) == PM_BUTTON_CONFIGURE)
1305
plugin_show_configure(&p->public);
1306
else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_HELP && p->help != NULL)
1314
free_non_active_plugin(gpointer data, gpointer user_data)
1316
Plugin *plugin = data;
1318
/* don't do anything when closing the plugin manager and it is an active plugin */
1319
if (is_active_plugin(plugin))
1322
plugin_free(plugin);
1326
static void pm_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1328
if (plugin_list != NULL)
1330
/* remove all non-active plugins from the list */
1331
g_list_foreach(plugin_list, free_non_active_plugin, NULL);
1332
g_list_free(plugin_list);
1335
gtk_widget_destroy(GTK_WIDGET(dialog));
1339
static GtkWidget *create_table_label(const gchar *text)
1342
PangoAttrList *attrs;
1344
attrs = pango_attr_list_new();
1345
pango_attr_list_insert(attrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
1346
label = gtk_label_new(text);
1347
gtk_label_set_attributes(GTK_LABEL(label), attrs);
1348
gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1349
pango_attr_list_unref(attrs);
1355
static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
1357
GtkWidget *vbox, *vbox2, *label_vbox, *hbox, *swin, *label, *label2, *desc_win, *table, *paned;
1359
/* before showing the dialog, we need to create the list of available plugins */
1362
pm_widgets.dialog = gtk_dialog_new_with_buttons(_("Plugins"), GTK_WINDOW(main_widgets.window),
1363
GTK_DIALOG_DESTROY_WITH_PARENT,
1364
GTK_STOCK_OK, GTK_RESPONSE_CANCEL, NULL);
1365
vbox = ui_dialog_vbox_new(GTK_DIALOG(pm_widgets.dialog));
1366
gtk_widget_set_name(pm_widgets.dialog, "GeanyDialog");
1367
gtk_box_set_spacing(GTK_BOX(vbox), 6);
1369
gtk_window_set_default_size(GTK_WINDOW(pm_widgets.dialog), 500, 450);
1371
pm_widgets.tree = gtk_tree_view_new();
1372
pm_widgets.store = gtk_list_store_new(
1373
PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
1374
pm_prepare_treeview(pm_widgets.tree, pm_widgets.store);
1376
swin = gtk_scrolled_window_new(NULL, NULL);
1377
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
1378
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1379
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin), GTK_SHADOW_IN);
1380
gtk_container_add(GTK_CONTAINER(swin), pm_widgets.tree);
1382
label = geany_wrap_label_new(_("Choose which plugins should be loaded at startup:"));
1384
pm_widgets.configure_button = gtk_button_new_from_stock(GTK_STOCK_PREFERENCES);
1385
gtk_widget_set_sensitive(pm_widgets.configure_button, FALSE);
1386
g_signal_connect(pm_widgets.configure_button, "clicked",
1387
G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_CONFIGURE));
1389
pm_widgets.help_button = gtk_button_new_from_stock(GTK_STOCK_HELP);
1390
gtk_widget_set_sensitive(pm_widgets.help_button, FALSE);
1391
g_signal_connect(pm_widgets.help_button, "clicked",
1392
G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_HELP));
1394
label2 = gtk_label_new(_("<b>Plugin details:</b>"));
1395
gtk_label_set_use_markup(GTK_LABEL(label2), TRUE);
1396
gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
1398
table = gtk_table_new(3, 2, FALSE);
1399
gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1400
pm_widgets.plugin_label = geany_wrap_label_new(NULL);
1401
pm_widgets.description_label = geany_wrap_label_new(NULL);
1402
pm_widgets.author_label = geany_wrap_label_new(NULL);
1403
gtk_table_attach(GTK_TABLE(table), create_table_label(_("Plugin:")), 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
1404
gtk_table_attach(GTK_TABLE(table), create_table_label(_("Description:")), 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
1405
gtk_table_attach(GTK_TABLE(table), create_table_label(_("Author(s):")), 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
1406
gtk_table_attach(GTK_TABLE(table), pm_widgets.plugin_label, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
1407
gtk_table_attach(GTK_TABLE(table), pm_widgets.description_label, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
1408
gtk_table_attach(GTK_TABLE(table), pm_widgets.author_label, 1, 2, 2, 3, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
1410
desc_win = gtk_scrolled_window_new(NULL, NULL);
1411
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(desc_win),
1412
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1413
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(desc_win), table);
1415
hbox = gtk_hbox_new(FALSE, 0);
1416
gtk_box_pack_start(GTK_BOX(hbox), label2, TRUE, TRUE, 0);
1417
gtk_box_pack_start(GTK_BOX(hbox), pm_widgets.help_button, FALSE, FALSE, 4);
1418
gtk_box_pack_start(GTK_BOX(hbox), pm_widgets.configure_button, FALSE, FALSE, 0);
1420
label_vbox = gtk_vbox_new(FALSE, 3);
1421
gtk_box_pack_start(GTK_BOX(label_vbox), hbox, FALSE, FALSE, 0);
1422
gtk_box_pack_start(GTK_BOX(label_vbox), desc_win, TRUE, TRUE, 0);
1424
paned = gtk_vpaned_new();
1425
gtk_paned_pack1(GTK_PANED(paned), swin, TRUE, FALSE);
1426
gtk_paned_pack2(GTK_PANED(paned), label_vbox, FALSE, FALSE);
1428
vbox2 = gtk_vbox_new(FALSE, 3);
1429
gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5);
1430
gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
1432
g_signal_connect(pm_widgets.dialog, "response", G_CALLBACK(pm_dialog_response), NULL);
1434
gtk_container_add(GTK_CONTAINER(vbox), vbox2);
1435
gtk_widget_show_all(pm_widgets.dialog);