make the creation and updating of .mh_sequences optional and disabled by default
[claws.git] / src / plugins / python / python_plugin.c
blobef84a75fc7faa7c1853b8dbbf24271b4d534ab57
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/>.
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #include "claws-features.h"
21 #endif
23 #include <Python.h>
25 #include <glib.h>
26 #include <glib/gi18n.h>
28 #include <errno.h>
30 #include "common/hooks.h"
31 #include "common/plugin.h"
32 #include "common/version.h"
33 #include "common/utils.h"
34 #include "gtk/menu.h"
35 #include "main.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"
43 #include "python_prefs.h"
45 #define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
46 #define PYTHON_SCRIPTS_MAIN_DIR "main"
47 #define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
48 #define PYTHON_SCRIPTS_AUTO_DIR "auto"
49 #define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
50 #define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
51 #define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
52 #define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"
54 static GSList *menu_id_list = NULL;
55 static GSList *python_mainwin_scripts_id_list = NULL;
56 static GSList *python_mainwin_scripts_names = NULL;
57 static GSList *python_compose_scripts_names = NULL;
59 static GtkWidget *python_console = NULL;
61 static gulong hook_compose_create = 0;
63 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
65 MainWindow *mainwin;
66 GtkToggleAction *action;
68 mainwin = mainwindow_get_mainwindow();
69 action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
70 gtk_toggle_action_set_active(action, FALSE);
71 return TRUE;
74 static void size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation)
76 cm_return_if_fail(allocation != NULL);
78 gtk_window_get_size(GTK_WINDOW(widget),
79 &python_config.console_win_width, &python_config.console_win_height);
82 static void setup_python_console(void)
84 GtkWidget *vbox;
85 GtkWidget *console;
86 static GdkGeometry geometry;
88 python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
89 g_signal_connect (G_OBJECT(python_console), "size_allocate",
90 G_CALLBACK (size_allocate_cb), NULL);
91 if (!geometry.min_height) {
92 geometry.min_width = 600;
93 geometry.min_height = 400;
96 gtk_window_set_geometry_hints(GTK_WINDOW(python_console), NULL, &geometry,
97 GDK_HINT_MIN_SIZE);
98 gtk_window_set_default_size(GTK_WINDOW(python_console),
99 python_config.console_win_width,
100 python_config.console_win_height);
102 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
103 gtk_container_add(GTK_CONTAINER(python_console), vbox);
105 console = parasite_python_shell_new();
106 gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
108 g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
110 gtk_widget_show_all(python_console);
112 parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
115 static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
117 if(gtk_toggle_action_get_active(action)) {
118 if(!python_console)
119 setup_python_console();
120 gtk_widget_show(python_console);
122 else {
123 gtk_widget_hide(python_console);
127 static void remove_python_scripts_menus(void)
129 GSList *walk;
130 MainWindow *mainwin;
132 mainwin = mainwindow_get_mainwindow();
134 /* toolbar */
135 for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
136 prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
138 /* ui */
139 for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
140 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
141 g_slist_free(python_mainwin_scripts_id_list);
142 python_mainwin_scripts_id_list = NULL;
144 /* actions */
145 for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
146 GtkAction *action;
147 gchar *entry;
148 entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
149 action = gtk_action_group_get_action(mainwin->action_group, entry);
150 g_free(entry);
151 if(action)
152 gtk_action_group_remove_action(mainwin->action_group, action);
153 g_free(walk->data);
155 g_slist_free(python_mainwin_scripts_names);
156 python_mainwin_scripts_names = NULL;
158 /* compose scripts */
159 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
160 prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
161 g_free(walk->data);
163 g_slist_free(python_compose_scripts_names);
164 python_compose_scripts_names = NULL;
167 static gchar* extract_filename(const gchar *str)
169 gchar *filename;
171 filename = g_strrstr(str, "/");
172 if(!filename || *(filename+1) == '\0') {
173 debug_print("Error: Could not extract filename from %s\n", str);
174 return NULL;
176 filename++;
177 return filename;
180 static void run_script_file(const gchar *filename, Compose *compose)
182 FILE *fp;
183 fp = claws_fopen(filename, "r");
184 if(!fp) {
185 debug_print("Error: Could not open file '%s'\n", filename);
186 return;
188 put_composewindow_into_module(compose);
189 if(PyRun_SimpleFile(fp, filename) == 0)
190 debug_print("Problem running script file '%s'\n", filename);
191 claws_fclose(fp);
194 static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
196 gchar *auto_filepath;
198 /* execute auto/autofilename, if it exists */
199 auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
200 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
201 PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
202 if(file_exist(auto_filepath, FALSE))
203 run_script_file(auto_filepath, compose);
204 g_free(auto_filepath);
207 static void python_mainwin_script_callback(GtkAction *action, gpointer data)
209 char *filename;
211 filename = extract_filename(data);
212 if(!filename)
213 return;
214 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);
215 run_script_file(filename, NULL);
216 g_free(filename);
219 typedef struct _ComposeActionData ComposeActionData;
220 struct _ComposeActionData {
221 gchar *name;
222 Compose *compose;
225 static void python_compose_script_callback(GtkAction *action, gpointer data)
227 char *filename;
228 ComposeActionData *dat = (ComposeActionData*)data;
230 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);
231 run_script_file(filename, dat->compose);
233 g_free(filename);
236 static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
238 gchar *script;
239 script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
240 python_mainwin_script_callback(NULL, script);
241 g_free(script);
244 static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
246 gchar *filename;
248 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
249 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
250 PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
251 item_name, NULL);
252 run_script_file(filename, (Compose*)parent);
253 g_free(filename);
256 static char* make_sure_script_directory_exists(const gchar *subdir)
258 char *dir;
259 char *retval = NULL;
260 dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
261 if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
262 if(g_mkdir(dir, 0777) != 0)
263 retval = g_strdup_printf("Could not create directory '%s': %s", dir, g_strerror(errno));
265 g_free(dir);
266 return retval;
269 static int make_sure_directories_exist(char **error)
271 const char* dirs[] = {
273 , PYTHON_SCRIPTS_MAIN_DIR
274 , PYTHON_SCRIPTS_COMPOSE_DIR
275 , PYTHON_SCRIPTS_AUTO_DIR
276 , NULL
278 const char **dir = dirs;
280 *error = NULL;
282 while(*dir) {
283 *error = make_sure_script_directory_exists(*dir);
284 if(*error)
285 break;
286 dir++;
289 return (*error == NULL);
292 static void migrate_scripts_out_of_base_dir(void)
294 char *base_dir;
295 GDir *dir;
296 const char *filename;
297 gchar *dest_dir;
299 base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
300 dir = g_dir_open(base_dir, 0, NULL);
301 g_free(base_dir);
302 if(!dir)
303 return;
305 dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
306 PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
307 PYTHON_SCRIPTS_MAIN_DIR, NULL);
308 if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
309 if(g_mkdir(dest_dir, 0777) != 0) {
310 g_free(dest_dir);
311 g_dir_close(dir);
312 return;
316 while((filename = g_dir_read_name(dir)) != NULL) {
317 gchar *filepath;
318 filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
319 if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
320 gchar *dest_file;
321 dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
322 if(move_file(filepath, dest_file, FALSE) == 0)
323 debug_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
324 else
325 debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
326 g_free(dest_file);
328 g_free(filepath);
330 g_dir_close(dir);
331 g_free(dest_dir);
335 static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
337 MainWindow *mainwin;
338 gint ii;
339 GSList *walk;
340 GtkActionEntry *entries;
342 /* create menu items */
343 entries = g_new0(GtkActionEntry, num_entries);
344 ii = 0;
345 mainwin = mainwindow_get_mainwindow();
346 for(walk = filenames; walk; walk = walk->next) {
347 entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
348 entries[ii].label = walk->data;
349 entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
350 gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
351 ii++;
353 for(ii = 0; ii < num_entries; ii++) {
354 guint id;
356 python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
357 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
358 entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
359 python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));
361 prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
364 g_free(entries);
368 /* this function doesn't really create menu items, but prepares a list that can be used
369 * in the compose create hook. It does however register the scripts for the toolbar editor */
370 static void create_compose_menus_and_items(GSList *filenames)
372 GSList *walk;
373 for(walk = filenames; walk; walk = walk->next) {
374 python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
375 prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
379 static GtkActionEntry compose_tools_python_actions[] = {
380 {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
383 static void ComposeActionData_destroy_cb(gpointer data)
385 ComposeActionData *dat = (ComposeActionData*)data;
386 g_free(dat->name);
387 g_free(dat);
390 static gboolean my_compose_create_hook(gpointer cw, gpointer data)
392 gint ii;
393 GSList *walk;
394 GtkActionEntry *entries;
395 GtkActionGroup *action_group;
396 Compose *compose = (Compose*)cw;
397 guint num_entries = g_slist_length(python_compose_scripts_names);
399 action_group = gtk_action_group_new("PythonPlugin");
400 gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
401 entries = g_new0(GtkActionEntry, num_entries);
402 ii = 0;
403 for(walk = python_compose_scripts_names; walk; walk = walk->next) {
404 ComposeActionData *dat;
406 entries[ii].name = walk->data;
407 entries[ii].label = walk->data;
408 entries[ii].callback = G_CALLBACK(python_compose_script_callback);
410 dat = g_new0(ComposeActionData, 1);
411 dat->name = g_strdup(walk->data);
412 dat->compose = compose;
414 gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
415 ii++;
417 gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
419 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
420 "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
422 for(ii = 0; ii < num_entries; ii++) {
423 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
424 entries[ii].name, GTK_UI_MANAGER_MENUITEM)
427 g_free(entries);
429 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
431 return FALSE;
435 static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
437 char *scripts_dir;
438 GDir *dir;
439 GError *error = NULL;
440 const char *filename;
441 GSList *filenames = NULL;
442 GSList *walk;
443 gint num_entries;
445 scripts_dir = g_strconcat(get_rc_dir(),
446 G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
447 G_DIR_SEPARATOR_S, subdir,
448 NULL);
449 debug_print("Refreshing: %s\n", scripts_dir);
451 dir = g_dir_open(scripts_dir, 0, &error);
452 g_free(scripts_dir);
454 if(!dir) {
455 debug_print("Could not open directory '%s': %s\n", subdir, error->message);
456 g_error_free(error);
457 return;
460 /* get filenames */
461 num_entries = 0;
462 while((filename = g_dir_read_name(dir)) != NULL) {
463 char *fn;
465 fn = g_strdup(filename);
466 filenames = g_slist_prepend(filenames, fn);
467 num_entries++;
469 g_dir_close(dir);
471 if(toolbar_type == TOOLBAR_MAIN)
472 create_mainwindow_menus_and_items(filenames, num_entries);
473 else if(toolbar_type == TOOLBAR_COMPOSE)
474 create_compose_menus_and_items(filenames);
476 /* cleanup */
477 for(walk = filenames; walk; walk = walk->next)
478 g_free(walk->data);
479 g_slist_free(filenames);
482 static void browse_python_scripts_dir(GtkAction *action, gpointer data)
484 gchar *uri;
485 GdkAppLaunchContext *launch_context;
486 GError *error = NULL;
487 MainWindow *mainwin;
489 mainwin = mainwindow_get_mainwindow();
490 if(!mainwin) {
491 debug_print("Browse Python scripts: Problems getting the mainwindow\n");
492 return;
494 launch_context = gdk_app_launch_context_new();
495 gdk_app_launch_context_set_screen(launch_context, gtk_widget_get_screen(mainwin->window));
496 uri = g_strconcat("file://", get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
497 g_app_info_launch_default_for_uri(uri, G_APP_LAUNCH_CONTEXT(launch_context), &error);
499 if(error) {
500 debug_print("Could not open scripts dir browser: '%s'\n", error->message);
501 g_error_free(error);
504 g_object_unref(launch_context);
505 g_free(uri);
508 static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
510 remove_python_scripts_menus();
512 migrate_scripts_out_of_base_dir();
514 refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
515 refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
518 static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
519 {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
520 NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
523 static GtkActionEntry mainwindow_tools_python_actions[] = {
524 {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
525 {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
526 NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
527 {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
528 NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
529 {"Tools/PythonScripts/---", NULL, "---", NULL, NULL, NULL },
532 static int python_menu_init(char **error)
534 MainWindow *mainwin;
535 guint id;
537 mainwin = mainwindow_get_mainwindow();
538 if(!mainwin) {
539 *error = g_strdup("Could not get main window");
540 return 0;
543 gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
544 gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);
546 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
547 "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
548 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
550 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
551 "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
552 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
554 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
555 "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
556 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
558 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
559 "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
560 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
562 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
563 "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
564 menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
566 refresh_python_scripts_menus(NULL, NULL);
568 return !0;
571 static void python_menu_done(void)
573 MainWindow *mainwin;
575 mainwin = mainwindow_get_mainwindow();
577 if(mainwin && !claws_is_exiting()) {
578 GSList *walk;
580 remove_python_scripts_menus();
582 for(walk = menu_id_list; walk; walk = walk->next)
583 gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
584 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
585 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
586 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
587 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
588 MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
593 static PyObject *get_StringIO_instance(void)
595 PyObject *module_StringIO = NULL;
596 PyObject *class_StringIO = NULL;
597 PyObject *inst_StringIO = NULL;
599 module_StringIO = PyImport_ImportModule("io");
600 if(!module_StringIO) {
601 debug_print("Error getting traceback: Could not import module io\n");
602 goto done;
605 class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
606 if(!class_StringIO) {
607 debug_print("Error getting traceback: Could not get StringIO class\n");
608 goto done;
611 inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
612 if(!inst_StringIO) {
613 debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
614 goto done;
617 done:
618 Py_XDECREF(module_StringIO);
619 Py_XDECREF(class_StringIO);
621 return inst_StringIO;
624 static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
628 gint plugin_init(gchar **error)
630 guint log_handler;
631 int parasite_retval;
632 PyObject *inst_StringIO = NULL;
634 /* Version check */
635 if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
636 return -1;
638 /* init/load prefs */
639 python_prefs_init();
641 /* load hooks */
642 hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
643 if(hook_compose_create == 0) {
644 *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
645 return -1;
648 /* script directories */
649 if(!make_sure_directories_exist(error))
650 goto err;
652 /* register moduke */
653 PyImport_AppendInittab("clawsmail", initclawsmail);
654 PyImport_AppendInittab("parasite", parasite_python_module_init);
656 /* initialize python interpreter */
657 Py_Initialize();
659 /* The Python C API only offers to print an exception to sys.stderr. In order to catch it
660 * in a string, a StringIO object is created, to which sys.stderr can be redirected in case
661 * an error occurred. */
662 inst_StringIO = get_StringIO_instance();
664 if(PyRun_SimpleString("import clawsmail") == -1) {
665 *error = g_strdup("Error importing the clawsmail module");
666 goto err;
669 /* initialize python interactive shell */
670 log_handler = g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO, log_func, NULL);
671 parasite_retval = parasite_python_init(error);
672 g_log_remove_handler(NULL, log_handler);
673 if(!parasite_retval) {
674 goto err;
677 /* load menu options */
678 if(!python_menu_init(error)) {
679 goto err;
682 /* problems here are not fatal */
683 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
685 debug_print("Python plugin loaded\n");
687 return 0;
689 err:
690 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
691 Py_XDECREF(inst_StringIO);
692 return -1;
695 gboolean plugin_done(void)
697 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
699 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
701 python_menu_done();
703 if(python_console) {
704 gtk_widget_destroy(python_console);
705 python_console = NULL;
708 /* finialize python interpreter */
709 Py_Finalize();
711 parasite_python_done();
713 /* save prefs */
714 python_prefs_done();
716 debug_print("Python plugin done and unloaded.\n");
717 return FALSE;
720 const gchar *plugin_name(void)
722 return _("Python");
725 const gchar *plugin_desc(void)
727 return _("This plugin provides Python integration features.\n"
728 "Python code can be entered interactively into an embedded Python console, "
729 "under Tools -> Show Python console, or stored in scripts.\n\n"
730 "These scripts are then available via the menu. You can assign "
731 "keyboard shortcuts to them just like it is done with other menu items. "
732 "You can also put buttons for script invocation into the toolbars "
733 "using Claws Mail's builtin toolbar editor.\n\n"
734 "You can provide scripts working on the main window by placing files "
735 "into ~/.claws-mail/python-scripts/main.\n\n"
736 "You can also provide scripts working on an open compose window "
737 "by placing files into ~/.claws-mail/python-scripts/compose.\n\n"
738 "The folder ~/.claws-mail/python-scripts/auto/ may contain some "
739 "scripts that are automatically executed when certain events "
740 "occur. Currently, the following files in this directory "
741 "are recognised:\n\n"
742 "compose_any\n"
743 "Gets executed whenever a compose window is opened, no matter "
744 "if that opening happened as a result of composing a new message, "
745 "replying or forwarding a message.\n\n"
746 "startup\n"
747 "Executed at plugin load\n\n"
748 "shutdown\n"
749 "Executed at plugin unload\n\n"
750 "\nFor the most up-to-date API documentation, type\n"
751 "\n help(clawsmail)\n"
752 "\nin the interactive Python console.\n"
753 "\nThe source distribution of this plugin comes with various example scripts "
754 "in the \"examples\" subdirectory. If you wrote a script that you would be "
755 "interested in sharing, feel free to send it to me to have it considered "
756 "for inclusion in the examples.\n"
757 "\nFeedback to <berndth@gmx.de> is welcome.");
760 const gchar *plugin_type(void)
762 return "GTK3";
765 const gchar *plugin_licence(void)
767 return "GPL3+";
770 const gchar *plugin_version(void)
772 return VERSION;
775 struct PluginFeature *plugin_provides(void)
777 static struct PluginFeature features[] =
778 { {PLUGIN_UTILITY, N_("Python integration")},
779 {PLUGIN_NOTHING, NULL}};
780 return features;