--- a/cmdopts.diff Wed Feb 27 23:35:53 2013 +0200
+++ b/cmdopts.diff Tue Mar 05 01:11:24 2013 +0200
@@ -1,6 +1,12 @@
# HG changeset patch
# Parent 92fa48ef53c909928706ab4c51518953339a38e4
-Unified command option parsing
+[work-in-progress] Unified command option parsing
+
+ * wrecking chaos all over the place
+ * started integration with cmd subsystem
+ * broke everything
+
+ ** PREVIOUS ACHIEVEMENTS **
* cmdopts_parse() & cmdopts_free() in utils.c/h
* /roster uses parser
@@ -29,7 +35,7 @@
diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_buffer.txt
--- a/mcabber/doc/help/cs/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Přesune se o [n] řádků nahoru (výchozí: polovina obrazovky).
/buffer down [n]
@@ -41,7 +47,7 @@
Přesune se na procentuální pozici n%.
diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_del.txt
--- a/mcabber/doc/help/cs/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -50,7 +56,7 @@
Smaže aktuální kontakt ze seznamu kontaktů (rosteru) a zruší povolení oznamování o stavu daného kontaktu (autorizaci) na obou stranách.
diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_move.txt
--- a/mcabber/doc/help/cs/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [skupina]
@@ -61,7 +67,7 @@
Tip: V módu rozhovoru lze použít "/roster alternate" pro skok na přesunutý kontakt.
diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_rename.txt
--- a/mcabber/doc/help/cs/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME jméno
@@ -73,7 +79,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_buffer.txt
--- a/mcabber/doc/help/de/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Scrollt den Puffer um n Zeilen hoch. Gibt man keine Zahl an, scrollt er um einen halben Bildschirm
/buffer down [n]
@@ -85,7 +91,7 @@
Springe zur Position "n" im Chatpuffer
diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_del.txt
--- a/mcabber/doc/help/de/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -94,7 +100,7 @@
Löscht den gerade ausgewählten Buddy vom Roster. Außerdem werden die automatischen Presence Benachrichtigungen vom/zum Buddy gestoppt.
diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_move.txt
--- a/mcabber/doc/help/de/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,6 +1,7 @@
- /MOVE [groupname]
@@ -106,7 +112,7 @@
Tipp: Wenn der Chatmodus aktiviert ist, kannst du "/roster alternate" benutzen um zu dem gerade bewegten Buddy zu springen.
diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_rename.txt
--- a/mcabber/doc/help/de/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -118,7 +124,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_buffer.txt
--- a/mcabber/doc/help/en/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Scroll the buffer up [n] lines (default: half a screen)
/buffer down [n]
@@ -130,7 +136,7 @@
Jump to position %n of the buddy chat buffer
diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_del.txt
--- a/mcabber/doc/help/en/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -140,7 +146,7 @@
+Delete the current buddy or one, specified with [jid] from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_move.txt
--- a/mcabber/doc/help/en/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groupname]
@@ -151,7 +157,7 @@
Tip: if the chatmode is enabled, you can use "/roster alternate" to jump to the moved buddy.
diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_rename.txt
--- a/mcabber/doc/help/en/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -163,7 +169,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_buffer.txt
--- a/mcabber/doc/help/fr/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Défile vers le haut de [n] lignes (par défaut un demi écran)
/buffer down [n]
@@ -175,7 +181,7 @@
Va à la position n% du tampon
diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_del.txt
--- a/mcabber/doc/help/fr/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -184,7 +190,7 @@
Supprime le contact sélectionné du roster, supprime notre abonnement à ses notifications de présence et supprime son abonnement aux nôtres.
diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_move.txt
--- a/mcabber/doc/help/fr/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groupname]
@@ -195,7 +201,7 @@
Astuce : si le mode discussion (chatmode) est activé, vous pouvez utiliser "/roster alternate" pour vous positionner sur le contact que vous venez de déplacer.
diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_rename.txt
--- a/mcabber/doc/help/fr/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nom
@@ -207,7 +213,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_buffer.txt
--- a/mcabber/doc/help/it/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Fa scorrere indietro il buffer di [n] linee (default: metà schermo)
/buffer down [n]
@@ -219,7 +225,7 @@
Salta alla posizione %n del buffer di chat corrente
diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_del.txt
--- a/mcabber/doc/help/it/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -228,7 +234,7 @@
Elimina il contatto corrente dal roster, cancellando la sottoscrizione alle reciproche notifiche della propria presenza.
diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_move.txt
--- a/mcabber/doc/help/it/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [gruppo]
@@ -239,7 +245,7 @@
Trucco: se la modalità chat è abilitata, puoi usare "/roster alternate" per spostarti sul contatto appena mosso.
diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_rename.txt
--- a/mcabber/doc/help/it/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nome
@@ -251,7 +257,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_buffer.txt
--- a/mcabber/doc/help/nl/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Scroll de buffer [n] regels omhoog (standaard: een half scherm)
/buffer down [n]
@@ -263,7 +269,7 @@
Spring naar positie %n in de buddy chat buffer
diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_del.txt
--- a/mcabber/doc/help/nl/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -272,7 +278,7 @@
Verwijder de actieve buddy uit ons roster, en zet het wederzijds toezenden van status veranderingen stop.
diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_move.txt
--- a/mcabber/doc/help/nl/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groepsnaam]
@@ -283,7 +289,7 @@
Tip: indien chatmode actief is, kun je "/roster alternate" gebruiken om direct naar de verplaatste buddy te springen.
diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_rename.txt
--- a/mcabber/doc/help/nl/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME naam
@@ -295,7 +301,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_del.txt
--- a/mcabber/doc/help/pl/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -304,7 +310,7 @@
Usuwa aktualnie zaznaczoną osobę z rostera, usuwa subskrypcję powiadomienia dostępności u danej osoby oraz u nas.
diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_move.txt
--- a/mcabber/doc/help/pl/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [nazwa grupy]
@@ -315,7 +321,7 @@
Podpowiedź: jeśli jest włączony tryb czatu, możesz użyć "/roster alternate" aby skoczyć do przeniesionej osoby.
diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_rename.txt
--- a/mcabber/doc/help/pl/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nazwa
@@ -327,7 +333,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_buffer.txt
--- a/mcabber/doc/help/ru/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Перемещает на [n] строк вверх в буфере (истории переписки) (по умолчанию: половина экрана)
/buffer down [n]
@@ -339,7 +345,7 @@
Перемещает на позицию %n в текущем буфере (истории переписки)
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_del.txt
--- a/mcabber/doc/help/ru/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -349,7 +355,7 @@
+Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_move.txt
--- a/mcabber/doc/help/ru/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,6 +1,7 @@
- /MOVE [groupname]
@@ -361,7 +367,7 @@
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_rename.txt
--- a/mcabber/doc/help/ru/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -373,7 +379,7 @@
+Для указания обьекта, отличного от текущего, можно использовать опции --jid, --group и --name.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_buffer.txt
--- a/mcabber/doc/help/uk/hlp_buffer.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_buffer.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_buffer.txt Tue Mar 05 01:08:31 2013 +0200
@@ -25,7 +25,7 @@
Посунути буфер вверх на n рядків (якщо не вказано - пів екрану).
/buffer down [n]
@@ -385,7 +391,7 @@
Перейти до вказаної у процентах позиції.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_del.txt
--- a/mcabber/doc/help/uk/hlp_del.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_del.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_del.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -395,7 +401,7 @@
+Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_move.txt
--- a/mcabber/doc/help/uk/hlp_move.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_move.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_move.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [група]
@@ -407,7 +413,7 @@
Примітка: в режимі розмови можна використати "/roster alternate", щоб перейти до нового місця контакту контакту.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_rename.txt
--- a/mcabber/doc/help/uk/hlp_rename.txt Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_rename.txt Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_rename.txt Tue Mar 05 01:08:31 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME ім'я
@@ -418,46 +424,1434 @@
+Опції --jid, --group та --name дозволяють перейменовувати об’єкти, відмінні від поточного.
diff -r 92fa48ef53c9 mcabber/mcabber/commands.c
--- a/mcabber/mcabber/commands.c Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.c Wed Feb 27 23:34:50 2013 +0200
-@@ -502,7 +502,9 @@
- if (!iscmd && scr_get_multimode() == 2
- && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
- // It isn't an /msay command
++++ b/mcabber/mcabber/commands.c Tue Mar 05 01:08:31 2013 +0200
+@@ -19,7 +19,7 @@
+ * USA
+ */
+
+-#include <string.h>
++#include <string.h> // g_memmove
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+@@ -43,512 +43,645 @@
+ #include "xmpp.h"
+ #include "main.h"
+
+-#define IMSTATUS_AWAY "away"
+-#define IMSTATUS_ONLINE "online"
+-#define IMSTATUS_OFFLINE "offline"
+-#define IMSTATUS_FREE4CHAT "free"
+-#define IMSTATUS_AVAILABLE "avail"
+-#define IMSTATUS_NOTAVAILABLE "notavail"
+-#define IMSTATUS_DONOTDISTURB "dnd"
+-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+-# define IMSTATUS_INVISIBLE "invisible"
++//
++// Pre-declarations
++//
++
++// for process_line()
++typedef enum {
++ scmd_group_unfold = 0,
++ scmd_group_fold = 1,
++ scmd_group_toggle = -1,
++} group_scmd_t;
++
++static void group_cmd (gpointer group, group_scmd_t action);
++
++//static void room_bookmark(gpointer bud, char *arg);
++
++#define BUILTIN_COUNT 3
++static cmdopts_t def_roster,
++ def_status,
++ def_status_to,
++#if 0
++ def_add,
++ def_del,
++ def_group,
++ def_say,
++ def_msay,
++ def_say_to,
++ def_buffer,
++ def_clear,
++ def_info,
++ def_rename,
++ def_move,
++ def_set,
++ def_alias,
++ def_bind,
++ def_connect,
++ def_disconnect,
++ def_rawxml,
++ def_room,
++ def_authorization,
++ def_version,
++ def_request,
++ def_event,
++ def_help,
++ def_pgp,
++ def_iline,
++ def_screen_refresh,
++ def_chat_disable,
++ def_source,
++ def_color,
++ def_otr,
++ def_otrpolicy,
++ def_echo,
++ def_module,
++ def_exit
+ #endif
+-
+-// Return value container for the following functions
+-static int retval_for_cmds;
+-
+-// Commands callbacks
+-static void do_roster(char *arg);
+-static void do_status(char *arg);
+-static void do_status_to(char *arg);
+-static void do_add(char *arg);
+-static void do_del(char *arg);
+-static void do_group(char *arg);
+-static void do_say(char *arg);
+-static void do_msay(char *arg);
+-static void do_say_to(char *arg);
+-static void do_buffer(char *arg);
+-static void do_clear(char *arg);
+-static void do_info(char *arg);
+-static void do_rename(char *arg);
+-static void do_move(char *arg);
+-static void do_set(char *arg);
+-static void do_alias(char *arg);
+-static void do_bind(char *arg);
+-static void do_connect(char *arg);
+-static void do_disconnect(char *arg);
+-static void do_rawxml(char *arg);
+-static void do_room(char *arg);
+-static void do_authorization(char *arg);
+-static void do_version(char *arg);
+-static void do_request(char *arg);
+-static void do_event(char *arg);
+-static void do_help(char *arg);
+-static void do_pgp(char *arg);
+-static void do_iline(char *arg);
+-static void do_screen_refresh(char *arg);
+-static void do_chat_disable(char *arg);
+-static void do_source(char *arg);
+-static void do_color(char *arg);
+-static void do_otr(char *arg);
+-static void do_otrpolicy(char *arg);
+-static void do_echo(char *arg);
+-static void do_module(char *arg);
+-static void do_exit(char *arg);
+-
+-static void room_bookmark(gpointer bud, char *arg);
+-
+-// Global variable for the commands list
+-static GSList *Commands;
+-static GSList *safe_commands;
+-
+-#ifdef MODULES_ENABLE
+-#include "modules.h"
+-
+-gpointer cmd_del(gpointer id)
++ ;
++
++static cmd_handler_t do_roster,
++ do_status,
++ do_status_to,
++#if 0
++ do_add,
++ do_del,
++ do_group,
++ do_say,
++ do_msay,
++ do_say_to,
++ do_buffer,
++ do_clear,
++ do_info,
++ do_rename,
++ do_move,
++ do_set,
++ do_alias,
++ do_bind,
++ do_connect,
++ do_disconnect,
++ do_rawxml,
++ do_room,
++ do_authorization,
++ do_version,
++ do_request,
++ do_event,
++ do_help,
++ do_pgp,
++ do_iline,
++ do_screen_refresh,
++ do_chat_disable,
++ do_source,
++ do_color,
++ do_otr,
++ do_otrpolicy,
++ do_echo,
++ do_module,
++ do_exit
++#endif
++ ;
++
++//
++// Main commands mechanics
++//
++// Functions to parse mcabber command line and execute commands accordingly.
++// Allows to define commands and remove definitions.
++
++cmdopts_t **cmd_list = NULL; // command structs list
++gsize cmd_count = 0; // number of commands (one less than memory allocated)
++
++// private
++void cmd_init (void)
+ {
+- GSList *sl_cmd;
+- if (!id) return NULL;
+- for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next)
+- if (sl_cmd -> data == id) {
+- cmd *command = (cmd *) sl_cmd->data;
+- gpointer userdata = command->userdata;
+- Commands = g_slist_delete_link(Commands, sl_cmd);
+- compl_del_category_word(COMPL_CMD, command->name);
+- g_slice_free(cmd, command);
+- return userdata;
++ cmd_list = g_new ((cmdopts_t *), BUILTIN_COUNT+1);
++ cmd_list[0] = def_roster;
++ cmd_list[1] = def_status;
++ cmd_list[2] = def_status_to;
++#if 0
++ cmd_list[3] = def_add;
++ cmd_list[4] = def_del;
++ cmd_list[5] = def_group;
++ cmd_list[6] = def_say;
++ cmd_list[7] = def_msay;
++ cmd_list[8] = def_say_to;
++ cmd_list[9] = def_buffer;
++ cmd_list[10] = def_clear;
++ cmd_list[11] = def_info;
++ cmd_list[12] = def_rename;
++ cmd_list[13] = def_move;
++ cmd_list[14] = def_set;
++ cmd_list[15] = def_alias;
++ cmd_list[16] = def_bind;
++ cmd_list[17] = def_connect;
++ cmd_list[18] = def_disconnect;
++ cmd_list[19] = def_rawxml;
++ cmd_list[20] = def_room;
++ cmd_list[21] = def_authorization;
++ cmd_list[22] = def_version;
++ cmd_list[23] = def_request;
++ cmd_list[24] = def_event;
++ cmd_list[25] = def_help;
++ cmd_list[26] = def_pgp;
++ cmd_list[27] = def_iline;
++ cmd_list[28] = def_screen_refresh;
++ cmd_list[29] = def_chat_disable;
++ cmd_list[30] = def_source;
++ cmd_list[31] = def_color;
++ cmd_list[32] = def_otr;
++ cmd_list[33] = def_otrpolicy;
++ cmd_list[34] = def_echo;
++ cmd_list[35] = def_module;
++ cmd_list[36] = def_exit;
++#endif
++ cmd_list[BUILTIN_COUNT] = NULL;
++ cmd_count = BUILTIN_COUNT;
++}
++
++// private
++void cmd_uninit (void)
++{
++ g_free (cmd_list);
++ cmd_list = NULL;
++ cmd_count = 0;
++}
++
++void cmd_define (cmdopts_t *command)
++{
++ cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count+2);
++ g_memmove(cmd_list, cmd_list+1, sizeof(cmdopts_t *) * cmd_count+1)
++ cmd_list[0] = command;
++ cmd_count ++;
++}
++
++void cmd_undef (cmdopts_t *command)
++{
++ gsize num = 0;
++ while (num < cmd_count) {
++ if (cmd_list[num] == command) {
++ g_memmove (cmd_list+num, cmd_list+num+1, sizeof(cmdopts_t *) * (cmd_count-num));
++ cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count);
++ cmd_count --;
++ return;
+ }
+- return NULL;
++ num ++;
++ }
+ }
+-#endif
+-
+-// cmd_add()
+-// Adds a command to the commands list and to the CMD completion list
+-gpointer cmd_add(const char *name, const char *help, guint flags_row1,
+- guint flags_row2, void (*f)(char*), gpointer userdata)
++
++// error cmdopts_parse_argument ( startptr, endptr, flags )
++// Parses next argument according to flags and finishes it with zero.
++// Updates current/end pointers. Parsed string MUST be writable.
++// String may shrink in size (quotation/escapes), thus endpointer is also
++// updated.
++const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags)
+ {
+- 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;
+- n_cmd->completion_flags[1] = flags_row2;
+- n_cmd->func = f;
+- n_cmd->userdata = userdata;
+- Commands = g_slist_prepend(Commands, n_cmd);
+- // Add to completion CMD category
+- compl_add_category_word(COMPL_CMD, name);
+- return n_cmd;
++ gchar *p = *pr;
++ gchar *e = *er;
++ const char *error = NULL;
++ gboolean quotes = FALSE;
++
++ if ((flags & cmdarg_catchall) && (flags & cmdarg_plain)) {
++ *pr = e;
++ return NULL;
++ }
++
++ while (p <= e && error != NULL) {
++ if (p == e && quotes) { // end of line in quotes
++ error = "Unfinished quoted argument.";
++ } else if ((*p == ' ' && (!quotes) && !(flags & cmdarg_catchall)) || p == e) { // argument ended
++ if (*p != '\0') {
++ *p = '\0';
++ p ++;
++ }
++ break;
++ } else if (*p == '\\' && !(flags & cmdarg_plain)) { // next char escape
++ g_memmove(p, p+1, e-(p+1)+1);
++ e --;
++ if (p == e) {
++ error = "Escape at the end of line.";
++ } else {
++ p ++;
++ }
++ } else if (*p == '"' && !(flags & cmdarg_plain)) { // quotation start/end
++ g_memmove(p, p+1, e-(p+1)+1);
++ e --;
++ quotes = !quotes;
++ } else { // still argument
++ p ++;
++ }
++ }
++
++ *pr = p;
++ *er = e;
++ return error;
+ }
+
+-// cmd_set_safe(name, safe)
+-// Sets if command can be used in startup configuration file.
+-gboolean cmd_set_safe(const gchar *name, gboolean safe)
++// error cmdopts_parse_internal ( startptr, endptr, commanddef )
++// Parses command arguments according to command definition.
++// Parsed string MUST be writable. Regardless of success or error, input
++// string should be considered corrupted by parsing process.
++// Even in case of error, commanddef should be passed to cmdopts_free().
++static gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command)
+ {
+- GSList *sel;
+- if (!name)
+- return FALSE;
+- for (sel = safe_commands; sel; sel = sel->next)
+- if (!strcmp((const char *)sel->data, name)) {
+- if (safe)
+- return FALSE;
+- else {
+- g_free(sel->data);
+- safe_commands = g_slist_delete_link(safe_commands, sel);
++ enum { // Parser state transitions:
++ in_space, // -> in_space, in_optstart, in_argstart
++ in_optstart, // -> in_shortoptend, in_longoptstart, in_argstart ("-")
++ in_shortoptend, // -> in_space, in_argstart, error
++ in_longoptstart, // -> in_longopt, in_space, in_argstart ("---")
++ in_longopt, // -> in_longopt, in_space, error
++ in_argstart, // -> in_space, error
++ } state = in_argstart; // current parser state
++ gchar *p = *pr; // current pointer
++ gchar *e = *er; // end of line pointer
++ gchar *s; // start of current object pointer
++ gboolean opts_ended = FALSE; // don't allow options any more
++ cmdopt_t *option = NULL; // option, for which argument is currently parsed
++ gsize argno = 0; // number of current positional argument
++ gchar *error = NULL; // error message to return
++
++ // general environment checking
++ if (command -> check) {
++ if ((error = command -> check (command))) {
++ gchar *err = error;
++ error = g_strdup_printf("%s: %s", command -> name, err);
++ g_free (err);
++ }
++ }
++
++ // prepare: set default values for arguments and unset some fields
++ if (error == NULL) {
++ gsize n;
++ if (command -> opts) {
++ for (n = 0; command -> opts[n] != NULL; n ++) {
++ cmdopt_t *opt = command -> opts[n];
++ opt -> arg.val.arg = opt -> arg.defval;
++ opt -> cnt = 0;
+ }
+ }
+- if (safe)
+- safe_commands = g_slist_append(safe_commands, g_strdup(name));
+- else
+- return FALSE;
+- return TRUE;
++ if (command -> args) {
++ for (n = 0; command -> args[n] != NULL; n ++) {
++ cmdarg_t *arg = command -> args[n];
++ arg -> value.roarg = arg -> defval;
++ arg -> flags &= ~(cmdarg_visited|cmdarg_checked|cmdarg_freed);
++ }
++ }
++ }
++
++ // we allow parser to do one extra run on final '\0'
++ while (p <= e && error == NULL) {
++ if (state == in_space) { // space between args/options
++ if (*p == ' ' || p == e) { // still space
++ p ++;
++ } else if (*p == '-' && !opts_ended) { // option
++ state = in_optstart;
++ p ++;
++ } else { // argument
++ if (!option) {
++ opts_ended = TRUE;
++ }
++ s = p;
++ state = in_argstart;
++ }
++ } else if (state == in_optstart) { // long/short option
++ if (*p == ' ' || p == e) { // argument '-'
++ opts_ended = TRUE;
++ s = p = p - 1;
++ state = in_argstart;
++ } else if (*p == '-') { // long option
++ state = in_longoptstart;
++ p ++;
++ } else { // short option
++ s = p;
++ state = in_shortoptend;
++ p ++;
++ }
++ } else if (state == in_shortoptend) { // short option
++ if (*p == ' ' || p == e) { // option really ended
++ gsize n;
++ for (n = 0; command -> opts[n] != NULL; n ++) {
++ if (command -> opts[n] -> shortopt == *s) {
++ option = command -> opts[n];
++ break;
++ }
++ }
++ if (option != NULL) { // option is known
++ if (option -> flags & cmdopt_switch) { // it is switch
++ option -> arg.value.swc ++;
++ option = NULL;
++ } else if (p == e) {
++ error = g_strdup_printf ("%s: Option -%c needs an argument.", command -> name, option -> shortopt);
++ }
++ state = in_space;
++ p ++;
++ } else { // option is unknown
++ error = g_strdup_printf ("%s: Unknown option -%c.", command -> name, *s);
++ }
++ } else { // short option not ended
++ // consider it argument (to allow -autojoin)
++ opts_ended = TRUE;
++ s = p = p - 2;
++ state = in_argstart;
++ }
++ } else if (state == in_longoptstart) { // long option initialization
++ if (*p == ' ' || p == e) { // end of options '--'
++ opts_ended = TRUE;
++ state = in_space;
++ p ++;
++ } else if (*p == '-') { // argument, starting with '---'
++ opts_ended = TRUE;
++ s = p = p - 2;
++ state = in_argstart;
++ } else { // it is long option
++ s = p;
++ state = in_longopt;
++ p ++;
++ }
++ } else if (state == in_longopt) { // long option name
++ if (*p == ' ' || p == e) { // long option ended
++ gsize n;
++ *p = '\0';
++ for (n = 0; command -> opts[n] != NULL; n ++) {
++ if (!g_strcmp0(command -> opts[n] -> longopt, s)) {
++ option = command -> opts[n];
++ break;
++ }
++ }
++ if (option != NULL) { // option is known
++ if (option -> flags & cmdopt_switch) { // it is switch
++ option -> arg.value.swc ++;
++ option = NULL;
++ } else if (p == e) {
++ error = g_strdup_printf ("%s: Option --%s needs an argument.", command -> name, option -> longopt);
++ }
++ state = in_space;
++ p ++;
++ } else { // option is unknown
++ error = g_strdup_printf ("%s: Unknown option --%s.", command -> name, option -> longopt);
++ }
++ } else { // still long option
++ p ++;
++ }
++ } else if (state == in_argstart) { // option/command argument initialization
++ cmdarg_flags_t flags;
++ const char *err;
++ cmdarg_t *arg;
++
++ if (option) { // option argument
++ arg = &(option -> arg);
++ } else if (!command -> args || !command -> args[argno]) { // no need to parse arguments any further
++ break;
++ } else { // normal aurgument
++ arg = command -> args[argno];
++ }
++
++ if ((err = cmdopt_parse_argument(&p, &e, arg -> flags))) { // get argument value
++ if (!option) {
++ error = g_strdup_printf ("%s: Can't parse argument #%u: %s", command -> name, argno + 1, err);
++ else if (option -> shortopt) {
++ error = g_strdup_printf ("%s: Can't parse argument for option -%c: %s", command -> name, option -> shortopt, err);
++ } else {
++ error = g_strdup_printt ("%s: Can't parse argument for option --%s: %s", command -> name, option -> longopt, err);
++ }
++ } else {
++ arg -> value.arg = s;
++ arg -> flags |= cmdarg_visited;
++ if (option) { // option argument
++ option = NULL;
++ } else { // normal argument
++ if (argument -> flags & cmdarg_subcmd) { // subcommand
++ cmdopts_t *subcmd;
++ gsize n; // XXX put command list into chkdata
++ for (n = 0; command -> cmds[n]; n ++) {
++ if (!strcmp (s, command -> cmds[n] -> name)) {
++ subcmd = command -> cmds[n];
++ break;
++ }
++ }
++ if (subcmd != NULL) { // found subcommand
++ arg -> value.cmd = subcmd;
++ if ((error = cmdopts_parse_internal (&p, &e, subcmd))) {
++ gchar *err = error;
++ error = g_strdup_printf("%s %s", command -> name, err);
++ g_free (err);
++ }
++ } else { // wrong subcommand
++ error = g_strdup_printf("%s: Unable to find subcommand \"%s\".", command -> name, s);
++ }
++ }
++ argno ++;
++ }
++ }
++ state = in_space;
++ }
++ }
++
++ *pr = p;
++ *er = e;
++
++ // perform option argument checking
++ if (error == NULL && command -> opts) {
++ gsize n;
++ for (n = 0; command -> opts[n]; n ++) {
++ cmdopt_t *opt = command -> opts[n];
++ if (!(opt -> flags & cmdopt_switch)) { // not a switch
++ // needs checking and not checked already
++ if ((opt -> arg.flags & (cmdarg_check | cmdarg_visited)) && !(opt -> arg.flags & cmdarg_checked)) {
++ if (opt -> arg.type && opt -> arg.type -> check) { // checker is present
++ opt -> arg.flags |= cmdarg_checked;
++ if ((error = opt -> arg.type -> check (&(opt -> arg)))) {
++ gchar *err = error;
++ if (option -> shortopt) {
++ error = g_strdup_printf ("%s: Error in argument for option -%c: %s", command -> name, option -> shortopt, err);
++ } else {
++ error = g_strdup_printt ("%s: Error in argument for option --%s: %s", command -> name, option -> longopt, err);
++ }
++ g_free (err);
++ break;
++ }
++ }
++ }
++ }
++ }
++ }
++
++ // perform positional argument checking
++ if (error == NULL && command -> args) {
++ gsize n;
++ for (n = 0; command -> args[n]; n ++) {
++ cmdarg_t *arg = command -> args[n];
++ // needs checking and not checked already
++ if ((arg -> flags & (cmdarg_check | cmdarg_visited)) && !(arg -> flags & cmdarg_checked)) {
++ if (arg -> flags & cmdarg_subcmd) { // subcommand
++ if (!arg -> value.cmd) {
++ error = g_strdup_printf ("%s: No subcommand specified.", command -> name);
++ break;
++ }
++ } else { // normal argument
++ if (arg -> type && arg -> type -> check) {
++ arg -> flags |= cmdarg_checked;
++ if ((error = arg -> type -> check (arg))) {
++ gchar *err = error;
++ error = g_strdup_printf ("%s: Error in argument #%u: %s", command -> name, n, err);
++ g_free (err);
++ break;
++ }
++ }
++ }
++ }
++ }
++ }
++
++ return error;
+ }
+
+-// cmd_is_safe(name)
+-// Returns if command is safe or not
+-gboolean cmd_is_safe(const gchar *name)
++// cmdopts_free ( commanddef )
++// Free various parser data, used in parsing process
++static void cmdopts_free(cmdopts_t *command)
+ {
+- GSList *sel;
+- if (!name)
+- return FALSE;
+- for (sel = safe_commands; sel; sel = sel->next)
+- if (!strcmp((const char *)sel->data, name))
+- return TRUE;
+- return FALSE;
++ gsize n;
++ if (command -> opts) {
++ for (n = 0; command -> opts[n]; n ++) {
++ cmdopt_t *opt = command -> opts[n];
++ if (!(opt -> flags & cmdopt_switch)) { // not switch
++ if (opt -> arg.flags & cmdarg_checked) { // can free something
++ opt -> arg.flags &= ~cmdarg_checked;
++ if (opt -> arg.type && opt -> arg.type -> free) { // need to free something
++ opt -> arg.type -> free (&(opt -> arg));
++ }
++ }
++ }
++ }
++ }
++ if (command -> args) {
++ for (n = 0; command -> args[n]; n ++) {
++ cmdarg_t *arg = command -> args[n];
++ if (arg -> flags & cmdarg_subcmd) { // subcommand
++ if (arg -> value.cmd) {
++ cmdopts_free (arg -> value.cmd);
++ arg -> value.cmd = NULL;
++ }
++ } else { // normal argument
++ if (arg -> flags & cmdarg_checked) { // can free something
++ arg -> flags &= ~cmdarg_checked;
++ if (arg -> type && arg -> type -> free) { // need to free something
++ arg -> type.free (arg);
++ }
++ }
++ }
++ }
++ }
+ }
+
+-// cmd_init()
+-// Commands table initialization
+-// !!!
+-// After changing commands names and it arguments names here, you must change
+-// ones in init_bindings()!
+-//
+-void cmd_init(void)
++cmd_result_t cmd_execute_internal (gchar *commandline, cmdexe_flags_t flags)
+ {
+- cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add, NULL);
+- cmd_add("alias", "Add an alias", 0, 0, &do_alias, NULL);
+- cmd_add("authorization", "Manage subscription authorizations",
+- COMPL_AUTH, COMPL_JID, &do_authorization, NULL);
+- cmd_add("bind", "Add an key binding", 0, 0, &do_bind, NULL);
+- cmd_add("buffer", "Manipulate current buddy's buffer (chat window)",
+- COMPL_BUFFER, 0, &do_buffer, NULL);
+- cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable, NULL);
+- cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear, NULL);
+- cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color, NULL);
+- cmd_add("connect", "Connect to the server", 0, 0, &do_connect, NULL);
+- cmd_add("del", "Delete the current buddy", 0, 0, &do_del, NULL);
+- cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect, NULL);
+- cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo, NULL);
+- cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event,
+- NULL);
+- cmd_add("exit", "Exit mcabber", 0, 0, &do_exit, NULL);
+- cmd_add("group", "Change group display settings",
+- COMPL_GROUP, COMPL_GROUPNAME, &do_group, NULL);
+- cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help, NULL);
+- cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline, NULL);
+- cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info, NULL);
+- cmd_add("module", "Manipulations with modules", COMPL_MODULE, 0, &do_module,
+- NULL);
+- cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME,
+- 0, &do_move, NULL);
+- cmd_add("msay", "Send a multi-lines message to the selected buddy",
+- COMPL_MULTILINE, 0, &do_msay, NULL);
+- cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr, NULL);
+- cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY,
+- &do_otrpolicy, NULL);
+- cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp, NULL);
+- cmd_add("quit", "Exit the software", 0, 0, NULL, NULL);
+- cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml, NULL);
+- cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename, NULL);
+- cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID,
+- &do_request, NULL);
+- cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room, NULL);
+- cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
+- &do_roster, NULL);
+- cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say, NULL);
+- cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0,
+- &do_say_to, NULL);
+- cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh,
+- NULL);
+- cmd_add("set", "Set/query an option value", 0, 0, &do_set, NULL);
+- cmd_add("source", "Read a configuration file", 0, 0, &do_source, NULL);
+- cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status,
+- NULL);
+- cmd_add("status_to", "Show or set your status for one recipient",
+- COMPL_JID, COMPL_STATUS, &do_status_to, NULL);
+- cmd_add("version", "Show mcabber version", 0, 0, &do_version, NULL);
+-
+- cmd_set_safe("set", TRUE);
+- cmd_set_safe("bind", TRUE);
+- cmd_set_safe("alias", TRUE);
+- cmd_set_safe("pgp", TRUE);
+- cmd_set_safe("source", TRUE);
+- cmd_set_safe("status", TRUE);
+- cmd_set_safe("color", TRUE);
+- cmd_set_safe("otrpolicy", TRUE);
+- cmd_set_safe("module", TRUE);
+-
+- // Status category
+- compl_add_category_word(COMPL_STATUS, "online");
+- compl_add_category_word(COMPL_STATUS, "avail");
+-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+- compl_add_category_word(COMPL_STATUS, "invisible");
+-#endif
+- compl_add_category_word(COMPL_STATUS, "free");
+- compl_add_category_word(COMPL_STATUS, "dnd");
+- compl_add_category_word(COMPL_STATUS, "notavail");
+- compl_add_category_word(COMPL_STATUS, "away");
+- compl_add_category_word(COMPL_STATUS, "offline");
+- compl_add_category_word(COMPL_STATUS, "message");
+-
+- // Roster category
+- compl_add_category_word(COMPL_ROSTER, "bottom");
+- compl_add_category_word(COMPL_ROSTER, "top");
+- compl_add_category_word(COMPL_ROSTER, "up");
+- compl_add_category_word(COMPL_ROSTER, "down");
+- compl_add_category_word(COMPL_ROSTER, "group_prev");
+- compl_add_category_word(COMPL_ROSTER, "group_next");
+- compl_add_category_word(COMPL_ROSTER, "hide");
+- compl_add_category_word(COMPL_ROSTER, "show");
+- compl_add_category_word(COMPL_ROSTER, "toggle");
+- compl_add_category_word(COMPL_ROSTER, "display");
+- compl_add_category_word(COMPL_ROSTER, "hide_offline");
+- compl_add_category_word(COMPL_ROSTER, "show_offline");
+- compl_add_category_word(COMPL_ROSTER, "toggle_offline");
+- compl_add_category_word(COMPL_ROSTER, "item_lock");
+- compl_add_category_word(COMPL_ROSTER, "item_unlock");
+- compl_add_category_word(COMPL_ROSTER, "item_toggle_lock");
+- compl_add_category_word(COMPL_ROSTER, "alternate");
+- compl_add_category_word(COMPL_ROSTER, "search");
+- compl_add_category_word(COMPL_ROSTER, "unread_first");
+- compl_add_category_word(COMPL_ROSTER, "unread_next");
+- compl_add_category_word(COMPL_ROSTER, "note");
+- compl_add_category_word(COMPL_ROSTER, "resource_lock");
+- compl_add_category_word(COMPL_ROSTER, "resource_unlock");
+-
+- // Buffer category
+- compl_add_category_word(COMPL_BUFFER, "clear");
+- compl_add_category_word(COMPL_BUFFER, "bottom");
+- compl_add_category_word(COMPL_BUFFER, "top");
+- compl_add_category_word(COMPL_BUFFER, "up");
+- compl_add_category_word(COMPL_BUFFER, "down");
+- compl_add_category_word(COMPL_BUFFER, "search_backward");
+- compl_add_category_word(COMPL_BUFFER, "search_forward");
+- compl_add_category_word(COMPL_BUFFER, "readmark");
+- compl_add_category_word(COMPL_BUFFER, "date");
+- compl_add_category_word(COMPL_BUFFER, "%");
+- compl_add_category_word(COMPL_BUFFER, "purge");
+- compl_add_category_word(COMPL_BUFFER, "close");
+- compl_add_category_word(COMPL_BUFFER, "close_all");
+- compl_add_category_word(COMPL_BUFFER, "scroll_lock");
+- compl_add_category_word(COMPL_BUFFER, "scroll_unlock");
+- compl_add_category_word(COMPL_BUFFER, "scroll_toggle");
+- compl_add_category_word(COMPL_BUFFER, "list");
+- compl_add_category_word(COMPL_BUFFER, "save");
+-
+- // Group category
+- compl_add_category_word(COMPL_GROUP, "fold");
+- compl_add_category_word(COMPL_GROUP, "unfold");
+- compl_add_category_word(COMPL_GROUP, "toggle");
+-
+- // Multi-line (msay) category
+- compl_add_category_word(COMPL_MULTILINE, "abort");
+- compl_add_category_word(COMPL_MULTILINE, "begin");
+- compl_add_category_word(COMPL_MULTILINE, "send");
+- compl_add_category_word(COMPL_MULTILINE, "send_to");
+- compl_add_category_word(COMPL_MULTILINE, "toggle");
+- compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim");
+- compl_add_category_word(COMPL_MULTILINE, "verbatim");
+-
+- // Room category
+- compl_add_category_word(COMPL_ROOM, "affil");
+- compl_add_category_word(COMPL_ROOM, "ban");
+- compl_add_category_word(COMPL_ROOM, "bookmark");
+- compl_add_category_word(COMPL_ROOM, "destroy");
+- compl_add_category_word(COMPL_ROOM, "invite");
+- compl_add_category_word(COMPL_ROOM, "join");
+- compl_add_category_word(COMPL_ROOM, "kick");
+- compl_add_category_word(COMPL_ROOM, "leave");
+- compl_add_category_word(COMPL_ROOM, "names");
+- compl_add_category_word(COMPL_ROOM, "nick");
+- compl_add_category_word(COMPL_ROOM, "privmsg");
+- compl_add_category_word(COMPL_ROOM, "remove");
+- compl_add_category_word(COMPL_ROOM, "role");
+- compl_add_category_word(COMPL_ROOM, "setopt");
+- compl_add_category_word(COMPL_ROOM, "topic");
+- compl_add_category_word(COMPL_ROOM, "unban");
+- compl_add_category_word(COMPL_ROOM, "unlock");
+- compl_add_category_word(COMPL_ROOM, "whois");
+-
+- // Authorization category
+- compl_add_category_word(COMPL_AUTH, "allow");
+- compl_add_category_word(COMPL_AUTH, "cancel");
+- compl_add_category_word(COMPL_AUTH, "request");
+- compl_add_category_word(COMPL_AUTH, "request_unsubscribe");
+-
+- // Request (query) category
+- compl_add_category_word(COMPL_REQUEST, "last");
+- compl_add_category_word(COMPL_REQUEST, "ping");
+- compl_add_category_word(COMPL_REQUEST, "time");
+- compl_add_category_word(COMPL_REQUEST, "vcard");
+- compl_add_category_word(COMPL_REQUEST, "version");
+-
+- // Events category
+- compl_add_category_word(COMPL_EVENTS, "accept");
+- compl_add_category_word(COMPL_EVENTS, "ignore");
+- compl_add_category_word(COMPL_EVENTS, "reject");
+-
+- // PGP category
+- compl_add_category_word(COMPL_PGP, "disable");
+- compl_add_category_word(COMPL_PGP, "enable");
+- compl_add_category_word(COMPL_PGP, "force");
+- compl_add_category_word(COMPL_PGP, "info");
+- compl_add_category_word(COMPL_PGP, "setkey");
+-
+- // OTR category
+- compl_add_category_word(COMPL_OTR, "start");
+- compl_add_category_word(COMPL_OTR, "stop");
+- compl_add_category_word(COMPL_OTR, "fingerprint");
+- compl_add_category_word(COMPL_OTR, "smpq");
+- compl_add_category_word(COMPL_OTR, "smpr");
+- compl_add_category_word(COMPL_OTR, "smpa");
+- compl_add_category_word(COMPL_OTR, "info");
+- compl_add_category_word(COMPL_OTR, "key");
+-
+- // OTR Policy category
+- compl_add_category_word(COMPL_OTRPOLICY, "plain");
+- compl_add_category_word(COMPL_OTRPOLICY, "manual");
+- compl_add_category_word(COMPL_OTRPOLICY, "opportunistic");
+- compl_add_category_word(COMPL_OTRPOLICY, "always");
+-
+- // Color category
+- compl_add_category_word(COMPL_COLOR, "roster");
+- compl_add_category_word(COMPL_COLOR, "muc");
+- compl_add_category_word(COMPL_COLOR, "mucnick");
+-
+-#ifdef MODULES_ENABLE
+- // Module category
+- compl_add_category_word(COMPL_MODULE, "info");
+- compl_add_category_word(COMPL_MODULE, "list");
+- compl_add_category_word(COMPL_MODULE, "load");
+- compl_add_category_word(COMPL_MODULE, "unload");
+-#endif
++ gchar *s = commandline;
++ gchar *p;
++ gchar *e;
++ gchar *freeme = NULL;
++ const char *err;
++ gchar *error;
++ cmdopts_t *command = NULL;
++ const char *alias = NULL;
++ gsize n;
++
++ // skip command indicator and spaces at the beginning
++ while (*s == COMMAND_CHAR || *s == ' ')
++ s ++;
++ gchar *p = s;
++ gchar *e = s + strlen (s);
++
++ // separate first word - command name
++ if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
++ scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
++ return cmd_result_syntax_error;
++ }
++
++ // check for quit command
++ if (!strcmp (s, "quit")) {
++ return cmd_result_quit;
++ }
++
++ // check if we're in verbatim mode
++ if ((flags & cmdexe_check_verbatim) && scr_get_multimode() == 2) {
++ if (strcmp(s, "msay")) { // it is not msay
++ return cmd_result_verbatim;
++ }
++ }
++
++ // check and expand alias
++ if ((alias = settings_get(SETTINGS_TYPE_ALIAS, s))) {
++ freeme = s = g_strdup_printf ("%s %s", alias, p);
++ p = s;
++ e = s + strlen (s);
++
++ if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
++ scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
++ g_free (freeme);
++ return cmd_result_syntax_error;
++ }
++
++ // check for quit command again
++ if (!strcmp (s, "quit")) {
++ g_free (freeme);
++ return cmd_result_quit;
++ }
++ }
++
++ // find command with this name
++ for (n = 0; n < cmd_count; n ++) {
++ if (!strcmp(s, cmd_list[n] -> name)) {
++ command = cmd_list[n];
++ break;
++ }
++ }
++ if (command == NULL) {
++ scr_log_print(LPRINT_NORMAL, "error: Unable to find command \"%s\".", s);
++ g_free(freeme);
++ return cmd_result_not_found;
++ }
++
++ // check safety
++ if ((flags & cmdexe_check_safe) && !(command -> flags & cmd_safe)) {
++ scr_log_print(LPRINT_NORMAL, "error: Command \"%s\" is not safe to execute here.", command -> name);
++ g_free(freeme);
++ return cmd_result_not_found;
++ }
++
++ // parse command line
++ if ((error = cmdopts_parse_internal (&p, &e, command))) {
++ scr_log_print(LPRINT_NORMAL, "%s", error);
++ g_free(error);
++ cmdopts_free (command);
++ g_free(freeme);
++ return cmd_result_syntax_error;
++ }
++
++ // execute command handler
++ if (command -> handler) {
++ if ((error = command -> handler(command))) {
++ scr_log_print(LPRINT_NORMAL, "%s: %s", command -> name, error);
++ g_free(error);
++ cmdopts_free (command);
++ g_free(freeme);
++ return cmd_result_runtime_error;
++ }
++ }
++
++ // free resources
++ cmdopts_free(command);
++ g_free(freeme);
++ return cmd_result_ok;
+ }
+
+-// expandalias(line)
+-// If there is one, expand the alias in line and returns a new allocated line
+-// If no alias is found, returns line
+-// Note: if the returned pointer is different from line, the caller should
+-// g_free() the pointer after use
+-char *expandalias(const char *line)
++// process_line(line)
++// Process a command/message line. If this isn't a command, this is a message
++// and it is sent to the currently selected buddy.
++// Returns 255 if the line is the /quit command, 0 on success and some other
++// error codes.
++cmd_result_t process_line(const char *line)
+ {
+- const char *p1, *p2;
+- char *word;
+- const gchar *value;
+- char *newline = (char*)line;
+-
+- // Ignore leading COMMAND_CHAR
+- for (p1 = line ; *p1 == COMMAND_CHAR ; p1++)
+- ;
+- // Locate the end of the word
+- for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+- ;
+- // Extract the word and look for an alias in the list
+- word = g_strndup(p1, p2-p1);
+- value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word);
+- g_free(word);
+-
+- if (value)
+- newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2);
+-
+- return newline;
+-}
+-
+-// cmd_get
+-// Finds command in the command list structure.
+-// Returns a pointer to the cmd entry, or NULL if command not found.
+-cmd *cmd_get(const char *command)
+-{
+- const char *p1, *p2;
+- char *com;
+- GSList *sl_com;
+-
+- // Ignore leading COMMAND_CHAR
+- for (p1 = command ; *p1 == COMMAND_CHAR ; p1++)
+- ;
+- // Locate the end of the command
+- for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+- ;
+- // Copy the clean command
+- com = g_strndup(p1, p2-p1);
+-
+- // Look for command in the list
+- for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) {
+- if (!strcasecmp(com, ((cmd*)sl_com->data)->name))
+- break;
+- }
+- g_free(com);
+-
+- if (sl_com) // Command has been found.
+- return (cmd*)sl_com->data;
+- return NULL;
+-}
+-
+-// process_command(line, iscmd)
+-// Process a command line.
+-// If iscmd is TRUE, process the command even if verbatim mmode is set;
+-// it is intended to be used for key bindings.
+-// Return 255 if this is the /quit command, and 0 for the other commands.
+-int process_command(const char *line, guint iscmd)
+-{
+- char *p;
+- char *xpline;
+- cmd *curcmd;
+-
+- if (!line)
+- return 0;
+-
+- // We do alias expansion here
+- if (iscmd || scr_get_multimode() != 2)
+- xpline = expandalias(line);
+- else
+- xpline = (char*)line; // No expansion in verbatim multi-line mode
+-
+- // We want to use a copy
+- if (xpline == line)
+- xpline = g_strdup(line);
+-
+- // Remove trailing spaces:
+- for (p=xpline ; *p ; p++)
+- ;
+- for (p-- ; p>xpline && (*p == ' ') ; p--)
+- *p = 0;
+-
+- // Command "quit"?
+- if ((iscmd || scr_get_multimode() != 2)
+- && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) {
+- if (!xpline[5] || xpline[5] == ' ') {
+- g_free(xpline);
+- return 255;
+- }
+- } else if (iscmd && !strncasecmp(xpline, "quit", 4) &&
+- (!xpline[4] || xpline[4] == ' ')) {
+- // If iscmd is true we can have the command without the command prefix
+- // character (usually '/').
+- g_free(xpline);
+- return 255;
+- }
+-
+- // If verbatim multi-line mode, we check if another /msay command is typed
+- if (!iscmd && scr_get_multimode() == 2
+- && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
+- // It isn't an /msay command
- scr_append_multiline(xpline);
-+ gchar *utf8 = to_utf8(xpline);
-+ scr_append_multiline(utf8);
-+ g_free(utf8);
- g_free(xpline);
- return 0;
+- g_free(xpline);
+- return 0;
+- }
+-
+- // Commands handling
+- curcmd = cmd_get(xpline);
+-
+- if (!curcmd) {
+- scr_LogPrint(LPRINT_NORMAL, "Unrecognized command. "
+- "Please see the manual for a list of known commands.");
+- g_free(xpline);
+- return 0;
+- }
+- if (!curcmd->func) {
+- scr_LogPrint(LPRINT_NORMAL,
+- "This functionality is not yet implemented, sorry.");
+- g_free(xpline);
+- return 0;
+- }
+- // Lets go to the command parameters
+- for (p = xpline+1; *p && (*p != ' ') ; p++)
+- ;
+- // Skip spaces
+- while (*p && (*p == ' '))
+- p++;
+- // Call command-specific function
+- retval_for_cmds = 0;
+-#ifdef MODULES_ENABLE
+- if (curcmd->userdata)
+- (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
+- else
+- (*curcmd->func)(p);
+-#else
+- (*curcmd->func)(p);
+-#endif
+- g_free(xpline);
+- return retval_for_cmds;
+-}
+-
+-// process_line(line)
+-// Process a command/message line.
+-// If this isn't a command, this is a message and it is sent to the
+-// currently selected buddy.
+-// Return 255 if the line is the /quit command, or 0.
+-int process_line(const char *line)
+-{
++ gchar *utf8;
++ int retval;
++
+ if (!*line) { // User only pressed enter
+ if (scr_get_multimode()) {
+ scr_append_multiline("");
+@@ -556,75 +689,114 @@
+ }
+ if (current_buddy) {
+ if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
+- do_group("toggle");
++ group_cmd (BUDDATA(current_buddy), scmd_group_toggle);
+ else {
+ // Enter chat mode
+ scr_set_chatmode(TRUE);
+ scr_show_buddy_window();
+ }
+ }
+- return 0;
++ return cmd_result_input;
}
-@@ -568,10 +570,12 @@
- if (*line != COMMAND_CHAR) {
- // This isn't a command
-+ gchar *utf8 = to_utf8(line);
+- if (*line != COMMAND_CHAR) {
+- // This isn't a command
++ utf8 = to_utf8(line);
++ if (*line != COMMAND_CHAR) { // input line
if (scr_get_multimode())
- scr_append_multiline(line);
+ scr_append_multiline(utf8);
else
- say_cmd((char*)line, 0);
+- return 0;
+- }
+-
+- /* It is _probably_ a command -- except for verbatim multi-line mode */
+- return process_command(line, FALSE);
+-}
+-
+-// Helper routine for buffer item_{lock,unlock,toggle_lock}
+-// "lock" values: 1=lock 0=unlock -1=invert
+-static void roster_buddylock(char *bjid, int lock)
+-{
+- gpointer bud = NULL;
+- bool may_need_refresh = FALSE;
+-
+- // Allow special jid "" or "." (current buddy)
+- if (bjid && (!*bjid || !strcmp(bjid, ".")))
+- bjid = NULL;
+-
+- if (bjid) {
+- // The JID has been specified. Quick check...
+- if (check_jid_syntax(bjid)) {
+- scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+- "<%s> is not a valid Jabber ID.", bjid);
+- } else {
+- // Find the buddy
+- GSList *roster_elt;
+- roster_elt = roster_find(bjid, jidsearch,
+- ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
+- if (roster_elt)
+- bud = roster_elt->data;
+- else
+- scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster.");
+- may_need_refresh = TRUE;
+- }
+- } else {
+- // Use the current buddy
+- if (current_buddy)
+- bud = BUDDATA(current_buddy);
+- }
+-
+- // Update the ROSTER_FLAG_USRLOCK flag
+- if (bud) {
+- if (lock == -1)
+- lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
+- buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
+- if (may_need_refresh) {
+- buddylist_build();
+- update_roster = TRUE;
+ say_cmd(utf8, msgtype_not_set);
-+ g_free(utf8);
- return 0;
++ retval = 0;
++ } else { // command or verbatim multiline message
++ retval = cmd_execute_internal (utf8, cmdexe_check_verbatim);
++ if (retval == cmd_result_verbatim) {
++ g_free(utf8); // buffer has been corrupted by parser
++ utf8 = to_utf8(line);
++ scr_append_multiline(utf8);
++ retval = cmd_result_input;
+ }
}
++ g_free(utf8);
++
++ return retval;
+ }
-@@ -755,7 +759,7 @@
+-static void roster_resourcelock(char *jidres, gboolean lock) {
++//
++// Standard types
++//
++// This section contains standard argument type definitions, used in built-in
++// mcabber commands.
++//
++
++// TODO: (and variations with 'required')
++// * cmdarg_type_roster_bjid - in roster, with specified types -> bud
++// * cmdarg_type_roster_fjid - in roster, with specified types -> bud + resource
++// * cmdarg_type_bjid - any bjid -> bjid
++// * cmdarg_type_fjid - any fjid -> fjid
++// * cmdarg_type_statusmask
++// * cmdarg_type_uint
++// * cmdarg_type_nonspace - space only -> null
++// * cmdarg_type_bjidmask
++// * cmdarg_type_color
++// * cmdarg_type_string2enum
++// * cmdarg_type_nick - provide completions first from current room, then from all other, nonspace, do not restrict
++
++// Uses chkdata as guint with allowed ROSTER_TYPE_*.
++// Returns buddy roster entry in value.bud.
++// Recognizes as current ".", but not "" or NULL - use defvalue.
++// Does not require freeing.
++gchar *cmdarg_check_roster_bjid_required(optarg_t *arg)
++{
++ const char *bjid;
++ guint types;
++ gchar *error;
++
++ if ((error = cmdarg_check_nospace_required(arg)))
++ return error;
++
++ bjid = arg -> value.arg;
++ types = (guint) arg -> chkdata;
++
++ if (strcmp(bjid, ".")) { // jid specified
++ GSList *found;
++ if (check_jid_syntax(bjid))
++ return g_strdup_printf("<%s> is not a valid Jabber ID.", bjid);
++ // find the buddy
++ found = roster_find(bjid, jidsearch, types);
++ if (found)
++ arg -> value.bud = found->data;
++ else
++ return g_strdup_printf("Jid <%s> is not in the roster.", bjid);
++ } else { // current buddy
++ if (!current_buddy)
++ return g_strdup_printf("No jid specified, and no buddy selected.");
++ else if (buddy_gettype(BUDDATA(current_buddy)) & types)
++ arg -> value.bud = BUDDATA(current_buddy);
++ else // TODO: improve message
++ return g_strdup_printf("Currently selected buddy is of wrong type.");
++ }
++
++ return NULL;
++}
++
++// The same as bjid_required, but in the case of error just sets value.bud
++// to NULL.
++gchar *cmdarg_check_roster_bjid(optarg_t *arg)
++{
++ gchar *error = cmdarg_check_roster_bjid_required(arg);
++
++ if (error) {
++ g_free (error);
++ arg -> value.bud = NULL;
++ }
++
++ return NULL;
++}
++
++// FIXME check fjid
++// XXX can't specify resource in bud - fjid or bud+resource?
++// resource must be present in roster - cmdarg_check_fjid_in_roster?
++gchac *cmdarg_check_fjid(optarg_t *arg)
++{
+ gpointer bud = NULL;
+ char *resource = NULL;
+
+@@ -664,33 +836,61 @@
+ resource = jidres;
+ }
+ }
+-
+- if (bud && buddy_gettype(bud) & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
+- if (lock) {
+- GSList *resources, *p_res;
+- gboolean found = FALSE;
+- resources = buddy_getresources(bud);
+- for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+- if (!g_strcmp0((char*)p_res->data, resource))
+- found = TRUE;
+- g_free(p_res->data);
+- }
+- g_slist_free(resources);
+- if (!found) {
+- scr_LogPrint(LPRINT_NORMAL, "No such resource <%s>...", jidres);
+- return;
+- }
+- } else {
+- resource = NULL;
+- }
+- buddy_setactiveresource(bud, resource);
+- scr_update_chat_status(TRUE);
++
++ GSList *resources, *p_res;
++ gboolean found = FALSE;
++ resources = buddy_getresources(bud);
++ for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
++ if (!g_strcmp0((char*)p_res->data, resource))
++ found = TRUE;
++ g_free(p_res->data);
++ }
++ g_slist_free(resources);
++ if (!found) {
++ scr_LogPrint(LPRINT_NORMAL, "No such resource <%s>...", jidres);
++ return;
+ }
+ }
++
++//
++// Command definitions
++//
++// This section contains definitions for built-in mcabber commands.
++// If you add a definition here, that should be automatically loaded,
++// you also have to add it to cmd_init().
++//
++
++//
++// /roster
++//
++
++// Helper routine for /roster item_{lock,unlock,toggle_lock}
++// "lock" values: 1=lock 0=unlock -1=invert
++static void roster_buddylock(gpointer bud, int lock)
++{
++ // Update the ROSTER_FLAG_USRLOCK flag
++ if (lock == -1)
++ lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
++ buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
++ if ((!current_buddy) || bud == BUDDATA(current_buddy)) {
++ buddylist_build();
++ update_roster = TRUE;
++ }
++}
++
++static void roster_resourcelock(gpointer bud, const char *resource, gboolean lock)
++{
++ if (lock)
++ buddy_setactiveresource(bud, resource);
++ else
++ buddy_setactiveresource(buf, NULL);
++ scr_update_chat_status(TRUE);
++}
++
+ // display_and_free_note(note, winId)
+ // Display the note information in the winId buffer, and free note
+ // (winId is a bare jid or NULL for the status window, in which case we
+-// display the note jid too)
++// display the note jid too).
+ static void display_and_free_note(struct annotation *note, const char *winId)
+ {
+ gchar tbuf[128];
+@@ -755,41 +955,15 @@
g_slist_free(notes);
}
-static void roster_note(char *arg)
-+static void roster_note(gchar *arg)
++static void roster_note(gpointer bud, gboolean reset, gchar *note)
{
- const char *bjid;
- guint type;
-@@ -781,14 +785,9 @@
- }
-
- if (arg && *arg) { // Set a note
+- const char *bjid;
+- guint type;
+-
+- if (!current_buddy)
+- return;
+-
+- bjid = buddy_getjid(BUDDATA(current_buddy));
+- type = buddy_gettype(BUDDATA(current_buddy));
+-
+- if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) {
+- // We're in the status window (the only special buffer currently)
+- // Let's display all server notes
+- display_all_annotations();
+- return;
+- }
+-
+- if (!bjid || (type != ROSTER_TYPE_USER &&
+- type != ROSTER_TYPE_ROOM &&
+- type != ROSTER_TYPE_AGENT)) {
+- scr_LogPrint(LPRINT_NORMAL, "This item can't have a note.");
+- return;
+- }
+-
+- if (arg && *arg) { // Set a note
- gchar *msg, *notetxt;
- msg = to_utf8(arg);
- if (!strcmp(msg, "-"))
@@ -466,19 +1860,105 @@
- notetxt = msg;
- xmpp_set_storage_rosternotes(bjid, notetxt);
- g_free(msg);
-+ if (!strcmp(arg, "-"))
-+ arg = NULL; // delete note
-+ xmpp_set_storage_rosternotes(bjid, arg);
- } else { // Display a note
+- } else { // Display a note
++ const char *bjid = buddy_getjid(bud);
++
++ if (note) // set note
++ xmpp_set_storage_rosternotes(bjid, note);
++ else if (reset) // delete note
++ xmpp_set_storage_rosternotes(bjid, NULL);
++ else { // display a note
struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
if (note) {
-@@ -819,175 +818,228 @@
- /* All these do_*() functions will be called with a "arg" parameter */
- /* (with arg not null) */
+ display_and_free_note(note, bjid);
+@@ -800,194 +974,244 @@
+ }
+ }
+-// roster_updown(updown, nitems)
+-// updown: -1=up, +1=down
+-inline static void roster_updown(int updown, char *nitems)
++typedef enum {
++ scmd_roster_bottom, scmd_roster_top, scmd_roster_up, scmd_roster_down,
++ scmd_roster_group_prev, scmd_roster_group_next,
++ scmd_roster_alternate,
++ scmd_roster_unread_first, scmd_roster_unread_next,
++ scmd_roster_search,
++ scmd_roster_display,
++ scmd_roster_hide_offline, scmd_roster_show_offline, scmd_roster_toggle_offline,
++ scmd_roster_item_lock, scmd_roster_item_unlock, scmd_roster_item_toggle_lock,
++ scmd_roster_note, scmd_roster_notes,
++ scmd_roster_resource_lock, scmd_roster_resource_unlock,
++ scmd_roster_hide, scmd_roster_show, scmd_roster_toggle,
++} scmd_roster_t;
++
++#define SCMD_ROSTER(NAME, ARGS) \
++ { #NAME, NULL, NULL, NULL, ARGS, NULL, (gpointer)scmd_roster_##NAME }
++static cmdopts_t def_roster = {
++ "roster",
++ NULL,
++ do_roster,
++ NULL,
++ {{cmdarg_subcmd | cmdarg_check, NULL, NULL}, NULL},
++ {
++ SCMD_ROSTER(bottom, NULL),
++ SCMD_ROSTER(top, NULL),
++ SCMD_ROSTER(up, {{cmdarg_check, "1", cmdarg_type_uint}, NULL}),
++ SCMD_ROSTER(down, {{cmdarg_check, "1", cmdarg_type_uint}, NULL}),
++ SCMD_ROSTER(group_prev, NULL),
++ SCMD_ROSTER(group_next, NULL),
++ SCMD_ROSTER(alternate, NULL),
++ SCMD_ROSTER(unread_first, NULL),
++ SCMD_ROSTER(unread_next, NULL),
++ SCMD_ROSTER(search, {{cmdarg_catchall|cmdarg_plain|cmdarg_check, NULL, cmdarg_type_nonspace}, NULL}),
++ SCMD_ROSTER(display, {{cmdarg_check, NULL, cmdarg_type_statusmask}, NULL}),
++ SCMD_ROSTER(hide_offline, NULL),
++ SCMD_ROSTER(show_offline, NULL),
++ SCMD_ROSTER(toggle_offline, NULL),
++ SCMD_ROSTER(item_lock, {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++ SCMD_ROSTER(item_unlock, {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++ SCMD_ROSTER(item_toggle_lock, {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++ { "note", NULL, NULL,
++ {
++ {cmdopt_switch, 'r', "reset", {cmdarg_default, NULL, NULL, NULL}},
++ {cmdopt_default, 'j', "jid", {cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT)}},
++ NULL,
++ },
++ {
++ {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++ NULL,
++ },
++ NULL, (gpointer)scmd_roster_note
++ },
++ SCMD_ROSTER(notes, NULL),
++ SCMD_ROSTER(resource_lock, {{cmdarg_check, NULL, cmdarg_type_fjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT}, NULL}),
++ SCMD_ROSTER(resource_unlock, {{cmdarg_check, NULL, cmdarg_type_fjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT}, NULL}),
++ SCMD_ROSTER(hide, NULL),
++ SCMD_ROSTER(show, NULL),
++ SCMD_ROSTER(toggle, NULL),
++ NULL,
++ },
++};
++
++static gchar *do_roster(cmdopts_t *options)
+ {
+- int nbitems;
+-
+- if (!nitems || !*nitems)
+- nbitems = 1;
+- else
+- nbitems = strtol(nitems, NULL, 10);
+-
+- if (nbitems > 0)
+- scr_roster_up_down(updown, nbitems);
+-}
+-
+-/* Commands callback functions */
+-/* All these do_*() functions will be called with a "arg" parameter */
+-/* (with arg not null) */
+-
-static void do_roster(char *arg)
-+static void do_roster(char *args)
- {
+-{
- char **paramlst;
- char *subcmd;
-
@@ -489,95 +1969,40 @@
- if (!subcmd || !*subcmd) {
- scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
- free_arg_lst(paramlst);
-+ enum roster_scmd_t {
-+ roster_scmd_bottom, roster_scmd_top, roster_scmd_up, roster_scmd_down,
-+ roster_scmd_group_prev, roster_scmd_group_next,
-+ roster_scmd_alternate,
-+ roster_scmd_unread_first, roster_scmd_unread_next,
-+ roster_scmd_search,
-+ roster_scmd_display,
-+ roster_scmd_hide_offline, roster_scmd_show_offline, roster_scmd_toggle_offline,
-+ roster_scmd_item_lock, roster_scmd_item_unlock, roster_scmd_item_toggle_lock,
-+ roster_scmd_note,
-+ roster_scmd_resource_lock, roster_scmd_resource_unlock,
-+ roster_scmd_hide, roster_scmd_show, roster_scmd_toggle,
-+ } subcmd;
-+#define ROSTER_SCMD_NOARG(NAME) \
-+ { #NAME, NULL, NULL, NULL, (gpointer)roster_scmd_##NAME, 0 }
-+// all of them have at most one argument
-+#define ROSTER_SCMD(NAME, FLAGS, VALUE) \
-+ { #NAME, NULL, \
-+ (cmdarg_t[1]){{ CMDOPT_LAST | FLAGS, { .arg = VALUE } }}, \
-+ NULL, (gpointer)roster_scmd_##NAME, 0 }
-+ cmdopts_t options = {
-+ "roster",
-+ NULL,
-+ (cmdarg_t[1]){
-+ { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } }
-+ },
-+ (cmdopts_t[23]){
-+ ROSTER_SCMD_NOARG(bottom),
-+ ROSTER_SCMD_NOARG(top),
-+ ROSTER_SCMD(up, 0, "1"), // num lines
-+ ROSTER_SCMD(down, 0, "1"), // num lines
-+ ROSTER_SCMD_NOARG(group_prev),
-+ ROSTER_SCMD_NOARG(group_next),
-+ ROSTER_SCMD_NOARG(alternate),
-+ ROSTER_SCMD_NOARG(unread_first),
-+ ROSTER_SCMD_NOARG(unread_next),
-+ ROSTER_SCMD(search, CMDOPT_REQUIRED | CMDOPT_CATCHALL, NULL), // to find
-+ ROSTER_SCMD(display, 0, NULL), // status mask
-+ ROSTER_SCMD_NOARG(hide_offline),
-+ ROSTER_SCMD_NOARG(show_offline),
-+ ROSTER_SCMD_NOARG(toggle_offline),
-+ ROSTER_SCMD(item_lock, 0, "."), // jid
-+ ROSTER_SCMD(item_unlock, 0, "."), // jid
-+ ROSTER_SCMD(item_toggle_lock, 0, "."), // jid
-+ ROSTER_SCMD(note, CMDOPT_CATCHALL, NULL), // note
-+ ROSTER_SCMD(resource_lock, 0, NULL), // resource/jid
-+ ROSTER_SCMD(resource_unlock, 0, NULL), // resource/jid
-+ ROSTER_SCMD_NOARG(hide),
-+ ROSTER_SCMD_NOARG(show),
-+ { "toggle", NULL, NULL, NULL, (gpointer)roster_scmd_toggle,
-+ CMDOPT_LAST },
-+ },
-+ NULL,
-+ };
-+ gchar *arg;
+- return;
+- }
+-
+- if (!strcasecmp(subcmd, "top")) {
++ scmd_roster_t subcmd = (scmd_roster_t) (options -> args[0] -> value.cmd -> userdata);
++ cmdarg_t *arg = NULL;
+
-+ if (cmdopts_parse(args, &options))
- return;
++ if (options -> args[0] -> value.cmd -> args)
++ arg = options -> args[0] -> value.cmd -> args[0];
+
-+ subcmd = (enum roster_scmd_t) (options.args[0].value.cmd -> userdata);
-+ if (options.args[0].value.cmd -> args != NULL) {
-+ arg = options.args[0].value.cmd -> args[0].value.arg;
- }
-
-- if (!strcasecmp(subcmd, "top")) {
-+ if (subcmd == roster_scmd_bottom) {
++ if (subcmd == scmd_roster_bottom) {
+ scr_roster_bottom();
+ update_roster = TRUE;
-+ } else if (subcmd == roster_scmd_top) {
++ } else if (subcmd == scmd_roster_top) {
scr_roster_top();
update_roster = TRUE;
- } else if (!strcasecmp(subcmd, "bottom")) {
- scr_roster_bottom();
-+ } else if (subcmd == roster_scmd_up) {
-+ roster_updown(-1, arg);
-+ } else if (subcmd == roster_scmd_down) {
-+ roster_updown(1, arg);
-+ } else if (subcmd == roster_scmd_group_prev) {
++ } else if (subcmd == scmd_roster_up) {
++ scr_roster_up_down(-1, arg -> value.uint);
++ } else if (subcmd == scmd_roster_down) {
++ scr_roster_up_down(1, arg -> value.uint);
++ } else if (subcmd == scmd_roster_group_prev) {
+ scr_roster_prev_group();
-+ } else if (subcmd == roster_scmd_group_next) {
++ } else if (subcmd == scmd_roster_group_next) {
+ scr_roster_next_group();
-+ } else if (subcmd == roster_scmd_alternate) {
++ } else if (subcmd == scmd_roster_alternate) {
+ scr_roster_jump_alternate();
-+ } else if (subcmd == roster_scmd_unread_first) {
++ } else if (subcmd == scmd_roster_unread_first) {
+ scr_roster_unread_message(0);
-+ } else if (subcmd == roster_scmd_unread_next) {
++ } else if (subcmd == scmd_roster_unread_next) {
+ scr_roster_unread_message(1);
-+ } else if (subcmd == roster_scmd_search) {
-+ scr_roster_search(arg);
++ } else if (subcmd == scmd_roster_search) {
++ scr_roster_search(arg -> value.arg);
update_roster = TRUE;
- } else if (!strcasecmp(subcmd, "hide")) {
- scr_roster_visibility(0);
@@ -586,35 +2011,32 @@
- } else if (!strcasecmp(subcmd, "toggle")) {
- scr_roster_visibility(-1);
- } else if (!strcasecmp(subcmd, "hide_offline")) {
-+ } else if (subcmd == roster_scmd_display) {
-+ scr_roster_display(arg);
-+ } else if (subcmd == roster_scmd_hide_offline) {
++ } else if (subcmd == scmd_roster_display) {
++ scr_roster_display(arg -> value.arg);
++ } else if (subcmd == scmd_roster_hide_offline) {
buddylist_set_hide_offline_buddies(TRUE);
- if (current_buddy)
+ if (current_buddy) // XXX
buddylist_build();
update_roster = TRUE;
- } else if (!strcasecmp(subcmd, "show_offline")) {
-+ } else if (subcmd == roster_scmd_show_offline) {
++ } else if (subcmd == scmd_roster_show_offline) {
buddylist_set_hide_offline_buddies(FALSE);
buddylist_build();
update_roster = TRUE;
- } else if (!strcasecmp(subcmd, "toggle_offline")) {
-+ } else if (subcmd == roster_scmd_toggle_offline) {
++ } else if (subcmd == scmd_roster_toggle_offline) {
buddylist_set_hide_offline_buddies(-1);
buddylist_build();
update_roster = TRUE;
- } else if (!strcasecmp(subcmd, "display")) {
- scr_roster_display(arg);
- } else if (!strcasecmp(subcmd, "item_lock")) {
-+ } else if (subcmd == roster_scmd_item_lock) {
- roster_buddylock(arg, 1);
+- roster_buddylock(arg, 1);
- } else if (!strcasecmp(subcmd, "item_unlock")) {
-+ } else if (subcmd == roster_scmd_item_unlock) {
- roster_buddylock(arg, 0);
+- roster_buddylock(arg, 0);
- } else if (!strcasecmp(subcmd, "item_toggle_lock")) {
-+ } else if (subcmd == roster_scmd_item_toggle_lock) {
- roster_buddylock(arg, -1);
+- roster_buddylock(arg, -1);
- } else if (!strcasecmp(subcmd, "unread_first")) {
- scr_roster_unread_message(0);
- } else if (!strcasecmp(subcmd, "unread_next")) {
@@ -639,29 +2061,92 @@
- } else if (!strcasecmp(subcmd, "group_next")) {
- scr_roster_next_group();
- } else if (!strcasecmp(subcmd, "note")) {
-+ } else if (subcmd == roster_scmd_note) {
- roster_note(arg);
+- roster_note(arg);
- } else if (!strcasecmp(subcmd, "resource_lock")) {
-+ } else if (subcmd == roster_scmd_resource_lock) {
- roster_resourcelock(arg, TRUE);
+- roster_resourcelock(arg, TRUE);
- } else if (!strcasecmp(subcmd, "resource_unlock")) {
-+ } else if (subcmd == roster_scmd_resource_unlock) {
- roster_resourcelock(arg, FALSE);
+- roster_resourcelock(arg, FALSE);
- } else
- scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
- free_arg_lst(paramlst);
-+ } else if (subcmd == roster_scmd_hide) {
++ } else if (subcmd == scmd_roster_item_lock) {
++ roster_buddylock(arg -> value.bud, 1);
++ } else if (subcmd == scmd_roster_item_unlock) {
++ roster_buddylock(arg -> value.bud, 0);
++ } else if (subcmd == scmd_roster_item_toggle_lock) {
++ roster_buddylock(arg -> value.bud, -1);
++ } else if (subcmd == scmd_roster_note) {
++ roster_note(options -> cmds[18] -> opts[1] -> value.bud,
++ options -> cmds[18] -> opts[0] -> value.swc,
++ arg -> value.arg);
++ } else if (subcmd == scmd_roster_notes) {
++ display_all_annotations();
++ } else if (subcmd == scmd_roster_resource_lock) { // FIXME implement fjid checker and decide where to get resource
++ roster_resourcelock(arg -> value.bud, resource, TRUE);
++ } else if (subcmd == scmd_roster_resource_unlock) {
++ roster_resourcelock(arg -> value.bud, NULL, FALSE);
++ } else if (subcmd == scmd_roster_hide) {
+ scr_roster_visibility(0);
-+ } else if (subcmd == roster_scmd_show) {
++ } else if (subcmd == scmd_roster_show) {
+ scr_roster_visibility(1);
-+ } else { // roster_scmd_toggle
++ } else { // scmd_roster_toggle
+ scr_roster_visibility(-1);
+ }
+
-+ cmdopts_free(&options);
++ return NULL;
}
- void do_color(char *arg)
+-void do_color(char *arg)
++//
++// /color
++//
++
++// TODO:
++// * cmdarg_type_color_statusmask - also accept "clear"
++// * cmdarg_type_color_roomjid - also accept "*"
++
++typedef enum {
++ scmd_color_roster,
++ scmd_color_mucnick,
++ scmd_color_muc,
++} scmd_color_t;
++
++static string2enum_t s2e_color_muc[] = {
++ { "on", MC_ALL },
++ { "off", MC_OFF },
++ { "preset", MC_PRESET },
++ { "-", MC_REMOVE },
++ { NULL, 0 },
++};
++
++cmdopts_t def_color = {
++ "color",
++ NULL,
++ do_color,
++ NULL,
++ {{ cmdarg_subcmd | cmdarg_check, NULL, NULL }, NULL},
++ {
++ {"roster", NULL, NULL, NULL, {
++ { cmdarg_check, NULL, cmdarg_type_color_statusmask },
++ { cmdarg_default, NULL, cmdarg_type_bjidmask },
++ { cmdarg_default, NULL, cmdarg_type_color },
++ NULL,
++ }, NULL, (gpointer)scmd_color_roster},
++ {"muc", NULL, NULL, NULL, {
++ { cmdarg_check, NULL, cmdarg_type_color_roomjid },
++ { cmdarg_check, "on", cmdarg_type_string2enum, (gpointer)s2e_color_muc},
++ NULL,
++ }, NULL, (gpointer)scmd_color_muc},
++ {"mucnick", NULL, NULL, NULL {
++ { cmdarg_check, NULL, cmdarg_type_nick },
++ { cmdarg_check, NULL, cmdarg_type_color },
++ NULL,
++ }, NULL, (gpointer)scmd_color_mucnick},
++ NULL,
++ },
++};
++
++static gchar *do_color(cmdopts_t *options)
{
- char **paramlst;
- char *subcmd;
@@ -673,45 +2158,7 @@
- if (!subcmd || !*subcmd) {
- scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
- free_arg_lst(paramlst);
-+ enum color_scmd_t {
-+ color_scmd_roster,
-+ color_scmd_mucnick,
-+ color_scmd_muc,
-+ } subcmd;
-+ cmdopts_t options = {
-+ "color",
-+ NULL,
-+ (cmdarg_t[1]){
-+ { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
-+ },
-+ (cmdopts_t[3]){
-+ { "roster", NULL,
-+ (cmdarg_t[3]){
-+ { CMDOPT_REQUIRED, { .arg = NULL } }, // status mask or "clear"
-+ { 0, { .arg = NULL } }, // jid mask
-+ { CMDOPT_LAST, { .arg = NULL } }, // color
-+ },
-+ NULL, (gpointer)color_scmd_roster, 0,
-+ },
-+ { "muc", NULL,
-+ (cmdarg_t[2]){
-+ { CMDOPT_REQUIRED, { .arg = NULL } }, // jid
-+ { CMDOPT_LAST, { .arg = "on" } }, // on/off/preset/-
-+ },
-+ NULL, (gpointer)color_scmd_muc, 0,
-+ },
-+ { "mucnick", NULL,
-+ (cmdarg_t[2]){
-+ { CMDOPT_REQUIRED, { .arg = NULL } }, // nick
-+ { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } }, // color
-+ },
-+ NULL, (gpointer)color_scmd_mucnick, CMDOPT_LAST,
-+ },
-+ },
-+ };
-+
-+ if (cmdopts_parse(arg, &options))
- return;
+- return;
- }
-
- if (!strcasecmp(subcmd, "roster")) {
@@ -723,21 +2170,21 @@
- color = arglist[2];
-
- if (status && !strcmp(status, "clear")) { // Not a color command, clear all
-+
-+ subcmd = (enum color_scmd_t) options.args[0].value.cmd -> userdata;
++ scmd_color_t subcmd = (scmd_color_t) options -> args[0] -> value.cmd -> userdata;
+
-+ if (subcmd == color_scmd_roster) {
-+ const gchar *status = options.cmds[0].args[0].value.arg;
-+ const gchar *wildcard = options.cmds[0].args[1].value.arg;
-+ const gchar *color = options.cmds[0].args[2].value.arg;
++ if (subcmd == scmd_color_roster) {
++ const gchar *status = options -> cmds[0] -> args[0] -> value.arg;
++ const gchar *wildcard = options -> cmds[0] -> args[1] -> value.arg;
++ const gchar *color = options -> cmds[0] -> args[2] -> value.arg;
+ if (!strcmp(status, "clear")) { // Not a color command, clear all
scr_roster_clear_color();
update_roster = TRUE;
} else {
- if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
- scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-+ if (!*status || !wildcard || !*wildcard || !color || !*color) {
-+ scr_log_print(LPRINT_NORMAL, "Missing argument");
++ if (!wildcard || !*wildcard || !color || !*color) {
++ // freaking "clear" :(
++ return g_strdup ("Missing argument.");
} else {
update_roster = scr_roster_color(status, wildcard, color) ||
update_roster;
@@ -770,28 +2217,8 @@
- else
- scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
- }
-+ } else if (subcmd == color_scmd_muc) {
-+ const gchar *muc = options.cmds[1].args[0].value.arg;
-+ const gchar *mode = options.cmds[1].args[1].value.arg;
-+ if (!strcmp(muc, "."))
-+ if (!(muc = CURRENT_JID))
-+ scr_LogPrint(LPRINT_NORMAL, "No JID selected");
-+ if (muc) {
-+ if (check_jid_syntax(muc) && strcmp(muc, "*"))
-+ scr_LogPrint(LPRINT_NORMAL, "Not a JID");
-+ else {
-+ if (!strcasecmp(mode, "on"))
-+ scr_muc_color(muc, MC_ALL);
-+ else if (!strcasecmp(mode, "preset"))
-+ scr_muc_color(muc, MC_PRESET);
-+ else if (!strcasecmp(mode, "off"))
-+ scr_muc_color(muc, MC_OFF);
-+ else if (!strcmp(mode, "-"))
-+ scr_muc_color(muc, MC_REMOVE);
-+ else
-+ scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
- }
- }
+- }
+- }
- free_arg_lst(arglist);
- g_free(free_muc);
- } else if (!strcasecmp(subcmd, "mucnick")) {
@@ -805,16 +2232,36 @@
- } else
- scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
- free_arg_lst(paramlst);
-+ } else { // color_scmd_mucnick
-+ const gchar *nick = options.cmds[2].args[0].value.arg;
-+ const gchar *color = options.cmds[2].args[1].value.arg;
-+ scr_muc_nick_color(nick, color);
++ } else if (subcmd == scmd_color_muc) {
++ scr_muc_color ( options -> cmds[1] -> args[0] -> value.arg,
++ options -> cmds[1] -> args[1] -> value.uint );
++ } else { // scmd_color_mucnick
++ scr_muc_nick_color( options -> cmds[2] -> args[0] -> value.arg,
++ options -> cmds[2] -> args[1] -> value.arg );
+ }
+
-+ cmdopts_free(&options);
++ return NULL;
}
-// cmd_setstatus(recipient, arg)
++//
++// /status
++//
++
++static string2enum_t s2e_status[] = {
++ { "away", away },
++ { "offline", offline },
++ { "online", available },
++ { "avail", available },
++ { "notavail", notavail },
++ { "dnd", dontdisturb },
++ { "free", freeforchat },
++#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
++ { "invisible", invisible },
++#endif
++ { NULL, 0 },
++};
++
+// cmd_setstatus(recipient, status, message)
// Set your Jabber status.
-// - if recipient is not NULL, the status is sent to this contact only
@@ -829,7 +2276,7 @@
enum imstatus st;
if (!xmpp_is_online())
-@@ -1000,15 +1052,15 @@
+@@ -1000,15 +1224,15 @@
if (!recipient)
scr_check_auto_away(TRUE);
@@ -850,7 +2297,7 @@
if (!strcasecmp(status, IMSTATUS_OFFLINE)) st = offline;
else if (!strcasecmp(status, IMSTATUS_ONLINE)) st = available;
else if (!strcasecmp(status, IMSTATUS_AVAILABLE)) st = available;
-@@ -1020,64 +1072,76 @@
+@@ -1020,64 +1244,88 @@
else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE)) st = notavail;
else if (!strcasecmp(status, IMSTATUS_FREE4CHAT)) st = freeforchat;
else if (!strcasecmp(status, "message")) {
@@ -882,43 +2329,51 @@
- free_arg_lst(paramlst);
}
- static void do_status(char *arg)
+-static void do_status(char *arg)
++
++cmdopts_t def_status = {
++ "status",
++ NULL,
++ do_status,
++ NULL,
++ {
++ {cmdarg_default, NULL, cmdarg_type_status_status, (string2enum_t)s2e_status2},
++ {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++ NULL,
++ },
++ NULL,
++};
++
++static gchar *do_status(cmdopts_t *options)
{
- if (!*arg) {
-+ cmdopts_t options = {
-+ "status",
-+ NULL,
-+ (cmdarg_t[2]){
-+ // status
-+ { 0, { .arg = NULL } },
-+ // message
-+ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+ },
-+ NULL,
-+ };
-+
-+ if (cmdopts_parse(arg, &options))
-+ return;
-+
+ if (options.args[0].value.arg == NULL) {
const char *sm = xmpp_getstatusmsg();
scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
imstatus2char[xmpp_getstatus()],
(sm ? sm : ""));
-- return;
+ } else {
+ cmd_setstatus(NULL, options.args[0].value.arg, options.args[1].value.arg);
- }
-- arg = to_utf8(arg);
-- cmd_setstatus(NULL, arg);
-- g_free(arg);
-+
-+ cmdopts_free(&options);
- }
-
- static void do_status_to(char *arg)
- {
-- char **paramlst;
++ }
++ return NULL;
++}
++
++cmdopts_t def_status_to = {
++ "status_to",
++ NULL,
++ do_status_to,
++ NULL,
++ { // FIXME NOT in roster, just any fjid, and as a string
++ {cmdarg_check, NULL, cmdarg_type_fjid},
++ {cmdarg_check, NULL, cmdarg_type_status_status, (string2enum_t)s2e_status2},
++ {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++ NULL,
++ },
++ NULL,
++};
++
++static gchar *do_status_to(cmdopts_t *options)
++{
+ cmdopts_t options = {
+ "status_to",
+ NULL,
@@ -932,7 +2387,20 @@
+ },
+ NULL,
+ };
- char *fjid, *st, *msg;
++ char *fjid, *st, *msg;
++
++ if (cmdopts_parse(arg, &options))
+ return;
+- }
+- arg = to_utf8(arg);
+- cmd_setstatus(NULL, arg);
+- g_free(arg);
+-}
+-
+-static void do_status_to(char *arg)
+-{
+- char **paramlst;
+- char *fjid, *st, *msg;
- char *jid_utf8 = NULL;
-
- paramlst = split_arg(arg, 3, 1); // jid, status, [message]
@@ -944,9 +2412,7 @@
- scr_LogPrint(LPRINT_NORMAL,
- "Please specify both a Jabber ID and a status.");
- free_arg_lst(paramlst);
-+
-+ if (cmdopts_parse(arg, &options))
- return;
+- return;
- }
+
+ fjid = options.args[0].value.arg;
@@ -955,7 +2421,7 @@
// Allow things like /status_to "" away
if (!*fjid || !strcmp(fjid, "."))
-@@ -1086,15 +1150,13 @@
+@@ -1086,15 +1334,13 @@
if (fjid) {
// The JID has been specified. Quick check...
if (check_jid_syntax(fjid)) {
@@ -972,7 +2438,7 @@
}
} else {
// Add the current buddy
-@@ -1105,144 +1167,190 @@
+@@ -1105,144 +1351,208 @@
}
if (fjid) {
@@ -995,6 +2461,7 @@
+ cmdopts_free(&options);
}
++#if 0
static void do_add(char *arg)
{
- char **paramlst;
@@ -1164,13 +2631,30 @@
+ cmdopts_free(&options);
+}
+
++#endif
++
++static void group_cmd (gpointer group, group_scmd_t action)
++{
++ // We'll have to redraw the chat window if we're not currently on the group
++ // entry itself, because it means we'll have to leave the current buddy
++ // chat window.
++ if (action != scmd_group_unfold &&
++ ((!current_buddy) ||
++ (group != BUDDATA(current_buddy) &&
++ group == buddy_getgroup(BUDDATA(current_buddy)))))
++ scr_roster_prev_group();
++
++ buddy_hide_group(group, action);
++
++ buddylist_build();
++ update_roster = TRUE;
++}
++
++#if 0
++
+static void do_group(char *arg)
+{
-+ enum group_scmd_t {
-+ group_scmd_unfold = 0,
-+ group_scmd_fold = 1,
-+ group_scmd_toggle = -1,
-+ } subcmd;
++ scmd_group_t subcmd;
+ cmdopts_t options = {
+ "group",
+ NULL,
@@ -1266,19 +2750,29 @@
if (roster_elt)
group = buddy_getgroup(roster_elt->data);
} else {
-@@ -1264,20 +1372,20 @@
+@@ -1253,31 +1563,19 @@
+ goto do_group_return;
+ }
+
+- // We'll have to redraw the chat window if we're not currently on the group
+- // entry itself, because it means we'll have to leave the current buddy
+- // chat window.
+- leave_buddywindow = (group != BUDDATA(current_buddy) &&
+- group == buddy_getgroup(BUDDATA(current_buddy)));
+-
+ if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) {
+ scr_LogPrint(LPRINT_NORMAL, "You need to select a group.");
goto do_group_return;
}
- if (group_state != group_unfold && leave_buddywindow)
-+ if (subcmd != group_scmd_unfold && leave_buddywindow)
- scr_roster_prev_group();
-
+- scr_roster_prev_group();
+-
- buddy_hide_group(group, group_state);
-+ buddy_hide_group(group, subcmd);
-
- buddylist_build();
- update_roster = TRUE;
+-
+- buddylist_build();
+- update_roster = TRUE;
++ group_cmd (group, subcmd);
do_group_return:
- free_arg_lst(paramlst);
@@ -1291,7 +2785,7 @@
{
char *bare_jid, *rp;
char *hmsg;
-@@ -1285,6 +1393,7 @@
+@@ -1285,6 +1583,7 @@
gint retval = 0;
int isroom;
gpointer xep184 = NULL;
@@ -1299,7 +2793,7 @@
if (!xmpp_is_online()) {
scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-@@ -1299,11 +1408,15 @@
+@@ -1299,11 +1598,15 @@
return 1;
}
if (check_jid_syntax((char*)fjid)) {
@@ -1317,7 +2811,7 @@
// We must use the bare jid in hk_message_out()
rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
if (rp)
-@@ -1354,8 +1467,7 @@
+@@ -1354,8 +1657,7 @@
// send_message(msg, subj, type_overwrite)
// Write the message in the buddy's window and send the message on
// the network.
@@ -1327,7 +2821,7 @@
{
const char *bjid;
char *jid;
-@@ -1378,34 +1490,13 @@
+@@ -1378,34 +1680,13 @@
else
jid = g_strdup(bjid);
@@ -1364,7 +2858,7 @@
scr_set_chatmode(TRUE);
scr_show_buddy_window();
-@@ -1424,80 +1515,131 @@
+@@ -1424,80 +1705,131 @@
}
buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -1545,7 +3039,7 @@
if (!scr_get_multimode()) {
scr_LogPrint(LPRINT_NORMAL, "No message to send. "
-@@ -1508,49 +1650,47 @@
+@@ -1508,49 +1840,47 @@
scr_set_chatmode(TRUE);
scr_show_buddy_window();
@@ -1631,7 +3125,7 @@
}
// load_message_from_file(filename)
-@@ -1566,7 +1706,7 @@
+@@ -1566,7 +1896,7 @@
char *next_utf8_char;
size_t len;
@@ -1640,7 +3134,7 @@
if (!fd || fstat(fileno(fd), &buf)) {
scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
-@@ -1634,130 +1774,103 @@
+@@ -1634,130 +1964,103 @@
static void do_say_to(char *arg)
{
@@ -1827,7 +3321,7 @@
}
// buffer_updown(updown, nblines)
-@@ -1775,27 +1888,10 @@
+@@ -1775,27 +2078,10 @@
scr_buffer_scroll_up_down(updown, nblines);
}
@@ -1855,7 +3349,7 @@
t = from_iso8601(date, 0);
if (t)
scr_buffer_date(t);
-@@ -1804,98 +1900,156 @@
+@@ -1804,98 +2090,156 @@
"not correctly formatted or invalid.");
}
@@ -2094,7 +3588,7 @@
}
static void do_info(char *arg)
-@@ -2033,29 +2187,20 @@
+@@ -2033,29 +2377,20 @@
}
}
@@ -2133,7 +3627,7 @@
// Enter chat mode
scr_set_chatmode(TRUE);
-@@ -2075,12 +2220,12 @@
+@@ -2075,12 +2410,12 @@
rstatus = buddy_getstatus(bud, p_res->data);
rst_msg = buddy_getstatusmsg(bud, p_res->data);
@@ -2148,7 +3642,7 @@
enum imrole role = buddy_getrole(bud, p_res->data);
enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
bool showaffil = (affil != affil_none);
-@@ -2096,12 +2241,12 @@
+@@ -2096,12 +2431,12 @@
snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
(char*)p_res->data);
scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
@@ -2163,7 +3657,7 @@
enum imrole role = buddy_getrole(bud, p_res->data);
enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
-@@ -2145,16 +2290,69 @@
+@@ -2145,16 +2480,69 @@
static void do_rename(char *arg)
{
@@ -2238,7 +3732,7 @@
bjid = buddy_getjid(bud);
group = buddy_getgroupname(bud);
type = buddy_gettype(bud);
-@@ -2162,11 +2360,13 @@
+@@ -2162,11 +2550,13 @@
if (type & ROSTER_TYPE_SPECIAL) {
scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
@@ -2253,7 +3747,7 @@
return;
}
-@@ -2181,90 +2381,117 @@
+@@ -2181,90 +2571,117 @@
// }
//}
@@ -2402,7 +3896,7 @@
} else {
// This is a local item, we move it without adding to roster.
guint msgflag;
-@@ -2276,7 +2503,7 @@
+@@ -2276,7 +2693,7 @@
msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
if (msgflag)
roster_msg_setflag(bjid, FALSE, FALSE);
@@ -2411,7 +3905,7 @@
if (msgflag)
roster_msg_setflag(bjid, FALSE, TRUE);
if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
-@@ -2285,8 +2512,7 @@
+@@ -2285,8 +2702,7 @@
}
}
@@ -2421,7 +3915,7 @@
update_roster = TRUE;
}
-@@ -2468,50 +2694,33 @@
+@@ -2468,50 +2884,33 @@
static void do_rawxml(char *arg)
{
@@ -2492,7 +3986,7 @@
}
// check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
-@@ -2874,7 +3083,7 @@
+@@ -2874,7 +3273,7 @@
fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
g_free (nick_utf8);
msg = to_utf8(arg);
@@ -2501,7 +3995,215 @@
g_free(fjid_utf8);
g_free(msg);
free_arg_lst(paramlst);
-@@ -3347,7 +3556,7 @@
+@@ -3290,6 +3689,207 @@
+
+ static void do_room(char *arg)
+ {
++ enum room_scmd_t {
++ room_scmd_join, room_scmd_leave,
++ room_scmd_names,
++ room_scmd_nick,
++ room_scmd_privmsg,
++ room_scmd_remove,
++ room_scmd_topic,
++ room_scmd_unlock,
++ room_scmd_destroy,
++ room_scmd_whois,
++ room_scmd_ban, room_scmd_unban,
++ room_scmd_invite, room_scmd_kick,
++ room_scmd_role, room_scmd_affil,
++ room_scmd_setopt, room_scmd_bookmark,
++ } subcmd;
++ cmdopts_t options = {
++ "room",
++ (cmdopt_t[1]){
++ { CMDOPT_LAST, 'j', "jid", { .opt = NULL } },
++ },
++ (cmdarg_t[1]){
++ { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[18]){
++ { "join", NULL,
++ (cmdarg_t[3]){
++ { 0, { .arg = "." } }, // room jid
++ { 0, { .arg = NULL } }, // nick
++ { CMDOPT_LAST, { .arg = NULL } }, // password
++ },
++ NULL, (gpointer)room_scmd_join, 0 },
++ { "leave", NULL,
++ (cmdarg_t[1]){
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_join, 0 },
++ { "names",
++ (cmdopt_t[4]){
++ { CMDOPT_SWITCH, 'd', "detail", { .swc = 0 } },
++ { CMDOPT_SWITCH, 's', "short", { .swc = 0 } },
++ { CMDOPT_SWITCH, 'q', "quiet", { .swc = 0 } },
++ { CMDOPT_SWITCH | CMDOPT_LAST, 'c', "compact", { .swc = 0 } },
++ },
++ NULL, NULL, (gpointe)room_scmd_names, 0 },
++ { "nick", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_nick, 0 },
++ { "privmsg", NULL,
++ (cmdarg_t[2]) {
++ // nick
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // message
++ { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
++ { .arg = NULL } },
++ }
++ NULL, (gpointer)room_scmd_privmsg, 0 },
++ { "remove", NULL, NULL, NULL, (gpointer)room_scmd_remove, 0 },
++ { "topic", NULL,
++ (cmdarg_t[1]){
++ // topic
++ { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
++ { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_topic, 0 },
++ { "unlock", NULL, NULL, NULL, (gpointer)room_scmd_unlock, 0 },
++ { "destroy", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_destroy, 0 },
++ { "whois", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
++ }, NULL, (gpointer)room_scmd_whois, 0 },
++ { "ban", NULL,
++ (cmdarg_t[2]){
++ // nick
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ }, NULL, (gpointer)room_scmd_ban, 0 },
++ { "unban", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
++ }, NULL, (gpointer)room_scmd_unban, 0 },
++ { "invite", NULL, // XXX [-d|direct] ?
++ (cmdarg_t[2]){
++ // jid
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_invite, 0 },
++ { "kick", NULL,
++ (cmdarg_t[2]){
++ // jid
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ },
++ NULL, (gpointer)room_scmd_kick, 0 },
++ { "role", NULL,
++ (cmdarg_t[3]){
++ // jid
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // role
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ }, NULL, (gpointer)room_scmd_role, 0 },
++ { "affil", NULL,
++ (cmdarg_t[3]){
++ // jid
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // affiliation
++ { CMDOPT_REQUIRED, { .arg = NULL } },
++ // reason
++ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++ }, NULL, (gpointer)room_scmd_affil, 0 },
++ // XXX below goes some crazy subcommands stuff, that should be rather static lists
++ { "setopt", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[3]){
++ { "print_status", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[4]){
++ { "default", NULL, NULL, NULL, NULL, 0 },
++ { "none", NULL, NULL, NULL, NULL, 0 },
++ { "in_and_out", NULL, NULL, NULL, NULL, 0 },
++ { "all", NULL, NULL, NULL, NULL, CMDOPT_LAST },
++ }, NULL, 0 },
++ { "auto_whois", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[3]){
++ { "default", NULL, NULL, NULL, NULL, 0 },
++ { "off", NULL, NULL, NULL, NULL, 0 },
++ { "on", NULL, NULL, NULL, NULL, CMDOPT_LAST },
++ }, NULL, 0 },
++ { "flag_joins", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[4]){
++ { "default", NULL, NULL, NULL, NULL, 0 },
++ { "none", NULL, NULL, NULL, NULL, 0 },
++ { "joins", NULL, NULL, NULL, NULL, 0 },
++ { "all", NULL, NULL, NULL, NULL, CMDOPT_LAST },
++ }, NULL, CMDOPT_LAST },
++ }, (gpointer)room_scmd_setopt, 0 },
++ { "bookmark", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[2]){
++ { "add", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[2]){
++ { "+autojoin", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDARG_LAST, { .arg = NULL } },
++ }, NULL, NULL, 0 },
++ { "-autojoin", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDARG_LAST, { .arg = NULL } },
++ }, NULL, NULL, CMDOPT_LAST },
++ }, NULL, 0 },
++ { "del", NULL,
++ (cmdarg_t[1]){
++ { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++ },
++ (cmdopts_t[2]){
++ { "+autojoin", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDARG_LAST, { .arg = NULL } },
++ }, NULL, NULL, 0 },
++ { "-autojoin", NULL,
++ (cmdarg_t[1]){
++ // nick
++ { CMDARG_LAST, { .arg = NULL } },
++ }, NULL, NULL, CMDOPT_LAST },
++ }, NULL, 0 },
++ }, (gpointer)room_scmd_bookmark, CMDOPT_LAST },
++ },
++ };
+ char **paramlst;
+ char *subcmd;
+ gpointer bud;
+@@ -3347,7 +3947,7 @@
cmd_room_leave(bud, arg);
} else if (!strcasecmp(subcmd, "names")) {
if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
@@ -2510,23 +4212,236 @@
} else if (!strcasecmp(subcmd, "nick")) {
if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
room_nick(bud, arg);
+@@ -4162,5 +4762,6 @@
+ }
+ mcabber_set_terminate_ui();
+ }
++#endif
+
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
diff -r 92fa48ef53c9 mcabber/mcabber/commands.h
--- a/mcabber/mcabber/commands.h Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.h Wed Feb 27 23:34:50 2013 +0200
-@@ -14,6 +14,12 @@
- gpointer userdata;
- } cmd;
++++ b/mcabber/mcabber/commands.h Tue Mar 05 01:08:31 2013 +0200
+@@ -5,32 +5,209 @@
+
+ #include <mcabber/config.h>
+
+-// Command structure
+-typedef struct {
+- char name[32];
+- const char *help;
+- guint completion_flags[2];
+- void (*func)(char *);
+- gpointer userdata;
+-} cmd;
++//
++// TODO:
++//
++// * Clustering of options/switches
++// * Multivalue options
++// - list
++// - special checker
++// - or run checker multiple times
++// - or run command several times
++// - what if there are several of such arguments?
++// * --help
++// - use argument type names and clarify, if needed in descriptions
++// - command/subcommand descriptions
++// - or maybe just do "/help foo"
++// * Add argument type field and subsystem to check values. Uses so far:
++// - fjid (handle . and ./resource, perform check_jid_syntax())
++// - bjid (handle ., strip resource, perform check_jid_syntax())
++// - bjid in roster/buddylist (result - buddy?)
++// - name in roster/buddylist (result - buddy?)
++// - group in roster (result - buddy?)
++// - other non-space string (strip trailing/starting spaces)
++// - statusmask (restrictive, check symbols)
++// - jidmask (non-restrictive, compl: inform what jids match)
++// - search string (non-restrictive, compl: inform what matches, if possible)
++// - rosternote (non-restrictive)
++// - color (restrictive, semi-defined list)
++// - nick (non-restrictive)
++// - defined lists (restrictive)
++// - date (restrictive)
++// - percent (numerical, restrictive)
++// - number (result - int)
++// - filename (expand_filename(), g_utf8_to_filename()?)
++// - custom (maybe some helpers for constant stringlists)
++// - text message (compl: nicks in rooms, dictionary?)
++// * Non-argument checks for commands/subcommands:
++// - xmpp_is_online()
++// - current_buddy
++// * Subcommands with fallback to argument?
++// * [+foo|-foo] support?
++// * Usable subsystem for completion, based on user-supplied completors
+-void cmd_init(void);
+-cmd *cmd_get(const char *command);
+-int process_line(const char *line);
+-int process_command(const char *line, guint iscmd);
+-char *expandalias(const char *line);
+-#ifdef MODULES_ENABLE
+-gpointer cmd_del(gpointer id);
+-gpointer cmd_add(const char *name, const char *help, guint flags1, guint flags2,
+- void (*f)(char*), gpointer userdata);
+-gboolean cmd_set_safe(const gchar *name, gboolean safe);
++// * for now we'll do without multi-options.
++// * and without clustering.
++// * checks
++// - argument type == public struct with name and callbacks, simple!
++// - command checks should be just single callbacks
++// * now, what checker needs?
++// - [in] value string (current or default)
++// - [in] additional input data for some generic checkers (like string2enum wordlist)
++// - [out] replacement value (not necessary string)
++// - replacement value may need freeing
++// - [out] error string
++// - that may need freeing
++
++typedef struct cmdopts_struct cmdopts_t;
++typedef struct cmdopt_struct cmdopt_t;
++typedef struct cmdarg_struct cmdarg_t;
++typedef struct cmdarg_value_struct cmdarg_value_t;
++typedef struct cmdarg_type_struct cmdarg_type_t;
++
++// note, this is called before options are parsed!
++typedef gchar *(*cmd_checker_t)(cmdopts_t *opts);
++// command function itself
++typedef gchar *(*cmd_handler_t)(cmdopts_t *opts);
++// should check arg -> val -> val.arg and replace, if needed
++typedef gchar *(*cmdarg_checker_t)(cmdarg_t *arg);
++// free resources, used in arg -> val -> val.arg, if needed
++typedef void (*cmdarg_destructor_t)(cmdarg_t *arg);
++// todo
++typedef void (*cmdarg_completor_t)(void); // FIXME
++
++typedef union {
++ guint uint; // unsigned integer
++ gint sint; // signed integer
++ guint swc; // switch count
++ const gchar *roarg; // default value
++ gchar *arg; // string argument
++ cmdopts_t *cmd; // subcommand
++ gpointer bud; // buddy data (roster entry)
++ gpointer ptr; // anything else
++} cmdarg_value_t;
++
++typedef enum {
++ cmd_default = 0, // no flags
++ cmd_safe = 1<<0, // command is safe to use in mcabberrc
++} cmd_flags_t;
++typedef enum {
++ cmdopt_default = 0, // no flags
++ cmdopt_switch = 1<<0, // option have no argument
++} cmdopt_flags_t;
++typedef enum {
++ cmdarg_default = 0, // no flags
++ cmdarg_catchall = 1<<0, // argument consumes the end of command line
++ cmdarg_plain = 1<<1, // quotes and escapes are not processed
++ cmdarg_subcmd = 1<<2, // argument is subcommand
++ cmdarg_check = 1<<3, // force checker call on argument
++ cmdarg_visited = 1<<4, // private, marks initialized arguments
++ cmdarg_checked = 1<<5, // private, marks checked argument
++} cmdarg_flags_t;
++
++struct cmdopts_struct {
++ const char *name; // [user,req] command name (error messages, help, subcommands)
++ cmd_flags_t flags; // [user,req] safe
++ cmd_checker_t check; // [user,req] checker routine
++ cmd_handler_t handle; // [user,req] main command processing function
++ cmdopt_t **opts; // [user,req] options
++ cmdarg_t **args; // [user,req] arguments
++ cmdopts_t **cmds; // [user,req] subcommands
++ gpointer userdata; // [user]
++};
++struct cmdopt_struct {
++ cmdopt_flags_t flags; // [user,req] switch
++ char shortopt; // [user,req]
++ const char *longopt; // [user,req]
++ cmdarg_t arg; // [user,req]
++};
++struct cmdarg_struct {
++ cmdarg_flags_t flags; // [user,req] catchall, plain, required, subcommand
++ const char *defval; // [user,req] default value
++ cmdarg_type_t *type; // [user,req] type cbs - checker and completor
++ gpointer chkdata; // [user] instance data for type checker - eg string2enum list
++ gpointer userdata; // [user]
++ cmdarg_value_t value; // [parser,chk] current value
++};
++
++struct cmdarg_type_struct {
++ const char *name; // [user,req] type name (help)
++ cmdarg_checker_t check; // [user,req] check string and set argument value
++ cmdarg_destructor_t free; // [user,req] free argument value
++ cmdarg_completor_t complete; // [user,req]
++};
++
++void cmd_define (cmdopts_t *command);
++void cmd_undef (cmdopts_t *command);
++
++// error cmdopts_parse_argument ( startptr, endptr, flags )
++// Parses next argument according to flags and finishes it with zero.
++// Updates current/end pointers. Parsed string MUST be writable.
++// String may shrink in size (quotation/escapes), thus endpointer is also
++// updated.
++const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags);
++
++typedef enum {
++ cmdexe_default = 0, // no flags
++ cmdexe_check_safe = 1<<0, // we're parsing main config file
++ cmdexe_check_verbatim = 1<<1, // check for verbatim input mode
++} cmdexe_flags_t;
++typedef enum {
++ cmd_result_ok = 0, // all is ok
++ cmd_result_syntax_error, // syntax, environment or argument check error
++ cmd_result_not_found, // no such command
++ cmd_result_runtime_error, // error while executing command
++ cmd_result_verbatim, // we're in verbatim mode and this is not "msay"
++ cmd_result_input, // [process_line only] line was a message
++ cmd_result_quit = 255, // this is hard-coded "quit" command
++} cmd_result_t;
++
++// error cmd_execute ( command, flags )
++// Checks for "quit", "msay" in verbatim mode, expands aliases, checks command
++// for safety of execution, parses command's arguments according to definition,
++// executes command function. Returns cmdexe_result_ok (0) if all was ok.
++// Non-zero values mean, that command was not executed for some reason.
++// One special value is cmd_result_quit (255) - if it is "quit" command.
++cmd_result_t cmd_execute (gchar *commandline, cmdexe_flags_t flags);
++
++// process_line(line)
++// Process a command/message line. If this isn't a command, this is a message
++// and it is sent to the currently selected buddy.
++// Returns 255 if the line is the /quit command, 0 on success and some other
++// error codes.
++cmd_result_t process_line(const char *line);
++
++//
++// Private
++//
++
++void cmd_init (void);
++void cmd_uninit (void);
++
++#if 0
++// error cmdopts_parse_internal ( startptr, endptr, commanddef )
++// Parses command arguments according to command definition.
++// Parsed string MUST be writable. Regardless of success or error, input
++// string should be considered corrupted by parsing process.
++// Even in case of error, commanddef should be passed to cmdopts_free().
++gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command);
++
++// cmdopts_free ( commanddef )
++// Free various parser data, used in parsing process
++void cmdopts_free(cmdopts_t *command);
+ #endif
+-gboolean cmd_is_safe(const gchar *name);
++
+typedef enum {
+ msgtype_not_set,
+ msgtype_normal,
+ msgtype_headline,
+} msgtype_t;
-+
- void cmd_init(void);
- cmd *cmd_get(const char *command);
- int process_line(const char *line);
-@@ -29,8 +35,9 @@
void cmd_room_whois(gpointer bud, const char *nick, guint interactive);
void cmd_room_leave(gpointer bud, char *arg);
@@ -2540,7 +4455,7 @@
diff -r 92fa48ef53c9 mcabber/mcabber/roster.c
--- a/mcabber/mcabber/roster.c Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/roster.c Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/mcabber/roster.c Tue Mar 05 01:08:31 2013 +0200
@@ -1586,13 +1586,14 @@
// Look for a buddy whose name or jid contains string.
// Search begins at current_buddy; if no match is found in the the buddylist,
@@ -2579,460 +4494,21 @@
if (found)
return buddy;
}
-diff -r 92fa48ef53c9 mcabber/mcabber/utils.c
---- a/mcabber/mcabber/utils.c Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.c Wed Feb 27 23:34:50 2013 +0200
-@@ -555,6 +555,318 @@
- *str = tolower(*str);
- }
-
-+// in_space -> in_space, in_optstart, in_argstart
-+// in_optstart -> in_shortoptend, in_longoptstart, in_argstart ('-')
-+// in_shortoptend -> in_space, in_argstart, error
-+// in_longoptstart -> in_longopt, in_space, in_argstart ('---')
-+// in_longopt -> in_longopt, in_space, error
-+// in_argstart -> in_arg, success
-+// in_arg -> in_arg, in_space, error
-+
-+// arguments: rw buffer in utf8, end of buffer pointer, options description struct
-+static const char *cmdopts_parse_internal(gchar *arg, gchar *e, cmdopts_t *options)
-+{
-+ // parser state
-+ enum {
-+ in_space,
-+ in_optstart,
-+ in_shortoptstart,
-+ in_shortoptend,
-+ in_longoptstart,
-+ in_longopt,
-+ in_argstart,
-+ in_arg,
-+ } state = in_space;
-+ // current pointer, start of object pointer
-+ gchar *p, *s;
-+ // inside of quotes in non-plain argument
-+ gboolean quotes = FALSE;
-+ // non-option argument or end-of-options marker encountered
-+ gboolean opts_ended = FALSE;
-+ // option, for which argument is currently parsed
-+ cmdopt_t *option = NULL;
-+ // argument, that we currently parse
-+ cmdarg_t *argument = NULL;
-+ // flags of option/argument
-+ guint flags = 0;
-+ // error message to return
-+ const char *error = NULL;
-+
-+ p = arg;
-+ // we allow parser to do one extra run on final '\0'
-+ while (p <= e && error == NULL) {
-+ if (state == in_space) { // space between args/options
-+ if (*p == ' ' || *p == '\0') { // still space
-+ p ++;
-+ } else if (*p == '-' && !opts_ended) { // option
-+ state = in_optstart;
-+ p ++;
-+ } else { // argument
-+ if (!option) {
-+ opts_ended = TRUE;
-+ }
-+ s = p;
-+ state = in_argstart;
-+ }
-+ } else if (state == in_optstart) { // long/short option
-+ if (*p == ' ' || *p == '\0') { // argument '-'
-+ opts_ended = TRUE;
-+ s = p - 1;
-+ state = in_argstart;
-+ } else if (*p == '-') { // long option
-+ state = in_longoptstart;
-+ p ++;
-+ } else { // short option
-+ s = p;
-+ state = in_shortoptend;
-+ p ++;
-+ }
-+ } else if (state == in_shortoptend) { // short option
-+ if (*p == ' ' || *p == '\0') { // option really ended
-+ gboolean found = FALSE;
-+ option = options -> opts;
-+ if (option) {
-+ do {
-+ if (option -> shortopt == *s) {
-+ found = TRUE;
-+ break;
-+ }
-+ } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
-+ }
-+ if (found) { // option is known
-+ if (option -> flags & CMDOPT_SWITCH) { // it is switch
-+ if (option -> flags & CMDOPT_CATCHALL) {
-+ option -> value.swc ++;
-+ } else {
-+ option -> value.swc = !option -> value.swc;
-+ }
-+ option = NULL;
-+ } else { // it is option
-+ if (*p == '\0') {
-+ error = "Short option argument not specified";
-+ }
-+ }
-+ state = in_space;
-+ p ++;
-+ } else { // option is unknown
-+ error = "Unknown short option";
-+ }
-+ } else { // short option not ended
-+ // consider it argument (to allow -autojoin)
-+ p --;
-+ opts_ended = TRUE;
-+ s = p - 1;
-+ state = in_argstart;
-+ }
-+ } else if (state == in_longoptstart) { // long option initialization
-+ if (*p == ' ' || *p == '\0') { // end of options '--'
-+ opts_ended = TRUE;
-+ state = in_space;
-+ p ++;
-+ } else if (*p == '-') { // argument, starting with '---'
-+ opts_ended = TRUE;
-+ s = p - 2;
-+ state = in_argstart;
-+ } else { // it is long option
-+ s = p;
-+ state = in_longopt;
-+ p ++;
-+ }
-+ } else if (state == in_longopt) { // long option name
-+ if (*p == ' ' || *p == '\0') { // long option ended
-+ gboolean found = FALSE;
-+ gboolean eof = *p == '\0';
-+ *p = '\0';
-+ option = options -> opts;
-+ if (option) {
-+ do {
-+ if (!g_strcmp0 (option -> longopt, s)) {
-+ found = TRUE;
-+ break;
-+ }
-+ } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
-+ }
-+ if (found) { // option is known
-+ if (option -> flags & CMDOPT_SWITCH) { // it is switch
-+ if (option -> flags & CMDOPT_CATCHALL) {
-+ option -> value.swc ++;
-+ } else {
-+ option -> value.swc = !option -> value.swc;
-+ }
-+ option = NULL;
-+ } else { // it is option
-+ if (eof) {
-+ error = "Long option argument not specified";
-+ }
-+ }
-+ state = in_space;
-+ p ++;
-+ } else { // option is unknown
-+ error = "Unknown long option";
-+ }
-+ } else { // still long option
-+ p ++;
-+ }
-+ } else if (state == in_argstart) { // option/command argument initialization
-+ if (option) {
-+ flags = option -> flags & ~CMDOPT_CATCHALL; // catchall in options indicates multi-options
-+ } else {
-+ if (!argument) {
-+ argument = options -> args;
-+ }
-+ if (!argument) { // no need to parse arguments at all
-+ break;
-+ }
-+ flags = argument -> flags;
-+ if ((flags & CMDOPT_CATCHALL) && (flags & CMDOPT_PLAIN)) { // can finish right away
-+ argument -> value.arg = s;
-+ break;
-+ }
-+ }
-+ quotes = FALSE;
-+ state = in_arg;
-+ } else if (state == in_arg) { // option/command argument value
-+ if (*p == '\0' && quotes) { // end of line in quotes
-+ error = "Unfinished quoted argument";
-+ } else if ((*p == ' ' && (!quotes) && !(flags & CMDOPT_CATCHALL)) || *p == '\0') { // argument ended
-+ if (*p != '\0') {
-+ *p = '\0';
-+ p ++;
-+ }
-+ if (option) { // option argument
-+ if (option -> flags & CMDOPT_CATCHALL) { // multi-value option
-+ option -> value.multiopt = g_slist_append (option -> value.multiopt, s);
-+ } else { // single-value option
-+ option -> value.opt = s;
-+ }
-+ option = NULL;
-+ } else { // command argument
-+ if (argument -> flags & CMDOPT_SUBCOMMAND) {
-+ gboolean found = FALSE;
-+ cmdopts_t *subcommand = options -> cmds;
-+ if (subcommand) {
-+ do {
-+ if (!g_strcmp0(s, subcommand -> name)) {
-+ found = TRUE;
-+ break;
-+ }
-+ } while (!(subcommand++ -> flags & CMDOPT_LAST));
-+ }
-+ if (found) {
-+ argument -> value.cmd = subcommand;
-+ error = cmdopts_parse_internal(p, e, subcommand);
-+ break;
-+ } else {
-+ error = "Unknown subcommand";
-+ }
-+ } else {
-+ argument -> value.arg = s;
-+ if (argument -> flags & CMDOPT_LAST) { // last argument
-+ break;
-+ }
-+ argument ++;
-+ }
-+ }
-+ state = in_space;
-+ } else if (*p == '\\' && !(flags & CMDOPT_PLAIN)) { // next char escape
-+ memmove(p, p+1, e-(p+1)+1);
-+ e --;
-+ if (*p == '\0') {
-+ error = "Escape at the end of line";
-+ }
-+ p ++;
-+ } else if (*p == '"' && !(flags & CMDOPT_PLAIN)) { // quotation start/end
-+ memmove(p, p+1, e-(p+1)+1);
-+ e --;
-+ quotes = !quotes;
-+ } else { // still argument
-+ p ++;
-+ }
-+ }
-+ }
-+
-+ // check required flags on options
-+ if (error == NULL && options -> opts) {
-+ option = options -> opts;
-+ do {
-+ if (option -> flags & CMDOPT_REQUIRED) {
-+ if (option -> flags & CMDOPT_SWITCH) {
-+ // no way to check trigger switches, but no point in it as well
-+ if (option -> flags & CMDOPT_CATCHALL && option -> value.swc == 0) {
-+ error = "Required switch is not specified";
-+ break;
-+ }
-+ } else {
-+ if ((option -> flags & CMDOPT_CATCHALL && option -> value.multiopt == NULL) ||
-+ ((!(option -> flags & CMDOPT_CATCHALL)) && option -> value.opt == NULL)) {
-+ error = "Required option is not specified";
-+ break;
-+ }
-+ }
-+ }
-+ } while (!(option++ -> flags & CMDOPT_LAST));
-+ }
-+
-+ // check required flags on arguments
-+ if (error == NULL && options -> args) {
-+ argument = options -> args;
-+ do {
-+ if (argument -> flags & CMDOPT_REQUIRED) {
-+ if (argument -> flags & CMDOPT_SUBCOMMAND) {
-+ if (argument -> value.cmd == NULL) {
-+ error = "Subcommand is not specified";
-+ break;
-+ }
-+ } else {
-+ if (argument -> value.arg == NULL) {
-+ error = "Required argument is not specified";
-+ break;
-+ }
-+ }
-+ }
-+ } while (!(argument++ -> flags & CMDOPT_LAST));
-+ }
-+
-+ return error;
-+}
-+
-+const char *cmdopts_parse(const char *arg, cmdopts_t *options)
-+{
-+ gchar *utf8 = to_utf8(arg);
-+ gchar *e;
-+ const char *error;
-+
-+ for (e = utf8; *e; e++);
-+ options -> freeme = utf8;
-+ error = cmdopts_parse_internal(utf8, e, options);
-+ if (error) {
-+ scr_log_print(LPRINT_NORMAL, "%s: %s", options -> name, error);
-+ cmdopts_free(options);
-+ }
-+ return error;
-+}
-+
-+void cmdopts_free(cmdopts_t *options)
-+{
-+ cmdopt_t *option = options -> opts;
-+ cmdopts_t *subcommand = options -> cmds;
-+ if (option) {
-+ do {
-+ if ((option -> flags & (CMDOPT_CATCHALL|CMDOPT_SWITCH)) == CMDOPT_CATCHALL) {
-+ g_slist_free(option -> value.multiopt);
-+ option -> value.multiopt = NULL;
-+ }
-+ } while (!(option++ -> flags & CMDOPT_LAST));
-+ }
-+ if (subcommand) {
-+ do {
-+ cmdopts_free(subcommand);
-+ } while (!(subcommand++ -> flags & CMDOPT_LAST));
-+ }
-+ g_free(options -> freeme);
-+ options -> freeme = NULL;
-+}
-+
- // strip_arg_special_chars(string)
- // Remove quotes and backslashes before an escaped quote
- // Only quotes need a backslash
-diff -r 92fa48ef53c9 mcabber/mcabber/utils.h
---- a/mcabber/mcabber/utils.h Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.h Wed Feb 27 23:34:50 2013 +0200
-@@ -43,6 +43,125 @@
- char **split_arg(const char *arg, unsigned int n, int dontstriplast);
- void free_arg_lst(char **arglst);
-
-+// error cmdopts_parse (argstring, optionlist)
-+// Function parses command argument string according to provided list of
-+// options and arguments. If in this process it encounters an error, it
-+// returns error string (that should be displayed and g_free'd afterwards).
-+// Note: For now returned error is constant string, that shouldn't be freed,
-+// but we're getting there.
-+// After processing you should free freeme and any GSList values of catchall
-+// options (only lists itself, not values). For your convenience, there is
-+// cmdopts_free(), that does exactly that.
-+// The function recognizes four kinds of expressions:
-+// - Options with arguments in a form '-f bar' or '--foo bar'
-+// - Switches without arguments in a form '-f' or '--foo'
-+// - End-of-options marker '--'
-+// - Individual arguments ('-' and '---' are considered arguments too)
-+// To define command line syntax, you pass cmdopts_t struct, that contains
-+// two contiguous lists of cmdopt_t and cmdarg_t structs accordingly. The
-+// last struct in list must have CMDOPT_LAST flag set.
-+// You can specify your own default values, they will be replaced/appended
-+// if needed.
-+// You can omit specifying longopt or shortopt (put NULL or '\0' there).
-+// Note: returned values and arguments are already converted to utf8.
-+
-+// Flags:
-+// Only applies to options, defined if option does not have argument.
-+#define CMDOPT_SWITCH ( 1<<0 )
-+// Don't process quotes and escapes in argument (applies to option arguments too).
-+#define CMDOPT_PLAIN ( 1<<1 )
-+// For options - put all encountered values into GSList value.multiopt
-+// instead of overwriting value.opt.
-+// For switches - increment value.swc instead of logical flipping.
-+// For arguments - grab the rest of the line without splitting on spaces.
-+// Implicitly last argument.
-+#define CMDOPT_CATCHALL ( 1<<2 )
-+// Option/argument must have value.
-+#define CMDOPT_REQUIRED ( 1<<3 )
-+// Last entry in struct sequence.
-+#define CMDOPT_LAST ( 1<<4 )
-+// Argument only, argument is the name for subcommand.
-+// Implicitly last argument.
-+#define CMDOPT_SUBCOMMAND ( 1<<5 )
-+
-+// TODO:
-+// * Options/arguments value should become 'default' field, value field will go
-+// later, so that user will not need to initialize it.
-+// * Clustering of options/switches
-+// * --help
-+// * Add argument names for --help.
-+// * Argument or command/subcommand descriptions for --help?
-+// * Or maybe just do "/help foo"?
-+// * Add argument type field and subsystem to check values. Uses so far:
-+// - fjid (handle . and ./resource, perform check_jid_syntax())
-+// - bjid (handle ., strip resource, perform check_jid_syntax())
-+// - bjid in roster/buddylist (result - buddy?)
-+// - name in roster/buddylist (result - buddy?)
-+// - group in roster (result - buddy?)
-+// - other non-space string (strip trailing/starting spaces)
-+// - statusmask (restrictive)
-+// - jidmask (non-restrictive)
-+// - search string
-+// - rosternote
-+// - color (restrictive, semi-defined list)
-+// - nick (non-restrictive)
-+// - defined lists (restrictive)
-+// - date (restrictive)
-+// - percent (numerical, restrictive)
-+// - number (result - int)
-+// - filename (expand_filename(), g_utf8_to_filename()?)
-+// - custom (maybe some helpers for constant stringlists)
-+// - text message (complete nicks in rooms)
-+// * Add non-argument checks for commands/subcommands:
-+// - xmpp_is_online()
-+// - current_buddy
-+// * --help generates error with short usage, based on info in options struct.
-+// * Subcommands with fallback to argument?
-+// * [+foo|-foo] support?
-+// * Integrate with commands
-+// * Integrate with completion
-+// * Usable subsystem for completion, based on user-supplied argument types
-+// * Update docs above
-+
-+// so, the process of command execution would look like:
-+// - we walk through the options, set default values
-+// - we parse argument string, populating options - on this or on next step
-+// - we check for required options availability - we can call generic argument check routine, based on argument type
-+// - we call callback
-+// - we free resources
-+typedef struct cmdopts_struct cmdopts_t;
-+typedef union {
-+ GSList *multiopt;
-+ gchar *opt;
-+ guint swc;
-+} cmdopt_value_t;
-+typedef struct {
-+ guint flags;
-+ char shortopt;
-+ const char *longopt;
-+ cmdopt_value_t value;
-+} cmdopt_t;
-+typedef union {
-+ gchar *arg;
-+ cmdopts_t *cmd;
-+} cmdarg_value_t;
-+typedef struct {
-+ guint flags;
-+ cmdarg_value_t value;
-+} cmdarg_t;
-+struct cmdopts_struct {
-+ const char *name;
-+ cmdopt_t *opts;
-+ cmdarg_t *args;
-+ cmdopts_t *cmds;
-+ gpointer userdata; // unused, for user's convenience
-+ guint flags; // only meaningful for subcommands
-+ gchar *freeme; // private
-+};
-+
-+const char *cmdopts_parse (const char *arg, cmdopts_t *options);
-+void cmdopts_free(cmdopts_t *options);
-+
- void replace_nl_with_dots(char *bufstr);
- char *ut_expand_tabs(const char *text);
- char *ut_unescape_tabs_cr(const char *text);
+diff -r 92fa48ef53c9 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/mcabber/screen.c Tue Mar 05 01:08:31 2013 +0200
+@@ -3626,7 +3626,7 @@
+ {
+ scr_check_auto_away(TRUE);
+ last_activity_buddy = current_buddy;
+- if (process_line(inputLine))
++ if (process_line(inputLine) == cmd_result_quit)
+ return 255;
+ // Add line to history
+ scr_cmdhisto_addline(inputLine);
diff -r 92fa48ef53c9 mcabber/mcabber/xmpp_iq.c
--- a/mcabber/mcabber/xmpp_iq.c Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/xmpp_iq.c Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/mcabber/xmpp_iq.c Tue Mar 05 01:08:31 2013 +0200
@@ -289,10 +289,7 @@
if (value) {
for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);