view mcabber/doc/HOWTO_modules.txt @ 1654:8effa82ae593

Do not print ssl error if user has not requested it
author Myhailo Danylenko <isbear@ukrpost.net>
date Sun, 29 Nov 2009 00:00:10 +0200
parents e3b93594ee6c
children 004739237999
line wrap: on
line source


===========================================

    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 <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 (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 <isbear@ukrpost.net>
  -- Mon, 05 Oct 2009 00:00:00 +0300