--- a/mcabber/configure.ac Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/configure.ac Sun Oct 11 16:01:52 2009 +0200
@@ -130,13 +130,22 @@
[Define if ncurses has ESCDELAY variable])
fi
+AC_ARG_ENABLE(modules, [ --enable-modules enable dynamic modules loading],
+ enable_modules=$enableval)
+if test "x$enable_modules" = "xyes"; then
+ AC_DEFINE(MODULES_ENABLE, 1, [Define if you want dynamic modules loading])
+ gmodule_module=gmodule
+else
+ gmodule_module=''
+fi
+
# Check for glib
AM_PATH_GLIB_2_0(2.14.0,
[AC_DEFINE([HAVE_GLIB_REGEX], 1,
[Define if GLib has regex support])],
[AM_PATH_GLIB_2_0(2.0.0, , AC_MSG_ERROR([glib is required]),
- [g_list_append])],
- [g_regex_new])
+ [g_list_append], ["$gmodule_module"])],
+ [g_regex_new "$gmodule_module"])
# Check for loudmouth
PKG_CHECK_MODULES(LOUDMOUTH, loudmouth-1.0 >= 1.4.3)
--- a/mcabber/src/commands.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/commands.c Sun Oct 11 16:01:52 2009 +0200
@@ -93,11 +93,47 @@
// Global variable for the commands list
static GSList *Commands;
+#ifdef MODULES_ENABLE
+#include <gmodule.h>
+
+static void do_load(char *arg);
+static void do_unload(char *arg);
+
+typedef struct {
+ char *name;
+ GModule *module;
+} loaded_module_t;
+
+GSList *loaded_modules = NULL;
+
+static gint cmd_del_comparator (cmd *a, const char *name)
+{
+ return strcmp(a->name, name);
+}
+
+gpointer cmd_del(const char *name)
+{
+ GSList *command = g_slist_find_custom (Commands, name, (GCompareFunc) cmd_del_comparator);
+ if (command) {
+ cmd *cmnd = command->data;
+ gpointer userdata = cmnd->userdata;
+ Commands = g_slist_delete_link(Commands, command);
+ compl_del_category_word(COMPL_CMD, cmnd->name);
+ g_free(cmnd);
+ return userdata;
+ }
+ return NULL;
+}
// cmd_add()
// Adds a command to the commands list and to the CMD completion list
+void cmd_add(const char *name, const char *help, guint flags_row1,
+ guint flags_row2, void (*f)(char*), gpointer userdata)
+#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL);
+#else
static void cmd_add(const char *name, const char *help,
guint flags_row1, guint flags_row2, void (*f)(char*))
+#endif
{
cmd *n_cmd = g_new0(cmd, 1);
strncpy(n_cmd->name, name, 32-1);
@@ -105,6 +141,9 @@
n_cmd->completion_flags[0] = flags_row1;
n_cmd->completion_flags[1] = flags_row2;
n_cmd->func = f;
+#ifdef MODULES_ENABLE
+ n_cmd->userdata = userdata;
+#endif
Commands = g_slist_append(Commands, n_cmd);
// Add to completion CMD category
compl_add_category_word(COMPL_CMD, name);
@@ -165,6 +204,10 @@
cmd_add("status_to", "Show or set your status for one recipient",
COMPL_JID, COMPL_STATUS, &do_status_to);
cmd_add("version", "Show mcabber version", 0, 0, &do_version);
+#ifdef MODULES_ENABLE
+ cmd_add("load", "Load module", 0, 0, &do_load);
+ cmd_add("unload", "Unload module", 0, 0, &do_unload);
+#endif
// Status category
compl_add_category_word(COMPL_STATUS, "online");
@@ -299,6 +342,23 @@
compl_add_category_word(COMPL_COLOR, "mucnick");
}
+#ifdef MODULES_ENABLE
+void cmd_deinit ()
+{
+ GSList *el = loaded_modules;
+ while (el) {
+ loaded_module_t *module = el->data;
+ if (!g_module_close ((GModule *) module->module))
+ scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s",
+ g_module_error ());
+ g_free (module->name);
+ g_free (module);
+ el = g_slist_next (el);
+ }
+ g_slist_free (loaded_modules);
+}
+#endif
+
// expandalias(line)
// If there is one, expand the alias in line and returns a new allocated line
// If no alias is found, returns line
@@ -435,7 +495,14 @@
p++;
// Call command-specific function
retval_for_cmds = 0;
+#ifdef MODULES_ENABLE
+ if (curcmd->userdata)
+ (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
+ else
+ (*curcmd->func)(p);
+#else
(*curcmd->func)(p);
+#endif
g_free(xpline);
return retval_for_cmds;
}
@@ -2902,6 +2969,57 @@
g_slist_free(bm);
}
+#ifdef MODULES_ENABLE
+static void do_load(char *arg)
+{
+ if (!arg || !*arg) {
+ scr_LogPrint (LPRINT_LOGNORM, "Missing modulename.");
+ return;
+ }
+ char *mdir = expand_filename (settings_opt_get ("modules_dir"));
+ char *path = g_module_build_path (mdir, arg);
+ GModule *mod = g_module_open (path, G_MODULE_BIND_LAZY);
+ if (!mod)
+ scr_LogPrint (LPRINT_LOGNORM, "Module %s loading failed: %s",
+ path, g_module_error ());
+ else {
+ loaded_module_t *module = g_new (loaded_module_t, 1);
+ module->name = g_strdup (arg);
+ module->module = mod;
+ loaded_modules = g_slist_prepend (loaded_modules, module);
+ scr_LogPrint (LPRINT_LOGNORM, "Loaded module %s", arg);
+ }
+ g_free (path);
+ if (mdir)
+ g_free (mdir);
+}
+
+static int module_list_comparator (loaded_module_t *module, const char *name)
+{
+ return g_strcmp0 (module->name, name);
+}
+
+static void do_unload(char *arg)
+{
+ if (!arg || !*arg) {
+ scr_LogPrint (LPRINT_LOGNORM, "Missing modulename.");
+ return;
+ }
+ GSList *module = g_slist_find_custom (loaded_modules, arg, (GCompareFunc) module_list_comparator);
+ if (module) {
+ loaded_module_t *mod = module->data;
+ if (!g_module_close ((GModule *) mod->module))
+ scr_LogPrint (LPRINT_LOGNORM, "Module unloading failed: %s",
+ g_module_error ());
+ else {
+ g_free (mod->name);
+ g_free (mod);
+ loaded_modules = g_slist_delete_link (loaded_modules, module);
+ }
+ } else
+ scr_LogPrint (LPRINT_LOGNORM, "Module %s not loaded.", arg);
+}
+#endif
static void do_room(char *arg)
{
--- a/mcabber/src/commands.h Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/commands.h Sun Oct 11 16:01:52 2009 +0200
@@ -3,12 +3,17 @@
#include <glib.h>
+#include "config.h"
+
// Command structure
typedef struct {
char name[32];
const char *help;
guint completion_flags[2];
void (*func)(char *);
+#ifdef MODULES_ENABLE
+ gpointer userdata;
+#endif
} cmd;
void cmd_init(void);
@@ -16,6 +21,11 @@
int process_line(const char *line);
int process_command(const char *line, guint iscmd);
char *expandalias(const char *line);
+#ifdef MODULES_ENABLE
+void cmd_deinit(void);
+gpointer cmd_del(const char *name);
+void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata);
+#endif
extern char *mcabber_version(void);
extern void mcabber_set_terminate_ui(void);
--- a/mcabber/src/compl.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/compl.c Sun Oct 11 16:01:52 2009 +0200
@@ -53,6 +53,44 @@
static GSList *Categories;
static compl *InputCompl;
+#ifdef MODULES_ENABLE
+guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \
+ COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \
+ COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \
+ COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \
+ COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \
+ COMPL_OTR|COMPL_OTRPOLICY| \
+ 0;
+
+// compl_new_category()
+// Reserves id for new completion category.
+// Returns 0, if no more categories can be allocated.
+// Note, that user should not make any assumptions about id nature,
+// as it is likely to change in future.
+guint compl_new_category (void)
+{
+ guint i = 0;
+ while ((registered_cats >> i) & 1)
+ i++;
+ if (i >= sizeof (guint)*8)
+ return 0;
+ else {
+ guint id = 1 << i;
+ registered_cats |= id;
+ return id;
+ }
+}
+
+// compl_del_category (id)
+// Frees reserved id for category.
+// Note, that for now it not validates its input, so, be careful
+// and specify exactly what you get from compl_new_category.
+void compl_del_category (guint id)
+{
+ registered_cats &= ~id;
+}
+#endif
+
// new_completion(prefix, compl_cat)
// . prefix = beginning of the word, typed by the user
// . compl_cat = pointer to a completion category list (list of *char)
--- a/mcabber/src/compl.h Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/compl.h Sun Oct 11 16:01:52 2009 +0200
@@ -3,6 +3,8 @@
#include <glib.h>
+#include "config.h"
+
#define COMPL_CMD (1U<<0)
#define COMPL_JID (1U<<1)
#define COMPL_URLJID (1U<<2) // Not implemented yet
@@ -24,6 +26,12 @@
#define COMPL_COLOR (1U<<18)
#define COMPL_OTR (1U<<19)
#define COMPL_OTRPOLICY (1U<<20)
+#ifdef MODULES_ENABLE
+#define COMPL_MAX_BUILTIN (1U<<20)
+
+guint compl_new_category (void);
+void compl_del_category (guint id);
+#endif
void compl_add_category_word(guint, const char *command);
void compl_del_category_word(guint categ, const char *word);
--- a/mcabber/src/events.h Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/events.h Sun Oct 11 16:01:52 2009 +0200
@@ -1,6 +1,7 @@
#ifndef __EVENTS_H__
#define __EVENTS_H__ 1
+#include "config.h" // MODULES_ENABLE
#define EVS_DEFAULT_TIMEOUT 90
#define EVS_MAX_TIMEOUT 432000
@@ -11,7 +12,10 @@
typedef enum {
EVS_TYPE_SUBSCRIPTION = 1,
- EVS_TYPE_INVITATION = 2
+ EVS_TYPE_INVITATION = 2,
+#ifdef MODULES_ENABLE
+ EVS_TYPE_USER = 3,
+#endif
} evs_type;
/* Common structure for events (evs) and IQ requests (iqs) */
--- a/mcabber/src/hooks.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/hooks.c Sun Oct 11 16:01:52 2009 +0200
@@ -36,6 +36,43 @@
#include "commands.h"
#include "fifo.h"
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+typedef struct {
+ hk_handler_t handler;
+ gpointer userdata;
+} hook_list_data_t;
+
+static GSList *hk_handler_queue = NULL;
+
+void hk_add_handler (hk_handler_t handler, gpointer userdata)
+{
+ hook_list_data_t *h = g_new (hook_list_data_t, 1);
+ h->handler = handler;
+ h->userdata = userdata;
+ hk_handler_queue = g_slist_append (hk_handler_queue, h);
+}
+
+static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b)
+{
+ if (a->handler == b->handler && a->userdata == b->userdata)
+ return 0;
+ else
+ return 1;
+}
+
+void hk_del_handler (hk_handler_t handler, gpointer userdata)
+{
+ hook_list_data_t h = { handler, userdata };
+ GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb);
+ if (el) {
+ g_free (el->data);
+ hk_handler_queue = g_slist_delete_link (hk_handler_queue, el);
+ }
+}
+#endif
+
static char *extcmd;
static const char *COMMAND_ME = "/me ";
@@ -209,6 +246,42 @@
if (settings_opt_get_int("eventcmd_use_nickname"))
ename = roster_getname(bjid);
+#ifdef MODULES_ENABLE
+ {
+ GSList *h = hk_handler_queue;
+ if (h) {
+#if 0
+ hk_arg_t *args = g_new (hk_arg_t, 5);
+ args[0].name = "hook";
+ args[0].value = "hook-message-in";
+ args[1].name = "jid";
+ args[1].value = bjid;
+ args[2].name = "message";
+ args[2].value = wmsg;
+ args[3].name = "groupchat";
+ args[3].value = is_groupchat ? "true" : "false";
+ args[4].name = NULL;
+ args[4].value = NULL;
+#else
+ // We can use a const array for keys/static values, so modules
+ // can do fast known to them args check by just comparing pointers...
+ hk_arg_t args[] = {
+ { "hook", "hook-message-in" },
+ { "jid", bjid },
+ { "message", wmsg },
+ { "groupchat", is_groupchat ? "true" : "false" },
+ { NULL, NULL },
+ };
+#endif
+ while (h) {
+ hook_list_data_t *data = h->data;
+ (data->handler) (args, data->userdata);
+ h = g_slist_next (h);
+ }
+ }
+ }
+#endif
+
// External command
// - We do not call hk_ext_cmd() for history lines in MUC
// - We do call hk_ext_cmd() for private messages in a room
@@ -287,6 +360,25 @@
if (!nick)
hlog_write_message(bjid, timestamp, 1, msg);
+#ifdef MODULES_ENABLE
+ {
+ GSList *h = hk_handler_queue;
+ if (h) {
+ hk_arg_t args[] = {
+ { "hook", "hook-message-out" },
+ { "jid", bjid },
+ { "message", wmsg },
+ { NULL, NULL },
+ };
+ while (h) {
+ hook_list_data_t *data = h->data;
+ (data->handler) (args, data->userdata);
+ h = g_slist_next (h);
+ }
+ }
+ }
+#endif
+
// External command
hk_ext_cmd(bjid, 'M', 'S', NULL);
@@ -357,6 +449,33 @@
buddylist_build();
scr_DrawRoster();
hlog_write_status(bjid, timestamp, status, status_msg);
+
+#ifdef MODULES_ENABLE
+ {
+ GSList *h = hk_handler_queue;
+ if (h) {
+ char os[2] = " \0";
+ char ns[2] = " \0";
+ hk_arg_t args[] = {
+ { "hook", "hook-status-change" },
+ { "jid", bjid },
+ { "resource", rn },
+ { "old_status", os },
+ { "new_status", ns },
+ { "message", status_msg ? status_msg : "" },
+ { NULL, NULL },
+ };
+ os[0] = imstatus2char[oldstat];
+ ns[0] = imstatus2char[status];
+ while (h) {
+ hook_list_data_t *data = h->data;
+ (data->handler) (args, data->userdata);
+ h = g_slist_next (h);
+ }
+ }
+ }
+#endif
+
// External command
hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
}
@@ -367,6 +486,28 @@
scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s",
imstatus2char[old_status], imstatus2char[new_status],
(msg ? msg : ""));
+
+#ifdef MODULES_ENABLE
+ {
+ GSList *h = hk_handler_queue;
+ if (h) {
+ char ns[2] = " \0";
+ hk_arg_t args[] = {
+ { "hook", "hook-my-status-change" },
+ { "new_status", ns },
+ { "message", msg ? msg : "" },
+ { NULL, NULL },
+ };
+ ns[0] = imstatus2char[new_status];
+ while (h) {
+ hook_list_data_t *data = h->data;
+ (data->handler) (args, data->userdata);
+ h = g_slist_next (h);
+ }
+ }
+ }
+#endif
+
//hlog_write_status(NULL, 0, status);
}
@@ -390,6 +531,23 @@
if (process_command(cmdline, TRUE) == 255)
mcabber_set_terminate_ui();
+#ifdef MODULES_ENABLE
+ {
+ GSList *h = hk_handler_queue;
+ if (h) {
+ hk_arg_t args[] = {
+ { "hook", hookname },
+ { NULL, NULL },
+ };
+ while (h) {
+ hook_list_data_t *data = h->data;
+ (data->handler) (args, data->userdata);
+ h = g_slist_next (h);
+ }
+ }
+ }
+#endif
+
g_free(cmdline);
g_free(buf);
}
--- a/mcabber/src/hooks.h Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/hooks.h Sun Oct 11 16:01:52 2009 +0200
@@ -9,6 +9,21 @@
#define ENCRYPTED_PGP 1
#define ENCRYPTED_OTR 2
+#include "config.h"
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+typedef struct {
+ const char *name;
+ const char *value;
+} hk_arg_t;
+
+typedef void (*hk_handler_t) (hk_arg_t *args, gpointer userdata);
+
+void hk_add_handler (hk_handler_t handler, gpointer userdata);
+void hk_del_handler (hk_handler_t handler, gpointer userdata);
+#endif
+
void hk_mainloop(void);
void hk_message_in(const char *bjid, const char *resname,
time_t timestamp, const char *msg, LmMessageSubType type,
--- a/mcabber/src/main.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/main.c Sun Oct 11 16:01:52 2009 +0200
@@ -169,6 +169,9 @@
#ifdef HAVE_GPGME
puts("Compiled with GPG support.");
#endif
+#ifdef MODULES_ENABLE
+ puts ("Compiled with modules support");
+#endif
#ifdef HAVE_LIBOTR
puts("Compiled with OTR support.");
#endif
@@ -416,6 +419,9 @@
g_main_loop_run(main_loop);
scr_TerminateCurses();
+#ifdef MODULES_ENABLE
+ cmd_deinit();
+#endif
fifo_deinit();
#ifdef HAVE_LIBOTR
otr_terminate();
--- a/mcabber/src/settings.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/settings.c Sun Oct 11 16:01:52 2009 +0200
@@ -23,6 +23,7 @@
#include <stdlib.h>
#include <string.h>
+#include "config.h"
#include "settings.h"
#include "commands.h"
#include "logprint.h"
@@ -176,12 +177,15 @@
continue;
// We only allow assignments line, except for commands "pgp", "source",
- // "color" and "otrpolicy", unless we're in runtime (i.e. not startup).
+ // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup).
if (runtime ||
(strchr(line, '=') != NULL) ||
startswith(line, "pgp ", FALSE) ||
startswith(line, "source ", FALSE) ||
startswith(line, "color ", FALSE) ||
+#ifdef MODULES_ENABLE
+ startswith(line, "load ", FALSE) ||
+#endif
startswith(line, "otrpolicy", FALSE)) {
// Only accept a few "safe" commands
if (!runtime &&
@@ -191,6 +195,9 @@
!startswith(line, "pgp ", FALSE) &&
!startswith(line, "source ", FALSE) &&
!startswith(line, "color ", FALSE) &&
+#ifdef MODULES_ENABLE
+ !startswith(line, "load ", FALSE) &&
+#endif
!startswith(line, "otrpolicy ", FALSE)) {
scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
"this command can't be used here", ln);
--- a/mcabber/src/xmpp_helper.c Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/xmpp_helper.c Sun Oct 11 16:01:52 2009 +0200
@@ -30,6 +30,7 @@
#include "utils.h"
#include "caps.h"
#include "logprint.h"
+#include "config.h"
time_t iqlast; // last message/status change time
@@ -74,6 +75,37 @@
};
+#ifdef MODULES_ENABLE
+static GSList *xmpp_additional_features = NULL;
+static char *ver, *ver_notavail;
+
+void xmpp_add_feature (const char *xmlns)
+{
+ if (xmlns) {
+ ver = NULL;
+ ver_notavail = NULL;
+ xmpp_additional_features = g_slist_append(xmpp_additional_features,
+ g_strdup (xmlns));
+ }
+}
+
+void xmpp_del_feature (const char *xmlns)
+{
+ GSList *feature = xmpp_additional_features;
+ while (feature) {
+ if (!strcmp(feature->data, xmlns)) {
+ ver = NULL;
+ ver_notavail = NULL;
+ g_free (feature->data);
+ xmpp_additional_features = g_slist_delete_link(xmpp_additional_features,
+ feature);
+ return;
+ }
+ feature = g_slist_next (feature);
+ }
+}
+#endif
+
const gchar* lm_message_node_get_child_value(LmMessageNode *node,
const gchar *child)
{
@@ -180,7 +212,9 @@
// number) so that it doesn't conflict with the official client.
const char *entity_version(enum imstatus status)
{
+#ifndef MODULES_ENABLE
static char *ver, *ver_notavail;
+#endif
if (ver && (status != notavail))
return ver;
@@ -204,6 +238,15 @@
(!settings_opt_get_int("iq_last_disable_when_notavail") ||
status != notavail))
caps_add_feature("", NS_LAST);
+#ifdef MODULES_ENABLE
+ {
+ GSList *el = xmpp_additional_features;
+ while (el) {
+ caps_add_feature("", el->data);
+ el = g_slist_next (el);
+ }
+ }
+#endif
if (status == notavail) {
ver_notavail = caps_generate();
--- a/mcabber/src/xmpp_helper.h Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/xmpp_helper.h Sun Oct 11 16:01:52 2009 +0200
@@ -6,6 +6,7 @@
#include "xmpp.h"
#include "xmpp_defines.h"
+#include "config.h"
extern time_t iqlast; /* last message/status change time */
@@ -23,6 +24,11 @@
};
+#ifdef MODULES_ENABLE
+void xmpp_add_feature (const char *xmlns);
+void xmpp_del_feature (const char *xmlns);
+#endif
+
LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
const char *xmlns);