diff options
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | Makefile.am | 9 | ||||
-rwxr-xr-x | configure-plugins | 4 | ||||
-rw-r--r-- | configure.ac | 37 | ||||
-rw-r--r-- | src/main.c | 6 | ||||
-rw-r--r-- | src/plugins/plugins.c | 36 | ||||
-rw-r--r-- | src/plugins/plugins.h | 2 | ||||
-rw-r--r-- | src/plugins/python_api.c | 474 | ||||
-rw-r--r-- | src/plugins/python_api.h | 46 | ||||
-rw-r--r-- | src/plugins/python_plugins.c | 595 | ||||
-rw-r--r-- | src/plugins/python_plugins.h | 68 |
11 files changed, 1275 insertions, 8 deletions
diff --git a/.travis.yml b/.travis.yml index 83c3e029..f7935f5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ install: - lsb_release -a - uname -a - sudo apt-get update - - sudo apt-get -y install libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev libgpgme11-dev expect-dev tcl-dev - - sudo apt-get -y install autoconf-archive libtool + - sudo apt-get -y install libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev libgpgme11-dev autoconf-archive expect-dev tcl-dev + - sudo apt-get -y install libtool python-dev lua5.2 liblua5.2-dev ruby-dev - git clone git://github.com/boothj5/libmesode.git - cd libmesode - mkdir m4 @@ -34,4 +34,4 @@ install: - cd .. - rm -rf stabber - ./bootstrap.sh -script: ./configure --enable-c-plugins && make && make check +script: ./configure --enable-python-plugins --enable-c-plugins && make && make check diff --git a/Makefile.am b/Makefile.am index 365fd21c..30694534 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,6 +130,10 @@ functionaltest_sources = \ main_source = src/main.c +python_sources = \ + src/plugins/python_plugins.h src/plugins/python_plugins.c \ + src/plugins/python_api.h src/plugins/python_api.c + c_sources = \ src/plugins/c_plugins.h src/plugins/c_plugins.c \ src/plugins/c_api.h src/plugins/c_api.c @@ -148,6 +152,11 @@ otr3_sources = \ otr4_sources = \ src/otr/otrlib.h src/otr/otrlibv4.c src/otr/otr.h src/otr/otr.c +if BUILD_PYTHON_API +core_sources += $(python_sources) +unittest_sources += $(python_sources) +endif + if BUILD_C_API core_sources += $(c_sources) unittest_sources += $(c_sources) diff --git a/configure-plugins b/configure-plugins new file mode 100755 index 00000000..af4db3d5 --- /dev/null +++ b/configure-plugins @@ -0,0 +1,4 @@ +#!/bin/sh + +./configure --enable-python-plugins --enable-c-plugins CFLAGS='-g -O0' CXXFLAGS='-g -O0' + diff --git a/configure.ac b/configure.ac index 2c36235d..38ca4f6d 100644 --- a/configure.ac +++ b/configure.ac @@ -42,9 +42,14 @@ AS_IF([test "x$PLATFORM" = xcygwin], AS_IF([test "x$PLATFORM" = xosx], [AC_DEFINE([PLATFORM_OSX], [1], [OSx])]) +### Environment variables +AC_ARG_VAR([PYTHON_FRAMEWORK], [Set base directory for Python Framework]) + ### Options AC_ARG_ENABLE([notifications], [AS_HELP_STRING([--enable-notifications], [enable desktop notifications])]) +AC_ARG_ENABLE([python-plugins], + [AS_HELP_STRING([--enable-python-plugins], [enable Python plugins])]) AC_ARG_ENABLE([c-plugins], [AS_HELP_STRING([--enable-c-plugins], [enable C plugins])]) AC_ARG_ENABLE([plugins], @@ -59,6 +64,34 @@ AC_ARG_WITH([themes], [AS_HELP_STRING([--with-themes[[=PATH]]], [install themes (default yes)])]) ### plugins + +# python +if test "x$enable_plugins" = xno; then + AM_CONDITIONAL([BUILD_PYTHON_API], [false]) +elif test "x$enable_python_plugins" != xno; then + AS_IF([test "x$PLATFORM" = xosx], [ + AS_IF([test "x$PYTHON_FRAMEWORK" = x], [ PYTHON_FRAMEWORK="/Library/Frameworks/Python.framework" ]) + AC_MSG_NOTICE([Symlinking Python.framework to $PYTHON_FRAMEWORK]) + rm -f Python.framework + ln -s $PYTHON_FRAMEWORK Python.framework ]) + AC_CHECK_PROG(PYTHON_CONFIG_EXISTS, python-config, yes, no) + if test "$PYTHON_CONFIG_EXISTS" == "yes"; then + AX_PYTHON_DEVEL + AM_CONDITIONAL([BUILD_PYTHON_API], [true]) + AC_DEFINE([HAVE_PYTHON], [1], [Python support]) + else + if test "x$enable_python_plugins" = xyes; then + AC_MSG_ERROR([Python not found, cannot enable Python plugins.]) + else + AM_CONDITIONAL([BUILD_PYTHON_API], [false]) + AC_MSG_NOTICE([Python development package not found, Python plugin support disabled.]) + fi + fi + AS_IF([test "x$PLATFORM" = xosx], [rm -f Python.framework]) +else + AM_CONDITIONAL([BUILD_PYTHON_API], [false]) +fi + # c LT_INIT if test "x$enable_plugins" = xno; then @@ -232,9 +265,9 @@ AM_CFLAGS="-Wall -Wno-deprecated-declarations" AS_IF([test "x$PACKAGE_STATUS" = xdevelopment], [AM_CFLAGS="$AM_CFLAGS -Wunused -Werror"]) AM_LDFLAGS="$AM_LDFLAGS -export-dynamic" -AM_CPPFLAGS="$AM_CPPFLAGS $glib_CFLAGS $curl_CFLAGS $libnotify_CFLAGS" +AM_CPPFLAGS="$AM_CPPFLAGS $glib_CFLAGS $curl_CFLAGS $libnotify_CFLAGS $PYTHON_CPPFLAGS" AM_CPPFLAGS="$AM_CPPFLAGS -DTHEMES_PATH=\"\\\"$THEMES_PATH\\\"\"" -LIBS="$glib_LIBS $curl_LIBS $libnotify_LIBS $LIBS" +LIBS="$glib_LIBS $curl_LIBS $libnotify_LIBS $PYTHON_LIBS $PYTHON_LDFLAGS $LIBS" AC_SUBST(AM_LDFLAGS) AC_SUBST(AM_CFLAGS) diff --git a/src/main.c b/src/main.c index be5191da..147d0b11 100644 --- a/src/main.c +++ b/src/main.c @@ -130,6 +130,12 @@ main(int argc, char **argv) g_print("C plugins: Disabled\n"); #endif +#ifdef PROF_HAVE_PYTHON + g_print("Python plugins: Enabled\n"); +#else + g_print("Python plugins: Disabled\n"); +#endif + return 0; } diff --git a/src/plugins/plugins.c b/src/plugins/plugins.c index c1c7df3a..976a6698 100644 --- a/src/plugins/plugins.c +++ b/src/plugins/plugins.c @@ -35,20 +35,26 @@ #include <string.h> #include <stdlib.h> +#include "prof_config.h" #include "common.h" #include "config/preferences.h" #include "log.h" #include "plugins/callbacks.h" #include "plugins/autocompleters.h" -#include "plugins/themes.h" #include "plugins/api.h" #include "plugins/plugins.h" +#include "plugins/themes.h" + +#ifdef PROF_HAVE_PYTHON +#include "plugins/python_plugins.h" +#include "plugins/python_api.h" +#endif #ifdef PROF_HAVE_C #include "plugins/c_plugins.h" #include "plugins/c_api.h" - #endif + #include "ui/ui.h" static GSList* plugins; @@ -59,12 +65,16 @@ plugins_init(void) plugins = NULL; callbacks_init(); autocompleters_init(); - plugin_themes_init(); +#ifdef PROF_HAVE_PYTHON + python_env_init(); +#endif #ifdef PROF_HAVE_C c_env_init(); #endif + plugin_themes_init(); + // load plugins gchar **plugins_load = prefs_get_plugins(); if (plugins_load) { @@ -73,6 +83,15 @@ plugins_init(void) { gboolean loaded = FALSE; gchar *filename = plugins_load[i]; +#ifdef PROF_HAVE_PYTHON + if (g_str_has_suffix(filename, ".py")) { + ProfPlugin *plugin = python_plugin_create(filename); + if (plugin) { + plugins = g_slist_append(plugins, plugin); + loaded = TRUE; + } + } +#endif #ifdef PROF_HAVE_C if (g_str_has_suffix(filename, ".so")) { ProfPlugin *plugin = c_plugin_create(filename); @@ -95,6 +114,7 @@ plugins_init(void) curr = g_slist_next(curr); } } + prefs_free_plugins(plugins_load); return; @@ -111,6 +131,8 @@ plugins_get_lang_string(ProfPlugin *plugin) { switch (plugin->lang) { + case LANG_PYTHON: + return "Python"; case LANG_C: return "C"; default: @@ -391,6 +413,11 @@ plugins_shutdown(void) GSList *curr = plugins; while (curr) { +#ifdef PROF_HAVE_PYTHON + if (((ProfPlugin *)curr->data)->lang == LANG_PYTHON) { + python_plugin_destroy(curr->data); + } +#endif #ifdef PROF_HAVE_C if (((ProfPlugin *)curr->data)->lang == LANG_C) { c_plugin_destroy(curr->data); @@ -399,6 +426,9 @@ plugins_shutdown(void) curr = g_slist_next(curr); } +#ifdef PROF_HAVE_PYTHON + python_shutdown(); +#endif #ifdef PROF_HAVE_C c_shutdown(); #endif diff --git a/src/plugins/plugins.h b/src/plugins/plugins.h index 9a3e8e74..3abd70b2 100644 --- a/src/plugins/plugins.h +++ b/src/plugins/plugins.h @@ -38,6 +38,7 @@ #include "command/command.h" typedef enum { + LANG_PYTHON, LANG_C } lang_t; @@ -106,4 +107,5 @@ gchar * plugins_get_dir(void); CommandHelp* plugins_get_help(const char *const cmd); void plugins_win_process_line(char *win, const char * const line); + #endif diff --git a/src/plugins/python_api.c b/src/plugins/python_api.c new file mode 100644 index 00000000..a3964889 --- /dev/null +++ b/src/plugins/python_api.c @@ -0,0 +1,474 @@ +/* + * python_api.c + * + * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include <Python.h> + +#include <glib.h> + +#include "plugins/api.h" +#include "plugins/python_api.h" +#include "plugins/python_plugins.h" +#include "plugins/callbacks.h" +#include "plugins/autocompleters.h" + +static PyObject* +python_api_cons_alert(PyObject *self, PyObject *args) +{ + api_cons_alert(); + return Py_BuildValue(""); +} + +static PyObject* +python_api_cons_show(PyObject *self, PyObject *args) +{ + const char *message = NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { + return Py_BuildValue(""); + } + api_cons_show(message); + return Py_BuildValue(""); +} + +static PyObject* +python_api_cons_show_themed(PyObject *self, PyObject *args) +{ + const char *group = NULL; + const char *key = NULL; + const char *def = NULL; + const char *message = NULL; + if (!PyArg_ParseTuple(args, "zzzs", &group, &key, &def, &message)) { + return Py_BuildValue(""); + } + api_cons_show_themed(group, key, def, message); + return Py_BuildValue(""); +} + +static PyObject* +python_api_cons_bad_cmd_usage(PyObject *self, PyObject *args) +{ + const char *cmd = NULL; + if (!PyArg_ParseTuple(args, "s", &cmd)) { + return Py_BuildValue(""); + } + api_cons_bad_cmd_usage(cmd); + return Py_BuildValue(""); +} + +static PyObject* +python_api_register_command(PyObject *self, PyObject *args) +{ + const char *command_name = NULL; + int min_args = 0; + int max_args = 0; + PyObject *synopsis = NULL; + const char *description = NULL; + PyObject *arguments = NULL; + PyObject *examples = NULL; + PyObject *p_callback = NULL; + + if (!PyArg_ParseTuple(args, "siiOsOOO", &command_name, &min_args, &max_args, + &synopsis, &description, &arguments, &examples, &p_callback)) { + return Py_BuildValue(""); + } + + if (p_callback && PyCallable_Check(p_callback)) { + Py_ssize_t len = PyList_Size(synopsis); + const char *c_synopsis[len == 0 ? 0 : len+1]; + Py_ssize_t i = 0; + for (i = 0; i < len; i++) { + PyObject *item = PyList_GetItem(synopsis, i); + char *c_item = PyString_AsString(item); + c_synopsis[i] = c_item; + } + c_synopsis[len] = NULL; + + Py_ssize_t args_len = PyList_Size(arguments); + const char *c_arguments[args_len == 0 ? 0 : args_len+1][2]; + i = 0; + for (i = 0; i < args_len; i++) { + PyObject *item = PyList_GetItem(arguments, i); + Py_ssize_t len2 = PyList_Size(item); + if (len2 != 2) { + return Py_BuildValue(""); + } + PyObject *arg = PyList_GetItem(item, 0); + char *c_arg = PyString_AsString(arg); + PyObject *desc = PyList_GetItem(item, 1); + char *c_desc = PyString_AsString(desc); + + c_arguments[i][0] = c_arg; + c_arguments[i][1] = c_desc; + } + + c_arguments[args_len][0] = NULL; + c_arguments[args_len][1] = NULL; + + len = PyList_Size(examples); + const char *c_examples[len == 0 ? 0 : len+1]; + i = 0; + for (i = 0; i < len; i++) { + PyObject *item = PyList_GetItem(examples, i); + char *c_item = PyString_AsString(item); + c_examples[i] = c_item; + } + c_examples[len] = NULL; + + api_register_command(command_name, min_args, max_args, c_synopsis, + description, c_arguments, c_examples, p_callback, python_command_callback); + } + + return Py_BuildValue(""); +} + +static PyObject * +python_api_register_timed(PyObject *self, PyObject *args) +{ + PyObject *p_callback = NULL; + int interval_seconds = 0; + + if (!PyArg_ParseTuple(args, "Oi", &p_callback, &interval_seconds)) { + return Py_BuildValue(""); + } + + if (p_callback && PyCallable_Check(p_callback)) { + api_register_timed(p_callback, interval_seconds, python_timed_callback); + } + + return Py_BuildValue(""); +} + +static PyObject * +python_api_register_ac(PyObject *self, PyObject *args) +{ + const char *key = NULL; + PyObject *items = NULL; + + if (!PyArg_ParseTuple(args, "sO", &key, &items)) { + return Py_BuildValue(""); + } + + Py_ssize_t len = PyList_Size(items); + char *c_items[len]; + + Py_ssize_t i = 0; + for (i = 0; i < len; i++) { + PyObject *item = PyList_GetItem(items, i); + char *c_item = PyString_AsString(item); + c_items[i] = c_item; + } + c_items[len] = NULL; + + autocompleters_add(key, c_items); + return Py_BuildValue(""); +} + +static PyObject* +python_api_notify(PyObject *self, PyObject *args) +{ + const char *message = NULL; + const char *category = NULL; + int timeout_ms = 5000; + + if (!PyArg_ParseTuple(args, "sis", &message, &timeout_ms, &category)) { + return Py_BuildValue(""); + } + + api_notify(message, category, timeout_ms); + + return Py_BuildValue(""); +} + +static PyObject* +python_api_send_line(PyObject *self, PyObject *args) +{ + char *line = NULL; + if (!PyArg_ParseTuple(args, "s", &line)) { + return Py_BuildValue(""); + } + + api_send_line(line); + + return Py_BuildValue(""); +} + +static PyObject * +python_api_get_current_recipient(PyObject *self, PyObject *args) +{ + char *recipient = api_get_current_recipient(); + if (recipient) { + return Py_BuildValue("s", recipient); + } else { + return Py_BuildValue(""); + } +} + +static PyObject * +python_api_get_current_muc(PyObject *self, PyObject *args) +{ + char *room = api_get_current_muc(); + if (room) { + return Py_BuildValue("s", room); + } else { + return Py_BuildValue(""); + } +} + +static PyObject* +python_api_current_win_is_console(PyObject *self, PyObject *args) +{ + int res = api_current_win_is_console(); + if (res) { + return Py_BuildValue("O", Py_True); + } else { + return Py_BuildValue("O", Py_False); + } +} + +static PyObject * +python_api_log_debug(PyObject *self, PyObject *args) +{ + const char *message = NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { + return Py_BuildValue(""); + } + api_log_debug(message); + return Py_BuildValue(""); +} + +static PyObject * +python_api_log_info(PyObject *self, PyObject *args) +{ + const char *message = NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { + return Py_BuildValue(""); + } + api_log_info(message); + return Py_BuildValue(""); +} + +static PyObject * +python_api_log_warning(PyObject *self, PyObject *args) +{ + const char *message = NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { + return Py_BuildValue(""); + } + api_log_warning(message); + return Py_BuildValue(""); +} + +static PyObject * +python_api_log_error(PyObject *self, PyObject *args) +{ + const char *message = NULL; + if (!PyArg_ParseTuple(args, "s", &message)) { + return Py_BuildValue(""); + } + api_log_error(message); + return Py_BuildValue(""); +} + +static PyObject * +python_api_win_exists(PyObject *self, PyObject *args) +{ + char *tag = NULL; + if (!PyArg_ParseTuple(args, "s", &tag)) { + return Py_BuildValue(""); + } + + if (api_win_exists(tag)) { + return Py_BuildValue("i", 1); + } else { + return Py_BuildValue("i", 0); + } +} + +static PyObject * +python_api_win_create(PyObject *self, PyObject *args) +{ + char *tag = NULL; + PyObject *p_callback = NULL; + + if (!PyArg_ParseTuple(args, "sO", &tag, &p_callback)) { + return Py_BuildValue(""); + } + + if (p_callback && PyCallable_Check(p_callback)) { + api_win_create(tag, p_callback, NULL, python_window_callback); + } + + return Py_BuildValue(""); +} + +static PyObject * +python_api_win_focus(PyObject *self, PyObject *args) +{ + char *tag = NULL; + + if (!PyArg_ParseTuple(args, "s", &tag)) { + return Py_BuildValue(""); + } + + api_win_focus(tag); + return Py_BuildValue(""); +} + +static PyObject * +python_api_win_show(PyObject *self, PyObject *args) +{ + char *tag = NULL; + char *line = NULL; + + if (!PyArg_ParseTuple(args, "ss", &tag, &line)) { + return Py_BuildValue(""); + } + + api_win_show(tag, line); + return Py_BuildValue(""); +} + +static PyObject * +python_api_win_show_themed(PyObject *self, PyObject *args) +{ + char *tag = NULL; + char *group = NULL; + char *key = NULL; + char *def = NULL; + char *line = NULL; + + if (!PyArg_ParseTuple(args, "szzzs", &tag, &group, &key, &def, &line)) { + return Py_BuildValue(""); + } + + api_win_show_themed(tag, group, key, def, line); + return Py_BuildValue(""); +} + +void +python_command_callback(PluginCommand *command, gchar **args) +{ + disable_python_threads(); + PyObject *p_args = NULL; + int num_args = g_strv_length(args); + if (num_args == 0) { + if (command->max_args == 1) { + p_args = Py_BuildValue("(O)", Py_BuildValue("")); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } else { + PyObject_CallObject(command->callback, p_args); + } + } else if (num_args == 1) { + p_args = Py_BuildValue("(s)", args[0]); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } else if (num_args == 2) { + p_args = Py_BuildValue("ss", args[0], args[1]); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } else if (num_args == 3) { + p_args = Py_BuildValue("sss", args[0], args[1], args[2]); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } else if (num_args == 4) { + p_args = Py_BuildValue("ssss", args[0], args[1], args[2], args[3]); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } else if (num_args == 5) { + p_args = Py_BuildValue("sssss", args[0], args[1], args[2], args[3], args[4]); + PyObject_CallObject(command->callback, p_args); + Py_XDECREF(p_args); + } + + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + allow_python_threads(); +} + +void +python_timed_callback(PluginTimedFunction *timed_function) +{ + disable_python_threads(); + PyObject_CallObject(timed_function->callback, NULL); + allow_python_threads(); +} + +void +python_window_callback(PluginWindowCallback *window_callback, char *tag, char *line) +{ + disable_python_threads(); + PyObject *p_args = NULL; + p_args = Py_BuildValue("ss", tag, line); + PyObject_CallObject(window_callback->callback, p_args); + Py_XDECREF(p_args); + + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + allow_python_threads(); +} + +static PyMethodDef apiMethods[] = { + { "cons_alert", python_api_cons_alert, METH_NOARGS, "Highlight the console window in the status bar." }, + { "cons_show", python_api_cons_show, METH_VARARGS, "Print a line to the console." }, + { "cons_show_themed", python_api_cons_show_themed, METH_VARARGS, "Print a themed line to the console" }, + { "cons_bad_cmd_usage", python_api_cons_bad_cmd_usage, METH_VARARGS, "Show invalid command message in console" }, + { "register_command", python_api_register_command, METH_VARARGS, "Register a command." }, + { "register_timed", python_api_register_timed, METH_VARARGS, "Register a timed function." }, + { "register_ac", python_api_register_ac, METH_VARARGS, "Register an autocompleter." }, + { "send_line", python_api_send_line, METH_VARARGS, "Send a line of input." }, + { "notify", python_api_notify, METH_VARARGS, "Send desktop notification." }, + { "get_current_recipient", python_api_get_current_recipient, METH_VARARGS, "Return the jid of the recipient of the current window." }, + { "get_current_muc", python_api_get_current_muc, METH_VARARGS, "Return the jid of the room of the current window." }, + { "current_win_is_console", python_api_current_win_is_console, METH_VARARGS, "Returns whether the current window is the console." }, + { "log_debug", python_api_log_debug, METH_VARARGS, "Log a debug message" }, + { "log_info", python_api_log_info, METH_VARARGS, "Log an info message" }, + { "log_warning", python_api_log_warning, METH_VARARGS, "Log a warning message" }, + { "log_error", python_api_log_error, METH_VARARGS, "Log an error message" }, + { "win_exists", python_api_win_exists, METH_VARARGS, "Determine whether a window exists." }, + { "win_create", python_api_win_create, METH_VARARGS, "Create a new window." }, + { "win_focus", python_api_win_focus, METH_VARARGS, "Focus a window." }, + { "win_show", python_api_win_show, METH_VARARGS, "Show text in the window." }, + { "win_show_themed", python_api_win_show_themed, METH_VARARGS, "Show themed text in the window." }, + { NULL, NULL, 0, NULL } +}; + +void +python_api_init(void) +{ + Py_InitModule("prof", apiMethods); +} diff --git a/src/plugins/python_api.h b/src/plugins/python_api.h new file mode 100644 index 00000000..f936a9cb --- /dev/null +++ b/src/plugins/python_api.h @@ -0,0 +1,46 @@ +/* + * python_api.h + * + * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef PYTHON_API_H +#define PYTHON_API_H + +void python_env_init(void); +void python_api_init(void); +void python_shutdown(void); + +void python_command_callback(PluginCommand *command, gchar **args); +void python_timed_callback(PluginTimedFunction *timed_function); +void python_window_callback(PluginWindowCallback *window_callback, char *tag, char *line); + +#endif diff --git a/src/plugins/python_plugins.c b/src/plugins/python_plugins.c new file mode 100644 index 00000000..f78dc598 --- /dev/null +++ b/src/plugins/python_plugins.c @@ -0,0 +1,595 @@ +/* + * python_plugins.c + * + * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include <Python.h> + +#include "config/preferences.h" +#include "plugins/api.h" +#include "plugins/callbacks.h" +#include "plugins/plugins.h" +#include "plugins/python_api.h" +#include "plugins/python_plugins.h" +#include "ui/ui.h" + +//static PyThreadState *thread_state; + +void +allow_python_threads() +{ +// thread_state = PyEval_SaveThread(); +} + +void +disable_python_threads() +{ +// PyEval_RestoreThread(thread_state); +} + +void +python_env_init(void) +{ + Py_Initialize(); + PyEval_InitThreads(); + python_api_init(); + GString *path = g_string_new(Py_GetPath()); + + g_string_append(path, ":"); + gchar *plugins_dir = plugins_get_dir(); + g_string_append(path, plugins_dir); + g_string_append(path, "/"); + g_free(plugins_dir); + + PySys_SetPath(path->str); + g_string_free(path, TRUE); + + // add site packages paths + PyRun_SimpleString( + "import site\n" + "import sys\n" + "from distutils.sysconfig import get_python_lib\n" + "sys.path.append(get_python_lib())\n" + "for dir in site.getsitepackages():\n" + " sys.path.append(dir)\n" + ); + + allow_python_threads(); +} + +ProfPlugin * +python_plugin_create(const char * const filename) +{ + disable_python_threads(); + gchar *module_name = g_strndup(filename, strlen(filename) - 3); + PyObject *p_module = PyImport_ImportModule(module_name); + python_check_error(); + if (p_module) { + ProfPlugin *plugin = malloc(sizeof(ProfPlugin)); + plugin->name = strdup(module_name); + plugin->lang = LANG_PYTHON; + plugin->module = p_module; + plugin->init_func = python_init_hook; + plugin->on_start_func = python_on_start_hook; + plugin->on_shutdown_func = python_on_shutdown_hook; + plugin->on_connect_func = python_on_connect_hook; + plugin->on_disconnect_func = python_on_disconnect_hook; + plugin->pre_chat_message_display = python_pre_chat_message_display_hook; + plugin->post_chat_message_display = python_post_chat_message_display_hook; + plugin->pre_chat_message_send = python_pre_chat_message_send_hook; + plugin->post_chat_message_send = python_post_chat_message_send_hook; + plugin->pre_room_message_display = python_pre_room_message_display_hook; + plugin->post_room_message_display = python_post_room_message_display_hook; + plugin->pre_room_message_send = python_pre_room_message_send_hook; + plugin->post_room_message_send = python_post_room_message_send_hook; + plugin->pre_priv_message_display = python_pre_priv_message_display_hook; + plugin->post_priv_message_display = python_post_priv_message_display_hook; + plugin->pre_priv_message_send = python_pre_priv_message_send_hook; + plugin->post_priv_message_send = python_post_priv_message_send_hook; + g_free(module_name); + + allow_python_threads(); + return plugin; + } else { + g_free(module_name); + allow_python_threads(); + return NULL; + } +} + +void +python_init_hook(ProfPlugin *plugin, const char * const version, const char * const status) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", version, status); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_init")) { + p_function = PyObject_GetAttrString(p_module, "prof_init"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + allow_python_threads(); +} + +void +python_on_start_hook(ProfPlugin *plugin) +{ + disable_python_threads(); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_on_start")) { + p_function = PyObject_GetAttrString(p_module, "prof_on_start"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, NULL); + python_check_error(); + Py_XDECREF(p_function); + } + } + allow_python_threads(); +} + +void +python_on_shutdown_hook(ProfPlugin *plugin) +{ + disable_python_threads(); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_on_shutdown")) { + p_function = PyObject_GetAttrString(p_module, "prof_on_shutdown"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, NULL); + python_check_error(); + Py_XDECREF(p_function); + } + } + allow_python_threads(); +} + +void +python_on_connect_hook(ProfPlugin *plugin, const char * const account_name, + const char * const fulljid) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", account_name, fulljid); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_on_connect")) { + p_function = PyObject_GetAttrString(p_module, "prof_on_connect"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + allow_python_threads(); +} + +void +python_on_disconnect_hook(ProfPlugin *plugin, const char * const account_name, + const char * const fulljid) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", account_name, fulljid); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_on_disconnect")) { + p_function = PyObject_GetAttrString(p_module, "prof_on_disconnect"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + allow_python_threads(); +} + +char* +python_pre_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", jid, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_chat_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_chat_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", jid, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_chat_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_chat_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +char* +python_pre_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", jid, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_chat_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_chat_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", jid, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_chat_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_chat_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +char* +python_pre_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_room_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_room_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_room_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_room_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +char* +python_pre_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", room, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_room_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_room_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("ss", room, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_room_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_room_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +char* +python_pre_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_priv_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_priv_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_priv_message_display")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_priv_message_display"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +char* +python_pre_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_pre_priv_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_pre_priv_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject *result = PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + if (PyUnicode_Check(result)) { + char *result_str = strdup(PyString_AsString(PyUnicode_AsUTF8String(result))); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else if (result != Py_None) { + char *result_str = strdup(PyString_AsString(result)); + Py_XDECREF(result); + allow_python_threads(); + return result_str; + } else { + allow_python_threads(); + return NULL; + } + } + } + + allow_python_threads(); + return NULL; +} + +void +python_post_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message) +{ + disable_python_threads(); + PyObject *p_args = Py_BuildValue("sss", room, nick, message); + PyObject *p_function; + + PyObject *p_module = plugin->module; + if (PyObject_HasAttrString(p_module, "prof_post_priv_message_send")) { + p_function = PyObject_GetAttrString(p_module, "prof_post_priv_message_send"); + python_check_error(); + if (p_function && PyCallable_Check(p_function)) { + PyObject_CallObject(p_function, p_args); + python_check_error(); + Py_XDECREF(p_function); + } + } + + allow_python_threads(); +} + +void +python_check_error(void) +{ + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } +} + +void +python_plugin_destroy(ProfPlugin *plugin) +{ + disable_python_threads(); + free(plugin->name); + Py_XDECREF(plugin->module); + free(plugin); + allow_python_threads(); +} + +void +python_shutdown(void) +{ + disable_python_threads(); + Py_Finalize(); +} diff --git a/src/plugins/python_plugins.h b/src/plugins/python_plugins.h new file mode 100644 index 00000000..24700731 --- /dev/null +++ b/src/plugins/python_plugins.h @@ -0,0 +1,68 @@ +/* + * python_plugins.h + * + * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef PYTHON_PLUGINS_H +#define PYTHON_PLUGINS_H + +#include "plugins/plugins.h" + +ProfPlugin* python_plugin_create(const char * const filename); +void python_plugin_destroy(ProfPlugin *plugin); +void python_check_error(void); +void allow_python_threads(); +void disable_python_threads(); + +void python_init_hook(ProfPlugin *plugin, const char * const version, const char * const status); +void python_on_start_hook(ProfPlugin *plugin); +void python_on_shutdown_hook(ProfPlugin *plugin); +void python_on_connect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid); +void python_on_disconnect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid); + +char* python_pre_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message); +void python_post_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message); +char* python_pre_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message); +void python_post_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message); + +char* python_pre_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message); +void python_post_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message); +char* python_pre_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message); +void python_post_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message); + +char* python_pre_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message); +void python_post_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message); +char* python_pre_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message); +void python_post_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message); + + +#endif |