Initial experimental branch commit
authorMyhailo Danylenko <isbear@ukrpost.net>
Fri, 02 Apr 2010 02:29:43 +0300
changeset 0 633272cbb544
child 1 024f3a2dda3e
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
.hgignore
direct-invite
fifo-use-guard
guard-xmpp-password
guardize-colors
modularize-extcmd
modularize-fifo
muc-multiple-statuses
separate-extcmd
series
switch-to-experimental
--- /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;