Merges from Irssi CVS.
[crypto.git] / apps / irssi / src / fe-common / core / fe-channels.c
1 /*
2  fe-channels.c : irssi
3
4     Copyright (C) 1999-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 "module-formats.h"
23 #include "modules.h"
24 #include "signals.h"
25 #include "commands.h"
26 #include "levels.h"
27 #include "misc.h"
28 #include "settings.h"
29
30 #include "chat-protocols.h"
31 #include "chatnets.h"
32 #include "servers.h"
33 #include "channels.h"
34 #include "channels-setup.h"
35 #include "nicklist.h"
36
37 #include "fe-windows.h"
38 #include "fe-channels.h"
39 #include "window-items.h"
40 #include "printtext.h"
41
42 static void signal_channel_created(CHANNEL_REC *channel, void *automatic)
43 {
44         if (window_item_window(channel) == NULL) {
45                 window_item_create((WI_ITEM_REC *) channel,
46                                    GPOINTER_TO_INT(automatic));
47         }
48 }
49
50 static void signal_channel_created_curwin(CHANNEL_REC *channel)
51 {
52         g_return_if_fail(channel != NULL);
53
54         window_item_add(active_win, (WI_ITEM_REC *) channel, FALSE);
55 }
56
57 static void signal_channel_destroyed(CHANNEL_REC *channel)
58 {
59         WINDOW_REC *window;
60
61         g_return_if_fail(channel != NULL);
62
63         window = window_item_window((WI_ITEM_REC *) channel);
64         if (window == NULL)
65                 return;
66
67         window_item_destroy((WI_ITEM_REC *) channel);
68
69         if (channel->joined && !channel->left &&
70             !channel->server->disconnected) {
71                 /* kicked out from channel */
72                 window_bind_add(window, channel->server->tag,
73                                 channel->name);
74         } else if (!channel->joined || channel->left)
75                 window_auto_destroy(window);
76 }
77
78 static void sig_disconnected(SERVER_REC *server)
79 {
80         WINDOW_REC *window;
81         GSList *tmp;
82
83         g_return_if_fail(IS_SERVER(server));
84
85         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
86                 CHANNEL_REC *channel = tmp->data;
87
88                 window = window_item_window((WI_ITEM_REC *) channel);
89                 window_bind_add(window, server->tag, channel->name);
90         }
91 }
92
93 static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
94 {
95         g_return_if_fail(window != NULL);
96         if (item == NULL) return;
97
98         if (g_slist_length(window->items) > 1 && IS_CHANNEL(item)) {
99                 printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE,
100                             TXT_TALKING_IN, item->name);
101                 signal_stop();
102         }
103 }
104
105 static void cmd_wjoin_pre(const char *data)
106 {
107         GHashTable *optlist;
108         char *nick;
109         void *free_arg;
110
111         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
112                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
113                             "join", &optlist, &nick))
114                 return;
115
116         if (g_hash_table_lookup(optlist, "window") != NULL) {
117                 signal_add("channel created",
118                            (SIGNAL_FUNC) signal_channel_created_curwin);
119         }
120         cmd_params_free(free_arg);
121 }
122
123 static void cmd_join(const char *data, SERVER_REC *server)
124 {
125         WINDOW_REC *window;
126         CHANNEL_REC *channel;
127
128         if (strchr(data, ' ') != NULL || strchr(data, ',') != NULL)
129                 return;
130
131         channel = channel_find(server, data);
132         if (channel == NULL)
133                 return;
134
135         /* already joined to channel, set it active */
136         window = window_item_window(channel);
137         if (window != active_win)
138                 window_set_active(window);
139
140         window_item_set_active(active_win, (WI_ITEM_REC *) channel);
141 }
142
143 static void cmd_wjoin_post(const char *data)
144 {
145         GHashTable *optlist;
146         char *nick;
147         void *free_arg;
148
149         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
150                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
151                             "join", &optlist, &nick))
152                 return;
153
154         if (g_hash_table_lookup(optlist, "window") != NULL) {
155                 signal_remove("channel created",
156                            (SIGNAL_FUNC) signal_channel_created_curwin);
157         }
158         cmd_params_free(free_arg);
159 }
160
161 static void cmd_channel_list_joined(void)
162 {
163         CHANNEL_REC *channel;
164         GString *nicks;
165         GSList *nicklist, *tmp, *ntmp;
166
167         if (channels == NULL) {
168                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_IN_CHANNELS);
169                 return;
170         }
171
172         /* print active channel */
173         channel = CHANNEL(active_win->active);
174         if (channel != NULL)
175                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CURRENT_CHANNEL, channel->name);
176
177         /* print list of all channels, their modes, server tags and nicks */
178         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_HEADER);
179         for (tmp = channels; tmp != NULL; tmp = tmp->next) {
180                 channel = tmp->data;
181
182                 nicklist = nicklist_getnicks(channel);
183                 nicks = g_string_new(NULL);
184                 for (ntmp = nicklist; ntmp != NULL; ntmp = ntmp->next) {
185                         NICK_REC *rec = ntmp->data;
186
187                         g_string_sprintfa(nicks, "%s ", rec->nick);
188                 }
189
190                 if (nicks->len > 1) g_string_truncate(nicks, nicks->len-1);
191                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_LINE,
192                             channel->name, channel->mode, channel->server->tag, nicks->str);
193
194                 g_slist_free(nicklist);
195                 g_string_free(nicks, TRUE);
196         }
197 }
198
199 /* SYNTAX: CHANNEL LIST */
200 static void cmd_channel_list(void)
201 {
202         GString *str;
203         GSList *tmp;
204
205         str = g_string_new(NULL);
206         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_HEADER);
207         for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
208                 CHANNEL_SETUP_REC *rec = tmp->data;
209
210                 g_string_truncate(str, 0);
211                 if (rec->autojoin)
212                         g_string_append(str, "autojoin, ");
213                 if (rec->botmasks != NULL && *rec->botmasks != '\0')
214                         g_string_sprintfa(str, "bots: %s, ", rec->botmasks);
215                 if (rec->autosendcmd != NULL && *rec->autosendcmd != '\0')
216                         g_string_sprintfa(str, "botcmd: %s, ", rec->autosendcmd);
217
218                 if (str->len > 2) g_string_truncate(str, str->len-2);
219                 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_LINE,
220                             rec->name, rec->chatnet == NULL ? "" : rec->chatnet,
221                             rec->password == NULL ? "" : rec->password, str->str);
222         }
223         g_string_free(str, TRUE);
224         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_FOOTER);
225 }
226
227 static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
228 {
229         if (*data == '\0')
230                 cmd_channel_list_joined();
231         else if (server != NULL && server_ischannel(server, data)) {
232                 signal_emit("command join", 3, data, server, item);
233         } else {
234                 command_runsub("channel", data, server, item);
235         }
236 }
237
238 /* SYNTAX: CHANNEL ADD [-auto | -noauto] [-bots <masks>] [-botcmd <command>]
239                        <channel> <chatnet> [<password>] */
240 static void cmd_channel_add(const char *data)
241 {
242         GHashTable *optlist;
243         CHATNET_REC *chatnetrec;
244         CHANNEL_SETUP_REC *rec;
245         char *botarg, *botcmdarg, *chatnet, *channel, *password;
246         void *free_arg;
247
248         if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
249                             "channel add", &optlist, &channel, &chatnet, &password))
250                 return;
251
252         if (*chatnet == '\0' || *channel == '\0')
253                 cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
254
255         chatnetrec = chatnet_find(chatnet);
256         if (chatnetrec == NULL) {
257                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
258                             TXT_UNKNOWN_CHATNET, chatnet);
259                 cmd_params_free(free_arg);
260                 return;
261         }
262
263         botarg = g_hash_table_lookup(optlist, "bots");
264         botcmdarg = g_hash_table_lookup(optlist, "botcmd");
265
266         rec = channel_setup_find(channel, chatnet);
267         if (rec == NULL) {
268                 rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
269                 rec->name = g_strdup(channel);
270                 rec->chatnet = g_strdup(chatnet);
271         } else {
272                 if (g_hash_table_lookup(optlist, "bots")) g_free_and_null(rec->botmasks);
273                 if (g_hash_table_lookup(optlist, "botcmd")) g_free_and_null(rec->autosendcmd);
274                 if (*password != '\0') g_free_and_null(rec->password);
275         }
276         if (g_hash_table_lookup(optlist, "auto")) rec->autojoin = TRUE;
277         if (g_hash_table_lookup(optlist, "noauto")) rec->autojoin = FALSE;
278         if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg);
279         if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg);
280         if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
281
282         signal_emit("channel add fill", 2, rec, optlist);
283
284         channel_setup_create(rec);
285         printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
286                     TXT_CHANSETUP_ADDED, channel, chatnet);
287
288         cmd_params_free(free_arg);
289 }
290
291 /* SYNTAX: CHANNEL REMOVE <channel> <chatnet> */
292 static void cmd_channel_remove(const char *data)
293 {
294         CHANNEL_SETUP_REC *rec;
295         char *chatnet, *channel;
296         void *free_arg;
297
298         if (!cmd_get_params(data, &free_arg, 2, &channel, &chatnet))
299                 return;
300         if (*chatnet == '\0' || *channel == '\0')
301                 cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
302
303         rec = channel_setup_find(channel, chatnet);
304         if (rec == NULL)
305                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
306         else {
307                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet);
308                 channel_setup_remove(rec);
309         }
310         cmd_params_free(free_arg);
311 }
312
313 static int get_nick_length(void *data)
314 {
315         return strlen(((NICK_REC *) data)->nick);
316 }
317
318 static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
319 {
320         WINDOW_REC *window;
321         TEXT_DEST_REC dest;
322         GString *str;
323         GSList *tmp;
324         char *format, *stripped, *prefix_format;
325         char *linebuf, nickmode[2] = { 0, 0 };
326         int *columns, cols, rows, last_col_rows, col, row, max_width;
327         int item_extra, linebuf_size, formatnum;
328
329         window = window_find_closest(channel->server, channel->name,
330                                      MSGLEVEL_CLIENTCRAP);
331         max_width = window->width;
332
333         /* get the length of item extra stuff ("[ ] ") */
334         format = format_get_text(MODULE_NAME, NULL,
335                                  channel->server, channel->name,
336                                  TXT_NAMES_NICK, " ", "");
337         stripped = strip_codes(format);
338         item_extra = strlen(stripped);
339         g_free(stripped);
340         g_free(format);
341
342         if (settings_get_int("names_max_width") > 0 &&
343             settings_get_int("names_max_width") < max_width)
344                 max_width = settings_get_int("names_max_width");
345
346         /* remove width of the timestamp from max_width */
347         format_create_dest(&dest, channel->server, channel->name,
348                            MSGLEVEL_CLIENTCRAP, NULL);
349         format = format_get_line_start(current_theme, &dest, time(NULL));
350         if (format != NULL) {
351                 stripped = strip_codes(format);
352                 max_width -= strlen(stripped);
353                 g_free(stripped);
354                 g_free(format);
355         }
356
357         /* remove width of the prefix from max_width */
358         prefix_format = format_get_text(MODULE_NAME, NULL,
359                                         channel->server, channel->name,
360                                         TXT_NAMES_PREFIX, channel->name);
361         if (prefix_format != NULL) {
362                 stripped = strip_codes(prefix_format);
363                 max_width -= strlen(stripped);
364                 g_free(stripped);
365         }
366
367         if (max_width <= 0) {
368                 /* we should always have at least some space .. if we
369                    really don't, it won't show properly anyway. */
370                 max_width = 10;
371         }
372
373         /* calculate columns */
374         cols = get_max_column_count(nicklist, get_nick_length, max_width,
375                                     settings_get_int("names_max_columns"),
376                                     item_extra, 3, &columns, &rows);
377         nicklist = columns_sort_list(nicklist, rows);
378
379         /* rows in last column */
380         last_col_rows = rows-(cols*rows-g_slist_length(nicklist));
381         if (last_col_rows == 0)
382                 last_col_rows = rows;
383
384         str = g_string_new(prefix_format);
385         linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size);
386
387         col = 0; row = 0;
388         for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
389                 NICK_REC *rec = tmp->data;
390
391                 if (rec->op)
392                         nickmode[0] = '@';
393                 else if (rec->halfop)
394                         nickmode[0] = '%';
395                 else if (rec->voice)
396                         nickmode[0] = '+';
397                 else
398                         nickmode[0] = ' ';
399                 
400                 if (linebuf_size < columns[col]-item_extra+1) {
401                         linebuf_size = (columns[col]-item_extra+1)*2;
402                         linebuf = g_realloc(linebuf, linebuf_size);
403                 }
404                 memset(linebuf, ' ', columns[col]-item_extra);
405                 linebuf[columns[col]-item_extra] = '\0';
406                 memcpy(linebuf, rec->nick, strlen(rec->nick));
407
408                 formatnum = rec->op ? TXT_NAMES_NICK_OP :
409                         rec->halfop ? TXT_NAMES_NICK_HALFOP :
410                         rec->voice ? TXT_NAMES_NICK_VOICE :
411                         TXT_NAMES_NICK;
412                 format = format_get_text(MODULE_NAME, NULL,
413                                          channel->server, channel->name,
414                                          formatnum, nickmode, linebuf);
415                 g_string_append(str, format);
416                 g_free(format);
417
418                 if (++col == cols) {
419                         printtext(channel->server, channel->name,
420                                   MSGLEVEL_CLIENTCRAP, "%s", str->str);
421                         g_string_truncate(str, 0);
422                         if (prefix_format != NULL)
423                                 g_string_assign(str, prefix_format);
424                         col = 0; row++;
425
426                         if (row == last_col_rows)
427                                 cols--;
428                 }
429         }
430
431         if (str->len > strlen(prefix_format)) {
432                 printtext(channel->server, channel->name,
433                           MSGLEVEL_CLIENTCRAP, "%s", str->str);
434         }
435
436         g_slist_free(nicklist);
437         g_string_free(str, TRUE);
438         g_free_not_null(columns);
439         g_free_not_null(prefix_format);
440         g_free(linebuf);
441 }
442
443 void fe_channels_nicklist(CHANNEL_REC *channel, int flags)
444 {
445         NICK_REC *nick;
446         GSList *tmp, *nicklist, *sorted;
447         int nicks, normal, voices, halfops, ops;
448
449         nicks = normal = voices = halfops = ops = 0;
450         nicklist = nicklist_getnicks(channel);
451         sorted = NULL;
452
453         /* sort the nicklist */
454         for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
455                 nick = tmp->data;
456
457                 nicks++;
458                 if (nick->op) {
459                         ops++;
460                         if ((flags & CHANNEL_NICKLIST_FLAG_OPS) == 0)
461                                 continue;
462                 } else if (nick->halfop) {
463                         halfops++;
464                         if ((flags & CHANNEL_NICKLIST_FLAG_HALFOPS) == 0)
465                                 continue;
466                 } else if (nick->voice) {
467                         voices++;
468                         if ((flags & CHANNEL_NICKLIST_FLAG_VOICES) == 0)
469                                 continue;
470                 } else {
471                         normal++;
472                         if ((flags & CHANNEL_NICKLIST_FLAG_NORMAL) == 0)
473                                 continue;
474                 }
475
476                 sorted = g_slist_insert_sorted(sorted, nick, (GCompareFunc)
477                                                nicklist_compare);
478         }
479         g_slist_free(nicklist);
480
481         /* display the nicks */
482         if ((flags & CHANNEL_NICKLIST_FLAG_COUNT) == 0) {
483                 printformat(channel->server, channel->name,
484                             MSGLEVEL_CLIENTCRAP, TXT_NAMES, channel->name, nicks, ops, halfops, voices, normal);
485                 display_sorted_nicks(channel, sorted);
486         }
487         g_slist_free(sorted);
488
489         printformat(channel->server, channel->name,
490                     MSGLEVEL_CLIENTNOTICE, TXT_ENDOFNAMES,
491                     channel->name, nicks, ops, halfops, voices, normal);
492 }
493
494 /* SYNTAX: NAMES [-count | -ops -halfops -voices -normal] [<channels> | **] */
495 static void cmd_names(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
496 {
497         CHANNEL_REC *chanrec;
498         GHashTable *optlist;
499         GString *unknowns;
500         char *channel, **channels, **tmp;
501         int flags;
502         void *free_arg;
503
504         g_return_if_fail(data != NULL);
505         if (!IS_SERVER(server) || !server->connected)
506                 cmd_return_error(CMDERR_NOT_CONNECTED);
507
508         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
509                             "names", &optlist, &channel))
510                 return;
511
512         if (strcmp(channel, "*") == 0 || *channel == '\0') {
513                 if (!IS_CHANNEL(item))
514                         cmd_param_error(CMDERR_NOT_JOINED);
515
516                 channel = item->name;
517         }
518
519         flags = 0;
520         if (g_hash_table_lookup(optlist, "ops") != NULL)
521                 flags |= CHANNEL_NICKLIST_FLAG_OPS;
522         if (g_hash_table_lookup(optlist, "halfops") != NULL)
523                 flags |= CHANNEL_NICKLIST_FLAG_HALFOPS;
524         if (g_hash_table_lookup(optlist, "voices") != NULL)
525                 flags |= CHANNEL_NICKLIST_FLAG_VOICES;
526         if (g_hash_table_lookup(optlist, "normal") != NULL)
527                 flags |= CHANNEL_NICKLIST_FLAG_NORMAL;
528         if (g_hash_table_lookup(optlist, "count") != NULL)
529                 flags |= CHANNEL_NICKLIST_FLAG_COUNT;
530
531         if (flags == 0) flags = CHANNEL_NICKLIST_FLAG_ALL;
532
533         unknowns = g_string_new(NULL);
534
535         channels = g_strsplit(channel, ",", -1);
536         for (tmp = channels; *tmp != NULL; tmp++) {
537                 chanrec = channel_find(server, *tmp);
538                 if (chanrec == NULL)
539                         g_string_sprintfa(unknowns, "%s,", *tmp);
540                 else {
541                         fe_channels_nicklist(chanrec, flags);
542                         signal_stop();
543                 }
544         }
545         g_strfreev(channels);
546
547         if (unknowns->len > 1)
548                 g_string_truncate(unknowns, unknowns->len-1);
549
550         if (unknowns->len > 0 && strcmp(channel, unknowns->str) != 0)
551                 signal_emit("command names", 3, unknowns->str, server, item);
552         g_string_free(unknowns, TRUE);
553
554         cmd_params_free(free_arg);
555 }
556
557 /* SYNTAX: CYCLE [<channel>] [<message>] */
558 static void cmd_cycle(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
559 {
560         CHANNEL_REC *chanrec;
561         char *channame, *msg, *joindata;
562         void *free_arg;
563
564         g_return_if_fail(data != NULL);
565         if (!IS_SERVER(server) || !server->connected)
566                 cmd_return_error(CMDERR_NOT_CONNECTED);
567
568         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN,
569                             item, &channame, &msg))
570                 return;
571         if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
572
573         chanrec = channel_find(server, channame);
574         if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
575
576         joindata = chanrec->get_join_data(chanrec);
577         window_bind_add(window_item_window(chanrec),
578                         chanrec->server->tag, chanrec->name);
579
580         /* FIXME: kludgy kludgy... */
581         signal_emit("command part", 3, data, server, item);
582
583         if (g_slist_find(channels, chanrec) != NULL) {
584                 chanrec->left = TRUE;
585                 channel_destroy(chanrec);
586         }
587
588         server->channels_join(server, joindata, FALSE);
589         g_free(joindata);
590
591         cmd_params_free(free_arg);
592 }
593
594 void fe_channels_init(void)
595 {
596         settings_add_bool("lookandfeel", "autoclose_windows", TRUE);
597         settings_add_int("lookandfeel", "names_max_columns", 6);
598         settings_add_int("lookandfeel", "names_max_width", 0);
599
600         signal_add("channel created", (SIGNAL_FUNC) signal_channel_created);
601         signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
602         signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
603         signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
604
605         command_bind_first("join", NULL, (SIGNAL_FUNC) cmd_wjoin_pre);
606         command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
607         command_bind_last("join", NULL, (SIGNAL_FUNC) cmd_wjoin_post);
608         command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel);
609         command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add);
610         command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove);
611         command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list);
612         command_bind("names", NULL, (SIGNAL_FUNC) cmd_names);
613         command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle);
614
615         command_set_options("channel add", "auto noauto -bots -botcmd");
616         command_set_options("names", "count ops halfops voices normal");
617         command_set_options("join", "window");
618 }
619
620 void fe_channels_deinit(void)
621 {
622         signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created);
623         signal_remove("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
624         signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
625         signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
626
627         command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_pre);
628         command_unbind("join", (SIGNAL_FUNC) cmd_join);
629         command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_post);
630         command_unbind("channel", (SIGNAL_FUNC) cmd_channel);
631         command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add);
632         command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove);
633         command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list);
634         command_unbind("names", (SIGNAL_FUNC) cmd_names);
635         command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle);
636 }