Merge branch 'master' of ssh://git.claws-mail.org/home/git/claws
[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 #include "file-utils.h"
43
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/"
52
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;
57
58 static GtkWidget *python_console = NULL;
59
60 static gulong hook_compose_create = 0;
61
62 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
63 {
64   MainWindow *mainwin;
65   GtkToggleAction *action;
66
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);
70   return TRUE;
71 }
72
73 static void setup_python_console(void)
74 {
75   GtkWidget *vbox;
76   GtkWidget *console;
77
78   python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
79   gtk_widget_set_size_request(python_console, 600, 400);
80
81   vbox = gtk_vbox_new(FALSE, 0);
82   gtk_container_add(GTK_CONTAINER(python_console), vbox);
83
84   console = parasite_python_shell_new();
85   gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
86
87   g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
88
89   gtk_widget_show_all(python_console);
90
91   parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
92 }
93
94 static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
95 {
96   if(gtk_toggle_action_get_active(action)) {
97     if(!python_console)
98       setup_python_console();
99     gtk_widget_show(python_console);
100   }
101   else {
102     gtk_widget_hide(python_console);
103   }
104 }
105
106 static void remove_python_scripts_menus(void)
107 {
108   GSList *walk;
109   MainWindow *mainwin;
110
111   mainwin =  mainwindow_get_mainwindow();
112
113   /* toolbar */
114   for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
115     prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
116
117   /* ui */
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;
122
123   /* actions */
124   for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
125     GtkAction *action;
126     gchar *entry;
127     entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
128     action = gtk_action_group_get_action(mainwin->action_group, entry);
129     g_free(entry);
130     if(action)
131       gtk_action_group_remove_action(mainwin->action_group, action);
132     g_free(walk->data);
133   }
134   g_slist_free(python_mainwin_scripts_names);
135   python_mainwin_scripts_names = NULL;
136
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);
140     g_free(walk->data);
141   }
142   g_slist_free(python_compose_scripts_names);
143   python_compose_scripts_names = NULL;
144 }
145
146 static gchar* extract_filename(const gchar *str)
147 {
148   gchar *filename;
149
150   filename = g_strrstr(str, "/");
151   if(!filename || *(filename+1) == '\0') {
152     debug_print("Error: Could not extract filename from %s\n", str);
153     return NULL;
154   }
155   filename++;
156   return filename;
157 }
158
159 static void run_script_file(const gchar *filename, Compose *compose)
160 {
161   FILE *fp;
162   fp = claws_fopen(filename, "r");
163   if(!fp) {
164     debug_print("Error: Could not open file '%s'\n", filename);
165     return;
166   }
167   put_composewindow_into_module(compose);
168   if(PyRun_SimpleFile(fp, filename) == 0)
169     debug_print("Problem running script file '%s'\n", filename);
170   claws_fclose(fp);
171 }
172
173 static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
174 {
175   gchar *auto_filepath;
176
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);
184 }
185
186 static void python_mainwin_script_callback(GtkAction *action, gpointer data)
187 {
188   char *filename;
189
190   filename = extract_filename(data);
191   if(!filename)
192     return;
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);
195   g_free(filename);
196 }
197
198 typedef struct _ComposeActionData ComposeActionData;
199 struct _ComposeActionData {
200   gchar *name;
201   Compose *compose;
202 };
203
204 static void python_compose_script_callback(GtkAction *action, gpointer data)
205 {
206   char *filename;
207   ComposeActionData *dat = (ComposeActionData*)data;
208
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);
211
212   g_free(filename);
213 }
214
215 static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
216 {
217         gchar *script;
218         script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
219         python_mainwin_script_callback(NULL, script);
220         g_free(script);
221 }
222
223 static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
224 {
225   gchar *filename;
226
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,
230       item_name, NULL);
231   run_script_file(filename, (Compose*)parent);
232   g_free(filename);
233 }
234
235 static char* make_sure_script_directory_exists(const gchar *subdir)
236 {
237   char *dir;
238   char *retval = NULL;
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));
243   }
244   g_free(dir);
245   return retval;
246 }
247
248 static int make_sure_directories_exist(char **error)
249 {
250   const char* dirs[] = {
251       ""
252       , PYTHON_SCRIPTS_MAIN_DIR
253       , PYTHON_SCRIPTS_COMPOSE_DIR
254       , PYTHON_SCRIPTS_AUTO_DIR
255       , NULL
256   };
257   const char **dir = dirs;
258
259   *error = NULL;
260
261   while(*dir) {
262     *error = make_sure_script_directory_exists(*dir);
263     if(*error)
264       break;
265     dir++;
266   }
267
268   return (*error == NULL);
269 }
270
271 static void migrate_scripts_out_of_base_dir(void)
272 {
273   char *base_dir;
274   GDir *dir;
275   const char *filename;
276   gchar *dest_dir;
277
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);
280   g_free(base_dir);
281   if(!dir)
282     return;
283
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) {
289       g_free(dest_dir);
290       g_dir_close(dir);
291       return;
292     }
293   }
294
295   while((filename = g_dir_read_name(dir)) != NULL) {
296     gchar *filepath;
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)) {
299       gchar *dest_file;
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);
303       else
304         debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
305       g_free(dest_file);
306     }
307     g_free(filepath);
308   }
309   g_dir_close(dir);
310   g_free(dest_dir);
311 }
312
313
314 static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
315 {
316   MainWindow *mainwin;
317   gint ii;
318   GSList *walk;
319   GtkActionEntry *entries;
320
321   /* create menu items */
322   entries = g_new0(GtkActionEntry, num_entries);
323   ii = 0;
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);
330     ii++;
331   }
332   for(ii = 0; ii < num_entries; ii++) {
333     guint id;
334
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));
339
340     prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
341   }
342
343   g_free(entries);
344 }
345
346
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)
350 {
351   GSList *walk;
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);
355   }
356 }
357
358 static GtkActionEntry compose_tools_python_actions[] = {
359     {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
360 };
361
362 static void ComposeActionData_destroy_cb(gpointer data)
363 {
364   ComposeActionData *dat = (ComposeActionData*)data;
365   g_free(dat->name);
366   g_free(dat);
367 }
368
369 static gboolean my_compose_create_hook(gpointer cw, gpointer data)
370 {
371   gint ii;
372   GSList *walk;
373   GtkActionEntry *entries;
374   GtkActionGroup *action_group;
375   Compose *compose = (Compose*)cw;
376   guint num_entries = g_slist_length(python_compose_scripts_names);
377
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);
381   ii = 0;
382   for(walk = python_compose_scripts_names; walk; walk = walk->next) {
383     ComposeActionData *dat;
384
385     entries[ii].name = walk->data;
386     entries[ii].label = walk->data;
387     entries[ii].callback = G_CALLBACK(python_compose_script_callback);
388
389     dat = g_new0(ComposeActionData, 1);
390     dat->name = g_strdup(walk->data);
391     dat->compose = compose;
392
393     gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
394     ii++;
395   }
396   gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
397
398   MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
399       "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
400
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)
404   }
405
406   g_free(entries);
407
408   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
409
410   return FALSE;
411 }
412
413
414 static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
415 {
416   char *scripts_dir;
417   GDir *dir;
418   GError *error = NULL;
419   const char *filename;
420   GSList *filenames = NULL;
421   GSList *walk;
422   gint num_entries;
423
424   scripts_dir = g_strconcat(get_rc_dir(),
425       G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
426       G_DIR_SEPARATOR_S, subdir,
427       NULL);
428   debug_print("Refreshing: %s\n", scripts_dir);
429
430   dir = g_dir_open(scripts_dir, 0, &error);
431   g_free(scripts_dir);
432
433   if(!dir) {
434     debug_print("Could not open directory '%s': %s\n", subdir, error->message);
435     g_error_free(error);
436     return;
437   }
438
439   /* get filenames */
440   num_entries = 0;
441   while((filename = g_dir_read_name(dir)) != NULL) {
442     char *fn;
443
444     fn = g_strdup(filename);
445     filenames = g_slist_prepend(filenames, fn);
446     num_entries++;
447   }
448   g_dir_close(dir);
449
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);
454
455   /* cleanup */
456   for(walk = filenames; walk; walk = walk->next)
457     g_free(walk->data);
458   g_slist_free(filenames);
459 }
460
461 static void browse_python_scripts_dir(GtkAction *action, gpointer data)
462 {
463   gchar *uri;
464   GdkAppLaunchContext *launch_context;
465   GError *error = NULL;
466   MainWindow *mainwin;
467
468   mainwin =  mainwindow_get_mainwindow();
469   if(!mainwin) {
470       debug_print("Browse Python scripts: Problems getting the mainwindow\n");
471       return;
472   }
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);
477
478   if(error) {
479       debug_print("Could not open scripts dir browser: '%s'\n", error->message);
480       g_error_free(error);
481   }
482
483   g_object_unref(launch_context);
484   g_free(uri);
485 }
486
487 static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
488 {
489   remove_python_scripts_menus();
490
491   migrate_scripts_out_of_base_dir();
492
493   refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
494   refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
495 }
496
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},
500 };
501
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 },
509 };
510
511 static int python_menu_init(char **error)
512 {
513   MainWindow *mainwin;
514   guint id;
515
516   mainwin =  mainwindow_get_mainwindow();
517   if(!mainwin) {
518     *error = g_strdup("Could not get main window");
519     return 0;
520   }
521
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);
524
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));
528
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));
532
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));
536
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));
540
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));
544
545   refresh_python_scripts_menus(NULL, NULL);
546
547   return !0;
548 }
549
550 static void python_menu_done(void)
551 {
552   MainWindow *mainwin;
553
554   mainwin = mainwindow_get_mainwindow();
555
556   if(mainwin && !claws_is_exiting()) {
557     GSList *walk;
558
559     remove_python_scripts_menus();
560
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);
568   }
569 }
570
571
572 static PyObject *get_StringIO_instance(void)
573 {
574   PyObject *module_StringIO = NULL;
575   PyObject *class_StringIO = NULL;
576   PyObject *inst_StringIO = NULL;
577
578   module_StringIO = PyImport_ImportModule("cStringIO");
579   if(!module_StringIO) {
580     debug_print("Error getting traceback: Could not import module cStringIO\n");
581     goto done;
582   }
583
584   class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
585   if(!class_StringIO) {
586     debug_print("Error getting traceback: Could not get StringIO class\n");
587     goto done;
588   }
589
590   inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
591   if(!inst_StringIO) {
592     debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
593     goto done;
594   }
595
596 done:
597   Py_XDECREF(module_StringIO);
598   Py_XDECREF(class_StringIO);
599
600   return inst_StringIO;
601 }
602
603 static char* get_exception_information(PyObject *inst_StringIO)
604 {
605   char *retval = NULL;
606   PyObject *meth_getvalue = NULL;
607   PyObject *result_getvalue = NULL;
608
609   if(!inst_StringIO)
610     goto done;
611
612   if(PySys_SetObject("stderr", inst_StringIO) != 0) {
613     debug_print("Error getting traceback: Could not set sys.stderr to a StringIO instance\n");
614     goto done;
615   }
616
617   meth_getvalue = PyObject_GetAttrString(inst_StringIO, "getvalue");
618   if(!meth_getvalue) {
619     debug_print("Error getting traceback: Could not get the getvalue method of the StringIO instance\n");
620     goto done;
621   }
622
623   PyErr_Print();
624
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");
628     goto done;
629   }
630
631   retval = g_strdup(PyString_AsString(result_getvalue));
632
633 done:
634
635   Py_XDECREF(meth_getvalue);
636   Py_XDECREF(result_getvalue);
637
638   return retval ? retval : g_strdup("Unspecified error occurred");
639 }
640
641 static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
642 {
643 }
644
645 gint plugin_init(gchar **error)
646 {
647   guint log_handler;
648   int parasite_retval;
649   PyObject *inst_StringIO = NULL;
650
651   /* Version check */
652   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
653     return -1;
654
655   /* load hooks */
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"));
659     return -1;
660   }
661
662   /* script directories */
663   if(!make_sure_directories_exist(error))
664     goto err;
665
666   /* initialize python interpreter */
667   Py_Initialize();
668
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();
673
674   /* initialize Claws Mail Python module */
675   initclawsmail();
676   if(PyErr_Occurred()) {
677     *error = get_exception_information(inst_StringIO);
678     goto err;
679   }
680
681   if(PyRun_SimpleString("import clawsmail") == -1) {
682     *error = g_strdup("Error importing the clawsmail module");
683     goto err;
684   }
685
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) {
691     goto err;
692   }
693
694   /* load menu options */
695   if(!python_menu_init(error)) {
696     goto err;
697   }
698
699   /* problems here are not fatal */
700   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
701
702   debug_print("Python plugin loaded\n");
703
704   return 0;
705
706 err:
707   hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
708   Py_XDECREF(inst_StringIO);
709   return -1;
710 }
711
712 gboolean plugin_done(void)
713 {
714   hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
715
716   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
717
718   python_menu_done();
719
720   if(python_console) {
721     gtk_widget_destroy(python_console);
722     python_console = NULL;
723   }
724
725   /* finialize python interpreter */
726   Py_Finalize();
727
728   parasite_python_done();
729
730   debug_print("Python plugin done and unloaded.\n");
731   return FALSE;
732 }
733
734 const gchar *plugin_name(void)
735 {
736   return _("Python");
737 }
738
739 const gchar *plugin_desc(void)
740 {
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"
756       "compose_any\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"
760       "startup\n"
761       "Executed at plugin load\n\n"
762       "shutdown\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.");
772 }
773
774 const gchar *plugin_type(void)
775 {
776   return "GTK2";
777 }
778
779 const gchar *plugin_licence(void)
780 {
781   return "GPL3+";
782 }
783
784 const gchar *plugin_version(void)
785 {
786   return VERSION;
787 }
788
789 struct PluginFeature *plugin_provides(void)
790 {
791   static struct PluginFeature features[] =
792     { {PLUGIN_UTILITY, N_("Python integration")},
793       {PLUGIN_NOTHING, NULL}};
794   return features;
795 }