Mercurial > hg
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 |