Python plugin: Silence parasite glib warnings
[claws.git] / src / plugins / python / python_plugin.c
1 /* Python plugin for Claws-Mail
2  * Copyright (C) 2009 Holger Berndt
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #include "claws-features.h"
21 #endif
22
23 #include <Python.h>
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27
28 #include <errno.h>
29
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"
38
39 #include "python-shell.h"
40 #include "python-hooks.h"
41 #include "clawsmailmodule.h"
42
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/"
51
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;
56
57 static GtkWidget *python_console = NULL;
58
59 static guint hook_compose_create;
60
61 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
62 {
63   MainWindow *mainwin;
64   GtkToggleAction *action;
65
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);
69   return TRUE;
70 }
71
72 static void setup_python_console(void)
73 {
74   GtkWidget *vbox;
75   GtkWidget *console;
76
77   python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
78   gtk_widget_set_size_request(python_console, 600, 400);
79
80   vbox = gtk_vbox_new(FALSE, 0);
81   gtk_container_add(GTK_CONTAINER(python_console), vbox);
82
83   console = parasite_python_shell_new();
84   gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
85
86   g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
87
88   gtk_widget_show_all(python_console);
89
90   parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
91 }
92
93 static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
94 {
95   if(gtk_toggle_action_get_active(action)) {
96     if(!python_console)
97       setup_python_console();
98     gtk_widget_show(python_console);
99   }
100   else {
101     gtk_widget_hide(python_console);
102   }
103 }
104
105 static void remove_python_scripts_menus(void)
106 {
107   GSList *walk;
108   MainWindow *mainwin;
109
110   mainwin =  mainwindow_get_mainwindow();
111
112   /* toolbar */
113   for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
114     prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
115
116   /* ui */
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;
121
122   /* actions */
123   for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
124     GtkAction *action;
125     gchar *entry;
126     entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
127     action = gtk_action_group_get_action(mainwin->action_group, entry);
128     g_free(entry);
129     if(action)
130       gtk_action_group_remove_action(mainwin->action_group, action);
131     g_free(walk->data);
132   }
133   g_slist_free(python_mainwin_scripts_names);
134   python_mainwin_scripts_names = NULL;
135
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);
139     g_free(walk->data);
140   }
141   g_slist_free(python_compose_scripts_names);
142   python_compose_scripts_names = NULL;
143 }
144
145 static gchar* extract_filename(const gchar *str)
146 {
147   gchar *filename;
148
149   filename = g_strrstr(str, "/");
150   if(!filename || *(filename+1) == '\0') {
151     debug_print("Error: Could not extract filename from %s\n", str);
152     return NULL;
153   }
154   filename++;
155   return filename;
156 }
157
158 static void run_script_file(const gchar *filename, Compose *compose)
159 {
160   FILE *fp;
161   fp = fopen(filename, "r");
162   if(!fp) {
163     debug_print("Error: Could not open file '%s'\n", filename);
164     return;
165   }
166   put_composewindow_into_module(compose);
167   if(PyRun_SimpleFile(fp, filename) == 0)
168     debug_print("Problem running script file '%s'\n", filename);
169   fclose(fp);
170 }
171
172 static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
173 {
174   gchar *auto_filepath;
175
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);
183 }
184
185 static void python_mainwin_script_callback(GtkAction *action, gpointer data)
186 {
187   char *filename;
188
189   filename = extract_filename(data);
190   if(!filename)
191     return;
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);
194   g_free(filename);
195 }
196
197 typedef struct _ComposeActionData ComposeActionData;
198 struct _ComposeActionData {
199   gchar *name;
200   Compose *compose;
201 };
202
203 static void python_compose_script_callback(GtkAction *action, gpointer data)
204 {
205   char *filename;
206   ComposeActionData *dat = (ComposeActionData*)data;
207
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);
210
211   g_free(filename);
212 }
213
214 static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
215 {
216         gchar *script;
217         script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
218         python_mainwin_script_callback(NULL, script);
219         g_free(script);
220 }
221
222 static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
223 {
224   gchar *filename;
225
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,
229       item_name, NULL);
230   run_script_file(filename, (Compose*)parent);
231   g_free(filename);
232 }
233
234 static char* make_sure_script_directory_exists(const gchar *subdir)
235 {
236   char *dir;
237   char *retval = NULL;
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));
242   }
243   g_free(dir);
244   return retval;
245 }
246
247 static int make_sure_directories_exist(char **error)
248 {
249   const char* dirs[] = {
250       ""
251       , PYTHON_SCRIPTS_MAIN_DIR
252       , PYTHON_SCRIPTS_COMPOSE_DIR
253       , PYTHON_SCRIPTS_AUTO_DIR
254       , NULL
255   };
256   const char **dir = dirs;
257
258   *error = NULL;
259
260   while(*dir) {
261     *error = make_sure_script_directory_exists(*dir);
262     if(*error)
263       break;
264     dir++;
265   }
266
267   return (*error == NULL);
268 }
269
270 static void migrate_scripts_out_of_base_dir(void)
271 {
272   char *base_dir;
273   GDir *dir;
274   const char *filename;
275   gchar *dest_dir;
276
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);
279   g_free(base_dir);
280   if(!dir)
281     return;
282
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) {
288       g_free(dest_dir);
289       g_dir_close(dir);
290       return;
291     }
292   }
293
294   while((filename = g_dir_read_name(dir)) != NULL) {
295     gchar *filepath;
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)) {
298       gchar *dest_file;
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);
302       else
303         debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
304       g_free(dest_file);
305     }
306     g_free(filepath);
307   }
308   g_dir_close(dir);
309   g_free(dest_dir);
310 }
311
312
313 static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
314 {
315   MainWindow *mainwin;
316   gint ii;
317   GSList *walk;
318   GtkActionEntry *entries;
319
320   /* create menu items */
321   entries = g_new0(GtkActionEntry, num_entries);
322   ii = 0;
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);
329     ii++;
330   }
331   for(ii = 0; ii < num_entries; ii++) {
332     guint id;
333
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));
338
339     prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
340   }
341
342   g_free(entries);
343 }
344
345
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)
349 {
350   GSList *walk;
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);
354   }
355 }
356
357 static GtkActionEntry compose_tools_python_actions[] = {
358     {"Tools/PythonScripts", NULL, N_("Python scripts") },
359 };
360
361 static void ComposeActionData_destroy_cb(gpointer data)
362 {
363   ComposeActionData *dat = (ComposeActionData*)data;
364   g_free(dat->name);
365   g_free(dat);
366 }
367
368 static gboolean my_compose_create_hook(gpointer cw, gpointer data)
369 {
370   gint ii;
371   GSList *walk;
372   GtkActionEntry *entries;
373   GtkActionGroup *action_group;
374   Compose *compose = (Compose*)cw;
375   guint num_entries = g_slist_length(python_compose_scripts_names);
376
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);
380   ii = 0;
381   for(walk = python_compose_scripts_names; walk; walk = walk->next) {
382     ComposeActionData *dat;
383
384     entries[ii].name = walk->data;
385     entries[ii].label = walk->data;
386     entries[ii].callback = G_CALLBACK(python_compose_script_callback);
387
388     dat = g_new0(ComposeActionData, 1);
389     dat->name = g_strdup(walk->data);
390     dat->compose = compose;
391
392     gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
393     ii++;
394   }
395   gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
396
397   MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
398       "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
399
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)
403   }
404
405   g_free(entries);
406
407   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
408
409   return FALSE;
410 }
411
412
413 static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
414 {
415   char *scripts_dir;
416   GDir *dir;
417   GError *error = NULL;
418   const char *filename;
419   GSList *filenames = NULL;
420   GSList *walk;
421   gint num_entries;
422
423   scripts_dir = g_strconcat(get_rc_dir(),
424       G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
425       G_DIR_SEPARATOR_S, subdir,
426       NULL);
427   debug_print("Refreshing: %s\n", scripts_dir);
428
429   dir = g_dir_open(scripts_dir, 0, &error);
430   g_free(scripts_dir);
431
432   if(!dir) {
433     debug_print("Could not open directory '%s': %s\n", subdir, error->message);
434     g_error_free(error);
435     return;
436   }
437
438   /* get filenames */
439   num_entries = 0;
440   while((filename = g_dir_read_name(dir)) != NULL) {
441     char *fn;
442
443     fn = g_strdup(filename);
444     filenames = g_slist_prepend(filenames, fn);
445     num_entries++;
446   }
447   g_dir_close(dir);
448
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);
453
454   /* cleanup */
455   for(walk = filenames; walk; walk = walk->next)
456     g_free(walk->data);
457   g_slist_free(filenames);
458 }
459
460 static void browse_python_scripts_dir(GtkAction *action, gpointer data)
461 {
462   gchar *uri;
463   GdkAppLaunchContext *launch_context;
464   GError *error = NULL;
465   MainWindow *mainwin;
466
467   mainwin =  mainwindow_get_mainwindow();
468   if(!mainwin) {
469       debug_print("Browse Python scripts: Problems getting the mainwindow\n");
470       return;
471   }
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);
476
477   if(error) {
478       debug_print("Could not open scripts dir browser: '%s'\n", error->message);
479       g_error_free(error);
480   }
481
482   g_object_unref(launch_context);
483   g_free(uri);
484 }
485
486 static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
487 {
488   remove_python_scripts_menus();
489
490   migrate_scripts_out_of_base_dir();
491
492   refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
493   refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
494 }
495
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},
499 };
500
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, "---" },
508 };
509
510 static int python_menu_init(char **error)
511 {
512   MainWindow *mainwin;
513   guint id;
514
515   mainwin =  mainwindow_get_mainwindow();
516   if(!mainwin) {
517     *error = g_strdup("Could not get main window");
518     return 0;
519   }
520
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);
523
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));
527
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));
531
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));
535
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));
539
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));
543
544   refresh_python_scripts_menus(NULL, NULL);
545
546   return !0;
547 }
548
549 static void python_menu_done(void)
550 {
551   MainWindow *mainwin;
552
553   mainwin = mainwindow_get_mainwindow();
554
555   if(mainwin && !claws_is_exiting()) {
556     GSList *walk;
557
558     remove_python_scripts_menus();
559
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);
567   }
568 }
569
570
571 static PyObject *get_StringIO_instance(void)
572 {
573   PyObject *module_StringIO = NULL;
574   PyObject *class_StringIO = NULL;
575   PyObject *inst_StringIO = NULL;
576
577   module_StringIO = PyImport_ImportModule("cStringIO");
578   if(!module_StringIO) {
579     debug_print("Error getting traceback: Could not import module cStringIO\n");
580     goto done;
581   }
582
583   class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
584   if(!class_StringIO) {
585     debug_print("Error getting traceback: Could not get StringIO class\n");
586     goto done;
587   }
588
589   inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
590   if(!inst_StringIO) {
591     debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
592     goto done;
593   }
594
595 done:
596   Py_XDECREF(module_StringIO);
597   Py_XDECREF(class_StringIO);
598
599   return inst_StringIO;
600 }
601
602 static char* get_exception_information(PyObject *inst_StringIO)
603 {
604   char *retval = NULL;
605   PyObject *meth_getvalue = NULL;
606   PyObject *result_getvalue = NULL;
607
608   if(!inst_StringIO)
609     goto done;
610
611   if(PySys_SetObject("stderr", inst_StringIO) != 0) {
612     debug_print("Error getting traceback: Could not set sys.stderr to a StringIO instance\n");
613     goto done;
614   }
615
616   meth_getvalue = PyObject_GetAttrString(inst_StringIO, "getvalue");
617   if(!meth_getvalue) {
618     debug_print("Error getting traceback: Could not get the getvalue method of the StringIO instance\n");
619     goto done;
620   }
621
622   PyErr_Print();
623
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");
627     goto done;
628   }
629
630   retval = g_strdup(PyString_AsString(result_getvalue));
631
632 done:
633
634   Py_XDECREF(meth_getvalue);
635   Py_XDECREF(result_getvalue);
636
637   return retval ? retval : g_strdup("Unspecified error occured");
638 }
639
640 static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
641 {
642 }
643
644 gint plugin_init(gchar **error)
645 {
646   guint log_handler;
647   int parasite_retval;
648   PyObject *inst_StringIO = NULL;
649
650   /* Version check */
651   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
652     return -1;
653
654   /* load hooks */
655   hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
656   if(hook_compose_create == (guint)-1) {
657     *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
658     return -1;
659   }
660
661   /* script directories */
662   if(!make_sure_directories_exist(error))
663     goto err;
664
665   /* initialize python interpreter */
666   Py_Initialize();
667
668   /* The Python C API only offers to print an exception to sys.stderr. In order to catch it
669    * in a string, a StringIO object is created, to which sys.stderr can be redirected in case
670    * an error occured. */
671   inst_StringIO = get_StringIO_instance();
672
673   /* initialize Claws Mail Python module */
674   initclawsmail();
675   if(PyErr_Occurred()) {
676     *error = get_exception_information(inst_StringIO);
677     goto err;
678   }
679
680   if(PyRun_SimpleString("import clawsmail") == -1) {
681     *error = g_strdup("Error importing the clawsmail module");
682     goto err;
683   }
684
685   /* initialize python interactive shell */
686   log_handler = g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO, log_func, NULL);
687   parasite_retval = parasite_python_init(error);
688   g_log_remove_handler(NULL, log_handler);
689   if(!parasite_retval) {
690     goto err;
691   }
692
693   /* load menu options */
694   if(!python_menu_init(error)) {
695     goto err;
696   }
697
698   /* problems here are not fatal */
699   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
700
701   debug_print("Python plugin loaded\n");
702
703   return 0;
704
705 err:
706   hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
707   Py_XDECREF(inst_StringIO);
708   return -1;
709 }
710
711 gboolean plugin_done(void)
712 {
713   hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
714
715   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
716
717   python_menu_done();
718
719   if(python_console) {
720     gtk_widget_destroy(python_console);
721     python_console = NULL;
722   }
723
724   /* finialize python interpreter */
725   Py_Finalize();
726
727   debug_print("Python plugin done and unloaded.\n");
728   return FALSE;
729 }
730
731 const gchar *plugin_name(void)
732 {
733   return _("Python");
734 }
735
736 const gchar *plugin_desc(void)
737 {
738   return _("This plugin provides Python integration features.\n"
739       "Python code can be entered interactively into an embedded Python console, "
740       "under Tools -> Show Python console, or stored in scripts.\n\n"
741       "These scripts are then available via the menu. You can assign "
742       "keyboard shortcuts to them just like it is done with other menu items. "
743       "You can also put buttons for script invocation into the toolbars "
744       "using Claws Mail's builtin toolbar editor.\n\n"
745       "You can provide scripts working on the main window by placing files "
746       "into ~/.claws-mail/python-scripts/main.\n\n"
747       "You can also provide scripts working on an open compose window "
748       "by placing files into ~/.claws-mail/python-scripts/compose.\n\n"
749       "The folder ~/.claws-mail/python-scripts/auto/ may contain some "
750       "scripts that are automatically executed when certain events "
751       "occur. Currently, the following files in this directory "
752       "are recognised:\n\n"
753       "compose_any\n"
754       "Gets executed whenever a compose window is opened, no matter "
755       "if that opening happened as a result of composing a new message, "
756       "replying or forwarding a message.\n\n"
757       "startup\n"
758       "Executed at plugin load\n\n"
759       "shutdown\n"
760       "Executed at plugin unload\n\n"
761       "\nFor the most up-to-date API documentation, type\n"
762       "\n help(clawsmail)\n"
763       "\nin the interactive Python console.\n"
764       "\nThe source distribution of this plugin comes with various example scripts "
765       "in the \"examples\" subdirectory. If you wrote a script that you would be "
766       "interested in sharing, feel free to send it to me to have it considered "
767       "for inclusion in the examples.\n"
768       "\nFeedback to <berndth@gmx.de> is welcome.");
769 }
770
771 const gchar *plugin_type(void)
772 {
773   return "GTK2";
774 }
775
776 const gchar *plugin_licence(void)
777 {
778   return "GPL3+";
779 }
780
781 const gchar *plugin_version(void)
782 {
783   return VERSION;
784 }
785
786 struct PluginFeature *plugin_provides(void)
787 {
788   static struct PluginFeature features[] =
789     { {PLUGIN_UTILITY, N_("Python integration")},
790       {PLUGIN_NOTHING, NULL}};
791   return features;
792 }