1 /* Python plugin for Claws-Mail
2 * Copyright (C) 2009 Holger Berndt
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "claws-features.h"
26 #include <glib/gi18n.h>
30 #include "common/hooks.h"
31 #include "common/plugin.h"
32 #include "common/version.h"
33 #include "common/utils.h"
36 #include "mainwindow.h"
37 #include "prefs_toolbar.h"
39 #include "python-shell.h"
40 #include "python-hooks.h"
41 #include "clawsmailmodule.h"
43 #define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
44 #define PYTHON_SCRIPTS_MAIN_DIR "main"
45 #define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
46 #define PYTHON_SCRIPTS_AUTO_DIR "auto"
47 #define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
48 #define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
49 #define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
50 #define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"
52 static GSList *menu_id_list = NULL;
53 static GSList *python_mainwin_scripts_id_list = NULL;
54 static GSList *python_mainwin_scripts_names = NULL;
55 static GSList *python_compose_scripts_names = NULL;
57 static GtkWidget *python_console = NULL;
59 static guint hook_compose_create;
61 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
64 GtkToggleAction *action;
66 mainwin = mainwindow_get_mainwindow();
67 action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
68 gtk_toggle_action_set_active(action, FALSE);
72 static void setup_python_console(void)
77 python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
78 gtk_widget_set_size_request(python_console, 600, 400);
80 vbox = gtk_vbox_new(FALSE, 0);
81 gtk_container_add(GTK_CONTAINER(python_console), vbox);
83 console = parasite_python_shell_new();
84 gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
86 g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
88 gtk_widget_show_all(python_console);
90 parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
93 static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
95 if(gtk_toggle_action_get_active(action)) {
97 setup_python_console();
98 gtk_widget_show(python_console);
101 gtk_widget_hide(python_console);
105 static void remove_python_scripts_menus(void)
110 mainwin = mainwindow_get_mainwindow();
113 for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
114 prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
117 for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
118 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
119 g_slist_free(python_mainwin_scripts_id_list);
120 python_mainwin_scripts_id_list = NULL;
123 for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
126 entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
127 action = gtk_action_group_get_action(mainwin->action_group, entry);
130 gtk_action_group_remove_action(mainwin->action_group, action);
133 g_slist_free(python_mainwin_scripts_names);
134 python_mainwin_scripts_names = NULL;
136 /* compose scripts */
137 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
138 prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
141 g_slist_free(python_compose_scripts_names);
142 python_compose_scripts_names = NULL;
145 static gchar* extract_filename(const gchar *str)
149 filename = g_strrstr(str, "/");
150 if(!filename || *(filename+1) == '\0') {
151 debug_print("Error: Could not extract filename from %s\n", str);
158 static void run_script_file(const gchar *filename, Compose *compose)
161 fp = fopen(filename, "r");
163 debug_print("Error: Could not open file '%s'\n", filename);
166 put_composewindow_into_module(compose);
167 if(PyRun_SimpleFile(fp, filename) == 0)
168 debug_print("Problem running script file '%s'\n", filename);
172 static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
174 gchar *auto_filepath;
176 /* execute auto/autofilename, if it exists */
177 auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
178 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
179 PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
180 if(file_exist(auto_filepath, FALSE))
181 run_script_file(auto_filepath, compose);
182 g_free(auto_filepath);
185 static void python_mainwin_script_callback(GtkAction *action, gpointer data)
189 filename = extract_filename(data);
192 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_MAIN_DIR, G_DIR_SEPARATOR_S, filename, NULL);
193 run_script_file(filename, NULL);
197 typedef struct _ComposeActionData ComposeActionData;
198 struct _ComposeActionData {
203 static void python_compose_script_callback(GtkAction *action, gpointer data)
206 ComposeActionData *dat = (ComposeActionData*)data;
208 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S, dat->name, NULL);
209 run_script_file(filename, dat->compose);
214 static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
217 script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
218 python_mainwin_script_callback(NULL, script);
222 static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
226 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
227 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
228 PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
230 run_script_file(filename, (Compose*)parent);
234 static char* make_sure_script_directory_exists(const gchar *subdir)
238 dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
239 if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
240 if(g_mkdir(dir, 0777) != 0)
241 retval = g_strdup_printf("Could not create directory '%s': %s", dir, g_strerror(errno));
247 static int make_sure_directories_exist(char **error)
249 const char* dirs[] = {
251 , PYTHON_SCRIPTS_MAIN_DIR
252 , PYTHON_SCRIPTS_COMPOSE_DIR
253 , PYTHON_SCRIPTS_AUTO_DIR
256 const char **dir = dirs;
261 *error = make_sure_script_directory_exists(*dir);
267 return (*error == NULL);
270 static void migrate_scripts_out_of_base_dir(void)
274 const char *filename;
277 base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
278 dir = g_dir_open(base_dir, 0, NULL);
283 dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
284 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
285 PYTHON_SCRIPTS_MAIN_DIR, NULL);
286 if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
287 if(g_mkdir(dest_dir, 0777) != 0) {
294 while((filename = g_dir_read_name(dir)) != NULL) {
296 filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
297 if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
299 dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
300 if(move_file(filepath, dest_file, FALSE) == 0)
301 debug_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
303 debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
313 static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
318 GtkActionEntry *entries;
320 /* create menu items */
321 entries = g_new0(GtkActionEntry, num_entries);
323 mainwin = mainwindow_get_mainwindow();
324 for(walk = filenames; walk; walk = walk->next) {
325 entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
326 entries[ii].label = walk->data;
327 entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
328 gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
331 for(ii = 0; ii < num_entries; ii++) {
334 python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
335 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
336 entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
337 python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));
339 prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
346 /* this function doesn't really create menu items, but prepares a list that can be used
347 * in the compose create hook. It does however register the scripts for the toolbar editor */
348 static void create_compose_menus_and_items(GSList *filenames)
351 for(walk = filenames; walk; walk = walk->next) {
352 python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
353 prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
357 static GtkActionEntry compose_tools_python_actions[] = {
358 {"Tools/PythonScripts", NULL, N_("Python scripts") },
361 static void ComposeActionData_destroy_cb(gpointer data)
363 ComposeActionData *dat = (ComposeActionData*)data;
368 static gboolean my_compose_create_hook(gpointer cw, gpointer data)
372 GtkActionEntry *entries;
373 GtkActionGroup *action_group;
374 Compose *compose = (Compose*)cw;
375 guint num_entries = g_slist_length(python_compose_scripts_names);
377 action_group = gtk_action_group_new("PythonPlugin");
378 gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
379 entries = g_new0(GtkActionEntry, num_entries);
381 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
382 ComposeActionData *dat;
384 entries[ii].name = walk->data;
385 entries[ii].label = walk->data;
386 entries[ii].callback = G_CALLBACK(python_compose_script_callback);
388 dat = g_new0(ComposeActionData, 1);
389 dat->name = g_strdup(walk->data);
390 dat->compose = compose;
392 gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
395 gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
398 "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
400 for(ii = 0; ii < num_entries; ii++) {
401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
402 entries[ii].name, GTK_UI_MANAGER_MENUITEM)
407 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
413 static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
417 GError *error = NULL;
418 const char *filename;
419 GSList *filenames = NULL;
423 scripts_dir = g_strconcat(get_rc_dir(),
424 G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
425 G_DIR_SEPARATOR_S, subdir,
427 debug_print("Refreshing: %s\n", scripts_dir);
429 dir = g_dir_open(scripts_dir, 0, &error);
433 debug_print("Could not open directory '%s': %s\n", subdir, error->message);
440 while((filename = g_dir_read_name(dir)) != NULL) {
443 fn = g_strdup(filename);
444 filenames = g_slist_prepend(filenames, fn);
449 if(toolbar_type == TOOLBAR_MAIN)
450 create_mainwindow_menus_and_items(filenames, num_entries);
451 else if(toolbar_type == TOOLBAR_COMPOSE)
452 create_compose_menus_and_items(filenames);
455 for(walk = filenames; walk; walk = walk->next)
457 g_slist_free(filenames);
460 static void browse_python_scripts_dir(GtkAction *action, gpointer data)
463 GdkAppLaunchContext *launch_context;
464 GError *error = NULL;
467 mainwin = mainwindow_get_mainwindow();
469 debug_print("Browse Python scripts: Problems getting the mainwindow\n");
472 launch_context = gdk_app_launch_context_new();
473 gdk_app_launch_context_set_screen(launch_context, gtk_widget_get_screen(mainwin->window));
474 uri = g_strconcat("file://", get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
475 g_app_info_launch_default_for_uri(uri, G_APP_LAUNCH_CONTEXT(launch_context), &error);
478 debug_print("Could not open scripts dir browser: '%s'\n", error->message);
482 g_object_unref(launch_context);
486 static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
488 remove_python_scripts_menus();
490 migrate_scripts_out_of_base_dir();
492 refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
493 refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
496 static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
497 {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
498 NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
501 static GtkActionEntry mainwindow_tools_python_actions[] = {
502 {"Tools/PythonScripts", NULL, N_("Python scripts") },
503 {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
504 NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
505 {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
506 NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
507 {"Tools/PythonScripts/---", NULL, "---" },
510 static int python_menu_init(char **error)
515 mainwin = mainwindow_get_mainwindow();
517 *error = g_strdup("Could not get main window");
521 gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
522 gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);
524 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
525 "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
526 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
528 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
529 "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
530 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
532 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
533 "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
534 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
536 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
537 "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
538 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
540 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
541 "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
542 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
544 refresh_python_scripts_menus(NULL, NULL);
549 static void python_menu_done(void)
553 mainwin = mainwindow_get_mainwindow();
555 if(mainwin && !claws_is_exiting()) {
558 remove_python_scripts_menus();
560 for(walk = menu_id_list; walk; walk = walk->next)
561 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
562 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
563 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
564 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
565 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
566 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
571 static PyObject *get_StringIO_instance(void)
573 PyObject *module_StringIO = NULL;
574 PyObject *class_StringIO = NULL;
575 PyObject *inst_StringIO = NULL;
577 module_StringIO = PyImport_ImportModule("cStringIO");
578 if(!module_StringIO) {
579 debug_print("Error getting traceback: Could not import module cStringIO\n");
583 class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
584 if(!class_StringIO) {
585 debug_print("Error getting traceback: Could not get StringIO class\n");
589 inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
591 debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
596 Py_XDECREF(module_StringIO);
597 Py_XDECREF(class_StringIO);
599 return inst_StringIO;
602 static char* get_exception_information(PyObject *inst_StringIO)
605 PyObject *meth_getvalue = NULL;
606 PyObject *result_getvalue = NULL;
611 if(PySys_SetObject("stderr", inst_StringIO) != 0) {
612 debug_print("Error getting traceback: Could not set sys.stderr to a StringIO instance\n");
616 meth_getvalue = PyObject_GetAttrString(inst_StringIO, "getvalue");
618 debug_print("Error getting traceback: Could not get the getvalue method of the StringIO instance\n");
624 result_getvalue = PyObject_CallObject(meth_getvalue, NULL);
625 if(!result_getvalue) {
626 debug_print("Error getting traceback: Could not call the getvalue method of the StringIO instance\n");
630 retval = g_strdup(PyString_AsString(result_getvalue));
634 Py_XDECREF(meth_getvalue);
635 Py_XDECREF(result_getvalue);
637 return retval ? retval : g_strdup("Unspecified error occured");
641 gint plugin_init(gchar **error)
643 PyObject *inst_StringIO = NULL;
646 if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
650 hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
651 if(hook_compose_create == (guint)-1) {
652 *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
656 /* script directories */
657 if(!make_sure_directories_exist(error))
660 /* initialize python interpreter */
663 /* The Python C API only offers to print an exception to sys.stderr. In order to catch it
664 * in a string, a StringIO object is created, to which sys.stderr can be redirected in case
665 * an error occured. */
666 inst_StringIO = get_StringIO_instance();
668 /* initialize Claws Mail Python module */
670 if(PyErr_Occurred()) {
671 *error = get_exception_information(inst_StringIO);
675 if(PyRun_SimpleString("import clawsmail") == -1) {
676 *error = g_strdup("Error importing the clawsmail module");
680 /* initialize python interactive shell */
681 if(!parasite_python_init(error)) {
685 /* load menu options */
686 if(!python_menu_init(error)) {
690 /* problems here are not fatal */
691 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
693 debug_print("Python plugin loaded\n");
698 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
699 Py_XDECREF(inst_StringIO);
703 gboolean plugin_done(void)
705 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
707 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
712 gtk_widget_destroy(python_console);
713 python_console = NULL;
716 /* finialize python interpreter */
719 debug_print("Python plugin done and unloaded.\n");
723 const gchar *plugin_name(void)
728 const gchar *plugin_desc(void)
730 return _("This plugin provides Python integration features.\n"
731 "Python code can be entered interactively into an embedded Python console, "
732 "under Tools -> Show Python console, or stored in scripts.\n\n"
733 "These scripts are then available via the menu. You can assign "
734 "keyboard shortcuts to them just like it is done with other menu items. "
735 "You can also put buttons for script invocation into the toolbars "
736 "using Claws Mail's builtin toolbar editor.\n\n"
737 "You can provide scripts working on the main window by placing files "
738 "into ~/.claws-mail/python-scripts/main.\n\n"
739 "You can also provide scripts working on an open compose window "
740 "by placing files into ~/.claws-mail/python-scripts/compose.\n\n"
741 "The folder ~/.claws-mail/python-scripts/auto/ may contain some "
742 "scripts that are automatically executed when certain events "
743 "occur. Currently, the following files in this directory "
744 "are recognised:\n\n"
746 "Gets executed whenever a compose window is opened, no matter "
747 "if that opening happened as a result of composing a new message, "
748 "replying or forwarding a message.\n\n"
750 "Executed at plugin load\n\n"
752 "Executed at plugin unload\n\n"
753 "\nFor the most up-to-date API documentation, type\n"
754 "\n help(clawsmail)\n"
755 "\nin the interactive Python console.\n"
756 "\nThe source distribution of this plugin comes with various example scripts "
757 "in the \"examples\" subdirectory. If you wrote a script that you would be "
758 "interested in sharing, feel free to send it to me to have it considered "
759 "for inclusion in the examples.\n"
760 "\nFeedback to <berndth@gmx.de> is welcome.");
763 const gchar *plugin_type(void)
768 const gchar *plugin_licence(void)
773 const gchar *plugin_version(void)
778 struct PluginFeature *plugin_provides(void)
780 static struct PluginFeature features[] =
781 { {PLUGIN_UTILITY, N_("Python integration")},
782 {PLUGIN_NOTHING, NULL}};