# HG changeset patch # User Myhailo Danylenko # Date 1255375875 -7200 # Node ID 2a82e6654c04ea9a11f529c4fb6e1d4e200e3450 # Parent 9296987856d9dea6356cd69044f6a2821ddc8ad3 Add a module writing howto diff -r 9296987856d9 -r 2a82e6654c04 mcabber/doc/HOWTO_modules.txt --- /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 +#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 (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 + -- Mon, 05 Oct 2009 00:00:00 +0300 +