about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorJames Booth <boothj5@gmail.com>2016-02-14 22:28:55 +0000
committerJames Booth <boothj5@gmail.com>2016-02-14 22:28:55 +0000
commit41fe8c22b1f8c1cf666d42052cd73d59e40a2ed2 (patch)
tree3b53c1fbc2e5dc8c6daccf7fe7bb9f27d1c9d05a /src
parentf887a35c0cd550a3c635630da2bd83bb7400b957 (diff)
downloadprofani-tty-41fe8c22b1f8c1cf666d42052cd73d59e40a2ed2.tar.gz
Added C plugin code from plugins branch
Diffstat (limited to 'src')
-rw-r--r--src/command/command.c38
-rw-r--r--src/command/command.h4
-rw-r--r--src/command/commands.c60
-rw-r--r--src/command/commands.h1
-rw-r--r--src/common.c14
-rw-r--r--src/config/preferences.c15
-rw-r--r--src/config/preferences.h4
-rw-r--r--src/config/theme.c6
-rw-r--r--src/config/theme.h2
-rw-r--r--src/event/client_events.c87
-rw-r--r--src/event/server_events.c81
-rw-r--r--src/main.c26
-rw-r--r--src/pgp/gpg.c2
-rw-r--r--src/plugins/api.c233
-rw-r--r--src/plugins/api.h69
-rw-r--r--src/plugins/autocompleters.c92
-rw-r--r--src/plugins/autocompleters.h46
-rw-r--r--src/plugins/c_api.c240
-rw-r--r--src/plugins/c_api.h41
-rw-r--r--src/plugins/c_plugins.c367
-rw-r--r--src/plugins/c_plugins.h67
-rw-r--r--src/plugins/callbacks.c126
-rw-r--r--src/plugins/callbacks.h68
-rw-r--r--src/plugins/plugins.c497
-rw-r--r--src/plugins/plugins.h108
-rw-r--r--src/plugins/profapi.c70
-rw-r--r--src/plugins/profapi.h72
-rw-r--r--src/profanity.c41
-rw-r--r--src/profanity.h3
-rw-r--r--src/ui/buffer.c6
-rw-r--r--src/ui/buffer.h2
-rw-r--r--src/ui/chatwin.c19
-rw-r--r--src/ui/console.c30
-rw-r--r--src/ui/core.c39
-rw-r--r--src/ui/inputwin.c6
-rw-r--r--src/ui/notifier.c35
-rw-r--r--src/ui/statusbar.c6
-rw-r--r--src/ui/titlebar.c2
-rw-r--r--src/ui/ui.h9
-rw-r--r--src/ui/win_types.h16
-rw-r--r--src/ui/window.c34
-rw-r--r--src/ui/window.h6
-rw-r--r--src/window_list.c34
-rw-r--r--src/window_list.h2
-rw-r--r--src/xmpp/bookmark.c6
-rw-r--r--src/xmpp/capabilities.c14
-rw-r--r--src/xmpp/capabilities.h6
-rw-r--r--src/xmpp/connection.c23
-rw-r--r--src/xmpp/connection.h6
-rw-r--r--src/xmpp/form.c6
-rw-r--r--src/xmpp/iq.c14
-rw-r--r--src/xmpp/message.c8
-rw-r--r--src/xmpp/presence.c6
-rw-r--r--src/xmpp/roster.c7
-rw-r--r--src/xmpp/stanza.c6
-rw-r--r--src/xmpp/stanza.h6
-rw-r--r--src/xmpp/xmpp.h8
57 files changed, 2580 insertions, 262 deletions
diff --git a/src/command/command.c b/src/command/command.c
index 05b047db..67a725f5 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <errno.h>
@@ -58,10 +58,11 @@
 #include "xmpp/form.h"
 #include "log.h"
 #include "muc.h"
-#ifdef HAVE_LIBOTR
+#include "plugins/plugins.h"
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 #include "pgp/gpg.h"
 #endif
 #include "profanity.h"
@@ -1733,6 +1734,17 @@ static struct cmd_t command_defs[] =
             "/account rename me gtalk")
     },
 
+    { "/plugins",
+        cmd_plugins, parse_args, 0, 0, NULL,
+        CMD_NOTAGS
+        CMD_SYN(
+            "/plugins")
+        CMD_DESC(
+            "Show currently installed plugins. ")
+        CMD_NOARGS
+        CMD_NOEXAMPLES
+    },
+
     { "/prefs",
         cmd_prefs, parse_args, 0, 1, NULL,
         CMD_NOTAGS
@@ -2641,7 +2653,7 @@ cmd_exists(char *cmd)
 }
 
 void
-cmd_autocomplete_add(char *value)
+cmd_autocomplete_add(const char *const value)
 {
     if (commands_ac) {
         autocomplete_add(commands_ac, value);
@@ -2687,7 +2699,7 @@ cmd_autocomplete_remove_form_fields(DataForm *form)
 }
 
 void
-cmd_autocomplete_remove(char *value)
+cmd_autocomplete_remove(const char *const value)
 {
     if (commands_ac) {
         autocomplete_remove(commands_ac, value);
@@ -2756,7 +2768,7 @@ cmd_reset_autocomplete(ProfWin *window)
     tlscerts_reset_ac();
     prefs_reset_boolean_choice();
     presence_reset_sub_request_search();
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_autocomplete_key_reset();
 #endif
     autocomplete_reset(help_ac);
@@ -2867,6 +2879,7 @@ cmd_reset_autocomplete(ProfWin *window)
     prefs_reset_room_trigger_ac();
     win_reset_search_attempts();
     win_close_reset_search_attempts();
+    plugins_reset_autocomplete();
 }
 
 gboolean
@@ -2968,6 +2981,8 @@ _cmd_execute(ProfWin *window, const char *const command, const char *const inp)
             g_strfreev(args);
             return result;
         }
+    } else if (plugins_run_command(inp)) {
+        return TRUE;
     } else {
         gboolean ran_alias = FALSE;
         gboolean alias_result = cmd_execute_alias(window, inp, &ran_alias);
@@ -3128,6 +3143,11 @@ _cmd_complete_parameters(ProfWin *window, const char *const input)
     }
     g_hash_table_destroy(ac_funcs);
 
+    result = plugins_autocomplete(input);
+    if (result) {
+        return result;
+    }
+
     if (g_str_has_prefix(input, "/field")) {
         result = _form_field_autocomplete(window, input);
         if (result) {
@@ -3190,7 +3210,7 @@ _who_autocomplete(ProfWin *window, const char *const input)
         }
     }
 
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3647,7 +3667,7 @@ _pgp_autocomplete(ProfWin *window, const char *const input)
         return found;
     }
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     gboolean result;
     gchar **args = parse_args(input, 2, 3, &result);
     if ((strncmp(input, "/pgp", 4) == 0) && (result == TRUE)) {
@@ -4568,7 +4588,7 @@ _account_autocomplete(ProfWin *window, const char *const input)
             if (found) {
                 return found;
             }
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
         } else if ((g_strv_length(args) > 3) && (g_strcmp0(args[2], "pgpkeyid")) == 0) {
             g_string_append(beginning, " ");
             g_string_append(beginning, args[2]);
diff --git a/src/command/command.h b/src/command/command.h
index cbab33f4..76f9560e 100644
--- a/src/command/command.h
+++ b/src/command/command.h
@@ -47,8 +47,8 @@ void cmd_uninit(void);
 
 char* cmd_autocomplete(ProfWin *window, const char *const input);
 void cmd_reset_autocomplete(ProfWin *window);
-void cmd_autocomplete_add(char *value);
-void cmd_autocomplete_remove(char *value);
+void cmd_autocomplete_add(const char *const value);
+void cmd_autocomplete_remove(const char *const value);
 void cmd_autocomplete_add_form_fields(DataForm *form);
 void cmd_autocomplete_remove_form_fields(DataForm *form);
 void cmd_alias_add(char *value);
diff --git a/src/command/commands.c b/src/command/commands.c
index ee2e9c83..74633e99 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -61,13 +61,14 @@
 #include "jid.h"
 #include "log.h"
 #include "muc.h"
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 #include "pgp/gpg.h"
 #endif
 #include "profanity.h"
+#include "plugins/plugins.h"
 #include "tools/autocomplete.h"
 #include "tools/parser.h"
 #include "tools/tinyurl.h"
@@ -100,12 +101,19 @@ cmd_execute_default(ProfWin *window, const char *inp)
         return TRUE;
     }
 
-    // handle non commands in non chat windows
-    if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
+    // handle non commands in non chat or plugin windows
+    if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE && window->type != WIN_PLUGIN) {
         cons_show("Unknown command: %s", inp);
         return TRUE;
     }
 
+    // handle plugin window
+    if (window->type == WIN_PLUGIN) {
+        ProfPluginWin *pluginwin = (ProfPluginWin*)window;
+        plugins_win_process_line(pluginwin->tag, inp);
+        return TRUE;
+    }
+
     jabber_conn_status_t status = jabber_get_connection_status();
     if (status != JABBER_CONNECTED) {
         ui_current_print_line("You are not currently connected.");
@@ -167,7 +175,7 @@ gboolean
 cmd_tls(ProfWin *window, const char *const command, gchar **args)
 {
     if (g_strcmp0(args[0], "certpath") == 0) {
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         if (g_strcmp0(args[1], "set") == 0) {
             if (args[2] == NULL) {
                 cons_bad_cmd_usage(command);
@@ -203,7 +211,7 @@ cmd_tls(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
 #endif
     } else if (g_strcmp0(args[0], "trust") == 0) {
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         jabber_conn_status_t conn_status = jabber_get_connection_status();
         if (conn_status != JABBER_CONNECTED) {
             cons_show("You are not currently connected.");
@@ -232,7 +240,7 @@ cmd_tls(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
 #endif
     } else if (g_strcmp0(args[0], "trusted") == 0) {
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         GList *certs = tlscerts_list();
         GList *curr = certs;
 
@@ -255,7 +263,7 @@ cmd_tls(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
 #endif
     } else if (g_strcmp0(args[0], "revoke") == 0) {
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         if (args[1] == NULL) {
             cons_bad_cmd_usage(command);
         } else {
@@ -274,7 +282,7 @@ cmd_tls(ProfWin *window, const char *const command, gchar **args)
     } else if (g_strcmp0(args[0], "show") == 0) {
         return _cmd_set_boolean_preference(args[1], command, "TLS titlebar indicator", PREF_TLS_SHOW);
     } else if (g_strcmp0(args[0], "cert") == 0) {
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         if (args[1]) {
             TLSCertificate *cert = tlscerts_get_trusted(args[1]);
             if (!cert) {
@@ -654,7 +662,7 @@ cmd_account(ProfWin *window, const char *const command, gchar **args)
                     }
                     cons_show("");
                 } else if (strcmp(property, "pgpkeyid") == 0) {
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
                     char *err_str = NULL;
                     if (!p_gpg_valid_key(value, &err_str)) {
                         cons_show("Invalid PGP key ID specified: %s, see /pgp keys", err_str);
@@ -1886,7 +1894,7 @@ cmd_msg(ProfWin *window, const char *const command, gchar **args)
         if (msg) {
             cl_ev_send_msg(chatwin, msg);
         } else {
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
             if (otr_is_secure(barejid)) {
                 chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
             }
@@ -2774,7 +2782,7 @@ cmd_resource(ProfWin *window, const char *const command, gchar **args)
             return TRUE;
         }
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
         if (otr_is_secure(chatwin->barejid)) {
             cons_show("Cannot choose resource during an OTR session.");
             return TRUE;
@@ -5603,9 +5611,30 @@ cmd_xa(ProfWin *window, const char *const command, gchar **args)
 }
 
 gboolean
+cmd_plugins(ProfWin *window, const char *const command, gchar **args)
+{
+    GSList *plugins = plugins_get_list();
+
+    GSList *curr = plugins;
+    if (curr == NULL) {
+        cons_show("No plugins installed.");
+    } else {
+        cons_show("Installed plugins:");
+        while (curr) {
+            ProfPlugin *plugin = curr->data;
+            char *lang = plugins_get_lang_string(plugin);
+            cons_show("  %s (%s)", plugin->name, lang);
+            curr = g_slist_next(curr);
+        }
+    }
+    g_slist_free(curr);
+    return TRUE;
+}
+
+gboolean
 cmd_pgp(ProfWin *window, const char *const command, gchar **args)
 {
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     if (args[0] == NULL) {
         cons_bad_cmd_usage(command);
         return TRUE;
@@ -5837,13 +5866,12 @@ cmd_pgp(ProfWin *window, const char *const command, gchar **args)
     cons_show("This version of Profanity has not been built with PGP support enabled");
     return TRUE;
 #endif
-
 }
 
 gboolean
 cmd_otr(ProfWin *window, const char *const command, gchar **args)
 {
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     if (args[0] == NULL) {
         cons_bad_cmd_usage(command);
         return TRUE;
diff --git a/src/command/commands.h b/src/command/commands.h
index 14683115..4da3f762 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -153,6 +153,7 @@ gboolean cmd_script(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_export(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_charset(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_console(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_plugins(ProfWin *window, const char *const command, gchar **args);
 
 gboolean cmd_form_field(ProfWin *window, char *tag, gchar **args);
 
diff --git a/src/common.c b/src/common.c
index 33a2dffd..ea4d4bfd 100644
--- a/src/common.c
+++ b/src/common.c
@@ -31,7 +31,7 @@
  * source files in the program, then also delete it here.
  *
  */
-#include "config.h"
+#include "prof_config.h"
 
 #include <sys/select.h>
 #include <assert.h>
@@ -46,6 +46,12 @@
 #include <curl/easy.h>
 #include <glib.h>
 
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
+#include <ncursesw/ncurses.h>
+#elif PROF_HAVE_NCURSES_H
+#include <ncurses.h>
+#endif
+
 #include "tools/p_sha1.h"
 
 #include "log.h"
@@ -341,7 +347,7 @@ release_is_new(char *found_version)
 {
     int curr_maj, curr_min, curr_patch, found_maj, found_min, found_patch;
 
-    int parse_curr = sscanf(PACKAGE_VERSION, "%d.%d.%d", &curr_maj, &curr_min,
+    int parse_curr = sscanf(PROF_PACKAGE_VERSION, "%d.%d.%d", &curr_maj, &curr_min,
         &curr_patch);
     int parse_found = sscanf(found_version, "%d.%d.%d", &found_maj, &found_min,
         &found_patch);
@@ -651,10 +657,10 @@ is_notify_enabled(void)
 {
     gboolean notify_enabled = FALSE;
 
-#ifdef HAVE_OSXNOTIFY
+#ifdef PROF_HAVE_OSXNOTIFY
     notify_enabled = TRUE;
 #endif
-#ifdef HAVE_LIBNOTIFY
+#ifdef PROF_HAVE_LIBNOTIFY
     notify_enabled = TRUE;
 #endif
 #ifdef PLATFORM_CYGWIN
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 32214c91..802f60bc 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <stdio.h>
@@ -591,6 +591,19 @@ prefs_set_autoxa_time(gint value)
     _save_prefs();
 }
 
+gchar **
+prefs_get_plugins(void)
+{
+    if (!g_key_file_has_group(prefs, "plugins")) {
+        return NULL;
+    }
+    if (!g_key_file_has_key(prefs, "plugins", "load", NULL)) {
+        return NULL;
+    }
+
+    return g_key_file_get_string_list(prefs, "plugins", "load", NULL, NULL);
+}
+
 void
 prefs_set_occupants_size(gint value)
 {
diff --git a/src/config/preferences.h b/src/config/preferences.h
index d97cd015..59874169 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -35,7 +35,7 @@
 #ifndef PREFERENCES_H
 #define PREFERENCES_H
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <glib.h>
 
@@ -183,6 +183,8 @@ void prefs_set_autoaway_time(gint value);
 gint prefs_get_autoxa_time(void);
 void prefs_set_autoxa_time(gint value);
 
+gchar** prefs_get_plugins(void);
+
 char prefs_get_otr_char(void);
 void prefs_set_otr_char(char ch);
 char prefs_get_pgp_char(void);
diff --git a/src/config/theme.c b/src/config/theme.c
index 82d5a988..437239ad 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -32,15 +32,15 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <string.h>
 
 #include <glib.h>
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
diff --git a/src/config/theme.h b/src/config/theme.h
index 6ea369d9..10769679 100644
--- a/src/config/theme.h
+++ b/src/config/theme.h
@@ -35,7 +35,7 @@
 #ifndef THEME_H
 #define THEME_H
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <glib.h>
 
diff --git a/src/event/client_events.c b/src/event/client_events.c
index 0df4468e..71d1c3c0 100644
--- a/src/event/client_events.c
+++ b/src/event/client_events.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <glib.h>
@@ -43,10 +43,11 @@
 #include "xmpp/xmpp.h"
 #include "roster_list.h"
 #include "chat_session.h"
-#ifdef HAVE_LIBOTR
+#include "plugins/plugins.h"
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 #include "pgp/gpg.h"
 #endif
 
@@ -80,7 +81,7 @@ cl_ev_disconnect(void)
     muc_invites_clear();
     chat_sessions_clear();
     tlscerts_clear_current();
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_on_disconnect();
 #endif
 }
@@ -90,7 +91,7 @@ cl_ev_presence_send(const resource_presence_t presence_type, const char *const m
 {
     char *signed_status = NULL;
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     char *account_name = jabber_get_account_name();
     ProfAccount *account = accounts_get_account(account_name);
     if (account->pgp_keyid) {
@@ -108,21 +109,22 @@ void
 cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg)
 {
     chat_state_active(chatwin->state);
+    char *plugin_msg = plugins_pre_chat_message_send(chatwin->barejid, msg);
 
 // OTR suported, PGP supported
-#ifdef HAVE_LIBOTR
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBGPGME
     if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, msg);
-        chat_log_pgp_msg_out(chatwin->barejid, msg);
-        chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PGP);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg);
+        chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PGP);
         free(id);
     } else {
-        gboolean handled = otr_on_message_send(chatwin, msg);
+        gboolean handled = otr_on_message_send(chatwin, plugin_msg);
         if (!handled) {
-            char *id = message_send_chat(chatwin->barejid, msg);
-            chat_log_msg_out(chatwin->barejid, msg);
-            chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PLAIN);
+            char *id = message_send_chat(chatwin->barejid, plugin_msg);
+            chat_log_msg_out(chatwin->barejid, plugin_msg);
+            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN);
             free(id);
         }
     }
@@ -131,13 +133,13 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg)
 #endif
 
 // OTR supported, PGP unsupported
-#ifdef HAVE_LIBOTR
-#ifndef HAVE_LIBGPGME
-    gboolean handled = otr_on_message_send(chatwin, msg);
+#ifdef PROF_HAVE_LIBOTR
+#ifndef PROF_HAVE_LIBGPGME
+    gboolean handled = otr_on_message_send(chatwin, plugin_msg);
     if (!handled) {
-        char *id = message_send_chat(chatwin->barejid, msg);
-        chat_log_msg_out(chatwin->barejid, msg);
-        chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PLAIN);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg);
+        chat_log_msg_out(chatwin->barejid, plugin_msg);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN);
         free(id);
     }
     return;
@@ -145,17 +147,17 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg)
 #endif
 
 // OTR unsupported, PGP supported
-#ifndef HAVE_LIBOTR
-#ifdef HAVE_LIBGPGME
+#ifndef PROF_HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBGPGME
     if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, msg);
-        chat_log_pgp_msg_out(chatwin->barejid, msg);
-        chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PGP);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg);
+        chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PGP);
         free(id);
     } else {
-        char *id = message_send_chat(chatwin->barejid, msg);
-        chat_log_msg_out(chatwin->barejid, msg);
-        chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PLAIN);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg);
+        chat_log_msg_out(chatwin->barejid, plugin_msg);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN);
         free(id);
     }
     return;
@@ -163,21 +165,29 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg)
 #endif
 
 // OTR unsupported, PGP unsupported
-#ifndef HAVE_LIBOTR
-#ifndef HAVE_LIBGPGME
-    char *id = message_send_chat(chatwin->barejid, msg);
-    chat_log_msg_out(chatwin->barejid, msg);
-    chatwin_outgoing_msg(chatwin, msg, id, PROF_MSG_PLAIN);
+#ifndef PROF_HAVE_LIBOTR
+#ifndef PROF_HAVE_LIBGPGME
+    char *id = message_send_chat(chatwin->barejid, plugin_msg);
+    chat_log_msg_out(chatwin->barejid, plugin_msg);
+    chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN);
     free(id);
     return;
 #endif
 #endif
+
+    plugins_post_chat_message_send(chatwin->barejid, plugin_msg);
+    free(plugin_msg);
 }
 
 void
 cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg)
 {
-    message_send_groupchat(mucwin->roomjid, msg);
+    char *plugin_msg = plugins_pre_room_message_send(mucwin->roomjid, msg);
+
+    message_send_groupchat(mucwin->roomjid, plugin_msg);
+
+    plugins_post_room_message_send(mucwin->roomjid, plugin_msg);
+    free(plugin_msg);
 }
 
 void
@@ -188,7 +198,12 @@ cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg)
     } else if (privwin->room_left) {
         privwin_message_left_room(privwin);
     } else {
-        message_send_private(privwin->fulljid, msg);
-        privwin_outgoing_msg(privwin, msg);
+        char *plugin_msg = plugins_pre_priv_message_send(privwin->fulljid, msg);
+
+        message_send_private(privwin->fulljid, plugin_msg);
+        privwin_outgoing_msg(privwin, plugin_msg);
+
+        plugins_post_priv_message_send(privwin->fulljid, plugin_msg);
+        free(plugin_msg);
     }
 }
diff --git a/src/event/server_events.c b/src/event/server_events.c
index 5d0fda77..85b48414 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -45,15 +45,16 @@
 #include "config/account.h"
 #include "config/scripts.h"
 #include "roster_list.h"
+#include "plugins/plugins.h"
 #include "window_list.h"
 #include "config/tlscerts.h"
 #include "profanity.h"
 #include "event/client_events.h"
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 #include "pgp/gpg.h"
 #endif
 
@@ -66,11 +67,11 @@ sv_ev_login_account_success(char *account_name, int secured)
 
     roster_create();
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     otr_on_connect(account);
 #endif
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_on_connect(account->jid);
 #endif
 
@@ -106,7 +107,7 @@ sv_ev_roster_received(void)
 
     char *account_name = jabber_get_account_name();
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     // check pgp key valid if specified
     ProfAccount *account = accounts_get_account(account_name);
     if (account && account->pgp_keyid) {
@@ -146,6 +147,9 @@ sv_ev_roster_received(void)
     } else {
         cl_ev_presence_send(conn_presence, NULL, 0);
     }
+
+    const char *fulljid = jabber_get_fulljid();
+    plugins_on_connect(account_name, fulljid);
 }
 
 void
@@ -153,7 +157,7 @@ sv_ev_lost_connection(void)
 {
     cons_show_error("Lost connection.");
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     GSList *recipients = wins_get_chat_recipients();
     GSList *curr = recipients;
     while (curr) {
@@ -174,7 +178,7 @@ sv_ev_lost_connection(void)
     chat_sessions_clear();
     ui_disconnected();
     roster_destroy();
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_on_disconnect();
 #endif
 }
@@ -226,7 +230,10 @@ sv_ev_room_history(const char *const room_jid, const char *const nick,
 {
     ProfMucWin *mucwin = wins_get_muc(room_jid);
     if (mucwin) {
-        mucwin_history(mucwin, nick, timestamp, message);
+        char *new_message = plugins_pre_room_message_display(room_jid, nick, message);
+        mucwin_history(mucwin, nick, timestamp, new_message);
+        plugins_post_room_message_display(room_jid, nick, new_message);
+        free(new_message);
     }
 }
 
@@ -244,10 +251,11 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
         return;
     }
 
+    char *new_message = plugins_pre_room_message_display(room_jid, nick, message);
     char *mynick = muc_nick(mucwin->roomjid);
 
     gboolean mention = FALSE;
-    char *message_lower = g_utf8_strdown(message, -1);
+    char *message_lower = g_utf8_strdown(new_message, -1);
     char *mynick_lower = g_utf8_strdown(mynick, -1);
     if (g_strrstr(message_lower, mynick_lower)) {
         mention = TRUE;
@@ -255,9 +263,9 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
     g_free(message_lower);
     g_free(mynick_lower);
 
-    GList *triggers = prefs_message_get_triggers(message);
+    GList *triggers = prefs_message_get_triggers(new_message);
 
-    mucwin_message(mucwin, nick, message, mention, triggers);
+    mucwin_message(mucwin, nick, new_message, mention, triggers);
 
     ProfWin *window = (ProfWin*)mucwin;
     int num = wins_get_num(window);
@@ -292,9 +300,9 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
         }
     }
 
-    if (prefs_do_room_notify(is_current, mucwin->roomjid, mynick, nick, message, mention, triggers != NULL)) {
+    if (prefs_do_room_notify(is_current, mucwin->roomjid, mynick, nick, new_message, mention, triggers != NULL)) {
         Jid *jidp = jid_create(mucwin->roomjid);
-        notify_room_message(nick, jidp->localpart, num, message);
+        notify_room_message(nick, jidp->localpart, num, new_message);
         jid_destroy(jidp);
     }
 
@@ -303,29 +311,44 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
     }
 
     rosterwin_roster();
+
+    plugins_post_room_message_display(room_jid, nick, new_message);
+    free(new_message);
 }
 
 void
 sv_ev_incoming_private_message(const char *const fulljid, char *message)
 {
+    char *plugin_message =  plugins_pre_priv_message_display(fulljid, message);
+
     ProfPrivateWin *privatewin = wins_get_private(fulljid);
     if (privatewin == NULL) {
         ProfWin *window = wins_new_private(fulljid);
         privatewin = (ProfPrivateWin*)window;
     }
-    privwin_incoming_msg(privatewin, message, NULL);
+    privwin_incoming_msg(privatewin, plugin_message, NULL);
+
+    plugins_post_priv_message_display(fulljid, plugin_message);
+
+    free(plugin_message);
     rosterwin_roster();
 }
 
 void
 sv_ev_delayed_private_message(const char *const fulljid, char *message, GDateTime *timestamp)
 {
+    char *new_message = plugins_pre_priv_message_display(fulljid, message);
+
     ProfPrivateWin *privatewin = wins_get_private(fulljid);
     if (privatewin == NULL) {
         ProfWin *window = wins_new_private(fulljid);
         privatewin = (ProfPrivateWin*)window;
     }
-    privwin_incoming_msg(privatewin, message, timestamp);
+    privwin_incoming_msg(privatewin, new_message, timestamp);
+
+    plugins_post_priv_message_display(fulljid, new_message);
+
+    free(new_message);
 }
 
 void
@@ -356,7 +379,7 @@ sv_ev_incoming_carbon(char *barejid, char *resource, char *message)
     chat_log_msg_in(barejid, message, NULL);
 }
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 static void
 _sv_ev_incoming_pgp(ProfChatWin *chatwin, gboolean new_win, char *barejid, char *resource, char *message, char *pgp_message, GDateTime *timestamp)
 {
@@ -374,7 +397,7 @@ _sv_ev_incoming_pgp(ProfChatWin *chatwin, gboolean new_win, char *barejid, char
 }
 #endif
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 static void
 _sv_ev_incoming_otr(ProfChatWin *chatwin, gboolean new_win, char *barejid, char *resource, char *message, GDateTime *timestamp)
 {
@@ -394,7 +417,7 @@ _sv_ev_incoming_otr(ProfChatWin *chatwin, gboolean new_win, char *barejid, char
 }
 #endif
 
-#ifndef HAVE_LIBOTR
+#ifndef PROF_HAVE_LIBOTR
 static void
 _sv_ev_incoming_plain(ProfChatWin *chatwin, gboolean new_win, char *barejid, char *resource, char *message, GDateTime *timestamp)
 {
@@ -416,8 +439,8 @@ sv_ev_incoming_message(char *barejid, char *resource, char *message, char *pgp_m
     }
 
 // OTR suported, PGP supported
-#ifdef HAVE_LIBOTR
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBGPGME
     if (pgp_message) {
         if (chatwin->is_otr) {
             win_println((ProfWin*)chatwin, 0, "PGP encrypted message received whilst in OTR session.");
@@ -433,8 +456,8 @@ sv_ev_incoming_message(char *barejid, char *resource, char *message, char *pgp_m
 #endif
 
 // OTR supported, PGP unsupported
-#ifdef HAVE_LIBOTR
-#ifndef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBOTR
+#ifndef PROF_HAVE_LIBGPGME
     _sv_ev_incoming_otr(chatwin, new_win, barejid, resource, message, timestamp);
     rosterwin_roster();
     return;
@@ -442,8 +465,8 @@ sv_ev_incoming_message(char *barejid, char *resource, char *message, char *pgp_m
 #endif
 
 // OTR unsupported, PGP supported
-#ifndef HAVE_LIBOTR
-#ifdef HAVE_LIBGPGME
+#ifndef PROF_HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBGPGME
     if (pgp_message) {
         _sv_ev_incoming_pgp(chatwin, new_win, barejid, resource, message, pgp_message, timestamp);
     } else {
@@ -455,8 +478,8 @@ sv_ev_incoming_message(char *barejid, char *resource, char *message, char *pgp_m
 #endif
 
 // OTR unsupported, PGP unsupported
-#ifndef HAVE_LIBOTR
-#ifndef HAVE_LIBGPGME
+#ifndef PROF_HAVE_LIBOTR
+#ifndef PROF_HAVE_LIBGPGME
     _sv_ev_incoming_plain(chatwin, new_win, barejid, resource, message, timestamp);
     rosterwin_roster();
     return;
@@ -568,7 +591,7 @@ sv_ev_contact_offline(char *barejid, char *resource, char *status)
         ui_contact_offline(barejid, resource, status);
     }
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin && otr_is_secure(barejid)) {
         chatwin_otr_unsecured(chatwin);
@@ -589,7 +612,7 @@ sv_ev_contact_online(char *barejid, Resource *resource, GDateTime *last_activity
         ui_contact_online(barejid, resource, last_activity);
     }
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     if (pgpsig) {
         p_gpg_verify(barejid, pgpsig);
     }
diff --git a/src/main.c b/src/main.c
index 0a49180b..e6ace26e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -32,12 +32,12 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <glib.h>
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -52,7 +52,7 @@ static char *account_name = NULL;
 int
 main(int argc, char **argv)
 {
-    if (argc == 2 && g_strcmp0(argv[1], "docgen") == 0 && g_strcmp0(PACKAGE_STATUS, "development") == 0) {
+    if (argc == 2 && g_strcmp0(argv[1], "docgen") == 0 && g_strcmp0(PROF_PACKAGE_STATUS, "development") == 0) {
         command_docgen();
         return 0;
     }
@@ -80,17 +80,17 @@ main(int argc, char **argv)
     g_option_context_free(context);
 
     if (version == TRUE) {
-        if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
-            g_print("Profanity, version %sdev.%s.%s\n", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+        if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
+            g_print("Profanity, version %sdev.%s.%s\n", PROF_PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-            g_print("Profanity, version %sdev\n", PACKAGE_VERSION);
+            g_print("Profanity, version %sdev\n", PROF_PACKAGE_VERSION);
 #endif
         } else {
-            g_print("Profanity, version %s\n", PACKAGE_VERSION);
+            g_print("Profanity, version %s\n", PROF_PACKAGE_VERSION);
         }
 
-        g_print("Copyright (C) 2012 - 2015 James Booth <%s>.\n", PACKAGE_BUGREPORT);
+        g_print("Copyright (C) 2012 - 2015 James Booth <%s>.\n", PROF_PACKAGE_BUGREPORT);
         g_print("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n");
         g_print("\n");
         g_print("This is free software; you are free to change and redistribute it.\n");
@@ -99,10 +99,10 @@ main(int argc, char **argv)
 
         g_print("Build information:\n");
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
         g_print("XMPP library: libmesode\n");
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
         g_print("XMPP library: libstrophe\n");
 #endif
 
@@ -112,13 +112,13 @@ main(int argc, char **argv)
             g_print("Desktop notification support: Disabled\n");
         }
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
         g_print("OTR support: Enabled\n");
 #else
         g_print("OTR support: Disabled\n");
 #endif
 
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
         g_print("PGP support: Enabled\n");
 #else
         g_print("PGP support: Disabled\n");
diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c
index b40df96f..64941f17 100644
--- a/src/pgp/gpg.c
+++ b/src/pgp/gpg.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <locale.h>
 #include <string.h>
diff --git a/src/plugins/api.c b/src/plugins/api.c
new file mode 100644
index 00000000..6e4ecbe4
--- /dev/null
+++ b/src/plugins/api.c
@@ -0,0 +1,233 @@
+/*
+ * api.c
+ *
+ * Copyright (C) 2012 - 2015 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 <stdlib.h>
+#include <assert.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "plugins/callbacks.h"
+#include "plugins/autocompleters.h"
+#include "profanity.h"
+#include "ui/ui.h"
+#include "config/theme.h"
+#include "command/command.h"
+#include "window_list.h"
+#include "common.h"
+
+void
+api_cons_alert(void)
+{
+    cons_alert();
+}
+
+void
+api_cons_show(const char * const message)
+{
+    if (message) {
+        char *parsed = str_replace(message, "\r\n", "\n");
+        cons_show("%s", parsed);
+        free(parsed);
+    }
+}
+
+void
+api_register_command(const char *command_name, int min_args, int max_args,
+    const char *usage, const char *short_help, const char *long_help, void *callback,
+    void(*callback_func)(PluginCommand *command, gchar **args))
+{
+    PluginCommand *command = malloc(sizeof(PluginCommand));
+    command->command_name = command_name;
+    command->min_args = min_args;
+    command->max_args = max_args;
+    command->usage = usage;
+    command->short_help = short_help;
+    command->long_help = long_help;
+    command->callback = callback;
+    command->callback_func = callback_func;
+
+    callbacks_add_command(command);
+}
+
+void
+api_register_timed(void *callback, int interval_seconds,
+    void (*callback_func)(PluginTimedFunction *timed_function))
+{
+    PluginTimedFunction *timed_function = malloc(sizeof(PluginTimedFunction));
+    timed_function->callback = callback;
+    timed_function->callback_func = callback_func;
+    timed_function->interval_seconds = interval_seconds;
+    timed_function->timer = g_timer_new();
+
+    callbacks_add_timed(timed_function);
+}
+
+void
+api_register_ac(const char *key, char **items)
+{
+    autocompleters_add(key, items);
+}
+
+void
+api_notify(const char *message, const char *category, int timeout_ms)
+{
+    notify(message, timeout_ms, category);
+}
+
+void
+api_send_line(char *line)
+{
+    ProfWin *current = wins_get_current();
+    cmd_process_input(current, line);
+}
+
+char *
+api_get_current_recipient(void)
+{
+    ProfWin *current = wins_get_current();
+    if (current->type == WIN_CHAT) {
+        ProfChatWin *chatwin = (ProfChatWin*)current;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+        return chatwin->barejid;
+    } else {
+        return NULL;
+    }
+}
+
+char *
+api_get_current_muc(void)
+{
+    ProfWin *current = wins_get_current();
+    if (current->type == WIN_MUC) {
+        ProfMucWin *mucwin = (ProfMucWin*)current;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+        return mucwin->roomjid;
+    } else {
+        return NULL;
+    }
+}
+
+void
+api_log_debug(const char *message)
+{
+    log_debug("%s", message);
+}
+
+void
+api_log_info(const char *message)
+{
+    log_info("%s", message);
+}
+
+void
+api_log_warning(const char *message)
+{
+    log_warning("%s", message);
+}
+
+void
+api_log_error(const char *message)
+{
+    log_error("%s", message);
+}
+
+int
+api_win_exists(const char *tag)
+{
+    return (wins_get_plugin(tag) != NULL);
+}
+
+void
+api_win_create(const char *tag, void *callback,
+    void(*callback_func)(PluginWindowCallback *window_callback, const char *tag, const char * const line))
+{
+    PluginWindowCallback *window = malloc(sizeof(PluginWindowCallback));
+    window->callback = callback;
+    window->callback_func = callback_func;
+    callbacks_add_window_handler(tag, window);
+    wins_new_plugin(tag);
+
+    // set status bar active
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    int num = wins_get_num((ProfWin*)pluginwin);
+    status_bar_active(num);
+}
+
+void
+api_win_focus(const char *tag)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ui_focus_win((ProfWin*)pluginwin);
+}
+
+void
+api_win_show(const char *tag, const char *line)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ProfWin *window = (ProfWin*)pluginwin;
+    win_print(window, '!', 0, NULL, 0, 0, "", line);
+}
+
+void
+api_win_show_green(const char *tag, const char *line)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ProfWin *window = (ProfWin*)pluginwin;
+    win_print(window, '!', 0, NULL, 0, THEME_GREEN, "", line);
+}
+
+void
+api_win_show_red(const char *tag, const char *line)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ProfWin *window = (ProfWin*)pluginwin;
+    win_print(window, '!', 0, NULL, 0, THEME_RED, "", line);
+}
+
+void
+api_win_show_cyan(const char *tag, const char *line)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ProfWin *window = (ProfWin*)pluginwin;
+    win_print(window, '!', 0, NULL, 0, THEME_CYAN, "", line);
+}
+
+void
+api_win_show_yellow(const char *tag, const char *line)
+{
+    ProfPluginWin *pluginwin = wins_get_plugin(tag);
+    ProfWin *window = (ProfWin*)pluginwin;
+    win_print(window, '!', 0, NULL, 0, THEME_YELLOW, "", line);
+}
diff --git a/src/plugins/api.h b/src/plugins/api.h
new file mode 100644
index 00000000..41c04535
--- /dev/null
+++ b/src/plugins/api.h
@@ -0,0 +1,69 @@
+/*
+ * api.h
+ *
+ * Copyright (C) 2012 - 2015 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 API_H
+#define API_H
+
+#include "plugins/callbacks.h"
+
+void api_cons_alert(void);
+void api_cons_show(const char * const message);
+void api_notify(const char *message, const char *category, int timeout_ms);
+void api_send_line(char *line);
+char * api_get_current_recipient(void);
+char * api_get_current_muc(void);
+
+void api_register_command(const char *command_name, int min_args, int max_args,
+    const char *usage, const char *short_help, const char *long_help,
+    void *callback, void(*callback_func)(PluginCommand *command, gchar **args));
+void api_register_timed(void *callback, int interval_seconds,
+    void (*callback_func)(PluginTimedFunction *timed_function));
+void api_register_ac(const char *key, char **items);
+
+void api_log_debug(const char *message);
+void api_log_info(const char *message);
+void api_log_warning(const char *message);
+void api_log_error(const char *message);
+
+int api_win_exists(const char *tag);
+void api_win_create(const char *tag, void *callback,
+    void(*callback_func)(PluginWindowCallback *window_callback, char *tag, char *line));
+void api_win_focus(const char *tag);
+void api_win_show(const char *tag, const char *line);
+void api_win_show_green(const char *tag, const char *line);
+void api_win_show_red(const char *tag, const char *line);
+void api_win_show_cyan(const char *tag, const char *line);
+void api_win_show_yellow(const char *tag, const char *line);
+
+#endif
diff --git a/src/plugins/autocompleters.c b/src/plugins/autocompleters.c
new file mode 100644
index 00000000..aa4563ea
--- /dev/null
+++ b/src/plugins/autocompleters.c
@@ -0,0 +1,92 @@
+/*
+ * autocompleters.c
+ *
+ * Copyright (C) 2012 - 2015 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 <string.h>
+
+#include <glib.h>
+
+#include "tools/autocomplete.h"
+
+static GHashTable *autocompleters;
+
+void
+autocompleters_init(void)
+{
+    autocompleters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)autocomplete_free);
+}
+
+void
+autocompleters_add(const char *key, char **items)
+{
+    Autocomplete new_ac = autocomplete_new();
+    int i = 0;
+    for (i = 0; i < g_strv_length(items); i++) {
+        autocomplete_add(new_ac, items[i]);
+    }
+    g_hash_table_insert(autocompleters, strdup(key), new_ac);
+}
+
+char *
+autocompleters_complete(const char * const input)
+{
+    char *result = NULL;
+
+    GList *keys = g_hash_table_get_keys(autocompleters);
+    GList *curr = keys;
+    while (curr) {
+        result = autocomplete_param_with_ac(input, curr->data, g_hash_table_lookup(autocompleters, curr->data), TRUE);
+        if (result) {
+            return result;
+        }
+        curr = g_list_next(curr);
+    }
+
+    return NULL;
+}
+
+void
+autocompleters_reset(void)
+{
+    GList *acs = g_hash_table_get_values(autocompleters);
+    GList *curr = acs;
+    while (curr) {
+        autocomplete_reset(curr->data);
+        curr = g_list_next(curr);
+    }
+}
+
+void autocompleters_destroy(void)
+{
+    g_hash_table_destroy(autocompleters);
+}
diff --git a/src/plugins/autocompleters.h b/src/plugins/autocompleters.h
new file mode 100644
index 00000000..47e34e7d
--- /dev/null
+++ b/src/plugins/autocompleters.h
@@ -0,0 +1,46 @@
+/*
+ * autocompleters.h
+ *
+ * Copyright (C) 2012 - 2015 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 AUTOCOMPLETERS_H
+#define AUTOCOMPLETERS_H
+
+#include <glib.h>
+
+void autocompleters_init(void);
+void autocompleters_add(const char *key, char **items);
+char* autocompleters_complete(const char * const input);
+void autocompleters_reset(void);
+void autocompleters_destroy(void);
+
+#endif
diff --git a/src/plugins/c_api.c b/src/plugins/c_api.c
new file mode 100644
index 00000000..1573a687
--- /dev/null
+++ b/src/plugins/c_api.c
@@ -0,0 +1,240 @@
+/*
+ * c_api.c
+ *
+ * Copyright (C) 2012 - 2015 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 <stdlib.h>
+#include <glib.h>
+
+#include "log.h"
+#include "plugins/api.h"
+#include "plugins/c_api.h"
+#include "plugins/callbacks.h"
+#include "plugins/profapi.h"
+
+typedef struct command_wrapper_t {
+    void(*func)(char **args);
+} CommandWrapper;
+
+typedef struct timed_wrapper_t {
+    void(*func)(void);
+} TimedWrapper;
+
+typedef struct window_wrapper_t {
+    void(*func)(char *tag, char *line);
+} WindowWrapper;
+
+static void
+c_api_cons_alert(void)
+{
+    api_cons_alert();
+}
+
+static void
+c_api_cons_show(const char * const message)
+{
+    if (message) {
+        api_cons_show(message);
+    }
+}
+
+static void
+c_api_register_command(const char *command_name, int min_args, int max_args,
+    const char *usage, const char *short_help, const char *long_help, void(*callback)(char **args))
+{
+    CommandWrapper *wrapper = malloc(sizeof(CommandWrapper));
+    wrapper->func = callback;
+    api_register_command(command_name, min_args, max_args, usage,
+        short_help, long_help, wrapper, c_command_callback);
+}
+
+static void
+c_api_register_timed(void(*callback)(void), int interval_seconds)
+{
+    TimedWrapper *wrapper = malloc(sizeof(TimedWrapper));
+    wrapper->func = callback;
+    api_register_timed(wrapper, interval_seconds, c_timed_callback);
+}
+
+static void
+c_api_register_ac(const char *key, char **items)
+{
+    api_register_ac(key, items);
+}
+
+static void
+c_api_notify(const char *message, int timeout_ms, const char *category)
+{
+    api_notify(message, category, timeout_ms);
+}
+
+static void
+c_api_send_line(char *line)
+{
+    api_send_line(line);
+}
+
+static char *
+c_api_get_current_recipient(void)
+{
+    return api_get_current_recipient();
+}
+
+static char *
+c_api_get_current_muc(void)
+{
+    return api_get_current_muc();
+}
+
+static void
+c_api_log_debug(const char *message)
+{
+    api_log_debug(message);
+}
+
+static void
+c_api_log_info(const char *message)
+{
+    api_log_info(message);
+}
+
+static void
+c_api_log_warning(const char *message)
+{
+    api_log_warning(message);
+}
+
+static void
+c_api_log_error(const char *message)
+{
+    api_log_error(message);
+}
+
+int
+c_api_win_exists(char *tag)
+{
+    return api_win_exists(tag);
+}
+
+void
+c_api_win_create(char *tag, void(*callback)(char *tag, char *line))
+{
+    WindowWrapper *wrapper = malloc(sizeof(WindowWrapper));
+    wrapper->func = callback;
+    api_win_create(tag, wrapper, c_window_callback);
+}
+
+void
+c_api_win_focus(char *tag)
+{
+    api_win_focus(tag);
+}
+
+void
+c_api_win_show(char *tag, char *line)
+{
+    api_win_show(tag, line);
+}
+
+void
+c_api_win_show_green(char *tag, char *line)
+{
+    api_win_show_green(tag, line);
+}
+
+void
+c_api_win_show_red(char *tag, char *line)
+{
+    api_win_show_red(tag, line);
+}
+
+void
+c_api_win_show_cyan(char *tag, char *line)
+{
+    api_win_show_cyan(tag, line);
+}
+
+void
+c_api_win_show_yellow(char *tag, char *line)
+{
+    api_win_show_yellow(tag, line);
+}
+
+void
+c_command_callback(PluginCommand *command, gchar **args)
+{
+    CommandWrapper *wrapper = command->callback;
+    void(*f)(gchar **args) = wrapper->func;
+    f(args);
+}
+
+void
+c_timed_callback(PluginTimedFunction *timed_function)
+{
+    TimedWrapper *wrapper = timed_function->callback;
+    void(*f)(void) = wrapper->func;
+    f();
+}
+
+void
+c_window_callback(PluginWindowCallback *window_callback, char *tag, char *line)
+{
+    WindowWrapper *wrapper = window_callback->callback;
+    void(*f)(char *tag, char *line) = wrapper->func;
+    f(tag, line);
+}
+
+void
+c_api_init(void)
+{
+    prof_cons_alert = c_api_cons_alert;
+    prof_cons_show = c_api_cons_show;
+    prof_register_command = c_api_register_command;
+    prof_register_timed = c_api_register_timed;
+    prof_register_ac = c_api_register_ac;
+    prof_notify = c_api_notify;
+    prof_send_line = c_api_send_line;
+    prof_get_current_recipient = c_api_get_current_recipient;
+    prof_get_current_muc = c_api_get_current_muc;
+    prof_log_debug = c_api_log_debug;
+    prof_log_info = c_api_log_info;
+    prof_log_warning = c_api_log_warning;
+    prof_log_error = c_api_log_error;
+    prof_win_exists = c_api_win_exists;
+    prof_win_create = c_api_win_create;
+    prof_win_focus = c_api_win_focus;
+    prof_win_show = c_api_win_show;
+    prof_win_show_green = c_api_win_show_green;
+    prof_win_show_red = c_api_win_show_red;
+    prof_win_show_cyan = c_api_win_show_cyan;
+    prof_win_show_yellow = c_api_win_show_yellow;
+}
diff --git a/src/plugins/c_api.h b/src/plugins/c_api.h
new file mode 100644
index 00000000..c32a7b2a
--- /dev/null
+++ b/src/plugins/c_api.h
@@ -0,0 +1,41 @@
+/*
+ * c_api.h
+ *
+ * Copyright (C) 2012 - 2015 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 <glib.h>
+
+void c_api_init(void);
+
+void c_command_callback(PluginCommand *command, gchar **args);
+void c_timed_callback(PluginTimedFunction *timed_function);
+void c_window_callback(PluginWindowCallback *window_callback, char *tag, char *line);
diff --git a/src/plugins/c_plugins.c b/src/plugins/c_plugins.c
new file mode 100644
index 00000000..38ed8137
--- /dev/null
+++ b/src/plugins/c_plugins.c
@@ -0,0 +1,367 @@
+/*
+ * c_plugins.c
+ *
+ * Copyright (C) 2012 - 2015 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 <dlfcn.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "config/preferences.h"
+#include "log.h"
+#include "plugins/api.h"
+#include "plugins/callbacks.h"
+#include "plugins/plugins.h"
+#include "plugins/c_plugins.h"
+#include "plugins/c_api.h"
+#include "ui/ui.h"
+
+void
+c_env_init(void)
+{
+    c_api_init();
+}
+
+ProfPlugin *
+c_plugin_create(const char * const filename)
+{
+    ProfPlugin *plugin;
+    void *handle = NULL;
+
+    gchar *plugins_dir = plugins_get_dir();
+    GString *path = g_string_new(plugins_dir);
+    g_free(plugins_dir);
+    g_string_append(path, "/");
+    g_string_append(path, filename);
+
+    handle = dlopen (path->str, RTLD_NOW | RTLD_GLOBAL);
+
+    if (!handle) {
+        log_warning ("dlopen failed to open `%s', %s", filename, dlerror ());
+        g_string_free(path, TRUE);
+        return NULL;
+    }
+
+    gchar *module_name = g_strndup(filename, strlen(filename) - 3);
+
+    plugin = malloc(sizeof(ProfPlugin));
+    plugin->name = strdup(module_name);
+    plugin->lang = LANG_C;
+    plugin->module = handle;
+    plugin->init_func = c_init_hook;
+    plugin->on_start_func = c_on_start_hook;
+    plugin->on_shutdown_func = c_on_shutdown_hook;
+    plugin->on_connect_func = c_on_connect_hook;
+    plugin->on_disconnect_func = c_on_disconnect_hook;
+    plugin->pre_chat_message_display = c_pre_chat_message_display_hook;
+    plugin->post_chat_message_display = c_post_chat_message_display_hook;
+    plugin->pre_chat_message_send = c_pre_chat_message_send_hook;
+    plugin->post_chat_message_send = c_post_chat_message_send_hook;
+    plugin->pre_room_message_display = c_pre_room_message_display_hook;
+    plugin->post_room_message_display = c_post_room_message_display_hook;
+    plugin->pre_room_message_send = c_pre_room_message_send_hook;
+    plugin->post_room_message_send = c_post_room_message_send_hook;
+    plugin->pre_priv_message_display = c_pre_priv_message_display_hook;
+    plugin->post_priv_message_display = c_post_priv_message_display_hook;
+    plugin->pre_priv_message_send = c_pre_priv_message_send_hook;
+    plugin->post_priv_message_send = c_post_priv_message_send_hook;
+
+    g_string_free(path, TRUE);
+    g_free(module_name);
+
+    return plugin;
+}
+
+void
+c_init_hook(ProfPlugin *plugin, const char *  const version, const char *  const status)
+{
+    void *  f = NULL;
+    void (*func)(const char * const __version, const char * const __status);
+
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_init"))) {
+        log_warning ("warning: %s does not have init function", plugin->name);
+        return ;
+    }
+
+    func = (void (*)(const char * const, const char * const))f;
+
+    // FIXME maybe we want to make it boolean to see if it succeeded or not?
+    func (version, status);
+}
+
+void
+c_on_start_hook(ProfPlugin *plugin)
+{
+    void * f = NULL;
+    void (*func)(void);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_on_start")))
+        return ;
+
+    func = (void (*)(void)) f;
+    func ();
+}
+
+void
+c_on_shutdown_hook(ProfPlugin *plugin)
+{
+    void * f = NULL;
+    void (*func)(void);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_on_shutdown")))
+        return ;
+
+    func = (void (*)(void)) f;
+    func ();
+}
+
+void
+c_on_connect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid)
+{
+    void * f = NULL;
+    void (*func)(const char * const __account_name, const char * const __fulljid);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_on_connect")))
+        return ;
+
+    func = (void (*)(const char * const, const char * const)) f;
+    func (account_name, fulljid);
+}
+
+void
+c_on_disconnect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid)
+{
+    void * f = NULL;
+    void (*func)(const char * const __account_name, const char * const __fulljid);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_on_disconnect")))
+        return ;
+
+    func = (void (*)(const char * const, const char * const)) f;
+    func (account_name, fulljid);
+}
+
+char *
+c_pre_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __jid, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_chat_message_display")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char *)) f;
+    return func (jid, message);
+}
+
+void
+c_post_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __jid, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_chat_message_display")))
+        return;
+
+    func = (void (*)(const char * const, const char *)) f;
+    func (jid, message);
+}
+
+char *
+c_pre_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __jid, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_chat_message_send")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char *)) f;
+    return func (jid, message);
+}
+
+void
+c_post_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __jid, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_chat_message_send")))
+        return;
+
+    func = (void (*)(const char * const, const char *)) f;
+    func (jid, message);
+}
+
+char *
+c_pre_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_room_message_display")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char * const, const char *)) f;
+    return func (room, nick, message);
+}
+
+void
+c_post_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_room_message_display")))
+        return;
+
+    func = (void (*)(const char * const, const char * const, const char *)) f;
+    func (room, nick, message);
+}
+
+char *
+c_pre_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __room, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_room_message_send")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char *)) f;
+    return func (room, message);
+}
+
+void
+c_post_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __room, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_room_message_send")))
+        return;
+
+    func = (void (*)(const char * const, const char *)) f;
+    func (room, message);
+}
+
+char *
+c_pre_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_priv_message_display")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char * const, const char *)) f;
+    return func (room, nick, message);
+}
+
+void
+c_post_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_priv_message_display")))
+        return;
+
+    func = (void (*)(const char * const, const char * const, const char *)) f;
+    func (room, nick, message);
+}
+
+char *
+c_pre_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    char* (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_pre_priv_message_send")))
+        return NULL;
+
+    func = (char* (*)(const char * const, const char * const, const char *)) f;
+    return func (room, nick, message);
+}
+
+void
+c_post_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message)
+{
+    void * f = NULL;
+    void (*func)(const char * const __room, const char * const __nick, const char * __message);
+    assert (plugin && plugin->module);
+
+    if (NULL == (f = dlsym (plugin->module, "prof_post_priv_message_send")))
+        return;
+
+    func = (void (*)(const char * const, const char * const, const char *)) f;
+    func (room, nick, message);
+}
+
+void
+c_plugin_destroy(ProfPlugin *plugin)
+{
+    assert (plugin && plugin->module);
+
+    if (dlclose (plugin->module)) {
+        log_warning ("dlclose failed to close `%s' with `%s'", plugin->name, dlerror ());
+    }
+
+    free(plugin->name);
+    free(plugin);
+}
+
+void
+c_shutdown(void)
+{
+
+}
diff --git a/src/plugins/c_plugins.h b/src/plugins/c_plugins.h
new file mode 100644
index 00000000..95343b43
--- /dev/null
+++ b/src/plugins/c_plugins.h
@@ -0,0 +1,67 @@
+/*
+ * c_plugins.h
+ *
+ * Copyright (C) 2012 - 2015 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 C_PLUGINS_H
+#define C_PLUGINS_H
+
+#include "plugins/plugins.h"
+
+void c_env_init(void);
+
+ProfPlugin* c_plugin_create(const char * const filename);
+void c_plugin_destroy(ProfPlugin * plugin);
+void c_shutdown(void);
+
+void c_init_hook(ProfPlugin *plugin, const char * const version, const char * const status);
+void c_on_start_hook(ProfPlugin *plugin);
+void c_on_shutdown_hook(ProfPlugin *plugin);
+void c_on_connect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid);
+void c_on_disconnect_hook(ProfPlugin *plugin, const char * const account_name, const char * const fulljid);
+
+char* c_pre_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message);
+void  c_post_chat_message_display_hook(ProfPlugin *plugin, const char * const jid, const char *message);
+char* c_pre_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message);
+void  c_post_chat_message_send_hook(ProfPlugin *plugin, const char * const jid, const char *message);
+
+char* c_pre_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message);
+void  c_post_room_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message);
+char* c_pre_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message);
+void  c_post_room_message_send_hook(ProfPlugin *plugin, const char * const room, const char *message);
+
+char* c_pre_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message);
+void  c_post_priv_message_display_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char *message);
+char* c_pre_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message);
+void  c_post_priv_message_send_hook(ProfPlugin *plugin, const char * const room, const char * const nick, const char * const message);
+
+#endif
diff --git a/src/plugins/callbacks.c b/src/plugins/callbacks.c
new file mode 100644
index 00000000..89a565b4
--- /dev/null
+++ b/src/plugins/callbacks.c
@@ -0,0 +1,126 @@
+/*
+ * callbacks.c
+ *
+ * Copyright (C) 2012 - 2015 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 <string.h>
+
+#include "command/command.h"
+#include "plugins/callbacks.h"
+#include "plugins/plugins.h"
+#include "tools/autocomplete.h"
+#include "tools/parser.h"
+
+#include "ui/ui.h"
+
+static GSList *p_commands = NULL;
+static GSList *p_timed_functions = NULL;
+static GHashTable *p_window_callbacks = NULL;
+
+void
+callbacks_add_command(PluginCommand *command)
+{
+    p_commands = g_slist_append(p_commands, command);
+    cmd_autocomplete_add(command->command_name);
+}
+
+void
+callbacks_add_timed(PluginTimedFunction *timed_function)
+{
+    p_timed_functions = g_slist_append(p_timed_functions, timed_function);
+}
+
+void
+callbacks_add_window_handler(const char *tag, PluginWindowCallback *window_callback)
+{
+    if (p_window_callbacks == NULL) {
+        p_window_callbacks = g_hash_table_new(g_str_hash, g_str_equal);
+    }
+
+    g_hash_table_insert(p_window_callbacks, strdup(tag), window_callback);
+}
+
+void *
+callbacks_get_window_handler(const char *tag)
+{
+    if (p_window_callbacks) {
+        return g_hash_table_lookup(p_window_callbacks, tag);
+    } else {
+        return NULL;
+    }
+}
+
+gboolean
+plugins_run_command(const char * const input)
+{
+    gchar **split = g_strsplit(input, " ", -1);
+
+    GSList *p_command = p_commands;
+    while (p_command) {
+        PluginCommand *command = p_command->data;
+        if (g_strcmp0(split[0], command->command_name) == 0) {
+            gboolean result;
+            gchar **args = parse_args(input, command->min_args, command->max_args, &result);
+            if (result == FALSE) {
+                ui_invalid_command_usage(command->usage, NULL);
+                return TRUE;
+            } else {
+                command->callback_func(command, args);
+                g_strfreev(split);
+                return TRUE;
+            }
+            g_strfreev(args);
+        }
+        p_command = g_slist_next(p_command);
+    }
+    g_strfreev(split);
+    return FALSE;
+}
+
+void
+plugins_run_timed(void)
+{
+    GSList *p_timed_function = p_timed_functions;
+
+    while (p_timed_function) {
+        PluginTimedFunction *timed_function = p_timed_function->data;
+        gdouble elapsed = g_timer_elapsed(timed_function->timer, NULL);
+
+        if (timed_function->interval_seconds > 0 && elapsed >= timed_function->interval_seconds) {
+            timed_function->callback_func(timed_function);
+            g_timer_start(timed_function->timer);
+        }
+
+        p_timed_function = g_slist_next(p_timed_function);
+    }
+    return;
+}
diff --git a/src/plugins/callbacks.h b/src/plugins/callbacks.h
new file mode 100644
index 00000000..8f045132
--- /dev/null
+++ b/src/plugins/callbacks.h
@@ -0,0 +1,68 @@
+/*
+ * callbacks.h
+ *
+ * Copyright (C) 2012 - 2015 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 CALLBACKS_H
+#define CALLBACKS_H
+
+#include <glib.h>
+
+typedef struct p_command {
+    const char *command_name;
+    int min_args;
+    int max_args;
+    const char *usage;
+    const char *short_help;
+    const char *long_help;
+    void *callback;
+    void (*callback_func)(struct p_command *command, gchar **args);
+} PluginCommand;
+
+typedef struct p_timed_function {
+    void *callback;
+    void (*callback_func)(struct p_timed_function *timed_function);
+    int interval_seconds;
+    GTimer *timer;
+} PluginTimedFunction;
+
+typedef struct p_window_input_callback {
+    void *callback;
+    void (*callback_func)(struct p_window_input_callback *window_callback, const char *tag, const char * const line);
+} PluginWindowCallback;
+
+void callbacks_add_command(PluginCommand *command);
+void callbacks_add_timed(PluginTimedFunction *timed_function);
+void callbacks_add_window_handler(const char *tag, PluginWindowCallback *window_callback);
+void * callbacks_get_window_handler(const char *tag);
+
+#endif
diff --git a/src/plugins/plugins.c b/src/plugins/plugins.c
new file mode 100644
index 00000000..fa34ab7f
--- /dev/null
+++ b/src/plugins/plugins.c
@@ -0,0 +1,497 @@
+/*
+ * plugins.c
+ *
+ * Copyright (C) 2012 - 2015 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 <string.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "config/preferences.h"
+#include "log.h"
+#include "plugins/callbacks.h"
+#include "plugins/autocompleters.h"
+#include "plugins/api.h"
+#include "plugins/plugins.h"
+
+#ifdef PROF_HAVE_PYTHON
+#include "plugins/python_plugins.h"
+#include "plugins/python_api.h"
+#endif
+
+#ifdef PROF_HAVE_RUBY
+#include "plugins/ruby_plugins.h"
+#include "plugins/ruby_api.h"
+#endif
+
+#ifdef PROF_HAVE_LUA
+#include "plugins/lua_plugins.h"
+#include "plugins/lua_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;
+
+void
+plugins_init(void)
+{
+    plugins = NULL;
+    autocompleters_init();
+
+#ifdef PROF_HAVE_PYTHON
+    python_env_init();
+#endif
+#ifdef PROF_HAVE_RUBY
+    ruby_env_init();
+#endif
+#ifdef PROF_HAVE_LUA
+    lua_env_init();
+#endif
+#ifdef PROF_HAVE_C
+    c_env_init();
+#endif
+
+    // load plugins
+    gchar **plugins_load = prefs_get_plugins();
+    if (plugins_load) {
+        int i;
+        for (i = 0; i < g_strv_length(plugins_load); i++)
+        {
+            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_RUBY
+            if (g_str_has_suffix(filename, ".rb")) {
+                ProfPlugin *plugin = ruby_plugin_create(filename);
+                if (plugin) {
+                    plugins = g_slist_append(plugins, plugin);
+                    loaded = TRUE;
+                }
+            }
+#endif
+#ifdef PROF_HAVE_LUA
+            if (g_str_has_suffix(filename, ".lua")) {
+                ProfPlugin *plugin = lua_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);
+                if (plugin) {
+                    plugins = g_slist_append(plugins, plugin);
+                    loaded = TRUE;
+                }
+            }
+#endif
+            if (loaded == TRUE) {
+                log_info("Loaded plugin: %s", filename);
+            }
+        }
+
+        // initialise plugins
+        GSList *curr = plugins;
+        while (curr) {
+            ProfPlugin *plugin = curr->data;
+            plugin->init_func(plugin, PROF_PACKAGE_VERSION, PROF_PACKAGE_STATUS);
+            curr = g_slist_next(curr);
+        }
+    }
+
+    return;
+}
+
+GSList *
+plugins_get_list(void)
+{
+    return plugins;
+}
+
+char *
+plugins_get_lang_string(ProfPlugin *plugin)
+{
+    switch (plugin->lang)
+    {
+        case LANG_PYTHON:
+            return "Python";
+        case LANG_RUBY:
+            return "Ruby";
+        case LANG_LUA:
+            return "Lua";
+        case LANG_C:
+            return "C";
+        default:
+            return "Unknown";
+    }
+}
+
+char *
+plugins_autocomplete(const char * const input)
+{
+    return autocompleters_complete(input);
+}
+
+void
+plugins_reset_autocomplete(void)
+{
+    autocompleters_reset();
+}
+
+void
+plugins_win_process_line(char *win, const char * const line)
+{
+    PluginWindowCallback *window = callbacks_get_window_handler(win);
+    window->callback_func(window, win, line);
+}
+
+void
+plugins_on_start(void)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->on_start_func(plugin);
+        curr = g_slist_next(curr);
+    }
+}
+
+void
+plugins_on_shutdown(void)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->on_shutdown_func(plugin);
+        curr = g_slist_next(curr);
+    }
+}
+
+void
+plugins_on_connect(const char * const account_name, const char * const fulljid)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->on_connect_func(plugin, account_name, fulljid);
+        curr = g_slist_next(curr);
+    }
+}
+
+void
+plugins_on_disconnect(const char * const account_name, const char * const fulljid)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->on_disconnect_func(plugin, account_name, fulljid);
+        curr = g_slist_next(curr);
+    }
+}
+
+char*
+plugins_pre_chat_message_display(const char * const jid, const char *message)
+{
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_chat_message_display(plugin, jid, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    return curr_message;
+}
+
+void
+plugins_post_chat_message_display(const char * const jid, const char *message)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_chat_message_display(plugin, jid, message);
+        curr = g_slist_next(curr);
+    }
+}
+
+char*
+plugins_pre_chat_message_send(const char * const jid, const char *message)
+{
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_chat_message_send(plugin, jid, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    return curr_message;
+}
+
+void
+plugins_post_chat_message_send(const char * const jid, const char *message)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_chat_message_send(plugin, jid, message);
+        curr = g_slist_next(curr);
+    }
+}
+
+char*
+plugins_pre_room_message_display(const char * const room, const char * const nick, const char *message)
+{
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_room_message_display(plugin, room, nick, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    return curr_message;
+}
+
+void
+plugins_post_room_message_display(const char * const room, const char * const nick, const char *message)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_room_message_display(plugin, room, nick, message);
+        curr = g_slist_next(curr);
+    }
+}
+
+char*
+plugins_pre_room_message_send(const char * const room, const char *message)
+{
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_room_message_send(plugin, room, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    return curr_message;
+}
+
+void
+plugins_post_room_message_send(const char * const room, const char *message)
+{
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_room_message_send(plugin, room, message);
+        curr = g_slist_next(curr);
+    }
+}
+
+char*
+plugins_pre_priv_message_display(const char * const jid, const char *message)
+{
+    Jid *jidp = jid_create(jid);
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_priv_message_display(plugin, jidp->barejid, jidp->resourcepart, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    jid_destroy(jidp);
+    return curr_message;
+}
+
+void
+plugins_post_priv_message_display(const char * const jid, const char *message)
+{
+    Jid *jidp = jid_create(jid);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_priv_message_display(plugin, jidp->barejid, jidp->resourcepart, message);
+        curr = g_slist_next(curr);
+    }
+
+    jid_destroy(jidp);
+}
+
+char*
+plugins_pre_priv_message_send(const char * const jid, const char * const message)
+{
+    Jid *jidp = jid_create(jid);
+    char *new_message = NULL;
+    char *curr_message = strdup(message);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        new_message = plugin->pre_priv_message_send(plugin, jidp->barejid, jidp->resourcepart, curr_message);
+        if (new_message) {
+            free(curr_message);
+            curr_message = strdup(new_message);
+            free(new_message);
+        }
+        curr = g_slist_next(curr);
+    }
+
+    jid_destroy(jidp);
+    return curr_message;
+}
+
+void
+plugins_post_priv_message_send(const char * const jid, const char * const message)
+{
+    Jid *jidp = jid_create(jid);
+
+    GSList *curr = plugins;
+    while (curr) {
+        ProfPlugin *plugin = curr->data;
+        plugin->post_priv_message_send(plugin, jidp->barejid, jidp->resourcepart, message);
+        curr = g_slist_next(curr);
+    }
+
+    jid_destroy(jidp);
+}
+
+void
+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_RUBY
+        if (((ProfPlugin *)curr->data)->lang == LANG_RUBY) {
+            ruby_plugin_destroy(curr->data);
+        }
+#endif
+#ifdef PROF_HAVE_LUA
+        if (((ProfPlugin *)curr->data)->lang == LANG_LUA) {
+            lua_plugin_destroy(curr->data);
+        }
+#endif
+#ifdef PROF_HAVE_C
+        if (((ProfPlugin *)curr->data)->lang == LANG_C) {
+            c_plugin_destroy(curr->data);
+        }
+#endif
+
+        curr = g_slist_next(curr);
+    }
+#ifdef PROF_HAVE_PYTHON
+    python_shutdown();
+#endif
+#ifdef PROF_HAVE_RUBY
+    ruby_shutdown();
+#endif
+#ifdef PROF_HAVE_LUA
+    lua_shutdown();
+#endif
+#ifdef PROF_HAVE_C
+    c_shutdown();
+#endif
+
+    autocompleters_destroy();
+}
+
+gchar *
+plugins_get_dir(void)
+{
+    gchar *xdg_data = xdg_get_data_home();
+    GString *plugins_dir = g_string_new(xdg_data);
+    g_string_append(plugins_dir, "/profanity/plugins");
+    gchar *result = strdup(plugins_dir->str);
+    g_free(xdg_data);
+    g_string_free(plugins_dir, TRUE);
+
+    return result;
+}
diff --git a/src/plugins/plugins.h b/src/plugins/plugins.h
new file mode 100644
index 00000000..5cbef180
--- /dev/null
+++ b/src/plugins/plugins.h
@@ -0,0 +1,108 @@
+/*
+ * plugins.h
+ *
+ * Copyright (C) 2012 - 2015 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 PLUGINS_H
+#define PLUGINS_H
+
+typedef enum {
+    LANG_PYTHON,
+    LANG_RUBY,
+    LANG_LUA,
+    LANG_C
+} lang_t;
+
+typedef struct prof_plugin_t {
+    char *name;
+    lang_t lang;
+    void *module;
+    void (*init_func)(struct prof_plugin_t* plugin, const char * const version,
+        const char * const status);
+
+    void (*on_start_func)(struct prof_plugin_t* plugin);
+    void (*on_shutdown_func)(struct prof_plugin_t* plugin);
+
+    void (*on_connect_func)(struct prof_plugin_t* plugin, const char * const account_name, const char * const fulljid);
+    void (*on_disconnect_func)(struct prof_plugin_t* plugin, const char * const account_name, const char * const fulljid);
+
+    char* (*pre_chat_message_display)(struct prof_plugin_t* plugin, const char * const jid, const char *message);
+    void  (*post_chat_message_display)(struct prof_plugin_t* plugin, const char * const jid, const char *message);
+    char* (*pre_chat_message_send)(struct prof_plugin_t* plugin, const char * const jid, const char *message);
+    void  (*post_chat_message_send)(struct prof_plugin_t* plugin, const char * const jid, const char *message);
+
+    char* (*pre_room_message_display)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char *message);
+    void  (*post_room_message_display)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char *message);
+    char* (*pre_room_message_send)(struct prof_plugin_t* plugin, const char * const room, const char *message);
+    void  (*post_room_message_send)(struct prof_plugin_t* plugin, const char * const room, const char *message);
+
+    char* (*pre_priv_message_display)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char *message);
+    void  (*post_priv_message_display)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char *message);
+    char* (*pre_priv_message_send)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char * const message);
+    void  (*post_priv_message_send)(struct prof_plugin_t* plugin, const char * const room, const char * const nick, const char * const message);
+
+} ProfPlugin;
+
+void plugins_init(void);
+GSList* plugins_get_list(void);
+char* plugins_get_lang_string(ProfPlugin *plugin);
+char* plugins_autocomplete(const char * const input);
+void plugins_reset_autocomplete(void);
+void plugins_shutdown(void);
+
+void plugins_on_start(void);
+void plugins_on_shutdown(void);
+
+void plugins_on_connect(const char * const account_name, const char * const fulljid);
+void plugins_on_disconnect(const char * const account_name, const char * const fulljid);
+
+char* plugins_pre_chat_message_display(const char * const jid, const char *message);
+void  plugins_post_chat_message_display(const char * const jid, const char *message);
+char* plugins_pre_chat_message_send(const char * const jid, const char *message);
+void  plugins_post_chat_message_send(const char * const jid, const char *message);
+
+char* plugins_pre_room_message_display(const char * const room, const char * const nick, const char *message);
+void  plugins_post_room_message_display(const char * const room, const char * const nick, const char *message);
+char* plugins_pre_room_message_send(const char * const room, const char *message);
+void  plugins_post_room_message_send(const char * const room, const char *message);
+
+char* plugins_pre_priv_message_display(const char * const jid, const char *message);
+void  plugins_post_priv_message_display(const char * const jid, const char *message);
+char* plugins_pre_priv_message_send(const char * const jid, const char * const message);
+void  plugins_post_priv_message_send(const char * const jid, const char * const message);
+
+gboolean plugins_run_command(const char * const cmd);
+void plugins_run_timed(void);
+gchar * plugins_get_dir(void);
+
+void plugins_win_process_line(char *win, const char * const line);
+#endif
diff --git a/src/plugins/profapi.c b/src/plugins/profapi.c
new file mode 100644
index 00000000..4a99aa21
--- /dev/null
+++ b/src/plugins/profapi.c
@@ -0,0 +1,70 @@
+/*
+ * prof_api.c
+ *
+ * Copyright (C) 2012 - 2015 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 <stdlib.h>
+
+#include "plugins/profapi.h"
+#include "plugins/callbacks.h"
+
+void (*prof_cons_alert)(void) = NULL;
+
+void (*prof_cons_show)(const char * const message) = NULL;
+
+void (*prof_register_command)(const char *command_name, int min_args, int max_args,
+    const char *usage, const char *short_help, const char *long_help, void(*callback)(char **args)) = NULL;
+
+void (*prof_register_timed)(void(*callback)(void), int interval_seconds) = NULL;
+
+void (*prof_register_ac)(const char *key, char **items) = NULL;
+
+void (*prof_notify)(const char *message, int timeout_ms, const char *category) = NULL;
+
+void (*prof_send_line)(char *line) = NULL;
+
+char* (*prof_get_current_recipient)(void) = NULL;
+char* (*prof_get_current_muc)(void) = NULL;
+
+void (*prof_log_debug)(const char *message) = NULL;
+void (*prof_log_info)(const char *message) = NULL;
+void (*prof_log_warning)(const char *message) = NULL;
+void (*prof_log_error)(const char *message) = NULL;
+
+int (*prof_win_exists)(PROF_WIN_TAG win) = NULL;
+void (*prof_win_create)(PROF_WIN_TAG win, void(*input_handler)(PROF_WIN_TAG win, char *line)) = NULL;
+void (*prof_win_focus)(PROF_WIN_TAG win) = NULL;
+void (*prof_win_show)(PROF_WIN_TAG win, char *line) = NULL;
+void (*prof_win_show_green)(PROF_WIN_TAG win, char *line) = NULL;
+void (*prof_win_show_red)(PROF_WIN_TAG win, char *line) = NULL;
+void (*prof_win_show_cyan)(PROF_WIN_TAG win, char *line) = NULL;
+void (*prof_win_show_yellow)(PROF_WIN_TAG win, char *line) = NULL;
diff --git a/src/plugins/profapi.h b/src/plugins/profapi.h
new file mode 100644
index 00000000..b27d167d
--- /dev/null
+++ b/src/plugins/profapi.h
@@ -0,0 +1,72 @@
+/*
+ * prof_api.h
+ *
+ * Copyright (C) 2012 - 2015 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 PROF_API_H
+#define PROF_API_H
+
+typedef char* PROF_WIN_TAG;
+
+void (*prof_cons_alert)(void);
+
+void (*prof_cons_show)(const char * const message);
+
+void (*prof_register_command)(const char *command_name, int min_args, int max_args,
+    const char *usage, const char *short_help, const char *long_help, void(*callback)(char **args));
+
+void (*prof_register_timed)(void(*callback)(void), int interval_seconds);
+
+void (*prof_register_ac)(const char *key, char **items);
+
+void (*prof_notify)(const char *message, int timeout_ms, const char *category);
+
+void (*prof_send_line)(char *line);
+
+char* (*prof_get_current_recipient)(void);
+char* (*prof_get_current_muc)(void);
+
+void (*prof_log_debug)(const char *message);
+void (*prof_log_info)(const char *message);
+void (*prof_log_warning)(const char *message);
+void (*prof_log_error)(const char *message);
+
+int (*prof_win_exists)(PROF_WIN_TAG win);
+void (*prof_win_create)(PROF_WIN_TAG win, void(*input_handler)(PROF_WIN_TAG win, char *line));
+void (*prof_win_focus)(PROF_WIN_TAG win);
+void (*prof_win_show)(PROF_WIN_TAG win, char *line);
+void (*prof_win_show_green)(PROF_WIN_TAG win, char *line);
+void (*prof_win_show_red)(PROF_WIN_TAG win, char *line);
+void (*prof_win_show_cyan)(PROF_WIN_TAG win, char *line);
+void (*prof_win_show_yellow)(PROF_WIN_TAG win, char *line);
+
+#endif
diff --git a/src/profanity.c b/src/profanity.c
index 1c9b4501..8a465693 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -31,9 +31,9 @@
  * source files in the program, then also delete it here.
  *
  */
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -59,10 +59,11 @@
 #include "config/tlscerts.h"
 #include "log.h"
 #include "muc.h"
-#ifdef HAVE_LIBOTR
+#include "plugins/plugins.h"
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
 #include "pgp/gpg.h"
 #endif
 #include "resource.h"
@@ -96,7 +97,9 @@ void
 prof_run(char *log_level, char *account_name)
 {
     _init(log_level);
+    plugins_on_start();
     _connect_default(account_name);
+
     ui_update();
 
     log_info("Starting main event loop");
@@ -119,9 +122,10 @@ prof_run(char *log_level, char *account_name)
             cont = TRUE;
         }
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
         otr_poll();
 #endif
+        plugins_run_timed();
         notify_remind();
         jabber_process_events(10);
         iq_autoping_check();
@@ -318,14 +322,14 @@ _init(char *log_level)
     prefs_load();
     log_init(prof_log_level);
     log_stderr_init(PROF_LEVEL_ERROR);
-    if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
-            log_info("Starting Profanity (%sdev.%s.%s)...", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+    if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
+            log_info("Starting Profanity (%sdev.%s.%s)...", PROF_PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-            log_info("Starting Profanity (%sdev)...", PACKAGE_VERSION);
+            log_info("Starting Profanity (%sdev)...", PROF_PACKAGE_VERSION);
 #endif
     } else {
-        log_info("Starting Profanity (%s)...", PACKAGE_VERSION);
+        log_info("Starting Profanity (%s)...", PROF_PACKAGE_VERSION);
     }
     chat_log_init();
     groupchat_log_init();
@@ -340,13 +344,14 @@ _init(char *log_level)
     muc_init();
     tlscerts_init();
     scripts_init();
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     otr_init();
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_init();
 #endif
     atexit(_shutdown);
+    plugins_init();
     inp_nonblocking(TRUE);
 }
 
@@ -367,13 +372,14 @@ _shutdown(void)
     }
 
     jabber_shutdown();
+    plugins_on_shutdown();
     muc_close();
     caps_close();
     ui_close();
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
     otr_shutdown();
 #endif
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     p_gpg_close();
 #endif
     chat_log_close();
@@ -383,6 +389,7 @@ _shutdown(void)
     cmd_uninit();
     log_stderr_close();
     log_close();
+    plugins_shutdown();
     prefs_close();
     if (saved_status) {
         free(saved_status);
@@ -401,6 +408,8 @@ _create_directories(void)
     g_string_append(chatlogs_dir, "/profanity/chatlogs");
     GString *logs_dir = g_string_new(xdg_data);
     g_string_append(logs_dir, "/profanity/logs");
+    GString *plugins_dir = g_string_new(xdg_data);
+    g_string_append(plugins_dir, "/profanity/plugins");
 
     if (!mkdir_recursive(themes_dir->str)) {
         log_error("Error while creating directory %s", themes_dir->str);
@@ -411,10 +420,14 @@ _create_directories(void)
     if (!mkdir_recursive(logs_dir->str)) {
         log_error("Error while creating directory %s", logs_dir->str);
     }
+    if (!mkdir_recursive(plugins_dir->str)) {
+        log_error("Error while creating directory %s", plugins_dir->str);
+    }
 
     g_string_free(themes_dir, TRUE);
     g_string_free(chatlogs_dir, TRUE);
     g_string_free(logs_dir, TRUE);
+    g_string_free(plugins_dir, TRUE);
 
     g_free(xdg_config);
     g_free(xdg_data);
diff --git a/src/profanity.h b/src/profanity.h
index f19f5f36..1cb84e0b 100644
--- a/src/profanity.h
+++ b/src/profanity.h
@@ -42,8 +42,7 @@ void prof_run(char *log_level, char *account_name);
 
 void prof_handle_idle(void);
 void prof_handle_activity(void);
-
-gboolean process_input(char *inp);
+gboolean prof_process_input(char *inp);
 
 void prof_set_quit(void);
 
diff --git a/src/ui/buffer.c b/src/ui/buffer.c
index 0b9da448..c78f3390 100644
--- a/src/ui/buffer.c
+++ b/src/ui/buffer.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -40,9 +40,9 @@
 #include <stdlib.h>
 
 #include <glib.h>
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
diff --git a/src/ui/buffer.h b/src/ui/buffer.h
index 0693d1ba..6e613ce6 100644
--- a/src/ui/buffer.h
+++ b/src/ui/buffer.h
@@ -35,7 +35,7 @@
 #ifndef UI_BUFFER_H
 #define UI_BUFFER_H
 
-#include "config.h"
+#include "prof_config.h"
 #include "config/theme.h"
 
 #include <glib.h>
diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c
index 1a87cbd8..45c36074 100644
--- a/src/ui/chatwin.c
+++ b/src/ui/chatwin.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -46,7 +46,8 @@
 #include "ui/ui.h"
 #include "ui/window.h"
 #include "ui/titlebar.h"
-#ifdef HAVE_LIBOTR
+#include "plugins/plugins.h"
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
 
@@ -84,7 +85,7 @@ chatwin_receipt_received(ProfChatWin *chatwin, const char *const id)
     win_mark_received(win, id);
 }
 
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 void
 chatwin_otr_secured(ProfChatWin *chatwin, gboolean trusted)
 {
@@ -234,6 +235,8 @@ chatwin_incoming_msg(ProfChatWin *chatwin, const char *const resource, const cha
 {
     assert(chatwin != NULL);
 
+    char *plugin_message = plugins_pre_chat_message_display(chatwin->barejid, message);
+
     ProfWin *window = (ProfWin*)chatwin;
     int num = wins_get_num(window);
 
@@ -244,7 +247,7 @@ chatwin_incoming_msg(ProfChatWin *chatwin, const char *const resource, const cha
 
     // currently viewing chat window with sender
     if (wins_is_current(window)) {
-        win_print_incoming_message(window, timestamp, display_name, message, enc_mode);
+        win_print_incoming_message(window, timestamp, display_name, plugin_message, enc_mode);
         title_bar_set_typing(FALSE);
         status_bar_active(num);
 
@@ -271,7 +274,7 @@ chatwin_incoming_msg(ProfChatWin *chatwin, const char *const resource, const cha
             }
         }
 
-        win_print_incoming_message(window, timestamp, display_name, message, enc_mode);
+        win_print_incoming_message(window, timestamp, display_name, plugin_message, enc_mode);
     }
 
     if (prefs_get_boolean(PREF_BEEP)) {
@@ -279,10 +282,14 @@ chatwin_incoming_msg(ProfChatWin *chatwin, const char *const resource, const cha
     }
 
     if (notify) {
-        notify_message(display_name, num, message);
+        notify_message(display_name, num, plugin_message);
     }
 
     free(display_name);
+
+    plugins_post_chat_message_display(chatwin->barejid, plugin_message);
+
+    free(plugin_message);
 }
 
 void
diff --git a/src/ui/console.c b/src/ui/console.c
index 8164ccf1..04232006 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -37,9 +37,9 @@
 #include <stdlib.h>
 #include <assert.h>
 
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
@@ -57,7 +57,7 @@
 #include "xmpp/xmpp.h"
 #include "xmpp/bookmark.h"
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -82,7 +82,7 @@ void
 cons_debug(const char *const msg, ...)
 {
     ProfWin *console = wins_get_console();
-    if (strcmp(PACKAGE_STATUS, "development") == 0) {
+    if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
         va_list arg;
         va_start(arg, msg);
         GString *fmt_msg = g_string_new(NULL);
@@ -416,18 +416,18 @@ cons_about(void)
         _cons_splash_logo();
     } else {
 
-        if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
-            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+        if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
+            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %sdev.%s.%s", PROF_PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %sdev", PACKAGE_VERSION);
+            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %sdev", PROF_PACKAGE_VERSION);
 #endif
         } else {
-            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %s", PACKAGE_VERSION);
+            win_vprint(console, '-', 0, NULL, 0, 0, "", "Welcome to Profanity, version %s", PROF_PACKAGE_VERSION);
         }
     }
 
-    win_vprint(console, '-', 0, NULL, 0, 0, "", "Copyright (C) 2012 - 2015 James Booth <%s>.", PACKAGE_BUGREPORT);
+    win_vprint(console, '-', 0, NULL, 0, 0, "", "Copyright (C) 2012 - 2015 James Booth <%s>.", PROF_PACKAGE_BUGREPORT);
     win_println(console, 0, "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>");
     win_println(console, 0, "");
     win_println(console, 0, "This is free software; you are free to change and redistribute it.");
@@ -2304,14 +2304,14 @@ _cons_splash_logo(void)
     win_print(console, '-', 0, NULL, 0, THEME_SPLASH, "", "|_|                                    (____/ ");
     win_print(console, '-', 0, NULL, 0, THEME_SPLASH, "", "");
 
-    if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
-        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+    if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
+        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %sdev.%s.%s", PROF_PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %sdev", PACKAGE_VERSION);
+        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %sdev", PROF_PACKAGE_VERSION);
 #endif
     } else {
-        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %s", PACKAGE_VERSION);
+        win_vprint(console, '-', 0, NULL, 0, 0, "", "Version %s", PROF_PACKAGE_VERSION);
     }
 }
 
diff --git a/src/ui/core.c b/src/ui/core.c
index 735dc03a..99861df0 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -32,9 +32,9 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -44,13 +44,13 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
-#ifdef HAVE_LIBXSS
+#ifdef PROF_HAVE_LIBXSS
 #include <X11/extensions/scrnsaver.h>
 #endif
 #include <glib.h>
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
@@ -64,7 +64,7 @@
 #include "jid.h"
 #include "log.h"
 #include "muc.h"
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
 #include "ui/ui.h"
@@ -74,13 +74,14 @@
 #include "ui/window.h"
 #include "window_list.h"
 #include "xmpp/xmpp.h"
+#include "plugins/plugins.h"
 
 static char *win_title;
 static int inp_size;
 static gboolean perform_resize = FALSE;
 static GTimer *ui_idle_time;
 
-#ifdef HAVE_LIBXSS
+#ifdef PROF_HAVE_LIBXSS
 static Display *display;
 #endif
 
@@ -104,7 +105,7 @@ ui_init(void)
     wins_init();
     notifier_initialise();
     cons_about();
-#ifdef HAVE_LIBXSS
+#ifdef PROF_HAVE_LIBXSS
     display = XOpenDisplay(0);
 #endif
     ui_idle_time = g_timer_new();
@@ -149,7 +150,7 @@ unsigned long
 ui_get_idle_time(void)
 {
 // if compiled with libxss, get the x sessions idle time
-#ifdef HAVE_LIBXSS
+#ifdef PROF_HAVE_LIBXSS
     XScreenSaverInfo *info = XScreenSaverAllocInfo();
     if (info && display) {
         XScreenSaverQueryInfo(display, DefaultRootWindow(display), info);
@@ -496,7 +497,7 @@ ui_close_connected_win(int index)
         } else if (window->type == WIN_CHAT) {
             ProfChatWin *chatwin = (ProfChatWin*) window;
             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
             if (chatwin->is_otr) {
                 otr_end_session(chatwin->barejid);
             }
@@ -1325,3 +1326,21 @@ ui_show_software_version(const char *const jid, const char *const  presence,
         win_vprint(window, '-', 0, NULL, 0, 0, "", "OS      : %s", os);
     }
 }
+
+void
+ui_status_bar_inactive(const int win)
+{
+    status_bar_inactive(win);
+}
+
+void
+ui_status_bar_active(const int win)
+{
+    status_bar_active(win);
+}
+
+void
+ui_status_bar_new(const int win)
+{
+    status_bar_new(win);
+}
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index eef58b32..ec8a8825 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -33,7 +33,7 @@
  */
 
 #define _XOPEN_SOURCE_EXTENDED
-#include "config.h"
+#include "prof_config.h"
 
 #include <sys/select.h>
 #include <stdlib.h>
@@ -46,9 +46,9 @@
 #include <readline/readline.h>
 #include <readline/history.h>
 
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
diff --git a/src/ui/notifier.c b/src/ui/notifier.c
index 23d942fb..deec3889 100644
--- a/src/ui/notifier.c
+++ b/src/ui/notifier.c
@@ -31,14 +31,14 @@
  * source files in the program, then also delete it here.
  *
  */
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 
 #include <glib.h>
-#ifdef HAVE_LIBNOTIFY
+#ifdef PROF_HAVE_LIBNOTIFY
 #include <libnotify/notify.h>
 #endif
 #ifdef PLATFORM_CYGWIN
@@ -51,8 +51,6 @@
 #include "window_list.h"
 #include "config/preferences.h"
 
-static void _notify(const char *const message, int timeout, const char *const category);
-
 static GTimer *remind_timer;
 
 void
@@ -64,7 +62,7 @@ notifier_initialise(void)
 void
 notifier_uninit(void)
 {
-#ifdef HAVE_LIBNOTIFY
+#ifdef PROF_HAVE_LIBNOTIFY
     if (notify_is_initted()) {
         notify_uninit();
     }
@@ -78,7 +76,7 @@ notify_typing(const char *const name)
     char message[strlen(name) + 1 + 11];
     sprintf(message, "%s: typing...", name);
 
-    _notify(message, 10000, "Incoming message");
+    notify(message, 10000, "Incoming message");
 }
 
 void
@@ -92,7 +90,7 @@ notify_invite(const char *const from, const char *const room, const char *const
         g_string_append_printf(message, "\n\"%s\"", reason);
     }
 
-    _notify(message->str, 10000, "Incoming message");
+    notify(message->str, 10000, "Incoming message");
 
     g_string_free(message, TRUE);
 }
@@ -111,8 +109,7 @@ notify_message(const char *const name, int num, const char *const text)
         g_string_append_printf(message, "\n%s", text);
     }
 
-    _notify(message->str, 10000, "incoming message");
-
+    notify(message->str, 10000, "incoming message");
     g_string_free(message, TRUE);
 }
 
@@ -130,7 +127,7 @@ notify_room_message(const char *const nick, const char *const room, int num, con
         g_string_append_printf(message, "\n%s", text);
     }
 
-    _notify(message->str, 10000, "incoming message");
+    notify(message->str, 10000, "incoming message");
 
     g_string_free(message, TRUE);
 }
@@ -140,7 +137,7 @@ notify_subscription(const char *const from)
 {
     GString *message = g_string_new("Subscription request: \n");
     g_string_append(message, from);
-    _notify(message->str, 10000, "Incoming message");
+    notify(message->str, 10000, "Incomming message");
     g_string_free(message, TRUE);
 }
 
@@ -150,14 +147,14 @@ notify_remind(void)
     gdouble elapsed = g_timer_elapsed(remind_timer, NULL);
     gint remind_period = prefs_get_notify_remind();
     if (remind_period > 0 && elapsed >= remind_period) {
-        gboolean notify = wins_do_notify_remind();
+        gboolean donotify = wins_do_notify_remind();
         gint unread = wins_get_total_unread();
         gint open = muc_invites_count();
         gint subs = presence_sub_request_count();
 
         GString *text = g_string_new("");
 
-        if (notify && unread > 0) {
+        if (donotify && unread > 0) {
             if (unread == 1) {
                 g_string_append(text, "1 unread message");
             } else {
@@ -186,8 +183,8 @@ notify_remind(void)
             }
         }
 
-        if ((notify && unread > 0) || (open > 0) || (subs > 0)) {
-            _notify(text->str, 5000, "Incoming message");
+        if ((donotify && unread > 0) || (open > 0) || (subs > 0)) {
+            notify(text->str, 5000, "Incoming message");
         }
 
         g_string_free(text, TRUE);
@@ -196,10 +193,10 @@ notify_remind(void)
     }
 }
 
-static void
-_notify(const char *const message, int timeout, const char *const category)
+void
+notify(const char *const message, int timeout, const char *const category)
 {
-#ifdef HAVE_LIBNOTIFY
+#ifdef PROF_HAVE_LIBNOTIFY
     log_debug("Attempting notification: %s", message);
     if (notify_is_initted()) {
         log_debug("Reinitialising libnotify");
@@ -251,7 +248,7 @@ _notify(const char *const message, int timeout, const char *const category)
 
     Shell_NotifyIcon(NIM_MODIFY, &nid);
 #endif
-#ifdef HAVE_OSXNOTIFY
+#ifdef PROF_HAVE_OSXNOTIFY
     GString *notify_command = g_string_new("terminal-notifier -title \"Profanity\" -message '");
 
     char *escaped_single = str_replace(message, "'", "'\\''");
diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c
index 5156aae2..9a690524 100644
--- a/src/ui/statusbar.c
+++ b/src/ui/statusbar.c
@@ -32,15 +32,15 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
 
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index 5894e0ac..82d261de 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -36,7 +36,7 @@
 #include <string.h>
 #include <assert.h>
 
-#include "config.h"
+#include "prof_config.h"
 
 #include "common.h"
 #include "config/theme.h"
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 8376cb90..c73eb359 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -35,12 +35,12 @@
 #ifndef UI_UI_H
 #define UI_UI_H
 
-#include "config.h"
+#include "prof_config.h"
 
 #include "command/commands.h"
 #include "ui/win_types.h"
 #include "muc.h"
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
 
@@ -135,8 +135,7 @@ void chatwin_outgoing_carbon(ProfChatWin *chatwin, const char *const message);
 void chatwin_contact_online(ProfChatWin *chatwin, Resource *resource, GDateTime *last_activity);
 void chatwin_contact_offline(ProfChatWin *chatwin, char *resource, char *status);
 char* chatwin_get_string(ProfChatWin *chatwin);
-
-#ifdef HAVE_LIBOTR
+#ifdef PROF_HAVE_LIBOTR
 void chatwin_otr_secured(ProfChatWin *chatwin, gboolean trusted);
 void chatwin_otr_unsecured(ProfChatWin *chatwin);
 void chatwin_otr_trust(ProfChatWin *chatwin);
@@ -343,6 +342,7 @@ ProfWin* win_create_chat(const char *const barejid);
 ProfWin* win_create_muc(const char *const roomjid);
 ProfWin* win_create_muc_config(const char *const title, DataForm *form);
 ProfWin* win_create_private(const char *const fulljid);
+ProfWin* win_create_plugin(const char *const tag);
 void win_update_virtual(ProfWin *window);
 void win_free(ProfWin *window);
 gboolean win_notify_remind(ProfWin *window);
@@ -372,6 +372,7 @@ void notify_message(const char *const name, int win, const char *const text);
 void notify_room_message(const char *const nick, const char *const room, int win, const char *const text);
 void notify_remind(void);
 void notify_invite(const char *const from, const char *const room, const char *const reason);
+void notify(const char *const message, int timeout, const char *const category);
 void notify_subscription(const char *const from);
 
 #endif
diff --git a/src/ui/win_types.h b/src/ui/win_types.h
index 54ded939..b71d7353 100644
--- a/src/ui/win_types.h
+++ b/src/ui/win_types.h
@@ -35,13 +35,13 @@
 #ifndef UI_WIN_TYPES_H
 #define UI_WIN_TYPES_H
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <wchar.h>
 #include <glib.h>
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
@@ -55,6 +55,7 @@
 #define PROFPRIVATEWIN_MEMCHECK     77437483
 #define PROFCONFWIN_MEMCHECK        64334685
 #define PROFXMLWIN_MEMCHECK         87333463
+#define PROFPLUGINWIN_MEMCHECK      43434777
 
 typedef enum {
     LAYOUT_SIMPLE,
@@ -86,7 +87,8 @@ typedef enum {
     WIN_MUC,
     WIN_MUC_CONFIG,
     WIN_PRIVATE,
-    WIN_XML
+    WIN_XML,
+    WIN_PLUGIN
 } win_type_t;
 
 typedef struct prof_win_t {
@@ -143,4 +145,10 @@ typedef struct prof_xml_win_t {
     unsigned long memcheck;
 } ProfXMLWin;
 
+typedef struct prof_plugin_win_t {
+    ProfWin super;
+    char *tag;
+    unsigned long memcheck;
+} ProfPluginWin;
+
 #endif
diff --git a/src/ui/window.c b/src/ui/window.c
index 66fcc334..50719d44 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -41,9 +41,9 @@
 #include <wchar.h>
 
 #include <glib.h>
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
@@ -237,6 +237,20 @@ win_create_xmlconsole(void)
     return &new_win->window;
 }
 
+ProfWin*
+win_create_plugin(const char *const tag)
+{
+    ProfPluginWin *new_win = malloc(sizeof(ProfPluginWin));
+    new_win->super.type = WIN_PLUGIN;
+    new_win->super.layout = _win_create_simple_layout();
+
+    new_win->tag = strdup(tag);
+
+    new_win->memcheck = PROFPLUGINWIN_MEMCHECK;
+
+    return &new_win->super;
+}
+
 char*
 win_get_title(ProfWin *window)
 {
@@ -287,6 +301,11 @@ win_get_title(ProfWin *window)
     if (window->type == WIN_XML) {
         return strdup(XML_WIN_TITLE);
     }
+    if (window->type == WIN_PLUGIN) {
+        ProfPluginWin *pluginwin = (ProfPluginWin*) window;
+        assert(pluginwin->memcheck == PROFPLUGINWIN_MEMCHECK);
+        return strdup(pluginwin->tag);
+    }
 
     return NULL;
 }
@@ -327,6 +346,15 @@ win_get_string(ProfWin *window)
             ProfXMLWin *xmlwin = (ProfXMLWin*)window;
             return xmlwin_get_string(xmlwin);
         }
+        case WIN_PLUGIN:
+        {
+            ProfPluginWin *pluginwin = (ProfPluginWin*)window;
+            GString *gstring = g_string_new("");
+            g_string_append_printf(gstring, "%s plugin", pluginwin->tag);
+            char *res = gstring->str;
+            g_string_free(gstring, FALSE);
+            return res;
+        }
         default:
             return NULL;
     }
diff --git a/src/ui/window.h b/src/ui/window.h
index f5c6aada..6f5a5e32 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -35,7 +35,7 @@
 #ifndef UI_WINDOW_H
 #define UI_WINDOW_H
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <wchar.h>
 
@@ -46,9 +46,9 @@
 #include "xmpp/xmpp.h"
 #include "chat_state.h"
 
-#ifdef HAVE_NCURSESW_NCURSES_H
+#ifdef PROF_HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
-#elif HAVE_NCURSES_H
+#elif PROF_HAVE_NCURSES_H
 #include <ncurses.h>
 #endif
 
diff --git a/src/window_list.c b/src/window_list.c
index 0dbaab85..32f9b59c 100644
--- a/src/window_list.c
+++ b/src/window_list.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <assert.h>
@@ -201,6 +201,27 @@ wins_get_private(const char *const fulljid)
     return NULL;
 }
 
+ProfPluginWin*
+wins_get_plugin(const char *const tag)
+{
+    GList *values = g_hash_table_get_values(windows);
+    GList *curr = values;
+
+    while (curr) {
+        ProfWin *window = curr->data;
+        if (window->type == WIN_PLUGIN) {
+            ProfPluginWin *pluginwin = (ProfPluginWin*)window;
+            if (g_strcmp0(pluginwin->tag, tag) == 0) {
+                return pluginwin;
+            }
+        }
+        curr = g_list_next(curr);
+    }
+
+    g_list_free(values);
+    return NULL;
+}
+
 GList*
 wins_get_private_chats(const char *const roomjid)
 {
@@ -614,6 +635,17 @@ wins_new_private(const char *const fulljid)
     return newwin;
 }
 
+ProfWin *
+wins_new_plugin(const char * const tag)
+{
+    GList *keys = g_hash_table_get_keys(windows);
+    int result = get_next_available_win_num(keys);
+    ProfWin *new = win_create_plugin(tag);
+    g_hash_table_insert(windows, GINT_TO_POINTER(result), new);
+    g_list_free(keys);
+    return new;
+}
+
 gboolean
 wins_do_notify_remind(void)
 {
diff --git a/src/window_list.h b/src/window_list.h
index 767dce1f..f92d71b0 100644
--- a/src/window_list.h
+++ b/src/window_list.h
@@ -44,6 +44,7 @@ ProfWin* wins_new_chat(const char *const barejid);
 ProfWin* wins_new_muc(const char *const roomjid);
 ProfWin* wins_new_muc_config(const char *const roomjid, DataForm *form);
 ProfWin* wins_new_private(const char *const fulljid);
+ProfWin* wins_new_plugin(const char *const tag);
 
 gboolean wins_chat_exists(const char *const barejid);
 GList* wins_get_private_chats(const char *const roomjid);
@@ -57,6 +58,7 @@ GList* wins_get_chat_unsubscribed(void);
 ProfMucWin* wins_get_muc(const char *const roomjid);
 ProfMucConfWin* wins_get_muc_conf(const char *const roomjid);
 ProfPrivateWin* wins_get_private(const char *const fulljid);
+ProfPluginWin* wins_get_plugin(const char *const tag);
 ProfXMLWin* wins_get_xmlconsole(void);
 
 ProfWin* wins_get_current(void);
diff --git a/src/xmpp/bookmark.c b/src/xmpp/bookmark.c
index 682aa2b2..99fd5293 100644
--- a/src/xmpp/bookmark.c
+++ b/src/xmpp/bookmark.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <stdio.h>
@@ -40,10 +40,10 @@
 #include <string.h>
 #include <glib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index b425c6f1..b2f36ae5 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -32,9 +32,9 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -44,10 +44,10 @@
 #include <glib.h>
 #include <glib/gstdio.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
@@ -565,9 +565,9 @@ caps_create_query_response_stanza(xmpp_ctx_t *const ctx)
     xmpp_stanza_set_attribute(identity, "type", "console");
 
     GString *name_str = g_string_new("Profanity ");
-    g_string_append(name_str, PACKAGE_VERSION);
-    if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
+    g_string_append(name_str, PROF_PACKAGE_VERSION);
+    if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
         g_string_append(name_str, "dev.");
         g_string_append(name_str, PROF_GIT_BRANCH);
         g_string_append(name_str, ".");
diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h
index 812a7eb1..2d251b00 100644
--- a/src/xmpp/capabilities.h
+++ b/src/xmpp/capabilities.h
@@ -35,12 +35,12 @@
 #ifndef XMPP_CAPABILITIES_H
 #define XMPP_CAPABILITIES_H
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index d72d10ca..667dfda6 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -32,16 +32,16 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
@@ -51,6 +51,7 @@
 #include "jid.h"
 #include "log.h"
 #include "muc.h"
+#include "plugins/plugins.h"
 #include "profanity.h"
 #include "event/server_events.h"
 #include "xmpp/bookmark.h"
@@ -93,12 +94,12 @@ static struct {
 static GTimer *reconnect_timer;
 
 static log_level_t _get_log_level(xmpp_log_level_t xmpp_level);
-static xmpp_log_level_t _get_xmpp_log_level();
+static xmpp_log_level_t _get_xmpp_log_level(void);
 
 static void _xmpp_file_logger(void *const userdata, const xmpp_log_level_t level, const char *const area,
     const char *const msg);
 
-static xmpp_log_t* _xmpp_get_file_logger();
+static xmpp_log_t* _xmpp_get_file_logger(void);
 
 static jabber_conn_status_t _jabber_connect(const char *const fulljid, const char *const passwd,
     const char *const altdomain, int port, const char *const tls_policy);
@@ -235,6 +236,9 @@ jabber_disconnect(void)
 {
     // if connected, send end stream and wait for response
     if (jabber_conn.conn_status == JABBER_CONNECTED) {
+        char *account_name = jabber_get_account_name();
+        const char *fulljid = jabber_get_fulljid();
+        plugins_on_disconnect(account_name, fulljid);
         log_info("Closing connection");
         accounts_set_last_activity(jabber_get_account_name());
         jabber_conn.conn_status = JABBER_DISCONNECTING;
@@ -418,7 +422,7 @@ _connection_free_session_data(void)
     presence_clear_sub_requests();
 }
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 static int
 _connection_certfail_cb(xmpp_tlscert_t *xmpptlscert, const char *const errormsg)
 {
@@ -526,7 +530,7 @@ _jabber_connect(const char *const fulljid, const char *const passwd, const char
         xmpp_conn_set_flags(jabber_conn.conn, XMPP_CONN_FLAG_DISABLE_TLS);
     }
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
     char *cert_path = prefs_get_string(PREF_TLS_CERTPATH);
     if (cert_path) {
         xmpp_conn_tlscert_path(jabber_conn.conn, cert_path);
@@ -534,7 +538,7 @@ _jabber_connect(const char *const fulljid, const char *const passwd, const char
     prefs_free_string(cert_path);
 #endif
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
     int connect_status = xmpp_connect_client(
         jabber_conn.conn,
         altdomain,
@@ -580,6 +584,9 @@ _jabber_reconnect(void)
 static void
 _jabber_lost_connection(void)
 {
+    char *account_name = jabber_get_account_name();
+    const char *fulljid = jabber_get_fulljid();
+    plugins_on_disconnect(account_name, fulljid);
     sv_ev_lost_connection();
     if (prefs_get_reconnect() != 0) {
         assert(reconnect_timer == NULL);
diff --git a/src/xmpp/connection.h b/src/xmpp/connection.h
index 0f9407f9..04c11ab1 100644
--- a/src/xmpp/connection.h
+++ b/src/xmpp/connection.h
@@ -35,12 +35,12 @@
 #ifndef XMPP_CONNECTION_H
 #define XMPP_CONNECTION_H
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/form.c b/src/xmpp/form.c
index 9631e8b6..670e3bd8 100644
--- a/src/xmpp/form.c
+++ b/src/xmpp/form.c
@@ -32,15 +32,15 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <string.h>
 #include <stdlib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 4cf3313e..cfe0f1c6 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -32,9 +32,9 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_GIT_VERSION
+#ifdef PROF_HAVE_GIT_VERSION
 #include "gitversion.h"
 #endif
 
@@ -43,10 +43,10 @@
 #include <stdio.h>
 #include <glib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
@@ -1081,9 +1081,9 @@ _version_get_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza,
         xmpp_stanza_t *version = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(version, "version");
         xmpp_stanza_t *version_txt = xmpp_stanza_new(ctx);
-        GString *version_str = g_string_new(PACKAGE_VERSION);
-        if (strcmp(PACKAGE_STATUS, "development") == 0) {
-#ifdef HAVE_GIT_VERSION
+        GString *version_str = g_string_new(PROF_PACKAGE_VERSION);
+        if (strcmp(PROF_PACKAGE_STATUS, "development") == 0) {
+#ifdef PROF_HAVE_GIT_VERSION
             g_string_append(version_str, "dev.");
             g_string_append(version_str, PROF_GIT_BRANCH);
             g_string_append(version_str, ".");
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 810cb6e1..fceae729 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -32,15 +32,15 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
@@ -156,7 +156,7 @@ message_send_chat_pgp(const char *const barejid, const char *const msg)
     char *id = create_unique_id("msg");
 
     xmpp_stanza_t *message = NULL;
-#ifdef HAVE_LIBGPGME
+#ifdef PROF_HAVE_LIBGPGME
     char *account_name = jabber_get_account_name();
     ProfAccount *account = accounts_get_account(account_name);
     if (account->pgp_keyid) {
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 0743cbe2..8207c8bb 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <stdlib.h>
@@ -41,10 +41,10 @@
 #include <glib.h>
 #include <glib/gprintf.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index 8b646efa..f90fc748 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -32,7 +32,7 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <assert.h>
 #include <stdlib.h>
@@ -40,14 +40,15 @@
 
 #include <glib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
 #include "log.h"
+#include "plugins/plugins.h"
 #include "profanity.h"
 #include "ui/ui.h"
 #include "event/server_events.h"
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 7679e6c3..b2d36461 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -32,17 +32,17 @@
  *
  */
 
-#include "config.h"
+#include "prof_config.h"
 
 #include <stdlib.h>
 #include <string.h>
 
 #include <glib.h>
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 7170e204..69cd4267 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -35,12 +35,12 @@
 #ifndef XMPP_STANZA_H
 #define XMPP_STANZA_H
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 7e5e430e..0827cc46 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -35,12 +35,12 @@
 #ifndef XMPP_XMPP_H
 #define XMPP_XMPP_H
 
-#include "config.h"
+#include "prof_config.h"
 
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 #include <mesode.h>
 #endif
-#ifdef HAVE_LIBSTROPHE
+#ifdef PROF_HAVE_LIBSTROPHE
 #include <strophe.h>
 #endif
 
@@ -155,7 +155,7 @@ char* jabber_get_account_name(void);
 GList* jabber_get_available_resources(void);
 char* jabber_create_uuid(void);
 void jabber_free_uuid(char *uuid);
-#ifdef HAVE_LIBMESODE
+#ifdef PROF_HAVE_LIBMESODE
 TLSCertificate* jabber_get_tls_peer_cert(void);
 #endif
 gboolean jabber_conn_is_secured(void);