comparison mcabber/doc/HOWTO_modules.txt @ 1619:2a82e6654c04

Add a module writing howto
author Myhailo Danylenko <isbear@ukrpost.net>
date Mon, 12 Oct 2009 21:31:15 +0200
parents
children a75611931642
comparison
equal deleted inserted replaced
1618:9296987856d9 1619:2a82e6654c04
1
2 ===========================================
3
4 Mcabber module writing brief howto
5
6 ===========================================
7
8 Mcabber loads modules via glib's GModule.
9
10 Thus, in your module you can provide functions
11
12 --------------------------------------------------------
13 const gchar* g_module_check_init (GModule *module);
14 void g_module_unload (GModule *module);
15 --------------------------------------------------------
16
17 to do something when module is loaded and unloaded.
18
19 As module is loaded, you can use mcabber functions,
20 declared in mcabber's header files (though you should
21 consider, that they may change their calling conventions
22 some day).
23
24 I will not explain them all, there are too much of
25 them, but will provide description for those, provided
26 especially for module writers.
27
28 --------------------------------------------------------
29 #include "commands.h"
30
31 void cmd_add (const char *name, const char *help,
32 guint flags1, guint flags2,
33 void (*f)(char*), gpointer userdata);
34 void cmd_del (const char *name);
35 --------------------------------------------------------
36
37 These two functions are provided to declare mcabber
38 commands, offered by your module.
39 - name is a command name.
40 - help is a short description of your command, however
41 for now it is not used at all and can be omitted.
42 - flags are completion identifiers for first and second
43 command arguments, for list of built-in completions,
44 see compl.h. You can declare your own completion
45 lists, using functions from compl.h, described later.
46 - f is a user-provided callback function, that will be
47 called upon executing mcabber command. If you will
48 provide non-NULL userdata, function must be of type
49 void (*f) (char *commandline, gpointer userdata).
50 - userdata is a pointer to data, transparently passed
51 to callback. See f description.
52
53 --------------------------------------------------------
54 #include "compl.h"
55
56 guint compl_new_category (void);
57 void compl_del_category (guint id);
58
59 void compl_add_category_word (guint categ,
60 const char *command);
61 void compl_del_category_word (guint categ,
62 const char *word);
63 GSList *compl_get_category_list (guint cat_flags,
64 guint *dynlist);
65 --------------------------------------------------------
66
67 These functions allow you to define and manage word
68 lists for completion categories, used by your commands.
69 First you need to obtain handle for completion type,
70 that you later will supply as flags, when declaring
71 your commands. For that use function compl_new_category.
72 It returns new category id or zero, if mcabber runs
73 out of completion ids (for now there are only 32 ids
74 available, and 20 of them are already taken by builtin
75 commands). compl_del_category allows you to delete
76 user-defined category, deleting all words in it too.
77
78 Now, that you have a completion category, you can at any
79 time add or delete words from it's completion list.
80 For that use functions compl_add_category_word and
81 compl_del_category_word. You can obtain current contents
82 of category by using gompl_get_category_list. If after
83 execution dynlist is TRUE, you should free obtained
84 list of commands.
85
86 --------------------------------------------------------
87 #include "hooks.h"
88
89 typedef struct {
90 const char *name;
91 const char *value;
92 } hk_arg_t;
93
94 typedef void (*hk_handler_t) (hk_arg_t *args,
95 gpointer userdata);
96
97 void hk_add_handler (hk_handler_t handler,
98 gpointer userdata);
99 void hk_del_handler (hk_handler_t handler,
100 gpointer userdata);
101 --------------------------------------------------------
102
103 These functions allow your module to react to events,
104 such as incoming and outgoing messages, buddy status
105 changes and sever connection establishment or breakup.
106 In fact, you specify only one handler (well, you can
107 specify as many, as you want, but they all will be
108 called on any event, that will occur). Which event is
109 occured can be determined from args, which is a list of
110 hk_arg_t structures, terminated with structure, whose
111 name field is set to NULL. Event type is specified in
112 the structure with name set to "hook". Usually this is
113 the first structure of the list, however it is not
114 guaranted, that this will be so forever.
115
116 Currently there are next events possible:
117 - hook-message-in with parameters
118 * jid - sender of the incoming message
119 * message - message body, converted to locale
120 charset
121 * groupchat ("true" or "false")
122 - hook-message-out with parameters
123 * jid - recipient of the outgoing message
124 * message - message body, converted to locale
125 charset
126 - hook-status-change wih parameters
127 * jid - buddy, whose status has changed
128 * resource - resource, whose status has changed
129 * old_status - old status of the buddy, one-char
130 string, representing mcabber status letter -
131 one of 'ofdna?_'.
132 * new_status - new buddy status. The same as
133 old_status.
134 * message - new status message. Old one should be
135 still available to module as the current buddy's
136 message.
137 - hook-my-status-change with parameters
138 * new_status - user's new status, see
139 hook-status-change. Old one should still be
140 available as the current status of the user.
141 * message - new status message
142 - hook-post-connect with no parameters
143 - hook-pre-disconnect with no parameters
144
145 #include "xmpp_helper.h"
146
147 void xmpp_add_feature (const char *xmlns);
148 void xmpp_del_feature (const char *xmlns);
149
150 These functions may be useful, if your module implements
151 some additional functionality to mcabber, that should be
152 advertised in a client's discovery features list.
153
154 =====================
155
156 Example: hello
157
158 =====================
159
160 Now, let's write a simple module, called "hello", that
161 will do no more than just print something on loading
162 and unloading.
163
164 --------------------------------------------------------
165 #include <glib.h>
166 #include <gmodule.h>
167
168 /* We will use scr_LogPrint mcabber function,
169 that does mcabber's messages output */
170 #include "logprint.h"
171
172 /* Print something on module loading */
173 const gchar* g_module_check_init (GModule *module)
174 {
175 scr_LogPrint (LPRINT_LOGNORM, "Hello, World!");
176 return NULL;
177 }
178
179 /* ... and unloading */
180 void g_module_unload (GModule *module)
181 {
182 scr_LogPrint (LPRINT_LOGNORM, "Bye, World!");
183 }
184
185 /* The End */
186 --------------------------------------------------------
187
188 Now, compile this file (hello.c) with
189
190 libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
191 gmodule-2.0` -c hello.c
192 libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
193 `pkg-config --libs glib-2.0 gmodule-2.0` -o libhello.la \
194 hello.lo
195
196 (you should substitute /usr/lib/mcabber to directory, where
197 your modules are located) and then install obtained module with
198
199 libtool --mode=install install libhello.la \
200 /usr/lib/mcabber/libhello.la
201
202 Note, that you, most likely need not run suggested by libtool
203 finish action, as we're working with module object, not system-
204 wide library, but maybe some systems require that.
205
206 Now, set modules_dir mcabber variable to point to your modules
207 dir, and try to run /load hello. If all goes well, you should
208 see in status buffer message "Hello World!".
209 Now unload module by running command /unload hello, that
210 should bring up message "Bye, World!".
211
212 That's it, you just created very simple dynamically loadable
213 mcabber module.
214
215 =======================
216
217 Example: command
218
219 =======================
220
221 Now, let's allow our module to do some real work.
222
223 --------------------------------------------------------
224 #include <glib.h>
225 #include <gmodule.h>
226
227 #include "logprint.h"
228 #include "commands.h"
229
230 /* Handler for command */
231 void do_hello (char *args)
232 {
233 /* args contains command line with command
234 * name and any spaces after it stripped */
235 scr_LogPrint (LPRINT_LOGNORM, "Hello, %s!",
236 *args != '\0' ? args : "World");
237 }
238
239 /* Register command */
240 const gchar* g_module_check_init (GModule *module)
241 {
242 cmd_add ("hello", "", 0, 0, do_hello, NULL);
243 return NULL;
244 }
245
246 /* Unregister command */
247 void g_module_unload (GModule *module)
248 {
249 cmd_del ("hello");
250 }
251
252 /* The End */
253 --------------------------------------------------------
254
255 There we will need also config.h with defined MODULES_ENABLE
256 to satisfy ifdefs in commands.h. You can get one from mcabber
257 build tree, generated by configure or just provide your own:
258
259 --------------------------------------------------------
260 #ifndef LOCAL_CONFIG_H
261 #define LOCAL_CONFIG_H
262
263 #define MODULES_ENABLE 1
264
265 #endif
266 --------------------------------------------------------
267
268 Now, compile it and try to load and run /hello with some
269 arguments.
270
271 Note, that we used one-argument version of command
272 handler, as we specified no userdata.
273
274 ==========================
275
276 Example: completion
277
278 ==========================
279
280 Now le's investigate how to provide custom completion to
281 your commands. You can as well use built-in completions,
282 their IDs are listed in compl.h.
283
284 --------------------------------------------------------
285 #include <glib.h>
286 #include <gmodule.h>
287
288 #include "logprint.h"
289 #include "commands.h"
290 #include "compl.h"
291
292 static guint hello_cid = 0;
293
294 /* hello command handler */
295 void do_hello (char *args)
296 {
297 /* If argument is provided, add it to
298 * completions list. */
299 if (hello_cid && *args != '\0')
300 compl_add_category_word (hello_cid,
301 args);
302 scr_LogPrint (LPRINT_LOGNORM, "Hello, %s!",
303 *args != '\0' ? args : "World");
304 }
305
306 /* Initialization */
307 const gchar* g_module_check_init (GModule *module)
308 {
309 /* Obtain handle for our completion
310 * category */
311 hello_cid = compl_new_category ();
312 if (hello_cid)
313 /* Add known default word to
314 * completion list */
315 compl_add_category_word (hello_cid,
316 "World");
317 cmd_add ("hello", "", hello_cid, 0, do_hello,
318 NULL);
319 return NULL;
320 }
321
322 /* Deinitialization */
323 void g_module_unload (GModule *module)
324 {
325 /* Give back category handle */
326 if (hello_cid)
327 compl_del_category (hello_cid);
328 cmd_del ("hello");
329 }
330
331 /* The End */
332 --------------------------------------------------------
333
334 Now you can use completion for hello command. Note, that
335 this code have some serious simplifications, made for
336 simplicity reasons. For now, compl_add_category_word
337 does not checks, if word already exists in completions
338 list (although it is marked as TODO, so, some day it
339 will), so, we should check it ourselves. Also, we should
340 check, that args contains only one word, or this will
341 confuse completion system, so, it will stop on this
342 completion.
343
344 =====================
345
346 Example: hooks
347
348 =====================
349
350 Now let's implement our own beeper. Why anyone may wish
351 to do this? I am not satisfied with default mcabber's
352 builtin beeper flexibility. I wanted beeping on any
353 muc conference message, not just ones, directed to me.
354
355 --------------------------------------------------------
356 #include <glib.h>
357 #include <gmodule.h>
358 #include <string.h>
359
360 #include "logprint.h"
361 #include "commands.h"
362 #include "compl.h"
363 #include "hooks.h"
364 #include "screen.h"
365 #include "settings.h"
366
367 static guint beep_cid = 0;
368
369 /* Event handler */
370 void beep_hh (hk_arg_t *args, gpointer userdata)
371 {
372 /* We are interested only in incoming
373 * message events */
374 if (!strcmp (args[0].value, "hook-message-in"))
375 /* Check if beeping is enabled */
376 if (settings_opt_get_int ("beep_enable"))
377 /* *BEEP*! */
378 scr_Beep ();
379 }
380
381 /* beep command handler */
382 void do_beep (char *args)
383 {
384 /* Check arguments, and if recognized,
385 * set mcabber option accordingly */
386 if (!strcmp (args, "enable") ||
387 !strcmp (args, "on") ||
388 !strcmp (args, "yes") ||
389 !strcmp (args, "1"))
390 settings_set (SETTINGS_TYPE_OPTION,
391 "beep_enable", "1");
392 else if (!strcmp (args, "disable") ||
393 !strcmp (args, "off") ||
394 !strcmp (args, "no") ||
395 !strcmp (args, "0"))
396 settings_set (SETTINGS_TYPE_OPTION,
397 "beep_enable", "0");
398
399 /* Output current state, either if state is
400 * changed and if argument is not recognized */
401 if (settings_opt_get_int ("beep_enable"))
402 scr_LogPrint (LPRINT_NORMAL,
403 "Beep on messages is enabled");
404 else
405 scr_LogPrint (LPRINT_NORMAL,
406 "Beep on messages is disabled");
407 }
408
409 /* Initialization */
410 const gchar* g_module_check_init (GModule *module)
411 {
412 /* Create completions */
413 beep_cid = compl_new_category ();
414 if (beep_cid) {
415 compl_add_category_word (beep_cid, "enable");
416 compl_add_category_word (beep_cid, "disable");
417 }
418 /* Add command */
419 cmd_add ("beep", "", beep_cid, 0, do_beep, NULL);
420 /* Add handler */
421 hk_add_handler (beep_hh, NULL);
422 return NULL;
423 }
424
425 /* Deinitialization */
426 void g_module_unload (GModule *module)
427 {
428 /* Unregister event handler */
429 hk_del_handler (beep_hh, NULL);
430 /* Unregister command */
431 cmd_del ("beep");
432 /* Give back completion handle */
433 if (beep_cid)
434 compl_del_category (beep_cid);
435 }
436
437 /* The End */
438 --------------------------------------------------------
439
440 As you can see, here we used the fact, that right now
441 all the hooks provide "hook" argument as a first element
442 in args, however this can change in future.
443
444 Note, that to compile this we also need to add loudmouth-1.0
445 to pkg-config command line and to add -I. to compilation
446 mode gcc command line (specify include directory with our
447 config.h as system include directory), so, you will have
448 something like
449
450 libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
451 gmodule-2.0 loudmouth-1.0` -I. -c beep.c
452 libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
453 `pkg-config --cflags glib-2.0 gmodule-2.0 loudmouth-1.0` \
454 -o libbeep.la beep.lo
455 libtool --mode=install install libbeep.la \
456 /usr/lib/mcabber/libbeep.la
457
458 If you use CMake (as do I), corresponding CMakeLists.txt
459 snippet:
460
461 --------------------------------------------------------
462 cmake_minimum_required(VERSION 2.6)
463 project(beep C)
464
465 add_library(beep MODULE beep.c)
466
467 set(MCABBER_INCLUDE_DIR "${beep_SOURCE_DIR}/include"
468 CACHE FILEPATH "Path to mcabber headers")
469
470 find_package(PkgConfig REQUIRED)
471 pkg_check_modules(GLIB REQUIRED glib-2.0)
472 pkg_check_modules(GMODULE REQUIRED gmodule-2.0)
473 pkg_check_modules(LM REQUIRED loudmouth-1.0)
474
475 include_directories(SYSTEM ${GLIB_INCLUDE_DIRS}
476 ${GMODULE_INCLUDE_DIRS}
477 ${LM_INCLUDE_DIRS})
478 target_link_libraries(beep ${GLIB_LIBRARIES}
479 ${GMODULE_LIBRARIES})
480 include_directories(${beep_SOURCE_DIR}
481 ${beep_BINARY_DIR}
482 ${MCABBER_INCLUDE_DIR})
483
484 install(TARGETS beep DESTINATION lib/mcabber)
485 --------------------------------------------------------
486
487 ==============
488
489 Further
490
491 ==============
492
493 As mcabber-lm uses glib mainloop, you can use glib's
494 event sources, for example, fifo reading can be easily
495 modularized with GIOChannels.
496
497 You can extend xmpp part of mcabber functionality by
498 providing lm message handlers with high priority and
499 allowing unhandled by your handler messages be taken
500 care by mcabber's handlers on normal priority level.
501 This is where you may need to modify set of advertised
502 supported disco features.
503
504 Many useful examples can be found in my mcabber-lua
505 module.
506
507 If you think, that your module needs to change
508 something, hardcoded in current implementation - feel
509 free to mail me or join mcabber's MUC room and
510 discuss this - for now I have only implemented things,
511 that I found necessary for mcabber-lua module.
512
513 Also I am not native English speaker, so, if you find
514 some errors or non-natural constructs in this howto,
515 please, inform me (I will be glad, if you also provide
516 a more suitable version of text in question).
517
518 -- Myhailo Danylenko <isbear@ukrpost.net>
519 -- Mon, 05 Oct 2009 00:00:00 +0300
520