4 Copyright (C) 1999-2000 Timo Sirainen
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.
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.
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
25 #include "special-vars.h"
26 #include "window-item-def.h"
31 #include "lib-config/iconfig.h"
35 char *current_command;
37 static int signal_default_command;
39 static GSList *alias_runstack;
41 COMMAND_REC *command_find(const char *cmd)
45 g_return_val_if_fail(cmd != NULL, NULL);
47 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
48 COMMAND_REC *rec = tmp->data;
50 if (g_strcasecmp(rec->cmd, cmd) == 0)
57 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
62 g_return_val_if_fail(rec != NULL, NULL);
63 g_return_val_if_fail(module != NULL, NULL);
65 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
66 COMMAND_MODULE_REC *rec = tmp->data;
68 if (g_strcasecmp(rec->name, module) == 0)
75 static COMMAND_MODULE_REC *
76 command_module_find_and_remove(COMMAND_REC *rec, SIGNAL_FUNC func)
80 g_return_val_if_fail(rec != NULL, NULL);
81 g_return_val_if_fail(func != NULL, NULL);
83 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
84 COMMAND_MODULE_REC *rec = tmp->data;
86 for (tmp2 = rec->callbacks; tmp2 != NULL; tmp2 = tmp2->next) {
87 COMMAND_CALLBACK_REC *cb = tmp2->data;
89 if (cb->func == func) {
91 g_slist_remove(rec->callbacks, cb);
101 int command_have_sub(const char *command)
106 g_return_val_if_fail(command != NULL, FALSE);
108 /* find "command "s */
109 len = strlen(command);
110 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
111 COMMAND_REC *rec = tmp->data;
113 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
114 rec->cmd[len] == ' ')
121 static COMMAND_MODULE_REC *
122 command_module_get(COMMAND_REC *rec, const char *module, int protocol)
124 COMMAND_MODULE_REC *modrec;
126 g_return_val_if_fail(rec != NULL, NULL);
128 modrec = command_module_find(rec, module);
129 if (modrec == NULL) {
130 modrec = g_new0(COMMAND_MODULE_REC, 1);
131 modrec->name = g_strdup(module);
132 modrec->protocol = -1;
133 rec->modules = g_slist_append(rec->modules, modrec);
137 modrec->protocol = protocol;
142 void command_bind_full(const char *module, int priority, const char *cmd,
143 int protocol, const char *category, SIGNAL_FUNC func,
147 COMMAND_MODULE_REC *modrec;
148 COMMAND_CALLBACK_REC *cb;
151 g_return_if_fail(module != NULL);
152 g_return_if_fail(cmd != NULL);
154 rec = command_find(cmd);
156 rec = g_new0(COMMAND_REC, 1);
157 rec->cmd = g_strdup(cmd);
158 rec->category = category == NULL ? NULL : g_strdup(category);
159 commands = g_slist_append(commands, rec);
161 modrec = command_module_get(rec, module, protocol);
163 cb = g_new0(COMMAND_CALLBACK_REC, 1);
165 cb->user_data = user_data;
166 modrec->callbacks = g_slist_append(modrec->callbacks, cb);
169 str = g_strconcat("command ", cmd, NULL);
170 signal_add_full(module, priority, str, func, user_data);
174 signal_emit("commandlist new", 1, rec);
177 static void command_free(COMMAND_REC *rec)
179 commands = g_slist_remove(commands, rec);
180 signal_emit("commandlist remove", 1, rec);
182 g_free_not_null(rec->category);
183 g_strfreev(rec->options);
188 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
190 rec->modules = g_slist_remove(rec->modules, modrec);
192 g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
193 g_slist_free(modrec->callbacks);
194 g_free(modrec->name);
195 g_free_not_null(modrec->options);
199 static void command_module_destroy(COMMAND_REC *rec,
200 COMMAND_MODULE_REC *modrec)
202 GSList *tmp, *freelist;
204 command_module_free(modrec, rec);
206 /* command_set_options() might have added module declaration of it's
207 own without any signals .. check if they're the only ones left
208 and if so, destroy them. */
210 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
211 COMMAND_MODULE_REC *rec = tmp->data;
213 if (rec->callbacks == NULL)
214 freelist = g_slist_append(freelist, rec);
216 g_slist_free(freelist);
222 g_slist_foreach(freelist, (GFunc) command_module_free, rec);
223 g_slist_free(freelist);
225 if (rec->modules == NULL)
229 void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
232 COMMAND_MODULE_REC *modrec;
235 g_return_if_fail(cmd != NULL);
236 g_return_if_fail(func != NULL);
238 rec = command_find(cmd);
240 modrec = command_module_find_and_remove(rec, func);
241 g_return_if_fail(modrec != NULL);
243 if (modrec->callbacks == NULL)
244 command_module_destroy(rec, modrec);
247 str = g_strconcat("command ", cmd, NULL);
248 signal_remove_data(str, func, user_data);
252 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
254 static const char *command_expand(char *cmd)
260 g_return_val_if_fail(cmd != NULL, NULL);
265 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
266 COMMAND_REC *rec = tmp->data;
268 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
269 strchr(rec->cmd+len, ' ') == NULL) {
270 if (rec->cmd[len] == '\0') {
276 /* multiple matches, we still need to check
277 if there's some command left that is a
282 /* check that this is the only match */
288 signal_emit("error command", 2,
289 GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
293 return match != NULL ? match : cmd;
296 void command_runsub(const char *cmd, const char *data,
297 void *server, void *item)
300 char *orig, *subcmd, *defcmd, *args;
302 g_return_if_fail(data != NULL);
304 while (*data == ' ') data++;
307 /* no subcommand given - list the subcommands */
308 signal_emit("list subcommands", 2, cmd);
313 orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
314 args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
315 if (args != NULL) *args++ = '\0'; else args = "";
316 while (*args == ' ') args++;
318 /* check if this command can be expanded */
319 newcmd = command_expand(subcmd+8);
320 if (newcmd == NULL) {
321 /* ambiguous command */
326 subcmd = g_strconcat("command ", newcmd, NULL);
329 if (!signal_emit(subcmd, 3, args, server, item)) {
330 defcmd = g_strdup_printf("default command %s", cmd);
331 if (!signal_emit(defcmd, 3, data, server, item)) {
332 signal_emit("error command", 2,
333 GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
342 static GSList *optlist_find(GSList *optlist, const char *option)
344 while (optlist != NULL) {
345 char *name = optlist->data;
346 if (iscmdtype(*name)) name++;
348 if (g_strcasecmp(name, option) == 0)
351 optlist = optlist->next;
357 int command_have_option(const char *cmd, const char *option)
362 g_return_val_if_fail(cmd != NULL, FALSE);
363 g_return_val_if_fail(option != NULL, FALSE);
365 rec = command_find(cmd);
366 g_return_val_if_fail(rec != NULL, FALSE);
368 if (rec->options == NULL)
371 for (tmp = rec->options; *tmp != NULL; tmp++) {
372 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
374 if (g_strcasecmp(name, option) == 0)
381 static void command_calc_options(COMMAND_REC *rec, const char *options)
383 char **optlist, **tmp, *name, *str;
384 GSList *list, *oldopt;
386 optlist = g_strsplit(options, " ", -1);
388 if (rec->options == NULL) {
389 /* first call - use specified args directly */
390 rec->options = optlist;
394 /* save old options to linked list */
396 for (tmp = rec->options; *tmp != NULL; tmp++)
397 list = g_slist_append(list, g_strdup(*tmp));
398 g_strfreev(rec->options);
400 /* merge the options */
401 for (tmp = optlist; *tmp != NULL; tmp++) {
402 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
404 oldopt = optlist_find(list, name);
405 if (oldopt != NULL) {
406 /* already specified - overwrite old defination */
407 g_free(oldopt->data);
408 oldopt->data = g_strdup(*tmp);
410 /* new option, append to list */
411 list = g_slist_append(list, g_strdup(*tmp));
416 /* linked list -> string[] */
417 str = gslist_to_string(list, " ");
418 rec->options = g_strsplit(str, " ", -1);
421 g_slist_foreach(list, (GFunc) g_free, NULL);
425 /* recalculate options to command from options in all modules */
426 static void command_update_options(COMMAND_REC *rec)
430 g_strfreev(rec->options);
433 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
434 COMMAND_MODULE_REC *modrec = tmp->data;
436 if (modrec->options != NULL)
437 command_calc_options(rec, modrec->options);
441 void command_set_options_module(const char *module,
442 const char *cmd, const char *options)
445 COMMAND_MODULE_REC *modrec;
448 g_return_if_fail(module != NULL);
449 g_return_if_fail(cmd != NULL);
450 g_return_if_fail(options != NULL);
452 rec = command_find(cmd);
453 g_return_if_fail(rec != NULL);
454 modrec = command_module_get(rec, module, -1);
456 reload = modrec->options != NULL;
458 /* options already set for the module ..
459 we need to recalculate everything */
460 g_free(modrec->options);
463 modrec->options = g_strdup(options);
466 command_update_options(rec);
468 command_calc_options(rec, options);
471 char *cmd_get_param(char **data)
475 g_return_val_if_fail(data != NULL, NULL);
476 g_return_val_if_fail(*data != NULL, NULL);
478 while (**data == ' ') (*data)++;
481 while (**data != '\0' && **data != ' ') (*data)++;
482 if (**data == ' ') *(*data)++ = '\0';
487 char *cmd_get_quoted_param(char **data)
491 g_return_val_if_fail(data != NULL, NULL);
492 g_return_val_if_fail(*data != NULL, NULL);
494 while (**data == ' ') (*data)++;
495 if (**data != '\'' && **data != '"')
496 return cmd_get_param(data);
498 quote = **data; (*data)++;
501 while (**data != '\0' && (**data != quote ||
502 ((*data)[1] != ' ' && (*data)[1] != '\0'))) {
503 if (**data == '\\' && (*data)[1] != '\0')
504 g_memmove(*data, (*data)+1, strlen(*data));
508 if (**data == quote) {
517 /* Find specified option from list of options - the `option' might be
518 shortened version of the full command. Returns index where the
519 option was found, -1 if not found or -2 if there was multiple matches. */
520 static int option_find(char **array, const char *option)
523 int index, found, len, multiple;
525 g_return_val_if_fail(array != NULL, -1);
526 g_return_val_if_fail(option != NULL, -1);
528 len = strlen(option);
530 found = -1; index = 0; multiple = FALSE;
531 for (tmp = array; *tmp != NULL; tmp++, index++) {
532 const char *text = *tmp + iscmdtype(**tmp);
534 if (g_strncasecmp(text, option, len) == 0) {
535 if (text[len] == '\0') {
541 /* multiple matches - we still need to check
542 if there's a full match left.. */
546 /* partial match, check that it's the only one */
557 static int get_cmd_options(char **data, int ignore_unknown,
558 const char *cmd, GHashTable *options)
561 char *option, *arg, **optlist;
564 /* get option definations */
565 rec = cmd == NULL ? NULL : command_find(cmd);
566 optlist = rec == NULL ? NULL : rec->options;
568 option = NULL; pos = -1;
571 if (option != NULL && *optlist[pos] == '+') {
572 /* required argument missing! */
573 *data = optlist[pos] + 1;
574 return CMDERR_OPTION_ARG_MISSING;
578 if (**data == '-' && (*data)[1] == ' ') {
579 /* -- option means end of options even
580 if next word starts with - */
582 while (**data == ' ') (*data)++;
588 else if (**data != ' ')
589 option = cmd_get_param(data);
595 /* check if this option can have argument */
596 pos = optlist == NULL ? -1 :
597 option_find(optlist, option);
599 if (pos == -1 && optlist != NULL &&
600 is_numeric(option, '\0')) {
601 /* check if we want -<number> option */
602 pos = option_find(optlist, "#");
604 g_hash_table_insert(options, "#",
610 if (pos == -1 && !ignore_unknown) {
611 /* unknown option! */
613 return CMDERR_OPTION_UNKNOWN;
615 if (pos == -2 && !ignore_unknown) {
616 /* multiple matches */
618 return CMDERR_OPTION_AMBIGUOUS;
621 /* if we used a shortcut of parameter, put
622 the whole parameter name in options table */
623 option = optlist[pos] +
624 iscmdtype(*optlist[pos]);
626 if (options != NULL && pos != -3)
627 g_hash_table_insert(options, option, "");
629 if (pos < 0 || !iscmdtype(*optlist[pos]) ||
630 *optlist[pos] == '!')
633 while (**data == ' ') (*data)++;
640 if (*optlist[pos] == '@' && !i_isdigit(**data))
641 break; /* expected a numeric argument */
643 /* save the argument */
644 arg = cmd_get_quoted_param(data);
645 if (options != NULL) {
646 g_hash_table_remove(options, option);
647 g_hash_table_insert(options, option, arg);
651 while (**data == ' ') (*data)++;
663 get_optional_channel(WI_ITEM_REC *active_item, char **data, int require_name)
665 CHANNEL_REC *chanrec;
667 char *tmp, *origtmp, *channel;
669 if (active_item == NULL) {
670 /* no active channel in window, channel required */
671 return cmd_get_param(data);
674 origtmp = tmp = g_strdup(*data);
675 channel = cmd_get_param(&tmp);
677 if (strcmp(channel, "*") == 0 && !require_name) {
678 /* "*" means active channel */
680 ret = window_item_get_target(active_item);
681 } else if (!server_ischannel(active_item->server, channel)) {
682 /* we don't have channel parameter - use active channel */
683 ret = window_item_get_target(active_item);
685 /* Find the channel first and use it's name if found.
686 This allows automatic !channel -> !XXXXXchannel replaces. */
687 channel = cmd_get_param(data);
689 chanrec = channel_find(active_item->server, channel);
690 ret = chanrec == NULL ? channel : chanrec->name;
697 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
701 GHashTable **opthash;
702 char **str, *arg, *datad;
704 int cnt, error, ignore_unknown, require_name;
706 g_return_val_if_fail(data != NULL, FALSE);
708 va_start(args, count);
710 rec = g_new0(CMD_TEMP_REC, 1);
711 rec->data = g_strdup(data);
717 item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
718 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
720 if (count & PARAM_FLAG_OPTIONS) {
721 arg = (char *) va_arg(args, char *);
722 opthash = (GHashTable **) va_arg(args, GHashTable **);
724 rec->options = *opthash =
725 g_hash_table_new((GHashFunc) g_istr_hash,
726 (GCompareFunc) g_istr_equal);
728 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
729 error = get_cmd_options(&datad, ignore_unknown,
734 /* and now handle the string */
735 cnt = PARAM_WITHOUT_FLAGS(count);
736 if (count & PARAM_FLAG_OPTCHAN) {
737 /* optional channel as first parameter */
738 require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
739 PARAM_FLAG_OPTCHAN_NAME;
740 arg = (char *) get_optional_channel(item, &datad, require_name);
742 str = (char **) va_arg(args, char **);
743 if (str != NULL) *str = arg;
748 if (cnt == 0 && count & PARAM_FLAG_GETREST) {
752 arg = (count & PARAM_FLAG_NOQUOTES) ?
753 cmd_get_param(&datad) :
754 cmd_get_quoted_param(&datad);
757 str = (char **) va_arg(args, char **);
758 if (str != NULL) *str = arg;
764 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
767 cmd_params_free(rec);
774 void cmd_params_free(void *free_me)
776 CMD_TEMP_REC *rec = free_me;
778 if (rec->options != NULL) g_hash_table_destroy(rec->options);
783 static void command_module_unbind_all(COMMAND_REC *rec,
784 COMMAND_MODULE_REC *modrec)
788 for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
789 COMMAND_CALLBACK_REC *cb = tmp->data;
792 command_unbind_full(rec->cmd, cb->func, cb->user_data);
795 if (g_slist_find(commands, rec) != NULL) {
796 /* this module might have removed some options
797 from command, update them. */
798 command_update_options(rec);
802 void commands_remove_module(const char *module)
804 GSList *tmp, *next, *modlist;
806 g_return_if_fail(module != NULL);
808 for (tmp = commands; tmp != NULL; tmp = next) {
809 COMMAND_REC *rec = tmp->data;
812 modlist = gslist_find_string(rec->modules, module);
814 command_module_unbind_all(rec, modlist->data);
818 static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
822 for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
823 COMMAND_MODULE_REC *rec = tmp->data;
825 if (rec->protocol == -1) {
826 /* at least one module accepts the command
827 without specific protocol */
831 if (server != NULL && rec->protocol == server->chat_type) {
832 /* matching protocol found */
840 #define alias_runstack_push(alias) \
841 alias_runstack = g_slist_append(alias_runstack, alias)
843 #define alias_runstack_pop(alias) \
844 alias_runstack = g_slist_remove(alias_runstack, alias)
846 #define alias_runstack_find(alias) \
847 (gslist_find_icase_string(alias_runstack, alias) != NULL)
849 static void parse_command(const char *command, int expand_aliases,
850 SERVER_REC *server, void *item)
853 const char *alias, *newcmd;
854 char *cmd, *orig, *args, *oldcmd;
856 g_return_if_fail(command != NULL);
858 cmd = orig = g_strconcat("command ", command, NULL);
859 args = strchr(cmd+8, ' ');
860 if (args != NULL) *args++ = '\0'; else args = "";
862 /* check if there's an alias for command. Don't allow
864 alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
867 alias_runstack_push(cmd+8);
868 eval_special_string(alias, args, server, item);
869 alias_runstack_pop(cmd+8);
874 /* check if this command can be expanded */
875 newcmd = command_expand(cmd+8);
876 if (newcmd == NULL) {
877 /* ambiguous command */
882 rec = command_find(newcmd);
883 if (rec != NULL && !cmd_protocol_match(rec, server)) {
886 signal_emit("error command", 2,
887 GINT_TO_POINTER(server == NULL ?
888 CMDERR_NOT_CONNECTED :
889 CMDERR_ILLEGAL_PROTO));
893 cmd = g_strconcat("command ", newcmd, NULL);
896 oldcmd = current_command;
897 current_command = cmd+8;
898 if (server != NULL) server_ref(server);
899 if (!signal_emit(cmd, 3, args, server, item)) {
900 signal_emit_id(signal_default_command, 3,
901 command, server, item);
903 if (server != NULL) {
904 if (server->connection_lost)
905 server_disconnect(server);
906 server_unref(server);
908 current_command = oldcmd;
914 static void event_command(const char *line, SERVER_REC *server, void *item)
917 int expand_aliases = TRUE;
919 g_return_if_fail(line != NULL);
921 cmdchar = *line == '\0' ? NULL :
922 strchr(settings_get_str("cmdchars"), *line);
923 if (cmdchar != NULL && line[1] == ' ') {
924 /* "/ text" = same as sending "text" to active channel. */
928 if (cmdchar == NULL) {
929 /* non-command - let someone else handle this */
930 signal_emit("send text", 3, line, server, item);
934 /* same cmdchar twice ignores aliases ignores aliases */
936 if (*line == *cmdchar) {
938 expand_aliases = FALSE;
941 /* ^command hides the output - we'll do this at fe-common but
942 we have to skip the ^ char here.. */
943 if (*line == '^') line++;
945 parse_command(line, expand_aliases, server, item);
948 static int eval_recursion_depth=0;
949 /* SYNTAX: EVAL <command(s)> */
950 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
952 g_return_if_fail(data != NULL);
953 if (eval_recursion_depth > 100)
954 cmd_return_error(CMDERR_EVAL_MAX_RECURSE);
957 eval_recursion_depth++;
958 eval_special_string(data, "", server, item);
959 eval_recursion_depth--;
962 /* SYNTAX: CD <directory> */
963 static void cmd_cd(const char *data)
967 g_return_if_fail(data != NULL);
968 if (*data == '\0') return;
970 str = convert_home(data);
975 void commands_init(void)
978 current_command = NULL;
979 alias_runstack = NULL;
981 signal_default_command = signal_get_uniq_id("default command");
983 settings_add_str("misc", "cmdchars", "/");
984 signal_add("send command", (SIGNAL_FUNC) event_command);
986 command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
987 command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
990 void commands_deinit(void)
992 g_free_not_null(current_command);
994 signal_remove("send command", (SIGNAL_FUNC) event_command);
996 command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
997 command_unbind("cd", (SIGNAL_FUNC) cmd_cd);