Initial experimental branch commit
* fifo-use-guard
* modularize-fifo
* separate-extcmd
* modularize-extcmd
* guard-xmpp-password
* guardize-colors
* roster-state-colors
* direct-invite
* muc-multiple-statuses
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,5 @@
+^\.hg
+^\.mq
+syntax: glob
+status
+guards
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/direct-invite Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,193 @@
+diff -r 0bfd182adcb7 mcabber/ChangeLog.api
+--- a/mcabber/ChangeLog.api Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/ChangeLog.api Fri Apr 02 02:20:08 2010 +0300
+@@ -1,3 +1,8 @@
++
++experimental (14)
++
++ * Add NS_X_CONFERENCE (derect invitation)
++ * MQ Patch: direct-invite
+
+ experimental (13)
+
+diff -r 0bfd182adcb7 mcabber/mcabber/api.h
+--- a/mcabber/mcabber/api.h Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/api.h Fri Apr 02 02:20:08 2010 +0300
+@@ -3,7 +3,7 @@
+
+ #include <mcabber/config.h> // For MCABBER_BRANCH
+
+-#define MCABBER_API_VERSION 13
++#define MCABBER_API_VERSION 14
+ #define MCABBER_API_MIN 12
+
+ extern const gchar *mcabber_branch;
+diff -r 0bfd182adcb7 mcabber/mcabber/xmpp.c
+--- a/mcabber/mcabber/xmpp.c Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/xmpp.c Fri Apr 02 02:20:08 2010 +0300
+@@ -1290,6 +1290,18 @@
+ "http://jabber.org/protocol/muc#user");
+ if (x && !strcmp(x->name, "x"))
+ got_muc_message(from, x);
++
++ x = lm_message_node_find_xmlns(m->node, NS_X_CONFERENCE);
++
++ if (x && !strcmp(x->name, "x")) {
++ const char *jid = lm_message_node_get_attribute(x, "jid");
++ if (jid) {
++ const char *reason = lm_message_node_get_attribute(x, "reason");
++ const char *password = lm_message_node_get_attribute(x, "password");
++ // FIXME we shouldn't send decline stanzas in this case
++ got_invite(from, jid, reason, password, FALSE);
++ }
++ }
+ }
+
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+diff -r 0bfd182adcb7 mcabber/mcabber/xmpp_defines.h
+--- a/mcabber/mcabber/xmpp_defines.h Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/xmpp_defines.h Fri Apr 02 02:20:08 2010 +0300
+@@ -27,6 +27,8 @@
+ #define NS_BROWSE "jabber:iq:browse"
+ #define NS_EVENT "jabber:x:event"
+ #define NS_CONFERENCE "jabber:iq:conference"
++// direct muc invitation (xep-0249)
++#define NS_X_CONFERENCE "jabber:x:conference"
+ #define NS_SIGNED "jabber:x:signed"
+ #define NS_ENCRYPTED "jabber:x:encrypted"
+ #define NS_GATEWAY "jabber:iq:gateway"
+diff -r 0bfd182adcb7 mcabber/mcabber/xmpp_helper.c
+--- a/mcabber/mcabber/xmpp_helper.c Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/xmpp_helper.c Fri Apr 02 02:20:08 2010 +0300
+@@ -232,6 +232,7 @@
+ caps_add_feature("", NS_PING);
+ caps_add_feature("", NS_COMMANDS);
+ caps_add_feature("", NS_RECEIPTS);
++ caps_add_feature("", NS_X_CONFERENCE);
+ if (!settings_opt_get_int("iq_last_disable") &&
+ (!settings_opt_get_int("iq_last_disable_when_notavail") ||
+ status != notavail))
+diff -r 0bfd182adcb7 mcabber/mcabber/xmpp_muc.c
+--- a/mcabber/mcabber/xmpp_muc.c Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/xmpp_muc.c Fri Apr 02 02:20:08 2010 +0300
+@@ -39,6 +39,8 @@
+ extern enum imstatus mystatus;
+ extern gchar *mystatusmsg;
+
++static GSList *invitations = NULL;
++
+ static void decline_invitation(event_muc_invitation *invitation, const char *reason)
+ {
+ // cut and paste from xmpp_room_invite
+@@ -66,10 +68,12 @@
+
+ void destroy_event_muc_invitation(event_muc_invitation *invitation)
+ {
++ invitations = g_slist_remove(invitations, invitation);
+ g_free(invitation->to);
+ g_free(invitation->from);
+ g_free(invitation->passwd);
+ g_free(invitation->reason);
++ g_free(invitation->evid);
+ g_free(invitation);
+ }
+
+@@ -105,7 +109,8 @@
+ g_free(nickname);
+ } else {
+ scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to);
+- decline_invitation(invitation, arg);
++ if (invitation->reply)
++ decline_invitation(invitation, arg);
+ }
+
+ return FALSE;
+@@ -642,14 +647,12 @@
+ g_free(to);
+ }
+
+-// got_invite(from, to, reason, passwd)
++// got_invite(from, to, reason, passwd, reply)
+ // This function should be called when receiving an invitation from user
+ // "from", to enter the room "to". Optional reason and room password can
+ // be provided.
+-// TODO: check for duplicate invites (need an existing invitation registry
+-// for that).
+-static void got_invite(const char* from, const char *to, const char* reason,
+- const char* passwd)
++void got_invite(const char* from, const char *to, const char* reason,
++ const char* passwd, gboolean reply)
+ {
+ GString *sbuf;
+ char *barejid;
+@@ -669,7 +672,21 @@
+ scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+ scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str);
+
+- {
++ { // remove any equal older invites
++ GSList *iel = invitations;
++ while (iel) {
++ event_muc_invitation *invitation = iel->data;
++ iel = iel -> next;
++ if (!g_strcmp0(to, invitation->to) &&
++ !g_strcmp0(passwd, invitation->passwd)) {
++ scr_LogPrint(LPRINT_DEBUG, "Destroying previous invitation event %s.",
++ invitation->evid);
++ evs_del(invitation->evid);
++ }
++ }
++ }
++
++ { // create event
+ const char *id;
+ char *desc = g_strdup_printf("<%s> invites you to %s", from, to);
+ event_muc_invitation *invitation;
+@@ -679,13 +696,18 @@
+ invitation->from = g_strdup(from);
+ invitation->passwd = g_strdup(passwd);
+ invitation->reason = g_strdup(reason);
++ invitation->reply = reply;
++ invitation->evid = NULL;
++
++ invitations = g_slist_append(invitations, invitation);
+
+ id = evs_new(desc, NULL, 0, evscallback_invitation, invitation,
+ (GDestroyNotify)destroy_event_muc_invitation);
+ g_free(desc);
+- if (id)
++ if (id) {
++ invitation->evid = g_strdup(id);
+ g_string_printf(sbuf, "Please use /event %s accept|reject", id);
+- else
++ } else
+ g_string_printf(sbuf, "Unable to create a new event!");
+ }
+ scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+@@ -717,7 +739,7 @@
+ reason = lm_message_node_get_child_value(invite, "reason");
+ password = lm_message_node_get_child_value(invite, "password");
+ if (invite_from)
+- got_invite(invite_from, from, reason, password);
++ got_invite(invite_from, from, reason, password, TRUE);
+ }
+ // TODO
+ // handle status code = 100 ( not anonymous )
+diff -r 0bfd182adcb7 mcabber/mcabber/xmpp_muc.h
+--- a/mcabber/mcabber/xmpp_muc.h Fri Apr 02 01:52:18 2010 +0300
++++ b/mcabber/mcabber/xmpp_muc.h Fri Apr 02 02:20:08 2010 +0300
+@@ -6,10 +6,14 @@
+ char *from;
+ char *passwd;
+ char *reason;
++ char *evid;
++ gboolean reply;
+ } event_muc_invitation;
+
+ void destroy_event_muc_invitation(event_muc_invitation *invitation);
+ void roompresence(gpointer room, void *presencedata);
++void got_invite(const char* from, const char *to, const char* reason,
++ const char* passwd, gboolean reply);
+ void got_muc_message(const char *from, LmMessageNode *x);
+ void handle_muc_presence(const char *from, LmMessageNode * xmldata,
+ const char *roomjid, const char *rname,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/fifo-use-guard Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,108 @@
+diff -r 347c20d4b0ee mcabber/mcabber/fifo.c
+--- a/mcabber/mcabber/fifo.c Thu Apr 01 23:28:43 2010 +0300
++++ b/mcabber/mcabber/fifo.c Thu Apr 01 23:48:34 2010 +0300
+@@ -91,6 +91,7 @@
+ {
+ GIOChannel *channel = (GIOChannel *)data;
+ g_io_channel_unref(channel);
++ channel = NULL;
+ }
+
+ static gboolean check_fifo(const char *name)
+@@ -137,15 +138,37 @@
+ return TRUE;
+ }
+
+-int fifo_init(const char *fifo_path)
++void fifo_deinit(void)
++{
++ unsetenv(FIFO_ENV_NAME);
++
++ if (fifo_channel)
++ g_source_remove_by_user_data(fifo_channel);
++ /* channel itself should be destroyed by destruction callback */
++ /* destroy open fifo */
++ if (fifo_name) {
++ /* well, that may create fifo, and then unlink,
++ * but at least we will not destroy non-fifo data */
++ if (check_fifo(fifo_name))
++ unlink(fifo_name);
++ g_free(fifo_name);
++ fifo_name = NULL;
++ }
++}
++
++// fifo_init_internal(path)
++// If path is NULL, reopen existing fifo, else open anew.
++static int fifo_init_internal(const char *fifo_path)
+ {
+ if (fifo_path) {
++ fifo_deinit();
+ fifo_name = expand_filename(fifo_path);
+
+ if (!check_fifo(fifo_name)) {
+ scr_LogPrint(LPRINT_LOGNORM, "WARNING: Cannot create the FIFO. "
+ "%s already exists and is not a pipe", fifo_name);
+ g_free(fifo_name);
++ fifo_name = NULL;
+ return -1;
+ }
+ } else if (fifo_name)
+@@ -164,15 +187,26 @@
+ return 1;
+ }
+
+-void fifo_deinit(void)
++static gchar *fifo_guard(const gchar *key, const gchar *new_value)
+ {
+- unsetenv(FIFO_ENV_NAME);
++ if (new_value)
++ fifo_init_internal(new_value);
++ else
++ fifo_deinit();
++ return g_strdup(new_value);
++}
+
+- /* destroy open fifo */
+- unlink(fifo_name);
+- g_source_remove_by_user_data(fifo_channel);
+- /* channel itself should be destroyed by destruction callback */
+- g_free(fifo_name);
++// Returns 1 in case of success, -1 on error
++int fifo_init(void)
++{
++ const char *path = settings_opt_get("fifo_name");
++ static gboolean guard_installed = FALSE;
++ if (!guard_installed)
++ if (!(guard_installed = settings_set_guard("fifo_name", fifo_guard)))
++ scr_LogPrint(LPRINT_DEBUG, "fifo: BUG: Cannot install option guard!");
++ if (path)
++ return fifo_init_internal(path);
++ return 1;
+ }
+
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
+diff -r 347c20d4b0ee mcabber/mcabber/fifo.h
+--- a/mcabber/mcabber/fifo.h Thu Apr 01 23:28:43 2010 +0300
++++ b/mcabber/mcabber/fifo.h Thu Apr 01 23:48:34 2010 +0300
+@@ -1,7 +1,7 @@
+ #ifndef __MCABBER_FIFO_H__
+ #define __MCABBER_FIFO_H__ 1
+
+-int fifo_init(const char *fifo_path);
++int fifo_init(void);
+ void fifo_deinit(void);
+
+ #endif /* __MCABBER_FIFO_H__ */
+diff -r 347c20d4b0ee mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Thu Apr 01 23:28:43 2010 +0300
++++ b/mcabber/mcabber/main.c Thu Apr 01 23:48:34 2010 +0300
+@@ -433,7 +433,7 @@
+ chatstates_disabled = settings_opt_get_int("disable_chatstates");
+
+ /* Initialize FIFO named pipe */
+- fifo_init(settings_opt_get("fifo_name"));
++ fifo_init();
+
+ /* Load previous roster state */
+ hlog_load_state();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/guard-xmpp-password Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,154 @@
+diff -r cc9cb758d7ae mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c Fri Apr 02 00:51:29 2010 +0300
++++ b/mcabber/mcabber/commands.c Fri Apr 02 01:05:03 2010 +0300
+@@ -2087,10 +2087,8 @@
+
+ static void list_option_cb(char *k, char *v, void *f)
+ {
+- if (strcmp(k, "password")) {
+- GSList **list = f;
+- *list = g_slist_insert_sorted(*list, k, (GCompareFunc)strcmp);
+- }
++ GSList **list = f;
++ *list = g_slist_insert_sorted(*list, k, (GCompareFunc)strcmp);
+ }
+
+ static void do_set(char *arg)
+diff -r cc9cb758d7ae mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Fri Apr 02 00:51:29 2010 +0300
++++ b/mcabber/mcabber/main.c Fri Apr 02 01:05:03 2010 +0300
+@@ -379,6 +379,7 @@
+ scr_init_locale_charset();
+ ut_init_debug();
+ help_init();
++ xmpp_init();
+
+ /* Parsing config file... */
+ ret = cfg_read_file(configFile, TRUE);
+@@ -390,7 +391,7 @@
+
+ /* If no password is stored, we ask for it before entering
+ ncurses mode -- unless the username is unknown. */
+- if (settings_opt_get("jid") && !settings_opt_get("password")) {
++ if (settings_opt_get("jid") && !xmpp_have_password) {
+ const char *p;
+ char *pwd;
+ p = settings_opt_get("server");
+@@ -401,6 +402,7 @@
+ printf("User JID: %s\n", p);
+
+ pwd = ask_password("your Jabber password");
++ /* Will be intercepted by guard */
+ settings_set(SETTINGS_TYPE_OPTION, "password", pwd);
+ g_free(pwd);
+ }
+diff -r cc9cb758d7ae mcabber/mcabber/xmpp.c
+--- a/mcabber/mcabber/xmpp.c Fri Apr 02 00:51:29 2010 +0300
++++ b/mcabber/mcabber/xmpp.c Fri Apr 02 01:05:03 2010 +0300
+@@ -23,6 +23,8 @@
+ */
+ #include <stdlib.h>
+ #include <string.h>
++#include <sys/mman.h>
++#include <errno.h>
+
+ #include "xmpp.h"
+ #include "xmpp_helper.h"
+@@ -53,6 +55,9 @@
+ static enum imstatus mywantedstatus = available;
+ gchar *mystatusmsg;
+
++static char *xmpp_password = NULL;
++gboolean xmpp_have_password = FALSE;
++
+ char imstatus2char[imstatus_size+1] = {
+ '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0'
+ };
+@@ -892,16 +897,15 @@
+ GError *error = NULL;
+
+ if (success) {
+- const char *password, *resource;
++ const char *resource;
+ char *username;
+ username = jid_get_username(settings_opt_get("jid"));
+- password = settings_opt_get("password");
+ resource = strchr(lm_connection_get_jid(connection),
+ JID_RESOURCE_SEPARATOR);
+ if (resource)
+ resource++;
+
+- if (!lm_connection_authenticate(lconnection, username, password, resource,
++ if (!lm_connection_authenticate(lconnection, username, xmpp_password, resource,
+ connection_auth_cb, NULL, FALSE, &error)) {
+ scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s",
+ error->message);
+@@ -1633,13 +1637,37 @@
+ }
+ }
+
++static gchar *xmpp_password_guard(const gchar *key, const gchar *new_value)
++{
++ if (xmpp_password) {
++ size_t len = strlen(xmpp_password);
++ memset(xmpp_password, '\0', len);
++ if (munlock(xmpp_password, len))
++ scr_LogPrint(LPRINT_DEBUG, "password guard: Cannot unlock memory: %s.",
++ strerror(errno));
++ g_free(xmpp_password);
++ }
++ xmpp_password = g_strdup(new_value);
++ if (xmpp_password) {
++ if (mlock(xmpp_password, strlen(xmpp_password)))
++ scr_LogPrint(LPRINT_DEBUG, "password guard: Cannot lock memory: %s.",
++ strerror(errno));
++ xmpp_have_password = TRUE;
++ }
++ return NULL;
++}
++
++void xmpp_init(void)
++{
++ settings_set_guard("password", xmpp_password_guard);
++}
+
+ // xmpp_connect()
+ // Return a non-zero value if there's an obvious problem
+ // (no JID, no password, etc.)
+ gint xmpp_connect(void)
+ {
+- const char *userjid, *password, *resource, *servername, *ssl_fpr;
++ const char *userjid, *resource, *servername, *ssl_fpr;
+ char *dynresource = NULL;
+ char fpr[16];
+ const char *proxy_host;
+@@ -1656,7 +1684,6 @@
+
+ servername = settings_opt_get("server");
+ userjid = settings_opt_get("jid");
+- password = settings_opt_get("password");
+ resource = settings_opt_get("resource");
+ proxy_host = settings_opt_get("proxy_host");
+ ssl_fpr = settings_opt_get("ssl_fingerprint");
+@@ -1665,7 +1692,7 @@
+ scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!");
+ return -1;
+ }
+- if (!password) {
++ if (!xmpp_password) {
+ scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!");
+ return -1;
+ }
+diff -r cc9cb758d7ae mcabber/mcabber/xmpp.h
+--- a/mcabber/mcabber/xmpp.h Fri Apr 02 00:51:29 2010 +0300
++++ b/mcabber/mcabber/xmpp.h Fri Apr 02 01:05:03 2010 +0300
+@@ -31,7 +31,9 @@
+
+ extern LmConnection* lconnection;
+ extern LmSSL* lssl;
++extern gboolean xmpp_have_password; /* private */
+
++void xmpp_init(void); /* private */
+ int xmpp_connect(void);
+ void xmpp_disconnect(void);
+ gboolean xmpp_is_online(void);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/guardize-colors Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,63 @@
+diff -r 779506ed5d59 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c Fri Apr 02 01:05:03 2010 +0300
++++ b/mcabber/mcabber/screen.c Fri Apr 02 01:43:23 2010 +0300
+@@ -72,6 +72,7 @@
+
+ static unsigned short int Log_Win_Height;
+ static unsigned short int Roster_Width;
++static gboolean colors_stalled = FALSE;
+
+ // Default attention sign trigger levels
+ static guint ui_attn_sign_prio_level_muc = ROSTER_UI_PRIO_MUC_HL_MESSAGE;
+@@ -560,6 +561,8 @@
+ (*nickcols)->color_attrib = A_NORMAL;
+ }
+ }
++
++ colors_stalled = FALSE;
+ }
+
+ static void init_keycodes(void)
+@@ -752,6 +755,13 @@
+ return Curses;
+ }
+
++static gchar *scr_color_guard(const gchar *key, const gchar *new_value)
++{
++ if (g_strcmp0(settings_opt_get(key), new_value))
++ colors_stalled = TRUE;
++ return g_strdup(new_value);
++}
++
+ void scr_init_curses(void)
+ {
+ /* Key sequences initialization */
+@@ -779,6 +789,19 @@
+
+ parse_colors();
+
++ settings_set_guard("color_background", scr_color_guard);
++ settings_set_guard("color_general", scr_color_guard);
++ settings_set_guard("color_info", scr_color_guard);
++ settings_set_guard("color_msgin", scr_color_guard);
++ settings_set_guard("color_msgout", scr_color_guard);
++ settings_set_guard("color_msghl", scr_color_guard);
++ settings_set_guard("color_bgstatus", scr_color_guard);
++ settings_set_guard("color_status", scr_color_guard);
++ settings_set_guard("color_roster", scr_color_guard);
++ settings_set_guard("color_bgrostersel", scr_color_guard);
++ settings_set_guard("color_rostersel", scr_color_guard);
++ settings_set_guard("color_rosterselmsg", scr_color_guard);
++
+ getmaxyx(stdscr, maxY, maxX);
+ Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
+ // Note scr_draw_main_window() should be called early after scr_init_curses()
+@@ -3952,6 +3975,8 @@
+
+ void scr_do_update(void)
+ {
++ if (colors_stalled)
++ parse_colors();
+ doupdate();
+ }
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modularize-extcmd Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,424 @@
+diff -r 422b477167b9 mcabber/ChangeLog.api
+--- a/mcabber/ChangeLog.api Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/ChangeLog.api Fri Apr 02 01:52:13 2010 +0300
+@@ -1,3 +1,8 @@
++
++experimental (13)
++
++ * Add "timestamp" argument to hook-message-in
++ * MQ Patch: modularize-extcmd
+
+ experimental (12)
+
+diff -r 422b477167b9 mcabber/configure.ac
+--- a/mcabber/configure.ac Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/configure.ac Fri Apr 02 01:52:13 2010 +0300
+@@ -275,6 +275,7 @@
+ modules/beep/Makefile
+ modules/xttitle/Makefile
+ modules/fifo/Makefile
++ modules/eventcmd/Makefile
+ doc/Makefile
+ doc/guide/Makefile
+ doc/help/Makefile
+diff -r 422b477167b9 mcabber/mcabber/Makefile.am
+--- a/mcabber/mcabber/Makefile.am Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/mcabber/Makefile.am Fri Apr 02 01:52:13 2010 +0300
+@@ -7,7 +7,7 @@
+ xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \
+ xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \
+ xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \
+- caps.c caps.h help.c help.h extcmd.c extcmd.h
++ caps.c caps.h help.c help.h
+
+ if OTR
+ mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h
+@@ -55,7 +55,7 @@
+
+ mcabberincludedir = $(includedir)/mcabber
+ else
+-mcabber_SOURCES += fifo.c fifo.h
++mcabber_SOURCES += fifo.c fifo.h extcmd.c extcmd.h
+ endif
+
+ #SUBDIRS =
+diff -r 422b477167b9 mcabber/mcabber/api.h
+--- a/mcabber/mcabber/api.h Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/mcabber/api.h Fri Apr 02 01:52:13 2010 +0300
+@@ -3,7 +3,7 @@
+
+ #include <mcabber/config.h> // For MCABBER_BRANCH
+
+-#define MCABBER_API_VERSION 12
++#define MCABBER_API_VERSION 13
+ #define MCABBER_API_MIN 12
+
+ extern const gchar *mcabber_branch;
+diff -r 422b477167b9 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/mcabber/hooks.c Fri Apr 02 01:52:13 2010 +0300
+@@ -36,7 +36,10 @@
+ #include "utf8.h"
+ #include "commands.h"
+ #include "main.h"
+-#include "extcmd.h"
++
++#ifndef MODULES_ENABLE
++# include "extcmd.h"
++#endif
+
+ #ifdef MODULES_ENABLE
+ #include <glib.h>
+@@ -189,7 +192,9 @@
+ int is_groupchat = FALSE; // groupchat message
+ int is_room = FALSE; // window is a room window
+ int log_muc_conf = FALSE;
++#ifndef MODULES_ENABLE
+ int active_window = FALSE;
++#endif
+ int message_flags = 0;
+ guint rtype = ROSTER_TYPE_USER;
+ char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
+@@ -369,18 +374,23 @@
+
+ #ifdef MODULES_ENABLE
+ {
++ char timestamp_str[20];
++ if (timestamp)
++ to_iso8601(timestamp_str, timestamp);
+ hk_arg_t args[] = {
+ { "jid", bjid },
+ { "resource", resname },
+ { "message", wmsg },
+ { "groupchat", is_groupchat ? "true" : "false" },
+ { "attention", attention ? "true" : "false" },
++ { "timestamp", timestamp ? timestamp_str : NULL },
+ { NULL, NULL },
+ };
+ hk_run_handlers(HOOK_POST_MESSAGE_IN, args);
+ }
+ #endif
+
++#ifndef MODULES_ENABLE
+ if (settings_opt_get_int("events_ignore_active_window") &&
+ current_buddy && scr_get_chatmode()) {
+ gpointer bud = BUDDATA(current_buddy);
+@@ -399,6 +409,7 @@
+ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE,
+ is_groupchat ? EXT_CMD_INFO_GROUPCHAT : EXT_CMD_INFO_RECEIVED,
+ wmsg);
++#endif
+
+ // Beep, if enabled:
+ // - if it's a private message
+@@ -475,8 +486,10 @@
+ }
+ #endif
+
++#ifndef MODULES_ENABLE
+ // External command
+ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE, EXT_CMD_INFO_SENT, NULL);
++#endif
+
+ g_free(bmsg);
+ g_free(mmsg);
+@@ -562,9 +575,11 @@
+ }
+ #endif
+
++#ifndef MODULES_ENABLE
+ // External command
+ newstatus[0] = toupper(newstatus[0]);
+ hk_ext_cmd(bjid, EXT_CMD_TYPE_STATUS, newstatus, status_msg);
++#endif
+ }
+
+ void hk_mystatuschange(time_t timestamp, enum imstatus old_status,
+@@ -688,11 +703,13 @@
+ prev_muc_unread = muc_unread;
+ prev_muc_attention = muc_attention;
+
++#ifndef MODULES_ENABLE
+ /* Call external command */
+ str_unread = g_strdup_printf("%u %u %u %u", unread_count, attention_count,
+ muc_unread, muc_attention);
+ hk_ext_cmd("", EXT_CMD_TYPE_UNREAD, str_unread, NULL);
+ g_free(str_unread);
++#endif
+ }
+
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
+diff -r 422b477167b9 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/mcabber/main.c Fri Apr 02 01:52:13 2010 +0300
+@@ -45,7 +45,10 @@
+ #include "xmpp.h"
+ #include "help.h"
+ #include "events.h"
+-#include "extcmd.h"
++
++#ifndef MODULES_ENABLE
++# include "extcmd.h"
++#endif
+
+ #ifndef MODULES_ENABLE
+ # include "fifo.h"
+@@ -425,9 +428,11 @@
+ }
+ #endif
+
++#ifndef MODULES_ENABLE
+ optstring = settings_opt_get("events_command");
+ if (optstring)
+ hk_ext_cmd_init(optstring);
++#endif
+
+ optstring = settings_opt_get("roster_display_filter");
+ if (optstring)
+diff -r 422b477167b9 mcabber/modules/Makefile.am
+--- a/mcabber/modules/Makefile.am Fri Apr 02 01:50:48 2010 +0300
++++ b/mcabber/modules/Makefile.am Fri Apr 02 01:52:13 2010 +0300
+@@ -1,1 +1,1 @@
+-SUBDIRS = beep xttitle fifo
++SUBDIRS = beep xttitle fifo eventcmd
+diff -r 422b477167b9 mcabber/modules/eventcmd/Makefile.am
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/eventcmd/Makefile.am Fri Apr 02 01:52:13 2010 +0300
+@@ -0,0 +1,12 @@
++
++if INSTALL_HEADERS
++pkglib_LTLIBRARIES = libeventcmd.la
++libeventcmd_la_SOURCES = eventcmd.c $(top_srcdir)/mcabber/extcmd.c $(top_srcdir)/mcabber/extcmd.h
++libeventcmd_la_LDFLAGS = -module -avoid-version -shared
++
++LDADD = $(GLIB_LIBS)
++AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) \
++ $(GPGME_CFLAGS) $(LIBOTR_CFLAGS) \
++ $(ENCHANT_CFLAGS)
++endif
++
+diff -r 422b477167b9 mcabber/modules/eventcmd/eventcmd.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/eventcmd/eventcmd.c Fri Apr 02 01:52:13 2010 +0300
+@@ -0,0 +1,221 @@
++
++/* Copyright 2009 Myhailo Danylenko
++ *
++ * This file is part of mcabber
++ *
++ * mcabber 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 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
++
++#include <glib.h>
++#include <gmodule.h>
++#include <string.h>
++#include <stdlib.h>
++#include <ctype.h> // tolower
++#include <unistd.h>
++
++#include <mcabber/settings.h>
++#include <mcabber/hooks.h>
++#include <mcabber/roster.h>
++#include <mcabber/utils.h>
++#include <mcabber/logprint.h>
++#include <mcabber/extcmd.h>
++#include <mcabber/modules.h>
++#include <mcabber/screen.h>
++#include <mcabber/config.h>
++
++static void ecm_init(void);
++static void ecm_uninit(void);
++
++module_info_t info_eventcmd = {
++ .branch = MCABBER_BRANCH,
++ .api = MCABBER_API_VERSION,
++ .version = MCABBER_VERSION,
++ .requires = NULL,
++ .init = ecm_init,
++ .uninit = ecm_uninit,
++ .description = "External command execution on events\n"
++ "Recognizes options events_command (required), events_ignore_active_window, "
++ "event_log_files, event_log_dir, eventcmd_use_nickname, eventcmd_checkstatus.",
++ .next = NULL,
++};
++
++static guint eventcmd_hpmi_hid = 0;
++static guint eventcmd_hmo_hid = 0;
++static guint eventcmd_hsc_hid = 0;
++static guint eventcmd_hulc_hid = 0;
++
++static guint eventcmd_hpmih(const gchar *name, hk_arg_t *args,
++ gpointer userdata)
++{
++ gboolean active_window = FALSE;
++ gboolean is_groupchat = FALSE;
++ gboolean timestamp = FALSE;
++ const char *bjid = NULL;
++ hk_arg_t *arg = args;
++ const char *wmsg = NULL;
++
++ while (arg->name) {
++ if (!strcmp(arg->name, "jid"))
++ bjid = arg->value;
++ else if (!strcmp(arg->name, "groupchat"))
++ is_groupchat = strcmp(arg->value, "true") ? FALSE : TRUE;
++ else if (!strcmp(arg->name, "timestamp") && arg->value)
++ timestamp = TRUE;
++ else if (!strcmp(arg->name, "message"))
++ wmsg = arg->value;
++ arg++;
++ }
++
++ if (!bjid) {
++ scr_LogPrint(LPRINT_LOGNORM, "eventcmd: post-message-in: No jid found.");
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++ }
++
++ if (settings_opt_get_int("events_ignore_active_window") &&
++ current_buddy && scr_get_chatmode()) {
++ gpointer bud = BUDDATA(current_buddy);
++ if (bud) {
++ const char *cjid = buddy_getjid(bud);
++ if (cjid && !strcasecmp(cjid, bjid))
++ active_window = TRUE;
++ }
++ }
++
++ if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat))
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE,
++ is_groupchat ? EXT_CMD_INFO_GROUPCHAT : EXT_CMD_INFO_RECEIVED, wmsg);
++
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++}
++
++static guint eventcmd_hmoh(const gchar *name, hk_arg_t *args, gpointer userdata)
++{
++ const char *bjid = NULL;
++ hk_arg_t *arg = args;
++
++ while (arg->name) {
++ if (!strcmp(arg->name, "jid")) {
++ bjid = arg->value;
++ break;
++ }
++ arg++;
++ }
++
++ if (!bjid) {
++ scr_LogPrint(LPRINT_LOGNORM, "eventcmd: message-out: No jid found.");
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++ }
++
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE, EXT_CMD_INFO_SENT, NULL);
++
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++}
++
++static guint eventcmd_hsch(const gchar *name, hk_arg_t *args, gpointer userdata)
++{
++ const char *bjid = NULL;
++ char status[2] = { '?', '\0' };
++ hk_arg_t *arg = args;
++ const char *message = NULL;
++
++ while (arg->name) {
++ if (!strcmp(arg->name, "jid"))
++ bjid = arg->value;
++ else if (!strcmp(arg->name, "new_status"))
++ status[0] = toupper (arg->value[0]);
++ else if (!strcmp(arg->name, "message") && arg->value && *(arg->value))
++ message = arg->value;
++ arg++;
++ }
++
++ if (!bjid) {
++ scr_LogPrint(LPRINT_LOGNORM, "eventcmd: status-change: No jid found.");
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++ }
++
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_STATUS, status, message);
++
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++}
++
++static guint eventcmd_hulch(const gchar *name, hk_arg_t *args, gpointer userdata)
++{
++ hk_arg_t *arg = args;
++ const gchar *unread = NULL;
++ const gchar *attention = NULL;
++ const gchar *muc_unread = NULL;
++ const gchar *muc_attention = NULL;
++
++ while (arg->name) {
++ if (!strcmp(arg->name, "unread"))
++ unread = arg->value;
++ else if (!strcmp(arg->name, "attention"))
++ attention = arg->value;
++ else if (!strcmp(arg->name, "muc_unread"))
++ muc_unread = arg->value;
++ else if (!strcmp(arg->name, "muc_attention"))
++ muc_attention = arg->value;
++ arg++;
++ }
++
++ if (!unread || !attention || !muc_unread || !muc_attention) {
++ scr_LogPrint(LPRINT_LOGNORM, "eventcmd: unread-list-change:Â Missing parameter.");
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++ }
++
++ {
++ gchar *str = g_strdup_printf("%s %s %s %s", unread, attention, muc_unread,
++ muc_attention);
++ hk_ext_cmd("", EXT_CMD_TYPE_UNREAD, str, NULL);
++ g_free(str);
++ }
++
++ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
++}
++
++const gchar *g_module_check_init(GModule *module)
++{
++ const char *command = settings_opt_get("events_command");
++ if (!command)
++ return "events_command not set";
++ hk_ext_cmd_init(command);
++ return NULL;
++}
++
++void g_module_unload(GModule *module)
++{
++ hk_ext_cmd_init(NULL);
++}
++
++static void ecm_init(void)
++{
++ eventcmd_hpmi_hid = hk_add_handler(eventcmd_hpmih, HOOK_POST_MESSAGE_IN,
++ G_PRIORITY_LOW, NULL);
++ eventcmd_hmo_hid = hk_add_handler(eventcmd_hmoh, HOOK_MESSAGE_OUT,
++ G_PRIORITY_LOW, NULL);
++ eventcmd_hsc_hid = hk_add_handler(eventcmd_hsch, HOOK_STATUS_CHANGE,
++ G_PRIORITY_LOW, NULL);
++ eventcmd_hulc_hid = hk_add_handler(eventcmd_hulch, HOOK_UNREAD_LIST_CHANGE,
++ G_PRIORITY_LOW, NULL);
++
++}
++
++static void ecm_uninit(void)
++{
++ hk_del_handler(HOOK_POST_MESSAGE_IN, eventcmd_hpmi_hid);
++ hk_del_handler(HOOK_MESSAGE_OUT, eventcmd_hmo_hid);
++ hk_del_handler(HOOK_STATUS_CHANGE, eventcmd_hsc_hid);
++ hk_del_handler(HOOK_UNREAD_LIST_CHANGE, eventcmd_hulc_hid);
++}
++
++/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modularize-fifo Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,159 @@
+diff -r 539e283d4af4 mcabber/configure.ac
+--- a/mcabber/configure.ac Thu Apr 01 23:48:34 2010 +0300
++++ b/mcabber/configure.ac Fri Apr 02 00:06:58 2010 +0300
+@@ -274,6 +274,7 @@
+ modules/Makefile
+ modules/beep/Makefile
+ modules/xttitle/Makefile
++ modules/fifo/Makefile
+ doc/Makefile
+ doc/guide/Makefile
+ doc/help/Makefile
+diff -r 539e283d4af4 mcabber/mcabber/Makefile.am
+--- a/mcabber/mcabber/Makefile.am Thu Apr 01 23:48:34 2010 +0300
++++ b/mcabber/mcabber/Makefile.am Fri Apr 02 00:06:58 2010 +0300
+@@ -7,7 +7,7 @@
+ xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \
+ xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \
+ xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \
+- caps.c caps.h fifo.c fifo.h help.c help.h
++ caps.c caps.h help.c help.h
+
+ if OTR
+ mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h
+@@ -53,6 +53,8 @@
+ endif
+
+ mcabberincludedir = $(includedir)/mcabber
++else
++mcabber_SOURCES += fifo.c fifo.h
+ endif
+
+ #SUBDIRS =
+diff -r 539e283d4af4 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Thu Apr 01 23:48:34 2010 +0300
++++ b/mcabber/mcabber/main.c Fri Apr 02 00:06:58 2010 +0300
+@@ -42,11 +42,14 @@
+ #include "utils.h"
+ #include "pgp.h"
+ #include "otr.h"
+-#include "fifo.h"
+ #include "xmpp.h"
+ #include "help.h"
+ #include "events.h"
+
++#ifndef MODULES_ENABLE
++# include "fifo.h"
++#endif
++
+ #ifdef MODULES_ENABLE
+ # include "modules.h"
+ #endif
+@@ -77,7 +80,9 @@
+
+ static void mcabber_terminate(const char *msg)
+ {
++#ifndef MODULES_ENABLE
+ fifo_deinit();
++#endif
+ xmpp_disconnect();
+ scr_terminate_curses();
+
+@@ -432,8 +437,10 @@
+
+ chatstates_disabled = settings_opt_get_int("disable_chatstates");
+
++#ifndef MODULES_ENABLE
+ /* Initialize FIFO named pipe */
+ fifo_init();
++#endif
+
+ /* Load previous roster state */
+ hlog_load_state();
+@@ -482,7 +489,9 @@
+ #ifdef MODULES_ENABLE
+ modules_deinit();
+ #endif
++#ifndef MODULES_ENABLE
+ fifo_deinit();
++#endif
+ #ifdef HAVE_LIBOTR
+ otr_terminate();
+ #endif
+diff -r 539e283d4af4 mcabber/modules/Makefile.am
+--- a/mcabber/modules/Makefile.am Thu Apr 01 23:48:34 2010 +0300
++++ b/mcabber/modules/Makefile.am Fri Apr 02 00:06:58 2010 +0300
+@@ -1,1 +1,1 @@
+-SUBDIRS = beep xttitle
++SUBDIRS = beep xttitle fifo
+diff -r 539e283d4af4 mcabber/modules/fifo/Makefile.am
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/fifo/Makefile.am Fri Apr 02 00:06:58 2010 +0300
+@@ -0,0 +1,12 @@
++
++if INSTALL_HEADERS
++pkglib_LTLIBRARIES = libfifo.la
++libfifo_la_SOURCES = fifo_module.c $(top_srcdir)/mcabber/fifo.c $(top_srcdir)/mcabber/fifo.h
++libfifo_la_LDFLAGS = -module -avoid-version -shared
++
++LDADD = $(GLIB_LIBS)
++AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) \
++ $(GPGME_CFLAGS) $(LIBOTR_CFLAGS) \
++ $(ENCHANT_CFLAGS)
++endif
++
+diff -r 539e283d4af4 mcabber/modules/fifo/fifo_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/fifo/fifo_module.c Fri Apr 02 00:06:58 2010 +0300
+@@ -0,0 +1,51 @@
++
++/* Copyright 2009 Myhailo Danylenko
++ *
++ * This file is part of mcabber
++ *
++ * mcabber 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 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
++
++#include <glib.h>
++#include <gmodule.h>
++
++#include <mcabber/fifo.h>
++#include <mcabber/modules.h>
++#include <mcabber/config.h>
++
++module_info_t info_fifo = {
++ .branch = MCABBER_BRANCH,
++ .api = MCABBER_API_VERSION,
++ .version = MCABBER_VERSION,
++ .requires = NULL,
++ .init = NULL,
++ .uninit = NULL,
++ .description = "Reads and executes command from FIFO pipe\n"
++ "Recognizes opttions fifo_name (required), fifo_hide_commands and fifo_ignore.",
++ .next = NULL,
++};
++
++gchar *g_module_check_init(GModule *module)
++{
++ if (fifo_init() == -1)
++ return "Fifo initialization failed";
++ else
++ return NULL;
++}
++
++void g_module_unload(GModule *module)
++{
++ fifo_deinit();
++}
++
++/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/muc-multiple-statuses Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,355 @@
+diff -r 9cdfb8ad447b mcabber/mcabber/xmpp.c
+--- a/mcabber/mcabber/xmpp.c Fri Apr 02 02:08:25 2010 +0300
++++ b/mcabber/mcabber/xmpp.c Fri Apr 02 02:17:03 2010 +0300
+@@ -1289,7 +1289,7 @@
+ x = lm_message_node_find_xmlns(m->node,
+ "http://jabber.org/protocol/muc#user");
+ if (x && !strcmp(x->name, "x"))
+- got_muc_message(from, x);
++ got_muc_message(from, x, timestamp);
+
+ x = lm_message_node_find_xmlns(m->node, NS_X_CONFERENCE);
+
+diff -r 9cdfb8ad447b mcabber/mcabber/xmpp_muc.c
+--- a/mcabber/mcabber/xmpp_muc.c Fri Apr 02 02:08:25 2010 +0300
++++ b/mcabber/mcabber/xmpp_muc.c Fri Apr 02 02:17:03 2010 +0300
+@@ -403,8 +403,6 @@
+ enum imstatus ust, const char *ustmsg,
+ time_t usttime, char bpprio)
+ {
+- LmMessageNode *y;
+- const char *p;
+ char *mbuf;
+ const char *ournick;
+ enum imrole mbrole = role_none;
+@@ -414,6 +412,7 @@
+ const char *mbjid = NULL, *mbnick = NULL;
+ const char *actorjid = NULL, *reason = NULL;
+ bool new_member = FALSE; // True if somebody else joins the room (not us)
++ bool our_presence = FALSE; // True if this presence is from us (i.e. bears code 110)
+ guint statuscode = 0;
+ guint nickchange = 0;
+ GSList *room_elt;
+@@ -444,30 +443,92 @@
+
+ if (!ournick) {
+ // It shouldn't happen, probably a server issue
+- mbuf = g_strdup_printf("Unexpected groupchat packet!");
+-
+- scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+- scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0);
+- g_free(mbuf);
++ scr_LogPrint(LPRINT_LOGNORM, "Unexpected groupchat packet!");
++ scr_WriteIncomingMessage(roomjid, "Unexpected groupchat packet!", 0, HBB_PREFIX_INFO, 0);
+ // Send back an unavailable packet
+ xmpp_setstatus(offline, roomjid, "", TRUE);
+ scr_draw_roster();
+ return;
+ }
+
+- // Get the status code
+- // 201: a room has been created
+- // 301: the user has been banned from the room
+- // 303: new room nickname
+- // 307: the user has been kicked from the room
+- // 321,322,332: the user has been removed from the room
+- y = lm_message_node_find_child(xmldata, "status");
+- if (y) {
+- p = lm_message_node_get_attribute(y, "code");
+- if (p)
+- statuscode = atoi(p);
++#define SETSTATUSCODE(VALUE) \
++{ \
++ if (G_UNLIKELY(statuscode)) \
++ scr_LogPrint(LPRINT_DEBUG, "handle_muc_presence: WARNING: " \
++ "replacing status code %u with %u.", statuscode, VALUE); \
++ statuscode = VALUE; \
++}
++
++ { // Get the status code
++ LmMessageNode *node;
++ for (node = xmldata -> children; node; node = node -> next) {
++ if (!g_strcmp0(node -> name, "status")) {
++ const char *codestr = lm_message_node_get_attribute(node, "code");
++ if (codestr) {
++ const char *mesg = NULL;
++ switch (atoi(codestr)) {
++ // initial
++ case 100:
++ mesg = "The room is not anonymous.";
++ break;
++ case 110: // It is our presence
++ our_presence = TRUE;
++ break;
++ // initial
++ case 170:
++ mesg = "The room is logged.";
++ break;
++ // initial
++ case 201: // Room created
++ SETSTATUSCODE(201);
++ break;
++ // initial
++ case 210: // Your nick change (on join)
++ // FIXME: print nick
++ mesg = "The room has changed your nick!";
++ buddy_setnickname(room_elt->data, rname);
++ ournick = rname;
++ break;
++ case 301: // User banned
++ SETSTATUSCODE(301);
++ break;
++ case 303: // Nick change
++ SETSTATUSCODE(303);
++ break;
++ case 307: // User kicked
++ SETSTATUSCODE(307);
++ break;
++ // XXX (next three)
++ case 321:
++ mesg = "User leaves room due to affilation change.";
++ break;
++ case 322:
++ mesg = "User leaves room, as room is only for members now.";
++ break;
++ case 332:
++ mesg = "User leaves room due to system shutdown.";
++ break;
++ default:
++ scr_LogPrint(LPRINT_DEBUG, "handle_muc_presence: Unknown MUC status code: %s.", codestr);
++ break;
++ }
++ if (mesg) {
++ scr_WriteIncomingMessage(roomjid, mesg, usttime,
++ HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
++ if (log_muc_conf)
++ hlog_write_message(roomjid, 0, -1, mesg);
++ }
++ }
++ }
++ }
+ }
+
++#undef SETSTATUSCODE
++
++ if (!our_presence)
++ if (ournick && !strcmp(ournick, rname))
++ our_presence = TRUE;
++
+ // Get the room's "print_status" settings
+ printstatus = buddy_getprintstatus(room_elt->data);
+ if (printstatus == status_default) {
+@@ -490,7 +551,7 @@
+ g_free(mbuf);
+ buddy_resource_setname(room_elt->data, rname, mbnick);
+ // Maybe it's _our_ nickname...
+- if (ournick && !strcmp(rname, ournick))
++ if (our_presence)
+ buddy_setnickname(room_elt->data, mbnick);
+ nickchange = TRUE;
+ }
+@@ -499,7 +560,6 @@
+ if (!mbnick && ust == offline) {
+ // Somebody is leaving
+ enum { leave=0, kick, ban } how = leave;
+- bool we_left = FALSE;
+
+ if (statuscode == 307)
+ how = kick;
+@@ -507,8 +567,7 @@
+ how = ban;
+
+ // If this is a leave, check if it is ourself
+- if (ournick && !strcmp(rname, ournick)) {
+- we_left = TRUE; // _We_ have left! (kicked, banned, etc.)
++ if (our_presence) {
+ buddy_setinsideroom(room_elt->data, FALSE);
+ buddy_setnickname(room_elt->data, NULL);
+ buddy_del_all_resources(room_elt->data);
+@@ -530,7 +589,7 @@
+ (how == ban ? "banned" : "kicked"),
+ roomjid);
+ }
+- if (we_left)
++ if (our_presence)
+ mbuf = g_strdup_printf("You have been %s", mbuf_end);
+ else
+ mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end);
+@@ -538,7 +597,7 @@
+ g_free(mbuf_end);
+ } else {
+ // Natural leave
+- if (we_left) {
++ if (our_presence) {
+ LmMessageNode *destroynode = lm_message_node_find_child(xmldata,
+ "destroy");
+ if (destroynode) {
+@@ -571,9 +630,9 @@
+
+ // Display the mbuf message if we're concerned
+ // or if the print_status isn't set to none.
+- if (we_left || printstatus != status_none) {
++ if (our_presence || printstatus != status_none) {
+ msgflags = HBB_PREFIX_INFO;
+- if (!we_left && settings_opt_get_int("muc_flag_joins") != 2)
++ if (!our_presence && settings_opt_get_int("muc_flag_joins") != 2)
+ msgflags |= HBB_PREFIX_NOFLAG;
+ scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0);
+ }
+@@ -581,26 +640,31 @@
+ if (log_muc_conf)
+ hlog_write_message(roomjid, 0, -1, mbuf);
+
+- if (we_left) {
++ if (our_presence) {
+ scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+ g_free(mbuf);
+ return;
+ }
+ g_free(mbuf);
+- } else if (buddy_getstatus(room_elt->data, rname) == offline &&
+- ust != offline) {
+- // Somebody is joining
+- new_member = muc_handle_join(room_elt, rname, roomjid, ournick,
+- printstatus, usttime, log_muc_conf);
+ } else {
+- // This is a simple member status change
++ enum imstatus old_ust = buddy_getstatus(room_elt->data, rname);
++ if (old_ust == offline && ust != offline) {
++ // Somebody is joining
++ new_member = muc_handle_join(room_elt, rname, roomjid, ournick,
++ printstatus, usttime, log_muc_conf);
++ } else {
++ // This is a simple member status change
+
+- if (printstatus == status_all && !nickchange) {
+- mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname,
+- imstatus2char[ust], ((ustmsg) ? ustmsg : ""));
+- scr_WriteIncomingMessage(roomjid, mbuf, usttime,
+- HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+- g_free(mbuf);
++ if (printstatus == status_all && !nickchange) {
++ const char *old_ustmsg = buddy_getstatusmsg(room_elt->data, rname);
++ if (old_ust != ust || g_strcmp0(old_ustmsg, ustmsg)) {
++ mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname,
++ imstatus2char[ust], ((ustmsg) ? ustmsg : ""));
++ scr_WriteIncomingMessage(roomjid, mbuf, usttime,
++ HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
++ g_free(mbuf);
++ }
++ }
+ }
+ }
+
+@@ -726,26 +790,87 @@
+
+
+ // Specific MUC message handling (for example invitation processing)
+-void got_muc_message(const char *from, LmMessageNode *x)
++void got_muc_message(const char *from, LmMessageNode *x, time_t timestamp)
+ {
+- LmMessageNode *invite = lm_message_node_get_child(x, "invite");
+- if (invite)
+- {
++ LmMessageNode *node;
++ // invitation
++ node = lm_message_node_get_child(x, "invite");
++ if (node) {
+ const char *invite_from;
+ const char *reason = NULL;
+ const char *password = NULL;
+
+- invite_from = lm_message_node_get_attribute(invite, "from");
+- reason = lm_message_node_get_child_value(invite, "reason");
+- password = lm_message_node_get_child_value(invite, "password");
++ invite_from = lm_message_node_get_attribute(node, "from");
++ reason = lm_message_node_get_child_value(node, "reason");
++ password = lm_message_node_get_child_value(node, "password");
+ if (invite_from)
+ got_invite(invite_from, from, reason, password, TRUE);
+ }
+- // TODO
+- // handle status code = 100 ( not anonymous )
+- // handle status code = 170 ( changement de config )
+- // 10.2.1 Notification of Configuration Changes
++
+ // declined invitation
++ node = lm_message_node_get_child(x, "decline");
++ if (node) {
++ const char *decline_from = lm_message_node_get_attribute(node, "from");
++ const char *reason = lm_message_node_get_child_value(node, "reason");
++ if (decline_from) {
++ if (reason)
++ scr_LogPrint(LPRINT_LOGNORM, "%s declines your invitation: %s.", from, reason);
++ else
++ scr_LogPrint(LPRINT_LOGNORM, "%s declines your invitation.", from);
++ }
++ }
++
++ // status codes
++ for (node = x -> children; node; node = node -> next) {
++ if (!g_strcmp0(node -> name, "status")) {
++ const char *codestr = lm_message_node_get_attribute(node, "code");
++ if (codestr) {
++ const char *mesg = NULL;
++ switch (atoi(codestr)) {
++ // initial
++ case 100:
++ mesg = "The room is not anonymous.";
++ break;
++ case 101:
++ mesg = "Your affilation has changed while absent.";
++ break;
++ case 102:
++ mesg = "The room shows unavailable members.";
++ break;
++ case 103:
++ mesg = "The room does not show unavailable members.";
++ break;
++ case 104:
++ mesg = "The room configuration has changed.";
++ break;
++ case 170:
++ mesg = "The room is logged.";
++ break;
++ case 171:
++ mesg = "The room is not logged.";
++ break;
++ case 172:
++ mesg = "The room is not anonymous.";
++ break;
++ case 173:
++ mesg = "The room is semi-anonymous.";
++ break;
++ case 174:
++ mesg = "The room is anonymous.";
++ break;
++ default:
++ scr_LogPrint(LPRINT_DEBUG, "got_muc_message: Unknown MUC status code: %s.", codestr);
++ break;
++ }
++ if (mesg) {
++ scr_WriteIncomingMessage(from, mesg, timestamp,
++ HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
++ if (settings_opt_get_int("log_muc_conf"))
++ hlog_write_message(from, 0, -1, mesg);
++ }
++ }
++ }
++ }
+ }
+
+ /* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */
+diff -r 9cdfb8ad447b mcabber/mcabber/xmpp_muc.h
+--- a/mcabber/mcabber/xmpp_muc.h Fri Apr 02 02:08:25 2010 +0300
++++ b/mcabber/mcabber/xmpp_muc.h Fri Apr 02 02:17:03 2010 +0300
+@@ -14,7 +14,8 @@
+ void roompresence(gpointer room, void *presencedata);
+ void got_invite(const char* from, const char *to, const char* reason,
+ const char* passwd, gboolean reply);
+-void got_muc_message(const char *from, LmMessageNode *x);
++void got_muc_message(const char *from, LmMessageNode *x,
++ time_t timestamp);
+ void handle_muc_presence(const char *from, LmMessageNode * xmldata,
+ const char *roomjid, const char *rname,
+ enum imstatus ust, const char *ustmsg,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/separate-extcmd Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,453 @@
+diff -r bb1396d6ba03 mcabber/mcabber/Makefile.am
+--- a/mcabber/mcabber/Makefile.am Fri Apr 02 00:06:58 2010 +0300
++++ b/mcabber/mcabber/Makefile.am Fri Apr 02 01:50:48 2010 +0300
+@@ -7,7 +7,7 @@
+ xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \
+ xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \
+ xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \
+- caps.c caps.h help.c help.h
++ caps.c caps.h help.c help.h extcmd.c extcmd.h
+
+ if OTR
+ mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h
+@@ -42,6 +42,7 @@
+ xmpp_iq.h xmpp_iqrequest.h \
+ xmpp_muc.h xmpp_s10n.h \
+ caps.h fifo.h help.h modules.h api.h \
++ extcmd.h \
+ $(top_srcdir)/include/config.h
+
+ if OTR
+diff -r bb1396d6ba03 mcabber/mcabber/extcmd.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/extcmd.c Fri Apr 02 01:50:48 2010 +0300
+@@ -0,0 +1,121 @@
++/*
++ * extcmd.c -- External event handler command
++ *
++ * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
++ *
++ * This program 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 2 of the License, or (at
++ * your option) any later version.
++ *
++ * This program 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 this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
++ * USA
++ */
++
++#include <stdlib.h>
++#include <string.h>
++#include <sys/types.h>
++#include <unistd.h>
++#include <glib.h>
++
++#include "screen.h"
++#include "roster.h"
++#include "settings.h"
++#include "utils.h"
++#include "utf8.h"
++
++static char *extcmd = NULL;
++
++// hk_ext_cmd_init()
++// Initialize external command variable.
++// Can be called with parameter NULL to reset and free memory
++void hk_ext_cmd_init(const char *command)
++{
++ if (extcmd) {
++ g_free(extcmd);
++ extcmd = NULL;
++ }
++ if (command)
++ extcmd = expand_filename(command);
++}
++
++// hk_ext_cmd()
++// Launch an external command (process) for the given event.
++// For now, data should be NULL.
++void hk_ext_cmd(const char *name, const char *arg_type, const char *arg_info, const char *data)
++{
++ pid_t pid;
++ char *arg_data = NULL;
++ char *datafname = NULL;
++
++ if (!arg_type || !arg_info) return;
++
++ if (*name && settings_opt_get_int("eventcmd_use_nickname"))
++ name = roster_getname(name);
++
++ if (data && settings_opt_get_int("event_log_files")) {
++ int fd;
++ const char *prefix;
++ char *prefix_xp = NULL;
++ char *data_locale;
++
++ data_locale = from_utf8(data);
++ prefix = settings_opt_get("event_log_dir");
++ if (prefix)
++ prefix = prefix_xp = expand_filename(prefix);
++ else
++ prefix = ut_get_tmpdir();
++ datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid());
++ g_free(prefix_xp);
++
++ // XXX Some old systems may require us to set umask first.
++ fd = mkstemp(datafname);
++ if (fd == -1) {
++ g_free(datafname);
++ datafname = NULL;
++ scr_LogPrint(LPRINT_LOGNORM,
++ "Unable to create temp file for external command.");
++ } else {
++ size_t data_locale_len = strlen(data_locale);
++ ssize_t a = write(fd, data_locale, data_locale_len);
++ ssize_t b = write(fd, "\n", 1);
++ if ((size_t)a != data_locale_len || b != 1) {
++ g_free(datafname);
++ datafname = NULL;
++ scr_LogPrint(LPRINT_LOGNORM,
++ "Unable to write to temp file for external command.");
++ }
++ close(fd);
++ arg_data = datafname;
++ }
++ g_free(data_locale);
++ }
++
++ if ((pid=fork()) == -1) {
++ scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command.");
++ g_free(datafname);
++ return;
++ }
++
++ if (pid == 0) { // child
++ // Close standard file descriptors
++ close(STDIN_FILENO);
++ close(STDOUT_FILENO);
++ close(STDERR_FILENO);
++ if (execl(extcmd, extcmd, arg_type, arg_info, name, arg_data,
++ (char *)NULL) == -1) {
++ // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command.");
++ exit(1);
++ }
++ }
++ g_free(datafname);
++}
++
++/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
+diff -r bb1396d6ba03 mcabber/mcabber/extcmd.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/extcmd.h Fri Apr 02 01:50:48 2010 +0300
+@@ -0,0 +1,15 @@
++#ifndef __MCABBER_EXTCMD_H__
++#define __MCABBER_EXTCMD_H__ 1
++
++#define EXT_CMD_TYPE_MESSAGE "MSG"
++#define EXT_CMD_TYPE_STATUS "STATUS"
++#define EXT_CMD_TYPE_UNREAD "UNREAD"
++
++#define EXT_CMD_INFO_RECEIVED "IN"
++#define EXT_CMD_INFO_SENT "OUT"
++#define EXT_CMD_INFO_GROUPCHAT "MUC"
++
++void hk_ext_cmd_init(const char *command);
++void hk_ext_cmd(const char *bjid, const char *type, const char *info, const char *data);
++
++#endif
+diff -r bb1396d6ba03 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c Fri Apr 02 00:06:58 2010 +0300
++++ b/mcabber/mcabber/hooks.c Fri Apr 02 01:50:48 2010 +0300
+@@ -24,6 +24,7 @@
+ #include <string.h>
+ #include <sys/types.h>
+ #include <unistd.h>
++#include <ctype.h>
+
+ #include "hooks.h"
+ #include "screen.h"
+@@ -35,6 +36,7 @@
+ #include "utf8.h"
+ #include "commands.h"
+ #include "main.h"
++#include "extcmd.h"
+
+ #ifdef MODULES_ENABLE
+ #include <glib.h>
+@@ -177,8 +179,6 @@
+ }
+ #endif
+
+-static char *extcmd;
+-
+ static const char *COMMAND_ME = "/me ";
+
+ void hk_message_in(const char *bjid, const char *resname,
+@@ -195,7 +195,6 @@
+ char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
+ GSList *roster_usr;
+ unsigned mucnicklen = 0;
+- const char *ename = NULL;
+ gboolean attention = FALSE, mucprivmsg = FALSE;
+
+ if (encrypted == ENCRYPTED_PGP)
+@@ -359,19 +358,6 @@
+ (!is_room || (is_groupchat && log_muc_conf && !timestamp)))
+ hlog_write_message(bjid, timestamp, 0, wmsg);
+
+- if (settings_opt_get_int("events_ignore_active_window") &&
+- current_buddy && scr_get_chatmode()) {
+- gpointer bud = BUDDATA(current_buddy);
+- if (bud) {
+- const char *cjid = buddy_getjid(bud);
+- if (cjid && !strcasecmp(cjid, bjid))
+- active_window = TRUE;
+- }
+- }
+-
+- if (settings_opt_get_int("eventcmd_use_nickname"))
+- ename = roster_getname(bjid);
+-
+ // Display the sender in the log window
+ if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) &&
+ settings_opt_get_int("log_display_sender")) {
+@@ -395,12 +381,24 @@
+ }
+ #endif
+
++ if (settings_opt_get_int("events_ignore_active_window") &&
++ current_buddy && scr_get_chatmode()) {
++ gpointer bud = BUDDATA(current_buddy);
++ if (bud) {
++ const char *cjid = buddy_getjid(bud);
++ if (cjid && !strcasecmp(cjid, bjid))
++ active_window = TRUE;
++ }
++ }
++
+ // 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
+ // - We do call hk_ext_cmd() for messages to the current window
+ if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat))
+- hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg);
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE,
++ is_groupchat ? EXT_CMD_INFO_GROUPCHAT : EXT_CMD_INFO_RECEIVED,
++ wmsg);
+
+ // Beep, if enabled:
+ // - if it's a private message
+@@ -478,7 +476,7 @@
+ #endif
+
+ // External command
+- hk_ext_cmd(bjid, 'M', 'S', NULL);
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_MESSAGE, EXT_CMD_INFO_SENT, NULL);
+
+ g_free(bmsg);
+ g_free(mmsg);
+@@ -493,10 +491,7 @@
+ char *bn;
+ char *logsmsg;
+ const char *rn = (resname ? resname : "");
+- const char *ename = NULL;
+-
+- if (settings_opt_get_int("eventcmd_use_nickname"))
+- ename = roster_getname(bjid);
++ char newstatus[2] = { '?', '\0' };
+
+ oldstat = roster_getstatus(bjid, resname);
+
+@@ -548,27 +543,28 @@
+ scr_draw_roster();
+ hlog_write_status(bjid, timestamp, status, status_msg);
+
++ newstatus[0] = imstatus2char[status];
++
+ #ifdef MODULES_ENABLE
+ {
+ char os[2] = " \0";
+- char ns[2] = " \0";
+ hk_arg_t args[] = {
+ { "jid", bjid },
+ { "resource", rn },
+ { "old_status", os },
+- { "new_status", ns },
++ { "new_status", newstatus },
+ { "message", status_msg ? status_msg : "" },
+ { NULL, NULL },
+ };
+ os[0] = imstatus2char[oldstat];
+- ns[0] = imstatus2char[status];
+
+ hk_run_handlers(HOOK_STATUS_CHANGE, args);
+ }
+ #endif
+
+ // External command
+- hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
++ newstatus[0] = toupper(newstatus[0]);
++ hk_ext_cmd(bjid, EXT_CMD_TYPE_STATUS, newstatus, status_msg);
+ }
+
+ void hk_mystatuschange(time_t timestamp, enum imstatus old_status,
+@@ -695,127 +691,8 @@
+ /* Call external command */
+ str_unread = g_strdup_printf("%u %u %u %u", unread_count, attention_count,
+ muc_unread, muc_attention);
+- hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), str_unread);
++ hk_ext_cmd("", EXT_CMD_TYPE_UNREAD, str_unread, NULL);
+ g_free(str_unread);
+ }
+
+-
+-/* External commands */
+-
+-// hk_ext_cmd_init()
+-// Initialize external command variable.
+-// Can be called with parameter NULL to reset and free memory.
+-void hk_ext_cmd_init(const char *command)
+-{
+- if (extcmd) {
+- g_free(extcmd);
+- extcmd = NULL;
+- }
+- if (command)
+- extcmd = expand_filename(command);
+-}
+-
+-// hk_ext_cmd()
+-// Launch an external command (process) for the given event.
+-// For now, data should be NULL.
+-void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data)
+-{
+- pid_t pid;
+- const char *arg_type = NULL;
+- const char *arg_info = NULL;
+- const char *arg_data = NULL;
+- char status_str[2];
+- char *datafname = NULL;
+-
+- if (!extcmd) return;
+-
+- // Prepare arg_* (external command parameters)
+- switch (type) {
+- case 'M': /* Normal message */
+- arg_type = "MSG";
+- if (info == 'R')
+- arg_info = "IN";
+- else if (info == 'S')
+- arg_info = "OUT";
+- break;
+- case 'G': /* Groupchat message */
+- arg_type = "MSG";
+- arg_info = "MUC";
+- break;
+- case 'S': /* Status change */
+- arg_type = "STATUS";
+- if (strchr(imstatus2char, tolower(info))) {
+- status_str[0] = toupper(info);
+- status_str[1] = 0;
+- arg_info = status_str;
+- }
+- break;
+- case 'U': /* Unread buffer count */
+- arg_type = "UNREAD";
+- arg_info = data;
+- break;
+- default:
+- return;
+- }
+-
+- if (!arg_type || !arg_info) return;
+-
+- if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) {
+- int fd;
+- const char *prefix;
+- char *prefix_xp = NULL;
+- char *data_locale;
+-
+- data_locale = from_utf8(data);
+- prefix = settings_opt_get("event_log_dir");
+- if (prefix)
+- prefix = prefix_xp = expand_filename(prefix);
+- else
+- prefix = ut_get_tmpdir();
+- datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid());
+- g_free(prefix_xp);
+-
+- // XXX Some old systems may require us to set umask first.
+- fd = mkstemp(datafname);
+- if (fd == -1) {
+- g_free(datafname);
+- datafname = NULL;
+- scr_LogPrint(LPRINT_LOGNORM,
+- "Unable to create temp file for external command.");
+- } else {
+- size_t data_locale_len = strlen(data_locale);
+- ssize_t a = write(fd, data_locale, data_locale_len);
+- ssize_t b = write(fd, "\n", 1);
+- if ((size_t)a != data_locale_len || b != 1) {
+- g_free(datafname);
+- datafname = NULL;
+- scr_LogPrint(LPRINT_LOGNORM,
+- "Unable to write to temp file for external command.");
+- }
+- close(fd);
+- arg_data = datafname;
+- }
+- g_free(data_locale);
+- }
+-
+- if ((pid=fork()) == -1) {
+- scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command.");
+- g_free(datafname);
+- return;
+- }
+-
+- if (pid == 0) { // child
+- // Close standard file descriptors
+- close(STDIN_FILENO);
+- close(STDOUT_FILENO);
+- close(STDERR_FILENO);
+- if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data,
+- (char *)NULL) == -1) {
+- // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command.");
+- exit(1);
+- }
+- }
+- g_free(datafname);
+-}
+-
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
+diff -r bb1396d6ba03 mcabber/mcabber/hooks.h
+--- a/mcabber/mcabber/hooks.h Fri Apr 02 00:06:58 2010 +0300
++++ b/mcabber/mcabber/hooks.h Fri Apr 02 01:50:48 2010 +0300
+@@ -62,9 +62,6 @@
+ void hk_unread_list_change(guint unread_count, guint attention_count,
+ guint muc_unread, guint muc_attention);
+
+-void hk_ext_cmd_init(const char *command);
+-void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data);
+-
+ #endif /* __MCABBER_HOOKS_H__ */
+
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
+diff -r bb1396d6ba03 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c Fri Apr 02 00:06:58 2010 +0300
++++ b/mcabber/mcabber/main.c Fri Apr 02 01:50:48 2010 +0300
+@@ -45,6 +45,7 @@
+ #include "xmpp.h"
+ #include "help.h"
+ #include "events.h"
++#include "extcmd.h"
+
+ #ifndef MODULES_ENABLE
+ # include "fifo.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/series Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,10 @@
+switch-to-experimental
+fifo-use-guard
+modularize-fifo
+separate-extcmd
+modularize-extcmd
+guard-xmpp-password
+guardize-colors
+roster-state-colors
+direct-invite
+muc-multiple-statuses
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/switch-to-experimental Fri Apr 02 02:29:43 2010 +0300
@@ -0,0 +1,40 @@
+diff -r 2e133fc0e3d9 mcabber/ChangeLog.api
+--- a/mcabber/ChangeLog.api Sun Mar 28 23:40:10 2010 +0200
++++ b/mcabber/ChangeLog.api Thu Apr 01 23:28:43 2010 +0300
+@@ -1,3 +1,12 @@
++
++experimental (12)
++
++ * Change branch to "experimental".
++ * Api as defined by dev:12
++ * MQ Patch: switch-to-experimental
++
++ -- Myhailo Danylenko, 2010-04-01
++
+ dev (12)
+
+ * Add scr_getlogwinheight()
+diff -r 2e133fc0e3d9 mcabber/configure.ac
+--- a/mcabber/configure.ac Sun Mar 28 23:40:10 2010 +0200
++++ b/mcabber/configure.ac Thu Apr 01 23:28:43 2010 +0300
+@@ -264,7 +264,7 @@
+ AM_CONDITIONAL([INSTALL_HEADERS], [test x$enable_modules != xno])
+
+ # Prepare some config.h variables
+-AC_DEFINE([MCABBER_BRANCH], "dev", [Mcabber branch])
++AC_DEFINE([MCABBER_BRANCH], "experimental", [Mcabber branch])
+ AC_DEFINE([MCABBER_VERSION], "AC_PACKAGE_VERSION", [Mcabber version string])
+
+ # We need _GNU_SOURCE for strptime() and strcasestr()
+diff -r 2e133fc0e3d9 mcabber/mcabber/api.h
+--- a/mcabber/mcabber/api.h Sun Mar 28 23:40:10 2010 +0200
++++ b/mcabber/mcabber/api.h Thu Apr 01 23:28:43 2010 +0300
+@@ -4,7 +4,7 @@
+ #include <mcabber/config.h> // For MCABBER_BRANCH
+
+ #define MCABBER_API_VERSION 12
+-#define MCABBER_API_MIN 11
++#define MCABBER_API_MIN 12
+
+ extern const gchar *mcabber_branch;
+ extern const guint mcabber_api_version;