changeset 1619:2a82e6654c04

Add a module writing howto
author Myhailo Danylenko <isbear@ukrpost.net>
date Mon, 12 Oct 2009 21:31:15 +0200
parents 9296987856d9
children 52b63087f421
files mcabber/doc/HOWTO_modules.txt
diffstat 1 files changed, 520 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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
+