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"
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
)
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
);
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)
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
,
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
)) {
119 setup_python_console();
120 gtk_widget_show(python_console
);
123 gtk_widget_hide(python_console
);
127 static void remove_python_scripts_menus(void)
132 mainwin
= mainwindow_get_mainwindow();
135 for(walk
= python_mainwin_scripts_names
; walk
; walk
= walk
->next
)
136 prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN
, "Python", walk
->data
);
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
;
145 for(walk
= python_mainwin_scripts_names
; walk
; walk
= walk
->next
) {
148 entry
= g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX
, walk
->data
, NULL
);
149 action
= gtk_action_group_get_action(mainwin
->action_group
, entry
);
152 gtk_action_group_remove_action(mainwin
->action_group
, action
);
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
);
163 g_slist_free(python_compose_scripts_names
);
164 python_compose_scripts_names
= NULL
;
167 static gchar
* extract_filename(const gchar
*str
)
171 filename
= g_strrstr(str
, "/");
172 if(!filename
|| *(filename
+1) == '\0') {
173 debug_print("Error: Could not extract filename from %s\n", str
);
180 static void run_script_file(const gchar
*filename
, Compose
*compose
)
183 fp
= claws_fopen(filename
, "r");
185 debug_print("Error: Could not open file '%s'\n", filename
);
188 put_composewindow_into_module(compose
);
189 if(PyRun_SimpleFile(fp
, filename
) == 0)
190 debug_print("Problem running script file '%s'\n", filename
);
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
)
211 filename
= extract_filename(data
);
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
);
219 typedef struct _ComposeActionData ComposeActionData
;
220 struct _ComposeActionData
{
225 static void python_compose_script_callback(GtkAction
*action
, gpointer data
)
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
);
236 static void mainwin_toolbar_callback(gpointer parent
, const gchar
*item_name
, gpointer data
)
239 script
= g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX
, item_name
, NULL
);
240 python_mainwin_script_callback(NULL
, script
);
244 static void compose_toolbar_callback(gpointer parent
, const gchar
*item_name
, gpointer data
)
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
,
252 run_script_file(filename
, (Compose
*)parent
);
256 static char* make_sure_script_directory_exists(const gchar
*subdir
)
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
));
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
278 const char **dir
= dirs
;
283 *error
= make_sure_script_directory_exists(*dir
);
289 return (*error
== NULL
);
292 static void migrate_scripts_out_of_base_dir(void)
296 const char *filename
;
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
);
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) {
316 while((filename
= g_dir_read_name(dir
)) != NULL
) {
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
)) {
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
);
325 debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename
, PYTHON_SCRIPTS_MAIN_DIR
);
335 static void create_mainwindow_menus_and_items(GSList
*filenames
, gint num_entries
)
340 GtkActionEntry
*entries
;
342 /* create menu items */
343 entries
= g_new0(GtkActionEntry
, num_entries
);
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
);
353 for(ii
= 0; ii
< num_entries
; ii
++) {
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
);
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
)
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
;
390 static gboolean
my_compose_create_hook(gpointer cw
, gpointer data
)
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
);
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
);
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
)
429 run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE
, compose
);
435 static void refresh_scripts_in_dir(const gchar
*subdir
, ToolbarType toolbar_type
)
439 GError
*error
= NULL
;
440 const char *filename
;
441 GSList
*filenames
= NULL
;
445 scripts_dir
= g_strconcat(get_rc_dir(),
446 G_DIR_SEPARATOR_S
, PYTHON_SCRIPTS_BASE_DIR
,
447 G_DIR_SEPARATOR_S
, subdir
,
449 debug_print("Refreshing: %s\n", scripts_dir
);
451 dir
= g_dir_open(scripts_dir
, 0, &error
);
455 debug_print("Could not open directory '%s': %s\n", subdir
, error
->message
);
462 while((filename
= g_dir_read_name(dir
)) != NULL
) {
465 fn
= g_strdup(filename
);
466 filenames
= g_slist_prepend(filenames
, fn
);
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
);
477 for(walk
= filenames
; walk
; walk
= walk
->next
)
479 g_slist_free(filenames
);
482 static void browse_python_scripts_dir(GtkAction
*action
, gpointer data
)
485 GdkAppLaunchContext
*launch_context
;
486 GError
*error
= NULL
;
489 mainwin
= mainwindow_get_mainwindow();
491 debug_print("Browse Python scripts: Problems getting the mainwindow\n");
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
);
500 debug_print("Could not open scripts dir browser: '%s'\n", error
->message
);
504 g_object_unref(launch_context
);
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
)
537 mainwin
= mainwindow_get_mainwindow();
539 *error
= g_strdup("Could not get main window");
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
);
571 static void python_menu_done(void)
575 mainwin
= mainwindow_get_mainwindow();
577 if(mainwin
&& !claws_is_exiting()) {
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");
605 class_StringIO
= PyObject_GetAttrString(module_StringIO
, "StringIO");
606 if(!class_StringIO
) {
607 debug_print("Error getting traceback: Could not get StringIO class\n");
611 inst_StringIO
= PyObject_CallObject(class_StringIO
, NULL
);
613 debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
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
)
632 PyObject
*inst_StringIO
= NULL
;
635 if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC
, _("Python"), error
))
638 /* init/load prefs */
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"));
648 /* script directories */
649 if(!make_sure_directories_exist(error
))
652 /* register moduke */
653 PyImport_AppendInittab("clawsmail", initclawsmail
);
654 PyImport_AppendInittab("parasite", parasite_python_module_init
);
656 /* initialize python interpreter */
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");
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
) {
677 /* load menu options */
678 if(!python_menu_init(error
)) {
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");
690 hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST
, hook_compose_create
);
691 Py_XDECREF(inst_StringIO
);
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
);
704 gtk_widget_destroy(python_console
);
705 python_console
= NULL
;
708 /* finialize python interpreter */
711 parasite_python_done();
716 debug_print("Python plugin done and unloaded.\n");
720 const gchar
*plugin_name(void)
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"
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"
747 "Executed at plugin load\n\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)
765 const gchar
*plugin_licence(void)
770 const gchar
*plugin_version(void)
775 struct PluginFeature
*plugin_provides(void)
777 static struct PluginFeature features
[] =
778 { {PLUGIN_UTILITY
, N_("Python integration")},
779 {PLUGIN_NOTHING
, NULL
}};