Add .diff to patch names
authorMyhailo Danylenko <isbear@ukrpost.net>
Sat, 28 Jul 2012 19:42:13 +0300
changeset 36 b8854e2fe147
parent 35 c80eb5663234
child 37 fbbcfc54d4d3
Add .diff to patch names
fix-module-messages
fix-module-messages.diff
fix-receipts
fix-receipts.diff
fix-typo
fix-typo.diff
guard-xmpp-password
guard-xmpp-password.diff
modularize-extcmd
modularize-extcmd.diff
roster-state-colors
roster-state-colors.diff
separate-extcmd
separate-extcmd.diff
series
switch-to-experimental
switch-to-experimental.diff
templates
templates.diff
update-pc
update-pc.diff
use-gslice
use-gslice.diff
warning-fixes
warning-fixes.diff
--- a/fix-module-messages	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-# HG changeset patch
-# Parent 8e120e293a253ab7c0cbfb85ac74ab12428cf0f7
-
-diff -r 8e120e293a25 mcabber/mcabber/commands.c
---- a/mcabber/mcabber/commands.c	Sat Jul 28 18:45:45 2012 +0300
-+++ b/mcabber/mcabber/commands.c	Sat Jul 28 19:00:28 2012 +0300
-@@ -3251,16 +3251,18 @@
-         ++name;
-     }
- 
--    if (!strcmp(args[0], "load"))
-+    if (!strcmp(args[0], "load")) {
-       error = module_load(name, TRUE, force);
--    else if (!strcmp(args[0], "unload"))
-+      if (error)
-+        scr_log_print(LPRINT_LOGNORM, "Module '%s' loading error: %s", name, error);
-+    } else if (!strcmp(args[0], "unload")) {
-       error = module_unload(name, TRUE, force);
--    else if (!strcmp(args[0], "info"))
-+      if (error)
-+        scr_log_print(LPRINT_LOGNORM, "Module '%s' unloading error: %s", name, error);
-+    } else if (!strcmp(args[0], "info"))
-       module_info_print(name);
-     else
--      error = "Unknown subcommand";
--    if (error)
--      scr_LogPrint(LPRINT_LOGNORM, "Error: %s.",  error);
-+      scr_log_print(LPRINT_LOGNORM, "Error: module: Unknown subcommand.");
-   }
-   free_arg_lst(args);
- #else
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fix-module-messages.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,31 @@
+# HG changeset patch
+# Parent 8e120e293a253ab7c0cbfb85ac74ab12428cf0f7
+
+diff -r 8e120e293a25 mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c	Sat Jul 28 18:45:45 2012 +0300
++++ b/mcabber/mcabber/commands.c	Sat Jul 28 19:00:28 2012 +0300
+@@ -3251,16 +3251,18 @@
+         ++name;
+     }
+ 
+-    if (!strcmp(args[0], "load"))
++    if (!strcmp(args[0], "load")) {
+       error = module_load(name, TRUE, force);
+-    else if (!strcmp(args[0], "unload"))
++      if (error)
++        scr_log_print(LPRINT_LOGNORM, "Module '%s' loading error: %s", name, error);
++    } else if (!strcmp(args[0], "unload")) {
+       error = module_unload(name, TRUE, force);
+-    else if (!strcmp(args[0], "info"))
++      if (error)
++        scr_log_print(LPRINT_LOGNORM, "Module '%s' unloading error: %s", name, error);
++    } else if (!strcmp(args[0], "info"))
+       module_info_print(name);
+     else
+-      error = "Unknown subcommand";
+-    if (error)
+-      scr_LogPrint(LPRINT_LOGNORM, "Error: %s.",  error);
++      scr_log_print(LPRINT_LOGNORM, "Error: module: Unknown subcommand.");
+   }
+   free_arg_lst(args);
+ #else
--- a/fix-receipts	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-# HG changeset patch
-# Parent 99db5d4ed047b3dbe54727c1df311531f611f31f
-Fix receipts handling according to recent XEP updates
-
-  * add lm_get_uid() @xmpp_helper.h
-  * identify receipts by id attribute of 'received' subelement
-    instead of stanza id
-
-diff -r 99db5d4ed047 mcabber/mcabber/hbuf.c
---- a/mcabber/mcabber/hbuf.c	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/hbuf.c	Mon Apr 30 23:10:28 2012 +0300
-@@ -525,7 +525,7 @@
- //  hbuf_remove_receipt(hbuf, xep184)
- // Remove the Receipt Flag for the message with the given xep184 id
- // Returns TRUE if it was found and removed, otherwise FALSE
--gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
-+gboolean hbuf_remove_receipt(GList *hbuf, gconstpointer xep184)
- {
-   hbuf_block *blk;
- 
-@@ -533,7 +533,8 @@
- 
-   for ( ; hbuf; hbuf = g_list_previous(hbuf)) {
-     blk = (hbuf_block*)(hbuf->data);
--    if (blk->prefix.xep184 == xep184) {
-+    if (!g_strcmp0 (blk->prefix.xep184, xep184)) {
-+      g_free (blk->prefix.xep184);
-       blk->prefix.xep184 = NULL;
-       blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
-       return TRUE;
-diff -r 99db5d4ed047 mcabber/mcabber/hbuf.h
---- a/mcabber/mcabber/hbuf.h	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/hbuf.h	Mon Apr 30 23:10:28 2012 +0300
-@@ -50,7 +50,7 @@
- GList *hbuf_jump_date(GList *hbuf, time_t t);
- GList *hbuf_jump_percent(GList *hbuf, int pc);
- GList *hbuf_jump_readmark(GList *hbuf);
--gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184);
-+gboolean hbuf_remove_receipt(GList *hbuf, gconstpointer xep184);
- void hbuf_set_readmark(GList *hbuf, gboolean action);
- void hbuf_remove_trailing_readmark(GList *hbuf);
- 
-diff -r 99db5d4ed047 mcabber/mcabber/screen.c
---- a/mcabber/mcabber/screen.c	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/screen.c	Mon Apr 30 23:10:28 2012 +0300
-@@ -2239,7 +2239,7 @@
-     scr_show_window(jidto, FALSE);
- }
- 
--void scr_remove_receipt_flag(const char *bjid, gpointer xep184)
-+void scr_remove_receipt_flag(const char *bjid, gconstpointer xep184)
- {
-   winbuf *win_entry = scr_search_window(bjid, FALSE);
-   if (win_entry) {
-diff -r 99db5d4ed047 mcabber/mcabber/screen.h
---- a/mcabber/mcabber/screen.h	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/screen.h	Mon Apr 30 23:10:28 2012 +0300
-@@ -108,7 +108,7 @@
- void scr_update_main_status(int forceupdate);
- void scr_update_chat_status(int forceupdate);
- void scr_roster_visibility(int status);
--void scr_remove_receipt_flag(const char *jidto, gpointer xep184);
-+void scr_remove_receipt_flag(const char *jidto, gconstpointer xep184);
- void scr_show_buddy_window(void);
- int  scr_buddy_buffer_exists(const char *jid);
- void scr_update_buddy_window(void);
-diff -r 99db5d4ed047 mcabber/mcabber/xmpp.c
---- a/mcabber/mcabber/xmpp.c	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/xmpp.c	Mon Apr 30 23:10:28 2012 +0300
-@@ -295,15 +295,6 @@
-   g_slist_free(resources);
- }
- 
--static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c,
--                                 LmMessage *m, gpointer user_data)
--{
--  char *from = jidtodisp(lm_message_get_from(m));
--  scr_remove_receipt_flag(from, h);
--  g_free(from);
--  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
--}
--
- //  xmpp_send_msg(jid, text, type, subject,
- //                otrinject, *encrypted, type_overwrite)
- // When encrypted is not NULL, the function set *encrypted to 1 if the
-@@ -432,7 +423,8 @@
-     lm_message_node_set_attribute
-             (lm_message_node_add_child(x->node, "request", NULL),
-              "xmlns", NS_RECEIPTS);
--    *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL);
-+    *xep184 = lm_get_uid ();
-+    lm_message_node_set_attribute (x->node, "id", (const gchar *)*xep184);
-   }
-   g_free(barejid);
- 
-@@ -499,11 +491,7 @@
-   if (mystatus != invisible)
- #endif
-     update_last_use();
--  if (xep184 && *xep184) {
--    lm_connection_send_with_reply(lconnection, x, *xep184, NULL);
--    lm_message_handler_unref(*xep184);
--  } else
--    lm_connection_send(lconnection, x, NULL);
-+  lm_connection_send(lconnection, x, NULL);
-   lm_message_unref(x);
- }
- 
-@@ -1305,6 +1293,16 @@
-     lm_message_unref(rcvd);
-   }
- 
-+  { // xep184 receipt confirmation
-+    LmMessageNode *received = lm_message_node_get_child (m->node, "received");
-+    if (received && !g_strcmp0 (lm_message_node_get_attribute (received, "xmlns"), NS_RECEIPTS)) {
-+      char       *jid = jidtodisp(from);
-+      const char *id  = lm_message_node_get_attribute (received, "id");
-+      scr_remove_receipt_flag(jid, id);
-+      g_free(jid);
-+    }
-+  }
-+
-   if (from) {
-     x = lm_message_node_find_xmlns(m->node, NS_MUC_USER);
-     if (x && !strcmp(x->name, "x"))
-diff -r 99db5d4ed047 mcabber/mcabber/xmpp_helper.c
---- a/mcabber/mcabber/xmpp_helper.c	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/xmpp_helper.c	Mon Apr 30 23:10:28 2012 +0300
-@@ -72,7 +72,6 @@
-   {0, NULL, NULL, NULL, NULL}
- };
- 
--
- #ifdef MODULES_ENABLE
- static GSList *xmpp_additional_features = NULL;
- static char *ver, *ver_notavail;
-@@ -104,6 +103,13 @@
- }
- #endif
- 
-+// The caller must g_free this after use
-+gchar *lm_get_uid ()
-+{
-+  static guint xmpp_uid = 0;
-+  return g_strdup_printf ("mc%u", ++xmpp_uid);
-+}
-+
- const gchar* lm_message_node_get_child_value(LmMessageNode *node,
-                                              const gchar *child)
- {
-diff -r 99db5d4ed047 mcabber/mcabber/xmpp_helper.h
---- a/mcabber/mcabber/xmpp_helper.h	Thu Mar 01 20:18:00 2012 +0100
-+++ b/mcabber/mcabber/xmpp_helper.h	Mon Apr 30 23:10:28 2012 +0300
-@@ -29,6 +29,8 @@
- void xmpp_del_feature (const char *xmlns);
- #endif
- 
-+gchar *lm_get_uid (void);
-+
- LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
- LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
-                                           const char *xmlns);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fix-receipts.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,162 @@
+# HG changeset patch
+# Parent 99db5d4ed047b3dbe54727c1df311531f611f31f
+Fix receipts handling according to recent XEP updates
+
+  * add lm_get_uid() @xmpp_helper.h
+  * identify receipts by id attribute of 'received' subelement
+    instead of stanza id
+
+diff -r 99db5d4ed047 mcabber/mcabber/hbuf.c
+--- a/mcabber/mcabber/hbuf.c	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/hbuf.c	Mon Apr 30 23:10:28 2012 +0300
+@@ -525,7 +525,7 @@
+ //  hbuf_remove_receipt(hbuf, xep184)
+ // Remove the Receipt Flag for the message with the given xep184 id
+ // Returns TRUE if it was found and removed, otherwise FALSE
+-gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
++gboolean hbuf_remove_receipt(GList *hbuf, gconstpointer xep184)
+ {
+   hbuf_block *blk;
+ 
+@@ -533,7 +533,8 @@
+ 
+   for ( ; hbuf; hbuf = g_list_previous(hbuf)) {
+     blk = (hbuf_block*)(hbuf->data);
+-    if (blk->prefix.xep184 == xep184) {
++    if (!g_strcmp0 (blk->prefix.xep184, xep184)) {
++      g_free (blk->prefix.xep184);
+       blk->prefix.xep184 = NULL;
+       blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
+       return TRUE;
+diff -r 99db5d4ed047 mcabber/mcabber/hbuf.h
+--- a/mcabber/mcabber/hbuf.h	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/hbuf.h	Mon Apr 30 23:10:28 2012 +0300
+@@ -50,7 +50,7 @@
+ GList *hbuf_jump_date(GList *hbuf, time_t t);
+ GList *hbuf_jump_percent(GList *hbuf, int pc);
+ GList *hbuf_jump_readmark(GList *hbuf);
+-gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184);
++gboolean hbuf_remove_receipt(GList *hbuf, gconstpointer xep184);
+ void hbuf_set_readmark(GList *hbuf, gboolean action);
+ void hbuf_remove_trailing_readmark(GList *hbuf);
+ 
+diff -r 99db5d4ed047 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/screen.c	Mon Apr 30 23:10:28 2012 +0300
+@@ -2239,7 +2239,7 @@
+     scr_show_window(jidto, FALSE);
+ }
+ 
+-void scr_remove_receipt_flag(const char *bjid, gpointer xep184)
++void scr_remove_receipt_flag(const char *bjid, gconstpointer xep184)
+ {
+   winbuf *win_entry = scr_search_window(bjid, FALSE);
+   if (win_entry) {
+diff -r 99db5d4ed047 mcabber/mcabber/screen.h
+--- a/mcabber/mcabber/screen.h	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/screen.h	Mon Apr 30 23:10:28 2012 +0300
+@@ -108,7 +108,7 @@
+ void scr_update_main_status(int forceupdate);
+ void scr_update_chat_status(int forceupdate);
+ void scr_roster_visibility(int status);
+-void scr_remove_receipt_flag(const char *jidto, gpointer xep184);
++void scr_remove_receipt_flag(const char *jidto, gconstpointer xep184);
+ void scr_show_buddy_window(void);
+ int  scr_buddy_buffer_exists(const char *jid);
+ void scr_update_buddy_window(void);
+diff -r 99db5d4ed047 mcabber/mcabber/xmpp.c
+--- a/mcabber/mcabber/xmpp.c	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/xmpp.c	Mon Apr 30 23:10:28 2012 +0300
+@@ -295,15 +295,6 @@
+   g_slist_free(resources);
+ }
+ 
+-static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c,
+-                                 LmMessage *m, gpointer user_data)
+-{
+-  char *from = jidtodisp(lm_message_get_from(m));
+-  scr_remove_receipt_flag(from, h);
+-  g_free(from);
+-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+-}
+-
+ //  xmpp_send_msg(jid, text, type, subject,
+ //                otrinject, *encrypted, type_overwrite)
+ // When encrypted is not NULL, the function set *encrypted to 1 if the
+@@ -432,7 +423,8 @@
+     lm_message_node_set_attribute
+             (lm_message_node_add_child(x->node, "request", NULL),
+              "xmlns", NS_RECEIPTS);
+-    *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL);
++    *xep184 = lm_get_uid ();
++    lm_message_node_set_attribute (x->node, "id", (const gchar *)*xep184);
+   }
+   g_free(barejid);
+ 
+@@ -499,11 +491,7 @@
+   if (mystatus != invisible)
+ #endif
+     update_last_use();
+-  if (xep184 && *xep184) {
+-    lm_connection_send_with_reply(lconnection, x, *xep184, NULL);
+-    lm_message_handler_unref(*xep184);
+-  } else
+-    lm_connection_send(lconnection, x, NULL);
++  lm_connection_send(lconnection, x, NULL);
+   lm_message_unref(x);
+ }
+ 
+@@ -1305,6 +1293,16 @@
+     lm_message_unref(rcvd);
+   }
+ 
++  { // xep184 receipt confirmation
++    LmMessageNode *received = lm_message_node_get_child (m->node, "received");
++    if (received && !g_strcmp0 (lm_message_node_get_attribute (received, "xmlns"), NS_RECEIPTS)) {
++      char       *jid = jidtodisp(from);
++      const char *id  = lm_message_node_get_attribute (received, "id");
++      scr_remove_receipt_flag(jid, id);
++      g_free(jid);
++    }
++  }
++
+   if (from) {
+     x = lm_message_node_find_xmlns(m->node, NS_MUC_USER);
+     if (x && !strcmp(x->name, "x"))
+diff -r 99db5d4ed047 mcabber/mcabber/xmpp_helper.c
+--- a/mcabber/mcabber/xmpp_helper.c	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/xmpp_helper.c	Mon Apr 30 23:10:28 2012 +0300
+@@ -72,7 +72,6 @@
+   {0, NULL, NULL, NULL, NULL}
+ };
+ 
+-
+ #ifdef MODULES_ENABLE
+ static GSList *xmpp_additional_features = NULL;
+ static char *ver, *ver_notavail;
+@@ -104,6 +103,13 @@
+ }
+ #endif
+ 
++// The caller must g_free this after use
++gchar *lm_get_uid ()
++{
++  static guint xmpp_uid = 0;
++  return g_strdup_printf ("mc%u", ++xmpp_uid);
++}
++
+ const gchar* lm_message_node_get_child_value(LmMessageNode *node,
+                                              const gchar *child)
+ {
+diff -r 99db5d4ed047 mcabber/mcabber/xmpp_helper.h
+--- a/mcabber/mcabber/xmpp_helper.h	Thu Mar 01 20:18:00 2012 +0100
++++ b/mcabber/mcabber/xmpp_helper.h	Mon Apr 30 23:10:28 2012 +0300
+@@ -29,6 +29,8 @@
+ void xmpp_del_feature (const char *xmlns);
+ #endif
+ 
++gchar *lm_get_uid (void);
++
+ LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
+ LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
+                                           const char *xmlns);
--- a/fix-typo	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-# HG changeset patch
-# Parent e9b6e168a45e12200f486929e5786ffd64ac143e
-s/Copyrigth/Copyright/ and so on
-
-diff -r e9b6e168a45e mcabber/mcabber/events.c
---- a/mcabber/mcabber/events.c	Wed Jul 18 00:24:13 2012 +0200
-+++ b/mcabber/mcabber/events.c	Wed Jul 18 08:31:55 2012 +0300
-@@ -2,7 +2,7 @@
-  * events.c     -- Events fonctions
-  *
-  * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
-- * Copyrigth (C) 2010      Myhailo Danylenko <isbear@ukrposte.net>
-+ * Copyright (C) 2010      Myhailo Danylenko <isbear@ukrpost.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
-diff -r e9b6e168a45e mcabber/mcabber/fifo.c
---- a/mcabber/mcabber/fifo.c	Wed Jul 18 00:24:13 2012 +0200
-+++ b/mcabber/mcabber/fifo.c	Wed Jul 18 08:31:55 2012 +0300
-@@ -2,7 +2,7 @@
-  * fifo.c       -- Read commands from a named pipe
-  *
-  * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
-- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
-+ * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.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
-diff -r e9b6e168a45e mcabber/mcabber/help.c
---- a/mcabber/mcabber/help.c	Wed Jul 18 00:24:13 2012 +0200
-+++ b/mcabber/mcabber/help.c	Wed Jul 18 08:31:55 2012 +0300
-@@ -2,7 +2,7 @@
-  * help.c       -- Help command
-  *
-  * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
-- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
-+ * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.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
-diff -r e9b6e168a45e mcabber/mcabber/xmpp_muc.c
---- a/mcabber/mcabber/xmpp_muc.c	Wed Jul 18 00:24:13 2012 +0200
-+++ b/mcabber/mcabber/xmpp_muc.c	Wed Jul 18 08:31:55 2012 +0300
-@@ -3,7 +3,7 @@
-  *
-  * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de>
-  * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
-- * Copyrigth (C) 2010      Myhailo Danylenko <isbear@ukrposte.net>
-+ * Copyright (C) 2010      Myhailo Danylenko <isbear@ukrpost.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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fix-typo.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,52 @@
+# HG changeset patch
+# Parent e9b6e168a45e12200f486929e5786ffd64ac143e
+s/Copyrigth/Copyright/ and so on
+
+diff -r e9b6e168a45e mcabber/mcabber/events.c
+--- a/mcabber/mcabber/events.c	Wed Jul 18 00:24:13 2012 +0200
++++ b/mcabber/mcabber/events.c	Wed Jul 18 08:31:55 2012 +0300
+@@ -2,7 +2,7 @@
+  * events.c     -- Events fonctions
+  *
+  * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
+- * Copyrigth (C) 2010      Myhailo Danylenko <isbear@ukrposte.net>
++ * Copyright (C) 2010      Myhailo Danylenko <isbear@ukrpost.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
+diff -r e9b6e168a45e mcabber/mcabber/fifo.c
+--- a/mcabber/mcabber/fifo.c	Wed Jul 18 00:24:13 2012 +0200
++++ b/mcabber/mcabber/fifo.c	Wed Jul 18 08:31:55 2012 +0300
+@@ -2,7 +2,7 @@
+  * fifo.c       -- Read commands from a named pipe
+  *
+  * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
+- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
++ * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.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
+diff -r e9b6e168a45e mcabber/mcabber/help.c
+--- a/mcabber/mcabber/help.c	Wed Jul 18 00:24:13 2012 +0200
++++ b/mcabber/mcabber/help.c	Wed Jul 18 08:31:55 2012 +0300
+@@ -2,7 +2,7 @@
+  * help.c       -- Help command
+  *
+  * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
+- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
++ * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.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
+diff -r e9b6e168a45e mcabber/mcabber/xmpp_muc.c
+--- a/mcabber/mcabber/xmpp_muc.c	Wed Jul 18 00:24:13 2012 +0200
++++ b/mcabber/mcabber/xmpp_muc.c	Wed Jul 18 08:31:55 2012 +0300
+@@ -3,7 +3,7 @@
+  *
+  * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de>
+  * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
+- * Copyrigth (C) 2010      Myhailo Danylenko <isbear@ukrposte.net>
++ * Copyright (C) 2010      Myhailo Danylenko <isbear@ukrpost.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
--- a/guard-xmpp-password	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-Guard XMPP password with option guard
-
-  * instead of filtering access to stored in public place
-    restricted data, better store restricted data somewhere else
-  * add xmpp_init() and xmpp_have_password (non-api)
-
-diff -r c290165bc534 mcabber/mcabber/commands.c
---- a/mcabber/mcabber/commands.c	Mon Apr 30 23:37:25 2012 +0300
-+++ b/mcabber/mcabber/commands.c	Mon Apr 30 23:38:20 2012 +0300
-@@ -2272,10 +2272,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 c290165bc534 mcabber/mcabber/main.c
---- a/mcabber/mcabber/main.c	Mon Apr 30 23:37:25 2012 +0300
-+++ b/mcabber/mcabber/main.c	Mon Apr 30 23:38:20 2012 +0300
-@@ -381,6 +381,7 @@
-   scr_init_locale_charset();
-   ut_init_debug();
-   help_init();
-+  xmpp_init();
- 
-   /* Parsing config file... */
-   ret = cfg_read_file(configFile, TRUE);
-@@ -417,8 +418,9 @@
- 
-   /* 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) {
-     char *pwd = ask_password("your Jabber password");
-+    /* Will be intercepted by guard */
-     settings_set(SETTINGS_TYPE_OPTION, "password", pwd);
-     g_free(pwd);
-   }
-diff -r c290165bc534 mcabber/mcabber/xmpp.c
---- a/mcabber/mcabber/xmpp.c	Mon Apr 30 23:37:25 2012 +0300
-+++ b/mcabber/mcabber/xmpp.c	Mon Apr 30 23:38:20 2012 +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);
-@@ -1780,13 +1784,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;
-@@ -1803,7 +1831,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");
-@@ -1812,7 +1839,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 c290165bc534 mcabber/mcabber/xmpp.h
---- a/mcabber/mcabber/xmpp.h	Mon Apr 30 23:37:25 2012 +0300
-+++ b/mcabber/mcabber/xmpp.h	Mon Apr 30 23:38:20 2012 +0300
-@@ -33,7 +33,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/guard-xmpp-password.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,154 @@
+Guard XMPP password with option guard
+
+  * instead of filtering access to stored in public place
+    restricted data, better store restricted data somewhere else
+  * add xmpp_init() and xmpp_have_password (non-api)
+
+diff -r c290165bc534 mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c	Mon Apr 30 23:37:25 2012 +0300
++++ b/mcabber/mcabber/commands.c	Mon Apr 30 23:38:20 2012 +0300
+@@ -2272,10 +2272,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 c290165bc534 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c	Mon Apr 30 23:37:25 2012 +0300
++++ b/mcabber/mcabber/main.c	Mon Apr 30 23:38:20 2012 +0300
+@@ -381,6 +381,7 @@
+   scr_init_locale_charset();
+   ut_init_debug();
+   help_init();
++  xmpp_init();
+ 
+   /* Parsing config file... */
+   ret = cfg_read_file(configFile, TRUE);
+@@ -417,8 +418,9 @@
+ 
+   /* 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) {
+     char *pwd = ask_password("your Jabber password");
++    /* Will be intercepted by guard */
+     settings_set(SETTINGS_TYPE_OPTION, "password", pwd);
+     g_free(pwd);
+   }
+diff -r c290165bc534 mcabber/mcabber/xmpp.c
+--- a/mcabber/mcabber/xmpp.c	Mon Apr 30 23:37:25 2012 +0300
++++ b/mcabber/mcabber/xmpp.c	Mon Apr 30 23:38:20 2012 +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);
+@@ -1780,13 +1784,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;
+@@ -1803,7 +1831,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");
+@@ -1812,7 +1839,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 c290165bc534 mcabber/mcabber/xmpp.h
+--- a/mcabber/mcabber/xmpp.h	Mon Apr 30 23:37:25 2012 +0300
++++ b/mcabber/mcabber/xmpp.h	Mon Apr 30 23:38:20 2012 +0300
+@@ -33,7 +33,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);
--- a/modularize-extcmd	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-Convert extcmd into a module
-
-diff -r 27d19b6bc194 mcabber/configure.ac
---- a/mcabber/configure.ac	Mon Apr 30 23:36:55 2012 +0300
-+++ b/mcabber/configure.ac	Mon Apr 30 23:37:25 2012 +0300
-@@ -283,6 +283,7 @@
-                  modules/beep/Makefile
-                  modules/xttitle/Makefile
-                  modules/fifo/Makefile
-+                 modules/eventcmd/Makefile
-                  modules/urlregex/Makefile
-                  doc/Makefile
-                  doc/guide/Makefile
-diff -r 27d19b6bc194 mcabber/mcabber/Makefile.am
---- a/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:55 2012 +0300
-+++ b/mcabber/mcabber/Makefile.am	Mon Apr 30 23:37:25 2012 +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 27d19b6bc194 mcabber/mcabber/hooks.c
---- a/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:55 2012 +0300
-+++ b/mcabber/mcabber/hooks.c	Mon Apr 30 23:37:25 2012 +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;
-@@ -397,6 +402,7 @@
-   }
- #endif
- 
-+#ifndef MODULES_ENABLE
-   if (settings_opt_get_int("events_ignore_active_window") &&
-       current_buddy && scr_get_chatmode()) {
-     gpointer bud = BUDDATA(current_buddy);
-@@ -415,6 +421,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
-@@ -491,8 +498,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);
-@@ -578,9 +587,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,
-@@ -704,11 +715,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
- }
- 
- //  hk_presence_subscription_request(jid, message)
-diff -r 27d19b6bc194 mcabber/mcabber/main.c
---- a/mcabber/mcabber/main.c	Mon Apr 30 23:36:55 2012 +0300
-+++ b/mcabber/mcabber/main.c	Mon Apr 30 23:37:25 2012 +0300
-@@ -44,7 +44,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"
-@@ -443,9 +446,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 27d19b6bc194 mcabber/modules/Makefile.am
---- a/mcabber/modules/Makefile.am	Mon Apr 30 23:36:55 2012 +0300
-+++ b/mcabber/modules/Makefile.am	Mon Apr 30 23:37:25 2012 +0300
-@@ -1,1 +1,1 @@
--SUBDIRS = beep xttitle fifo urlregex
-+SUBDIRS = beep xttitle fifo eventcmd urlregex
-diff -r 27d19b6bc194 mcabber/modules/eventcmd/Makefile.am
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/modules/eventcmd/Makefile.am	Mon Apr 30 23:37:25 2012 +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 27d19b6bc194 mcabber/modules/eventcmd/eventcmd.c
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/modules/eventcmd/eventcmd.c	Mon Apr 30 23:37:25 2012 +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, "delayed") && *(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-extcmd.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,386 @@
+Convert extcmd into a module
+
+diff -r 27d19b6bc194 mcabber/configure.ac
+--- a/mcabber/configure.ac	Mon Apr 30 23:36:55 2012 +0300
++++ b/mcabber/configure.ac	Mon Apr 30 23:37:25 2012 +0300
+@@ -283,6 +283,7 @@
+                  modules/beep/Makefile
+                  modules/xttitle/Makefile
+                  modules/fifo/Makefile
++                 modules/eventcmd/Makefile
+                  modules/urlregex/Makefile
+                  doc/Makefile
+                  doc/guide/Makefile
+diff -r 27d19b6bc194 mcabber/mcabber/Makefile.am
+--- a/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:55 2012 +0300
++++ b/mcabber/mcabber/Makefile.am	Mon Apr 30 23:37:25 2012 +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 27d19b6bc194 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:55 2012 +0300
++++ b/mcabber/mcabber/hooks.c	Mon Apr 30 23:37:25 2012 +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;
+@@ -397,6 +402,7 @@
+   }
+ #endif
+ 
++#ifndef MODULES_ENABLE
+   if (settings_opt_get_int("events_ignore_active_window") &&
+       current_buddy && scr_get_chatmode()) {
+     gpointer bud = BUDDATA(current_buddy);
+@@ -415,6 +421,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
+@@ -491,8 +498,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);
+@@ -578,9 +587,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,
+@@ -704,11 +715,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
+ }
+ 
+ //  hk_presence_subscription_request(jid, message)
+diff -r 27d19b6bc194 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c	Mon Apr 30 23:36:55 2012 +0300
++++ b/mcabber/mcabber/main.c	Mon Apr 30 23:37:25 2012 +0300
+@@ -44,7 +44,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"
+@@ -443,9 +446,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 27d19b6bc194 mcabber/modules/Makefile.am
+--- a/mcabber/modules/Makefile.am	Mon Apr 30 23:36:55 2012 +0300
++++ b/mcabber/modules/Makefile.am	Mon Apr 30 23:37:25 2012 +0300
+@@ -1,1 +1,1 @@
+-SUBDIRS = beep xttitle fifo urlregex
++SUBDIRS = beep xttitle fifo eventcmd urlregex
+diff -r 27d19b6bc194 mcabber/modules/eventcmd/Makefile.am
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/eventcmd/Makefile.am	Mon Apr 30 23:37:25 2012 +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 27d19b6bc194 mcabber/modules/eventcmd/eventcmd.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/modules/eventcmd/eventcmd.c	Mon Apr 30 23:37:25 2012 +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, "delayed") && *(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... */
--- a/roster-state-colors	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-Change roster item colors on chatstate change
-
-diff -r 7222f7b386cb mcabber/contrib/themes/light.txt
---- a/mcabber/contrib/themes/light.txt	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/contrib/themes/light.txt	Mon Apr 30 23:38:36 2012 +0300
-@@ -13,6 +13,5 @@
- set color_bgrostersel  = green
- set color_rostersel    = yellow
- set color_rosterselmsg = red
--set color_rosternewmsg = red
- 
- # vim:set ft=conf:
-diff -r 7222f7b386cb mcabber/contrib/themes/light2.txt
---- a/mcabber/contrib/themes/light2.txt	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/contrib/themes/light2.txt	Mon Apr 30 23:38:36 2012 +0300
-@@ -13,6 +13,5 @@
- set color_bgrostersel  = black
- set color_rostersel    = brightgreen
- set color_rosterselmsg = red
--set color_rosternewmsg = red
- 
- # vim:set ft=conf:
-diff -r 7222f7b386cb mcabber/contrib/themes/oliver.txt
---- a/mcabber/contrib/themes/oliver.txt	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/contrib/themes/oliver.txt	Mon Apr 30 23:38:36 2012 +0300
-@@ -13,6 +13,5 @@
- set color_bgrostersel  = yellow
- set color_rostersel    = blue
- set color_rosterselmsg = red
--set color_rosternewmsg = red
- 
- # vim:set ft=conf:
-diff -r 7222f7b386cb mcabber/doc/help/en/hlp_color.txt
---- a/mcabber/doc/help/en/hlp_color.txt	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/doc/help/en/hlp_color.txt	Mon Apr 30 23:38:36 2012 +0300
-@@ -11,6 +11,7 @@
-  Set a color rule (or overwrite, if it already exists).  The status is string containing all statuses the roster item can have for the rule to match, or * if any status is OK.  Wildcard is the file-matching wildcard that will be applied to JID.  Color is the wanted color.
-  If color is -, the rule is removed.
-  If more than one rule matches, the color from the last created (not overwritten) is used.
-+ Also mask can contain symbols '!', '#', '+' and '.', that represent buddies with urgent condition, with new messages, that are typing and that have ceased typing. These symbols have higher priority than status ones.
- /color mucnick nick (color|-)
-  Marks the nick to be colored by given color.  If a MUC has colored nicks, this one will be used.  If color is -, the color is marked as chosen automatically, which means it will not be used in 'preset' coloring mode, but will stay the same in 'on' coloring mode.
- /color muc (jid|.|*) [on|preset|off|-]
-diff -r 7222f7b386cb mcabber/doc/help/uk/hlp_color.txt
---- a/mcabber/doc/help/uk/hlp_color.txt	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/doc/help/uk/hlp_color.txt	Mon Apr 30 23:38:36 2012 +0300
-@@ -11,6 +11,7 @@
-  Додає нове чи змінює існуюче правило кольору. "Статус" складений з літер статусу, до яких докладається це правило (наприклад "n_d?"). Зірочка ("*") відповідає будь-якому статусу. "Маска" подібна до файлової маски й докладається до jid. Правило встановлює колір контактів, що йому відповідають.
-  Щоб вилучити правило, вкажіть колір "-".
-  Коли є декілька правил, що відповідають контакту, спрацьовує останнє додане (_не_ змінене).
-+ Також рядок статусів може містити символи '!', '#', '+' та '.', що відповідають користувачам, які відповідно мають негайні події, нові повідомлення, друкують та друкували але спинилися. Ці символи мають більший пріоритет ніж символи статусів.
- /color muc (jid|.|*) [on|preset|off|-]
-  Встановлює режим забарвлення прізвиськ у чаті. Якщо замість jid вказано * режим докладається до всіх чатів, для яких не визначено особистого правила (з вказанням jid).
-  У режимі on забарвлюються усі прізвиська, у режимі preset - лише ті, для яких колір встановлено командою /color mucnick, а режим off вимикає забарвлення прізвиськ. Режим - прибирає з вказаних jid особисті режими забарвлення. Тоді до них докладається глобальний. Глобальний режим прибрати не можна.
-diff -r 7222f7b386cb mcabber/mcabber/screen.c
---- a/mcabber/mcabber/screen.c	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/mcabber/screen.c	Mon Apr 30 23:38:36 2012 +0300
-@@ -435,7 +435,7 @@
-     "roster",
-     "rostersel",
-     "rosterselmsg",
--    "rosternewmsg",
-+    "rosternewmsg", /* not removing this to not break binary compatibility with mainstream */
-     "info",
-     "msgin",
-     "readmark",
-@@ -504,10 +504,6 @@
-           init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
-                     find_color(backselected));
-           break;
--      case COLOR_ROSTERNMSG:
--          init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
--                    find_color(background));
--          break;
-       case COLOR_INFO:
-           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
-                     find_color(background));
-@@ -805,6 +801,21 @@
-   settings_set_guard("color_rosterselmsg", scr_color_guard);
-   settings_set_guard("color_rosternewmsg", scr_color_guard);
- 
-+  { // Add default rule only if user has not defined one already
-+    GSList *rel;
-+    gboolean found = FALSE;
-+    for (rel = rostercolrules; rel; rel = rel->next) {
-+      rostercolor *rule = rel->data;
-+      if ((strchr(rule->status, '#') || strchr(rule->status, '!'))
-+          && (!strcmp(rule->wildcard, "*"))) {
-+        found = TRUE;
-+        break;
-+      }
-+    }
-+    if (!found)
-+      scr_roster_color("#!", "*", "red");
-+  }
-+
-   getmaxyx(stdscr, maxY, maxX);
-   Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
-   // Note scr_draw_main_window() should be called early after scr_init_curses()
-@@ -2071,6 +2082,10 @@
-     // for unfolded groups.
-     if (ismsg && (!isgrp || ishid)) {
-       pending = '#';
-+      // Attention sign?
-+      if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
-+          (!ismuc && isurg >= ui_attn_sign_prio_level))
-+        pending = '!';
-     }
- 
-     if (ismuc) {
-@@ -2094,24 +2109,22 @@
-       for (n = 0; n < maxx; n++)
-         waddch(rosterWnd, ' ');
-     } else {
--      if (pending == '#')
--        wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG));
--      else {
--        int color = get_color(COLOR_ROSTER);
--        if ((!isspe) && (!isgrp)) { // Look for color rules
--          GSList *head;
--          const char *bjid = buddy_getjid(BUDDATA(buddy));
--          for (head = rostercolrules; head; head = g_slist_next(head)) {
--            rostercolor *rc = head->data;
--            if (g_pattern_match_string(rc->compiled, bjid) &&
--                (!strcmp("*", rc->status) || strchr(rc->status, status))) {
--              color = compose_color(rc->color);
--              break;
--            }
-+      int color = get_color(COLOR_ROSTER);
-+      if ((!isspe) && (!isgrp)) { // Look for color rules
-+        GSList *head;
-+        const char *jid = buddy_getjid(BUDDATA(buddy));
-+        for (head = rostercolrules; head; head = g_slist_next(head)) {
-+          rostercolor *rc = head->data;
-+          if (g_pattern_match_string(rc->compiled, jid) &&
-+              (!strcmp("*", rc->status) ||
-+               strchr(rc->status, pending) ||
-+               strchr(rc->status, status))) {
-+            color = compose_color(rc->color);
-+            break;
-           }
-         }
--        wattrset(rosterWnd, color);
-       }
-+      wattrset(rosterWnd, color);
-     }
- 
-     if (Roster_Width > prefix_length)
-@@ -2119,13 +2132,6 @@
-     else
-       name[0] = 0;
- 
--    if (pending == '#') {
--      // Attention sign?
--      if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
--          (!ismuc && isurg >= ui_attn_sign_prio_level))
--        pending = '!';
--    }
--
-     if (isgrp) {
-       if (ishid) {
-         int group_count = 0;
-diff -r 7222f7b386cb mcabber/mcabber/screen.h
---- a/mcabber/mcabber/screen.h	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/mcabber/screen.h	Mon Apr 30 23:38:36 2012 +0300
-@@ -46,7 +46,7 @@
-   COLOR_ROSTER,
-   COLOR_ROSTERSEL,
-   COLOR_ROSTERSELNMSG,
--  COLOR_ROSTERNMSG,
-+  COLOR_ROSTERNMSG, /* not removing this to not break binary compatibility with mainstream */
-   COLOR_INFO,
-   COLOR_MSGIN,
-   COLOR_READMARK,
-diff -r 7222f7b386cb mcabber/mcabberrc.example
---- a/mcabber/mcabberrc.example	Mon Apr 30 23:38:20 2012 +0300
-+++ b/mcabber/mcabberrc.example	Mon Apr 30 23:38:36 2012 +0300
-@@ -395,7 +395,6 @@
- # bgrostersel: background color of the selected roster item
- # rostersel:   text color of the selected roster item
- # rosterselmsg:text color of the selected roster item, if there is a new msg
--# rosternewmsg: text color of items with unread messages
- #
- #set color_background   = black
- #set color_general      = white
-@@ -409,15 +408,24 @@
- #set color_bgrostersel  = cyan
- #set color_rostersel    = blue
- #set color_rosterselmsg = red
--#set color_rosternewmsg = red
- #set color_readmark     = red
- 
--# You can color roster items by their status and JID.  For example, to have
--# all roster items white, just all contacts from jabber.org that are away,
--# not available or do not disturb yellow, you do this:
-+# You can color roster items by their status, state and JID.  For example,
-+# to have all roster items white, just all contacts from jabber.org that
-+# are away, not available or do not disturb yellow, you do this:
- #
- #color roster * * white
- #color roster adn *@jabber.org yellow
-+#
-+# You can specify '!', '#', '+' and '.' in status mask, they will take
-+# precedence over status letters and will select buddies accordingly with
-+# urgent condition, with new messages, that are typing and that ceased
-+# typing. By default there exists one rule:
-+#
-+#color roster !# * red
-+#
-+# However, if you define a rule with either '!' or '#', this default rule
-+# will not be added.
- 
- # You can let mcabber color nicks in MUC.
- # These colors will by used automatically:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/roster-state-colors.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,214 @@
+Change roster item colors on chatstate change
+
+diff -r 7222f7b386cb mcabber/contrib/themes/light.txt
+--- a/mcabber/contrib/themes/light.txt	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/contrib/themes/light.txt	Mon Apr 30 23:38:36 2012 +0300
+@@ -13,6 +13,5 @@
+ set color_bgrostersel  = green
+ set color_rostersel    = yellow
+ set color_rosterselmsg = red
+-set color_rosternewmsg = red
+ 
+ # vim:set ft=conf:
+diff -r 7222f7b386cb mcabber/contrib/themes/light2.txt
+--- a/mcabber/contrib/themes/light2.txt	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/contrib/themes/light2.txt	Mon Apr 30 23:38:36 2012 +0300
+@@ -13,6 +13,5 @@
+ set color_bgrostersel  = black
+ set color_rostersel    = brightgreen
+ set color_rosterselmsg = red
+-set color_rosternewmsg = red
+ 
+ # vim:set ft=conf:
+diff -r 7222f7b386cb mcabber/contrib/themes/oliver.txt
+--- a/mcabber/contrib/themes/oliver.txt	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/contrib/themes/oliver.txt	Mon Apr 30 23:38:36 2012 +0300
+@@ -13,6 +13,5 @@
+ set color_bgrostersel  = yellow
+ set color_rostersel    = blue
+ set color_rosterselmsg = red
+-set color_rosternewmsg = red
+ 
+ # vim:set ft=conf:
+diff -r 7222f7b386cb mcabber/doc/help/en/hlp_color.txt
+--- a/mcabber/doc/help/en/hlp_color.txt	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/doc/help/en/hlp_color.txt	Mon Apr 30 23:38:36 2012 +0300
+@@ -11,6 +11,7 @@
+  Set a color rule (or overwrite, if it already exists).  The status is string containing all statuses the roster item can have for the rule to match, or * if any status is OK.  Wildcard is the file-matching wildcard that will be applied to JID.  Color is the wanted color.
+  If color is -, the rule is removed.
+  If more than one rule matches, the color from the last created (not overwritten) is used.
++ Also mask can contain symbols '!', '#', '+' and '.', that represent buddies with urgent condition, with new messages, that are typing and that have ceased typing. These symbols have higher priority than status ones.
+ /color mucnick nick (color|-)
+  Marks the nick to be colored by given color.  If a MUC has colored nicks, this one will be used.  If color is -, the color is marked as chosen automatically, which means it will not be used in 'preset' coloring mode, but will stay the same in 'on' coloring mode.
+ /color muc (jid|.|*) [on|preset|off|-]
+diff -r 7222f7b386cb mcabber/doc/help/uk/hlp_color.txt
+--- a/mcabber/doc/help/uk/hlp_color.txt	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/doc/help/uk/hlp_color.txt	Mon Apr 30 23:38:36 2012 +0300
+@@ -11,6 +11,7 @@
+  Додає нове чи змінює існуюче правило кольору. "Статус" складений з літер статусу, до яких докладається це правило (наприклад "n_d?"). Зірочка ("*") відповідає будь-якому статусу. "Маска" подібна до файлової маски й докладається до jid. Правило встановлює колір контактів, що йому відповідають.
+  Щоб вилучити правило, вкажіть колір "-".
+  Коли є декілька правил, що відповідають контакту, спрацьовує останнє додане (_не_ змінене).
++ Також рядок статусів може містити символи '!', '#', '+' та '.', що відповідають користувачам, які відповідно мають негайні події, нові повідомлення, друкують та друкували але спинилися. Ці символи мають більший пріоритет ніж символи статусів.
+ /color muc (jid|.|*) [on|preset|off|-]
+  Встановлює режим забарвлення прізвиськ у чаті. Якщо замість jid вказано * режим докладається до всіх чатів, для яких не визначено особистого правила (з вказанням jid).
+  У режимі on забарвлюються усі прізвиська, у режимі preset - лише ті, для яких колір встановлено командою /color mucnick, а режим off вимикає забарвлення прізвиськ. Режим - прибирає з вказаних jid особисті режими забарвлення. Тоді до них докладається глобальний. Глобальний режим прибрати не можна.
+diff -r 7222f7b386cb mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/mcabber/screen.c	Mon Apr 30 23:38:36 2012 +0300
+@@ -435,7 +435,7 @@
+     "roster",
+     "rostersel",
+     "rosterselmsg",
+-    "rosternewmsg",
++    "rosternewmsg", /* not removing this to not break binary compatibility with mainstream */
+     "info",
+     "msgin",
+     "readmark",
+@@ -504,10 +504,6 @@
+           init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
+                     find_color(backselected));
+           break;
+-      case COLOR_ROSTERNMSG:
+-          init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
+-                    find_color(background));
+-          break;
+       case COLOR_INFO:
+           init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
+                     find_color(background));
+@@ -805,6 +801,21 @@
+   settings_set_guard("color_rosterselmsg", scr_color_guard);
+   settings_set_guard("color_rosternewmsg", scr_color_guard);
+ 
++  { // Add default rule only if user has not defined one already
++    GSList *rel;
++    gboolean found = FALSE;
++    for (rel = rostercolrules; rel; rel = rel->next) {
++      rostercolor *rule = rel->data;
++      if ((strchr(rule->status, '#') || strchr(rule->status, '!'))
++          && (!strcmp(rule->wildcard, "*"))) {
++        found = TRUE;
++        break;
++      }
++    }
++    if (!found)
++      scr_roster_color("#!", "*", "red");
++  }
++
+   getmaxyx(stdscr, maxY, maxX);
+   Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
+   // Note scr_draw_main_window() should be called early after scr_init_curses()
+@@ -2071,6 +2082,10 @@
+     // for unfolded groups.
+     if (ismsg && (!isgrp || ishid)) {
+       pending = '#';
++      // Attention sign?
++      if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
++          (!ismuc && isurg >= ui_attn_sign_prio_level))
++        pending = '!';
+     }
+ 
+     if (ismuc) {
+@@ -2094,24 +2109,22 @@
+       for (n = 0; n < maxx; n++)
+         waddch(rosterWnd, ' ');
+     } else {
+-      if (pending == '#')
+-        wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG));
+-      else {
+-        int color = get_color(COLOR_ROSTER);
+-        if ((!isspe) && (!isgrp)) { // Look for color rules
+-          GSList *head;
+-          const char *bjid = buddy_getjid(BUDDATA(buddy));
+-          for (head = rostercolrules; head; head = g_slist_next(head)) {
+-            rostercolor *rc = head->data;
+-            if (g_pattern_match_string(rc->compiled, bjid) &&
+-                (!strcmp("*", rc->status) || strchr(rc->status, status))) {
+-              color = compose_color(rc->color);
+-              break;
+-            }
++      int color = get_color(COLOR_ROSTER);
++      if ((!isspe) && (!isgrp)) { // Look for color rules
++        GSList *head;
++        const char *jid = buddy_getjid(BUDDATA(buddy));
++        for (head = rostercolrules; head; head = g_slist_next(head)) {
++          rostercolor *rc = head->data;
++          if (g_pattern_match_string(rc->compiled, jid) &&
++              (!strcmp("*", rc->status) ||
++               strchr(rc->status, pending) ||
++               strchr(rc->status, status))) {
++            color = compose_color(rc->color);
++            break;
+           }
+         }
+-        wattrset(rosterWnd, color);
+       }
++      wattrset(rosterWnd, color);
+     }
+ 
+     if (Roster_Width > prefix_length)
+@@ -2119,13 +2132,6 @@
+     else
+       name[0] = 0;
+ 
+-    if (pending == '#') {
+-      // Attention sign?
+-      if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
+-          (!ismuc && isurg >= ui_attn_sign_prio_level))
+-        pending = '!';
+-    }
+-
+     if (isgrp) {
+       if (ishid) {
+         int group_count = 0;
+diff -r 7222f7b386cb mcabber/mcabber/screen.h
+--- a/mcabber/mcabber/screen.h	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/mcabber/screen.h	Mon Apr 30 23:38:36 2012 +0300
+@@ -46,7 +46,7 @@
+   COLOR_ROSTER,
+   COLOR_ROSTERSEL,
+   COLOR_ROSTERSELNMSG,
+-  COLOR_ROSTERNMSG,
++  COLOR_ROSTERNMSG, /* not removing this to not break binary compatibility with mainstream */
+   COLOR_INFO,
+   COLOR_MSGIN,
+   COLOR_READMARK,
+diff -r 7222f7b386cb mcabber/mcabberrc.example
+--- a/mcabber/mcabberrc.example	Mon Apr 30 23:38:20 2012 +0300
++++ b/mcabber/mcabberrc.example	Mon Apr 30 23:38:36 2012 +0300
+@@ -395,7 +395,6 @@
+ # bgrostersel: background color of the selected roster item
+ # rostersel:   text color of the selected roster item
+ # rosterselmsg:text color of the selected roster item, if there is a new msg
+-# rosternewmsg: text color of items with unread messages
+ #
+ #set color_background   = black
+ #set color_general      = white
+@@ -409,15 +408,24 @@
+ #set color_bgrostersel  = cyan
+ #set color_rostersel    = blue
+ #set color_rosterselmsg = red
+-#set color_rosternewmsg = red
+ #set color_readmark     = red
+ 
+-# You can color roster items by their status and JID.  For example, to have
+-# all roster items white, just all contacts from jabber.org that are away,
+-# not available or do not disturb yellow, you do this:
++# You can color roster items by their status, state and JID.  For example,
++# to have all roster items white, just all contacts from jabber.org that
++# are away, not available or do not disturb yellow, you do this:
+ #
+ #color roster * * white
+ #color roster adn *@jabber.org yellow
++#
++# You can specify '!', '#', '+' and '.' in status mask, they will take
++# precedence over status letters and will select buddies accordingly with
++# urgent condition, with new messages, that are typing and that ceased
++# typing. By default there exists one rule:
++#
++#color roster !# * red
++#
++# However, if you define a rule with either '!' or '#', this default rule
++# will not be added.
+ 
+ # You can let mcabber color nicks in MUC.
+ # These colors will by used automatically:
--- a/separate-extcmd	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-Move extcmd code from hooks
-
-diff -r 64da54766f99 mcabber/mcabber/Makefile.am
---- a/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:31 2012 +0300
-+++ b/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/extcmd.c
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/extcmd.c	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/extcmd.h
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/extcmd.h	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/hooks.c
---- a/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:31 2012 +0300
-+++ b/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:55 2012 +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;
-   gboolean error_msg_subtype = (type == LM_MESSAGE_SUB_TYPE_ERROR);
- #ifdef MODULES_ENABLE
-@@ -373,19 +372,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")) {
-@@ -411,12 +397,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
-@@ -494,7 +492,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);
-@@ -509,10 +507,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);
- 
-@@ -564,27 +559,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,
-@@ -711,7 +707,7 @@
-   /* 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);
- }
- 
-@@ -751,123 +747,4 @@
-   return 0;
- }
- 
--
--/* 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 64da54766f99 mcabber/mcabber/hooks.h
---- a/mcabber/mcabber/hooks.h	Mon Apr 30 23:36:31 2012 +0300
-+++ b/mcabber/mcabber/hooks.h	Mon Apr 30 23:36:55 2012 +0300
-@@ -66,9 +66,6 @@
- guint hk_subscription(LmMessageSubType mstype, const gchar *bjid,
-                       const gchar *msg);
- 
--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 64da54766f99 mcabber/mcabber/main.c
---- a/mcabber/mcabber/main.c	Mon Apr 30 23:36:31 2012 +0300
-+++ b/mcabber/mcabber/main.c	Mon Apr 30 23:36:55 2012 +0300
-@@ -44,6 +44,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/separate-extcmd.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,459 @@
+Move extcmd code from hooks
+
+diff -r 64da54766f99 mcabber/mcabber/Makefile.am
+--- a/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:31 2012 +0300
++++ b/mcabber/mcabber/Makefile.am	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/extcmd.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/extcmd.c	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/extcmd.h
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/extcmd.h	Mon Apr 30 23:36:55 2012 +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 64da54766f99 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:31 2012 +0300
++++ b/mcabber/mcabber/hooks.c	Mon Apr 30 23:36:55 2012 +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;
+   gboolean error_msg_subtype = (type == LM_MESSAGE_SUB_TYPE_ERROR);
+ #ifdef MODULES_ENABLE
+@@ -373,19 +372,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")) {
+@@ -411,12 +397,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
+@@ -494,7 +492,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);
+@@ -509,10 +507,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);
+ 
+@@ -564,27 +559,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,
+@@ -711,7 +707,7 @@
+   /* 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);
+ }
+ 
+@@ -751,123 +747,4 @@
+   return 0;
+ }
+ 
+-
+-/* 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 64da54766f99 mcabber/mcabber/hooks.h
+--- a/mcabber/mcabber/hooks.h	Mon Apr 30 23:36:31 2012 +0300
++++ b/mcabber/mcabber/hooks.h	Mon Apr 30 23:36:55 2012 +0300
+@@ -66,9 +66,6 @@
+ guint hk_subscription(LmMessageSubType mstype, const gchar *bjid,
+                       const gchar *msg);
+ 
+-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 64da54766f99 mcabber/mcabber/main.c
+--- a/mcabber/mcabber/main.c	Mon Apr 30 23:36:31 2012 +0300
++++ b/mcabber/mcabber/main.c	Mon Apr 30 23:36:55 2012 +0300
+@@ -44,6 +44,7 @@
+ #include "xmpp.h"
+ #include "help.h"
+ #include "events.h"
++#include "extcmd.h"
+ 
+ #ifndef MODULES_ENABLE
+ # include "fifo.h"
--- a/series	Sat Jul 28 19:37:32 2012 +0300
+++ b/series	Sat Jul 28 19:42:13 2012 +0300
@@ -1,13 +1,13 @@
-fix-typo
-fix-receipts
-update-pc
-warning-fixes
-fix-module-messages
-switch-to-experimental
-separate-extcmd
-modularize-extcmd
-guard-xmpp-password
-roster-state-colors
-add-cmake
-use-gslice
-templates
+fix-typo.diff
+fix-receipts.diff
+update-pc.diff
+warning-fixes.diff
+fix-module-messages.diff
+switch-to-experimental.diff
+separate-extcmd.diff
+modularize-extcmd.diff
+guard-xmpp-password.diff
+roster-state-colors.diff
+add-cmake.diff
+use-gslice.diff
+templates.diff
--- a/switch-to-experimental	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-[local] Switch to experimental branch and api version
-
-diff -r bd387d7410d0 mcabber/ChangeLog.api
---- a/mcabber/ChangeLog.api	Mon Apr 30 23:21:22 2012 +0300
-+++ b/mcabber/ChangeLog.api	Mon Apr 30 23:36:31 2012 +0300
-@@ -1,3 +1,13 @@
-+
-+experimental (35)
-+
-+ * Change branch to "experimental".
-+ * Api as defined by dev:23
-+ * Add MCABBER_BRANCH_EXPERIMENTAL define to api.h
-+ * MQ Patch: switch-to-experimental
-+
-+  -- Myhailo Danylenko, 2012-04-30
-+
- dev (23)
-  * Changeset 8dc418af3e72
-    Add buddy_(get|set)activeresource() functions
-@@ -16,6 +26,12 @@
- 
-   -- Hermitifier, 2011-10-03
- 
-+experimental (34)
-+
-+ * Api as defined by dev:20
-+
-+  -- Myhailo Danylenko, 2011-06-01
-+
- dev (20)
- 
-  * Add cmd_set_safe() and cmd_is_safe()
-diff -r bd387d7410d0 mcabber/configure.ac
---- a/mcabber/configure.ac	Mon Apr 30 23:21:22 2012 +0300
-+++ b/mcabber/configure.ac	Mon Apr 30 23:36:31 2012 +0300
-@@ -272,7 +272,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 bd387d7410d0 mcabber/mcabber/api.h
---- a/mcabber/mcabber/api.h	Mon Apr 30 23:21:22 2012 +0300
-+++ b/mcabber/mcabber/api.h	Mon Apr 30 23:36:31 2012 +0300
-@@ -3,10 +3,10 @@
- 
- #include <mcabber/config.h> // For MCABBER_BRANCH
- 
--#define MCABBER_API_VERSION 23
--#define MCABBER_API_MIN     21
-+#define MCABBER_API_VERSION 35
-+#define MCABBER_API_MIN     35
- 
--#define MCABBER_BRANCH_DEV  1
-+#define MCABBER_BRANCH_EXPERIMENTAL 1
- 
- #define MCABBER_API_HAVE_CMD_ID 1
- 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/switch-to-experimental.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,61 @@
+[local] Switch to experimental branch and api version
+
+diff -r bd387d7410d0 mcabber/ChangeLog.api
+--- a/mcabber/ChangeLog.api	Mon Apr 30 23:21:22 2012 +0300
++++ b/mcabber/ChangeLog.api	Mon Apr 30 23:36:31 2012 +0300
+@@ -1,3 +1,13 @@
++
++experimental (35)
++
++ * Change branch to "experimental".
++ * Api as defined by dev:23
++ * Add MCABBER_BRANCH_EXPERIMENTAL define to api.h
++ * MQ Patch: switch-to-experimental
++
++  -- Myhailo Danylenko, 2012-04-30
++
+ dev (23)
+  * Changeset 8dc418af3e72
+    Add buddy_(get|set)activeresource() functions
+@@ -16,6 +26,12 @@
+ 
+   -- Hermitifier, 2011-10-03
+ 
++experimental (34)
++
++ * Api as defined by dev:20
++
++  -- Myhailo Danylenko, 2011-06-01
++
+ dev (20)
+ 
+  * Add cmd_set_safe() and cmd_is_safe()
+diff -r bd387d7410d0 mcabber/configure.ac
+--- a/mcabber/configure.ac	Mon Apr 30 23:21:22 2012 +0300
++++ b/mcabber/configure.ac	Mon Apr 30 23:36:31 2012 +0300
+@@ -272,7 +272,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 bd387d7410d0 mcabber/mcabber/api.h
+--- a/mcabber/mcabber/api.h	Mon Apr 30 23:21:22 2012 +0300
++++ b/mcabber/mcabber/api.h	Mon Apr 30 23:36:31 2012 +0300
+@@ -3,10 +3,10 @@
+ 
+ #include <mcabber/config.h> // For MCABBER_BRANCH
+ 
+-#define MCABBER_API_VERSION 23
+-#define MCABBER_API_MIN     21
++#define MCABBER_API_VERSION 35
++#define MCABBER_API_MIN     35
+ 
+-#define MCABBER_BRANCH_DEV  1
++#define MCABBER_BRANCH_EXPERIMENTAL 1
+ 
+ #define MCABBER_API_HAVE_CMD_ID 1
+ 
--- a/templates	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1346 +0,0 @@
-# HG changeset patch
-# Parent 456d832740146c5c9a0428c9ca0a1a0c431481b0
-[work-in-progress] Use templates for statusbars
-
-diff -r 456d83274014 mcabber/CMakeLists.txt
---- a/mcabber/CMakeLists.txt	Fri Jul 20 17:29:53 2012 +0300
-+++ b/mcabber/CMakeLists.txt	Fri Jul 20 17:30:20 2012 +0300
-@@ -154,8 +154,8 @@
- 
- ## Define targets
- set ( mcabber_SUBSYSTEMS
--	caps commands compl events hbuf help histolog hooks
--	modules nohtml otr pgp roster screen settings utf8 utils
-+	caps commands compl events hbuf help histolog hooks modules
-+	nohtml otr parser pgp roster screen settings templates utf8 utils
- 	xmpp xmpp_helper xmpp_iq xmpp_iqrequest xmpp_muc xmpp_s10n )
- if ( NOT MODULES_ENABLE )
- 	list ( APPEND mcabber_SUBSYSTEMS extcmd fifo )
-diff -r 456d83274014 mcabber/mcabber/parser.c
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/parser.c	Fri Jul 20 17:30:20 2012 +0300
-@@ -0,0 +1,853 @@
-+
-+/* Copyright 2012 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 <ctype.h>   // is*
-+#include <stdlib.h>  // strtol
-+#include <string.h>  // strchr
-+
-+#include "parser.h"
-+
-+#include "config.h"
-+
-+//
-+//  Private types
-+//
-+
-+typedef enum {
-+	in_string,
-+	in_escape,
-+	in_var,
-+	in_plainvarstart,
-+	in_plainvar,
-+	in_prevarname,
-+	in_varnamestart,
-+	in_varname,
-+	in_varchop,
-+	in_varstrip,
-+	in_varstripchop,
-+	in_varslash,
-+	in_varmatch,
-+	in_varpresubst,
-+	in_varsubst,
-+	in_varcolon,
-+	in_varminus,
-+	in_varplus,
-+	in_varpos,
-+	in_postvarpos,
-+	in_varlen,
-+	in_varsubstring,
-+	in_varend,
-+	in_evalstart,
-+	in_mathexpand,
-+	in_dblparen_eol,
-+	nostate,
-+} tmpl_parser_state_t;
-+
-+const static char * const tmpl_statenames [ ] = {
-+	"in string",
-+	"in escape",
-+	"in var",
-+	"in plainvarstart",
-+	"in plainvar",
-+	"in prevarname",
-+	"in varnamestart",
-+	"in varname",
-+	"in varchop",
-+	"in varstrip",
-+	"in varstripchop",
-+	"in varslash",
-+	"in varmatch",
-+	"in varpresubst",
-+	"in varsubst",
-+	"in varcolon",
-+	"in varminus",
-+	"in varplus",
-+	"in varpos",
-+	"in postvarpos",
-+	"in varlen",
-+	"in varsubstring",
-+	"in varend",
-+	"in evalstart",
-+	"in mathexpand",
-+	"in dblparen eol",
-+	"wrong state",
-+};
-+
-+typedef enum {
-+	no_eol         =  0x0,
-+	brace_eol      =  0x1,
-+	slash_eol      =  0x2,
-+	colon_eol      =  0x4,
-+	paren_eol      =  0x8,
-+	dblparen_eol   = 0x10,
-+	balance_parens = 0x20,
-+} tmpl_parser_flags_t;
-+
-+typedef enum {
-+	no_operation,
-+	expand_var,
-+	value_length,
-+} tmpl_var_operation_t;
-+
-+typedef enum {
-+	no_op,
-+	noempty_op,
-+	paren_op,
-+	plus_op,
-+	minus_op,
-+	remainder_op,
-+	division_op,
-+	multiplication_op,
-+} tmpl_math_op_t;
-+
-+typedef enum {
-+	in_preargument,
-+	in_hexoctzero,
-+	in_number,
-+	in_variable,
-+	in_postparen,
-+	in_op,
-+} tmpl_math_state_t;
-+
-+//
-+//  Code
-+//
-+
-+GQuark tmpl_gerror_quark ( void )
-+{
-+	return g_quark_from_static_string ( "tmpl_gerror_quark" );
-+}
-+
-+// match tmpl_glob ( string, pattern, greedy, right-to-left )
-+// Performs matching of given pattern against given string,
-+// returns first substring in input, that matched. In pattern,
-+// there are currently two tokens recognized: '*' and '?'.
-+const gchar *tmpl_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
-+{
-+	*ret_len = 0;
-+	if ( rtl ) { // rtl
-+		const char *p           = pat + patlen - 1;
-+		const char *s           = str + length - 1;
-+		const char *matchend    = NULL;
-+		const char *patrollback = p;
-+		const char *strrollback = s;
-+		while ( p >= pat && s >= str ) {
-+			if ( *p == '*' ) {
-+				if ( matchend == NULL && patrollback != NULL )
-+					matchend = strrollback;
-+				patrollback = NULL;
-+				strrollback = NULL;
-+				p --;
-+			} else if ( *p == '?' || *p == *s ) {
-+				if ( patrollback == NULL ) {
-+					patrollback = p;
-+					strrollback = s;
-+				}
-+				p --;
-+				s --;
-+			} else if ( patrollback != NULL ) {
-+				p =    patrollback;
-+				s = -- strrollback;
-+			} else {
-+				s --;
-+			}
-+		}
-+		// now s, p or both have finished run
-+		if ( s >= str && patrollback == NULL ) // s remains and final star
-+			s = str - 1; // value at the end of loop
-+		if ( p >= pat ) { // p remains
-+			while ( *p == '*' && p >= pat ) // ignore empty stars
-+				p --;
-+			if ( p >= pat ) // symbols remain in pattern, no match
-+				return NULL;
-+		}
-+		if ( matchend == NULL && patrollback != NULL ) // no stars
-+			matchend = strrollback;
-+		if ( matchend != NULL ) {
-+			*ret_len = matchend - s;
-+			return s + 1;
-+		} else
-+			return NULL;
-+	} else { // ltr
-+		const char * const  pe          = pat + patlen;
-+		const char * const  se          = str + length;
-+		const char         *p           = pat;
-+		const char         *s           = str;
-+		const char         *matchstart  = NULL;
-+		const char         *patrollback = p;
-+		const char         *strrollback = s;
-+		while ( p < pe && s < se ) {
-+			if ( *p == '*' ) {
-+				if ( matchstart == NULL && patrollback != NULL )
-+					matchstart = strrollback;
-+				patrollback = NULL;
-+				strrollback = NULL;
-+				p ++;
-+			} else if ( *p == '?' || *p == *s ) {
-+				if ( patrollback == NULL ) {
-+					patrollback = p;
-+					strrollback = s;
-+				}
-+				p ++;
-+				s ++;
-+			} else if ( patrollback != NULL ) { // start/nomatch-recovery eat
-+				p =    patrollback;
-+				s = ++ strrollback;
-+			} else { // star-eat
-+				s ++;
-+			}
-+		}
-+		// now s, p or both have finished run
-+		if ( s < se && patrollback == NULL ) // s remains and final star
-+				s = se;
-+		if ( p < pe ) { // p remains
-+			while ( *p == '*' && p < pe ) // ignore empty stars
-+				p ++;
-+			if ( p < pe ) // symbols remain in pattern, no match
-+				return NULL;
-+		}
-+		if ( matchstart == NULL && patrollback != NULL ) // no stars
-+			matchstart = strrollback;
-+		if ( matchstart != NULL )
-+			*ret_len = s - matchstart;
-+		return matchstart;
-+	}
-+}
-+
-+// match tmpl_greedy_glob ( string, pattern, right-to-left )
-+// The same as above, but greedy.
-+const gchar *tmpl_greedy_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
-+{
-+	const char * const  pe = pat + patlen;
-+	const char         *p  = pat;
-+	while ( p < pe ) {
-+		if ( *p == '*' ) {
-+			gsize       llen = 0;
-+			const char *left = tmpl_glob ( str, length, pat, p - pat, FALSE, &llen );
-+			if ( left != NULL ) {
-+				gsize       rlen  = 0;
-+				const char *right = tmpl_glob ( left + llen, str + length - ( left + llen ), p + 1, pe - p - 1, TRUE, &rlen );
-+				if ( right != NULL ) {
-+					*ret_len = right + rlen - left;
-+					return left;
-+				}
-+			}
-+			return NULL;
-+		}
-+		p ++;
-+	}
-+	return tmpl_glob ( str, length, pat, patlen, rtl, ret_len );
-+}
-+
-+// - + * / % ( )
-+static gssize tmpl_math_expand_internal ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **error, tmpl_math_op_t bound, gsize *proc_len )
-+{
-+	g_assert ( error != NULL && *error == NULL );
-+
-+	gssize             result   = 0;
-+	const char *       p        = str;
-+	const char * const e        = str + length;
-+	tmpl_math_state_t   state    = in_preargument;
-+	const char *       varname  = NULL;  // variable name start
-+	gsize              base     = 10;    // number base
-+	gboolean           invert   = FALSE; // number change sign
-+	gboolean           errstate = bound >= noempty_op; // indicates erroneous state
-+
-+	// process first argument
-+	while ( p < e && *error == NULL ) {
-+		if ( state == in_preargument ) { // initial space-skipping state
-+			if ( isspace ( *p ) )
-+				p ++;
-+			else if ( *p == '-' ) {
-+				invert = ! invert;
-+				errstate = TRUE;
-+				p ++;
-+			} else if ( *p == '+' ) {
-+				errstate = TRUE;
-+				p ++;
-+			} else if ( *p == '(' ) {
-+				gsize processed = 0;
-+				p ++;
-+				result = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, paren_op, &processed );
-+				if ( *error == NULL )
-+					state = in_postparen;
-+				errstate  = TRUE;
-+				p        += processed;
-+			} else if ( *p == '0' ) {
-+				result   = 0;
-+				errstate = FALSE;
-+				state    = in_hexoctzero;
-+				p ++;
-+			} else if ( isdigit ( *p ) ) {
-+				result   = 0;
-+				base     = 10;
-+				state    = in_number;
-+			} else if ( isalpha ( *p ) || *p == '_' ) {
-+				state    = in_variable;
-+				errstate = FALSE;
-+				varname  = p;
-+				p ++;
-+			} else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGARG, "Wrong symbol in math argument" );
-+		} else if ( state == in_hexoctzero ) {
-+			if ( *p == 'x' ) {
-+				errstate = TRUE;
-+				base     = 16;
-+				p ++;
-+			} else
-+				base = 8;
-+			state  = in_number;
-+		} else if ( state == in_number ) {
-+			short d = -1;
-+			if ( *p == '0' )
-+				d = 0;
-+			else if ( *p == '1' )
-+				d = 1;
-+			else if ( *p == '2' )
-+				d = 2;
-+			else if ( *p == '3' )
-+				d = 3;
-+			else if ( *p == '4' )
-+				d = 4;
-+			else if ( *p == '5' )
-+				d = 5;
-+			else if ( *p == '6' )
-+				d = 6;
-+			else if ( *p == '7' )
-+				d = 7;
-+			else if ( base <= 8 )
-+				;
-+			else if ( *p == '8' )
-+				d = 8;
-+			else if ( *p == '9' )
-+				d = 9;
-+			else if ( base <= 10 )
-+				;
-+			else if ( *p == 'a' || *p == 'A' )
-+				d = 10;
-+			else if ( *p == 'b' || *p == 'B' )
-+				d = 11;
-+			else if ( *p == 'c' || *p == 'C' )
-+				d = 12;
-+			else if ( *p == 'd' || *p == 'D' )
-+				d = 13;
-+			else if ( *p == 'e' || *p == 'E' )
-+				d = 14;
-+			else if ( *p == 'f' || *p == 'F' )
-+				d = 15;
-+			if ( d >= 0 ) {
-+				errstate = FALSE;
-+				result   = result * base + d;
-+				p ++;
-+			} else if ( ! errstate )
-+				state = in_op;
-+			else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGNUMBER, "Too short math number" );
-+		} else if ( state == in_variable ) {
-+			if ( ! ( isalnum ( *p ) || *p == '_' ) ) { // XXX vars with '-' have to be expanded earlier
-+				gsize        vlen  = 0;
-+				const char * value = get_val ( varname, p - varname, udata, &vlen );
-+				// TODO control recursion level
-+				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
-+				if ( *error == NULL )
-+					state = in_op;
-+			} else
-+				p ++;
-+		} else if ( state == in_postparen ) {
-+			if ( *p == ')' ) {
-+				errstate = FALSE;
-+				p ++;
-+				state    = in_op;
-+			} else
-+				g_assert_not_reached ();
-+		} else if ( state == in_op )
-+			break;
-+		else
-+			g_assert_not_reached ();
-+	}
-+
-+	if ( *error == NULL ) {
-+		// in_preargument - empty argument, possibly +-+- - 0 / errstate
-+		// in_hexoctzero  - 0                             - 0
-+		// in_number      - 0, 0x, 01, 0x1, 1             - failure at 0x (errstate), invert
-+		// in_variable    - varname                       - get var, invert
-+		// in_postparen   - eof                           - failure (errstate)
-+		// in_op          - ok                            - invert, continue parsing
-+		if ( errstate )
-+			g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGSTATE, "Wrong state at the end of first math argument parsing" );
-+		else {
-+			if ( state == in_op ) {
-+				if ( invert )
-+					result = - result;
-+			
-+				// process operator+second argument pairs
-+				while ( p < e && *error == NULL ) {
-+					tmpl_math_op_t op = no_op;
-+					if ( isspace ( *p ) )
-+						p ++;
-+					else if ( *p == '+' ) // set op
-+						op = plus_op;
-+					else if ( *p == '-' )
-+						op = minus_op;
-+					else if ( *p == '/' )
-+						op = division_op;
-+					else if ( *p == '*' )
-+						op = multiplication_op;
-+					else if ( *p == '%' )
-+						op = remainder_op;
-+					else if ( *p == ')' ) {
-+						if ( bound < paren_op )
-+							g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHUNBALPAREN, "Unbalanced parens in math expression" );
-+						else
-+							break;
-+					} else
-+						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGOP, "Wrong math operator '%c'", *p );
-+
-+					if ( op != no_op ) {
-+						// lower priority operator, returning result
-+						if ( op < bound )
-+							break;
-+
-+						p ++;
-+						gsize  processed = 0;
-+						gssize arg       = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, op, &processed );
-+						if ( *error == NULL ) {
-+							if ( op == plus_op )
-+								result += arg;
-+							else if ( op == minus_op )
-+								result -= arg;
-+							else if ( op == division_op )
-+								result /= arg;
-+							else if ( op == multiplication_op )
-+								result *= arg;
-+							else if ( op == remainder_op )
-+								result %= arg;
-+							else
-+								g_assert_not_reached ();
-+						}
-+						p += processed;
-+					}
-+				}
-+			} else if ( state == in_variable ) {
-+				gsize        vlen  = 0;
-+				const char * value = get_val ( varname, p - varname, udata, &vlen );
-+				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
-+				if ( *error == NULL && invert )
-+					result = - result;
-+			} else if ( invert )
-+				result = - result;
-+			// XXX may add long conditional with g_assert here, but I'm lazy
-+		}
-+	}
-+
-+	// return result
-+	if ( proc_len != NULL )
-+		*proc_len = p - str;
-+	return result;
-+}
-+
-+// result tmpl_math_expand ( plainexpanded string, callback )
-+// Performs mathematical expansion on given string.
-+// Note, that it does not perform variable/quote/etc expansion,
-+// if you want it, you should do that first.
-+gssize tmpl_math_expand ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **ret_err )
-+{
-+	g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, -1 ); // XXX
-+
-+	GError * error  = NULL;
-+	gssize   result = tmpl_math_expand_internal ( str, length, get_val, udata, &error, no_op, NULL );
-+	if ( error != NULL )
-+		g_propagate_error ( ret_err, error );
-+	return result;
-+}
-+
-+// expansion tmpl_expand ( template, callback )
-+// Parse template, substitute shell-like expressions:
-+// * $var ${var}
-+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
-+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
-+// * ${var:+tmpl} ${var:-tmpl}
-+// * ${var:uint} ${var:uint:uint}
-+// * ${#var} ${!var} ${!var[operation]}
-+// * \n \t \e \$ \\ \X
-+// Callback will be called to obtain variable values.
-+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
-+// For pattern rules see tmpl_glob ().
-+static gchar *tmpl_expand_internal ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error, tmpl_parser_flags_t flags, gsize *proc_len )
-+{
-+	g_assert ( error != NULL && *error == NULL );
-+
-+	if ( template == NULL || len == 0 )
-+		return NULL;
-+
-+	GString            *result     = g_string_new ( NULL );
-+	const char         *p          = template;
-+	const char * const  e          = template + len;
-+	tmpl_parser_state_t  state      = in_string;
-+	const char         *a1s        = p;     // string, varname
-+	const gchar        *value      = NULL;  // variable value
-+	gsize               vlen       = 0;     // variable value length
-+	tmpl_var_operation_t operation  = no_operation;
-+	gchar              *pattern    = NULL;
-+	gsize               plen       = 0;
-+	gboolean            rtl        = FALSE; // strip/chop
-+	gboolean            greedy     = FALSE; // strip/chop
-+	gboolean            multiglob  = FALSE; // replace
-+	gsize               parencount = 0;
-+	gssize              subsoffset = -1;
-+	gssize              subslength = -1;
-+
-+	while ( p < e && *error == NULL ) {
-+		if ( state == in_string ) { // nothing special
-+			if ( *p == '\\' ) { // escape next char
-+				g_string_append_len ( result, a1s, p - a1s );
-+				state = in_escape;
-+			} else if ( *p == '$' ) { // start variable
-+				g_string_append_len ( result, a1s, p - a1s );
-+				state = in_var;
-+			} else if ( flags & balance_parens && *p == '(' ) {
-+				parencount ++;
-+			} else if ( *p == ')' ) {
-+				if ( flags & balance_parens && parencount > 0 ) {
-+					parencount --;
-+				} else if ( flags & dblparen_eol ) {
-+					g_string_append_len ( result, a1s, p - a1s );
-+					state = in_dblparen_eol;
-+				} else if ( flags & balance_parens )
-+					g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
-+			} else if ( ( flags & brace_eol && *p == '}' ) ||
-+			            ( flags & slash_eol && *p == '/' ) ||
-+						( flags & colon_eol && parencount == 0 && *p == ':' ) )
-+				break;
-+			p ++;
-+		} else if ( state == in_escape ) { // escape (on escaped char)
-+			if ( *p == 'n' )
-+				g_string_append_c ( result, '\n' );
-+			else if ( *p == 't' )
-+				g_string_append_c ( result, '\t' );
-+			else if ( *p == 'e' ) // for some experimentation with colors...
-+				g_string_append_c ( result, '\033' );
-+			else
-+				g_string_append_c ( result, *p );
-+			p ++;
-+			state = in_string;
-+			a1s   = p;
-+		} else if ( state == in_var ) { // some variable
-+			if ( *p == '{' ) { // enclosed variable
-+				state = in_prevarname;
-+				p ++;
-+			} else if ( *p == '(' ) {
-+				state = in_evalstart;
-+				p ++;
-+			} else // unenclosed variable
-+				state = in_plainvarstart;
-+		} else if ( state == in_plainvarstart ) {
-+			if ( isalpha ( *p ) || *p == '_' ) {
-+				state = in_plainvar;
-+				a1s   = p;
-+				p ++;
-+			} else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at the start of variable name" );
-+		} else if ( state == in_plainvar ) { // unenclosed variable
-+			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
-+				gsize        vlen  = 0;
-+				const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
-+				g_string_append_len ( result, value, vlen );
-+				state = in_string;
-+				a1s   = p;
-+			} else
-+				p ++;
-+		} else if ( state == in_prevarname ) { // allow ! and # at varname start
-+			if ( *p == '!' ) {
-+				operation = expand_var;
-+				p++;
-+			} else if ( *p == '#' ) {
-+				operation = value_length;
-+				p++;
-+			} else
-+				operation = no_operation;
-+			state = in_varnamestart;
-+		} else if ( state == in_varnamestart ) {
-+			if ( isalpha ( *p ) || *p == '_' ) {
-+				state = in_varname;
-+				a1s   = p;
-+				p ++;
-+			} else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at start of variable name" );
-+		} else if ( state == in_varname ) { // enclosed variable name
-+			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
-+				value = get_val ( a1s, p - a1s, udata, &vlen );
-+				if ( operation == value_length ) {
-+					if ( *p == '}' ) {
-+						g_string_append_printf ( result, "%lu", vlen );
-+						state = in_varend;
-+					} else
-+						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
-+				} else {
-+					if ( operation == expand_var && value != NULL && vlen > 0 )
-+						value = get_val ( value, vlen, udata, &vlen );
-+					if ( *p == '}' ) { // end of variable
-+						g_string_append_len ( result, value, vlen );
-+						state = in_varend;
-+					} else if ( *p == '#' ) { // strip expression
-+						state = in_varstrip;
-+						p ++;
-+					} else if ( *p == '%' ) { // chop expression
-+						state = in_varchop;
-+						p ++;
-+					} else if ( *p == '/' ) { // replace expression
-+						state = in_varslash;
-+						p ++;
-+					} else if ( *p == ':' ) { // substring expression
-+						state = in_varcolon;
-+						p ++;
-+					} else // wrong symbols
-+						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
-+				}
-+			} else
-+				p ++;
-+		} else if ( state == in_varstrip ) { // one of strip expressions
-+			if ( *p == '#' ) {
-+				greedy = TRUE;
-+				p ++;
-+			}
-+			rtl   = FALSE;
-+			state = in_varstripchop;
-+		} else if ( state == in_varchop ) { // one of chop expressions
-+			if ( *p == '%' ) {
-+				greedy = TRUE;
-+				p ++;
-+			}
-+			rtl   = TRUE;
-+			state = in_varstripchop;
-+		} else if ( state == in_varstripchop ) { // pattern expressions
-+			gsize  elen      = 0;
-+			gsize  processed = 0;
-+			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
-+			if ( *error == NULL ) {
-+				gsize       mlen  = 0;
-+				const char *match = NULL;
-+				if ( greedy )
-+					match = tmpl_greedy_glob ( value, vlen, expansion, elen, rtl, &mlen );
-+				else
-+					match = tmpl_glob ( value, vlen, expansion, elen, rtl, &mlen );
-+				if ( mlen > 0 ) {
-+					if ( rtl ) { // % and %%
-+						if ( match + mlen == value + vlen )
-+							vlen -= mlen;
-+					} else if ( match == value ) { // # and ##
-+						value  = match + mlen;
-+						vlen  -= mlen;
-+					}
-+				}
-+				g_string_append_len ( result, value, vlen );
-+			}
-+			g_free ( expansion );
-+			p     += processed;
-+			state  = in_varend;
-+		} else if ( state == in_varslash ) { // replace expression
-+			multiglob = FALSE;
-+			rtl       = FALSE;
-+			if ( *p == '#' )
-+				p ++;
-+			else if ( *p == '%' ) {
-+				rtl = TRUE;
-+				p ++;
-+			} else if ( *p == '/' ) {
-+				multiglob = TRUE;
-+				p ++;
-+			}
-+			state = in_varmatch;
-+		} else if ( state == in_varmatch ) { // match part of replace expression
-+			gsize processed  = 0;
-+			pattern          = tmpl_expand_internal ( p, e - p, get_val, udata, &plen, error, brace_eol | slash_eol, &processed );
-+			p               += processed;
-+			state            = in_varpresubst;
-+		} else if ( state == in_varpresubst ) { // skip slash
-+			if ( *p == '/' )
-+				p ++;
-+			state = in_varsubst;
-+		} else if ( state == in_varsubst ) { // replace expression
-+			gsize  processed = 0;
-+			gsize  slen      = 0;
-+			gchar *subst     = tmpl_expand_internal ( p, e - p, get_val, udata, &slen, error, brace_eol, &processed );
-+			if ( *error == NULL ) {
-+				const char       * start = value;
-+				const char * const ve    = value + vlen;
-+				while ( start < ve ) {
-+					gsize       mlen  = 0;
-+					const char *match = tmpl_glob ( start, ve - start, pattern, plen, rtl, &mlen );
-+					if ( mlen > 0 ) {
-+						g_string_append_len ( result, start, match - start );
-+						g_string_append_len ( result, subst, slen );
-+						start = match + mlen;
-+					} else
-+						break;
-+					if ( ! multiglob )
-+						break;
-+				}
-+				g_string_append_len ( result, start, ve - start );
-+			}
-+			g_free ( subst );
-+			g_free ( pattern );
-+			pattern  = NULL;
-+			p       += processed;
-+			state    = in_varend;
-+		} else if ( state == in_varcolon ) { // substring or substitution expression
-+			if ( *p == '-' ) {
-+				state = in_varminus;
-+				p ++;
-+			} else if ( *p == '+' ) {
-+				state = in_varplus;
-+				p ++;
-+			} else {
-+				subsoffset = -1;
-+				subslength = -1;
-+				state      = in_varpos;
-+			}
-+		} else if ( state == in_varminus || state == in_varplus ) { // zero and non-zero substitution
-+			gsize        elen      = 0;
-+			gsize        processed = 0;
-+			gchar       *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
-+			if ( *error == NULL ) {
-+				if ( ( vlen == 0 && state == in_varminus ) || ( vlen > 0 && state == in_varplus ) )
-+					g_string_append_len ( result, expansion, elen );
-+				else if ( vlen > 0 && state == in_varminus )
-+					g_string_append_len ( result, value, vlen );
-+			}
-+			g_free ( expansion );
-+			p     += processed;
-+			state  = in_varend;
-+		} else if ( state == in_varpos ) { // substring expression
-+			gsize  elen      = 0;
-+			gsize  processed = 0;
-+			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, colon_eol | brace_eol | balance_parens, &processed );
-+			if ( *error == NULL ) {
-+				subsoffset = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
-+				if ( subsoffset < 0 )
-+					subsoffset = 0;
-+			}
-+			g_free ( expansion );
-+			p     += processed;
-+			state  = in_postvarpos;
-+		} else if ( state == in_postvarpos ) {
-+			if ( *p == ':' ) {
-+				state = in_varlen;
-+				p ++;
-+			} else if ( *p == '}' )
-+				state = in_varsubstring;
-+			else
-+				g_assert_not_reached ();
-+		} else if ( state == in_varlen ) {
-+			gsize  elen      = 0;
-+			gsize  processed = 0;
-+			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol | balance_parens, &processed );
-+			if ( *error == NULL ) {
-+				subslength = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
-+				if ( subslength < 0 )
-+					subslength = 0;
-+			}
-+			g_free ( expansion );
-+			p     += processed;
-+			state  = in_varsubstring;
-+		} else if ( state == in_varsubstring ) {
-+			g_assert ( subsoffset >= 0 );
-+			if ( subslength == -1 )
-+				subslength = vlen;
-+			if ( subsoffset < vlen ) {
-+				if ( subsoffset + subslength > vlen )
-+					g_string_append_len ( result, value + subsoffset, vlen - subsoffset );
-+				else
-+					g_string_append_len ( result, value + subsoffset, subslength );
-+			}
-+			state = in_varend;
-+		} else if ( state == in_varend ) { // end of enclosed variable
-+			if ( *p == '}' ) {
-+				p ++;
-+				state = in_string;
-+				a1s   = p;
-+			} else
-+				g_assert_not_reached (); // this state must be used only on '}' or EOL
-+		} else if ( state == in_evalstart ) {
-+			if ( *p == '(' ) {
-+				p ++;
-+				state = in_mathexpand;
-+			} else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Command evaluation is not supported" );
-+		} else if ( state == in_mathexpand ) {
-+			gsize  elen      = 0;
-+			gsize  processed = 0;
-+			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, dblparen_eol | balance_parens, &processed );
-+			if ( *error == NULL ) {
-+				gssize value = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
-+				if ( *error == NULL )
-+					g_string_append_printf ( result, "%li", value );
-+			}
-+			g_free ( expansion );
-+			p     += processed;
-+			state  = in_string;
-+			a1s    = p;
-+		} else if ( state == in_dblparen_eol ) {
-+			if ( *p == ')' ) {
-+				p ++;
-+				break;
-+			} else
-+				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
-+		} else
-+			g_assert_not_reached ();
-+	}
-+
-+	if ( *error == NULL ) {
-+		if ( state == in_plainvar ) { // plain variable at the end of template
-+			gsize        vlen  = 0;
-+			const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
-+			g_string_append_len ( result, value, vlen );
-+		} else if ( state == in_string ) // end of string
-+			g_string_append_len ( result, a1s, p - a1s );
-+		else if ( state != in_dblparen_eol )
-+			g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGENDSTATE, "Parser stopped in incorrect state '%s'", state < nostate ? tmpl_statenames [ state ] : "wrong state" );
-+	}
-+
-+	if ( pattern != NULL )
-+		g_free ( pattern );
-+
-+	if ( proc_len != NULL )
-+		*proc_len = p - template;
-+	if ( ret_len != NULL )
-+		*ret_len = result -> len;
-+	return g_string_free ( result, FALSE );
-+}
-+
-+//
-+//  public wrappers
-+//
-+
-+gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **ret_err )
-+{
-+	g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
-+
-+	GError * error  = NULL;
-+	gchar *  result = tmpl_expand_internal ( template, len, get_val, udata, ret_len, &error, no_eol, NULL );
-+	if ( error != NULL )
-+		g_propagate_error ( ret_err, error );
-+	return result;
-+}
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 456d83274014 mcabber/mcabber/parser.h
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/parser.h	Fri Jul 20 17:30:20 2012 +0300
-@@ -0,0 +1,77 @@
-+
-+#ifndef MCABBER_PARSER_H
-+#define MCABBER_PARSER_H
-+
-+/* Copyright 2012 Myhailo Danylenko
-+ *
-+ * This file is part of mcabber
-+ *
-+ * mcabber 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>
-+
-+#define TMPL_GERROR_QUARK tmpl_gerror_quark ()
-+
-+#define TMPL_EWRONGVAREXP   ( 0x01 )
-+#define TMPL_EWRONGVARNAME  ( 0x03 )
-+#define TMPL_EUNBALPAREN    ( 0x04 )
-+#define TMPL_EWRONGENDSTATE ( 0x02 )
-+
-+#define TMPL_EMATHWRONGARG    ( 0x05 )
-+#define TMPL_EMATHWRONGNUMBER ( 0x06 )
-+#define TMPL_EMATHUNBALPAREN  ( 0x07 )
-+#define TMPL_EMATHWRONGOP     ( 0x08 )
-+#define TMPL_EMATHWRONGSTATE  ( 0x09 )
-+
-+typedef const char *(*tmpl_variable_callback_t)   ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
-+
-+GQuark tmpl_gerror_quark ( void );
-+
-+//  match tmpl_glob ( string, pattern, right-to-left )
-+// Performs matching of given pattern against given string,
-+// returns substring, that matched. In pattern, there are currently
-+// two tokens recognized: '*' and '?'. Two flags determine, if
-+// '*' should be greedy and which end of string must match the
-+// pattern (i.e. have anchor).
-+const char *tmpl_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
-+
-+//  match tmpl_greedy_glob ( string, pattern, rigt-to-left )
-+// The same, as above, but greedy.
-+const char *tmpl_greedy_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
-+
-+//  result tmpl_math_expand ( string, callback )
-+// Performs mathematical expansion of given string.
-+// Note, that $var expressions are not recognized, you have to
-+// supply already expanded string here.
-+// Supported operators:
-+// ( ) + - * / %
-+gssize tmpl_math_expand ( const char *str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError **error );
-+
-+//  expansion tmpl_expand ( template, callback )
-+// Parse template, substitute shell-like expressions:
-+// * $var ${var}
-+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
-+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
-+// * ${var:+tmpl} ${var:-tmpl}
-+// * ${var:mathexp} ${var:mathexp:mathexp}
-+// * ${#var} ${!var} ${!var[operation]}
-+// * $(( mathexp ))
-+// * \n \t \e \$ \\ \X
-+// Callback will be called to obtain variable values.
-+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
-+// For pattern rules see mms_glob ().
-+gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error );
-+
-+# endif
-+
-diff -r 456d83274014 mcabber/mcabber/templates.c
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/templates.c	Fri Jul 20 17:30:20 2012 +0300
-@@ -0,0 +1,277 @@
-+
-+/* Copyright 2012 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 <string.h>    // strlen
-+
-+#include "logprint.h"
-+#include "settings.h"
-+#include "main.h"      // main_context
-+#include "templates.h"
-+#include "parser.h"
-+
-+//
-+//  globals
-+//
-+
-+typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
-+
-+typedef struct {
-+	gchar           * name;
-+	tmpl_callback_t   callback;
-+	gpointer          userdata;
-+	gboolean          in_use;
-+	GSList          * guards;
-+	gboolean          changed;
-+	gchar           * prev_expansion;
-+} template_t;
-+
-+typedef struct {
-+	gchar  * name;
-+	GSList * templates;
-+} tmpl_guard_t;
-+
-+static GHashTable * tmpl_templates   = NULL;
-+static GHashTable * tmpl_guards      = NULL;
-+static guint        tmpl_attached_id = 0;
-+
-+//
-+//  predeclarations
-+//
-+
-+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value );
-+
-+//
-+//  code
-+//
-+
-+// [cb] drops template from guard's 'templates' list
-+static void tmpl_unguard ( gpointer data, gpointer udata )
-+{
-+	tmpl_guard_t *guard    = data;
-+	template_t   *template = udata;
-+	guard -> templates = g_slist_remove ( guard -> templates, template );
-+}
-+
-+// [destructor cb] releases guard hash table entry
-+static void tmpl_free_guard ( gpointer data )
-+{
-+	tmpl_guard_t *guard = data;
-+	settings_del_guard ( guard -> name );
-+	g_slist_free ( guard -> templates );
-+	g_free ( guard -> name );
-+	g_slice_free ( tmpl_guard_t, guard );
-+}
-+
-+// [destructor cb] releases taken guards and frees command
-+static void tmpl_free_template ( gpointer data )
-+{
-+	template_t *template = data;
-+	g_slist_foreach ( template -> guards, tmpl_unguard, template );
-+	g_slist_free ( template -> guards );
-+	g_free ( template -> name );
-+	g_free ( template -> prev_expansion );
-+	g_slice_free ( template_t, template );
-+}
-+
-+// install guard (name must be glib-allocated string)
-+static void tmpl_install_guard ( gchar *name, template_t *template, settings_guard_t callback )
-+{
-+	tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, name );
-+	if ( guard == NULL ) {
-+		if ( ! settings_set_guard ( name, callback ) ) {
-+			scr_log_print ( LPRINT_LOGNORM, "Error: Unable to install guard for variable '%s' for template '%s'.", name, template -> name );
-+			g_free ( name );
-+		} else {
-+			guard = g_slice_new ( tmpl_guard_t );
-+			guard -> name      = name;
-+			guard -> templates = NULL;
-+			g_hash_table_replace ( tmpl_guards, guard -> name, guard ); // to be sure
-+		}
-+	} else
-+		g_free ( name );
-+	if ( ! g_slist_find ( template -> guards, guard ) ) {
-+		template -> guards = g_slist_prepend ( template -> guards, guard );
-+		guard -> templates = g_slist_prepend ( guard -> templates, template );
-+	}
-+}
-+
-+// [parser cb] provides mcabber option values & reinstalls guards
-+static const char *tmpl_get_var ( const gchar *name, gsize len, gpointer udata, gsize *ret_len )
-+{
-+	const char *result = NULL;
-+	if ( name != NULL && len > 0 ) {
-+		template_t * template = udata;
-+		gchar      * var      = g_strndup ( name, len );
-+		result = settings_opt_get ( var );
-+		// consumes var
-+		tmpl_install_guard ( var, template, tmpl_guard );
-+	}
-+	if ( ret_len != NULL ) {
-+		if ( result != NULL )
-+			*ret_len = strlen ( result );
-+		else
-+			*ret_len = 0;
-+	}
-+	return result;
-+}
-+
-+// [cb] mark unused guards for removal
-+static gboolean tmpl_drop_unused_guards ( gpointer key, gpointer value, gpointer udata )
-+{
-+	tmpl_guard_t * guard = value;
-+	if ( guard -> templates == NULL )
-+		return TRUE;
-+	return FALSE;
-+}
-+
-+// [cb] mark deleted templates for removal, reevaluate changed
-+static gboolean reevaluate_template ( gpointer key, gpointer value, gpointer udata )
-+{
-+	template_t * template = value;
-+
-+	if ( ! template -> in_use )
-+		return TRUE;
-+
-+	if ( template -> changed ) {
-+		const gchar * expression = settings_opt_get ( template -> name );
-+		gchar       * expansion  = NULL;
-+		// release guards (but do not free them)
-+		g_slist_foreach ( template -> guards, tmpl_unguard, template );
-+		g_slist_free ( template -> guards );
-+		template -> guards = NULL;
-+		// re-install guards & get updated expansion
-+		if ( expression != NULL ) {
-+			GError *error = NULL;
-+			expansion = tmpl_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
-+			if ( error != NULL ) {
-+				scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on template '%s': %s.\nExpansion stopped at: '%s'", template -> name, error -> message, expansion );
-+				g_error_free ( error );
-+				g_free ( expansion );
-+				expansion = NULL;
-+			}
-+		}
-+		// re-install guard on template itself
-+		tmpl_install_guard ( g_strdup ( template -> name ), template, tmpl_guard );
-+		template -> changed = FALSE;
-+		// pass result to callback
-+		if ( g_strcmp0 ( expansion, template -> prev_expansion ) ) {
-+			g_free ( template -> prev_expansion );
-+			template -> prev_expansion = expansion;
-+			template -> callback ( expansion, template -> userdata );
-+		} else
-+			g_free ( expansion );
-+	}
-+
-+	return FALSE;
-+}
-+
-+// [idle cb] update commands/guards & call cbs when necessary
-+static gboolean reevaluate_templates ( gpointer data )
-+{
-+	// allow reschedule in a process of reevaluation
-+	tmpl_attached_id = 0;
-+	// drop removed & reevaluate changed templates
-+	g_hash_table_foreach_remove ( tmpl_templates, reevaluate_template, NULL );
-+	// free unused guards TODO do only when needed
-+	g_hash_table_foreach_remove ( tmpl_guards, tmpl_drop_unused_guards, NULL );
-+	// always return false, this is oneshot idle call
-+	return FALSE;
-+}
-+
-+// schedule templates reevaluation
-+static void tmpl_schedule_rerun ( void )
-+{
-+	if ( tmpl_attached_id == 0 ) {
-+		GSource * source = g_idle_source_new ();
-+		g_source_set_callback ( source, reevaluate_templates, NULL, NULL );
-+		tmpl_attached_id = g_source_attach ( source, main_context );
-+		g_source_unref ( source );
-+	}
-+}
-+
-+// [cb] sets changed flag on template
-+static void template_set_changed ( gpointer data, gpointer udata )
-+{
-+	template_t * template = data;
-+	template -> changed   = TRUE;
-+}
-+
-+// [guard] generic guard for variable
-+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value )
-+{
-+	if ( g_strcmp0 ( new_value, settings_opt_get ( key ) ) ) {
-+		// mark dependent commands as modified
-+		tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, key );
-+		g_slist_foreach ( guard -> templates, template_set_changed, NULL );
-+		// schedule execution of modified commands
-+		tmpl_schedule_rerun ();
-+	}
-+	return g_strdup ( new_value );
-+}
-+
-+// public
-+gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata )
-+{
-+	g_assert ( name != NULL && callback != NULL );
-+	// check for existing template
-+	template_t * template = g_hash_table_lookup ( tmpl_templates, name );
-+	if ( template != NULL )
-+		return FALSE;
-+	// create new
-+	template = g_slice_new ( template_t );
-+	template -> name           = g_strdup ( name );
-+	template -> callback       = callback;
-+	template -> userdata       = udata;
-+	template -> in_use         = TRUE;
-+	template -> guards         = NULL;
-+	template -> changed        = TRUE;
-+	template -> prev_expansion = NULL;
-+	// schedule reevaluation
-+	tmpl_schedule_rerun ();
-+	return TRUE;
-+}
-+
-+// public
-+void template_set_in_use ( const gchar * name, gboolean in_use )
-+{
-+	g_assert ( name != NULL );
-+	template_t * template = g_hash_table_lookup ( tmpl_templates, name );
-+	g_assert ( template != NULL );
-+	template -> in_use = in_use;
-+}
-+
-+// private
-+void templates_init ( void )
-+{
-+	// the key will be freed by destruction cb
-+	tmpl_guards    = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_guard );
-+	tmpl_templates = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_template );
-+}
-+
-+// private
-+void templates_uninit ( void )
-+{
-+	if ( tmpl_attached_id != 0 )
-+		g_source_remove ( tmpl_attached_id );
-+	g_hash_table_destroy ( tmpl_templates );
-+	g_hash_table_destroy ( tmpl_guards );
-+}
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 456d83274014 mcabber/mcabber/templates.h
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/templates.h	Fri Jul 20 17:30:20 2012 +0300
-@@ -0,0 +1,44 @@
-+
-+/* Copyright 2012 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>
-+
-+// Type for template callback
-+typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
-+
-+//  success template_add ( option name, cb, cb udata )
-+// Adds given mcabber option to list of watched templates.
-+// If any option, used in that template (or template itself) will change,
-+// callback will be called with new expansion of template.
-+gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata );
-+
-+//  template_set_in_use ( option name, used flag )
-+// Marks template as (un)used.
-+// Note: Template will be actually removed only on next evaluation run,
-+// though call to this function schedules such run. This way, you can
-+// mark a bunch of templates as unused and then mark some of them as used.
-+void template_set_in_use ( const gchar * name, gboolean in_use );
-+
-+// XXX do we need this?
-+// void tmpl_schedule_rerun ( void );
-+
-+// private
-+void templates_init ( void );
-+void templates_uninit ( void );
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 456d83274014 mcabber/mcabber/utils.c
---- a/mcabber/mcabber/utils.c	Fri Jul 20 17:29:53 2012 +0300
-+++ b/mcabber/mcabber/utils.c	Fri Jul 20 17:30:20 2012 +0300
-@@ -650,6 +650,42 @@
-   g_free(arglst);
- }
- 
-+//  parse_list(arg, cb, udata)
-+// Calls cb for every element in space/semicolon/comma-separated list.
-+// Designed to work in-place, so, no escapes, quoting etc.
-+// Terminates parsing if callback returns false.
-+void parse_list(const char *arg, parse_list_cb_t cb, void *udata)
-+{
-+  const char *p, *start;
-+  enum {
-+    in_space,
-+    in_string,
-+  } state;
-+
-+  if (!arg) 
-+    return;
-+
-+  state = in_space;
-+  while ( *p ) {
-+    if ( *p == ' ' || *p == ';' || *p == ',' ) {
-+      if ( state == in_string ) {
-+        if ( ! cb ( start, p, udata ) )
-+          return;
-+        state = in_space;
-+      }
-+    } else {
-+      if ( state == in_space ) {
-+        start = p;
-+        state = in_string;
-+      }
-+    }
-+    p ++;
-+  }
-+
-+  if ( state == in_string )
-+    cb ( start, p, udata );
-+}
-+
- //  replace_nl_with_dots(bufstr)
- // Replace '\n' with "(...)" (or with a NUL if the string is too short)
- void replace_nl_with_dots(char *bufstr)
-diff -r 456d83274014 mcabber/mcabber/utils.h
---- a/mcabber/mcabber/utils.h	Fri Jul 20 17:29:53 2012 +0300
-+++ b/mcabber/mcabber/utils.h	Fri Jul 20 17:30:20 2012 +0300
-@@ -43,6 +43,11 @@
- char **split_arg(const char *arg, unsigned int n, int dontstriplast);
- void free_arg_lst(char **arglst);
- 
-+/* fast in-place string split on space/semicolon/comma
-+ * stops processing if callback returns false value */
-+typedef int (*parse_list_cb_t)(const char *start, const char *end, void *udata);
-+void parse_list(const char *arg, parse_list_cb_t cb, void *udata);
-+
- void replace_nl_with_dots(char *bufstr);
- char *ut_expand_tabs(const char *text);
- 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,1346 @@
+# HG changeset patch
+# Parent 456d832740146c5c9a0428c9ca0a1a0c431481b0
+[work-in-progress] Use templates for statusbars
+
+diff -r 456d83274014 mcabber/CMakeLists.txt
+--- a/mcabber/CMakeLists.txt	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/CMakeLists.txt	Fri Jul 20 17:30:20 2012 +0300
+@@ -154,8 +154,8 @@
+ 
+ ## Define targets
+ set ( mcabber_SUBSYSTEMS
+-	caps commands compl events hbuf help histolog hooks
+-	modules nohtml otr pgp roster screen settings utf8 utils
++	caps commands compl events hbuf help histolog hooks modules
++	nohtml otr parser pgp roster screen settings templates utf8 utils
+ 	xmpp xmpp_helper xmpp_iq xmpp_iqrequest xmpp_muc xmpp_s10n )
+ if ( NOT MODULES_ENABLE )
+ 	list ( APPEND mcabber_SUBSYSTEMS extcmd fifo )
+diff -r 456d83274014 mcabber/mcabber/parser.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/parser.c	Fri Jul 20 17:30:20 2012 +0300
+@@ -0,0 +1,853 @@
++
++/* Copyright 2012 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 <ctype.h>   // is*
++#include <stdlib.h>  // strtol
++#include <string.h>  // strchr
++
++#include "parser.h"
++
++#include "config.h"
++
++//
++//  Private types
++//
++
++typedef enum {
++	in_string,
++	in_escape,
++	in_var,
++	in_plainvarstart,
++	in_plainvar,
++	in_prevarname,
++	in_varnamestart,
++	in_varname,
++	in_varchop,
++	in_varstrip,
++	in_varstripchop,
++	in_varslash,
++	in_varmatch,
++	in_varpresubst,
++	in_varsubst,
++	in_varcolon,
++	in_varminus,
++	in_varplus,
++	in_varpos,
++	in_postvarpos,
++	in_varlen,
++	in_varsubstring,
++	in_varend,
++	in_evalstart,
++	in_mathexpand,
++	in_dblparen_eol,
++	nostate,
++} tmpl_parser_state_t;
++
++const static char * const tmpl_statenames [ ] = {
++	"in string",
++	"in escape",
++	"in var",
++	"in plainvarstart",
++	"in plainvar",
++	"in prevarname",
++	"in varnamestart",
++	"in varname",
++	"in varchop",
++	"in varstrip",
++	"in varstripchop",
++	"in varslash",
++	"in varmatch",
++	"in varpresubst",
++	"in varsubst",
++	"in varcolon",
++	"in varminus",
++	"in varplus",
++	"in varpos",
++	"in postvarpos",
++	"in varlen",
++	"in varsubstring",
++	"in varend",
++	"in evalstart",
++	"in mathexpand",
++	"in dblparen eol",
++	"wrong state",
++};
++
++typedef enum {
++	no_eol         =  0x0,
++	brace_eol      =  0x1,
++	slash_eol      =  0x2,
++	colon_eol      =  0x4,
++	paren_eol      =  0x8,
++	dblparen_eol   = 0x10,
++	balance_parens = 0x20,
++} tmpl_parser_flags_t;
++
++typedef enum {
++	no_operation,
++	expand_var,
++	value_length,
++} tmpl_var_operation_t;
++
++typedef enum {
++	no_op,
++	noempty_op,
++	paren_op,
++	plus_op,
++	minus_op,
++	remainder_op,
++	division_op,
++	multiplication_op,
++} tmpl_math_op_t;
++
++typedef enum {
++	in_preargument,
++	in_hexoctzero,
++	in_number,
++	in_variable,
++	in_postparen,
++	in_op,
++} tmpl_math_state_t;
++
++//
++//  Code
++//
++
++GQuark tmpl_gerror_quark ( void )
++{
++	return g_quark_from_static_string ( "tmpl_gerror_quark" );
++}
++
++// match tmpl_glob ( string, pattern, greedy, right-to-left )
++// Performs matching of given pattern against given string,
++// returns first substring in input, that matched. In pattern,
++// there are currently two tokens recognized: '*' and '?'.
++const gchar *tmpl_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
++{
++	*ret_len = 0;
++	if ( rtl ) { // rtl
++		const char *p           = pat + patlen - 1;
++		const char *s           = str + length - 1;
++		const char *matchend    = NULL;
++		const char *patrollback = p;
++		const char *strrollback = s;
++		while ( p >= pat && s >= str ) {
++			if ( *p == '*' ) {
++				if ( matchend == NULL && patrollback != NULL )
++					matchend = strrollback;
++				patrollback = NULL;
++				strrollback = NULL;
++				p --;
++			} else if ( *p == '?' || *p == *s ) {
++				if ( patrollback == NULL ) {
++					patrollback = p;
++					strrollback = s;
++				}
++				p --;
++				s --;
++			} else if ( patrollback != NULL ) {
++				p =    patrollback;
++				s = -- strrollback;
++			} else {
++				s --;
++			}
++		}
++		// now s, p or both have finished run
++		if ( s >= str && patrollback == NULL ) // s remains and final star
++			s = str - 1; // value at the end of loop
++		if ( p >= pat ) { // p remains
++			while ( *p == '*' && p >= pat ) // ignore empty stars
++				p --;
++			if ( p >= pat ) // symbols remain in pattern, no match
++				return NULL;
++		}
++		if ( matchend == NULL && patrollback != NULL ) // no stars
++			matchend = strrollback;
++		if ( matchend != NULL ) {
++			*ret_len = matchend - s;
++			return s + 1;
++		} else
++			return NULL;
++	} else { // ltr
++		const char * const  pe          = pat + patlen;
++		const char * const  se          = str + length;
++		const char         *p           = pat;
++		const char         *s           = str;
++		const char         *matchstart  = NULL;
++		const char         *patrollback = p;
++		const char         *strrollback = s;
++		while ( p < pe && s < se ) {
++			if ( *p == '*' ) {
++				if ( matchstart == NULL && patrollback != NULL )
++					matchstart = strrollback;
++				patrollback = NULL;
++				strrollback = NULL;
++				p ++;
++			} else if ( *p == '?' || *p == *s ) {
++				if ( patrollback == NULL ) {
++					patrollback = p;
++					strrollback = s;
++				}
++				p ++;
++				s ++;
++			} else if ( patrollback != NULL ) { // start/nomatch-recovery eat
++				p =    patrollback;
++				s = ++ strrollback;
++			} else { // star-eat
++				s ++;
++			}
++		}
++		// now s, p or both have finished run
++		if ( s < se && patrollback == NULL ) // s remains and final star
++				s = se;
++		if ( p < pe ) { // p remains
++			while ( *p == '*' && p < pe ) // ignore empty stars
++				p ++;
++			if ( p < pe ) // symbols remain in pattern, no match
++				return NULL;
++		}
++		if ( matchstart == NULL && patrollback != NULL ) // no stars
++			matchstart = strrollback;
++		if ( matchstart != NULL )
++			*ret_len = s - matchstart;
++		return matchstart;
++	}
++}
++
++// match tmpl_greedy_glob ( string, pattern, right-to-left )
++// The same as above, but greedy.
++const gchar *tmpl_greedy_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
++{
++	const char * const  pe = pat + patlen;
++	const char         *p  = pat;
++	while ( p < pe ) {
++		if ( *p == '*' ) {
++			gsize       llen = 0;
++			const char *left = tmpl_glob ( str, length, pat, p - pat, FALSE, &llen );
++			if ( left != NULL ) {
++				gsize       rlen  = 0;
++				const char *right = tmpl_glob ( left + llen, str + length - ( left + llen ), p + 1, pe - p - 1, TRUE, &rlen );
++				if ( right != NULL ) {
++					*ret_len = right + rlen - left;
++					return left;
++				}
++			}
++			return NULL;
++		}
++		p ++;
++	}
++	return tmpl_glob ( str, length, pat, patlen, rtl, ret_len );
++}
++
++// - + * / % ( )
++static gssize tmpl_math_expand_internal ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **error, tmpl_math_op_t bound, gsize *proc_len )
++{
++	g_assert ( error != NULL && *error == NULL );
++
++	gssize             result   = 0;
++	const char *       p        = str;
++	const char * const e        = str + length;
++	tmpl_math_state_t   state    = in_preargument;
++	const char *       varname  = NULL;  // variable name start
++	gsize              base     = 10;    // number base
++	gboolean           invert   = FALSE; // number change sign
++	gboolean           errstate = bound >= noempty_op; // indicates erroneous state
++
++	// process first argument
++	while ( p < e && *error == NULL ) {
++		if ( state == in_preargument ) { // initial space-skipping state
++			if ( isspace ( *p ) )
++				p ++;
++			else if ( *p == '-' ) {
++				invert = ! invert;
++				errstate = TRUE;
++				p ++;
++			} else if ( *p == '+' ) {
++				errstate = TRUE;
++				p ++;
++			} else if ( *p == '(' ) {
++				gsize processed = 0;
++				p ++;
++				result = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, paren_op, &processed );
++				if ( *error == NULL )
++					state = in_postparen;
++				errstate  = TRUE;
++				p        += processed;
++			} else if ( *p == '0' ) {
++				result   = 0;
++				errstate = FALSE;
++				state    = in_hexoctzero;
++				p ++;
++			} else if ( isdigit ( *p ) ) {
++				result   = 0;
++				base     = 10;
++				state    = in_number;
++			} else if ( isalpha ( *p ) || *p == '_' ) {
++				state    = in_variable;
++				errstate = FALSE;
++				varname  = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGARG, "Wrong symbol in math argument" );
++		} else if ( state == in_hexoctzero ) {
++			if ( *p == 'x' ) {
++				errstate = TRUE;
++				base     = 16;
++				p ++;
++			} else
++				base = 8;
++			state  = in_number;
++		} else if ( state == in_number ) {
++			short d = -1;
++			if ( *p == '0' )
++				d = 0;
++			else if ( *p == '1' )
++				d = 1;
++			else if ( *p == '2' )
++				d = 2;
++			else if ( *p == '3' )
++				d = 3;
++			else if ( *p == '4' )
++				d = 4;
++			else if ( *p == '5' )
++				d = 5;
++			else if ( *p == '6' )
++				d = 6;
++			else if ( *p == '7' )
++				d = 7;
++			else if ( base <= 8 )
++				;
++			else if ( *p == '8' )
++				d = 8;
++			else if ( *p == '9' )
++				d = 9;
++			else if ( base <= 10 )
++				;
++			else if ( *p == 'a' || *p == 'A' )
++				d = 10;
++			else if ( *p == 'b' || *p == 'B' )
++				d = 11;
++			else if ( *p == 'c' || *p == 'C' )
++				d = 12;
++			else if ( *p == 'd' || *p == 'D' )
++				d = 13;
++			else if ( *p == 'e' || *p == 'E' )
++				d = 14;
++			else if ( *p == 'f' || *p == 'F' )
++				d = 15;
++			if ( d >= 0 ) {
++				errstate = FALSE;
++				result   = result * base + d;
++				p ++;
++			} else if ( ! errstate )
++				state = in_op;
++			else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGNUMBER, "Too short math number" );
++		} else if ( state == in_variable ) {
++			if ( ! ( isalnum ( *p ) || *p == '_' ) ) { // XXX vars with '-' have to be expanded earlier
++				gsize        vlen  = 0;
++				const char * value = get_val ( varname, p - varname, udata, &vlen );
++				// TODO control recursion level
++				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL )
++					state = in_op;
++			} else
++				p ++;
++		} else if ( state == in_postparen ) {
++			if ( *p == ')' ) {
++				errstate = FALSE;
++				p ++;
++				state    = in_op;
++			} else
++				g_assert_not_reached ();
++		} else if ( state == in_op )
++			break;
++		else
++			g_assert_not_reached ();
++	}
++
++	if ( *error == NULL ) {
++		// in_preargument - empty argument, possibly +-+- - 0 / errstate
++		// in_hexoctzero  - 0                             - 0
++		// in_number      - 0, 0x, 01, 0x1, 1             - failure at 0x (errstate), invert
++		// in_variable    - varname                       - get var, invert
++		// in_postparen   - eof                           - failure (errstate)
++		// in_op          - ok                            - invert, continue parsing
++		if ( errstate )
++			g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGSTATE, "Wrong state at the end of first math argument parsing" );
++		else {
++			if ( state == in_op ) {
++				if ( invert )
++					result = - result;
++			
++				// process operator+second argument pairs
++				while ( p < e && *error == NULL ) {
++					tmpl_math_op_t op = no_op;
++					if ( isspace ( *p ) )
++						p ++;
++					else if ( *p == '+' ) // set op
++						op = plus_op;
++					else if ( *p == '-' )
++						op = minus_op;
++					else if ( *p == '/' )
++						op = division_op;
++					else if ( *p == '*' )
++						op = multiplication_op;
++					else if ( *p == '%' )
++						op = remainder_op;
++					else if ( *p == ')' ) {
++						if ( bound < paren_op )
++							g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHUNBALPAREN, "Unbalanced parens in math expression" );
++						else
++							break;
++					} else
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGOP, "Wrong math operator '%c'", *p );
++
++					if ( op != no_op ) {
++						// lower priority operator, returning result
++						if ( op < bound )
++							break;
++
++						p ++;
++						gsize  processed = 0;
++						gssize arg       = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, op, &processed );
++						if ( *error == NULL ) {
++							if ( op == plus_op )
++								result += arg;
++							else if ( op == minus_op )
++								result -= arg;
++							else if ( op == division_op )
++								result /= arg;
++							else if ( op == multiplication_op )
++								result *= arg;
++							else if ( op == remainder_op )
++								result %= arg;
++							else
++								g_assert_not_reached ();
++						}
++						p += processed;
++					}
++				}
++			} else if ( state == in_variable ) {
++				gsize        vlen  = 0;
++				const char * value = get_val ( varname, p - varname, udata, &vlen );
++				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL && invert )
++					result = - result;
++			} else if ( invert )
++				result = - result;
++			// XXX may add long conditional with g_assert here, but I'm lazy
++		}
++	}
++
++	// return result
++	if ( proc_len != NULL )
++		*proc_len = p - str;
++	return result;
++}
++
++// result tmpl_math_expand ( plainexpanded string, callback )
++// Performs mathematical expansion on given string.
++// Note, that it does not perform variable/quote/etc expansion,
++// if you want it, you should do that first.
++gssize tmpl_math_expand ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **ret_err )
++{
++	g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, -1 ); // XXX
++
++	GError * error  = NULL;
++	gssize   result = tmpl_math_expand_internal ( str, length, get_val, udata, &error, no_op, NULL );
++	if ( error != NULL )
++		g_propagate_error ( ret_err, error );
++	return result;
++}
++
++// expansion tmpl_expand ( template, callback )
++// Parse template, substitute shell-like expressions:
++// * $var ${var}
++// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
++// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
++// * ${var:+tmpl} ${var:-tmpl}
++// * ${var:uint} ${var:uint:uint}
++// * ${#var} ${!var} ${!var[operation]}
++// * \n \t \e \$ \\ \X
++// Callback will be called to obtain variable values.
++// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
++// For pattern rules see tmpl_glob ().
++static gchar *tmpl_expand_internal ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error, tmpl_parser_flags_t flags, gsize *proc_len )
++{
++	g_assert ( error != NULL && *error == NULL );
++
++	if ( template == NULL || len == 0 )
++		return NULL;
++
++	GString            *result     = g_string_new ( NULL );
++	const char         *p          = template;
++	const char * const  e          = template + len;
++	tmpl_parser_state_t  state      = in_string;
++	const char         *a1s        = p;     // string, varname
++	const gchar        *value      = NULL;  // variable value
++	gsize               vlen       = 0;     // variable value length
++	tmpl_var_operation_t operation  = no_operation;
++	gchar              *pattern    = NULL;
++	gsize               plen       = 0;
++	gboolean            rtl        = FALSE; // strip/chop
++	gboolean            greedy     = FALSE; // strip/chop
++	gboolean            multiglob  = FALSE; // replace
++	gsize               parencount = 0;
++	gssize              subsoffset = -1;
++	gssize              subslength = -1;
++
++	while ( p < e && *error == NULL ) {
++		if ( state == in_string ) { // nothing special
++			if ( *p == '\\' ) { // escape next char
++				g_string_append_len ( result, a1s, p - a1s );
++				state = in_escape;
++			} else if ( *p == '$' ) { // start variable
++				g_string_append_len ( result, a1s, p - a1s );
++				state = in_var;
++			} else if ( flags & balance_parens && *p == '(' ) {
++				parencount ++;
++			} else if ( *p == ')' ) {
++				if ( flags & balance_parens && parencount > 0 ) {
++					parencount --;
++				} else if ( flags & dblparen_eol ) {
++					g_string_append_len ( result, a1s, p - a1s );
++					state = in_dblparen_eol;
++				} else if ( flags & balance_parens )
++					g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
++			} else if ( ( flags & brace_eol && *p == '}' ) ||
++			            ( flags & slash_eol && *p == '/' ) ||
++						( flags & colon_eol && parencount == 0 && *p == ':' ) )
++				break;
++			p ++;
++		} else if ( state == in_escape ) { // escape (on escaped char)
++			if ( *p == 'n' )
++				g_string_append_c ( result, '\n' );
++			else if ( *p == 't' )
++				g_string_append_c ( result, '\t' );
++			else if ( *p == 'e' ) // for some experimentation with colors...
++				g_string_append_c ( result, '\033' );
++			else
++				g_string_append_c ( result, *p );
++			p ++;
++			state = in_string;
++			a1s   = p;
++		} else if ( state == in_var ) { // some variable
++			if ( *p == '{' ) { // enclosed variable
++				state = in_prevarname;
++				p ++;
++			} else if ( *p == '(' ) {
++				state = in_evalstart;
++				p ++;
++			} else // unenclosed variable
++				state = in_plainvarstart;
++		} else if ( state == in_plainvarstart ) {
++			if ( isalpha ( *p ) || *p == '_' ) {
++				state = in_plainvar;
++				a1s   = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at the start of variable name" );
++		} else if ( state == in_plainvar ) { // unenclosed variable
++			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
++				gsize        vlen  = 0;
++				const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
++				g_string_append_len ( result, value, vlen );
++				state = in_string;
++				a1s   = p;
++			} else
++				p ++;
++		} else if ( state == in_prevarname ) { // allow ! and # at varname start
++			if ( *p == '!' ) {
++				operation = expand_var;
++				p++;
++			} else if ( *p == '#' ) {
++				operation = value_length;
++				p++;
++			} else
++				operation = no_operation;
++			state = in_varnamestart;
++		} else if ( state == in_varnamestart ) {
++			if ( isalpha ( *p ) || *p == '_' ) {
++				state = in_varname;
++				a1s   = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at start of variable name" );
++		} else if ( state == in_varname ) { // enclosed variable name
++			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
++				value = get_val ( a1s, p - a1s, udata, &vlen );
++				if ( operation == value_length ) {
++					if ( *p == '}' ) {
++						g_string_append_printf ( result, "%lu", vlen );
++						state = in_varend;
++					} else
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
++				} else {
++					if ( operation == expand_var && value != NULL && vlen > 0 )
++						value = get_val ( value, vlen, udata, &vlen );
++					if ( *p == '}' ) { // end of variable
++						g_string_append_len ( result, value, vlen );
++						state = in_varend;
++					} else if ( *p == '#' ) { // strip expression
++						state = in_varstrip;
++						p ++;
++					} else if ( *p == '%' ) { // chop expression
++						state = in_varchop;
++						p ++;
++					} else if ( *p == '/' ) { // replace expression
++						state = in_varslash;
++						p ++;
++					} else if ( *p == ':' ) { // substring expression
++						state = in_varcolon;
++						p ++;
++					} else // wrong symbols
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
++				}
++			} else
++				p ++;
++		} else if ( state == in_varstrip ) { // one of strip expressions
++			if ( *p == '#' ) {
++				greedy = TRUE;
++				p ++;
++			}
++			rtl   = FALSE;
++			state = in_varstripchop;
++		} else if ( state == in_varchop ) { // one of chop expressions
++			if ( *p == '%' ) {
++				greedy = TRUE;
++				p ++;
++			}
++			rtl   = TRUE;
++			state = in_varstripchop;
++		} else if ( state == in_varstripchop ) { // pattern expressions
++			gsize  elen      = 0;
++			gsize  processed = 0;
++			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
++			if ( *error == NULL ) {
++				gsize       mlen  = 0;
++				const char *match = NULL;
++				if ( greedy )
++					match = tmpl_greedy_glob ( value, vlen, expansion, elen, rtl, &mlen );
++				else
++					match = tmpl_glob ( value, vlen, expansion, elen, rtl, &mlen );
++				if ( mlen > 0 ) {
++					if ( rtl ) { // % and %%
++						if ( match + mlen == value + vlen )
++							vlen -= mlen;
++					} else if ( match == value ) { // # and ##
++						value  = match + mlen;
++						vlen  -= mlen;
++					}
++				}
++				g_string_append_len ( result, value, vlen );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varend;
++		} else if ( state == in_varslash ) { // replace expression
++			multiglob = FALSE;
++			rtl       = FALSE;
++			if ( *p == '#' )
++				p ++;
++			else if ( *p == '%' ) {
++				rtl = TRUE;
++				p ++;
++			} else if ( *p == '/' ) {
++				multiglob = TRUE;
++				p ++;
++			}
++			state = in_varmatch;
++		} else if ( state == in_varmatch ) { // match part of replace expression
++			gsize processed  = 0;
++			pattern          = tmpl_expand_internal ( p, e - p, get_val, udata, &plen, error, brace_eol | slash_eol, &processed );
++			p               += processed;
++			state            = in_varpresubst;
++		} else if ( state == in_varpresubst ) { // skip slash
++			if ( *p == '/' )
++				p ++;
++			state = in_varsubst;
++		} else if ( state == in_varsubst ) { // replace expression
++			gsize  processed = 0;
++			gsize  slen      = 0;
++			gchar *subst     = tmpl_expand_internal ( p, e - p, get_val, udata, &slen, error, brace_eol, &processed );
++			if ( *error == NULL ) {
++				const char       * start = value;
++				const char * const ve    = value + vlen;
++				while ( start < ve ) {
++					gsize       mlen  = 0;
++					const char *match = tmpl_glob ( start, ve - start, pattern, plen, rtl, &mlen );
++					if ( mlen > 0 ) {
++						g_string_append_len ( result, start, match - start );
++						g_string_append_len ( result, subst, slen );
++						start = match + mlen;
++					} else
++						break;
++					if ( ! multiglob )
++						break;
++				}
++				g_string_append_len ( result, start, ve - start );
++			}
++			g_free ( subst );
++			g_free ( pattern );
++			pattern  = NULL;
++			p       += processed;
++			state    = in_varend;
++		} else if ( state == in_varcolon ) { // substring or substitution expression
++			if ( *p == '-' ) {
++				state = in_varminus;
++				p ++;
++			} else if ( *p == '+' ) {
++				state = in_varplus;
++				p ++;
++			} else {
++				subsoffset = -1;
++				subslength = -1;
++				state      = in_varpos;
++			}
++		} else if ( state == in_varminus || state == in_varplus ) { // zero and non-zero substitution
++			gsize        elen      = 0;
++			gsize        processed = 0;
++			gchar       *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
++			if ( *error == NULL ) {
++				if ( ( vlen == 0 && state == in_varminus ) || ( vlen > 0 && state == in_varplus ) )
++					g_string_append_len ( result, expansion, elen );
++				else if ( vlen > 0 && state == in_varminus )
++					g_string_append_len ( result, value, vlen );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varend;
++		} else if ( state == in_varpos ) { // substring expression
++			gsize  elen      = 0;
++			gsize  processed = 0;
++			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, colon_eol | brace_eol | balance_parens, &processed );
++			if ( *error == NULL ) {
++				subsoffset = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( subsoffset < 0 )
++					subsoffset = 0;
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_postvarpos;
++		} else if ( state == in_postvarpos ) {
++			if ( *p == ':' ) {
++				state = in_varlen;
++				p ++;
++			} else if ( *p == '}' )
++				state = in_varsubstring;
++			else
++				g_assert_not_reached ();
++		} else if ( state == in_varlen ) {
++			gsize  elen      = 0;
++			gsize  processed = 0;
++			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol | balance_parens, &processed );
++			if ( *error == NULL ) {
++				subslength = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( subslength < 0 )
++					subslength = 0;
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varsubstring;
++		} else if ( state == in_varsubstring ) {
++			g_assert ( subsoffset >= 0 );
++			if ( subslength == -1 )
++				subslength = vlen;
++			if ( subsoffset < vlen ) {
++				if ( subsoffset + subslength > vlen )
++					g_string_append_len ( result, value + subsoffset, vlen - subsoffset );
++				else
++					g_string_append_len ( result, value + subsoffset, subslength );
++			}
++			state = in_varend;
++		} else if ( state == in_varend ) { // end of enclosed variable
++			if ( *p == '}' ) {
++				p ++;
++				state = in_string;
++				a1s   = p;
++			} else
++				g_assert_not_reached (); // this state must be used only on '}' or EOL
++		} else if ( state == in_evalstart ) {
++			if ( *p == '(' ) {
++				p ++;
++				state = in_mathexpand;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Command evaluation is not supported" );
++		} else if ( state == in_mathexpand ) {
++			gsize  elen      = 0;
++			gsize  processed = 0;
++			gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, dblparen_eol | balance_parens, &processed );
++			if ( *error == NULL ) {
++				gssize value = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL )
++					g_string_append_printf ( result, "%li", value );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_string;
++			a1s    = p;
++		} else if ( state == in_dblparen_eol ) {
++			if ( *p == ')' ) {
++				p ++;
++				break;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
++		} else
++			g_assert_not_reached ();
++	}
++
++	if ( *error == NULL ) {
++		if ( state == in_plainvar ) { // plain variable at the end of template
++			gsize        vlen  = 0;
++			const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
++			g_string_append_len ( result, value, vlen );
++		} else if ( state == in_string ) // end of string
++			g_string_append_len ( result, a1s, p - a1s );
++		else if ( state != in_dblparen_eol )
++			g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGENDSTATE, "Parser stopped in incorrect state '%s'", state < nostate ? tmpl_statenames [ state ] : "wrong state" );
++	}
++
++	if ( pattern != NULL )
++		g_free ( pattern );
++
++	if ( proc_len != NULL )
++		*proc_len = p - template;
++	if ( ret_len != NULL )
++		*ret_len = result -> len;
++	return g_string_free ( result, FALSE );
++}
++
++//
++//  public wrappers
++//
++
++gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **ret_err )
++{
++	g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
++
++	GError * error  = NULL;
++	gchar *  result = tmpl_expand_internal ( template, len, get_val, udata, ret_len, &error, no_eol, NULL );
++	if ( error != NULL )
++		g_propagate_error ( ret_err, error );
++	return result;
++}
++
++/* vim: se ts=4 sw=4: */
+diff -r 456d83274014 mcabber/mcabber/parser.h
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/parser.h	Fri Jul 20 17:30:20 2012 +0300
+@@ -0,0 +1,77 @@
++
++#ifndef MCABBER_PARSER_H
++#define MCABBER_PARSER_H
++
++/* Copyright 2012 Myhailo Danylenko
++ *
++ * This file is part of mcabber
++ *
++ * mcabber 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>
++
++#define TMPL_GERROR_QUARK tmpl_gerror_quark ()
++
++#define TMPL_EWRONGVAREXP   ( 0x01 )
++#define TMPL_EWRONGVARNAME  ( 0x03 )
++#define TMPL_EUNBALPAREN    ( 0x04 )
++#define TMPL_EWRONGENDSTATE ( 0x02 )
++
++#define TMPL_EMATHWRONGARG    ( 0x05 )
++#define TMPL_EMATHWRONGNUMBER ( 0x06 )
++#define TMPL_EMATHUNBALPAREN  ( 0x07 )
++#define TMPL_EMATHWRONGOP     ( 0x08 )
++#define TMPL_EMATHWRONGSTATE  ( 0x09 )
++
++typedef const char *(*tmpl_variable_callback_t)   ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
++
++GQuark tmpl_gerror_quark ( void );
++
++//  match tmpl_glob ( string, pattern, right-to-left )
++// Performs matching of given pattern against given string,
++// returns substring, that matched. In pattern, there are currently
++// two tokens recognized: '*' and '?'. Two flags determine, if
++// '*' should be greedy and which end of string must match the
++// pattern (i.e. have anchor).
++const char *tmpl_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
++
++//  match tmpl_greedy_glob ( string, pattern, rigt-to-left )
++// The same, as above, but greedy.
++const char *tmpl_greedy_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
++
++//  result tmpl_math_expand ( string, callback )
++// Performs mathematical expansion of given string.
++// Note, that $var expressions are not recognized, you have to
++// supply already expanded string here.
++// Supported operators:
++// ( ) + - * / %
++gssize tmpl_math_expand ( const char *str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError **error );
++
++//  expansion tmpl_expand ( template, callback )
++// Parse template, substitute shell-like expressions:
++// * $var ${var}
++// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
++// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
++// * ${var:+tmpl} ${var:-tmpl}
++// * ${var:mathexp} ${var:mathexp:mathexp}
++// * ${#var} ${!var} ${!var[operation]}
++// * $(( mathexp ))
++// * \n \t \e \$ \\ \X
++// Callback will be called to obtain variable values.
++// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
++// For pattern rules see mms_glob ().
++gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error );
++
++# endif
++
+diff -r 456d83274014 mcabber/mcabber/templates.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.c	Fri Jul 20 17:30:20 2012 +0300
+@@ -0,0 +1,277 @@
++
++/* Copyright 2012 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 <string.h>    // strlen
++
++#include "logprint.h"
++#include "settings.h"
++#include "main.h"      // main_context
++#include "templates.h"
++#include "parser.h"
++
++//
++//  globals
++//
++
++typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
++
++typedef struct {
++	gchar           * name;
++	tmpl_callback_t   callback;
++	gpointer          userdata;
++	gboolean          in_use;
++	GSList          * guards;
++	gboolean          changed;
++	gchar           * prev_expansion;
++} template_t;
++
++typedef struct {
++	gchar  * name;
++	GSList * templates;
++} tmpl_guard_t;
++
++static GHashTable * tmpl_templates   = NULL;
++static GHashTable * tmpl_guards      = NULL;
++static guint        tmpl_attached_id = 0;
++
++//
++//  predeclarations
++//
++
++static gchar *tmpl_guard ( const gchar *key, const gchar *new_value );
++
++//
++//  code
++//
++
++// [cb] drops template from guard's 'templates' list
++static void tmpl_unguard ( gpointer data, gpointer udata )
++{
++	tmpl_guard_t *guard    = data;
++	template_t   *template = udata;
++	guard -> templates = g_slist_remove ( guard -> templates, template );
++}
++
++// [destructor cb] releases guard hash table entry
++static void tmpl_free_guard ( gpointer data )
++{
++	tmpl_guard_t *guard = data;
++	settings_del_guard ( guard -> name );
++	g_slist_free ( guard -> templates );
++	g_free ( guard -> name );
++	g_slice_free ( tmpl_guard_t, guard );
++}
++
++// [destructor cb] releases taken guards and frees command
++static void tmpl_free_template ( gpointer data )
++{
++	template_t *template = data;
++	g_slist_foreach ( template -> guards, tmpl_unguard, template );
++	g_slist_free ( template -> guards );
++	g_free ( template -> name );
++	g_free ( template -> prev_expansion );
++	g_slice_free ( template_t, template );
++}
++
++// install guard (name must be glib-allocated string)
++static void tmpl_install_guard ( gchar *name, template_t *template, settings_guard_t callback )
++{
++	tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, name );
++	if ( guard == NULL ) {
++		if ( ! settings_set_guard ( name, callback ) ) {
++			scr_log_print ( LPRINT_LOGNORM, "Error: Unable to install guard for variable '%s' for template '%s'.", name, template -> name );
++			g_free ( name );
++		} else {
++			guard = g_slice_new ( tmpl_guard_t );
++			guard -> name      = name;
++			guard -> templates = NULL;
++			g_hash_table_replace ( tmpl_guards, guard -> name, guard ); // to be sure
++		}
++	} else
++		g_free ( name );
++	if ( ! g_slist_find ( template -> guards, guard ) ) {
++		template -> guards = g_slist_prepend ( template -> guards, guard );
++		guard -> templates = g_slist_prepend ( guard -> templates, template );
++	}
++}
++
++// [parser cb] provides mcabber option values & reinstalls guards
++static const char *tmpl_get_var ( const gchar *name, gsize len, gpointer udata, gsize *ret_len )
++{
++	const char *result = NULL;
++	if ( name != NULL && len > 0 ) {
++		template_t * template = udata;
++		gchar      * var      = g_strndup ( name, len );
++		result = settings_opt_get ( var );
++		// consumes var
++		tmpl_install_guard ( var, template, tmpl_guard );
++	}
++	if ( ret_len != NULL ) {
++		if ( result != NULL )
++			*ret_len = strlen ( result );
++		else
++			*ret_len = 0;
++	}
++	return result;
++}
++
++// [cb] mark unused guards for removal
++static gboolean tmpl_drop_unused_guards ( gpointer key, gpointer value, gpointer udata )
++{
++	tmpl_guard_t * guard = value;
++	if ( guard -> templates == NULL )
++		return TRUE;
++	return FALSE;
++}
++
++// [cb] mark deleted templates for removal, reevaluate changed
++static gboolean reevaluate_template ( gpointer key, gpointer value, gpointer udata )
++{
++	template_t * template = value;
++
++	if ( ! template -> in_use )
++		return TRUE;
++
++	if ( template -> changed ) {
++		const gchar * expression = settings_opt_get ( template -> name );
++		gchar       * expansion  = NULL;
++		// release guards (but do not free them)
++		g_slist_foreach ( template -> guards, tmpl_unguard, template );
++		g_slist_free ( template -> guards );
++		template -> guards = NULL;
++		// re-install guards & get updated expansion
++		if ( expression != NULL ) {
++			GError *error = NULL;
++			expansion = tmpl_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
++			if ( error != NULL ) {
++				scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on template '%s': %s.\nExpansion stopped at: '%s'", template -> name, error -> message, expansion );
++				g_error_free ( error );
++				g_free ( expansion );
++				expansion = NULL;
++			}
++		}
++		// re-install guard on template itself
++		tmpl_install_guard ( g_strdup ( template -> name ), template, tmpl_guard );
++		template -> changed = FALSE;
++		// pass result to callback
++		if ( g_strcmp0 ( expansion, template -> prev_expansion ) ) {
++			g_free ( template -> prev_expansion );
++			template -> prev_expansion = expansion;
++			template -> callback ( expansion, template -> userdata );
++		} else
++			g_free ( expansion );
++	}
++
++	return FALSE;
++}
++
++// [idle cb] update commands/guards & call cbs when necessary
++static gboolean reevaluate_templates ( gpointer data )
++{
++	// allow reschedule in a process of reevaluation
++	tmpl_attached_id = 0;
++	// drop removed & reevaluate changed templates
++	g_hash_table_foreach_remove ( tmpl_templates, reevaluate_template, NULL );
++	// free unused guards TODO do only when needed
++	g_hash_table_foreach_remove ( tmpl_guards, tmpl_drop_unused_guards, NULL );
++	// always return false, this is oneshot idle call
++	return FALSE;
++}
++
++// schedule templates reevaluation
++static void tmpl_schedule_rerun ( void )
++{
++	if ( tmpl_attached_id == 0 ) {
++		GSource * source = g_idle_source_new ();
++		g_source_set_callback ( source, reevaluate_templates, NULL, NULL );
++		tmpl_attached_id = g_source_attach ( source, main_context );
++		g_source_unref ( source );
++	}
++}
++
++// [cb] sets changed flag on template
++static void template_set_changed ( gpointer data, gpointer udata )
++{
++	template_t * template = data;
++	template -> changed   = TRUE;
++}
++
++// [guard] generic guard for variable
++static gchar *tmpl_guard ( const gchar *key, const gchar *new_value )
++{
++	if ( g_strcmp0 ( new_value, settings_opt_get ( key ) ) ) {
++		// mark dependent commands as modified
++		tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, key );
++		g_slist_foreach ( guard -> templates, template_set_changed, NULL );
++		// schedule execution of modified commands
++		tmpl_schedule_rerun ();
++	}
++	return g_strdup ( new_value );
++}
++
++// public
++gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata )
++{
++	g_assert ( name != NULL && callback != NULL );
++	// check for existing template
++	template_t * template = g_hash_table_lookup ( tmpl_templates, name );
++	if ( template != NULL )
++		return FALSE;
++	// create new
++	template = g_slice_new ( template_t );
++	template -> name           = g_strdup ( name );
++	template -> callback       = callback;
++	template -> userdata       = udata;
++	template -> in_use         = TRUE;
++	template -> guards         = NULL;
++	template -> changed        = TRUE;
++	template -> prev_expansion = NULL;
++	// schedule reevaluation
++	tmpl_schedule_rerun ();
++	return TRUE;
++}
++
++// public
++void template_set_in_use ( const gchar * name, gboolean in_use )
++{
++	g_assert ( name != NULL );
++	template_t * template = g_hash_table_lookup ( tmpl_templates, name );
++	g_assert ( template != NULL );
++	template -> in_use = in_use;
++}
++
++// private
++void templates_init ( void )
++{
++	// the key will be freed by destruction cb
++	tmpl_guards    = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_guard );
++	tmpl_templates = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_template );
++}
++
++// private
++void templates_uninit ( void )
++{
++	if ( tmpl_attached_id != 0 )
++		g_source_remove ( tmpl_attached_id );
++	g_hash_table_destroy ( tmpl_templates );
++	g_hash_table_destroy ( tmpl_guards );
++}
++
++/* vim: se ts=4 sw=4: */
+diff -r 456d83274014 mcabber/mcabber/templates.h
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.h	Fri Jul 20 17:30:20 2012 +0300
+@@ -0,0 +1,44 @@
++
++/* Copyright 2012 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>
++
++// Type for template callback
++typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
++
++//  success template_add ( option name, cb, cb udata )
++// Adds given mcabber option to list of watched templates.
++// If any option, used in that template (or template itself) will change,
++// callback will be called with new expansion of template.
++gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata );
++
++//  template_set_in_use ( option name, used flag )
++// Marks template as (un)used.
++// Note: Template will be actually removed only on next evaluation run,
++// though call to this function schedules such run. This way, you can
++// mark a bunch of templates as unused and then mark some of them as used.
++void template_set_in_use ( const gchar * name, gboolean in_use );
++
++// XXX do we need this?
++// void tmpl_schedule_rerun ( void );
++
++// private
++void templates_init ( void );
++void templates_uninit ( void );
++
++/* vim: se ts=4 sw=4: */
+diff -r 456d83274014 mcabber/mcabber/utils.c
+--- a/mcabber/mcabber/utils.c	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/mcabber/utils.c	Fri Jul 20 17:30:20 2012 +0300
+@@ -650,6 +650,42 @@
+   g_free(arglst);
+ }
+ 
++//  parse_list(arg, cb, udata)
++// Calls cb for every element in space/semicolon/comma-separated list.
++// Designed to work in-place, so, no escapes, quoting etc.
++// Terminates parsing if callback returns false.
++void parse_list(const char *arg, parse_list_cb_t cb, void *udata)
++{
++  const char *p, *start;
++  enum {
++    in_space,
++    in_string,
++  } state;
++
++  if (!arg) 
++    return;
++
++  state = in_space;
++  while ( *p ) {
++    if ( *p == ' ' || *p == ';' || *p == ',' ) {
++      if ( state == in_string ) {
++        if ( ! cb ( start, p, udata ) )
++          return;
++        state = in_space;
++      }
++    } else {
++      if ( state == in_space ) {
++        start = p;
++        state = in_string;
++      }
++    }
++    p ++;
++  }
++
++  if ( state == in_string )
++    cb ( start, p, udata );
++}
++
+ //  replace_nl_with_dots(bufstr)
+ // Replace '\n' with "(...)" (or with a NUL if the string is too short)
+ void replace_nl_with_dots(char *bufstr)
+diff -r 456d83274014 mcabber/mcabber/utils.h
+--- a/mcabber/mcabber/utils.h	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/mcabber/utils.h	Fri Jul 20 17:30:20 2012 +0300
+@@ -43,6 +43,11 @@
+ char **split_arg(const char *arg, unsigned int n, int dontstriplast);
+ void free_arg_lst(char **arglst);
+ 
++/* fast in-place string split on space/semicolon/comma
++ * stops processing if callback returns false value */
++typedef int (*parse_list_cb_t)(const char *start, const char *end, void *udata);
++void parse_list(const char *arg, parse_list_cb_t cb, void *udata);
++
+ void replace_nl_with_dots(char *bufstr);
+ char *ut_expand_tabs(const char *text);
+ 
--- a/update-pc	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# HG changeset patch
-# Parent ec1520e78aae04d3516b09c694ca6f3c2d4967de
-Make linking with mcabber deps optional
-
-diff -r ec1520e78aae -r 9d2c1bf277e5 mcabber/mcabber.pc.in
---- a/mcabber/mcabber.pc.in	Thu Jul 19 08:01:36 2012 +0300
-+++ b/mcabber/mcabber.pc.in	Thu Jul 19 08:01:53 2012 +0300
-@@ -5,7 +5,8 @@
- 
- Name: MCabber
- Description: Modular XMPP client
--Requires: glib-2.0 gmodule-2.0 loudmouth-1.0
-+URL: http://mcabber.com
-+Requires.private: glib-2.0 gmodule-2.0 loudmouth-1.0
- Version: @VERSION@
- Libs: 
- Cflags: -I${includedir} @LIBOTR_CFLAGS@ @GPGME_CFLAGS@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/update-pc.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,17 @@
+# HG changeset patch
+# Parent ec1520e78aae04d3516b09c694ca6f3c2d4967de
+Make linking with mcabber deps optional
+
+diff -r ec1520e78aae -r 9d2c1bf277e5 mcabber/mcabber.pc.in
+--- a/mcabber/mcabber.pc.in	Thu Jul 19 08:01:36 2012 +0300
++++ b/mcabber/mcabber.pc.in	Thu Jul 19 08:01:53 2012 +0300
+@@ -5,7 +5,8 @@
+ 
+ Name: MCabber
+ Description: Modular XMPP client
+-Requires: glib-2.0 gmodule-2.0 loudmouth-1.0
++URL: http://mcabber.com
++Requires.private: glib-2.0 gmodule-2.0 loudmouth-1.0
+ Version: @VERSION@
+ Libs: 
+ Cflags: -I${includedir} @LIBOTR_CFLAGS@ @GPGME_CFLAGS@
--- a/use-gslice	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,434 +0,0 @@
-# HG changeset patch
-# Parent 70b1f1918050fa1126a21f4772d5d644799af32b
-[work-in-progress] Use glib slices to allocate constant-size blocks
-
-diff -r 70b1f1918050 mcabber/mcabber/caps.c
---- a/mcabber/mcabber/caps.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/caps.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -52,7 +52,7 @@
-   g_hash_table_destroy(c->identities);
-   g_hash_table_destroy(c->features);
-   g_hash_table_destroy(c->forms);
--  g_free(c);
-+  g_slice_free(caps, c);
- }
- 
- void identity_destroy(gpointer data)
-@@ -61,14 +61,14 @@
-   g_free(i->category);
-   g_free(i->type);
-   g_free(i->name);
--  g_free(i);
-+  g_slice_free(identity, i);
- }
- 
- void form_destroy(gpointer data)
- {
-   dataform *f = data;
-   g_hash_table_destroy(f->fields);
--  g_free(f);
-+  g_slice_free(dataform, f);
- }
- 
- void field_destroy(gpointer data)
-@@ -97,7 +97,7 @@
- {
-   if (!hash)
-     return;
--  caps *c = g_new0(caps, 1);
-+  caps *c = g_slice_new(caps);
-   c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-   c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
-   c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
-@@ -158,7 +158,7 @@
- 
-   c = g_hash_table_lookup(caps_cache, hash);
-   if (c) {
--    identity *i = g_new0(identity, 1);
-+    identity *i = g_slice_new(identity);
- 
-     i->category = g_strdup(category);
-     i->name = g_strdup(name);
-@@ -182,7 +182,7 @@
-     return;
-   c = g_hash_table_lookup(caps_cache, hash);
-   if (c) {
--    dataform *d = g_new0(dataform, 1);
-+    dataform *d = g_slice_new(dataform);
-     char *f = g_strdup(formtype);
- 
-     d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
-diff -r 70b1f1918050 mcabber/mcabber/commands.c
---- a/mcabber/mcabber/commands.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/commands.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -114,7 +114,7 @@
-       gpointer userdata = command->userdata;
-       Commands = g_slist_delete_link(Commands, sl_cmd);
-       compl_del_category_word(COMPL_CMD, command->name);
--      g_free(command);
-+      g_slice_free(cmd, command);
-       return userdata;
-     }
-   return NULL;
-@@ -126,7 +126,7 @@
- gpointer cmd_add(const char *name, const char *help, guint flags_row1,
-                  guint flags_row2, void (*f)(char*), gpointer userdata)
- {
--  cmd *n_cmd = g_new0(cmd, 1);
-+  cmd *n_cmd = g_slice_new0(cmd);
-   strncpy(n_cmd->name, name, 32-1);
-   n_cmd->help = help;
-   n_cmd->completion_flags[0] = flags_row1;
-@@ -1579,7 +1579,7 @@
-     return NULL;
-   }
- 
--  msgbuf = g_new0(char, HBB_BLOCKSIZE);
-+  msgbuf = g_slice_alloc0(HBB_BLOCKSIZE);
-   len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd);
-   fclose(fd);
- 
-@@ -1603,7 +1603,7 @@
-   if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line...
-     scr_LogPrint(LPRINT_LOGNORM, "Message file contains "
-                  "invalid characters (%s)", filename);
--    g_free(msgbuf);
-+    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
-     return NULL;
-   }
- 
-@@ -1617,7 +1617,7 @@
-   // It could be empty, once the trailing newlines are gone
-   if (p == msgbuf && *p == '\n') {
-     scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
--    g_free(msgbuf);
-+    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
-     return NULL;
-   }
- 
-@@ -1626,7 +1626,7 @@
-   if (!msgbuf_utf8 && msgbuf)
-     scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)",
-                  filename);
--  g_free(msgbuf);
-+  g_slice_free1(HBB_BLOCKSIZE, msgbuf);
-   return msgbuf_utf8;
- }
- 
-@@ -1898,7 +1898,7 @@
-   esub   = buddy_getsubscription(bud);
-   on_srv = buddy_getonserverflag(bud);
- 
--  buffer = g_new(char, 4096);
-+  buffer = g_slice_alloc(4096);
- 
-   if (bjid) {
-     GSList *resources, *p_res;
-@@ -1997,7 +1997,7 @@
-                  type == ROSTER_TYPE_GROUP ? "group" :
-                  (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown"));
-   }
--  g_free(buffer);
-+  g_slice_free1(4096, buffer);
- 
-   // Tell the user if this item has an annotation.
-   if (type == ROSTER_TYPE_USER ||
-@@ -2045,7 +2045,7 @@
- 
-   bjid = buddy_getjid(bud);
- 
--  buffer = g_new(char, 4096);
-+  buffer = g_slice_alloc(4096);
-   strncpy(buffer, "Room members:", 127);
-   scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
- 
-@@ -2100,7 +2100,7 @@
-     g_free(p_res->data);
-   }
-   g_slist_free(resources);
--  g_free(buffer);
-+  g_slice_free1(4096, buffer);
- }
- 
- static void move_group_member(gpointer bud, void *groupnamedata)
-@@ -3091,7 +3091,7 @@
-   affil = buddy_getaffil(bud, nick);
-   realjid = buddy_getrjid(bud, nick);
- 
--  buffer = g_new(char, 4096);
-+  buffer = g_slice_alloc(4096);
- 
-   snprintf(buffer, 4095, "Whois [%s]", nick);
-   scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0);
-@@ -3121,7 +3121,7 @@
- 
-   scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0);
- 
--  g_free(buffer);
-+  g_slice_free1(4096, buffer);
-   g_free(nick);
-   if (paramlst)
-     free_arg_lst(paramlst);
-diff -r 70b1f1918050 mcabber/mcabber/events.c
---- a/mcabber/mcabber/events.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/events.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -83,7 +83,7 @@
-     return NULL;
-   }
- 
--  event = g_new(evs_t, 1);
-+  event = g_slice_new(evs_t);
- 
-   event->id          = stridn;
-   event->description = g_strdup(desc);
-@@ -134,7 +134,7 @@
-   evs_list = g_slist_remove(evs_list, event);
-   g_free(event->id);
-   g_free(event->description);
--  g_free(event);
-+  g_slice_free(evs_t, event);
- 
-   return 0; // Ok, deleted
- }
-@@ -213,7 +213,7 @@
-     evs_list = g_slist_remove(evs_list, event);
-     g_free(event->id);
-     g_free(event->description);
--    g_free(event);
-+    g_slice_free(evs_t, event);
-   }
-   g_slist_free(evs_list);
-   evs_list = NULL;
-diff -r 70b1f1918050 mcabber/mcabber/hbuf.c
---- a/mcabber/mcabber/hbuf.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/hbuf.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -29,7 +29,6 @@
- #include "utf8.h"
- #include "screen.h"
- 
--
- /* This is a private structure type */
- 
- typedef struct {
-@@ -59,7 +58,7 @@
- {
-   GList *curr_elt = first_hbuf_elt;
- 
--  // Let's add non-persistent blocs if necessary
-+  // Let's add non-persistent blocks if necessary
-   // - If there are '\n' in the string
-   // - If length > width (and width != 0)
-   while (curr_elt) {
-@@ -95,17 +94,21 @@
-       end = hbuf_b_curr->ptr_end;
-       hbuf_b_curr->ptr_end = br;
-       // Create another block
--      hbuf_b_curr = g_new0(hbuf_block, 1);
-+      hbuf_b_curr = g_slice_new(hbuf_block);
-       // The block must be persistent after a CR
-       if (cr) {
-         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
-         hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
-       } else {
-         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
--        hbuf_b_curr->flags    = 0;
-+        hbuf_b_curr->flags  = 0;
-       }
--      hbuf_b_curr->ptr_end  = end;
-+      hbuf_b_curr->ptr_end       = end;
-       hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
-+      hbuf_b_curr->prefix.timestamp  = 0;
-+      hbuf_b_curr->prefix.flags      = 0;
-+      hbuf_b_curr->prefix.mucnicklen = 0;
-+      hbuf_b_curr->prefix.xep184     = NULL;
-       // This is OK because insert_before(NULL) == append():
-       *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
-     }
-@@ -138,7 +141,7 @@
-   textlen = strlen(text);
-   hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
- 
--  hbuf_block_elt = g_new0(hbuf_block, 1);
-+  hbuf_block_elt = g_slice_new(hbuf_block);
-   hbuf_block_elt->prefix.timestamp  = timestamp;
-   hbuf_block_elt->prefix.flags      = prefix_flags;
-   hbuf_block_elt->prefix.mucnicklen = mucnicklen;
-@@ -146,7 +149,7 @@
-   if (!*p_hbuf) {
-     hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
-     if (!hbuf_block_elt->ptr) {
--      g_free(hbuf_block_elt);
-+      g_slice_free(hbuf_block, hbuf_block_elt);
-       return;
-     }
-     hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
-@@ -208,7 +211,7 @@
-                 g_free(hbuf_b_elt->ptr);
-               }
-             }
--            g_free(hbuf_b_elt);
-+            g_slice_free(hbuf_block, hbuf_b_elt);
-             hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
-           }
-           n--;
-@@ -245,7 +248,7 @@
-     if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
-       g_free(hbuf_b_elt->ptr);
-     }
--    g_free(hbuf_b_elt);
-+    g_slice_free(hbuf_block, hbuf_b_elt);
-   }
- 
-   g_list_free(first_elt);
-@@ -275,7 +278,7 @@
-     // Is next line not-persistent?
-     if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
-       hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
--      g_free(hbuf_b_next);
-+      g_slice_free(hbuf_block, hbuf_b_next);
-       curr_elt = g_list_delete_link(curr_elt, next_elt);
-     } else
-       curr_elt = next_elt;
-@@ -309,8 +312,8 @@
- //  hbuf_get_lines(hbuf, n)
- // Returns an array of n hbb_line pointers
- // (The first line will be the line currently pointed by hbuf)
--// Note: The caller should free the array, the hbb_line pointers and the
--// text pointers after use.
-+// Note: The caller should g_free the array, g_slice_free hbb_line pointers
-+// and g_free text pointers after use.
- hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
- {
-   unsigned int i;
-@@ -349,7 +352,7 @@
- 
-       blk = (hbuf_block*)(hbuf->data);
-       maxlen = blk->ptr_end - blk->ptr;
--      *array_elt = (hbb_line*)g_new(hbb_line, 1);
-+      *array_elt = (hbb_line*)g_slice_new(hbb_line);
-       (*array_elt)->timestamp  = blk->prefix.timestamp;
-       (*array_elt)->flags      = blk->prefix.flags;
-       (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
-diff -r 70b1f1918050 mcabber/mcabber/hooks.c
---- a/mcabber/mcabber/hooks.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/hooks.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -99,7 +99,7 @@
-                      gint priority, gpointer userdata)
- {
-   GSList **hqueue = NULL;
--  hook_list_data_t *h = g_new(hook_list_data_t, 1);
-+  hook_list_data_t *h = g_slice_new(hook_list_data_t);
- 
-   h->handler  = handler;
-   h->priority = priority;
-@@ -148,7 +148,7 @@
-   el = g_slist_find_custom(*hqueue, &hid,
-                            (GCompareFunc)_hk_queue_search_cb);
-   if (el) {
--    g_free(el->data);
-+    g_slice_free(hook_list_data_t, el->data);
-     *hqueue = g_slist_delete_link(*hqueue, el);
-     // Remove hook hash table entry if the hook queue is empty
-     if (!*hqueue)
-diff -r 70b1f1918050 mcabber/mcabber/modules.c
---- a/mcabber/mcabber/modules.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/modules.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -176,7 +176,7 @@
-   }
- 
-   { // Register module
--    loaded_module_t *module = g_new(loaded_module_t, 1);
-+    loaded_module_t *module = g_slice_new(loaded_module_t);
- 
-     module->refcount     = 1;
-     module->locked       = manual;
-@@ -267,7 +267,7 @@
-   // Output this here, as arg may point to module->name
-   scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", module->name);
-   g_free(module->name);
--  g_free(module);
-+  g_slice_free(loaded_module_t, module);
- 
-   return NULL;
- }
-diff -r 70b1f1918050 mcabber/mcabber/screen.c
---- a/mcabber/mcabber/screen.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/screen.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -1305,7 +1305,7 @@
-         wattrset(win_entry->win, get_color(COLOR_GENERAL));
- 
-       g_free(line->text);
--      g_free(line);
-+      g_slice_free(hbb_line, line);
-     } else {
-       wclrtobot(win_entry->win);
-       break;
-diff -r 70b1f1918050 mcabber/mcabber/settings.c
---- a/mcabber/mcabber/settings.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/settings.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -453,7 +453,7 @@
-     // If value is 0, we do not need to create a structure (that's
-     // the default value).
-     if (value) {
--      pgpdata = g_new0(T_pgpopt, 1);
-+      pgpdata = g_slice_new0(T_pgpopt);
-       pgpdata->pgp_disabled = value;
-       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-     }
-@@ -493,7 +493,7 @@
-     // If value is 0, we do not need to create a structure (that's
-     // the default value).
-     if (value) {
--      pgpdata = g_new0(T_pgpopt, 1);
-+      pgpdata = g_slice_new0(T_pgpopt);
-       pgpdata->pgp_force = value;
-       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-     }
-@@ -533,7 +533,7 @@
-     // If keyid is NULL, we do not need to create a structure (that's
-     // the default value).
-     if (keyid) {
--      pgpdata = g_new0(T_pgpopt, 1);
-+      pgpdata = g_slice_new0(T_pgpopt);
-       pgpdata->pgp_keyid = g_strdup(keyid);
-       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-     }
-diff -r 70b1f1918050 mcabber/mcabber/xmpp_iq.c
---- a/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:41:15 2012 +0300
-+++ b/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:51:21 2012 +0300
-@@ -751,7 +751,7 @@
-                  lm_message_get_from(m));
-   }
- 
--  buf = g_new0(char, 512);
-+  buf = g_slice_alloc(512);
- 
-   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-   query = lm_message_node_add_child(r->node, "query", NULL);
-@@ -778,7 +778,7 @@
- 
-   lm_connection_send(c, r, NULL);
-   lm_message_unref(r);
--  g_free(buf);
-+  g_slice_free1(512, buf);
-   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
- }
- 
-@@ -801,7 +801,7 @@
-                  lm_message_get_from(m));
-   }
- 
--  buf = g_new0(char, 512);
-+  buf = g_slice_alloc(512);
- 
-   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-   query = lm_message_node_add_child(r->node, "time", NULL);
-@@ -838,7 +838,7 @@
- 
-   lm_connection_send(c, r, NULL);
-   lm_message_unref(r);
--  g_free(buf);
-+  g_slice_free1(512, buf);
-   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
- }
- 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/use-gslice.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,434 @@
+# HG changeset patch
+# Parent 70b1f1918050fa1126a21f4772d5d644799af32b
+[work-in-progress] Use glib slices to allocate constant-size blocks
+
+diff -r 70b1f1918050 mcabber/mcabber/caps.c
+--- a/mcabber/mcabber/caps.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/caps.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -52,7 +52,7 @@
+   g_hash_table_destroy(c->identities);
+   g_hash_table_destroy(c->features);
+   g_hash_table_destroy(c->forms);
+-  g_free(c);
++  g_slice_free(caps, c);
+ }
+ 
+ void identity_destroy(gpointer data)
+@@ -61,14 +61,14 @@
+   g_free(i->category);
+   g_free(i->type);
+   g_free(i->name);
+-  g_free(i);
++  g_slice_free(identity, i);
+ }
+ 
+ void form_destroy(gpointer data)
+ {
+   dataform *f = data;
+   g_hash_table_destroy(f->fields);
+-  g_free(f);
++  g_slice_free(dataform, f);
+ }
+ 
+ void field_destroy(gpointer data)
+@@ -97,7 +97,7 @@
+ {
+   if (!hash)
+     return;
+-  caps *c = g_new0(caps, 1);
++  caps *c = g_slice_new(caps);
+   c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+   c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
+   c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
+@@ -158,7 +158,7 @@
+ 
+   c = g_hash_table_lookup(caps_cache, hash);
+   if (c) {
+-    identity *i = g_new0(identity, 1);
++    identity *i = g_slice_new(identity);
+ 
+     i->category = g_strdup(category);
+     i->name = g_strdup(name);
+@@ -182,7 +182,7 @@
+     return;
+   c = g_hash_table_lookup(caps_cache, hash);
+   if (c) {
+-    dataform *d = g_new0(dataform, 1);
++    dataform *d = g_slice_new(dataform);
+     char *f = g_strdup(formtype);
+ 
+     d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
+diff -r 70b1f1918050 mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/commands.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -114,7 +114,7 @@
+       gpointer userdata = command->userdata;
+       Commands = g_slist_delete_link(Commands, sl_cmd);
+       compl_del_category_word(COMPL_CMD, command->name);
+-      g_free(command);
++      g_slice_free(cmd, command);
+       return userdata;
+     }
+   return NULL;
+@@ -126,7 +126,7 @@
+ gpointer cmd_add(const char *name, const char *help, guint flags_row1,
+                  guint flags_row2, void (*f)(char*), gpointer userdata)
+ {
+-  cmd *n_cmd = g_new0(cmd, 1);
++  cmd *n_cmd = g_slice_new0(cmd);
+   strncpy(n_cmd->name, name, 32-1);
+   n_cmd->help = help;
+   n_cmd->completion_flags[0] = flags_row1;
+@@ -1579,7 +1579,7 @@
+     return NULL;
+   }
+ 
+-  msgbuf = g_new0(char, HBB_BLOCKSIZE);
++  msgbuf = g_slice_alloc0(HBB_BLOCKSIZE);
+   len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd);
+   fclose(fd);
+ 
+@@ -1603,7 +1603,7 @@
+   if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line...
+     scr_LogPrint(LPRINT_LOGNORM, "Message file contains "
+                  "invalid characters (%s)", filename);
+-    g_free(msgbuf);
++    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+     return NULL;
+   }
+ 
+@@ -1617,7 +1617,7 @@
+   // It could be empty, once the trailing newlines are gone
+   if (p == msgbuf && *p == '\n') {
+     scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
+-    g_free(msgbuf);
++    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+     return NULL;
+   }
+ 
+@@ -1626,7 +1626,7 @@
+   if (!msgbuf_utf8 && msgbuf)
+     scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)",
+                  filename);
+-  g_free(msgbuf);
++  g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+   return msgbuf_utf8;
+ }
+ 
+@@ -1898,7 +1898,7 @@
+   esub   = buddy_getsubscription(bud);
+   on_srv = buddy_getonserverflag(bud);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+ 
+   if (bjid) {
+     GSList *resources, *p_res;
+@@ -1997,7 +1997,7 @@
+                  type == ROSTER_TYPE_GROUP ? "group" :
+                  (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown"));
+   }
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+ 
+   // Tell the user if this item has an annotation.
+   if (type == ROSTER_TYPE_USER ||
+@@ -2045,7 +2045,7 @@
+ 
+   bjid = buddy_getjid(bud);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+   strncpy(buffer, "Room members:", 127);
+   scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+ 
+@@ -2100,7 +2100,7 @@
+     g_free(p_res->data);
+   }
+   g_slist_free(resources);
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+ }
+ 
+ static void move_group_member(gpointer bud, void *groupnamedata)
+@@ -3091,7 +3091,7 @@
+   affil = buddy_getaffil(bud, nick);
+   realjid = buddy_getrjid(bud, nick);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+ 
+   snprintf(buffer, 4095, "Whois [%s]", nick);
+   scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0);
+@@ -3121,7 +3121,7 @@
+ 
+   scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0);
+ 
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+   g_free(nick);
+   if (paramlst)
+     free_arg_lst(paramlst);
+diff -r 70b1f1918050 mcabber/mcabber/events.c
+--- a/mcabber/mcabber/events.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/events.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -83,7 +83,7 @@
+     return NULL;
+   }
+ 
+-  event = g_new(evs_t, 1);
++  event = g_slice_new(evs_t);
+ 
+   event->id          = stridn;
+   event->description = g_strdup(desc);
+@@ -134,7 +134,7 @@
+   evs_list = g_slist_remove(evs_list, event);
+   g_free(event->id);
+   g_free(event->description);
+-  g_free(event);
++  g_slice_free(evs_t, event);
+ 
+   return 0; // Ok, deleted
+ }
+@@ -213,7 +213,7 @@
+     evs_list = g_slist_remove(evs_list, event);
+     g_free(event->id);
+     g_free(event->description);
+-    g_free(event);
++    g_slice_free(evs_t, event);
+   }
+   g_slist_free(evs_list);
+   evs_list = NULL;
+diff -r 70b1f1918050 mcabber/mcabber/hbuf.c
+--- a/mcabber/mcabber/hbuf.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/hbuf.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -29,7 +29,6 @@
+ #include "utf8.h"
+ #include "screen.h"
+ 
+-
+ /* This is a private structure type */
+ 
+ typedef struct {
+@@ -59,7 +58,7 @@
+ {
+   GList *curr_elt = first_hbuf_elt;
+ 
+-  // Let's add non-persistent blocs if necessary
++  // Let's add non-persistent blocks if necessary
+   // - If there are '\n' in the string
+   // - If length > width (and width != 0)
+   while (curr_elt) {
+@@ -95,17 +94,21 @@
+       end = hbuf_b_curr->ptr_end;
+       hbuf_b_curr->ptr_end = br;
+       // Create another block
+-      hbuf_b_curr = g_new0(hbuf_block, 1);
++      hbuf_b_curr = g_slice_new(hbuf_block);
+       // The block must be persistent after a CR
+       if (cr) {
+         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
+         hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
+       } else {
+         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
+-        hbuf_b_curr->flags    = 0;
++        hbuf_b_curr->flags  = 0;
+       }
+-      hbuf_b_curr->ptr_end  = end;
++      hbuf_b_curr->ptr_end       = end;
+       hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
++      hbuf_b_curr->prefix.timestamp  = 0;
++      hbuf_b_curr->prefix.flags      = 0;
++      hbuf_b_curr->prefix.mucnicklen = 0;
++      hbuf_b_curr->prefix.xep184     = NULL;
+       // This is OK because insert_before(NULL) == append():
+       *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
+     }
+@@ -138,7 +141,7 @@
+   textlen = strlen(text);
+   hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
+ 
+-  hbuf_block_elt = g_new0(hbuf_block, 1);
++  hbuf_block_elt = g_slice_new(hbuf_block);
+   hbuf_block_elt->prefix.timestamp  = timestamp;
+   hbuf_block_elt->prefix.flags      = prefix_flags;
+   hbuf_block_elt->prefix.mucnicklen = mucnicklen;
+@@ -146,7 +149,7 @@
+   if (!*p_hbuf) {
+     hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
+     if (!hbuf_block_elt->ptr) {
+-      g_free(hbuf_block_elt);
++      g_slice_free(hbuf_block, hbuf_block_elt);
+       return;
+     }
+     hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+@@ -208,7 +211,7 @@
+                 g_free(hbuf_b_elt->ptr);
+               }
+             }
+-            g_free(hbuf_b_elt);
++            g_slice_free(hbuf_block, hbuf_b_elt);
+             hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
+           }
+           n--;
+@@ -245,7 +248,7 @@
+     if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+       g_free(hbuf_b_elt->ptr);
+     }
+-    g_free(hbuf_b_elt);
++    g_slice_free(hbuf_block, hbuf_b_elt);
+   }
+ 
+   g_list_free(first_elt);
+@@ -275,7 +278,7 @@
+     // Is next line not-persistent?
+     if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
+       hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
+-      g_free(hbuf_b_next);
++      g_slice_free(hbuf_block, hbuf_b_next);
+       curr_elt = g_list_delete_link(curr_elt, next_elt);
+     } else
+       curr_elt = next_elt;
+@@ -309,8 +312,8 @@
+ //  hbuf_get_lines(hbuf, n)
+ // Returns an array of n hbb_line pointers
+ // (The first line will be the line currently pointed by hbuf)
+-// Note: The caller should free the array, the hbb_line pointers and the
+-// text pointers after use.
++// Note: The caller should g_free the array, g_slice_free hbb_line pointers
++// and g_free text pointers after use.
+ hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
+ {
+   unsigned int i;
+@@ -349,7 +352,7 @@
+ 
+       blk = (hbuf_block*)(hbuf->data);
+       maxlen = blk->ptr_end - blk->ptr;
+-      *array_elt = (hbb_line*)g_new(hbb_line, 1);
++      *array_elt = (hbb_line*)g_slice_new(hbb_line);
+       (*array_elt)->timestamp  = blk->prefix.timestamp;
+       (*array_elt)->flags      = blk->prefix.flags;
+       (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
+diff -r 70b1f1918050 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/hooks.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -99,7 +99,7 @@
+                      gint priority, gpointer userdata)
+ {
+   GSList **hqueue = NULL;
+-  hook_list_data_t *h = g_new(hook_list_data_t, 1);
++  hook_list_data_t *h = g_slice_new(hook_list_data_t);
+ 
+   h->handler  = handler;
+   h->priority = priority;
+@@ -148,7 +148,7 @@
+   el = g_slist_find_custom(*hqueue, &hid,
+                            (GCompareFunc)_hk_queue_search_cb);
+   if (el) {
+-    g_free(el->data);
++    g_slice_free(hook_list_data_t, el->data);
+     *hqueue = g_slist_delete_link(*hqueue, el);
+     // Remove hook hash table entry if the hook queue is empty
+     if (!*hqueue)
+diff -r 70b1f1918050 mcabber/mcabber/modules.c
+--- a/mcabber/mcabber/modules.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/modules.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -176,7 +176,7 @@
+   }
+ 
+   { // Register module
+-    loaded_module_t *module = g_new(loaded_module_t, 1);
++    loaded_module_t *module = g_slice_new(loaded_module_t);
+ 
+     module->refcount     = 1;
+     module->locked       = manual;
+@@ -267,7 +267,7 @@
+   // Output this here, as arg may point to module->name
+   scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", module->name);
+   g_free(module->name);
+-  g_free(module);
++  g_slice_free(loaded_module_t, module);
+ 
+   return NULL;
+ }
+diff -r 70b1f1918050 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/screen.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -1305,7 +1305,7 @@
+         wattrset(win_entry->win, get_color(COLOR_GENERAL));
+ 
+       g_free(line->text);
+-      g_free(line);
++      g_slice_free(hbb_line, line);
+     } else {
+       wclrtobot(win_entry->win);
+       break;
+diff -r 70b1f1918050 mcabber/mcabber/settings.c
+--- a/mcabber/mcabber/settings.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/settings.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -453,7 +453,7 @@
+     // If value is 0, we do not need to create a structure (that's
+     // the default value).
+     if (value) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_disabled = value;
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+@@ -493,7 +493,7 @@
+     // If value is 0, we do not need to create a structure (that's
+     // the default value).
+     if (value) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_force = value;
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+@@ -533,7 +533,7 @@
+     // If keyid is NULL, we do not need to create a structure (that's
+     // the default value).
+     if (keyid) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_keyid = g_strdup(keyid);
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+diff -r 70b1f1918050 mcabber/mcabber/xmpp_iq.c
+--- a/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -751,7 +751,7 @@
+                  lm_message_get_from(m));
+   }
+ 
+-  buf = g_new0(char, 512);
++  buf = g_slice_alloc(512);
+ 
+   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+   query = lm_message_node_add_child(r->node, "query", NULL);
+@@ -778,7 +778,7 @@
+ 
+   lm_connection_send(c, r, NULL);
+   lm_message_unref(r);
+-  g_free(buf);
++  g_slice_free1(512, buf);
+   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+ 
+@@ -801,7 +801,7 @@
+                  lm_message_get_from(m));
+   }
+ 
+-  buf = g_new0(char, 512);
++  buf = g_slice_alloc(512);
+ 
+   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+   query = lm_message_node_add_child(r->node, "time", NULL);
+@@ -838,7 +838,7 @@
+ 
+   lm_connection_send(c, r, NULL);
+   lm_message_unref(r);
+-  g_free(buf);
++  g_slice_free1(512, buf);
+   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+ 
--- a/warning-fixes	Sat Jul 28 19:37:32 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-# HG changeset patch
-# Parent b5ea7efbf1f97502016888c5c7f463bae703c13e
-Fix some compiler warnings
-
-diff -r b5ea7efbf1f9 mcabber/mcabber/commands.c
---- a/mcabber/mcabber/commands.c	Thu Jul 19 08:03:37 2012 +0300
-+++ b/mcabber/mcabber/commands.c	Thu Jul 19 08:04:52 2012 +0300
-@@ -1949,13 +1949,11 @@
-       enum imstatus rstatus;
-       const char *rst_msg;
-       time_t rst_time;
--      struct pgp_data *rpgp;
- 
-       rprio   = buddy_getresourceprio(bud, p_res->data);
-       rstatus = buddy_getstatus(bud, p_res->data);
-       rst_msg = buddy_getstatusmsg(bud, p_res->data);
-       rst_time = buddy_getstatustime(bud, p_res->data);
--      rpgp = buddy_resource_pgp(bud, p_res->data);
- 
-       snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus],
-                rprio, (char*)p_res->data);
-@@ -1974,6 +1972,8 @@
-                                  0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-       }
- #ifdef HAVE_GPGME
-+      struct pgp_data *rpgp = buddy_resource_pgp(bud, p_res->data);
-+
-       if (rpgp && rpgp->sign_keyid) {
-         snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid);
-         scr_WriteIncomingMessage(bjid, buffer,
-diff -r b5ea7efbf1f9 mcabber/mcabber/xmpp_helper.c
---- a/mcabber/mcabber/xmpp_helper.c	Thu Jul 19 08:03:37 2012 +0300
-+++ b/mcabber/mcabber/xmpp_helper.c	Thu Jul 19 08:04:52 2012 +0300
-@@ -22,6 +22,7 @@
- 
- #include <string.h>
- #include <stdlib.h>
-+#include <stdio.h>   // snprintf
- 
- #include "xmpp_helper.h"
- #include "settings.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/warning-fixes.diff	Sat Jul 28 19:42:13 2012 +0300
@@ -0,0 +1,41 @@
+# HG changeset patch
+# Parent b5ea7efbf1f97502016888c5c7f463bae703c13e
+Fix some compiler warnings
+
+diff -r b5ea7efbf1f9 mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c	Thu Jul 19 08:03:37 2012 +0300
++++ b/mcabber/mcabber/commands.c	Thu Jul 19 08:04:52 2012 +0300
+@@ -1949,13 +1949,11 @@
+       enum imstatus rstatus;
+       const char *rst_msg;
+       time_t rst_time;
+-      struct pgp_data *rpgp;
+ 
+       rprio   = buddy_getresourceprio(bud, p_res->data);
+       rstatus = buddy_getstatus(bud, p_res->data);
+       rst_msg = buddy_getstatusmsg(bud, p_res->data);
+       rst_time = buddy_getstatustime(bud, p_res->data);
+-      rpgp = buddy_resource_pgp(bud, p_res->data);
+ 
+       snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus],
+                rprio, (char*)p_res->data);
+@@ -1974,6 +1972,8 @@
+                                  0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+       }
+ #ifdef HAVE_GPGME
++      struct pgp_data *rpgp = buddy_resource_pgp(bud, p_res->data);
++
+       if (rpgp && rpgp->sign_keyid) {
+         snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid);
+         scr_WriteIncomingMessage(bjid, buffer,
+diff -r b5ea7efbf1f9 mcabber/mcabber/xmpp_helper.c
+--- a/mcabber/mcabber/xmpp_helper.c	Thu Jul 19 08:03:37 2012 +0300
++++ b/mcabber/mcabber/xmpp_helper.c	Thu Jul 19 08:04:52 2012 +0300
+@@ -22,6 +22,7 @@
+ 
+ #include <string.h>
+ #include <stdlib.h>
++#include <stdio.h>   // snprintf
+ 
+ #include "xmpp_helper.h"
+ #include "settings.h"