Mercurial > hg
view mcabber/doc/HOWTO_modules.txt @ 1743:dcbf31f99fa3
Do not automatically append ":" to the "info" string
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Tue, 09 Mar 2010 20:45:57 +0100 |
parents | 5093b5ca1572 |
children | 4a7c7900f600 |
line wrap: on
line source
=========================================== Mcabber module writing brief howto =========================================== To obtain information on module mcabber uses struct module_info_t, that module should provide in public variable with name info_<modulename>. If module name contains any extra symbols except [a-z0-9_] they should be replaced with '_'. -------------------------------------------------------- #include <mcabber/modules.h> typedef void (*module_init_t)(void); typedef void (*module_uninit_t)(void); typedef struct { const gchar *mcabber_version; module_init_t init; module_uninit_t uninit; const gchar **requires; } module_info_t; -------------------------------------------------------- Callbacks init and uninit will be called after module and it's dependencies loading. 'requires' should contain NULL-terminated list of module names, that should be loaded before this. 'mcabber_version' is required and should contain mcabber version, that this module is designed to work with. Three other fields may be NULL. To load modules, mcabber uses glib's GModule, thus, in your module you can also use functions -------------------------------------------------------- #include <glib.h> #include <gmodule.h> const gchar* g_module_check_init (GModule *module); void g_module_unload (GModule *module); -------------------------------------------------------- to do something before any version/dependency checks will be performed when module is loaded/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 <mcabber/modules.h> const gchar *module_load (const gchar *name, gboolean manual, gboolean force); const gchar *module_unload (const gchar *name, gboolean manual, gboolean force); -------------------------------------------------------- These functions load and unload modules respectively. You can use them to handle optional dependencies. What happens, when module is loaded: - check if module is present, and if present just increase it's reference count - load .so via glib (and call g_module_check_init, if present) - check for information structure presence - check target mcabber version compatibility - load modules, that this module requires (note, that dependency problems will be reported as error invariably, force flag have no effect on this check) - module placed into a list of modules - module init routine is called And when unloaded: - check if module is present - decrease reference count, if it is not zero, return - run module uninit routine - unload modules, that were loaded as dependencies for this - remove from modules list They return error message or NULL in case of success. 'manual' flag indicates, that module will be loaded by direct user request. It serves the purpose of tracking user and automatic references (user can have only one). 'force' flag on module loading causes mcabber to ignore most of the loading errors. On unload it forces unloading even if reference count is not zero. -------------------------------------------------------- #include <mcabber/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 <mcabber/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 <mcabber/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_POST_CONNECT) with no parameters - hook-pre-disconnect (HOOK_PRE_DICSONNECT) with no parameters -------------------------------------------------------- #include <mcabber/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 <mcabber/logprint.h> /* Print something on module loading */ const gchar* g_module_check_init (GModule *module) { scr_LogPrint (LPRINT_NORMAL, "Hello, World!"); return NULL; } /* ... and unloading */ void g_module_unload (GModule *module) { scr_LogPrint (LPRINT_NORMAL, "Bye, World!"); } /* The End */ -------------------------------------------------------- Now, compile this file (hello.c) with libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \ gmodule-2.0 mcabber` -c hello.c libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ `pkg-config --libs glib-2.0 gmodule-2.0 mcabber` \ -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 /module -f load hello. If all goes well, you should see in status buffer message "Hello World!" (as well as some complaints, as we forced module loading). Now unload module by running command /module unload hello, that should bring up message "Bye, World!". That's it, you just created very simple dynamically loadable mcabber module. But, as you noticed, it needs force to be loaded. Now, let's add information structure, that mcabber wants. ========================== Example: info struct ========================== -------------------------------------------------------- #include <mcabber/logprint.h> /* module_info_t definition */ #include <mcabber/modules.h> /* Print something on module loading */ void hello_init (void) { scr_LogPrint (LPRINT_NORMAL, "Hello, World!"); } /* ... and unloading */ void hello_uninit (void) { scr_LogPrint (LPRINT_NORMAL, "Bye, World!"); } module_info_t info_hello = { .mcabber_version = "0.10.0", .requires = NULL, .init = hello_init, .uninit = hello_uninit, }; /* The End */ -------------------------------------------------------- Here we now do not use glib nor gmodule, so, we can omit them in compilation lines: libtool --mode=compile gcc `pkg-config --cflags mcabber` \ -c hello.c libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ `pkg-config --libs mcabber` -o libhello.la hello.lo Again compile it, copy, and try to load, now without -f flag. As you may notice, when loading previous example, mcabber first printed "Hello, World!", and only then complaint about module not having information struct. That's because g_module_check_init is called right after module loading, before mcabber even have a chance to look at module, while .init from info struct is called afterwards by mcabber itself. You can try to introduce some error (eg too high or missing target mcabber version) and see the difference. ======================= Example: command ======================= Now, let's allow our module to do some real work. -------------------------------------------------------- #include <mcabber/logprint.h> #include <mcabber/commands.h> #include <mcabber/modules.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_NORMAL, "Hello, %s!", *args != '\0' ? args : "World"); } /* Register command */ void hello_init (void) { cmd_add ("hello", "", 0, 0, do_hello, NULL); } /* Unregister command */ void hello_uninit (void) { cmd_del ("hello"); } module_info_t hello_info = { .mcabber_version = "0.10.0", .requires = NULL, .init = hello_init, .uninit = hello_uninit, } /* The End */ -------------------------------------------------------- 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 let'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 <mcabber/logprint.h> #include <mcabber/commands.h> #include <mcabber/modules.h> #include <mcabber/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_NORMAL, "Hello, %s!", *args != '\0' ? args : "World"); } /* Initialization */ void hello_init (void) { /* 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); } /* Deinitialization */ void hello_uninit (void) { /* Give back category handle */ if (hello_cid) compl_del_category (hello_cid); cmd_del ("hello"); } module_info_t hello_info = { .mcabber_version = "0.10.0", .requires = NULL, .init = hello_init, .uninit = hello_uninit, } /* 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. ===================== 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 <string.h> #include <mcabber/logprint.h> #include <mcabber/commands.h> #include <mcabber/compl.h> #include <mcabber/hooks.h> #include <mcabber/screen.h> #include <mcabber/settings.h> #include <mcabber/module.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 */ void beep_init (void) { /* 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); } /* Deinitialization */ void beep_uninit (void) { /* 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); } module_info_t beep_info = { .mcabber_version = "0.10.0", .requires = NULL, .init = beep_init, .uninit = beep_uninit, } /* The End */ -------------------------------------------------------- If you use CMake (as do I), corresponding CMakeLists.txt snippet: -------------------------------------------------------- cmake_minimum_required(VERSION 2.6) project(beep C) find_package(PkgConfig REQUIRED) pkg_check_modules(MCABBER REQUIRED mcabber) # this one should be before any target definitions link_directories(${MCABBER_LIBRARY_DIRS}) add_library(beep MODULE beep.c) include_directories(SYSTEM ${MCABBER_INCLUDE_DIRS}) target_link_libraries(beep ${MCABBER_LIBRARIES) include_directories(${beep_SOURCE_DIR} ${beep_BINARY_DIR}) install(TARGETS beep DESTINATION lib/mcabber) -------------------------------------------------------- =========================== Example: dependencies =========================== I will not provide here a complete example of two modules, one of which depends on other, only some use cases. Info struct for module, that depends on two other modules: -------------------------------------------------------- #include <mcabber/modules.h> const gchar *a_deps[] = { "b", "c", NULL }; module_info_t info_a = { .mcabber_version = "0.10.0", .requires = a_deps, .init = a_init, .uninit = a_uninit, }; -------------------------------------------------------- If your module needs to "authenticate" mcabber version too, this can be done in g_module_check_init: -------------------------------------------------------- #include <glib.h> #include <gmodule.h> #include <mcabber/main.h> const gchar *g_module_check_init (GModule *module) { char *ver = mcabber_version (); // ver now contains version in format // X.X.X[-xxx][ (XXXXXXXXX)] if (...) return "Incompatible mcabber version"; g_free (ver); return NULL; } -------------------------------------------------------- Also you can use glib check_init routine to modify module information, that will be checked by mcabber, eg. if you want your module to always pass mcabber version check, you can assign version, obtained from mcabber_version() to corresponding field in your struct. Or you can modify your module's dependencies, though direct module_load() will have the same effect, and can be used for optional dependencies, that your module can work without. Note: remember, that g_module_check_init will be always called, even if later module will not pass checks, thus: - do not use functions from other modules there; - provide g_module_unload to undo anything, check_init has done. ============== Further ============== As mcabber now uses glib mainloop, you can use glib's event sources, for example, fifo reading already uses GIOChannels for non-blocking IO. 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 modules, that can be found at http://isbear.unixzone.org.ua/source. 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 written by me modules. 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 -- mailto:isbear@ukrpost.net -- xmpp:isbear@unixzone.org.ua -- Thu, 04 Mar 2010 09:32:38 +0200