imported.
[runtime.git] / apps / irssi / src / core / expandos.c
1 /*
2  expandos.c : irssi
3
4     Copyright (C) 2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "modules.h"
23 #include "signals.h"
24 #include "expandos.h"
25 #include "settings.h"
26 #include "commands.h"
27 #include "misc.h"
28 #include "irssi-version.h"
29
30 #include "servers.h"
31 #include "channels.h"
32 #include "queries.h"
33 #include "window-item-def.h"
34
35 #ifdef HAVE_SYS_UTSNAME_H
36 #  include <sys/utsname.h>
37 #endif
38
39 #define MAX_EXPANDO_SIGNALS 10
40
41 typedef struct {
42         EXPANDO_FUNC func;
43
44         int signals;
45         int signal_ids[MAX_EXPANDO_SIGNALS];
46         int signal_args[MAX_EXPANDO_SIGNALS];
47 } EXPANDO_REC;
48
49 static int timer_tag;
50
51 static EXPANDO_REC *char_expandos[127];
52 static GHashTable *expandos;
53 static time_t client_start_time;
54 static char *last_sent_msg, *last_sent_msg_body;
55 static char *last_privmsg_from, *last_public_from;
56 static char *sysname, *sysrelease, *sysarch;
57 static const char *timestamp_format;
58
59 #define CHAR_EXPANDOS_COUNT \
60         ((int) (sizeof(char_expandos) / sizeof(char_expandos[0])))
61
62 /* Create expando - overrides any existing ones. */
63 void expando_create(const char *key, EXPANDO_FUNC func, ...)
64 {
65         EXPANDO_REC *rec;
66         const char *signal;
67         va_list va;
68
69         g_return_if_fail(key != NULL || *key == '\0');
70         g_return_if_fail(func != NULL);
71
72         if (key[1] != '\0')
73                 rec = g_hash_table_lookup(expandos, key);
74         else {
75                 /* single character expando */
76                 rec = char_expandos[(int) *key];
77         }
78
79         if (rec != NULL)
80                 rec->signals = 0;
81         else {
82                 rec = g_new0(EXPANDO_REC, 1);
83                 if (key[1] != '\0')
84                         g_hash_table_insert(expandos, g_strdup(key), rec);
85                 else
86                         char_expandos[(int) *key] = rec;
87         }
88
89         rec->func = func;
90
91         va_start(va, func);
92         while ((signal = (const char *) va_arg(va, const char *)) != NULL)
93                expando_add_signal(key, signal, (int) va_arg(va, int));
94         va_end(va);
95 }
96
97 static EXPANDO_REC *expando_find(const char *key)
98 {
99         if (key[1] != '\0')
100                 return g_hash_table_lookup(expandos, key);
101         else
102                 return char_expandos[(int) *key];
103 }
104
105 /* Add new signal to expando */
106 void expando_add_signal(const char *key, const char *signal, ExpandoArg arg)
107 {
108         EXPANDO_REC *rec;
109
110         g_return_if_fail(key != NULL);
111         g_return_if_fail(signal != NULL);
112
113         rec = expando_find(key);
114         g_return_if_fail(rec != NULL);
115
116         if (arg == EXPANDO_NEVER) {
117                 /* expando changes never */
118                 rec->signals = -1;
119         } else if (rec->signals < MAX_EXPANDO_SIGNALS) {
120                 g_return_if_fail(rec->signals != -1);
121
122                 rec->signal_ids[rec->signals] = signal_get_uniq_id(signal);
123                 rec->signal_args[rec->signals] = arg;
124                 rec->signals++;
125         }
126 }
127
128 /* Destroy expando */
129 void expando_destroy(const char *key, EXPANDO_FUNC func)
130 {
131         gpointer origkey;
132         EXPANDO_REC *rec;
133
134         g_return_if_fail(key != NULL || *key == '\0');
135         g_return_if_fail(func != NULL);
136
137         if (key[1] == '\0') {
138                 /* single character expando */
139                 rec = char_expandos[(int) *key];
140                 if (rec != NULL && rec->func == func) {
141                         char_expandos[(int) *key] = NULL;
142                         g_free(rec);
143                 }
144         } else if (g_hash_table_lookup_extended(expandos, key, &origkey,
145                                                 (gpointer *) &rec)) {
146                 if (rec->func == func) {
147                         g_free(origkey);
148                         g_hash_table_remove(expandos, key);
149                         g_free(rec);
150                 }
151         }
152 }
153
154 void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs)
155 {
156         SIGNAL_FUNC func;
157         EXPANDO_REC *rec;
158         int n, arg;
159
160         g_return_if_fail(key != NULL);
161         g_return_if_fail(funccount >= 1);
162         g_return_if_fail(funcs != NULL);
163         g_return_if_fail(funcs[0] != NULL);
164
165         rec = expando_find(key);
166         g_return_if_fail(rec != NULL);
167
168         if (rec->signals == 0) {
169                 /* it's unknown when this expando changes..
170                    check it once in a second */
171                 signal_add("expando timer", funcs[EXPANDO_ARG_NONE]);
172         }
173
174         for (n = 0; n < rec->signals; n++) {
175                 arg = rec->signal_args[n];
176                 func = arg < funccount ? funcs[arg] : NULL;
177                 if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
178
179                 signal_add_to_id(MODULE_NAME, 1, rec->signal_ids[n], func);
180         }
181 }
182
183 void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs)
184 {
185         SIGNAL_FUNC func;
186         EXPANDO_REC *rec;
187         int n, arg;
188
189         g_return_if_fail(key != NULL);
190         g_return_if_fail(funccount >= 1);
191         g_return_if_fail(funcs != NULL);
192         g_return_if_fail(funcs[0] != NULL);
193
194         rec = expando_find(key);
195         g_return_if_fail(rec != NULL);
196
197         if (rec->signals == 0) {
198                 /* it's unknown when this expando changes..
199                    check it once in a second */
200                 signal_remove("expando timer", funcs[EXPANDO_ARG_NONE]);
201         }
202
203         for (n = 0; n < rec->signals; n++) {
204                 arg = rec->signal_args[n];
205                 func = arg < funccount ? funcs[arg] : NULL;
206                 if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
207
208                 signal_remove_id(rec->signal_ids[n], func);
209         }
210 }
211
212 EXPANDO_FUNC expando_find_char(char chr)
213 {
214         g_return_val_if_fail(chr < CHAR_EXPANDOS_COUNT, NULL);
215
216         return char_expandos[(int) chr] == NULL ? NULL :
217                 char_expandos[(int) chr]->func;
218 }
219
220 EXPANDO_FUNC expando_find_long(const char *key)
221 {
222         EXPANDO_REC *rec = g_hash_table_lookup(expandos, key);
223         return rec == NULL ? NULL : rec->func;
224 }
225
226 /* last person who sent you a MSG */
227 static char *expando_lastmsg(SERVER_REC *server, void *item, int *free_ret)
228 {
229         return last_privmsg_from;
230 }
231
232 /* last person to whom you sent a MSG */
233 static char *expando_lastmymsg(SERVER_REC *server, void *item, int *free_ret)
234 {
235         return last_sent_msg;
236 }
237
238 /* last person to send a public message to a channel you are on */
239 static char *expando_lastpublic(SERVER_REC *server, void *item, int *free_ret)
240 {
241         return last_public_from;
242 }
243
244 /* text of your AWAY message, if any */
245 static char *expando_awaymsg(SERVER_REC *server, void *item, int *free_ret)
246 {
247         return server == NULL ? "" : server->away_reason;
248 }
249
250 /* body of last MSG you sent */
251 static char *expando_lastmymsg_body(SERVER_REC *server, void *item, int *free_ret)
252 {
253         return last_sent_msg_body;
254 }
255
256 /* current channel */
257 static char *expando_channel(SERVER_REC *server, void *item, int *free_ret)
258 {
259         return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->name;
260 }
261
262 /* time client was started, $time() format */
263 static char *expando_clientstarted(SERVER_REC *server, void *item, int *free_ret)
264 {
265         *free_ret = TRUE;
266         return g_strdup_printf("%ld", (long) client_start_time);
267 }
268
269 /* channel you were last INVITEd to */
270 static char *expando_last_invite(SERVER_REC *server, void *item, int *free_ret)
271 {
272         return server == NULL ? "" : server->last_invite;
273 }
274
275 /* client version text string */
276 static char *expando_version(SERVER_REC *server, void *item, int *free_ret)
277 {
278         return IRSSI_VERSION;
279 }
280
281 /* current value of CMDCHARS */
282 static char *expando_cmdchars(SERVER_REC *server, void *item, int *free_ret)
283 {
284         return (char *) settings_get_str("cmdchars");
285 }
286
287 /* modes of current channel, if any */
288 static char *expando_chanmode(SERVER_REC *server, void *item, int *free_ret)
289 {
290         return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->mode;
291 }
292
293 /* current nickname */
294 static char *expando_nick(SERVER_REC *server, void *item, int *free_ret)
295 {
296         return server == NULL ? "" : server->nick;
297 }
298
299 /* value of STATUS_OPER if you are an irc operator */
300 static char *expando_statusoper(SERVER_REC *server, void *item, int *free_ret)
301 {
302         return server == NULL || !server->server_operator ? "" :
303                 (char *) settings_get_str("STATUS_OPER");
304 }
305
306 /* if you are a channel operator in $C, expands to a '@' */
307 static char *expando_chanop(SERVER_REC *server, void *item, int *free_ret)
308 {
309         return IS_CHANNEL(item) && CHANNEL(item)->chanop ? "@" : "";
310 }
311
312 /* nickname of whomever you are QUERYing */
313 static char *expando_query(SERVER_REC *server, void *item, int *free_ret)
314 {
315         return !IS_QUERY(item) ? "" : QUERY(item)->name;
316 }
317
318 /* version of current server */
319 static char *expando_serverversion(SERVER_REC *server, void *item, int *free_ret)
320 {
321         return server == NULL ? "" : server->version;
322 }
323
324 /* target of current input (channel or QUERY nickname) */
325 static char *expando_target(SERVER_REC *server, void *item, int *free_ret)
326 {
327         return item == NULL ? "" : ((WI_ITEM_REC *) item)->name;
328 }
329
330 /* client release date (numeric version string) */
331 static char *expando_releasedate(SERVER_REC *server, void *item, int *free_ret)
332 {
333         return IRSSI_VERSION_DATE;
334 }
335
336 /* current working directory */
337 static char *expando_workdir(SERVER_REC *server, void *item, int *free_ret)
338 {
339         *free_ret = TRUE;
340         return g_get_current_dir();
341 }
342
343 /* value of REALNAME */
344 static char *expando_realname(SERVER_REC *server, void *item, int *free_ret)
345 {
346         return server == NULL ? "" : server->connrec->realname;
347 }
348
349 /* time of day (hh:mm) */
350 static char *expando_time(SERVER_REC *server, void *item, int *free_ret)
351 {
352         time_t now;
353         struct tm *tm;
354         char str[256];
355
356         now = time(NULL);
357         tm = localtime(&now);
358
359         if (strftime(str, sizeof(str), timestamp_format, tm) == 0)
360                 return "";
361
362         *free_ret = TRUE;
363         return g_strdup(str);
364 }
365
366 /* a literal '$' */
367 static char *expando_dollar(SERVER_REC *server, void *item, int *free_ret)
368 {
369         return "$";
370 }
371
372 /* system name */
373 static char *expando_sysname(SERVER_REC *server, void *item, int *free_ret)
374 {
375         return sysname;
376 }
377
378 /* system release */
379 static char *expando_sysrelease(SERVER_REC *server, void *item, int *free_ret)
380 {
381         return sysrelease;
382 }
383
384 /* system architecture */
385 static char *expando_sysarch(SERVER_REC *server, void *item, int *free_ret)
386 {
387         return sysarch;
388 }
389
390 /* Topic of active channel (or address of queried nick) */
391 static char *expando_topic(SERVER_REC *server, void *item, int *free_ret)
392 {
393         return IS_CHANNEL(item) ? CHANNEL(item)->topic :
394                 IS_QUERY(item) ? QUERY(item)->address : "";
395 }
396
397 /* Server tag */
398 static char *expando_servertag(SERVER_REC *server, void *item, int *free_ret)
399 {
400         return server == NULL ? "" : server->tag;
401 }
402
403 /* Server chatnet */
404 static char *expando_chatnet(SERVER_REC *server, void *item, int *free_ret)
405 {
406         return server == NULL ? "" : server->connrec->chatnet;
407 }
408
409 static void sig_message_public(SERVER_REC *server, const char *msg,
410                                const char *nick, const char *address,
411                                const char *target)
412 {
413         g_free_not_null(last_public_from);
414         last_public_from = g_strdup(nick);
415 }
416
417 static void sig_message_private(SERVER_REC *server, const char *msg,
418                                 const char *nick, const char *address)
419 {
420         g_free_not_null(last_privmsg_from);
421         last_privmsg_from = g_strdup(nick);
422 }
423
424 static void sig_message_own_private(SERVER_REC *server, const char *msg,
425                                     const char *target, const char *origtarget)
426 {
427         g_return_if_fail(server != NULL);
428         g_return_if_fail(msg != NULL);
429
430         if (target != NULL) {
431                 if (target != last_sent_msg) {
432                         g_free_not_null(last_sent_msg);
433                         last_sent_msg = g_strdup(target);
434                 }
435                 g_free_not_null(last_sent_msg_body);
436                 last_sent_msg_body = g_strdup(msg);
437         }
438 }
439
440 static int sig_timer(void)
441 {
442         signal_emit("expando timer", 0);
443         return 1;
444 }
445
446 static void read_settings(void)
447 {
448         timestamp_format = settings_get_str("timestamp_format");
449 }
450
451 void expandos_init(void)
452 {
453 #ifdef HAVE_SYS_UTSNAME_H
454         struct utsname un;
455 #endif
456         settings_add_str("misc", "STATUS_OPER", "*");
457         settings_add_str("misc", "timestamp_format", "%H:%M");
458
459         client_start_time = time(NULL);
460         last_sent_msg = NULL; last_sent_msg_body = NULL;
461         last_privmsg_from = NULL; last_public_from = NULL;
462
463         sysname = sysrelease = sysarch = NULL;
464 #ifdef HAVE_SYS_UTSNAME_H
465         if (uname(&un) == 0) {
466                 sysname = g_strdup(un.sysname);
467                 sysrelease = g_strdup(un.release);
468                 sysarch = g_strdup(un.machine);
469         }
470 #endif
471
472         memset(char_expandos, 0, sizeof(char_expandos));
473         expandos = g_hash_table_new((GHashFunc) g_str_hash,
474                                     (GCompareFunc) g_str_equal);
475
476         expando_create(",", expando_lastmsg,
477                        "message private", EXPANDO_ARG_SERVER, NULL);
478         expando_create(".", expando_lastmymsg,
479                        "command msg", EXPANDO_ARG_NONE, NULL);
480         expando_create(";", expando_lastpublic,
481                        "message public", EXPANDO_ARG_SERVER, NULL);
482         expando_create("A", expando_awaymsg,
483                        "away mode changed", EXPANDO_ARG_NONE, NULL);
484         expando_create("B", expando_lastmymsg_body,
485                        "command msg", EXPANDO_ARG_NONE, NULL);
486         expando_create("C", expando_channel,
487                        "window changed", EXPANDO_ARG_NONE,
488                        "window item changed", EXPANDO_ARG_WINDOW, NULL);
489         expando_create("F", expando_clientstarted,
490                        "", EXPANDO_NEVER, NULL);
491         expando_create("I", expando_last_invite, NULL);
492         expando_create("J", expando_version,
493                        "", EXPANDO_NEVER, NULL);
494         expando_create("K", expando_cmdchars,
495                        "setup changed", EXPANDO_ARG_NONE, NULL);
496         expando_create("M", expando_chanmode,
497                        "window changed", EXPANDO_ARG_NONE,
498                        "window item changed", EXPANDO_ARG_WINDOW,
499                        "channel mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
500         expando_create("N", expando_nick,
501                        "window changed", EXPANDO_ARG_NONE,
502                        "window server changed", EXPANDO_ARG_WINDOW,
503                        "server nick changed", EXPANDO_ARG_SERVER, NULL);
504         expando_create("O", expando_statusoper,
505                        "setup changed", EXPANDO_ARG_NONE,
506                        "window changed", EXPANDO_ARG_NONE,
507                        "window server changed", EXPANDO_ARG_WINDOW,
508                        "user mode changed", EXPANDO_ARG_WINDOW, NULL);
509         expando_create("P", expando_chanop,
510                        "window changed", EXPANDO_ARG_NONE,
511                        "window item changed", EXPANDO_ARG_WINDOW,
512                        "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
513         expando_create("Q", expando_query,
514                        "window changed", EXPANDO_ARG_NONE,
515                        "window item changed", EXPANDO_ARG_WINDOW, NULL);
516         expando_create("R", expando_serverversion,
517                        "window changed", EXPANDO_ARG_NONE,
518                        "window server changed", EXPANDO_ARG_WINDOW, NULL);
519         expando_create("T", expando_target,
520                        "window changed", EXPANDO_ARG_NONE,
521                        "window item changed", EXPANDO_ARG_WINDOW, NULL);
522         expando_create("V", expando_releasedate,
523                        "", EXPANDO_NEVER, NULL);
524         expando_create("W", expando_workdir, NULL);
525         expando_create("Y", expando_realname,
526                        "window changed", EXPANDO_ARG_NONE,
527                        "window server changed", EXPANDO_ARG_WINDOW, NULL);
528         expando_create("Z", expando_time, NULL);
529         expando_create("$", expando_dollar,
530                        "", EXPANDO_NEVER, NULL);
531
532         expando_create("sysname", expando_sysname,
533                        "", EXPANDO_NEVER, NULL);
534         expando_create("sysrelease", expando_sysrelease,
535                        "", EXPANDO_NEVER, NULL);
536         expando_create("sysarch", expando_sysarch,
537                        "", EXPANDO_NEVER, NULL);
538         expando_create("topic", expando_topic,
539                        "window changed", EXPANDO_ARG_NONE,
540                        "window item changed", EXPANDO_ARG_WINDOW,
541                        "channel topic changed", EXPANDO_ARG_WINDOW_ITEM,
542                        "query address changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
543         expando_create("tag", expando_servertag,
544                        "window changed", EXPANDO_ARG_NONE,
545                        "window server changed", EXPANDO_ARG_WINDOW, NULL);
546         expando_create("chatnet", expando_chatnet,
547                        "window changed", EXPANDO_ARG_NONE,
548                        "window server changed", EXPANDO_ARG_WINDOW, NULL);
549
550         read_settings();
551
552         timer_tag = g_timeout_add(1000, (GSourceFunc) sig_timer, NULL);
553         signal_add("message public", (SIGNAL_FUNC) sig_message_public);
554         signal_add("message private", (SIGNAL_FUNC) sig_message_private);
555         signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
556         signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
557 }
558
559 void expandos_deinit(void)
560 {
561         int n;
562
563         for (n = 0; n < CHAR_EXPANDOS_COUNT; n++)
564                 g_free_not_null(char_expandos[n]);
565
566         expando_destroy("sysname", expando_sysname);
567         expando_destroy("sysrelease", expando_sysrelease);
568         expando_destroy("sysarch", expando_sysarch);
569         expando_destroy("topic", expando_topic);
570         expando_destroy("tag", expando_servertag);
571         expando_destroy("chatnet", expando_chatnet);
572
573         g_hash_table_destroy(expandos);
574
575         g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body);
576         g_free_not_null(last_privmsg_from); g_free_not_null(last_public_from);
577         g_free_not_null(sysname); g_free_not_null(sysrelease);
578         g_free_not_null(sysarch);
579
580         g_source_remove(timer_tag);
581         signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
582         signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
583         signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
584         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
585 }