--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/doc/HOWTO_modules.txt Mon Oct 12 21:31:15 2009 +0200
@@ -0,0 +1,520 @@
+
+===========================================
+
+ Mcabber module writing brief howto
+
+===========================================
+
+Mcabber loads modules via glib's GModule.
+
+Thus, in your module you can provide functions
+
+--------------------------------------------------------
+ const gchar* g_module_check_init (GModule *module);
+ void g_module_unload (GModule *module);
+--------------------------------------------------------
+
+to do something when module is loaded and unloaded.
+
+As module is loaded, you can use mcabber functions,
+declared in mcabber's header files (though you should
+consider, that they may change their calling conventions
+some day).
+
+I will not explain them all, there are too much of
+them, but will provide description for those, provided
+especially for module writers.
+
+--------------------------------------------------------
+ #include "commands.h"
+
+ void cmd_add (const char *name, const char *help,
+ guint flags1, guint flags2,
+ void (*f)(char*), gpointer userdata);
+ void cmd_del (const char *name);
+--------------------------------------------------------
+
+These two functions are provided to declare mcabber
+commands, offered by your module.
+ - name is a command name.
+ - help is a short description of your command, however
+ for now it is not used at all and can be omitted.
+ - flags are completion identifiers for first and second
+ command arguments, for list of built-in completions,
+ see compl.h. You can declare your own completion
+ lists, using functions from compl.h, described later.
+ - f is a user-provided callback function, that will be
+ called upon executing mcabber command. If you will
+ provide non-NULL userdata, function must be of type
+ void (*f) (char *commandline, gpointer userdata).
+ - userdata is a pointer to data, transparently passed
+ to callback. See f description.
+
+--------------------------------------------------------
+ #include "compl.h"
+
+ guint compl_new_category (void);
+ void compl_del_category (guint id);
+
+ void compl_add_category_word (guint categ,
+ const char *command);
+ void compl_del_category_word (guint categ,
+ const char *word);
+ GSList *compl_get_category_list (guint cat_flags,
+ guint *dynlist);
+--------------------------------------------------------
+
+These functions allow you to define and manage word
+lists for completion categories, used by your commands.
+First you need to obtain handle for completion type,
+that you later will supply as flags, when declaring
+your commands. For that use function compl_new_category.
+It returns new category id or zero, if mcabber runs
+out of completion ids (for now there are only 32 ids
+available, and 20 of them are already taken by builtin
+commands). compl_del_category allows you to delete
+user-defined category, deleting all words in it too.
+
+Now, that you have a completion category, you can at any
+time add or delete words from it's completion list.
+For that use functions compl_add_category_word and
+compl_del_category_word. You can obtain current contents
+of category by using gompl_get_category_list. If after
+execution dynlist is TRUE, you should free obtained
+list of commands.
+
+--------------------------------------------------------
+ #include "hooks.h"
+
+ typedef struct {
+ const char *name;
+ const char *value;
+ } hk_arg_t;
+
+ typedef void (*hk_handler_t) (hk_arg_t *args,
+ gpointer userdata);
+
+ void hk_add_handler (hk_handler_t handler,
+ gpointer userdata);
+ void hk_del_handler (hk_handler_t handler,
+ gpointer userdata);
+--------------------------------------------------------
+
+These functions allow your module to react to events,
+such as incoming and outgoing messages, buddy status
+changes and sever connection establishment or breakup.
+In fact, you specify only one handler (well, you can
+specify as many, as you want, but they all will be
+called on any event, that will occur). Which event is
+occured can be determined from args, which is a list of
+hk_arg_t structures, terminated with structure, whose
+name field is set to NULL. Event type is specified in
+the structure with name set to "hook". Usually this is
+the first structure of the list, however it is not
+guaranted, that this will be so forever.
+
+Currently there are next events possible:
+ - hook-message-in with parameters
+ * jid - sender of the incoming message
+ * message - message body, converted to locale
+ charset
+ * groupchat ("true" or "false")
+ - hook-message-out with parameters
+ * jid - recipient of the outgoing message
+ * message - message body, converted to locale
+ charset
+ - hook-status-change wih parameters
+ * jid - buddy, whose status has changed
+ * resource - resource, whose status has changed
+ * old_status - old status of the buddy, one-char
+ string, representing mcabber status letter -
+ one of 'ofdna?_'.
+ * new_status - new buddy status. The same as
+ old_status.
+ * message - new status message. Old one should be
+ still available to module as the current buddy's
+ message.
+ - hook-my-status-change with parameters
+ * new_status - user's new status, see
+ hook-status-change. Old one should still be
+ available as the current status of the user.
+ * message - new status message
+ - hook-post-connect with no parameters
+ - hook-pre-disconnect with no parameters
+
+ #include "xmpp_helper.h"
+
+ void xmpp_add_feature (const char *xmlns);
+ void xmpp_del_feature (const char *xmlns);
+
+These functions may be useful, if your module implements
+some additional functionality to mcabber, that should be
+advertised in a client's discovery features list.
+
+=====================
+
+ Example: hello
+
+=====================
+
+Now, let's write a simple module, called "hello", that
+will do no more than just print something on loading
+and unloading.
+
+--------------------------------------------------------
+#include <glib.h>
+#include <gmodule.h>
+
+/* We will use scr_LogPrint mcabber function,
+ that does mcabber's messages output */
+#include "logprint.h"
+
+/* Print something on module loading */
+const gchar* g_module_check_init (GModule *module)
+{
+ scr_LogPrint (LPRINT_LOGNORM, "Hello, World!");
+ return NULL;
+}
+
+/* ... and unloading */
+void g_module_unload (GModule *module)
+{
+ scr_LogPrint (LPRINT_LOGNORM, "Bye, World!");
+}
+
+/* The End */
+--------------------------------------------------------
+
+Now, compile this file (hello.c) with
+
+libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
+ gmodule-2.0` -c hello.c
+libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
+ `pkg-config --libs glib-2.0 gmodule-2.0` -o libhello.la \
+ hello.lo
+
+(you should substitute /usr/lib/mcabber to directory, where
+your modules are located) and then install obtained module with
+
+libtool --mode=install install libhello.la \
+ /usr/lib/mcabber/libhello.la
+
+Note, that you, most likely need not run suggested by libtool
+finish action, as we're working with module object, not system-
+wide library, but maybe some systems require that.
+
+Now, set modules_dir mcabber variable to point to your modules
+dir, and try to run /load hello. If all goes well, you should
+see in status buffer message "Hello World!".
+Now unload module by running command /unload hello, that
+should bring up message "Bye, World!".
+
+That's it, you just created very simple dynamically loadable
+mcabber module.
+
+=======================
+
+ Example: command
+
+=======================
+
+Now, let's allow our module to do some real work.
+
+--------------------------------------------------------
+#include <glib.h>
+#include <gmodule.h>
+
+#include "logprint.h"
+#include "commands.h"
+
+/* Handler for command */
+void do_hello (char *args)
+{
+ /* args contains command line with command
+ * name and any spaces after it stripped */
+ scr_LogPrint (LPRINT_LOGNORM, "Hello, %s!",
+ *args != '\0' ? args : "World");
+}
+
+/* Register command */
+const gchar* g_module_check_init (GModule *module)
+{
+ cmd_add ("hello", "", 0, 0, do_hello, NULL);
+ return NULL;
+}
+
+/* Unregister command */
+void g_module_unload (GModule *module)
+{
+ cmd_del ("hello");
+}
+
+/* The End */
+--------------------------------------------------------
+
+There we will need also config.h with defined MODULES_ENABLE
+to satisfy ifdefs in commands.h. You can get one from mcabber
+build tree, generated by configure or just provide your own:
+
+--------------------------------------------------------
+#ifndef LOCAL_CONFIG_H
+#define LOCAL_CONFIG_H
+
+#define MODULES_ENABLE 1
+
+#endif
+--------------------------------------------------------
+
+Now, compile it and try to load and run /hello with some
+arguments.
+
+Note, that we used one-argument version of command
+handler, as we specified no userdata.
+
+==========================
+
+ Example: completion
+
+==========================
+
+Now le's investigate how to provide custom completion to
+your commands. You can as well use built-in completions,
+their IDs are listed in compl.h.
+
+--------------------------------------------------------
+#include <glib.h>
+#include <gmodule.h>
+
+#include "logprint.h"
+#include "commands.h"
+#include "compl.h"
+
+static guint hello_cid = 0;
+
+/* hello command handler */
+void do_hello (char *args)
+{
+ /* If argument is provided, add it to
+ * completions list. */
+ if (hello_cid && *args != '\0')
+ compl_add_category_word (hello_cid,
+ args);
+ scr_LogPrint (LPRINT_LOGNORM, "Hello, %s!",
+ *args != '\0' ? args : "World");
+}
+
+/* Initialization */
+const gchar* g_module_check_init (GModule *module)
+{
+ /* Obtain handle for our completion
+ * category */
+ hello_cid = compl_new_category ();
+ if (hello_cid)
+ /* Add known default word to
+ * completion list */
+ compl_add_category_word (hello_cid,
+ "World");
+ cmd_add ("hello", "", hello_cid, 0, do_hello,
+ NULL);
+ return NULL;
+}
+
+/* Deinitialization */
+void g_module_unload (GModule *module)
+{
+ /* Give back category handle */
+ if (hello_cid)
+ compl_del_category (hello_cid);
+ cmd_del ("hello");
+}
+
+/* The End */
+--------------------------------------------------------
+
+Now you can use completion for hello command. Note, that
+this code have some serious simplifications, made for
+simplicity reasons. For now, compl_add_category_word
+does not checks, if word already exists in completions
+list (although it is marked as TODO, so, some day it
+will), so, we should check it ourselves. Also, we should
+check, that args contains only one word, or this will
+confuse completion system, so, it will stop on this
+completion.
+
+=====================
+
+ Example: hooks
+
+=====================
+
+Now let's implement our own beeper. Why anyone may wish
+to do this? I am not satisfied with default mcabber's
+builtin beeper flexibility. I wanted beeping on any
+muc conference message, not just ones, directed to me.
+
+--------------------------------------------------------
+#include <glib.h>
+#include <gmodule.h>
+#include <string.h>
+
+#include "logprint.h"
+#include "commands.h"
+#include "compl.h"
+#include "hooks.h"
+#include "screen.h"
+#include "settings.h"
+
+static guint beep_cid = 0;
+
+/* Event handler */
+void beep_hh (hk_arg_t *args, gpointer userdata)
+{
+ /* We are interested only in incoming
+ * message events */
+ if (!strcmp (args[0].value, "hook-message-in"))
+ /* Check if beeping is enabled */
+ if (settings_opt_get_int ("beep_enable"))
+ /* *BEEP*! */
+ scr_Beep ();
+}
+
+/* beep command handler */
+void do_beep (char *args)
+{
+ /* Check arguments, and if recognized,
+ * set mcabber option accordingly */
+ if (!strcmp (args, "enable") ||
+ !strcmp (args, "on") ||
+ !strcmp (args, "yes") ||
+ !strcmp (args, "1"))
+ settings_set (SETTINGS_TYPE_OPTION,
+ "beep_enable", "1");
+ else if (!strcmp (args, "disable") ||
+ !strcmp (args, "off") ||
+ !strcmp (args, "no") ||
+ !strcmp (args, "0"))
+ settings_set (SETTINGS_TYPE_OPTION,
+ "beep_enable", "0");
+
+ /* Output current state, either if state is
+ * changed and if argument is not recognized */
+ if (settings_opt_get_int ("beep_enable"))
+ scr_LogPrint (LPRINT_NORMAL,
+ "Beep on messages is enabled");
+ else
+ scr_LogPrint (LPRINT_NORMAL,
+ "Beep on messages is disabled");
+}
+
+/* Initialization */
+const gchar* g_module_check_init (GModule *module)
+{
+ /* Create completions */
+ beep_cid = compl_new_category ();
+ if (beep_cid) {
+ compl_add_category_word (beep_cid, "enable");
+ compl_add_category_word (beep_cid, "disable");
+ }
+ /* Add command */
+ cmd_add ("beep", "", beep_cid, 0, do_beep, NULL);
+ /* Add handler */
+ hk_add_handler (beep_hh, NULL);
+ return NULL;
+}
+
+/* Deinitialization */
+void g_module_unload (GModule *module)
+{
+ /* Unregister event handler */
+ hk_del_handler (beep_hh, NULL);
+ /* Unregister command */
+ cmd_del ("beep");
+ /* Give back completion handle */
+ if (beep_cid)
+ compl_del_category (beep_cid);
+}
+
+/* The End */
+--------------------------------------------------------
+
+As you can see, here we used the fact, that right now
+all the hooks provide "hook" argument as a first element
+in args, however this can change in future.
+
+Note, that to compile this we also need to add loudmouth-1.0
+to pkg-config command line and to add -I. to compilation
+mode gcc command line (specify include directory with our
+config.h as system include directory), so, you will have
+something like
+
+libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
+ gmodule-2.0 loudmouth-1.0` -I. -c beep.c
+libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
+ `pkg-config --cflags glib-2.0 gmodule-2.0 loudmouth-1.0` \
+ -o libbeep.la beep.lo
+libtool --mode=install install libbeep.la \
+ /usr/lib/mcabber/libbeep.la
+
+If you use CMake (as do I), corresponding CMakeLists.txt
+snippet:
+
+--------------------------------------------------------
+cmake_minimum_required(VERSION 2.6)
+project(beep C)
+
+add_library(beep MODULE beep.c)
+
+set(MCABBER_INCLUDE_DIR "${beep_SOURCE_DIR}/include"
+ CACHE FILEPATH "Path to mcabber headers")
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GLIB REQUIRED glib-2.0)
+pkg_check_modules(GMODULE REQUIRED gmodule-2.0)
+pkg_check_modules(LM REQUIRED loudmouth-1.0)
+
+include_directories(SYSTEM ${GLIB_INCLUDE_DIRS}
+ ${GMODULE_INCLUDE_DIRS}
+ ${LM_INCLUDE_DIRS})
+target_link_libraries(beep ${GLIB_LIBRARIES}
+ ${GMODULE_LIBRARIES})
+include_directories(${beep_SOURCE_DIR}
+ ${beep_BINARY_DIR}
+ ${MCABBER_INCLUDE_DIR})
+
+install(TARGETS beep DESTINATION lib/mcabber)
+--------------------------------------------------------
+
+==============
+
+ Further
+
+==============
+
+As mcabber-lm uses glib mainloop, you can use glib's
+event sources, for example, fifo reading can be easily
+modularized with GIOChannels.
+
+You can extend xmpp part of mcabber functionality by
+providing lm message handlers with high priority and
+allowing unhandled by your handler messages be taken
+care by mcabber's handlers on normal priority level.
+This is where you may need to modify set of advertised
+supported disco features.
+
+Many useful examples can be found in my mcabber-lua
+module.
+
+If you think, that your module needs to change
+something, hardcoded in current implementation - feel
+free to mail me or join mcabber's MUC room and
+discuss this - for now I have only implemented things,
+that I found necessary for mcabber-lua module.
+
+Also I am not native English speaker, so, if you find
+some errors or non-natural constructs in this howto,
+please, inform me (I will be glad, if you also provide
+a more suitable version of text in question).
+
+ -- Myhailo Danylenko <isbear@ukrpost.net>
+ -- Mon, 05 Oct 2009 00:00:00 +0300
+