=========================================== 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. On success g_module_check_init should return NULL, and error message otherwise. 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 words (both, words and list). -------------------------------------------------------- #include "hooks.h" typedef struct { const char *name; const char *value; } hk_arg_t; typedef void (*hk_handler_t) (guint32 hookid, hk_arg_t *args, gpointer userdata); void hk_add_handler (hk_handler_t handler, guint32 flags, 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. Flags field specifies mask of events, upon which this handler should be called. Flags, that comprise this mask can be found in hooks.h. You can specify not yet used flags in mask, if you need to handle all events. Handler can determine, which event is occured by hookid argument and by a "hook" field in args, that may provide more precise information in some cases. Args argument is a list of hk_arg_t structures, terminated by structure, whose name field is set to NULL. Usually the "hook" field is in 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 (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 (HOOK_MESSAGE_OUT) with parameters * jid - recipient of the outgoing message * message - message body, converted to locale charset - hook-status-change (HOOK_STATUS_CHANGE) with 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 (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 (HOOK_INTERNAL) with no parameters - hook-pre-disconnect (HOOK_INTERNAL) 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 #include /* 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 #include #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 #include #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 #include #include #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 (guint32 hid, hk_arg_t *args, gpointer userdata) { /* 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 * We are only interested in incoming message events */ hk_add_handler (beep_hh, HOOK_MESSAGE_IN, 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 */ -------------------------------------------------------- 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 now 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 -- Mon, 05 Oct 2009 00:00:00 +0300