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"
42 #include "file-utils.h"
44 #define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
45 #define PYTHON_SCRIPTS_MAIN_DIR "main"
46 #define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
47 #define PYTHON_SCRIPTS_AUTO_DIR "auto"
48 #define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
49 #define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
50 #define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
51 #define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"
53 static GSList *menu_id_list = NULL;
54 static GSList *python_mainwin_scripts_id_list = NULL;
55 static GSList *python_mainwin_scripts_names = NULL;
56 static GSList *python_compose_scripts_names = NULL;
58 static GtkWidget *python_console = NULL;
60 static gulong hook_compose_create = 0;
62 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
65 GtkToggleAction *action;
67 mainwin = mainwindow_get_mainwindow();
68 action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
69 gtk_toggle_action_set_active(action, FALSE);
73 static void setup_python_console(void)
78 python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
79 gtk_widget_set_size_request(python_console, 600, 400);
81 vbox = gtk_vbox_new(FALSE, 0);
82 gtk_container_add(GTK_CONTAINER(python_console), vbox);
84 console = parasite_python_shell_new();
85 gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
87 g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
89 gtk_widget_show_all(python_console);
91 parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
94 static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
96 if(gtk_toggle_action_get_active(action)) {
98 setup_python_console();
99 gtk_widget_show(python_console);
102 gtk_widget_hide(python_console);
106 static void remove_python_scripts_menus(void)
111 mainwin = mainwindow_get_mainwindow();
114 for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
115 prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
118 for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
119 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
120 g_slist_free(python_mainwin_scripts_id_list);
121 python_mainwin_scripts_id_list = NULL;
124 for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
127 entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
128 action = gtk_action_group_get_action(mainwin->action_group, entry);
131 gtk_action_group_remove_action(mainwin->action_group, action);
134 g_slist_free(python_mainwin_scripts_names);
135 python_mainwin_scripts_names = NULL;
137 /* compose scripts */
138 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
139 prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
142 g_slist_free(python_compose_scripts_names);
143 python_compose_scripts_names = NULL;
146 static gchar* extract_filename(const gchar *str)
150 filename = g_strrstr(str, "/");
151 if(!filename || *(filename+1) == '\0') {
152 debug_print("Error: Could not extract filename from %s\n", str);
159 static void run_script_file(const gchar *filename, Compose *compose)
162 fp = claws_fopen(filename, "r");
164 debug_print("Error: Could not open file '%s'\n", filename);
167 put_composewindow_into_module(compose);
168 if(PyRun_SimpleFile(fp, filename) == 0)
169 debug_print("Problem running script file '%s'\n", filename);
173 static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
175 gchar *auto_filepath;
177 /* execute auto/autofilename, if it exists */
178 auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
179 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
180 PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
181 if(file_exist(auto_filepath, FALSE))
182 run_script_file(auto_filepath, compose);
183 g_free(auto_filepath);
186 static void python_mainwin_script_callback(GtkAction *action, gpointer data)
190 filename = extract_filename(data);
193 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);
194 run_script_file(filename, NULL);
198 typedef struct _ComposeActionData ComposeActionData;
199 struct _ComposeActionData {
204 static void python_compose_script_callback(GtkAction *action, gpointer data)
207 ComposeActionData *dat = (ComposeActionData*)data;
209 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);
210 run_script_file(filename, dat->compose);
215 static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
218 script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
219 python_mainwin_script_callback(NULL, script);
223 static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
227 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
228 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
229 PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
231 run_script_file(filename, (Compose*)parent);
235 static char* make_sure_script_directory_exists(const gchar *subdir)
239 dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
240 if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
241 if(g_mkdir(dir, 0777) != 0)
242 retval = g_strdup_printf("Could not create directory '%s': %s", dir, g_strerror(errno));
248 static int make_sure_directories_exist(char **error)
250 const char* dirs[] = {
252 , PYTHON_SCRIPTS_MAIN_DIR
253 , PYTHON_SCRIPTS_COMPOSE_DIR
254 , PYTHON_SCRIPTS_AUTO_DIR
257 const char **dir = dirs;
262 *error = make_sure_script_directory_exists(*dir);
268 return (*error == NULL);
271 static void migrate_scripts_out_of_base_dir(void)
275 const char *filename;
278 base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
279 dir = g_dir_open(base_dir, 0, NULL);
284 dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
285 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
286 PYTHON_SCRIPTS_MAIN_DIR, NULL);
287 if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
288 if(g_mkdir(dest_dir, 0777) != 0) {
295 while((filename = g_dir_read_name(dir)) != NULL) {
297 filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
298 if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
300 dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
301 if(move_file(filepath, dest_file, FALSE) == 0)
302 debug_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
304 debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
314 static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
319 GtkActionEntry *entries;
321 /* create menu items */
322 entries = g_new0(GtkActionEntry, num_entries);
324 mainwin = mainwindow_get_mainwindow();
325 for(walk = filenames; walk; walk = walk->next) {
326 entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
327 entries[ii].label = walk->data;
328 entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
329 gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
332 for(ii = 0; ii < num_entries; ii++) {
335 python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
336 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
337 entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
338 python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));
340 prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
347 /* this function doesn't really create menu items, but prepares a list that can be used
348 * in the compose create hook. It does however register the scripts for the toolbar editor */
349 static void create_compose_menus_and_items(GSList *filenames)
352 for(walk = filenames; walk; walk = walk->next) {
353 python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
354 prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
358 static GtkActionEntry compose_tools_python_actions[] = {
359 {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
362 static void ComposeActionData_destroy_cb(gpointer data)
364 ComposeActionData *dat = (ComposeActionData*)data;
369 static gboolean my_compose_create_hook(gpointer cw, gpointer data)
373 GtkActionEntry *entries;
374 GtkActionGroup *action_group;
375 Compose *compose = (Compose*)cw;
376 guint num_entries = g_slist_length(python_compose_scripts_names);
378 action_group = gtk_action_group_new("PythonPlugin");
379 gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
380 entries = g_new0(GtkActionEntry, num_entries);
382 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
383 ComposeActionData *dat;
385 entries[ii].name = walk->data;
386 entries[ii].label = walk->data;
387 entries[ii].callback = G_CALLBACK(python_compose_script_callback);
389 dat = g_new0(ComposeActionData, 1);
390 dat->name = g_strdup(walk->data);
391 dat->compose = compose;
393 gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
396 gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
399 "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
401 for(ii = 0; ii < num_entries; ii++) {
402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
403 entries[ii].name, GTK_UI_MANAGER_MENUITEM)
408 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
414 static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
418 GError *error = NULL;
419 const char *filename;
420 GSList *filenames = NULL;
424 scripts_dir = g_strconcat(get_rc_dir(),
425 G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
426 G_DIR_SEPARATOR_S, subdir,
428 debug_print("Refreshing: %s\n", scripts_dir);
430 dir = g_dir_open(scripts_dir, 0, &error);
434 debug_print("Could not open directory '%s': %s\n", subdir, error->message);
441 while((filename = g_dir_read_name(dir)) != NULL) {
444 fn = g_strdup(filename);
445 filenames = g_slist_prepend(filenames, fn);
450 if(toolbar_type == TOOLBAR_MAIN)
451 create_mainwindow_menus_and_items(filenames, num_entries);
452 else if(toolbar_type == TOOLBAR_COMPOSE)
453 create_compose_menus_and_items(filenames);
456 for(walk = filenames; walk; walk = walk->next)
458 g_slist_free(filenames);
461 static void browse_python_scripts_dir(GtkAction *action, gpointer data)
464 GdkAppLaunchContext *launch_context;
465 GError *error = NULL;
468 mainwin = mainwindow_get_mainwindow();
470 debug_print("Browse Python scripts: Problems getting the mainwindow\n");
473 launch_context = gdk_app_launch_context_new();
474 gdk_app_launch_context_set_screen(launch_context, gtk_widget_get_screen(mainwin->window));
475 uri = g_strconcat("file://", get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
476 g_app_info_launch_default_for_uri(uri, G_APP_LAUNCH_CONTEXT(launch_context), &error);
479 debug_print("Could not open scripts dir browser: '%s'\n", error->message);
483 g_object_unref(launch_context);
487 static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
489 remove_python_scripts_menus();
491 migrate_scripts_out_of_base_dir();
493 refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
494 refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
497 static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
498 {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
499 NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
502 static GtkActionEntry mainwindow_tools_python_actions[] = {
503 {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
504 {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
505 NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
506 {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
507 NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
508 {"Tools/PythonScripts/---", NULL, "---", NULL, NULL, NULL },
511 static int python_menu_init(char **error)
516 mainwin = mainwindow_get_mainwindow();
518 *error = g_strdup("Could not get main window");
522 gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
523 gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);
525 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
526 "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
527 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
529 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
530 "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
531 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
533 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
534 "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
535 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
537 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
538 "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
539 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
541 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
542 "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
543 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
545 refresh_python_scripts_menus(NULL, NULL);
550 static void python_menu_done(void)
554 mainwin = mainwindow_get_mainwindow();
556 if(mainwin && !claws_is_exiting()) {
559 remove_python_scripts_menus();
561 for(walk = menu_id_list; walk; walk = walk->next)
562 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
563 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
564 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
565 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
566 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
567 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
572 static PyObject *get_StringIO_instance(void)
574 PyObject *module_StringIO = NULL;
575 PyObject *class_StringIO = NULL;
576 PyObject *inst_StringIO = NULL;
578 module_StringIO = PyImport_ImportModule("cStringIO");
579 if(!module_StringIO) {
580 debug_print("Error getting traceback: Could not import module cStringIO\n");
584 class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
585 if(!class_StringIO) {
586 debug_print("Error getting traceback: Could not get StringIO class\n");
590 inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
592 debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
597 Py_XDECREF(module_StringIO);
598 Py_XDECREF(class_StringIO);
600 return inst_StringIO;
603 static char* get_exception_information(PyObject *inst_StringIO)
606 PyObject *meth_getvalue = NULL;
607 PyObject *result_getvalue = NULL;
612 if(PySys_SetObject("stderr", inst_StringIO) != 0) {
613 debug_print("Error getting traceback: Could not set sys.stderr to a StringIO instance\n");
617 meth_getvalue = PyObject_GetAttrString(inst_StringIO, "getvalue");
619 debug_print("Error getting traceback: Could not get the getvalue method of the StringIO instance\n");
625 result_getvalue = PyObject_CallObject(meth_getvalue, NULL);
626 if(!result_getvalue) {
627 debug_print("Error getting traceback: Could not call the getvalue method of the StringIO instance\n");
631 retval = g_strdup(PyString_AsString(result_getvalue));
635 Py_XDECREF(meth_getvalue);
636 Py_XDECREF(result_getvalue);
638 return retval ? retval : g_strdup("Unspecified error occurred");
641 static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
645 gint plugin_init(gchar **error)
649 PyObject *inst_StringIO = NULL;
652 if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
656 hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
657 if(hook_compose_create == 0) {
658 *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
662 /* script directories */
663 if(!make_sure_directories_exist(error))
666 /* initialize python interpreter */
669 /* The Python C API only offers to print an exception to sys.stderr. In order to catch it
670 * in a string, a StringIO object is created, to which sys.stderr can be redirected in case
671 * an error occurred. */
672 inst_StringIO = get_StringIO_instance();
674 /* initialize Claws Mail Python module */
676 if(PyErr_Occurred()) {
677 *error = get_exception_information(inst_StringIO);
681 if(PyRun_SimpleString("import clawsmail") == -1) {
682 *error = g_strdup("Error importing the clawsmail module");
686 /* initialize python interactive shell */
687 log_handler = g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO, log_func, NULL);
688 parasite_retval = parasite_python_init(error);
689 g_log_remove_handler(NULL, log_handler);
690 if(!parasite_retval) {
694 /* load menu options */
695 if(!python_menu_init(error)) {
699 /* problems here are not fatal */
700 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
702 debug_print("Python plugin loaded\n");
707 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
708 Py_XDECREF(inst_StringIO);
712 gboolean plugin_done(void)
714 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
716 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
721 gtk_widget_destroy(python_console);
722 python_console = NULL;
725 /* finialize python interpreter */
728 parasite_python_done();
730 debug_print("Python plugin done and unloaded.\n");
734 const gchar *plugin_name(void)
739 const gchar *plugin_desc(void)
741 return _("This plugin provides Python integration features.\n"
742 "Python code can be entered interactively into an embedded Python console, "
743 "under Tools -> Show Python console, or stored in scripts.\n\n"
744 "These scripts are then available via the menu. You can assign "
745 "keyboard shortcuts to them just like it is done with other menu items. "
746 "You can also put buttons for script invocation into the toolbars "
747 "using Claws Mail's builtin toolbar editor.\n\n"
748 "You can provide scripts working on the main window by placing files "
749 "into ~/.claws-mail/python-scripts/main.\n\n"
750 "You can also provide scripts working on an open compose window "
751 "by placing files into ~/.claws-mail/python-scripts/compose.\n\n"
752 "The folder ~/.claws-mail/python-scripts/auto/ may contain some "
753 "scripts that are automatically executed when certain events "
754 "occur. Currently, the following files in this directory "
755 "are recognised:\n\n"
757 "Gets executed whenever a compose window is opened, no matter "
758 "if that opening happened as a result of composing a new message, "
759 "replying or forwarding a message.\n\n"
761 "Executed at plugin load\n\n"
763 "Executed at plugin unload\n\n"
764 "\nFor the most up-to-date API documentation, type\n"
765 "\n help(clawsmail)\n"
766 "\nin the interactive Python console.\n"
767 "\nThe source distribution of this plugin comes with various example scripts "
768 "in the \"examples\" subdirectory. If you wrote a script that you would be "
769 "interested in sharing, feel free to send it to me to have it considered "
770 "for inclusion in the examples.\n"
771 "\nFeedback to <berndth@gmx.de> is welcome.");
774 const gchar *plugin_type(void)
779 const gchar *plugin_licence(void)
784 const gchar *plugin_version(void)
789 struct PluginFeature *plugin_provides(void)
791 static struct PluginFeature features[] =
792 { {PLUGIN_UTILITY, N_("Python integration")},
793 {PLUGIN_NOTHING, NULL}};