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);
100 int command_have_sub(const char *command)
105 g_return_val_if_fail(command != NULL, FALSE);
107 /* find "command "s */
108 len = strlen(command);
109 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
110 COMMAND_REC *rec = tmp->data;
112 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
113 rec->cmd[len] == ' ')
120 static COMMAND_MODULE_REC *
121 command_module_get(COMMAND_REC *rec, const char *module, int protocol)
123 COMMAND_MODULE_REC *modrec;
125 g_return_val_if_fail(rec != NULL, NULL);
127 modrec = command_module_find(rec, module);
128 if (modrec == NULL) {
129 modrec = g_new0(COMMAND_MODULE_REC, 1);
130 modrec->name = g_strdup(module);
131 modrec->protocol = -1;
132 rec->modules = g_slist_append(rec->modules, modrec);
136 modrec->protocol = protocol;
141 void command_bind_full(const char *module, int priority, const char *cmd,
142 int protocol, const char *category, SIGNAL_FUNC func,
146 COMMAND_MODULE_REC *modrec;
147 COMMAND_CALLBACK_REC *cb;
150 g_return_if_fail(module != NULL);
151 g_return_if_fail(cmd != NULL);
153 rec = command_find(cmd);
155 rec = g_new0(COMMAND_REC, 1);
156 rec->cmd = g_strdup(cmd);
157 rec->category = category == NULL ? NULL : g_strdup(category);
158 commands = g_slist_append(commands, rec);
160 modrec = command_module_get(rec, module, protocol);
162 cb = g_new0(COMMAND_CALLBACK_REC, 1);
164 cb->user_data = user_data;
165 modrec->callbacks = g_slist_append(modrec->callbacks, cb);
168 str = g_strconcat("command ", cmd, NULL);
169 signal_add_full(module, priority, str, func, user_data);
173 signal_emit("commandlist new", 1, rec);
176 static void command_free(COMMAND_REC *rec)
178 commands = g_slist_remove(commands, rec);
179 signal_emit("commandlist remove", 1, rec);
181 g_free_not_null(rec->category);
182 g_strfreev(rec->options);
187 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
189 rec->modules = g_slist_remove(rec->modules, modrec);
191 g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
192 g_slist_free(modrec->callbacks);
193 g_free(modrec->name);
194 g_free_not_null(modrec->options);
198 static void command_module_destroy(COMMAND_REC *rec,
199 COMMAND_MODULE_REC *modrec)
201 GSList *tmp, *freelist;
203 command_module_free(modrec, rec);
205 /* command_set_options() might have added module declaration of it's
206 own without any signals .. check if they're the only ones left
207 and if so, destroy them. */
209 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
210 COMMAND_MODULE_REC *rec = tmp->data;
212 if (rec->callbacks == NULL)
213 freelist = g_slist_append(freelist, rec);
215 g_slist_free(freelist);
221 g_slist_foreach(freelist, (GFunc) command_module_free, rec);
222 g_slist_free(freelist);
224 if (rec->modules == NULL)
228 void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
231 COMMAND_MODULE_REC *modrec;
234 g_return_if_fail(cmd != NULL);
235 g_return_if_fail(func != NULL);
237 rec = command_find(cmd);
239 modrec = command_module_find_and_remove(rec, func);
240 g_return_if_fail(modrec != NULL);
242 if (modrec->callbacks == NULL)
243 command_module_destroy(rec, modrec);
246 str = g_strconcat("command ", cmd, NULL);
247 signal_remove_data(str, func, user_data);
251 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
253 static const char *command_expand(char *cmd)
259 g_return_val_if_fail(cmd != NULL, NULL);
264 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
265 COMMAND_REC *rec = tmp->data;
267 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
268 strchr(rec->cmd+len, ' ') == NULL) {
269 if (rec->cmd[len] == '\0') {
275 /* multiple matches, we still need to check
276 if there's some command left that is a
281 /* check that this is the only match */
287 signal_emit("error command", 2,
288 GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
292 return match != NULL ? match : cmd;
295 void command_runsub(const char *cmd, const char *data,
296 void *server, void *item)
299 char *orig, *subcmd, *defcmd, *args;
301 g_return_if_fail(data != NULL);
303 while (*data == ' ') data++;
306 /* no subcommand given - list the subcommands */
307 signal_emit("list subcommands", 2, cmd);
312 orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
313 args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
314 if (args != NULL) *args++ = '\0'; else args = "";
315 while (*args == ' ') args++;
317 /* check if this command can be expanded */
318 newcmd = command_expand(subcmd+8);
319 if (newcmd == NULL) {
320 /* ambiguous command */
325 subcmd = g_strconcat("command ", newcmd, NULL);
328 if (!signal_emit(subcmd, 3, args, server, item)) {
329 defcmd = g_strdup_printf("default command %s", cmd);
330 if (!signal_emit(defcmd, 3, data, server, item)) {
331 signal_emit("error command", 2,
332 GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
341 static GSList *optlist_find(GSList *optlist, const char *option)
343 while (optlist != NULL) {
344 char *name = optlist->data;
345 if (iscmdtype(*name)) name++;
347 if (g_strcasecmp(name, option) == 0)
350 optlist = optlist->next;
356 int command_have_option(const char *cmd, const char *option)
361 g_return_val_if_fail(cmd != NULL, FALSE);
362 g_return_val_if_fail(option != NULL, FALSE);
364 rec = command_find(cmd);
365 g_return_val_if_fail(rec != NULL, FALSE);
367 if (rec->options == NULL)
370 for (tmp = rec->options; *tmp != NULL; tmp++) {
371 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
373 if (g_strcasecmp(name, option) == 0)
380 static void command_calc_options(COMMAND_REC *rec, const char *options)
382 char **optlist, **tmp, *name, *str;
383 GSList *list, *oldopt;
385 optlist = g_strsplit(options, " ", -1);
387 if (rec->options == NULL) {
388 /* first call - use specified args directly */
389 rec->options = optlist;
393 /* save old options to linked list */
395 for (tmp = rec->options; *tmp != NULL; tmp++)
396 list = g_slist_append(list, g_strdup(*tmp));
397 g_strfreev(rec->options);
399 /* merge the options */
400 for (tmp = optlist; *tmp != NULL; tmp++) {
401 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
403 oldopt = optlist_find(list, name);
404 if (oldopt != NULL) {
405 /* already specified - overwrite old defination */
406 g_free(oldopt->data);
407 oldopt->data = g_strdup(*tmp);
409 /* new option, append to list */
410 list = g_slist_append(list, g_strdup(*tmp));
415 /* linked list -> string[] */
416 str = gslist_to_string(list, " ");
417 rec->options = g_strsplit(str, " ", -1);
420 g_slist_foreach(list, (GFunc) g_free, NULL);
424 /* recalculate options to command from options in all modules */
425 static void command_update_options(COMMAND_REC *rec)
429 g_strfreev(rec->options);
432 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
433 COMMAND_MODULE_REC *modrec = tmp->data;
435 if (modrec->options != NULL)
436 command_calc_options(rec, modrec->options);
440 void command_set_options_module(const char *module,
441 const char *cmd, const char *options)
444 COMMAND_MODULE_REC *modrec;
447 g_return_if_fail(module != NULL);
448 g_return_if_fail(cmd != NULL);
449 g_return_if_fail(options != NULL);
451 rec = command_find(cmd);
452 g_return_if_fail(rec != NULL);
453 modrec = command_module_get(rec, module, -1);
455 reload = modrec->options != NULL;
457 /* options already set for the module ..
458 we need to recalculate everything */
459 g_free(modrec->options);
462 modrec->options = g_strdup(options);
465 command_update_options(rec);
467 command_calc_options(rec, options);
470 char *cmd_get_param(char **data)
474 g_return_val_if_fail(data != NULL, NULL);
475 g_return_val_if_fail(*data != NULL, NULL);
477 while (**data == ' ') (*data)++;
480 while (**data != '\0' && **data != ' ') (*data)++;
481 if (**data == ' ') *(*data)++ = '\0';
486 char *cmd_get_quoted_param(char **data)
490 g_return_val_if_fail(data != NULL, NULL);
491 g_return_val_if_fail(*data != NULL, NULL);
493 while (**data == ' ') (*data)++;
494 if (**data != '\'' && **data != '"')
495 return cmd_get_param(data);
497 quote = **data; (*data)++;
500 while (**data != '\0' && (**data != quote ||
501 ((*data)[1] != ' ' && (*data)[1] != '\0'))) {
502 if (**data == '\\' && (*data)[1] != '\0')
503 g_memmove(*data, (*data)+1, strlen(*data));
507 if (**data == quote) {
516 /* Find specified option from list of options - the `option' might be
517 shortened version of the full command. Returns index where the
518 option was found, -1 if not found or -2 if there was multiple matches. */
519 static int option_find(char **array, const char *option)
522 int index, found, len, multiple;
524 g_return_val_if_fail(array != NULL, -1);
525 g_return_val_if_fail(option != NULL, -1);
527 len = strlen(option);
529 found = -1; index = 0; multiple = FALSE;
530 for (tmp = array; *tmp != NULL; tmp++, index++) {
531 const char *text = *tmp + iscmdtype(**tmp);
533 if (g_strncasecmp(text, option, len) == 0) {
534 if (text[len] == '\0') {
540 /* multiple matches - we still need to check
541 if there's a full match left.. */
545 /* partial match, check that it's the only one */
556 static int get_cmd_options(char **data, int ignore_unknown,
557 const char *cmd, GHashTable *options)
560 char *option, *arg, **optlist;
563 /* get option definations */
564 rec = cmd == NULL ? NULL : command_find(cmd);
565 optlist = rec == NULL ? NULL : rec->options;
567 option = NULL; pos = -1;
570 if (option != NULL && *optlist[pos] == '+') {
571 /* required argument missing! */
572 *data = optlist[pos] + 1;
573 return CMDERR_OPTION_ARG_MISSING;
577 if (**data == '-' && i_isspace((*data)[1])) {
578 /* -- option means end of options even
579 if next word starts with - */
581 while (i_isspace(**data)) (*data)++;
587 else if (!i_isspace(**data))
588 option = cmd_get_param(data);
594 /* check if this option can have argument */
595 pos = optlist == NULL ? -1 :
596 option_find(optlist, option);
598 if (pos == -1 && optlist != NULL &&
599 is_numeric(option, '\0')) {
600 /* check if we want -<number> option */
601 pos = option_find(optlist, "#");
603 g_hash_table_insert(options, "#",
609 if (pos == -1 && !ignore_unknown) {
610 /* unknown option! */
612 return CMDERR_OPTION_UNKNOWN;
614 if (pos == -2 && !ignore_unknown) {
615 /* multiple matches */
617 return CMDERR_OPTION_AMBIGUOUS;
620 /* if we used a shortcut of parameter, put
621 the whole parameter name in options table */
622 option = optlist[pos] +
623 iscmdtype(*optlist[pos]);
625 if (options != NULL && pos != -3)
626 g_hash_table_insert(options, option, "");
628 if (pos < 0 || !iscmdtype(*optlist[pos]) ||
629 *optlist[pos] == '!')
632 while (i_isspace(**data)) (*data)++;
639 if (*optlist[pos] == '@' && !i_isdigit(**data))
640 break; /* expected a numeric argument */
642 /* save the argument */
643 arg = cmd_get_quoted_param(data);
644 if (options != NULL) {
645 g_hash_table_remove(options, option);
646 g_hash_table_insert(options, option, arg);
650 while (i_isspace(**data)) (*data)++;
662 get_optional_channel(WI_ITEM_REC *active_item, char **data, int require_name)
664 CHANNEL_REC *chanrec;
666 char *tmp, *origtmp, *channel;
668 if (active_item == NULL) {
669 /* no active channel in window, channel required */
670 return cmd_get_param(data);
673 origtmp = tmp = g_strdup(*data);
674 channel = cmd_get_param(&tmp);
676 if (strcmp(channel, "*") == 0 && !require_name) {
677 /* "*" means active channel */
679 ret = window_item_get_target(active_item);
680 } else if (!server_ischannel(active_item->server, channel)) {
681 /* we don't have channel parameter - use active channel */
682 ret = window_item_get_target(active_item);
684 /* Find the channel first and use it's name if found.
685 This allows automatic !channel -> !XXXXXchannel replaces. */
686 channel = cmd_get_param(data);
688 chanrec = channel_find(active_item->server, channel);
689 ret = chanrec == NULL ? channel : chanrec->name;
696 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
700 GHashTable **opthash;
701 char **str, *arg, *datad;
703 int cnt, error, ignore_unknown, require_name;
705 g_return_val_if_fail(data != NULL, FALSE);
707 va_start(args, count);
709 rec = g_new0(CMD_TEMP_REC, 1);
710 rec->data = g_strdup(data);
716 item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
717 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
719 if (count & PARAM_FLAG_OPTIONS) {
720 arg = (char *) va_arg(args, char *);
721 opthash = (GHashTable **) va_arg(args, GHashTable **);
723 rec->options = *opthash =
724 g_hash_table_new((GHashFunc) g_istr_hash,
725 (GCompareFunc) g_istr_equal);
727 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
728 error = get_cmd_options(&datad, ignore_unknown,
733 /* and now handle the string */
734 cnt = PARAM_WITHOUT_FLAGS(count);
735 if (count & PARAM_FLAG_OPTCHAN) {
736 /* optional channel as first parameter */
737 require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
738 PARAM_FLAG_OPTCHAN_NAME;
739 arg = (char *) get_optional_channel(item, &datad, require_name);
741 str = (char **) va_arg(args, char **);
742 if (str != NULL) *str = arg;
747 if (cnt == 0 && count & PARAM_FLAG_GETREST) {
751 arg = (count & PARAM_FLAG_NOQUOTES) ?
752 cmd_get_param(&datad) :
753 cmd_get_quoted_param(&datad);
756 str = (char **) va_arg(args, char **);
757 if (str != NULL) *str = arg;
763 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
766 cmd_params_free(rec);
773 void cmd_params_free(void *free_me)
775 CMD_TEMP_REC *rec = free_me;
777 if (rec->options != NULL) g_hash_table_destroy(rec->options);
782 static void command_module_unbind_all(COMMAND_REC *rec,
783 COMMAND_MODULE_REC *modrec)
787 for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
788 COMMAND_CALLBACK_REC *cb = tmp->data;
791 command_unbind_full(rec->cmd, cb->func, cb->user_data);
794 if (g_slist_find(commands, rec) != NULL) {
795 /* this module might have removed some options
796 from command, update them. */
797 command_update_options(rec);
801 void commands_remove_module(const char *module)
803 GSList *tmp, *next, *modlist;
805 g_return_if_fail(module != NULL);
807 for (tmp = commands; tmp != NULL; tmp = next) {
808 COMMAND_REC *rec = tmp->data;
811 modlist = gslist_find_string(rec->modules, module);
813 command_module_unbind_all(rec, modlist->data);
817 static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
821 for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
822 COMMAND_MODULE_REC *rec = tmp->data;
824 if (rec->protocol == -1) {
825 /* at least one module accepts the command
826 without specific protocol */
830 if (server != NULL && rec->protocol == server->chat_type) {
831 /* matching protocol found */
839 #define alias_runstack_push(alias) \
840 alias_runstack = g_slist_append(alias_runstack, alias)
842 #define alias_runstack_pop(alias) \
843 alias_runstack = g_slist_remove(alias_runstack, alias)
845 #define alias_runstack_find(alias) \
846 (gslist_find_icase_string(alias_runstack, alias) != NULL)
848 static void parse_command(const char *command, int expand_aliases,
849 SERVER_REC *server, void *item)
852 const char *alias, *newcmd;
853 char *cmd, *orig, *args, *oldcmd;
855 g_return_if_fail(command != NULL);
857 cmd = orig = g_strconcat("command ", command, NULL);
858 args = strchr(cmd+8, ' ');
859 if (args != NULL) *args++ = '\0'; else args = "";
861 /* check if there's an alias for command. Don't allow
863 alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
866 alias_runstack_push(cmd+8);
867 eval_special_string(alias, args, server, item);
868 alias_runstack_pop(cmd+8);
873 /* check if this command can be expanded */
874 newcmd = command_expand(cmd+8);
875 if (newcmd == NULL) {
876 /* ambiguous command */
881 rec = command_find(newcmd);
882 if (rec != NULL && !cmd_protocol_match(rec, server)) {
885 signal_emit("error command", 2,
886 GINT_TO_POINTER(server == NULL ?
887 CMDERR_NOT_CONNECTED :
888 CMDERR_ILLEGAL_PROTO));
892 cmd = g_strconcat("command ", newcmd, NULL);
895 oldcmd = current_command;
896 current_command = cmd+8;
897 if (server != NULL) server_ref(server);
898 if (!signal_emit(cmd, 3, args, server, item)) {
899 signal_emit_id(signal_default_command, 3,
900 command, server, item);
902 if (server != NULL) {
903 if (server->connection_lost)
904 server_disconnect(server);
905 server_unref(server);
907 current_command = oldcmd;
913 static void event_command(const char *line, SERVER_REC *server, void *item)
916 int expand_aliases = TRUE;
918 g_return_if_fail(line != NULL);
920 cmdchar = *line == '\0' ? NULL :
921 strchr(settings_get_str("cmdchars"), *line);
922 if (cmdchar != NULL && line[1] == ' ') {
923 /* "/ text" = same as sending "text" to active channel. */
927 if (cmdchar == NULL) {
928 /* non-command - let someone else handle this */
929 signal_emit("send text", 3, line, server, item);
933 /* same cmdchar twice ignores aliases ignores aliases */
935 if (*line == *cmdchar) {
937 expand_aliases = FALSE;
940 /* ^command hides the output - we'll do this at fe-common but
941 we have to skip the ^ char here.. */
942 if (*line == '^') line++;
944 parse_command(line, expand_aliases, server, item);
947 /* SYNTAX: EVAL <command(s)> */
948 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
950 g_return_if_fail(data != NULL);
952 eval_special_string(data, "", server, item);
955 /* SYNTAX: CD <directory> */
956 static void cmd_cd(const char *data)
960 g_return_if_fail(data != NULL);
961 if (*data == '\0') return;
963 str = convert_home(data);
968 void commands_init(void)
971 current_command = NULL;
972 alias_runstack = NULL;
974 signal_default_command = signal_get_uniq_id("default command");
976 settings_add_str("misc", "cmdchars", "/");
977 signal_add("send command", (SIGNAL_FUNC) event_command);
979 command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
980 command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
983 void commands_deinit(void)
985 g_free_not_null(current_command);
987 signal_remove("send command", (SIGNAL_FUNC) event_command);
989 command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
990 command_unbind("cd", (SIGNAL_FUNC) cmd_cd);