diff options
Diffstat (limited to 'src')
206 files changed, 31247 insertions, 0 deletions
diff --git a/src/core/Makefile.am b/src/core/Makefile.am new file mode 100644 index 00000000..c7cd93b5 --- /dev/null +++ b/src/core/Makefile.am @@ -0,0 +1,59 @@ +noinst_LTLIBRARIES = libcore.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core + +if BUILD_MEMDEBUG +memdebug_src=memdebug.c +else +memdebug_src= +endif + +libcore_la_SOURCES = \ + args.c \ + commands.c \ + core.c \ + levels.c \ + line-split.c \ + log.c \ + $(memdebug_src) \ + misc.c \ + modules.c \ + net-disconnect.c \ + net-nonblock.c \ + network.c \ + pidwait.c \ + rawlog.c \ + server.c \ + server-redirect.c \ + settings.c \ + signals.c \ + special-vars.c + +noinst_HEADERS = \ + args.h \ + commands.h \ + core.h \ + levels.h \ + line-split.h \ + log.h \ + memdebug.h \ + misc.h \ + module.h \ + modules.h \ + net-disconnect.h \ + net-nonblock.h \ + network.h \ + pidwait.h \ + rawlog.h \ + server.h \ + server-redirect.h \ + settings.h \ + signals.h \ + special-vars.h + +EXTRA_DIST = \ + memdebug.c diff --git a/src/core/args.c b/src/core/args.c new file mode 100644 index 00000000..cf48e02c --- /dev/null +++ b/src/core/args.c @@ -0,0 +1,60 @@ +/* + args.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "args.h" + +GArray *iopt_tables = NULL; + +void args_register(struct poptOption *options) +{ + if (iopt_tables == NULL) + iopt_tables = g_array_new(TRUE, TRUE, sizeof(struct poptOption)); + + while (options->longName != NULL || options->shortName != '\0' || options->arg != NULL) { + g_array_append_val(iopt_tables, *options); + options = options+1; + } +} + +void args_execute(int argc, char *argv[]) +{ + poptContext con; + int nextopt; + + if (iopt_tables == NULL) + return; + + con = poptGetContext(PACKAGE, argc, argv, (struct poptOption *) (iopt_tables->data), 0); + poptReadDefaultConfig(con, TRUE); + + while ((nextopt = poptGetNextOpt(con)) > 0) ; + + if (nextopt != -1) { + printf(_("Error on option %s: %s.\nRun '%s --help' to see a full list of available command line options.\n"), + poptBadOption(con, 0), + poptStrerror(nextopt), + argv[0]); + exit(1); + } + + g_array_free(iopt_tables, TRUE); + iopt_tables = NULL; +} diff --git a/src/core/args.h b/src/core/args.h new file mode 100644 index 00000000..4d1b82fb --- /dev/null +++ b/src/core/args.h @@ -0,0 +1,15 @@ +#ifndef __ARGS_H +#define __ARGS_H + +#ifdef HAVE_POPT_H +# include <popt.h> +#else +# include "lib-popt/popt.h" +#endif + +extern GArray *iopt_tables; + +void args_register(struct poptOption *options); +void args_execute(int argc, char *argv[]); + +#endif diff --git a/src/core/commands.c b/src/core/commands.c new file mode 100644 index 00000000..e7f2560a --- /dev/null +++ b/src/core/commands.c @@ -0,0 +1,462 @@ +/* + commands.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "server.h" +#include "server-redirect.h" +#include "special-vars.h" + +#include "lib-config/iconfig.h" +#include "settings.h" + +#define alias_find(alias) \ + iconfig_get_str("aliases", alias, NULL) + +GSList *commands; +char *current_command; + +static GSList *cmdget_funcs; +static int signal_default_command; + +void command_bind(const char *cmd, const char *category, SIGNAL_FUNC func) +{ + COMMAND_REC *rec; + char *str; + + g_return_if_fail(cmd != NULL); + + rec = g_new0(COMMAND_REC, 1); + rec->cmd = g_strdup(cmd); + rec->category = category == NULL ? NULL : g_strdup(category); + commands = g_slist_append(commands, rec); + + if (func != NULL) { + str = g_strconcat("command ", cmd, NULL); + signal_add(str, func); + g_free(str); + } + + signal_emit("commandlist new", 1, rec); +} + +void command_free(COMMAND_REC *rec) +{ + commands = g_slist_remove(commands, rec); + signal_emit("commandlist remove", 1, rec); + + g_free_not_null(rec->category); + g_free(rec->cmd); + g_free(rec); +} + +void command_unbind(const char *cmd, SIGNAL_FUNC func) +{ + GSList *tmp; + char *str; + + g_return_if_fail(cmd != NULL); + + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strcasecmp(rec->cmd, cmd) == 0) { + command_free(rec); + break; + } + } + + if (func != NULL) { + str = g_strconcat("command ", cmd, NULL); + signal_remove(str, func); + g_free(str); + } +} + +void command_runsub(const char *cmd, const char *data, void *p1, void *p2) +{ + char *subcmd, *defcmd, *args; + + g_return_if_fail(data != NULL); + + /* get command.. */ + subcmd = g_strdup_printf("command %s %s", cmd, data); + args = strchr(subcmd+9 + strlen(cmd), ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + while (*args == ' ') args++; + + g_strdown(subcmd); + if (!signal_emit(subcmd, 3, args, p1, p2)) { + defcmd = g_strdup_printf("default command %s", cmd); + if (!signal_emit(defcmd, 3, data, p1, p2)) + signal_emit("unknown command", 3, strchr(subcmd, ' ')+1, p1, p2); + g_free(defcmd); + } + g_free(subcmd); +} + +char *cmd_get_param(char **data) +{ + char *pos; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + while (**data == ' ') (*data)++; + pos = *data; + + while (**data != '\0' && **data != ' ') (*data)++; + if (**data == ' ') *(*data)++ = '\0'; + + return pos; +} + +char *cmd_get_quoted_param(char **data) +{ + char *pos, quote; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + while (**data == ' ') (*data)++; + if (**data != '\'' && **data != '"') + return cmd_get_param(data); + + quote = **data; (*data)++; + + pos = *data; + while (**data != '\0' && **data != quote) { + if (**data == '\\' && (*data)[1] != '\0') + g_memmove(*data, (*data)+1, strlen(*data)); + (*data)++; + } + + if (**data != '\0') *(*data)++ = '\0'; + + return pos; +} + +static char *get_opt_args(char **data) +{ + /* -cmd1 -cmd2 -cmd3 ... */ + char *p, *ret; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + ret = NULL; + for (p = *data;;) { + if (*p != '-') { + if (p == *data) return ""; + + while (isspace(p[-1]) && p > *data) p--; + if (*p != '\0') *p++ = '\0'; + ret = *data; + *data = p; + return ret; + } + + while (!isspace(*p) && *p != '\0') p++; + while (isspace(*p)) p++; + } +} + +static void cmd_params_pack(char ***subargs, char *end, char *start, char *newstart) +{ + char ***tmp; + char *data; + int bufsize, datalen, len; + + bufsize = (int) (end-newstart)+1; + + data = g_malloc(bufsize); datalen = 0; + for (tmp = subargs; *tmp != NULL; tmp++) { + if (**tmp < start || **tmp > end) + continue; + + len = strlen(**tmp)+1; + if (datalen+len > bufsize) + g_error("cmd_params_pack() : buffer overflow!"); + + memcpy(data+datalen, **tmp, len); + **tmp = newstart+datalen; + datalen += len; + } + + g_memmove(newstart, data, datalen); + g_free(data); +} + +int arg_find(char **array, const char *item) +{ + char **tmp; + int index; + + g_return_val_if_fail(array != NULL, 0); + g_return_val_if_fail(item != NULL, 0); + + index = 0; + for (tmp = array; *tmp != NULL; tmp++, index++) { + if (g_strcasecmp(*tmp + (**tmp == '@'), item) == 0) + return index; + } + + return -1; +} + +static int get_multi_args(char **data, va_list va) +{ + /* -cmd1 arg1 -cmd2 "argument two" -cmd3 */ + GString *returnargs; + char **args, **arglist, *arg, *origdata; + char **nextarg, ***subargs; + int eat, pos; + + eat = 0; + args = (char **) va_arg(va, char **); + g_return_val_if_fail(args != NULL && *args != NULL && **args != '\0', 0); + + arglist = g_strsplit(*args, " ", -1); + eat = strarray_length(arglist); + + subargs = g_new(char **, eat+1); + for (pos = 0; pos < eat; pos++) { + subargs[pos] = (char **) va_arg(va, char **); + if (subargs[pos] == NULL) { + g_free(subargs); + g_warning("get_multi_args() : subargument == NULL"); + return eat; + } + *subargs[pos] = ""; + } + subargs[eat] = NULL; + + origdata = *data; + returnargs = g_string_new(NULL); + nextarg = NULL; + for (;;) { + if (**data == '-') { + (*data)++; arg = cmd_get_param(data); + g_string_sprintfa(returnargs, "-%s ", arg); + + /* check if this argument can have parameter */ + pos = arg_find(arglist, arg); + nextarg = pos == -1 ? NULL : subargs[pos]; + + while (isspace(**data)) (*data)++; + continue; + } + + if (nextarg == NULL) + break; + + if (*arglist[pos] == '@' && !isdigit(**data)) + break; /* expected a numeric argument */ + + /* save the sub-argument to `nextarg' */ + arg = cmd_get_quoted_param(data); + *nextarg = arg; nextarg = NULL; + + while (isspace(**data)) (*data)++; + } + + /* ok, this is a bit stupid. this will pack the arguments in `data' + like "-arg1 subarg -arg2 sub2\0" -> "-arg1 -arg2\0subarg\0sub2\0" + this is because it's easier to free only _one_ string instead of + two (`args') when using PARAM_FLAG_MULTIARGS. */ + if (origdata == *data) + *args = ""; + else { + cmd_params_pack(subargs, **data == '\0' ? *data : (*data)-1, + origdata, origdata+returnargs->len); + + g_string_truncate(returnargs, returnargs->len-1); + strcpy(origdata, returnargs->str); + *args = origdata; + } + + g_string_free(returnargs, TRUE); + g_strfreev(arglist); + g_free(subargs); + + return eat; +} + +char *cmd_get_callfuncs(const char *data, int *count, va_list *args) +{ + CMD_GET_FUNC func; + GSList *tmp; + char *ret, *old; + + ret = g_strdup(data); + for (tmp = cmdget_funcs; tmp != NULL; tmp = tmp->next) { + func = tmp->data; + + old = ret; + ret = func(ret, count, args); + g_free(old); + } + + return ret; +} + +char *cmd_get_params(const char *data, int count, ...) +{ + char **str, *arg, *ret, *datad; + va_list args; + int cnt, eat; + + g_return_val_if_fail(data != NULL, NULL); + + va_start(args, count); + ret = datad = cmd_get_callfuncs(data, &count, &args); + + cnt = PARAM_WITHOUT_FLAGS(count); + while (cnt-- > 0) { + if (count & PARAM_FLAG_OPTARGS) { + arg = get_opt_args(&datad); + count &= ~PARAM_FLAG_OPTARGS; + } else if (count & PARAM_FLAG_MULTIARGS) { + eat = get_multi_args(&datad, args)+1; + count &= ~PARAM_FLAG_MULTIARGS; + + cnt -= eat-1; + while (eat-- > 0) + str = (char **) va_arg(args, char **); + continue; + } else if (cnt == 0 && count & PARAM_FLAG_GETREST) { + /* get rest */ + arg = datad; + } else { + arg = cmd_get_quoted_param(&datad); + } + + str = (char **) va_arg(args, char **); + if (str != NULL) *str = arg; + } + va_end(args); + + return ret; +} + +void cmd_get_add_func(CMD_GET_FUNC func) +{ + cmdget_funcs = g_slist_prepend(cmdget_funcs, func); +} + +void cmd_get_remove_func(CMD_GET_FUNC func) +{ + cmdget_funcs = g_slist_prepend(cmdget_funcs, func); +} + +static void parse_outgoing(const char *line, SERVER_REC *server, void *item) +{ + const char *cmdchars, *alias; + char *cmd, *str, *args, *oldcmd; + int use_alias = TRUE; + + g_return_if_fail(line != NULL); + + if (*line == '\0') { + /* empty line, forget it. */ + signal_stop(); + return; + } + + cmdchars = settings_get_str("cmdchars"); + if (strchr(cmdchars, *line) == NULL) + return; /* handle only /commands here */ + line++; + + /* //command ignores aliases */ + if (strchr(cmdchars, *line) != NULL) { + line++; + use_alias = FALSE; + } + + cmd = str = g_strconcat("command ", line, NULL); + args = strchr(cmd+8, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + /* check if there's an alias for command */ + alias = use_alias ? alias_find(cmd+8) : NULL; + if (alias != NULL) + eval_special_string(alias, args, server, item); + else { + if (server != NULL) + server_redirect_default((SERVER_REC *) server, cmd); + + g_strdown(cmd); + oldcmd = current_command; + current_command = cmd+8; + if (!signal_emit(cmd, 3, args, server, item)) + signal_emit_id(signal_default_command, 3, line, server, item); + current_command = oldcmd; + } + + g_free(str); +} + +static void cmd_eval(const char *data, SERVER_REC *server, void *item) +{ + g_return_if_fail(data != NULL); + + eval_special_string(data, "", server, item); +} + +static void cmd_cd(const char *data) +{ + char *str; + + g_return_if_fail(data != NULL); + if (*data == '\0') return; + + str = convert_home(data); + chdir(str); + g_free(str); +} + +void commands_init(void) +{ + cmdget_funcs = NULL; + current_command = NULL; + + signal_default_command = module_get_uniq_id_str("signals", "default command"); + + settings_add_str("misc", "cmdchars", "/"); + signal_add("send command", (SIGNAL_FUNC) parse_outgoing); + + command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval); + command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd); +} + +void commands_deinit(void) +{ + g_free_not_null(current_command); + g_slist_free(cmdget_funcs); + + signal_remove("send command", (SIGNAL_FUNC) parse_outgoing); + + command_unbind("eval", (SIGNAL_FUNC) cmd_eval); + command_unbind("cd", (SIGNAL_FUNC) cmd_cd); +} diff --git a/src/core/commands.h b/src/core/commands.h new file mode 100644 index 00000000..f91bc2ce --- /dev/null +++ b/src/core/commands.h @@ -0,0 +1,74 @@ +#ifndef __COMMANDS_H +#define __COMMANDS_H + +#include "signals.h" + +typedef struct { + char *category; + char *cmd; +} +COMMAND_REC; + +enum { + CMDERR_PARAM, /* invalid parameter */ + CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */ + CMDERR_NOT_CONNECTED, /* not connected to IRC server */ + CMDERR_NOT_JOINED, /* not joined to any channels in this window */ + CMDERR_GETSOCKNAME, /* getsockname() failed */ + CMDERR_LISTEN, /* listen() failed */ + CMDERR_MULTIPLE_MATCHES, /* multiple matches found, didn't do anything */ + CMDERR_NICK_NOT_FOUND, /* nick not found */ + CMDERR_CHAN_NOT_FOUND, /* channel not found */ + CMDERR_SERVER_NOT_FOUND, /* server not found */ + CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */ + CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */ +}; + +#define cmd_return_error(a) { signal_emit("error command", 1, GINT_TO_POINTER(a)); return; } +#define cmd_param_error(a) { g_free(params); cmd_return_error(a); } + +extern GSList *commands; +extern char *current_command; + +void command_bind(const char *cmd, const char *category, SIGNAL_FUNC func); +void command_unbind(const char *cmd, SIGNAL_FUNC func); + +void command_runsub(const char *cmd, const char *data, void *p1, void *p2); + +/* count can have these flags: */ +#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00ffffff) +/* cmd_get_params() */ +#define PARAM_FLAG_GETREST 0x02000000 +/* optional arguments (-cmd -cmd2 -cmd3) */ +#define PARAM_FLAG_OPTARGS 0x04000000 +/* arguments can have arguments too. Example: + + -cmd arg -noargcmd -cmd2 "another arg -optnumarg rest of the text + + You would call this with: + + args = "cmd cmd2 @optnumarg"; + cmd_get_params(data, 5 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, + &args, &arg_cmd, &arg_cmd2, &arg_optnum, &rest); + + The variables are filled as following: + + args = "-cmd -noargcmd -cmd2 -optnumarg" + arg_cmd = "arg" + arg_cmd2 = "another arg" + rest = "rest of the text" + arg_optnum = "" - this is because "rest" isn't a numeric value +*/ +#define PARAM_FLAG_MULTIARGS 0x08000000 + +char *cmd_get_param(char **data); +char *cmd_get_params(const char *data, int count, ...); + +typedef char* (*CMD_GET_FUNC) (const char *data, int *count, va_list *args); +void cmd_get_add_func(CMD_GET_FUNC func); +void cmd_get_remove_func(CMD_GET_FUNC func); + +void commands_init(void); +void commands_deinit(void); + +#endif diff --git a/src/core/core.c b/src/core/core.c new file mode 100644 index 00000000..c850a572 --- /dev/null +++ b/src/core/core.c @@ -0,0 +1,68 @@ +/* + core.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "modules.h" +#include "pidwait.h" + +#include "net-disconnect.h" +#include "signals.h" +#include "settings.h" + +#include "server.h" +#include "commands.h" +#include "log.h" +#include "rawlog.h" +#include "special-vars.h" + +int irssi_gui; + +void core_init(void) +{ + modules_init(); + pidwait_init(); + + net_disconnect_init(); + signals_init(); + settings_init(); + + servers_init(); + commands_init(); + log_init(); + rawlog_init(); + special_vars_init(); +} + +void core_deinit(void) +{ + special_vars_deinit(); + rawlog_deinit(); + log_deinit(); + commands_deinit(); + servers_deinit(); + + settings_deinit(); + signals_deinit(); + net_disconnect_deinit(); + + pidwait_deinit(); + modules_deinit(); +} diff --git a/src/core/core.h b/src/core/core.h new file mode 100644 index 00000000..61d6ef70 --- /dev/null +++ b/src/core/core.h @@ -0,0 +1,17 @@ +#ifndef __IRSSI_CORE_H +#define __IRSSI_CORE_H + +/* for determining what GUI is currently in use: */ +#define IRSSI_GUI_NONE 0 +#define IRSSI_GUI_TEXT 1 +#define IRSSI_GUI_GTK 2 +#define IRSSI_GUI_GNOME 3 +#define IRSSI_GUI_QT 4 +#define IRSSI_GUI_KDE 5 + +extern int irssi_gui; + +void core_init(void); +void core_deinit(void); + +#endif diff --git a/src/core/levels.c b/src/core/levels.c new file mode 100644 index 00000000..7e55d738 --- /dev/null +++ b/src/core/levels.c @@ -0,0 +1,160 @@ +/* + levels.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "levels.h" + +static char *levels[] = +{ + "CRAP", + "MSGS", + "PUBLICS", + "NOTICES", + "SNOTES", + "CTCPS", + "ACTIONS", + "JOINS", + "PARTS", + "QUITS", + "KICKS", + "MODES", + "TOPICS", + "WALLS", + "WALLOPS", + "INVITES", + "NICKS", + "DCC", + "CLIENTNOTICES", + "CLIENTCRAP", + "CLIENTERRORS", + "HILIGHT", + + "NOHILIGHT", + NULL +}; + +int level_get(const char *level) +{ + int n, len; + + if (strcmp(level, "ALL") == 0) + return MSGLEVEL_ALL; + + if (strcmp(level, "NEVER") == 0) + return MSGLEVEL_NEVER; + + /* I never remember if it was PUBLIC or PUBLICS, MSG or MSGS, etc. + So, make it work with both. */ + len = strlen(level); + if (toupper(level[len-1]) == 'S') len--; + + for (n = 0; levels[n] != NULL; n++) { + if (strncmp(levels[n], level, len) == 0 && + (levels[n][len] == '\0' || strcmp(levels[n]+len, "S") == 0)) + return 1 << n; + } + + return 0; +} + +int level2bits(const char *level) +{ + char *orig, *str, *ptr; + int ret, slevel, neg; + + g_return_val_if_fail(level != NULL, 0); + + if (*level == '\0') + return 0; + + orig = str = g_strdup(level); + g_strup(str); + + ret = 0; + for (ptr = str; ; str++) { + if (*str == ' ') + *str++ = '\0'; + else if (*str != '\0') + continue; + + neg = *ptr == '-' ? 1 : 0; + if (*ptr == '-' || *ptr == '+') ptr++; + + slevel = level_get(ptr); + if (slevel != 0) { + ret = !neg ? (ret | slevel) : + (ret & ~slevel); + } + + while (*str == ' ') str++; + if (*str == '\0') break; + + ptr = str; + } + g_free(orig); + + return ret; +} + +char *bits2level(int bits) +{ + GString *str; + char *ret; + int n; + + if (bits == MSGLEVEL_ALL) + return g_strdup("ALL"); + + str = g_string_new(NULL); + if (bits & MSGLEVEL_NEVER) + g_string_append(str, "NEVER "); + + for (n = 0; levels[n] != NULL; n++) { + if (bits & (1 << n)) + g_string_sprintfa(str, "%s ", levels[n]); + } + g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +int combine_level(int dest, const char *src) +{ + char **list, **item; + int itemlevel; + + g_return_val_if_fail(src != NULL, dest); + + list = g_strsplit(src, " ", -1); + for (item = list; *item != NULL; item++) { + g_strup(*item); + itemlevel = level_get(*item + (**item == '+' || **item == '-' ? 1 : 0)); + if (**item == '-') + dest &= ~(itemlevel); + else + dest |= itemlevel; + } + g_strfreev(list); + + return dest; +} diff --git a/src/core/levels.h b/src/core/levels.h new file mode 100644 index 00000000..f3a54507 --- /dev/null +++ b/src/core/levels.h @@ -0,0 +1,44 @@ +#ifndef __LEVELS_H +#define __LEVELS_H + +/* This is pretty much IRC specific, but I think it would be easier for + other chats to try to use these same levels instead of implementing too + difficult message leveling system (which might be done if really + needed..). */ + +/* Message levels */ +#define MSGLEVEL_CRAP 0x0000001 +#define MSGLEVEL_MSGS 0x0000002 +#define MSGLEVEL_PUBLIC 0x0000004 +#define MSGLEVEL_NOTICES 0x0000008 +#define MSGLEVEL_SNOTES 0x0000010 +#define MSGLEVEL_CTCPS 0x0000020 +#define MSGLEVEL_ACTIONS 0x0000040 +#define MSGLEVEL_JOINS 0x0000080 +#define MSGLEVEL_PARTS 0x0000100 +#define MSGLEVEL_QUITS 0x0000200 +#define MSGLEVEL_KICKS 0x0000400 +#define MSGLEVEL_MODES 0x0000800 +#define MSGLEVEL_TOPICS 0x0001000 +#define MSGLEVEL_WALLS 0x0002000 +#define MSGLEVEL_WALLOPS 0x0004000 +#define MSGLEVEL_INVITES 0x0008000 +#define MSGLEVEL_NICKS 0x0010000 +#define MSGLEVEL_DCC 0x0020000 +#define MSGLEVEL_CLIENTNOTICE 0x0040000 +#define MSGLEVEL_CLIENTCRAP 0x0080000 +#define MSGLEVEL_CLIENTERROR 0x0100000 +#define MSGLEVEL_HILIGHT 0x0200000 + +#define MSGLEVEL_ALL 0x03fffff + +#define MSGLEVEL_NOHILIGHT 0x1000000 /* Don't try to hilight words in this message */ +#define MSGLEVEL_NO_ACT 0x2000000 /* Don't trigger channel activity */ +#define MSGLEVEL_NEVER 0x4000000 /* never ignore / never log */ +#define MSGLEVEL_LASTLOG 0x8000000 /* never ignore / never log */ + +int level2bits(const char *level); +char *bits2level(int bits); +int combine_level(int dest, const char *src); + +#endif diff --git a/src/core/line-split.c b/src/core/line-split.c new file mode 100644 index 00000000..fd27256e --- /dev/null +++ b/src/core/line-split.c @@ -0,0 +1,147 @@ +/* + line-split.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +/* Maximum line length - split to two lines if it's longer than this. + + This is mostly to prevent excessive memory usage. Like if someone DCC + chats you, you both have very fast connections and the other side sends + you 100 megs of text without any line feeds -> irssi will (try to) + allocate 128M of memory for the line and will eventually crash when it + can't allocate any more memory. If the line is split at every 64k the + text buffer will free the old lines and the memory usage never gets + too high. */ +#define MAX_CHARS_IN_LINE 65536 + +typedef struct { + int len; + int alloc; + int remove; + char *str; +} LINEBUF_REC; + +static inline int nearest_power(int num) +{ + int n = 1; + + while (n < num) n <<= 1; + return n; +} + +static void linebuf_append(LINEBUF_REC *rec, const char *data, int len) +{ + if (rec->len+len > rec->alloc) { + rec->alloc = nearest_power(rec->len+len);; + rec->str = rec->str == NULL ? g_malloc(rec->alloc) : + g_realloc(rec->str, rec->alloc); + } + + memcpy(rec->str + rec->len, data, len); + rec->len += len; +} + +static char *linebuf_find(LINEBUF_REC *rec, char chr) +{ + int n; + + for (n = 0; n < rec->len; n++) + if (rec->str[n] == chr) return rec->str+n; + + return NULL; +} + +static int remove_newline(LINEBUF_REC *rec) +{ + char *ptr; + + ptr = linebuf_find(rec, '\n'); + if (ptr == NULL) { + /* LF wasn't found, wait for more data.. */ + if (rec->len < MAX_CHARS_IN_LINE) + return 0; + + /* line buffer is too big - force a newline. */ + linebuf_append(rec, "\n", 1); + ptr = rec->str+rec->len-1; + } + + rec->remove = (int) (ptr-rec->str)+1; + if (ptr != rec->str && ptr[-1] == '\r') { + /* remove CR too. */ + ptr--; + } + + *ptr = '\0'; + return 1; +} + +/* line-split `data'. Initially `*buffer' should contain NULL. */ +int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer) +{ + LINEBUF_REC *rec; + + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(output != NULL, -1); + g_return_val_if_fail(buffer != NULL, -1); + + if (*buffer == NULL) + *buffer = g_new0(LINEBUF_REC, 1); + rec = *buffer; + + if (rec->remove > 0) { + rec->len = rec->len - rec->remove; + memcpy(rec->str, rec->str+rec->remove, rec->len); + rec->remove = 0; + } + + if (len > 0) + linebuf_append(rec, data, len); + else if (len < 0) { + /* connection closed.. */ + if (rec->len == 0) + return -1; + + /* no new data got but still something in buffer.. */ + len = 0; + if (linebuf_find(rec, '\n') == NULL) { + /* connection closed and last line is missing \n .. + just add it so we can see if it had anything useful.. */ + linebuf_append(rec, "\n", 1); + } + } + + *output = rec->str; + return remove_newline(rec); +} + +void line_split_free(LINEBUF_REC *buffer) +{ + if (buffer != NULL) { + if (buffer->str != NULL) g_free(buffer->str); + g_free(buffer); + } +} + +/* Return 1 if there is no data in the buffer */ +int line_split_is_empty(LINEBUF_REC *buffer) +{ + return buffer->len == 0; +} diff --git a/src/core/line-split.h b/src/core/line-split.h new file mode 100644 index 00000000..56d694c3 --- /dev/null +++ b/src/core/line-split.h @@ -0,0 +1,13 @@ +#ifndef __LINE_SPLIT_H +#define __LINE_SPLIT_H + +typedef struct _LINEBUF_REC LINEBUF_REC; + +/* line-split `data'. Initially `*buffer' should contain NULL. */ +int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer); +void line_split_free(LINEBUF_REC *buffer); + +/* Return 1 if there is no data in the buffer */ +int line_split_is_empty(LINEBUF_REC *buffer); + +#endif diff --git a/src/core/log.c b/src/core/log.c new file mode 100644 index 00000000..69c2fab5 --- /dev/null +++ b/src/core/log.c @@ -0,0 +1,438 @@ +/* + log.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "log.h" + +#include "lib-config/iconfig.h" +#include "settings.h" + +#define LOG_FILE_CREATE_MODE 644 + +#ifdef HAVE_FCNTL +static struct flock lock; +#endif + +GSList *logs; + +const char *log_timestamp; +static int log_file_create_mode; +static int rotate_tag; + +static void log_write_timestamp(int handle, const char *format, const char *suffix, time_t t) +{ + struct tm *tm; + char str[256]; + + tm = localtime(&t); + + str[sizeof(str)-1] = '\0'; + strftime(str, sizeof(str)-1, format, tm); + + write(handle, str, strlen(str)); + if (suffix != NULL) write(handle, suffix, strlen(suffix)); +} + +int log_start_logging(LOG_REC *log) +{ + char *str, fname[1024]; + struct tm *tm; + time_t t; + + g_return_val_if_fail(log != NULL, FALSE); + + if (log->handle != -1) + return TRUE; + + t = time(NULL); + tm = localtime(&t); + + /* Append/create log file */ + str = convert_home(log->fname); + strftime(fname, sizeof(fname), str, tm); + log->handle = open(fname, O_WRONLY | O_APPEND | O_CREAT, log_file_create_mode); + g_free(str); + + if (log->handle == -1) return FALSE; +#ifdef HAVE_FCNTL + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + if (fcntl(log->handle, F_SETLK, &lock) == -1) { + close(log->handle); + log->handle = -1; + signal_emit("log locked", 1, log); + return FALSE; + } +#endif + lseek(log->handle, 0, SEEK_END); + + log->opened = log->last = time(NULL); + log_write_timestamp(log->handle, settings_get_str("log_open_string"), "\n", log->last); + + signal_emit("log started", 1, log); + return TRUE; +} + +void log_stop_logging(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log->handle == -1) + return; + + signal_emit("log stopped", 1, log); + + log_write_timestamp(log->handle, settings_get_str("log_close_string"), "\n", time(NULL)); + +#ifdef HAVE_FCNTL + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + fcntl(log->handle, F_SETLK, &lock); +#endif + + close(log->handle); + log->handle = -1; +} + +void log_write_rec(LOG_REC *log, const char *str) +{ + struct tm *tm; + time_t t; + int day; + + g_return_if_fail(log != NULL); + g_return_if_fail(str != NULL); + + if (log->handle == -1) + return; + + t = time(NULL); + tm = localtime(&t); + day = tm->tm_mday; + + tm = localtime(&log->last); + if (tm->tm_mday != day) { + /* day changed */ + log_write_timestamp(log->handle, settings_get_str("log_day_changed"), "\n", t); + } + log->last = t; + + log_write_timestamp(log->handle, log_timestamp, str, t); + write(log->handle, "\n", 1); + + signal_emit("log written", 2, log, str); +} + +void log_file_write(const char *item, int level, const char *str, int no_fallbacks) +{ + GSList *tmp, *fallbacks; + char *tmpstr; + int found; + + g_return_if_fail(str != NULL); + + fallbacks = NULL; found = FALSE; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->handle == -1) + continue; /* log not opened yet */ + + if ((level & rec->level) == 0) + continue; + + if (rec->items == NULL) + fallbacks = g_slist_append(fallbacks, rec); + else if (item != NULL && strarray_find(rec->items, item) != -1) + log_write_rec(rec, str); + } + + if (!found && !no_fallbacks && fallbacks != NULL) { + /* not found from any items, so write it to all main logs */ + tmpstr = NULL; + if (level & MSGLEVEL_PUBLIC) + tmpstr = g_strconcat(item, ": ", str, NULL); + + g_slist_foreach(fallbacks, (GFunc) log_write_rec, tmpstr ? tmpstr : (char *)str); + g_free_not_null(tmpstr); + } + g_slist_free(fallbacks); +} + +void log_write(const char *item, int level, const char *str) +{ + log_file_write(item, level, str, TRUE); +} + +LOG_REC *log_find(const char *fname) +{ + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (strcmp(rec->fname, fname) == 0) + return rec; + } + + return NULL; +} + +const char *log_rotate2str(int rotate) +{ + switch (rotate) { + case LOG_ROTATE_HOUR: + return "hour"; + case LOG_ROTATE_DAY: + return "day"; + case LOG_ROTATE_WEEK: + return "week"; + case LOG_ROTATE_MONTH: + return "month"; + } + + return NULL; +} + +int log_str2rotate(const char *str) +{ + if (str == NULL) + return -1; + + if (g_strncasecmp(str, "hour", 4) == 0) + return LOG_ROTATE_HOUR; + if (g_strncasecmp(str, "day", 3) == 0) + return LOG_ROTATE_DAY; + if (g_strncasecmp(str, "week", 4) == 0) + return LOG_ROTATE_WEEK; + if (g_strncasecmp(str, "month", 5) == 0) + return LOG_ROTATE_MONTH; + if (g_strncasecmp(str, "never", 5) == 0) + return LOG_ROTATE_NEVER; + + return -1; +} + +static void log_set_config(LOG_REC *log) +{ + CONFIG_NODE *node; + char *levelstr; + + node = iconfig_node_traverse("logs", TRUE); + node = config_node_section(node, log->fname, NODE_TYPE_BLOCK); + + if (log->autoopen) + config_node_set_bool(node, "auto_open", TRUE); + else + config_node_set_str(node, "auto_open", NULL); + + config_node_set_str(node, "rotate", log_rotate2str(log->rotate)); + + levelstr = bits2level(log->level); + config_node_set_str(node, "level", levelstr); + g_free(levelstr); + + config_node_set_str(node, "items", NULL); + + if (log->items != NULL && *log->items != NULL) { + node = config_node_section(node, "items", NODE_TYPE_LIST); + config_node_add_list(node, log->items); + } +} + +static void log_remove_config(LOG_REC *log) +{ + iconfig_set_str("logs", log->fname, NULL); +} + +LOG_REC *log_create_rec(const char *fname, int level, const char *items) +{ + LOG_REC *rec; + + g_return_val_if_fail(fname != NULL, NULL); + + rec = log_find(fname); + if (rec == NULL) { + rec = g_new0(LOG_REC, 1); + rec->fname = g_strdup(fname); + rec->handle = -1; + } else { + g_strfreev(rec->items); + } + + rec->items = items == NULL || *items == '\0' ? NULL : + g_strsplit(items, " ", -1); + rec->level = level; + return rec; +} + +void log_update(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log_find(log->fname) == NULL) { + logs = g_slist_append(logs, log); + log->handle = -1; + } + + log_set_config(log); + signal_emit("log new", 1, log); +} + +void log_close(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + log_remove_config(log); + if (log->handle != -1) + log_stop_logging(log); + + logs = g_slist_remove(logs, log); + signal_emit("log remove", 1, log); + + if (log->items != NULL) g_strfreev(log->items); + g_free(log->fname); + g_free(log); +} + +static void sig_printtext_stripped(void *server, const char *item, gpointer levelp, const char *str) +{ + int level; + + g_return_if_fail(str != NULL); + + level = GPOINTER_TO_INT(levelp); + if (logs != NULL && level != MSGLEVEL_NEVER) + log_file_write(item, level, str, FALSE); +} + +static int sig_rotate_check(void) +{ + static int last_hour = -1; + struct tm tm_now, *tm; + GSList *tmp; + time_t now; + + /* don't do anything until hour is changed */ + now = time(NULL); + memcpy(&tm_now, localtime(&now), sizeof(tm_now)); + if (tm_now.tm_hour == last_hour) return 1; + last_hour = tm_now.tm_hour; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->handle == -1 || rec->rotate == LOG_ROTATE_NEVER) + continue; + + tm = localtime(&rec->opened); + if (rec->rotate == LOG_ROTATE_MONTH) { + if (tm->tm_mon == tm_now.tm_mon) + continue; + } else if (rec->rotate == LOG_ROTATE_WEEK) { + if (tm->tm_wday != 1 || tm->tm_mday == tm_now.tm_mday) + continue; + } else if (rec->rotate == LOG_ROTATE_DAY) { + if (tm->tm_mday == tm_now.tm_mday) + continue; + } + + log_stop_logging(rec); + log_start_logging(rec); + } + + return 1; +} + +static void log_read_config(void) +{ + CONFIG_NODE *node; + LOG_REC *log; + GSList *tmp; + + while (logs != NULL) + log_close(logs->data); + + node = iconfig_node_traverse("logs", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + log = g_new0(LOG_REC, 1); + logs = g_slist_append(logs, log); + + log->handle = -1; + log->fname = g_strdup(node->key); + log->autoopen = config_node_get_bool(node, "auto_open", FALSE); + log->level = level2bits(config_node_get_str(node, "level", 0)); + log->rotate = log_str2rotate(config_node_get_str(node, "rotate", NULL)); + if (log->rotate < 0) log->rotate = LOG_ROTATE_NEVER; + + node = config_node_section(node, "items", -1); + if (node != NULL) log->items = config_node_get_list(node); + + if (log->autoopen) log_start_logging(log); + } +} + +static void read_settings(void) +{ + log_timestamp = settings_get_str("log_timestamp"); + log_file_create_mode = octal2dec(settings_get_int("log_create_mode")); +} + +void log_init(void) +{ + rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL); + logs = NULL; + + settings_add_int("log", "log_create_mode", LOG_FILE_CREATE_MODE); + settings_add_str("log", "log_timestamp", "%H:%M "); + settings_add_str("log", "log_open_string", "--- Log opened %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_close_string", "--- Log closed %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_day_changed", "--- Day changed %a %b %d %Y"); + + read_settings(); + log_read_config(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("setup reread", (SIGNAL_FUNC) log_read_config); + signal_add("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); +} + +void log_deinit(void) +{ + g_source_remove(rotate_tag); + + while (logs != NULL) + log_close(logs->data); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("setup reread", (SIGNAL_FUNC) log_read_config); + signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); +} diff --git a/src/core/log.h b/src/core/log.h new file mode 100644 index 00000000..599be9e2 --- /dev/null +++ b/src/core/log.h @@ -0,0 +1,49 @@ +#ifndef __LOG_H +#define __LOG_H + +enum { + LOG_ROTATE_NEVER, + LOG_ROTATE_HOUR, + LOG_ROTATE_DAY, + LOG_ROTATE_WEEK, + LOG_ROTATE_MONTH +}; + +typedef struct { + char *fname; /* file name */ + int handle; /* file handle */ + time_t opened; + + int level; /* log only these levels */ + char **items; /* log only on these items (channels, queries, window refnums) */ + + time_t last; /* when last message was written */ + int rotate; + + int autoopen:1; /* automatically start logging at startup */ + int temp:1; /* don't save this to config file */ +} LOG_REC; + +extern GSList *logs; + +/* Create log record - you still need to call log_update() to actually add it + into log list */ +LOG_REC *log_create_rec(const char *fname, int level, const char *items); +void log_update(LOG_REC *log); +void log_close(LOG_REC *log); + +LOG_REC *log_find(const char *fname); + +void log_write(const char *item, int level, const char *str); +void log_write_rec(LOG_REC *log, const char *str); + +const char *log_rotate2str(int rotate); +int log_str2rotate(const char *str); + +int log_start_logging(LOG_REC *log); +void log_stop_logging(LOG_REC *log); + +void log_init(void); +void log_deinit(void); + +#endif diff --git a/src/core/memdebug.c b/src/core/memdebug.c new file mode 100644 index 00000000..1be7bbd8 --- /dev/null +++ b/src/core/memdebug.c @@ -0,0 +1,356 @@ +/* + memdebug.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#define ENABLE_BUFFER_CHECKS +#define BUFFER_CHECK_SIZE 5 +#define MIN_BUFFER_CHECK_SIZE 2 + +typedef struct { + void *p; + int size; + char *file; + int line; + char *comment; +} MEM_REC; + +static GHashTable *data = NULL, *preallocs = NULL; +static const char *comment = ""; + +static void add_flow_checks(guchar *p, unsigned long size) +{ +#ifdef ENABLE_BUFFER_CHECKS + int n; + + for (n = 0; n < BUFFER_CHECK_SIZE; n++) + p[n] = n ^ 0x7f; + for (n = 0; n < BUFFER_CHECK_SIZE; n++) + p[size-BUFFER_CHECK_SIZE+n] = n ^ 0x7f; +#endif +} + +void ig_memcheck_rec(void *key, MEM_REC *rec) +{ + guchar *p; + int n; + + if (rec->size != INT_MIN){ + p = rec->p; + + for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++) + if (p[n] != (n ^ 0x7f)) + g_error("buffer underflow, file %s line %d!\n", rec->file, rec->line); + + for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++) + if (p[rec->size-BUFFER_CHECK_SIZE+n] != (n ^ 0x7f)) + g_error("buffer overflow, file %s line %d!\n", rec->file, rec->line); + } +} + +static void mem_check(void) +{ +#ifdef ENABLE_BUFFER_CHECKS + g_hash_table_foreach(data, (GHFunc) ig_memcheck_rec, NULL); +#endif +} + +static void data_add(void *p, int size, const char *file, int line) +{ + MEM_REC *rec; + + if (size <= 0 && size != INT_MIN) + g_error("size = %d, file %s line %d", size, file, line); + + if (data == NULL) { + data = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + preallocs = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + } + + if (g_hash_table_lookup(data, p) != NULL) + g_error("data_add() already malloc()'ed %p (in %s:%d)", p, file, line); + + rec = g_new(MEM_REC, 1); + g_hash_table_insert(data, p, rec); + + rec->p = p; + rec->size = size; + rec->file = (char *) file; + rec->line = line; + rec->comment = g_strdup(comment); + + if (size == INT_MIN) + g_hash_table_insert(preallocs, p-BUFFER_CHECK_SIZE, p); + else + add_flow_checks(p, size); + mem_check(); +} + +static void *data_remove(void *p, const char *file, int line) +{ + MEM_REC *rec; + + mem_check(); + + if (g_hash_table_lookup(preallocs, p) != NULL) { + g_hash_table_remove(preallocs, p); + p += BUFFER_CHECK_SIZE; + } + + rec = g_hash_table_lookup(data, p); + if (rec == NULL) { + g_warning("data_remove() data %p not found (in %s:%d)", p, file, line); + return p+BUFFER_CHECK_SIZE; + } + + g_hash_table_remove(data, p); + g_free(rec->comment); + g_free(rec); + + return p; +} + +void *ig_malloc(int size, const char *file, int line) +{ +#if 1 + void *p; + + size += BUFFER_CHECK_SIZE*2; + p = g_malloc(size); + data_add(p, size, file, line); + return p+BUFFER_CHECK_SIZE; +#else + return g_malloc(size); +#endif +} + +void *ig_malloc0(int size, const char *file, int line) +{ +#if 1 + void *p; + + size += BUFFER_CHECK_SIZE*2; + p = g_malloc0(size); + data_add(p, size, file, line); + return p+BUFFER_CHECK_SIZE; +#else + return g_malloc0(size); +#endif +} + +void *ig_realloc(void *mem, unsigned long size, const char *file, int line) +{ +#if 1 + void *p; + + size += BUFFER_CHECK_SIZE*2; + mem -= BUFFER_CHECK_SIZE; + data_remove(mem, file, line); + p = g_realloc(mem, size); + data_add(p, size, file, line); + return p+BUFFER_CHECK_SIZE; +#else + return g_realloc(mem, size); +#endif +} + +char *ig_strdup(const char *str, const char *file, int line) +{ + void *p; + + if (str == NULL) return NULL; + + p = ig_malloc(strlen(str)+1, file, line); + strcpy(p, str); + + return p; +} + +char *ig_strndup(const char *str, int count, const char *file, int line) +{ + char *p; + + if (str == NULL) return NULL; + + p = ig_malloc(count+1, file, line); + strncpy(p, str, count); p[count] = '\0'; + + return p; +} + +char *ig_strconcat(const char *file, int line, const char *str, ...) +{ + guint l; + va_list args; + char *s; + char *concat; + + g_return_val_if_fail (str != NULL, NULL); + + l = 1 + strlen (str); + va_start (args, str); + s = va_arg (args, char*); + while (s) + { + l += strlen (s); + s = va_arg (args, char*); + } + va_end (args); + + concat = ig_malloc(l, file, line); + concat[0] = 0; + + strcat (concat, str); + va_start (args, str); + s = va_arg (args, char*); + while (s) + { + strcat (concat, s); + s = va_arg (args, char*); + } + va_end (args); + + return concat; +} + +char *ig_strdup_printf(const char *file, int line, const char *format, ...) +{ + char *buffer, *p; + va_list args; + + va_start (args, format); + buffer = g_strdup_vprintf (format, args); + va_end (args); + + p = ig_malloc(strlen(buffer)+1, file, line); + strcpy(p, buffer); + g_free(buffer); + + return p; +} + +char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args) +{ + char *buffer, *p; + + buffer = g_strdup_vprintf (format, args); + + p = ig_malloc(strlen(buffer)+1, file, line); + strcpy(p, buffer); + g_free(buffer); + + return p; +} + +void ig_free(void *p) +{ +#if 1 + p -= BUFFER_CHECK_SIZE; + p = data_remove(p, "??", 0); + if (p != NULL) +#endif + g_free(p); +} + +GString *ig_string_new(const char *file, int line, const char *str) +{ + GString *ret; + + ret = g_string_new(str); + data_add(ret, INT_MIN, file, line); + return ret; +} + +void ig_string_free(const char *file, int line, GString *str, gboolean freeit) +{ + data_remove(str, file, line); + if (!freeit) + data_add(str->str, INT_MIN, file, line); + + g_string_free(str, freeit); +} + +char *ig_strjoinv(const char *file, int line, const char *sepa, char **array) +{ + char *ret; + + ret = g_strjoinv(sepa, array); + data_add(ret, INT_MIN, file, line); + return ret; +} + +void ig_profile_line(void *key, MEM_REC *rec) +{ + char *data; + + if (*rec->comment == '\0' && + (strcmp(rec->file, "ig_strdup_printf") == 0 || + strcmp(rec->file, "ig_strdup_vprintf") == 0 || + strcmp(rec->file, "ig_strconcat") == 0 || + strcmp(rec->file, "ig_string_free (free = FALSE)") == 0)) + data = rec->p + BUFFER_CHECK_SIZE; + else + data = rec->comment; + fprintf(stderr, "%s:%d %d bytes (%s)\n", rec->file, rec->line, rec->size, data); +} + +void ig_mem_profile(void) +{ + g_hash_table_foreach(data, (GHFunc) ig_profile_line, NULL); + g_hash_table_destroy(data); + g_hash_table_destroy(preallocs); +} + +static MEM_REC *largest[10]; + +void ig_profile_largest(void *key, MEM_REC *rec) +{ + int n; + + for (n = 0; n < 10; n++) + { + if (largest[n] == NULL || rec->size > largest[n]->size) + { + g_memmove(largest+n+1, largest+n, sizeof(void *)*(9-n)); + largest[n] = rec; + } + } +} + +void ig_mem_profile_largest(void) +{ + /*int n;*/ + + memset(&largest, 0, sizeof(MEM_REC*)*10); + /*g_hash_table_foreach(data, (GHFunc) ig_profile_largest, NULL); + + for (n = 0; n < 10 && largest[n] != NULL; n++) + { + ig_profile_line(NULL, largest[n]); + }*/ +} + +void ig_set_data(const char *data) +{ + comment = data; +} diff --git a/src/core/memdebug.h b/src/core/memdebug.h new file mode 100644 index 00000000..1cf187f1 --- /dev/null +++ b/src/core/memdebug.h @@ -0,0 +1,31 @@ +#ifdef MEM_DEBUG +void ig_mem_profile(void); + +void ig_set_data(const char *data); + +void *ig_malloc(int size, const char *file, int line); +void *ig_malloc0(int size, const char *file, int line); +void *ig_realloc(void *mem, unsigned long size, const char *file, int line); +char *ig_strdup(const char *str, const char *file, int line); +char *ig_strndup(const char *str, int count, const char *file, int line); +char *ig_strconcat(const char *file, int line, const char *str, ...); +char *ig_strdup_printf(const char *file, int line, const char *format, ...) G_GNUC_PRINTF (3, 4); +char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args); +void ig_free(void *p); +GString *ig_string_new(const char *file, int line, const char *str); +void ig_string_free(const char *file, int line, GString *str, int freeit); +char *ig_strjoinv(const char *file, int line, const char *sepa, char **array); + +#define g_malloc(a) ig_malloc(a, __FILE__, __LINE__) +#define g_malloc0(a) ig_malloc0(a, __FILE__, __LINE__) +#define g_realloc(a,b) ig_realloc(a, b, __FILE__, __LINE__) +#define g_strdup(a) ig_strdup(a, __FILE__, __LINE__) +#define g_strndup(a, b) ig_strndup(a, b, __FILE__, __LINE__) +#define g_strconcat(a...) ig_strconcat(__FILE__, __LINE__, ##a) +#define g_strdup_printf(a, b...) ig_strdup_printf(__FILE__, __LINE__, a, ##b) +#define g_strdup_vprintf(a, b...) ig_strdup_vprintf(__FILE__, __LINE__, a, ##b) +#define g_free ig_free +#define g_string_new(a) ig_string_new(__FILE__, __LINE__, a) +#define g_string_free(a, b) ig_string_free(__FILE__, __LINE__, a, b) +#define g_strjoinv(a,b) ig_strjoinv(__FILE__, __LINE__, a, b) +#endif diff --git a/src/core/misc.c b/src/core/misc.c new file mode 100644 index 00000000..28aaae84 --- /dev/null +++ b/src/core/misc.c @@ -0,0 +1,467 @@ +/* + misc.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "misc.h" +#include "pidwait.h" + +#include <errno.h> +#include <regex.h> + +typedef struct { + GInputCondition condition; + GInputFunction function; + gpointer data; +} IRSSI_INPUT_REC; + +static gboolean irssi_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data) +{ + IRSSI_INPUT_REC *rec = data; + GInputCondition icond = 0; + + if (condition & (G_IO_IN | G_IO_PRI)) + icond |= G_INPUT_READ; + if (condition & G_IO_OUT) + icond |= G_INPUT_WRITE; + if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + icond |= G_INPUT_EXCEPTION; + + if (rec->condition & icond) + rec->function(rec->data, g_io_channel_unix_get_fd(source), icond); + + return TRUE; +} + +int g_input_add(int source, GInputCondition condition, + GInputFunction function, gpointer data) +{ + IRSSI_INPUT_REC *rec; + unsigned int result; + GIOChannel *channel; + GIOCondition cond = 0; + + rec = g_new(IRSSI_INPUT_REC, 1); + rec->condition = condition; + rec->function = function; + rec->data = data; + + if (condition & G_INPUT_READ) + cond |= (G_IO_IN | G_IO_PRI); + if (condition & G_INPUT_WRITE) + cond |= G_IO_OUT; + if (condition & G_INPUT_EXCEPTION) + cond |= G_IO_ERR|G_IO_HUP|G_IO_NVAL; + + channel = g_io_channel_unix_new (source); + result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, + irssi_io_invoke, rec, g_free); + g_io_channel_unref(channel); + + return result; +} + +long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2) +{ + long secs, usecs; + + secs = tv1->tv_sec - tv2->tv_sec; + usecs = tv1->tv_usec - tv2->tv_usec; + if (usecs < 0) { + usecs += 1000000; + secs--; + } + usecs = usecs/1000 + secs * 1000; + + return usecs; +} + +int find_substr(const char *list, const char *item) +{ + const char *ptr; + + g_return_val_if_fail(list != NULL, FALSE); + g_return_val_if_fail(item != NULL, FALSE); + + if (*item == '\0') + return FALSE; + + for (;;) { + while (isspace((gint) *list)) list++; + if (*list == '\0') break; + + ptr = strchr(list, ' '); + if (ptr == NULL) ptr = list+strlen(list); + + if (g_strncasecmp(list, item, ptr-list) == 0 && item[ptr-list] == '\0') + return TRUE; + + list = ptr; + } + + return FALSE; +} + +int strarray_length(char **array) +{ + int len; + + g_return_val_if_fail(array != NULL, 0); + + len = 0; + while (*array) { + len++; + array++; + } + return len; +} + +int strarray_find(char **array, const char *item) +{ + char **tmp; + int index; + + g_return_val_if_fail(array != NULL, 0); + g_return_val_if_fail(item != NULL, 0); + + index = 0; + for (tmp = array; *tmp != NULL; tmp++, index++) { + if (g_strcasecmp(*tmp, item) == 0) + return index; + } + + return -1; +} + +int copyfile(const char *src, const char *dest) +{ + FILE *fs, *fd; + int ret; + + g_return_val_if_fail(src != NULL, FALSE); + g_return_val_if_fail(dest != NULL, FALSE); + + fs = fopen(src, "rb"); + if (fs == NULL) return FALSE; + + ret = FALSE; + remove(dest); /* just to be sure there's no link already in dest */ + + fd = fopen(dest, "w+"); + if (fd != NULL) { + int len; + char buf[1024]; + + while ((len = fread(buf, 1, sizeof(buf), fs)) > 0) + if (fwrite(buf, 1, len, fd) != len) break; + fclose(fd); + ret = TRUE; + } + + fclose(fs); + return ret; +} + +int execute(const char *cmd) +{ + char **args; + char *prev, *dupcmd; + int pid, max, cnt; + + g_return_val_if_fail(cmd != NULL, -1); + + pid = fork(); + if (pid == -1) return FALSE; + if (pid != 0) { + pidwait_add(pid); + return pid; + } + + dupcmd = g_strdup(cmd); + max = 5; cnt = 0; + args = g_malloc(sizeof(char *)*max); + for (prev = dupcmd; ; dupcmd++) { + if (*dupcmd == '\0' || *dupcmd == ' ') { + args[cnt++] = prev; + if (cnt == max) { + max += 5; + args = g_realloc(args, sizeof(char *)*max); + } + if (*dupcmd == '\0') break; + *dupcmd++ = '\0'; + prev = dupcmd; + } + } + args[cnt] = NULL; + + execvp(args[0], args); + g_free(dupcmd); + + _exit(99); + return -1; +} + +GSList *gslist_find_string(GSList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (strcmp(list->data, key) == 0) return list; + + return NULL; +} + +GSList *gslist_find_icase_string(GSList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (g_strcasecmp(list->data, key) == 0) return list; + + return NULL; +} + +void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, void *data) +{ + void *ret; + + while (list != NULL) { + ret = func(list->data, data); + if (ret != NULL) return ret; + + list = list->next; + } + + return NULL; +} + +char *gslist_to_string(GSList *list, int offset, const char *delimiter) +{ + GString *str; + char **data, *ret; + + str = g_string_new(NULL); + while (list != NULL) { + data = G_STRUCT_MEMBER_P(list->data, offset); + + if (str->len != 0) g_string_append(str, delimiter); + g_string_append(str, *data); + list = list->next; + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +GList *glist_find_string(GList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (strcmp(list->data, key) == 0) return list; + + return NULL; +} + +GList *glist_find_icase_string(GList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (g_strcasecmp(list->data, key) == 0) return list; + + return NULL; +} + +char *stristr(const char *data, const char *key) +{ + const char *pos, *max; + int keylen, datalen; + + keylen = strlen(key); + datalen = strlen(data); + + if (keylen > datalen) + return NULL; + + max = data+datalen-keylen; + for (pos = data; pos <= max; pos++) + if (g_strncasecmp(pos, key, keylen) == 0) return (char *) pos; + + return NULL; +} + +#define isbound(c) \ + ((unsigned char) (c) < 128 && \ + (isspace((int) (c)) || ispunct((int) (c)))) + +char *stristr_full(const char *data, const char *key) +{ + const char *pos, *max; + int keylen, datalen; + + keylen = strlen(key); + datalen = strlen(data); + + if (keylen > datalen) + return NULL; + + max = data+datalen-keylen; + for (pos = data; pos <= max; pos++) { + if (pos > data && !isbound(pos[-1])) continue; + + if (g_strncasecmp(pos, key, keylen) == 0 && + (pos[keylen] == '\0' || isbound(pos[keylen]))) + return (char *) pos; + } + + return NULL; +} + +int regexp_match(const char *str, const char *regexp) +{ + regex_t preg; + int ret; + + if (regcomp(&preg, regexp, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0) + return 0; + + ret = regexec(&preg, str, 0, NULL, 0); + regfree(&preg); + + return ret == 0; +} + +char *convert_home(const char *path) +{ + return *path == '~' && (*(path+1) == '/' || *(path+1) == '\0') ? + g_strconcat(g_get_home_dir(), path+1, NULL) : + g_strdup(path); +} + +int g_istr_equal(gconstpointer v, gconstpointer v2) +{ + return g_strcasecmp((const char *) v, (const char *) v2) == 0; +} + +/* a char* hash function from ASU */ +unsigned int g_istr_hash(gconstpointer v) +{ + const char *s = (char *) v; + unsigned int h = 0, g; + + while (*s != '\0') { + h = (h << 4) + toupper(*s); + if ((g = h & 0xf0000000)) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h /* % M */; +} + +/* Find `mask' from `data', you can use * and ? wildcards. */ +int match_wildcards(const char *cmask, const char *data) +{ + char *mask, *newmask, *p1, *p2; + int ret; + + newmask = mask = strdup(cmask); + for (; *mask != '\0' && *data != '\0'; mask++) { + if (*mask == '?' || toupper(*mask) == toupper(*data)) { + data++; + continue; + } + + if (*mask != '*') + break; + + while (*mask == '?' || *mask == '*') mask++; + if (*mask == '\0') { + data += strlen(data); + break; + } + + p1 = strchr(mask, '*'); + p2 = strchr(mask, '?'); + if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2; + + if (p1 != NULL) *p1 = '\0'; + + data = stristr(data, mask); + if (data == NULL) break; + + data += strlen(mask); + mask += strlen(mask)-1; + + if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*'; + } + + ret = data != NULL && *data == '\0' && *mask == '\0'; + free(newmask); + + return ret; +} + +/* Return TRUE if all characters in `str' are numbers. + Stop when `end_char' is found from string. */ +int is_numeric(const char *str, char end_char) +{ + g_return_val_if_fail(str != NULL, FALSE); + + while (*str != '\0' && *str != end_char) { + if (!isdigit(*str)) return FALSE; + str++; + } + + return TRUE; +} + +/* replace all `from' chars in string to `to' chars. returns `str' */ +char *replace_chars(char *str, char from, char to) +{ + char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == from) *p = to; + } + return str; +} + +int octal2dec(int octal) +{ + int dec, n; + + dec = 0; n = 1; + while (octal != 0) { + dec += n*(octal%10); + octal /= 10; n *= 8; + } + + return dec; +} + +int dec2octal(int decimal) +{ + int octal, pos; + + octal = 0; pos = 0; + while (decimal > 0) { + octal += (decimal & 7)*(pos == 0 ? 1 : pos); + decimal /= 8; + pos += 10; + } + + return octal; +} diff --git a/src/core/misc.h b/src/core/misc.h new file mode 100644 index 00000000..b836e3dd --- /dev/null +++ b/src/core/misc.h @@ -0,0 +1,57 @@ +#ifndef __MISC_H +#define __MISC_H + +/* `str' should be type char[MAX_INT_STRLEN] */ +#define ltoa(str, num) \ + g_snprintf(str, sizeof(str), "%d", num) + +typedef void* (*FOREACH_FIND_FUNC) (void *item, void *data); + +long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2); + +/* find `item' from a space separated `list' */ +int find_substr(const char *list, const char *item); +/* return how many items `array' has */ +int strarray_length(char **array); +/* return index of `item' in `array' or -1 if not found */ +int strarray_find(char **array, const char *item); + +int copyfile(const char *src, const char *dest); +int execute(const char *cmd); /* returns pid or -1 = error */ + +GSList *gslist_find_string(GSList *list, const char *key); +GSList *gslist_find_icase_string(GSList *list, const char *key); +GList *glist_find_string(GList *list, const char *key); +GList *glist_find_icase_string(GList *list, const char *key); + +void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, void *data); +char *gslist_to_string(GSList *list, int offset, const char *delimiter); + +/* strstr() with case-ignoring */ +char *stristr(const char *data, const char *key); +/* stristr(), but matches only for full words */ +char *stristr_full(const char *data, const char *key); +/* easy way to check if regexp matches */ +int regexp_match(const char *str, const char *regexp); + +char *convert_home(const char *path); + +/* Case-insensitive string hash functions */ +int g_istr_equal(gconstpointer v, gconstpointer v2); +unsigned int g_istr_hash(gconstpointer v); + +/* Find `mask' from `data', you can use * and ? wildcards. */ +int match_wildcards(const char *mask, const char *data); + +/* Return TRUE if all characters in `str' are numbers. + Stop when `end_char' is found from string. */ +int is_numeric(const char *str, char end_char); + +/* replace all `from' chars in string to `to' chars. returns `str' */ +char *replace_chars(char *str, char from, char to); + +/* octal <-> decimal conversions */ +int octal2dec(int octal); +int dec2octal(int decimal); + +#endif diff --git a/src/core/module.h b/src/core/module.h new file mode 100644 index 00000000..89784389 --- /dev/null +++ b/src/core/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "core" diff --git a/src/core/modules.c b/src/core/modules.c new file mode 100644 index 00000000..d850f60d --- /dev/null +++ b/src/core/modules.c @@ -0,0 +1,185 @@ +/* + modules.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" + +static GHashTable *uniqids, *uniqstrids; +static GHashTable *idlookup, *stridlookup; +static int next_uniq_id; + +/* return unique number across all modules for `id' */ +int module_get_uniq_id(const char *module, int id) +{ + GHashTable *ids; + gpointer origkey, uniqid; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + ids = g_hash_table_lookup(idlookup, module); + if (ids == NULL) { + /* new module */ + ids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + g_hash_table_insert(idlookup, g_strdup(module), ids); + } + + if (!g_hash_table_lookup_extended(ids, GINT_TO_POINTER(id), &origkey, &uniqid)) { + /* not found */ + ret = next_uniq_id++; + g_hash_table_insert(ids, GINT_TO_POINTER(id), GINT_TO_POINTER(ret)); + g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), GINT_TO_POINTER(id)); + } else { + ret = GPOINTER_TO_INT(uniqid); + } + + return ret; +} + +/* return unique number across all modules for `id' */ +int module_get_uniq_id_str(const char *module, const char *id) +{ + GHashTable *ids; + gpointer origkey, uniqid; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + ids = g_hash_table_lookup(stridlookup, module); + if (ids == NULL) { + /* new module */ + ids = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + g_hash_table_insert(stridlookup, g_strdup(module), ids); + } + + if (!g_hash_table_lookup_extended(ids, id, &origkey, &uniqid)) { + /* not found */ + char *saveid; + + saveid = g_strdup(id); + ret = next_uniq_id++; + g_hash_table_insert(ids, saveid, GINT_TO_POINTER(ret)); + g_hash_table_insert(uniqstrids, GINT_TO_POINTER(ret), saveid); + } else { + ret = GPOINTER_TO_INT(uniqid); + } + + return ret; +} + +/* returns the original module specific id, -1 = not found */ +int module_find_id(const char *module, int uniqid) +{ + GHashTable *ids; + gpointer origkey, id; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + ret = g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid), &origkey, &id) ? + GPOINTER_TO_INT(id) : -1; + + if (ret != -1) { + /* check that module matches */ + ids = g_hash_table_lookup(idlookup, module); + if (ids == NULL || !g_hash_table_lookup_extended(ids, GINT_TO_POINTER(ret), &origkey, &id)) + ret = -1; + } + + return ret; +} + +/* returns the original module specific id, NULL = not found */ +const char *module_find_id_str(const char *module, int uniqid) +{ + GHashTable *ids; + gpointer origkey, id; + const char *ret; + + g_return_val_if_fail(module != NULL, NULL); + + ret = g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid), + &origkey, &id) ? id : NULL; + + if (ret != NULL) { + /* check that module matches */ + ids = g_hash_table_lookup(stridlookup, module); + if (ids == NULL || !g_hash_table_lookup_extended(ids, GINT_TO_POINTER(ret), &origkey, &id)) + ret = NULL; + } + + return ret; +} + +static void gh_uniq_destroy(gpointer key, gpointer value) +{ + g_hash_table_remove(uniqids, value); +} + +static void gh_uniq_destroy_str(gpointer key, gpointer value) +{ + g_hash_table_remove(uniqstrids, value); + g_free(key); +} + +/* Destroy unique IDs from `module'. This function is automatically called + when module is destroyed with module's name as the parameter. */ +void module_uniq_destroy(const char *module) +{ + GHashTable *ids; + gpointer key; + + if (g_hash_table_lookup_extended(idlookup, module, &key, (gpointer *) &ids)) { + g_hash_table_remove(idlookup, key); + g_free(key); + + g_hash_table_foreach(ids, (GHFunc) gh_uniq_destroy, NULL); + g_hash_table_destroy(ids); + } + + if (g_hash_table_lookup_extended(stridlookup, module, &key, (gpointer *) &ids)) { + g_hash_table_remove(stridlookup, key); + g_free(key); + + g_hash_table_foreach(ids, (GHFunc) gh_uniq_destroy_str, NULL); + g_hash_table_destroy(ids); + } +} + +void modules_init(void) +{ + idlookup = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + uniqids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + + stridlookup = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + uniqstrids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + next_uniq_id = 0; +} + +void modules_deinit(void) +{ + g_hash_table_foreach(idlookup, (GHFunc) module_uniq_destroy, NULL); + g_hash_table_destroy(idlookup); + g_hash_table_destroy(uniqids); + + g_hash_table_foreach(stridlookup, (GHFunc) module_uniq_destroy, NULL); + g_hash_table_destroy(stridlookup); + g_hash_table_destroy(uniqstrids); +} diff --git a/src/core/modules.h b/src/core/modules.h new file mode 100644 index 00000000..f175957b --- /dev/null +++ b/src/core/modules.h @@ -0,0 +1,33 @@ +#ifndef __MODULES_H +#define __MODULES_H + +#define MODULE_DATA_INIT(rec) \ + (rec)->module_data = g_hash_table_new(g_str_hash, g_str_equal) + +#define MODULE_DATA_DEINIT(rec) \ + g_hash_table_destroy((rec)->module_data) + +#define MODULE_DATA_SET(rec, data) \ + g_hash_table_insert((rec)->module_data, MODULE_NAME, data) + +#define MODULE_DATA(rec) \ + g_hash_table_lookup((rec)->module_data, MODULE_NAME) + +/* return unique number across all modules for `id' */ +int module_get_uniq_id(const char *module, int id); +/* return unique number across all modules for `id'. */ +int module_get_uniq_id_str(const char *module, const char *id); + +/* returns the original module specific id, -1 = not found */ +int module_find_id(const char *module, int uniqid); +/* returns the original module specific id, NULL = not found */ +const char *module_find_id_str(const char *module, int uniqid); + +/* Destroy unique IDs from `module'. This function is automatically called + when module is destroyed with module's name as the parameter. */ +void module_uniq_destroy(const char *module); + +void modules_init(void); +void modules_deinit(void); + +#endif diff --git a/src/core/net-disconnect.c b/src/core/net-disconnect.c new file mode 100644 index 00000000..9f95a34f --- /dev/null +++ b/src/core/net-disconnect.c @@ -0,0 +1,155 @@ +/* + net-disconnect.c : + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" + +/* when quitting, wait for max. 5 seconds before forcing to close the socket */ +#define MAX_QUIT_CLOSE_WAIT 5 + +/* wait for max. 2 minutes for other side to close the socket */ +#define MAX_CLOSE_WAIT (60*2) + +typedef struct { + time_t created; + int handle; + int tag; +} NET_DISCONNECT_REC; + +static GSList *disconnects; + +static int timeout_tag; + +static void net_disconnect_remove(NET_DISCONNECT_REC *rec) +{ + disconnects = g_slist_remove(disconnects, rec); + + g_source_remove(rec->tag); + g_free(rec); +} + +static void sig_disconnect(NET_DISCONNECT_REC *rec) +{ + char buf[128]; + int ret; + + /* check if there's any data waiting in socket */ + for (;;) { + ret = net_receive(rec->handle, buf, sizeof(buf)); + if (ret <= 0) + break; + } + + if (ret == -1) { + /* socket was closed */ + net_disconnect_remove(rec); + } +} + +static int sig_timeout_disconnect(void) +{ + NET_DISCONNECT_REC *rec; + GSList *tmp, *next; + time_t now; + int ret; + + /* check if we've waited enough for sockets to close themselves */ + now = time(NULL); + for (tmp = disconnects; tmp != NULL; tmp = next) { + rec = tmp->data; + next = tmp->next; + + if (rec->created+MAX_CLOSE_WAIT <= now) + sig_disconnect(rec); + } + + if (disconnects == NULL) { + /* no more sockets in disconnect queue, stop calling this + function */ + timeout_tag = -1; + } + ret = disconnects != NULL ? 1 : 0; + + return ret; +} + +/* Try to let the other side close the connection, if it still isn't + disconnected after certain amount of time, close it ourself */ +void net_disconnect_later(int handle) +{ + NET_DISCONNECT_REC *rec; + + rec = g_new(NET_DISCONNECT_REC, 1); + disconnects = g_slist_append(disconnects, rec); + + rec->created = time(NULL); + rec->handle = handle; + rec->tag = g_input_add(handle, G_INPUT_READ, (GInputFunction) sig_disconnect, rec); + + if (timeout_tag == -1) + timeout_tag = g_timeout_add(10000, (GSourceFunc) sig_timeout_disconnect, NULL); +} + +void net_disconnect_init(void) +{ + disconnects = NULL; + timeout_tag = -1; +} + +void net_disconnect_deinit(void) +{ + NET_DISCONNECT_REC *rec; + time_t now, max; + int first; + struct timeval tv; + fd_set set; + + if (disconnects == NULL) + return; + + /* give the sockets a chance to disconnect themselves.. */ + max = time(NULL)+MAX_QUIT_CLOSE_WAIT; + first = 1; + while (disconnects != NULL) { + rec = disconnects->data; + + now = time(NULL); + if (rec->created+MAX_QUIT_CLOSE_WAIT <= now || max <= now) { + /* this one has waited enough */ + net_disconnect_remove(rec); + continue; + } + + FD_ZERO(&set); + FD_SET(rec->handle, &set); + tv.tv_sec = first ? 0 : max-now; + tv.tv_usec = first ? 100000 : 0; + if (select(rec->handle+1, &set, NULL, NULL, &tv) > 0 && FD_ISSET(rec->handle, &set)) { + /* data coming .. check if we can close the handle */ + sig_disconnect(rec); + } else if (first) { + /* Display the text when we have already waited for a while */ + printf(_("Please wait, waiting for servers to close connections..\n")); + fflush(stdout); + + first = 0; + } + } +} diff --git a/src/core/net-disconnect.h b/src/core/net-disconnect.h new file mode 100644 index 00000000..a1ca0643 --- /dev/null +++ b/src/core/net-disconnect.h @@ -0,0 +1,7 @@ +#ifndef __NET_DISCONNECT_H +#define __NET_DISCONNECT_H + +void net_disconnect_init(void); +void net_disconnect_deinit(void); + +#endif diff --git a/src/core/net-internal.h b/src/core/net-internal.h new file mode 100644 index 00000000..79da708e --- /dev/null +++ b/src/core/net-internal.h @@ -0,0 +1,6 @@ +#ifdef HAVE_SOCKS_H +#include <socks.h> +#endif + +#include <netdb.h> +#include <arpa/inet.h> diff --git a/src/core/net-nonblock.c b/src/core/net-nonblock.c new file mode 100644 index 00000000..b6e9264f --- /dev/null +++ b/src/core/net-nonblock.c @@ -0,0 +1,210 @@ +/* + net-nonblock.c : Nonblocking net_connect() + + Copyright (C) 1998-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include <signal.h> + +#include "pidwait.h" +#include "net-nonblock.h" + +typedef struct +{ + NET_CALLBACK func; + void *data; + + int pipes[2]; + int port; + IPADDR *my_ip; + int tag; +} +SIMPLE_THREAD_REC; + +/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is + written to pipe when found PID of the resolver child is returned */ +int net_gethostname_nonblock(const char *addr, int pipe) +{ + RESOLVED_IP_REC rec; + const char *errorstr; + int pid; + + g_return_val_if_fail(addr != NULL, FALSE); + + pid = fork(); + if (pid > 0) { + /* parent */ + pidwait_add(pid); + return pid; + } + + if (pid != 0) { + /* failed! */ + g_warning("net_connect_thread(): fork() failed! Using blocking resolving"); + } + + /* child */ + rec.error = net_gethostname(addr, &rec.ip); + if (rec.error == 0) { + errorstr = NULL; + } else { + errorstr = net_gethosterror(rec.error); + rec.errlen = strlen(errorstr)+1; + } + + write(pipe, &rec, sizeof(rec)); + if (rec.error != 0) + write(pipe, errorstr, rec.errlen); + + if (pid == 0) + _exit(99); + + /* we used blocking lookup */ + return 0; +} + +/* get the resolved IP address */ +int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec) +{ + time_t maxwait; + int len, ret; + + rec->error = -1; + rec->errorstr = NULL; + + /* get ip+error - try for max. 1-2 seconds */ + fcntl(pipe, F_SETFL, O_NONBLOCK); + + maxwait = time(NULL)+2; + len = 0; + do { + ret = read(pipe, (char *) rec+len, sizeof(*rec)-len); + if (ret == -1) return -1; + + len += ret; + } while (len < sizeof(*rec) && time(NULL) < maxwait); + + if (len < sizeof(*rec)) + return -1; /* timeout */ + + if (rec->error) { + /* read error string */ + rec->errorstr = g_malloc(rec->errlen); + len = 0; + do { + ret = read(pipe, rec->errorstr+len, rec->errlen-len); + if (ret == -1) break; + len += ret; + } while (len < rec->errlen && time(NULL) < maxwait); + + if (len < rec->errlen) { + /* just ignore the rest of the error message.. */ + rec->errorstr[len] = '\0'; + } + } + + return 0; +} + +/* Kill the resolver child */ +void net_disconnect_nonblock(int pid) +{ + g_return_if_fail(pid > 0); + + kill(pid, SIGKILL); +} + +static void simple_init(SIMPLE_THREAD_REC *rec, int handle) +{ + g_return_if_fail(rec != NULL); + + g_source_remove(rec->tag); + + if (net_geterror(handle) != 0) { + /* failed */ + close(handle); + handle = -1; + } + + rec->func(handle, rec->data); + g_free(rec); +} + +static void simple_readpipe(SIMPLE_THREAD_REC *rec, int pipe) +{ + RESOLVED_IP_REC iprec; + int handle; + + g_return_if_fail(rec != NULL); + + g_source_remove(rec->tag); + + net_gethostbyname_return(pipe, &iprec); + g_free_not_null(iprec.errorstr); + + close(rec->pipes[0]); + close(rec->pipes[1]); + + handle = iprec.error == -1 ? -1 : + net_connect_ip(&iprec.ip, rec->port, rec->my_ip); + + g_free_not_null(rec->my_ip); + + if (handle == -1) { + /* failed */ + rec->func(-1, rec->data); + g_free(rec); + return; + } + + rec->tag = g_input_add(handle, G_INPUT_WRITE, + (GInputFunction) simple_init, rec); +} + +/* Connect to server, call func when finished */ +int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, NET_CALLBACK func, void *data) +{ + SIMPLE_THREAD_REC *rec; + int fd[2]; + + g_return_val_if_fail(server != NULL, FALSE); + g_return_val_if_fail(func != NULL, FALSE); + + if (pipe(fd) != 0) { + g_warning("net_connect_nonblock(): pipe() failed."); + return FALSE; + } + + /* start nonblocking host name lookup */ + net_gethostname_nonblock(server, fd[1]); + + rec = g_new0(SIMPLE_THREAD_REC, 1); + rec->port = port; + if (my_ip != NULL) { + rec->my_ip = g_malloc(sizeof(IPADDR)); + memcpy(rec->my_ip, my_ip, sizeof(IPADDR)); + } + rec->func = func; + rec->data = data; + rec->pipes[0] = fd[0]; + rec->pipes[1] = fd[1]; + rec->tag = g_input_add(fd[0], G_INPUT_READ, (GInputFunction) simple_readpipe, rec); + + return 1; +} diff --git a/src/core/net-nonblock.h b/src/core/net-nonblock.h new file mode 100644 index 00000000..6ae05e82 --- /dev/null +++ b/src/core/net-nonblock.h @@ -0,0 +1,26 @@ +#ifndef __NET_NONBLOCK_H +#define __NET_NONBLOCK_H + +#include "network.h" + +typedef struct { + IPADDR ip; /* resolved ip addres */ + int error; /* error, 0 = no error, -1 = error: */ + int errlen; /* error text length */ + char *errorstr; /* error string - dynamically allocated, you'll + need to free() it yourself unless it's NULL */ +} RESOLVED_IP_REC; + +typedef void (*NET_CALLBACK) (int, void *); + +/* nonblocking gethostbyname(), PID of the resolver child is returned. */ +int net_gethostname_nonblock(const char *addr, int pipe); +/* get the resolved IP address. returns -1 if some error occured with read() */ +int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec); + +/* Connect to server, call func when finished */ +int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, NET_CALLBACK func, void *data); +/* Kill the resolver child */ +void net_disconnect_nonblock(int pid); + +#endif diff --git a/src/core/network.c b/src/core/network.c new file mode 100644 index 00000000..d962f35f --- /dev/null +++ b/src/core/network.c @@ -0,0 +1,451 @@ +/* + network.c : Network stuff + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "net-internal.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +/* Cygwin need this, don't know others.. */ +/*#define BLOCKING_SOCKETS 1*/ + +int net_ip_compare(IPADDR *ip1, IPADDR *ip2) +{ + if (ip1->family != ip2->family) + return 0; + +#ifdef HAVE_IPV6 + if (ip1->family == AF_INET6) + return memcmp(&ip1->addr, &ip2->addr, sizeof(ip1->addr)) == 0; +#endif + + return memcmp(&ip1->addr, &ip2->addr, 4) == 0; +} + + +/* copy IP to sockaddr */ +inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip) +{ + so->sin.sin_family = ip->family; +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&so->sin6.sin6_addr, &ip->addr, sizeof(ip->addr.ip6)); + else +#endif + memcpy(&so->sin.sin_addr, &ip->addr, 4); +} + +inline void sin_get_ip(const union sockaddr_union *so, IPADDR *ip) +{ + ip->family = so->sin.sin_family; + +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&ip->addr, &so->sin6.sin6_addr, sizeof(ip->addr.ip6)); + else +#endif + memcpy(&ip->addr, &so->sin.sin_addr, 4); +} + +G_INLINE_FUNC void sin_set_port(union sockaddr_union *so, int port) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + so->sin6.sin6_port = htons(port); + else +#endif + so->sin.sin_port = htons(port); +} + +G_INLINE_FUNC int sin_get_port(union sockaddr_union *so) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + return ntohs(so->sin6.sin6_port); +#endif + return ntohs(so->sin.sin_port); +} + +/* Connect to socket */ +int net_connect(const char *addr, int port, IPADDR *my_ip) +{ + IPADDR ip; + + g_return_val_if_fail(addr != NULL, -1); + + if (net_gethostname(addr, &ip) == -1) + return -1; + + return net_connect_ip(&ip, port, my_ip); +} + +/* Connect to socket with ip address */ +int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) +{ + union sockaddr_union so; + int handle, ret, opt = 1; + + /* create the socket */ + memset(&so, 0, sizeof(so)); + so.sin.sin_family = ip->family; + handle = socket(ip->family, SOCK_STREAM, 0); + + if (handle == -1) + return -1; + + /* set socket options */ + fcntl(handle, F_SETFL, O_NONBLOCK); + setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)); + setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt)); + + /* set our own address, ignore if bind() fails */ + if (my_ip != NULL) { + sin_set_ip(&so, my_ip); + bind(handle, &so.sa, sizeof(so)); + } + + /* connect */ + sin_set_ip(&so, ip); + sin_set_port(&so, port); + ret = connect(handle, &so.sa, sizeof(so)); + + if (ret < 0 && errno != EINPROGRESS) { + close(handle); + return -1; + } + + return handle; +} + +/* Disconnect socket */ +void net_disconnect(int handle) +{ + g_return_if_fail(handle != -1); + + close(handle); +} + +/* Listen for connections on a socket */ +int net_listen(IPADDR *my_ip, int *port) +{ + union sockaddr_union so; + int ret, handle, opt = 1; + socklen_t len = sizeof(so); + + g_return_val_if_fail(my_ip != NULL, -1); + g_return_val_if_fail(port != NULL, -1); + + /* create the socket */ + memset(&so, 0, sizeof(so)); + so.sin.sin_family = my_ip->family; + handle = socket(my_ip->family, SOCK_STREAM, 0); + + if (handle == -1) + return -1; + + /* set socket options */ + fcntl(handle, F_SETFL, O_NONBLOCK); + setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)); + setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt)); + + /* specify the address/port we want to listen in */ + sin_set_port(&so, *port); + ret = bind(handle, &so.sa, sizeof(so)); + if (ret < 0) { + close(handle); + return -1; + } + + /* get the actual port we started listen */ + ret = getsockname(handle, &so.sa, &len); + if (ret < 0) { + close(handle); + return -1; + } + + *port = sin_get_port(&so); + + /* start listening */ + if (listen(handle, 1) < 0) + { + close(handle); + return -1; + } + + return handle; +} + +/* Accept a connection on a socket */ +int net_accept(int handle, IPADDR *addr, int *port) +{ + union sockaddr_union so; + int ret; + socklen_t addrlen; + + g_return_val_if_fail(handle != -1, -1); + g_return_val_if_fail(addr != NULL, -1); + g_return_val_if_fail(port != NULL, -1); + + addrlen = sizeof(so); + ret = accept(handle, &so.sa, &addrlen); + + if (ret < 0) + return -1; + + sin_get_ip(&so, addr); + *port = sin_get_port(&so); + + fcntl(ret, F_SETFL, O_NONBLOCK); + return ret; +} + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(int handle, char *buf, int len) +{ +#ifdef BLOCKING_SOCKETS + fd_set set; + struct timeval tv; +#endif + int ret; + + g_return_val_if_fail(handle != -1, -1); + g_return_val_if_fail(buf != NULL, -1); + +#ifdef BLOCKING_SOCKETS + FD_ZERO(&set); + FD_SET(handle, &set); + tv.tv_sec = 0; + tv.tv_usec = 0; + if (select(handle+1, &set, NULL, NULL, &tv) <= 0 || + !FD_ISSET(handle, &set)) return 0; +#endif + + ret = recv(handle, buf, len, 0); + if (ret == 0) + return -1; /* disconnected */ + + if (ret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) + return 0; /* no bytes received */ + + return ret; +} + +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(int handle, const char *data, int len) +{ + int n; + + g_return_val_if_fail(handle != -1, -1); + g_return_val_if_fail(data != NULL, -1); + + n = send(handle, data, len, 0); + if (n == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) + return 0; + + return n > 0 ? n : -1; +} + +/* Get socket address/port */ +int net_getsockname(int handle, IPADDR *addr, int *port) +{ + union sockaddr_union so; +#ifdef HAVE_IPV6 + socklen_t len = sizeof(so.sin6); +#else + socklen_t len = sizeof(so.sin); +#endif + + g_return_val_if_fail(handle != -1, -1); + g_return_val_if_fail(addr != NULL, -1); + +#ifdef HAVE_IPV6 + if (getsockname(handle, &so.sin6, &len) == -1) +#else + if (getsockname(handle, &so.sin, &len) == -1) +#endif + return -1; + + sin_get_ip(&so, addr); + if (port) *port = sin_get_port(&so); + + return 0; +} + +/* Get IP address for host, returns 0 = ok, + others = error code for net_gethosterror() */ +int net_gethostname(const char *addr, IPADDR *ip) +{ +#ifdef HAVE_IPV6 + union sockaddr_union *so; + struct addrinfo req, *ai; + char hbuf[NI_MAXHOST]; + int host_error; +#else + struct hostent *hp; +#endif + + g_return_val_if_fail(addr != NULL, -1); + + /* host name */ +#ifdef HAVE_IPV6 + memset(ip, 0, sizeof(IPADDR)); + memset(&req, 0, sizeof(struct addrinfo)); + req.ai_socktype = SOCK_STREAM; + + /* save error to host_error for later use */ + host_error = getaddrinfo(addr, NULL, &req, &ai); + if (host_error != 0) + return host_error; + + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)) + return 1; + + so = (union sockaddr_union *) ai->ai_addr; + sin_get_ip(so, ip); + freeaddrinfo(ai); +#else + hp = gethostbyname(addr); + if (hp == NULL) return -1; + + ip->family = AF_INET; + memcpy(&ip->addr, hp->h_addr, 4); +#endif + + return 0; +} + +int net_ip2host(IPADDR *ip, char *host) +{ +#ifdef HAVE_IPV6 + if (!inet_ntop(ip->family, &ip->addr, host, MAX_IP_LEN)) + return -1; +#else + unsigned long ip4; + + ip4 = ntohl(ip->addr.ip.s_addr); + sprintf(host, "%lu.%lu.%lu.%lu", + (ip4 & 0xff000000) >> 24, + (ip4 & 0x00ff0000) >> 16, + (ip4 & 0x0000ff00) >> 8, + (ip4 & 0x000000ff)); +#endif + return 0; +} + +int net_host2ip(const char *host, IPADDR *ip) +{ + unsigned long addr; + +#ifdef HAVE_IPV6 + if (strchr(host, ':') != NULL) { + /* IPv6 */ + ip->family = AF_INET6; + if (inet_pton(AF_INET6, host, &ip->addr) == 0) + return -1; + } else +#endif + { + /* IPv4 */ + ip->family = AF_INET; +#ifdef HAVE_INET_ATON + if (inet_aton(host, &ip->addr.ip.s_addr) == 0) + return -1; +#else + addr = inet_addr(host); + if (addr == INADDR_NONE) + return -1; + + memcpy(&ip->addr, &addr, 4); +#endif + } + + return 0; +} + +/* Get socket error */ +int net_geterror(int handle) +{ + int data; + socklen_t len = sizeof(data); + + if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &data, &len) == -1) + return -1; + + return data; +} + +/* get error of net_gethostname() */ +const char *net_gethosterror(int error) +{ +#ifdef HAVE_IPV6 + g_return_val_if_fail(error != 0, NULL); + + if (error == 1) { + /* getnameinfo() failed .. + FIXME: does strerror return the right error message?? */ + return g_strerror(errno); + } + + return gai_strerror(error); +#else + switch (h_errno) { + case HOST_NOT_FOUND: + return _("Host not found"); + case NO_ADDRESS: + return _("No IP address found for name"); + case NO_RECOVERY: + return _("A non-recovable name server error occurred"); + case TRY_AGAIN: + return _("A temporary error on an authoritative name server"); + } + + /* unknown error */ + return NULL; +#endif +} + +int is_ipv4_address(const char *host) +{ + while (*host != '\0') { + if (*host != '.' && !isdigit(*host)) + return 0; + host++; + } + + return 1; +} + +int is_ipv6_address(const char *host) +{ + while (*host != '\0') { + if (*host != ':' && !isxdigit(*host)) + return 0; + host++; + } + + return 1; +} diff --git a/src/core/network.h b/src/core/network.h new file mode 100644 index 00000000..a206da45 --- /dev/null +++ b/src/core/network.h @@ -0,0 +1,71 @@ +#ifndef __NETWORK_H +#define __NETWORK_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +struct _ipaddr { + unsigned short family; + union { +#ifdef HAVE_IPV6 + struct in6_addr ip6; +#else + struct in_addr ip; +#endif + } addr; +}; + +typedef struct _ipaddr IPADDR; + +/* maxmimum string length of IP address */ +#ifdef HAVE_IPV6 +# define MAX_IP_LEN INET6_ADDRSTRLEN +#else +# define MAX_IP_LEN 20 +#endif + +#define is_ipv6_addr(ip) ((ip)->family != AF_INET) + +/* returns 1 if IPADDRs are the same */ +int net_ip_compare(IPADDR *ip1, IPADDR *ip2); + +/* Connect to socket */ +int net_connect(const char *addr, int port, IPADDR *my_ip); +/* Connect to socket with ip address */ +int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); +/* Disconnect socket */ +void net_disconnect(int handle); +/* Try to let the other side close the connection, if it still isn't + disconnected after certain amount of time, close it ourself */ +void net_disconnect_later(int handle); + +/* Listen for connections on a socket */ +int net_listen(IPADDR *my_ip, int *port); +/* Accept a connection on a socket */ +int net_accept(int handle, IPADDR *addr, int *port); + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(int handle, char *buf, int len); +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(int handle, const char *data, int len); + +/* Get IP address for host, returns 0 = ok, + others = error code for net_gethosterror() */ +int net_gethostname(const char *addr, IPADDR *ip); +/* get error of net_gethostname() */ +const char *net_gethosterror(int error); + +/* Get socket address/port */ +int net_getsockname(int handle, IPADDR *addr, int *port); + +int net_ip2host(IPADDR *ip, char *host); +int net_host2ip(const char *host, IPADDR *ip); + +/* Get socket error */ +int net_geterror(int handle); + +int is_ipv4_address(const char *host); +int is_ipv6_address(const char *host); + +#endif diff --git a/src/core/pidwait.c b/src/core/pidwait.c new file mode 100644 index 00000000..7f6d77e3 --- /dev/null +++ b/src/core/pidwait.c @@ -0,0 +1,74 @@ +/* + pidwait.c : + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "modules.h" + +#include <sys/wait.h> + +static GSList *pids; + +static unsigned int childcheck_tag; +static int signal_pidwait; + +/* add a pid to wait list */ +void pidwait_add(int pid) +{ + pids = g_slist_append(pids, GINT_TO_POINTER(pid)); +} + +/* remove pid from wait list */ +void pidwait_remove(int pid) +{ + pids = g_slist_remove(pids, GINT_TO_POINTER(pid)); +} + +static int child_check(void) +{ + GSList *tmp, *next; + int status; + + /* wait for each pid.. */ + for (tmp = pids; tmp != NULL; tmp = next) { + next = tmp->next; + if (waitpid(GPOINTER_TO_INT(tmp->data), &status, WNOHANG) > 0) { + /* process terminated, remove from list */ + pids = g_slist_remove(pids, tmp->data); + signal_emit_id(signal_pidwait, 1, GPOINTER_TO_INT(tmp->data)); + } + } + return 1; +} + +void pidwait_init(void) +{ + pids = NULL; + childcheck_tag = g_timeout_add(1000, (GSourceFunc) child_check, NULL); + + signal_pidwait = module_get_uniq_id_str("signals", "pidwait"); +} + +void pidwait_deinit(void) +{ + g_slist_free(pids); + + g_source_remove(childcheck_tag); +} diff --git a/src/core/pidwait.h b/src/core/pidwait.h new file mode 100644 index 00000000..3f6b84cd --- /dev/null +++ b/src/core/pidwait.h @@ -0,0 +1,12 @@ +#ifndef __PIDWAIT_H +#define __PIDWAIT_H + +void pidwait_init(void); +void pidwait_deinit(void); + +/* add a pid to wait list */ +void pidwait_add(int pid); +/* remove pid from wait list */ +void pidwait_remove(int pid); + +#endif diff --git a/src/core/rawlog.c b/src/core/rawlog.c new file mode 100644 index 00000000..791e594d --- /dev/null +++ b/src/core/rawlog.c @@ -0,0 +1,167 @@ +/* + rawlog.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "rawlog.h" +#include "modules.h" +#include "signals.h" +#include "misc.h" + +#include "settings.h" +#include "common-setup.h" + +static int rawlog_lines; +static int signal_rawlog; + +RAWLOG_REC *rawlog_create(void) +{ + RAWLOG_REC *rec; + + rec = g_new0(RAWLOG_REC, 1); + return rec; +} + +void rawlog_destroy(RAWLOG_REC *rawlog) +{ + g_return_if_fail(rawlog != NULL); + + g_slist_foreach(rawlog->lines, (GFunc) g_free, NULL); + g_slist_free(rawlog->lines); + + if (rawlog->logging) close(rawlog->file); + g_free(rawlog); +} + +/* NOTE! str must be dynamically allocated and must not be freed after! */ +static void rawlog_add(RAWLOG_REC *rawlog, char *str) +{ + if (rawlog->nlines < rawlog_lines || rawlog_lines <= 2) + rawlog->nlines++; + else { + g_free(rawlog->lines->data); + rawlog->lines = g_slist_remove(rawlog->lines, rawlog->lines->data); + } + + if (rawlog->logging) { + write(rawlog->file, str, strlen(str)); + write(rawlog->file, "\n", 1); + } + + rawlog->lines = g_slist_append(rawlog->lines, str); + signal_emit_id(signal_rawlog, 2, rawlog, str); +} + +void rawlog_input(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf(">> %s", str)); +} + +void rawlog_output(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf("<< %s", str)); +} + +void rawlog_redirect(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf("--> %s", str)); +} + +static void rawlog_dump(RAWLOG_REC *rawlog, int f) +{ + GSList *tmp; + + for (tmp = rawlog->lines; tmp != NULL; tmp = tmp->next) { + write(f, tmp->data, strlen((char *) tmp->data)); + write(f, "\n", 1); + } +} + +void rawlog_open(RAWLOG_REC *rawlog, const char *fname) +{ + char *path; + + g_return_if_fail(rawlog != NULL); + g_return_if_fail(fname != NULL); + + if (rawlog->logging) + return; + + path = convert_home(fname); + rawlog->file = open(path, O_WRONLY | O_APPEND | O_CREAT, LOG_FILE_CREATE_MODE); + g_free(path); + + rawlog_dump(rawlog, rawlog->file); + rawlog->logging = rawlog->file != -1; +} + +void rawlog_close(RAWLOG_REC *rawlog) +{ + if (rawlog->logging) { + close(rawlog->file); + rawlog->logging = 0; + } +} + +void rawlog_save(RAWLOG_REC *rawlog, const char *fname) +{ + char *path; + int f; + + path = convert_home(fname); + f = open(path, O_WRONLY | O_APPEND | O_CREAT, LOG_FILE_CREATE_MODE); + g_free(path); + + rawlog_dump(rawlog, f); + close(f); +} + +void rawlog_set_size(int lines) +{ + rawlog_lines = lines; +} + +static void read_settings(void) +{ + rawlog_set_size(settings_get_int("rawlog_lines")); +} + +void rawlog_init(void) +{ + signal_rawlog = module_get_uniq_id_str("signals", "rawlog"); + + settings_add_int("history", "rawlog_lines", 200); + read_settings(); + + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void rawlog_deinit(void) +{ + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/core/rawlog.h b/src/core/rawlog.h new file mode 100644 index 00000000..ad1c8b53 --- /dev/null +++ b/src/core/rawlog.h @@ -0,0 +1,28 @@ +#ifndef __RAWLOG_H +#define __RAWLOG_H + +typedef struct { + int logging; + int file; + + int nlines; + GSList *lines; +} RAWLOG_REC; + +RAWLOG_REC *rawlog_create(void); +void rawlog_destroy(RAWLOG_REC *rawlog); + +void rawlog_input(RAWLOG_REC *rawlog, const char *str); +void rawlog_output(RAWLOG_REC *rawlog, const char *str); +void rawlog_redirect(RAWLOG_REC *rawlog, const char *str); + +void rawlog_set_size(int lines); + +void rawlog_open(RAWLOG_REC *rawlog, const char *fname); +void rawlog_close(RAWLOG_REC *rawlog); +void rawlog_save(RAWLOG_REC *rawlog, const char *fname); + +void rawlog_init(void); +void rawlog_deinit(void); + +#endif diff --git a/src/core/server-redirect.c b/src/core/server-redirect.c new file mode 100644 index 00000000..792fdb0f --- /dev/null +++ b/src/core/server-redirect.c @@ -0,0 +1,323 @@ +/* + server-redirect.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "server.h" +#include "server-redirect.h" + +static int redirect_group; + +static void server_eventtable_destroy(char *key, GSList *value) +{ + GSList *tmp; + + g_free(key); + + for (tmp = value; tmp != NULL; tmp = tmp->next) { + REDIRECT_REC *rec = tmp->data; + + g_free_not_null(rec->arg); + g_free(rec->name); + g_free(rec); + } + g_slist_free(value); +} + +static void server_eventgrouptable_destroy(gpointer key, GSList *value) +{ + g_slist_foreach(value, (GFunc) g_free, NULL); + g_slist_free(value); +} + +static void server_cmdtable_destroy(char *key, REDIRECT_CMD_REC *value) +{ + g_free(key); + + g_slist_foreach(value->events, (GFunc) g_free, NULL); + g_slist_free(value->events); + g_free(value); +} + +static void sig_disconnected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (server->eventtable != NULL) { + g_hash_table_foreach(server->eventtable, (GHFunc) server_eventtable_destroy, NULL); + g_hash_table_destroy(server->eventtable); + } + + g_hash_table_foreach(server->eventgrouptable, (GHFunc) server_eventgrouptable_destroy, NULL); + g_hash_table_destroy(server->eventgrouptable); + + if (server->cmdtable != NULL) { + g_hash_table_foreach(server->cmdtable, (GHFunc) server_cmdtable_destroy, NULL); + g_hash_table_destroy(server->cmdtable); + } +} + +void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list) +{ + REDIRECT_CMD_REC *rec; + + g_return_if_fail(server != NULL); + g_return_if_fail(command != NULL); + g_return_if_fail(last > 0); + + if (g_hash_table_lookup(server->cmdtable, command) != NULL) { + /* already in hash table. list of events SHOULD be the same... */ + g_slist_foreach(list, (GFunc) g_free, NULL); + g_slist_free(list); + return; + } + + rec = g_new(REDIRECT_CMD_REC, 1); + rec->last = last; + rec->events = list; + g_hash_table_insert(server->cmdtable, g_strdup(command), rec); +} + +void server_redirect_init(SERVER_REC *server, const char *command, int last, ...) +{ + va_list args; + GSList *list; + char *event; + + va_start(args, last); + list = NULL; + while ((event = va_arg(args, gchar *)) != NULL) + list = g_slist_append(list, g_strdup(event)); + va_end(args); + + server_redirect_initv(server, command, last, list); +} + +int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group, + const char *event, const char *signal, int argpos) +{ + REDIRECT_REC *rec; + GSList *list, *grouplist; + char *origkey; + + g_return_val_if_fail(server != NULL, 0); + g_return_val_if_fail(event != NULL, 0); + g_return_val_if_fail(signal != NULL, 0); + g_return_val_if_fail(arg != NULL || argpos == -1, 0); + + if (group == 0) group = ++redirect_group; + + rec = g_new0(REDIRECT_REC, 1); + rec->arg = arg == NULL ? NULL : g_strdup(arg); + rec->argpos = argpos; + rec->name = g_strdup(signal); + rec->group = group; + rec->last = last; + + if (g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list)) + g_hash_table_remove(server->eventtable, origkey); + else { + list = NULL; + origkey = g_strdup(event); + } + + grouplist = g_hash_table_lookup(server->eventgrouptable, GINT_TO_POINTER(group)); + if (grouplist != NULL) g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group)); + + list = g_slist_append(list, rec); + grouplist = g_slist_append(grouplist, g_strdup(event)); + + g_hash_table_insert(server->eventtable, origkey, list); + g_hash_table_insert(server->eventgrouptable, GINT_TO_POINTER(group), grouplist); + + return group; +} + +void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...) +{ + va_list args; + char *event, *signal; + int argpos, group; + + g_return_if_fail(server != NULL); + + va_start(args, last); + + group = 0; + while ((event = va_arg(args, gchar *)) != NULL) { + signal = va_arg(args, gchar *); + argpos = va_arg(args, gint); + + group = server_redirect_single_event(server, arg, last > 0, group, event, signal, argpos); + last--; + } + + va_end(args); +} + +void server_redirect_default(SERVER_REC *server, const char *command) +{ + REDIRECT_CMD_REC *cmdrec; + REDIRECT_REC *rec; + GSList *events, *list, *grouplist; + char *event, *origkey; + int last; + + g_return_if_fail(server != NULL); + g_return_if_fail(command != NULL); + + if (server->cmdtable == NULL) + return; /* not connected yet */ + + cmdrec = g_hash_table_lookup(server->cmdtable, command); + if (cmdrec == NULL) return; + + /* add all events used by command to eventtable and eventgrouptable */ + redirect_group++; grouplist = NULL; last = cmdrec->last; + for (events = cmdrec->events; events != NULL; events = events->next, last--) { + event = events->data; + + if (g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list)) + g_hash_table_remove(server->eventtable, origkey); + else { + list = NULL; + origkey = g_strdup(event); + } + + rec = g_new0(REDIRECT_REC, 1); + rec->argpos = -1; + rec->name = g_strdup(event); + rec->group = redirect_group; + rec->last = last > 0; + + grouplist = g_slist_append(grouplist, g_strdup(event)); + list = g_slist_append(list, rec); + g_hash_table_insert(server->eventtable, origkey, list); + } + + g_hash_table_insert(server->eventgrouptable, GINT_TO_POINTER(redirect_group), grouplist); +} + +void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item) +{ + REDIRECT_REC *rec; + GSList *grouplist, *list, *events, *tmp; + char *origkey; + int group; + + g_return_if_fail(server != NULL); + g_return_if_fail(event != NULL); + + if (!g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list)) + return; + + rec = item == NULL ? list->data : item->data; + if (!rec->last) { + /* this wasn't last expected event */ + return; + } + group = rec->group; + + /* get list of events from this group */ + grouplist = g_hash_table_lookup(server->eventgrouptable, GINT_TO_POINTER(group)); + + /* remove all of them */ + for (list = grouplist; list != NULL; list = list->next) { + char *event = list->data; + + if (!g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &events)) { + g_warning("server_redirect_remove_next() : event in eventgrouptable but not in eventtable"); + continue; + } + + /* remove the right group */ + for (tmp = events; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (rec->group == group) + break; + } + + if (rec == NULL) { + g_warning("server_redirect_remove_next() : event in eventgrouptable but not in eventtable (group)"); + continue; + } + + g_free(event); + + events = g_slist_remove(events, rec); + g_free_not_null(rec->arg); + g_free(rec->name); + g_free(rec); + + /* update hash table */ + g_hash_table_remove(server->eventtable, origkey); + if (events == NULL) + g_free(origkey); + else + g_hash_table_insert(server->eventtable, origkey, events); + } + + g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group)); + g_slist_free(grouplist); +} + +GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args) +{ + REDIRECT_REC *rec; + GSList *list; + char **arglist; + int found; + + list = g_hash_table_lookup(server->eventtable, event); + + for (; list != NULL; list = list->next) { + rec = list->data; + if (rec->argpos == -1) + break; + + if (rec->arg == NULL) + continue; + + /* we need to check that the argument is right.. */ + arglist = g_strsplit(args, " ", -1); + found = (strarray_length(arglist) > rec->argpos && + find_substr(rec->arg, arglist[rec->argpos])); + g_strfreev(arglist); + + if (found) break; + } + + return list; +} + +void servers_redirect_init(void) +{ + redirect_group = 0; + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void servers_redirect_deinit(void) +{ + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/core/server-redirect.h b/src/core/server-redirect.h new file mode 100644 index 00000000..977dded1 --- /dev/null +++ b/src/core/server-redirect.h @@ -0,0 +1,38 @@ +#ifndef __SERVER_REDIRECT_H +#define __SERVER_REDIRECT_H + +#include "server.h" + +typedef struct { + int last; /* number of "last" events at the start of the events list */ + GSList *events; /* char* list of events */ +} REDIRECT_CMD_REC; + +typedef struct { + char *name; /* event name */ + + char *arg; /* argument for event we are expecting or NULL */ + int argpos; /* argument position */ + + int group; /* group of events this belongs to */ + int last; /* if this event is received, remove all the events in this group */ +} +REDIRECT_REC; + +void server_redirect_init(SERVER_REC *server, const char *command, int last, ...); +void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list); +/* ... = char *event1, char *event2, ..., NULL */ + +void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...); +/* ... = char *event, char *callback_signal, int argpos, ..., NULL */ + +int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group, + const char *event, const char *signal, int argpos); +void server_redirect_default(SERVER_REC *server, const char *command); +void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item); +GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args); + +void servers_redirect_init(void); +void servers_redirect_deinit(void); + +#endif diff --git a/src/core/server.c b/src/core/server.c new file mode 100644 index 00000000..dcfefc5a --- /dev/null +++ b/src/core/server.c @@ -0,0 +1,273 @@ +/* + server.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "modules.h" +#include "signals.h" +#include "line-split.h" +#include "net-nonblock.h" +#include "rawlog.h" +#include "misc.h" +#include "server.h" +#include "server-redirect.h" +#include "settings.h" + +GSList *servers, *lookup_servers; + +/* connection to server failed */ +static void server_cant_connect(SERVER_REC *server, const char *msg) +{ + g_return_if_fail(server != NULL); + + lookup_servers = g_slist_remove(lookup_servers, server); + + signal_emit("server connect failed", 2, server, msg); + if (server->connect_tag != -1) + g_source_remove(server->connect_tag); + + if (server->connect_pipe[0] != -1) { + close(server->connect_pipe[0]); + close(server->connect_pipe[1]); + } + + MODULE_DATA_DEINIT(server); + g_free(server->tag); + g_free(server->nick); + g_free(server); +} + +/* generate tag from server's address */ +static char *server_create_address_tag(const char *address) +{ + const char *start, *end; + + /* try to generate a reasonable server tag */ + if (g_strncasecmp(address, "irc", 3) == 0 || + g_strncasecmp(address, "chat", 4) == 0) { + /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */ + end = strrchr(address, '.'); + start = end-1; + while (start > address && *start != '.') start--; + } else { + /* efnet.cs.hut.fi -> efnet */ + end = strchr(address, '.'); + start = end; + } + + if (start == end) start = address; else start++; + if (end == NULL) end = address + strlen(address); + + return g_strndup(start, (int) (end-start)); +} + +/* create unique tag for server. prefer ircnet's name or + generate it from server's address */ +static char *server_create_tag(SERVER_CONNECT_REC *conn) +{ + GString *str; + char *tag; + int num; + + tag = conn->ircnet != NULL ? g_strdup(conn->ircnet) : + server_create_address_tag(conn->address); + + /* then just append numbers after tag until unused is found.. */ + str = g_string_new(tag); + for (num = 2; server_find_tag(str->str) != NULL; num++) + g_string_sprintf(str, "%s%d", tag, num); + g_free(tag); + + tag = str->str; + g_string_free(str, FALSE); + return tag; +} + +static void server_connect_callback_init(SERVER_REC *server, int handle) +{ + int error; + + error = net_geterror(handle); + if (error != 0) { + server->connection_lost = TRUE; + server_cant_connect(server, g_strerror(error)); + return; + } + + lookup_servers = g_slist_remove(lookup_servers, server); + + g_source_remove(server->connect_tag); + server->connect_tag = -1; + server->connect_time = time(NULL); + server->rawlog = rawlog_create(); + servers = g_slist_append(servers, server); + + signal_emit("server connected", 1, server); +} + +static void server_connect_callback_readpipe(SERVER_REC *server, int handle) +{ + SERVER_CONNECT_REC *conn; + RESOLVED_IP_REC iprec; + + g_source_remove(server->connect_tag); + server->connect_tag = -1; + + net_gethostbyname_return(handle, &iprec); + + close(server->connect_pipe[0]); + close(server->connect_pipe[1]); + + server->connect_pipe[0] = -1; + server->connect_pipe[1] = -1; + + conn = server->connrec; + server->handle = iprec.error == -1 ? -1 : + net_connect_ip(&iprec.ip, conn->proxy != NULL ? + conn->proxy_port : conn->port, + conn->own_ip != NULL ? conn->own_ip : NULL); + if (server->handle == -1) { + /* failed */ + server->connection_lost = TRUE; + server_cant_connect(server, + iprec.error != -1 ? g_strerror(errno) : /* connect() failed */ + (iprec.errorstr != NULL ? iprec.errorstr : "Host lookup failed")); /* gethostbyname() failed */ + g_free_not_null(iprec.errorstr); + return; + } + + server->connect_tag = g_input_add(server->handle, G_INPUT_WRITE|G_INPUT_READ, + (GInputFunction) server_connect_callback_init, server); + signal_emit("server connecting", 2, server, &iprec.ip); +} + +int server_connect(SERVER_REC *server) +{ + g_return_val_if_fail(server != NULL, FALSE); + + MODULE_DATA_INIT(server); + + if (pipe(server->connect_pipe) != 0) { + g_warning("server_connect(): pipe() failed."); + return FALSE; + } + + server->tag = server_create_tag(server->connrec); + server->handle = -1; + + server->connect_pid = + net_gethostname_nonblock(server->connrec->proxy != NULL ? + server->connrec->proxy : server->connrec->address, + server->connect_pipe[1]); + + server->connect_tag = + g_input_add(server->connect_pipe[0], G_INPUT_READ, + (GInputFunction) server_connect_callback_readpipe, server); + + lookup_servers = g_slist_append(lookup_servers, server); + + signal_emit("server looking", 1, server); + return TRUE; +} + +void server_disconnect(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (server->connect_tag != -1) { + /* still connecting to server.. */ + if (server->connect_pid != -1) + net_disconnect_nonblock(server->connect_pid); + server_cant_connect(server, NULL); + return; + } + + servers = g_slist_remove(servers, server); + + signal_emit("server disconnected", 1, server); + + if (server->handle != -1) + net_disconnect(server->handle); + + MODULE_DATA_DEINIT(server); + rawlog_destroy(server->rawlog); + line_split_free(server->buffer); + g_free(server->tag); + g_free(server->nick); + g_free(server); +} + +SERVER_REC *server_find_tag(const char *tag) +{ + GSList *tmp; + + g_return_val_if_fail(tag != NULL, NULL); + if (*tag == '\0') return NULL; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (strcmp(server->tag, tag) == 0) + return server; + } + + for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (strcmp(server->tag, tag) == 0) + return server; + } + + return NULL; +} + +SERVER_REC *server_find_ircnet(const char *ircnet) +{ + GSList *tmp; + + g_return_val_if_fail(ircnet != NULL, NULL); + if (*ircnet == '\0') return NULL; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (server->connrec->ircnet != NULL && + strcmp(server->connrec->ircnet, ircnet) == 0) return server; + } + + return NULL; +} + +void servers_init(void) +{ + lookup_servers = servers = NULL; + + servers_redirect_init(); +} + +void servers_deinit(void) +{ + while (servers != NULL) + server_disconnect(servers->data); + while (lookup_servers != NULL) + server_cant_connect(lookup_servers->data, NULL); + + servers_redirect_deinit(); +} diff --git a/src/core/server.h b/src/core/server.h new file mode 100644 index 00000000..ea6ef94e --- /dev/null +++ b/src/core/server.h @@ -0,0 +1,66 @@ +#ifndef __SERVER_H +#define __SERVER_H + +#ifndef __NETWORK_H +typedef struct _ipaddr IPADDR; +#endif + +/* all strings should be either NULL or dynamically allocated */ +/* address and nick are mandatory, rest are optional */ +typedef struct { + /* if we're connecting via proxy, or just NULLs */ + char *proxy; + int proxy_port; + char *proxy_string; + + char *address; + int port; + char *ircnet; + + IPADDR *own_ip; +} SERVER_CONNECT_REC; + +typedef struct { + int type; /* server type */ + + SERVER_CONNECT_REC *connrec; + time_t connect_time; /* connection time */ + + char *tag; /* tag name for addressing server */ + char *nick; /* current nick */ + + int connected:1; /* connected to server */ + int connection_lost:1; /* Connection lost unintentionally */ + + int handle; /* socket handle */ + int readtag; /* input tag */ + + /* for net_connect_nonblock() */ + int connect_pipe[2]; + int connect_tag; + int connect_pid; + + /* For deciding if event should be handled internally */ + GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */ + GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */ + GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */ + + void *rawlog; + void *buffer; /* receive buffer */ + GHashTable *module_data; +} SERVER_REC; + +extern GSList *servers, *lookup_servers; + +/* Connect to server */ +int server_connect(SERVER_REC *server); +/* Disconnect from server */ +void server_disconnect(SERVER_REC *server); + +SERVER_REC *server_find_tag(const char *tag); +SERVER_REC *server_find_ircnet(const char *ircnet); + +void servers_init(void); +void servers_deinit(void); + +#endif diff --git a/src/core/settings.c b/src/core/settings.c new file mode 100644 index 00000000..af6e5ee6 --- /dev/null +++ b/src/core/settings.c @@ -0,0 +1,336 @@ +/* + settings.c : Irssi settings + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" + +#include "lib-config/iconfig.h" +#include "settings.h" +#include "default-config.h" + +#include <signal.h> + +CONFIG_REC *mainconfig; + +static GHashTable *settings; +static char *last_error_msg; + +static const char *settings_get_default_str(const char *key) +{ + SETTINGS_REC *rec; + + g_return_val_if_fail(key != NULL, NULL); + + rec = g_hash_table_lookup(settings, key); + if (rec == NULL) { + g_warning("settings_get_default_str(%s) : unknown setting", key); + return NULL; + } + + return rec->def; +} + +static int settings_get_default_int(const char *key) +{ + SETTINGS_REC *rec; + + g_return_val_if_fail(key != NULL, -1); + + rec = g_hash_table_lookup(settings, key); + if (rec == NULL) { + g_warning("settings_get_default_int(%s) : unknown setting", key); + return -1; + } + + return GPOINTER_TO_INT(rec->def); +} + +const char *settings_get_str(const char *key) +{ + return iconfig_get_str("settings", key, settings_get_default_str(key)); +} + +const int settings_get_int(const char *key) +{ + return iconfig_get_int("settings", key, settings_get_default_int(key)); +} + +const int settings_get_bool(const char *key) +{ + return iconfig_get_bool("settings", key, settings_get_default_int(key)); +} + +void settings_add_str(const char *section, const char *key, const char *def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = def == NULL ? NULL : g_strdup(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +void settings_add_int(const char *section, const char *key, int def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->type = SETTING_TYPE_INT; + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = GINT_TO_POINTER(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +void settings_add_bool(const char *section, const char *key, int def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->type = SETTING_TYPE_BOOLEAN; + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = GINT_TO_POINTER(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +static void settings_destroy(SETTINGS_REC *rec) +{ + if (rec->type == SETTING_TYPE_STRING) + g_free_not_null(rec->def); + g_free(rec->section); + g_free(rec->key); + g_free(rec); +} + +void settings_remove(const char *key) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + + rec = g_hash_table_lookup(settings, key); + if (rec == NULL) return; + + g_hash_table_remove(settings, key); + settings_destroy(rec); +} + +int settings_get_type(const char *key) +{ + SETTINGS_REC *rec; + + g_return_val_if_fail(key != NULL, -1); + + rec = g_hash_table_lookup(settings, key); + return rec == NULL ? -1 : rec->type; +} + +/* Get the record of the setting */ +SETTINGS_REC *settings_get_record(const char *key) +{ + g_return_val_if_fail(key != NULL, NULL); + + return g_hash_table_lookup(settings, key); +} + +static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2) +{ + return strcmp(v1->section, v2->section); +} + +static void settings_hash_get(const char *key, SETTINGS_REC *rec, GSList **list) +{ + *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) settings_compare); +} + +GSList *settings_get_sorted(void) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list); + return list; +} + +void sig_term(int n) +{ + /* if we get SIGTERM after this, just die instead of coming back here. */ + signal(SIGTERM, SIG_DFL); + + /* quit from all servers too.. */ + signal_emit("command quit", 1, ""); + + /* and die */ + raise(SIGTERM); +} + +static CONFIG_REC *parse_configfile(const char *fname) +{ + CONFIG_REC *config; + char *str; + + str = fname != NULL ? g_strdup(fname) : + g_strdup_printf("%s/.irssi/config", g_get_home_dir()); + config = config_open(str, -1); + g_free(str); + + if (config == NULL && *fname != '\0') + return NULL; /* specified config file not found */ + + if (config != NULL) + config_parse(config); + else { + /* user configuration file not found, use the default one + from sysconfdir */ + config = config_open(SYSCONFDIR"/irssi/config", -1); + if (config != NULL) + config_parse(config); + else { + /* no configuration file in sysconfdir .. + use the build-in configuration */ + config = config_open(NULL, -1); + config_parse_data(config, default_config, "internal"); + } + + config_change_file_name(config, fname, 0660); + } + + return config; +} + +static void sig_print_config_error(void) +{ + signal_emit("gui dialog", 2, "error", last_error_msg); + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_config_error); + + g_free_and_null(last_error_msg); +} + +static void init_configfile(void) +{ + struct stat statbuf; + char *str; + + str = g_strdup_printf("%s/.irssi", g_get_home_dir()); + if (stat(str, &statbuf) != 0) { + /* ~/.irssi not found, create it. */ + if (mkdir(str, 0700) != 0) + g_error("Couldn't create %s/.irssi directory", g_get_home_dir()); + } + g_free(str); + + mainconfig = parse_configfile(NULL); + + /* any errors? */ + if (config_last_error(mainconfig) != NULL) { + last_error_msg = g_strdup_printf("Ignored errors in configuration file:\n%s", + config_last_error(mainconfig)); + signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_config_error); + } + + signal(SIGTERM, sig_term); +} + +static void cmd_rehash(const char *data) +{ + CONFIG_REC *tempconfig; + char *str, *fname; + + fname = *data != '\0' ? g_strdup(data) : + g_strdup_printf("%s/.irssi/config", g_get_home_dir()); + tempconfig = parse_configfile(fname); + g_free(fname); + + if (tempconfig == NULL) { + signal_emit("gui dialog", 2, "error", g_strerror(errno)); + return; + } + + if (config_last_error(tempconfig) != NULL) { + /* error */ + str = g_strdup_printf("Errors in configuration file:\n%s", + config_last_error(tempconfig)); + signal_emit("gui dialog", 2, "error", str); + g_free(str); + config_close(tempconfig); + return; + } + + config_close(mainconfig); + mainconfig = tempconfig; + + signal_emit("setup changed", 0); + signal_emit("setup reread", 0); +} + +static void cmd_save(const char *data) +{ + config_write(mainconfig, *data == '\0' ? NULL : data, 0660); +} + +void settings_init(void) +{ + settings = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + + init_configfile(); + command_bind("rehash", NULL, (SIGNAL_FUNC) cmd_rehash); + command_bind("save", NULL, (SIGNAL_FUNC) cmd_save); +} + +static void settings_hash_free(const char *key, SETTINGS_REC *rec) +{ + settings_destroy(rec); +} + +void settings_deinit(void) +{ + command_unbind("rehash", (SIGNAL_FUNC) cmd_rehash); + command_unbind("save", (SIGNAL_FUNC) cmd_save); + + g_free_not_null(last_error_msg); + g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL); + g_hash_table_destroy(settings); + + if (mainconfig != NULL) config_close(mainconfig); +} diff --git a/src/core/settings.h b/src/core/settings.h new file mode 100644 index 00000000..9198bba9 --- /dev/null +++ b/src/core/settings.h @@ -0,0 +1,56 @@ +#ifndef __SETTINGS_H +#define __SETTINGS_H + +#ifndef __ICONFIG_H +typedef struct _config_rec CONFIG_REC; +#endif + +enum { + SETTING_TYPE_STRING, + SETTING_TYPE_INT, + SETTING_TYPE_BOOLEAN, +}; + +typedef struct { + int type; + char *key; + char *section; + void *def; +} SETTINGS_REC; + +/* macros for handling the default Irssi configuration */ +#define iconfig_get_str(a, b, c) config_get_str(mainconfig, a, b,c) +#define iconfig_get_int(a, b, c) config_get_int(mainconfig, a, b,c) +#define iconfig_get_bool(a, b, c) config_get_bool(mainconfig, a, b,c) +#define iconfig_list_find(a, b, c, d) config_list_find(mainconfig, a, b, c, d) + +#define iconfig_set_str(a, b, c) config_set_str(mainconfig, a, b,c) +#define iconfig_set_int(a, b, c) config_set_int(mainconfig, a, b,c) +#define iconfig_set_bool(a, b, c) config_set_bool(mainconfig, a, b,c) + +#define iconfig_node_traverse(a, b) config_node_traverse(mainconfig, a, b) + +extern CONFIG_REC *mainconfig; + +/* Functions for handling the "settings" node of Irssi configuration */ +const char *settings_get_str(const char *key); +const int settings_get_int(const char *key); +const int settings_get_bool(const char *key); + +/* Functions to add/remove settings */ +void settings_add_str(const char *section, const char *key, const char *def); +void settings_add_int(const char *section, const char *key, int def); +void settings_add_bool(const char *section, const char *key, int def); +void settings_remove(const char *key); + +/* Get the type (SETTING_TYPE_xxx) of `key' */ +int settings_get_type(const char *key); +/* Get all settings sorted by section. Free the result with g_slist_free() */ +GSList *settings_get_sorted(void); +/* Get the record of the setting */ +SETTINGS_REC *settings_get_record(const char *key); + +void settings_init(void); +void settings_deinit(void); + +#endif diff --git a/src/core/signals.c b/src/core/signals.c new file mode 100644 index 00000000..cbd92d45 --- /dev/null +++ b/src/core/signals.c @@ -0,0 +1,356 @@ +/* + signals.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "../common.h" +#include "signals.h" +#include "modules.h" + +#define SIGNAL_LISTS 3 + +typedef struct { + int emitting; /* signal is being emitted */ + int altered; /* some signal functions are marked as NULL */ + int stop_emit; /* this signal was stopped */ + + GPtrArray *modulelist[SIGNAL_LISTS]; /* list of what signals belong + to which module */ + GPtrArray *siglist[SIGNAL_LISTS]; /* signal lists */ +} SIGNAL_REC; + +#define signal_is_emitlist_empty(a) \ + (!(a)->siglist[0] && !(a)->siglist[1] && !(a)->siglist[2]) + +static GMemChunk *signals_chunk; +static GHashTable *signals; +static SIGNAL_REC *first_signal_rec, *last_signal_rec; /* "signal" and "last signal" */ +static SIGNAL_REC *current_emitted_signal; + +/* bind a signal */ +void signal_add_to(const char *module, int pos, const char *signal, SIGNAL_FUNC func) +{ + SIGNAL_REC *rec; + int signal_id; + + g_return_if_fail(signal != NULL); + g_return_if_fail(func != NULL); + g_return_if_fail(pos >= 0 && pos < SIGNAL_LISTS); + + signal_id = module_get_uniq_id_str("signals", signal); + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec == NULL) { + rec = g_mem_chunk_alloc0(signals_chunk); + g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), rec); + } + + if (strcmp(signal, "signal") == 0) + first_signal_rec = rec; + else if (strcmp(signal, "last signal") == 0) + last_signal_rec = rec; + + if (rec->siglist[pos] == NULL) { + rec->siglist[pos] = g_ptr_array_new(); + rec->modulelist[pos] = g_ptr_array_new(); + } + + g_ptr_array_add(rec->siglist[pos], (void *) func); + g_ptr_array_add(rec->modulelist[pos], (void *) module); +} + +/* Destroy the whole signal */ +static void signal_destroy(int signal_id) +{ + SIGNAL_REC *rec; + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec != NULL) { + /* remove whole signal from memory */ + g_hash_table_remove(signals, GINT_TO_POINTER(signal_id)); + g_free(rec); + } + + if (first_signal_rec == rec) + first_signal_rec = NULL; + else if (last_signal_rec == rec) + last_signal_rec = NULL; +} + +static int signal_list_find(GPtrArray *array, void *data) +{ + int n; + + for (n = 0; n < array->len; n++) { + if (g_ptr_array_index(array, n) == data) + return n; + } + + return -1; +} + +static void signal_remove_from_list(SIGNAL_REC *rec, int signal_id, int list, int index) +{ + if (rec->emitting) { + g_ptr_array_index(rec->siglist[list], index) = NULL; + rec->altered = TRUE; + } else { + g_ptr_array_remove_index(rec->siglist[list], index); + g_ptr_array_remove_index(rec->modulelist[list], index); + if (signal_is_emitlist_empty(rec)) + signal_destroy(signal_id); + } +} + +/* Remove signal from emit lists */ +static int signal_remove_from_lists(SIGNAL_REC *rec, int signal_id, SIGNAL_FUNC func) +{ + int n, index; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] == NULL) + continue; + + index = signal_list_find(rec->siglist[n], (void *) func); + if (index != -1) { + /* remove the function from emit list */ + signal_remove_from_list(rec, signal_id, n, index); + return 1; + } + } + + return 0; +} + +/* unbind signal */ +void signal_remove(const char *signal, SIGNAL_FUNC func) +{ + SIGNAL_REC *rec; + int signal_id, found; + + g_return_if_fail(signal != NULL); + g_return_if_fail(func != NULL); + + signal_id = module_get_uniq_id_str("signals", signal); + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + found = rec == NULL ? 0 : signal_remove_from_lists(rec, signal_id, func); + + if (!found) g_warning("signal_remove() : signal \"%s\" isn't grabbed for %p", signal, func); +} + +/* Remove all NULL functions from signal list */ +static void signal_list_clean(SIGNAL_REC *rec) +{ + int n, index; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] == NULL) + continue; + + for (index = rec->siglist[n]->len-1; index >= 0; index--) { + if (g_ptr_array_index(rec->siglist[n], index) == NULL) { + g_ptr_array_remove_index(rec->siglist[n], index); + g_ptr_array_remove_index(rec->modulelist[n], index); + } + } + } +} + +static int signal_emit_real(SIGNAL_REC *rec, gconstpointer *arglist) +{ + SIGNAL_REC *prev_emitted_signal; + SIGNAL_FUNC func; + int n, index, stopped; + + stopped = FALSE; + rec->emitting++; + for (n = 0; n < SIGNAL_LISTS; n++) { + /* run signals in emit lists */ + if (rec->siglist[n] == NULL) + continue; + + for (index = rec->siglist[n]->len-1; index >= 0; index--) { + func = (SIGNAL_FUNC) g_ptr_array_index(rec->siglist[n], index); + + if (func != NULL) { + prev_emitted_signal = current_emitted_signal; + current_emitted_signal = rec; + func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5], arglist[6]); + current_emitted_signal = prev_emitted_signal; + } + + if (rec->stop_emit) { + stopped = TRUE; + rec->stop_emit--; + n = SIGNAL_LISTS; + break; + } + } + } + rec->emitting--; + + if (!rec->emitting && rec->altered) { + signal_list_clean(rec); + rec->altered = FALSE; + } + + return stopped; +} + +static int signal_emitv_id(int signal_id, int params, va_list va) +{ + gconstpointer arglist[8]; + SIGNAL_REC *rec; + int n; + + g_return_val_if_fail(signal_id >= 0, FALSE); + g_return_val_if_fail(params >= 0 && params <= sizeof(arglist)/sizeof(arglist[0]), FALSE); + + arglist[0] = GINT_TO_POINTER(signal_id); + for (n = 1; n < 8; n++) + arglist[n] = n >= params ? NULL : va_arg(va, gconstpointer); + + /* send "signal" */ + if (first_signal_rec != NULL) { + if (signal_emit_real(first_signal_rec, arglist)) + return TRUE; + } + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec != NULL && signal_emit_real(rec, arglist+1)) + return TRUE; + + /* send "last signal" */ + if (last_signal_rec != NULL) { + if (signal_emit_real(last_signal_rec, arglist)) + return TRUE; + } + + return rec != NULL; +} + +int signal_emit(const char *signal, int params, ...) +{ + va_list va; + int signal_id, ret; + + /* get arguments */ + signal_id = module_get_uniq_id_str("signals", signal); + + va_start(va, params); + ret = signal_emitv_id(signal_id, params, va); + va_end(va); + + return ret; +} + +int signal_emit_id(int signal_id, int params, ...) +{ + va_list va; + int ret; + + /* get arguments */ + va_start(va, params); + ret = signal_emitv_id(signal_id, params, va); + va_end(va); + + return ret; +} + +/* stop the current ongoing signal emission */ +void signal_stop(void) +{ + SIGNAL_REC *rec; + + rec = current_emitted_signal; + if (rec == NULL || rec->emitting <= rec->stop_emit) + g_warning("signal_stop() : no signals are being emitted currently"); + else + rec->stop_emit++; +} + +/* stop ongoing signal emission by signal name */ +void signal_stop_by_name(const char *signal) +{ + SIGNAL_REC *rec; + + rec = g_hash_table_lookup(signals, signal); + if (rec == NULL) + g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal); + else if (rec->emitting <= rec->stop_emit) + g_warning("signal_stop_by_name() : signal \"%s\" not being emitted", signal); + else + rec->stop_emit++; +} + +static void signal_remove_module(void *signal, SIGNAL_REC *rec, const char *module) +{ + int signal_id, list, index; + + signal_id = GPOINTER_TO_INT(signal); + + for (list = 0; list < SIGNAL_LISTS; list++) { + for (index = 0; index < rec->modulelist[list]->len; index++) { + if (g_strcasecmp(g_ptr_array_index(rec->modulelist[list], index), module) == 0) + signal_remove_from_list(rec, signal_id, list, index); + } + } +} + +/* remove all signals that belong to `module' */ +void signals_remove_module(const char *module) +{ + g_return_if_fail(module != NULL); + + g_hash_table_foreach(signals, (GHFunc) signal_remove_module, (void *) module); +} + +void signals_init(void) +{ + signals_chunk = g_mem_chunk_new("signals", sizeof(SIGNAL_REC), + sizeof(SIGNAL_REC)*200, G_ALLOC_AND_FREE); + signals = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + + first_signal_rec = NULL; + last_signal_rec = NULL; +} + +static void signal_free(void *key, SIGNAL_REC *rec) +{ + int n; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] != NULL) { + g_ptr_array_free(rec->siglist[n], TRUE); + g_ptr_array_free(rec->modulelist[n], TRUE); + } + } + + g_mem_chunk_free(signals_chunk, rec); + current_emitted_signal = NULL; +} + +void signals_deinit(void) +{ + g_hash_table_foreach(signals, (GHFunc) signal_free, NULL); + g_hash_table_destroy(signals); + + module_uniq_destroy("signals"); + g_mem_chunk_destroy(signals_chunk); +} diff --git a/src/core/signals.h b/src/core/signals.h new file mode 100644 index 00000000..613aa245 --- /dev/null +++ b/src/core/signals.h @@ -0,0 +1,30 @@ +#ifndef __SIGNAL_H +#define __SIGNAL_H + +typedef void (*SIGNAL_FUNC) (gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer); + +void signals_init(void); +void signals_deinit(void); + +/* bind a signal */ +void signal_add_to(const char *module, int pos, const char *signal, SIGNAL_FUNC func); +#define signal_add(a, b) signal_add_to(MODULE_NAME, 1, a, b) +#define signal_add_first(a, b) signal_add_to(MODULE_NAME, 0, a, b) +#define signal_add_last(a, b) signal_add_to(MODULE_NAME, 2, a, b) + +/* unbind signal */ +void signal_remove(const char *signal, SIGNAL_FUNC func); + +/* emit signal */ +int signal_emit(const char *signal, int params, ...); +int signal_emit_id(int signal_id, int params, ...); + +/* stop the current ongoing signal emission */ +void signal_stop(void); +/* stop ongoing signal emission by signal name */ +void signal_stop_by_name(const char *signal); + +/* remove all signals that belong to `module' */ +void signals_remove_module(const char *module); + +#endif diff --git a/src/core/special-vars.c b/src/core/special-vars.c new file mode 100644 index 00000000..26788a65 --- /dev/null +++ b/src/core/special-vars.c @@ -0,0 +1,635 @@ +/* + special-vars.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "special-vars.h" +#include "settings.h" +#include "misc.h" +#include "irssi-version.h" + +#include <sys/utsname.h> + +#define ALIGN_RIGHT 0x01 +#define ALIGN_CUT 0x02 + +static EXPANDO_FUNC char_expandos[256]; +static GHashTable *expandos; +static time_t client_start_time; +static SPECIAL_HISTORY_FUNC history_func; + +static char *get_argument(char **cmd, char **arglist) +{ + GString *str; + char *ret; + int max, arg, argcount; + + arg = 0; + max = -1; + + argcount = strarray_length(arglist); + + if (**cmd == '*') { + /* get all arguments */ + } else if (**cmd == '~') { + /* get last argument */ + arg = max = argcount-1; + } else { + if (isdigit(**cmd)) { + /* first argument */ + arg = max = (**cmd)-'0'; + (*cmd)++; + } + + if (**cmd == '-') { + /* get more than one argument */ + (*cmd)++; + if (!isdigit(**cmd)) + max = -1; /* get all the rest */ + else { + max = (**cmd)-'0'; + (*cmd)++; + } + } + (*cmd)--; + } + + str = g_string_new(NULL); + while (arg < argcount && (arg <= max || max == -1)) { + g_string_append(str, arglist[arg]); + g_string_append_c(str, ' '); + arg++; + } + if (str->len > 0) g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static char *get_internal_setting(const char *key, int type, int *free_ret) +{ + switch (type) { + case SETTING_TYPE_BOOLEAN: + return settings_get_bool(key) ? "yes" : "no"; + case SETTING_TYPE_INT: + *free_ret = TRUE; + return g_strdup_printf("%d", settings_get_int(key)); + case SETTING_TYPE_STRING: + return (char *) settings_get_str(key); + } + + return NULL; +} + +static char *get_long_variable_value(const char *key, void *server, void *item, int *free_ret) +{ + EXPANDO_FUNC func; + char *ret; + int type; + + *free_ret = FALSE; + + /* expando? */ + func = g_hash_table_lookup(expandos, key); + if (func != NULL) + return func(server, item, free_ret); + + /* internal setting? */ + type = settings_get_type(key); + if (type != -1) + return get_internal_setting(key, type, free_ret); + + /* environment variable? */ + ret = g_getenv(key); + if (ret != NULL) { + *free_ret = TRUE; + return ret; + } + + return NULL; +} + +static char *get_long_variable(char **cmd, void *server, void *item, int *free_ret) +{ + char *start, *var, *ret; + + /* get variable name */ + start = *cmd; + while (isalnum((*cmd)[1])) (*cmd)++; + + var = g_strndup(start, (int) (*cmd-start)+1); + ret = get_long_variable_value(var, server, item, free_ret); + g_free(var); + return ret; +} + +/* return the value of the variable found from `cmd' */ +static char *get_variable(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used) +{ + if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') { + /* argument */ + *free_ret = TRUE; + if (arg_used != NULL) *arg_used = TRUE; + return get_argument(cmd, arglist); + } + + if (isalpha(**cmd) && isalnum((*cmd)[1])) { + /* long variable name.. */ + return get_long_variable(cmd, server, item, free_ret); + } + + /* single character variable. */ + *free_ret = FALSE; + return char_expandos[(int) **cmd] == NULL ? NULL : + char_expandos[(int) **cmd](server, item, free_ret); +} + +static char *get_history(char **cmd, void *item, int *free_ret) +{ + char *start, *text, *ret; + + /* get variable name */ + start = ++(*cmd); + while (**cmd != '\0' && **cmd != '!') (*cmd)++; + + if (history_func == NULL) + ret = NULL; + else { + text = g_strndup(start, (int) (*cmd-start)+1); + ret = history_func(text, item, free_ret); + g_free(text); + } + + if (**cmd == '\0') (*cmd)--; + return ret; +} + +static char *get_special_value(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used) +{ + char command, *value, *p; + int len; + + if (**cmd == '!') { + /* find text from command history */ + return get_history(cmd, item, free_ret); + } + + command = 0; + if (**cmd == '#' || **cmd == '@') { + command = **cmd; + if ((*cmd)[1] != '\0') + (*cmd)++; + else { + /* default to $* */ + char *temp_cmd = "*"; + + *free_ret = TRUE; + return get_argument(&temp_cmd, arglist); + } + } + + value = get_variable(cmd, server, item, arglist, free_ret, arg_used); + + if (command == '#') { + /* number of words */ + if (value == NULL || *value == '\0') { + if (value != NULL && *free_ret) { + g_free(value); + *free_ret = FALSE; + } + return "0"; + } + + len = 1; + for (p = value; *p != '\0'; p++) { + if (*p == ' ' && (p[1] != ' ' && p[1] != '\0')) + len++; + } + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + if (command == '@') { + /* number of characters */ + if (value == NULL) return "0"; + + len = strlen(value); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + return value; +} + +/* get alignment arguments (inside the []) */ +static int get_alignment_args(char **data, int *align, int *flags, char *pad) +{ + char *str; + + *align = 0; + *flags = ALIGN_CUT; + *pad = ' '; + + /* '!' = don't cut, '-' = right padding */ + str = *data; + while (*str != '\0' && *str != ']' && !isdigit(*str)) { + if (*str == '!') + *flags &= ~ALIGN_CUT; + else if (*str == '-') + *flags |= ALIGN_RIGHT; + str++; + } + if (!isdigit(*str)) + return FALSE; /* expecting number */ + + /* get the alignment size */ + while (isdigit(*str)) { + *align = (*align) * 10 + (*str-'0'); + str++; + } + + /* get the pad character */ + while (*str != '\0' && *str != ']') { + *pad = *str; + str++; + } + + if (*str++ != ']') return FALSE; + + *data = str; + return TRUE; +} + +/* return the aligned text */ +static char *get_alignment(const char *text, int align, int flags, char pad) +{ + GString *str; + char *ret; + + g_return_val_if_fail(text != NULL, NULL); + + str = g_string_new(text); + + /* cut */ + if ((flags & ALIGN_CUT) && align > 0 && str->len > align) + g_string_truncate(str, align); + + /* add pad characters */ + while (str->len < align) { + if (flags & ALIGN_RIGHT) + g_string_prepend_c(str, pad); + else + g_string_append_c(str, pad); + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* Parse and expand text after '$' character. return value has to be + g_free()'d if `free_ret' is TRUE. */ +char *parse_special(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used) +{ + static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */ + char command, *value; + + char align_pad; + int align, align_flags; + + char *nest_value; + int brackets, nest_free; + + *free_ret = FALSE; + + command = **cmd; (*cmd)++; + switch (command) { + case '[': + /* alignment */ + if (!get_alignment_args(cmd, &align, &align_flags, &align_pad) || + **cmd == '\0') { + (*cmd)--; + return NULL; + } + break; + default: + command = 0; + (*cmd)--; + } + + nest_free = FALSE; nest_value = NULL; + if (**cmd == '(') { + /* subvariable */ + int toplevel = nested_orig_cmd == NULL; + + if (toplevel) nested_orig_cmd = cmd; + (*cmd)++; + if (**cmd != '$') { + /* ... */ + nest_value = *cmd; + } else { + (*cmd)++; + nest_value = parse_special(cmd, server, item, arglist, &nest_free, arg_used); + } + + while ((*nested_orig_cmd)[1] != '\0') { + (*nested_orig_cmd)++; + if (**nested_orig_cmd == ')') break; + } + cmd = &nest_value; + + if (toplevel) nested_orig_cmd = NULL; + } + + if (**cmd != '{') + brackets = FALSE; + else { + /* special value is inside {...} (foo${test}bar -> fooXXXbar) */ + (*cmd)++; + brackets = TRUE; + } + + value = get_special_value(cmd, server, item, arglist, free_ret, arg_used); + if (**cmd == '\0') + g_error("parse_special() : buffer overflow!"); + + if (brackets) { + while (**cmd != '}' && (*cmd)[1] != '\0') + (*cmd)++; + } + + if (nest_free) g_free(nest_value); + + if (command == '[') { + /* alignment */ + char *p; + + if (value == NULL) return ""; + + p = get_alignment(value, align, align_flags, align_pad); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return p; + } + + return value; +} + +/* parse the whole string. $ and \ chars are replaced */ +char *parse_special_string(const char *cmd, void *server, void *item, const char *data, int *arg_used) +{ + char code, **arglist, *ret; + GString *str; + int need_free; + + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(data != NULL, NULL); + + /* create the argument list */ + arglist = g_strsplit(data, " ", -1); + + if (arg_used != NULL) *arg_used = FALSE; + code = 0; + str = g_string_new(NULL); + while (*cmd != '\0') { + if (code == '\\'){ + g_string_append_c(str, *cmd); + code = 0; + } else if (code == '$') { + char *ret; + + ret = parse_special((char **) &cmd, server, item, arglist, &need_free, arg_used); + if (ret != NULL) { + g_string_append(str, ret); + if (need_free) g_free(ret); + } + code = 0; + } else { + if (*cmd == '\\' || *cmd == '$') + code = *cmd; + else + g_string_append_c(str, *cmd); + } + + cmd++; + } + g_strfreev(arglist); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* execute the commands in string - commands can be split with ';' */ +void eval_special_string(const char *cmd, const char *data, void *server, void *item) +{ + const char *cmdchars; + char *orig, *str, *start, *ret; + int arg_used; + + cmdchars = settings_get_str("cmdchars"); + orig = start = str = g_strdup(cmd); + do { + if (*str == ';' && (start == str || (str[-1] != '\\' && str[-1] != '$'))) + *str++ = '\0'; + else if (*str != '\0') { + str++; + continue; + } + + ret = parse_special_string(start, server, item, data, &arg_used); + if (strchr(cmdchars, *ret) == NULL) { + /* no command char - let's put it there.. */ + char *old = ret; + + ret = g_strdup_printf("%c%s", *cmdchars, old); + g_free(old); + } + if (!arg_used && *data != '\0') { + /* append the string with all the arguments */ + char *old = ret; + + ret = g_strconcat(old, " ", data, NULL); + g_free(old); + } + signal_emit("send command", 3, ret, server, item); + g_free(ret); + + start = str; + } while (*start != '\0'); + + g_free(orig); +} + +/* Create expando - overrides any existing ones. */ +void expando_create(const char *key, EXPANDO_FUNC func) +{ + gpointer origkey, origvalue; + + g_return_if_fail(key != NULL || *key == '\0'); + g_return_if_fail(func != NULL); + + if (key[1] == '\0') { + /* single character expando */ + char_expandos[(int) *key] = func; + return; + } + + if (g_hash_table_lookup_extended(expandos, key, &origkey, &origvalue)) { + g_free(origkey); + g_hash_table_remove(expandos, key); + } + g_hash_table_insert(expandos, g_strdup(key), func); +} + +/* Destroy expando */ +void expando_destroy(const char *key, EXPANDO_FUNC func) +{ + gpointer origkey, origvalue; + + g_return_if_fail(key != NULL || *key == '\0'); + g_return_if_fail(func != NULL); + + if (key[1] == '\0') { + /* single character expando */ + if (char_expandos[(int) *key] == func) + char_expandos[(int) *key] = NULL; + return; + } + + if (g_hash_table_lookup_extended(expandos, key, &origkey, &origvalue)) { + g_free(origkey); + g_hash_table_remove(expandos, key); + } +} + +void special_history_func_set(SPECIAL_HISTORY_FUNC func) +{ + history_func = func; +} + +/* time client was started, $time() format */ +static char *expando_clientstarted(void *server, void *item, int *free_ret) +{ + *free_ret = TRUE; + return g_strdup_printf("%ld", (long) client_start_time); +} + +/* client version text string */ +static char *expando_version(void *server, void *item, int *free_ret) +{ + return IRSSI_VERSION; +} + +/* current value of CMDCHARS */ +static char *expando_cmdchars(void *server, void *item, int *free_ret) +{ + return (char *) settings_get_str("cmdchars"); +} + +/* client release date (numeric version string) */ +static char *expando_releasedate(void *server, void *item, int *free_ret) +{ + return IRSSI_VERSION_DATE; +} + +/* current working directory */ +static char *expando_workdir(void *server, void *item, int *free_ret) +{ + *free_ret = TRUE; + return g_get_current_dir(); +} + +/* time of day (hh:mm) */ +static char *expando_time(void *server, void *item, int *free_ret) +{ + time_t now = time(NULL); + struct tm *tm; + + tm = localtime(&now); + *free_ret = TRUE; + return g_strdup_printf("%02d:%02d", tm->tm_hour, tm->tm_min); +} + +/* a literal '$' */ +static char *expando_dollar(void *server, void *item, int *free_ret) +{ + return "$"; +} + +/* system name */ +static char *expando_sysname(void *server, void *item, int *free_ret) +{ + struct utsname un; + + if (uname(&un) != 0) + return NULL; + + *free_ret = TRUE; + return g_strdup(un.sysname); + +} + +/* system release */ +static char *expando_sysrelease(void *server, void *item, int *free_ret) +{ + struct utsname un; + + if (uname(&un) != 0) + return NULL; + + *free_ret = TRUE; + return g_strdup(un.release); + +} + +void special_vars_init(void) +{ + client_start_time = time(NULL); + + memset(char_expandos, 0, sizeof(char_expandos)); + expandos = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + history_func = NULL; + + char_expandos['F'] = expando_clientstarted; + char_expandos['J'] = expando_version; + char_expandos['K'] = expando_cmdchars; + char_expandos['V'] = expando_releasedate; + char_expandos['W'] = expando_workdir; + char_expandos['Z'] = expando_time; + char_expandos['$'] = expando_dollar; + + expando_create("sysname", expando_sysname); + expando_create("sysrelease", expando_sysrelease); +} + +void special_vars_deinit(void) +{ + expando_destroy("sysname", expando_sysname); + expando_destroy("sysrelease", expando_sysrelease); + + g_hash_table_destroy(expandos); +} diff --git a/src/core/special-vars.h b/src/core/special-vars.h new file mode 100644 index 00000000..c40a2fcb --- /dev/null +++ b/src/core/special-vars.h @@ -0,0 +1,27 @@ +#ifndef __SPECIAL_VARS_H +#define __SPECIAL_VARS_H + +typedef char* (*EXPANDO_FUNC) (void *server, void *item, int *free_ret); +typedef char* (*SPECIAL_HISTORY_FUNC) (const char *text, void *item, int *free_ret); + +/* Parse and expand text after '$' character. return value has to be + g_free()'d if `free_ret' is TRUE. */ +char *parse_special(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used); + +/* parse the whole string. $ and \ chars are replaced */ +char *parse_special_string(const char *cmd, void *server, void *item, const char *data, int *arg_used); + +/* execute the commands in string - commands can be split with ';' */ +void eval_special_string(const char *cmd, const char *data, void *server, void *item); + +/* Create expando - overrides any existing ones. */ +void expando_create(const char *key, EXPANDO_FUNC func); +/* Destroy expando */ +void expando_destroy(const char *key, EXPANDO_FUNC func); + +void special_history_func_set(SPECIAL_HISTORY_FUNC func); + +void special_vars_init(void); +void special_vars_deinit(void); + +#endif diff --git a/src/fe-common/Makefile.am b/src/fe-common/Makefile.am new file mode 100644 index 00000000..d5f92eec --- /dev/null +++ b/src/fe-common/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = core irc diff --git a/src/fe-common/core/Makefile.am b/src/fe-common/core/Makefile.am new file mode 100644 index 00000000..025a9dda --- /dev/null +++ b/src/fe-common/core/Makefile.am @@ -0,0 +1,38 @@ +noinst_LTLIBRARIES = libfe_common_core.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ \ + -DHELPDIR=\""$(datadir)/irssi/help"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +libfe_common_core_la_SOURCES = \ + autorun.c \ + command-history.c \ + fe-common-core.c \ + fe-core-commands.c \ + fe-log.c \ + fe-server.c \ + fe-settings.c \ + hilight-text.c \ + keyboard.c \ + module-formats.c \ + nick-hilight.c \ + printtext.c \ + themes.c \ + translation.c \ + window-items.c \ + windows.c + +noinst_HEADERS = \ + command-history.h \ + fe-common-core.h \ + hilight-text.h \ + keyboard.h \ + module-formats.h \ + module.h \ + printtext.h \ + themes.h \ + translation.h \ + window-items.h \ + windows.h diff --git a/src/fe-common/core/autorun.c b/src/fe-common/core/autorun.c new file mode 100644 index 00000000..f1e5d88d --- /dev/null +++ b/src/fe-common/core/autorun.c @@ -0,0 +1,62 @@ +/* + autorun.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "line-split.h" +#include "special-vars.h" + +#include "windows.h" + +static void sig_autorun(void) +{ + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer = NULL; + int f, ret, recvlen; + + /* open ~/.irssi/startup and run all commands in it */ + path = g_strdup_printf("%s/.irssi/startup", g_get_home_dir()); + f = open(path, O_RDONLY); + g_free(path); + if (f == -1) { + /* file not found */ + return; + } + + do { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + eval_special_string(str, "", active_win->active_server, active_win->active); + } while (ret > 0); + line_split_free(buffer); + + close(f); +} + +void autorun_init(void) +{ + signal_add_last("irssi init finished", (SIGNAL_FUNC) sig_autorun); +} + +void autorun_deinit(void) +{ + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_autorun); +} diff --git a/src/fe-common/core/command-history.c b/src/fe-common/core/command-history.c new file mode 100644 index 00000000..01ae46d7 --- /dev/null +++ b/src/fe-common/core/command-history.c @@ -0,0 +1,182 @@ +/* + command-history.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "special-vars.h" +#include "settings.h" + +#include "windows.h" +#include "window-items.h" + +/* command history */ +static GList *cmdhist, *histpos; +static int histlines; +static int window_history; + +void command_history_add(WINDOW_REC *window, const char *text, int prepend) +{ + GList **pcmdhist, *link; + int *phistlines; + + g_return_if_fail(text != NULL); + + if (window_history) { + /* window specific command history */ + pcmdhist = &window->cmdhist; + phistlines = &window->histlines; + } else { + /* global command history */ + pcmdhist = &cmdhist; + phistlines = &histlines; + } + + if (settings_get_int("max_command_history") < 1 || *phistlines < settings_get_int("max_command_history")) + (*phistlines)++; + else { + link = *pcmdhist; + g_free(link->data); + *pcmdhist = g_list_remove_link(*pcmdhist, link); + g_list_free_1(link); + } + + if (prepend) + *pcmdhist = g_list_prepend(*pcmdhist, g_strdup(text)); + else + *pcmdhist = g_list_append(*pcmdhist, g_strdup(text)); +} + +const char *command_history_prev(WINDOW_REC *window, const char *text) +{ + GList *pos, **phistpos; + + phistpos = window_history ? &window->histpos : &histpos; + + pos = *phistpos; + if (*phistpos == NULL) + *phistpos = g_list_last(window_history ? window->cmdhist : cmdhist); + else + *phistpos = (*phistpos)->prev; + + if (*text != '\0' && + (pos == NULL || strcmp(pos->data, text) != 0)) { + /* save the old entry to history */ + command_history_add(window, text, FALSE); + } + + return *phistpos == NULL ? "" : (*phistpos)->data; +} + +const char *command_history_next(WINDOW_REC *window, const char *text) +{ + GList *pos, **phistpos; + + phistpos = window_history ? &window->histpos : &histpos; + + pos = *phistpos; + if (*phistpos == NULL) + *phistpos = window_history ? window->cmdhist : cmdhist; + else + *phistpos = (*phistpos)->next; + + if (*text != '\0' && + (pos == NULL || strcmp(pos->data, text) != 0)) { + /* save the old entry to history */ + command_history_add(window, text, TRUE); + } + return *phistpos == NULL ? "" : (*phistpos)->data; +} + +void command_history_clear_pos(WINDOW_REC *window) +{ + window->histpos = NULL; + histpos = NULL; +} + +static void sig_window_created(WINDOW_REC *window) +{ + window->histlines = 0; + window->cmdhist = NULL; + window->histpos = NULL; +} + +static void sig_window_destroyed(WINDOW_REC *window) +{ + g_list_foreach(window->cmdhist, (GFunc) g_free, NULL); + g_list_free(window->cmdhist); +} + +static char *special_history_func(const char *text, void *item, int *free_ret) +{ + WINDOW_REC *window; + GList *tmp; + char *findtext, *ret; + + window = item == NULL ? active_win : window_item_window(item); + + findtext = g_strdup_printf("*%s*", text); + ret = NULL; + + tmp = window_history ? window->cmdhist : cmdhist; + for (; tmp != NULL; tmp = tmp->next) { + const char *line = tmp->data; + + if (match_wildcards(findtext, line)) { + *free_ret = TRUE; + ret = g_strdup(line); + } + } + g_free(findtext); + + return ret; +} + +static void read_settings(void) +{ + window_history = settings_get_bool("toggle_window_history"); +} + +void command_history_init(void) +{ + settings_add_int("history", "max_textwidget_lines", 1000); + settings_add_int("history", "block_remove_lines", 20); + settings_add_int("history", "max_command_history", 100); + settings_add_bool("history", "toggle_window_history", FALSE); + + special_history_func_set(special_history_func); + + histlines = 0; + cmdhist = NULL; histpos = NULL; + read_settings(); + signal_add("window created", (SIGNAL_FUNC) sig_window_created); + signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void command_history_deinit(void) +{ + signal_remove("window created", (SIGNAL_FUNC) sig_window_created); + signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + g_list_foreach(cmdhist, (GFunc) g_free, NULL); + g_list_free(cmdhist); +} diff --git a/src/fe-common/core/command-history.h b/src/fe-common/core/command-history.h new file mode 100644 index 00000000..8ed26757 --- /dev/null +++ b/src/fe-common/core/command-history.h @@ -0,0 +1,16 @@ +#ifndef __COMMAND_HISTORY_H +#define __COMMAND_HISTORY_H + +#include "windows.h" + +void command_history_init(void); +void command_history_deinit(void); + +void command_history_add(WINDOW_REC *window, const char *text, int prepend); + +const char *command_history_prev(WINDOW_REC *window, const char *text); +const char *command_history_next(WINDOW_REC *window, const char *text); + +void command_history_clear_pos(WINDOW_REC *window); + +#endif diff --git a/src/fe-common/core/fe-common-core.c b/src/fe-common/core/fe-common-core.c new file mode 100644 index 00000000..cc76aea6 --- /dev/null +++ b/src/fe-common/core/fe-common-core.c @@ -0,0 +1,132 @@ +/* + fe-common-core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "module.h" +#include "levels.h" +#include "settings.h" + +#include "hilight-text.h" +#include "command-history.h" +#include "keyboard.h" +#include "printtext.h" +#include "themes.h" +#include "translation.h" +#include "windows.h" +#include "window-items.h" + +#include <sys/signal.h> + +void autorun_init(void); +void autorun_deinit(void); + +void fe_core_log_init(void); +void fe_core_log_deinit(void); + +void fe_server_init(void); +void fe_server_deinit(void); + +void fe_settings_init(void); +void fe_settings_deinit(void); + +void nick_hilight_init(void); +void nick_hilight_deinit(void); + +void fe_core_commands_init(void); +void fe_core_commands_deinit(void); + +void fe_common_core_init(void) +{ + settings_add_bool("lookandfeel", "toggle_show_menubar", TRUE); + settings_add_bool("lookandfeel", "toggle_show_toolbar", FALSE); + settings_add_bool("lookandfeel", "toggle_show_statusbar", TRUE); + settings_add_bool("lookandfeel", "toggle_show_nicklist", TRUE); + settings_add_bool("lookandfeel", "toggle_show_timestamps", FALSE); + settings_add_bool("lookandfeel", "toggle_show_msgs_timestamps", FALSE); + settings_add_bool("lookandfeel", "toggle_hide_text_style", FALSE); + settings_add_bool("lookandfeel", "toggle_bell_beeps", FALSE); + settings_add_bool("lookandfeel", "toggle_actlist_moves", FALSE); + settings_add_bool("lookandfeel", "toggle_show_nickmode", TRUE); + settings_add_bool("lookandfeel", "toggle_show_topicbar", TRUE); + + settings_add_bool("lookandfeel", "toggle_use_status_window", FALSE); + settings_add_bool("lookandfeel", "toggle_use_msgs_window", TRUE); + settings_add_bool("lookandfeel", "toggle_autoraise_msgs_window", FALSE); + settings_add_bool("lookandfeel", "toggle_autocreate_query", TRUE); + settings_add_bool("lookandfeel", "toggle_notifylist_popups", FALSE); + settings_add_bool("lookandfeel", "toggle_use_tabbed_windows", TRUE); + settings_add_int("lookandfeel", "tab_orientation", 3); + settings_add_str("lookandfeel", "current_theme", "default"); + + autorun_init(); + nick_hilight_init(); + hilight_text_init(); + command_history_init(); + keyboard_init(); + printtext_init(); + fe_log_init(); + fe_server_init(); + fe_settings_init(); + themes_init(); + translation_init(); + windows_init(); + window_items_init(); + fe_core_commands_init(); +} + +void fe_common_core_deinit(void) +{ + autorun_deinit(); + nick_hilight_deinit(); + hilight_text_deinit(); + command_history_deinit(); + keyboard_deinit(); + printtext_deinit(); + fe_log_deinit(); + fe_server_deinit(); + fe_settings_deinit(); + themes_deinit(); + translation_deinit(); + windows_deinit(); + window_items_deinit(); + fe_core_commands_deinit(); +} + +void fe_common_core_finish_init(void) +{ + WINDOW_REC *window; + + signal(SIGPIPE, SIG_IGN); + + if (settings_get_bool("toggle_use_status_window")) { + window = window_create(NULL, TRUE); + window_set_name(window, "(status)"); + window_set_level(window, MSGLEVEL_ALL ^ (settings_get_bool("toggle_use_msgs_window") ? (MSGLEVEL_MSGS|MSGLEVEL_ACTIONS) : 0)); + } + + if (settings_get_bool("toggle_use_msgs_window")) { + window = window_create(NULL, TRUE); + window_set_name(window, "(msgs)"); + window_set_level(window, MSGLEVEL_MSGS|MSGLEVEL_ACTIONS); + } + + if (windows == NULL) { + /* we have to have at least one window.. */ + window = window_create(NULL, TRUE); + } +} diff --git a/src/fe-common/core/fe-common-core.h b/src/fe-common/core/fe-common-core.h new file mode 100644 index 00000000..1c12047b --- /dev/null +++ b/src/fe-common/core/fe-common-core.h @@ -0,0 +1,8 @@ +#ifndef __FE_COMMON_CORE_H +#define __FE_COMMON_CORE_H + +void fe_common_core_init(void); +void fe_common_core_deinit(void); +void fe_common_core_finish_init(void); + +#endif diff --git a/src/fe-common/core/fe-core-commands.c b/src/fe-common/core/fe-core-commands.c new file mode 100644 index 00000000..b036385d --- /dev/null +++ b/src/fe-common/core/fe-core-commands.c @@ -0,0 +1,266 @@ +/* + fe-core-commands.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "line-split.h" +#include "irssi-version.h" + +#include "windows.h" + +static gchar *ret_texts[] = +{ + "Invalid parameter", + "Not enough parameters given", + "Not connected to IRC server yet", + "Not joined to any channels yet", + "Error: getsockname() failed", + "Error: listen() failed", + "Multiple matches found, be more specific", + "Nick not found", + "Not joined to such channel", + "Server not found", + "Channel not fully synchronized yet, try again after a while", + "Doing this is not a good idea. Add -YES if you really mean it", +}; + +static gint commands_compare(COMMAND_REC *rec, COMMAND_REC *rec2) +{ + if (rec->category == NULL && rec2->category != NULL) + return -1; + if (rec2->category == NULL && rec->category != NULL) + return 1; + + return strcmp(rec->cmd, rec2->cmd); +} + +static void help_category(GSList *cmdlist, gint items, gint max) +{ + COMMAND_REC *rec, *last; + GString *str; + GSList *tmp; + gint lines, cols, line, col, skip; + gchar *cmdbuf; + + str = g_string_new(NULL); + + cols = max > 65 ? 1 : (65 / max); + lines = items <= cols ? 1 : items / cols+1; + + last = NULL; cmdbuf = g_malloc(max+1); cmdbuf[max] = '\0'; + for (line = 0, col = 0, skip = 1, tmp = cmdlist; line < lines; last = rec, tmp = tmp->next) + { + rec = tmp->data; + + if (--skip == 0) + { + skip = lines; + memset(cmdbuf, ' ', max); + memcpy(cmdbuf, rec->cmd, strlen(rec->cmd)); + g_string_sprintfa(str, "%s ", cmdbuf); + cols++; + } + + if (col == cols || tmp->next == NULL) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, str->str); + g_string_truncate(str, 0); + col = 0; line++; + tmp = g_slist_nth(cmdlist, line-1); skip = 1; + } + } + if (str->len != 0) + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, str->str); + g_string_free(str, TRUE); + g_free(cmdbuf); +} + +static int show_help(COMMAND_REC *cmd) +{ + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer = NULL; + int f, ret, recvlen; + + /* helpdir/command or helpdir/category/command */ + if (cmd->category == NULL) + path = g_strdup_printf("%s/%s", HELPDIR, cmd->cmd); + else + path = g_strdup_printf("%s/%s/%s", HELPDIR, cmd->category, cmd->cmd); + f = open(path, O_RDONLY); + g_free(path); + + if (f == -1) + return FALSE; + + /* just print to screen whatever is in the file */ + do + { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + printtext(NULL, NULL, MSGLEVEL_NEVER, str); + } + while (ret > 0); + line_split_free(buffer); + + close(f); + return TRUE; +} + +static void cmd_help(gchar *data) +{ + COMMAND_REC *rec, *last, *helpitem; + GSList *tmp, *cmdlist; + gint len, max, items, findlen; + gboolean header; + + g_return_if_fail(data != NULL); + + /* sort the commands list */ + commands = g_slist_sort(commands, (GCompareFunc) commands_compare); + + /* print command, sort by category */ + cmdlist = NULL; last = NULL; header = FALSE; helpitem = NULL; + max = items = 0; findlen = strlen(data); + for (tmp = commands; tmp != NULL; last = rec, tmp = tmp->next) + { + rec = tmp->data; + + if (last != NULL && rec->category != NULL && + (last->category == NULL || strcmp(rec->category, last->category) != 0)) + { + /* category changed */ + if (items > 0) + { + if (!header) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "Irssi commands:"); + header = TRUE; + } + if (last->category != NULL) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, ""); + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s:", last->category); + } + help_category(cmdlist, items, max); + } + + g_slist_free(cmdlist); cmdlist = NULL; + items = 0; max = 0; + } + + if (last != NULL && g_strcasecmp(rec->cmd, last->cmd) == 0) + continue; /* don't display same command twice */ + + if (strlen(rec->cmd) >= findlen && g_strncasecmp(rec->cmd, data, findlen) == 0) + { + if (rec->cmd[findlen] == '\0') + { + helpitem = rec; + break; + } + else if (strchr(rec->cmd+findlen+1, ' ') == NULL) + { + /* not a subcommand (and matches the query) */ + len = strlen(rec->cmd); + if (max < len) max = len; + items++; + cmdlist = g_slist_append(cmdlist, rec); + } + } + } + + if ((helpitem == NULL && items == 0) || (helpitem != NULL && !show_help(helpitem))) + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "No help for %s", data); + + if (items != 0) + { + /* display the last category */ + if (!header) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "Irssi commands:"); + header = TRUE; + } + + if (last->category != NULL) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, ""); + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s:", last->category); + } + help_category(cmdlist, items, max); + g_slist_free(cmdlist); + } +} + +static void cmd_echo(const char *data, void *server, WI_ITEM_REC *item) +{ + g_return_if_fail(data != NULL); + + printtext(server, item == NULL ? NULL : item->name, MSGLEVEL_CRAP, "%s", data); +} + +static void cmd_version(char *data) +{ + g_return_if_fail(data != NULL); + + if (*data == '\0') + printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, "Client: "PACKAGE" " IRSSI_VERSION); +} + +static void cmd_unknown(const char *data, void *server, WI_ITEM_REC *item) +{ + char *cmd; + + cmd = g_strdup(data); g_strup(cmd); + printtext(server, item == NULL ? NULL : item->name, MSGLEVEL_CRAP, "Unknown command: %s", cmd); + g_free(cmd); + + signal_stop(); +} + +static void event_cmderror(gpointer error) +{ + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[GPOINTER_TO_INT(error)]); +} + +void fe_core_commands_init(void) +{ + command_bind("help", NULL, (SIGNAL_FUNC) cmd_help); + command_bind("echo", NULL, (SIGNAL_FUNC) cmd_echo); + command_bind("version", NULL, (SIGNAL_FUNC) cmd_version); + + signal_add("unknown command", (SIGNAL_FUNC) cmd_unknown); + signal_add("default command", (SIGNAL_FUNC) cmd_unknown); + signal_add("error command", (SIGNAL_FUNC) event_cmderror); +} + +void fe_core_commands_deinit(void) +{ + command_unbind("help", (SIGNAL_FUNC) cmd_help); + command_unbind("echo", (SIGNAL_FUNC) cmd_echo); + command_unbind("version", (SIGNAL_FUNC) cmd_version); + + signal_remove("unknown command", (SIGNAL_FUNC) cmd_unknown); + signal_remove("default command", (SIGNAL_FUNC) cmd_unknown); + signal_remove("error command", (SIGNAL_FUNC) event_cmderror); +} diff --git a/src/fe-common/core/fe-log.c b/src/fe-common/core/fe-log.c new file mode 100644 index 00000000..560e0f35 --- /dev/null +++ b/src/fe-common/core/fe-log.c @@ -0,0 +1,402 @@ +/* + fe-log.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "server.h" +#include "levels.h" +#include "misc.h" +#include "log.h" +#include "special-vars.h" +#include "settings.h" + +#include "windows.h" +#include "window-items.h" + +/* close autologs after 5 minutes of inactivity */ +#define AUTOLOG_INACTIVITY_CLOSE (60*5) + +#define LOG_DIR_CREATE_MODE 0770 + +static int autolog_level; +static int autoremove_tag; +static const char *autolog_path; + +static void cmd_log_open(const char *data) +{ + /* /LOG OPEN [-noopen] [-autoopen] [-channels <channels>] [-window] + [-rotate hour|day|week|month] <fname> [<levels>] */ + char *params, *args, *itemarg, *rotatearg, *fname, *levels; + char window[MAX_INT_STRLEN]; + LOG_REC *log; + int opened, level, rotate; + + args = "channels rotate"; + params = cmd_get_params(data, 5 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, + &args, &itemarg, &rotatearg, &fname, &levels); + if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + rotate = LOG_ROTATE_NEVER; + if (stristr(args, "-rotate")) { + rotate = log_str2rotate(rotatearg); + if (rotate < 0) rotate = LOG_ROTATE_NEVER; + } + + level = level2bits(levels); + if (level == 0) level = MSGLEVEL_ALL; + + if (stristr(args, "-window")) { + /* log by window ref# */ + ltoa(window, active_win->refnum); + itemarg = window; + } + + log = log_create_rec(fname, level, itemarg); + if (log != NULL && log->handle == -1 && stristr(args, "-noopen") == NULL) { + /* start logging */ + opened = log_start_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + opened ? IRCTXT_LOG_OPENED : + IRCTXT_LOG_CREATE_FAILED, fname); + if (!opened) log_close(log); + } + if (log != NULL) { + if (stristr(args, "-autoopen")) + log->autoopen = TRUE; + log->rotate = rotate; + log_update(log); + } + + g_free(params); +} + +static void cmd_log_close(const char *data) +{ + LOG_REC *log; + + log = log_find(data); + if (log == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_LOG_NOT_OPEN, data); + else { + log_close(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOG_CLOSED, data); + } +} + +static void cmd_log_start(const char *data) +{ + LOG_REC *log; + + log = log_find(data); + if (log != NULL) { + log_start_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOG_OPENED, data); + } +} + +static void cmd_log_stop(const char *data) +{ + LOG_REC *log; + + log = log_find(data); + if (log == NULL || log->handle == -1) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_LOG_NOT_OPEN, data); + else { + log_stop_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOG_CLOSED, data); + } +} + +static void cmd_log_list(void) +{ + GSList *tmp; + char *levelstr, *items, *rotate; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_LOG_LIST_HEADER); + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + levelstr = bits2level(rec->level); + items = rec->items == NULL ? NULL : + g_strjoinv(",", rec->items); + rotate = rec->rotate == 0 ? NULL : + g_strdup_printf(" -rotate %s", log_rotate2str(rec->rotate)); + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_LOG_LIST, + rec->fname, items != NULL ? items : "", + levelstr, rotate != NULL ? rotate : "", + rec->autoopen ? " -autoopen" : ""); + + g_free_not_null(rotate); + g_free_not_null(items); + g_free(levelstr); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_LOG_LIST_FOOTER); +} + +static void cmd_log(const char *data, SERVER_REC *server, void *item) +{ + command_runsub("log", data, server, item); +} + +static LOG_REC *log_find_item(const char *item) +{ + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->items != NULL && strarray_find(rec->items, item) != -1) + return rec; + } + + return NULL; +} + +static void cmd_window_log(const char *data) +{ + /* /WINDOW LOG ON|OFF|TOGGLE [<filename>] */ + LOG_REC *log; + char *params, *set, *fname, window[MAX_INT_STRLEN]; + int open_log, close_log; + + params = cmd_get_params(data, 2, &set, &fname); + + ltoa(window, active_win->refnum); + log = log_find_item(window); + + open_log = close_log = FALSE; + if (g_strcasecmp(set, "ON") == 0) + open_log = TRUE; + else if (g_strcasecmp(set, "OFF") == 0) { + close_log = TRUE; + } else if (g_strcasecmp(set, "TOGGLE") == 0) { + open_log = log == NULL; + close_log = log != NULL; + } else { + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NOT_TOGGLE); + g_free(params); + return; + } + + if (open_log && log == NULL) { + /* irc.log.<windowname> or irc.log.Window<ref#> */ + fname = *fname != '\0' ? g_strdup(fname) : + g_strdup_printf("~/irc.log.%s%s", + active_win->name != NULL ? active_win->name : "Window", + active_win->name != NULL ? "" : window); + log = log_create_rec(fname, MSGLEVEL_ALL, window); + if (log != NULL) log_update(log); + g_free(fname); + } + + if (open_log && log != NULL) { + log_start_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOG_OPENED, log->fname); + } else if (close_log && log != NULL && log->handle != -1) { + log_stop_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOG_CLOSED, log->fname); + } + + g_free(params); +} + +/* Create log file entry to window, but don't start logging */ +static void cmd_window_logfile(const char *data) +{ + LOG_REC *log; + char window[MAX_INT_STRLEN]; + + ltoa(window, active_win->refnum); + log = log_find_item(window); + + if (log != NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_WINDOWLOG_FILE_LOGGING); + return; + } + + log = log_create_rec(data, MSGLEVEL_ALL, window); + if (log == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_WINDOWLOG_FILE, data); + else + log_update(log); +} + +static void autologs_close_all(void) +{ + GSList *tmp, *next; + + for (tmp = logs; tmp != NULL; tmp = next) { + LOG_REC *rec = tmp->data; + + next = tmp->next; + if (rec->temp) log_close(rec); + } +} + +static void autolog_log(void *server, const char *target) +{ + LOG_REC *log; + char *fname, *dir, *str; + + log = log_find_item(target); + if (log != NULL) return; + + fname = parse_special_string(autolog_path, server, NULL, target, NULL); + if (log_find(fname) == NULL) { + str = convert_home(fname); + dir = g_dirname(str); + g_free(str); + + mkdir(dir, LOG_DIR_CREATE_MODE); + g_free(dir); + + log = log_create_rec(fname, autolog_level, target); + if (log != NULL) { + log->temp = TRUE; + log_update(log); + log_start_logging(log); + } + } + g_free(fname); +} + +/* write to logs created with /WINDOW LOG */ +static void sig_printtext_stripped(void *server, const char *target, gpointer levelp, const char *text) +{ + char windownum[MAX_INT_STRLEN]; + WINDOW_REC *window; + LOG_REC *log; + int level; + + level = GPOINTER_TO_INT(levelp); + if ((autolog_level & level) && target != NULL && *target != '\0') + autolog_log(server, target); + + window = window_find_closest(server, target, level); + if (window != NULL) { + ltoa(windownum, window->refnum); + + log = log_find_item(windownum); + if (log != NULL) log_write_rec(log, text); + } +} + +static int sig_autoremove(void) +{ + GSList *tmp, *next; + time_t removetime; + + removetime = time(NULL)-AUTOLOG_INACTIVITY_CLOSE; + for (tmp = logs; tmp != NULL; tmp = next) { + LOG_REC *rec = tmp->data; + + next = tmp->next; + /* FIXME: here is a small kludge - We don't want autolog to + automatically close the logs with channels, only with + private messages. However, this is CORE module and we + don't know how to figure out if item is a channel or not, + so just assume that channels are everything that don't + start with alphanumeric character. */ + if (!rec->temp || rec->last > removetime || + rec->items == NULL || !isalnum(**rec->items)) + continue; + + log_close(rec); + } + return 1; +} + +static void sig_window_item_remove(WINDOW_REC *window, WI_ITEM_REC *item) +{ + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->temp && g_strcasecmp(rec->items[0], item->name) == 0) { + log_close(rec); + break; + } + } +} + +static void sig_log_locked(LOG_REC *log) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_LOG_LOCKED, log->fname); +} + +static void read_settings(void) +{ + int old_autolog = autolog_level; + + autolog_path = settings_get_str("autolog_path"); + autolog_level = !settings_get_bool("autolog") ? 0 : + level2bits(settings_get_str("autolog_level")); + + if (old_autolog && !autolog_level) + autologs_close_all(); +} + +void fe_log_init(void) +{ + autoremove_tag = g_timeout_add(60000, (GSourceFunc) sig_autoremove, NULL); + + settings_add_str("log", "autolog_path", "~/irclogs/$tag/$0.log"); + settings_add_str("log", "autolog_level", "all"); + settings_add_bool("log", "autolog", FALSE); + + autolog_level = 0; + read_settings(); + + command_bind("log", NULL, (SIGNAL_FUNC) cmd_log); + command_bind("log open", NULL, (SIGNAL_FUNC) cmd_log_open); + command_bind("log close", NULL, (SIGNAL_FUNC) cmd_log_close); + command_bind("log start", NULL, (SIGNAL_FUNC) cmd_log_start); + command_bind("log stop", NULL, (SIGNAL_FUNC) cmd_log_stop); + command_bind("log ", NULL, (SIGNAL_FUNC) cmd_log_list); + command_bind("window log", NULL, (SIGNAL_FUNC) cmd_window_log); + command_bind("window logfile", NULL, (SIGNAL_FUNC) cmd_window_logfile); + signal_add_first("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); + signal_add("window item remove", (SIGNAL_FUNC) sig_window_item_remove); + signal_add("log locked", (SIGNAL_FUNC) sig_log_locked); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void fe_log_deinit(void) +{ + g_source_remove(autoremove_tag); + + command_unbind("log", (SIGNAL_FUNC) cmd_log); + command_unbind("log open", (SIGNAL_FUNC) cmd_log_open); + command_unbind("log close", (SIGNAL_FUNC) cmd_log_close); + command_unbind("log start", (SIGNAL_FUNC) cmd_log_start); + command_unbind("log stop", (SIGNAL_FUNC) cmd_log_stop); + command_unbind("log ", (SIGNAL_FUNC) cmd_log_list); + command_unbind("window log", (SIGNAL_FUNC) cmd_window_log); + command_unbind("window logfile", (SIGNAL_FUNC) cmd_window_logfile); + signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); + signal_remove("window item remove", (SIGNAL_FUNC) sig_window_item_remove); + signal_remove("log locked", (SIGNAL_FUNC) sig_log_locked); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c new file mode 100644 index 00000000..960b94f9 --- /dev/null +++ b/src/fe-common/core/fe-server.c @@ -0,0 +1,96 @@ +/* + fe-server.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "settings.h" +#include "network.h" + +#include "levels.h" +#include "server.h" + +static void sig_server_looking(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_LOOKING_UP, server->connrec->address); +} + +static void sig_server_connecting(SERVER_REC *server, IPADDR *ip) +{ + char ipaddr[MAX_IP_LEN]; + + g_return_if_fail(server != NULL); + g_return_if_fail(ip != NULL); + + net_ip2host(ip, ipaddr); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_CONNECTING, + server->connrec->address, ipaddr, server->connrec->port); +} + +static void sig_server_connected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_CONNECTION_ESTABLISHED, server->connrec->address); +} + +static void sig_connect_failed(SERVER_REC *server, gchar *msg) +{ + g_return_if_fail(server != NULL); + + if (msg == NULL) { + /* no message so this wasn't unexpected fail - send + connection_lost message instead */ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_CONNECTION_LOST, server->connrec->address); + } else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + IRCTXT_CANT_CONNECT, server->connrec->address, server->connrec->port, msg); + } +} + +static void sig_server_disconnected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_CONNECTION_LOST, server->connrec->address); +} + +void fe_server_init(void) +{ + signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting); + signal_add("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_add("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); +} + +void fe_server_deinit(void) +{ + signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_remove("server connecting", (SIGNAL_FUNC) sig_server_connecting); + signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); +} diff --git a/src/fe-common/core/fe-settings.c b/src/fe-common/core/fe-settings.c new file mode 100644 index 00000000..5e4b6df6 --- /dev/null +++ b/src/fe-common/core/fe-settings.c @@ -0,0 +1,215 @@ +/* + fe-settings.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "server.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "levels.h" + +static void set_print(SETTINGS_REC *rec) +{ + const char *value; + char value_int[MAX_INT_STRLEN]; + + switch (rec->type) { + case SETTING_TYPE_BOOLEAN: + value = settings_get_bool(rec->key) ? "ON" : "OFF"; + break; + case SETTING_TYPE_INT: + g_snprintf(value_int, sizeof(value_int), "%d", settings_get_int(rec->key)); + value = value_int; + break; + case SETTING_TYPE_STRING: + value = settings_get_str(rec->key); + break; + default: + value = ""; + } + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s = %s", rec->key, value); +} + +static void set_boolean(const char *key, const char *value) +{ + if (g_strcasecmp(value, "ON") == 0) + iconfig_set_bool("settings", key, TRUE); + else if (g_strcasecmp(value, "OFF") == 0) + iconfig_set_bool("settings", key, FALSE); + else if (g_strcasecmp(value, "TOGGLE") == 0) + iconfig_set_bool("settings", key, !settings_get_bool(key)); + else + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NOT_TOGGLE); +} + +static void cmd_set(char *data) +{ + GSList *sets, *tmp; + char *params, *key, *value, *last_section; + int keylen, found; + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &key, &value); + + keylen = strlen(key); + last_section = ""; found = 0; + + sets = settings_get_sorted(); + for (tmp = sets; tmp != NULL; tmp = tmp->next) { + SETTINGS_REC *rec = tmp->data; + + if ((*value != '\0' && g_strcasecmp(rec->key, key) != 0) || + (*value == '\0' && keylen != 0 && g_strncasecmp(rec->key, key, keylen) != 0)) + continue; + + if (strcmp(last_section, rec->section) != 0) { + /* print section */ + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%_[ %s ]", rec->section); + last_section = rec->section; + } + + if (*value != '\0') { + /* change the setting */ + switch (rec->type) { + case SETTING_TYPE_BOOLEAN: + set_boolean(key, value); + break; + case SETTING_TYPE_INT: + iconfig_set_int("settings", key, atoi(value)); + break; + case SETTING_TYPE_STRING: + iconfig_set_str("settings", key, value); + break; + } + signal_emit("setup changed", 0); + } + + set_print(rec); + found = TRUE; + } + g_slist_free(sets); + + if (!found) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %s", key); + + g_free(params); +} + +static void cmd_toggle(const char *data) +{ + char *params, *key, *value; + int type; + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &key, &value); + + type = settings_get_type(key); + if (type == -1) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %_%s", key); + else if (type != SETTING_TYPE_BOOLEAN) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Setting %_%s%_ isn't boolean, use /SET", key); + else { + set_boolean(key, *value != '\0' ? value : "TOGGLE"); + set_print(settings_get_record(key)); + } + + g_free(params); +} + +static void show_aliases(const char *alias) +{ + CONFIG_NODE *node; + GSList *tmp; + int aliaslen; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIASLIST_HEADER); + + node = iconfig_node_traverse("aliases", FALSE); + tmp = node == NULL ? NULL : node->value; + + aliaslen = strlen(alias); + for (; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->type != NODE_TYPE_KEY) + continue; + + if (aliaslen != 0 && g_strncasecmp(node->key, alias, aliaslen) != 0) + continue; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIASLIST_LINE, + node->key, node->value); + } + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIASLIST_FOOTER); +} + +static void alias_remove(const char *alias) +{ + if (iconfig_get_str("aliases", alias, NULL) == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIAS_NOT_FOUND, alias); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIAS_REMOVED, alias); + iconfig_set_str("aliases", alias, NULL); + } +} + +static void cmd_alias(const char *data) +{ + char *params, *alias, *value; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &alias, &value); + if (*alias == '-') { + if (alias[1] != '\0') alias_remove(alias+1); + } else if (*alias == '\0' || *value == '\0') + show_aliases(alias); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_ALIAS_ADDED, alias); + iconfig_set_str("aliases", alias, value); + } + g_free(params); +} + +static void cmd_unalias(const char *data) +{ + g_return_if_fail(data != NULL); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + alias_remove(data); +} + +void fe_settings_init(void) +{ + command_bind("set", NULL, (SIGNAL_FUNC) cmd_set); + command_bind("toggle", NULL, (SIGNAL_FUNC) cmd_toggle); + command_bind("alias", NULL, (SIGNAL_FUNC) cmd_alias); + command_bind("unalias", NULL, (SIGNAL_FUNC) cmd_unalias); +} + +void fe_settings_deinit(void) +{ + command_unbind("set", (SIGNAL_FUNC) cmd_set); + command_unbind("toggle", (SIGNAL_FUNC) cmd_toggle); + command_unbind("alias", (SIGNAL_FUNC) cmd_alias); + command_unbind("unalias", (SIGNAL_FUNC) cmd_unalias); +} diff --git a/src/fe-common/core/hilight-text.c b/src/fe-common/core/hilight-text.c new file mode 100644 index 00000000..a96f7395 --- /dev/null +++ b/src/fe-common/core/hilight-text.c @@ -0,0 +1,354 @@ +/* + hilight-text.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "levels.h" +#include "server.h" + +#include "hilight-text.h" + +#define DEFAULT_HILIGHT_CHECK_LEVEL \ + (MSGLEVEL_PUBLIC | MSGLEVEL_MSGS | MSGLEVEL_NOTICES | MSGLEVEL_ACTIONS) + +static int hilight_next; +GSList *hilights; + +static void hilight_add_config(HILIGHT_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("(hilights", TRUE); + node = config_node_section(node, NULL, NODE_TYPE_BLOCK); + + config_node_set_str(node, "text", rec->text); + if (rec->level > 0) config_node_set_int(node, "level", rec->level); + if (rec->color) config_node_set_str(node, "color", rec->color); + if (rec->nickmask) config_node_set_bool(node, "nickmask", TRUE); + if (rec->fullword) config_node_set_bool(node, "fullword", TRUE); + if (rec->regexp) config_node_set_bool(node, "regexp", TRUE); + + if (rec->channels != NULL && *rec->channels != NULL) { + node = config_node_section(node, "channels", NODE_TYPE_LIST); + config_node_add_list(node, rec->channels); + } +} + +static void hilight_remove_config(HILIGHT_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("hilights", FALSE); + if (node != NULL) config_node_list_remove(node, g_slist_index(hilights, rec)); +} + +static void hilight_destroy(HILIGHT_REC *rec) +{ + g_free(rec->text); + g_free_not_null(rec->color); + g_free(rec); +} + +static void hilights_destroy_all(void) +{ + g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL); + g_slist_free(hilights); + hilights = NULL; +} + +static void hilight_remove(HILIGHT_REC *rec) +{ + hilight_remove_config(rec); + hilights = g_slist_remove(hilights, rec); + hilight_destroy(rec); +} + +static HILIGHT_REC *hilight_find(const char *text, char **channels) +{ + GSList *tmp; + char **chan; + + g_return_val_if_fail(text != NULL, NULL); + + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (g_strcasecmp(rec->text, text) != 0) + continue; + + if ((channels == NULL && rec->channels == NULL)) + return rec; /* no channels - ok */ + + if (channels != NULL && strcmp(*channels, "*") == 0) + return rec; /* ignore channels */ + + if (channels == NULL || rec->channels == NULL) + continue; /* other doesn't have channels */ + + if (strarray_length(channels) != strarray_length(rec->channels)) + continue; /* different amount of channels */ + + /* check that channels match */ + for (chan = channels; *chan != NULL; chan++) { + if (strarray_find(rec->channels, *chan) == -1) + break; + } + + if (*chan == NULL) + return rec; /* channels ok */ + } + + return NULL; +} + +static void sig_print_text(SERVER_REC *server, const char *channel, gpointer level, const char *str) +{ + if (hilight_next) { + hilight_next = FALSE; + signal_stop(); + } +} + +static void sig_print_text_stripped(SERVER_REC *server, const char *channel, gpointer plevel, const char *str) +{ + GSList *tmp; + char *color, *newstr; + int len, level, best_match; + + g_return_if_fail(str != NULL); + + level = GPOINTER_TO_INT(plevel); + if (level & (MSGLEVEL_NOHILIGHT|MSGLEVEL_HILIGHT)) return; + + color = NULL; best_match = 0; + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (rec->nickmask) + continue; + if ((level & (rec->level > 0 ? rec->level : DEFAULT_HILIGHT_CHECK_LEVEL)) == 0) + continue; + if (rec->channels != NULL && !strarray_find(rec->channels, channel)) + continue; + if (rec->regexp) { + if (!regexp_match(str, rec->text)) + continue; + } else if (rec->fullword) { + if (stristr_full(str, rec->text) == NULL) + continue; + } else { + if (stristr(str, rec->text) == NULL) + continue; + } + + len = strlen(rec->text); + if (best_match < len) { + best_match = len; + color = rec->color; + } + } + + if (best_match > 0) { + hilight_next = FALSE; + + if (color == NULL) color = "\00316"; + newstr = g_strconcat(isdigit(*color) ? "\003" : "", color, str, NULL); + signal_emit("print text", 4, server, channel, GINT_TO_POINTER(level | MSGLEVEL_HILIGHT), newstr); + g_free(newstr); + + hilight_next = TRUE; + } +} + +static void read_hilight_config(void) +{ + CONFIG_NODE *node; + HILIGHT_REC *rec; + GSList *tmp; + char *text, *color; + + hilights_destroy_all(); + + node = iconfig_node_traverse("hilights", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + text = config_node_get_str(node, "text", NULL); + if (text == NULL || *text == '\0') + continue; + + rec = g_new0(HILIGHT_REC, 1); + hilights = g_slist_append(hilights, rec); + + color = config_node_get_str(node, "color", NULL); + + rec->text = g_strdup(text); + rec->color = color == NULL || *color == '\0' ? NULL : + g_strdup(color); + rec->level = config_node_get_int(node, "level", 0); + rec->nickmask = config_node_get_bool(node, "nickmask", FALSE); + rec->fullword = config_node_get_bool(node, "fullword", FALSE); + rec->regexp = config_node_get_bool(node, "regexp", FALSE); + + node = config_node_section(node, "channels", -1); + if (node != NULL) rec->channels = config_node_get_list(node); + } +} + +static void hilight_print(int index, HILIGHT_REC *rec) +{ + char *chans, *levelstr; + + chans = rec->channels == NULL ? NULL : + g_strjoinv(",", rec->channels); + levelstr = rec->level == 0 ? NULL : + bits2level(rec->level); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, + IRCTXT_HILIGHT_LINE, index, rec->text, + chans != NULL ? chans : "", + levelstr != NULL ? levelstr : "", + rec->nickmask ? " -nick" : "", + rec->fullword ? " -word" : "", + rec->regexp ? " -regexp" : ""); + g_free_not_null(chans); + g_free_not_null(levelstr); +} + +static void cmd_hilight_show(void) +{ + GSList *tmp; + int index; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_HILIGHT_HEADER); + index = 1; + for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) { + HILIGHT_REC *rec = tmp->data; + + hilight_print(index, rec); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_HILIGHT_FOOTER); +} + +static void cmd_hilight(const char *data) +{ + /* /HILIGHT [-nick | -regexp | -word] [-color <color>] [-level <level>] [-channels <channels>] <text> */ + char *params, *args, *colorarg, *levelarg, *chanarg, *text; + char **channels; + HILIGHT_REC *rec; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + cmd_hilight_show(); + return; + } + + args = "color level channels"; + params = cmd_get_params(data, 5 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, + &args, &colorarg, &levelarg, &chanarg, &text); + if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + channels = *chanarg == '\0' ? NULL : + g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1); + + rec = hilight_find(text, channels); + if (rec == NULL) { + rec = g_new0(HILIGHT_REC, 1); + + rec->text = g_strdup(text); + rec->channels = channels; + } else { + g_free_and_null(rec->color); + g_strfreev(channels); + + hilight_remove_config(rec); + hilights = g_slist_remove(hilights, rec); + } + + hilights = g_slist_append(hilights, rec); + rec->nickmask = stristr(args, "-nick") != NULL; + rec->fullword = stristr(args, "-word") != NULL; + rec->regexp = stristr(args, "-regexp") != NULL; + + rec->level = level2bits(replace_chars(levelarg, ',', ' ')); + if (*colorarg != '\0') rec->color = g_strdup(colorarg); + + hilight_print(g_slist_index(hilights, rec)+1, rec); + + hilight_add_config(rec); + g_free(params); +} + +static void cmd_dehilight(const char *data) +{ + HILIGHT_REC *rec; + GSList *tmp; + + if (is_numeric(data, ' ')) { + /* with index number */ + tmp = g_slist_nth(hilights, atol(data)-1); + rec = tmp == NULL ? NULL : tmp->data; + } else { + /* with mask */ + char *chans[2] = { "*", NULL }; + rec = hilight_find(data, chans); + } + + if (rec == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_HILIGHT_NOT_FOUND, data); + else + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_HILIGHT_REMOVED, rec->text); + hilight_remove(rec); +} + +void hilight_text_init(void) +{ + hilight_next = FALSE; + + read_hilight_config(); + + signal_add_first("print text", (SIGNAL_FUNC) sig_print_text); + signal_add_first("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped); + signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config); + command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight); + command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight); +} + +void hilight_text_deinit(void) +{ + hilights_destroy_all(); + + signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + signal_remove("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped); + signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config); + command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight); + command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight); +} diff --git a/src/fe-common/core/hilight-text.h b/src/fe-common/core/hilight-text.h new file mode 100644 index 00000000..6e047278 --- /dev/null +++ b/src/fe-common/core/hilight-text.h @@ -0,0 +1,22 @@ +#ifndef __HILIGHT_TEXT_H +#define __HILIGHT_TEXT_H + +typedef struct { + char *text; + + char **channels; /* if non-NULL, check the text only from these channels */ + int level; /* match only messages with this level, 0=default */ + char *color; /* if starts with number, \003 is automatically + inserted before it. */ + + int nickmask:1; /* `text 'is a nick mask - colorify the nick */ + int fullword:1; /* match `text' only for full words */ + int regexp:1; /* `text' is a regular expression */ +} HILIGHT_REC; + +extern GSList *hilights; + +void hilight_text_init(void); +void hilight_text_deinit(void); + +#endif diff --git a/src/fe-common/core/keyboard.c b/src/fe-common/core/keyboard.c new file mode 100644 index 00000000..6881d77a --- /dev/null +++ b/src/fe-common/core/keyboard.c @@ -0,0 +1,297 @@ +/* + keyboard.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "keyboard.h" +#include "windows.h" + +GSList *keyinfos; +static GHashTable *keys; + +KEYINFO_REC *key_info_find(gchar *id) +{ + GSList *tmp; + + for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) + { + KEYINFO_REC *rec = tmp->data; + + if (g_strcasecmp(rec->id, id) == 0) + return rec; + } + + return NULL; +} + +/* Bind a key for function */ +void key_bind(gchar *id, gchar *data, gchar *description, gchar *key_default, SIGNAL_FUNC func) +{ + KEYINFO_REC *info; + KEY_REC *rec; + + g_return_if_fail(id != NULL); + g_return_if_fail(func != NULL); + + /* create key info record */ + info = key_info_find(id); + if (info == NULL) + { + g_return_if_fail(description != NULL); + info = g_new0(KEYINFO_REC, 1); + info->id = g_strdup(id); + info->description = g_strdup(description); + keyinfos = g_slist_append(keyinfos, info); + + /* add the signal */ + id = g_strconcat("key ", id, NULL); + signal_add(id, func); + g_free(id); + + signal_emit("keyinfo created", 1, info); + } + + if (key_default == NULL || *key_default == '\0') + { + /* just create a possible key command, don't bind it to any key yet */ + return; + } + + /* create/replace key record */ + rec = g_hash_table_lookup(keys, key_default); + if (rec != NULL) + { + if (rec->data != NULL) + g_free(rec->data); + } + else + { + rec = g_new0(KEY_REC, 1); + info->keys = g_slist_append(info->keys, rec); + rec->key = g_strdup(key_default); + g_hash_table_insert(keys, rec->key, rec); + } + rec->info = info; + rec->data = data == NULL ? NULL : g_strdup(data); +} + +static void keyinfo_remove(KEYINFO_REC *info) +{ + GSList *tmp; + + g_return_if_fail(info != NULL); + + keyinfos = g_slist_remove(keyinfos, info); + signal_emit("keyinfo destroyed", 1, info); + + /* destroy all keys */ + for (tmp = info->keys; tmp != NULL; tmp = tmp->next) + { + KEY_REC *rec = tmp->data; + + g_hash_table_remove(keys, rec->key); + if (rec->data != NULL) g_free(rec->data); + g_free(rec->key); + g_free(rec); + } + + /* destroy key info */ + g_slist_free(info->keys); + g_free(info->description); + g_free(info->id); + g_free(info); +} + +/* Unbind key */ +void key_unbind(gchar *id, SIGNAL_FUNC func) +{ + KEYINFO_REC *info; + + g_return_if_fail(id != NULL); + g_return_if_fail(func != NULL); + + /* remove keys */ + info = key_info_find(id); + if (info != NULL) + keyinfo_remove(info); + + /* remove signal */ + id = g_strconcat("key ", id, NULL); + signal_remove(id, func); + g_free(id); +} + +/* Configure new key */ +void key_configure_add(gchar *id, gchar *data, gchar *key) +{ + KEYINFO_REC *info; + KEY_REC *rec; + + g_return_if_fail(id != NULL); + g_return_if_fail(key != NULL && *key != '\0'); + + info = key_info_find(id); + if (info == NULL) + return; + + rec = g_new0(KEY_REC, 1); + info->keys = g_slist_append(info->keys, rec); + + rec->info = info; + rec->data = data == NULL ? NULL : g_strdup(data); + rec->key = g_strdup(key); + g_hash_table_insert(keys, rec->key, rec); +} + +/* Remove key */ +void key_configure_remove(gchar *key) +{ + KEY_REC *rec; + + g_return_if_fail(key != NULL); + + rec = g_hash_table_lookup(keys, key); + if (rec == NULL) return; + + rec->info->keys = g_slist_remove(rec->info->keys, rec); + g_hash_table_remove(keys, key); + + if (rec->data != NULL) g_free(rec->data); + g_free(rec->key); + g_free(rec); +} + +gboolean key_pressed(gchar *key, gpointer data) +{ + KEY_REC *rec; + gboolean ret; + gchar *str; + + g_return_val_if_fail(key != NULL, FALSE); + + rec = g_hash_table_lookup(keys, key); + if (rec == NULL) return FALSE; + + str = g_strconcat("key ", rec->info->id, NULL); + ret = signal_emit(str, 3, rec->data, data, rec->info); + g_free(str); + + return ret; +} + +void keyboard_save(void) +{ + CONFIG_NODE *keyboard, *node, *listnode; + GSList *tmp, *tmp2; + + /* remove old keyboard settings */ + config_node_set_str(NULL, "(keyboard", NULL); + keyboard = iconfig_node_traverse("(keyboard", TRUE); + + for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) { + KEYINFO_REC *info = tmp->data; + + node = config_node_section(keyboard, info->id, TRUE); + for (tmp2 = info->keys; tmp2 != NULL; tmp2 = tmp2->next) { + KEY_REC *key = tmp2->data; + + listnode = config_node_section(node, NULL, NODE_TYPE_BLOCK); + if (key->data != NULL) + config_node_set_str(listnode, "data", key->data); + config_node_set_str(listnode, "key", key->key); + } + } +} + +static void sig_command(gchar *data) +{ + signal_emit("send command", 3, data, active_win->active_server, active_win->active); +} + +void read_keyinfo(KEYINFO_REC *info, CONFIG_NODE *node) +{ + GSList *tmp; + char *data, *key; + + g_return_if_fail(info != NULL); + g_return_if_fail(node != NULL); + g_return_if_fail(is_node_list(node)); + + /* remove all old keys */ + while (info->keys != NULL) + key_configure_remove(((KEY_REC *) info->keys->data)->key); + + /* add the new keys */ + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + data = config_node_get_str(node->value, "data", NULL); + key = config_node_get_str(node->value, "key", NULL); + + if (key != NULL) key_configure_add(info->id, data, key); + } +} + +static void read_keyboard_config(void) +{ + KEYINFO_REC *info; + CONFIG_NODE *node; + GSList *tmp; + + while (keyinfos != NULL) + keyinfo_remove(keyinfos->data); + if (keys != NULL) g_hash_table_destroy(keys); + + keys = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + + node = iconfig_node_traverse("keyboard", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key == NULL || node->value == NULL) + continue; + + info = key_info_find(node->key); + if (info != NULL) read_keyinfo(info, node->value); + } +} + +void keyboard_init(void) +{ + keyinfos = NULL; keys = NULL; + key_bind("command", NULL, "Run any IRC command", NULL, (SIGNAL_FUNC) sig_command); + + read_keyboard_config(); + signal_add("setup reread", (SIGNAL_FUNC) read_keyboard_config); +} + +void keyboard_deinit(void) +{ + while (keyinfos != NULL) + keyinfo_remove(keyinfos->data); + g_hash_table_destroy(keys); + + signal_remove("setup reread", (SIGNAL_FUNC) read_keyboard_config); +} diff --git a/src/fe-common/core/keyboard.h b/src/fe-common/core/keyboard.h new file mode 100644 index 00000000..a6278adc --- /dev/null +++ b/src/fe-common/core/keyboard.h @@ -0,0 +1,40 @@ +#ifndef __KEYBOARD_H +#define __KEYBOARD_H + +#include "signals.h" + +typedef struct +{ + char *id; + char *description; + + GSList *keys; +} +KEYINFO_REC; + +typedef struct +{ + KEYINFO_REC *info; + + char *key; + void *data; +} +KEY_REC; + +extern GSList *keyinfos; + +void key_bind(gchar *id, gchar *data, gchar *description, gchar *key_default, SIGNAL_FUNC func); +void key_unbind(gchar *id, SIGNAL_FUNC func); + +void key_configure_add(gchar *id, gchar *data, gchar *key); +void key_configure_remove(gchar *key); + +KEYINFO_REC *key_info_find(gchar *id); +gboolean key_pressed(gchar *key, gpointer data); + +void keyboard_save(void); + +void keyboard_init(void); +void keyboard_deinit(void); + +#endif diff --git a/src/fe-common/core/module-formats.c b/src/fe-common/core/module-formats.c new file mode 100644 index 00000000..39546ea3 --- /dev/null +++ b/src/fe-common/core/module-formats.c @@ -0,0 +1,87 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC fecommon_core_formats[] = +{ + { MODULE_NAME, N_("Core"), 0 }, + + /* ---- */ + { NULL, N_("Windows"), 0 }, + + { "line_start", N_("%B-%W!%B-%n "), 0 }, + { "line_start_irssi", N_("%B-%W!%B- %WIrssi:%n "), 0 }, + { "timestamp", N_("[$[-2.0]3:$[-2.0]4] "), 6, { 1, 1, 1, 1, 1, 1 } }, + { "daychange", N_("Day changed to $[-2.0]1-$[-2.0]0 $2"), 3, { 1, 1, 1 } }, + { "talking_with", N_("You are now talking with %_$0%_"), 1, { 0 } }, + + /* ---- */ + { NULL, N_("Server"), 0 }, + + { "looking_up", N_("Looking up %_$0%_"), 1, { 0 } }, + { "connecting", N_("Connecting to %_$0%_ %K[%n$1%K]%n port %_$2%_"), 3, { 0, 0, 1 } }, + { "connection_established", N_("Connection to %_$0%_ established"), 1, { 0 } }, + { "cant_connect", N_("Unable to connect server %_$0%_ port %_$1%_ %K[%n$2%K]"), 3, { 0, 1, 0 } }, + { "connection_lost", N_("Connection lost to %_$0%_"), 1, { 0 } }, + { "server_changed", N_("Changed to %_$2%_ server %_$1%_"), 3, { 0, 0, 0 } }, + { "unknown_server_tag", N_("Unknown server tag %_$0%_"), 1, { 0 } }, + + /* ---- */ + { NULL, N_("Highlighting"), 0 }, + + { "hilight_header", N_("Highlights:"), 0 }, + { "hilight_line", N_("$[-4]0 $1 $2 $3$3$4$5"), 7, { 1, 0, 0, 0, 0, 0, 0 } }, + { "hilight_footer", "", 0 }, + { "hilight_not_found", N_("Highlight not found: $0"), 1, { 0 } }, + { "hilight_removed", N_("Highlight removed: $0"), 1, { 0 } }, + + /* ---- */ + { NULL, N_("Aliases"), 0 }, + + { "alias_added", N_("Alias $0 added"), 1, { 0 } }, + { "alias_removed", N_("Alias $0 removed"), 1, { 0 } }, + { "alias_not_found", N_("No such alias: $0"), 1, { 0 } }, + { "aliaslist_header", N_("Aliases:"), 0 }, + { "aliaslist_line", N_("$[10]0 $1"), 2, { 0, 0 } }, + { "aliaslist_footer", "", 0 }, + + /* ---- */ + { NULL, N_("Logging"), 0 }, + + { "log_opened", N_("Log file %W$0%n opened"), 1, { 0 } }, + { "log_closed", N_("Log file %W$0%n closed"), 1, { 0 } }, + { "log_create_failed", N_("Couldn't create log file %W$0"), 1, { 0 } }, + { "log_locked", N_("Log file %W$0%n is locked, probably by another running Irssi"), 1, { 0 } }, + { "log_not_open", N_("Log file %W$0%n not open"), 1, { 0 } }, + { "log_started", N_("Started logging to file %W$0"), 1, { 0 } }, + { "log_stopped", N_("Stopped logging to file %W$0"), 1, { 0 } }, + { "log_list_header", N_("Logs:"), 0 }, + { "log_list", N_("$0: $1 $2$3$4"), 5, { 0, 0, 0, 0, 0 } }, + { "log_list_footer", N_(""), 0 }, + { "windowlog_file", N_("Window LOGFILE set to $0"), 1, { 0 } }, + { "windowlog_file_logging", N_("Can't change window's logfile while log is on"), 0 }, + + /* ---- */ + { NULL, N_("Misc"), 0 }, + + { "not_toggle", N_("Value must be either ON, OFF or TOGGLE"), 0 } +}; diff --git a/src/fe-common/core/module-formats.h b/src/fe-common/core/module-formats.h new file mode 100644 index 00000000..cce8d48b --- /dev/null +++ b/src/fe-common/core/module-formats.h @@ -0,0 +1,62 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_FILL_1, + + IRCTXT_LINE_START, + IRCTXT_LINE_START_IRSSI, + IRCTXT_TIMESTAMP, + IRCTXT_DAYCHANGE, + IRCTXT_TALKING_WITH, + + IRCTXT_FILL_2, + + IRCTXT_LOOKING_UP, + IRCTXT_CONNECTING, + IRCTXT_CONNECTION_ESTABLISHED, + IRCTXT_CANT_CONNECT, + IRCTXT_CONNECTION_LOST, + IRCTXT_SERVER_CHANGED, + IRCTXT_UNKNOWN_SERVER_TAG, + + IRCTXT_FILL_3, + + IRCTXT_HILIGHT_HEADER, + IRCTXT_HILIGHT_LINE, + IRCTXT_HILIGHT_FOOTER, + IRCTXT_HILIGHT_NOT_FOUND, + IRCTXT_HILIGHT_REMOVED, + + IRCTXT_FILL_4, + + IRCTXT_ALIAS_ADDED, + IRCTXT_ALIAS_REMOVED, + IRCTXT_ALIAS_NOT_FOUND, + IRCTXT_ALIASLIST_HEADER, + IRCTXT_ALIASLIST_LINE, + IRCTXT_ALIASLIST_FOOTER, + + IRCTXT_FILL_5, + + IRCTXT_LOG_OPENED, + IRCTXT_LOG_CLOSED, + IRCTXT_LOG_CREATE_FAILED, + IRCTXT_LOG_LOCKED, + IRCTXT_LOG_NOT_OPEN, + IRCTXT_LOG_STARTED, + IRCTXT_LOG_STOPPED, + IRCTXT_LOG_LIST_HEADER, + IRCTXT_LOG_LIST, + IRCTXT_LOG_LIST_FOOTER, + IRCTXT_WINDOWLOG_FILE, + IRCTXT_WINDOWLOG_FILE_LOGGING, + + IRCTXT_FILL_6, + + IRCTXT_NOT_TOGGLE +}; + +extern FORMAT_REC fecommon_core_formats[]; +#define MODULE_FORMATS fecommon_core_formats diff --git a/src/fe-common/core/module.h b/src/fe-common/core/module.h new file mode 100644 index 00000000..4f6dbc87 --- /dev/null +++ b/src/fe-common/core/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "fe-common/core" diff --git a/src/fe-common/core/nick-hilight.c b/src/fe-common/core/nick-hilight.c new file mode 100644 index 00000000..8244ff50 --- /dev/null +++ b/src/fe-common/core/nick-hilight.c @@ -0,0 +1,115 @@ +/* + nick-hilight.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "levels.h" +#include "server.h" + +#include "windows.h" +#include "window-items.h" + +static void sig_hilight_text(SERVER_REC *server, const char *channel, gpointer levelptr, const char *msg) +{ + WINDOW_REC *window; + int level, oldlevel; + + level = GPOINTER_TO_INT(levelptr); + window = window_find_closest(server, channel, level); + if (window == active_win || (level & (MSGLEVEL_NEVER|MSGLEVEL_NO_ACT|MSGLEVEL_MSGS))) + return; + + oldlevel = window->new_data; + if (window->new_data < NEWDATA_TEXT) { + window->new_data = NEWDATA_TEXT; + signal_emit("window hilight", 1, window); + } + + signal_emit("window activity", 2, window, GINT_TO_POINTER(oldlevel)); +} + +static void sig_dehilight(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + + if (item != NULL && item->new_data != 0) { + item->new_data = 0; + signal_emit("window item hilight", 1, item); + } +} + +static void sig_dehilight_window(WINDOW_REC *window) +{ + int oldlevel; + + g_return_if_fail(window != NULL); + + if (window->new_data == 0) + return; + + if (window->new_data != 0) { + oldlevel = window->new_data; + window->new_data = 0; + signal_emit("window hilight", 2, window, GINT_TO_POINTER(oldlevel)); + } + signal_emit("window activity", 2, window, GINT_TO_POINTER(oldlevel)); + + g_slist_foreach(window->items, (GFunc) sig_dehilight, NULL); +} + +static void sig_hilight_window_item(WI_ITEM_REC *item) +{ + WINDOW_REC *window; + GSList *tmp; + int level, oldlevel; + + window = window_item_window(item); level = 0; + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + item = tmp->data; + + if (item->new_data > level) + level = item->new_data; + } + + oldlevel = window->new_data; + if (window->new_data < level || level == 0) { + window->new_data = level; + signal_emit("window hilight", 2, window, GINT_TO_POINTER(oldlevel)); + } + signal_emit("window activity", 2, window, GINT_TO_POINTER(oldlevel)); +} + +void nick_hilight_init(void) +{ + signal_add("print text", (SIGNAL_FUNC) sig_hilight_text); + signal_add("window item changed", (SIGNAL_FUNC) sig_dehilight); + signal_add("window changed", (SIGNAL_FUNC) sig_dehilight_window); + signal_add("window dehilight", (SIGNAL_FUNC) sig_dehilight_window); + signal_add("window item hilight", (SIGNAL_FUNC) sig_hilight_window_item); +} + +void nick_hilight_deinit(void) +{ + signal_remove("print text", (SIGNAL_FUNC) sig_hilight_text); + signal_remove("window item changed", (SIGNAL_FUNC) sig_dehilight); + signal_remove("window changed", (SIGNAL_FUNC) sig_dehilight_window); + signal_remove("window dehilight", (SIGNAL_FUNC) sig_dehilight_window); + signal_remove("window item hilight", (SIGNAL_FUNC) sig_hilight_window_item); +} diff --git a/src/fe-common/core/printtext.c b/src/fe-common/core/printtext.c new file mode 100644 index 00000000..b03658b1 --- /dev/null +++ b/src/fe-common/core/printtext.c @@ -0,0 +1,858 @@ +/* + printtext.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "special-vars.h" +#include "settings.h" + +#include "levels.h" +#include "server.h" + +#include "translation.h" +#include "themes.h" +#include "windows.h" + +static gboolean toggle_show_timestamps, toggle_show_msgs_timestamps, toggle_hide_text_style; +static gint printtag; +static gchar ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +static gint signal_gui_print_text; +static gint signal_print_text_stripped; +static gint signal_print_text; +static gint signal_print_text_finished; + +void printbeep(void) +{ + signal_emit_id(signal_gui_print_text, 6, active_win, NULL, NULL, + GINT_TO_POINTER(PRINTFLAG_BEEP), "", MSGLEVEL_NEVER); +} + +/* parse ANSI color string */ +static char *convert_ansi(char *str, int *fgcolor, int *bgcolor, int *flags) +{ + gchar *start; + gint fg, bg, fl, num; + + if (*str != '[') return str; + + start = str; + + fg = *fgcolor < 0 ? current_theme->default_color : *fgcolor; + bg = *bgcolor < 0 ? -1 : *bgcolor; + fl = *flags; + + str++; num = 0; + for (;; str++) + { + if (*str == '\0') return start; + + if (isdigit((gint) *str)) + { + num = num*10 + (*str-'0'); + continue; + } + + if (*str != ';' && *str != 'm') return start; + + switch (num) + { + case 0: + /* reset colors back to default */ + fg = current_theme->default_color; + bg = -1; + break; + case 1: + /* hilight */ + fg |= 8; + break; + case 5: + /* blink */ + bg = bg == -1 ? 8 : bg | 8; + break; + case 7: + /* reverse */ + fl |= PRINTFLAG_REVERSE; + break; + default: + if (num >= 30 && num <= 37) + fg = (fg & 0xf8) + ansitab[num-30]; + if (num >= 40 && num <= 47) + { + if (bg == -1) bg = 0; + bg = (bg & 0xf8) + ansitab[num-40]; + } + break; + } + num = 0; + + if (*str == 'm') + { + if (!toggle_hide_text_style) + { + *fgcolor = fg; + *bgcolor = bg == -1 ? -1 : bg; + *flags = fl; + } + str++; + break; + } + } + + return str; +} + +#define IN_COLOR_CODE 2 +#define IN_SECOND_CODE 4 +char *strip_codes(const char *input) +{ + const char *p; + gchar *str, *out; + gint loop_state; + + loop_state = 0; + out = str = g_strdup(input); + for (p = input; *p != '\0'; p++) /* Going through the string till the end k? */ + { + if (*p == '\003') + { + if (p[1] < 17 && p[1] > 0) + { + p++; + if (p[1] < 17 && p[1] > 0) p++; + continue; + } + loop_state = IN_COLOR_CODE; + continue; + } + + if (loop_state & IN_COLOR_CODE) + { + if (isdigit( (gint) *p )) continue; + if (*p != ',' || (loop_state & IN_SECOND_CODE)) + { + /* we're no longer in a color code */ + *out++ = *p; + loop_state &= ~IN_COLOR_CODE|IN_SECOND_CODE; + continue; + } + + /* we're in the second code */ + loop_state |= IN_SECOND_CODE; + continue; + + } + + /* we're not in a color code that means we should add the character */ + if (*p == 4 && p[1] != '\0' && p[2] != '\0') + { + p += 2; + continue; + } + + if (*p == 2 || *p == 22 || *p == 27 || *p == 31 || *p == 15) + continue; + *out++ = *p; + } + + *out = '\0'; + return str; +} + +static gboolean expand_styles(GString *out, char format, void *server, const char *channel, int level) +{ + static const char *backs = "01234567"; + static const char *fores = "krgybmcw"; + static const char *boldfores = "KRGYBMCW"; + gchar *p; + + /* p/P -> m/M */ + if (format == 'p') + format = 'm'; + else if (format == 'P') + format = 'M'; + + switch (format) + { + case 'U': + /* Underline on/off */ + g_string_append_c(out, 4); + g_string_append_c(out, -1); + g_string_append_c(out, 2); + break; + case '9': + case '_': + /* bold on/off */ + g_string_append_c(out, 4); + g_string_append_c(out, -1); + g_string_append_c(out, 1); + break; + case '8': + /* reverse */ + g_string_append_c(out, 4); + g_string_append_c(out, -1); + g_string_append_c(out, 3); + break; + case '%': + g_string_append_c(out, '%'); + break; + case ':': + /* Newline */ + printtext(server, channel, level, out->str); + g_string_truncate(out, 0); + break; + + case '|': + /* Indent here mark */ + g_string_append_c(out, 4); + g_string_append_c(out, -1); + g_string_append_c(out, 4); + break; + + case 'F': + /* flashing - ignore */ + break; + + case 'N': + /* don't put clear-color tag at the end of the output - ignore */ + break; + + case 'n': + /* default color */ + g_string_append_c(out, 4); + g_string_append_c(out, -1); + g_string_append_c(out, -1); + break; + + default: + /* check if it's a background color */ + p = strchr(backs, format); + if (p != NULL) + { + g_string_append_c(out, 4); + g_string_append_c(out, -2); + g_string_append_c(out, ansitab[(gint) (p-backs)]+1); + break; + } + + /* check if it's a foreground color */ + p = strchr(fores, format); + if (p != NULL) + { + g_string_append_c(out, 4); + g_string_append_c(out, ansitab[(gint) (p-fores)]+1); + g_string_append_c(out, -2); + break; + } + + /* check if it's a bold foreground color */ + p = strchr(boldfores, format); + if (p != NULL) + { + g_string_append_c(out, 4); + g_string_append_c(out, 8+ansitab[(gint) (p-boldfores)]+1); + g_string_append_c(out, -2); + break; + } + return FALSE; + } + + return TRUE; +} + +static void read_arglist(va_list va, FORMAT_REC *format, + char **arglist, int arglist_size, + char *buffer, int buffer_size) +{ + int num, len, bufpos; + + bufpos = 0; + for (num = 0; num < format->params && num < arglist_size; num++) { + switch (format->paramtypes[num]) { + case FORMAT_STRING: + arglist[num] = (char *) va_arg(va, char *); + if (arglist[num] == NULL) { + g_warning("output_format_text_args() : parameter %d is NULL", num); + arglist[num] = ""; + } + break; + case FORMAT_INT: { + int d = (int) va_arg(va, int); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%d", d); + bufpos += len+1; + break; + } + case FORMAT_LONG: { + long l = (long) va_arg(va, long); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%ld", l); + bufpos += len+1; + break; + } + case FORMAT_FLOAT: { + double f = (double) va_arg(va, double); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%0.2f", f); + bufpos += len+1; + break; + } + } + } +} + +static void output_format_text_args(GString *out, void *server, const char *channel, int level, FORMAT_REC *format, const char *text, va_list args) +{ + char *arglist[10]; + char buffer[200]; /* should be enough? (won't overflow even if it isn't) */ + + const char *str; + char code; + int need_free; + + str = current_theme != NULL && text != NULL ? text : format->def; + + /* read all optional arguments to arglist[] list + so they can be used in any order.. */ + read_arglist(args, format, + arglist, sizeof(arglist)/sizeof(void*), + buffer, sizeof(buffer)); + + code = 0; + while (*str != '\0') { + if (code == '%') { + /* color code */ + if (!expand_styles(out, *str, server, channel, level)) { + g_string_append_c(out, '%'); + g_string_append_c(out, '%'); + g_string_append_c(out, *str); + } + code = 0; + } else if (code == '$') { + /* argument */ + char *ret; + + ret = parse_special((char **) &str, active_win->active_server, active_win->active, arglist, &need_free, NULL); + if (ret != NULL) { + g_string_append(out, ret); + if (need_free) g_free(ret); + } + code = 0; + } else { + if (*str == '%' || *str == '$') + code = *str; + else + g_string_append_c(out, *str); + } + + str++; + } +} + +static void output_format_text(GString *out, void *server, const char *channel, int level, int formatnum, ...) +{ + MODULE_THEME_REC *theme; + va_list args; + + theme = g_hash_table_lookup(current_theme->modules, MODULE_FORMATS->tag); + + va_start(args, formatnum); + output_format_text_args(out, server, channel, level, + &MODULE_FORMATS[formatnum], + theme == NULL ? NULL : theme->format[formatnum], args); + va_end(args); +} + +static void add_timestamp(WINDOW_REC *window, GString *out, void *server, const char *channel, int level) +{ + time_t t; + struct tm *tm; + GString *tmp; + + if (!(level != MSGLEVEL_NEVER && (toggle_show_timestamps || (toggle_show_msgs_timestamps && (level & MSGLEVEL_MSGS) != 0)))) + return; + + t = time(NULL); + + if ((t - window->last_timestamp) < settings_get_int("timestamp_timeout")) { + window->last_timestamp = t; + return; + } + window->last_timestamp = t; + + tmp = g_string_new(NULL); + tm = localtime(&t); + output_format_text(tmp, server, channel, level, IRCTXT_TIMESTAMP, + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + /* insert the timestamp right after \n */ + g_string_prepend(out, tmp->str); + g_string_free(tmp, TRUE); +} + +static void new_line_stuff(GString *out, void *server, const char *channel, int level) +{ + if ((level & (MSGLEVEL_CLIENTERROR|MSGLEVEL_CLIENTNOTICE)) != 0) + output_format_text(out, server, channel, level, IRCTXT_LINE_START_IRSSI); + else if ((level & (MSGLEVEL_MSGS|MSGLEVEL_PUBLIC|MSGLEVEL_NOTICES|MSGLEVEL_SNOTES|MSGLEVEL_CTCPS|MSGLEVEL_ACTIONS|MSGLEVEL_DCC|MSGLEVEL_CLIENTCRAP)) == 0 && level != MSGLEVEL_NEVER) + output_format_text(out, server, channel, level, IRCTXT_LINE_START); +} + +/* Write text to channel - convert color codes */ +void printtext(void *server, const char *channel, int level, const char *str, ...) +{ + va_list args; + GString *out; + gchar *tmpstr; + gint pros; + + g_return_if_fail(str != NULL); + + va_start(args, str); + + pros = 0; + out = g_string_new(NULL); + + new_line_stuff(out, server, channel, level); + for (; *str != '\0'; str++) + { + if (*str != '%') + { + g_string_append_c(out, *str); + continue; + } + + if (*++str == '\0') break; + switch (*str) + { + /* standard parameters */ + case 's': + { + gchar *s = (gchar *) va_arg(args, gchar *); + if (s && *s) g_string_append(out, s); + break; + } + case 'd': + { + gint d = (gint) va_arg(args, gint); + g_string_sprintfa(out, "%d", d); + break; + } + case 'f': + { + gdouble f = (gdouble) va_arg(args, gdouble); + g_string_sprintfa(out, "%0.2f", f); + break; + } + case 'u': + { + guint d = (guint) va_arg(args, guint); + g_string_sprintfa(out, "%u", d); + break; + } + case 'l': + { + gulong d = (gulong) va_arg(args, gulong); + if (*++str != 'd' && *str != 'u') + { + g_string_sprintfa(out, "%ld", d); + str--; + } + else + { + if (*str == 'd') + g_string_sprintfa(out, "%ld", d); + else + g_string_sprintfa(out, "%lu", d); + } + break; + } + default: + if (!expand_styles(out, *str, server, channel, level)) + { + g_string_append_c(out, '%'); + g_string_append_c(out, *str); + } + break; + } + } + va_end(args); + + /* send the plain text version for logging.. */ + tmpstr = strip_codes(out->str); + signal_emit_id(signal_print_text_stripped, 4, server, channel, GINT_TO_POINTER(level), tmpstr); + g_free(tmpstr); + + signal_emit_id(signal_print_text, 4, server, channel, GINT_TO_POINTER(level), out->str); + + g_string_free(out, TRUE); +} + +void printformat_format(FORMAT_REC *formats, void *server, const char *channel, int level, int formatnum, ...) +{ + MODULE_THEME_REC *theme; + GString *out; + va_list args; + + va_start(args, formatnum); + out = g_string_new(NULL); + + theme = g_hash_table_lookup(current_theme->modules, formats->tag); + + output_format_text_args(out, server, channel, level, + &formats[formatnum], + theme == NULL ? NULL : theme->format[formatnum], args); + if (out->len > 0) printtext(server, channel, level, "%s", out->str); + + g_string_free(out, TRUE); + va_end(args); +} + +static void newline(WINDOW_REC *window) +{ + window->lines++; + if (window->lines != 1) { + signal_emit_id(signal_gui_print_text, 6, window, + GINT_TO_POINTER(-1), GINT_TO_POINTER(-1), + GINT_TO_POINTER(0), "\n", GINT_TO_POINTER(-1)); + } +} + +static void sig_print_text(void *server, const char *target, gpointer level, const char *text) +{ + WINDOW_REC *window; + GString *out; + gchar *dup, *ptr, type, *str; + gint fgcolor, bgcolor; + gint flags; + + g_return_if_fail(text != NULL); + + window = window_find_closest(server, target, GPOINTER_TO_INT(level)); + g_return_if_fail(window != NULL); + + flags = 0; fgcolor = -1; bgcolor = -1; type = '\0'; + + newline(window); + + out = g_string_new(text); + if (server != NULL && servers != NULL && servers->next != NULL && + (window->active == NULL || window->active->server != server)) + { + /* connected to more than one server and active server isn't the + same where the message came or we're in status/msgs/empty window - + prefix with a [server tag] */ + gchar *str; + + str = g_strdup_printf("[%s] ", ((SERVER_REC *) server)->tag); + g_string_prepend(out, str); + g_free(str); + } + + add_timestamp(window, out, server, target, GPOINTER_TO_INT(level)); + + dup = str = out->str; + g_string_free(out, FALSE); + + while (*str != '\0') + { + for (ptr = str; *ptr != '\0'; ptr++) + { + if (*ptr == 2 || *ptr == 3 || *ptr == 4 || *ptr == 6 || *ptr == 7 || *ptr == 15 || *ptr == 22 || *ptr == 27 || *ptr == 31) + { + type = *ptr; + *ptr++ = '\0'; + break; + } + + *ptr = (gchar) translation_in[(gint) (guchar) *ptr]; + } + + if (type == 7) + { + /* bell */ + if (settings_get_bool("toggle_bell_beeps")) + flags |= PRINTFLAG_BEEP; + } + if (*str != '\0' || flags & PRINTFLAG_BEEP) + { + signal_emit_id(signal_gui_print_text, 6, window, + GINT_TO_POINTER(fgcolor), GINT_TO_POINTER(bgcolor), + GINT_TO_POINTER(flags), str, level); + flags &= ~(PRINTFLAG_BEEP|PRINTFLAG_INDENT); + } + if (*ptr == '\0') break; + + switch (type) + { + case 2: + /* bold */ + if (!toggle_hide_text_style) + flags ^= PRINTFLAG_BOLD; + break; + case 6: + /* blink */ + if (!toggle_hide_text_style) + flags ^= PRINTFLAG_BLINK; + break; + case 15: + /* remove all styling */ + flags &= PRINTFLAG_BEEP; + fgcolor = bgcolor = -1; + break; + case 22: + /* reverse */ + if (!toggle_hide_text_style) + flags ^= PRINTFLAG_REVERSE; + break; + case 31: + /* underline */ + if (!toggle_hide_text_style) + flags ^= PRINTFLAG_UNDERLINE; + case 27: + /* ansi color code */ + ptr = convert_ansi(ptr, &fgcolor, &bgcolor, &flags); + break; + case 4: + /* user specific colors */ + flags &= ~PRINTFLAG_MIRC_COLOR; + if ((signed char) *ptr == -1) + { + ptr++; + if ((signed char) *ptr == -1) + { + fgcolor = bgcolor = -1; + flags &= PRINTFLAG_INDENT; + } + else if (*ptr == 1) + flags ^= PRINTFLAG_BOLD; + else if (*ptr == 2) + flags ^= PRINTFLAG_UNDERLINE; + else if (*ptr == 3) + flags ^= PRINTFLAG_REVERSE; + else if (*ptr == 4) + flags |= PRINTFLAG_INDENT; + } + else + { + if ((signed char) *ptr != -2) + { + fgcolor = (guchar) *ptr-1; + if (fgcolor <= 7) + flags &= ~PRINTFLAG_BOLD; + else + { + /* bold */ + if (fgcolor != 8) fgcolor -= 8; + flags |= PRINTFLAG_BOLD; + } + } + ptr++; + if ((signed char) *ptr != -2) + bgcolor = (signed char) *ptr == -1 ? -1 : *ptr-1; + } + ptr++; + break; + case 3: + if (*ptr < 17) + { + /* mostly just for irssi's internal use.. */ + fgcolor = (*ptr++)-1; + if (*ptr == 0 || *ptr >= 17) + bgcolor = -1; + else + bgcolor = (*ptr++)-1; + if (fgcolor & 8) + flags |= PRINTFLAG_BOLD; + else + flags &= ~PRINTFLAG_BOLD; + break; + } + + /* MIRC color */ + if (toggle_hide_text_style) + { + /* don't show them. */ + if (isdigit((gint) *ptr)) + { + ptr++; + if (isdigit((gint) *ptr)) ptr++; + if (*ptr == ',') + { + ptr++; + if (isdigit((gint) *ptr)) + { + ptr++; + if (isdigit((gint) *ptr)) ptr++; + } + } + } + break; + } + + flags |= PRINTFLAG_MIRC_COLOR; + if (!isdigit((gint) *ptr) && *ptr != ',') + { + fgcolor = -1; + bgcolor = -1; + } + else + { + /* foreground color */ + if (*ptr != ',') + { + fgcolor = *ptr++-'0'; + if (isdigit((gint) *ptr)) + fgcolor = fgcolor*10 + (*ptr++-'0'); + } + if (*ptr == ',') + { + /* back color */ + bgcolor = 0; + if (!isdigit((gint) *++ptr)) + bgcolor = -1; + else + { + bgcolor = *ptr++-'0'; + if (isdigit((gint) *ptr)) + bgcolor = bgcolor*10 + (*ptr++-'0'); + } + } + } + break; + } + + str = ptr; + } + g_free(dup); + signal_emit_id(signal_print_text_finished, 1, window); +} + +static int sig_check_daychange(void) +{ + static gint lastday = -1; + GSList *tmp; + time_t t; + struct tm *tm; + + if (!toggle_show_timestamps) + { + /* display day change notice only when using timestamps */ + return TRUE; + } + + t = time(NULL); + tm = localtime(&t); + + if (lastday == -1) + { + /* First check, don't display. */ + lastday = tm->tm_mday; + return TRUE; + } + + if (tm->tm_mday == lastday) + return TRUE; + + /* day changed, print notice about it to every window */ + for (tmp = windows; tmp != NULL; tmp = tmp->next) + { + WINDOW_REC *win = tmp->data; + + printformat(win->active->server, win->active->name, MSGLEVEL_NEVER, + IRCTXT_DAYCHANGE, tm->tm_mday, tm->tm_mon+1, 1900+tm->tm_year); + } + lastday = tm->tm_mday; + return TRUE; +} + +static void sig_gui_dialog(const char *type, const char *text) +{ + char **lines, **tmp; + + if (g_strcasecmp(type, "warning") == 0) + type = _("%_Warning:%_ %s"); + else if (g_strcasecmp(type, "error") == 0) + type = _("%_Error:%_ %s"); + else + type = "%s"; + + lines = g_strsplit(text, "\n", -1); + for (tmp = lines; *tmp != NULL; tmp++) + printtext(NULL, NULL, MSGLEVEL_NEVER, type, *tmp); + g_strfreev(lines); +} + +static void read_settings(void) +{ + toggle_show_timestamps = settings_get_bool("toggle_show_timestamps"); + toggle_show_msgs_timestamps = settings_get_bool("toggle_show_msgs_timestamps"); + toggle_hide_text_style = settings_get_bool("toggle_hide_text_style"); +} + +void printtext_init(void) +{ + settings_add_int("misc", "timestamp_timeout", 0); + + signal_gui_print_text = module_get_uniq_id_str("signals", "gui print text"); + signal_print_text_stripped = module_get_uniq_id_str("signals", "print text stripped"); + signal_print_text = module_get_uniq_id_str("signals", "print text"); + signal_print_text_finished = module_get_uniq_id_str("signals", "print text finished"); + + read_settings(); + printtag = g_timeout_add(30000, (GSourceFunc) sig_check_daychange, NULL); + signal_add("print text", (SIGNAL_FUNC) sig_print_text); + signal_add("gui dialog", (SIGNAL_FUNC) sig_gui_dialog); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + command_bind("beep", NULL, (SIGNAL_FUNC) printbeep); +} + +void printtext_deinit(void) +{ + g_source_remove(printtag); + signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + command_unbind("beep", (SIGNAL_FUNC) printbeep); +} diff --git a/src/fe-common/core/printtext.h b/src/fe-common/core/printtext.h new file mode 100644 index 00000000..fc2b2c57 --- /dev/null +++ b/src/fe-common/core/printtext.h @@ -0,0 +1,61 @@ +#ifndef __PRINTTEXT_H +#define __PRINTTEXT_H + +enum { + FORMAT_STRING, + FORMAT_INT, + FORMAT_LONG, + FORMAT_FLOAT +}; + +typedef struct { + char *tag; + char *def; + + int params; + int paramtypes[10]; +} FORMAT_REC; + +#define PRINTFLAG_BOLD 0x01 +#define PRINTFLAG_REVERSE 0x02 +#define PRINTFLAG_UNDERLINE 0x04 +#define PRINTFLAG_BEEP 0x08 +#define PRINTFLAG_BLINK 0x10 +#define PRINTFLAG_MIRC_COLOR 0x20 +#define PRINTFLAG_INDENT 0x40 + +/* printformat(...) = printformat_format(module_formats, ...) + + Could this be any harder? :) With GNU C compiler and C99 compilers, + use #define. With others use either inline functions if they are + supported or static functions if they are not.. + */ +#ifdef __GNUC__ +/* GCC */ +# define printformat(server, channel, level, formatnum...) \ + printformat_format(MODULE_FORMATS, server, channel, level, ##formatnum) +#elif defined (_ISOC99_SOURCE) +/* C99 */ +# define printformat(server, channel, level, formatnum, ...) \ + printformat_format(MODULE_FORMATS, server, channel, level, formatnum, __VA_ARGS__) +#else +/* inline/static */ +#ifdef G_CAN_INLINE +inline +#else +static +#endif +void printformat(void *server, const char *channel, int level, int formatnum, ...) +{ + printformat_format(MODULE_FORMATS, server, channel, level, ##formatnum); +} +#endif +void printformat_format(FORMAT_REC *formats, void *server, const char *channel, int level, int formatnum, ...); + +void printtext(void *server, const char *channel, int level, const char *str, ...); +void printbeep(void); + +void printtext_init(void); +void printtext_deinit(void); + +#endif diff --git a/src/fe-common/core/themes.c b/src/fe-common/core/themes.c new file mode 100644 index 00000000..0d9735ab --- /dev/null +++ b/src/fe-common/core/themes.c @@ -0,0 +1,278 @@ +/* + themes.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "printtext.h" +#include "themes.h" + +GSList *themes; +THEME_REC *current_theme; + +THEME_REC *theme_create(const char *path, const char *name) +{ + THEME_REC *rec; + + g_return_val_if_fail(path != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + rec = g_new0(THEME_REC, 1); + rec->path = g_strdup(path); + rec->name = g_strdup(name); + rec->modules = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + signal_emit("theme created", 1, rec); + + return rec; +} + +static void theme_destroy_hash(const char *key, MODULE_THEME_REC *rec) +{ + int n, max; + + max = strarray_length(rec->formatlist); + for (n = 0; n < max; n++) + if (rec->format[n] != NULL) + g_free(rec->format[n]); + g_free(rec->format); + + g_strfreev(rec->formatlist); + g_free(rec->name); + g_free(rec); +} + +void theme_destroy(THEME_REC *rec) +{ + signal_emit("theme destroyed", 1, rec); + g_hash_table_foreach(rec->modules, (GHFunc) theme_destroy_hash, NULL); + g_hash_table_destroy(rec->modules); + + if (rec->bg_pixmap != NULL) g_free(rec->bg_pixmap); + if (rec->font != NULL) g_free(rec->font); + g_free(rec->path); + g_free(rec->name); + g_free(rec); +} + +static THEME_REC *theme_find(const char *name) +{ + GSList *tmp; + + for (tmp = themes; tmp != NULL; tmp = tmp->next) { + THEME_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +/* Add all *.theme files from directory to themes */ +static void find_themes(gchar *path) +{ + DIR *dirp; + struct dirent *dp; + char *fname, *name; + int len; + + dirp = opendir(path); + if (dirp == NULL) return; + + while ((dp = readdir(dirp)) != NULL) { + len = strlen(dp->d_name); + if (len <= 6 || strcmp(dp->d_name+len-6, ".theme") != 0) + continue; + + name = g_strndup(dp->d_name, strlen(dp->d_name)-6); + if (!theme_find(name)) { + fname = g_strdup_printf("%s/%s", path, dp->d_name); + themes = g_slist_append(themes, theme_create(fname, name)); + g_free(fname); + } + g_free(name); + } + closedir(dirp); +} + +/* Read module texts into theme */ +static void theme_read_module_texts(const char *hashkey, MODULE_THEME_REC *rec, CONFIG_REC *config) +{ + CONFIG_NODE *formats; + GSList *tmp; + char **flist; + int n; + + formats = config_node_traverse(config, "moduleformats", FALSE); + if (formats == NULL) return; + + for (tmp = formats->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->key == NULL || node->value == NULL) + continue; + + for (n = 0, flist = rec->formatlist; *flist != NULL; flist++, n++) { + if (g_strcasecmp(*flist, node->key) == 0) { + rec->format[n] = g_strdup(node->value); + break; + } + } + } +} + +static int theme_read(THEME_REC *theme, const char *path) +{ + MODULE_THEME_REC *mrec; + CONFIG_REC *config; + CONFIG_NODE *formats; + GSList *tmp; + char *value; + int errors; + + config = config_open(path, -1); + if (config == NULL) { + /* didn't exist or no access? */ + theme->default_color = 15; + return FALSE; + } + + errors = config_parse(config) == -1; + + /* default color */ + theme->default_color = config_get_int(config, NULL, "default_color", 15); + /* get font */ + value = config_get_str(config, NULL, "font", NULL); + theme->font = (value == NULL || *value == '\0') ? NULL : g_strdup(value); + + /* get background pixmap */ + value = config_get_str(config, NULL, "bg_pixmap", NULL); + theme->bg_pixmap = (value == NULL || *value == '\0') ? NULL : g_strdup(value); + /* get background pixmap properties */ + if (config_get_bool(config, NULL, "bg_scrollable", FALSE)) + theme->flags |= THEME_FLAG_BG_SCROLLABLE; + if (config_get_bool(config, NULL, "bg_scaled", TRUE)) + theme->flags |= THEME_FLAG_BG_SCALED; + if (config_get_bool(config, NULL, "bg_shaded", FALSE)) + theme->flags |= THEME_FLAG_BG_SHADED; + + /* Read modules that are defined in this theme. */ + formats = config_node_traverse(config, "modules", FALSE); + if (formats != NULL) { + for (tmp = formats->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->key == NULL || node->value == NULL) + continue; + + mrec = g_new0(MODULE_THEME_REC, 1); + mrec->name = g_strdup(node->key); + mrec->formatlist = g_strsplit(node->value, " ", -1); + mrec->format = g_new0(char*, strarray_length(mrec->formatlist)); + g_hash_table_insert(theme->modules, mrec->name, mrec); + } + } + + /* Read the texts inside the plugin */ + g_hash_table_foreach(theme->modules, (GHFunc) theme_read_module_texts, config); + + if (errors) { + /* errors fixed - save the theme */ + if (config_write(config, NULL, 0660) == -1) { + /* we probably tried to save to global directory + where we didn't have access.. try saving it to + home dir instead. */ + char *str; + + /* check that we really didn't try to save + it to home dir.. */ + str = g_strdup_printf("%s/.irssi/", g_get_home_dir()); + if (strncmp(path, str, strlen(str)) != 0) { + g_free(str); + str = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(), g_basename(path)); + + config_write(config, str, 0660); + } + g_free(str); + } + } + config_close(config); + + return errors; +} + +static void sig_formats_error(void) +{ + signal_emit("gui dialog", 2, "warning", + "Your theme(s) had some old format strings, " + "these have been changed back to their default values."); + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_formats_error); +} + +void themes_init(void) +{ + THEME_REC *rec; + GSList *tmp; + const char *value; + char *str; + int errors; + + /* first there's default theme.. */ + str = g_strdup_printf("%s/.irssi/default.theme", g_get_home_dir()); + current_theme = theme_create(str, "default"); + current_theme->default_color = 15; + themes = g_slist_append(NULL, current_theme); + g_free(str); + + /* read list of themes */ + str = g_strdup_printf("%s/.irssi", g_get_home_dir()); + find_themes(str); + g_free(str); + find_themes(SYSCONFDIR"/irssi"); + + /* read formats for all themes */ + errors = FALSE; + for (tmp = themes; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (theme_read(rec, rec->path)) + errors = TRUE; + } + + if (errors) + signal_add("irssi init finished", (SIGNAL_FUNC) sig_formats_error); + + /* find the current theme to use */ + value = settings_get_str("current_theme"); + + rec = theme_find(value); + if (rec != NULL) current_theme = rec; +} + +void themes_deinit(void) +{ + /* free memory used by themes */ + g_slist_foreach(themes, (GFunc) theme_destroy, NULL); + g_slist_free(themes); + themes = NULL; +} diff --git a/src/fe-common/core/themes.h b/src/fe-common/core/themes.h new file mode 100644 index 00000000..7bd4a8f8 --- /dev/null +++ b/src/fe-common/core/themes.h @@ -0,0 +1,40 @@ +#ifndef __THEMES_H +#define __THEMES_H + +#define THEME_FLAG_BG_SCROLLABLE 0x0001 +#define THEME_FLAG_BG_SCALED 0x0002 +#define THEME_FLAG_BG_SHADED 0x0004 + +typedef struct +{ + char *name; + + char **formatlist; + char **format; +} +MODULE_THEME_REC; + +typedef struct { + char *path; + char *name; + + int default_color; + char *bg_pixmap; + char *font; + int flags; + + GHashTable *modules; + + gpointer gui_data; +} THEME_REC; + +extern GSList *themes; +extern THEME_REC *current_theme; + +THEME_REC *theme_create(const char *path, const char *name); +void theme_destroy(THEME_REC *rec); + +void themes_init(void); +void themes_deinit(void); + +#endif diff --git a/src/fe-common/core/translation.c b/src/fe-common/core/translation.c new file mode 100644 index 00000000..2713cc73 --- /dev/null +++ b/src/fe-common/core/translation.c @@ -0,0 +1,122 @@ +/* + translation.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "line-split.h" +#include "misc.h" +#include "settings.h" + +unsigned char translation_in[256], translation_out[256]; + +void translation_reset(void) +{ + int n; + + for (n = 0; n < 256; n++) + translation_in[n] = (unsigned char) n; + for (n = 0; n < 256; n++) + translation_out[n] = (unsigned char) n; +} + +void translate_output(char *text) +{ + while (*text != '\0') { + *text = (char) translation_out[(int) (unsigned char) *text]; + text++; + } +} + +#define gethex(a) \ + (isdigit(a) ? ((a)-'0') : (toupper(a)-'A'+10)) + +void translation_parse_line(const char *str, int *pos) +{ + const char *ptr; + int value; + + for (ptr = str; *ptr != '\0'; ptr++) { + if (ptr[0] != '0' || ptr[1] != 'x') + break; + ptr += 2; + + value = (gethex(ptr[0]) << 4) + gethex(ptr[1]); + if (*pos < 256) + translation_in[*pos] = (unsigned char) value; + else + translation_out[*pos-256] = (unsigned char) value; + (*pos)++; + + ptr += 2; + if (*ptr != ',') break; + } +} + +int translation_read(const char *file) +{ + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer; + int f, pos, ret, recvlen; + + g_return_val_if_fail(file != NULL, FALSE); + + path = convert_home(file); + f = open(file, O_RDONLY); + g_free(path); + + if (f == -1) return FALSE; + + pos = 0; buffer = NULL; + while (pos < 512) { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + if (ret <= 0) break; + + translation_parse_line(str, &pos); + } + line_split_free(buffer); + + close(f); + if (pos != 512) + translation_reset(); + return pos == 512; +} + +static void read_settings(void) +{ + translation_read(settings_get_str("translation")); +} + +void translation_init(void) +{ + translation_reset(); + + settings_add_str("misc", "translation", ""); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + read_settings(); +} + +void translation_deinit(void) +{ + read_settings(); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-common/core/translation.h b/src/fe-common/core/translation.h new file mode 100644 index 00000000..48b2c3dc --- /dev/null +++ b/src/fe-common/core/translation.h @@ -0,0 +1,12 @@ +#ifndef __TRANSLATION_H +#define __TRANSLATION_H + +extern unsigned char translation_in[256], translation_out[256]; + +int translation_read(const char *file); +void translate_output(char *text); + +void translation_init(void); +void translation_deinit(void); + +#endif diff --git a/src/fe-common/core/window-items.c b/src/fe-common/core/window-items.c new file mode 100644 index 00000000..ac4c30d5 --- /dev/null +++ b/src/fe-common/core/window-items.c @@ -0,0 +1,224 @@ +/* + window-items.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "server.h" +#include "settings.h" + +#include "levels.h" + +#include "printtext.h" +#include "windows.h" +#include "window-items.h" + +void window_add_item(WINDOW_REC *window, WI_ITEM_REC *item, int automatic) +{ + g_return_if_fail(window != NULL); + g_return_if_fail(item != NULL); + + MODULE_DATA_SET(item, window); + + if (window->items == NULL) { + window->active = item; + window->active_server = item->server; + } + + signal_emit("gui window item init", 1, item); + + if (!automatic || settings_get_bool("window_auto_change")) { + if (automatic) + signal_emit("window changed automatic", 1, window); + window_set_active(window); + } + + window->items = g_slist_append(window->items, item); + signal_emit("window item new", 2, window, item); + + if (!automatic || g_slist_length(window->items) == 1) { + window->active = NULL; + window_item_set_active(window, item); + } +} + +void window_remove_item(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + g_return_if_fail(item != NULL); + + if (g_slist_find(window->items, item) == NULL) + return; + + MODULE_DATA_SET(item, NULL); + window->items = g_slist_remove(window->items, item); + + if (window->active == item) { + window->active = window->items == NULL ? NULL : + window->items->data; + } + + signal_emit("window item remove", 2, window, item); +} + +WINDOW_REC *window_item_window(WI_ITEM_REC *item) +{ + g_return_val_if_fail(item != NULL, NULL); + + return MODULE_DATA(item); +} + +void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + + if (window->active != item) { + window->active = item; + if (item != NULL) window_change_server(window, window->active_server); + signal_emit("window item changed", 2, window, item); + } +} + +void window_item_change_server(WI_ITEM_REC *item, void *server) +{ + WINDOW_REC *window; + + g_return_if_fail(item != NULL); + + window = MODULE_DATA(item); + item->server = server; + + signal_emit("window item server changed", 2, window, item); + if (window->active == item) window_change_server(window, item->server); +} + +static WI_ITEM_REC *window_item_find_window(WINDOW_REC *window, void *server, const char *name) +{ + GSList *tmp; + + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if ((server == NULL || rec->server == server) && + g_strcasecmp(name, rec->name) == 0) return rec; + } + + return NULL; +} + +/* Find wanted window item by name. `server' can be NULL. */ +WI_ITEM_REC *window_item_find(void *server, const char *name) +{ + WI_ITEM_REC *item; + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + item = window_item_find_window(rec, server, name); + if (item != NULL) return item; + } + + return NULL; +} + +static int waiting_channels_get(WINDOW_REC *window, const char *tag) +{ + GSList *tmp; + + g_return_val_if_fail(window != NULL, FALSE); + g_return_val_if_fail(tag != NULL, FALSE); + + for (tmp = window->waiting_channels; tmp != NULL; tmp = tmp->next) { + if (g_strcasecmp(tmp->data, tag) == 0) { + g_free(tmp->data); + window->waiting_channels = g_slist_remove(window->waiting_channels, tmp->data); + return TRUE; + } + } + + return FALSE; +} + +void window_item_create(WI_ITEM_REC *item, int automatic) +{ + WINDOW_REC *window; + GSList *tmp; + char *str; + + g_return_if_fail(item != NULL); + + str = item->server == NULL ? NULL : + g_strdup_printf("%s %s", ((SERVER_REC *) item->server)->tag, item->name); + + window = NULL; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->items == NULL && rec->level == 0 && + (window == NULL || rec == active_win)) { + /* no items in this window, we should probably use it.. */ + window = rec; + } + + if (rec->waiting_channels != NULL && str != NULL) { + /* right name/server tag combination in + some waiting list? */ + if (waiting_channels_get(rec, str)) { + window = rec; + break; + } + } + } + g_free_not_null(str); + + if (window == NULL) { + /* create new window to use */ + window = window_create(item, automatic); + } else { + /* use existing window */ + window_add_item(window, item, automatic); + } +} + +static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + + if (g_slist_length(window->items) > 1) { + /* default to printing "talking with ...", + you can override it it you wish */ + printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE, + IRCTXT_TALKING_WITH, item->name); + } +} + +void window_items_init(void) +{ + signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed); +} + +void window_items_deinit(void) +{ + signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed); +} diff --git a/src/fe-common/core/window-items.h b/src/fe-common/core/window-items.h new file mode 100644 index 00000000..c760ee36 --- /dev/null +++ b/src/fe-common/core/window-items.h @@ -0,0 +1,22 @@ +#ifndef __WINDOW_ITEMS_H +#define __WINDOW_ITEMS_H + +#include "windows.h" + +/* Add/remove window item from `window' */ +void window_add_item(WINDOW_REC *window, WI_ITEM_REC *item, int automatic); +void window_remove_item(WINDOW_REC *window, WI_ITEM_REC *item); +/* Find a window for `item' and call window_add_item(). */ +void window_item_create(WI_ITEM_REC *item, int automatic); + +WINDOW_REC *window_item_window(WI_ITEM_REC *item); +void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item); +void window_item_change_server(WI_ITEM_REC *item, void *server); + +/* Find wanted window item by name. `server' can be NULL. */ +WI_ITEM_REC *window_item_find(void *server, const char *name); + +void window_items_init(void); +void window_items_deinit(void); + +#endif diff --git a/src/fe-common/core/windows.c b/src/fe-common/core/windows.c new file mode 100644 index 00000000..3a88833b --- /dev/null +++ b/src/fe-common/core/windows.c @@ -0,0 +1,466 @@ +/* + windows.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "server.h" +#include "settings.h" + +#include "levels.h" + +#include "printtext.h" +#include "windows.h" +#include "window-items.h" + +GSList *windows; +WINDOW_REC *active_win; + +static int window_get_new_refnum(void) +{ + WINDOW_REC *win; + GSList *tmp; + int refnum; + + refnum = 1; + tmp = windows; + while (tmp != NULL) { + win = tmp->data; + + if (refnum != win->refnum) { + tmp = tmp->next; + continue; + } + + refnum++; + tmp = windows; + } + + return refnum; +} + +WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic) +{ + WINDOW_REC *rec; + + rec = g_new0(WINDOW_REC, 1); + rec->refnum = window_get_new_refnum(); + + windows = g_slist_append(windows, rec); + signal_emit("window created", 2, rec, GINT_TO_POINTER(automatic)); + + if (item != NULL) window_add_item(rec, item, automatic); + if (windows->next == NULL || !automatic || settings_get_bool("window_auto_change")) { + if (automatic && windows->next != NULL) + signal_emit("window changed automatic", 1, rec); + window_set_active(rec); + } + return rec; +} + +void window_destroy(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + if (window->destroying) return; + window->destroying = TRUE; + + while (window->items != NULL) + window_remove_item(window, window->items->data); + + windows = g_slist_remove(windows, window); + signal_emit("window destroyed", 1, window); + + g_slist_foreach(window->waiting_channels, (GFunc) g_free, NULL); + g_slist_free(window->waiting_channels); + + g_free_not_null(window->name); + g_free(window); +} + +void window_set_active_num(int number) +{ + GSList *win; + + win = g_slist_nth(windows, number); + if (win == NULL) return; + + active_win = win->data; + signal_emit("window changed", 1, active_win); +} + +void window_set_active(WINDOW_REC *window) +{ + int number; + + number = g_slist_index(windows, window); + if (number == -1) return; + + active_win = window; + signal_emit("window changed", 1, active_win); +} + +void window_change_server(WINDOW_REC *window, void *server) +{ + window->active_server = server; + signal_emit("window server changed", 2, window, server); +} + +void window_set_name(WINDOW_REC *window, const char *name) +{ + g_free_not_null(window->name); + window->name = g_strdup(name); + + signal_emit("window name changed", 1, window); +} + +void window_set_level(WINDOW_REC *window, int level) +{ + g_return_if_fail(window != NULL); + + window->level = level; + signal_emit("window level changed", 1, window); +} + +WINDOW_REC *window_find_level(void *server, int level) +{ + WINDOW_REC *match; + GSList *tmp; + + match = NULL; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if ((server == NULL || rec->active_server == server) && + (rec->level & level)) { + if (server == NULL || rec->active_server == server) + return rec; + match = rec; + } + } + + return match; +} + +WINDOW_REC *window_find_closest(void *server, const char *name, int level) +{ + WINDOW_REC *window; + WI_ITEM_REC *item; + + /* match by name */ + item = name == NULL ? NULL : + window_item_find(server, name); + if (item != NULL) + return window_item_window(item); + + /* match by level */ + if (level != MSGLEVEL_HILIGHT) + level &= ~(MSGLEVEL_HILIGHT | MSGLEVEL_NOHILIGHT); + window = window_find_level(server, level); + if (window != NULL) return window; + + /* fallback to active */ + return active_win; +} + +static void cmd_window(const char *data, void *server, WI_ITEM_REC *item) +{ + command_runsub("window", data, server, item); +} + +static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + int type; + + g_return_if_fail(data != NULL); + + type = (g_strcasecmp(data, "hide") == 0 || g_strcasecmp(data, "tab") == 0) ? 1 : + (g_strcasecmp(data, "split") == 0 ? 2 : 0); + signal_emit("gui window create override", 1, GINT_TO_POINTER(type)); + + window = window_create(NULL, FALSE); + window_change_server(window, server); +} + +static void cmd_window_close(const char *data) +{ + /* destroy window unless it's the last one */ + if (windows->next != NULL) + window_destroy(active_win); +} + +/* return the first window number with the highest activity */ +static int window_highest_activity(WINDOW_REC *window) +{ + WINDOW_REC *rec; + GSList *tmp; + int max_num, max_act, through; + + max_num = 0; max_act = 0; through = FALSE; + + tmp = g_slist_find(windows, window); + for (;; tmp = tmp->next) { + if (tmp == NULL) { + tmp = windows; + through = TRUE; + } + + if (through && tmp->data == window) + break; + + rec = tmp->data; + + if (rec->new_data && max_act < rec->new_data) { + max_act = rec->new_data; + max_num = g_slist_index(windows, rec)+1; + } + } + + return max_num; +} + +/* channel name - first try channel from same server */ +static int window_find_name(WINDOW_REC *window, const char *name) +{ + WI_ITEM_REC *item; + int num; + + item = window_item_find(window->active_server, name); + if (item == NULL && window->active_server != NULL) { + /* not found from the active server - any server? */ + item = window_item_find(NULL, name); + } + + if (item == NULL) { + char *chan; + + /* still nothing? maybe user just left the # in front of + channel, try again with it.. */ + chan = g_strdup_printf("#%s", name); + item = window_item_find(window->active_server, chan); + if (item == NULL) item = window_item_find(NULL, chan); + g_free(chan); + } + + if (item == NULL) + return 0; + + /* get the window number */ + window = MODULE_DATA(item); + if (window == NULL) return 0; + + num = g_slist_index(windows, window); + return num < 0 ? 0 : num+1; +} + +static void cmd_window_goto(const char *data) +{ + int num; + + g_return_if_fail(data != NULL); + + num = 0; + if (g_strcasecmp(data, "active") == 0) + num = window_highest_activity(active_win); + else if (isdigit(*data)) + num = atol(data); + else + num = window_find_name(active_win, data); + + if (num > 0) + window_set_active_num(num-1); +} + +static void cmd_window_next(const char *data) +{ + int num; + + num = g_slist_index(windows, active_win)+1; + if (num >= g_slist_length(windows)) num = 0; + window_set_active_num(num); +} + +static void cmd_window_prev(const char *data) +{ + int num; + + num = g_slist_index(windows, active_win)-1; + if (num < 0) num = g_slist_length(windows)-1; + window_set_active_num(num); +} + +static void cmd_window_level(const char *data) +{ + g_return_if_fail(data != NULL); + + window_set_level(active_win, combine_level(active_win->level, data)); + printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, "Window level is now %s", + bits2level(active_win->level)); +} + +static void cmd_window_server(const char *data) +{ + SERVER_REC *server; + + g_return_if_fail(data != NULL); + + server = server_find_tag(data); + if (server == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_UNKNOWN_SERVER_TAG, data); + else if (active_win->active == NULL) { + window_change_server(active_win, server); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_SERVER_CHANGED, server->tag, server->connrec->address, + server->connrec->ircnet == NULL ? "" : server->connrec->ircnet); + } +} + +static void cmd_window_item_prev(const char *data, void *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + WI_ITEM_REC *last; + GSList *tmp; + + window = item == NULL ? NULL : MODULE_DATA(item); + if (window == NULL) return; + + last = NULL; + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if (rec != item) + last = rec; + else { + /* current channel. did we find anything? + if not, go to the last channel */ + if (last != NULL) break; + } + } + + if (last != NULL) + window_item_set_active(window, last); +} + +static void cmd_window_item_next(const char *data, void *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + WI_ITEM_REC *next; + GSList *tmp; + int gone; + + window = item == NULL ? NULL : MODULE_DATA(item); + if (window == NULL) return; + + next = NULL; gone = FALSE; + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if (rec == item) + gone = TRUE; + else { + if (gone) { + /* found the next channel */ + next = rec; + break; + } + + if (next == NULL) + next = rec; /* fallback to first channel */ + } + } + + if (next != NULL) + window_item_set_active(window, next); +} + +static void cmd_window_name(const char *data) +{ + window_set_name(active_win, data); +} + +static void sig_server_looking(void *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + /* try to keep some server assigned to windows.. */ + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->active_server == NULL) + window_change_server(rec, server); + } +} + +static void sig_server_disconnected(void *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->active_server == server) + window_change_server(rec, NULL); + } +} + +void windows_init(void) +{ + active_win = NULL; + settings_add_bool("lookandfeel", "window_auto_change", FALSE); + + command_bind("window", NULL, (SIGNAL_FUNC) cmd_window); + command_bind("window new", NULL, (SIGNAL_FUNC) cmd_window_new); + command_bind("window close", NULL, (SIGNAL_FUNC) cmd_window_close); + command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server); + command_bind("window goto", NULL, (SIGNAL_FUNC) cmd_window_goto); + command_bind("window prev", NULL, (SIGNAL_FUNC) cmd_window_prev); + command_bind("window next", NULL, (SIGNAL_FUNC) cmd_window_next); + command_bind("window level", NULL, (SIGNAL_FUNC) cmd_window_level); + command_bind("window item prev", NULL, (SIGNAL_FUNC) cmd_window_item_prev); + command_bind("window item next", NULL, (SIGNAL_FUNC) cmd_window_item_next); + command_bind("window name", NULL, (SIGNAL_FUNC) cmd_window_name); + signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("server connect failed", (SIGNAL_FUNC) sig_server_disconnected); +} + +void windows_deinit(void) +{ + command_unbind("window", (SIGNAL_FUNC) cmd_window); + command_unbind("window new", (SIGNAL_FUNC) cmd_window_new); + command_unbind("window close", (SIGNAL_FUNC) cmd_window_close); + command_unbind("window server", (SIGNAL_FUNC) cmd_window_server); + command_unbind("window goto", (SIGNAL_FUNC) cmd_window_goto); + command_unbind("window prev", (SIGNAL_FUNC) cmd_window_prev); + command_unbind("window next", (SIGNAL_FUNC) cmd_window_next); + command_unbind("window level", (SIGNAL_FUNC) cmd_window_level); + command_unbind("window item prev", (SIGNAL_FUNC) cmd_window_item_prev); + command_unbind("window item next", (SIGNAL_FUNC) cmd_window_item_next); + command_unbind("window name", (SIGNAL_FUNC) cmd_window_name); + signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_server_disconnected); +} diff --git a/src/fe-common/core/windows.h b/src/fe-common/core/windows.h new file mode 100644 index 00000000..f279e888 --- /dev/null +++ b/src/fe-common/core/windows.h @@ -0,0 +1,67 @@ +#ifndef __WINDOWS_H +#define __WINDOWS_H + +enum { + NEWDATA_TEXT = 1, + NEWDATA_MSG, + NEWDATA_MSG_FORYOU, + NEWDATA_CUSTOM +}; + +/* All window items *MUST* have these variables in same order + at the start of the structure - the server's type can of course be + replaced with the preferred record type. */ +typedef struct { + int type; + GHashTable *module_data; + + void *server; + char *name; + + int new_data; +} WI_ITEM_REC; + +typedef struct { + int refnum; + char *name; + + GSList *items; + WI_ITEM_REC *active; + void *active_server; + + GSList *waiting_channels; /* list of "<server tag> <channel>" */ + + int lines; + int destroying:1; + + /* window-specific command line history */ + GList *cmdhist, *histpos; + int histlines; + + int level; + int new_data; + time_t last_timestamp; /* When was last timestamp printed */ + + gpointer gui_data; +} WINDOW_REC; + +extern GSList *windows; +extern WINDOW_REC *active_win; + +WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic); +void window_destroy(WINDOW_REC *window); + +void window_set_active_num(int number); +void window_set_active(WINDOW_REC *window); +void window_change_server(WINDOW_REC *window, void *server); + +void window_set_name(WINDOW_REC *window, const char *name); + +void window_set_level(WINDOW_REC *window, int level); +WINDOW_REC *window_find_level(void *server, int level); +WINDOW_REC *window_find_closest(void *server, const char *name, int level); + +void windows_init(void); +void windows_deinit(void); + +#endif diff --git a/src/fe-common/irc/Makefile.am b/src/fe-common/irc/Makefile.am new file mode 100644 index 00000000..440f14bd --- /dev/null +++ b/src/fe-common/irc/Makefile.am @@ -0,0 +1,32 @@ +SUBDIRS = dcc flood notifylist + +noinst_LTLIBRARIES = libfe_common_irc.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + -DHELPDIR=\""$(datadir)/irssi/help"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +libfe_common_irc_la_SOURCES = \ + completion.c \ + fe-channels.c \ + fe-irc-commands.c \ + fe-ctcp.c \ + fe-events.c \ + fe-events-numeric.c \ + fe-ignore.c \ + fe-query.c \ + fe-common-irc.c \ + irc-nick-hilight.c \ + irc-hilight-text.c \ + module-formats.c + +noinst_HEADERS = \ + completion.h \ + fe-common-irc.h \ + irc-hilight-text.h \ + module-formats.h diff --git a/src/fe-common/irc/completion.c b/src/fe-common/irc/completion.c new file mode 100644 index 00000000..444a3a06 --- /dev/null +++ b/src/fe-common/irc/completion.c @@ -0,0 +1,628 @@ +/* + completion.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "irc.h" +#include "server.h" +#include "channels.h" +#include "nicklist.h" + +#include "completion.h" +#include "window-items.h" + +typedef struct { + time_t time; + char *nick; +} COMPLETION_REC; + +#define replace_find(replace) \ + iconfig_list_find("replaces", "text", replace, "replace") + +#define completion_find(completion) \ + iconfig_list_find("completions", "short", completion, "long") + +static gint comptag; +static GList *complist; + +static COMPLETION_REC *nick_completion_find(GSList *list, gchar *nick) +{ + GSList *tmp; + + for (tmp = list; tmp != NULL; tmp = tmp->next) + { + COMPLETION_REC *rec = tmp->data; + + if (g_strcasecmp(rec->nick, nick) == 0) return rec; + } + + return NULL; +} + +static void completion_destroy(GSList **list, COMPLETION_REC *rec) +{ + *list = g_slist_remove(*list, rec); + + g_free(rec->nick); + g_free(rec); +} + +static COMPLETION_REC *nick_completion_create(GSList **list, time_t time, gchar *nick) +{ + COMPLETION_REC *rec; + + rec = nick_completion_find(*list, nick); + if (rec != NULL) + { + /* remove the old one */ + completion_destroy(list, rec); + } + + rec = g_new(COMPLETION_REC, 1); + *list = g_slist_prepend(*list, rec); + + rec->time = time; + rec->nick = g_strdup(nick); + return rec; +} + +static void completion_checklist(GSList **list, gint timeout, time_t t) +{ + GSList *tmp, *next; + + for (tmp = *list; tmp != NULL; tmp = next) + { + COMPLETION_REC *rec = tmp->data; + + next = tmp->next; + if (t-rec->time > timeout) + completion_destroy(list, rec); + } +} + +static gint completion_timeout(void) +{ + GSList *tmp, *link; + time_t t; + gint len; + + t = time(NULL); + for (tmp = servers; tmp != NULL; tmp = tmp->next) + { + IRC_SERVER_REC *rec = tmp->data; + + len = g_slist_length(rec->lastmsgs); + if (len > 0 && len >= settings_get_int("completion_keep_privates")) + { + link = g_slist_last(rec->lastmsgs); + g_free(link->data); + rec->lastmsgs = g_slist_remove_link(rec->lastmsgs, link); + g_slist_free_1(link); + } + } + + for (tmp = channels; tmp != NULL; tmp = tmp->next) + { + CHANNEL_REC *rec = tmp->data; + + completion_checklist(&rec->lastownmsgs, settings_get_int("completion_keep_ownpublics"), t); + completion_checklist(&rec->lastmsgs, settings_get_int("completion_keep_publics"), t); + } + + return 1; +} + +static void add_private_msg(IRC_SERVER_REC *server, gchar *nick) +{ + GSList *link; + + link = gslist_find_icase_string(server->lastmsgs, nick); + if (link != NULL) + { + g_free(link->data); + server->lastmsgs = g_slist_remove_link(server->lastmsgs, link); + g_slist_free_1(link); + } + server->lastmsgs = g_slist_prepend(server->lastmsgs, g_strdup(nick)); +} + +static void event_privmsg(gchar *data, IRC_SERVER_REC *server, gchar *nick) +{ + gchar *params, *target, *msg; + GSList **list; + + g_return_if_fail(server != NULL); + if (nick == NULL) return; /* from server */ + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + + if (*msg == 1) + { + /* ignore ctcp messages */ + g_free(params); + return; + } + + if (ischannel(*target)) + { + /* channel message */ + CHANNEL_REC *channel; + + channel = channel_find(server, target); + if (channel == NULL) + { + g_free(params); + return; + } + + list = completion_msgtoyou((SERVER_REC *) server, msg) ? + &channel->lastownmsgs : + &channel->lastmsgs; + nick_completion_create(list, time(NULL), nick); + } + else + { + /* private message */ + add_private_msg(server, nick); + } + + g_free(params); +} + +static void cmd_msg(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *target, *msg; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target != '\0' && *msg != '\0') + { + if (!ischannel(*target) && *target != '=' && server != NULL) + add_private_msg(server, target); + } + + g_free(params); +} + +int completion_msgtoyou(SERVER_REC *server, const char *msg) +{ + gchar *stripped, *nick; + gboolean ret; + gint len; + + g_return_val_if_fail(msg != NULL, FALSE); + + if (g_strncasecmp(msg, server->nick, strlen(server->nick)) == 0 && + !isalnum((gint) msg[strlen(server->nick)])) return TRUE; + + stripped = nick_strip(server->nick); + nick = nick_strip(msg); + + len = strlen(stripped); + ret = *stripped != '\0' && + g_strncasecmp(nick, stripped, len) == 0 && + !isalnum((gint) nick[len]) && + (guchar) nick[len] < 128; + + g_free(nick); + g_free(stripped); + return ret; +} + +static void complete_list(GList **outlist, GSList *list, gchar *nick) +{ + GSList *tmp; + gint len; + + len = strlen(nick); + for (tmp = list; tmp != NULL; tmp = tmp->next) + { + COMPLETION_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + glist_find_icase_string(*outlist, rec->nick) == NULL) + *outlist = g_list_append(*outlist, g_strdup(rec->nick)); + } +} + +static GList *completion_getlist(CHANNEL_REC *channel, gchar *nick) +{ + GSList *nicks, *tmp; + GList *list; + gint len; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + if (*nick == '\0') return NULL; + + list = NULL; + complete_list(&list, channel->lastownmsgs, nick); + complete_list(&list, channel->lastmsgs, nick); + + len = strlen(nick); + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) + { + NICK_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + glist_find_icase_string(list, rec->nick) == NULL && + g_strcasecmp(rec->nick, channel->server->nick) != 0) + list = g_list_append(list, g_strdup(rec->nick)); + } + g_slist_free(nicks); + + return list; +} + +static GList *completion_getmsglist(IRC_SERVER_REC *server, gchar *nick) +{ + GSList *tmp; + GList *list; + gint len; + + list = NULL; len = strlen(nick); + for (tmp = server->lastmsgs; tmp != NULL; tmp = tmp->next) + { + if (len == 0 || g_strncasecmp(tmp->data, nick, len) == 0) + list = g_list_append(list, g_strdup(tmp->data)); + } + + return list; +} + +static void event_command(gchar *line, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + CHANNEL_REC *channel; + GList *comp; + gchar *str, *ptr; + + g_return_if_fail(line != NULL); + + if (!irc_item_check(item)) + return; + + if (strchr(settings_get_str("cmdchars"), *line) != NULL) + return; + + line = g_strdup(line); + + /* check for nick completion */ + if (settings_get_bool("completion_disable_auto") || *settings_get_str("completion_char") == '\0') + { + ptr = NULL; + comp = NULL; + } + else + { + ptr = strchr(line, *settings_get_str("completion_char")); + if (ptr != NULL) *ptr++ = '\0'; + + channel = irc_item_channel(item); + + comp = ptr == NULL || channel == NULL || + nicklist_find(channel, line) != NULL ? NULL : + completion_getlist(channel, line); + } + + /* message to channel */ + if (ptr == NULL) + str = g_strdup_printf("%s %s", item->name, line); + else + { + str = g_strdup_printf("%s %s%s%s", item->name, + comp != NULL ? (gchar *) comp->data : line, + settings_get_str("completion_char"), ptr); + } + signal_emit("command msg", 3, str, server, item); + + g_free(str); + g_free(line); + + if (comp != NULL) + { + g_list_foreach(comp, (GFunc) g_free, NULL); + g_list_free(comp); + } + + signal_stop(); +} + +static GList *completion_joinlist(GList *list1, GList *list2) +{ + while (list2 != NULL) + { + if (!glist_find_icase_string(list1, list2->data)) + list1 = g_list_append(list1, list2->data); + else + g_free(list2->data); + + list2 = list2->next; + } + g_list_free(list2); + return list1; +} + +char *auto_completion(const char *line, int *pos) +{ + const char *replace; + gchar *word, *ret; + gint spos, epos, n, wordpos; + GString *result; + + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(pos != NULL, NULL); + + spos = *pos; + + /* get the word we are completing.. */ + while (spos > 0 && isspace((gint) line[spos-1])) spos--; + epos = spos; + while (spos > 0 && !isspace((gint) line[spos-1])) spos--; + while (line[epos] != '\0' && !isspace((gint) line[epos])) epos++; + + word = g_strdup(line+spos); + word[epos-spos] = '\0'; + + /* word position in line */ + wordpos = 0; + for (n = 0; n < spos; ) + { + while (n < spos && isspace((gint) line[n])) n++; + while (n < spos && !isspace((gint) line[n])) n++; + if (n < spos) wordpos++; + } + + result = g_string_new(line); + g_string_erase(result, spos, epos-spos); + + /* check for words in autocompletion list */ + replace = replace_find(word); g_free(word); + if (replace != NULL) + { + *pos = spos+strlen(replace); + + g_string_insert(result, spos, replace); + ret = result->str; + g_string_free(result, FALSE); + return ret; + } + + g_string_free(result, TRUE); + return NULL; +} + +#define issplit(a) ((a) == ',' || (a) == ' ') + +char *completion_line(WINDOW_REC *window, const char *line, int *pos) +{ + static gboolean msgcomp = FALSE; + const char *completion; + CHANNEL_REC *channel; + SERVER_REC *server; + gchar *word, *ret; + gint spos, epos, len, n, wordpos; + gboolean msgcompletion; + GString *result; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(pos != NULL, NULL); + + spos = *pos; + + /* get the word we are completing.. */ + while (spos > 0 && issplit((gint) line[spos-1])) spos--; + epos = spos; + if (line[epos] == ',') epos++; + while (spos > 0 && !issplit((gint) line[spos-1])) spos--; + while (line[epos] != '\0' && !issplit((gint) line[epos])) epos++; + + word = g_strdup(line+spos); + word[epos-spos] = '\0'; + + /* word position in line */ + wordpos = 0; + for (n = 0; n < spos; ) + { + while (n < spos && issplit((gint) line[n])) n++; + while (n < spos && !issplit((gint) line[n])) n++; + if (n < spos) wordpos++; + } + + server = window->active == NULL ? window->active_server : window->active->server; + msgcompletion = server != NULL && + (*line == '\0' || ((wordpos == 0 || wordpos == 1) && g_strncasecmp(line, "/msg ", 5) == 0)); + + if (msgcompletion && wordpos == 0 && issplit((gint) line[epos])) + { + /* /msg <tab> */ + *word = '\0'; epos++; spos = epos; wordpos = 1; + } + + /* are we completing the same nick as last time? + if not, forget the old completion.. */ + len = strlen(word)-(msgcomp == FALSE && word[strlen(word)-1] == *settings_get_str("completion_char")); + if (complist != NULL && (strlen(complist->data) != len || g_strncasecmp(complist->data, word, len) != 0)) + { + g_list_foreach(complist, (GFunc) g_free, NULL); + g_list_free(complist); + + complist = NULL; + } + + result = g_string_new(line); + g_string_erase(result, spos, epos-spos); + + /* check for words in completion list */ + completion = completion_find(word); + if (completion != NULL) + { + g_free(word); + *pos = spos+strlen(completion); + + g_string_insert(result, spos, completion); + ret = result->str; + g_string_free(result, FALSE); + return ret; + } + + channel = irc_item_channel(window->active); + if (complist == NULL && !msgcompletion && channel == NULL) + { + /* don't try nick completion */ + g_free(word); + g_string_free(result, TRUE); + return NULL; + } + + if (complist == NULL) + { + /* start new nick completion */ + complist = channel == NULL ? NULL : completion_getlist(channel, word); + + if (!msgcompletion) + { + /* nick completion in channel */ + msgcomp = FALSE; + } + else + { + GList *tmpcomp; + + /* /msg completion */ + msgcomp = TRUE; + + /* first get the list of msg nicks and then nicks from current + channel. */ + tmpcomp = complist; + complist = completion_getmsglist((IRC_SERVER_REC *) server, word); + complist = completion_joinlist(complist, tmpcomp); + if (*line == '\0') + { + /* completion in empty line -> /msg <nick> */ + g_free(word); + g_string_free(result, TRUE); + + if (complist == NULL) + ret = g_strdup("/msg "); + else + ret = g_strdup_printf("/msg %s ", (gchar *) complist->data); + *pos = strlen(ret); + return ret; + } + } + + if (complist == NULL) + { + g_free(word); + g_string_free(result, TRUE); + return NULL; + } + } + else + { + /* continue the last completion */ + complist = complist->next == NULL ? g_list_first(complist) : complist->next; + } + g_free(word); + + /* insert the nick.. */ + g_string_insert(result, spos, complist->data); + *pos = spos+strlen(complist->data); + + if (!msgcomp && wordpos == 0) + { + /* insert completion character */ + g_string_insert(result, *pos, settings_get_str("completion_char")); + *pos += strlen(settings_get_str("completion_char")); + } + if (msgcomp || wordpos == 0) + { + if (!issplit((gint) result->str[*pos])) + { + /* insert space */ + g_string_insert(result, *pos, " "); + } + (*pos)++; + } + + ret = result->str; + g_string_free(result, FALSE); + return ret; +} + +static void completion_deinit_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_slist_foreach(server->lastmsgs, (GFunc) g_free, NULL); + g_slist_free(server->lastmsgs); +} + +static void completion_deinit_channel(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + while (channel->lastmsgs != NULL) + completion_destroy(&channel->lastmsgs, channel->lastmsgs->data); + while (channel->lastownmsgs != NULL) + completion_destroy(&channel->lastownmsgs, channel->lastownmsgs->data); + g_slist_free(channel->lastmsgs); + g_slist_free(channel->lastownmsgs); +} + +void completion_init(void) +{ + settings_add_str("completion", "completion_char", ":"); + settings_add_bool("completion", "completion_disable_auto", FALSE); + settings_add_int("completion", "completion_keep_publics", 180); + settings_add_int("completion", "completion_keep_ownpublics", 360); + settings_add_int("completion", "completion_keep_privates", 10); + + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("send command", (SIGNAL_FUNC) event_command); + signal_add("server disconnected", (SIGNAL_FUNC) completion_deinit_server); + signal_add("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + + comptag = g_timeout_add(1000, (GSourceFunc) completion_timeout, NULL); + complist = NULL; +} + +void completion_deinit(void) +{ + g_list_foreach(complist, (GFunc) g_free, NULL); + g_list_free(complist); + + g_source_remove(comptag); + + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("send command", (SIGNAL_FUNC) event_command); + signal_remove("server disconnected", (SIGNAL_FUNC) completion_deinit_server); + signal_remove("channel destroyed", (SIGNAL_FUNC) completion_deinit_channel); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); +} diff --git a/src/fe-common/irc/completion.h b/src/fe-common/irc/completion.h new file mode 100644 index 00000000..3ecb0339 --- /dev/null +++ b/src/fe-common/irc/completion.h @@ -0,0 +1,13 @@ +#ifndef __COMPLETION_H +#define __COMPLETION_H + +#include "window-items.h" + +int completion_msgtoyou(SERVER_REC *server, const char *msg); +char *completion_line(WINDOW_REC *window, const char *line, int *pos); +char *auto_completion(const char *line, int *pos); + +void completion_init(void); +void completion_deinit(void); + +#endif diff --git a/src/fe-common/irc/dcc/Makefile.am b/src/fe-common/irc/dcc/Makefile.am new file mode 100644 index 00000000..0424c7b2 --- /dev/null +++ b/src/fe-common/irc/dcc/Makefile.am @@ -0,0 +1,17 @@ +noinst_LTLIBRARIES = libfe_common_irc_dcc.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + -DHELPDIR=\""$(datadir)/irssi/help"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +libfe_common_irc_dcc_la_SOURCES = \ + fe-dcc.c \ + module-formats.c + +noinst_HEADERS = \ + module-formats.h diff --git a/src/fe-common/irc/dcc/fe-dcc.c b/src/fe-common/irc/dcc/fe-dcc.c new file mode 100644 index 00000000..d798b0a3 --- /dev/null +++ b/src/fe-common/irc/dcc/fe-dcc.c @@ -0,0 +1,457 @@ +/* + fe-dcc.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "network.h" + +#include "levels.h" +#include "irc.h" +#include "channels.h" + +#include "irc/dcc/dcc.h" + +#include "windows.h" + +static void dcc_connected(DCC_REC *dcc) +{ + gchar *str; + + g_return_if_fail(dcc != NULL); + + switch (dcc->dcc_type) + { + case DCC_TYPE_CHAT: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CHAT_CONNECTED, + dcc->nick, dcc->addrstr, dcc->port); + + str = g_strconcat("=", dcc->nick, NULL); + /*FIXME: dcc_chat_create(dcc->server, str, FALSE);*/ + g_free(str); + break; + case DCC_TYPE_SEND: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_CONNECTED, + dcc->arg, dcc->nick, dcc->addrstr, dcc->port); + break; + case DCC_TYPE_GET: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_CONNECTED, + dcc->arg, dcc->nick, dcc->addrstr, dcc->port); + break; + } +} + +static void dcc_rejected(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CLOSE, + dcc_type2str(dcc->dcc_type), dcc->nick, dcc->arg); +} + +static void dcc_closed(DCC_REC *dcc) +{ + time_t secs; + gdouble kbs; + + g_return_if_fail(dcc != NULL); + + secs = dcc->starttime == 0 ? -1 : time(NULL)-dcc->starttime; + kbs = (gdouble) (dcc->transfd-dcc->skipped) / (secs == 0 ? 1 : secs) / 1024.0; + + switch (dcc->dcc_type) + { + case DCC_TYPE_CHAT: + { + /* nice kludge :) if connection was lost, close the channel. + after closed channel (can be done with /unquery too) + prints the disconnected-text.. */ + CHANNEL_REC *channel; + gchar *str; + + str = g_strdup_printf("=%s", dcc->nick); + printformat(dcc->server, str, MSGLEVEL_DCC, + IRCTXT_DCC_CHAT_DISCONNECTED, dcc->nick); + + channel = channel_find(dcc->server, str); + if (channel != NULL) + channel_destroy(channel); + g_free(str); + } + break; + case DCC_TYPE_SEND: + if (secs == -1) + { + /* aborted */ + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_ABORTED, + dcc->arg, dcc->nick); + } + else + { + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_COMPLETE, + dcc->arg, dcc->transfd/1024, dcc->nick, (glong) secs, kbs); + } + break; + case DCC_TYPE_GET: + if (secs == -1) + { + /* aborted */ + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_ABORTED, + dcc->arg, dcc->nick); + } + else + { + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_COMPLETE, + dcc->arg, dcc->transfd/1024, dcc->nick, (glong) secs, kbs); + } + break; + } +} + +static void dcc_chat_in_action(gchar *msg, DCC_REC *dcc) +{ + gchar *sender; + + g_return_if_fail(dcc != NULL); + g_return_if_fail(msg != NULL); + + sender = g_strconcat("=", dcc->nick, NULL); + printformat(NULL, sender, MSGLEVEL_DCC, + IRCTXT_ACTION_DCC, dcc->nick, msg); + g_free(sender); +} + +static void dcc_chat_ctcp(gchar *msg, DCC_REC *dcc) +{ + gchar *sender; + + g_return_if_fail(dcc != NULL); + g_return_if_fail(msg != NULL); + + sender = g_strconcat("=", dcc->nick, NULL); + printformat(NULL, sender, MSGLEVEL_DCC, IRCTXT_DCC_CTCP, dcc->nick, msg); + g_free(sender); +} + +static void dcc_chat_msg(DCC_REC *dcc, gchar *msg) +{ + gchar *nick; + + g_return_if_fail(dcc != NULL); + g_return_if_fail(msg != NULL); + + nick = g_strconcat("=", dcc->nick, NULL); + printformat(NULL, nick, MSGLEVEL_DCC, IRCTXT_DCC_MSG, dcc->nick, msg); + g_free(nick); +} + +static void dcc_request(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + + switch (dcc->dcc_type) + { + case DCC_TYPE_CHAT: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CHAT, + dcc->nick, dcc->addrstr, dcc->port); + break; + case DCC_TYPE_GET: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND, + dcc->nick, dcc->addrstr, dcc->port, dcc->arg, dcc->size); + break; + } +} + +static void dcc_error_connect(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CONNECT_ERROR, dcc->addrstr, dcc->port); +} + +static void dcc_error_file_create(DCC_REC *dcc, gchar *fname) +{ + g_return_if_fail(dcc != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CANT_CREATE, fname); +} + +static void dcc_error_file_not_found(gchar *nick, gchar *fname) +{ + g_return_if_fail(nick != NULL); + g_return_if_fail(fname != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_FILE_NOT_FOUND, fname); +} + +static void dcc_error_get_not_found(gchar *nick) +{ + g_return_if_fail(nick != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_NOT_FOUND, nick); +} + +static void dcc_error_send_exists(gchar *nick, gchar *fname) +{ + g_return_if_fail(nick != NULL); + g_return_if_fail(fname != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_EXISTS, fname, nick); +} + +static void dcc_error_unknown_type(gchar *type) +{ + g_return_if_fail(type != NULL); + + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_TYPE, type); +} + +static void dcc_error_close_not_found(gchar *type, gchar *nick, gchar *fname) +{ + g_return_if_fail(type != NULL); + g_return_if_fail(nick != NULL); + g_return_if_fail(fname != NULL); + + if (fname == '\0') fname = "(ANY)"; + switch (dcc_str2type(type)) + { + case DCC_TYPE_CHAT: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CHAT_NOT_FOUND, nick); + break; + case DCC_TYPE_SEND: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_SEND_NOT_FOUND, nick, fname); + break; + case DCC_TYPE_GET: + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_NOT_FOUND, nick, fname); + break; + } +} + +static void dcc_unknown_ctcp(gchar *data, gchar *sender) +{ + gchar *params, *type, *args; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &type, &args); + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_CTCP, type, sender, args); + g_free(params); +} + +static void dcc_unknown_reply(gchar *data, gchar *sender) +{ + gchar *params, *type, *args; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &type, &args); + printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_REPLY, type, sender, args); + g_free(params); +} + +static void dcc_chat_write(gchar *data) +{ + DCC_REC *dcc; + gchar *params, *text, *target; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &text); + + if (*target == '=') + { + /* dcc msg */ + dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL); + if (dcc == NULL) + { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + IRCTXT_DCC_CHAT_NOT_FOUND, target+1); + return; + } + + printformat(NULL, target, MSGLEVEL_DCC, IRCTXT_OWN_DCC, target+1, text); + } + + g_free(params); +} + +static void dcc_chat_out_me(gchar *data, SERVER_REC *server, WI_IRC_REC *item) +{ + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + dcc = irc_item_dcc_chat(item); + if (dcc == NULL) return; + + printformat(NULL, item->name, MSGLEVEL_DCC, + IRCTXT_OWN_ME, dcc->mynick, data); +} + +static void dcc_chat_out_action(const char *data, SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *target, *text; + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + if (*data != '=') { + /* handle only DCC actions */ + return; + } + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &text); + if (*target == '\0' || *text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL); + if (dcc == NULL){ + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + IRCTXT_DCC_CHAT_NOT_FOUND, target+1); + } else { + printformat(NULL, item->name, MSGLEVEL_DCC, + IRCTXT_OWN_ME, dcc->mynick, text); + } + g_free(params); +} + +static void dcc_chat_out_ctcp(gchar *data, SERVER_REC *server) +{ + char *params, *target, *ctcpcmd, *ctcpdata; + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target != '=') { + /* handle only DCC CTCPs */ + g_free(params); + return; + } + + dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL); + if (dcc == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + IRCTXT_DCC_CHAT_NOT_FOUND, target+1); + } else { + g_strup(ctcpcmd); + printformat(server, target, MSGLEVEL_DCC, IRCTXT_OWN_CTCP, + target, ctcpcmd, ctcpdata); + } + + g_free(params); +} + +static void cmd_dcc_list(gchar *data) +{ + GSList *tmp; + time_t going; + + g_return_if_fail(data != NULL); + + printtext(NULL, NULL, MSGLEVEL_DCC, "%gDCC connections"); + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + DCC_REC *dcc = tmp->data; + + going = time(NULL) - dcc->starttime; + if (going == 0) going = 1; /* no division by zeros :) */ + + if (dcc->dcc_type == DCC_TYPE_CHAT) + printtext(NULL, NULL, MSGLEVEL_DCC, "%g %s %s", dcc->nick, dcc_type2str(dcc->dcc_type)); + else + printtext(NULL, NULL, MSGLEVEL_DCC, "%g %s %s: %luk of %luk (%d%%) - %fkB/s - %s", + dcc->nick, dcc_type2str(dcc->dcc_type), dcc->transfd/1024, dcc->size/1024, + dcc->size == 0 ? 0 : (100*dcc->transfd/dcc->size), + (gdouble) (dcc->transfd-dcc->skipped)/going/1024, dcc->arg); + } +} + +static void dcc_chat_closed(WINDOW_REC *window, WI_IRC_REC *item) +{ + DCC_REC *dcc; + + dcc = irc_item_dcc_chat(item); + if (dcc == NULL) return; + + /* check that we haven't got here from dcc_destroy() so we won't try to + close the dcc again.. */ + if (!dcc->destroyed) { + /* DCC query window closed, close the dcc chat too. */ + dcc_destroy(dcc); + } +} + +void fe_dcc_init(void) +{ + signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected); + signal_add("dcc rejected", (SIGNAL_FUNC) dcc_rejected); + signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed); + signal_add("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_add("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_in_action); + signal_add("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp); + signal_add("dcc request", (SIGNAL_FUNC) dcc_request); + signal_add("dcc error connect", (SIGNAL_FUNC) dcc_error_connect); + signal_add("dcc error file create", (SIGNAL_FUNC) dcc_error_file_create); + signal_add("dcc error file not found", (SIGNAL_FUNC) dcc_error_file_not_found); + signal_add("dcc error get not found", (SIGNAL_FUNC) dcc_error_get_not_found); + signal_add("dcc error send exists", (SIGNAL_FUNC) dcc_error_send_exists); + signal_add("dcc error unknown type", (SIGNAL_FUNC) dcc_error_unknown_type); + signal_add("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found); + signal_add("dcc unknown ctcp", (SIGNAL_FUNC) dcc_unknown_ctcp); + signal_add("dcc unknown reply", (SIGNAL_FUNC) dcc_unknown_reply); + command_bind("msg", NULL, (SIGNAL_FUNC) dcc_chat_write); + command_bind("me", NULL, (SIGNAL_FUNC) dcc_chat_out_me); + command_bind("action", NULL, (SIGNAL_FUNC) dcc_chat_out_action); + command_bind("ctcp", NULL, (SIGNAL_FUNC) dcc_chat_out_ctcp); + command_bind("dcc ", NULL, (SIGNAL_FUNC) cmd_dcc_list); + command_bind("dcc list", NULL, (SIGNAL_FUNC) cmd_dcc_list); + signal_add("window item remove", (SIGNAL_FUNC) dcc_chat_closed); +} + +void fe_dcc_deinit(void) +{ + signal_remove("dcc connected", (SIGNAL_FUNC) dcc_connected); + signal_remove("dcc rejected", (SIGNAL_FUNC) dcc_rejected); + signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed); + signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_remove("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_in_action); + signal_remove("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp); + signal_remove("dcc request", (SIGNAL_FUNC) dcc_request); + signal_remove("dcc error connect", (SIGNAL_FUNC) dcc_error_connect); + signal_remove("dcc error file create", (SIGNAL_FUNC) dcc_error_file_create); + signal_remove("dcc error file not found", (SIGNAL_FUNC) dcc_error_file_not_found); + signal_remove("dcc error get not found", (SIGNAL_FUNC) dcc_error_get_not_found); + signal_remove("dcc error send exists", (SIGNAL_FUNC) dcc_error_send_exists); + signal_remove("dcc error unknown type", (SIGNAL_FUNC) dcc_error_unknown_type); + signal_remove("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found); + signal_remove("dcc unknown ctcp", (SIGNAL_FUNC) dcc_unknown_ctcp); + signal_remove("dcc unknown reply", (SIGNAL_FUNC) dcc_unknown_reply); + command_unbind("msg", (SIGNAL_FUNC) dcc_chat_write); + command_unbind("me", (SIGNAL_FUNC) dcc_chat_out_me); + command_unbind("action", (SIGNAL_FUNC) dcc_chat_out_action); + command_unbind("ctcp", (SIGNAL_FUNC) dcc_chat_out_ctcp); + command_unbind("dcc ", (SIGNAL_FUNC) cmd_dcc_list); + command_unbind("dcc list", (SIGNAL_FUNC) cmd_dcc_list); + signal_remove("window item remove", (SIGNAL_FUNC) dcc_chat_closed); +} diff --git a/src/fe-common/irc/dcc/module-formats.c b/src/fe-common/irc/dcc/module-formats.c new file mode 100644 index 00000000..d26fbf49 --- /dev/null +++ b/src/fe-common/irc/dcc/module-formats.c @@ -0,0 +1,57 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC fecommon_irc_dcc_formats[] = +{ + { MODULE_NAME, N_("IRC"), 0 }, + + /* ---- */ + { NULL, N_("DCC"), 0 }, + + { "own_dcc", N_("%K[%rdcc%K(%R$0%K)]%n $1"), 2, { 0, 0 } }, + { "dcc_msg", N_("%K[%G$0%K(%gdcc%K)]%n $1"), 2, { 0, 0 } }, + { "action_dcc", N_("%W (*dcc*) $0%n $1"), 2, { 0, 0 } }, + { "dcc_ctcp", N_("%g>>> DCC CTCP received from %_$0%_%K: %g$1"), 2, { 0, 0 } }, + { "dcc_chat", N_("%gDCC CHAT from %_$0%_ %K[%g$1 port $2%K]"), 3, { 0, 0, 1 } }, + { "dcc_chat_not_found", N_("%gNo DCC CHAT connection open to %_$0"), 1, { 0 } }, + { "dcc_chat_connected", N_("%gDCC %_CHAT%_ connection with %_$0%_ %K%K[%g$1 port $2%K]%g established"), 3, { 0, 0, 1 } }, + { "dcc_chat_disconnected", N_("%gDCC lost chat to %_$0"), 1, { 0 } }, + { "dcc_send", N_("%gDCC SEND from %_$0%_ %K[%g$1 port $2%K]: %g$3 %K[%g$4 bytes%K]"), 5, { 0, 0, 1, 0, 2 } }, + { "dcc_send_exists", N_("%gDCC already sending file %G$0%g for %_$1%_"), 2, { 0, 0 } }, + { "dcc_send_not_found", N_("%gDCC not sending file %G$1%g to %_$0"), 2, { 0, 0 } }, + { "dcc_send_file_not_found", N_("%gDCC file not found: %G$0%g"), 1, { 0 } }, + { "dcc_send_connected", N_("%gDCC sending file %G$0%g for %_$1%_ %K[%g$2 port $3%K]"), 4, { 0, 0, 0, 1 } }, + { "dcc_send_complete", N_("%gDCC sent file $0 %K[%g%_$1%_kb%K]%g for %_$2%_ in %_$3%_ secs %K[%g%_$4kb/s%_%K]"), 5, { 0, 2, 0, 2, 3 } }, + { "dcc_send_aborted", N_("%gDCC aborted sending file $0 for %_$1%_"), 2, { 0, 0 } }, + { "dcc_get_not_found", N_("%gDCC no file offered by %_$0"), 1, { 0 } }, + { "dcc_get_connected", N_("%gDCC receiving file %G$0%g from %_$1%_ %K[%g$2 port $3%K]"), 4, { 0, 0, 0, 1 } }, + { "dcc_get_complete", N_("%gDCC received file %G$0%g %K[%g$1kb%K]%g from %_$2%_ in %_$3%_ secs %K[%g$4kb/s%K]"), 5, { 0, 2, 0, 2, 3 } }, + { "dcc_get_aborted", N_("%gDCC aborted receiving file $0 from %_$1%_"), 2, { 0, 0 } }, + { "dcc_unknown_ctcp", N_("%gDCC unknown ctcp %G$0%g from %_$1%_ %K[%g$2%K]"), 3, { 0, 0, 0 } }, + { "dcc_unknown_reply", N_("%gDCC unknown reply %G$0%g from %_$1%_ %K[%g$2%K]"), 3, { 0, 0, 0 } }, + { "dcc_unknown_type", N_("%gDCC unknown type %_$0"), 1, { 0 } }, + { "dcc_connect_error", N_("%gDCC can't connect to %_$0%_ port %_$1"), 2, { 0, 1 } }, + { "dcc_cant_create", N_("%gDCC can't create file %G$0%g"), 1, { 0 } }, + { "dcc_rejected", N_("%gDCC %G$0%g was rejected by %_$1%_ %K[%G$2%K]"), 3, { 0, 0, 0 } }, + { "dcc_close", N_("%gDCC %G$0%g close for %_$1%_ %K[%G$2%K]"), 3, { 0, 0, 0 } } +}; diff --git a/src/fe-common/irc/dcc/module-formats.h b/src/fe-common/irc/dcc/module-formats.h new file mode 100644 index 00000000..ef0459db --- /dev/null +++ b/src/fe-common/irc/dcc/module-formats.h @@ -0,0 +1,37 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_FILL_1, + + IRCTXT_OWN_DCC, + IRCTXT_DCC_MSG, + IRCTXT_ACTION_DCC, + IRCTXT_DCC_CTCP, + IRCTXT_DCC_CHAT, + IRCTXT_DCC_CHAT_NOT_FOUND, + IRCTXT_DCC_CHAT_CONNECTED, + IRCTXT_DCC_CHAT_DISCONNECTED, + IRCTXT_DCC_SEND, + IRCTXT_DCC_SEND_EXISTS, + IRCTXT_DCC_SEND_NOT_FOUND, + IRCTXT_DCC_SEND_FILE_NOT_FOUND, + IRCTXT_DCC_SEND_CONNECTED, + IRCTXT_DCC_SEND_COMPLETE, + IRCTXT_DCC_SEND_ABORTED, + IRCTXT_DCC_GET_NOT_FOUND, + IRCTXT_DCC_GET_CONNECTED, + IRCTXT_DCC_GET_COMPLETE, + IRCTXT_DCC_GET_ABORTED, + IRCTXT_DCC_UNKNOWN_CTCP, + IRCTXT_DCC_UNKNOWN_REPLY, + IRCTXT_DCC_UNKNOWN_TYPE, + IRCTXT_DCC_CONNECT_ERROR, + IRCTXT_DCC_CANT_CREATE, + IRCTXT_DCC_REJECTED, + IRCTXT_DCC_CLOSE, +}; + +extern FORMAT_REC fecommon_irc_dcc_formats[]; +#define MODULE_FORMATS fecommon_irc_dcc_formats diff --git a/src/fe-common/irc/fe-channels.c b/src/fe-common/irc/fe-channels.c new file mode 100644 index 00000000..c95e6411 --- /dev/null +++ b/src/fe-common/irc/fe-channels.c @@ -0,0 +1,123 @@ +/* + fe-channels.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" + +#include "irc.h" +#include "channels.h" + +#include "windows.h" +#include "window-items.h" + +static void signal_channel_created(CHANNEL_REC *channel, gpointer automatic) +{ + window_item_create((WI_ITEM_REC *) channel, GPOINTER_TO_INT(automatic)); +} + +static void signal_channel_created_curwin(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + window_add_item(active_win, (WI_ITEM_REC *) channel, FALSE); + signal_stop(); +} + +static void signal_channel_destroyed(CHANNEL_REC *channel) +{ + WINDOW_REC *window; + + g_return_if_fail(channel != NULL); + + window = window_item_window((WI_ITEM_REC *) channel); + if (window != NULL) window_remove_item(window, (WI_ITEM_REC *) channel); +} + +static void signal_window_item_removed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + CHANNEL_REC *channel; + + g_return_if_fail(window != NULL); + + channel = irc_item_channel(item); + if (channel != NULL) channel_destroy(channel); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + WINDOW_REC *window; + GSList *tmp; + + g_return_if_fail(server != NULL); + if (!irc_server_check(server)) + return; + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + window = window_item_window((WI_ITEM_REC *) channel); + window->waiting_channels = + g_slist_append(window->waiting_channels, g_strdup_printf("%s %s", server->tag, channel->name)); + } +} + +static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(item != NULL); + + if (g_slist_length(window->items) > 1 && irc_item_channel(item)) { + printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE, + IRCTXT_TALKING_IN, item->name); + signal_stop(); + } +} + +static void cmd_wjoin(const char *data, void *server, WI_ITEM_REC *item) +{ + signal_add("channel created", (SIGNAL_FUNC) signal_channel_created_curwin); + signal_emit("command join", 3, data, server, item); + signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created_curwin); +} + +void fe_channels_init(void) +{ + signal_add("channel created", (SIGNAL_FUNC) signal_channel_created); + signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed); + signal_add("window item remove", (SIGNAL_FUNC) signal_window_item_removed); + signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + + command_bind("wjoin", NULL, (SIGNAL_FUNC) cmd_wjoin); +} + +void fe_channels_deinit(void) +{ + signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed); + signal_remove("window item remove", (SIGNAL_FUNC) signal_window_item_removed); + signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + + command_unbind("wjoin", (SIGNAL_FUNC) cmd_wjoin); +} diff --git a/src/fe-common/irc/fe-common-irc.c b/src/fe-common/irc/fe-common-irc.c new file mode 100644 index 00000000..b8166f5e --- /dev/null +++ b/src/fe-common/irc/fe-common-irc.c @@ -0,0 +1,172 @@ +/* + fe-common-irc.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "args.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "server-setup.h" + +#include "completion.h" + +void fe_channels_init(void); +void fe_channels_deinit(void); + +void fe_irc_commands_init(void); +void fe_irc_commands_deinit(void); + +void fe_ctcp_init(void); +void fe_ctcp_deinit(void); + +void fe_dcc_init(void); +void fe_dcc_deinit(void); + +void fe_events_init(void); +void fe_events_deinit(void); + +void fe_events_numeric_init(void); +void fe_events_numeric_deinit(void); + +void fe_ignore_init(void); +void fe_ignore_deinit(void); + +void fe_query_init(void); +void fe_query_deinit(void); + +void irc_nick_hilight_init(void); +void irc_nick_hilight_deinit(void); + +void fe_notifylist_init(void); +void fe_notifylist_deinit(void); + +void fe_flood_init(void); +void fe_flood_deinit(void); + +static char *autocon_server; +static char *autocon_password; +static int autocon_port; +static int no_autoconnect; +static char *cmdline_nick; +static char *cmdline_hostname; + +void fe_common_irc_init(void) +{ + static struct poptOption options[] = { + { "connect", 'c', POPT_ARG_STRING, &autocon_server, 0, N_("Automatically connect to server/ircnet"), N_("SERVER") }, + { "password", 'w', POPT_ARG_STRING, &autocon_password, 0, N_("Autoconnect password"), N_("SERVER") }, + { "port", 'p', POPT_ARG_INT, &autocon_port, 0, N_("Autoconnect port"), N_("PORT") }, + { "noconnect", '!', POPT_ARG_NONE, &no_autoconnect, 0, N_("Disable autoconnecting"), NULL }, + { "nick", 'n', POPT_ARG_STRING, &cmdline_nick, 0, N_("Specify nick to use"), NULL }, + { "hostname", 'h', POPT_ARG_STRING, &cmdline_hostname, 0, N_("Specify host name to use"), NULL }, + { NULL, '\0', 0, NULL } + }; + + autocon_server = NULL; + autocon_password = NULL; + autocon_port = 6667; + no_autoconnect = FALSE; + cmdline_nick = NULL; + cmdline_hostname = NULL; + args_register(options); + + settings_add_str("lookandfeel", "beep_on_msg", ""); + settings_add_bool("lookandfeel", "beep_when_away", TRUE); + settings_add_bool("lookandfeel", "show_away_once", TRUE); + settings_add_bool("lookandfeel", "show_quit_once", FALSE); + + fe_channels_init(); + fe_irc_commands_init(); + fe_ctcp_init(); + fe_dcc_init(); + fe_events_init(); + fe_events_numeric_init(); + fe_ignore_init(); + fe_notifylist_init(); + fe_flood_init(); + fe_query_init(); + completion_init(); + irc_nick_hilight_init(); +} + +void fe_common_irc_deinit(void) +{ + fe_channels_deinit(); + fe_irc_commands_deinit(); + fe_ctcp_deinit(); + fe_dcc_deinit(); + fe_events_deinit(); + fe_events_numeric_deinit(); + fe_ignore_deinit(); + fe_notifylist_deinit(); + fe_flood_deinit(); + fe_query_deinit(); + completion_deinit(); + irc_nick_hilight_deinit(); +} + +void fe_common_irc_finish_init(void) +{ + GSList *tmp, *ircnets; + char *str; + + if (cmdline_nick != NULL) { + /* override nick found from setup */ + iconfig_set_str("settings", "default_nick", cmdline_nick); + } + + if (cmdline_hostname != NULL) { + /* override host name found from setup */ + iconfig_set_str("settings", "hostname", cmdline_hostname); + } + + if (autocon_server != NULL) { + /* connect to specified server */ + str = g_strdup_printf(autocon_password == NULL ? "%s %d" : "%s %d %s", + autocon_server, autocon_port, autocon_password); + signal_emit("command connect", 1, str); + g_free(str); + return; + } + + if (no_autoconnect) { + /* don't autoconnect */ + return; + } + + /* connect to autoconnect servers */ + ircnets = NULL; + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SETUP_SERVER_REC *rec = tmp->data; + + if (rec->autoconnect && (*rec->ircnet == '\0' || gslist_find_icase_string(ircnets, rec->ircnet) == NULL)) { + if (*rec->ircnet != '\0') + ircnets = g_slist_append(ircnets, rec->ircnet); + + str = g_strdup_printf("%s %d", rec->server, rec->port); + signal_emit("command connect", 1, str); + g_free(str); + } + } + + g_slist_free(ircnets); +} diff --git a/src/fe-common/irc/fe-common-irc.h b/src/fe-common/irc/fe-common-irc.h new file mode 100644 index 00000000..0ad3a564 --- /dev/null +++ b/src/fe-common/irc/fe-common-irc.h @@ -0,0 +1,8 @@ +#ifndef __FE_COMMON_IRC_H +#define __FE_COMMON_IRC_H + +void fe_common_irc_init(void); +void fe_common_irc_deinit(void); +void fe_common_irc_finish_init(void); + +#endif diff --git a/src/fe-common/irc/fe-ctcp.c b/src/fe-common/irc/fe-ctcp.c new file mode 100644 index 00000000..a8d9a1c5 --- /dev/null +++ b/src/fe-common/irc/fe-ctcp.c @@ -0,0 +1,110 @@ +/* + fe-ctcp.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "levels.h" +#include "server.h" +#include "channels.h" +#include "query.h" +#include "ignore.h" + +#include "windows.h" +#include "window-items.h" + +static void ctcp_print(const char *pre, const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + char *str; + + g_return_if_fail(data != NULL); + + str = g_strconcat(pre, " ", data, NULL); + printformat(server, ischannel(*target) ? target : nick, MSGLEVEL_CTCPS, + IRCTXT_CTCP_REQUESTED, nick, addr, str, target); + g_free(str); +} + +static void ctcp_default_msg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + return ctcp_print("unknown CTCP", data, server, nick, addr, target); +} + +static void ctcp_ping_msg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + return ctcp_print("CTCP PING", data, server, nick, addr, target); +} + +static void ctcp_version_msg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + return ctcp_print("CTCP VERSION", data, server, nick, addr, target); +} + +static void ctcp_default_reply(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + char *ptr, *str; + + g_return_if_fail(data != NULL); + + str = g_strdup(data); + ptr = strchr(str, ' '); + if (ptr != NULL) *ptr++ = '\0'; else ptr = ""; + + printformat(server, ischannel(*target) ? target : nick, MSGLEVEL_CTCPS, + IRCTXT_CTCP_REPLY, str, nick, ptr); + g_free(str); +} + +static void ctcp_ping_reply(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + GTimeVal tv, tv2; + long usecs; + + g_return_if_fail(data != NULL); + + if (sscanf(data, "%ld %ld", &tv2.tv_sec, &tv2.tv_usec) != 2) + return; + + g_get_current_time(&tv); + usecs = get_timeval_diff(&tv, &tv2); + printformat(server, ischannel(*target) ? target : nick, MSGLEVEL_CTCPS, + IRCTXT_CTCP_PING_REPLY, nick, usecs/1000, usecs%1000); +} + +void fe_ctcp_init(void) +{ + signal_add("default ctcp msg", (SIGNAL_FUNC) ctcp_default_msg); + signal_add("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping_msg); + signal_add("ctcp msg version", (SIGNAL_FUNC) ctcp_version_msg); + signal_add("default ctcp reply", (SIGNAL_FUNC) ctcp_default_reply); + signal_add("ctcp reply ping", (SIGNAL_FUNC) ctcp_ping_reply); +} + +void fe_ctcp_deinit(void) +{ + signal_remove("default ctcp msg", (SIGNAL_FUNC) ctcp_default_msg); + signal_remove("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping_msg); + signal_remove("ctcp msg version", (SIGNAL_FUNC) ctcp_version_msg); + signal_remove("default ctcp reply", (SIGNAL_FUNC) ctcp_default_reply); + signal_remove("ctcp reply ping", (SIGNAL_FUNC) ctcp_ping_reply); +} diff --git a/src/fe-common/irc/fe-events-numeric.c b/src/fe-common/irc/fe-events-numeric.c new file mode 100644 index 00000000..17a7f9ae --- /dev/null +++ b/src/fe-common/irc/fe-events-numeric.c @@ -0,0 +1,707 @@ +/* + fe-events-numeric.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "settings.h" + +#include "irc.h" +#include "levels.h" +#include "server.h" +#include "channels.h" +#include "nicklist.h" + +static char *last_away_nick = NULL; +static char *last_away_msg = NULL; + +static void event_user_mode(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *mode; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &mode); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_USER_MODE, mode); + g_free(params); +} + +static void event_ison(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *online; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &online); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_ONLINE, online); + g_free(params); +} + +static void event_names_list(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *names; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, NULL, &channel, &names); + if (channel_find(server, channel) == NULL) + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_NAMES, channel, names); + g_free(params); +} + +static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist, gint items, gint max) +{ + NICK_REC *rec, *last; + GString *str; + GSList *tmp; + gint lines, cols, line, col, skip; + gchar *linebuf; + + max++; /* op/voice */ + str = g_string_new(NULL); + + cols = max > 65 ? 1 : (65 / (max+3)); /* "[] " */ + lines = items <= cols ? 1 : items / cols+1; + + last = NULL; linebuf = g_malloc(max+1); linebuf[max] = '\0'; + for (line = 0, col = 0, skip = 1, tmp = nicklist; line < lines; last = rec, tmp = tmp->next) + { + rec = tmp->data; + + if (--skip == 0) + { + skip = lines; + memset(linebuf, ' ', max); + linebuf[0] = rec->op ? '@' : rec->voice ? '+' : ' '; + memcpy(linebuf+1, rec->nick, strlen(rec->nick)); + g_string_sprintfa(str, "%%K[%%n%%_%c%%_%s%%K] ", linebuf[0], linebuf+1); + cols++; + } + + if (col == cols || tmp->next == NULL) + { + printtext(channel->server, channel->name, MSGLEVEL_CLIENTCRAP, str->str); + g_string_truncate(str, 0); + col = 0; line++; + tmp = g_slist_nth(nicklist, line-1); skip = 1; + } + } + if (str->len != 0) + printtext(channel->server, channel->name, MSGLEVEL_CLIENTCRAP, str->str); + g_string_free(str, TRUE); + g_free(linebuf); +} + +static void display_nicks(CHANNEL_REC *channel) +{ + NICK_REC *nick; + GSList *tmp, *nicklist, *sorted; + gint nicks, normal, voices, ops, len, max; + + nicks = normal = voices = ops = 0; + nicklist = nicklist_getnicks(channel); + sorted = NULL; + + /* sort the nicklist */ + max = 0; + for (tmp = nicklist; tmp != NULL; tmp = tmp->next) + { + nick = tmp->data; + + sorted = g_slist_insert_sorted(sorted, nick, (GCompareFunc) nicklist_compare); + if (nick->op) + ops++; + else if (nick->voice) + voices++; + else + normal++; + nicks++; + + len = strlen(nick->nick); + if (len > max) max = len; + } + g_slist_free(nicklist); + + /* display the nicks */ + printformat(channel->server, channel->name, MSGLEVEL_CRAP, IRCTXT_NAMES, channel->name, ""); + display_sorted_nicks(channel, sorted, nicks, max); + g_slist_free(sorted); + + printformat(channel->server, channel->name, MSGLEVEL_CRAP, IRCTXT_ENDOFNAMES, + channel->name, nicks, ops, voices, normal); +} + +static void event_end_of_names(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + + chanrec = channel_find(server, channel); + if (chanrec == NULL) + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_ENDOFNAMES, channel, 0, 0, 0, 0); + else + display_nicks(chanrec); + g_free(params); +} + +static void event_who(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *channel, *user, *host, *stat, *realname, *hops; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 8, NULL, &channel, &user, &host, NULL, &nick, &stat, &realname); + + /* split hops/realname */ + hops = realname; + while (*realname != '\0' && *realname != ' ') realname++; + while (*realname == ' ') realname++; + if (realname > hops) realname[-1] = '\0'; + + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHO, + channel, nick, stat, hops, user, host, realname); + + g_free(params); +} + +static void event_end_of_who(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_END_OF_WHO, channel); + g_free(params); +} + +static void event_ban_list(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *ban, *setby, *tims; + glong secs, tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims); + + if (sscanf(tims, "%ld", &tim) != 1) tim = (glong) time(NULL); + secs = (glong) time(NULL)-tim; + + printformat(server, channel, MSGLEVEL_CRAP, + *setby == '\0' ? IRCTXT_BANLIST : IRCTXT_BANLIST_LONG, + channel, ban, setby, secs); + + g_free(params); +} + +static void event_eban_list(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *ban, *setby, *tims; + glong secs, tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims); + + if (sscanf(tims, "%ld", &tim) != 1) tim = (glong) time(NULL); + secs = (glong) time(NULL)-tim; + + printformat(server, channel, MSGLEVEL_CRAP, + *setby == '\0' ? IRCTXT_EBANLIST : IRCTXT_EBANLIST_LONG, + channel, ban, setby, secs); + + g_free(params); +} + +static void event_invite_list(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *invite; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, &invite); + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_INVITELIST, channel, invite); + g_free(params); +} + +static void event_nick_in_use(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + if (server->connected) + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_NICK_IN_USE, nick); + + g_free(params); +} + +static void event_topic_get(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *topic; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, &topic); + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_TOPIC, channel, topic); + g_free(params); +} + +static void event_topic_info(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *timestr, *channel, *topicby, *topictime; + glong ltime; + time_t t; + struct tm *tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &channel, &topicby, &topictime); + + if (sscanf(topictime, "%lu", <ime) != 1) ltime = 0; /* topic set date */ + t = (time_t) ltime; + tim = localtime(&t); + timestr = g_strdup(asctime(tim)); + if (timestr[strlen(timestr)-1] == '\n') timestr[strlen(timestr)-1] = '\0'; + + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_TOPIC_INFO, topicby, timestr); + g_free(timestr); + g_free(params); +} + +static void event_channel_mode(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3 | PARAM_FLAG_GETREST, NULL, &channel, &mode); + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_CHANNEL_MODE, channel, mode); + g_free(params); +} + +static void event_channel_created(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel, *times, *timestr; + glong timeval; + time_t t; + struct tm *tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, ×); + + if (sscanf(times, "%ld", &timeval) != 1) timeval = 0; + t = (time_t) timeval; + tim = localtime(&t); + timestr = g_strdup(asctime(tim)); + if (timestr[strlen(timestr)-1] == '\n') timestr[strlen(timestr)-1] = '\0'; + + printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_CHANNEL_CREATED, channel, timestr); + g_free(timestr); + g_free(params); +} + +static void event_away(gchar *data, IRC_SERVER_REC *server) +{ + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_AWAY); +} + +static void event_unaway(gchar *data, IRC_SERVER_REC *server) +{ + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_UNAWAY); +} + +static void event_userhost(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *hosts; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &hosts); + printtext(server, NULL, MSGLEVEL_CRAP, "%s", hosts); + g_free(params); +} + +static void event_whois(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *user, *host, *realname; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS, nick, user, host, realname); + g_free(params); +} + +static void event_whois_idle(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *secstr, *signon, *rest; + glong secs, lsignon; + gint h, m, s; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5 | PARAM_FLAG_GETREST, NULL, &nick, &secstr, &signon, &rest); + if (sscanf(secstr, "%ld", &secs) == 0) secs = 0; + lsignon = 0; + if (strstr(rest, ", signon time") != NULL) + sscanf(signon, "%ld", &lsignon); + + h = secs/3600; m = (secs%3600)/60; s = secs%60; + if (lsignon == 0) + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS_IDLE, nick, h, m, s); + else + { + gchar *timestr; + struct tm *tim; + time_t t; + + t = (time_t) lsignon; + tim = localtime(&t); + timestr = g_strdup(asctime(tim)); + if (timestr[strlen(timestr)-1] == '\n') timestr[strlen(timestr)-1] = '\0'; + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS_IDLE_SIGNON, nick, h, m, s, timestr); + g_free(timestr); + } + g_free(params); +} + +static void event_whois_server(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *whoserver, *desc; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &nick, &whoserver, &desc); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS_SERVER, nick, whoserver, desc); + g_free(params); +} + +static void event_whois_oper(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS_OPER, nick); + g_free(params); +} + +static void event_whois_channels(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *chans; + GString *str; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &chans); + + str = g_string_new(NULL); + for (; *chans != '\0'; chans++) + { + if ((unsigned char) *chans >= 32) + g_string_append_c(str, *chans); + else + { + g_string_append_c(str, '^'); + g_string_append_c(str, *chans+'A'-1); + } + } + + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHOIS_CHANNELS, nick, str->str); + g_free(params); + g_string_free(str, TRUE); +} + +static void event_whois_away(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + if (server->whois_coming || !settings_get_bool("show_away_once") || + last_away_nick == NULL || g_strcasecmp(last_away_nick, nick) != 0 || + last_away_msg == NULL || g_strcasecmp(last_away_msg, awaymsg) != 0) { + /* don't show the same away message from the same nick all the time */ + g_free_not_null(last_away_nick); + g_free_not_null(last_away_msg); + last_away_nick = g_strdup(nick); + last_away_msg = g_strdup(awaymsg); + + printformat(server, NULL, MSGLEVEL_CRAP, server->whois_coming ? + IRCTXT_WHOIS_AWAY : IRCTXT_NICK_AWAY, nick, awaymsg); + } + g_free(params); +} + +static void event_end_of_whois(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_END_OF_WHOIS, nick); + g_free(params); +} + +static void event_target_unavailable(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (!ischannel(*channel)) + { + /* nick unavailable */ + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_NICK_UNAVAILABLE, channel); + } + else + { + /* channel is unavailable. */ + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_JOINERROR_UNAVAIL, channel); + } + + g_free(params); +} + +static void event_no_such_nick(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_NO_SUCH_NICK, nick); + g_free(params); +} + +static void event_no_such_channel(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_NO_SUCH_CHANNEL, channel); + g_free(params); +} + +static void cannot_join(gchar *data, IRC_SERVER_REC *server, gint format) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + printformat(server, NULL, MSGLEVEL_CRAP, format, channel); + g_free(params); +} + +static void event_too_many_channels(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_TOOMANY); +} + +static void event_channel_is_full(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_FULL); +} + +static void event_invite_only(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_INVITE); +} + +static void event_banned(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_BANNED); +} + +static void event_bad_channel_key(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_BAD_KEY); +} + +static void event_bad_channel_mask(gchar *data, IRC_SERVER_REC *server) +{ + cannot_join(data, server, IRCTXT_JOINERROR_BAD_MASK); +} + +static void event_unknown_mode(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &mode); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_UNKNOWN_MODE, mode); + g_free(params); +} + +static void event_not_chanop(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_NOT_CHANOP, channel); + g_free(params); +} + +static void event_received(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + gchar *params, *args, *ptr; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, NULL, &args); + ptr = strstr(args, " :"); + if (ptr != NULL) *(ptr+1) = ' '; + printtext(server, NULL, MSGLEVEL_CRAP, "%s", args); + g_free(params); +} + +static void event_motd(gchar *data, SERVER_REC *server, gchar *nick, gchar *addr) +{ + /* numeric event. */ + gchar *params, *args, *ptr; + + if (settings_get_bool("toggle_skip_motd")) + return; + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, NULL, &args); + ptr = strstr(args, " :"); + if (ptr != NULL) *(ptr+1) = ' '; + printtext(server, NULL, MSGLEVEL_CRAP, "%s", args); + g_free(params); +} + +void fe_events_numeric_init(void) +{ + last_away_nick = NULL; + last_away_msg = NULL; + + signal_add("event 221", (SIGNAL_FUNC) event_user_mode); + signal_add("event 303", (SIGNAL_FUNC) event_ison); + signal_add("event 353", (SIGNAL_FUNC) event_names_list); + signal_add("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_add("event 352", (SIGNAL_FUNC) event_who); + signal_add("event 315", (SIGNAL_FUNC) event_end_of_who); + signal_add("event 367", (SIGNAL_FUNC) event_ban_list); + signal_add("event 348", (SIGNAL_FUNC) event_eban_list); + signal_add("event 346", (SIGNAL_FUNC) event_invite_list); + signal_add("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_add("event 332", (SIGNAL_FUNC) event_topic_get); + signal_add("event 333", (SIGNAL_FUNC) event_topic_info); + signal_add("event 324", (SIGNAL_FUNC) event_channel_mode); + signal_add("event 329", (SIGNAL_FUNC) event_channel_created); + signal_add("event 306", (SIGNAL_FUNC) event_away); + signal_add("event 305", (SIGNAL_FUNC) event_unaway); + signal_add("event 311", (SIGNAL_FUNC) event_whois); + signal_add("event 301", (SIGNAL_FUNC) event_whois_away); + signal_add("event 312", (SIGNAL_FUNC) event_whois_server); + signal_add("event 313", (SIGNAL_FUNC) event_whois_oper); + signal_add("event 317", (SIGNAL_FUNC) event_whois_idle); + signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois); + signal_add("event 319", (SIGNAL_FUNC) event_whois_channels); + signal_add("event 302", (SIGNAL_FUNC) event_userhost); + + signal_add("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick); + signal_add("event 403", (SIGNAL_FUNC) event_no_such_channel); + signal_add("event 405", (SIGNAL_FUNC) event_too_many_channels); + signal_add("event 471", (SIGNAL_FUNC) event_channel_is_full); + signal_add("event 472", (SIGNAL_FUNC) event_unknown_mode); + signal_add("event 473", (SIGNAL_FUNC) event_invite_only); + signal_add("event 474", (SIGNAL_FUNC) event_banned); + signal_add("event 475", (SIGNAL_FUNC) event_bad_channel_key); + signal_add("event 476", (SIGNAL_FUNC) event_bad_channel_mask); + signal_add("event 482", (SIGNAL_FUNC) event_not_chanop); + signal_add("event 375", (SIGNAL_FUNC) event_motd); + signal_add("event 376", (SIGNAL_FUNC) event_motd); + signal_add("event 372", (SIGNAL_FUNC) event_motd); + + signal_add("event 004", (SIGNAL_FUNC) event_received); + signal_add("event 364", (SIGNAL_FUNC) event_received); + signal_add("event 365", (SIGNAL_FUNC) event_received); +} + +void fe_events_numeric_deinit(void) +{ + g_free_not_null(last_away_nick); + g_free_not_null(last_away_msg); + + signal_remove("event 221", (SIGNAL_FUNC) event_user_mode); + signal_remove("event 303", (SIGNAL_FUNC) event_ison); + signal_remove("event 353", (SIGNAL_FUNC) event_names_list); + signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_remove("event 352", (SIGNAL_FUNC) event_who); + signal_remove("event 315", (SIGNAL_FUNC) event_end_of_who); + signal_remove("event 367", (SIGNAL_FUNC) event_ban_list); + signal_remove("event 348", (SIGNAL_FUNC) event_eban_list); + signal_remove("event 346", (SIGNAL_FUNC) event_invite_list); + signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_remove("event 332", (SIGNAL_FUNC) event_topic_get); + signal_remove("event 333", (SIGNAL_FUNC) event_topic_info); + signal_remove("event 324", (SIGNAL_FUNC) event_channel_mode); + signal_remove("event 329", (SIGNAL_FUNC) event_channel_created); + signal_remove("event 306", (SIGNAL_FUNC) event_away); + signal_remove("event 305", (SIGNAL_FUNC) event_unaway); + signal_remove("event 311", (SIGNAL_FUNC) event_whois); + signal_remove("event 301", (SIGNAL_FUNC) event_whois_away); + signal_remove("event 312", (SIGNAL_FUNC) event_whois_server); + signal_remove("event 313", (SIGNAL_FUNC) event_whois_oper); + signal_remove("event 317", (SIGNAL_FUNC) event_whois_idle); + signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois); + signal_remove("event 319", (SIGNAL_FUNC) event_whois_channels); + signal_remove("event 302", (SIGNAL_FUNC) event_userhost); + + signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick); + signal_remove("event 403", (SIGNAL_FUNC) event_no_such_channel); + signal_remove("event 405", (SIGNAL_FUNC) event_too_many_channels); + signal_remove("event 471", (SIGNAL_FUNC) event_channel_is_full); + signal_remove("event 472", (SIGNAL_FUNC) event_unknown_mode); + signal_remove("event 473", (SIGNAL_FUNC) event_invite_only); + signal_remove("event 474", (SIGNAL_FUNC) event_banned); + signal_remove("event 475", (SIGNAL_FUNC) event_bad_channel_key); + signal_remove("event 476", (SIGNAL_FUNC) event_bad_channel_mask); + signal_remove("event 482", (SIGNAL_FUNC) event_not_chanop); + signal_remove("event 375", (SIGNAL_FUNC) event_motd); + signal_remove("event 376", (SIGNAL_FUNC) event_motd); + signal_remove("event 372", (SIGNAL_FUNC) event_motd); + + signal_remove("event 004", (SIGNAL_FUNC) event_received); + signal_remove("event 364", (SIGNAL_FUNC) event_received); + signal_remove("event 365", (SIGNAL_FUNC) event_received); +} diff --git a/src/fe-common/irc/fe-events.c b/src/fe-common/irc/fe-events.c new file mode 100644 index 00000000..7ea6ea25 --- /dev/null +++ b/src/fe-common/irc/fe-events.c @@ -0,0 +1,682 @@ +/* + fe-events.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "settings.h" + +#include "irc.h" +#include "levels.h" +#include "server.h" +#include "server-redirect.h" +#include "server-reconnect.h" +#include "channels.h" +#include "query.h" +#include "nicklist.h" +#include "ignore.h" + +#include "irc-hilight-text.h" +#include "windows.h" + +#include "completion.h" + +static int beep_msg_level, beep_when_away; + +static void msg_beep_check(IRC_SERVER_REC *server, int level) +{ + if (level != 0 && (beep_msg_level & level) && + (!server->usermode_away || beep_when_away)) { + printbeep(); + } +} + +static void event_privmsg(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + CHANNEL_REC *chanrec; + WI_ITEM_REC *item; + gchar *params, *target, *msg, *nickmode; + int level; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (nick == NULL) nick = server->real_address; + + level = 0; + if (*msg == 1) + { + /* ctcp message, handled in fe-ctcp.c */ + } + else if (ignore_check(server, nick, addr, target, msg, + ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS)) + { + /* ignored */ + } + else if (ischannel(*target)) + { + /* message to some channel */ + WINDOW_REC *window; + NICK_REC *nickrec; + gboolean toyou; + gchar *color; + + chanrec = channel_find(server, target); + toyou = completion_msgtoyou((SERVER_REC *) server, msg); + color = irc_hilight_find_nick(target, nick, addr); + + nickrec = chanrec == NULL ? NULL : nicklist_find(chanrec, nick); + nickmode = !settings_get_bool("toggle_show_nickmode") || nickrec == NULL ? "" : + nickrec->op ? "@" : nickrec->voice ? "+" : " "; + + window = chanrec == NULL ? NULL : window_item_window((WI_ITEM_REC *) chanrec); + if (window != NULL && window->active == (WI_ITEM_REC *) chanrec) + { + /* message to active channel in window */ + if (color != NULL) + { + /* highlighted nick */ + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT, + IRCTXT_PUBMSG_HILIGHT, color, nick, msg, nickmode); + } + else + { + printformat(server, target, MSGLEVEL_PUBLIC | (toyou ? MSGLEVEL_NOHILIGHT : 0), + toyou ? IRCTXT_PUBMSG_ME : IRCTXT_PUBMSG, nick, msg, nickmode); + } + } + else + { + /* message to not existing/active channel */ + if (color != NULL) + { + /* highlighted nick */ + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT, + IRCTXT_PUBMSG_HILIGHT_CHANNEL, color, nick, target, msg, nickmode); + } + else + { + printformat(server, target, MSGLEVEL_PUBLIC | (toyou ? MSGLEVEL_NOHILIGHT : 0), + toyou ? IRCTXT_PUBMSG_ME_CHANNEL : IRCTXT_PUBMSG_CHANNEL, + nick, target, msg, nickmode); + } + } + + g_free_not_null(color); + level = MSGLEVEL_PUBLIC; + } + else + { + /* private message */ + if (settings_get_bool("toggle_autocreate_query") && query_find(server, nick) == NULL) + item = (WI_ITEM_REC *) query_create(server, nick, TRUE); + else + item = (WI_ITEM_REC *) query_find(server, nick); + + printformat(server, nick, MSGLEVEL_MSGS, + item == NULL ? IRCTXT_MSG_PRIVATE : IRCTXT_MSG_PRIVATE_QUERY, nick, addr == NULL ? "" : addr, msg); + level = MSGLEVEL_MSGS; + } + + msg_beep_check(server, level); + + g_free(params); +} + +/* we use "ctcp msg" here because "ctcp msg action" can be ignored with + /IGNORE * CTCPS */ +static void ctcp_action_msg(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr, gchar *target) +{ + WINDOW_REC *window; + CHANNEL_REC *channel; + WI_ITEM_REC *item; + int level; + + g_return_if_fail(data != NULL); + + if (g_strncasecmp(data, "ACTION ", 7) != 0) + return; + data += 7; + + level = 0; + if (ignore_check(server, nick, addr, target, data, MSGLEVEL_ACTIONS)) + { + /* ignored */ + } + else if (ischannel(*target)) + { + /* channel action */ + channel = channel_find(server, target); + + window = channel == NULL ? NULL : window_item_window((WI_ITEM_REC *) channel); + if (window != NULL && window->active == (WI_ITEM_REC *) channel) + { + /* message to active channel in window */ + printformat(server, target, MSGLEVEL_ACTIONS, + IRCTXT_ACTION_PUBLIC, nick, data); + } + else + { + /* message to not existing/active channel */ + printformat(server, target, MSGLEVEL_ACTIONS, + IRCTXT_ACTION_PUBLIC_CHANNEL, nick, target, data); + } + level = MSGLEVEL_PUBLIC; + } + else + { + /* private action */ + if (settings_get_bool("toggle_autocreate_query") && query_find(server, nick) == NULL) + item = (WI_ITEM_REC *) query_create(server, nick, TRUE); + else + item = (WI_ITEM_REC *) channel_find(server, nick); + + printformat(server, nick, MSGLEVEL_ACTIONS, + item == NULL ? IRCTXT_ACTION_PRIVATE : IRCTXT_ACTION_PRIVATE_QUERY, nick, addr == NULL ? "" : addr, data); + level = MSGLEVEL_MSGS; + } + + msg_beep_check(server, level); +} + +static void event_notice(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + char *params, *target, *msg; + int level; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (nick == NULL) nick = server->real_address; + + level = 0; + if (*msg == 1) + { + /* ctcp reply */ + } + else if (addr == NULL) + { + /* notice from server */ + if (nick == NULL || !ignore_check(server, nick, "", target, msg, MSGLEVEL_SNOTES)) + printformat(server, target, MSGLEVEL_SNOTES, IRCTXT_NOTICE_SERVER, nick == NULL ? "" : nick, msg); + } + else if (ischannel(*target) || (*target == '@' && ischannel(target[1]))) + { + /* notice in some channel */ + if (!ignore_check(server, nick, addr, target, msg, MSGLEVEL_NOTICES)) + printformat(server, target, MSGLEVEL_NOTICES, + *target == '@' ? IRCTXT_NOTICE_PUBLIC_OPS : IRCTXT_NOTICE_PUBLIC, + nick, *target == '@' ? target+1 : target, msg); + level = MSGLEVEL_NOTICES; + } + else + { + /* private notice */ + if (!ignore_check(server, nick, addr, NULL, msg, MSGLEVEL_NOTICES)) + printformat(server, nick, MSGLEVEL_NOTICES, IRCTXT_NOTICE_PRIVATE, nick, addr, msg); + level = MSGLEVEL_NOTICES; + } + + msg_beep_check(server, level); + + g_free(params); +} + +static void event_join(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + gchar *params, *channel, *tmp; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 1, &channel); + tmp = strchr(channel, 7); /* ^G does something weird.. */ + if (tmp != NULL) *tmp = '\0'; + + if (!ignore_check(server, nick, addr, channel, NULL, MSGLEVEL_JOINS)) + printformat(server, channel, MSGLEVEL_JOINS, IRCTXT_JOIN, nick, addr, channel); + g_free(params); +} + +static void event_part(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + gchar *params, *channel, *reason; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &reason); + + if (!ignore_check(server, nick, addr, channel, NULL, MSGLEVEL_PARTS)) + printformat(server, channel, MSGLEVEL_PARTS, IRCTXT_PART, nick, addr, channel, reason); + g_free(params); +} + +static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + GString *chans; + GSList *tmp; + int once; + + g_return_if_fail(data != NULL); + + if (ignore_check(server, nick, addr, NULL, NULL, MSGLEVEL_QUITS)) + return; + + if (*data == ':') data++; /* quit message */ + + once = settings_get_bool("show_quit_once"); + chans = !once ? NULL : g_string_new(NULL); + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (rec->server == server && nicklist_find(rec, nick) && + !ignore_check(server, nick, addr, rec->name, data, MSGLEVEL_QUITS)) { + if (once) + g_string_sprintfa(chans, "%s,", rec->name); + else + printformat(server, rec->name, MSGLEVEL_QUITS, IRCTXT_QUIT, nick, addr, data); + } + } + + if (once) { + g_string_truncate(chans, chans->len-1); + printformat(server, NULL, MSGLEVEL_QUITS, + chans->len == 0 ? IRCTXT_QUIT : IRCTXT_QUIT_ONCE, + nick, addr, data, chans->str); + g_string_free(chans, TRUE); + } +} + +static void event_kick(gchar *data, IRC_SERVER_REC *server, gchar *kicker, gchar *addr) +{ + gchar *params, *channel, *nick, *reason; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3 | PARAM_FLAG_GETREST, &channel, &nick, &reason); + if (!ignore_check(server, kicker, addr, channel, reason, MSGLEVEL_KICKS)) + { + printformat(server, channel, MSGLEVEL_KICKS, + IRCTXT_KICK, nick, channel, kicker, reason); + } + g_free(params); +} + +static void print_nick_change(IRC_SERVER_REC *server, const char *target, const char *newnick, const char *oldnick, const char *addr, int ownnick) +{ + if (ignore_check(server, oldnick, addr, target, newnick, MSGLEVEL_NICKS)) + return; + + if (ownnick) + printformat(server, target, MSGLEVEL_NICKS, IRCTXT_YOUR_NICK_CHANGED, newnick); + else + printformat(server, target, MSGLEVEL_NICKS, IRCTXT_NICK_CHANGED, oldnick, newnick); +} + +static void event_nick(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *addr) +{ + GSList *tmp; + char *params, *newnick; + int ownnick, msgprint; + + g_return_if_fail(data != NULL); + + if (ignore_check(server, sender, addr, NULL, NULL, MSGLEVEL_NICKS)) + return; + + params = event_get_params(data, 1, &newnick); + + msgprint = FALSE; + ownnick = g_strcasecmp(sender, server->nick) == 0; + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + if (nicklist_find(channel, sender)) { + print_nick_change(server, channel->name, newnick, sender, addr, ownnick); + msgprint = TRUE; + } + } + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *query = tmp->data; + + if (g_strcasecmp(query->nick, sender) == 0) { + print_nick_change(server, query->nick, newnick, sender, addr, ownnick); + msgprint = TRUE; + } + } + + if (!msgprint && ownnick) + printformat(server, NULL, MSGLEVEL_NICKS, IRCTXT_YOUR_NICK_CHANGED, newnick); + g_free(params); +} + +static void event_mode(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *channel, *mode; + + g_return_if_fail(data != NULL); + if (nick == NULL) nick = server->real_address; + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &mode); + if (ignore_check(server, nick, addr, channel, mode, MSGLEVEL_MODES)) { + g_free(params); + return; + } + + if (!ischannel(*channel)) { + /* user mode change */ + printformat(server, NULL, MSGLEVEL_MODES, IRCTXT_USERMODE_CHANGE, mode, channel); + } else if (addr == NULL) { + /* channel mode changed by server */ + printformat(server, channel, MSGLEVEL_MODES, + IRCTXT_SERVER_CHANMODE_CHANGE, channel, mode, nick); + } else { + /* channel mode changed by normal user */ + printformat(server, channel, MSGLEVEL_MODES, + IRCTXT_CHANMODE_CHANGE, channel, mode, nick); + } + + g_free(params); +} + +static void event_pong(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + char *params, *host, *reply; + + g_return_if_fail(data != NULL); + if (nick == NULL) nick = server->real_address; + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &host, &reply); + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_PONG, host, reply); + g_free(params); +} + +static void event_invite(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + gchar *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (*channel != '\0' && !ignore_check(server, nick, addr, channel, NULL, MSGLEVEL_INVITES)) + printformat(server, NULL, MSGLEVEL_INVITES, IRCTXT_INVITE, nick, channel); + g_free(params); +} + +static void event_topic(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + gchar *params, *channel, *topic; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &topic); + + if (!ignore_check(server, nick, addr, channel, topic, MSGLEVEL_TOPICS)) + printformat(server, channel, MSGLEVEL_TOPICS, + *topic != '\0' ? IRCTXT_NEW_TOPIC : IRCTXT_TOPIC_UNSET, + nick, channel, topic); + g_free(params); +} + +static void event_error(gchar *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + + if (*data == ':') data++; + printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_ERROR, data); +} + +static void event_wallops(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + g_return_if_fail(data != NULL); + + if (*data == ':') data++; + if (!ignore_check(server, nick, addr, NULL, data, MSGLEVEL_WALLOPS)) + { + if (g_strncasecmp(data, "\001ACTION", 7) != 0) + printformat(server, NULL, MSGLEVEL_WALLOPS, IRCTXT_WALLOPS, nick, data); + else + { + /* Action in WALLOP */ + gint len; + + data = g_strdup(data); + len = strlen(data); + if (data[len-1] == 1) data[len-1] = '\0'; + printformat(server, NULL, MSGLEVEL_WALLOPS, IRCTXT_ACTION_WALLOPS, nick, data); + g_free(data); + } + msg_beep_check(server, MSGLEVEL_WALLOPS); + } +} + +static void channel_sync(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + printformat(channel->server, channel->name, MSGLEVEL_CLIENTNOTICE|MSGLEVEL_NO_ACT, IRCTXT_CHANNEL_SYNCED, + channel->name, (glong) (time(NULL)-channel->createtime)); +} + +static void event_connected(IRC_SERVER_REC *server) +{ + gchar *str; + + g_return_if_fail(server != NULL); + + if (*settings_get_str("default_nick") == '\0' || + g_strcasecmp(server->nick, settings_get_str("default_nick")) == 0) + return; + + /* someone has our nick, find out who. */ + str = g_strdup_printf("WHOIS %s", settings_get_str("default_nick")); + irc_send_cmd(server, str); + g_free(str); + + server_redirect_event((SERVER_REC *) server, settings_get_str("default_nick"), 1, + "event 318", "event empty", 1, + "event 401", "event empty", 1, + "event 311", "nickfind event whois", 1, + "event 301", "event empty", 1, + "event 312", "event empty", 1, + "event 313", "event empty", 1, + "event 317", "event empty", 1, + "event 319", "event empty", 1, NULL); + +} + +static void event_nickfind_whois(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick, *user, *host, *realname; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_YOUR_NICK_OWNED, nick, user, host, realname); + g_free(params); +} + +static void event_ban_type_changed(gchar *bantype) +{ + GString *str; + + g_return_if_fail(bantype != NULL); + + if (strcmp(bantype, "UD") == 0) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_BANTYPE, "Normal"); + else if (strcmp(bantype, "HD") == 0 || strcmp(bantype, "H") == 0) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_BANTYPE, "Host"); + else if (strcmp(bantype, "D") == 0) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_BANTYPE, "Domain"); + else + { + str = g_string_new("Custom:"); + if (*bantype == 'N') + { + g_string_append(str, " Nick"); + bantype++; + } + if (*bantype == 'U') + { + g_string_append(str, " User"); + bantype++; + } + if (*bantype == 'H') + { + g_string_append(str, " Host"); + bantype++; + } + if (*bantype == 'D') + g_string_append(str, " Domain"); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_BANTYPE, str->str); + g_string_free(str, TRUE); + } +} + +/*FIXME: move to core +static void event_perl_error(gchar *text) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_PERL_ERROR, text); +}*/ + +static void sig_server_lag_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_LAG_DISCONNECTED, server->connrec->address, time(NULL)-server->lag_sent); +} + +static void sig_server_reconnect_removed(RECONNECT_REC *reconnect) +{ + g_return_if_fail(reconnect != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_RECONNECT_REMOVED, reconnect->conn->address, reconnect->conn->port, + reconnect->conn->ircnet == NULL ? "" : reconnect->conn->ircnet); +} + +static void sig_server_reconnect_not_found(gchar *tag) +{ + g_return_if_fail(tag != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + IRCTXT_RECONNECT_NOT_FOUND, tag); +} + +static void event_received(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *addr) +{ + g_return_if_fail(data != NULL); + + if (!isdigit((gint) *data)) + printtext(server, NULL, MSGLEVEL_CRAP, "%s", data); + else + { + /* numeric event. */ + gchar *params, *cmd, *args, *ptr; + + params = event_get_params(data, 3 | PARAM_FLAG_GETREST, &cmd, NULL, &args); + ptr = strstr(args, " :"); + if (ptr != NULL) *(ptr+1) = ' '; + printtext(server, NULL, MSGLEVEL_CRAP, "%s", args); + g_free(params); + } +} + +static void sig_empty(void) +{ +} + +static void read_settings(void) +{ + beep_msg_level = level2bits(settings_get_str("beep_on_msg")); + beep_when_away = settings_get_bool("beep_when_away"); +} + +void fe_events_init(void) +{ + beep_msg_level = 0; + + read_settings(); + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_action_msg); + signal_add("ctcp msg action", (SIGNAL_FUNC) sig_empty); + signal_add("event notice", (SIGNAL_FUNC) event_notice); + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("event part", (SIGNAL_FUNC) event_part); + signal_add("event quit", (SIGNAL_FUNC) event_quit); + signal_add("event kick", (SIGNAL_FUNC) event_kick); + signal_add("event nick", (SIGNAL_FUNC) event_nick); + signal_add("event mode", (SIGNAL_FUNC) event_mode); + signal_add("event pong", (SIGNAL_FUNC) event_pong); + signal_add("event invite", (SIGNAL_FUNC) event_invite); + signal_add("event topic", (SIGNAL_FUNC) event_topic); + signal_add("event error", (SIGNAL_FUNC) event_error); + signal_add("event wallops", (SIGNAL_FUNC) event_wallops); + + signal_add("default event", (SIGNAL_FUNC) event_received); + + signal_add("channel sync", (SIGNAL_FUNC) channel_sync); + signal_add("event connected", (SIGNAL_FUNC) event_connected); + signal_add("nickfind event whois", (SIGNAL_FUNC) event_nickfind_whois); + signal_add("ban type changed", (SIGNAL_FUNC) event_ban_type_changed); + //signal_add("perl error", (SIGNAL_FUNC) event_perl_error); + + signal_add("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected); + signal_add("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed); + signal_add("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found); + + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void fe_events_deinit(void) +{ + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_action_msg); + signal_remove("ctcp msg action", (SIGNAL_FUNC) sig_empty); + signal_remove("event notice", (SIGNAL_FUNC) event_notice); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event part", (SIGNAL_FUNC) event_part); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("event kick", (SIGNAL_FUNC) event_kick); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); + signal_remove("event mode", (SIGNAL_FUNC) event_mode); + signal_remove("event pong", (SIGNAL_FUNC) event_pong); + signal_remove("event invite", (SIGNAL_FUNC) event_invite); + signal_remove("event topic", (SIGNAL_FUNC) event_topic); + signal_remove("event error", (SIGNAL_FUNC) event_error); + signal_remove("event wallops", (SIGNAL_FUNC) event_wallops); + + signal_remove("default event", (SIGNAL_FUNC) event_received); + + signal_remove("channel sync", (SIGNAL_FUNC) channel_sync); + signal_remove("event connected", (SIGNAL_FUNC) event_connected); + signal_remove("nickfind event whois", (SIGNAL_FUNC) event_nickfind_whois); + signal_remove("ban type changed", (SIGNAL_FUNC) event_ban_type_changed); + //signal_remove("perl error", (SIGNAL_FUNC) event_perl_error); + + signal_remove("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected); + signal_remove("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed); + signal_remove("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-common/irc/fe-ignore.c b/src/fe-common/irc/fe-ignore.c new file mode 100644 index 00000000..35da7c84 --- /dev/null +++ b/src/fe-common/irc/fe-ignore.c @@ -0,0 +1,248 @@ +/* + fe-ignore.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" + +#include "irc.h" +#include "irc-server.h" +#include "ignore.h" + +static char *ignore_get_key(IGNORE_REC *rec) +{ + char *chans, *ret; + + if (rec->channels == NULL) + return rec->mask != NULL ? g_strdup(rec->mask) : NULL; + + chans = g_strjoinv(",", rec->channels); + if (rec->mask == NULL) return chans; + + ret = g_strdup_printf("%s %s", rec->mask, chans); + g_free(chans); + return ret; +} + +static char *ignore_get_levels(int level, int xlevel) +{ + GString *str; + char *levelstr, *p, *ret; + + str = g_string_new(NULL); + if (level != 0) { + levelstr = bits2level(level); + g_string_append(str, levelstr); + g_free(levelstr); + } + + if (xlevel != 0) { + if (str->len > 0) g_string_append_c(str, ' '); + + levelstr = bits2level(xlevel); + for (p = levelstr; *p != '\0'; p++) { + if (!isspace(*p) && (p == levelstr || isspace(p[-1]))) + g_string_append_c(str, '^'); + g_string_append_c(str, *p); + } + g_free(levelstr); + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* msgs ^notices : level=msgs, xlevel=notices */ +static void ignore_split_levels(const char *levels, int *level, int *xlevel) +{ + GString *slevel, *sxlevel; + char **levellist, **tmp; + + if (*levels == '\0') return; + + slevel = g_string_new(NULL); + sxlevel = g_string_new(NULL); + + levellist = g_strsplit(levels, " ", -1); + for (tmp = levellist; *tmp != NULL; tmp++) { + if (**tmp == '^') + g_string_sprintfa(sxlevel, "%s ", (*tmp)+1); + else if (**tmp == '-' && (*tmp)[1] == '^') + g_string_sprintfa(sxlevel, "-%s ", (*tmp)+2); + else + g_string_sprintfa(slevel, "%s ", *tmp); + } + g_strfreev(levellist); + + *level = combine_level(*level, slevel->str); + *xlevel = combine_level(*xlevel, sxlevel->str); + + g_string_free(slevel, TRUE); + g_string_free(sxlevel, TRUE); +} + +static void ignore_print(int index, IGNORE_REC *rec) +{ + char *key, *levels; + + key = ignore_get_key(rec); + levels = ignore_get_levels(rec->level, rec->except_level); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, + IRCTXT_IGNORE_LINE, index, + key != NULL ? key : "", + levels != NULL ? levels : "", + rec->fullword ? " -word" : "", + rec->regexp ? " -regexp" : ""); + g_free(key); + g_free(levels); +} + +static void cmd_ignore_show(void) +{ + GSList *tmp; + int index; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_IGNORE_HEADER); + index = 1; + for (tmp = ignores; tmp != NULL; tmp = tmp->next, index++) { + IGNORE_REC *rec = tmp->data; + + ignore_print(index, rec); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_IGNORE_FOOTER); +} + +static void cmd_ignore(const char *data) +{ + /* /IGNORE [-regexp | -word] [-pattern <pattern>] [-except] + [-channels <channel>] <mask> <levels> + OR + + /IGNORE [-regexp | -word] [-pattern <pattern>] [-except] + <channels> <levels> */ + char *params, *args, *patternarg, *chanarg, *mask, *levels, *key; + char **channels; + IGNORE_REC *rec; + int new_ignore; + + if (*data == '\0') { + cmd_ignore_show(); + return; + } + + args = "pattern channels"; + params = cmd_get_params(data, 5 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, + &args, &patternarg, &chanarg, &mask, &levels); + if (levels == 0) cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (ischannel(*mask)) { + chanarg = mask; + mask = ""; + } + channels = *chanarg == '\0' ? NULL : + g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1); + + rec = ignore_find(NULL, mask, channels); + new_ignore = rec == NULL; + + if (rec == NULL) { + rec = g_new0(IGNORE_REC, 1); + + rec->mask = *mask == '\0' ? NULL : g_strdup(mask); + rec->channels = channels; + } else { + g_free_and_null(rec->pattern); + g_strfreev(channels); + } + + if (stristr(args, "-except") != NULL) { + rec->except_level = combine_level(rec->except_level, levels); + } else { + ignore_split_levels(levels, &rec->level, &rec->except_level); + } + + rec->pattern = *patternarg == '\0' ? NULL : g_strdup(patternarg); + rec->fullword = stristr(args, "-word") != NULL; + rec->regexp = stristr(args, "-regexp") != NULL; + + if (rec->level == 0 && rec->except_level == 0) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_UNIGNORED, rec->mask); + else { + key = ignore_get_key(rec); + levels = ignore_get_levels(rec->level, rec->except_level); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_IGNORED, key, levels); + g_free(key); + g_free(levels); + } + + if (new_ignore) + ignore_add_rec(rec); + else + ignore_update_rec(rec); + + g_free(params); +} + +static void cmd_unignore(const char *data) +{ + IGNORE_REC *rec; + GSList *tmp; + char *key; + + if (is_numeric(data, ' ')) { + /* with index number */ + tmp = g_slist_nth(ignores, atol(data)-1); + rec = tmp == NULL ? NULL : tmp->data; + } else { + /* with mask */ + char *chans[2] = { "*", NULL }; + + if (ischannel(*data)) chans[0] = (char *) data; + rec = ignore_find("*", ischannel(*data) ? NULL : data, chans); + } + + if (rec == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_IGNORE_NOT_FOUND, data); + else { + key = ignore_get_key(rec); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_UNIGNORED, key); + g_free(key); + + rec->level = 0; + rec->except_level = 0; + ignore_update_rec(rec); + } +} + +void fe_ignore_init(void) +{ + command_bind("ignore", NULL, (SIGNAL_FUNC) cmd_ignore); + command_bind("unignore", NULL, (SIGNAL_FUNC) cmd_unignore); +} + +void fe_ignore_deinit(void) +{ + command_unbind("ignore", (SIGNAL_FUNC) cmd_ignore); + command_unbind("unignore", (SIGNAL_FUNC) cmd_unignore); +} diff --git a/src/fe-common/irc/fe-irc-commands.c b/src/fe-common/irc/fe-irc-commands.c new file mode 100644 index 00000000..bdaa1376 --- /dev/null +++ b/src/fe-common/irc/fe-irc-commands.c @@ -0,0 +1,541 @@ +/* + fe-irc-commands.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "special-vars.h" +#include "settings.h" + +#include "levels.h" +#include "irc.h" +#include "server.h" +#include "server-reconnect.h" +#include "mode-lists.h" +#include "nicklist.h" +#include "channels.h" +#include "query.h" + +#include "windows.h" +#include "window-items.h" + +static void cmd_server(const char *data) +{ + if (*data == '+' && data[1] != '\0') + window_create(NULL, FALSE); +} + +static void print_servers(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CRAP, IRCTXT_SERVER_LIST, + rec->tag, rec->connrec->address, rec->connrec->port, + rec->connrec->ircnet == NULL ? "" : rec->connrec->ircnet, rec->connrec->nick); + } +} + +static void print_lookup_servers(void) +{ + GSList *tmp; + for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CRAP, IRCTXT_SERVER_LOOKUP_LIST, + rec->tag, rec->connrec->address, rec->connrec->port, + rec->connrec->ircnet == NULL ? "" : rec->connrec->ircnet, rec->connrec->nick); + } +} + +static void print_reconnects(void) +{ + GSList *tmp; + char *tag, *next_connect; + int left; + + for (tmp = reconnects; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + IRC_SERVER_CONNECT_REC *conn = rec->conn; + + tag = g_strdup_printf("RECON-%d", rec->tag); + left = rec->next_connect-time(NULL); + next_connect = g_strdup_printf("%02d:%02d", left/60, left%60); + printformat(NULL, NULL, MSGLEVEL_CRAP, IRCTXT_SERVER_RECONNECT_LIST, + tag, conn->address, conn->port, + conn->ircnet == NULL ? "" : conn->ircnet, + conn->nick, next_connect); + g_free(next_connect); + g_free(tag); + } +} + +static void cmd_servers(void) +{ + print_servers(); + print_lookup_servers(); + print_reconnects(); +} + +static void cmd_unquery(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + QUERY_REC *query; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + /* remove current query */ + query = irc_item_query(item); + if (query == NULL) return; + } else { + query = query_find(server, data); + if (query == NULL) { + printformat(server, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_NO_QUERY, data); + return; + } + } + + query_destroy(query); +} + +static void cmd_query(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + WINDOW_REC *window; + QUERY_REC *query; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + /* remove current query */ + cmd_unquery("", server, item); + return; + } + + if (*data != '=' && (server == NULL || !server->connected)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + query = query_find(server, data); + if (query != NULL) { + /* query already existed - change to query window */ + window = window_item_window((WI_ITEM_REC *) query); + g_return_if_fail(window != NULL); + + window_set_active(window); + window_item_set_active(window, (WI_ITEM_REC *) query); + return; + } + + query_create(server, data, FALSE); +} + +static void cmd_msg(gchar *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + CHANNEL_REC *channel; + NICK_REC *nickrec; + char *params, *target, *msg, *nickmode, *freestr, *newtarget; + int free_ret; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target == '=') + { + /* dcc msg - handled in fe-dcc.c */ + g_free(params); + return; + } + + free_ret = FALSE; + if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) + newtarget = parse_special(&target, server, item, NULL, &free_ret, NULL); + else if (strcmp(target, "*") == 0 && + (irc_item_channel(item) || irc_item_query(item))) + newtarget = item->name; + else newtarget = target; + + if (newtarget == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, *target == ',' ? + IRCTXT_NO_MSGS_GOT : IRCTXT_NO_MSGS_SENT); + g_free(params); + signal_stop(); + return; + } + target = newtarget; + + if (server == NULL || !server->connected) cmd_param_error(CMDERR_NOT_CONNECTED); + channel = channel_find(server, target); + + freestr = !free_ret ? NULL : target; + if (*target == '@' && ischannel(target[1])) + target++; /* Hybrid 6 feature, send msg to all ops in channel */ + + if (ischannel(*target)) + { + /* msg to channel */ + nickrec = channel == NULL ? NULL : nicklist_find(channel, server->nick); + nickmode = !settings_get_bool("toggle_show_nickmode") || nickrec == NULL ? "" : + nickrec->op ? "@" : nickrec->voice ? "+" : " "; + + window = channel == NULL ? NULL : window_item_window((WI_ITEM_REC *) channel); + if (window != NULL && window->active == (WI_ITEM_REC *) channel) + { + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT, + IRCTXT_OWN_MSG, server->nick, msg, nickmode); + } + else + { + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT, + IRCTXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode); + } + } + else + { + /* private message */ + printformat(server, target, MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT, + channel == NULL ? IRCTXT_OWN_MSG_PRIVATE : IRCTXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick); + } + g_free_not_null(freestr); + + g_free(params); +} + +static void cmd_notice(gchar *data, IRC_SERVER_REC *server) +{ + char *params, *target, *msg; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target == '@' && ischannel(target[1])) + target++; /* Hybrid 6 feature, send notice to all ops in channel */ + + printformat(server, target, MSGLEVEL_NOTICES | MSGLEVEL_NOHILIGHT, + IRCTXT_OWN_NOTICE, target, msg); + + g_free(params); +} + +static void cmd_me(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + g_return_if_fail(data != NULL); + + if (!irc_item_check(item)) + return; + + if (irc_item_dcc_chat(item)) { + /* DCC action - handled by fe-dcc.c */ + return; + } + + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + printformat(server, item->name, MSGLEVEL_ACTIONS, + IRCTXT_OWN_ME, server->nick, data); + + irc_send_cmdv(server, "PRIVMSG %s :\001ACTION %s\001", item->name, data); +} + +static void cmd_action(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *text; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '=') { + /* DCC action - handled by fe-dcc.c */ + return; + } + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &text); + if (*target == '\0' || *text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + printformat(server, target, MSGLEVEL_ACTIONS, IRCTXT_OWN_ME, server->nick, text); + irc_send_cmdv(server, "PRIVMSG %s :\001ACTION %s\001", target, text); + g_free(params); +} + +static void cmd_ctcp(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *ctcpcmd, *ctcpdata; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target == '=') { + /* send CTCP via DCC CHAT */ + g_free(params); + return; + } + if (*target == '@' && ischannel(target[1])) + target++; /* Hybrid 6 feature, send ctcp to all ops in channel */ + + g_strup(ctcpcmd); + printformat(server, target, MSGLEVEL_CTCPS, IRCTXT_OWN_CTCP, target, ctcpcmd, ctcpdata); + + g_free(params); +} + +static void cmd_nctcp(const char *data, IRC_SERVER_REC *server) +{ + gchar *params, *target, *ctcpcmd, *ctcpdata; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target == '@' && ischannel(target[1])) + target++; /* Hybrid 6 feature, send notice to all ops in channel */ + + g_strup(ctcpcmd); + printformat(server, target, MSGLEVEL_NOTICES, IRCTXT_OWN_NOTICE, target, ctcpcmd, ctcpdata); + + g_free(params); +} + +static void cmd_banstat(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + CHANNEL_REC *cur_channel, *channel; + GSList *tmp; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + cur_channel = irc_item_channel(item); + if (cur_channel == NULL) cmd_return_error(CMDERR_NOT_JOINED); + + if (strcmp(data, "*") == 0 || *data == '\0') + channel = cur_channel; + else { + channel = channel_find(server, data); + if (channel == NULL) { + /* not joined to such channel, but ask ban lists from server */ + GString *str; + + str = g_string_new(NULL); + g_string_sprintf(str, "%s b", data); + signal_emit("command mode", 3, str->str, server, cur_channel); + g_string_sprintf(str, "%s e", data); + signal_emit("command mode", 3, str->str, server, cur_channel); + g_string_free(str, TRUE); + return; + } + } + + if (channel == NULL) cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + /* show bans.. */ + for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) { + BAN_REC *rec; + + rec = (BAN_REC *) tmp->data; + if (*rec->setby == '\0') + printformat(server, channel->name, MSGLEVEL_CRAP, IRCTXT_BANLIST, channel->name, rec->ban); + else + printformat(server, channel->name, MSGLEVEL_CRAP, IRCTXT_BANLIST, + channel->name, rec->ban, rec->setby, (gint) (time(NULL)-rec->time)); + } + + /* ..and show ban exceptions.. */ + for (tmp = channel->ebanlist; tmp != NULL; tmp = tmp->next) { + BAN_REC *rec; + + rec = (BAN_REC *) tmp->data; + if (*rec->setby == '\0') + printformat(server, channel->name, MSGLEVEL_CRAP, IRCTXT_EBANLIST, channel->name, rec->ban); + else + printformat(server, channel->name, MSGLEVEL_CRAP, IRCTXT_EBANLIST, + channel->name, rec->ban, rec->setby, (gint) (time(NULL)-rec->time)); + } +} + +static void cmd_invitelist(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + CHANNEL_REC *channel, *cur_channel; + GSList *tmp; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + cur_channel = irc_item_channel(item); + if (cur_channel == NULL) cmd_return_error(CMDERR_NOT_JOINED); + + if (strcmp(data, "*") == 0 || *data == '\0') + channel = cur_channel; + else + channel = channel_find(server, data); + if (channel == NULL) cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + for (tmp = channel->invitelist; tmp != NULL; tmp = tmp->next) + printformat(server, channel->name, MSGLEVEL_CRAP, IRCTXT_INVITELIST, channel->name, tmp->data); +} + +static void cmd_join(const char *data, IRC_SERVER_REC *server) +{ + if ((*data == '\0' || g_strncasecmp(data, "-invite", 7) == 0) && + server->last_invite == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOT_INVITED); + signal_stop(); + } +} + +static void cmd_channel(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *channel; + GString *nicks; + GSList *nicklist, *tmp, *ntmp; + char *mode; + + if (*data != '\0') { + cmd_join(data, server); + return; + } + + if (channels == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOT_IN_CHANNELS); + return; + } + + /* print active channel */ + channel = irc_item_channel(active_win->active); + if (channel != NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_CURRENT_CHANNEL, channel->name); + + /* print list of all channels, their modes, server tags and nicks */ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_CHANLIST_HEADER); + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + channel = tmp->data; + + nicklist = nicklist_getnicks(channel); + mode = channel_get_mode(channel); + nicks = g_string_new(NULL); + for (ntmp = nicklist; ntmp != NULL; ntmp = ntmp->next) { + NICK_REC *rec = ntmp->data; + + g_string_sprintfa(nicks, "%s ", rec->nick); + } + + g_string_truncate(nicks, nicks->len-1); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_CHANLIST_LINE, + channel->name, mode, channel->server->tag, nicks->str); + + g_free(mode); + g_slist_free(nicklist); + g_string_free(nicks, TRUE); + } +} + +static void cmd_nick(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + + if (*data != '\0') return; + if (server == NULL || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + /* display current nick */ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_YOUR_NICK, server->nick); + signal_stop(); +} + +static void cmd_ver(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *str; + + g_return_if_fail(data != NULL); + + if (!irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '\0' && !irc_item_check(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + str = g_strdup_printf("%s VERSION", *data == '\0' ? item->name : data); + signal_emit("command ctcp", 3, str, server, item); + g_free(str); +} + +static void cmd_ts(const char *data) +{ + GSList *tmp; + + g_return_if_fail(data != NULL); + + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_TOPIC, + rec->name, rec->topic == NULL ? "" : rec->topic); + } +} + +void fe_irc_commands_init(void) +{ + command_bind("server", NULL, (SIGNAL_FUNC) cmd_server); + command_bind("servers", NULL, (SIGNAL_FUNC) cmd_servers); + command_bind("query", NULL, (SIGNAL_FUNC) cmd_query); + command_bind("unquery", NULL, (SIGNAL_FUNC) cmd_unquery); + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + command_bind("notice", NULL, (SIGNAL_FUNC) cmd_notice); + command_bind("me", NULL, (SIGNAL_FUNC) cmd_me); + command_bind("action", NULL, (SIGNAL_FUNC) cmd_action); + command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); + command_bind("nctcp", NULL, (SIGNAL_FUNC) cmd_nctcp); + command_bind("banstat", NULL, (SIGNAL_FUNC) cmd_banstat); + command_bind("invitelist", NULL, (SIGNAL_FUNC) cmd_invitelist); + command_bind("join", NULL, (SIGNAL_FUNC) cmd_join); + command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel); + command_bind("nick", NULL, (SIGNAL_FUNC) cmd_nick); + command_bind("ver", NULL, (SIGNAL_FUNC) cmd_ver); + command_bind("ts", NULL, (SIGNAL_FUNC) cmd_ts); +} + +void fe_irc_commands_deinit(void) +{ + command_unbind("server", (SIGNAL_FUNC) cmd_server); + command_unbind("servers", (SIGNAL_FUNC) cmd_servers); + command_unbind("query", (SIGNAL_FUNC) cmd_query); + command_unbind("unquery", (SIGNAL_FUNC) cmd_unquery); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); + command_unbind("notice", (SIGNAL_FUNC) cmd_notice); + command_unbind("me", (SIGNAL_FUNC) cmd_me); + command_unbind("action", (SIGNAL_FUNC) cmd_action); + command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); + command_unbind("nctcp", (SIGNAL_FUNC) cmd_nctcp); + command_unbind("banstat", (SIGNAL_FUNC) cmd_banstat); + command_unbind("invitelist", (SIGNAL_FUNC) cmd_invitelist); + command_unbind("join", (SIGNAL_FUNC) cmd_join); + command_unbind("channel", (SIGNAL_FUNC) cmd_channel); + command_unbind("nick", (SIGNAL_FUNC) cmd_nick); + command_unbind("ver", (SIGNAL_FUNC) cmd_ver); + command_unbind("ts", (SIGNAL_FUNC) cmd_ts); +} diff --git a/src/fe-common/irc/fe-query.c b/src/fe-common/irc/fe-query.c new file mode 100644 index 00000000..be46ea0b --- /dev/null +++ b/src/fe-common/irc/fe-query.c @@ -0,0 +1,133 @@ +/* + fe-query.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" + +#include "irc.h" +#include "levels.h" +#include "query.h" + +#include "windows.h" +#include "window-items.h" + +static void signal_query_created(QUERY_REC *query, gpointer automatic) +{ + window_item_create((WI_ITEM_REC *) query, GPOINTER_TO_INT(automatic)); +} + +static void signal_query_created_curwin(QUERY_REC *query) +{ + g_return_if_fail(query != NULL); + + window_add_item(active_win, (WI_ITEM_REC *) query, FALSE); + signal_stop(); +} + +static void signal_query_destroyed(QUERY_REC *query) +{ + WINDOW_REC *window; + + g_return_if_fail(query != NULL); + + window = window_item_window((WI_ITEM_REC *) query); + if (window != NULL) window_remove_item(window, (WI_ITEM_REC *) query); +} + +static void signal_window_item_removed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + QUERY_REC *query; + + g_return_if_fail(window != NULL); + + query = irc_item_query(item); + if (query != NULL) query_destroy(query); +} + +static void sig_server_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!irc_server_check(server)) + return; + + /* check if there's any queries without server */ + for (tmp = queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (rec->server == NULL && + g_strcasecmp(rec->server_tag, server->tag) == 0) { + window_item_change_server((WI_ITEM_REC *) rec, server); + server->queries = g_slist_append(server->queries, rec); + } + } +} + +static void cmd_window_server(const char *data) +{ + SERVER_REC *server; + + g_return_if_fail(data != NULL); + + server = server_find_tag(data); + if (irc_server_check(server) && irc_item_query(active_win->active)) { + /* /WINDOW SERVER used in a query window */ + query_change_server((QUERY_REC *) active_win->active, + (IRC_SERVER_REC *) server); + window_change_server(active_win, server); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_QUERY_SERVER_CHANGED, server->tag, server->connrec->address, + server->connrec->ircnet == NULL ? "" : server->connrec->ircnet); + + signal_stop(); + } +} + +static void cmd_wquery(const char *data, void *server, WI_ITEM_REC *item) +{ + signal_add("query created", (SIGNAL_FUNC) signal_query_created_curwin); + signal_emit("command query", 3, data, server, item); + signal_remove("query created", (SIGNAL_FUNC) signal_query_created_curwin); +} + +void fe_query_init(void) +{ + signal_add("query created", (SIGNAL_FUNC) signal_query_created); + signal_add("query destroyed", (SIGNAL_FUNC) signal_query_destroyed); + signal_add("window item remove", (SIGNAL_FUNC) signal_window_item_removed); + signal_add("server connected", (SIGNAL_FUNC) sig_server_connected); + + command_bind("wquery", NULL, (SIGNAL_FUNC) cmd_wquery); + command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server); +} + +void fe_query_deinit(void) +{ + signal_remove("query created", (SIGNAL_FUNC) signal_query_created); + signal_remove("query destroyed", (SIGNAL_FUNC) signal_query_destroyed); + signal_remove("window item remove", (SIGNAL_FUNC) signal_window_item_removed); + signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected); + + command_unbind("wquery", (SIGNAL_FUNC) cmd_wquery); + command_unbind("window server", (SIGNAL_FUNC) cmd_window_server); +} diff --git a/src/fe-common/irc/flood/Makefile.am b/src/fe-common/irc/flood/Makefile.am new file mode 100644 index 00000000..c802dfd9 --- /dev/null +++ b/src/fe-common/irc/flood/Makefile.am @@ -0,0 +1,17 @@ +noinst_LTLIBRARIES = libfe_common_irc_flood.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + -DHELPDIR=\""$(datadir)/irssi/help"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +libfe_common_irc_flood_la_SOURCES = \ + fe-flood.c \ + module-formats.c + +noinst_headers = \ + module-formats.h diff --git a/src/fe-common/irc/flood/fe-flood.c b/src/fe-common/irc/flood/fe-flood.c new file mode 100644 index 00000000..d21d6952 --- /dev/null +++ b/src/fe-common/irc/flood/fe-flood.c @@ -0,0 +1,54 @@ +/* + fe-flood.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "levels.h" + +#include "irc-server.h" +#include "irc/flood/autoignore.h" + +static void event_autoignore_new(IRC_SERVER_REC *server, AUTOIGNORE_REC *ignore) +{ + g_return_if_fail(ignore != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_AUTOIGNORE, + ignore->nick, (ignore->timeleft+59)/60); +} + +static void event_autoignore_remove(IRC_SERVER_REC *server, AUTOIGNORE_REC *ignore) +{ + g_return_if_fail(ignore != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_AUTOUNIGNORE, ignore->nick); +} + +void fe_flood_init(void) +{ + signal_add("autoignore new", (SIGNAL_FUNC) event_autoignore_new); + signal_add("autoignore remove", (SIGNAL_FUNC) event_autoignore_remove); +} + +void fe_flood_deinit(void) +{ + signal_remove("autoignore new", (SIGNAL_FUNC) event_autoignore_new); + signal_remove("autoignore remove", (SIGNAL_FUNC) event_autoignore_remove); +} diff --git a/src/fe-common/irc/flood/module-formats.c b/src/fe-common/irc/flood/module-formats.c new file mode 100644 index 00000000..942c13d6 --- /dev/null +++ b/src/fe-common/irc/flood/module-formats.c @@ -0,0 +1,33 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC fecommon_irc_flood_formats[] = +{ + { MODULE_NAME, N_("Flood"), 0 }, + + /* ---- */ + { NULL, N_("Autoignore"), 0 }, + + { "autoignore", N_("Flood detected from %_$0%_, autoignoring for %_$1%_ minutes"), 2, { 0, 1 } }, + { "autounignore", N_("Unignoring %_$0"), 1, { 0 } } +}; diff --git a/src/fe-common/irc/flood/module-formats.h b/src/fe-common/irc/flood/module-formats.h new file mode 100644 index 00000000..b435a752 --- /dev/null +++ b/src/fe-common/irc/flood/module-formats.h @@ -0,0 +1,13 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_FILL_1, + + IRCTXT_AUTOIGNORE, + IRCTXT_AUTOUNIGNORE +}; + +extern FORMAT_REC fecommon_irc_flood_formats[]; +#define MODULE_FORMATS fecommon_irc_flood_formats diff --git a/src/fe-common/irc/irc-hilight-text.c b/src/fe-common/irc/irc-hilight-text.c new file mode 100644 index 00000000..da9bb258 --- /dev/null +++ b/src/fe-common/irc/irc-hilight-text.c @@ -0,0 +1,54 @@ +/* + irc-hilight-text.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "hilight-text.h" + +char *irc_hilight_find_nick(const char *channel, const char *nick, const char *address) +{ + GSList *tmp; + char *color; + int len, best_match; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(address != NULL, NULL); + + color = NULL; best_match = 0; + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (!rec->nickmask) + continue; + + len = strlen(rec->text); + if (best_match < len) { + best_match = len; + color = rec->color; + } + } + + if (best_match == 0) + return NULL; + + if (color == NULL) color = "\00316"; + return g_strconcat(isdigit(*color) ? "\003" : "", color, NULL); +} diff --git a/src/fe-common/irc/irc-hilight-text.h b/src/fe-common/irc/irc-hilight-text.h new file mode 100644 index 00000000..6acf8a8b --- /dev/null +++ b/src/fe-common/irc/irc-hilight-text.h @@ -0,0 +1,6 @@ +#ifndef __IRC_HILIGHT_TEXT_H +#define __IRC_HILIGHT_TEXT_H + +char *irc_hilight_find_nick(const char *channel, const char *nick, const char *address); + +#endif diff --git a/src/fe-common/irc/irc-nick-hilight.c b/src/fe-common/irc/irc-nick-hilight.c new file mode 100644 index 00000000..0d790822 --- /dev/null +++ b/src/fe-common/irc/irc-nick-hilight.c @@ -0,0 +1,89 @@ +/* + irc-nick-hilight.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "levels.h" + +#include "irc.h" +#include "ignore.h" +#include "irc-server.h" + +#include "completion.h" +#include "windows.h" +#include "window-items.h" + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + WINDOW_REC *window; + WI_ITEM_REC *item; + char *params, *target, *msg; + int level; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + + if (*msg == 1) { + /* don't hilight CTCPs */ + g_free(params); + return; + } + + /* get window and window item */ + level = ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; + item = window_item_find(server, ischannel(*target) ? target : nick); + window = item == NULL ? + window_find_closest(server, target, GPOINTER_TO_INT(level)) : + window_item_window(item); + + /* check that msg wasn't send to current window and + that it didn't get ignored */ + if (window != active_win && !ignore_check(server, nick, addr, target, msg, level)) { + /* hilight */ + level = !ischannel(*target) || + completion_msgtoyou((SERVER_REC *) server, msg) ? + NEWDATA_MSG_FORYOU : NEWDATA_MSG; + if (item != NULL && item->new_data < level) { + item->new_data = level; + signal_emit("window item hilight", 1, item); + } else { + int oldlevel = window->new_data; + + if (window->new_data < level) { + window->new_data = level; + signal_emit("window hilight", 2, window, GINT_TO_POINTER(oldlevel)); + } + signal_emit("window activity", 2, window, GINT_TO_POINTER(oldlevel)); + } + } + + g_free(params); +} + +void irc_nick_hilight_init(void) +{ + signal_add_last("event privmsg", (SIGNAL_FUNC) event_privmsg); +} + +void irc_nick_hilight_deinit(void) +{ + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); +} diff --git a/src/fe-common/irc/module-formats.c b/src/fe-common/irc/module-formats.c new file mode 100644 index 00000000..5097e2aa --- /dev/null +++ b/src/fe-common/irc/module-formats.c @@ -0,0 +1,174 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC fecommon_irc_formats[] = +{ + { MODULE_NAME, N_("IRC"), 0 }, + + /* ---- */ + { NULL, N_("Server"), 0 }, + + { "lag_disconnected", N_("No PONG reply from server %_$0%_ in $1 seconds, disconnecting"), 2, { 0, 1 } }, + { "disconnected", N_("Disconnected from %_$0%_ %K[%n$1%K]"), 2, { 0, 0 } }, + { "server_list", N_("%_$0%_: $1:$2 ($3)"), 5, { 0, 0, 1, 0, 0 } }, + { "server_lookup_list", N_("%_$0%_: $1:$2 ($3) (connecting...)"), 5, { 0, 0, 1, 0, 0 } }, + { "server_reconnect_list", N_("%_$0%_: $1:$2 ($3) ($5 left before reconnecting)"), 6, { 0, 0, 1, 0, 0, 0 } }, + { "server_reconnect_removed", N_("Removed reconnection to server %_$0%_ port %_$1%_"), 3, { 0, 1, 0 } }, + { "server_reconnect_not_found", N_("Reconnection tag %_$0%_ not found"), 1, { 0 } }, + { "query_server_changed", N_("Query with %_$2%_ changed to server %_$1%_"), 3, { 0, 0, 0 } }, + + /* ---- */ + { NULL, N_("Channels"), 0 }, + + { "join", N_("%c%_$0%_ %K[%c$1%K]%n has joined %_$2"), 3, { 0, 0, 0 } }, + { "part", N_("%c$0 %K[%n$1%K]%n has left %_$2%_ %K[%n$3%K]"), 4, { 0, 0, 0, 0 } }, + { "joinerror_toomany", N_("Cannot join to channel %_$0%_ %K(%nYou have joined to too many channels%K)"), 1, { 0 } }, + { "joinerror_full", N_("Cannot join to channel %_$0%_ %K(%nChannel is full%K)"), 1, { 0 } }, + { "joinerror_invite", N_("Cannot join to channel %_$0%_ %K(%nYou must be invited%K)"), 1, { 0 } }, + { "joinerror_banned", N_("Cannot join to channel %_$0%_ %K(%nYou are banned%K)"), 1, { 0 } }, + { "joinerror_bad_key", N_("Cannot join to channel %_$0%_ %K(%nBad channel key%K)"), 1, { 0 } }, + { "joinerror_bad_mask", N_("Cannot join to channel %_$0%_ %K(%nBad channel mask%K)"), 1, { 0 } }, + { "joinerror_unavail", N_("Cannot join to channel %_$0%_ %K(%nChannel is temporarily unavailable%K)"), 1, { 0 } }, + { "kick", N_("%c$0%n was kicked from %_$1%_ by %_$2%_ %K[%n$3%K]"), 4, { 0, 0, 0, 0 } }, + { "quit", N_("%c$0 %K[%n$1%K]%n has quit IRC %K[%n$2%K]"), 3, { 0, 0, 0 } }, + { "quit_once", N_("%_$3%_ %c$0 %K[%n$1%K]%n has quit IRC %K[%n$2%K]"), 4, { 0, 0, 0, 0 } }, + { "invite", N_("%_$0%_ invites you to %_$1"), 2, { 0, 0 } }, + { "not_invited", N_("You have not been invited to a channel!"), 0 }, + { "names", N_("%K[%g%_Users%_%K(%g$0%K)]%n $1"), 2, { 0, 0 } }, + { "endofnames", N_("%g%_$0%_%K:%n Total of %_$1%_ nicks %K[%n%_$2%_ ops, %_$3%_ voices, %_$4%_ normal%K]"), 5, { 0, 1, 1, 1, 1 } }, + { "channel_created", N_("Channel %_$0%_ created $1"), 2, { 0, 0 } }, + { "topic", N_("Topic for %c$0%K:%n $1"), 2, { 0, 0 } }, + { "no_topic", N_("No topic set for %c$0"), 1, { 0 } }, + { "new_topic", N_("%_$0%_ changed the topic of %c$1%n to%K:%n $2"), 3, { 0, 0, 0 } }, + { "topic_unset", N_("Topic unset by %_$0%_ on %c$1"), 2, { 0, 0 } }, + { "topic_info", N_("Topic set by %_$0%_ %K[%n$1%K]"), 2, { 0, 0 } }, + { "chanmode_change", N_("mode/%c$0 %K[%n$1%K]%n by %_$2"), 3, { 0, 0, 0 } }, + { "server_chanmode_change", N_("%RServerMode/%c$0 %K[%n$1%K]%n by %_$2"), 3, { 0, 0, 0 } }, + { "channel_mode", N_("mode/%c$0 %K[%n$1%K]"), 2, { 0, 0 } }, + { "bantype", N_("Ban type changed to %_$0"), 1, { 0 } }, + { "banlist", N_("%_$0%_: ban %c$1"), 2, { 0, 0 } }, + { "banlist_long", N_("%_$0%_: ban %c$1 %K[%nby %_$2%_, $3 secs ago%K]"), 4, { 0, 0, 0, 1 } }, + { "ebanlist", N_("%_$0%_: ban exception %c$1"), 2, { 0, 0 } }, + { "ebanlist_long", N_("%_$0%_: ban exception %c$1 %K[%nby %_$2%_, $3 secs ago%K]"), 4, { 0, 0, 0, 1 } }, + { "invitelist", N_("%_$0%_: invite %c$1"), 2, { 0, 0 } }, + { "no_such_channel", N_("$0: No such channel"), 1, { 0 } }, + { "not_in_channels", N_("You are not on any channels"), 0 }, + { "current_channel", N_("Current channel $0"), 1, { 0 } }, + { "chanlist_header", N_("You are on the following channels:"), 0 }, + { "chanlist_line", N_("$[-10]0 %|+$1 ($2): $3"), 4, { 0, 0, 0, 0 } }, + { "channel_synced", N_("Join to %_$0%_ was synced in %_$1%_ secs"), 2, { 0, 2 } }, + + /* ---- */ + { NULL, N_("Nick"), 0 }, + + { "usermode_change", N_("Mode change %K[%n%_$0%_%K]%n for user %c$1"), 2, { 0, 0 } }, + { "user_mode", N_("Your user mode is %K[%n%_$0%_%K]"), 1, { 0 } }, + { "away", N_("You have been marked as being away"), 0 }, + { "unaway", N_("You are no longer marked as being away"), 0 }, + { "nick_away", N_("$0 is away: $1"), 2, { 0, 0 } }, + { "no_such_nick", N_("$0: No such nick/channel"), 1, { 0 } }, + { "your_nick", N_("Your nickname is $0"), 1, { 0 } }, + { "your_nick_changed", N_("You're now known as %c$0"), 1, { 0 } }, + { "nick_changed", N_("%_$0%_ is now known as %c$1"), 2, { 0, 0 } }, + { "nick_in_use", N_("Nick %_$0%_ is already in use"), 1, { 0 } }, + { "nick_unavailable", N_("Nick %_$0%_ is temporarily unavailable"), 1, { 0 } }, + { "your_nick_owned", N_("Your nick is owned by %_$3%_ %K[%n$1@$2%K]"), 4, { 0, 0, 0, 0 } }, + + /* ---- */ + { NULL, N_("Who queries"), 0 }, + + { "whois", N_("%_$0%_ %K[%n$1@$2%K]%n%: ircname : $3"), 4, { 0, 0, 0, 0 } }, + { "whois_idle", N_(" idle : $1 hours $2 mins $3 secs"), 4, { 0, 1, 1, 1 } }, + { "whois_idle_signon", N_(" idle : $1 hours $2 mins $3 secs %K[%nsignon: $4%K]"), 5, { 0, 1, 1, 1, 0 } }, + { "whois_server", N_(" server : $1 %K[%n$2%K]"), 3, { 0, 0, 0 } }, + { "whois_oper", N_(" : %_IRC operator%_"), 1, { 0 } }, + { "whois_channels", N_(" channels : $1"), 2, { 0, 0 } }, + { "whois_away", N_(" away : $1"), 2, { 0, 0 } }, + { "end_of_whois", N_("End of WHOIS"), 1, { 0 } }, + { "who", N_("$[-10]0 %|%_$[!9]1%_ $[!3]2 $[!2]3 $4@$5 %K(%W$6%K)"), 7, { 0, 0, 0, 0, 0, 0, 0 } }, + { "end_of_who", N_("End of /WHO list"), 1, { 0 } }, + + /* ---- */ + { NULL, N_("Your messages"), 0 }, + + { "own_msg", N_("%K<%n$2%W$0%K>%n %|$1"), 3, { 0, 0, 0 } }, + { "own_msg_channel", N_("%K<%n$3%W$0%K:%c$1%K>%n %|$2"), 4, { 0, 0, 0, 0 } }, + { "own_msg_private", N_("%K[%rmsg%K(%R$0%K)]%n $1"), 2, { 0, 0 } }, + { "own_msg_private_query", N_("%K<%W$2%K>%n %|$1"), 3, { 0, 0, 0 } }, + { "own_notice", N_("%K[%rnotice%K(%R$0%K)]%n $1"), 2, { 0, 0 } }, + { "own_me", N_("%W * $0%n $1"), 2, { 0, 0 } }, + { "own_ctcp", N_("%K[%rctcp%K(%R$0%K)]%n $1 $2"), 3, { 0, 0, 0 } }, + + /* ---- */ + { NULL, N_("Received messages"), 0 }, + + { "pubmsg_me", N_("%K<%n$2%Y$0%K>%n %|$1"), 3, { 0, 0, 0 } }, + { "pubmsg_me_channel", N_("%K<%n$3%Y$0%K:%c$1%K>%n %|$2"), 4, { 0, 0, 0, 0 } }, + { "pubmsg_hilight", N_("%K<%n$3$0$1%K>%n %|$2"), 4, { 0, 0, 0, 0 } }, + { "pubmsg_hilight_channel", N_("%K<%n$4$0$1%K:%c$2%K>%n %|$3"), 5, { 0, 0, 0, 0, 0 } }, + { "pubmsg", N_("%K<%n$2$0%K>%n %|$1"), 3, { 0, 0, 0 } }, + { "pubmsg_channel", N_("%K<%n$3$0%K:%c$1%K>%n %|$2"), 4, { 0, 0, 0, 0 } }, + { "msg_private", N_("%K[%R$0%K(%r$1%K)]%n $2"), 3, { 0, 0, 0 } }, + { "msg_private_query", N_("%K<%R$0%K>%n %|$2"), 3, { 0, 0, 0 } }, + { "notice_server", N_("%g!$0%n $1"), 2, { 0, 0 } }, + { "notice_public", N_("%K-%M$0%K:%m$1%K-%n $2"), 3, { 0, 0, 0 } }, + { "notice_public_ops", N_("%K-%M$0%K:%m@$1%K-%n $2"), 3, { 0, 0, 0 } }, + { "notice_private", N_("%K-%M$0%K(%m$1%K)-%n $2"), 3, { 0, 0, 0 } }, + { "action_private", N_("%W (*) $0%n $2"), 3, { 0, 0, 0 } }, + { "action_private_query", N_("%W * $0%n $2"), 3, { 0, 0, 0 } }, + { "action_public", N_("%W * $0%n $1"), 2, { 0, 0 } }, + { "action_public_channel", N_("%W * $0%K:%c$1%n $2"), 3, { 0, 0, 0 } }, + + /* ---- */ + { NULL, N_("CTCPs"), 0 }, + + { "ctcp_reply", N_("CTCP %_$0%_ reply from %_$1%_%K:%n $2"), 3, { 0, 0, 0 } }, + { "ctcp_ping_reply", N_("CTCP %_PING%_ reply from %_$0%_: $1.$2 seconds"), 3, { 0, 2, 2 } }, + { "ctcp_requested", N_("%g>>> %_$0%_ %K[%g$1%K] %grequested %_$2%_ from %_$3"), 4, { 0, 0, 0, 0 } }, + + /* ---- */ + { NULL, N_("Other server events"), 0 }, + + { "online", N_("Users online: %_$0"), 1, { 0 } }, + { "pong", N_("PONG received from $0: $1"), 2, { 0, 0 } }, + { "wallops", N_("%WWALLOP%n $0: $1"), 2, { 0, 0 } }, + { "action_wallops", N_("%WWALLOP * $0%n $1"), 2, { 0, 0 } }, + { "error", N_("%_ERROR%_ $0"), 1, { 0 } }, + { "unknown_mode", N_("Unknown mode character $0"), 1, { 0 } }, + { "not_chanop", N_("You're not channel operator in $0"), 1, { 0 } }, + + /* ---- */ + { NULL, N_("Misc"), 0 }, + + { "ignored", N_("Ignoring %_$1%_ from %_$0%_"), 2, { 0, 0 } }, + { "unignored", N_("Unignored %_$0%_"), 1, { 0 } }, + { "ignore_not_found", N_("%_$0%_ is not being ignored"), 1, { 0 } }, + { "ignore_no_ignores", N_("There are no ignores"), 0 }, + { "ignore_header", N_("Ignorance List:"), 0 }, + { "ignore_line", N_("$[-4]0 $1: $2 $3 $4"), 5, { 1, 0, 0, 0, 0 } }, + { "ignore_footer", N_(""), 0 }, + { "talking_in", N_("You are now talking in %_$0%_"), 1, { 0 } }, + { "no_query", N_("No query with %_$0%_"), 1, { 0 } }, + { "no_msgs_got", N_("You have not received a message from anyone yet"), 0 }, + { "no_msgs_sent", N_("You have not sent a message to anyone yet"), 0 } +}; diff --git a/src/fe-common/irc/module-formats.h b/src/fe-common/irc/module-formats.h new file mode 100644 index 00000000..004e6008 --- /dev/null +++ b/src/fe-common/irc/module-formats.h @@ -0,0 +1,146 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_FILL_1, + + IRCTXT_LAG_DISCONNECTED, + IRCTXT_DISCONNECTED, + IRCTXT_SERVER_LIST, + IRCTXT_SERVER_LOOKUP_LIST, + IRCTXT_SERVER_RECONNECT_LIST, + IRCTXT_RECONNECT_REMOVED, + IRCTXT_RECONNECT_NOT_FOUND, + IRCTXT_QUERY_SERVER_CHANGED, + + IRCTXT_FILL_2, + + IRCTXT_JOIN, + IRCTXT_PART, + IRCTXT_JOINERROR_TOOMANY, + IRCTXT_JOINERROR_FULL, + IRCTXT_JOINERROR_INVITE, + IRCTXT_JOINERROR_BANNED, + IRCTXT_JOINERROR_BAD_KEY, + IRCTXT_JOINERROR_BAD_MASK, + IRCTXT_JOINERROR_UNAVAIL, + IRCTXT_KICK, + IRCTXT_QUIT, + IRCTXT_QUIT_ONCE, + IRCTXT_INVITE, + IRCTXT_NOT_INVITED, + IRCTXT_NAMES, + IRCTXT_ENDOFNAMES, + IRCTXT_CHANNEL_CREATED, + IRCTXT_TOPIC, + IRCTXT_NO_TOPIC, + IRCTXT_NEW_TOPIC, + IRCTXT_TOPIC_UNSET, + IRCTXT_TOPIC_INFO, + IRCTXT_CHANMODE_CHANGE, + IRCTXT_SERVER_CHANMODE_CHANGE, + IRCTXT_CHANNEL_MODE, + IRCTXT_BANTYPE, + IRCTXT_BANLIST, + IRCTXT_BANLIST_LONG, + IRCTXT_EBANLIST, + IRCTXT_EBANLIST_LONG, + IRCTXT_INVITELIST, + IRCTXT_NO_SUCH_CHANNEL, + IRCTXT_NOT_IN_CHANNELS, + IRCTXT_CURRENT_CHANNEL, + IRCTXT_CHANLIST_HEADER, + IRCTXT_CHANLIST_LINE, + IRCTXT_CHANNEL_SYNCED, + + IRCTXT_FILL_4, + + IRCTXT_USERMODE_CHANGE, + IRCTXT_USER_MODE, + IRCTXT_AWAY, + IRCTXT_UNAWAY, + IRCTXT_NICK_AWAY, + IRCTXT_NO_SUCH_NICK, + IRCTXT_YOUR_NICK, + IRCTXT_YOUR_NICK_CHANGED, + IRCTXT_NICK_CHANGED, + IRCTXT_NICK_IN_USE, + IRCTXT_NICK_UNAVAILABLE, + IRCTXT_YOUR_NICK_OWNED, + + IRCTXT_FILL_5, + + IRCTXT_WHOIS, + IRCTXT_WHOIS_IDLE, + IRCTXT_WHOIS_IDLE_SIGNON, + IRCTXT_WHOIS_SERVER, + IRCTXT_WHOIS_OPER, + IRCTXT_WHOIS_CHANNELS, + IRCTXT_WHOIS_AWAY, + IRCTXT_END_OF_WHOIS, + IRCTXT_WHO, + IRCTXT_END_OF_WHO, + + IRCTXT_FILL_6, + + IRCTXT_OWN_MSG, + IRCTXT_OWN_MSG_CHANNEL, + IRCTXT_OWN_MSG_PRIVATE, + IRCTXT_OWN_MSG_PRIVATE_QUERY, + IRCTXT_OWN_NOTICE, + IRCTXT_OWN_ME, + IRCTXT_OWN_CTCP, + + IRCTXT_FILL_7, + + IRCTXT_PUBMSG_ME, + IRCTXT_PUBMSG_ME_CHANNEL, + IRCTXT_PUBMSG_HILIGHT, + IRCTXT_PUBMSG_HILIGHT_CHANNEL, + IRCTXT_PUBMSG, + IRCTXT_PUBMSG_CHANNEL, + IRCTXT_MSG_PRIVATE, + IRCTXT_MSG_PRIVATE_QUERY, + IRCTXT_NOTICE_SERVER, + IRCTXT_NOTICE_PUBLIC, + IRCTXT_NOTICE_PUBLIC_OPS, + IRCTXT_NOTICE_PRIVATE, + IRCTXT_ACTION_PRIVATE, + IRCTXT_ACTION_PRIVATE_QUERY, + IRCTXT_ACTION_PUBLIC, + IRCTXT_ACTION_PUBLIC_CHANNEL, + + IRCTXT_FILL_8, + + IRCTXT_CTCP_REPLY, + IRCTXT_CTCP_PING_REPLY, + IRCTXT_CTCP_REQUESTED, + + IRCTXT_FILL_10, + + IRCTXT_ONLINE, + IRCTXT_PONG, + IRCTXT_WALLOPS, + IRCTXT_ACTION_WALLOPS, + IRCTXT_ERROR, + IRCTXT_UNKNOWN_MODE, + IRCTXT_NOT_CHANOP, + + IRCTXT_FILL_11, + + IRCTXT_IGNORED, + IRCTXT_UNIGNORED, + IRCTXT_IGNORE_NOT_FOUND, + IRCTXT_IGNORE_NO_IGNORES, + IRCTXT_IGNORE_HEADER, + IRCTXT_IGNORE_LINE, + IRCTXT_IGNORE_FOOTER, + IRCTXT_TALKING_IN, + IRCTXT_NO_QUERY, + IRCTXT_NO_MSGS_GOT, + IRCTXT_NO_MSGS_SENT +}; + +extern FORMAT_REC fecommon_irc_formats[]; +#define MODULE_FORMATS fecommon_irc_formats diff --git a/src/fe-common/irc/module.h b/src/fe-common/irc/module.h new file mode 100644 index 00000000..e1cc2773 --- /dev/null +++ b/src/fe-common/irc/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "fe-common/irc" diff --git a/src/fe-common/irc/notifylist/Makefile.am b/src/fe-common/irc/notifylist/Makefile.am new file mode 100644 index 00000000..c44da278 --- /dev/null +++ b/src/fe-common/irc/notifylist/Makefile.am @@ -0,0 +1,17 @@ +noinst_LTLIBRARIES = libfe_common_irc_notifylist.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + -DHELPDIR=\""$(datadir)/irssi/help"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +libfe_common_irc_notifylist_la_SOURCES = \ + fe-notifylist.c \ + module-formats.c + +noinst_headers = \ + module-formats.h diff --git a/src/fe-common/irc/notifylist/fe-notifylist.c b/src/fe-common/irc/notifylist/fe-notifylist.c new file mode 100644 index 00000000..7520cdb8 --- /dev/null +++ b/src/fe-common/irc/notifylist/fe-notifylist.c @@ -0,0 +1,241 @@ +/* + fe-notifylist.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "levels.h" +#include "irc-server.h" +#include "ircnet-setup.h" +#include "irc/notifylist/notifylist.h" + +/* add the nick of a hostmask to list if it isn't there already */ +static GSList *mask_add_once(GSList *list, const char *mask) +{ + char *str, *ptr; + + g_return_val_if_fail(mask != NULL, NULL); + + ptr = strchr(mask, '!'); + str = ptr == NULL ? g_strdup(mask) : + g_strndup(mask, (int) (ptr-mask)+1); + + if (gslist_find_icase_string(list, str) == NULL) + return g_slist_append(list, str); + + g_free(str); + return list; +} + +/* search for online people, print them and update offline list */ +static void print_notify_onserver(IRC_SERVER_REC *server, GSList *nicks, + GSList **offline, const char *desc) +{ + GSList *tmp; + GString *str; + + g_return_if_fail(server != NULL); + g_return_if_fail(offline != NULL); + g_return_if_fail(desc != NULL); + + str = g_string_new(NULL); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + char *nick = tmp->data; + + if (!notifylist_ison_server(server, nick)) + continue; + + g_string_sprintfa(str, "%s, ", nick); + *offline = g_slist_remove(*offline, nick); + } + + if (str->len > 0) { + g_string_truncate(str, str->len-2); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_ONLINE, desc, str->str); + } + + g_string_free(str, TRUE); +} + +/* show the notify list, displaying who is on which net */ +static void cmd_notify_show(void) +{ + GSList *nicks, *offline, *tmp; + IRC_SERVER_REC *server; + + if (notifies == NULL) + return; + + /* build a list containing only the nicks */ + nicks = NULL; + for (tmp = notifies; tmp != NULL; tmp = tmp->next) { + NOTIFYLIST_REC *rec = tmp->data; + + nicks = mask_add_once(nicks, rec->mask); + } + offline = g_slist_copy(nicks); + + /* print the notifies on specific ircnets */ + for (tmp = ircnets; tmp != NULL; tmp = tmp->next) { + IRCNET_REC *rec = tmp->data; + + server = (IRC_SERVER_REC *) server_find_ircnet(rec->name); + if (server == NULL) continue; + + print_notify_onserver(server, nicks, &offline, rec->name); + } + + /* print the notifies on servers without a specified ircnet */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + server = tmp->data; + + if (server->connrec->ircnet != NULL) + continue; + print_notify_onserver(server, nicks, &offline, server->tag); + } + + /* print offline people */ + if (offline != NULL) { + GString *str; + + str = g_string_new(NULL); + for (tmp = offline; tmp != NULL; tmp = tmp->next) + g_string_sprintfa(str, "%s, ", (char *) tmp->data); + + g_string_truncate(str, str->len-2); + printformat(NULL,NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_OFFLINE, str->str); + g_string_free(str, TRUE); + + g_slist_free(offline); + } + + g_slist_foreach(nicks, (GFunc) g_free, NULL); + g_slist_free(nicks); +} + +static void notifylist_print(NOTIFYLIST_REC *rec) +{ + char idle[MAX_INT_STRLEN], *ircnets; + + if (rec->idle_check_time <= 0) + idle[0] = '\0'; + else + g_snprintf(idle, sizeof(idle), "%d", rec->idle_check_time); + + ircnets = rec->ircnets == NULL ? NULL : + g_strjoinv(",", rec->ircnets); + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NOTIFY_LIST, + rec->mask, ircnets != NULL ? ircnets : "", + rec->away_check ? "-away" : "", idle); + + g_free_not_null(ircnets); +} + +static void cmd_notifylist_show(void) +{ + g_slist_foreach(notifies, (GFunc) notifylist_print, NULL); +} + +static void cmd_notify(const char *data) +{ + if (*data == '\0') { + cmd_notify_show(); + signal_stop(); + } + + if (g_strcasecmp(data, "-list") == 0) { + cmd_notifylist_show(); + signal_stop(); + } +} + +static void notifylist_joined(IRC_SERVER_REC *server, const char *nick, + const char *username, const char *host, + const char *realname, const char *awaymsg) +{ + g_return_if_fail(nick != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_JOIN, + nick, username, host, realname, + server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet); +} + +static void notifylist_left(IRC_SERVER_REC *server, const char *nick, + const char *username, const char *host, + const char *realname, const char *awaymsg) +{ + g_return_if_fail(nick != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_PART, + nick, username, host, realname, + server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet); +} + +static void notifylist_away(IRC_SERVER_REC *server, const char *nick, + const char *username, const char *host, + const char *realname, const char *awaymsg) +{ + g_return_if_fail(nick != NULL); + + if (awaymsg != NULL) { + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_AWAY, + nick, username, host, realname, awaymsg, + server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet); + } else { + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_UNAWAY, + nick, username, host, realname, + server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet); + } +} + +static void notifylist_unidle(IRC_SERVER_REC *server, const char *nick, + const char *username, const char *host, + const char *realname, const char *awaymsg) +{ + g_return_if_fail(nick != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_UNIDLE, + nick, username, host, realname, awaymsg != NULL ? awaymsg : "", + server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet); +} + +void fe_notifylist_init(void) +{ + command_bind("notify", NULL, (SIGNAL_FUNC) cmd_notify); + signal_add("notifylist joined", (SIGNAL_FUNC) notifylist_joined); + signal_add("notifylist left", (SIGNAL_FUNC) notifylist_left); + signal_add("notifylist away changed", (SIGNAL_FUNC) notifylist_away); + signal_add("notifylist unidle", (SIGNAL_FUNC) notifylist_unidle); +} + +void fe_notifylist_deinit(void) +{ + command_unbind("notify", (SIGNAL_FUNC) cmd_notify); + signal_remove("notifylist joined", (SIGNAL_FUNC) notifylist_joined); + signal_remove("notifylist left", (SIGNAL_FUNC) notifylist_left); + signal_remove("notifylist away changed", (SIGNAL_FUNC) notifylist_away); + signal_remove("notifylist unidle", (SIGNAL_FUNC) notifylist_unidle); +} diff --git a/src/fe-common/irc/notifylist/module-formats.c b/src/fe-common/irc/notifylist/module-formats.c new file mode 100644 index 00000000..ad1d3f1e --- /dev/null +++ b/src/fe-common/irc/notifylist/module-formats.c @@ -0,0 +1,39 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC fecommon_irc_notifylist_formats[] = +{ + { MODULE_NAME, N_("Notifylist"), 0 }, + + /* ---- */ + { NULL, N_("Notifylist"), 0 }, + + { "notify_join", N_("%_$0%_ %K[%n$1@$2%K] [%n%_$3%_%K]%n has joined to $4"), 5, { 0, 0, 0, 0, 0 } }, + { "notify_part", N_("%_$0%_ has left $4"), 5, { 0, 0, 0, 0, 0 } }, + { "notify_away", N_("%_$0%_ %K[%n$5%K]%n %K[%n$1@$2%K] [%n%_$3%_%K]%n is now away: $4"), 6, { 0, 0, 0, 0, 0, 0 } }, + { "notify_unaway", N_("%_$0%_ %K[%n$4%K]%n %K[%n$1@$2%K] [%n%_$3%_%K]%n is now unaway"), 5, { 0, 0, 0, 0, 0 } }, + { "notify_unidle", N_("%_$0%_ %K[%n$5%K]%n %K[%n$1@$2%K] [%n%_$3%_%K]%n just stopped idling"), 6, { 0, 0, 0, 0, 0, 0 } }, + { "notify_online", N_("On $0: %_$1%_"), 2, { 0, 0 } }, + { "notify_offline", N_("Offline: $0"), 1, { 0 } }, + { "notify_list", N_("$0: $1 $2 $3"), 4, { 0, 0, 0, 0 } } +}; diff --git a/src/fe-common/irc/notifylist/module-formats.h b/src/fe-common/irc/notifylist/module-formats.h new file mode 100644 index 00000000..77b40cc2 --- /dev/null +++ b/src/fe-common/irc/notifylist/module-formats.h @@ -0,0 +1,19 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_FILL_1, + + IRCTXT_NOTIFY_JOIN, + IRCTXT_NOTIFY_PART, + IRCTXT_NOTIFY_AWAY, + IRCTXT_NOTIFY_UNAWAY, + IRCTXT_NOTIFY_UNIDLE, + IRCTXT_NOTIFY_ONLINE, + IRCTXT_NOTIFY_OFFLINE, + IRCTXT_NOTIFY_LIST +}; + +extern FORMAT_REC fecommon_irc_notifylist_formats[]; +#define MODULE_FORMATS fecommon_irc_notifylist_formats diff --git a/src/fe-none/Makefile.am b/src/fe-none/Makefile.am new file mode 100644 index 00000000..b61f4dd2 --- /dev/null +++ b/src/fe-none/Makefile.am @@ -0,0 +1,24 @@ +bin_PROGRAMS = irssi-bot + +INCLUDES = $(GLIB_CFLAGS) + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ + +irssi_bot_DEPENDENCIES = @IRC_LIBS@ @CORE_LIBS@ + +irssi_bot_LDADD = \ + @IRC_LIBS@ \ + @CORE_LIBS@ \ + $(PROG_LIBS) \ + $(INTLLIBS) \ + $(PERL_LDFLAGS) + +irssi_bot_SOURCES = \ + irssi.c + +noinst_HEADERS = \ + module.h diff --git a/src/fe-none/irssi.c b/src/fe-none/irssi.c new file mode 100644 index 00000000..1ec3ca84 --- /dev/null +++ b/src/fe-none/irssi.c @@ -0,0 +1,94 @@ +/* + irssi.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "args.h" +#include "signals.h" +#include "core.h" +#include "irc-core.h" + +void irc_init(void); +void irc_deinit(void); + +static GMainLoop *main_loop; +static char *autoload_module; +static int reload; + +static void sig_exit(void) +{ + g_main_quit(main_loop); +} + +static void sig_reload(void) +{ + reload = TRUE; +} + +void noui_init(void) +{ + static struct poptOption options[] = { + POPT_AUTOHELP + { "load", 'l', POPT_ARG_STRING, &autoload_module, 0, "Module to load (default = bot)", "MODULE" }, + { NULL, '\0', 0, NULL } + }; + + autoload_module = NULL; + args_register(options); + + irssi_gui = IRSSI_GUI_NONE; + core_init(); + irc_init(); + + signal_add("reload", (SIGNAL_FUNC) sig_reload); + signal_add("gui exit", (SIGNAL_FUNC) sig_exit); + signal_emit("irssi init finished", 0); +} + +void noui_deinit(void) +{ + signal_remove("reload", (SIGNAL_FUNC) sig_reload); + signal_remove("gui exit", (SIGNAL_FUNC) sig_exit); + irc_deinit(); + core_deinit(); +} + +int main(int argc, char **argv) +{ +#ifdef HAVE_SOCKS + SOCKSinit(argv[0]); +#endif + noui_init(); + args_execute(argc, argv); + + if (autoload_module == NULL) + autoload_module = "bot"; + + do { + reload = FALSE; + module_load(autoload_module, ""); + main_loop = g_main_new(TRUE); + g_main_run(main_loop); + g_main_destroy(main_loop); + } + while (reload); + noui_deinit(); + + return 0; +} diff --git a/src/fe-none/module.h b/src/fe-none/module.h new file mode 100644 index 00000000..1bb9e252 --- /dev/null +++ b/src/fe-none/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "fe-none" diff --git a/src/fe-text/Makefile.am b/src/fe-text/Makefile.am new file mode 100644 index 00000000..c651cf0e --- /dev/null +++ b/src/fe-text/Makefile.am @@ -0,0 +1,46 @@ +bin_PROGRAMS = irssi-text + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core/ \ + -I$(top_srcdir)/src/irc/core/ \ + -I$(top_srcdir)/src/fe-common/core/ \ + -I$(top_srcdir)/src/fe-common/irc/ \ + $(CURSES_INCLUDEDIR) + +irssi_text_DEPENDENCIES = @COMMON_LIBS@ + +irssi_text_LDADD = \ + @COMMON_LIBS@ \ + $(PROG_LIBS) \ + $(CURSES_LIBS) \ + $(INTLLIBS) \ + $(PERL_LDFLAGS) + +irssi_text_SOURCES = \ + gui-entry.c \ + gui-mainwindows.c \ + gui-printtext.c \ + gui-readline.c \ + gui-special-vars.c \ + gui-statusbar.c \ + gui-statusbar-items.c \ + gui-textwidget.c \ + gui-windows.c \ + irssi.c \ + module-formats.c \ + screen.c + +noinst_HEADERS = \ + gui-entry.h \ + gui-mainwindows.h \ + gui-printtext.h \ + gui-readline.h \ + gui-special-vars.h \ + gui-statusbar.h \ + gui-statusbar-items.h \ + gui-textwidget.h \ + gui-windows.h \ + module-formats.h \ + screen.h diff --git a/src/fe-text/gui-entry.c b/src/fe-text/gui-entry.c new file mode 100644 index 00000000..d4505195 --- /dev/null +++ b/src/fe-text/gui-entry.c @@ -0,0 +1,177 @@ +/* + gui-entry.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "screen.h" + +static GString *entry; +static int promptlen, pos, scrstart, scrpos; +static char *prompt; + +static void entry_screenpos(void) +{ + if (pos-scrstart < COLS-1-promptlen && pos-scrstart > 0) { + scrpos = pos-scrstart; + return; + } + + if (pos < COLS-1-promptlen) { + scrstart = 0; + scrpos = pos; + } else { + scrpos = COLS-1-promptlen; + scrstart = pos-scrpos; + } +} + +static void entry_update(void) +{ + char *p; + int n, len; + + len = entry->len-scrstart > COLS-promptlen ? + COLS-promptlen : entry->len-scrstart; + + set_color(0); + move(LINES-1, promptlen); + + for (p = entry->str+scrstart, n = 0; n < len; n++, p++) { + if ((unsigned char) *p >= 32) + addch((unsigned char) *p); + else { + set_color(ATTR_REVERSE); + addch(*p+'A'-1); + set_color(0); + } + } + clrtoeol(); + + move_cursor(LINES-1, scrpos+promptlen); + screen_refresh(); +} + +void gui_entry_set_prompt(const char *str) +{ + if (str != NULL) { + if (prompt != NULL) g_free(prompt); + prompt = g_strdup(str); + + promptlen = strlen(prompt); + if (promptlen > 20) { + promptlen = 20; + prompt[20] = '\0'; + } + } + + set_color(0); + mvaddstr(LINES-1, 0, prompt); + + entry_screenpos(); + entry_update(); +} + +void gui_entry_set_text(const char *str) +{ + g_return_if_fail(str != NULL); + + g_string_assign(entry, str); + pos = entry->len; + + entry_screenpos(); + entry_update(); +} + +char *gui_entry_get_text(void) +{ + return entry->str; +} + +void gui_entry_insert_text(const char *str) +{ + g_return_if_fail(str != NULL); + + g_string_insert(entry, pos, str); + pos += strlen(str); + + entry_screenpos(); + entry_update(); +} + +void gui_entry_insert_char(char chr) +{ + g_string_insert_c(entry, pos, chr); + pos++; + + entry_screenpos(); + entry_update(); +} + +void gui_entry_erase(int size) +{ + if (pos < size) return; + + pos -= size; + g_string_erase(entry, pos, size); + + entry_screenpos(); + entry_update(); +} + +int gui_entry_get_pos(void) +{ + return pos; +} + +void gui_entry_set_pos(int p) +{ + if (p >= 0 && p <= entry->len) + pos = p; +} + +void gui_entry_move_pos(int p) +{ + if (pos+p >= 0 && pos+p <= entry->len) + pos += p; + + entry_screenpos(); + entry_update(); +} + +void gui_entry_redraw(void) +{ + gui_entry_set_prompt(NULL); + + entry_screenpos(); + entry_update(); +} + +void gui_entry_init(void) +{ + entry = g_string_new(NULL); + pos = scrpos = 0; + prompt = NULL; promptlen = 0; +} + +void gui_entry_deinit(void) +{ + if (prompt != NULL) g_free(prompt); + g_string_free(entry, TRUE); +} diff --git a/src/fe-text/gui-entry.h b/src/fe-text/gui-entry.h new file mode 100644 index 00000000..443d2509 --- /dev/null +++ b/src/fe-text/gui-entry.h @@ -0,0 +1,22 @@ +#ifndef __GUI_ENTRY_H +#define __GUI_ENTRY_H + +void gui_entry_set_prompt(const char *str); + +void gui_entry_set_text(const char *str); +char *gui_entry_get_text(void); + +void gui_entry_insert_text(const char *str); +void gui_entry_insert_char(char chr); +void gui_entry_erase(int size); + +int gui_entry_get_pos(void); +void gui_entry_set_pos(int pos); +void gui_entry_move_pos(int pos); + +void gui_entry_redraw(void); + +void gui_entry_init(void); +void gui_entry_deinit(void); + +#endif diff --git a/src/fe-text/gui-mainwindows.c b/src/fe-text/gui-mainwindows.c new file mode 100644 index 00000000..d5d66c1d --- /dev/null +++ b/src/fe-text/gui-mainwindows.c @@ -0,0 +1,66 @@ +/* + gui-mainwindows.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "windows.h" +#include "gui-mainwindows.h" + +GList *mainwindows; + +MAIN_WINDOW_REC *gui_mainwindow_create(void) +{ + MAIN_WINDOW_REC *window; + + window = g_new0(MAIN_WINDOW_REC, 1); + mainwindows = g_list_append(mainwindows, window); + + return window; +} + +void gui_mainwindow_destroy(MAIN_WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + if (window->destroying) return; + + mainwindows = g_list_remove(mainwindows, window); + + window->destroying = TRUE; + while (window->children != NULL) + window_destroy(window->children->data); + window->destroying = FALSE; + + g_free(window); + + if (mainwindows == NULL) + signal_emit("gui exit", 0); +} + +void gui_mainwindows_init(void) +{ + mainwindows = NULL; +} + +void gui_mainwindows_deinit(void) +{ + while (mainwindows != NULL) + gui_mainwindow_destroy(mainwindows->data); +} diff --git a/src/fe-text/gui-mainwindows.h b/src/fe-text/gui-mainwindows.h new file mode 100644 index 00000000..b91f35a9 --- /dev/null +++ b/src/fe-text/gui-mainwindows.h @@ -0,0 +1,21 @@ +#ifndef __GUI_MAINWINDOWS_H +#define __GUI_MAINWINDOWS_H + +#include "windows.h" + +typedef struct { + WINDOW_REC *active; + GList *children; + + int destroying; +} MAIN_WINDOW_REC; + +extern GList *mainwindows; + +void gui_mainwindows_init(void); +void gui_mainwindows_deinit(void); + +MAIN_WINDOW_REC *gui_mainwindow_create(void); +void gui_mainwindow_destroy(MAIN_WINDOW_REC *window); + +#endif diff --git a/src/fe-text/gui-printtext.c b/src/fe-text/gui-printtext.c new file mode 100644 index 00000000..1ef4aa32 --- /dev/null +++ b/src/fe-text/gui-printtext.c @@ -0,0 +1,334 @@ +/* + gui-printtext.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "settings.h" + +#include "printtext.h" +#include "windows.h" +#include "themes.h" + +#include "screen.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +#define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-sizeof(char*)) + +static gint mirc_colors[] = { 15, 0, 1, 2, 4, 6, 5, 4, 14, 10, 3, 11, 9, 13, 8, 7, 15 }; +static gint max_textwidget_lines; + +static LINE_REC *create_line(GUI_WINDOW_REC *gui, gint level) +{ + g_return_val_if_fail(gui != NULL, NULL); + g_return_val_if_fail(gui->cur_text != NULL, NULL); + + gui->cur_line = g_mem_chunk_alloc(gui->line_chunk); + gui->cur_line->text = gui->cur_text->buffer+gui->cur_text->pos; + gui->cur_line->level = (gint32) GPOINTER_TO_INT(level); + gui->cur_line->time = time(NULL); + + gui->last_color = -1; + gui->last_flags = 0; + + gui->lines = g_list_append(gui->lines, gui->cur_line); + if (gui->startline == NULL) + { + gui->startline = gui->lines; + gui->bottom_startline = gui->lines; + } + return gui->cur_line; +} + +static TEXT_CHUNK_REC *create_text_chunk(GUI_WINDOW_REC *gui) +{ + TEXT_CHUNK_REC *rec; + guchar *buffer; + gchar *ptr; + + g_return_val_if_fail(gui != NULL, NULL); + + rec = g_new(TEXT_CHUNK_REC, 1); + rec->pos = 0; + rec->lines = 0; + + if (gui->cur_line != NULL && gui->cur_line->text != NULL) + { + /* mark the next block text block position.. */ + buffer = (guchar *) gui->cur_text->buffer+gui->cur_text->pos; + if (gui->cur_text->pos+2+sizeof(gchar *) > LINE_TEXT_CHUNK_SIZE) + g_error("create_text_chunk() : buffer overflow?!"); + *buffer++ = 0; *buffer++ = LINE_CMD_CONTINUE; + ptr = rec->buffer; + memcpy(buffer, &ptr, sizeof(gchar *)); + } + gui->cur_text = rec; + gui->text_chunks = g_slist_append(gui->text_chunks, rec); + return rec; +} + +static void text_chunk_free(GUI_WINDOW_REC *gui, TEXT_CHUNK_REC *chunk) +{ + g_return_if_fail(gui != NULL); + g_return_if_fail(chunk != NULL); + + gui->text_chunks = g_slist_remove(gui->text_chunks, chunk); + g_free(chunk); +} + +static void remove_first_line(WINDOW_REC *window) +{ + GUI_WINDOW_REC *gui; + TEXT_CHUNK_REC *chunk; + + g_return_if_fail(window != NULL); + + gui = WINDOW_GUI(window); + chunk = gui->text_chunks->data; + + if (--chunk->lines == 0) + text_chunk_free(gui, chunk); + + if (gui->startline->prev == NULL) + { + gui->startline = gui->startline->next; + gui->subline = 0; + } + if (gui->bottom_startline->prev == NULL) + { + gui->bottom_startline = gui->bottom_startline->next; + gui->bottom_subline = 0; + } + + window->lines--; + g_mem_chunk_free(gui->line_chunk, gui->lines->data); + gui->lines = g_list_remove(gui->lines, gui->lines->data); + + if (gui->startline->prev == NULL && is_window_visible(window)) + gui_window_redraw(window); +} + +static void get_colors(gint flags, gint *fg, gint *bg) +{ + if (flags & PRINTFLAG_MIRC_COLOR) + { + /* mirc colors */ + *fg = *fg < 0 || *fg > 16 ? + current_theme->default_color : mirc_colors[*fg]; + *bg = *bg < 0 || *bg > 16 ? 0 : mirc_colors[*bg]; + } + else + { + /* default colors */ + *fg = *fg < 0 || *fg > 15 ? + current_theme->default_color : *fg; + *bg = *bg < 0 || *bg > 15 ? 0 : *bg; + + if (*fg > 8) *fg -= 8; + } + + if (flags & PRINTFLAG_REVERSE) + { + gint tmp; + + tmp = *fg; *fg = *bg; *bg = tmp; + } + + if (*fg == 8) *fg |= ATTR_COLOR8; + if (flags & PRINTFLAG_BOLD) *fg |= 8; + if (flags & PRINTFLAG_UNDERLINE) *fg |= ATTR_UNDERLINE; + if (flags & PRINTFLAG_BLINK) *bg |= 0x80; +} + +static void linebuf_add(GUI_WINDOW_REC *gui, gchar *str, gint len) +{ + gint left; + + if (len == 0) return; + + while (gui->cur_text->pos+len >= TEXT_CHUNK_USABLE_SIZE) + { + left = TEXT_CHUNK_USABLE_SIZE-gui->cur_text->pos; + if (str[left-1] == 0) left--; /* don't split the command! */ + memcpy(gui->cur_text->buffer+gui->cur_text->pos, str, left); + gui->cur_text->pos += left; + create_text_chunk(gui); + len -= left; str += left; + } + + memcpy(gui->cur_text->buffer+gui->cur_text->pos, str, len); + gui->cur_text->pos += len; +} + +static void line_add_colors(GUI_WINDOW_REC *gui, gint fg, gint bg, gint flags) +{ + guchar buffer[12]; + gint color, pos; + + color = (fg & 0x0f) | (bg << 4); + pos = 0; + + if (((fg & ATTR_COLOR8) == 0 && (fg|(bg << 4)) != gui->last_color) || + ((fg & ATTR_COLOR8) && (fg & 0xf0) != (gui->last_color & 0xf0))) + { + buffer[pos++] = 0; + buffer[pos++] = (gchar) color; + } + + if ((flags & PRINTFLAG_UNDERLINE) != (gui->last_flags & PRINTFLAG_UNDERLINE)) + { + buffer[pos++] = 0; + buffer[pos++] = LINE_CMD_UNDERLINE; + } + if (fg & ATTR_COLOR8) + { + buffer[pos++] = 0; + buffer[pos++] = LINE_CMD_COLOR8; + } + if (flags & PRINTFLAG_BEEP) + { + buffer[pos++] = 0; + buffer[pos++] = LINE_CMD_BEEP; + } + if (flags & PRINTFLAG_INDENT) + { + buffer[pos++] = 0; + buffer[pos++] = LINE_CMD_INDENT; + } + + linebuf_add(gui, (gchar *) buffer, pos); + + gui->last_flags = flags; + gui->last_color = fg | (bg << 4); +} + +static void gui_printtext(WINDOW_REC *window, gpointer fgcolor, gpointer bgcolor, gpointer pflags, gchar *str, gpointer level) +{ + GUI_WINDOW_REC *gui; + LINE_REC *line; + gboolean visible; + gint fg, bg, flags, lines, n; + + g_return_if_fail(window != NULL); + + gui = WINDOW_GUI(window); + if (max_textwidget_lines > 0 && max_textwidget_lines <= window->lines) + remove_first_line(window); + + visible = is_window_visible(window) && gui->bottom; + flags = GPOINTER_TO_INT(pflags); + fg = GPOINTER_TO_INT(fgcolor); + bg = GPOINTER_TO_INT(bgcolor); + + if (gui->cur_text == NULL) + create_text_chunk(gui); + + /* \n can be only at the start of the line.. */ + if (*str == '\n') + { + linebuf_add(gui, "\0\x80", 2); /* mark EOL */ + line = create_line(gui, 0); + gui_window_newline(gui, visible); + str++; + gui->cur_text->lines++; + gui->last_subline = 0; + } + else + { + line = gui->cur_line != NULL ? gui->cur_line : + create_line(gui, 0); + if (line->level == 0) line->level = GPOINTER_TO_INT(level); + } + + get_colors(flags, &fg, &bg); + line_add_colors(gui, fg, bg, flags); + linebuf_add(gui, str, strlen(str)); + + /* temporarily mark the end of line. */ + memcpy(gui->cur_text->buffer+gui->cur_text->pos, "\0\x80", 2); + + if (visible) + { + /* draw the line to screen. */ + lines = gui_window_line_draw(gui, line, first_text_line+gui->ypos, gui->last_subline, -1); + } + else + { + /* we still need to update the bottom's position */ + lines = gui_window_get_linecount(gui, line)-1-gui->last_subline; + for (n = 0; n < lines; n++) + gui_window_newline(gui, visible); + } + if (lines > 0) gui->last_subline += lines; +} + +static void cmd_clear(gchar *data) +{ + GUI_WINDOW_REC *gui; + gint n; + + gui = WINDOW_GUI(active_win); + + if (is_window_visible(active_win)) + { + for (n = first_text_line; n < last_text_line; n++) + { + move(n, 0); + clrtoeol(); + } + screen_refresh(); + } + + gui->ypos = -1; + gui->bottom_startline = gui->startline = g_list_last(gui->lines); + gui->bottom_subline = gui->subline = gui->last_subline+1; + gui->empty_linecount = last_text_line-first_text_line; + gui->bottom = TRUE; +} + +static void sig_printtext_finished(WINDOW_REC *window) +{ + if (is_window_visible(window)) + screen_refresh(); +} + +static void read_settings(void) +{ + max_textwidget_lines = settings_get_int("max_textwidget_lines"); +} + +void gui_printtext_init(void) +{ + signal_add("gui print text", (SIGNAL_FUNC) gui_printtext); + command_bind("clear", NULL, (SIGNAL_FUNC) cmd_clear); + signal_add("print text finished", (SIGNAL_FUNC) sig_printtext_finished); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + read_settings(); +} + +void gui_printtext_deinit(void) +{ + signal_remove("gui print text", (SIGNAL_FUNC) gui_printtext); + command_unbind("clear", (SIGNAL_FUNC) cmd_clear); + signal_remove("print text finished", (SIGNAL_FUNC) sig_printtext_finished); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-text/gui-printtext.h b/src/fe-text/gui-printtext.h new file mode 100644 index 00000000..6c6e24d9 --- /dev/null +++ b/src/fe-text/gui-printtext.h @@ -0,0 +1,28 @@ +#ifndef __GUI_PRINTTEXT_H +#define __GUI_PRINTTEXT_H + +enum +{ + BLACK = 0, + BLUE, + GREEN, + CYAN, + RED, + MAGENTA, + YELLOW, + WHITE, + BBLACK, + BBLUE, + BGREEN, + BCYAN, + BRED, + BMAGENTA, + BYELLOW, + BWHITE, + NUM_COLORS +}; + +void gui_printtext_init(void); +void gui_printtext_deinit(void); + +#endif diff --git a/src/fe-text/gui-readline.c b/src/fe-text/gui-readline.c new file mode 100644 index 00000000..e53c628b --- /dev/null +++ b/src/fe-text/gui-readline.c @@ -0,0 +1,374 @@ +/* + gui-readline.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "server.h" +#include "misc.h" + +#include "completion.h" +#include "command-history.h" +#include "keyboard.h" +#include "translation.h" +#include "windows.h" + +#include "screen.h" +#include "gui-entry.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +#include <signal.h> + +static gint readtag, sigint_count = 0; +static time_t idle_time; + +static void window_prev_page(void) +{ + gui_window_scroll(active_win, -(last_text_line-first_text_line)/2); +} + +static void window_next_page(void) +{ + gui_window_scroll(active_win, (last_text_line-first_text_line)/2); +} + +static char *get_key_name(int key) +{ + static char str[MAX_INT_STRLEN]; + + switch (key) { + case KEY_HOME: + return "Home"; + case KEY_END: + return "End"; + case KEY_PPAGE: + return "Prior"; + case KEY_NPAGE: + return "Next"; + case KEY_UP: + return "Up"; + case KEY_DOWN: + return "Down"; + case KEY_LEFT: + return "Left"; + case KEY_RIGHT: + return "Right"; + default: + ltoa(str, key); + return str; + } +} + +void handle_key(gint key) +{ + const char *text; + char *str; + int c; + + /* Quit if we get 5 CTRL-C's in a row. */ + if (key != 3) + sigint_count = 0; + else if (++sigint_count >= 5) + raise(SIGTERM); + + idle_time = time(NULL); + switch (key) + { + case 27: + c = getch(); + if (c == toupper(c) && c != tolower(c)) + str = g_strdup_printf("ALT-SHIFT-%c", c); + else { + if (c < 256) + str = g_strdup_printf("ALT-%c", toupper(c)); + else + str = g_strdup_printf("ALT-%s", get_key_name(c)); + } + key_pressed(str, NULL); + g_free(str); + break; + + case KEY_HOME: + /* home */ + gui_entry_set_pos(0); + gui_entry_move_pos(0); + break; + case KEY_END: + /* end */ + gui_entry_set_pos(strlen(gui_entry_get_text())); + gui_entry_move_pos(0); + break; + case KEY_PPAGE: + /* page up */ + window_prev_page(); + break; + case KEY_NPAGE: + /* page down */ + window_next_page(); + break; + + case KEY_UP: + /* up */ + text = command_history_prev(active_win, gui_entry_get_text()); + gui_entry_set_text(text); + break; + case KEY_DOWN: + /* down */ + text = command_history_next(active_win, gui_entry_get_text()); + gui_entry_set_text(text); + break; + case KEY_RIGHT: + /* right */ + gui_entry_move_pos(1); + break; + case KEY_LEFT: + /* left */ + gui_entry_move_pos(-1); + break; + + case 21: + /* Ctrl-U, clear line */ + gui_entry_set_text(""); + break; + + case 9: + key_pressed("Tab", NULL); + break; + + case 8: + case 127: + case KEY_BACKSPACE: + gui_entry_erase(1); + break; + + case 0: + /* Ctrl-space - ignore */ + break; + case 1: + /* C-A, home */ + gui_entry_set_pos(0); + gui_entry_move_pos(0); + break; + case 5: + /* C-E, end */ + gui_entry_set_pos(strlen(gui_entry_get_text())); + gui_entry_move_pos(0); + break; + + case '\n': + case 13: + key_pressed("Return", NULL); + + str = gui_entry_get_text(); + if (*str == '\0') break; + + translate_output(str); + signal_emit("send command", 3, str, active_win->active_server, active_win->active); + + command_history_add(active_win, gui_entry_get_text(), FALSE); + gui_entry_set_text(""); + command_history_clear_pos(active_win); + break; + + default: + if (key > 0 && key < 32) + { + str = g_strdup_printf("CTRL-%c", key == 31 ? '-' : key+'A'-1); + key_pressed(str, NULL); + g_free(str); + break; + } + + if (key < 256) + { + gchar str[2]; + + str[0] = toupper(key); str[1] = '\0'; + key_pressed(str, NULL); + gui_entry_insert_char((gchar) key); + } + break; + } +} + +void readline(void) +{ + gint key; + + for (;;) + { + key = getch(); + if (key == ERR) break; + + handle_key(key); + } +} + +time_t get_idle_time(void) +{ + return idle_time; +} + +static void sig_prev_page(void) +{ + window_prev_page(); +} + +static void sig_next_page(void) +{ + window_next_page(); +} + +static void sig_change_window(gchar *data) +{ + signal_emit("command window goto", 3, data, active_win->active_server, active_win->active); +} + +static void sig_completion(void) +{ + gchar *line; + gint pos; + + pos = gui_entry_get_pos(); + + line = completion_line(active_win, gui_entry_get_text(), &pos); + if (line != NULL) + { + gui_entry_set_text(line); + gui_entry_set_pos(pos); + g_free(line); + } +} + +static void sig_replace(void) +{ + gchar *line; + gint pos; + + pos = gui_entry_get_pos(); + + line = auto_completion(gui_entry_get_text(), &pos); + if (line != NULL) + { + gui_entry_set_text(line); + gui_entry_set_pos(pos); + g_free(line); + } +} + +static void sig_prev_window(void) +{ + signal_emit("command window prev", 3, "", active_win->active_server, active_win->active); +} + +static void sig_next_window(void) +{ + signal_emit("command window next", 3, "", active_win->active_server, active_win->active); +} + +static void sig_window_goto_active(void) +{ + signal_emit("command window goto", 3, "active", active_win->active_server, active_win->active); +} + +static void sig_prev_channel(void) +{ + signal_emit("command channel prev", 3, "", active_win->active_server, active_win->active); +} + +static void sig_next_channel(void) +{ + signal_emit("command channel next", 3, "", active_win->active_server, active_win->active); +} + +static void sig_addchar(gchar *data) +{ + gui_entry_insert_char(*data); +} + +static void signal_window_auto_changed(WINDOW_REC *window) +{ + command_history_next(active_win, gui_entry_get_text()); + gui_entry_set_text(""); +} + +void gui_readline_init(void) +{ + static gchar changekeys[] = "1234567890QWERTYUIO"; + gchar *key, *data; + gint n; + + idle_time = time(NULL); + readtag = g_input_add(0, G_INPUT_READ, (GInputFunction) readline, NULL); + + key_bind("completion", NULL, "Nick completion", "Tab", (SIGNAL_FUNC) sig_completion); + key_bind("check replaces", NULL, "Check word replaces", " ", (SIGNAL_FUNC) sig_replace); + key_bind("check replaces", NULL, NULL, "Return", (SIGNAL_FUNC) sig_replace); + key_bind("window prev", NULL, "Previous window", "CTRL-P", (SIGNAL_FUNC) sig_prev_window); + key_bind("window prev", NULL, NULL, "ALT-Left", (SIGNAL_FUNC) sig_prev_window); + key_bind("window next", NULL, "Next window", "CTRL-N", (SIGNAL_FUNC) sig_next_window); + key_bind("window next", NULL, NULL, "ALT-Right", (SIGNAL_FUNC) sig_next_window); + key_bind("window active", NULL, "Go to next window with the highest activity", "ALT-A", (SIGNAL_FUNC) sig_window_goto_active); + key_bind("channel next", NULL, "Next channel", "CTRL-X", (SIGNAL_FUNC) sig_next_channel); + key_bind("channel prev", NULL, "Next channel", NULL, (SIGNAL_FUNC) sig_prev_channel); + + key_bind("redraw", NULL, "Redraw window", "CTRL-L", (SIGNAL_FUNC) irssi_redraw); + key_bind("prev page", NULL, "Previous page", "ALT-P", (SIGNAL_FUNC) sig_prev_page); + key_bind("next page", NULL, "Next page", "ALT-N", (SIGNAL_FUNC) sig_next_page); + + key_bind("special char", "\x02", "Insert special character", "CTRL-B", (SIGNAL_FUNC) sig_addchar); + key_bind("special char", "\x1f", NULL, "CTRL--", (SIGNAL_FUNC) sig_addchar); + key_bind("special char", "\x03", NULL, "CTRL-C", (SIGNAL_FUNC) sig_addchar); + key_bind("special char", "\x16", NULL, "CTRL-V", (SIGNAL_FUNC) sig_addchar); + key_bind("special char", "\x07", NULL, "CTRL-G", (SIGNAL_FUNC) sig_addchar); + key_bind("special char", "\x0f", NULL, "CTRL-O", (SIGNAL_FUNC) sig_addchar); + + for (n = 0; changekeys[n] != '\0'; n++) + { + key = g_strdup_printf("ALT-%c", changekeys[n]); + data = g_strdup_printf("%d", n+1); + key_bind("change window", data, "Change window", key, (SIGNAL_FUNC) sig_change_window); + g_free(data); g_free(key); + } + + signal_add("window changed automatic", (SIGNAL_FUNC) signal_window_auto_changed); +} + +void gui_readline_deinit(void) +{ + g_source_remove(readtag); + + key_unbind("completion", (SIGNAL_FUNC) sig_completion); + key_unbind("check replaces", (SIGNAL_FUNC) sig_replace); + key_unbind("window prev", (SIGNAL_FUNC) sig_prev_window); + key_unbind("window next", (SIGNAL_FUNC) sig_next_window); + key_unbind("window active", (SIGNAL_FUNC) sig_window_goto_active); + key_unbind("channel next", (SIGNAL_FUNC) sig_next_channel); + key_unbind("channel prev", (SIGNAL_FUNC) sig_prev_channel); + + key_unbind("redraw", (SIGNAL_FUNC) irssi_redraw); + key_unbind("prev page", (SIGNAL_FUNC) sig_prev_page); + key_unbind("next page", (SIGNAL_FUNC) sig_next_page); + + key_unbind("special char", (SIGNAL_FUNC) sig_addchar); + key_unbind("change window", (SIGNAL_FUNC) sig_change_window); + + signal_remove("window changed automatic", (SIGNAL_FUNC) signal_window_auto_changed); +} diff --git a/src/fe-text/gui-readline.h b/src/fe-text/gui-readline.h new file mode 100644 index 00000000..d6be0d59 --- /dev/null +++ b/src/fe-text/gui-readline.h @@ -0,0 +1,10 @@ +#ifndef __GUI_READLINE_H +#define __GUI_READLINE_H + +void readline(void); +time_t get_idle_time(void); + +void gui_readline_init(void); +void gui_readline_deinit(void); + +#endif diff --git a/src/fe-text/gui-special-vars.c b/src/fe-text/gui-special-vars.c new file mode 100644 index 00000000..ac648ff1 --- /dev/null +++ b/src/fe-text/gui-special-vars.c @@ -0,0 +1,61 @@ +/* + gui-special-vars.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "special-vars.h" + +#include "gui-entry.h" +#include "gui-readline.h" + +/* idle time */ +static char *expando_idletime(void *server, void *item, int *free_ret) +{ + int diff; + + *free_ret = TRUE; + diff = (int) (time(NULL) - get_idle_time()); + return g_strdup_printf("%d", diff); +} + +/* current contents of the input line */ +static char *expando_inputline(void *server, void *item, int *free_ret) +{ + return gui_entry_get_text(); +} + +/* FIXME: value of cutbuffer */ +static char *expando_cutbuffer(void *server, void *item, int *free_ret) +{ + return NULL; +} + +void gui_special_vars_init(void) +{ + expando_create("E", expando_idletime); + expando_create("L", expando_inputline); + expando_create("U", expando_cutbuffer); +} + +void gui_special_vars_deinit(void) +{ + expando_destroy("E", expando_idletime); + expando_destroy("L", expando_inputline); + expando_destroy("U", expando_cutbuffer); +} diff --git a/src/fe-text/gui-special-vars.h b/src/fe-text/gui-special-vars.h new file mode 100644 index 00000000..392efc85 --- /dev/null +++ b/src/fe-text/gui-special-vars.h @@ -0,0 +1,7 @@ +#ifndef __GUI_SPECIAL_VARS_H +#define __GUI_SPECIAL_VARS_H + +void gui_special_vars_init(void); +void gui_special_vars_deinit(void); + +#endif diff --git a/src/fe-text/gui-statusbar-items.c b/src/fe-text/gui-statusbar-items.c new file mode 100644 index 00000000..1bb1c6f0 --- /dev/null +++ b/src/fe-text/gui-statusbar-items.c @@ -0,0 +1,691 @@ +/* + gui-statusbar-items.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "server.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "channels.h" +#include "query.h" +#include "irc-server.h" +#include "nicklist.h" + +#include "windows.h" + +#include "screen.h" +#include "gui-statusbar.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +/* how often to redraw lagging time */ +#define LAG_REFRESH_TIME 10 +/* If we haven't been able to check lag for this long, "(??)" is added after + the lag */ +#define MAX_LAG_UNKNOWN_TIME 30 + +/* clock */ +static int clock_tag, clock_timetag; +static time_t clock_last; + +/* nick */ +static int nick_tag; + +/* channel */ +static int channel_tag; + +/* activity */ +static int activity_tag; +static GList *activity_list; + +/* more */ +static int more_tag; + +/* lag */ +static int lag_tag, lag_timetag, lag_min_show; +static time_t lag_last_draw; + +/* topic */ +static int topic_tag; + +/* redraw clock */ +static void statusbar_clock(int xpos, int ypos, int size) +{ + struct tm *tm; + gchar str[5]; + + clock_last = time(NULL); + tm = localtime(&clock_last); + + sprintf(str, "%02d:%02d", tm->tm_hour, tm->tm_min); + + move(ypos, xpos); + set_color((1 << 4)+3); addch('['); + set_color((1 << 4)+15); addstr(str); + set_color((1 << 4)+3); addch(']'); + + screen_refresh(); +} + +/* check if we need to redraw clock.. */ +static int statusbar_clock_timeout(void) +{ + struct tm *tm; + time_t t; + int min; + + tm = localtime(&clock_last); + min = tm->tm_min; + + t = time(NULL); + tm = localtime(&t); + + if (tm->tm_min != min) + { + /* minute changed, redraw! */ + gui_statusbar_redraw(clock_tag); + } + return 1; +} + +/* redraw nick */ +static void statusbar_nick(int xpos, int ypos, int size) +{ + CHANNEL_REC *channel; + IRC_SERVER_REC *server; + NICK_REC *nickrec; + int size_needed; + int umode_size; + gchar nick[10]; + + server = (IRC_SERVER_REC *) (active_win == NULL ? NULL : active_win->active_server); + + umode_size = server == NULL || server->usermode == NULL ? 0 : strlen(server->usermode)+3; + + /* nick */ + if (server == NULL || server->nick == NULL) + { + nick[0] = '\0'; + nickrec = NULL; + } + else + { + strncpy(nick, server->nick, 9); + nick[9] = '\0'; + + channel = irc_item_channel(active_win->active); + nickrec = channel == NULL ? NULL : nicklist_find(channel, server->nick); + } + + size_needed = 2 + strlen(nick) + umode_size + + (server != NULL && server->usermode_away ? 7 : 0) + + (nickrec != NULL && (nickrec->op || nickrec->voice) ? 1 : 0); /* @ + */ + + if (size != size_needed) + { + /* we need more (or less..) space! */ + gui_statusbar_resize(nick_tag, size_needed); + return; + } + + /* size ok, draw the nick */ + move(ypos, xpos); + + set_color((1 << 4)+3); addch('['); + if (nickrec != NULL && (nickrec->op || nickrec->voice)) + { + set_color((1 << 4)+15); addch(nickrec->op ? '@' : '+'); + } + set_color((1 << 4)+7); addstr(nick); + if (umode_size) + { + set_color((1 << 4)+15); addch('('); + set_color((1 << 4)+3); addch('+'); + set_color((1 << 4)+7); addstr(server->usermode); + set_color((1 << 4)+15); addch(')'); + if (server->usermode_away) + { + set_color((1 << 4)+7); addstr(" ("); + set_color((1 << 4)+10); addstr("zZzZ"); + set_color((1 << 4)+7); addch(')'); + } + } + set_color((1 << 4)+3); addch(']'); + screen_refresh(); +} + +static void sig_statusbar_nick_redraw(void) +{ + gui_statusbar_redraw(nick_tag); +} + +/* redraw channel */ +static void statusbar_channel(int xpos, int ypos, int size) +{ + WI_ITEM_REC *item; + CHANNEL_REC *channel; + SERVER_REC *server; + gchar channame[21], window[MAX_INT_STRLEN], *mode; + int size_needed; + int mode_size; + + server = active_win == NULL ? NULL : active_win->active_server; + + ltoa(window, active_win == NULL ? 0 : + g_slist_index(windows, active_win)+1); + + item = active_win != NULL && irc_item_check(active_win->active) ? + active_win->active : NULL; + if (item == NULL) + { + /* display server tag */ + channame[0] = '\0'; + mode = NULL; + mode_size = 0; + + size_needed = 3 + strlen(window) + (server == NULL ? 0 : strlen(server->tag)); + } + else + { + /* display channel + mode */ + strncpy(channame, item->name, 20); channame[20] = '\0'; + + channel = irc_item_channel(item); + if (channel == NULL) { + mode_size = 0; + mode = NULL; + } else { + mode = channel_get_mode(channel); + mode_size = strlen(mode); + if (mode_size > 0) mode_size += 3; /* (+) */ + } + + size_needed = 3 + strlen(window) + strlen(channame) + mode_size; + } + + if (size != size_needed) + { + /* we need more (or less..) space! */ + gui_statusbar_resize(channel_tag, size_needed); + if (mode != NULL) g_free(mode); + return; + } + + move(ypos, xpos); + set_color((1 << 4)+3); addch('['); + + /* window number */ + set_color((1 << 4)+7); addstr(window); + set_color((1 << 4)+3); addch(':'); + + if (channame[0] == '\0' && server != NULL) + { + /* server tag */ + set_color((1 << 4)+7); addstr(server->tag); + } + else if (channame[0] != '\0') + { + /* channel + mode */ + set_color((1 << 4)+7); addstr(channame); + if (mode_size) + { + set_color((1 << 4)+15); addch('('); + set_color((1 << 4)+3); addch('+'); + set_color((1 << 4)+7); addstr(mode); + set_color((1 << 4)+15); addch(')'); + } + } + set_color((1 << 4)+3); addch(']'); + screen_refresh(); + + if (mode != NULL) g_free(mode); +} + +static void sig_statusbar_channel_redraw(void) +{ + gui_statusbar_redraw(channel_tag); +} + +static void draw_activity(gchar *title, gboolean act, gboolean det) +{ + WINDOW_REC *window; + GList *tmp; + gchar str[(sizeof(int) * CHAR_BIT + 2) / 3 + 1]; + gboolean first, is_det; + + set_color((1 << 4)+7); addstr(title); + + first = TRUE; + for (tmp = activity_list; tmp != NULL; tmp = tmp->next) + { + window = tmp->data; + + is_det = window->new_data == NEWDATA_MSG_FORYOU; + if (is_det && !det) continue; + if (!is_det && !act) continue; + + if (first) + first = FALSE; + else + { + set_color((1 << 4)+3); + addch(','); + } + + sprintf(str, "%d", g_slist_index(windows, window)+1); + switch (window->new_data) + { + case NEWDATA_TEXT: + set_color((1 << 4)+3); + break; + case NEWDATA_MSG: + set_color((1 << 4)+15); + break; + case NEWDATA_MSG_FORYOU: + set_color((1 << 4)+13); + break; + } + addstr(str); + } +} + +/* redraw activity */ +static void statusbar_activity(int xpos, int ypos, int size) +{ + WINDOW_REC *window; + GList *tmp; + gchar str[MAX_INT_STRLEN]; + int size_needed; + gboolean act, det; + + size_needed = 0; act = det = FALSE; + for (tmp = activity_list; tmp != NULL; tmp = tmp->next) + { + window = tmp->data; + + size_needed += 1+g_snprintf(str, sizeof(str), "%d", g_slist_index(windows, window)+1); + + if (!use_colors && window->new_data == NEWDATA_MSG_FORYOU) + det = TRUE; + else + act = TRUE; + } + + if (act) size_needed += 6; /* [Act: ], -1 */ + if (det) size_needed += 6; /* [Det: ], -1 */ + if (act && det) size_needed--; + + if (size != size_needed) + { + /* we need more (or less..) space! */ + gui_statusbar_resize(activity_tag, size_needed); + return; + } + + if (size == 0) + return; + + move(ypos, xpos); + set_color((1 << 4)+3); addch('['); + if (act) draw_activity("Act: ", TRUE, !det); + if (act && det) addch(' '); + if (det) draw_activity("Det: ", FALSE, TRUE); + set_color((1 << 4)+3); addch(']'); + + screen_refresh(); +} + +static void sig_statusbar_activity_hilight(WINDOW_REC *window, gpointer oldlevel) +{ + int pos, inspos; + GList *tmp; + + g_return_if_fail(window != NULL); + + if (settings_get_bool("toggle_actlist_moves")) + { + /* Move the window to the first in the activity list */ + if (g_list_find(activity_list, window) != NULL) + activity_list = g_list_remove(activity_list, window); + if (window->new_data != 0) + activity_list = g_list_prepend(activity_list, window); + gui_statusbar_redraw(activity_tag); + return; + } + + if (g_list_find(activity_list, window) != NULL) + { + /* already in activity list */ + if (window->new_data == 0) + { + /* remove from activity list */ + activity_list = g_list_remove(activity_list, window); + gui_statusbar_redraw(activity_tag); + } + else if (window->new_data != GPOINTER_TO_INT(oldlevel)) + { + /* different level as last time, just redraw it. */ + gui_statusbar_redraw(activity_tag); + } + return; + } + + if (window->new_data == 0) + return; + + /* add window to activity list .. */ + pos = g_slist_index(windows, window); + + inspos = 0; + for (tmp = activity_list; tmp != NULL; tmp = tmp->next, inspos++) + { + if (pos < g_slist_index(windows, tmp->data)) + { + activity_list = g_list_insert(activity_list, window, inspos); + break; + } + } + if (tmp == NULL) + activity_list = g_list_append(activity_list, window); + + gui_statusbar_redraw(activity_tag); +} + +static void sig_statusbar_activity_window_destroyed(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + if (g_list_find(activity_list, window) != NULL) + { + activity_list = g_list_remove(activity_list, window); + gui_statusbar_redraw(activity_tag); + } +} + +/* redraw -- more -- */ +static void statusbar_more(int xpos, int ypos, int size) +{ + if (size != 10) return; + + move(ypos, xpos); + set_color((1 << 4)+15); addstr("-- more --"); + screen_refresh(); +} + +static void sig_statusbar_more_check_remove(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + if (!is_window_visible(window)) + return; + + if (more_tag != -1 && WINDOW_GUI(window)->bottom) + { + gui_statusbar_remove(more_tag); + more_tag = -1; + } +} + +static void sig_statusbar_more_check(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + if (WINDOW_GUI(window)->parent->active != window) + return; + + if (!WINDOW_GUI(window)->bottom) + { + if (more_tag == -1) + more_tag = gui_statusbar_allocate(10, FALSE, FALSE, 0, statusbar_more); + } + else if (more_tag != -1) + { + gui_statusbar_remove(more_tag); + more_tag = -1; + } +} + +static void statusbar_lag(int xpos, int ypos, int size) +{ + IRC_SERVER_REC *server; + GString *str; + int size_needed, lag_unknown; + time_t now; + + now = time(NULL); + str = g_string_new(NULL); + + server = (IRC_SERVER_REC *) (active_win == NULL ? NULL : active_win->active_server); + if (server == NULL || server->lag_last_check == 0) + size_needed = 0; + else if (server->lag_sent == 0 || now-server->lag_sent < 5) { + lag_unknown = now-server->lag_last_check > MAX_LAG_UNKNOWN_TIME; + + if (server->lag < lag_min_show && !lag_unknown) + size_needed = 0; /* small lag, don't display */ + else { + g_string_sprintf(str, "%d.%02d", server->lag/1000, (server->lag % 1000)/10); + if (lag_unknown) + g_string_append(str, " (??)"); + size_needed = str->len+7; + } + } else { + /* big lag, still waiting .. */ + g_string_sprintf(str, "%ld (??)", now-server->lag_sent); + size_needed = str->len+7; + } + + if (size != size_needed) + { + /* we need more (or less..) space! */ + gui_statusbar_resize(lag_tag, size_needed); + g_string_free(str, TRUE); + return; + } + + if (size != 0) + { + lag_last_draw = now; + move(ypos, xpos); + set_color((1 << 4)+3); addch('['); + set_color((1 << 4)+7); addstr("Lag: "); + + set_color((1 << 4)+15); addstr(str->str); + set_color((1 << 4)+3); addch(']'); + + screen_refresh(); + } + g_string_free(str, TRUE); +} + +static void sig_statusbar_lag_redraw(void) +{ + gui_statusbar_redraw(lag_tag); +} + +static int statusbar_lag_timeout(void) +{ + /* refresh statusbar every 10 seconds */ + if (time(NULL)-lag_last_draw < LAG_REFRESH_TIME) + return 1; + + gui_statusbar_redraw(lag_tag); + return 1; +} + +static void statusbar_topic(int xpos, int ypos, int size) +{ + CHANNEL_REC *channel; + QUERY_REC *query; + char *str, *topic; + + if (size != COLS-2) { + /* get all space for topic */ + gui_statusbar_resize(topic_tag, COLS-2); + return; + } + + move(ypos, xpos); + set_bg((1<<4)+7); clrtoeol(); set_bg(0); + + if (active_win == NULL) + return; + + topic = NULL; + channel = irc_item_channel(active_win->active); + query = irc_item_query(active_win->active); + if (channel != NULL && channel->topic != NULL) topic = channel->topic; + if (query != NULL && query->address != NULL) topic = query->address; + if (topic == NULL) return; + + str = g_strdup_printf("%.*s", size, topic); + set_color((1<<4)+15); addstr(str); + g_free(str); + + screen_refresh(); +} + +static void sig_statusbar_topic_redraw(void) +{ + gui_statusbar_redraw(topic_tag); +} + +static void read_settings(void) +{ + int ypos; + + if (topic_tag == -1 && settings_get_bool("toggle_show_topicbar")) { + ypos = gui_statusbar_create(TRUE); + topic_tag = gui_statusbar_allocate(0, FALSE, TRUE, ypos, statusbar_topic); + signal_add("window changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + signal_add("window item changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + signal_add("channel topic changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + } else if (topic_tag != -1 && !settings_get_bool("toggle_show_topicbar")) { + gui_statusbar_delete(TRUE, 0); + topic_tag = -1; + signal_remove("window changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + signal_remove("window item changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + signal_remove("channel topic changed", (SIGNAL_FUNC) sig_statusbar_topic_redraw); + } + + lag_min_show = settings_get_int("lag_min_show")*10; +} + +void gui_statusbar_items_init(void) +{ + settings_add_int("misc", "lag_min_show", 100); + + /* clock */ + clock_tag = gui_statusbar_allocate(7, FALSE, FALSE, 0, statusbar_clock); + clock_timetag = g_timeout_add(1000, (GSourceFunc) statusbar_clock_timeout, NULL); + + /* nick */ + nick_tag = gui_statusbar_allocate(2, FALSE, FALSE, 0, statusbar_nick); + signal_add("server connected", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("channel wholist", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("window changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("window item changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("nick mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("user mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("server nick changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("window server changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_add("away mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + + /* channel */ + channel_tag = gui_statusbar_allocate(2, FALSE, FALSE, 0, statusbar_channel); + signal_add("window changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_add("window item changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_add("channel mode changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_add("window server changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + + /* activity */ + activity_list = NULL; + activity_tag = gui_statusbar_allocate(0, FALSE, FALSE, 0, statusbar_activity); + signal_add("window activity", (SIGNAL_FUNC) sig_statusbar_activity_hilight); + signal_add("window destroyed", (SIGNAL_FUNC) sig_statusbar_activity_window_destroyed); + + /* more */ + more_tag = -1; + signal_add("gui page scrolled", (SIGNAL_FUNC) sig_statusbar_more_check_remove); + signal_add("window item changed", (SIGNAL_FUNC) sig_statusbar_more_check); + signal_add("gui print text", (SIGNAL_FUNC) sig_statusbar_more_check); + + /* lag */ + lag_tag = gui_statusbar_allocate(0, FALSE, FALSE, 0, statusbar_lag); + lag_timetag = g_timeout_add(1000*LAG_REFRESH_TIME, (GSourceFunc) statusbar_lag_timeout, NULL); + signal_add("server lag", (SIGNAL_FUNC) sig_statusbar_lag_redraw); + signal_add("window server changed", (SIGNAL_FUNC) sig_statusbar_lag_redraw); + + /* topic bar */ + topic_tag = -1; + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + read_settings(); +} + +void gui_statusbar_items_deinit(void) +{ + /* clock */ + gui_statusbar_remove(clock_tag); + + /* nick */ + gui_statusbar_remove(nick_tag); + g_source_remove(clock_timetag); + signal_remove("server connected", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("channel wholist", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("window changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("window item changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("nick mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("user mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("server nick changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("window server changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + signal_remove("away mode changed", (SIGNAL_FUNC) sig_statusbar_nick_redraw); + + /* channel */ + gui_statusbar_remove(channel_tag); + signal_remove("window changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_remove("window item changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_remove("channel mode changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + signal_remove("window server changed", (SIGNAL_FUNC) sig_statusbar_channel_redraw); + + /* activity */ + gui_statusbar_remove(activity_tag); + signal_remove("window activity", (SIGNAL_FUNC) sig_statusbar_activity_hilight); + signal_remove("window destroyed", (SIGNAL_FUNC) sig_statusbar_activity_window_destroyed); + g_list_free(activity_list); + + /* more */ + if (more_tag != -1) gui_statusbar_remove(more_tag); + signal_remove("gui page scrolled", (SIGNAL_FUNC) sig_statusbar_more_check_remove); + signal_remove("window item changed", (SIGNAL_FUNC) sig_statusbar_more_check); + signal_remove("gui print text", (SIGNAL_FUNC) sig_statusbar_more_check); + + /* lag */ + gui_statusbar_remove(lag_tag); + g_source_remove(lag_timetag); + signal_remove("server lag", (SIGNAL_FUNC) sig_statusbar_lag_redraw); + signal_remove("window server changed", (SIGNAL_FUNC) sig_statusbar_lag_redraw); + + /* topic */ + if (topic_tag != -1) gui_statusbar_delete(TRUE, 0); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-text/gui-statusbar-items.h b/src/fe-text/gui-statusbar-items.h new file mode 100644 index 00000000..f71415f0 --- /dev/null +++ b/src/fe-text/gui-statusbar-items.h @@ -0,0 +1,7 @@ +#ifndef __GUI_STATUSBAR_ITEMS_H +#define __GUI_STATUSBAR_ITEMS_H + +void gui_statusbar_items_init(void); +void gui_statusbar_items_deinit(void); + +#endif diff --git a/src/fe-text/gui-statusbar.c b/src/fe-text/gui-statusbar.c new file mode 100644 index 00000000..130993c2 --- /dev/null +++ b/src/fe-text/gui-statusbar.c @@ -0,0 +1,237 @@ +/* + gui-statusbar.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "server.h" + +#include "windows.h" + +#include "screen.h" +#include "gui-statusbar.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +typedef struct +{ + gint tag; + + gint xpos, ypos; + gint size; + gboolean right_justify, up; + STATUSBAR_FUNC func; +} +STATUSBAR_REC; + +static GList *sbars; +static gint sbars_tag; + +static void gui_statusbar_redraw_line(gboolean up, gint ypos) +{ + GList *tmp; + gint xpos, rxpos; + + xpos = 1; + for (tmp = sbars; tmp != NULL; tmp = tmp->next) + { + STATUSBAR_REC *rec = tmp->data; + + if (!rec->right_justify) + { + if (rec->up == up && rec->ypos == ypos && xpos+rec->size < COLS) + { + rec->xpos = xpos; + rec->func(xpos, rec->ypos + (rec->up ? 0 : last_text_line), rec->size); + if (rec->size > 0) xpos += rec->size+1; + } + } + } + + rxpos = COLS-1; + for (tmp = sbars; tmp != NULL; tmp = tmp->next) + { + STATUSBAR_REC *rec = tmp->data; + + if (rec->right_justify) + { + if (rec->up == up && rec->ypos == ypos && rxpos-rec->size > xpos) + { + rec->xpos = rxpos-rec->size; + rec->func(rec->xpos, rec->ypos + (rec->up ? 0 : last_text_line), rec->size); + if (rec->size > 0) rxpos -= rec->size+1; + } + } + } +} + +static void gui_statusbar_redraw_all(void) +{ + gint n; + + screen_refresh_freeze(); + set_bg((1<<4)+15); + for (n = 0; n < first_text_line; n++) + { + move(n, 0); clrtoeol(); + } + for (n = last_text_line; n < LINES-1; n++) + { + move(n, 0); clrtoeol(); + } + set_bg(0); + + for (n = 0; n < LINES-1; n++) + { + gui_statusbar_redraw_line(FALSE, n); + gui_statusbar_redraw_line(TRUE, n); + } + screen_refresh_thaw(); +} + +void gui_statusbar_redraw(gint tag) +{ + GList *tmp; + + if (tag == -1) + { + gui_statusbar_redraw_all(); + return; + } + + for (tmp = sbars; tmp != NULL; tmp = tmp->next) + { + STATUSBAR_REC *rec = tmp->data; + + if (rec->tag == tag) + { + rec->func(rec->xpos, rec->ypos + (rec->up ? 0 : last_text_line), rec->size); + break; + } + } +} + +/* create new statusbar, return position */ +gint gui_statusbar_create(gboolean up) +{ + gint pos; + + pos = up ? first_text_line++ : + (LINES-2)-last_text_line--; + + set_bg((1<<4)+15); + move(up ? pos : last_text_line+pos, 0); clrtoeol(); + set_bg(0); + + gui_windows_resize(-1, FALSE); + return pos; +} + +void gui_statusbar_delete(gboolean up, gint ypos) +{ + GList *tmp, *next; + + if (up && first_text_line > 0) + first_text_line--; + else if (!up && last_text_line < LINES-1) + last_text_line++; + + for (tmp = sbars; tmp != NULL; tmp = next) + { + STATUSBAR_REC *rec = tmp->data; + + next = tmp->next; + if (rec->up == up && rec->ypos == ypos) + gui_statusbar_remove(rec->tag); + else if (rec->up == up && rec->ypos > ypos) + rec->ypos--; + } + + gui_windows_resize(1, FALSE); +} + +/* allocate area in statusbar, returns tag or -1 if failed */ +gint gui_statusbar_allocate(gint size, gboolean right_justify, gboolean up, gint ypos, STATUSBAR_FUNC func) +{ + STATUSBAR_REC *rec; + + g_return_val_if_fail(func != NULL, -1); + + rec = g_new0(STATUSBAR_REC, 1); + sbars = g_list_append(sbars, rec); + + rec->tag = ++sbars_tag; + rec->xpos = -1; + rec->up = up; + rec->ypos = ypos; + rec->size = size; + rec->right_justify = right_justify; + rec->func = func; + + gui_statusbar_redraw_all(); + return rec->tag; +} + +void gui_statusbar_resize(gint tag, gint size) +{ + GList *tmp; + + for (tmp = sbars; tmp != NULL; tmp = tmp->next) + { + STATUSBAR_REC *rec = tmp->data; + + if (rec->tag == tag) + { + rec->size = size; + gui_statusbar_redraw_all(); + break; + } + } +} + +void gui_statusbar_remove(gint tag) +{ + GList *tmp; + + for (tmp = sbars; tmp != NULL; tmp = tmp->next) + { + STATUSBAR_REC *rec = tmp->data; + + if (rec->tag == tag) + { + g_free(rec); + sbars = g_list_remove(sbars, rec); + if (!quitting) gui_statusbar_redraw_all(); + break; + } + } +} + +void gui_statusbar_init(void) +{ + sbars = NULL; + sbars_tag = 0; + + gui_statusbar_create(FALSE); +} + +void gui_statusbar_deinit(void) +{ + gui_statusbar_delete(FALSE, 0); +} diff --git a/src/fe-text/gui-statusbar.h b/src/fe-text/gui-statusbar.h new file mode 100644 index 00000000..bdaba584 --- /dev/null +++ b/src/fe-text/gui-statusbar.h @@ -0,0 +1,21 @@ +#ifndef __GUI_STATUSBAR_H +#define __GUI_STATUSBAR_H + +typedef void (*STATUSBAR_FUNC) (gint xpos, gint ypos, gint size); + +/* create new statusbar, return position */ +gint gui_statusbar_create(gboolean up); +void gui_statusbar_delete(gboolean up, gint ypos); + +/* allocate area in statusbar, returns tag or -1 if failed */ +gint gui_statusbar_allocate(gint size, gboolean right_justify, gboolean up, gint ypos, STATUSBAR_FUNC func); +void gui_statusbar_resize(gint tag, gint size); +void gui_statusbar_remove(gint tag); + +/* redraw item, -1 = all */ +void gui_statusbar_redraw(gint tag); + +void gui_statusbar_init(void); +void gui_statusbar_deinit(void); + +#endif diff --git a/src/fe-text/gui-textwidget.c b/src/fe-text/gui-textwidget.c new file mode 100644 index 00000000..c4d6d2f4 --- /dev/null +++ b/src/fe-text/gui-textwidget.c @@ -0,0 +1,390 @@ +/* + gui-textwidget.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "levels.h" +#include "settings.h" + +#include "windows.h" + +#include "screen.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +static gchar *gui_window_line2text(LINE_REC *line) +{ + GString *str; + gint color; + gchar *ret, *ptr, *tmp; + + g_return_val_if_fail(line != NULL, NULL); + + str = g_string_new(NULL); + + color = 0; + for (ptr = line->text; ; ptr++) + { + if (*ptr != 0) + { + g_string_append_c(str, *ptr); + continue; + } + + ptr++; + if ((*ptr & 0x80) == 0) + { + /* set color */ + color = *ptr; + g_string_sprintfa(str, "\003%c%c", (color & 0x07)+1, ((color & 0xf0) >> 4)+1); + if (color & 0x08) g_string_sprintfa(str, "\002"); + } + else switch ((guchar) *ptr) + { + case LINE_CMD_EOL: + ret = str->str; + g_string_free(str, FALSE); + return ret; + case LINE_CMD_CONTINUE: + memcpy(&tmp, ptr+1, sizeof(gchar *)); + ptr = tmp-1; + break; + case LINE_CMD_UNDERLINE: + g_string_append_c(str, 31); + break; + case LINE_CMD_COLOR8: + g_string_sprintfa(str, "\003%c%c", 9, ((color & 0xf0) >> 4)+1); + color &= 0xfff0; + color |= 8|ATTR_COLOR8; + break; + case LINE_CMD_BEEP: + break; + case LINE_CMD_INDENT: + break; + } + } + + return NULL; +} + +#define LASTLOG_FLAG_NEW 0x01 +#define LASTLOG_FLAG_NOHEADERS 0x02 +#define LASTLOG_FLAG_WORD 0x04 +#define LASTLOG_FLAG_REGEXP 0x08 + +static int lastlog_parse_args(char *args, int *flags) +{ + char **arglist, **tmp; + int level; + + /* level can be specified in arguments.. */ + level = 0; *flags = 0; + arglist = g_strsplit(args, " ", -1); + for (tmp = arglist; *tmp != NULL; tmp++) { + if (strcmp(*tmp, "-") == 0) + *flags |= LASTLOG_FLAG_NOHEADERS; + else if (g_strcasecmp(*tmp, "-new") == 0) + *flags |= LASTLOG_FLAG_NEW; + else if (g_strcasecmp(*tmp, "-word") == 0) + *flags |= LASTLOG_FLAG_WORD; + else if (g_strcasecmp(*tmp, "-regexp") == 0) + *flags |= LASTLOG_FLAG_REGEXP; + else + level |= level2bits(tmp[0]+1); + } + if (level == 0) level = MSGLEVEL_ALL; + g_strfreev(arglist); + + return level; +} + +#define lastlog_match(line, level) \ + (((line)->level & level) != 0 && ((line)->level & MSGLEVEL_LASTLOG) == 0) + +static GList *lastlog_window_startline(int only_new) +{ + GList *startline, *tmp; + + startline = WINDOW_GUI(active_win)->lines; + if (!only_new) return startline; + + for (tmp = startline; tmp != NULL; tmp = tmp->next) { + LINE_REC *rec = tmp->data; + + if (rec->level & MSGLEVEL_LASTLOG) + startline = tmp; + } + + return startline; +} + +static GList *lastlog_find_startline(GList *list, int count, int start, int level) +{ + GList *tmp; + + if (count <= 0) return list; + + for (tmp = g_list_last(list); tmp != NULL; tmp = tmp->prev) { + LINE_REC *rec = tmp->data; + + if (!lastlog_match(rec, level)) + continue; + + if (start > 0) { + start--; + continue; + } + + if (--count == 0) + return tmp; + } + + return list; +} + +static void cmd_lastlog(const char *data) +{ + GList *startline, *list, *tmp; + char *params, *str, *args, *text, *countstr, *start; + struct tm *tm; + int level, flags, count; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 4 | PARAM_FLAG_OPTARGS, &args, &text, &countstr, &start); + if (*start == '\0' && is_numeric(text, 0)) { + if (is_numeric(countstr, 0)) + start = countstr; + countstr = text; + text = ""; + } + count = atol(countstr); + if (count == 0) count = -1; + + level = lastlog_parse_args(args, &flags); + startline = lastlog_window_startline(flags & LASTLOG_FLAG_NEW); + + if ((flags & LASTLOG_FLAG_NOHEADERS) == 0) + printformat(NULL, NULL, MSGLEVEL_LASTLOG, IRCTXT_LASTLOG_START); + + list = gui_window_find_text(active_win, text, startline, flags & LASTLOG_FLAG_REGEXP, flags & LASTLOG_FLAG_WORD); + tmp = lastlog_find_startline(list, count, atol(start), level); + + for (; tmp != NULL && (count < 0 || count > 0); tmp = tmp->next, count--) { + LINE_REC *rec = tmp->data; + + if (!lastlog_match(rec, level)) + continue; + + text = gui_window_line2text(rec); + if (settings_get_bool("toggle_show_timestamps")) + printtext(NULL, NULL, MSGLEVEL_LASTLOG, text); + else { + tm = localtime(&rec->time); + + str = g_strdup_printf("[%02d:%02d] %s", tm->tm_hour, tm->tm_min, text); + printtext(NULL, NULL, MSGLEVEL_LASTLOG, str); + g_free(str); + } + g_free(text); + } + + if ((flags & LASTLOG_FLAG_NOHEADERS) == 0) + printformat(NULL, NULL, MSGLEVEL_LASTLOG, IRCTXT_LASTLOG_END); + + g_list_free(list); + g_free(params); +} + +static void cmd_scrollback(gchar *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + command_runsub("scrollback", data, server, item); +} + +static void cmd_scrollback_clear(gchar *data) +{ + gui_window_clear(active_win); +} + +static void scrollback_goto_pos(WINDOW_REC *window, GList *pos) +{ + GUI_WINDOW_REC *gui; + + g_return_if_fail(window != NULL); + g_return_if_fail(pos != NULL); + + gui = WINDOW_GUI(window); + + if (g_list_find(gui->bottom_startline, pos->data) == NULL) + { + gui->startline = pos; + gui->subline = 0; + gui->bottom = FALSE; + } + else + { + /* reached the last line */ + if (gui->bottom) return; + + gui->bottom = TRUE; + gui->startline = gui->bottom_startline; + gui->subline = gui->bottom_subline; + gui->ypos = last_text_line-first_text_line-1; + } + + if (is_window_visible(window)) + gui_window_redraw(window); + signal_emit("gui page scrolled", 1, window); +} + +static void cmd_scrollback_goto(gchar *data) +{ + GList *pos; + gchar *params, *arg1, *arg2; + gint lines; + + params = cmd_get_params(data, 2, &arg1, &arg2); + if (*arg2 == '\0' && (*arg1 == '-' || *arg1 == '+')) + { + /* go forward/backward n lines */ + if (sscanf(arg1 + (*arg1 == '-' ? 0 : 1), "%d", &lines) == 1) + gui_window_scroll(active_win, lines); + } + else if (*arg2 == '\0' && strchr(arg1, ':') == NULL && strchr(arg1, '.') == NULL && + sscanf(arg1, "%d", &lines) == 1) + { + /* go to n'th line. */ + pos = g_list_nth(WINDOW_GUI(active_win)->lines, lines); + if (pos != NULL) + scrollback_goto_pos(active_win, pos); + } + else + { + struct tm tm; + time_t stamp; + gint day, month; + + /* [dd.mm | -<days ago>] hh:mi[:ss] */ + stamp = time(NULL); + if (*arg1 == '-') + { + /* -<days ago> */ + if (sscanf(arg1+1, "%d", &day) == 1) + stamp -= day*3600*24; + memcpy(&tm, localtime(&stamp), sizeof(struct tm)); + } + else if (*arg2 != '\0') + { + /* dd.mm */ + if (sscanf(arg1, "%d.%d", &day, &month) == 2) + { + month--; + memcpy(&tm, localtime(&stamp), sizeof(struct tm)); + + if (tm.tm_mon < month) + tm.tm_year--; + tm.tm_mon = month; + tm.tm_mday = day; + stamp = mktime(&tm); + } + } + else + { + /* move time argument to arg2 */ + arg2 = arg1; + } + + /* hh:mi[:ss] */ + memcpy(&tm, localtime(&stamp), sizeof(struct tm)); + tm.tm_sec = 0; + sscanf(arg2, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + stamp = mktime(&tm); + + /* find the first line after timestamp */ + for (pos = WINDOW_GUI(active_win)->lines; pos != NULL; pos = pos->next) + { + LINE_REC *rec = pos->data; + + if (rec->time >= stamp) + { + scrollback_goto_pos(active_win, pos); + break; + } + } + } + g_free(params); +} + +static void cmd_scrollback_home(gchar *data) +{ + GUI_WINDOW_REC *gui; + + gui = WINDOW_GUI(active_win); + + if (gui->bottom_startline == gui->startline) + return; + + gui->bottom = FALSE; + gui->startline = gui->lines; + gui->subline = 0; + + if (is_window_visible(active_win)) + gui_window_redraw(active_win); + signal_emit("gui page scrolled", 1, active_win); +} + +static void cmd_scrollback_end(gchar *data) +{ + GUI_WINDOW_REC *gui; + + gui = WINDOW_GUI(active_win); + + gui->bottom = TRUE; + gui->startline = gui->bottom_startline; + gui->subline = gui->bottom_subline; + gui->ypos = last_text_line-first_text_line-1; + + if (is_window_visible(active_win)) + gui_window_redraw(active_win); + signal_emit("gui page scrolled", 1, active_win); +} + +void gui_textwidget_init(void) +{ + command_bind("lastlog", NULL, (SIGNAL_FUNC) cmd_lastlog); + command_bind("scrollback", NULL, (SIGNAL_FUNC) cmd_scrollback); + command_bind("scrollback clear", NULL, (SIGNAL_FUNC) cmd_scrollback_clear); + command_bind("scrollback goto", NULL, (SIGNAL_FUNC) cmd_scrollback_goto); + command_bind("scrollback home", NULL, (SIGNAL_FUNC) cmd_scrollback_home); + command_bind("scrollback end", NULL, (SIGNAL_FUNC) cmd_scrollback_end); +} + +void gui_textwidget_deinit(void) +{ + command_unbind("lastlog", (SIGNAL_FUNC) cmd_lastlog); + command_unbind("scrollback", (SIGNAL_FUNC) cmd_scrollback); + command_unbind("scrollback clear", (SIGNAL_FUNC) cmd_scrollback_clear); + command_unbind("scrollback goto", (SIGNAL_FUNC) cmd_scrollback_goto); + command_unbind("scrollback home", (SIGNAL_FUNC) cmd_scrollback_home); + command_unbind("scrollback end", (SIGNAL_FUNC) cmd_scrollback_end); +} diff --git a/src/fe-text/gui-textwidget.h b/src/fe-text/gui-textwidget.h new file mode 100644 index 00000000..f27ae7fb --- /dev/null +++ b/src/fe-text/gui-textwidget.h @@ -0,0 +1,7 @@ +#ifndef __GUI_TEXTWIDGET_H +#define __GUI_TEXTWIDGET_H + +void gui_textwidget_init(void); +void gui_textwidget_deinit(void); + +#endif diff --git a/src/fe-text/gui-windows.c b/src/fe-text/gui-windows.c new file mode 100644 index 00000000..0afcf011 --- /dev/null +++ b/src/fe-text/gui-windows.c @@ -0,0 +1,780 @@ +/* + gui-windows.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "server.h" +#include "misc.h" + +#include "irc.h" +#include "channels.h" +#include "windows.h" + +#include "screen.h" +#include "gui-entry.h" +#include "gui-mainwindows.h" +#include "gui-windows.h" + +#include <regex.h> + +#define DEFAULT_INDENT_POS 10 + +int first_text_line = 0, last_text_line = 0; + +static GUI_WINDOW_REC *gui_window_init(WINDOW_REC *window, MAIN_WINDOW_REC *parent) +{ + GUI_WINDOW_REC *gui; + + gui = g_new0(GUI_WINDOW_REC, 1); + gui->parent = parent; + + gui->bottom = TRUE; + gui->line_chunk = g_mem_chunk_new("line chunk", sizeof(LINE_REC), + sizeof(LINE_REC)*100, G_ALLOC_AND_FREE); + gui->empty_linecount = last_text_line-first_text_line-1; + + return gui; +} + +static void gui_window_deinit(GUI_WINDOW_REC *gui) +{ + g_slist_foreach(gui->text_chunks, (GFunc) g_free, NULL); + g_slist_free(gui->text_chunks); + + g_mem_chunk_destroy(gui->line_chunk); + g_list_free(gui->lines); + + g_free(gui); +} + +static void gui_window_created(WINDOW_REC *window) +{ + MAIN_WINDOW_REC *parent; + + g_return_if_fail(window != NULL); + + parent = (active_win == NULL || WINDOW_GUI(active_win) == NULL) ? + gui_mainwindow_create() : WINDOW_GUI(active_win)->parent; + if (parent->children == NULL) parent->active = window; + parent->children = g_list_append(parent->children, window); + + window->gui_data = gui_window_init(window, parent); + signal_emit("gui window created", 1, window); +} + +static void gui_window_destroyed(WINDOW_REC *window) +{ + MAIN_WINDOW_REC *parent; + GUI_WINDOW_REC *gui; + + g_return_if_fail(window != NULL); + + gui = WINDOW_GUI(window); + parent = gui->parent; + parent->children = g_list_remove(parent->children, window); + + signal_emit("gui window destroyed", 1, window); + + gui_window_deinit(gui); + window->gui_data = NULL; + + if (parent->children == NULL) + gui_mainwindow_destroy(parent); + + if (windows != NULL && active_win == window && !quitting) + window_set_active(windows->data); +} + +void gui_window_clear(WINDOW_REC *window) +{ + MAIN_WINDOW_REC *parent; + + g_return_if_fail(window != NULL); + + parent = WINDOW_GUI(window)->parent; + gui_window_deinit(WINDOW_GUI(window)); + window->gui_data = gui_window_init(window, parent); + + window->lines = 0; + + if (is_window_visible(window)) + gui_window_redraw(window); +} + +gint gui_window_update_bottom(GUI_WINDOW_REC *gui, gint lines) +{ + gint linecount, last_linecount; + + if (gui->bottom_startline == NULL) + return -1; + + while (lines < 0) + { + if (gui->bottom_subline > 0) + gui->bottom_subline--; + else + { + if (gui->bottom_startline->prev == NULL) + return -1; + gui->bottom_startline = gui->bottom_startline->prev; + + linecount = gui_window_get_linecount(gui, gui->bottom_startline->data); + gui->bottom_subline = linecount-1; + } + lines++; + } + + last_linecount = linecount = -1; + while (lines > 0) + { + if (linecount == -1) + last_linecount = linecount = gui_window_get_linecount(gui, gui->bottom_startline->data); + + if (linecount > gui->bottom_subline+1) + gui->bottom_subline++; + else + { + gui->bottom_subline = 0; + linecount = -1; + + if (gui->bottom_startline->next == NULL) + break; + gui->bottom_startline = gui->bottom_startline->next; + } + lines--; + } + + return last_linecount; +} + +void gui_window_newline(GUI_WINDOW_REC *gui, gboolean visible) +{ + gboolean last_line; + gint linecount; + + g_return_if_fail(gui != NULL); + + gui->xpos = 0; + last_line = gui->ypos >= last_text_line-first_text_line-1; + + if (gui->empty_linecount > 0) + { + /* window buffer height isn't even the size of the screen yet */ + gui->empty_linecount--; + linecount = gui_window_get_linecount(gui, gui->startline->data); + } + else + { + linecount = gui_window_update_bottom(gui, 1); + } + + if (!last_line || !gui->bottom) + { + gui->ypos++; + } + else if (gui->bottom) + { + if (gui->subline >= linecount) + { + /* after screen gets full after /CLEAR we end up here.. */ + gui->startline = gui->startline->next; + gui->subline = 0; + + linecount = gui_window_update_bottom(gui, 1); + } + + if (linecount > 1+gui->subline) + gui->subline++; + else + { + gui->startline = gui->startline->next; + gui->subline = 0; + } + + if (visible) + { + scroll_up(first_text_line, last_text_line-1); + move(last_text_line-1, 0); clrtoeol(); + } + } +} + +/* get number of real lines that line record takes - this really should share + at least some code with gui_window_line_draw().. */ +gint gui_window_get_linecount(GUI_WINDOW_REC *gui, LINE_REC *line) +{ + gchar *ptr, *last_space_ptr, *tmp; + gint lines, xpos, indent_pos, last_space; + + g_return_val_if_fail(gui != NULL, -1); + g_return_val_if_fail(line != NULL, -1); + + if (line->text == NULL) + return 0; + + xpos = 0; lines = 1; indent_pos = DEFAULT_INDENT_POS; + last_space = 0; last_space_ptr = NULL; + for (ptr = line->text;; ptr++) + { + if (*ptr == '\0') + { + /* command */ + ptr++; + switch ((guchar) *ptr) + { + case LINE_CMD_EOL: + return lines; + case LINE_CMD_CONTINUE: + memcpy(&tmp, ptr+1, sizeof(gchar *)); + ptr = tmp-1; + break; + case LINE_CMD_INDENT: + indent_pos = xpos; + break; + } + continue; + } + + if (xpos == COLS) + { + xpos = indent_pos >= COLS-5 ? DEFAULT_INDENT_POS : indent_pos; + + if (last_space > indent_pos && last_space > 10) + { + ptr = last_space_ptr; + while (*ptr == ' ') ptr++; + } + + last_space = 0; + lines++; + ptr--; + continue; + } + + xpos++; + if (*ptr == ' ') + { + last_space = xpos-1; + last_space_ptr = ptr+1; + } + } +} + +/* draw line - ugly code.. */ +gint gui_window_line_draw(GUI_WINDOW_REC *gui, LINE_REC *line, gint ypos, gint skip, gint max) +{ + gchar *ptr, *last_space_ptr, *tmp; + gint lines, xpos, color, indent_pos, last_space, last_space_color; + + g_return_val_if_fail(gui != NULL, -1); + g_return_val_if_fail(line != NULL, -1); + + if (line->text == NULL) + return 0; + + move(ypos, 0); + xpos = 0; color = 0; lines = -1; indent_pos = DEFAULT_INDENT_POS; + last_space = last_space_color = 0; last_space_ptr = NULL; + for (ptr = line->text;; ptr++) + { + if (*ptr == '\0') + { + /* command */ + ptr++; + if ((*ptr & 0x80) == 0) + { + /* set color */ + color = (color & ATTR_UNDERLINE) | *ptr; + } + else switch ((guchar) *ptr) + { + case LINE_CMD_EOL: + return lines; + case LINE_CMD_CONTINUE: + memcpy(&tmp, ptr+1, sizeof(gchar *)); + ptr = tmp-1; + break; + case LINE_CMD_UNDERLINE: + color ^= ATTR_UNDERLINE; + break; + case LINE_CMD_COLOR8: + color &= 0xfff0; + color |= 8|ATTR_COLOR8; + break; + case LINE_CMD_BEEP: + beep(); + break; + case LINE_CMD_INDENT: + indent_pos = xpos; + break; + } + set_color(color); + continue; + } + + if (xpos == COLS) + { + xpos = indent_pos >= COLS-5 ? DEFAULT_INDENT_POS : indent_pos; + + if (last_space > indent_pos && last_space > 10) + { + /* remove the last word */ + if (!skip) + { + move(ypos, last_space); + set_color(0); + clrtoeol(); + } + + /* skip backwards to draw the line again. */ + ptr = last_space_ptr; + color = last_space_color; + if (!skip) set_color(color); + while (*ptr == ' ') ptr++; + } + last_space = 0; + + if (skip > 0) + { + if (--skip == 0) set_color(color); + } + else + { + if (lines == max) + return lines; + if (max != -1) + ypos++; + else + { + gui_window_newline(gui, TRUE); + ypos = first_text_line+gui->ypos; + } + lines++; + } + move(ypos, indent_pos); + + /* we could have \0.. */ + ptr--; + continue; + } + + xpos++; + if (*ptr == ' ') + { + last_space = xpos-1; + last_space_color = color; + last_space_ptr = ptr+1; + } + + if (skip) continue; + if (lines == -1) lines = 0; + + if ((guchar) *ptr >= 32) + addch((guchar) *ptr); + else + { + /* low-ascii */ + set_color(ATTR_REVERSE); + addch(*ptr+'A'-1); + set_color(color); + } + } +} + +void gui_window_redraw(WINDOW_REC *window) +{ + GUI_WINDOW_REC *gui; + GList *line; + gint ypos, lines, skip, max; + + g_return_if_fail(window != NULL); + + gui = WINDOW_GUI(window); + + for (ypos = first_text_line; ypos < last_text_line; ypos++) + { + set_color(0); + move(ypos, 0); + clrtoeol(); + } + + skip = gui->subline; + ypos = first_text_line; + for (line = gui->startline; line != NULL; line = line->next) + { + LINE_REC *rec = line->data; + + max = last_text_line - ypos-1; + if (max < 0) break; + + lines = gui_window_line_draw(gui, rec, ypos, skip, max); + skip = 0; + + ypos += lines+1; + } + screen_refresh(); +} + +static void gui_window_scroll_up(GUI_WINDOW_REC *gui, gint lines) +{ + LINE_REC *line; + gint count, linecount; + + if (gui->startline == NULL) + return; + + count = lines-gui->subline; gui->ypos += gui->subline; + gui->subline = 0; + + while (gui->startline->prev != NULL && count > 0) + { + gui->startline = gui->startline->prev; + + line = gui->startline->data; + linecount = gui_window_get_linecount(gui, line); + count -= linecount; + gui->ypos += linecount; + } + + if (count < 0) + { + gui->subline = -count; + gui->ypos -= -count; + } + + gui->bottom = (gui->ypos >= -1 && gui->ypos <= last_text_line-first_text_line-1); +} + +static void gui_window_scroll_down(GUI_WINDOW_REC *gui, gint lines) +{ + LINE_REC *line; + gint count, linecount; + + if (gui->startline == gui->bottom_startline && gui->subline == gui->bottom_subline) + return; + + count = lines+gui->subline; gui->ypos += gui->subline; + gui->subline = 0; + + while (count > 0) + { + line = gui->startline->data; + + linecount = gui_window_get_linecount(gui, line); + count -= linecount; + gui->ypos -= linecount; + + if (gui->startline == gui->bottom_startline && + linecount+count > gui->bottom_subline) + { + /* reached the last screenful of text */ + gui->subline = gui->bottom_subline; + gui->ypos += linecount; + gui->ypos -= gui->subline; + break; + } + + if (count <= 0) + { + gui->subline = linecount+count; + gui->ypos += -count; + break; + } + + if (gui->startline->next == NULL) + { + gui->subline = linecount; + break; + } + gui->startline = gui->startline->next; + } + + gui->bottom = (gui->ypos >= -1 && gui->ypos <= last_text_line-first_text_line-1); +} + +void gui_window_scroll(WINDOW_REC *window, gint lines) +{ + GUI_WINDOW_REC *gui; + + g_return_if_fail(window != NULL); + + gui = WINDOW_GUI(window); + + if (lines < 0) + gui_window_scroll_up(gui, -lines); + else + gui_window_scroll_down(gui, lines); + + if (is_window_visible(window)) + gui_window_redraw(window); + signal_emit("gui page scrolled", 1, window); +} + +static void window_update_prompt(WINDOW_REC *window) +{ + WI_ITEM_REC *item; + char *text, *str; + + item = window->active; + if (item != NULL) + text = item->name; + else if (window->name != NULL) + text = window->name; + else { + gui_entry_set_prompt(""); + return; + } + + /* set prompt */ + str = g_strdup_printf("[%1.17s] ", text); + gui_entry_set_prompt(str); + if (*str != '\0') g_free(str); +} + +static void signal_window_changed(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + WINDOW_GUI(window)->parent->active = window; + + screen_refresh_freeze(); + window_update_prompt(window); + gui_window_redraw(window); + screen_refresh_thaw(); +} + +static void signal_window_item_update(WINDOW_REC *window) +{ + CHANNEL_REC *channel; + + channel = irc_item_channel(window->active); + if (channel != NULL) { + /* redraw channel widgets */ + signal_emit("channel topic changed", 1, channel); + signal_emit("channel mode changed", 1, channel); + } + + window_update_prompt(window); +} + +GList *gui_window_find_text(WINDOW_REC *window, gchar *text, GList *startline, int regexp, int fullword) +{ + regex_t preg; + GList *tmp; + GList *matches; + gchar *str, *ptr; + gint n, size; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(text != NULL, NULL); + + text = g_strdup(text); g_strup(text); + matches = NULL; size = 1024; str = g_malloc(1024); + + if (regcomp(&preg, text, REG_EXTENDED|REG_NOSUB) != 0) { + g_free(text); + return 0; + } + + if (startline == NULL) startline = WINDOW_GUI(window)->lines; + for (tmp = startline; tmp != NULL; tmp = tmp->next) + { + LINE_REC *rec = tmp->data; + + for (n = 0, ptr = rec->text; ; ptr++) + { + if (*ptr != 0) + { + if (n+2 > size) + { + size += 1024; + str = g_realloc(str, size); + } + str[n++] = toupper(*ptr); + } + else + { + ptr++; + + if ((guchar) *ptr == LINE_CMD_CONTINUE) + { + gchar *tmp; + + memcpy(&tmp, ptr+1, sizeof(gchar *)); + ptr = tmp-1; + } + else if ((guchar) *ptr == LINE_CMD_EOL) + break; + } + } + str[n] = '\0'; + + if (regexp ? /*regexec(&preg, str, 0, NULL, 0) == 0*/regexp_match(str, text) : + fullword ? stristr_full(str, text) != NULL : + strstr(str, text) != NULL) { + /* matched */ + matches = g_list_append(matches, rec); + } + } + regfree(&preg); + + if (str != NULL) g_free(str); + g_free(text); + return matches; +} + +static void gui_window_horiz_resize(WINDOW_REC *window) +{ + GUI_WINDOW_REC *gui; + gint linecount; + + gui = WINDOW_GUI(window); + if (gui->lines == NULL) return; + + linecount = gui_window_get_linecount(gui, g_list_last(gui->lines)->data); + gui->last_subline = linecount-1; + + /* fake a /CLEAR and scroll window up one page */ + gui->ypos = -1; + gui->bottom = TRUE; + gui->empty_linecount = last_text_line-first_text_line-1; + + gui->bottom_startline = gui->startline = g_list_last(gui->lines); + gui->bottom_subline = gui->subline = gui->last_subline+1; + gui_window_scroll(window, -gui->empty_linecount-1); + + gui->bottom_startline = gui->startline; + gui->bottom_subline = gui->subline; + + /* remove the empty lines from the end */ + if (gui->bottom && gui->startline == gui->lines) + gui->empty_linecount = (last_text_line-first_text_line-1); + else + gui->empty_linecount = 0; +} + +void gui_windows_resize(gint ychange, gboolean xchange) +{ + GUI_WINDOW_REC *gui; + WINDOW_REC *window; + GSList *tmp; + + screen_refresh_freeze(); + for (tmp = windows; tmp != NULL; tmp = tmp->next) + { + window = tmp->data; + + gui = WINDOW_GUI(window); + + if (xchange) + { + /* window width changed, we'll need to recalculate a few things.. */ + gui_window_horiz_resize(window); + continue; + } + + if (ychange < 0 && gui->empty_linecount > 0) + { + /* empty space at the bottom of the screen - just eat it. */ + gui->empty_linecount += ychange; + if (gui->empty_linecount < 0) + gui->empty_linecount = 0; + } + else if (gui->bottom && gui->startline == gui->lines && ychange > 0) + { + /* less than screenful of text, add empty space */ + gui->empty_linecount += ychange; + } + else + { + gui_window_update_bottom(WINDOW_GUI(window), -ychange); + gui_window_scroll(window, -ychange); + } + } + + irssi_redraw(); + screen_refresh_thaw(); +} + +static void cmd_window_move(gchar *data) +{ + GSList *w1, *w2; + WINDOW_REC *window; + + g_return_if_fail(data != NULL); + + window = active_win; + w1 = g_slist_find(windows, window); + if (g_strcasecmp(data, "LEFT") == 0 || g_strncasecmp(data, "PREV", 4) == 0) + { + w2 = g_slist_nth(windows, g_slist_index(windows, window)-1); + if (w2 == NULL) + { + window = w1->data; + windows = g_slist_remove(windows, window); + windows = g_slist_append(windows, window); + w2 = g_slist_last(windows); + } + } + else if (g_strcasecmp(data, "RIGHT") == 0 || g_strcasecmp(data, "NEXT") == 0) + { + w2 = w1->next; + if (w2 == NULL) + { + window = w1->data; + windows = g_slist_remove(windows, window); + windows = g_slist_prepend(windows, window); + } + } + else + return; + + if (w2 != NULL) + { + window = w1->data; + w1->data = w2->data; + w2->data = window; + } + + window_set_active(window); +} + +void gui_windows_init(void) +{ + signal_add("window created", (SIGNAL_FUNC) gui_window_created); + signal_add("window destroyed", (SIGNAL_FUNC) gui_window_destroyed); + signal_add("window changed", (SIGNAL_FUNC) signal_window_changed); + signal_add("window item changed", (SIGNAL_FUNC) signal_window_item_update); + signal_add("window name changed", (SIGNAL_FUNC) signal_window_item_update); + signal_add("window item remove", (SIGNAL_FUNC) signal_window_item_update); + command_bind("window move", NULL, (SIGNAL_FUNC) cmd_window_move); +} + +void gui_windows_deinit(void) +{ + signal_remove("window created", (SIGNAL_FUNC) gui_window_created); + signal_remove("window destroyed", (SIGNAL_FUNC) gui_window_destroyed); + signal_remove("window changed", (SIGNAL_FUNC) signal_window_changed); + signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_update); + signal_remove("window name changed", (SIGNAL_FUNC) signal_window_item_update); + signal_remove("window item remove", (SIGNAL_FUNC) signal_window_item_update); + command_unbind("window move", (SIGNAL_FUNC) cmd_window_move); +} diff --git a/src/fe-text/gui-windows.h b/src/fe-text/gui-windows.h new file mode 100644 index 00000000..14a3a982 --- /dev/null +++ b/src/fe-text/gui-windows.h @@ -0,0 +1,93 @@ +#ifndef __GUI_WINDOWS_H +#define __GUI_WINDOWS_H + +#include "server.h" +#include "gui-mainwindows.h" + +#define WINDOW_GUI(a) ((GUI_WINDOW_REC *) ((a)->gui_data)) + +#define is_window_visible(win) \ + (WINDOW_GUI(win)->parent->active == (win)) + +#define LINE_TEXT_CHUNK_SIZE 2048 + +/* 7 first bits of LINE_REC's info byte. */ +enum +{ + LINE_CMD_EOL=0x80, /* line ends here. */ + LINE_CMD_CONTINUE, /* line continues in next block */ + LINE_CMD_COLOR8, /* change to dark grey, normally 8 = bold black */ + LINE_CMD_UNDERLINE, /* enable/disable underlining */ + LINE_CMD_BEEP, /* beep */ + LINE_CMD_INDENT /* if line is split, indent it at this position */ +}; + +typedef struct +{ + gchar *text; /* text in the line. \0 means that the next char will be a + color or command. <= 127 = color or if 8.bit is set, the + first 7 bits are the command. See LINE_CMD_xxxx. */ + + gint32 level; + time_t time; +} +LINE_REC; + +typedef struct +{ + gchar buffer[LINE_TEXT_CHUNK_SIZE]; + gint pos; + gint lines; +} +TEXT_CHUNK_REC; + +typedef struct +{ + MAIN_WINDOW_REC *parent; + + GMemChunk *line_chunk; + GSList *text_chunks; + GList *lines; + + LINE_REC *cur_line; + TEXT_CHUNK_REC *cur_text; + + gint xpos, ypos; /* cursor position in screen */ + GList *startline; /* line at the top of the screen */ + gint subline; /* number of "real lines" to skip from `startline' */ + + GList *bottom_startline; /* marks the bottom of the text buffer */ + gint bottom_subline; + gint empty_linecount; /* how many empty lines are in screen. + a screenful when started or used /CLEAR */ + gboolean bottom; /* window is at the bottom of the text buffer */ + + /* for gui-printtext.c */ + gint last_subline; + gint last_color, last_flags; +} +GUI_WINDOW_REC; + +extern gint first_text_line, last_text_line; + +void gui_windows_init(void); +void gui_windows_deinit(void); + +WINDOW_REC *gui_window_create(MAIN_WINDOW_REC *parent); + +void gui_window_set_server(WINDOW_REC *window, SERVER_REC *server); +GList *gui_window_find_text(WINDOW_REC *window, gchar *text, GList *startline, int regexp, int fullword); + +/* get number of real lines that line record takes */ +gint gui_window_get_linecount(GUI_WINDOW_REC *gui, LINE_REC *line); +gint gui_window_line_draw(GUI_WINDOW_REC *gui, LINE_REC *line, gint ypos, gint skip, gint max); + +void gui_window_clear(WINDOW_REC *window); +void gui_window_redraw(WINDOW_REC *window); +void gui_windows_resize(gint ychange, gboolean xchange); + +void gui_window_newline(GUI_WINDOW_REC *gui, gboolean visible); +gint gui_window_update_bottom(GUI_WINDOW_REC *gui, gint lines); +void gui_window_scroll(WINDOW_REC *window, gint lines); + +#endif diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c new file mode 100644 index 00000000..723d5d9e --- /dev/null +++ b/src/fe-text/irssi.c @@ -0,0 +1,156 @@ +/* + irssi.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "args.h" +#include "signals.h" +#include "core.h" + +#include "irc-core.h" +#include "fe-common-core.h" +#include "fe-common-irc.h" + +#include "screen.h" +#include "gui-entry.h" +#include "gui-mainwindows.h" +#include "gui-printtext.h" +#include "gui-readline.h" +#include "gui-special-vars.h" +#include "gui-statusbar.h" +#include "gui-statusbar-items.h" +#include "gui-textwidget.h" +#include "gui-windows.h" + +#include <signal.h> + +void irc_init(void); +void irc_deinit(void); + +static GMainLoop *main_loop; +int quitting; + +static void sig_exit(void) +{ + g_main_quit(main_loop); +} + +/* redraw irssi's screen.. */ +void irssi_redraw(void) +{ + clear(); + + /* current window */ + gui_window_redraw(active_win); + /* statusbar */ + gui_statusbar_redraw(-1); + /* entry line */ + gui_entry_redraw(); +} + +static void textui_init(void) +{ + static struct poptOption options[] = { + POPT_AUTOHELP + { NULL, '\0', 0, NULL } + }; + + args_register(options); + + irssi_gui = IRSSI_GUI_TEXT; + core_init(); + irc_init(); + fe_common_core_init(); + fe_common_irc_init(); + signal_add("gui exit", (SIGNAL_FUNC) sig_exit); +} + +static void textui_finish_init(void) +{ + quitting = FALSE; + + screen_refresh_freeze(); + gui_entry_init(); + gui_mainwindows_init(); + gui_printtext_init(); + gui_readline_init(); + gui_special_vars_init(); + gui_textwidget_init(); + gui_windows_init(); + + fe_common_core_finish_init(); + fe_common_irc_finish_init(); + + gui_statusbar_init(); + gui_statusbar_items_init(); + + signal_emit("irssi init finished", 0); + screen_refresh_thaw(); +} + +static void textui_deinit(void) +{ + quitting = TRUE; + signal(SIGINT, SIG_DFL); + + signal_remove("gui exit", (SIGNAL_FUNC) sig_exit); + gui_textwidget_deinit(); + gui_special_vars_deinit(); + gui_statusbar_items_deinit(); + gui_statusbar_deinit(); + gui_printtext_deinit(); + gui_readline_deinit(); + gui_mainwindows_deinit(); + gui_windows_deinit(); + gui_entry_deinit(); + deinit_screen(); + + fe_common_irc_deinit(); + fe_common_core_deinit(); + irc_deinit(); + core_deinit(); +} + +int main(int argc, char **argv) +{ +#ifdef HAVE_SOCKS + SOCKSinit(argv[0]); +#endif + + textui_init(); + args_execute(argc, argv); + + if (!init_screen()) + { + printf("Can't initialize screen handling, quitting.\n"); + return 1; + } + + textui_finish_init(); + main_loop = g_main_new(TRUE); + g_main_run(main_loop); + g_main_destroy(main_loop); + textui_deinit(); + +#ifdef MEM_DEBUG + ig_mem_profile(); +#endif + + return 0; +} diff --git a/src/fe-text/module-formats.c b/src/fe-text/module-formats.c new file mode 100644 index 00000000..fc7f0a63 --- /dev/null +++ b/src/fe-text/module-formats.c @@ -0,0 +1,30 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "printtext.h" + +FORMAT_REC gui_text_formats[] = +{ + { MODULE_NAME, N_("Text user interface"), 0 }, + + { "lastlog_start", N_("%_Lastlog:"), 0 }, + { "lastlog_end", N_("%_End of Lastlog"), 0 }, +}; diff --git a/src/fe-text/module-formats.h b/src/fe-text/module-formats.h new file mode 100644 index 00000000..d8f7a3b8 --- /dev/null +++ b/src/fe-text/module-formats.h @@ -0,0 +1,11 @@ +#include "printtext.h" + +enum { + IRCTXT_MODULE_NAME, + + IRCTXT_LASTLOG_START, + IRCTXT_LASTLOG_END +}; + +extern FORMAT_REC gui_text_formats[]; +#define MODULE_FORMATS gui_text_formats diff --git a/src/fe-text/module.h b/src/fe-text/module.h new file mode 100644 index 00000000..55e08e40 --- /dev/null +++ b/src/fe-text/module.h @@ -0,0 +1,6 @@ +#include "common.h" + +#define MODULE_NAME "gui-text" + +extern int quitting; +void irssi_redraw(void); diff --git a/src/fe-text/screen.c b/src/fe-text/screen.c new file mode 100644 index 00000000..19c79f5a --- /dev/null +++ b/src/fe-text/screen.c @@ -0,0 +1,254 @@ +/* + screen.c : All virtual screen management, real screen management is in + con_???.c files. + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "settings.h" + +#include "screen.h" +#include "gui-readline.h" +#include "gui-windows.h" + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#include <signal.h> + +#ifndef COLOR_PAIRS +#define COLOR_PAIRS 64 +#endif + +#define MIN_SCREEN_WIDTH 20 + +static gint scrx, scry; +gboolean use_colors; +static gint freeze_refresh; + +#ifdef SIGWINCH + +static void sig_winch(int p) +{ +#ifdef TIOCGWINSZ + struct winsize ws; + gint ychange, xchange; + + /* Get new window size */ + if (ioctl(0, TIOCGWINSZ, &ws) < 0) + return; + + if (ws.ws_row == LINES && ws.ws_col == COLS) + { + /* Same size, abort. */ + return; + } + + if (ws.ws_col < MIN_SCREEN_WIDTH) + ws.ws_col = MIN_SCREEN_WIDTH; + + /* Resize curses terminal */ + ychange = ws.ws_row-LINES; + xchange = ws.ws_col-COLS; +#ifdef HAVE_CURSES_RESIZETERM + resizeterm(ws.ws_row, ws.ws_col); +#else + deinit_screen(); + init_screen(); +#endif + + last_text_line += ychange; + gui_windows_resize(ychange, xchange != 0); +#endif +} +#endif + +/* SIGINT != ^C .. any better way to make this work? */ +void sigint_handler(int p) +{ + ungetch(3); + readline(); +} + +static void read_settings(void) +{ + use_colors = settings_get_bool("colors"); + irssi_redraw(); +} + +/* Initialize screen, detect screen length */ +gint init_screen(void) +{ + gchar ansi_tab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + gint num; + + if (!initscr()) return 0; + + if (COLS < MIN_SCREEN_WIDTH) + COLS = MIN_SCREEN_WIDTH; + + signal(SIGINT, sigint_handler); + cbreak(); noecho(); idlok(stdscr, 1); +#ifdef HAVE_CURSES_IDCOK + idcok(stdscr, 1); +#endif + intrflush(stdscr, FALSE); halfdelay(1); keypad(stdscr, 1); + + settings_add_bool("lookandfeel", "colors", TRUE); + + use_colors = settings_get_bool("colors") && has_colors(); + if (has_colors()) start_color(); + +#ifdef HAVE_NCURSES_USE_DEFAULT_COLORS + /* this lets us to use the "default" background color for colors <= 7 so + background pixmaps etc. show up right */ + use_default_colors(); + + for (num = 1; num < COLOR_PAIRS; num++) + init_pair(num, ansi_tab[num & 7], num <= 7 ? -1 : ansi_tab[num >> 3]); + + init_pair(63, 0, -1); /* hm.. not THAT good idea, but probably more people + want dark grey than white on white.. */ +#else + for (num = 1; num < COLOR_PAIRS; num++) + init_pair(num, ansi_tab[num & 7], ansi_tab[num >> 3]); + init_pair(63, 0, 0); +#endif + + scrx = scry = 0; + if (last_text_line == 0) + { + first_text_line = 0; + last_text_line = LINES-1; + } +#ifdef SIGWINCH + signal(SIGWINCH, sig_winch); +#endif + + freeze_refresh = 0; + clear(); + + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + return 1; +} + +/* Deinitialize screen */ +void deinit_screen(void) +{ + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + endwin(); +} + +void set_color(gint col) +{ + gint attr; + + if (!use_colors) + { + if ((col & 0x70) != 0) + attr = A_REVERSE; + else + attr = 0; + } + else + { + if (col & ATTR_COLOR8) + attr = A_DIM | COLOR_PAIR(63); + else + attr = COLOR_PAIR((col&7) + (col&0x70)/2); + } + + if (col & 0x08) attr |= A_BOLD; + if (col & 0x80) attr |= A_BLINK; + + if (col & ATTR_UNDERLINE) attr |= A_UNDERLINE; + if (col & ATTR_REVERSE) attr |= A_REVERSE; + + attrset(attr); +} + +void set_bg(gint col) +{ + gint attr; + + if (!use_colors) + { + if ((col & 0x70) != 0) + attr = A_REVERSE; + else + attr = 0; + } + else + { + if (col == 8) + attr = A_DIM | COLOR_PAIR(63); + else + attr = COLOR_PAIR((col&7) + (col&0x70)/2); + } + + if (col & 0x08) attr |= A_BOLD; + if (col & 0x80) attr |= A_BLINK; + + bkgdset(' ' | attr); +} + +/* Scroll area up */ +void scroll_up(gint y1, gint y2) +{ + scrollok(stdscr, TRUE); + setscrreg(y1, y2); scrl(1); + scrollok(stdscr, FALSE); +} + +/* Scroll area down */ +void scroll_down(gint y1, gint y2) +{ + scrollok(stdscr, TRUE); + setscrreg(y1, y2); scrl(-1); + scrollok(stdscr, FALSE); +} + +void move_cursor(gint y, gint x) +{ + scry = y; + scrx = x; +} + +void screen_refresh_freeze(void) +{ + freeze_refresh++; +} + +void screen_refresh_thaw(void) +{ + if (freeze_refresh > 0) + { + freeze_refresh--; + if (freeze_refresh == 0) screen_refresh(); + } +} + +void screen_refresh(void) +{ + if (freeze_refresh == 0) + { + move(scry, scrx); + refresh(); + } +} diff --git a/src/fe-text/screen.h b/src/fe-text/screen.h new file mode 100644 index 00000000..3fc5694e --- /dev/null +++ b/src/fe-text/screen.h @@ -0,0 +1,31 @@ +#ifndef __SCREEN_H +#define __SCREEN_H + +#if defined(USE_NCURSES) && !defined(RENAMED_NCURSES) +#include <ncurses.h> +#else +#include <curses.h> +#endif + +#define ATTR_UNDERLINE 0x100 +#define ATTR_COLOR8 0x200 +#define ATTR_REVERSE 0x400 + +extern gboolean use_colors; + +gint init_screen(void); /* Initialize screen, detect screen length */ +void deinit_screen(void); /* Deinitialize screen */ + +void set_color(gint col); +void set_bg(gint col); + +void scroll_up(gint y1, gint y2); /* Scroll area up */ +void scroll_down(gint y1, gint y2); /* Scroll area down */ + +void move_cursor(gint y, gint x); + +void screen_refresh_freeze(void); +void screen_refresh_thaw(void); +void screen_refresh(void); + +#endif diff --git a/src/irc/Makefile.am b/src/irc/Makefile.am new file mode 100644 index 00000000..b2972564 --- /dev/null +++ b/src/irc/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = core dcc flood notifylist + +noinst_LTLIBRARIES = libirc.la + +libirc_la_SOURCES = irc.c diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am new file mode 100644 index 00000000..6e13c75c --- /dev/null +++ b/src/irc/core/Makefile.am @@ -0,0 +1,61 @@ +noinst_LTLIBRARIES = libirc_core.la + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core + +libirc_core_la_SOURCES = \ + bans.c \ + ctcp.c \ + channels.c \ + channels-query.c \ + channels-setup.c \ + channel-events.c \ + ignore.c \ + irc.c \ + irc-core.c \ + irc-commands.c \ + irc-rawlog.c \ + irc-server.c \ + irc-special-vars.c \ + ircnet-setup.c \ + lag.c \ + masks.c \ + massjoin.c \ + modes.c \ + mode-lists.c \ + netsplit.c \ + nicklist.c \ + query.c \ + server-idle.c \ + server-reconnect.c \ + server-setup.c + +noinst_HEADERS = \ + bans.h \ + ctcp.h \ + channels.h \ + channels-query.h \ + channels-setup.h \ + channel-events.h \ + ignore.h \ + irc.h \ + irc-core.h \ + irc-commands.h \ + irc-rawlog.h \ + irc-server.h \ + irc-special-vars.h \ + ircnet-setup.h \ + lag.h \ + masks.h \ + massjoin.h \ + modes.h \ + mode-lists.h \ + module.h \ + netsplit.h \ + nicklist.h \ + query.h \ + server-idle.h \ + server-reconnect.h \ + server-setup.h diff --git a/src/irc/core/bans.c b/src/irc/core/bans.c new file mode 100644 index 00000000..c492d3da --- /dev/null +++ b/src/irc/core/bans.c @@ -0,0 +1,218 @@ +/* + bans.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "commands.h" +#include "misc.h" +#include "signals.h" + +#include "masks.h" +#include "modes.h" +#include "mode-lists.h" +#include "irc.h" +#include "nicklist.h" + +static int bantype; + +/* Get ban mask */ +char *ban_get_mask(CHANNEL_REC *channel, const char *nick) +{ + NICK_REC *rec; + char *str, *user, *host; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = nicklist_find(channel, nick); + if (rec == NULL || rec->host == NULL) return NULL; + + str = irc_get_mask(nick, rec->host, bantype); + + /* there's a limit of 10 characters in user mask. so, banning + someone with user mask of 10 characters gives us "*1234567890", + which is one too much.. so, replace the 10th character with '*' */ + user = strchr(str, '!'); + if (user == NULL) return str; + + host = strchr(++user, '@'); + if (host == NULL) return str; + + if ((int) (host-user) < 10) { + /* too long user mask */ + user[9] = '*'; + g_memmove(user+10, host, strlen(host)+1); + } + return str; +} + +void ban_set_type(const char *type) +{ + char bantypestr[5], **list; + int n, max; + + g_return_if_fail(type != NULL); + + if (toupper(type[0]) == 'N') { + bantype = IRC_MASK_USER | IRC_MASK_DOMAIN; + strcpy(bantypestr, "UD"); + } + else if (toupper(type[0]) == 'H') { + bantype = IRC_MASK_HOST | IRC_MASK_DOMAIN; + strcpy(bantypestr, "HD"); + } + else if (toupper(type[0]) == 'D') { + bantype = IRC_MASK_DOMAIN; + strcpy(bantypestr, "D"); + } + else if (toupper(type[0]) == 'C') { + list = g_strsplit(type, " ", -1); + + max = strarray_length(list); + bantype = 0; + for (n = 1; n < max; n++) { + if (toupper(list[n][0]) == 'N') + bantype |= IRC_MASK_NICK; + else if (toupper(list[n][0]) == 'U') + bantype |= IRC_MASK_USER; + else if (toupper(list[n][0]) == 'H') + bantype |= IRC_MASK_HOST; + else if (toupper(list[n][0]) == 'D') + bantype |= IRC_MASK_DOMAIN; + } + g_strfreev(list); + + bantypestr[0] = '\0'; + if (bantype & IRC_MASK_NICK) strcat(bantypestr, "N"); + if (bantype & IRC_MASK_USER) strcat(bantypestr, "U"); + if (bantype & IRC_MASK_HOST) strcat(bantypestr, "H"); + if (bantype & IRC_MASK_DOMAIN) strcat(bantypestr, "D"); + } + + signal_emit("ban type changed", 1, bantypestr); +} + +void ban_set(CHANNEL_REC *channel, const char *bans) +{ + GString *str; + char **ban, **banlist; + + g_return_if_fail(bans != NULL); + + str = g_string_new(NULL); + banlist = g_strsplit(bans, " ", -1); + for (ban = banlist; *ban != NULL; ban++) { + if (strchr(*ban, '!') != NULL) { + /* explicit ban */ + g_string_sprintfa(str, " %s", *ban); + continue; + } + + /* ban nick */ + *ban = ban_get_mask(channel, *ban); + if (*ban != NULL) { + g_string_sprintfa(str, " %s", *ban); + g_free(*ban); + } + } + g_strfreev(banlist); + + channel_set_singlemode(channel->server, channel->name, str->str, "+b"); + g_string_free(str, TRUE); +} + +void ban_remove(CHANNEL_REC *channel, const char *bans) +{ + GString *str; + GSList *tmp; + char **ban, **banlist; + + str = g_string_new(NULL); + banlist = g_strsplit(bans, " ", -1); + for (ban = banlist; *ban != NULL; ban++) { + for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) { + BAN_REC *rec = tmp->data; + + if (match_wildcards(*ban, rec->ban)) + g_string_sprintfa(str, "%s ", rec->ban); + } + } + g_strfreev(banlist); + + channel_set_singlemode(channel->server, channel->name, str->str, "-b"); + g_string_free(str, TRUE); +} + +static void command_set_ban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item, int set) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *nicks; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, + item, &channel, &nicks); + if (!ischannel(*channel)) cmd_param_error(CMDERR_NOT_JOINED); + if (*nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = channel_find(server, channel); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + if (!chanrec->wholist) cmd_param_error(CMDERR_CHAN_NOT_SYNCED); + + if (set) + ban_set(chanrec, nicks); + else + ban_remove(chanrec, nicks); + + g_free(params); +} + +static void cmd_bantype(const char *data) +{ + ban_set_type(data); +} + +static void cmd_ban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + command_set_ban(data, server, item, TRUE); +} + +static void cmd_unban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + command_set_ban(data, server, item, FALSE); +} + +void bans_init(void) +{ + /* default bantype */ + bantype = IRC_MASK_USER | IRC_MASK_DOMAIN; + command_bind("bantype", NULL, (SIGNAL_FUNC) cmd_bantype); + command_bind("ban", NULL, (SIGNAL_FUNC) cmd_ban); + command_bind("unban", NULL, (SIGNAL_FUNC) cmd_unban); +} + +void bans_deinit(void) +{ + command_unbind("bantype", (SIGNAL_FUNC) cmd_bantype); + command_unbind("ban", (SIGNAL_FUNC) cmd_ban); + command_unbind("unban", (SIGNAL_FUNC) cmd_unban); +} diff --git a/src/irc/core/bans.h b/src/irc/core/bans.h new file mode 100644 index 00000000..aa6ff346 --- /dev/null +++ b/src/irc/core/bans.h @@ -0,0 +1,15 @@ +#ifndef __BANS_H +#define __BANS_H + +#include "channels.h" + +void bans_init(void); +void bans_deinit(void); + +char *ban_get_mask(CHANNEL_REC *channel, const char *nick); + +void ban_set_type(const char *type); +void ban_set(CHANNEL_REC *channel, const char *bans); +void ban_remove(CHANNEL_REC *channel, const char *ban); + +#endif diff --git a/src/irc/core/channel-events.c b/src/irc/core/channel-events.c new file mode 100644 index 00000000..8f11ee47 --- /dev/null +++ b/src/irc/core/channel-events.c @@ -0,0 +1,241 @@ +/* + channel-events.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "misc.h" +#include "channels.h" +#include "irc.h" + +static void event_cannot_join(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + + if (channel[0] == '!' && channel[1] == '!') + channel++; /* server didn't understand !channels */ + + chanrec = channel_find(server, channel); + if (chanrec != NULL && !chanrec->names_got) { + chanrec->left = TRUE; + channel_destroy(chanrec); + } + + g_free(params); +} + +static void event_target_unavailable(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (ischannel(*channel)) { + /* channel is unavailable. */ + event_cannot_join(data, server); + } + + g_free(params); +} + +static void channel_change_topic(IRC_SERVER_REC *server, const char *channel, const char *topic) +{ + CHANNEL_REC *chanrec; + + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + g_free_not_null(chanrec->topic); + chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic); + + signal_emit("channel topic changed", 1, chanrec); + } +} +static void event_topic_get(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel, *topic; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, &topic); + channel_change_topic(server, channel, topic); + g_free(params); +} + +static void event_topic(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel, *topic; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &channel, &topic); + channel_change_topic(server, channel, topic); + g_free(params); +} + +static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + char *params, *channel, *tmp; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + if (g_strcasecmp(nick, server->nick) != 0) { + /* someone else joined channel, no need to do anything */ + return; + } + + if (server->userhost == NULL) + server->userhost = g_strdup(address); + + params = event_get_params(data, 1, &channel); + tmp = strchr(channel, 7); /* ^G does something weird.. */ + if (tmp != NULL) *tmp = '\0'; + + if (*channel == '!') { + /* !channels have 5 chars long identification string before + it's name, it's not known when /join is called so rename + !channel here to !ABCDEchannel */ + char *shortchan; + + shortchan = g_strdup(channel); + sprintf(shortchan, "!%s", channel+6); + + chanrec = channel_find(server, shortchan); + if (chanrec != NULL) { + g_free(chanrec->name); + chanrec->name = g_strdup(channel); + } + + g_free(shortchan); + } + + chanrec = channel_find(server, channel); + if (chanrec == NULL) { + /* didn't get here with /join command.. */ + chanrec = channel_create(server, channel, TRUE); + } + + g_free(params); +} + +static void event_part(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + char *params, *channel, *reason; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + if (g_strcasecmp(nick, server->nick) != 0) { + /* someone else part, no need to do anything here */ + return; + } + + params = event_get_params(data, 2, &channel, &reason); + + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + chanrec->left = TRUE; + channel_destroy(chanrec); + } + + g_free(params); +} + +static void event_kick(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *nick, *reason; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, &channel, &nick, &reason); + + if (g_strcasecmp(nick, server->nick) != 0) { + /* someone else was kicked, no need to do anything */ + g_free(params); + return; + } + + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + chanrec->kicked = TRUE; + channel_destroy(chanrec); + } + + g_free(params); +} + +static void event_invite(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + g_free_not_null(server->last_invite); + server->last_invite = g_strdup(channel); + g_free(params); +} + +void channel_events_init(void) +{ + signal_add_first("event 403", (SIGNAL_FUNC) event_cannot_join); /* no such channel */ + signal_add_first("event 405", (SIGNAL_FUNC) event_cannot_join); /* too many channels */ + signal_add_first("event 407", (SIGNAL_FUNC) event_cannot_join); /* duplicate channel */ + signal_add_first("event 471", (SIGNAL_FUNC) event_cannot_join); /* channel is full */ + signal_add_first("event 473", (SIGNAL_FUNC) event_cannot_join); /* invite only */ + signal_add_first("event 474", (SIGNAL_FUNC) event_cannot_join); /* banned */ + signal_add_first("event 475", (SIGNAL_FUNC) event_cannot_join); /* bad channel key */ + signal_add_first("event 476", (SIGNAL_FUNC) event_cannot_join); /* bad channel mask */ + + signal_add("event topic", (SIGNAL_FUNC) event_topic); + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("event part", (SIGNAL_FUNC) event_part); + signal_add("event kick", (SIGNAL_FUNC) event_kick); + signal_add("event invite", (SIGNAL_FUNC) event_invite); + signal_add("event 332", (SIGNAL_FUNC) event_topic_get); + signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable); /* channel/nick unavailable */ +} + +void channel_events_deinit(void) +{ + signal_remove("event 403", (SIGNAL_FUNC) event_cannot_join); /* no such channel */ + signal_remove("event 405", (SIGNAL_FUNC) event_cannot_join); /* too many channels */ + signal_remove("event 407", (SIGNAL_FUNC) event_cannot_join); /* duplicate channel */ + signal_remove("event 471", (SIGNAL_FUNC) event_cannot_join); /* channel is full */ + signal_remove("event 473", (SIGNAL_FUNC) event_cannot_join); /* invite only */ + signal_remove("event 474", (SIGNAL_FUNC) event_cannot_join); /* banned */ + signal_remove("event 475", (SIGNAL_FUNC) event_cannot_join); /* bad channel key */ + signal_remove("event 476", (SIGNAL_FUNC) event_cannot_join); /* bad channel mask */ + + signal_remove("event topic", (SIGNAL_FUNC) event_topic); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event part", (SIGNAL_FUNC) event_part); + signal_remove("event kick", (SIGNAL_FUNC) event_kick); + signal_remove("event invite", (SIGNAL_FUNC) event_invite); + signal_remove("event 332", (SIGNAL_FUNC) event_topic_get); + signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); /* channel/nick unavailable */ +} diff --git a/src/irc/core/channel-events.h b/src/irc/core/channel-events.h new file mode 100644 index 00000000..f1fe69ba --- /dev/null +++ b/src/irc/core/channel-events.h @@ -0,0 +1,7 @@ +#ifndef __CHANNEL_EVENTS_H +#define __CHANNEL_EVENTS_H + +void channel_events_init(void); +void channel_events_deinit(void); + +#endif diff --git a/src/irc/core/channels-query.c b/src/irc/core/channels-query.c new file mode 100644 index 00000000..c6d4f51e --- /dev/null +++ b/src/irc/core/channels-query.c @@ -0,0 +1,594 @@ +/* + channels-query.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + + How the thing works: + + - After channel is joined and NAMES list is got, send "channel query" signal + - "channel query" : add channel to server->quries lists + +loop: + - Wait for NAMES list from all channels before doing anything else.. + - After got the last NAMES list, start sending the queries .. + - find the query to send, check where server->queries list isn't NULL + (mode, who, banlist, ban exceptions, invite list) + - if not found anything -> all channels are synced + - send "command #chan1,#chan2,#chan3,.." command to server + - wait for reply from server, then check if it was last query to be sent to + server. If it was, send "channel sync" signal + - check if the reply was for last channel in the command list. If so, + goto loop +*/ + +#include "module.h" +#include "modules.h" +#include "misc.h" +#include "signals.h" + +#include "channels.h" +#include "irc.h" +#include "modes.h" +#include "mode-lists.h" +#include "nicklist.h" +#include "irc-server.h" +#include "server-redirect.h" + +enum { + CHANNEL_QUERY_MODE, + CHANNEL_QUERY_WHO, + CHANNEL_QUERY_BMODE, + CHANNEL_QUERY_EMODE, + CHANNEL_QUERY_IMODE, + + CHANNEL_QUERIES +}; + +#define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO) + +typedef struct { + char *last_query_chan; + GSList *queries[CHANNEL_QUERIES]; +} SERVER_QUERY_REC; + +static void sig_connected(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(server != NULL); + if (!irc_server_check(server)) return; + + rec = g_new0(SERVER_QUERY_REC, 1); + server->chanqueries = rec; +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + int n; + + g_return_if_fail(server != NULL); + if (!irc_server_check(server)) return; + + rec = server->chanqueries; + g_return_if_fail(rec != NULL); + + for (n = 0; n < CHANNEL_QUERIES; n++) + g_slist_free(rec->queries[n]); + g_free_not_null(rec->last_query_chan); + g_free(rec); +} + +/* Add channel to query list */ +static void channel_query_add(CHANNEL_REC *channel, int query) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(channel != NULL); + + rec = channel->server->chanqueries; + g_return_if_fail(rec != NULL); + + rec->queries[query] = g_slist_append(rec->queries[query], channel); +} + +static void channel_query_remove_all(CHANNEL_REC *channel) +{ + SERVER_QUERY_REC *rec; + int n; + + rec = channel->server->chanqueries; + g_return_if_fail(rec != NULL); + + /* remove channel from query lists */ + for (n = 0; n < CHANNEL_QUERIES; n++) + rec->queries[n] = g_slist_remove(rec->queries[n], channel); +} + + +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + if (channel->server != NULL && !channel->synced) + channel_query_remove_all(channel); +} + +static int channels_have_all_names(IRC_SERVER_REC *server) +{ + GSList *tmp; + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (!rec->names_got) + return 0; + } + + return 1; +} + +static int find_next_query(SERVER_QUERY_REC *server) +{ + int n; + + for (n = 0; n < CHANNEL_QUERIES; n++) { + if (server->queries[n] != NULL) + return n; + } + + return -1; +} + +static void channel_send_query(IRC_SERVER_REC *server, int query) +{ + SERVER_QUERY_REC *rec; + CHANNEL_REC *chanrec; + GSList *tmp, *chans; + char *cmd, *chanstr_commas, *chanstr; + int onlyone; + + rec = server->chanqueries; + g_return_if_fail(rec != NULL); + + onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) || + (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query)); + + if (onlyone) { + chanrec = rec->queries[query]->data; + chans = g_slist_append(NULL, chanrec); + chanstr_commas = g_strdup(chanrec->name); + chanstr = g_strdup(chanrec->name); + } else { + char *chanstr_spaces; + + chans = rec->queries[query]; + + chanstr_commas = gslist_to_string(rec->queries[query], G_STRUCT_OFFSET(CHANNEL_REC, name), ","); + chanstr_spaces = gslist_to_string(rec->queries[query], G_STRUCT_OFFSET(CHANNEL_REC, name), " "); + + chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL); + g_free(chanstr_spaces); + } + + switch (query) { + case CHANNEL_QUERY_MODE: + cmd = g_strdup_printf("MODE %s", chanstr_commas); + for (tmp = chans; tmp != NULL; tmp = tmp->next) { + chanrec = tmp->data; + + server_redirect_event((SERVER_REC *) server, chanstr, 3, + "event 403", "chanquery mode abort", 1, + "event 442", "chanquery mode abort", 1, /* "you're not on that channel" */ + "event 324", "chanquery mode", 1, NULL); + } + break; + + case CHANNEL_QUERY_WHO: + cmd = g_strdup_printf("WHO %s", chanstr_commas); + + for (tmp = chans; tmp != NULL; tmp = tmp->next) { + chanrec = tmp->data; + + server_redirect_event((SERVER_REC *) server, chanstr, 2, + "event 401", "chanquery who abort", 1, + "event 315", "chanquery who end", 1, + "event 352", "silent event who", 1, NULL); + } + break; + + case CHANNEL_QUERY_BMODE: + cmd = g_strdup_printf("MODE %s b", chanstr_commas); + for (tmp = chans; tmp != NULL; tmp = tmp->next) { + chanrec = tmp->data; + + server_redirect_event((SERVER_REC *) server, chanrec->name, 2, + "event 403", "chanquery mode abort", 1, + "event 368", "chanquery ban end", 1, + "event 367", "chanquery ban", 1, NULL); + } + break; + + case CHANNEL_QUERY_EMODE: + cmd = g_strdup_printf("MODE %s e", chanstr_commas); + for (tmp = chans; tmp != NULL; tmp = tmp->next) { + chanrec = tmp->data; + + server_redirect_event((SERVER_REC *) server, chanrec->name, 4, + "event 403", "chanquery mode abort", 1, + "event 349", "chanquery eban end", 1, + "event 348", "chanquery eban", 1, NULL); + } + break; + + case CHANNEL_QUERY_IMODE: + cmd = g_strdup_printf("MODE %s I", chanstr_commas); + for (tmp = chans; tmp != NULL; tmp = tmp->next) { + chanrec = tmp->data; + + server_redirect_event((SERVER_REC *) server, chanrec->name, 4, + "event 403", "chanquery mode abort", 1, + "event 347", "chanquery ilist end", 1, + "event 346", "chanquery ilist", 1, NULL); + } + break; + + default: + cmd = NULL; + } + + g_free(chanstr); + g_free(chanstr_commas); + + /* Get the channel of last query */ + chanrec = g_slist_last(chans)->data; + rec->last_query_chan = g_strdup(chanrec->name); + + if (!onlyone) { + /* all channels queried, set to NULL */ + g_slist_free(rec->queries[query]); + rec->queries[query] = NULL; + } else { + /* remove the first channel from list */ + rec->queries[query] = g_slist_remove(rec->queries[query], chans->data); + } + + /* send the command */ + irc_send_cmd(server, cmd); + g_free(cmd); +} + +static void channels_query_check(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + int query; + + g_return_if_fail(server != NULL); + + rec = server->chanqueries; + g_return_if_fail(rec != NULL); + + g_free_and_null(rec->last_query_chan); + if (!channels_have_all_names(server)) { + /* all channels haven't sent /NAMES list yet */ + return; + } + + query = find_next_query(rec); + if (query == -1) { + /* no queries left */ + return; + } + + channel_send_query(server, query); +} + +static void sig_channel_query(CHANNEL_REC *channel) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(channel != NULL); + + /* Add channel to query lists */ + if (!channel->no_modes) + channel_query_add(channel, CHANNEL_QUERY_MODE); + channel_query_add(channel, CHANNEL_QUERY_WHO); + if (!channel->no_modes) { + channel_query_add(channel, CHANNEL_QUERY_BMODE); + if (channel->server->emode_known) { + channel_query_add(channel, CHANNEL_QUERY_EMODE); + channel_query_add(channel, CHANNEL_QUERY_IMODE); + } + } + + rec = channel->server->chanqueries; + if (rec->last_query_chan == NULL) + channels_query_check(channel->server); +} + +/* if there's no more queries in queries in buffer, send the sync signal */ +static void channel_checksync(CHANNEL_REC *channel) +{ + SERVER_QUERY_REC *rec; + int n; + + g_return_if_fail(channel != NULL); + + if (channel->synced) + return; /* already synced */ + + rec = channel->server->chanqueries; + g_return_if_fail(rec != NULL); + + for (n = 0; n < CHANNEL_QUERIES; n++) { + if (g_slist_find(rec->queries[n], channel)) + return; + } + + channel->synced = TRUE; + signal_emit("channel sync", 1, channel); +} + +static void channel_got_query(IRC_SERVER_REC *server, CHANNEL_REC *chanrec, const char *channel) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(server != NULL); + g_return_if_fail(channel != NULL); + + rec = server->chanqueries; + g_return_if_fail(rec != NULL); + g_return_if_fail(rec->last_query_chan != NULL); + + /* check if we need to get another query.. */ + if (g_strcasecmp(rec->last_query_chan, channel) == 0) + channels_query_check(server); + + /* check if channel is synced */ + if (chanrec != NULL) channel_checksync(chanrec); +} + +static void event_channel_mode(char *data, IRC_SERVER_REC *server, const char *nick) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3 | PARAM_FLAG_GETREST, NULL, &channel, &mode); + chanrec = channel_find(server, channel); + if (chanrec != NULL) + parse_channel_modes(chanrec, nick, mode); + channel_got_query(server, chanrec, channel); + + g_free(params); +} + +static void multi_query_remove(IRC_SERVER_REC *server, const char *event, const char *data) +{ + GSList *queue; + + while ((queue = server_redirect_getqueue((SERVER_REC *) server, event, data)) != NULL) + server_redirect_remove_next((SERVER_REC *) server, event, queue); +} + +static void event_end_of_who(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + NICK_REC *nick; + char *params, *channel, **chans; + int n, onewho; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + + onewho = strchr(channel, ',') != NULL; + if (onewho) { + /* instead of multiple End of WHO replies we get + only this one... */ + server->one_endofwho = TRUE; + multi_query_remove(server, "event 315", data); + + /* check that the WHO actually did return something + (that it understood #chan1,#chan2,..) */ + chanrec = channel_find(server, channel); + nick = nicklist_find(chanrec, server->nick); + if (nick->host == NULL) + server->no_multi_who = TRUE; + } + + chans = g_strsplit(channel, ",", -1); + for (n = 0; chans[n] != NULL; n++) { + chanrec = channel_find(server, chans[n]); + if (chanrec == NULL) + continue; + + if (onewho && server->no_multi_who) { + channel_query_add(chanrec, CHANNEL_QUERY_WHO); + continue; + } + + chanrec->wholist = TRUE; + signal_emit("channel wholist", 1, chanrec); + + /* check if we need can send another query */ + channel_got_query(server, chanrec, chans[n]); + } + + g_strfreev(chans); + g_free(params); + + if (onewho && server->no_multi_who) { + /* server didn't understand multiple WHO replies, + send them again separately */ + channels_query_check(server); + } +} + +static void event_end_of_banlist(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + chanrec = channel_find(server, channel); + + channel_got_query(server, chanrec, channel); + + g_free(params); +} + +static void event_end_of_ebanlist(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + chanrec = channel_find(server, channel); + + channel_got_query(server, chanrec, channel); + + g_free(params); +} + +static void event_end_of_invitelist(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + chanrec = channel_find(server, channel); + + channel_got_query(server, chanrec, channel); + + g_free(params); +} + +static void channel_lost(IRC_SERVER_REC *server, const char *channel) +{ + CHANNEL_REC *chanrec; + + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + /* channel not found - probably created a new channel + and left it immediately. */ + channel_query_remove_all(chanrec); + } + + channel_got_query(server, chanrec, channel); +} + +static void multi_command_error(IRC_SERVER_REC *server, const char *data, int query, const char *event) +{ + CHANNEL_REC *chanrec; + char *params, *channel, **chans; + int n; + + multi_query_remove(server, event, data); + + params = event_get_params(data, 2, NULL, &channel); + + chans = g_strsplit(channel, ",", -1); + for (n = 0; chans[n] != NULL; n++) + { + chanrec = channel_find(server, chans[n]); + if (chanrec != NULL) + channel_query_add(chanrec, query); + } + g_strfreev(chans); + g_free(params); + + channels_query_check(server); +} + +static void event_mode_abort(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + params = event_get_params(data, 2, NULL, &channel); + + if (strchr(channel, ',') == NULL) { + channel_lost(server, channel); + } else { + server->no_multi_mode = TRUE; + multi_command_error(server, data, CHANNEL_QUERY_MODE, "event 324"); + } + + g_free(params); +} + +static void event_who_abort(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + params = event_get_params(data, 2, NULL, &channel); + + if (strchr(channel, ',') == NULL) { + channel_lost(server, channel); + } else { + server->no_multi_who = TRUE; + multi_command_error(server, data, CHANNEL_QUERY_WHO, "event 315"); + } + + g_free(params); +} + +void channels_query_init(void) +{ + signal_add("server connected", (SIGNAL_FUNC) sig_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add("channel query", (SIGNAL_FUNC) sig_channel_query); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode); + signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who); + + signal_add("chanquery eban end", (SIGNAL_FUNC) event_end_of_ebanlist); + signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); + signal_add("chanquery ilist end", (SIGNAL_FUNC) event_end_of_invitelist); + signal_add("chanquery mode abort", (SIGNAL_FUNC) event_mode_abort); + signal_add("chanquery who abort", (SIGNAL_FUNC) event_who_abort); +} + +void channels_query_deinit(void) +{ + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("channel query", (SIGNAL_FUNC) sig_channel_query); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode); + signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who); + + signal_remove("chanquery eban end", (SIGNAL_FUNC) event_end_of_ebanlist); + signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); + signal_remove("chanquery ilist end", (SIGNAL_FUNC) event_end_of_invitelist); + signal_remove("chanquery mode abort", (SIGNAL_FUNC) event_mode_abort); + signal_remove("chanquery who abort", (SIGNAL_FUNC) event_who_abort); +} diff --git a/src/irc/core/channels-query.h b/src/irc/core/channels-query.h new file mode 100644 index 00000000..2498afb7 --- /dev/null +++ b/src/irc/core/channels-query.h @@ -0,0 +1,7 @@ +#ifndef __CHANNELS_QUERY +#define __CHANNELS_QUERY + +void channels_query_init(void); +void channels_query_deinit(void); + +#endif diff --git a/src/irc/core/channels-setup.c b/src/irc/core/channels-setup.c new file mode 100644 index 00000000..c8400d4e --- /dev/null +++ b/src/irc/core/channels-setup.c @@ -0,0 +1,215 @@ +/* + channels-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "channels.h" +#include "channels-setup.h" +#include "nicklist.h" +#include "irc-server.h" +#include "server-setup.h" + +#include "lib-config/iconfig.h" +#include "settings.h" + +GSList *setupchannels; + +#define ircnet_match(a, b) \ + ((a[0]) == '\0' || (b != NULL && g_strcasecmp(a, b) == 0)) + +SETUP_CHANNEL_REC *channels_setup_find(const char *channel, IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(server != NULL, NULL); + + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + SETUP_CHANNEL_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, channel) == 0 && + ircnet_match(rec->ircnet, server->connrec->ircnet)) + return rec; + } + + return NULL; +} + +void channels_setup_destroy(SETUP_CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + g_free(channel->name); + g_free(channel->ircnet); + g_free_not_null(channel->password); + g_free_not_null(channel->botmasks); + g_free_not_null(channel->autosendcmd); + g_free_not_null(channel->background); + g_free_not_null(channel->font); + g_free(channel); + + setupchannels = g_slist_remove(setupchannels, channel); +} + +/* connected to server, autojoin to channels. */ +static void event_connected(IRC_SERVER_REC *server) +{ + GString *chans, *keys; + GSList *tmp; + int use_keys; + + g_return_if_fail(server != NULL); + + if (server->connrec->reconnection) + return; + + /* join to the channels marked with autojoin in setup */ + chans = g_string_new(NULL); + keys = g_string_new(NULL); + + use_keys = FALSE; + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + SETUP_CHANNEL_REC *rec = tmp->data; + + if (!rec->autojoin || !ircnet_match(rec->ircnet, server->connrec->ircnet)) + continue; + + g_string_sprintfa(chans, "%s,", rec->name); + g_string_sprintfa(keys, "%s,", rec->password == NULL ? "x" : rec->password); + if (rec->password != NULL) + use_keys = TRUE; + } + + if (chans->len > 0) { + g_string_truncate(chans, chans->len-1); + g_string_truncate(keys, keys->len-1); + if (use_keys) g_string_sprintfa(chans, " %s", keys->str); + + channels_join(server, chans->str, TRUE); + } + + g_string_free(chans, TRUE); + g_string_free(keys, TRUE); +} + +/* channel wholist received: send the auto send command */ +static void channel_wholist(CHANNEL_REC *channel) +{ + SETUP_CHANNEL_REC *rec; + NICK_REC *nick; + char **bots, **bot, *str; + + g_return_if_fail(channel != NULL); + + rec = channels_setup_find(channel->name, channel->server); + if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd) + return; + + if (rec->botmasks == NULL || !*rec->botmasks) { + /* just send the command. */ + signal_emit("send command", 3, rec->autosendcmd, channel->server, channel); + } + + /* find first available bot.. */ + bots = g_strsplit(rec->botmasks, " ", -1); + for (bot = bots; *bot != NULL; bot++) { + nick = nicklist_find(channel, *bot); + if (nick == NULL) + continue; + + /* got one! */ + str = g_strdup_printf(rec->autosendcmd, nick->nick); + signal_emit("send command", 3, str, channel->server, channel); + g_free(str); + break; + } + g_strfreev(bots); +} + +static SETUP_CHANNEL_REC *setupchannel_add(CONFIG_NODE *node) +{ + SETUP_CHANNEL_REC *rec; + char *channel, *ircnet, *password, *botmasks, *autosendcmd, *background, *font; + + g_return_val_if_fail(node != NULL, NULL); + + channel = config_node_get_str(node, "name", NULL); + ircnet = config_node_get_str(node, "ircnet", NULL); + if (channel == NULL || ircnet == NULL) { + /* missing information.. */ + return NULL; + } + + password = config_node_get_str(node, "password", NULL); + botmasks = config_node_get_str(node, "botmasks", NULL); + autosendcmd = config_node_get_str(node, "autosendcmd", NULL); + background = config_node_get_str(node, "background", NULL); + font = config_node_get_str(node, "font", NULL); + + rec = g_new(SETUP_CHANNEL_REC, 1); + rec->autojoin = config_node_get_bool(node, "autojoin", FALSE); + rec->name = g_strdup(channel); + rec->ircnet = g_strdup(ircnet); + rec->password = (password == NULL || *password == '\0') ? NULL : g_strdup(password); + rec->botmasks = (botmasks == NULL || *botmasks == '\0') ? NULL : g_strdup(botmasks); + rec->autosendcmd = (autosendcmd == NULL || *autosendcmd == '\0') ? NULL : g_strdup(autosendcmd); + rec->background = (background == NULL || *background == '\0') ? NULL : g_strdup(background); + rec->font = (font == NULL || *font == '\0') ? NULL : g_strdup(font); + + setupchannels = g_slist_append(setupchannels, rec); + return rec; +} + +static void channels_read_config(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + while (setupchannels != NULL) + channels_setup_destroy(setupchannels->data); + + /* Read channels */ + node = iconfig_node_traverse("channels", FALSE); + if (node != NULL) { + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + setupchannel_add(tmp->data); + } +} + +void channels_setup_init(void) +{ + source_host_ok = FALSE; + + channels_read_config(); + signal_add("event connected", (SIGNAL_FUNC) event_connected); + signal_add("channel wholist", (SIGNAL_FUNC) channel_wholist); + signal_add("setup reread", (SIGNAL_FUNC) channels_read_config); +} + +void channels_setup_deinit(void) +{ + while (setupchannels != NULL) + channels_setup_destroy(setupchannels->data); + + signal_remove("event connected", (SIGNAL_FUNC) event_connected); + signal_remove("channel wholist", (SIGNAL_FUNC) channel_wholist); + signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config); +} diff --git a/src/irc/core/channels-setup.h b/src/irc/core/channels-setup.h new file mode 100644 index 00000000..0556019a --- /dev/null +++ b/src/irc/core/channels-setup.h @@ -0,0 +1,27 @@ +#ifndef __CHANNELS_SETUP_H +#define __CHANNELS_SETUP_H + +typedef struct { + int autojoin; + + char *name; + char *ircnet; + char *password; + + char *botmasks; + char *autosendcmd; + + char *background; + char *font; +} SETUP_CHANNEL_REC; + +extern GSList *setupchannels; + +void channels_setup_init(void); +void channels_setup_deinit(void); + +void channels_setup_destroy(SETUP_CHANNEL_REC *channel); + +SETUP_CHANNEL_REC *channels_setup_find(const char *channel, IRC_SERVER_REC *server); + +#endif diff --git a/src/irc/core/channels.c b/src/irc/core/channels.c new file mode 100644 index 00000000..7f835af3 --- /dev/null +++ b/src/irc/core/channels.c @@ -0,0 +1,231 @@ +/* + channels.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "modules.h" +#include "misc.h" + +#include "bans.h" +#include "channels.h" +#include "channel-events.h" +#include "channels-query.h" +#include "channels-setup.h" +#include "irc.h" +#include "modes.h" +#include "levels.h" +#include "mode-lists.h" +#include "massjoin.h" +#include "nicklist.h" + +GSList *channels; /* List of all channels */ + +CHANNEL_REC *channel_create(IRC_SERVER_REC *server, const char *channel, int automatic) +{ + CHANNEL_REC *rec; + + g_return_val_if_fail(channel != NULL, NULL); + + rec = g_new0(CHANNEL_REC, 1); + channels = g_slist_append(channels, rec); + if (server != NULL) + server->channels = g_slist_append(server->channels, rec); + + MODULE_DATA_INIT(rec); + rec->type = module_get_uniq_id("IRC", WI_IRC_CHANNEL); + rec->name = g_strdup(channel); + rec->server = server; + rec->createtime = time(NULL); + + if (*channel == '+') + rec->no_modes = TRUE; + + signal_emit("channel created", 2, rec, GINT_TO_POINTER(automatic)); + + return rec; +} + +void channel_destroy(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + if (channel->destroying) return; + channel->destroying = TRUE; + + channels = g_slist_remove(channels, channel); + if (channel->server != NULL) + channel->server->channels = g_slist_remove(channel->server->channels, channel); + signal_emit("channel destroyed", 1, channel); + + if (channel->server != NULL && !channel->left && !channel->kicked) { + /* destroying channel record without actually left the channel yet */ + irc_send_cmdv(channel->server, "PART %s", channel->name); + } + + MODULE_DATA_DEINIT(channel); + g_free_not_null(channel->topic); + g_free_not_null(channel->key); + g_free(channel->name); + g_free(channel); +} + +static CHANNEL_REC *channel_find_server(IRC_SERVER_REC *server, const char *channel) +{ + GSList *tmp; + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (g_strcasecmp(channel, rec->name) == 0) + return rec; + + /* check after removing ABCDE from !ABCDEchannel */ + if (*channel == '!' && *rec->name == '!' && + g_strcasecmp(channel+1, rec->name+6) == 0) + return rec; + } + + return NULL; +} + +CHANNEL_REC *channel_find(IRC_SERVER_REC *server, const char *channel) +{ + g_return_val_if_fail(channel != NULL, NULL); + + if (server != NULL) + return channel_find_server(server, channel); + + /* find from any server */ + return gslist_foreach_find(servers, (FOREACH_FIND_FUNC) channel_find_server, (void *) channel); +} + + +char *channel_get_mode(CHANNEL_REC *channel) +{ + GString *mode; + char *ret; + + g_return_val_if_fail(channel != NULL, NULL); + + mode = g_string_new(NULL); + + if (channel->mode_secret) g_string_append_c(mode, 's'); + if (channel->mode_private) g_string_append_c(mode, 'p'); + if (channel->mode_moderate) g_string_append_c(mode, 'm'); + if (channel->mode_invite) g_string_append_c(mode, 'i'); + if (channel->mode_nomsgs) g_string_append_c(mode, 'n'); + if (channel->mode_optopic) g_string_append_c(mode, 't'); + if (channel->mode_anonymous) g_string_append_c(mode, 'a'); + if (channel->mode_reop) g_string_append_c(mode, 'r'); + if (channel->mode_key) g_string_append_c(mode, 'k'); + if (channel->limit > 0) g_string_append_c(mode, 'l'); + + if (channel->mode_key) g_string_sprintfa(mode, " %s", channel->key); + if (channel->limit > 0) g_string_sprintfa(mode, " %d", channel->limit); + + ret = mode->str; + g_string_free(mode, FALSE); + return ret; +} + +#define get_join_key(key) \ + (((key) == NULL || *(key) == '\0') ? "x" : (key)) + +void channels_join(IRC_SERVER_REC *server, const char *data, int automatic) +{ + CHANNEL_REC *chanrec; + GString *outchans, *outkeys; + char *params, *channels, *keys; + char **chanlist, **keylist, **tmp, **tmpkey, *channel; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &channels, &keys); + if (*channels == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanlist = g_strsplit(channels, ",", -1); + keylist = g_strsplit(keys, ",", -1); + + outchans = g_string_new(NULL); + outkeys = g_string_new(NULL); + + tmpkey = keylist; + for (tmp = chanlist; *tmp != NULL; tmp++) { + channel = ischannel(**tmp) ? g_strdup(*tmp) : + g_strdup_printf("#%s", *tmp); + + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + /* already joined this channel */ + signal_emit("gui channel open", 1, chanrec); + } else { + g_string_sprintfa(outchans, "%s,", channel); + if (*keys != '\0') + g_string_sprintfa(outkeys, "%s,", get_join_key(*tmpkey)); + + channel_create(server, channel + (channel[0] == '!' && channel[1] == '!'), automatic); + } + g_free(channel); + + if (*tmpkey != NULL) + tmpkey++; + } + + if (outchans->len > 0) { + irc_send_cmdv(server, *keys == '\0' ? "JOIN %s" : "JOIN %s %s", + outchans->str, outkeys->str); + } + + g_string_free(outchans, TRUE); + g_string_free(outkeys, TRUE); + + g_strfreev(chanlist); + g_strfreev(keylist); + + g_free(params); +} + +void channels_init(void) +{ + channel_events_init(); + channels_query_init(); + channels_setup_init(); + + bans_init(); + modes_init(); + mode_lists_init(); + massjoin_init(); + nicklist_init(); +} + +void channels_deinit(void) +{ + channel_events_deinit(); + channels_query_deinit(); + channels_setup_deinit(); + + bans_deinit(); + modes_deinit(); + mode_lists_deinit(); + massjoin_deinit(); + nicklist_deinit(); +} diff --git a/src/irc/core/channels.h b/src/irc/core/channels.h new file mode 100644 index 00000000..5cf53aa5 --- /dev/null +++ b/src/irc/core/channels.h @@ -0,0 +1,73 @@ +#ifndef __CHANNELS_H +#define __CHANNELS_H + +#include "irc-server.h" + +typedef struct { + int type; + GHashTable *module_data; + + IRC_SERVER_REC *server; + char *name; + + int new_data; + + time_t createtime; + + GHashTable *nicks; /* list of nicks */ + GSList *banlist; /* list of bans */ + GSList *ebanlist; /* list of ban exceptions */ + GSList *invitelist; /* invite list */ + + char *topic; + int limit; /* user limit */ + char *key; /* password key */ + + /* channel mode */ + int no_modes:1; /* channel doesn't support modes */ + int mode_invite:1; + int mode_secret:1; + int mode_private:1; + int mode_moderate:1; + int mode_nomsgs:1; + int mode_optopic:1; + int mode_key:1; + int mode_anonymous:1; + int mode_reop:1; + + int chanop:1; /* You're a channel operator */ + + int names_got:1; /* Received /NAMES list */ + int wholist:1; /* WHO list got */ + int synced:1; /* Channel synced - all queries done */ + + int left:1; /* You just left the channel */ + int kicked:1; /* You just got kicked */ + int destroying:1; + + time_t massjoin_start; /* Massjoin start time */ + int massjoins; /* Number of nicks waiting for massjoin signal.. */ + int last_massjoins; /* Massjoins when last checked in timeout function */ + + GSList *lastmsgs; /* List of nicks who last send message */ + GSList *lastownmsgs; /* List of nicks who last send message to you */ +} CHANNEL_REC; + +extern GSList *channels; + +void channels_init(void); +void channels_deinit(void); + +/* Create new channel record */ +CHANNEL_REC *channel_create(IRC_SERVER_REC *server, const char *channel, int automatic); +void channel_destroy(CHANNEL_REC *channel); + +/* find channel by name, if `server' is NULL, search from all servers */ +CHANNEL_REC *channel_find(IRC_SERVER_REC *server, const char *channel); + +char *channel_get_mode(CHANNEL_REC *channel); + +/* Join to channels. `data' contains channels and channel keys */ +void channels_join(IRC_SERVER_REC *server, const char *data, int automatic); + +#endif diff --git a/src/irc/core/ctcp.c b/src/irc/core/ctcp.c new file mode 100644 index 00000000..967c22a3 --- /dev/null +++ b/src/irc/core/ctcp.c @@ -0,0 +1,193 @@ +/* + ctcp.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "levels.h" +#include "special-vars.h" +#include "settings.h" +#include "irssi-version.h" + +#include "irc.h" +#include "irc-server.h" +#include "server-idle.h" +#include "ignore.h" + +static void ctcp_queue_clean(IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + + for (tmp = server->ctcpqueue; tmp != NULL; tmp = tmp->next) { + next = tmp->next; + if (!server_idle_find(server, GPOINTER_TO_INT(tmp->data))) + server->ctcpqueue = g_slist_remove(server->ctcpqueue, tmp->data); + } +} + +/* Send CTCP reply with flood protection */ +void ctcp_send_reply(IRC_SERVER_REC *server, const char *data) +{ + int tag; + + g_return_if_fail(server != NULL); + g_return_if_fail(data != NULL); + + ctcp_queue_clean(server); + + if (g_slist_length(server->ctcpqueue) < settings_get_int("max_ctcp_queue")) { + /* Add to first in idle queue */ + tag = server_idle_add_first(server, data, NULL, 0, NULL); + server->ctcpqueue = g_slist_append(server->ctcpqueue, GINT_TO_POINTER(tag)); + } +} + +/* CTCP ping */ +static void ctcp_ping(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + char *str; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + str = g_strdup_printf("NOTICE %s :\001PING %s\001", nick, data); + ctcp_send_reply(server, str); + g_free(str); +} + +/* CTCP version */ +static void ctcp_version(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + char *str, *reply; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + reply = parse_special_string(settings_get_str("ctcp_version_reply"), server, NULL, "", NULL); + str = g_strdup_printf("NOTICE %s :\001VERSION %s\001", nick, reply); + ctcp_send_reply(server, str); + g_free(str); + g_free(reply); +} + +static void ctcp_msg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + char *args, *str; + + if (ignore_check(server, nick, addr, target, data, MSGLEVEL_CTCPS)) + return; + + str = g_strconcat("ctcp msg ", data, NULL); + args = strchr(str+9, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + g_strdown(str+9); + if (!signal_emit(str, 5, args, server, nick, addr, target)) + signal_emit("default ctcp msg", 5, data, server, nick, addr, target); + g_free(str); +} + +static void ctcp_reply(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + char *args, *str; + + if (ignore_check(server, nick, addr, target, data, MSGLEVEL_CTCPS)) + return; + + str = g_strconcat("ctcp reply ", data, NULL); + args = strchr(str+11, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + g_strdown(str+11); + if (!signal_emit(str, 5, args, server, nick, addr, target)) + signal_emit("default ctcp reply", 5, data, server, nick, addr, target); + g_free(str); +} + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *target, *msg, *ptr; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &target, &msg); + + /* handle only ctcp messages.. */ + if (*msg == 1) { + /* remove the later \001 */ + ptr = strrchr(++msg, 1); + if (ptr != NULL) *ptr = '\0'; + + signal_emit("ctcp msg", 5, msg, server, nick, addr, target); + } + + g_free(params); +} + +static void event_notice(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *target, *ptr, *msg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &target, &msg); /* Channel or nick name */ + + /* handle only ctcp replies */ + if (*msg == 1) { + ptr = strrchr(++msg, 1); + if (ptr != NULL) *ptr = '\0'; + + signal_emit("ctcp reply", 5, msg, server, nick, addr, target); + } + + g_free(params); +} + +static void ctcp_deinit_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_slist_free(server->ctcpqueue); +} + +void ctcp_init(void) +{ + settings_add_str("misc", "ctcp_version_reply", PACKAGE" v$J - running on $sysname $sysrelease"); + settings_add_int("flood", "max_ctcp_queue", 5); + + signal_add("server disconnected", (SIGNAL_FUNC) ctcp_deinit_server); + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("event notice", (SIGNAL_FUNC) event_notice); + signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_add("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping); + signal_add("ctcp msg version", (SIGNAL_FUNC) ctcp_version); +} + +void ctcp_deinit(void) +{ + signal_remove("server disconnected", (SIGNAL_FUNC) ctcp_deinit_server); + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event notice", (SIGNAL_FUNC) event_notice); + signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_remove("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping); + signal_remove("ctcp msg version", (SIGNAL_FUNC) ctcp_version); +} diff --git a/src/irc/core/ctcp.h b/src/irc/core/ctcp.h new file mode 100644 index 00000000..cb326228 --- /dev/null +++ b/src/irc/core/ctcp.h @@ -0,0 +1,10 @@ +#ifndef __CTCP_H +#define __CTCP_H + +void ctcp_init(void); +void ctcp_deinit(void); + +/* Send CTCP reply with flood protection */ +void ctcp_send_reply(SERVER_REC *server, gchar *data); + +#endif diff --git a/src/irc/core/ignore.c b/src/irc/core/ignore.c new file mode 100644 index 00000000..ab817ae1 --- /dev/null +++ b/src/irc/core/ignore.c @@ -0,0 +1,296 @@ +/* + ignore.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "levels.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "irc.h" +#include "masks.h" +#include "irc-server.h" + +#include "ignore.h" + +GSList *ignores; + +int ignore_check(IRC_SERVER_REC *server, const char *nick, const char *host, + const char *channel, const char *text, int level) +{ + GSList *tmp; + int ok, mask_len, patt_len; + int best_mask, best_patt, best_ignore; + + g_return_val_if_fail(server != NULL, 0); + + best_mask = 0; best_patt = 0; best_ignore = FALSE; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if ((level & (rec->level|rec->except_level)) == 0) + continue; + + /* server */ + if (rec->servertag != NULL && g_strcasecmp(server->tag, rec->servertag) != 0) + continue; + + /* nick mask */ + mask_len = 0; + if (rec->mask != NULL) { + if (nick == NULL) + continue; + + mask_len = strlen(rec->mask); + if (mask_len <= best_mask) continue; + + ok = ((host == NULL || *host == '\0')) ? + match_wildcards(rec->mask, nick) : + irc_mask_match_address(rec->mask, nick, host); + if (!ok) continue; + } + + /* channel list */ + if (rec->channels != NULL) { + if (channel == NULL || !ischannel(*channel)) + continue; + if (strarray_find(rec->channels, channel) == -1) + continue; + } + + /* pattern */ + patt_len = 0; + if (rec->pattern != NULL) { + if (!mask_len && !best_mask) { + patt_len = strlen(rec->pattern); + if (patt_len <= best_patt) continue; + } + + ok = rec->regexp ? regexp_match(text, rec->pattern) : + rec->fullword ? stristr_full(text, rec->pattern) != NULL : + stristr(text, rec->pattern) != NULL; + if (!ok) continue; + } + + if (mask_len || best_mask) + best_mask = mask_len; + else if (patt_len) + best_patt = patt_len; + + best_ignore = (rec->level & level) != 0; + } + + return best_ignore; +} + +IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels) +{ + GSList *tmp; + char **chan; + int ignore_servertag; + + if (mask != NULL && *mask == '\0') mask = NULL; + + ignore_servertag = servertag != NULL && strcmp(servertag, "*") == 0; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (!ignore_servertag) { + if ((servertag == NULL && rec->servertag != NULL) || + (servertag != NULL && rec->servertag == NULL)) + continue; + + if (servertag != NULL && g_strcasecmp(servertag, rec->servertag) != 0) + continue; + } + + if ((rec->mask == NULL && mask != NULL) || + (rec->mask != NULL && mask == NULL)) continue; + + if (rec->mask != NULL && g_strcasecmp(rec->mask, mask) != 0) + continue; + + if ((channels == NULL && rec->channels == NULL)) + return rec; /* no channels - ok */ + + if (channels != NULL && strcmp(*channels, "*") == 0) + return rec; /* ignore channels */ + + if (channels == NULL || rec->channels == NULL) + continue; /* other doesn't have channels */ + + if (strarray_length(channels) != strarray_length(rec->channels)) + continue; /* different amount of channels */ + + /* check that channels match */ + for (chan = channels; *chan != NULL; chan++) { + if (strarray_find(rec->channels, *chan) == -1) + break; + } + + if (*chan == NULL) + return rec; /* channels ok */ + } + + return NULL; +} + +static void ignore_set_config(IGNORE_REC *rec) +{ + CONFIG_NODE *node; + char *levelstr; + + if (rec->level == 0 && rec->except_level == 0) + return; + + node = iconfig_node_traverse("(ignores", TRUE); + node = config_node_section(node, NULL, NODE_TYPE_BLOCK); + + if (rec->mask != NULL) config_node_set_str(node, "mask", rec->mask); + if (rec->level) { + levelstr = bits2level(rec->level); + config_node_set_str(node, "level", levelstr); + g_free(levelstr); + } + if (rec->except_level) { + levelstr = bits2level(rec->except_level); + config_node_set_str(node, "except_level", levelstr); + g_free(levelstr); + } + config_node_set_str(node, "pattern", rec->pattern); + if (rec->regexp) config_node_set_bool(node, "regexp", TRUE); + if (rec->fullword) config_node_set_bool(node, "fullword", TRUE); + + if (rec->channels != NULL && *rec->channels != NULL) { + node = config_node_section(node, "channels", NODE_TYPE_LIST); + config_node_add_list(node, rec->channels); + } +} + +static int ignore_index(IGNORE_REC *find) +{ + GSList *tmp; + int index; + + index = 0; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (rec->servertag != NULL) + continue; + + if (rec == find) + return index; + index++; + } + + return -1; +} + +static void ignore_remove_config(IGNORE_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("ignores", FALSE); + if (node != NULL) config_node_list_remove(node, ignore_index(rec)); +} + +void ignore_add_rec(IGNORE_REC *rec) +{ + ignores = g_slist_append(ignores, rec); + ignore_set_config(rec); +} + +static void ignore_destroy(IGNORE_REC *rec) +{ + ignores = g_slist_remove(ignores, rec); + + if (rec->channels != NULL) g_strfreev(rec->channels); + g_free_not_null(rec->mask); + g_free_not_null(rec->servertag); + g_free_not_null(rec->pattern); + g_free(rec); +} + +void ignore_update_rec(IGNORE_REC *rec) +{ + if (rec->level == 0 && rec->except_level == 0) { + /* unignored everything */ + ignore_remove_config(rec); + ignore_destroy(rec); + } else { + /* unignore just some levels.. */ + ignore_remove_config(rec); + ignores = g_slist_remove(ignores, rec); + + ignores = g_slist_append(ignores, rec); + ignore_set_config(rec); + } +} + +static void read_ignores(void) +{ + IGNORE_REC *rec; + CONFIG_NODE *node; + GSList *tmp; + + while (ignores != NULL) + ignore_destroy(ignores->data); + + node = iconfig_node_traverse("ignores", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + rec = g_new0(IGNORE_REC, 1); + ignores = g_slist_append(ignores, rec); + + rec->mask = g_strdup(config_node_get_str(node, "mask", NULL)); + rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL)); + rec->level = level2bits(config_node_get_str(node, "level", 0)); + rec->except_level = level2bits(config_node_get_str(node, "except_level", 0)); + rec->regexp = config_node_get_bool(node, "regexp", FALSE); + rec->fullword = config_node_get_bool(node, "fullword", FALSE); + + node = config_node_section(node, "channels", -1); + if (node != NULL) rec->channels = config_node_get_list(node); + } +} + +void ignore_init(void) +{ + ignores = NULL; + + read_ignores(); + signal_add("setup reread", (SIGNAL_FUNC) read_ignores); +} + +void ignore_deinit(void) +{ + while (ignores != NULL) + ignore_destroy(ignores->data); + + signal_remove("setup reread", (SIGNAL_FUNC) read_ignores); +} diff --git a/src/irc/core/ignore.h b/src/irc/core/ignore.h new file mode 100644 index 00000000..17e591d4 --- /dev/null +++ b/src/irc/core/ignore.h @@ -0,0 +1,30 @@ +#ifndef __IGNORE_H +#define __IGNORE_H + +typedef struct { + char *mask; /* nick mask */ + char *servertag; /* this is for autoignoring */ + char **channels; /* ignore only in these channels */ + char *pattern; /* text body must match this pattern */ + + int level; /* ignore these levels */ + int except_level; /* don't ignore these levels */ + + int regexp:1; + int fullword:1; +} IGNORE_REC; + +extern GSList *ignores; + +int ignore_check(IRC_SERVER_REC *server, const char *nick, const char *host, + const char *channel, const char *text, int level); + +IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels); + +void ignore_add_rec(IGNORE_REC *rec); +void ignore_update_rec(IGNORE_REC *rec); + +void ignore_init(void); +void ignore_deinit(void); + +#endif diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c new file mode 100644 index 00000000..8fcd4259 --- /dev/null +++ b/src/irc/core/irc-commands.c @@ -0,0 +1,878 @@ +/* + irc-commands.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "commands.h" +#include "misc.h" +#include "special-vars.h" +#include "settings.h" +#include "common-setup.h" + +#include "bans.h" +#include "channels.h" +#include "irc-server.h" +#include "irc.h" +#include "nicklist.h" +#include "server-redirect.h" +#include "server-setup.h" + +typedef struct { + CHANNEL_REC *channel; + char *ban; + int timeleft; +} KNOCKOUT_REC; + +static GString *tmpstr; +static int knockout_tag; + +static IRC_SERVER_REC *connect_server(const char *data) +{ + IRC_SERVER_CONNECT_REC *conn; + IRC_SERVER_REC *server; + char *params, *addr, *portstr, *password, *nick; + int port; + + g_return_val_if_fail(data != NULL, NULL); + + params = cmd_get_params(data, 4, &addr, &portstr, &password, &nick); + if (*addr == '\0') return NULL; + + if (strcmp(password, "-") == 0) + *password = '\0'; + + port = 6667; + if (*portstr != '\0') + sscanf(portstr, "%d", &port); + + /* connect to server */ + conn = irc_server_create_conn(addr, port, password, nick); + server = irc_server_connect(conn); + + g_free(params); + return server; +} + +static void cmd_connect(const char *data) +{ + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + connect_server(data); +} + +static void cmd_disconnect(const char *data, IRC_SERVER_REC *server) +{ + IRC_SERVER_REC *ircserver; + char *params, *tag, *msg; + + g_return_if_fail(data != NULL); + + if (g_strncasecmp(data, "RECON-", 6) == 0) + return; /* remove reconnection, handle in server-reconnect.c */ + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &tag, &msg); + + if (*tag != '\0' && strcmp(tag, "*") != 0) + server = (IRC_SERVER_REC *) server_find_tag(tag); + if (server == NULL || !irc_server_check(server)) + cmd_param_error(CMDERR_NOT_CONNECTED); + + ircserver = (IRC_SERVER_REC *) server; + if (ircserver->handle != -1 && ircserver->buffer != NULL) { + /* flush transmit queue */ + g_slist_foreach(ircserver->cmdqueue, (GFunc) g_free, NULL); + g_slist_free(ircserver->cmdqueue); + ircserver->cmdqueue = NULL; + ircserver->cmdcount = 0; + + /* then send quit message */ + if (*msg == '\0') msg = (char *) settings_get_str("default_quit_message"); + irc_send_cmdv(ircserver, "QUIT :%s", msg); + } + g_free(params); + + server_disconnect((SERVER_REC *) server); +} + +static void cmd_server(const char *data, IRC_SERVER_REC *server) +{ + char *channels, *away_reason, *usermode, *ircnet; + + g_return_if_fail(data != NULL); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*data == '+' || server == NULL) { + channels = away_reason = usermode = ircnet = NULL; + } else { + ircnet = g_strdup(server->connrec->ircnet); + channels = irc_server_get_channels((IRC_SERVER_REC *) server); + if (*channels == '\0') + g_free_and_null(channels); + usermode = g_strdup(server->usermode); + away_reason = !server->usermode_away ? NULL : + g_strdup(server->away_reason); + cmd_disconnect("* Changing server", server); + } + + server = connect_server(data + (*data == '+' ? 1 : 0)); + if (*data == '+' || server == NULL || + (ircnet != NULL && server->connrec->ircnet != NULL && + g_strcasecmp(ircnet, server->connrec->ircnet) != 0)) { + g_free_not_null(channels); + g_free_not_null(usermode); + g_free_not_null(away_reason); + } else if (server != NULL) { + server->connrec->reconnection = TRUE; + server->connrec->channels = channels; + server->connrec->usermode = usermode; + server->connrec->away_reason = away_reason; + } + g_free_not_null(ircnet); +} + +static void cmd_quit(const char *data) +{ + GSList *tmp, *next; + const char *quitmsg; + char *str; + + g_return_if_fail(data != NULL); + + quitmsg = *data != '\0' ? data : + settings_get_str("default_quit_message"); + + /* disconnect from every server */ + for (tmp = servers; tmp != NULL; tmp = next) { + next = tmp->next; + + str = g_strdup_printf("* %s", quitmsg); + cmd_disconnect(str, tmp->data); + g_free(str); + } + + signal_emit("gui exit", 0); +} + +static void cmd_msg(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *target, *msg; + int free_ret; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target == '=') { + /* dcc msg - don't even try to handle here.. */ + g_free(params); + return; + } + + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_param_error(CMDERR_NOT_CONNECTED); + + free_ret = FALSE; + if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) + target = parse_special(&target, server, item, NULL, &free_ret, NULL); + else if (strcmp(target, "*") == 0 && + (irc_item_channel(item) || irc_item_query(item))) + target = item->name; + if (target != NULL) { + g_string_sprintf(tmpstr, "PRIVMSG %s :%s", target, msg); + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + } + + if (free_ret && target != NULL) g_free(target); + + g_free(params); +} + +static void cmd_notice(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *msg; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + g_string_sprintf(tmpstr, "NOTICE %s :%s", target, msg); + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + g_free(params); +} + +static void cmd_ctcp(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *ctcpcmd, *ctcpdata; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + g_strup(ctcpcmd); + if (*ctcpdata == '\0') + g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s\001", target, ctcpcmd); + else + g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s %s\001", target, ctcpcmd, ctcpdata); + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + g_free(params); +} + +static void cmd_nctcp(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *ctcpcmd, *ctcpdata; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + g_strup(ctcpcmd); + g_string_sprintf(tmpstr, "NOTICE %s :\001%s %s\001", target, ctcpcmd, ctcpdata); + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + g_free(params); +} + +static void cmd_join(const char *data, IRC_SERVER_REC *server) +{ + if (*data == '\0' || g_strncasecmp(data, "-invite", 7) == 0) { + if (server->last_invite != NULL) + channels_join(server, server->last_invite, FALSE); + } else + channels_join(server, data, FALSE); +} + +static void cmd_part(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channame, *msg; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg); + if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = channel_find(server, channame); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + irc_send_cmdv(server, *msg == '\0' ? "PART %s" : "PART %s %s", + channame, msg); + + g_free(params); +} + +static void cmd_kick(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channame, *nicks, *reason; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, + item, &channame, &nicks, &reason); + + if (*channame == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (!ischannel(*channame)) cmd_param_error(CMDERR_NOT_JOINED); + + g_string_sprintf(tmpstr, "KICK %s %s :%s", channame, nicks, reason); + irc_send_cmd_split(server, tmpstr->str, 3, server->max_kicks_in_cmd); + + g_free(params); +} + +static void cmd_topic(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channame, *topic; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &topic); + + irc_send_cmdv(server, *topic == '\0' ? "TOPIC %s" : "TOPIC %s :%s", + channame, topic); + + g_free(params); +} + +static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *nick, *channame; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2, &nick, &channame); + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (*channame == '\0' || strcmp(channame, "*") == 0) { + if (!irc_item_channel(item)) + cmd_param_error(CMDERR_NOT_JOINED); + + channame = item->name; + } + + irc_send_cmdv(server, "INVITE %s %s", nick, channame); + g_free(params); +} + +static void cmd_list(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *args, *str; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &str); + + if (*str == '\0' && stristr(args, "-yes") == NULL) + cmd_param_error(CMDERR_NOT_GOOD_IDEA); + + irc_send_cmdv(server, "LIST %s", str); + g_free(params); + + /* add default redirection */ + server_redirect_default((SERVER_REC *) server, "bogus command list"); +} + +static void cmd_who(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channel, *args, *rest; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &channel, &rest); + + if (strcmp(channel, "*") == 0 || *channel == '\0') { + if (!irc_item_check(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + data = item->name; + } + if (strcmp(channel, "**") == 0) { + /* ** displays all nicks.. */ + *channel = '\0'; + } + + irc_send_cmdv(server, *rest == '\0' ? "WHO %s" : "WHO %s %s", + channel, rest); + g_free(params); + + /* add default redirection */ + server_redirect_default((SERVER_REC *) server, "bogus command who"); +} + +static void cmd_names(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + g_return_if_fail(data != NULL); + + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '\0') cmd_return_error(CMDERR_NOT_GOOD_IDEA); + + if (strcmp(data, "*") == 0) { + if (!irc_item_channel(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + data = item->name; + } + + if (g_strcasecmp(data, "-YES") == 0) + irc_send_cmd(server, "NAMES"); + else + irc_send_cmdv(server, "NAMES %s", data); +} + +static void cmd_whois(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + while (*data == ' ') data++; + if (*data == '\0') data = server->nick; + + g_string_sprintf(tmpstr, "WHOIS %s", data); + irc_send_cmd_split(server, tmpstr->str, 2, server->max_whois_in_cmd); +} + +static void cmd_whowas(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + while (*data == ' ') data++; + if (*data == '\0') data = server->nick; + + irc_send_cmdv(server, "WHOWAS %s", data); +} + +static void cmd_ping(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + GTimeVal tv; + char *str; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (*data == '\0' || strcmp(data, "*") == 0) { + if (!irc_item_check(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + data = item->name; + } + + g_get_current_time(&tv); + + str = g_strdup_printf("%s PING %ld %ld", data, tv.tv_sec, tv.tv_usec); + signal_emit("command ctcp", 3, str, server, item); + g_free(str); +} + +static void server_send_away(IRC_SERVER_REC *server, const char *reason) +{ + g_free_not_null(server->away_reason); + server->away_reason = g_strdup(reason); + + irc_send_cmdv(server, "AWAY :%s", reason); +} + +static void cmd_away(const char *data, IRC_SERVER_REC *server) +{ + char *params, *args, *reason; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &reason); + + if (stristr(args, "-all") != NULL) + g_slist_foreach(servers, (GFunc) server_send_away, reason); + else + server_send_away(server, reason); + + g_free(params); +} + +static void cmd_deop(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (*data == '\0') + irc_send_cmdv(server, "MODE %s -o", server->nick); +} + +static void cmd_sconnect(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "CONNECT %s", data); +} + +static void cmd_quote(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + irc_send_cmd(server, data); +} + +static void cmd_wall_hash(gpointer key, NICK_REC *nick, GSList **nicks) +{ + if (nick->op) *nicks = g_slist_append(*nicks, nick); +} + +static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channame, *msg; + CHANNEL_REC *chanrec; + GSList *tmp, *nicks; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg); + if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = channel_find(server, channame); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + /* send notice to all ops */ + nicks = NULL; + g_hash_table_foreach(chanrec->nicks, (GHFunc) cmd_wall_hash, &nicks); + + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + irc_send_cmdv(server, "NOTICE %s :%s", rec->nick, msg); + } + g_slist_free(nicks); + + g_free(params); +} + +static void cmd_cycle(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *channame, *msg; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN, item, &channame, &msg); + if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = channel_find(server, channame); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + irc_send_cmdv(server, *msg == '\0' ? "PART %s" : "PART %s %s", + channame, msg); + irc_send_cmdv(server, chanrec->key == NULL ? "JOIN %s" : "JOIN %s %s", + channame, chanrec->key); + + g_free(params); +} + +static void cmd_kickban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *nick; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 1, &nick); + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + signal_emit("command ban", 3, nick, server, item); + signal_emit("command kick", 3, data, server, item); + g_free(params); +} + +static void knockout_destroy(IRC_SERVER_REC *server, KNOCKOUT_REC *rec) +{ + server->knockoutlist = g_slist_remove(server->knockoutlist, rec); + g_free(rec->ban); + g_free(rec); +} + +/* timeout function: knockout */ +static void knockout_timeout_server(IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + time_t t; + + g_return_if_fail(server != NULL); + + t = server->knockout_lastcheck == 0 ? 0 : + time(NULL)-server->knockout_lastcheck; + server->knockout_lastcheck = time(NULL); + + for (tmp = server->knockoutlist; tmp != NULL; tmp = next) { + KNOCKOUT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->timeleft > t) + rec->timeleft -= t; + else { + /* timeout, unban. */ + ban_remove(rec->channel, rec->ban); + knockout_destroy(server, rec); + } + } +} + +static int knockout_timeout(void) +{ + g_slist_foreach(servers, (GFunc) knockout_timeout_server, NULL); + return 1; +} + +static void cmd_knockout(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + KNOCKOUT_REC *rec; + CHANNEL_REC *channel; + char *params, *nick, *reason, *timeoutstr, *str; + int timeleft; + + g_return_if_fail(data != NULL); + if (server == NULL) cmd_return_error(CMDERR_NOT_CONNECTED); + + channel = irc_item_channel(item); + if (channel == NULL) cmd_return_error(CMDERR_NOT_JOINED); + + if (is_numeric(data, ' ')) { + /* first argument is the timeout */ + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &timeoutstr, &nick, &reason); + timeleft = atol(timeoutstr); + } else { + timeleft = 0; + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &reason); + } + + if (timeleft == 0) timeleft = settings_get_int("knockout_time"); + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + signal_emit("command ban", 3, nick, server, channel); + + str = g_strdup_printf("%s %s", nick, reason); + signal_emit("command kick", 3, str, server, channel); + g_free(str); + + /* create knockout record */ + rec = g_new(KNOCKOUT_REC, 1); + rec->timeleft = timeleft; + rec->channel = channel; + rec->ban = ban_get_mask(channel, nick); + + server->knockoutlist = g_slist_append(server->knockoutlist, rec); + + g_free(params); +} + +/* destroy all knockouts in server */ +static void sig_server_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + while (server->knockoutlist != NULL) + knockout_destroy(server, server->knockoutlist->data); +} + +/* destroy all knockouts in channel */ +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + GSList *tmp, *next; + + g_return_if_fail(channel != NULL); + if (channel->server == NULL) return; + + for (tmp = channel->server->knockoutlist; tmp != NULL; tmp = next) { + KNOCKOUT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->channel == channel) + knockout_destroy(channel->server, rec); + } +} + +static void command_self(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + irc_send_cmdv(server, *data == '\0' ? "%s" : "%s %s", current_command, data); +} + +static void command_1self(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "%s :%s", current_command, data); +} + +static void command_2self(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *text; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &text); + if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + irc_send_cmdv(server, "%s %s :%s", current_command, target, text); + g_free(params); +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + server_redirect_init((SERVER_REC *) server, "", 2, "event 318", "event 402", "event 401", + "event 301", "event 311", "event 312", "event 313", + "event 317", "event 319", NULL); + + /* gui-gnome can use server_redirect_event() in who/list commands so + we can't use "command who" or list here.. */ + server_redirect_init((SERVER_REC *) server, "bogus command who", 2, "event 401", "event 315", "event 352", NULL); + server_redirect_init((SERVER_REC *) server, "bogus command list", 1, "event 321", "event 322", "event 323", NULL); +} + +void irc_commands_init(void) +{ + tmpstr = g_string_new(NULL); + + settings_add_str("misc", "default_quit_message", "leaving"); + settings_add_int("misc", "knockout_time", 300); + + knockout_tag = g_timeout_add(KNOCKOUT_TIMECHECK, (GSourceFunc) knockout_timeout, NULL); + + signal_add("server connected", (SIGNAL_FUNC) sig_connected); + command_bind("server", NULL, (SIGNAL_FUNC) cmd_server); + command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect); + command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect); + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + command_bind("notice", NULL, (SIGNAL_FUNC) cmd_notice); + command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); + command_bind("nctcp", NULL, (SIGNAL_FUNC) cmd_nctcp); + command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit); + command_bind("join", NULL, (SIGNAL_FUNC) cmd_join); + command_bind("part", NULL, (SIGNAL_FUNC) cmd_part); + command_bind("kick", NULL, (SIGNAL_FUNC) cmd_kick); + command_bind("topic", NULL, (SIGNAL_FUNC) cmd_topic); + command_bind("invite", NULL, (SIGNAL_FUNC) cmd_invite); + command_bind("list", NULL, (SIGNAL_FUNC) cmd_list); + command_bind("who", NULL, (SIGNAL_FUNC) cmd_who); + command_bind("names", NULL, (SIGNAL_FUNC) cmd_names); + command_bind("nick", NULL, (SIGNAL_FUNC) command_self); + command_bind("note", NULL, (SIGNAL_FUNC) command_self); + command_bind("whois", NULL, (SIGNAL_FUNC) cmd_whois); + command_bind("whowas", NULL, (SIGNAL_FUNC) cmd_whowas); + command_bind("ping", NULL, (SIGNAL_FUNC) cmd_ping); + command_bind("kill", NULL, (SIGNAL_FUNC) command_2self); + command_bind("away", NULL, (SIGNAL_FUNC) cmd_away); + command_bind("ison", NULL, (SIGNAL_FUNC) command_1self); + command_bind("admin", NULL, (SIGNAL_FUNC) command_self); + command_bind("info", NULL, (SIGNAL_FUNC) command_self); + command_bind("links", NULL, (SIGNAL_FUNC) command_self); + command_bind("lusers", NULL, (SIGNAL_FUNC) command_self); + command_bind("map", NULL, (SIGNAL_FUNC) command_self); + command_bind("motd", NULL, (SIGNAL_FUNC) command_self); + command_bind("stats", NULL, (SIGNAL_FUNC) command_self); + command_bind("time", NULL, (SIGNAL_FUNC) command_self); + command_bind("trace", NULL, (SIGNAL_FUNC) command_self); + command_bind("version", NULL, (SIGNAL_FUNC) command_self); + command_bind("servlist", NULL, (SIGNAL_FUNC) command_self); + command_bind("silence", NULL, (SIGNAL_FUNC) command_self); + command_bind("sconnect", NULL, (SIGNAL_FUNC) cmd_sconnect); + command_bind("squery", NULL, (SIGNAL_FUNC) command_2self); + command_bind("deop", NULL, (SIGNAL_FUNC) cmd_deop); + command_bind("die", NULL, (SIGNAL_FUNC) command_self); + command_bind("hash", NULL, (SIGNAL_FUNC) command_self); + command_bind("oper", NULL, (SIGNAL_FUNC) command_self); + command_bind("restart", NULL, (SIGNAL_FUNC) command_self); + command_bind("rping", NULL, (SIGNAL_FUNC) command_self); + command_bind("squit", NULL, (SIGNAL_FUNC) command_2self); + command_bind("uping", NULL, (SIGNAL_FUNC) command_self); + command_bind("quote", NULL, (SIGNAL_FUNC) cmd_quote); + command_bind("wall", NULL, (SIGNAL_FUNC) cmd_wall); + command_bind("wallops", NULL, (SIGNAL_FUNC) command_1self); + command_bind("wallchops", NULL, (SIGNAL_FUNC) command_2self); + command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle); + command_bind("kickban", NULL, (SIGNAL_FUNC) cmd_kickban); + command_bind("knockout", NULL, (SIGNAL_FUNC) cmd_knockout); + + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); +} + +void irc_commands_deinit(void) +{ + g_source_remove(knockout_tag); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + command_unbind("server", (SIGNAL_FUNC) cmd_server); + command_unbind("connect", (SIGNAL_FUNC) cmd_connect); + command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); + command_unbind("notice", (SIGNAL_FUNC) cmd_notice); + command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); + command_unbind("nctcp", (SIGNAL_FUNC) cmd_nctcp); + command_unbind("quit", (SIGNAL_FUNC) cmd_quit); + command_unbind("join", (SIGNAL_FUNC) cmd_join); + command_unbind("part", (SIGNAL_FUNC) cmd_part); + command_unbind("kick", (SIGNAL_FUNC) cmd_kick); + command_unbind("topic", (SIGNAL_FUNC) cmd_topic); + command_unbind("invite", (SIGNAL_FUNC) cmd_invite); + command_unbind("list", (SIGNAL_FUNC) cmd_list); + command_unbind("who", (SIGNAL_FUNC) cmd_who); + command_unbind("names", (SIGNAL_FUNC) cmd_names); + command_unbind("nick", (SIGNAL_FUNC) command_self); + command_unbind("note", (SIGNAL_FUNC) command_self); + command_unbind("whois", (SIGNAL_FUNC) cmd_whois); + command_unbind("whowas", (SIGNAL_FUNC) cmd_whowas); + command_unbind("ping", (SIGNAL_FUNC) cmd_ping); + command_unbind("kill", (SIGNAL_FUNC) command_2self); + command_unbind("away", (SIGNAL_FUNC) cmd_away); + command_unbind("ison", (SIGNAL_FUNC) command_1self); + command_unbind("admin", (SIGNAL_FUNC) command_self); + command_unbind("info", (SIGNAL_FUNC) command_self); + command_unbind("links", (SIGNAL_FUNC) command_self); + command_unbind("lusers", (SIGNAL_FUNC) command_self); + command_unbind("map", (SIGNAL_FUNC) command_self); + command_unbind("motd", (SIGNAL_FUNC) command_self); + command_unbind("stats", (SIGNAL_FUNC) command_self); + command_unbind("time", (SIGNAL_FUNC) command_self); + command_unbind("trace", (SIGNAL_FUNC) command_self); + command_unbind("version", (SIGNAL_FUNC) command_self); + command_unbind("servlist", (SIGNAL_FUNC) command_self); + command_unbind("silence", (SIGNAL_FUNC) command_self); + command_unbind("sconnect", (SIGNAL_FUNC) cmd_sconnect); + command_unbind("squery", (SIGNAL_FUNC) command_2self); + command_unbind("deop", (SIGNAL_FUNC) cmd_deop); + command_unbind("die", (SIGNAL_FUNC) command_self); + command_unbind("hash", (SIGNAL_FUNC) command_self); + command_unbind("oper", (SIGNAL_FUNC) command_self); + command_unbind("restart", (SIGNAL_FUNC) command_self); + command_unbind("rping", (SIGNAL_FUNC) command_self); + command_unbind("squit", (SIGNAL_FUNC) command_2self); + command_unbind("uping", (SIGNAL_FUNC) command_self); + command_unbind("quote", (SIGNAL_FUNC) cmd_quote); + command_unbind("wall", (SIGNAL_FUNC) cmd_wall); + command_unbind("wallops", (SIGNAL_FUNC) command_1self); + command_unbind("wallchops", (SIGNAL_FUNC) command_2self); + command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle); + command_unbind("kickban", (SIGNAL_FUNC) cmd_kickban); + command_unbind("knockout", (SIGNAL_FUNC) cmd_knockout); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + + g_string_free(tmpstr, TRUE); +} diff --git a/src/irc/core/irc-commands.h b/src/irc/core/irc-commands.h new file mode 100644 index 00000000..f368be96 --- /dev/null +++ b/src/irc/core/irc-commands.h @@ -0,0 +1,7 @@ +#ifndef __IRC_COMMANDS_H +#define __IRC_COMMANDS_H + +void irc_commands_init(void); +void irc_commands_deinit(void); + +#endif diff --git a/src/irc/core/irc-core.c b/src/irc/core/irc-core.c new file mode 100644 index 00000000..5969e41d --- /dev/null +++ b/src/irc/core/irc-core.c @@ -0,0 +1,63 @@ +/* + irc-core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "irc-server.h" +#include "channels.h" + +#include "ctcp.h" +#include "irc-commands.h" +#include "irc-rawlog.h" +#include "irc-special-vars.h" +#include "ignore.h" +#include "irc.h" +#include "lag.h" +#include "netsplit.h" + +void irc_core_init(void) +{ + irc_servers_init(); + channels_init(); + + ctcp_init(); + irc_commands_init(); + irc_irc_init(); + lag_init(); + netsplit_init(); + ignore_init(); + irc_rawlog_init(); + irc_special_vars_init(); +} + +void irc_core_deinit(void) +{ + irc_special_vars_deinit(); + irc_rawlog_deinit(); + ignore_deinit(); + netsplit_deinit(); + lag_deinit(); + irc_irc_deinit(); + irc_commands_deinit(); + ctcp_deinit(); + + channels_deinit(); + irc_servers_deinit(); +} diff --git a/src/irc/core/irc-core.h b/src/irc/core/irc-core.h new file mode 100644 index 00000000..31b1fc26 --- /dev/null +++ b/src/irc/core/irc-core.h @@ -0,0 +1,7 @@ +#ifndef __IRC_CORE_H +#define __IRC_CORE_H + +void irc_core_init(void); +void irc_core_deinit(void); + +#endif diff --git a/src/irc/core/irc-log.c b/src/irc/core/irc-log.c new file mode 100644 index 00000000..c6dabc13 --- /dev/null +++ b/src/irc/core/irc-log.c @@ -0,0 +1,70 @@ +static void sig_log(SERVER_REC *server, const char *channel, gpointer level, const char *str) +{ + gint loglevel; + + g_return_if_fail(str != NULL); + + loglevel = GPOINTER_TO_INT(level); + if (loglevel == MSGLEVEL_NEVER || logs == NULL) return; + + /* Check if line should be saved in logs */ + log_file_write(server, channel, loglevel, str); +} + + +static void event_away(const char *data, IRC_SERVER_REC *server) +{ + LOG_REC *log; + const char *fname, *level; + + fname = settings_get_str("awaylog_file"); + level = settings_get_str("awaylog_level"); + if (*fname == '\0' || *level == '\0') return; + + log = log_file_find(fname); + if (log != NULL) { + /* awaylog already created */ + if (log->handle == -1) { + /* ..but not open, open it. */ + log_file_open(log); + } + return; + } + + log = log_create(fname, level); + if (log != NULL) log_file_open(log); +} + +static void event_unaway(const char *data, IRC_SERVER_REC *server) +{ + LOG_REC *rec; + const char *fname; + + fname = settings_get_str("awaylog_file"); + if (*fname == '\0') return; + + rec = log_file_find(fname); + if (rec == NULL || rec->handle == -1) { + /* awaylog not open */ + return; + } + + log_file_destroy(rec); +} + +void log_init(void) +{ + settings_add_str("misc", "awaylog_file", "~/.irssi/away.log"); + settings_add_str("misc", "awaylog_level", "-all +msgs +hilight"); + + signal_add("print text stripped", (SIGNAL_FUNC) sig_log); + signal_add("event 306", (SIGNAL_FUNC) event_away); + signal_add("event 305", (SIGNAL_FUNC) event_unaway); +} + +void log_deinit(void) +{ + signal_remove("print text stripped", (SIGNAL_FUNC) sig_log); + signal_remove("event 306", (SIGNAL_FUNC) event_away); + signal_remove("event 305", (SIGNAL_FUNC) event_unaway); +} diff --git a/src/irc/core/irc-rawlog.c b/src/irc/core/irc-rawlog.c new file mode 100644 index 00000000..b2fd26bc --- /dev/null +++ b/src/irc/core/irc-rawlog.c @@ -0,0 +1,77 @@ +/* + irc-rawlog.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "rawlog.h" +#include "modules.h" +#include "signals.h" +#include "misc.h" + +#include "commands.h" +#include "server.h" + +#include "settings.h" + +static void cmd_rawlog(const char *data, SERVER_REC *server, void *item) +{ + command_runsub("rawlog", data, server, item); +} + +static void cmd_rawlog_save(const char *data, SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + rawlog_save(server->rawlog, data); +} + +static void cmd_rawlog_open(const char *data, SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + rawlog_open(server->rawlog, data); +} + +static void cmd_rawlog_close(const char *data, SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + rawlog_close(server->rawlog); +} + +void irc_rawlog_init(void) +{ + command_bind("rawlog", NULL, (SIGNAL_FUNC) cmd_rawlog); + command_bind("rawlog save", NULL, (SIGNAL_FUNC) cmd_rawlog_save); + command_bind("rawlog open", NULL, (SIGNAL_FUNC) cmd_rawlog_open); + command_bind("rawlog close", NULL, (SIGNAL_FUNC) cmd_rawlog_close); +} + +void irc_rawlog_deinit(void) +{ + command_unbind("rawlog", (SIGNAL_FUNC) cmd_rawlog); + command_unbind("rawlog save", (SIGNAL_FUNC) cmd_rawlog_save); + command_unbind("rawlog open", (SIGNAL_FUNC) cmd_rawlog_open); + command_unbind("rawlog close", (SIGNAL_FUNC) cmd_rawlog_close); +} diff --git a/src/irc/core/irc-rawlog.h b/src/irc/core/irc-rawlog.h new file mode 100644 index 00000000..ebab7155 --- /dev/null +++ b/src/irc/core/irc-rawlog.h @@ -0,0 +1,7 @@ +#ifndef __IRC_RAWLOG_H +#define __IRC_RAWLOG_H + +void irc_rawlog_init(void); +void irc_rawlog_deinit(void); + +#endif diff --git a/src/irc/core/irc-server.c b/src/irc/core/irc-server.c new file mode 100644 index 00000000..a02181e6 --- /dev/null +++ b/src/irc/core/irc-server.c @@ -0,0 +1,457 @@ +/* + irc-server.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "net-nonblock.h" +#include "line-split.h" +#include "signals.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" + +#include "irc-server.h" +#include "server-idle.h" +#include "server-reconnect.h" +#include "server-setup.h" +#include "ircnet-setup.h" +#include "channels.h" +#include "modes.h" +#include "irc.h" +#include "query.h" + +#include "settings.h" + +#define DEFAULT_MAX_KICKS 1 +#define DEFAULT_MAX_MODES 3 +#define DEFAULT_MAX_WHOIS 4 +#define DEFAULT_MAX_MSGS 1 + +#define DEFAULT_USER_MODE "+i" +#define DEFAULT_CMD_QUEUE_SPEED 2200 +#define DEFAULT_CMDS_MAX_AT_ONCE 5 + +static int cmd_tag; + +void irc_server_connect_free(IRC_SERVER_CONNECT_REC *rec) +{ + g_return_if_fail(rec != NULL); + + g_free_not_null(rec->proxy); + g_free_not_null(rec->proxy_string); + g_free_not_null(rec->ircnet); + g_free_not_null(rec->password); + g_free_not_null(rec->nick); + g_free_not_null(rec->alternate_nick); + g_free_not_null(rec->username); + g_free_not_null(rec->realname); + g_free_not_null(rec->own_ip); + g_free_not_null(rec->channels); + g_free_not_null(rec->away_reason); + g_free_not_null(rec->usermode); + g_free(rec->address); + g_free(rec); +} + +static void server_init(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + if (conn->proxy_string != NULL) + irc_send_cmdv(server, conn->proxy_string, conn->address, conn->port); + + if (conn->password != NULL && *conn->password != '\0') { + /* send password */ + server->cmdcount = 0; + irc_send_cmdv(server, "PASS %s", conn->password); + } + + /* send nick */ + server->cmdcount = 0; + irc_send_cmdv(server, "NICK %s", conn->nick); + + /* send user/realname */ + server->cmdcount = 0; + irc_send_cmdv(server, "USER %s - - :%s", conn->username, conn->realname); + + server->cmdcount = 0; +} + +IRC_SERVER_REC *irc_server_connect(IRC_SERVER_CONNECT_REC *conn) +{ + IRC_SERVER_REC *server; + + g_return_val_if_fail(conn != NULL, NULL); + if (conn->address == NULL || *conn->address == '\0') return NULL; + if (conn->nick == NULL || *conn->nick == '\0') return NULL; + + server = g_new0(IRC_SERVER_REC, 1); + server->type = module_get_uniq_id("IRC SERVER", SERVER_TYPE_IRC); + + server->connrec = conn; + if (conn->port <= 0) conn->port = 6667; + if (conn->username == NULL || *conn->username == '\0') { + g_free_not_null(conn->username); + + conn->username = g_get_user_name(); + if (*conn->username == '\0') conn->username = "-"; + conn->username = g_strdup(conn->username); + } + if (conn->realname == NULL || *conn->realname == '\0') { + g_free_not_null(conn->realname); + + conn->realname = g_get_real_name(); + if (*conn->realname == '\0') conn->realname = "-"; + conn->realname = g_strdup(conn->realname); + } + + server->nick = g_strdup(conn->nick); + + server->cmd_queue_speed = conn->cmd_queue_speed > 0 ? + conn->cmd_queue_speed : settings_get_int("cmd_queue_speed"); + server->max_cmds_at_once = conn->max_cmds_at_once > 0 ? + conn->max_cmds_at_once : settings_get_int("cmds_max_at_once"); + + server->max_kicks_in_cmd = conn->max_kicks > 0 ? + conn->max_kicks : DEFAULT_MAX_KICKS; + server->max_modes_in_cmd = conn->max_modes > 0 ? + conn->max_modes : DEFAULT_MAX_MODES; + server->max_whois_in_cmd = conn->max_whois > 0 ? + conn->max_whois : DEFAULT_MAX_WHOIS; + server->max_msgs_in_cmd = conn->max_msgs > 0 ? + conn->max_msgs : DEFAULT_MAX_MSGS; + + if (!server_connect((SERVER_REC *) server)) { + irc_server_connect_free(conn); + g_free(server->nick); + g_free(server); + return NULL; + } + return server; +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + server->splits = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + + server_init(server); +} + +static int server_remove_channels(IRC_SERVER_REC *server) +{ + GSList *tmp; + int found; + + g_return_val_if_fail(server != NULL, FALSE); + + found = FALSE; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + channel->server = NULL; + channel_destroy(channel); + found = TRUE; + } + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) + query_change_server(tmp->data, NULL); + + g_slist_free(server->channels); + g_slist_free(server->queries); + + return found; +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + int chans; + + /* close all channels */ + chans = server_remove_channels(server); + + g_slist_foreach(server->cmdqueue, (GFunc) g_free, NULL); + g_slist_free(server->cmdqueue); + + if (server->handle != -1) { + if (!chans || server->connection_lost) + net_disconnect(server->handle); + else { + /* we were on some channels, try to let the server + disconnect so that our quit message is guaranteed + to get displayed */ + net_disconnect_later(server->handle); + } + server->handle = -1; + } + + irc_server_connect_free(server->connrec); + g_free_not_null(server->real_address); + g_free_not_null(server->version); + g_free_not_null(server->usermode); + g_free_not_null(server->userhost); + g_free_not_null(server->last_invite); + g_free_not_null(server->away_reason); +} + +static void sig_connect_failed(IRC_SERVER_REC *server) +{ + server_remove_channels(server); + irc_server_connect_free(server->connrec); +} + +static void server_cmd_timeout(IRC_SERVER_REC *server, GTimeVal *now) +{ + long usecs; + char *cmd; + int len, ret, add_rawlog; + + if (server->cmdcount == 0 && server->cmdqueue == NULL) + return; + + if (!server->cmd_last_split) { + usecs = get_timeval_diff(now, &server->last_cmd); + if (usecs < server->cmd_queue_speed) + return; + } + + server->cmdcount--; + if (server->cmdqueue == NULL) return; + + /* send command */ + cmd = server->cmdqueue->data; + len = strlen(cmd); + + add_rawlog = !server->cmd_last_split; + + ret = net_transmit(server->handle, cmd, len); + if (ret != len) { + /* we didn't transmit all data, try again a bit later.. */ + if (ret > 0) { + cmd = g_strdup((char *) (server->cmdqueue->data) + ret); + g_free(server->cmdqueue->data); + server->cmdqueue->data = cmd; + } + server->cmd_last_split = TRUE; + server->cmdcount++; + } else { + memcpy(&server->last_cmd, now, sizeof(GTimeVal)); + if (server->cmd_last_split) + server->cmd_last_split = FALSE; + } + + if (add_rawlog) { + /* add to rawlog without CR+LF */ + int slen; + + slen = strlen(cmd); + cmd[slen-2] = '\0'; + rawlog_output(server->rawlog, cmd); + cmd[slen-2] = '\r'; + } + + if (ret == len) { + /* remove from queue */ + g_free(cmd); + server->cmdqueue = g_slist_remove(server->cmdqueue, cmd); + } +} + +/* check every now and then if there's data to be sent in command buffer */ +static int servers_cmd_timeout(void) +{ + GTimeVal now; + + g_get_current_time(&now); + g_slist_foreach(servers, (GFunc) server_cmd_timeout, &now); + return 1; +} + +/* Return a string of all channels (and keys, if any have them) in server, + like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ +char *irc_server_get_channels(IRC_SERVER_REC *server) +{ + GSList *tmp; + GString *chans, *keys; + char *ret; + int use_keys; + + g_return_val_if_fail(server != NULL, FALSE); + + chans = g_string_new(NULL); + keys = g_string_new(NULL); + + use_keys = FALSE; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + g_string_sprintfa(chans, "%s,", channel->name); + g_string_sprintfa(keys, "%s,", channel->key == NULL ? "x" : channel->key); + if (channel->key != NULL) + use_keys = TRUE; + } + + if (chans->len > 0) { + g_string_truncate(chans, chans->len-1); + g_string_truncate(keys, keys->len-1); + if (use_keys) g_string_sprintfa(chans, " %s", keys->str); + } + + ret = chans->str; + g_string_free(chans, FALSE); + g_string_free(keys, TRUE); + + return ret; +} + +static int sig_set_user_mode(IRC_SERVER_REC *server) +{ + const char *mode; + char *newmode; + + if (g_slist_find(servers, server) == NULL) + return 0; /* got disconnected */ + + mode = settings_get_str("default_user_mode"); + newmode = modes_join(server->usermode, mode); + if (strcmp(newmode, server->usermode) != 0) + irc_send_cmdv(server, "MODE %s %s", server->nick, mode); + g_free(newmode); + return 0; +} + +static void event_connected(const char *data, IRC_SERVER_REC *server, const char *from) +{ + char *params, *nick; + const char *mode; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 1, &nick); + + if (strcmp(server->nick, nick) != 0) { + /* nick changed unexpectedly .. connected via proxy, etc. */ + g_free(server->nick); + server->nick = g_strdup(nick); + } + + if (server->real_address == NULL) { + /* set the server address */ + server->real_address = g_strdup(from); + } + + /* last welcome message found - commands can be sent to server now. */ + server->connected = 1; + + if (!server->connrec->reconnection) { + /* wait a second and then send the user mode */ + mode = settings_get_str("default_user_mode"); + if (*mode != '\0') + g_timeout_add(1000, (GSourceFunc) sig_set_user_mode, server); + } + + signal_emit("event connected", 1, server); + g_free(params); +} + +static void event_server_info(const char *data, IRC_SERVER_REC *server) +{ + char *params, *ircd_version, *usermodes, *chanmodes; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 5, NULL, NULL, &ircd_version, &usermodes, &chanmodes); + + /* check if server understands I and e channel modes */ + if (strchr(chanmodes, 'I') && strchr(chanmodes, 'e')) + server->emode_known = TRUE; + + /* save server version */ + g_free_not_null(server->version); + server->version = g_strdup(ircd_version); + + g_free(params); +} + +static void event_ping(const char *data, IRC_SERVER_REC *server) +{ + char *str; + + g_return_if_fail(data != NULL); + + str = g_strdup_printf("PONG %s", data); + irc_send_cmd_now(server, str); + g_free(str); +} + +static void event_empty(void) +{ +} + +void irc_servers_init(void) +{ + settings_add_str("misc", "default_user_mode", DEFAULT_USER_MODE); + settings_add_int("flood", "cmd_queue_speed", DEFAULT_CMD_QUEUE_SPEED); + settings_add_int("flood", "cmds_max_at_once", DEFAULT_CMDS_MAX_AT_ONCE); + + cmd_tag = g_timeout_add(500, (GSourceFunc) servers_cmd_timeout, NULL); + + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_last("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_add("event 001", (SIGNAL_FUNC) event_connected); + signal_add("event 004", (SIGNAL_FUNC) event_server_info); + signal_add("event ping", (SIGNAL_FUNC) event_ping); + signal_add("event empty", (SIGNAL_FUNC) event_empty); + + servers_setup_init(); + ircnets_setup_init(); + servers_idle_init(); + servers_reconnect_init(); +} + +void irc_servers_deinit(void) +{ + while (servers != NULL) + server_disconnect(servers->data); + while (lookup_servers != NULL) + server_disconnect(lookup_servers->data); + + g_source_remove(cmd_tag); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_remove("event 001", (SIGNAL_FUNC) event_connected); + signal_remove("event 004", (SIGNAL_FUNC) event_server_info); + signal_remove("event ping", (SIGNAL_FUNC) event_ping); + signal_remove("event empty", (SIGNAL_FUNC) event_empty); + + servers_setup_deinit(); + ircnets_setup_deinit(); + servers_idle_deinit(); + servers_reconnect_deinit(); +} diff --git a/src/irc/core/irc-server.h b/src/irc/core/irc-server.h new file mode 100644 index 00000000..21e3e73c --- /dev/null +++ b/src/irc/core/irc-server.h @@ -0,0 +1,146 @@ +#ifndef __IRC_SERVER_H +#define __IRC_SERVER_H + +#include "server.h" + +enum { + SERVER_TYPE_IRC +}; + +/* return if `server' doesn't point to IRC server record. */ +#define irc_server_check(server) \ + ((server) != NULL && module_find_id("IRC SERVER", (server)->type) != -1) + +/* all strings should be either NULL or dynamically allocated */ +/* address and nick are mandatory, rest are optional */ +typedef struct { + /* -- GENERIC SERVER_CONNECT_REC - don't change! -- */ + /* if we're connecting via proxy, or just NULLs */ + char *proxy; + int proxy_port; + char *proxy_string; + + /* server where we want to connect */ + char *address; + int port; + char *ircnet; + + IPADDR *own_ip; + + /* -- IRC specific - change if you wish -- */ + char *password; + char *nick, *alternate_nick; + char *username; + char *realname; + + int max_cmds_at_once; + int cmd_queue_speed; + int max_kicks, max_msgs, max_modes, max_whois; + + /* when reconnecting, the old server status */ + int reconnection:1; /* we're trying to reconnect */ + char *channels; + char *away_reason; + char *usermode; +} IRC_SERVER_CONNECT_REC; + +typedef struct { + /* -- GENERIC SERVER_REC - don't change! -- */ + int type; /* server type */ + + IRC_SERVER_CONNECT_REC *connrec; + time_t connect_time; /* connection time */ + + char *tag; /* tag name for addressing server */ + char *nick; /* current nick */ + + int connected:1; /* connected to server */ + int connection_lost:1; /* Connection lost unintentionally */ + + int handle; /* socket handle */ + int readtag; /* input tag */ + + /* for net_connect_nonblock() */ + int connect_pipe[2]; + int connect_tag; + int connect_pid; + + /* For deciding if event should be handled internally */ + GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */ + GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */ + GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */ + + void *rawlog; + void *buffer; /* receive buffer */ + GHashTable *module_data; + + /* -- IRC specific - change if you wish -- */ + char *real_address; /* address the irc server gives */ + char *version; /* server version - taken from 004 event */ + char *usermode; /* The whole mode string .. */ + char *userhost; /* /USERHOST <nick> - set when joined to first channel */ + char *last_invite; /* channel where you were last invited */ + char *away_reason; + int usermode_away:1; + int server_operator:1; + + int whois_coming:1; /* Mostly just to display away message right.. */ + + int emode_known:1; /* Server understands ban exceptions and invite lists */ + int no_multi_mode:1; /* Server doesn't understand MODE #chan1,#chan2,... */ + int no_multi_who:1; /* Server doesn't understand WHO #chan1,#chan2,... */ + int one_endofwho:1; /* /WHO #a,#b,.. replies only with one End of WHO message */ + + int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */ + int max_modes_in_cmd; /* max. number of mode changes in one /MODE command */ + int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */ + int max_msgs_in_cmd; /* max. number of targets in one /MSG */ + + /* Command sending queue */ + int cmdcount; /* number of commands in `cmdqueue'. Can be more than + there actually is, to make flood control remember + how many messages can be sent before starting the + flood control */ + int cmd_last_split; /* Last command wasn't sent entirely to server. + First item in `cmdqueue' should be re-sent. */ + GSList *cmdqueue; + GTimeVal last_cmd; /* last time command was sent to server */ + + int max_cmds_at_once; /* How many messages can be sent immediately before timeouting starts */ + int cmd_queue_speed; /* Timeout between sending commands */ + + GSList *idles; /* Idle queue - send these commands to server + if there's nothing else to do */ + + GSList *ctcpqueue; /* CTCP flood protection - list of tags in idle queue */ + + /* /knockout ban list */ + GSList *knockoutlist; + time_t knockout_lastcheck; + + GSList *lastmsgs; /* List of nicks who last send you msg */ + GHashTable *splits; /* For keeping track of netsplits */ + + time_t lag_sent; /* 0 or time when last lag query was sent to server */ + time_t lag_last_check; /* last time we checked lag */ + int lag; /* server lag in milliseconds */ + + GSList *channels; + GSList *queries; + + gpointer chanqueries; +} IRC_SERVER_REC; + +IRC_SERVER_REC *irc_server_connect(IRC_SERVER_CONNECT_REC *conn); + +/* Return a string of all channels (and keys, if any have them) in server, + like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ +char *irc_server_get_channels(IRC_SERVER_REC *server); + +/* INTERNAL: Free memory used by connection record */ +void irc_server_connect_free(IRC_SERVER_CONNECT_REC *rec); + +void irc_servers_init(void); +void irc_servers_deinit(void); + +#endif diff --git a/src/irc/core/irc-special-vars.c b/src/irc/core/irc-special-vars.c new file mode 100644 index 00000000..9fcb5808 --- /dev/null +++ b/src/irc/core/irc-special-vars.c @@ -0,0 +1,332 @@ +/* + irc-special-vars.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "misc.h" +#include "special-vars.h" +#include "settings.h" + +#include "irc.h" +#include "irc-server.h" +#include "channels.h" +#include "query.h" + +static char *last_privmsg_from; +static char *last_sent_msg, *last_sent_msg_body; +static char *last_join, *last_public_from; + +/* last person who sent you a MSG */ +static char *expando_lastmsg(void *server, void *item, int *free_ret) +{ + return last_privmsg_from; +} + +/* last person to whom you sent a MSG */ +static char *expando_lastmymsg(void *server, void *item, int *free_ret) +{ + return last_sent_msg; +} + +/* last person to join a channel you are on */ +static char *expando_lastjoin(void *server, void *item, int *free_ret) +{ + return last_join; +} + +/* last person to send a public message to a channel you are on */ +static char *expando_lastpublic(void *server, void *item, int *free_ret) +{ + return last_public_from; +} + +/* text of your AWAY message, if any */ +static char *expando_awaymsg(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->away_reason; +} + +/* body of last MSG you sent */ +static char *expando_lastmymsg_body(void *server, void *item, int *free_ret) +{ + return last_sent_msg_body; +} + +/* current channel */ +static char *expando_channel(void *server, void *item, int *free_ret) +{ + CHANNEL_REC *channel; + + channel = irc_item_channel(item); + return channel == NULL ? NULL : channel->name; +} + +/* current server numeric being processed */ +static char *expando_server_numeric(void *server, void *item, int *free_ret) +{ + return current_server_event == NULL || + !is_numeric(current_server_event, 0) ? NULL : + current_server_event; +} + +/* channel you were last INVITEd to */ +static char *expando_last_invite(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->last_invite; +} + +/* modes of current channel, if any */ +static char *expando_chanmode(void *server, void *item, int *free_ret) +{ + CHANNEL_REC *channel; + + channel = irc_item_channel(item); + if (channel == NULL) return NULL; + + *free_ret = TRUE; + return channel_get_mode(channel); +} + +/* current nickname */ +static char *expando_nick(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->nick; +} + +/* value of STATUS_OPER if you are an irc operator */ +static char *expando_statusoper(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL || !ircserver->server_operator ? "" : + (char *) settings_get_str("STATUS_OPER"); +} + +/* if you are a channel operator in $C, expands to a '@' */ +static char *expando_chanop(void *server, void *item, int *free_ret) +{ + CHANNEL_REC *channel; + + channel = irc_item_channel(item); + if (channel == NULL) return NULL; + + return channel->chanop ? "@" : ""; +} + +/* nickname of whomever you are QUERYing */ +static char *expando_query(void *server, void *item, int *free_ret) +{ + QUERY_REC *query; + + query = irc_item_query(item); + return query == NULL ? NULL : query->nick; +} + +/* version of current server */ +static char *expando_serverversion(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->version; +} + +/* current server name */ +static char *expando_servername(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->real_address; +} + +/* target of current input (channel or QUERY nickname) */ +static char *expando_target(void *server, void *item, int *free_ret) +{ + if (!irc_item_check(item)) + return NULL; + + return ((WI_IRC_REC *) item)->name; +} +/* your /userhost $N address (user@host) */ +static char *expando_userhost(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + const char *username; + char hostname[100]; + + /* prefer the _real_ /userhost reply */ + if (ircserver != NULL && ircserver->userhost != NULL) + return ircserver->userhost; + + /* haven't received userhost reply yet. guess something */ + *free_ret = TRUE; + if (server == NULL) + username = settings_get_str("user_name"); + else + username = ircserver->connrec->username; + + if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0') + strcpy(hostname, "??"); + return g_strconcat(username, "@", hostname, NULL);; +} + +/* value of REALNAME */ +static char *expando_realname(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->connrec->realname; +} + +/* Server tag */ +static char *expando_servertag(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->tag; +} + +/* Server ircnet */ +static char *expando_ircnet(void *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = server; + + return ircserver == NULL ? "" : ircserver->connrec->ircnet; +} + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *target, *msg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + + if (*msg != 1) { + if (!ischannel(*target)) { + g_free_not_null(last_privmsg_from); + last_privmsg_from = g_strdup(nick); + } else { + g_free_not_null(last_public_from); + last_public_from = g_strdup(nick); + } + } + + g_free(params); +} + +static void cmd_msg(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *msg; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + if (*target != '\0' && *msg != '\0' && !ischannel(*target) && isalpha(*target)) { + g_free_not_null(last_sent_msg); + g_free_not_null(last_sent_msg_body); + last_sent_msg = g_strdup(target); + last_sent_msg_body = g_strdup(msg); + } + + g_free(params); +} + +static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + g_return_if_fail(nick != NULL); + + if (g_strcasecmp(nick, server->nick) != 0) { + g_free_not_null(last_join); + last_join = g_strdup(nick); + } +} + +void irc_special_vars_init(void) +{ + settings_add_str("misc", "STATUS_OPER", "*"); + + last_privmsg_from = NULL; + last_sent_msg = NULL; last_sent_msg_body = NULL; + last_join = NULL; last_public_from = NULL; + + expando_create(",", expando_lastmsg); + expando_create(".", expando_lastmymsg); + expando_create(":", expando_lastjoin); + expando_create(";", expando_lastpublic); + expando_create("A", expando_awaymsg); + expando_create("B", expando_lastmymsg_body); + expando_create("C", expando_channel); + expando_create("H", expando_server_numeric); + expando_create("I", expando_last_invite); + expando_create("M", expando_chanmode); + expando_create("N", expando_nick); + expando_create("O", expando_statusoper); + expando_create("P", expando_chanop); + expando_create("Q", expando_query); + expando_create("R", expando_serverversion); + expando_create("S", expando_servername); + expando_create("T", expando_target); + expando_create("X", expando_userhost); + expando_create("Y", expando_realname); + expando_create("tag", expando_servertag); + expando_create("ircnet", expando_ircnet); + + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("command msg", (SIGNAL_FUNC) cmd_msg); +} + +void irc_special_vars_deinit(void) +{ + g_free_not_null(last_privmsg_from); + g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body); + g_free_not_null(last_join); g_free_not_null(last_public_from); + + expando_destroy(",", expando_lastmsg); + expando_destroy(".", expando_lastmymsg); + expando_destroy(":", expando_lastjoin); + expando_destroy(";", expando_lastpublic); + expando_destroy("A", expando_awaymsg); + expando_destroy("B", expando_lastmymsg_body); + expando_destroy("C", expando_channel); + expando_destroy("H", expando_server_numeric); + expando_destroy("I", expando_last_invite); + expando_destroy("M", expando_chanmode); + expando_destroy("N", expando_nick); + expando_destroy("O", expando_statusoper); + expando_destroy("P", expando_chanop); + expando_destroy("Q", expando_query); + expando_destroy("R", expando_serverversion); + expando_destroy("S", expando_servername); + expando_destroy("T", expando_target); + expando_destroy("X", expando_userhost); + expando_destroy("Y", expando_realname); + expando_destroy("tag", expando_servertag); + expando_destroy("ircnet", expando_ircnet); + + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("command msg", (SIGNAL_FUNC) cmd_msg); +} diff --git a/src/irc/core/irc-special-vars.h b/src/irc/core/irc-special-vars.h new file mode 100644 index 00000000..49e0f1d6 --- /dev/null +++ b/src/irc/core/irc-special-vars.h @@ -0,0 +1,7 @@ +#ifndef __IRC_SPECIAL_VARS_H +#define __IRC_SPECIAL_VARS_H + +void irc_special_vars_init(void); +void irc_special_vars_deinit(void); + +#endif diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c new file mode 100644 index 00000000..c25f32b6 --- /dev/null +++ b/src/irc/core/irc.c @@ -0,0 +1,440 @@ +/* + irc.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "network.h" +#include "line-split.h" +#include "rawlog.h" + +#include "irc.h" +#include "irc-server.h" +#include "channels.h" +#include "server-redirect.h" + +char *current_server_event; +static int signal_send_command; +static int signal_default_event; +static int signal_server_event; +static int signal_server_incoming; + +static void cmd_send(IRC_SERVER_REC *server, const char *cmd, int send_now, int immediate) +{ + char str[513], *ptr; + int len, ret; + + server->cmdcount++; + + if (send_now) + rawlog_output(server->rawlog, cmd); + + /* just check that we don't send any longer commands than 512 bytes.. */ + strncpy(str, cmd, 510); + len = strlen(cmd); + str[len++] = 13; str[len++] = 10; str[len] = '\0'; + + ptr = str; + if (send_now) { + ret = net_transmit(server->handle, str, len); + if (ret == len) { + g_get_current_time(&server->last_cmd); + return; + } + + /* we didn't transmit all data, try again a bit later.. */ + ptr += ret; + server->cmd_last_split = TRUE; + } + + /* add to queue */ + ptr = g_strdup(ptr); + if (!immediate) + server->cmdqueue = g_slist_append(server->cmdqueue, ptr); + else if (send_now) + server->cmdqueue = g_slist_prepend(server->cmdqueue, ptr); + else + server->cmdqueue = g_slist_insert(server->cmdqueue, ptr, 1); +} + +/* Send command to IRC server */ +void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd) +{ + int send_now; + + g_return_if_fail(cmd != NULL); + if (server == NULL) return; + + send_now = !server->cmd_last_split && + (server->cmdcount < server->max_cmds_at_once || + server->cmd_queue_speed <= 0); + + cmd_send(server, cmd, send_now, FALSE); +} + +/* Send command to IRC server */ +void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) +{ + va_list args; + char *str; + + va_start(args, cmd); + + str = g_strdup_vprintf(cmd, args); + irc_send_cmd(server, str); + g_free(str); + + va_end(args); +} + +/* Send command to server immediately bypassing all flood protections + and queues. */ +void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd) +{ + g_return_if_fail(cmd != NULL); + if (server == NULL) return; + + cmd_send(server, cmd, !server->cmd_last_split, TRUE); +} + +static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg) +{ + char *p; + + *pre = g_strdup(cmd); + *post = *nicks = NULL; + for (p = *pre; *p != '\0'; p++) { + if (!isspace(*p)) + continue; + + if (arg == 1) { + /* text after nicks */ + *p++ = '\0'; + while (isspace(*p)) p++; + *post = p; + break; + } + + /* find nicks */ + while (isspace(p[1])) p++; + if (--arg == 1) { + *p = '\0'; + *nicks = p+1; + } + } + + return *pre; +} + +void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, + int nickarg, int max_nicks) +{ + char *str, *pre, *post, *nicks; + char **nicklist, **tmp; + GString *nickstr; + int count; + + g_return_if_fail(server != NULL); + g_return_if_fail(cmd != NULL); + + str = split_nicks(cmd, &pre, &nicks, &post, nickarg); + + /* split the nicks */ + nickstr = g_string_new(NULL); + nicklist = g_strsplit(nicks, ",", -1); count = 0; + + tmp = nicklist; + for (;; tmp++) { + if (*tmp != NULL) { + g_string_sprintfa(nickstr, "%s,", *tmp); + if (++count < max_nicks) + continue; + } + + count = 0; + g_string_truncate(nickstr, nickstr->len-1); + irc_send_cmdv(server, post == NULL ? "%s %s" : "%s %s %s", + pre, nickstr->str, post); + g_string_truncate(nickstr, 0); + + if (*tmp == NULL || tmp[1] == NULL) + break; + } + g_strfreev(nicklist); + g_string_free(nickstr, TRUE); + + g_free(str); +} + +/* Nick can be in format "servertag/nick" - Update `nick' to + position "nick" and return "servertag" which you need to free */ +char *irc_nick_get_server(char **nick) +{ + char *ptr, *tag; + + ptr = strchr(*nick, '/'); + if (ptr == NULL) return NULL; + if (ptr == *nick) { + (*nick)++; + return NULL; + } + + tag = g_strndup(*nick, (int) (ptr-*nick)); + *nick = ptr+1; + + return tag; +} + +/* Get next parameter */ +char *event_get_param(char **data) +{ + char *pos; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + if (**data == ':') { + /* last parameter */ + pos = *data; + *data += strlen(*data); + return pos+1; + } + + pos = *data; + while (**data != '\0' && **data != ' ') (*data)++; + if (**data == ' ') *(*data)++ = '\0'; + + return pos; +} + +/* Get count parameters from data */ +char *event_get_params(const char *data, int count, ...) +{ + char **str, *tmp, *duprec, *datad; + gboolean rest; + va_list args; + + g_return_val_if_fail(data != NULL, NULL); + + va_start(args, count); + duprec = datad = g_strdup(data); + + rest = count & PARAM_FLAG_GETREST; + count = PARAM_WITHOUT_FLAGS(count); + + while (count-- > 0) { + str = (char **) va_arg(args, char **); + if (count == 0 && rest) { + /* put the rest to last parameter */ + tmp = *datad == ':' ? datad+1 : datad; + } else { + tmp = event_get_param(&datad); + } + if (str != NULL) *str = tmp; + } + va_end(args); + + return duprec; +} + +static void irc_server_event(const char *line, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + char *event, *args, *callcmd; + GSList *list; + + g_return_if_fail(line != NULL); + + /* get command.. */ + event = g_strconcat("event ", line, NULL); + args = strchr(event+6, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + while (*args == ' ') args++; + + list = server_redirect_getqueue((SERVER_REC *) server, event, args); + if (list == NULL) + callcmd = g_strdup(event); + else { + /* event is redirected somewhere else.. */ + REDIRECT_REC *rec; + + rec = list->data; + callcmd = g_strdup(rec->name); + rawlog_redirect(server->rawlog, callcmd); + server_redirect_remove_next((SERVER_REC *) server, event, list); + } + + current_server_event = event+6; + g_strdown(callcmd); + if (!signal_emit(callcmd, 4, args, server, nick, address)) + signal_emit_id(signal_default_event, 4, line, server, nick, address); + current_server_event = NULL; + + g_free(callcmd); + g_free(event); +} + +/* Read line from server */ +static int irc_receive_line(SERVER_REC *server, char **str) +{ + char tmpbuf[512]; + int recvlen, ret; + + g_return_val_if_fail(server != NULL, -1); + g_return_val_if_fail(str != NULL, -1); + + recvlen = net_receive(server->handle, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, str, (LINEBUF_REC **) &server->buffer); + if (ret == -1) { + /* connection lost */ + server->connection_lost = TRUE; + server_disconnect(server); + } + return ret; +} + +static char *irc_parse_prefix(char *line, char **nick, char **address) +{ + *nick = *address = NULL; + + if (*line != ':') + return line; + + *nick = ++line; + while (*line != '\0' && *line != ' ') { + if (*line == '!') { + *line = '\0'; + *address = line+1; + } + line++; + } + + if (*line == ' ') { + *line++ = '\0'; + while (*line == ' ') line++; + } + + return line; +} + +/* Parse command line sent by server */ +static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line) +{ + char *nick, *address; + + g_return_if_fail(server != NULL); + g_return_if_fail(line != NULL); + + line = irc_parse_prefix(line, &nick, &address); + if (*line != '\0') + signal_emit_id(signal_server_event, 4, line, server, nick, address); +} + +/* input function: handle incoming server messages */ +static void irc_parse_incoming(SERVER_REC *server) +{ + char *str; + + g_return_if_fail(server != NULL); + + while (irc_receive_line(server, &str) > 0) { + rawlog_input(server->rawlog, str); + signal_emit_id(signal_server_incoming, 2, server, str); + } +} + +static void irc_init_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + server->readtag = + g_input_add(server->handle, G_INPUT_READ, + (GInputFunction) irc_parse_incoming, server); +} + +static void irc_deinit_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (server->readtag > 0) + g_source_remove(server->readtag); +} + +#define isoptchan(a) \ + (ischannel((a)[0]) || ((a)[0] == '*' && ((a)[1] == '\0' || (a)[1] == ' '))) + +static char *irc_cmd_get_func(const char *data, int *count, va_list *vargs) +{ + WI_IRC_REC *item; + CHANNEL_REC *channel; + char *ret, *args, *chan, *p; + + if ((*count & PARAM_FLAG_OPTCHAN) == 0) + return g_strdup(data); + + *count &= ~PARAM_FLAG_OPTCHAN; + item = (WI_IRC_REC *) va_arg(*vargs, WI_IRC_REC *); + channel = irc_item_channel(item); + + /* change first argument in data to full channel name. */ + p = args = g_strdup(data); + + chan = isoptchan(args) ? cmd_get_param(&args) : NULL; + if (chan != NULL && *chan == '!') { + /* whenever trying to send something to !channel, + change it to the real joined !XXXXXchannel */ + channel = channel_find(channel->server, chan); + if (channel != NULL) chan = channel->name; + } + + if (chan == NULL || strcmp(chan, "*") == 0) { + chan = channel == NULL ? "*" : channel->name; + } + + ret = g_strconcat(chan, " ", args, NULL); + g_free(p); + return ret; +} + +void irc_irc_init(void) +{ + cmd_get_add_func(irc_cmd_get_func); + + signal_add("server event", (SIGNAL_FUNC) irc_server_event); + signal_add("server connected", (SIGNAL_FUNC) irc_init_server); + signal_add_first("server disconnected", (SIGNAL_FUNC) irc_deinit_server); + signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); + + current_server_event = NULL; + signal_send_command = module_get_uniq_id_str("signals", "send command"); + signal_default_event = module_get_uniq_id_str("signals", "default event"); + signal_server_event = module_get_uniq_id_str("signals", "server event"); + signal_server_incoming = module_get_uniq_id_str("signals", "server incoming"); +} + +void irc_irc_deinit(void) +{ + signal_remove("server event", (SIGNAL_FUNC) irc_server_event); + signal_remove("server connected", (SIGNAL_FUNC) irc_init_server); + signal_remove("server disconnected", (SIGNAL_FUNC) irc_deinit_server); + signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); + + module_uniq_destroy("IRC"); + module_uniq_destroy("IRC SERVER"); +} diff --git a/src/irc/core/irc.h b/src/irc/core/irc.h new file mode 100644 index 00000000..aa5fbc9d --- /dev/null +++ b/src/irc/core/irc.h @@ -0,0 +1,90 @@ +#ifndef __IRC_H +#define __IRC_H + +#include "modules.h" +#include "irc-server.h" + +/* From ircd 2.9.5: + none I line with ident + ^ I line with OTHER type ident + ~ I line, no ident + + i line with ident + = i line with OTHER type ident + - i line, no ident +*/ +#define ishostflag(a) ((a) == '^' || (a) == '~' || (a) == '+' || (a) == '=' || (a) == '-') +#define isnickflag(a) ((a) == '@' || (a) == '+' || (a) == '-' || (a) == '~') +#define ischannel(a) ((a) == '#' || (a) == '&' || (a) == '!' || (a) == '+') + +/* values returned by module_category() */ +enum { + WI_IRC_CHANNEL, + WI_IRC_QUERY, + WI_IRC_DCC_CHAT +}; + +/* *MUST* have the same contents as WI_ITEM_REC in same order. */ +typedef struct { + int type; + GHashTable *module_data; + + IRC_SERVER_REC *server; + char *name; + + int new_data; +} WI_IRC_REC; + +/* return TRUE if `item' is an IRC type. */ +#define irc_item_check(item) \ + (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) != -1) + +/* return `item' type, or -1 if it's not IRC type. */ +#define irc_item_get(item) \ + (item == NULL ? -1 : module_find_id("IRC", ((WI_IRC_REC *) (item))->type)) + +/* Return `item' if it's channel, NULL if it isn't. */ +#define irc_item_channel(item) \ + (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_CHANNEL ? \ + (void *) (item) : NULL) + +/* Return `item' if it's query, NULL if it isn't. */ +#define irc_item_query(item) \ + (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_QUERY ? \ + (void *) (item) : NULL) + +/* Return `item' if it's DCC chat, NULL if it isn't. */ +#define irc_item_dcc_chat(item) \ + (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_DCC_CHAT ? \ + (void *) (item) : NULL) + +extern char *current_server_event; /* current server event being processed */ + +/* Send command to IRC server */ +void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd); +void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) G_GNUC_PRINTF (2, 3);; +/* Send command to IRC server, split to multiple commands if necessary so + that command will never have more target nicks than `max_nicks'. Nicks + are separated with commas. (works with /msg, /kick, ...) */ +void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, + int nickarg, int max_nicks); +/* Send command to server immediately bypassing all flood protections + and queues. */ +void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd); + +/* Nick can be in format "servertag/nick" - Update `nick' to + position "nick" and return "servertag" which you need to free */ +char *irc_nick_get_server(char **nick); + +#include "commands.h" /* contains the generic PARAM_FLAG_xxx defines */ + +/* IRC specific: optional channel in first argument */ +#define PARAM_FLAG_OPTCHAN 0x10000000 + +/* Get count parameters from data */ +char *event_get_param(char **data); +char *event_get_params(const char *data, int count, ...); + +void irc_irc_init(void); +void irc_irc_deinit(void); + +#endif diff --git a/src/irc/core/ircnet-setup.c b/src/irc/core/ircnet-setup.c new file mode 100644 index 00000000..0e19f3a5 --- /dev/null +++ b/src/irc/core/ircnet-setup.c @@ -0,0 +1,116 @@ +/* + ircnet-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "signals.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "irc-server.h" +#include "ircnet-setup.h" + +GSList *ircnets; /* list of available ircnets */ + +/* Find the irc network by name */ +IRCNET_REC *ircnet_find(const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = ircnets; tmp != NULL; tmp = tmp->next) { + IRCNET_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +static void ircnet_destroy(IRCNET_REC *rec) +{ + ircnets = g_slist_remove(ircnets, rec); + + g_free(rec->name); + if (rec->nick != NULL) g_free(rec->nick); + if (rec->username != NULL) g_free(rec->username); + if (rec->realname != NULL) g_free(rec->realname); + g_free(rec); +} + +static IRCNET_REC *ircnet_add(CONFIG_NODE *node) +{ + IRCNET_REC *rec; + char *name, *nick, *username, *realname; + + g_return_val_if_fail(node != NULL, NULL); + + name = config_node_get_str(node, "name", NULL); + if (name == NULL) return NULL; + + nick = config_node_get_str(node, "nick", NULL); + username = config_node_get_str(node, "username", NULL); + realname = config_node_get_str(node, "realname", NULL); + + rec = g_new0(IRCNET_REC, 1); + rec->max_kicks = config_node_get_int(node, "max_kicks", 0); + rec->max_msgs = config_node_get_int(node, "max_msgs", 0); + rec->max_modes = config_node_get_int(node, "max_modes", 0); + rec->max_whois = config_node_get_int(node, "max_whois", 0); + + rec->name = g_strdup(name); + rec->nick = (nick == NULL || *nick == '\0') ? NULL : g_strdup(nick); + rec->username = (username == NULL || *username == '\0') ? NULL : g_strdup(username); + rec->realname = (realname == NULL || *realname == '\0') ? NULL : g_strdup(realname); + + ircnets = g_slist_append(ircnets, rec); + return rec; +} + +static void read_ircnets(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + while (ircnets != NULL) + ircnet_destroy(ircnets->data); + + /* read ircnets */ + node = iconfig_node_traverse("ircnets", FALSE); + if (node != NULL) { + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + ircnet_add(tmp->data); + } +} + +void ircnets_setup_init(void) +{ + signal_add("setup reread", (SIGNAL_FUNC) read_ircnets); +} + +void ircnets_setup_deinit(void) +{ + while (ircnets != NULL) + ircnet_destroy(ircnets->data); + + signal_remove("setup reread", (SIGNAL_FUNC) read_ircnets); +} diff --git a/src/irc/core/ircnet-setup.h b/src/irc/core/ircnet-setup.h new file mode 100644 index 00000000..dea530fb --- /dev/null +++ b/src/irc/core/ircnet-setup.h @@ -0,0 +1,23 @@ +#ifndef __IRCNET_SETUP_H +#define __IRCNET_SETUP_H + +typedef struct { + char *name; + + char *nick; + char *username; + char *realname; + + /* max. number of kicks/msgs/mode/whois per command */ + int max_kicks, max_msgs, max_modes, max_whois; +} IRCNET_REC; + +extern GSList *ircnets; /* list of available ircnets */ + +/* Find the irc network by name */ +IRCNET_REC *ircnet_find(const char *name); + +void ircnets_setup_init(void); +void ircnets_setup_deinit(void); + +#endif diff --git a/src/irc/core/lag.c b/src/irc/core/lag.c new file mode 100644 index 00000000..5b52fcce --- /dev/null +++ b/src/irc/core/lag.c @@ -0,0 +1,178 @@ +/* + lag.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "irc-server.h" + +typedef struct { + IRC_SERVER_REC *server; + GTimeVal time; +} LAG_REC; + +static gint timeout_tag; +static GSList *lags; + +static LAG_REC *lag_find(IRC_SERVER_REC *server) +{ + GSList *tmp; + + for (tmp = lags; tmp != NULL; tmp = tmp->next) { + LAG_REC *lag = tmp->data; + + if (lag->server == server) + return lag; + } + + return NULL; +} + +static void lag_free(LAG_REC *rec) +{ + lags = g_slist_remove(lags, rec); + g_free(rec); +} + +static void lag_get(IRC_SERVER_REC *server) +{ + LAG_REC *lag; + + g_return_if_fail(server != NULL); + + lag = g_new0(LAG_REC, 1); + lags = g_slist_append(lags, lag); + lag->server = server; + + g_get_current_time(&lag->time); + + if (server->lag_sent == 0) + server->lag_sent = time(NULL); + server->lag_last_check = time(NULL); + + /* NOTE: this will fail if there's any nick changes in buffer - + that's why this function should be called only when the buffer + is empty */ + irc_send_cmdv(server, "NOTICE %s :\001IRSSILAG %ld %ld\001", + server->nick, lag->time.tv_sec, lag->time.tv_usec); +} + +/* we use "ctcp reply" signal here, because "ctcp reply irssilag" can be + ignored with /IGNORE * CTCPS */ +static void sig_irssilag(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target) +{ + GTimeVal now, sent; + LAG_REC *lag; + + g_return_if_fail(data != NULL); + + if (strncmp(data, "IRSSILAG ", 9) != 0) + return; + data += 9; + + lag = lag_find(server); + if (lag == NULL) { + /* not expecting lag reply.. */ + return; + } + + if (g_strcasecmp(nick, server->nick) != 0) { + /* we didn't sent this - not a lag notice */ + return; + } + + /* OK, it is a lag notice. */ + server->lag_sent = 0; + + if (sscanf(data, "%ld %ld", &sent.tv_sec, &sent.tv_usec) == 2) { + g_get_current_time(&now); + server->lag = (int) get_timeval_diff(&now, &sent); + signal_emit("server lag", 1, server); + } + + lag_free(lag); +} + +static int sig_check_lag(void) +{ + GSList *tmp, *next; + time_t now; + int lag_check_time, max_lag; + + lag_check_time = settings_get_int("lag_check_time"); + max_lag = settings_get_int("lag_max_before_disconnect"); + + if (lag_check_time <= 0) + return 1; + + now = time(NULL); + for (tmp = servers; tmp != NULL; tmp = next) { + IRC_SERVER_REC *rec = tmp->data; + + next = tmp->next; + if (!irc_server_check(rec)) + continue; + + if (rec->lag_sent != 0) { + /* waiting for lag reply */ + if (max_lag > 1 && now-rec->lag_sent > max_lag) { + /* too much lag, disconnect */ + signal_emit("server lag disconnect", 1, rec); + rec->connection_lost = TRUE; + server_disconnect((SERVER_REC *) rec); + } + } + else if (rec->lag_last_check+lag_check_time < now && + rec->cmdcount == 0 && rec->connected) { + /* no commands in buffer - get the lag */ + lag_get(rec); + } + } + + return 1; +} + +static void sig_empty(void) +{ + /* don't print the "CTCP IRSSILAG reply .." text */ +} + +void lag_init(void) +{ + settings_add_int("misc", "lag_check_time", 30); + settings_add_int("misc", "lag_max_before_disconnect", 300); + + lags = NULL; + timeout_tag = g_timeout_add(1000, (GSourceFunc) sig_check_lag, NULL); + signal_add("ctcp reply", (SIGNAL_FUNC) sig_irssilag); + signal_add("ctcp reply irssilag", (SIGNAL_FUNC) sig_empty); +} + +void lag_deinit(void) +{ + g_source_remove(timeout_tag); + while (lags != NULL) + lag_free(lags->data); + signal_remove("ctcp reply", (SIGNAL_FUNC) sig_irssilag); + signal_remove("ctcp reply irssilag", (SIGNAL_FUNC) sig_empty); +} diff --git a/src/irc/core/lag.h b/src/irc/core/lag.h new file mode 100644 index 00000000..219de265 --- /dev/null +++ b/src/irc/core/lag.h @@ -0,0 +1,7 @@ +#ifndef __LAG_H +#define __LAG_H + +void lag_init(void); +void lag_deinit(void); + +#endif diff --git a/src/irc/core/masks.c b/src/irc/core/masks.c new file mode 100644 index 00000000..17e3048a --- /dev/null +++ b/src/irc/core/masks.c @@ -0,0 +1,170 @@ +/* + masks.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "misc.h" + +#include "irc.h" +#include "masks.h" + +static int check_mask(const char *mask, int *wildcards) +{ + while (*mask != '\0') { + if (*mask == '!') + return TRUE; + + if (*mask == '?' || *mask == '*') + *wildcards = TRUE; + mask++; + } + + return FALSE; +} + +int irc_mask_match(const char *mask, const char *nick, const char *user, const char *host) +{ + char *str; + int ret, wildcards; + + if (!check_mask(mask, &wildcards)) { + return wildcards ? + match_wildcards(mask, nick) : + g_strcasecmp(mask, nick) == 0; + } + + str = g_strdup_printf("%s!%s@%s", nick, user, host); + ret = match_wildcards(mask, str); + g_free(str); + + return ret; +} + +int irc_mask_match_address(const char *mask, const char *nick, const char *address) +{ + char *str; + int ret, wildcards; + + g_return_val_if_fail(address != NULL, FALSE); + + if (!check_mask(mask, &wildcards)) { + return wildcards ? + match_wildcards(mask, nick) : + g_strcasecmp(mask, nick) == 0; + } + + str = g_strdup_printf("%s!%s", nick, address); + ret = match_wildcards(mask, str); + g_free(str); + + return ret; +} + +int irc_masks_match(const char *masks, const char *nick, const char *address) +{ + char **list, **tmp, *mask; + + g_return_val_if_fail(masks != NULL, FALSE); + + mask = g_strdup_printf("%s!%s", nick, address); + list = g_strsplit(masks, " ", -1); + for (tmp = list; *tmp != NULL; tmp++) { + if (strchr(*tmp, '!') == NULL && g_strcasecmp(*tmp, nick) == 0) + break; + + if (match_wildcards(*tmp, mask)) + break; + } + g_strfreev(list); + g_free(mask); + + return *tmp != NULL; +} + +static char *get_domain_mask(char *host) +{ + char *ptr; + + if (strchr(host, '.') == NULL) { + /* no dots - toplevel domain or IPv6 address */ + ptr = strrchr(host, ':'); + if (ptr != NULL) { + /* IPv6 address, ban the last 64k addresses */ + if (ptr[1] != '\0') strcpy(ptr+1, "*"); + } + + return host; + } + + if (is_ipv4_address(host)) { + /* it's an IP address, change last digit to * */ + ptr = strrchr(host, '.'); + if (ptr != NULL && isdigit(ptr[1])) + strcpy(ptr+1, "*"); + } else { + /* if more than one dot, skip the first + (dyn123.blah.net -> *.blah.net) */ + ptr = strchr(host, '.'); + if (ptr != NULL && strchr(ptr+1, '.') != NULL) { + host = ptr-1; + host[0] = '*'; + } + } + + return host; +} + +char *irc_get_mask(const char *nick, const char *address, int flags) +{ + char *ret, *user, *host; + + /* strip -, ^ or ~ from start.. */ + user = g_strconcat("*", ishostflag(*address) ? address+1 : address, NULL); + + /* split user and host */ + host = strchr(user, '@'); + if (host == NULL) { + g_free(user); + return NULL; + } + *host++ = '\0'; + + switch (flags & (IRC_MASK_HOST|IRC_MASK_DOMAIN)) { + case IRC_MASK_HOST: + /* we already have the host */ + break; + case IRC_MASK_DOMAIN: + /* domain - *.blah.org */ + host = get_domain_mask(host); + break; + default: + /* no domain/host */ + host = "*"; + break; + } + + ret = g_strdup_printf("%s!%s@%s", + (flags & IRC_MASK_NICK) ? nick : "*", + (flags & IRC_MASK_USER) ? user : "*", + host); + g_free(user); + + return ret; +} diff --git a/src/irc/core/masks.h b/src/irc/core/masks.h new file mode 100644 index 00000000..a735dd9d --- /dev/null +++ b/src/irc/core/masks.h @@ -0,0 +1,15 @@ +#ifndef __MASKS_H +#define __MASKS_H + +#define IRC_MASK_NICK 0x01 +#define IRC_MASK_USER 0x02 +#define IRC_MASK_HOST 0x04 +#define IRC_MASK_DOMAIN 0x08 + +int irc_mask_match(const char *mask, const char *nick, const char *user, const char *host); +int irc_mask_match_address(const char *mask, const char *nick, const char *address); +int irc_masks_match(const char *masks, const char *nick, const char *address); + +char *irc_get_mask(const char *nick, const char *address, int flags); + +#endif diff --git a/src/irc/core/massjoin.c b/src/irc/core/massjoin.c new file mode 100644 index 00000000..3cd0d31a --- /dev/null +++ b/src/irc/core/massjoin.c @@ -0,0 +1,254 @@ +/* + massjoin.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "common-setup.h" + +#include "channels.h" +#include "irc.h" +#include "nicklist.h" +#include "irc-server.h" + +static int massjoin_tag; + +/* Massjoin support - really useful when trying to do things (like op/deop) + to people after netjoins. It sends + "massjoin #channel nick!user@host nick2!user@host ..." signals */ +static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + char *params, *channel, *ptr; + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + GSList *nicks, *tmp; + + g_return_if_fail(data != NULL); + + if (g_strcasecmp(nick, server->nick) == 0) { + /* You joined, no need to do anything here */ + return; + } + + params = event_get_params(data, 1, &channel); + ptr = strchr(channel, 7); /* ^G does something weird.. */ + if (ptr != NULL) *ptr = '\0'; + + /* find channel */ + chanrec = channel_find(server, channel); + g_free(params); + if (chanrec == NULL) return; + + /* add user to nicklist */ + nickrec = nicklist_insert(chanrec, nick, FALSE, FALSE, TRUE); + nickrec->host = g_strdup(address); + + if (chanrec->massjoins == 0) { + /* no nicks waiting in massjoin queue */ + chanrec->massjoin_start = time(NULL); + chanrec->last_massjoins = 0; + } + + /* Check if user is already in some other channel, + get the realname from there */ + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + if (rec->realname != NULL) { + nickrec->last_check = rec->last_check; + nickrec->realname = g_strdup(rec->realname); + nickrec->gone = rec->gone; + } + } + g_slist_free(nicks); + + chanrec->massjoins++; +} + +static void event_part(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *channel, *reason; + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + if (g_strcasecmp(nick, server->nick) == 0) { + /* you left channel, no need to do anything here */ + return; + } + + params = event_get_params(data, 2, &channel, &reason); + + /* find channel */ + chanrec = channel_find(server, channel); + if (chanrec == NULL) { + g_free(params); + return; + } + + /* remove user from nicklist */ + nickrec = nicklist_find(chanrec, nick); + if (nickrec != NULL) { + if (nickrec->send_massjoin) { + /* quick join/part after which it's useless to send + nick in massjoin */ + chanrec->massjoins--; + } + nicklist_remove(chanrec, nickrec); + } + g_free(params); +} + +static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + CHANNEL_REC *channel; + NICK_REC *nickrec; + GSList *nicks, *tmp; + + g_return_if_fail(data != NULL); + + if (g_strcasecmp(nick, server->nick) == 0) { + /* you quit, don't do anything here */ + return; + } + + /* Remove nick from all channels */ + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + nickrec = tmp->next->data; + + if (nickrec->send_massjoin) { + /* quick join/quit after which it's useless to + send nick in massjoin */ + channel->massjoins--; + } + nicklist_remove(channel, nickrec); + } + g_slist_free(nicks); +} + +static void event_kick(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel, *nick, *reason; + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, &channel, &nick, &reason); + + if (g_strcasecmp(nick, server->nick) == 0) { + /* you were kicked, no need to do anything */ + g_free(params); + return; + } + + /* Remove user from nicklist */ + chanrec = channel_find(server, channel); + nickrec = chanrec == NULL ? NULL : nicklist_find(chanrec, nick); + if (chanrec != NULL && nickrec != NULL) { + if (nickrec->send_massjoin) { + /* quick join/kick after which it's useless to + send nick in massjoin */ + chanrec->massjoins--; + } + nicklist_remove(chanrec, nickrec); + } + + g_free(params); +} + +static void massjoin_send_hash(gpointer key, NICK_REC *nick, GSList **list) +{ + if (nick->send_massjoin) { + nick->send_massjoin = FALSE; + *list = g_slist_append(*list, nick); + } +} + +/* Send channel's massjoin list signal */ +static void massjoin_send(CHANNEL_REC *channel) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(channel->nicks, (GHFunc) massjoin_send_hash, &list); + + channel->massjoins = 0; + signal_emit("massjoin", 2, channel, list); + g_slist_free(list); +} + +static void server_check_massjoins(IRC_SERVER_REC *server, time_t max) +{ + GSList *tmp; + + /* Scan all channels through for massjoins */ + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (rec->massjoins <= 0) + continue; + + if (rec->massjoin_start < max || /* We've waited long enough */ + rec->massjoins-5 < rec->last_massjoins) { /* Less than 5 joins since last check */ + /* send them */ + massjoin_send(rec); + } else { + /* Wait for some more.. */ + rec->last_massjoins = rec->massjoins; + } + } + +} + +static int sig_massjoin_timeout(void) +{ + GSList *tmp; + time_t max; + + max = time(NULL)-MAX_MASSJOIN_WAIT; + for (tmp = servers; tmp != NULL; tmp = tmp->next) + server_check_massjoins(tmp->data, max); + + return 1; +} + +void massjoin_init(void) +{ + massjoin_tag = g_timeout_add(1000, (GSourceFunc) sig_massjoin_timeout, NULL); + + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("event part", (SIGNAL_FUNC) event_part); + signal_add("event kick", (SIGNAL_FUNC) event_kick); + signal_add("event quit", (SIGNAL_FUNC) event_quit); +} + +void massjoin_deinit(void) +{ + g_source_remove(massjoin_tag); + + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event part", (SIGNAL_FUNC) event_part); + signal_remove("event kick", (SIGNAL_FUNC) event_kick); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); +} diff --git a/src/irc/core/massjoin.h b/src/irc/core/massjoin.h new file mode 100644 index 00000000..021884af --- /dev/null +++ b/src/irc/core/massjoin.h @@ -0,0 +1,7 @@ +#ifndef __MASSJOIN_H +#define __MASSJOIN_H + +void massjoin_init(void); +void massjoin_deinit(void); + +#endif diff --git a/src/irc/core/mode-lists.c b/src/irc/core/mode-lists.c new file mode 100644 index 00000000..21e7b563 --- /dev/null +++ b/src/irc/core/mode-lists.c @@ -0,0 +1,234 @@ +/* + mode-lists.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "misc.h" +#include "signals.h" + +#include "irc.h" +#include "mode-lists.h" + +static void ban_free(GSList **list, BAN_REC *rec) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(rec != NULL); + + g_free(rec->ban); + g_free_not_null(rec->setby); + g_free(rec); + + *list = g_slist_remove(*list, rec); +} + +void banlist_free(GSList *banlist) +{ + while (banlist != NULL) + ban_free(&banlist, banlist->data); +} + +BAN_REC *banlist_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time) +{ + BAN_REC *rec; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(ban != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new(BAN_REC, 1); + rec->ban = g_strdup(ban); + rec->setby = g_strdup(nick); + rec->time = time; + + channel->banlist = g_slist_append(channel->banlist, rec); + + signal_emit("ban new", 1, rec); + return rec; +} + +void banlist_remove(CHANNEL_REC *channel, const char *ban) +{ + GSList *tmp; + + g_return_if_fail(ban != NULL); + + for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) + { + BAN_REC *rec = tmp->data; + + if (g_strcasecmp(rec->ban, ban) == 0) + { + signal_emit("ban remove", 1, rec); + ban_free(&channel->banlist, rec); + break; + } + } +} + +BAN_REC *banlist_exception_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time) +{ + BAN_REC *rec; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(ban != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new(BAN_REC, 1); + rec->ban = g_strdup(ban); + rec->setby = g_strdup(nick); + rec->time = time; + + channel->ebanlist = g_slist_append(channel->ebanlist, rec); + + signal_emit("ban exception new", 1, rec); + return rec; +} + +void banlist_exception_remove(CHANNEL_REC *channel, const char *ban) +{ + GSList *tmp; + + g_return_if_fail(ban != NULL); + + for (tmp = channel->ebanlist; tmp != NULL; tmp = tmp->next) + { + BAN_REC *rec = tmp->data; + + if (g_strcasecmp(rec->ban, ban) == 0) + { + signal_emit("ban exception remove", 1, rec); + ban_free(&channel->ebanlist, rec); + break; + } + } +} + +static void invitelist_free(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + g_slist_foreach(channel->invitelist, (GFunc) g_free, NULL); + g_slist_free(channel->invitelist); +} + +void invitelist_add(CHANNEL_REC *channel, const char *mask) +{ + g_return_if_fail(channel != NULL); + g_return_if_fail(mask != NULL); + + channel->invitelist = g_slist_append(channel->invitelist, g_strdup(mask)); + + signal_emit("invitelist new", 2, channel, mask); +} + +void invitelist_remove(CHANNEL_REC *channel, const char *mask) +{ + GSList *tmp; + + g_return_if_fail(channel != NULL); + g_return_if_fail(mask != NULL); + + tmp = gslist_find_icase_string(channel->invitelist, mask); + if (tmp == NULL) return; + + signal_emit("invitelist remove", 2, channel, tmp->data); + g_free(tmp->data); + channel->invitelist = g_slist_remove(channel->invitelist, tmp->data); +} + +static void channel_destroyed(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + banlist_free(channel->banlist); + banlist_free(channel->ebanlist); + invitelist_free(channel); +} + +static void event_banlist(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *ban, *setby, *tims; + time_t tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims); + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + if (sscanf(tims, "%ld", (long *) &tim) != 1) + tim = time(NULL); + + banlist_add(chanrec, ban, setby, tim); + } + g_free(params); +} + +static void event_ebanlist(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *ban, *setby, *tims; + time_t tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims); + chanrec = channel_find(server, channel); + if (chanrec != NULL) { + if (sscanf(tims, "%ld", (long *) &tim) != 1) + tim = time(NULL); + + banlist_exception_add(chanrec, ban, setby, tim); + } + g_free(params); +} + +static void event_invite_list(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *invite; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, &invite); + chanrec = channel_find(server, channel); + + if (chanrec != NULL) + invitelist_add(chanrec, invite); + + g_free(params); +} + +void mode_lists_init(void) +{ + signal_add("channel destroyed", (SIGNAL_FUNC) channel_destroyed); + + signal_add("chanquery ban", (SIGNAL_FUNC) event_banlist); + signal_add("chanquery eban", (SIGNAL_FUNC) event_ebanlist); + signal_add("chanquery ilist", (SIGNAL_FUNC) event_invite_list); +} + +void mode_lists_deinit(void) +{ + signal_remove("channel destroyed", (SIGNAL_FUNC) channel_destroyed); + + signal_remove("chanquery ban", (SIGNAL_FUNC) event_banlist); + signal_remove("chanquery eban", (SIGNAL_FUNC) event_ebanlist); + signal_remove("chanquery ilist", (SIGNAL_FUNC) event_invite_list); +} diff --git a/src/irc/core/mode-lists.h b/src/irc/core/mode-lists.h new file mode 100644 index 00000000..473ba5be --- /dev/null +++ b/src/irc/core/mode-lists.h @@ -0,0 +1,24 @@ +#ifndef __MODE_LISTS_H +#define __MODE_LISTS_H + +#include "channels.h" + +typedef struct { + char *ban; + char *setby; + time_t time; +} BAN_REC; + +BAN_REC *banlist_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time); +void banlist_remove(CHANNEL_REC *channel, const char *ban); + +BAN_REC *banlist_exception_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time); +void banlist_exception_remove(CHANNEL_REC *channel, const char *ban); + +void invitelist_add(CHANNEL_REC *channel, const char *mask); +void invitelist_remove(CHANNEL_REC *channel, const char *mask); + +void mode_lists_init(void); +void mode_lists_deinit(void); + +#endif diff --git a/src/irc/core/modes.c b/src/irc/core/modes.c new file mode 100644 index 00000000..b73d5eb8 --- /dev/null +++ b/src/irc/core/modes.c @@ -0,0 +1,451 @@ +/* + modes.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "commands.h" +#include "signals.h" + +#include "irc.h" +#include "mode-lists.h" +#include "nicklist.h" + +/* Change nick's mode in channel */ +static void nick_mode_change(CHANNEL_REC *channel, const char *nick, const char mode, gboolean set) +{ + NICK_REC *nickrec; + + g_return_if_fail(channel != NULL); + g_return_if_fail(nick != NULL); + + nickrec = nicklist_find(channel, nick); + if (nickrec == NULL) return; /* No /names list got yet */ + + if (mode == '@') nickrec->op = set; + if (mode == '+') nickrec->voice = set; + + signal_emit("nick mode changed", 2, channel, nickrec); +} + +/* Parse channel mode string */ +void parse_channel_modes(CHANNEL_REC *channel, const char *setby, const char *mode) +{ + char *dup, *modestr, *ptr, *curmode, type; + + g_return_if_fail(channel != NULL); + g_return_if_fail(setby != NULL); + g_return_if_fail(modestr != NULL); + + type = '+'; + + dup = modestr = g_strdup(mode); + curmode = cmd_get_param(&modestr); + while (*curmode != '\0') { + switch (*curmode) { + case '+': + case '-': + type = *curmode; + break; + + case 'b': + ptr = cmd_get_param(&modestr); + if (*ptr == '\0') break; + + if (type == '+') + banlist_add(channel, ptr, setby, time(NULL)); + else + banlist_remove(channel, ptr); + break; + + case 'e': + ptr = cmd_get_param(&modestr); + if (*ptr == '\0') break; + + if (type == '+') + banlist_exception_add(channel, ptr, setby, time(NULL)); + else + banlist_exception_remove(channel, ptr); + break; + + case 'I': + ptr = cmd_get_param(&modestr); + if (*ptr == '\0') break; + + if (type == '+') + invitelist_add(channel, ptr); + else + invitelist_remove(channel, ptr); + break; + + case 'v': + ptr = cmd_get_param(&modestr); + if (*ptr != '\0') + nick_mode_change(channel, ptr, '+', type == '+'); + break; + + case 'o': + ptr = cmd_get_param(&modestr); + if (*ptr == '\0') break; + + if (strcmp(channel->server->nick, ptr) == 0) + channel->chanop = type == '+' ? TRUE : FALSE; + nick_mode_change(channel, ptr, '@', type == '+'); + break; + + case 'l': + if (type == '-') + channel->limit = 0; + else { + ptr = cmd_get_param(&modestr); + sscanf(ptr, "%d", &channel->limit); + } + signal_emit("channel mode changed", 1, channel); + break; + case 'k': + ptr = cmd_get_param(&modestr); + if (*ptr != '\0' || type == '-') { + g_free_and_null(channel->key); + if (type == '+') { + channel->key = g_strdup(ptr); + channel->mode_key = TRUE; + } + } + signal_emit("channel mode changed", 1, channel); + break; + + default: + switch (*curmode) { + case 'i': + channel->mode_invite = type == '+'; + break; + case 'm': + channel->mode_moderate = type == '+'; + break; + case 's': + channel->mode_secret = type == '+'; + break; + case 'p': + channel->mode_private = type == '+'; + break; + case 'n': + channel->mode_nomsgs = type == '+'; + break; + case 't': + channel->mode_optopic = type == '+'; + break; + case 'a': + channel->mode_anonymous = type == '+'; + break; + case 'r': + channel->mode_reop = type == '+'; + break; + } + signal_emit("channel mode changed", 1, channel); + break; + } + + curmode++; + } + g_free(dup); + + if (!channel->mode_key && channel->key != NULL) { + /* join was used with key but there's no key set + in channel modes.. */ + g_free(channel->key); + channel->key = NULL; + } +} + +static int compare_char(const void *p1, const void *p2) +{ + const char *c1 = p1, *c2 = p2; + + return *c1 < *c2 ? -1 : + (*c1 > *c2 ? 1 : 0); +} + +/* add `mode' to `old' - return newly allocated mode. */ +char *modes_join(const char *old, const char *mode) +{ + GString *newmode; + char type, *p; + + g_return_val_if_fail(mode != NULL, NULL); + + type = '+'; + newmode = g_string_new(old); + while (*mode != '\0' && *mode != ' ') { + if (*mode == '+' || *mode == '-') { + type = *mode; + } else { + p = strchr(newmode->str, *mode); + + if (type == '+' && p == NULL) + g_string_append_c(newmode, *mode); + else if (type == '-' && p != NULL) + g_string_erase(newmode, (int) (p-newmode->str), 1); + } + + mode++; + } + + qsort(newmode->str, sizeof(char), newmode->len, compare_char); + + p = newmode->str; + g_string_free(newmode, FALSE); + return p; +} + +/* Parse user mode string */ +static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr) +{ + char *newmode, *oldmode; + + g_return_if_fail(server != NULL); + g_return_if_fail(modestr != NULL); + + newmode = modes_join(server->usermode, modestr); + oldmode = server->usermode; + server->usermode = newmode; + server->server_operator = (strchr(newmode, 'o') != NULL); + + signal_emit("user mode changed", 2, server, oldmode); + g_free_not_null(oldmode); +} + +static void event_user_mode(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &mode); + parse_user_mode(server, mode); + + g_free(params); +} + +static void event_mode(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &mode); + + if (!ischannel(*channel)) { + /* user mode change */ + parse_user_mode(server, mode); + } else { + /* channel mode change */ + chanrec = channel_find(server, channel); + if (chanrec != NULL) + parse_channel_modes(chanrec, nick, mode); + } + + g_free(params); +} + +static void event_away(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + server->usermode_away = TRUE; + signal_emit("away mode changed", 1, server); +} + +static void event_unaway(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + server->usermode_away = FALSE; + g_free_and_null(server->away_reason); + signal_emit("away mode changed", 1, server); +} + +void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const char *nicks, const char *mode) +{ + GString *str; + int num, modepos; + char **nick, **nicklist; + + g_return_if_fail(server != NULL); + g_return_if_fail(channel != NULL); + g_return_if_fail(nicks != NULL); + g_return_if_fail(mode != NULL); + if (*nicks == '\0') return; + + num = modepos = 0; + str = g_string_new(NULL); + + nicklist = g_strsplit(nicks, " ", -1); + for (nick = nicklist; *nick != NULL; nick++) { + if (num == 0) + { + g_string_sprintf(str, "MODE %s %s", channel, mode); + modepos = str->len; + } else { + /* insert the mode string */ + g_string_insert(str, modepos, mode); + } + + g_string_sprintfa(str, " %s", *nick); + + if (++num == server->connrec->max_modes) { + /* max. modes / command reached, send to server */ + irc_send_cmd(server, str->str); + num = 0; + } + } + if (num > 0) irc_send_cmd(server, str->str); + + g_strfreev(nicklist); + g_string_free(str, TRUE); +} + +#define MODE_HAS_ARG(c) ((c) == 'b' || (c) == 'e' || (c) == 'I' || \ + (c) == 'v' || (c) == 'o' || (c) == 'l' || (c) == 'k') + +void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *mode) +{ + char *modestr, *curmode, type, *orig; + GString *tmode, *targs; + int count; + + g_return_if_fail(server != NULL); + g_return_if_fail(channel != NULL); + g_return_if_fail(modestr != NULL); + + tmode = g_string_new(NULL); + targs = g_string_new(NULL); + type = '+'; count = 0; + + orig = modestr = g_strdup(mode); + + curmode = cmd_get_param(&modestr); + for (; *curmode != '\0'; curmode++) { + if (*curmode == '+' || *curmode == '-') { + type = *curmode; + continue; + } + + if (count == server->connrec->max_modes && MODE_HAS_ARG(*curmode)) { + irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str); + + count = 0; + g_string_truncate(tmode, 0); + g_string_truncate(targs, 0); + } + + g_string_append_c(tmode, *curmode); + + if (MODE_HAS_ARG(*curmode)) { + char *arg; + + count++; + arg = cmd_get_param(&modestr); + if (*arg != '\0') g_string_sprintfa(targs, " %s", arg); + } + } + + if (tmode->len > 0) + irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str); + + g_string_free(tmode, TRUE); + g_string_free(targs, TRUE); + g_free(orig); +} + +static void cmd_op(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + if (!irc_item_channel(item)) return; + channel_set_singlemode(server, item->name, data, "+o"); +} + +static void cmd_deop(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + if (!irc_item_channel(item)) return; + channel_set_singlemode(server, item->name, data, "-o"); +} + +static void cmd_voice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + if (!irc_item_channel(item)) return; + channel_set_singlemode(server, item->name, data, "+v"); +} + +static void cmd_devoice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + if (!irc_item_channel(item)) return; + channel_set_singlemode(server, item->name, data, "-v"); +} + +static void cmd_mode(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + char *params, *target, *mode; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !irc_server_check(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &mode); + if (strcmp(target, "*") == 0) { + if (!irc_item_channel(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + target = item->name; + } + if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (ischannel(*target)) + channel_set_mode(server, target, mode); + else + irc_send_cmdv(server, "MODE %s %s", target, mode); + + g_free(params); +} + +void modes_init(void) +{ + signal_add("event 221", (SIGNAL_FUNC) event_user_mode); + signal_add("event 305", (SIGNAL_FUNC) event_unaway); + signal_add("event 306", (SIGNAL_FUNC) event_away); + signal_add("event mode", (SIGNAL_FUNC) event_mode); + + command_bind("op", NULL, (SIGNAL_FUNC) cmd_op); + command_bind("deop", NULL, (SIGNAL_FUNC) cmd_deop); + command_bind("voice", NULL, (SIGNAL_FUNC) cmd_voice); + command_bind("devoice", NULL, (SIGNAL_FUNC) cmd_devoice); + command_bind("mode", NULL, (SIGNAL_FUNC) cmd_mode); +} + +void modes_deinit(void) +{ + signal_remove("event 221", (SIGNAL_FUNC) event_user_mode); + signal_remove("event 305", (SIGNAL_FUNC) event_unaway); + signal_remove("event 306", (SIGNAL_FUNC) event_away); + signal_remove("event mode", (SIGNAL_FUNC) event_mode); + + command_unbind("op", (SIGNAL_FUNC) cmd_op); + command_unbind("deop", (SIGNAL_FUNC) cmd_deop); + command_unbind("voice", (SIGNAL_FUNC) cmd_voice); + command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice); + command_unbind("mode", (SIGNAL_FUNC) cmd_mode); +} diff --git a/src/irc/core/modes.h b/src/irc/core/modes.h new file mode 100644 index 00000000..10ea28d5 --- /dev/null +++ b/src/irc/core/modes.h @@ -0,0 +1,18 @@ +#ifndef __MODES_H +#define __MODES_H + +#include "server.h" +#include "channels.h" + +void modes_init(void); +void modes_deinit(void); + +/* add `mode' to `old' - return newly allocated mode. */ +char *modes_join(const char *old, const char *mode); + +void parse_channel_modes(CHANNEL_REC *channel, const char *setby, const char *modestr); + +void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const char *nicks, const char *mode); +void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *mode); + +#endif diff --git a/src/irc/core/module.h b/src/irc/core/module.h new file mode 100644 index 00000000..00599d91 --- /dev/null +++ b/src/irc/core/module.h @@ -0,0 +1,4 @@ +#include "common.h" + +#define MODULE_NAME "irc/core" + diff --git a/src/irc/core/netsplit.c b/src/irc/core/netsplit.c new file mode 100644 index 00000000..b455f740 --- /dev/null +++ b/src/irc/core/netsplit.c @@ -0,0 +1,270 @@ +/* + netsplit.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "common-setup.h" + +#include "irc-server.h" +#include "netsplit.h" + +static int split_tag; + +static NETSPLIT_REC *netsplit_add(IRC_SERVER_REC *server, const char *nick, const char *address, const char *servers) +{ + NETSPLIT_REC *rec; + NETSPLIT_CHAN_REC *splitchan; + NICK_REC *nickrec; + GSList *tmp; + char *p, *dupservers; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(address != NULL, NULL); + + dupservers = g_strdup(servers); + p = strchr(dupservers, ' '); + if (p == NULL) { + g_free(dupservers); + g_return_val_if_fail(p != NULL, NULL); + } + *p++ = '\0'; + + rec = g_new0(NETSPLIT_REC, 1); + rec->nick = g_strdup(nick); + rec->address = g_strdup(address); + rec->destroy = time(NULL)+NETSPLIT_MAX_REMEMBER; + + /* get splitted servers */ + rec->server = g_strdup(dupservers); + rec->destserver = g_strdup(p); + g_free(dupservers); + + /* copy the channel nick records.. */ + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + nickrec = nicklist_find(channel, nick); + if (nickrec == NULL) + continue; + + splitchan = g_new0(NETSPLIT_CHAN_REC, 1); + splitchan->name = g_strdup(channel->name); + memcpy(&splitchan->nick, nickrec, sizeof(NICK_REC)); + + rec->channels = g_slist_append(rec->channels, splitchan); + } + + g_hash_table_insert(server->splits, rec->nick, rec); + signal_emit("netsplit add", 1, rec); + return rec; +} + +static void netsplit_destroy(NETSPLIT_REC *rec) +{ + GSList *tmp; + + g_return_if_fail(rec != NULL); + + signal_emit("netsplit remove", 1, rec); + for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { + NETSPLIT_CHAN_REC *rec = tmp->data; + + g_free(rec->name); + g_free(rec); + } + + g_free(rec->server); + g_free(rec->destserver); + g_free(rec->nick); + g_free(rec->address); + g_free(rec); +} + +static void netsplit_destroy_hash(gpointer key, NETSPLIT_REC *rec) +{ + netsplit_destroy(rec); +} + +NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, const char *address) +{ + NETSPLIT_REC *rec; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_hash_table_lookup(server->splits, nick); + if (rec == NULL) return NULL; + + return (address == NULL || g_strcasecmp(rec->address, address) == 0) ? rec : NULL; +} + +NICK_REC *netsplit_find_channel(IRC_SERVER_REC *server, const char *nick, const char *address, const char *channel) +{ + NETSPLIT_REC *rec; + GSList *tmp; + + rec = netsplit_find(server, nick, address); + if (rec == NULL) return NULL; + + for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { + NETSPLIT_CHAN_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, channel) == 0) + return &rec->nick; + } + + return NULL; +} + +static int is_split(const char *msg) +{ + char *params, *host1, *host2, *p; + int ok; + + g_return_val_if_fail(msg != NULL, FALSE); + + /* must have only two words */ + p = strchr(msg, ' '); + if (p == NULL || strchr(p+1, ' ') != NULL) return FALSE; + + /* check that it looks ok.. */ + if (!match_wildcards("*.* *.*", msg) || + match_wildcards("*..*", msg) || strstr(msg, "))") != NULL) + return FALSE; + + /* get the two hosts */ + ok = FALSE; + params = cmd_get_params(msg, 2, &host1, &host2); + if (g_strcasecmp(host1, host2) != 0) { /* hosts can't be same.. */ + /* check that domain length is 2 or 3 */ + p = strrchr(host1, '.'); + if (p != NULL && (strlen(p+1) == 2 || strlen(p+1) == 3)) { + p = strrchr(host2, '.'); + if (p != NULL && (strlen(p+1) == 2 || strlen(p+1) == 3)) { + /* it looks just like a netsplit to me. */ + ok = TRUE; + } + } + } + g_free(params); + + return ok; +} + +static void split_set_timeout(gpointer key, NETSPLIT_REC *rec, NETSPLIT_REC *orig) +{ + if (rec == orig) { + /* original nick, destroy it in a few seconds.. */ + rec->destroy = time(NULL)+4; + } else if (g_strcasecmp(rec->server, orig->server) == 0 && + g_strcasecmp(rec->destserver, orig->destserver) == 0) { + /* same servers -> split over -> destroy old records sooner.. */ + rec->destroy = time(NULL)+60; + } +} + +static void event_join(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *address) +{ + NETSPLIT_REC *rec; + + /* check if split is over */ + rec = g_hash_table_lookup(server->splits, nick); + if (rec == NULL) return; + + if (g_strcasecmp(rec->address, address) == 0) { + /* yep, looks like it is. for same people that had the same + splitted servers set the timeout to one minute. + + .. if the user just changed server, he/she can't use the + same nick (unless the server is broken) so don't bother + checking that the nick's server matches the split. */ + g_hash_table_foreach(server->splits, (GHFunc) split_set_timeout, rec); + } else { + /* back from different address.. just destroy it. */ + g_hash_table_remove(server->splits, rec->nick); + netsplit_destroy(rec); + } +} + +static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + g_return_if_fail(data != NULL); + + if (*data == ':') data++; + if (g_strcasecmp(nick, server->nick) != 0 && is_split(data)) { + /* netsplit! */ + netsplit_add(server, nick, address, data); + } +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_hash_table_foreach(server->splits, (GHFunc) netsplit_destroy_hash, NULL); + g_hash_table_destroy(server->splits); +} + +static int split_server_check(gpointer key, NETSPLIT_REC *rec, IRC_SERVER_REC *server) +{ + /* Check if this split record is too old.. */ + if (rec->destroy > time(NULL)) + return FALSE; + + netsplit_destroy(rec); + return TRUE; +} + +static int split_check_old(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *server = tmp->data; + + g_hash_table_foreach_remove(server->splits, (GHRFunc) split_server_check, server); + } + + return 1; +} + +void netsplit_init(void) +{ + split_tag = g_timeout_add(1000, (GSourceFunc) split_check_old, NULL); + signal_add_first("event join", (SIGNAL_FUNC) event_join); + signal_add_first("event quit", (SIGNAL_FUNC) event_quit); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void netsplit_deinit(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) + sig_disconnected(tmp->data); + + g_source_remove(split_tag); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/netsplit.h b/src/irc/core/netsplit.h new file mode 100644 index 00000000..c2d221da --- /dev/null +++ b/src/irc/core/netsplit.h @@ -0,0 +1,27 @@ +#ifndef __NETSPLIT_H +#define __NETSPLIT_H + +#include "nicklist.h" + +typedef struct { + char *nick; + char *address; + char *server; + char *destserver; + GSList *channels; + + time_t destroy; +} NETSPLIT_REC; + +typedef struct { + char *name; + NICK_REC nick; +} NETSPLIT_CHAN_REC; + +void netsplit_init(void); +void netsplit_deinit(void); + +NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, const char *address); +NICK_REC *netsplit_find_channel(IRC_SERVER_REC *server, const char *nick, const char *address, const char *channel); + +#endif diff --git a/src/irc/core/nicklist.c b/src/irc/core/nicklist.c new file mode 100644 index 00000000..16305548 --- /dev/null +++ b/src/irc/core/nicklist.c @@ -0,0 +1,566 @@ +/* + nicklist.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "channels.h" +#include "irc.h" +#include "masks.h" +#include "nicklist.h" +#include "irc-server.h" + +/* Add new nick to list */ +NICK_REC *nicklist_insert(CHANNEL_REC *channel, const char *nick, int op, int voice, int send_massjoin) +{ + NICK_REC *rec; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new0(NICK_REC, 1); + + if (op) rec->op = TRUE; + if (voice) rec->voice = TRUE; + + rec->send_massjoin = send_massjoin; + rec->nick = g_strdup(nick); + rec->host = NULL; + + g_hash_table_insert(channel->nicks, rec->nick, rec); + signal_emit("nicklist new", 2, channel, rec); + return rec; +} + +static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick) +{ + signal_emit("nicklist remove", 2, channel, nick); + + g_free(nick->nick); + g_free_not_null(nick->realname); + g_free_not_null(nick->host); + g_free(nick); +} + +/* remove nick from list */ +void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick) +{ + g_return_if_fail(channel != NULL); + g_return_if_fail(nick != NULL); + + g_hash_table_remove(channel->nicks, nick->nick); + nicklist_destroy(channel, nick); +} + +static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel, const char *mask) +{ + GSList *nicks, *tmp; + NICK_REC *nick; + + nicks = nicklist_getnicks(channel); + nick = NULL; + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + nick = tmp->data; + + if (irc_mask_match_address(mask, nick->nick, nick->host == NULL ? "" : nick->host)) + break; + } + g_slist_free(nicks); + return tmp == NULL ? NULL : nick; +} + +/* Find nick record from list */ +NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *mask) +{ + NICK_REC *nickrec; + char *nick, *host; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(mask != NULL, NULL); + + nick = g_strdup(mask); + host = strchr(nick, '!'); + if (host != NULL) *host++ = '\0'; + + if (strchr(nick, '*') || strchr(nick, '?')) { + g_free(nick); + return nicklist_find_wildcards(channel, mask); + } + + nickrec = g_hash_table_lookup(channel->nicks, nick); + + if (nickrec != NULL && host != NULL && + (nickrec->host == NULL || !match_wildcards(host, nickrec->host))) { + /* hosts didn't match */ + nickrec = NULL; + } + g_free(nick); + return nickrec; +} + +static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list) +{ + *list = g_slist_append(*list, rec); +} + +/* Get list of nicks */ +GSList *nicklist_getnicks(CHANNEL_REC *channel) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list); + return list; +} + +typedef struct { + CHANNEL_REC *channel; + const char *nick; + GSList *list; +} NICKLIST_GET_SAME_REC; + +static void get_nicks_same_hash(gpointer key, NICK_REC *nick, NICKLIST_GET_SAME_REC *rec) +{ + if (g_strcasecmp(nick->nick, rec->nick) == 0) { + rec->list = g_slist_append(rec->list, rec->channel); + rec->list = g_slist_append(rec->list, nick); + } +} + +GSList *nicklist_get_same(IRC_SERVER_REC *server, const char *nick) +{ + NICKLIST_GET_SAME_REC rec; + GSList *tmp; + + rec.nick = nick; + rec.list = NULL; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + rec.channel = tmp->data; + g_hash_table_foreach(rec.channel->nicks, (GHFunc) get_nicks_same_hash, &rec); + } + return rec.list; +} + +/* nick record comparision for sort functions */ +int nicklist_compare(NICK_REC *p1, NICK_REC *p2) +{ + if (p1 == NULL) return -1; + if (p2 == NULL) return 1; + + if (p1->op && !p2->op) return -1; + if (!p1->op && p2->op) return 1; + + if (!p1->op) { + if (p1->voice && !p2->voice) return -1; + if (!p1->voice && p2->voice) return 1; + } + + return g_strcasecmp(p1->nick, p2->nick); +} + +#define isnickchar(a) \ + (isalnum(a) || (a) == '`' || (a) == '-' || (a) == '_' || \ + (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \ + (a) == '|' || (a) == '\\' || (a) == '^') + +char *nick_strip(const char *nick) +{ + char *stripped, *spos; + + g_return_val_if_fail(nick != NULL, NULL); + + spos = stripped = g_strdup(nick); + while (isnickchar(*nick)) { + if (isalnum((gint) *nick)) *spos++ = *nick; + nick++; + } + if ((unsigned char) *nick >= 128) + *spos++ = *nick; /* just add it so that nicks won't match.. */ + *spos = '\0'; + return stripped; +} + +static void event_names_list(const char *data, IRC_SERVER_REC *server) +{ + CHANNEL_REC *chanrec; + char *params, *type, *channel, *names, *ptr; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &type, &channel, &names); + + chanrec = channel_find(server, channel); + if (chanrec == NULL || chanrec->names_got) { + /* unknown channel / names list already read */ + g_free(params); + return; + } + + /* type = '=' = public, '*' = private, '@' = secret. + + This is actually pretty useless to check here, but at least we + get to know if the channel is +p or +s a few seconds before + we receive the MODE reply... */ + if (*type == '*') + chanrec->mode_private = TRUE; + else if (*type == '@') + chanrec->mode_secret = TRUE; + + while (*names != '\0') { + while (*names == ' ') names++; + ptr = names; + while (*names != '\0' && *names != ' ') names++; + if (*names != '\0') *names++ = '\0'; + + if (*ptr == '@' && strcmp(server->nick, ptr+1) == 0) + chanrec->chanop = TRUE; + + nicklist_insert(chanrec, ptr+isnickflag(*ptr), *ptr == '@', *ptr == '+', FALSE); + } + + g_free(params); +} + +static void event_end_of_names(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + CHANNEL_REC *chanrec; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &channel); + + chanrec = channel_find(server, channel); + if (chanrec != NULL && !chanrec->names_got) { + chanrec->names_got = TRUE; + signal_emit("channel query", 1, chanrec); + } + + g_free(params); +} + +static void nicklist_update_flags(IRC_SERVER_REC *server, const char *nick, int gone, int ircop) +{ + GSList *nicks, *tmp; + CHANNEL_REC *channel; + NICK_REC *rec; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + rec = tmp->next->data; + + rec->last_check = time(NULL); + + if (gone != -1 && rec->gone != gone) { + rec->gone = gone; + signal_emit("nick gone changed", 2, channel, rec); + } + + if (ircop != -1 && rec->ircop != ircop) { + rec->ircop = ircop; + signal_emit("nick ircop changed", 2, channel, rec); + } + } + g_slist_free(nicks); +} + +static void event_who(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *channel, *user, *host, *stat, *realname, *hops; + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 8, NULL, &channel, &user, &host, NULL, &nick, &stat, &realname); + + /* get hop count */ + hops = realname; + while (*realname != '\0' && *realname != ' ') realname++; + *realname++ = '\0'; + while (*realname == ' ') realname++; + + /* update host, realname, hopcount */ + chanrec = channel_find(server, channel); + nickrec = chanrec == NULL ? NULL : nicklist_find(chanrec, nick); + if (nickrec != NULL) { + if (nickrec->host == NULL) + nickrec->host = g_strdup_printf("%s@%s", user, host); + if (nickrec->realname == NULL) + nickrec->realname = g_strdup(realname); + sscanf(hops, "%d", &nickrec->hops); + } + + nicklist_update_flags(server, nick, + strchr(stat, 'G') != NULL, /* gone */ + strchr(stat, '*') != NULL); /* ircop */ + + g_free(params); +} + +static void event_whois(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *realname; + GSList *nicks, *tmp; + NICK_REC *rec; + + g_return_if_fail(data != NULL); + + server->whois_coming = TRUE; + + /* first remove the gone-flag, if user is gone it will be set later.. */ + params = event_get_params(data, 6, NULL, &nick, NULL, NULL, NULL, &realname); + + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + rec = tmp->next->data; + + if (rec->realname == NULL) + rec->realname = g_strdup(realname); + } + g_slist_free(nicks); + + /* reset gone and ircop status, we'll handle them in the following + WHOIS replies */ + nicklist_update_flags(server, nick, FALSE, FALSE); + g_free(params); +} + +static void event_whois_away(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + nicklist_update_flags(server, nick, TRUE, -1); + g_free(params); +} + +static void event_whois_ircop(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + nicklist_update_flags(server, nick, -1, TRUE); + g_free(params); +} + +static void event_end_of_whois(const char *data, IRC_SERVER_REC *server) +{ + server->whois_coming = FALSE; +} + +static void event_nick_in_use(const char *data, IRC_SERVER_REC *server) +{ + char *str; + int n; + + g_return_if_fail(data != NULL); + + if (server->connected) { + /* Already connected, no need to handle this anymore. */ + return; + } + + /* nick already in use - need to change it .. */ + if (strcmp(server->nick, server->connrec->nick) == 0) { + /* first try, so try the alternative nick.. */ + g_free(server->nick); + server->nick = g_strdup(server->connrec->alternate_nick); + } + else if (strlen(server->nick) < 9) { + /* keep adding '_' to end of nick.. */ + str = g_strdup_printf("%s_", server->nick); + g_free(server->nick); + server->nick = str; + } else { + /* nick full, keep adding number at the end */ + for (n = 8; n > 0; n--) { + if (server->nick[n] < '0' || server->nick[n] > '9') { + server->nick[n] = '1'; + break; + } + + if (server->nick[n] < '9') { + server->nick[n]++; + break; + } + server->nick[n] = '0'; + } + } + + irc_send_cmdv(server, "NICK %s", server->nick); +} + +static void event_target_unavailable(const char *data, IRC_SERVER_REC *server) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (!ischannel(*channel)) { + /* nick is unavailable. */ + event_nick_in_use(data, server); + } + + g_free(params); +} + +static void event_nick(const char *data, IRC_SERVER_REC *server, const char *orignick) +{ + CHANNEL_REC *channel; + NICK_REC *nickrec; + GSList *nicks, *tmp; + char *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 1, &nick); + + if (g_strcasecmp(orignick, server->nick) == 0) { + /* You changed your nick */ + g_free(server->connrec->nick); + g_free(server->nick); + server->connrec->nick = g_strdup(nick); + server->nick = g_strdup(nick); + signal_emit("server nick changed", 1, server); + } + + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + nickrec = tmp->next->data; + + /* remove old nick from hash table */ + g_hash_table_remove(channel->nicks, nickrec->nick); + + g_free(nickrec->nick); + nickrec->nick = g_strdup(nick); + + /* add new nick to hash table */ + g_hash_table_insert(channel->nicks, nickrec->nick, nickrec); + + signal_emit("nicklist changed", 3, channel, nickrec, orignick); + } + g_slist_free(nicks); + + g_free(params); +} + +static void event_userhost(const char *data, IRC_SERVER_REC *server) +{ + char *params, *hosts, **phosts, **pos, *ptr; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 2, NULL, &hosts); + + phosts = g_strsplit(hosts, " ", -1); + for (pos = phosts; pos != NULL; pos++) { + ptr = strchr(*pos, '='); + if (ptr == NULL) continue; + *ptr++ = '\0'; + + nicklist_update_flags(server, *pos, *ptr == '-', -1); + } + g_strfreev(phosts); + g_free(params); +} + +static void sig_usermode(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + nicklist_update_flags(server, server->nick, server->usermode_away, -1); +} + +static void sig_channel_created(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); +} + +static void nicklist_remove_hash(gpointer key, NICK_REC *nick, CHANNEL_REC *channel) +{ + nicklist_destroy(channel, nick); +} + +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + g_hash_table_foreach(channel->nicks, (GHFunc) nicklist_remove_hash, channel); + g_hash_table_destroy(channel->nicks); +} + +void nicklist_init(void) +{ + signal_add("event nick", (SIGNAL_FUNC) event_nick); + signal_add_first("event 352", (SIGNAL_FUNC) event_who); + signal_add("silent event who", (SIGNAL_FUNC) event_who); + signal_add("silent event whois", (SIGNAL_FUNC) event_whois); + signal_add_first("event 311", (SIGNAL_FUNC) event_whois); + signal_add_first("event 301", (SIGNAL_FUNC) event_whois_away); + signal_add_first("event 313", (SIGNAL_FUNC) event_whois_ircop); + signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois); + signal_add("event 353", (SIGNAL_FUNC) event_names_list); + signal_add("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_add("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_add("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_add("event 302", (SIGNAL_FUNC) event_userhost); + signal_add("userhost event", (SIGNAL_FUNC) event_userhost); + signal_add("user mode changed", (SIGNAL_FUNC) sig_usermode); + signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); +} + +void nicklist_deinit(void) +{ + signal_remove("event nick", (SIGNAL_FUNC) event_nick); + signal_remove("event 352", (SIGNAL_FUNC) event_who); + signal_remove("silent event who", (SIGNAL_FUNC) event_who); + signal_remove("silent event whois", (SIGNAL_FUNC) event_whois); + signal_remove("event 311", (SIGNAL_FUNC) event_whois); + signal_remove("event 301", (SIGNAL_FUNC) event_whois_away); + signal_remove("event 313", (SIGNAL_FUNC) event_whois_ircop); + signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois); + signal_remove("event 353", (SIGNAL_FUNC) event_names_list); + signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_remove("event 302", (SIGNAL_FUNC) event_userhost); + signal_remove("userhost event", (SIGNAL_FUNC) event_userhost); + signal_remove("user mode changed", (SIGNAL_FUNC) sig_usermode); + signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); +} diff --git a/src/irc/core/nicklist.h b/src/irc/core/nicklist.h new file mode 100644 index 00000000..8e83e97f --- /dev/null +++ b/src/irc/core/nicklist.h @@ -0,0 +1,41 @@ +#ifndef __NICKLIST_H +#define __NICKLIST_H + +#include "channels.h" + +typedef struct { + time_t last_check; /* last time gone was checked */ + int send_massjoin; /* Waiting to be sent in massjoin signal */ + + char *nick; + char *host; + char *realname; + + int hops; + + int op:1; + int voice:1; + int gone:1; + int ircop:1; +} NICK_REC; + +/* Add new nick to list */ +NICK_REC *nicklist_insert(CHANNEL_REC *channel, const char *nick, int op, int voice, int send_massjoin); +/* remove nick from list */ +void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick); +/* Find nick record from list */ +NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *mask); +/* Get list of nicks */ +GSList *nicklist_getnicks(CHANNEL_REC *channel); +/* Get all the nick records of `nick'. Returns channel, nick, channel, ... */ +GSList *nicklist_get_same(IRC_SERVER_REC *server, const char *nick); + +/* nick record comparision for sort functions */ +int nicklist_compare(NICK_REC *p1, NICK_REC *p2); + +char *nick_strip(const char *nick); + +void nicklist_init(void); +void nicklist_deinit(void); + +#endif diff --git a/src/irc/core/query.c b/src/irc/core/query.c new file mode 100644 index 00000000..cce333af --- /dev/null +++ b/src/irc/core/query.c @@ -0,0 +1,158 @@ +/* + query.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "misc.h" +#include "signals.h" +#include "modules.h" + +#include "irc.h" +#include "query.h" + +GSList *queries; + +QUERY_REC *query_create(IRC_SERVER_REC *server, const char *nick, int automatic) +{ + QUERY_REC *rec; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new0(QUERY_REC, 1); + queries = g_slist_append(queries, rec); + server->queries = g_slist_append(server->queries, rec); + + MODULE_DATA_INIT(rec); + rec->type = module_get_uniq_id("IRC", WI_IRC_QUERY); + rec->nick = g_strdup(nick); + rec->server_tag = g_strdup(server->tag); + rec->server = server; + + signal_emit("query created", 2, rec, GINT_TO_POINTER(automatic)); + return rec; +} + +void query_destroy(QUERY_REC *query) +{ + g_return_if_fail(query != NULL); + + if (query->destroying) return; + query->destroying = TRUE; + + queries = g_slist_remove(queries, query); + if (query->server != NULL) + query->server->queries = g_slist_remove(query->server->queries, query); + signal_emit("query destroyed", 1, query); + + MODULE_DATA_DEINIT(query); + g_free(query->nick); + g_free(query->server_tag); + g_free(query); +} + + +static QUERY_REC *query_find_server(IRC_SERVER_REC *server, const char *nick) +{ + GSList *tmp; + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (g_strcasecmp(nick, rec->nick) == 0) + return rec; + } + + return NULL; +} + +QUERY_REC *query_find(IRC_SERVER_REC *server, const char *nick) +{ + g_return_val_if_fail(nick != NULL, NULL); + + if (server != NULL) + return query_find_server(server, nick); + + /* find from any server */ + return gslist_foreach_find(servers, (FOREACH_FIND_FUNC) query_find_server, (void *) nick); +} + +void query_change_server(QUERY_REC *query, IRC_SERVER_REC *server) +{ + g_return_if_fail(query != NULL); + + query->server = server; + signal_emit("query server changed", 2, query, server); +} + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *target, *msg; + QUERY_REC *query; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); + + if (addr != NULL && *msg != 1 && !ischannel(*target)) { + /* save nick's address to query */ + query = query_find(server, nick); + if (query != NULL && (query->address == NULL || strcmp(query->address, addr) != 0)) { + g_free_not_null(query->address); + query->address = g_strdup(addr); + + signal_emit("query address changed", 1, query); + } + } + + g_free(params); +} + +static void event_nick(const char *data, IRC_SERVER_REC *server, const char *orignick) +{ + char *params, *nick; + GSList *tmp; + + params = event_get_params(data, 1, &nick); + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (g_strcasecmp(rec->nick, orignick) == 0) { + g_free(rec->nick); + rec->nick = g_strdup(nick); + signal_emit("query nick changed", 1, rec); + } + } + + g_free(params); +} + +void query_init(void) +{ + signal_add_last("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("event nick", (SIGNAL_FUNC) event_nick); +} + +void query_deinit(void) +{ + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); +} diff --git a/src/irc/core/query.h b/src/irc/core/query.h new file mode 100644 index 00000000..048c890a --- /dev/null +++ b/src/irc/core/query.h @@ -0,0 +1,30 @@ +#ifndef __QUERY_H +#define __QUERY_H + +#include "server.h" + +typedef struct { + int type; + GHashTable *module_data; + + IRC_SERVER_REC *server; + char *nick; + + int new_data; + + char *address; + char *server_tag; + int destroying:1; +} QUERY_REC; + +extern GSList *queries; + +QUERY_REC *query_create(IRC_SERVER_REC *server, const char *nick, int automatic); +void query_destroy(QUERY_REC *query); + +/* Find query by name, if `server' is NULL, search from all servers */ +QUERY_REC *query_find(IRC_SERVER_REC *server, const char *nick); + +void query_change_server(QUERY_REC *query, IRC_SERVER_REC *server); + +#endif diff --git a/src/irc/core/server-idle.c b/src/irc/core/server-idle.c new file mode 100644 index 00000000..273ee231 --- /dev/null +++ b/src/irc/core/server-idle.c @@ -0,0 +1,252 @@ +/* + server-idle.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "irc-server.h" +#include "server-idle.h" +#include "server-redirect.h" +#include "irc.h" + +typedef struct { + char *event; + char *signal; + int argpos; +} REDIRECT_IDLE_REC; + +typedef struct { + char *cmd; + char *arg; + int tag; + + int last; + GSList *redirects; +} SERVER_IDLE_REC; + +static int idle_tag, idlepos; + +/* Add new idle command to queue */ +static SERVER_IDLE_REC *server_idle_create(const char *cmd, const char *arg, int last, va_list args) +{ + REDIRECT_IDLE_REC *rrec; + SERVER_IDLE_REC *rec; + char *event; + + g_return_val_if_fail(cmd != NULL, FALSE); + + rec = g_new0(SERVER_IDLE_REC, 1); + + rec->tag = ++idlepos; + rec->arg = arg == NULL ? NULL : g_strdup(arg); + rec->cmd = g_strdup(cmd); + rec->last = last; + + while ((event = va_arg(args, char *)) != NULL) { + rrec = g_new(REDIRECT_IDLE_REC, 1); + rec->redirects = g_slist_append(rec->redirects, rrec); + + rrec->event = g_strdup(event); + rrec->signal = g_strdup(va_arg(args, char *)); + rrec->argpos = va_arg(args, int); + } + + return rec; +} + +static SERVER_IDLE_REC *server_idle_find_rec(IRC_SERVER_REC *server, int tag) +{ + GSList *tmp; + + g_return_val_if_fail(server != NULL, FALSE); + + for (tmp = server->idles; tmp != NULL; tmp = tmp->next) { + SERVER_IDLE_REC *rec = tmp->data; + + if (rec->tag == tag) + return rec; + } + + return NULL; +} + +/* Add new idle command to queue */ +int server_idle_add(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...) +{ + va_list args; + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, -1); + + va_start(args, last); + rec = server_idle_create(cmd, arg, last, args); + server->idles = g_slist_append(server->idles, rec); + va_end(args); + + return rec->tag; +} + +/* Add new idle command to first of queue */ +int server_idle_add_first(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...) +{ + va_list args; + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, -1); + + va_start(args, last); + rec = server_idle_create(cmd, arg, last, args); + server->idles = g_slist_prepend(server->idles, rec); + va_end(args); + + return rec->tag; +} + +/* Add new idle command to specified position of queue */ +int server_idle_insert(IRC_SERVER_REC *server, const char *cmd, const char *arg, int tag, int last, ...) +{ + va_list args; + SERVER_IDLE_REC *rec; + int pos; + + g_return_val_if_fail(server != NULL, -1); + + va_start(args, last); + + /* find the position of tag in idle list */ + rec = server_idle_find_rec(server, tag); + pos = g_slist_index(server->idles, rec); + + rec = server_idle_create(cmd, arg, last, args); + server->idles = pos < 0 ? + g_slist_append(server->idles, rec) : + g_slist_insert(server->idles, rec, pos); + va_end(args); + return rec->tag; +} + +static void server_idle_destroy(IRC_SERVER_REC *server, SERVER_IDLE_REC *rec) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + server->idles = g_slist_remove(server->idles, rec); + + for (tmp = rec->redirects; tmp != NULL; tmp = tmp->next) { + REDIRECT_IDLE_REC *rec = tmp->data; + + g_free(rec->event); + g_free(rec->signal); + g_free(rec); + } + g_slist_free(rec->redirects); + + g_free_not_null(rec->arg); + g_free(rec->cmd); + g_free(rec); +} + +/* Check if record is still in queue */ +int server_idle_find(IRC_SERVER_REC *server, int tag) +{ + return server_idle_find_rec(server, tag) != NULL; +} + +/* Remove record from idle queue */ +int server_idle_remove(IRC_SERVER_REC *server, int tag) +{ + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, FALSE); + + rec = server_idle_find_rec(server, tag); + if (rec == NULL) + return FALSE; + + server_idle_destroy(server, rec); + return TRUE; +} + +/* Execute next idle command */ +static void server_idle_next(IRC_SERVER_REC *server) +{ + SERVER_IDLE_REC *rec; + GSList *tmp; + int group; + + g_return_if_fail(server != NULL); + + if (server->idles == NULL) return; + rec = server->idles->data; + + /* Send command */ + irc_send_cmd(server, rec->cmd); + + /* Add server redirections */ + group = 0; + for (tmp = rec->redirects; tmp != NULL; tmp = tmp->next) { + REDIRECT_IDLE_REC *rrec = tmp->data; + + group = server_redirect_single_event((SERVER_REC *) server, rec->arg, rec->last > 0, + group, rrec->event, rrec->signal, rrec->argpos); + if (rec->last > 0) rec->last--; + } + + server_idle_destroy(server, rec); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + while (server->idles != NULL) + server_idle_destroy(server, server->idles->data); +} + +static int sig_idle_timeout(void) +{ + GSList *tmp; + + /* Scan through every server */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + if (rec->idles != NULL && rec->cmdcount == 0) { + /* We're idling and we have idle commands to run! */ + server_idle_next(rec); + } + } + return 1; +} + +void servers_idle_init(void) +{ + idlepos = 0; + idle_tag = g_timeout_add(1000, (GSourceFunc) sig_idle_timeout, NULL); + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void servers_idle_deinit(void) +{ + g_source_remove(idle_tag); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/server-idle.h b/src/irc/core/server-idle.h new file mode 100644 index 00000000..a8ef8ec6 --- /dev/null +++ b/src/irc/core/server-idle.h @@ -0,0 +1,24 @@ +#ifndef __SERVER_IDLE_H +#define __SERVER_IDLE_H + +#include "irc-server.h" + +/* Add new idle command to queue */ +int server_idle_add(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...); + +/* Add new idle command to first of queue */ +int server_idle_add_first(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...); + +/* Add new idle command to specified position of queue */ +int server_idle_insert(IRC_SERVER_REC *server, const char *cmd, const char *arg, int tag, int last, ...); + +/* Check if record is still in queue */ +int server_idle_find(IRC_SERVER_REC *server, int tag); + +/* Remove record from idle queue */ +int server_idle_remove(IRC_SERVER_REC *server, int tag); + +void servers_idle_init(void); +void servers_idle_deinit(void); + +#endif diff --git a/src/irc/core/server-reconnect.c b/src/irc/core/server-reconnect.c new file mode 100644 index 00000000..1edfe187 --- /dev/null +++ b/src/irc/core/server-reconnect.c @@ -0,0 +1,398 @@ +/* + server-reconnect.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "commands.h" +#include "network.h" +#include "signals.h" + +#include "irc.h" +#include "modes.h" +#include "irc-server.h" +#include "server-setup.h" +#include "server-reconnect.h" + +#include "settings.h" +#include "common-setup.h" + +GSList *reconnects; +static int last_reconnect_tag; +static int reconnect_timeout_tag; +static int reconnect_time; + +static void server_reconnect_add(IRC_SERVER_CONNECT_REC *conn, time_t next_connect) +{ + RECONNECT_REC *rec; + + rec = g_new(RECONNECT_REC, 1); + rec->tag = ++last_reconnect_tag; + rec->conn = conn; + rec->next_connect = next_connect; + + reconnects = g_slist_append(reconnects, rec); +} + +static void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn) +{ + reconnects = g_slist_remove(reconnects, rec); + + signal_emit("server reconnect remove", 1, rec); + if (free_conn) irc_server_connect_free(rec->conn); + g_free(rec); + + if (reconnects == NULL) + last_reconnect_tag = 0; +} + +static int server_reconnect_timeout(void) +{ + IRC_SERVER_CONNECT_REC *conn; + GSList *tmp, *next; + time_t now; + + now = time(NULL); + for (tmp = reconnects; tmp != NULL; tmp = next) { + RECONNECT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->next_connect <= now) { + conn = rec->conn; + server_reconnect_destroy(rec, FALSE); + irc_server_connect(conn); + } + } + + return 1; +} + +static void sserver_connect(SETUP_SERVER_REC *rec, IRC_SERVER_CONNECT_REC *conn) +{ + conn->address = g_strdup(rec->server); + conn->port = rec->port; + conn->password = rec->password == NULL ? NULL : + g_strdup(rec->password); + if (rec->cmd_queue_speed > 0) + conn->cmd_queue_speed = rec->cmd_queue_speed; + + if (rec->last_connect > time(NULL)-reconnect_time) { + /* can't reconnect this fast, wait.. */ + server_reconnect_add(conn, rec->last_connect+reconnect_time); + } else { + /* connect to server.. */ + irc_server_connect(conn); + } +} + +static void server_connect_copy_skeleton(IRC_SERVER_CONNECT_REC *dest, IRC_SERVER_CONNECT_REC *src) +{ + dest->proxy = src->proxy == NULL ? NULL : + g_strdup(src->proxy); + dest->proxy_port = src->proxy_port; + dest->proxy_string = src->proxy_string == NULL ? NULL : + g_strdup(src->proxy_string); + + dest->ircnet = src->ircnet == NULL ? NULL : + g_strdup(src->ircnet); + dest->nick = src->nick == NULL ? NULL : + g_strdup(src->nick); + dest->username = src->username == NULL ? NULL : + g_strdup(src->username); + dest->realname = src->realname == NULL ? NULL : + g_strdup(src->realname); + + if (src->own_ip != NULL) { + dest->own_ip = g_new(IPADDR, 1); + memcpy(dest->own_ip, src->own_ip, sizeof(IPADDR)); + } + + dest->cmd_queue_speed = src->cmd_queue_speed; + dest->max_kicks = src->max_kicks; + dest->max_modes = src->max_modes; + dest->max_msgs = src->max_msgs; +} + +static void sig_reconnect(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + SETUP_SERVER_REC *sserver; + GSList *tmp; + int found, through; + time_t now; + + g_return_if_fail(server != NULL); + + if (reconnect_time == -1 || !server->connection_lost || !irc_server_check(server)) + return; + + conn = g_new0(IRC_SERVER_CONNECT_REC, 1); + conn->reconnection = TRUE; + server_connect_copy_skeleton(conn, server->connrec); + + /* save the server status */ + if (!server->connected) { + conn->channels = g_strdup(server->connrec->channels); + conn->away_reason = g_strdup(server->connrec->away_reason); + conn->usermode = g_strdup(server->connrec->usermode); + } else { + conn->channels = irc_server_get_channels(server); + conn->away_reason = !server->usermode_away ? NULL : + g_strdup(server->away_reason); + conn->usermode = g_strdup(server->usermode); + } + + sserver = server_setup_find(server->connrec->address, server->connrec->port); + if (sserver == NULL) { + /* port specific record not found, try without port.. */ + sserver = server_setup_find(server->connrec->address, -1); + } + + if (sserver != NULL) { + /* save the last connection time/status */ + sserver->last_connect = server->connect_time == 0 ? + time(NULL) : server->connect_time; + sserver->last_failed = !server->connected; + } + + if (sserver == NULL || conn->ircnet == NULL) { + /* not in any ircnet, just reconnect back to same server */ + conn->address = g_strdup(server->connrec->address); + conn->port = server->connrec->port; + conn->password = server->connrec->password == NULL ? NULL : + g_strdup(server->connrec->password); + + if (server->connect_time != 0 && + time(NULL)-server->connect_time > reconnect_time) { + /* there's been enough time since last connection, + reconnect back immediately */ + irc_server_connect(conn); + } else { + /* reconnect later.. */ + server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) : + server->connect_time) + reconnect_time); + } + return; + } + + /* always try to first connect to the first on the list where we + haven't got unsuccessful connection attempts for the last half + an hour. */ + + now = time(NULL); + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SETUP_SERVER_REC *rec = tmp->data; + + if (rec->ircnet == NULL || g_strcasecmp(conn->ircnet, rec->ircnet) != 0) + continue; + + if (!rec->last_connect || !rec->last_failed || rec->last_connect < now-FAILED_RECONNECT_WAIT) { + sserver_connect(rec, conn); + return; + } + } + + /* just try the next server in list */ + found = through = FALSE; + for (tmp = setupservers; tmp != NULL; ) { + SETUP_SERVER_REC *rec = tmp->data; + + if (!found && g_strcasecmp(rec->server, server->connrec->address) == 0 && + server->connrec->port == rec->port) + found = TRUE; + else if (found && rec->ircnet != NULL && g_strcasecmp(conn->ircnet, rec->ircnet) == 0) { + sserver_connect(rec, conn); + break; + } + + if (tmp->next != NULL) { + tmp = tmp->next; + continue; + } + + if (through) { + /* shouldn't happen unless there's no servers in + this ircnet in setup.. */ + break; + } + + tmp = setupservers; + found = through = TRUE; + } +} + +static void sig_server_looking(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + GSList *tmp, *next; + + g_return_if_fail(server != NULL); + if (!irc_server_check(server)) + return; + + /* trying to connect somewhere, check if there's anything in reconnect + queue waiting to connect to same ircnet or same server+port.. */ + conn = server->connrec; + for (tmp = reconnects; tmp != NULL; tmp = next) { + RECONNECT_REC *rec = tmp->data; + + next = tmp->next; + if (g_strcasecmp(conn->address, rec->conn->address) == 0 && + conn->port == rec->conn->port) { + server_reconnect_destroy(rec, TRUE); + } + else if (conn->ircnet != NULL && rec->conn->ircnet != NULL && + g_strcasecmp(conn->ircnet, rec->conn->ircnet) == 0) { + server_reconnect_destroy(rec, TRUE); + } + } +} + +/* Remove all servers from reconnect list */ +static void cmd_rmreconns(void) +{ + while (reconnects != NULL) + server_reconnect_destroy(reconnects->data, TRUE); +} + +static RECONNECT_REC *reconnect_find_tag(int tag) +{ + GSList *tmp; + + for (tmp = reconnects; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + + if (rec->tag == tag) + return rec; + } + + return NULL; +} + +/* Try to reconnect immediately */ +static void cmd_reconnect(const char *data) +{ + IRC_SERVER_CONNECT_REC *conn; + RECONNECT_REC *rec; + int tag; + + if (g_strncasecmp(data, "RECON-", 6) == 0) + data += 6; + + rec = sscanf(data, "%d", &tag) == 1 && tag > 0 ? + reconnect_find_tag(tag) : NULL; + + if (rec == NULL) + signal_emit("server reconnect not found", 1, data); + else { + conn = rec->conn; + server_reconnect_destroy(rec, FALSE); + irc_server_connect(rec->conn); + } +} + +static void cmd_disconnect(const char *data, SERVER_REC *server) +{ + RECONNECT_REC *rec; + int tag; + + if (g_strncasecmp(data, "RECON-", 6) != 0) + return; /* handle only reconnection removing */ + + rec = sscanf(data+6, "%d", &tag) == 1 && tag > 0 ? + reconnect_find_tag(tag) : NULL; + + if (rec == NULL) + signal_emit("server reconnect not found", 1, data); + else + server_reconnect_destroy(rec, TRUE); +} + +static int sig_set_user_mode(IRC_SERVER_REC *server) +{ + const char *mode; + char *newmode; + + if (g_slist_find(servers, server) == NULL) + return 0; /* got disconnected */ + + mode = server->connrec->usermode; + if (mode == NULL) return 0; + + newmode = modes_join(server->usermode, mode); + if (strcmp(newmode, server->usermode) != 0) + irc_send_cmdv(server, "MODE %s -%s+%s", server->nick, server->usermode, mode); + g_free(newmode); + return 0; +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + if (!server->connrec->reconnection) + return; + + if (server->connrec->channels != NULL) + channels_join(server, server->connrec->channels, TRUE); + if (server->connrec->away_reason != NULL) + signal_emit("command away", 2, server->connrec->away_reason, server, NULL); + if (server->connrec->usermode != NULL) { + /* wait a second and then send the user mode */ + g_timeout_add(1000, (GSourceFunc) sig_set_user_mode, server); + } +} + +static void read_settings(void) +{ + reconnect_time = settings_get_int("server_reconnect_time"); +} + +void servers_reconnect_init(void) +{ + reconnects = NULL; + last_reconnect_tag = 0; + + reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL); + read_settings(); + + signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect); + signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect); + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns); + command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect); + command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void servers_reconnect_deinit(void) +{ + g_source_remove(reconnect_timeout_tag); + + while (reconnects != NULL) + server_reconnect_destroy(reconnects->data, TRUE); + + signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect); + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns); + command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect); + command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/irc/core/server-reconnect.h b/src/irc/core/server-reconnect.h new file mode 100644 index 00000000..6f4b5336 --- /dev/null +++ b/src/irc/core/server-reconnect.h @@ -0,0 +1,16 @@ +#ifndef __SERVER_RECONNECT_H +#define __SERVER_RECONNECT_H + +typedef struct { + int tag; + time_t next_connect; + + IRC_SERVER_CONNECT_REC *conn; +} RECONNECT_REC; + +extern GSList *reconnects; + +void servers_reconnect_init(void); +void servers_reconnect_deinit(void); + +#endif diff --git a/src/irc/core/server-setup.c b/src/irc/core/server-setup.c new file mode 100644 index 00000000..f56d839f --- /dev/null +++ b/src/irc/core/server-setup.c @@ -0,0 +1,317 @@ +/* + server-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "network.h" +#include "lib-config/iconfig.h" +#include "settings.h" +#include "common-setup.h" + +#include "irc-server.h" +#include "server-setup.h" +#include "ircnet-setup.h" + +GSList *setupservers; /* list of irc servers */ + +int source_host_ok; /* Use source_host_ip .. */ +IPADDR *source_host_ip; /* Resolved address */ + +static void get_source_host_ip(void) +{ + IPADDR ip; + + /* FIXME: This will block! */ + if (!source_host_ok) { + source_host_ok = *settings_get_str("hostname") != '\0' && + net_gethostname(settings_get_str("hostname"), &ip) == 0; + if (source_host_ok) { + source_host_ip = g_new(IPADDR, 1); + memcpy(source_host_ip, &ip, sizeof(IPADDR)); + } + } +} + +/* Create server connection record. `address' is required, rest can be NULL */ +static IRC_SERVER_CONNECT_REC * +create_addr_conn(const char *address, int port, const char *password, + const char *nick) +{ + IRC_SERVER_CONNECT_REC *conn; + SETUP_SERVER_REC *sserver; + IRCNET_REC *ircnet; + + g_return_val_if_fail(address != NULL, NULL); + + conn = g_new0(IRC_SERVER_CONNECT_REC, 1); + + conn->address = g_strdup(address); + conn->port = port > 0 ? port : 6667; + + if (password && *password) conn->password = g_strdup(password); + if (nick && *nick) conn->nick = g_strdup(nick); + + if (!conn->nick) conn->nick = g_strdup(settings_get_str("default_nick")); + conn->alternate_nick = g_strdup(settings_get_str("alternate_nick")); + conn->username = g_strdup(settings_get_str("user_name")); + conn->realname = g_strdup(settings_get_str("real_name")); + + /* proxy settings */ + if (settings_get_bool("toggle_use_ircproxy")) { + conn->proxy = g_strdup(settings_get_str("proxy_address")); + conn->proxy_port = settings_get_int("proxy_port"); + conn->proxy_string = g_strdup(settings_get_str("proxy_string")); + } + + /* source IP */ + get_source_host_ip(); + if (source_host_ok) { + conn->own_ip = g_new(IPADDR, 1); + memcpy(conn->own_ip, source_host_ip, sizeof(IPADDR)); + } + + /* fill the information from setup */ + sserver = server_setup_find(address, -1); + if (sserver == NULL) return conn; + + sserver->last_connect = time(NULL); + + if (sserver->ircnet) conn->ircnet = g_strdup(sserver->ircnet); + if (sserver->password && !conn->password) + conn->password = g_strdup(sserver->password); + if (sserver->cmd_queue_speed > 0) + conn->cmd_queue_speed = sserver->cmd_queue_speed; + if (sserver->max_cmds_at_once > 0) + conn->max_cmds_at_once = sserver->max_cmds_at_once; + + /* fill the rest from IRC network settings */ + ircnet = sserver->ircnet == NULL ? NULL : ircnet_find(sserver->ircnet); + if (ircnet == NULL) return conn; + + if (ircnet->nick && !nick) { + g_free(conn->nick); + conn->nick = g_strdup(ircnet->nick);; + } + if (ircnet->username) { + g_free(conn->username); + conn->username = g_strdup(ircnet->username);; + } + if (ircnet->realname) { + g_free(conn->realname); + conn->realname = g_strdup(ircnet->realname);; + } + if (ircnet->max_kicks > 0) conn->max_kicks = ircnet->max_kicks; + if (ircnet->max_msgs > 0) conn->max_msgs = ircnet->max_msgs; + if (ircnet->max_modes > 0) conn->max_modes = ircnet->max_modes; + if (ircnet->max_whois > 0) conn->max_whois = ircnet->max_whois; + + return conn; +} + +/* Create server connection record. `dest' is required, rest can be NULL. + `dest' is either a server address or irc network */ +IRC_SERVER_CONNECT_REC * +irc_server_create_conn(const char *dest, int port, const char *password, const char *nick) +{ + GSList *tmp; + time_t now; + int n; + + g_return_val_if_fail(dest != NULL, NULL); + + /* check if `dest' is IRC network */ + if (ircnet_find(dest) == NULL) + return create_addr_conn(dest, port, password, nick); + + /* first try to find a server that hasn't had any connection failures + for the past half an hour. If that isn't found, try any server. */ + now = time(NULL); + for (n = 0; n < 2; n++) { + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SETUP_SERVER_REC *rec = tmp->data; + + if (rec->ircnet == NULL || g_strcasecmp(rec->ircnet, dest) != 0) + continue; + + if (n == 1 || !rec->last_failed || rec->last_connect < now-FAILED_RECONNECT_WAIT) + return create_addr_conn(rec->server, port, password, nick); + } + } + + return NULL; +} + +/* Find matching server from setup. Set port to -1 if you don't care about it */ +SETUP_SERVER_REC *server_setup_find(const char *address, int port) +{ + GSList *tmp; + + g_return_val_if_fail(address != NULL, NULL); + + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SETUP_SERVER_REC *rec = tmp->data; + + if (g_strcasecmp(rec->server, address) == 0 && + (port == -1 || rec->port == port)) return rec; + } + + return NULL; +} + +static void init_userinfo(void) +{ + const char *set, *default_nick, *user_name; + char *str; + + /* check if nick/username/realname wasn't read from setup.. */ + set = settings_get_str("real_name"); + if (set == NULL || *set == '\0') { + str = g_getenv("IRCNAME"); + iconfig_set_str("settings", "real_name", + str != NULL ? str : g_get_real_name()); + g_free_not_null(str); + } + + /* username */ + user_name = settings_get_str("user_name"); + if (user_name == NULL || *user_name == '\0') { + str = g_getenv("IRCUSER"); + iconfig_set_str("settings", "user_name", + str != NULL ? str : g_get_user_name()); + g_free_not_null(str); + + user_name = settings_get_str("user_name"); + } + + /* nick */ + default_nick = settings_get_str("default_nick"); + if (default_nick == NULL || *default_nick == '\0') { + str = g_getenv("IRCNICK"); + iconfig_set_str("settings", "default_nick", + str != NULL ? str : user_name); + g_free_not_null(str); + + default_nick = settings_get_str("default_nick"); + } + + /* alternate nick */ + set = settings_get_str("alternate_nick"); + if (set == NULL || *set == '\0') { + if (strlen(default_nick) < 9) + str = g_strconcat(default_nick, "_", NULL); + else { + str = g_strdup(default_nick); + str[strlen(str)-1] = '_'; + } + iconfig_set_str("settings", "alternate_nick", str); + g_free(str); + } +} + +static SETUP_SERVER_REC *setupserver_add(CONFIG_NODE *node) +{ + SETUP_SERVER_REC *rec; + char *ircnet, *server; + int port; + + g_return_val_if_fail(node != NULL, NULL); + + ircnet = config_node_get_str(node, "ircnet", NULL); + server = config_node_get_str(node, "server", NULL); + if (ircnet == NULL || server == NULL) return NULL; + + port = config_node_get_int(node, "port", 6667); + if (server_setup_find(server, port) != NULL) { + /* already exists - don't let it get there twice or + server reconnects will screw up! */ + return NULL; + } + + rec = g_new0(SETUP_SERVER_REC, 1); + rec->ircnet = g_strdup(ircnet); + rec->server = g_strdup(server); + rec->password = g_strdup(config_node_get_str(node, "password", "")); + rec->port = port; + rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE); + rec->max_cmds_at_once = config_node_get_int(node, "cmds_max_at_once", 0); + rec->cmd_queue_speed = config_node_get_int(node, "cmd_queue_speed", 0); + + setupservers = g_slist_append(setupservers, rec); + return rec; +} + +static void setupserver_destroy(SETUP_SERVER_REC *rec) +{ + setupservers = g_slist_remove(setupservers, rec); + + g_free(rec->ircnet); + g_free(rec->server); + g_free(rec->password); + g_free(rec); +} + +static void read_servers(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + while (setupservers != NULL) + setupserver_destroy(setupservers->data); + + /* Read servers */ + node = iconfig_node_traverse("(setupservers", FALSE); + if (node != NULL) { + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + setupserver_add(tmp->data); + } +} + +void servers_setup_init(void) +{ + source_host_ok = FALSE; + source_host_ip = NULL; + + settings_add_int("server", "server_reconnect_time", 300); + settings_add_str("server", "hostname", ""); + settings_add_bool("server", "toggle_skip_motd", FALSE); + + settings_add_str("server", "default_nick", NULL); + settings_add_str("server", "alternate_nick", NULL); + settings_add_str("server", "user_name", NULL); + settings_add_str("server", "real_name", NULL); + + settings_add_bool("ircproxy", "toggle_use_ircproxy", FALSE); + settings_add_str("ircproxy", "proxy_address", ""); + settings_add_int("ircproxy", "proxy_port", 6667); + settings_add_str("ircproxy", "proxy_string", "CONNECT %s %d"); + + init_userinfo(); + + read_servers(); + signal_add("setup reread", (SIGNAL_FUNC) read_servers); +} + +void servers_setup_deinit(void) +{ + while (setupservers != NULL) + setupserver_destroy(setupservers->data); + + signal_remove("setup reread", (SIGNAL_FUNC) read_servers); +} diff --git a/src/irc/core/server-setup.h b/src/irc/core/server-setup.h new file mode 100644 index 00000000..a3a3d4ff --- /dev/null +++ b/src/irc/core/server-setup.h @@ -0,0 +1,40 @@ +#ifndef __SERVER_SETUP_H +#define __SERVER_SETUP_H + +#include "irc-server.h" + +/* servers */ +typedef struct { + char *server; + int port; + + char *ircnet; + char *password; + int autoconnect; + int max_cmds_at_once; /* override the default if > 0 */ + int cmd_queue_speed; /* override the default if > 0 */ + + char *own_address; /* address to use when connecting this server */ + IPADDR *own_ip; /* resolved own_address or full of zeros */ + + time_t last_connect; /* to avoid reconnecting too fast.. */ + int last_failed; /* if last connection attempt failed */ +} SETUP_SERVER_REC; + +extern GSList *setupservers; /* list of irc servers */ + +extern IPADDR *source_host_ip; /* Resolved address */ +extern gboolean source_host_ok; /* Use source_host_ip .. */ + +/* Create server connection record. `dest' is required, rest can be NULL. + `dest' is either a server address or irc network */ +IRC_SERVER_CONNECT_REC * +irc_server_create_conn(const char *dest, int port, const char *password, const char *nick); + +/* Find matching server from setup. Set port to -1 if you don't care about it */ +SETUP_SERVER_REC *server_setup_find(const char *address, int port); + +void servers_setup_init(void); +void servers_setup_deinit(void); + +#endif diff --git a/src/irc/dcc/Makefile.am b/src/irc/dcc/Makefile.am new file mode 100644 index 00000000..b7cd30e9 --- /dev/null +++ b/src/irc/dcc/Makefile.am @@ -0,0 +1,14 @@ +noinst_LTLIBRARIES = libirc_dcc.la + +INCLUDES = $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/ + +libirc_dcc_la_SOURCES = \ + dcc.c \ + dcc-chat.c \ + dcc-files.c + +noinst_HEADERS = \ + dcc.h \ + dcc-chat.h \ + dcc-files.h diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c new file mode 100644 index 00000000..5cddddfa --- /dev/null +++ b/src/irc/dcc/dcc-chat.c @@ -0,0 +1,371 @@ +/* + dcc-chat.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "network.h" +#include "net-nonblock.h" +#include "line-split.h" +#include "settings.h" + +#include "masks.h" +#include "irc.h" +#include "server-setup.h" + +#include "dcc.h" + +/* Send text to DCC chat */ +static void dcc_chat_write(gchar *data) +{ + DCC_REC *dcc; + gchar *params, *text, *target; + gint len; + + g_return_if_fail(text != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &text); + + if (*target == '=') + { + /* dcc msg */ + dcc = dcc_find_item(DCC_TYPE_CHAT, ++target, NULL); + if (dcc != NULL) + { + len = strlen(text); + /* FIXME: we need output queue! */ + if (net_transmit(dcc->handle, text, len) != len) + g_warning("dcc_chat_write() : could not send all data!"); + net_transmit(dcc->handle, "\n", 1); + } + } + + g_free(params); +} + +static void dcc_chat_me(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + DCC_REC *dcc; + char *str; + + g_return_if_fail(data != NULL); + + dcc = irc_item_dcc_chat(item); + if (dcc == NULL) return; + + str = g_strdup_printf("ACTION %s", data); + dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str); + g_free(str); +} + +static void dcc_chat_action(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *text; + DCC_REC *dcc; + char *str; + + g_return_if_fail(data != NULL); + + if (*data != '=') { + /* handle only DCC actions */ + return; + } + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &text); + if (*target == '\0' || *text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL); + if (dcc != NULL) { + str = g_strdup_printf("ACTION %s", data); + dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str); + g_free(str); + } + g_free(params); +} + +static void dcc_chat_ctcp(const char *data, IRC_SERVER_REC *server) +{ + char *params, *target, *ctcpcmd, *ctcpdata; + DCC_REC *dcc; + char *str; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata); + if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target != '=') { + /* handle only DCC CTCPs */ + g_free(params); + return; + } + + dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL); + if (dcc != NULL) { + g_strup(ctcpcmd); + + str = g_strdup_printf("%s %s", ctcpcmd, ctcpdata); + dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str); + g_free(str); + } + + g_free(params); +} + +/* DCC CHAT: text received */ +static void dcc_chat_msg(DCC_REC *dcc, gchar *msg) +{ + gchar *cmd, *ptr; + gboolean reply; + + g_return_if_fail(dcc != NULL); + g_return_if_fail(msg != NULL); + + reply = FALSE; + if (g_strncasecmp(msg, "CTCP_MESSAGE ", 13) != 0) + { + if (g_strncasecmp(msg, "CTCP_REPLY ", 11) != 0) + { + /* Use the mirc style CTCPing from now on.. */ + dcc->mirc_ctcp = TRUE; + } + else + { + /* bitchx (and ircii?) sends this */ + msg += 11; + reply = TRUE; + dcc->mirc_ctcp = FALSE; + } + } + else + { + /* bitchx (and ircii?) sends this */ + msg += 13; + dcc->mirc_ctcp = FALSE; + } + + /* Handle only DCC CTCPs */ + if (*msg != 1) + return; + + msg = g_strdup(msg+1); + /* remove the later \001 */ + ptr = strrchr(msg, 1); + if (ptr != NULL) *ptr = '\0'; + + /* get ctcp command */ + cmd = g_strconcat(reply ? "dcc reply " : "dcc ctcp ", msg, NULL); + ptr = strchr(cmd+9, ' '); + if (ptr != NULL) *ptr++ = '\0'; else ptr = ""; + + g_strdown(cmd+9); + if (!signal_emit(cmd, 2, ptr, dcc)) + signal_emit(reply ? "default dcc reply" : "default dcc ctcp", 2, msg, dcc); + + g_free(cmd); + g_free(msg); + + signal_stop(); +} + +/* input function: DCC CHAT received some data.. */ +static void dcc_chat_input(DCC_REC *dcc) +{ + char tmpbuf[512], *str; + int recvlen, ret; + + g_return_if_fail(dcc != NULL); + + do { + recvlen = net_receive(dcc->handle, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, (LINEBUF_REC **) &dcc->databuf); + if (ret == -1) { + /* connection lost */ + dcc->destroyed = TRUE; + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + break; + } + + if (ret > 0) { + dcc->transfd += ret; + signal_emit("dcc chat message", 2, dcc, str); + } + } while (ret > 0); +} + +/* input function: DCC CHAT - someone tried to connect to our socket */ +static void dcc_chat_listen(DCC_REC *dcc) +{ + IPADDR ip; + gint handle, port; + + g_return_if_fail(dcc != NULL); + + /* accept connection */ + handle = net_accept(dcc->handle, &ip, &port); + if (handle == -1) + return; + + /* FIXME: add paranoia checking, check if host ip is the same as to who + we sent the DCC CHAT request.. */ + + g_source_remove(dcc->tagread); + close(dcc->handle); + + dcc->starttime = time(NULL); + dcc->handle = handle; + memcpy(&dcc->addr, &ip, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + dcc->tagread = g_input_add(handle, G_INPUT_READ, + (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* callback: DCC CHAT - net_connect_nonblock() finished */ +static void dcc_chat_connect(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + + g_source_remove(dcc->tagread); + if (net_geterror(dcc->handle) != 0) + { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(dcc); + return; + } + + /* connect ok. */ + dcc->starttime = time(NULL); + dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ, + (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* command: DCC CHAT */ +static void cmd_dcc_chat(gchar *data, IRC_SERVER_REC *server) +{ + DCC_REC *dcc; + IPADDR addr; + gchar *str; + gint port, handle; + + g_return_if_fail(data != NULL); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = dcc_find_item(DCC_TYPE_CHAT, data, NULL); + if (dcc != NULL) + { + if (dcc->addrstr[0] == '\0' || dcc->starttime != 0) + { + /* already sent a chat request / already chatting */ + return; + } + + /* found from dcc list - so we're the connecting side.. */ + dcc->handle = net_connect_ip(&dcc->addr, dcc->port, + source_host_ok ? source_host_ip : NULL); + if (dcc->handle != -1) + { + dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE, + (GInputFunction) dcc_chat_connect, dcc); + } + else + { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(dcc); + } + + return; + } + + /* send dcc chat request */ + if (server == NULL || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!net_getsockname(server->handle, &addr, NULL)) + cmd_return_error(CMDERR_GETSOCKNAME); + + port = settings_get_int("dcc_port"); + handle = net_listen(&addr, &port); + if (handle == -1) + cmd_return_error(CMDERR_LISTEN); + + dcc = dcc_create(DCC_TYPE_CHAT, handle, data, "chat", server, NULL); + dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ, + (GInputFunction) dcc_chat_listen, dcc); + + /* send the request */ + str = g_strdup_printf("PRIVMSG %s :\001DCC CHAT CHAT %s %d\001", + data, dcc_make_address(&addr), port); + irc_send_cmd(server, str); + g_free(str); +} + +static void cmd_mircdcc(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + dcc = irc_item_dcc_chat(item); + if (dcc == NULL) return; + + dcc->mirc_ctcp = toupper(*data) == 'N' ? FALSE : TRUE; +} + +static void dcc_ctcp_redirect(gchar *msg, DCC_REC *dcc) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(dcc != NULL); + + signal_emit("ctcp msg dcc", 6, msg, dcc->server, dcc->nick, "dcc", dcc->mynick, dcc); +} + +void dcc_chat_init(void) +{ + command_bind("msg", NULL, (SIGNAL_FUNC) dcc_chat_write); + command_bind("me", NULL, (SIGNAL_FUNC) dcc_chat_me); + command_bind("action", NULL, (SIGNAL_FUNC) dcc_chat_action); + command_bind("ctcp", NULL, (SIGNAL_FUNC) dcc_chat_ctcp); + command_bind("dcc chat", NULL, (SIGNAL_FUNC) cmd_dcc_chat); + signal_add_first("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + command_bind("mircdcc", NULL, (SIGNAL_FUNC) cmd_mircdcc); + signal_add("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); +} + +void dcc_chat_deinit(void) +{ + command_unbind("msg", (SIGNAL_FUNC) dcc_chat_write); + command_unbind("me", (SIGNAL_FUNC) dcc_chat_me); + command_unbind("action", (SIGNAL_FUNC) dcc_chat_action); + command_unbind("ctcp", (SIGNAL_FUNC) dcc_chat_ctcp); + command_unbind("dcc chat", (SIGNAL_FUNC) cmd_dcc_chat); + signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + command_unbind("mircdcc", (SIGNAL_FUNC) cmd_mircdcc); + signal_remove("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); +} diff --git a/src/irc/dcc/dcc-chat.h b/src/irc/dcc/dcc-chat.h new file mode 100644 index 00000000..9ae9503f --- /dev/null +++ b/src/irc/dcc/dcc-chat.h @@ -0,0 +1,7 @@ +#ifndef __DCC_CHAT_H +#define __DCC_CHAT_H + +void dcc_chat_init(void); +void dcc_chat_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-files.c b/src/irc/dcc/dcc-files.c new file mode 100644 index 00000000..23b1cdce --- /dev/null +++ b/src/irc/dcc/dcc-files.c @@ -0,0 +1,577 @@ +/* + dcc-files.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "network.h" +#include "line-split.h" +#include "misc.h" +#include "settings.h" + +#include "masks.h" +#include "irc.h" +#include "server-setup.h" + +#include "dcc.h" + +static gint dcc_file_create_mode; + +static gchar *dcc_prepare_path(gchar *fname) +{ + gchar *str, *ptr, *downpath; + + /* strip all paths from file. */ + ptr = strrchr(fname, '/'); + if (ptr == NULL) ptr = fname; else ptr++; + + downpath = convert_home(settings_get_str("dcc_download_path")); + str = g_strdup_printf("%s/%s", downpath, ptr); + g_free(downpath); + + return str; +} + +/* input function: DCC GET received data */ +static void dcc_receive(DCC_REC *dcc) +{ + guint32 recd; + gint len, ret; + + g_return_if_fail(dcc != NULL); + + for (;;) + { + len = net_receive(dcc->handle, dcc->databuf, dcc->databufsize); + if (len == 0) break; + if (len < 0) + { + /* socket closed - transmit complete (or other side died..) */ + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + return; + } + + write(dcc->fhandle, dcc->databuf, len); + dcc->transfd += len; + } + + /* send number of total bytes received - if for some reason we couldn't + send the 4 characters last time, try to somehow fix it this time by + sending missing amount of 0 characters.. */ + if (dcc->trans_bytes != 0) + { + recd = (guint32) htonl(0); + dcc->trans_bytes += net_transmit(dcc->handle, ((gchar *) &recd)+dcc->trans_bytes, 4-dcc->trans_bytes); + if (dcc->trans_bytes == 4) dcc->trans_bytes = 0; + } + + if (dcc->trans_bytes == 0) + { + recd = (guint32) htonl(dcc->transfd); + ret = net_transmit(dcc->handle, ((gchar *) &recd), 4); + if (ret > 0 && ret < 4) dcc->trans_bytes = ret; + } + signal_emit("dcc transfer update", 1, dcc); +} + +/* callback: net_connect() finished for DCC GET */ +static void dcc_get_connect(DCC_REC *dcc) +{ + struct stat statbuf; + + g_return_if_fail(dcc != NULL); + + g_source_remove(dcc->tagread); + if (net_geterror(dcc->handle) != 0) + { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(dcc); + return; + } + dcc->file = dcc_prepare_path(dcc->arg); + + /* if some plugin wants to change the file name/path here.. */ + signal_emit("dcc get receive", 1, dcc); + + if (stat(dcc->file, &statbuf) == 0 && + (dcc->get_type == DCC_GET_RENAME || dcc->get_type == DCC_GET_DEFAULT)) + { + /* file exists, rename.. */ + GString *newname; + gint num; + + newname = g_string_new(NULL); + for (num = 1; ; num++) + { + g_string_sprintf(newname, "%s.%d", dcc->file, num); + if (stat(newname->str, &statbuf) != 0) break; + } + g_free(dcc->file); + dcc->file = newname->str; + g_string_free(newname, FALSE); + } + + if (dcc->get_type != DCC_GET_RESUME) + { + dcc->fhandle = open(dcc->file, O_WRONLY | O_TRUNC | O_CREAT, dcc_file_create_mode); + if (dcc->fhandle == -1) + { + signal_emit("dcc error file create", 2, dcc, dcc->file); + dcc_destroy(dcc); + return; + } + } + + dcc->databufsize = settings_get_int("dcc_block_size") > 0 ? settings_get_int("dcc_block_size") : 2048; + dcc->databuf = g_malloc(dcc->databufsize); + + dcc->starttime = time(NULL); + dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ, + (GInputFunction) dcc_receive, dcc); + signal_emit("dcc connected", 1, dcc); +} + +/* command: DCC GET */ +static void cmd_dcc_get(gchar *data) +{ + DCC_REC *dcc; + GSList *tmp, *next; + gchar *params, *nick, *fname; + gboolean found; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &fname); + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = NULL; found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) + { + dcc = tmp->data; + next = tmp->next; + + if (dcc->dcc_type == DCC_TYPE_GET && dcc->handle == -1 && g_strcasecmp(dcc->nick, nick) == 0 && + (*fname == '\0' || strcmp(dcc->arg, fname) == 0)) + { + /* found! */ + found = TRUE; + dcc->handle = net_connect_ip(&dcc->addr, dcc->port, + source_host_ok ? source_host_ip : NULL); + if (dcc->handle != -1) + { + dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE, + (GInputFunction) dcc_get_connect, dcc); + } + else + { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(dcc); + } + } + } + + if (!found) + signal_emit("dcc error get not found", 1, nick); + + g_free(params); +} + +/* resume setup: DCC SEND - we either said resume on get, or when we sent, + someone chose resume */ +static void dcc_resume_setup(DCC_REC *dcc, gint port) +{ + gchar *str; + + /* Check for DCC_SEND_RESUME */ + if (dcc->dcc_type == DCC_TYPE_SEND) + { + if (lseek(dcc->fhandle, dcc->transfd, SEEK_SET) == -1) + { + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + return; + } + else + { + str = g_strdup_printf("DCC ACCEPT %s %d %lu", + dcc->arg, port, dcc->transfd); + dcc_ctcp_message(dcc->nick, dcc->server, dcc->chat, FALSE, str); + g_free(str); + } + } + + /* Check for DCC_GET_RESUME */ + if (dcc->dcc_type == DCC_TYPE_GET && dcc->get_type == DCC_GET_RESUME) + { + dcc->handle = net_connect_ip(&dcc->addr, dcc->port, + source_host_ok ? source_host_ip : NULL); + if (dcc->handle != -1) + { + dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE, + (GInputFunction) dcc_get_connect, dcc); + } + else + { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(dcc); + } + } +} + +static void dcc_ctcp_msg(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr, gchar *target, DCC_REC *chat) +{ + gchar *params, *type, *arg, *portstr, *sizestr; + gulong size; + gint port; + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + g_return_if_fail(sender != NULL); + + params = cmd_get_params(data, 4, &type, &arg, &portstr, &sizestr); + if (g_strcasecmp(type, "RESUME") == 0 || g_strcasecmp(type, "ACCEPT") == 0) + { + if (sscanf(portstr, "%d", &port) != 1) port = 0; + if (sscanf(sizestr, "%lu", &size) != 1) size = 0; + + dcc = dcc_find_by_port(sender, port); + if (dcc != NULL && (dcc->dcc_type == DCC_TYPE_GET || dcc->transfd == 0)) + { + dcc->transfd = size; + dcc->skipped = size; + dcc_resume_setup(dcc, port); + } + } + + g_free(params); +} + +static void dcc_resume_rec(DCC_REC *dcc) +{ + gchar *str; + + dcc->file = dcc_prepare_path(dcc->arg); + + dcc->fhandle = open(dcc->file, O_WRONLY, dcc_file_create_mode); + if (dcc->fhandle == -1) + { + signal_emit("dcc error file not found", 2, dcc, dcc->file); + dcc_destroy(dcc); + } + else + { + dcc->transfd = lseek(dcc->fhandle, 0, SEEK_END); + if (dcc->transfd < 0) dcc->transfd = 0; + dcc->skipped = dcc->transfd; + + str = g_strdup_printf("DCC RESUME %s %d %lu", + dcc->arg, dcc->port, dcc->transfd); + dcc_ctcp_message(dcc->nick, dcc->server, dcc->chat, FALSE, str); + g_free(str); + } +} + +/* command: DCC RESUME */ +static void cmd_dcc_resume(gchar *data) +{ + DCC_REC *dcc; + GSList *tmp; + gchar *params, *nick, *fname; + gboolean found; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &fname); + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = NULL; found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + dcc = tmp->data; + + if (dcc->dcc_type == DCC_TYPE_GET && dcc->handle == -1 && g_strcasecmp(dcc->nick, nick) == 0 && + (*fname == '\0' || strcmp(dcc->arg, fname) == 0)) + { + /* found! */ + dcc->get_type = DCC_GET_RESUME; + dcc_resume_rec(dcc); + found = TRUE; + } + } + + if (!found) + signal_emit("dcc error get not found", 1, nick); + + g_free(params); +} + +/* input function: DCC SEND send more data */ +static void dcc_send_data(DCC_REC *dcc) +{ + gint n; + + g_return_if_fail(dcc != NULL); + + if (!dcc->fastsend && !dcc->gotalldata) + { + /* haven't received everything we've send there yet.. */ + return; + } + + n = read(dcc->fhandle, dcc->databuf, dcc->databufsize); + if (n <= 0) + { + /* end of file .. or some error .. */ + if (dcc->fastsend) + { + /* no need to call this function anymore.. in fact it just eats + all the cpu.. */ + dcc->waitforend = TRUE; + g_source_remove(dcc->tagwrite); + dcc->tagwrite = -1; + } + else + { + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + } + return; + } + + dcc->transfd += net_transmit(dcc->handle, dcc->databuf, n); + dcc->gotalldata = FALSE; + + lseek(dcc->fhandle, dcc->transfd, SEEK_SET); + + signal_emit("dcc transfer update", 1, dcc); +} + +/* input function: DCC SEND received some data */ +static void dcc_send_read_size(DCC_REC *dcc) +{ + guint32 bytes; + gint ret; + + g_return_if_fail(dcc != NULL); + + if (dcc->read_pos == 4) + return; + + /* we need to get 4 bytes.. */ + ret = net_receive(dcc->handle, dcc->read_buf+dcc->read_pos, 4-dcc->read_pos); + if (ret == -1) + { + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + return; + } + + dcc->read_pos += ret; + + if (dcc->read_pos == 4) + { + bytes = 0; memcpy(&bytes, dcc->read_buf, 4); + bytes = (guint32) ntohl(bytes); + + dcc->gotalldata = bytes == dcc->transfd; + dcc->read_pos = 0; + + if (!dcc->fastsend) + { + /* send more data.. */ + dcc_send_data(dcc); + } + + if (dcc->waitforend && dcc->gotalldata) + { + /* file is sent */ + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + return; + } + } +} + +/* input function: DCC SEND - someone tried to connect to our socket */ +static void dcc_send_init(DCC_REC *dcc) +{ + gint handle, port; + IPADDR addr; + + g_return_if_fail(dcc != NULL); + + /* accept connection */ + handle = net_accept(dcc->handle, &addr, &port); + if (handle == -1) + return; + + /* FIXME: add paranoia checking, check if host ip is the same as to who + we sent the DCC SEND request.. */ + + g_source_remove(dcc->tagread); + close(dcc->handle); + + dcc->fastsend = settings_get_bool("toggle_dcc_fast_send"); + dcc->handle = handle; + memcpy(&dcc->addr, &addr, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + dcc->databufsize = settings_get_int("dcc_block_size") > 0 ? settings_get_int("dcc_block_size") : 2048; + dcc->databuf = g_malloc(dcc->databufsize); + dcc->starttime = time(NULL); + dcc->tagread = g_input_add(handle, G_INPUT_READ, + (GInputFunction) dcc_send_read_size, dcc); + dcc->tagwrite = !dcc->fastsend ? -1 : + g_input_add(handle, G_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc); + + signal_emit("dcc connected", 1, dcc); + + if (!dcc->fastsend) + { + /* send first block */ + dcc->gotalldata = TRUE; + dcc_send_data(dcc); + } +} + +/* command: DCC SEND */ +static void cmd_dcc_send(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + gchar *params, *target, *fname, *str, *ptr; + gint fh, h, port; + glong fsize; + DCC_REC *dcc, *chat; + IPADDR addr; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &fname); + + /* if we're in dcc chat, send the request via it. */ + chat = irc_item_dcc_chat(item); + + if (chat != NULL && (chat->mirc_ctcp || g_strcasecmp(target, chat->nick) != 0)) + chat = NULL; + + if ((server == NULL || !server->connected) && chat == NULL) + cmd_param_error(CMDERR_NOT_CONNECTED); + + if (*target == '\0' || *fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (dcc_find_item(DCC_TYPE_SEND, target, fname)) + { + signal_emit("dcc error send exists", 2, target, fname); + g_free(params); + return; + } + + str = convert_home(fname); + if (*str != '/') + { + gchar *path; + + g_free(str); + path = convert_home(settings_get_str("dcc_upload_path")); + str = g_strconcat(path, "/", fname, NULL); + g_free(path); + } + + fh = open(str, O_RDONLY); + g_free(str); + + if (fh == -1) + { + signal_emit("dcc error file not found", 2, target, fname); + g_free(params); + return; + } + fsize = lseek(fh, 0, SEEK_END); + lseek(fh, 0, SEEK_SET); + + /* get the IP address we use with IRC server */ + if (!net_getsockname(chat != NULL ? chat->handle : server->handle, &addr, NULL)) + { + close(fh); + cmd_param_error(CMDERR_GETSOCKNAME); + } + + /* start listening */ + port = settings_get_int("dcc_port"); + h = net_listen(&addr, &port); + if (h == -1) + { + close(fh); + cmd_param_error(CMDERR_LISTEN); + } + + /* skip path */ + ptr = strrchr(fname, '/'); + if (ptr != NULL) fname = ptr+1; + + /* change all spaces to _ */ + fname = g_strdup(fname); + for (ptr = fname; *ptr != '\0'; ptr++) + if (*ptr == ' ') *ptr = '_'; + + dcc = dcc_create(DCC_TYPE_SEND, h, target, fname, server, chat); + dcc->port = port; + dcc->size = fsize; + dcc->fhandle = fh; + dcc->tagread = g_input_add(h, G_INPUT_READ, + (GInputFunction) dcc_send_init, dcc); + + /* send DCC request */ + str = g_strdup_printf("DCC SEND %s %s %d %lu", + fname, dcc_make_address(&addr), port, fsize); + dcc_ctcp_message(target, server, chat, FALSE, str); + g_free(str); + + g_free(fname); + g_free(params); +} + +static void read_settings(void) +{ + dcc_file_create_mode = octal2dec(settings_get_int("dcc_file_create_mode")); +} + +void dcc_files_init(void) +{ + signal_add("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("irssi init finished", (SIGNAL_FUNC) read_settings); + command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send); + command_bind("dcc get", NULL, (SIGNAL_FUNC) cmd_dcc_get); + command_bind("dcc resume", NULL, (SIGNAL_FUNC) cmd_dcc_resume); +} + +void dcc_files_deinit(void) +{ + signal_remove("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("irssi init finished", (SIGNAL_FUNC) read_settings); + command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send); + command_unbind("dcc get", (SIGNAL_FUNC) cmd_dcc_get); + command_unbind("dcc resume", (SIGNAL_FUNC) cmd_dcc_resume); +} diff --git a/src/irc/dcc/dcc-files.h b/src/irc/dcc/dcc-files.h new file mode 100644 index 00000000..3d12ffc1 --- /dev/null +++ b/src/irc/dcc/dcc-files.h @@ -0,0 +1,7 @@ +#ifndef __DCC_FILES_H +#define __DCC_FILES_H + +void dcc_files_init(void); +void dcc_files_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c new file mode 100644 index 00000000..41833744 --- /dev/null +++ b/src/irc/dcc/dcc.c @@ -0,0 +1,550 @@ +/* + dcc.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "network.h" +#include "line-split.h" +#include "settings.h" + +#include "masks.h" +#include "irc.h" + +#include "dcc.h" + +#define DCC_TYPES 5 + +static gchar *dcc_types[] = +{ + "CHAT", + "SEND", + "GET", + "RESUME", + "ACCEPT" +}; + +GSList *dcc_conns; + +static gint dcc_timeouttag; + +/* Create new DCC record */ +DCC_REC *dcc_create(gint type, gint handle, gchar *nick, gchar *arg, IRC_SERVER_REC *server, DCC_REC *chat) +{ + DCC_REC *dcc; + + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(arg != NULL, NULL); + + dcc = g_new0(DCC_REC, 1); + dcc->type = type == DCC_TYPE_CHAT ? module_get_uniq_id("IRC", WI_IRC_DCC_CHAT) : -1; + dcc->mirc_ctcp = settings_get_bool("toggle_dcc_mirc_ctcp"); + dcc->created = time(NULL); + dcc->chat = chat; + dcc->dcc_type = type; + dcc->arg = g_strdup(arg); + dcc->nick = g_strdup(nick); + dcc->handle = handle; + dcc->fhandle = -1; + dcc->tagread = dcc->tagwrite = -1; + dcc->server = server; + dcc->mynick = g_strdup(server != NULL ? server->nick : + chat != NULL ? chat->nick : "??"); + dcc->ircnet = server == NULL ? + chat == NULL || chat->ircnet == NULL ? NULL : g_strdup(chat->ircnet) : + server->connrec->ircnet == NULL ? NULL : g_strdup(server->connrec->ircnet); + dcc_conns = g_slist_append(dcc_conns, dcc); + + signal_emit("dcc created", 1, dcc); + return dcc; +} + +/* Destroy DCC record */ +void dcc_destroy(DCC_REC *dcc) +{ + GSList *tmp; + + g_return_if_fail(dcc != NULL); + + dcc_conns = g_slist_remove(dcc_conns, dcc); + + /* remove dcc chat references.. */ + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + DCC_REC *rec = tmp->data; + + if (rec->chat == dcc) + rec->chat = NULL; + } + + signal_emit("dcc destroyed", 1, dcc); + + if (dcc->fhandle != -1) close(dcc->fhandle); + if (dcc->handle != -1) net_disconnect(dcc->handle); + if (dcc->tagread != -1) g_source_remove(dcc->tagread); + if (dcc->tagwrite != -1) g_source_remove(dcc->tagwrite); + + if (dcc->dcc_type == DCC_TYPE_CHAT) + line_split_free((LINEBUF_REC *) dcc->databuf); + else if (dcc->databuf != NULL) g_free(dcc->databuf); + if (dcc->file != NULL) g_free(dcc->file); + if (dcc->ircnet != NULL) g_free(dcc->ircnet); + g_free(dcc->mynick); + g_free(dcc->nick); + g_free(dcc->arg); + g_free(dcc); +} + +gchar *dcc_make_address(IPADDR *ip) +{ + static gchar str[MAX_IP_LEN]; + gulong addr; + + if (is_ipv6_addr(ip)) + { + /* IPv6 */ + net_ip2host(ip, str); + } + else + { + memcpy(&addr, &ip->addr, 4); + sprintf(str, "%lu", (unsigned long) htonl(addr)); + } + + return str; +} + +/* Find DCC record, arg can be NULL */ +DCC_REC *dcc_find_item(gint type, gchar *nick, gchar *arg) +{ + DCC_REC *dcc; + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + dcc = tmp->data; + + if (dcc->dcc_type == type && g_strcasecmp(dcc->nick, nick) == 0 && + (arg == NULL || strcmp(dcc->arg, arg) == 0)) + return dcc; + } + + return NULL; +} + +/* Find DCC record by port # */ +DCC_REC *dcc_find_by_port(gchar *nick, gint port) +{ + DCC_REC *dcc; + GSList *tmp; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + dcc = tmp->data; + + if (dcc->port == port && ((dcc->dcc_type == DCC_TYPE_GET || dcc->dcc_type == DCC_TYPE_SEND) && g_strcasecmp(dcc->nick, nick) == 0)) + { + /* found! */ + return dcc; + } + } + + return NULL; +} + +gchar *dcc_type2str(gint type) +{ + g_return_val_if_fail(type >= 1 && type <= DCC_TYPES, NULL); + return dcc_types[type-1]; +} + +gint dcc_str2type(gchar *type) +{ + gint num; + + for (num = 0; num < DCC_TYPES; num++) + if (g_strcasecmp(dcc_types[num], type) == 0) return num+1; + + return 0; +} + +void dcc_ctcp_message(gchar *target, IRC_SERVER_REC *server, DCC_REC *chat, gboolean notice, gchar *msg) +{ + gchar *str; + + if (chat != NULL) + { + /* send it via open DCC chat */ + /* FIXME: we need output queue! */ + str = g_strdup_printf("%s\001%s\001\n", chat->mirc_ctcp ? "" : + notice ? "CTCP_REPLY " : "CTCP_MESSAGE ", msg); + net_transmit(chat->handle, str, strlen(str)); + } + else + { + str = g_strdup_printf("%s %s :\001%s\001", + notice ? "NOTICE" : "PRIVMSG", target, msg); + irc_send_cmd(server, str); + } + + g_free(str); +} + +/* Server connected, check if there's any open dcc sessions for this ircnet.. */ +static void dcc_server_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + if (server->connrec->ircnet == NULL) + return; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == NULL && dcc->ircnet != NULL && + g_strcasecmp(dcc->ircnet, server->connrec->ircnet) == 0) { + dcc->server = server; + g_free(dcc->mynick); + dcc->mynick = g_strdup(server->nick); + } + } +} + +/* Server disconnected, remove it from all dcc records */ +static void dcc_server_disconnected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) + { + DCC_REC *dcc = tmp->data; + + if (dcc->server == server) + { + if (dcc->ircnet == NULL) + dcc->server = NULL; + else + { + dcc->server = (IRC_SERVER_REC *) server_find_ircnet(dcc->ircnet); + if (dcc->server != NULL) + { + g_free(dcc->mynick); + dcc->mynick = g_strdup(dcc->server->nick); + } + } + } + } +} + +static void dcc_get_address(gchar *str, IPADDR *ip) +{ + gulong addr; + + if (strchr(str, ':') == NULL) + { + /* normal IPv4 address */ + if (sscanf(str, "%lu", &addr)!=1) + addr = 0; + ip->family = AF_INET; + addr = (gulong) ntohl(addr); + memcpy(&ip->addr, &addr, 4); + } + else + { + /* IPv6 */ + net_host2ip(str, ip); + } +} + +/* Handle incoming DCC CTCP messages */ +static void dcc_ctcp_msg(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr, gchar *target, DCC_REC *chat) +{ + gchar *params, *type, *arg, *addrstr, *portstr, *sizestr, *str; + const char *cstr; + DCC_REC *dcc; + gulong size; + gint port; + + g_return_if_fail(data != NULL); + g_return_if_fail(sender != NULL); + + params = cmd_get_params(data, 5, &type, &arg, &addrstr, &portstr, &sizestr); + + if (sscanf(portstr, "%d", &port) != 1) port = 0; + if (sscanf(sizestr, "%lu", &size) != 1) size = 0; + + dcc = dcc_create(SWAP_SENDGET(dcc_str2type(type)), -1, sender, arg, server, chat); + dcc_get_address(addrstr, &dcc->addr); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + dcc->size = size; + + switch (dcc->dcc_type) + { + case DCC_TYPE_GET: + cstr = settings_get_str("dcc_autoget_masks"); + /* check that autoget masks match */ + if (settings_get_bool("toggle_dcc_autoget") && (*cstr == '\0' || irc_masks_match(cstr, sender, sendaddr)) && + /* check file size limit, FIXME: it's possible to send a bogus file size and then just send what ever sized file.. */ + (settings_get_int("dcc_max_autoget_size") <= 0 || (settings_get_int("dcc_max_autoget_size") > 0 && size <= settings_get_int("dcc_max_autoget_size")*1024))) + { + /* automatically get */ + str = g_strdup_printf("GET %s %s", dcc->nick, dcc->arg); + signal_emit("command dcc", 2, str, server); + g_free(str); + } + else + { + /* send request */ + signal_emit("dcc request", 1, dcc); + } + break; + + case DCC_TYPE_CHAT: + cstr = settings_get_str("dcc_autochat_masks"); + if (*cstr != '\0' && irc_masks_match(cstr, sender, sendaddr)) + { + /* automatically accept chat */ + str = g_strdup_printf("CHAT %s", dcc->nick); + signal_emit("command dcc", 2, str, server); + g_free(str); + } + else + { + /* send request */ + signal_emit("dcc request", 1, dcc); + } + break; + + case DCC_TYPE_RESUME: + case DCC_TYPE_ACCEPT: + /* handle this in dcc-files.c */ + dcc_destroy(dcc); + break; + + default: + /* unknown DCC command */ + signal_emit("dcc unknown ctcp", 3, data, sender, sendaddr); + dcc_destroy(dcc); + break; + } + + g_free(params); +} + +/* Handle incoming DCC CTCP replies */ +static void dcc_ctcp_reply(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr) +{ + gchar *params, *cmd, *subcmd, *args; + gint type; + DCC_REC *dcc; + + g_return_if_fail(data != NULL); + g_return_if_fail(sender != NULL); + + params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &cmd, &subcmd, &args); + + if (g_strcasecmp(cmd, "REJECT") == 0) + { + type = dcc_str2type(subcmd); + dcc = dcc_find_item(type, sender, type == DCC_TYPE_CHAT ? NULL : args); + if (dcc != NULL) + { + dcc->destroyed = TRUE; + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); + } + } + else + { + /* unknown dcc ctcp reply */ + signal_emit("dcc unknown reply", 3, data, sender, sendaddr); + } + + g_free(params); +} + +static void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server) +{ + gchar *str; + + g_return_if_fail(dcc != NULL); + + if (dcc->server != NULL) server = dcc->server; + if (server != NULL && (dcc->dcc_type != DCC_TYPE_CHAT || dcc->starttime == 0)) + { + signal_emit("dcc rejected", 1, dcc); + str = g_strdup_printf("NOTICE %s :\001DCC REJECT %s %s\001", + dcc->nick, dcc_type2str(SWAP_SENDGET(dcc->dcc_type)), dcc->arg); + + irc_send_cmd(server, str); + g_free(str); + } + + dcc->destroyed = TRUE; + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); +} + +/* command: DCC CLOSE */ +static void cmd_dcc_close(gchar *data, IRC_SERVER_REC *server) +{ + DCC_REC *dcc; + GSList *tmp, *next; + gchar *params, *type, *nick, *arg; + gboolean found; + gint itype; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 3, &type, &nick, &arg); + + g_strup(type); + itype = dcc_str2type(type); + if (itype == 0) + { + signal_emit("dcc error unknown type", 1, type); + g_free(params); + return; + } + + dcc = NULL; found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) + { + dcc = tmp->data; + next = tmp->next; + + if (dcc->dcc_type == itype && g_strcasecmp(nick, dcc->nick) == 0) + { + dcc_reject(dcc, server); + found = TRUE; + } + } + + if (!found) + signal_emit("dcc error close not found", 3, type, nick, arg); + + g_free(params); +} + +static void cmd_dcc(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) +{ + command_runsub("dcc", data, server, item); +} + +static int dcc_timeout_func(void) +{ + GSList *tmp, *next; + time_t now; + + now = time(NULL)-settings_get_int("dcc_timeout"); + for (tmp = dcc_conns; tmp != NULL; tmp = next) + { + DCC_REC *rec = tmp->data; + + next = tmp->next; + if (rec->tagread == -1 && now > rec->created) + { + /* timed out. */ + dcc_reject(rec, NULL); + } + } + return 1; +} + +static void event_no_such_nick(gchar *data, IRC_SERVER_REC *server) +{ + gchar *params, *nick; + GSList *tmp, *next; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + + /* check if we've send any dcc requests to this nick.. */ + for (tmp = dcc_conns; tmp != NULL; tmp = next) + { + DCC_REC *rec = tmp->data; + + next = tmp->next; + if (g_strcasecmp(rec->nick, nick) == 0 && rec->starttime == 0) + { + /* timed out. */ + rec->destroyed = TRUE; + signal_emit("dcc closed", 1, rec); + dcc_destroy(rec); + } + } + + g_free(params); +} + +void dcc_init(void) +{ + dcc_conns = NULL; + dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL); + + settings_add_bool("dcc", "toggle_dcc_autorename", FALSE); + settings_add_bool("dcc", "toggle_dcc_autogete", FALSE); + settings_add_int("dcc", "dcc_max_autoget_size", 1000); + settings_add_str("dcc", "dcc_download_path", "~"); + settings_add_int("dcc", "dcc_file_create_mode", 644); + settings_add_str("dcc", "dcc_autoget_masks", ""); + settings_add_str("dcc", "dcc_autochat_masks", ""); + + settings_add_bool("dcc", "toggle_dcc_fast_send", TRUE); + settings_add_str("dcc", "dcc_upload_path", "~"); + + settings_add_bool("dcc", "toggle_dcc_mirc_ctcp", FALSE); + settings_add_bool("dcc", "toggle_dcc_autodisplay_dialog", TRUE); + settings_add_int("dcc", "dcc_block_size", 2048); + settings_add_int("dcc", "dcc_port", 0); + settings_add_int("dcc", "dcc_timeout", 300); + + signal_add("server connected", (SIGNAL_FUNC) dcc_server_connected); + signal_add("server disconnected", (SIGNAL_FUNC) dcc_server_disconnected); + signal_add("ctcp reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply); + signal_add("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg); + command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick); +} + +void dcc_deinit(void) +{ + signal_remove("server connected", (SIGNAL_FUNC) dcc_server_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) dcc_server_disconnected); + signal_remove("ctcp reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply); + signal_remove("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg); + command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick); + + g_source_remove(dcc_timeouttag); + + while (dcc_conns != NULL) + dcc_destroy(dcc_conns->data); +} diff --git a/src/irc/dcc/dcc.h b/src/irc/dcc/dcc.h new file mode 100644 index 00000000..da640b41 --- /dev/null +++ b/src/irc/dcc/dcc.h @@ -0,0 +1,91 @@ +#ifndef __DCC_H +#define __DCC_H + +#include "network.h" + +enum +{ + DCC_TYPE_CHAT = 1, + DCC_TYPE_SEND, + DCC_TYPE_GET, + DCC_TYPE_RESUME, + DCC_TYPE_ACCEPT +}; + +enum +{ + DCC_GET_DEFAULT = 0, + DCC_GET_OVERWRITE, + DCC_GET_RENAME, + DCC_GET_RESUME +}; + +#define SWAP_SENDGET(a) ((a) == DCC_TYPE_SEND ? DCC_TYPE_GET : \ + (a) == DCC_TYPE_GET ? DCC_TYPE_SEND : (a)) + +typedef struct DCC_REC +{ + int type; + GHashTable *module_data; + + IRC_SERVER_REC *server; + gchar *nick; + + struct DCC_REC *chat; /* if the request came through DCC chat */ + + gchar *ircnet; + gchar *mynick; + + gchar *arg; + gchar *file; /* file name we're really moving, arg is just the reference.. */ + + time_t created; + gint dcc_type; + + IPADDR addr; /* address we're connected in */ + gchar addrstr[MAX_IP_LEN]; /* in readable form */ + gint port; /* port we're connected in */ + + glong size, transfd, skipped; /* file size / bytes transferred / skipped at start */ + gint handle; /* socket handle */ + gint tagread, tagwrite; + gint fhandle; /* file handle */ + time_t starttime; /* transfer start time */ + gint trans_bytes; + + gboolean fastsend; /* fastsending (just in case that global fastsend toggle changes while transferring..) */ + gboolean waitforend; /* DCC fast send: file is sent, just wait for the replies from the other side */ + gboolean gotalldata; /* DCC fast send: got all acks from the other end (needed to make sure the end of transfer works right) */ + gint get_type; /* DCC get: what to do if file exists? */ + + gboolean mirc_ctcp; /* DCC chat: Send CTCPs without the CTCP_MESSAGE prefix */ + gboolean destroyed; /* We're about to destroy this DCC recond */ + + /* read counter buffer */ + gchar read_buf[4]; + gint read_pos; + + gchar *databuf; /* buffer for receiving/transmitting data */ + gint databufsize; +} +DCC_REC; + +extern GSList *dcc_conns; + +void dcc_init(void); +void dcc_deinit(void); + +/* Find DCC record, arg can be NULL */ +DCC_REC *dcc_find_item(gint type, gchar *nick, gchar *arg); +DCC_REC *dcc_find_by_port(gchar *nick, gint port); + +gchar *dcc_type2str(gint type); +gint dcc_str2type(gchar *type); +gchar *dcc_make_address(IPADDR *ip); + +DCC_REC *dcc_create(gint type, gint handle, gchar *nick, gchar *arg, IRC_SERVER_REC *server, DCC_REC *chat); +void dcc_destroy(DCC_REC *dcc); + +void dcc_ctcp_message(gchar *target, IRC_SERVER_REC *server, DCC_REC *chat, gboolean notice, gchar *msg); + +#endif diff --git a/src/irc/dcc/module.h b/src/irc/dcc/module.h new file mode 100644 index 00000000..2557ed0f --- /dev/null +++ b/src/irc/dcc/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "irc/dcc" diff --git a/src/irc/flood/Makefile.am b/src/irc/flood/Makefile.am new file mode 100644 index 00000000..1ebebff4 --- /dev/null +++ b/src/irc/flood/Makefile.am @@ -0,0 +1,12 @@ +noinst_LTLIBRARIES = libirc_flood.la + +INCLUDES = $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/ + +libirc_flood_la_SOURCES = \ + autoignore.c \ + flood.c + +noinst_HEADERS = \ + autoignore.h \ + flood.h diff --git a/src/irc/flood/autoignore.c b/src/irc/flood/autoignore.c new file mode 100644 index 00000000..528ac618 --- /dev/null +++ b/src/irc/flood/autoignore.c @@ -0,0 +1,250 @@ +/* + autoignore.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "settings.h" +#include "common-setup.h" + +#include "irc-server.h" +#include "ignore.h" + +#include "autoignore.h" + +static int ignore_tag; + +GSList *server_autoignores(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *rec; + + g_return_val_if_fail(server != NULL, NULL); + + rec = MODULE_DATA(server); + return rec->ignorelist; +} + +static void autoignore_remove_rec(IRC_SERVER_REC *server, AUTOIGNORE_REC *rec) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + g_return_if_fail(rec != NULL); + + signal_emit("autoignore remove", 2, server, rec); + + g_free(rec->nick); + g_free(rec); + + mserver = MODULE_DATA(server); + mserver->ignorelist = g_slist_remove(mserver->ignorelist, rec); +} + +static AUTOIGNORE_REC *autoignore_find(IRC_SERVER_REC *server, const char *mask) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(mask != NULL, NULL); + + mserver = MODULE_DATA(server); + for (tmp = mserver->ignorelist; tmp != NULL; tmp = tmp->next) { + AUTOIGNORE_REC *rec = tmp->data; + + if (g_strcasecmp(rec->nick, mask) == 0) + return rec; + } + + return NULL; +} + +/* timeout function: unignore old ignores.. */ +static void autoignore_timeout_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp, *next; + time_t t; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + t = time(NULL); + t -= mserver->ignore_lastcheck; + + for (tmp = mserver->ignorelist; tmp != NULL; tmp = next) { + AUTOIGNORE_REC *rec = tmp->data; + + next = tmp->next; + if (rec->timeleft > t) + rec->timeleft -= t; + else + autoignore_remove_rec(server, rec); + } + + mserver->ignore_lastcheck = time(NULL); +} + +static int autoignore_timeout(void) +{ + g_slist_foreach(servers, (GFunc) autoignore_timeout_server, NULL); + return 1; +} + +static void autoignore_init_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + mserver->ignorelist = NULL; + mserver->ignore_lastcheck = time(NULL)-AUTOIGNORE_TIMECHECK; +} + +static void autoignore_deinit_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + while (mserver->ignorelist != NULL) + autoignore_remove_rec(server, (AUTOIGNORE_REC *) mserver->ignorelist->data); +} + +IGNORE_REC *ignore_find_server(IRC_SERVER_REC *server, const char *mask) +{ + GSList *tmp; + + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (rec->servertag != NULL && + g_strcasecmp(rec->mask, mask) == 0 && + g_strcasecmp(rec->servertag, server->tag) == 0) + return rec; + } + + return NULL; +} + +void autoignore_add(IRC_SERVER_REC *server, const char *nick, int level) +{ + MODULE_SERVER_REC *mserver; + AUTOIGNORE_REC *rec; + IGNORE_REC *irec; + int igtime; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + if (level == 0) return; + + igtime = settings_get_int("autoignore_time"); + if (igtime <= 0) return; + + irec = ignore_find_server(server, nick); + if (irec == NULL) { + irec = g_new0(IGNORE_REC, 1); + irec->servertag = g_strdup(server->tag); + irec->mask = g_strdup(nick); + irec->level = level; + ignore_add_rec(irec); + } else { + irec->level |= level; + ignore_update_rec(irec); + } + + rec = autoignore_find(server, nick); + if (rec != NULL) { + /* already being ignored */ + rec->timeleft = igtime; + return; + } + + rec = g_new(AUTOIGNORE_REC, 1); + rec->nick = g_strdup(nick); + rec->timeleft = igtime; + rec->level = level; + + mserver = MODULE_DATA(server); + mserver->ignorelist = g_slist_append(mserver->ignorelist, rec); + + signal_emit("autoignore new", 2, server, rec); +} + +int autoignore_remove(IRC_SERVER_REC *server, const char *mask, int level) +{ + AUTOIGNORE_REC *rec; + IGNORE_REC *irec; + + g_return_val_if_fail(server != NULL, FALSE); + g_return_val_if_fail(mask != NULL, FALSE); + + irec = ignore_find_server(server, mask); + if (irec != NULL) { + irec->level &= ~level; + ignore_update_rec(irec); + } + + rec = autoignore_find(server, mask); + if (rec != NULL && (level & rec->level)) { + rec->level &= ~level; + if (rec->level == 0) autoignore_remove_rec(server, rec); + return TRUE; + } + + return FALSE; +} + +static void sig_flood(IRC_SERVER_REC *server, const char *nick, const char *host, const char *levelstr) +{ + int level, check_level; + + level = level2bits(levelstr); + check_level = level2bits(settings_get_str("autoignore_levels")); + + if (level & check_level) + autoignore_add(server, nick, level); +} + +void autoignore_init(void) +{ + settings_add_int("flood", "autoignore_time", 300); + settings_add_str("flood", "autoignore_levels", "ctcps"); + + ignore_tag = g_timeout_add(AUTOIGNORE_TIMECHECK, (GSourceFunc) autoignore_timeout, NULL); + + signal_add("server connected", (SIGNAL_FUNC) autoignore_init_server); + signal_add("server disconnected", (SIGNAL_FUNC) autoignore_deinit_server); + signal_add("flood", (SIGNAL_FUNC) sig_flood); +} + +void autoignore_deinit(void) +{ + g_source_remove(ignore_tag); + + signal_remove("server connected", (SIGNAL_FUNC) autoignore_init_server); + signal_remove("server disconnected", (SIGNAL_FUNC) autoignore_deinit_server); + signal_remove("flood", (SIGNAL_FUNC) sig_flood); +} diff --git a/src/irc/flood/autoignore.h b/src/irc/flood/autoignore.h new file mode 100644 index 00000000..efa01d27 --- /dev/null +++ b/src/irc/flood/autoignore.h @@ -0,0 +1,18 @@ +#ifndef __AUTOIGNORE_H +#define __AUTOIGNORE_H + +typedef struct { + char *nick; + int timeleft; + int level; +} AUTOIGNORE_REC; + +GSList *server_autoignores(IRC_SERVER_REC *server); + +void autoignore_add(IRC_SERVER_REC *server, const char *nick, int level); +int autoignore_remove(IRC_SERVER_REC *server, const char *mask, int level); + +void autoignore_init(void); +void autoignore_deinit(void); + +#endif diff --git a/src/irc/flood/flood.c b/src/irc/flood/flood.c new file mode 100644 index 00000000..5522243e --- /dev/null +++ b/src/irc/flood/flood.c @@ -0,0 +1,212 @@ +/* + + flood.c : Flood protection (see also ctcp.c) + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" +#include "levels.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "irc-server.h" +#include "autoignore.h" +#include "ignore.h" + +typedef struct { + char *nick; + int level; + int msgcount; +} FLOOD_REC; + +static int flood_tag; +static int flood_max_msgs; + +static int flood_hash_deinit(const char *key, FLOOD_REC *rec) +{ + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(rec != NULL, FALSE); + + g_free(rec->nick); + g_free(rec); + return TRUE; +} + +/* timeout function: flood protection */ +static int flood_timeout(void) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp; + + /* remove everyone from flood list */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + mserver = MODULE_DATA(rec); + g_hash_table_foreach_remove(mserver->floodlist, (GHRFunc) flood_hash_deinit, NULL); + } + return 1; +} + +/* Initialize flood protection */ +static void flood_init_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *rec; + + g_return_if_fail(server != NULL); + + rec = g_new0(MODULE_SERVER_REC, 1); + MODULE_DATA_SET(server, rec); + + rec->floodlist = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); +} + +/* Deinitialize flood protection */ +static void flood_deinit_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + if (mserver != NULL && mserver->floodlist != NULL) { + g_hash_table_freeze(mserver->floodlist); + g_hash_table_foreach(mserver->floodlist, (GHFunc) flood_hash_deinit, NULL); + g_hash_table_thaw(mserver->floodlist); + g_hash_table_destroy(mserver->floodlist); + } + g_free(mserver); +} + +/* All messages should go through here.. */ +static void flood_newmsg(IRC_SERVER_REC *server, int level, const char *nick, const char *host, const char *target) +{ + MODULE_SERVER_REC *mserver; + FLOOD_REC *rec; + char *levelstr; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + mserver = MODULE_DATA(server); + rec = g_hash_table_lookup(mserver->floodlist, nick); + if (rec != NULL) { + if (++rec->msgcount > flood_max_msgs) { + /* flooding! */ + levelstr = bits2level(rec->level); + signal_emit("flood", 5, server, nick, host, levelstr, target); + g_free(levelstr); + } + return; + } + + rec = g_new(FLOOD_REC, 1); + rec->level = level; + rec->msgcount = 1; + rec->nick = g_strdup(nick); + + g_hash_table_insert(mserver->floodlist, rec->nick, rec); +} + +static void flood_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + int publiclevel; + char *params, *target, *text; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + if (nick == NULL) { + /* don't try to ignore server messages.. */ + return; + } + + params = event_get_params(data, 2, &target, &text); + + if (*text == 1) { + /* CTCP */ + if (!ignore_check(server, nick, addr, target, text, MSGLEVEL_CTCPS)) + flood_newmsg(server, MSGLEVEL_CTCPS, nick, addr, target); + } else { + publiclevel = ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; + + if (addr != NULL && !ignore_check(server, nick, addr, target, text, publiclevel)) + flood_newmsg(server, publiclevel, nick, addr, target); + } + + g_free(params); +} + +static void flood_notice(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr) +{ + char *params, *target, *text; + + g_return_if_fail(text != NULL); + g_return_if_fail(server != NULL); + + if (nick == NULL) { + /* don't try to ignore server messages.. */ + return; + } + + params = event_get_params(data, 2, &target, &text); + if (addr != NULL && !ignore_check(server, nick, addr, target, text, MSGLEVEL_NOTICES)) + flood_newmsg(server, MSGLEVEL_NOTICES | ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS, nick, addr, target); + + g_free(params); +} + +static void read_settings(void) +{ + if (flood_tag != -1) g_source_remove(flood_tag); + flood_tag = g_timeout_add(settings_get_int("flood_timecheck"), (GSourceFunc) flood_timeout, NULL); + + flood_max_msgs = settings_get_int("flood_max_msgs"); +} + +void flood_init(void) +{ + settings_add_int("flood", "flood_timecheck", 1000); + settings_add_int("flood", "flood_max_msgs", 5); + + flood_tag = -1; + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add_first("server connected", (SIGNAL_FUNC) flood_init_server); + signal_add("server disconnected", (SIGNAL_FUNC) flood_deinit_server); + signal_add("event privmsg", (SIGNAL_FUNC) flood_privmsg); + signal_add("event notice", (SIGNAL_FUNC) flood_notice); + + autoignore_init(); +} + +void flood_deinit(void) +{ + autoignore_deinit(); + + if (flood_tag != -1) g_source_remove(flood_tag); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("server connected", (SIGNAL_FUNC) flood_init_server); + signal_remove("server disconnected", (SIGNAL_FUNC) flood_deinit_server); + signal_remove("event privmsg", (SIGNAL_FUNC) flood_privmsg); + signal_remove("event notice", (SIGNAL_FUNC) flood_notice); +} diff --git a/src/irc/flood/flood.h b/src/irc/flood/flood.h new file mode 100644 index 00000000..e6454729 --- /dev/null +++ b/src/irc/flood/flood.h @@ -0,0 +1,7 @@ +#ifndef __FLOOD_H +#define __FLOOD_H + +void flood_init(void); +void flood_deinit(void); + +#endif diff --git a/src/irc/flood/module.h b/src/irc/flood/module.h new file mode 100644 index 00000000..9abdbf41 --- /dev/null +++ b/src/irc/flood/module.h @@ -0,0 +1,12 @@ +#include "common.h" + +typedef struct { + /* Flood protection */ + GHashTable *floodlist; + + /* Auto ignore list */ + GSList *ignorelist; + time_t ignore_lastcheck; +} MODULE_SERVER_REC; + +#define MODULE_NAME "irc/flood" diff --git a/src/irc/irc.c b/src/irc/irc.c new file mode 100644 index 00000000..609e239b --- /dev/null +++ b/src/irc/irc.c @@ -0,0 +1,27 @@ +void irc_core_init(void); +void irc_core_deinit(void); + +void dcc_init(void); +void dcc_deinit(void); + +void flood_init(void); +void flood_deinit(void); + +void notifylist_init(void); +void notifylist_deinit(void); + +void irc_init(void) +{ + irc_core_init(); + dcc_init(); + flood_init(); + notifylist_init(); +} + +void irc_deinit(void) +{ + notifylist_deinit(); + flood_deinit(); + dcc_deinit(); + irc_core_deinit(); +} diff --git a/src/irc/notifylist/Makefile.am b/src/irc/notifylist/Makefile.am new file mode 100644 index 00000000..48dd5640 --- /dev/null +++ b/src/irc/notifylist/Makefile.am @@ -0,0 +1,15 @@ +noinst_LTLIBRARIES = libirc_notifylist.la + +INCLUDES = $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/ + +libirc_notifylist_la_SOURCES = \ + notifylist.c \ + notify-commands.c \ + notify-ison.c \ + notify-setup.c \ + notify-whois.c + +noinst_HEADERS = \ + notifylist.h \ + notify-setup.h diff --git a/src/irc/notifylist/module.h b/src/irc/notifylist/module.h new file mode 100644 index 00000000..ac0eadac --- /dev/null +++ b/src/irc/notifylist/module.h @@ -0,0 +1,44 @@ +#include "common.h" + +#define MODULE_NAME "irc/notifylist" + +#define ISON_EVENT "event 303" + +typedef struct { + char *nick; + char *user, *host, *realname, *awaymsg; + time_t idle_time; + + int host_ok:1; /* host matches the one in notifylist = this is the right person*/ + int away_ok:1; /* not away, or we don't care about it */ + int idle_ok:1; /* idle time is low enough, or we don't care about it */ + + int away:1; /* nick is away */ + int join_announced:1; /* join to IRC has been announced */ + int idle_changed:1; /* idle time is lower than in last check */ + + time_t last_whois; +} NOTIFY_NICK_REC; + +typedef struct { + GSList *notify_users; /* NOTIFY_NICK_REC's of notifylist people who are in IRC */ + GSList *ison_tempusers; /* Temporary list for saving /ISON events.. */ +} MODULE_SERVER_REC; + +#include "irc-server.h" + +NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick); +void notify_nick_destroy(NOTIFY_NICK_REC *rec); +NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick); + +void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec); +void notifylist_destroy_all(void); + +void notifylist_commands_init(void); +void notifylist_commands_deinit(void); + +void notifylist_whois_init(void); +void notifylist_whois_deinit(void); + +void notifylist_ison_init(void); +void notifylist_ison_deinit(void); diff --git a/src/irc/notifylist/notify-commands.c b/src/irc/notifylist/notify-commands.c new file mode 100644 index 00000000..9ae5a076 --- /dev/null +++ b/src/irc/notifylist/notify-commands.c @@ -0,0 +1,81 @@ +/* + notify-commands.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "settings.h" + +#include "notifylist.h" + +#define DEFAULT_NOTIFY_IDLE_TIME 60 + +static void cmd_notify(gchar *data) +{ + char *params, *mask, *ircnets, *args, *idletime; + int away_check, idle_check_time; + + g_return_if_fail(data != NULL); + + args = "@idle"; + params = cmd_get_params(data, 4 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, &args, &idletime, &mask, &ircnets); + if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (stristr(args, "-idle") == NULL) + idle_check_time = 0; + else { + idle_check_time = is_numeric(idletime, 0) ? (atol(idletime)*60) : + (settings_get_int("notify_idle_time")*60); + } + + away_check = stristr(args, "-away") != NULL; + notifylist_remove(mask); + notifylist_add(mask, ircnets, away_check, idle_check_time); + + g_free(params); +} + +static void cmd_unnotify(const char *data) +{ + char *params, *mask; + + g_return_if_fail(data != NULL); + + params = cmd_get_params(data, 1, &mask); + if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + notifylist_remove(mask); + + g_free(params); +} + +void notifylist_commands_init(void) +{ + settings_add_int("misc", "notify_idle_time", DEFAULT_NOTIFY_IDLE_TIME); + command_bind("notify", NULL, (SIGNAL_FUNC) cmd_notify); + command_bind("unnotify", NULL, (SIGNAL_FUNC) cmd_unnotify); +} + +void notifylist_commands_deinit(void) +{ + command_unbind("notify", (SIGNAL_FUNC) cmd_notify); + command_unbind("unnotify", (SIGNAL_FUNC) cmd_unnotify); +} diff --git a/src/irc/notifylist/notify-ison.c b/src/irc/notifylist/notify-ison.c new file mode 100644 index 00000000..46aaa3b6 --- /dev/null +++ b/src/irc/notifylist/notify-ison.c @@ -0,0 +1,354 @@ +/* + notify-ison.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "irc-server.h" +#include "server-redirect.h" + +#include "notifylist.h" + +#define DEFAULT_NOTIFY_CHECK_TIME 60 +#define DEFAULT_NOTIFY_WHOIS_TIME (60*5) + +typedef struct { + char *nick; + int hostok; +} ISON_REC; + +static int notify_tag; +static int notify_whois_time; + +NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + + mserver = MODULE_DATA(server); + + rec = g_new0(NOTIFY_NICK_REC, 1); + rec->nick = g_strdup(nick); + + mserver->notify_users = g_slist_append(mserver->notify_users, rec); + return rec; +} + +void notify_nick_destroy(NOTIFY_NICK_REC *rec) +{ + g_free(rec->nick); + g_free_not_null(rec->user); + g_free_not_null(rec->host); + g_free_not_null(rec->realname); + g_free_not_null(rec->awaymsg); + g_free(rec); +} + +NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + GSList *tmp; + + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (g_strcasecmp(rec->nick, nick) == 0) + return rec; + } + + return NULL; +} + +static int is_ison_queue_empty(IRC_SERVER_REC *server) +{ + GSList *tmp; + + tmp = server_redirect_getqueue((SERVER_REC *) server, ISON_EVENT, NULL); + for (; tmp != NULL; tmp = tmp->next) { + REDIRECT_REC *rec = tmp->data; + + if (strcmp(rec->name, "notifylist event") == 0) + return FALSE; + } + + return TRUE; +} + +static void ison_send(IRC_SERVER_REC *server, GString *cmd) +{ + g_string_truncate(cmd, cmd->len-1); + g_string_prepend(cmd, "ISON :"); + + irc_send_cmd(server, cmd->str); + server_redirect_event((SERVER_REC *) server, NULL, 1, ISON_EVENT, "notifylist event", -1, NULL); + + g_string_truncate(cmd, 0); +} + +/* timeout function: send /ISON commands to server to check if someone in + notify list is in IRC */ +static void notifylist_timeout_server(IRC_SERVER_REC *server) +{ + GSList *tmp; + GString *cmd; + char *nick, *ptr; + int len; + + g_return_if_fail(server != NULL); + + if (!is_ison_queue_empty(server)) { + /* still not received all replies to previous /ISON commands.. */ + return; + } + + cmd = g_string_new(NULL); + for (tmp = notifies; tmp != NULL; tmp = tmp->next) { + NOTIFYLIST_REC *rec = tmp->data; + + if (!notify_ircnets_match(rec, server->connrec->ircnet)) + continue; + + nick = g_strdup(rec->mask); + ptr = strchr(nick, '!'); + if (ptr != NULL) *ptr = '\0'; + + len = strlen(nick); + + if (cmd->len+len+1 > 510) + ison_send(server, cmd); + + g_string_sprintfa(cmd, "%s ", nick); + g_free(nick); + } + + if (cmd->len > 0) + ison_send(server, cmd); + g_string_free(cmd, TRUE); +} + +static int notifylist_timeout_func(void) +{ + g_slist_foreach(servers, (GFunc) notifylist_timeout_server, NULL); + return 1; +} + +static void ison_save_users(MODULE_SERVER_REC *mserver, char *online) +{ + char *ptr; + + while (online != NULL && *online != '\0') { + ptr = strchr(online, ' '); + if (ptr != NULL) *ptr++ = '\0'; + + mserver->ison_tempusers = + g_slist_append(mserver->ison_tempusers, g_strdup(online)); + online = ptr; + } +} + +static void whois_send(IRC_SERVER_REC *server, char *nicks) +{ + char *p, *str; + + irc_send_cmdv(server, "WHOIS %s", nicks); + + /* "nick1,nick2" -> "nick1,nick2 nick1 nick2" because + End of WHOIS give nick1,nick2 while other whois events give + nick1 or nick2 */ + str = g_strconcat(nicks, " ", nicks, NULL); + for (p = str+strlen(nicks)+1; *p != '\0'; p++) + if (*p == ',') *p = ' '; + + server_redirect_event((SERVER_REC *) server, str, 2, + "event 318", "notifylist event whois end", 1, + "event 402", "event empty", -1, + "event 401", "event empty", 1, + "event 311", "notifylist event whois", 1, + "event 301", "notifylist event whois away", 1, + "event 312", "event empty", 1, + "event 313", "event empty", 1, + "event 317", "notifylist event whois idle", 1, + "event 319", "event empty", 1, NULL); + g_free(str); +} + +static void whois_send_server(IRC_SERVER_REC *server, char *nick) +{ + char *str; + + str = g_strdup_printf("%s %s", nick, nick); + whois_send(server, str); + g_free(str); +} + +/* try to send as many nicks in one WHOIS as possible */ +static void whois_list_send(IRC_SERVER_REC *server, GSList *nicks) +{ + GSList *tmp; + GString *str; + char *nick; + int count; + + str = g_string_new(NULL); + count = 0; + + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + nick = tmp->data; + + count++; + g_string_sprintfa(str, "%s,", nick); + + if (count >= server->max_whois_in_cmd) { + g_string_truncate(str, str->len-1); + whois_send(server, str->str); + count = 0; + } + } + + if (str->len > 0) { + g_string_truncate(str, str->len-1); + whois_send(server, str->str); + } + + g_string_free(str, TRUE); +} + +static void ison_check_joins(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + GSList *tmp, *newnicks; + int send_whois; + time_t now; + + mserver = MODULE_DATA(server); + + now = time(NULL); + newnicks = NULL; + for (tmp = mserver->ison_tempusers; tmp != NULL; tmp = tmp->next) { + char *nick = tmp->data; + + notify = notifylist_find(nick, server->connrec->ircnet); + send_whois = notify != NULL && + (notify->away_check || notify->idle_check_time > 0); + + rec = notify_nick_find(server, nick); + if (rec != NULL) { + /* check if we want to send WHOIS yet.. */ + if (now-rec->last_whois < notify_whois_time) + continue; + } else { + rec = notify_nick_create(server, nick); + if (!send_whois) newnicks = g_slist_append(newnicks, nick); + } + + if (send_whois) { + /* we need away message or idle time - + send the WHOIS reply to the nick's server */ + rec->last_whois = now; + whois_send_server(server, nick); + } + } + + whois_list_send(server, newnicks); + g_slist_free(newnicks); +} + +static void ison_check_parts(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp, *next; + + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = next) { + NOTIFY_NICK_REC *rec = tmp->data; + next = tmp->next; + + if (gslist_find_icase_string(mserver->ison_tempusers, rec->nick) != NULL) + continue; + + notifylist_left(server, rec); + notify_nick_destroy(rec); + } +} + +static void event_ison(const char *data, IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + char *params, *online; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &online); + + mserver = MODULE_DATA(server); + ison_save_users(mserver, online); + + if (!is_ison_queue_empty(server)) { + /* wait for the rest of the /ISON replies */ + g_free(params); + return; + } + + ison_check_joins(server); + ison_check_parts(server); + + /* free memory used by temp list */ + g_slist_foreach(mserver->ison_tempusers, (GFunc) g_free, NULL); + g_slist_free(mserver->ison_tempusers); + mserver->ison_tempusers = NULL; + + g_free(params); +} + +static void read_settings(void) +{ + if (notify_tag != -1) g_source_remove(notify_tag); + notify_tag = g_timeout_add(1000*settings_get_int("notify_check_time"), (GSourceFunc) notifylist_timeout_func, NULL); + + notify_whois_time = settings_get_int("notify_whois_time"); +} + +void notifylist_ison_init(void) +{ + settings_add_int("misc", "notify_check_time", DEFAULT_NOTIFY_CHECK_TIME); + settings_add_int("misc", "notify_whois_time", DEFAULT_NOTIFY_WHOIS_TIME); + + notify_tag = -1; + read_settings(); + + signal_add("notifylist event", (SIGNAL_FUNC) event_ison); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void notifylist_ison_deinit(void) +{ + g_source_remove(notify_tag); + + signal_remove("notifylist event", (SIGNAL_FUNC) event_ison); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/irc/notifylist/notify-setup.c b/src/irc/notifylist/notify-setup.c new file mode 100644 index 00000000..6ecbfa27 --- /dev/null +++ b/src/irc/notifylist/notify-setup.c @@ -0,0 +1,84 @@ +/* + notify-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "irc-server.h" +#include "notifylist.h" + +void notifylist_add_config(NOTIFYLIST_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("notifies", TRUE); + node = config_node_section(node, rec->mask, NODE_TYPE_BLOCK); + + if (rec->away_check) + config_node_set_bool(node, "away_check", TRUE); + else + config_node_set_str(node, "away_check", NULL); + + if (rec->idle_check_time > 0) + config_node_set_int(node, "idle_check_time", rec->idle_check_time/60); + else + config_node_set_str(node, "idle_check_time", NULL); + + config_node_set_str(node, "ircnets", NULL); + if (rec->ircnets != NULL && *rec->ircnets != NULL) { + node = config_node_section(node, "ircnets", NODE_TYPE_LIST); + config_node_add_list(node, rec->ircnets); + } +} + +void notifylist_remove_config(NOTIFYLIST_REC *rec) +{ + iconfig_set_str("notifies", rec->mask, NULL); +} + +void notifylist_read_config(void) +{ + CONFIG_NODE *node; + NOTIFYLIST_REC *rec; + GSList *tmp; + + notifylist_destroy_all(); + + node = iconfig_node_traverse("notifies", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + rec = g_new0(NOTIFYLIST_REC, 1); + notifies = g_slist_append(notifies, rec); + + rec->mask = g_strdup(node->key); + rec->away_check = config_node_get_bool(node, "away_check", FALSE); + rec->idle_check_time = config_node_get_int(node, "idle_check_time", 0)*60; + + node = config_node_section(node, "ircnets", -1); + if (node != NULL) rec->ircnets = config_node_get_list(node); + } +} diff --git a/src/irc/notifylist/notify-setup.h b/src/irc/notifylist/notify-setup.h new file mode 100644 index 00000000..bfaef0c8 --- /dev/null +++ b/src/irc/notifylist/notify-setup.h @@ -0,0 +1,9 @@ +#ifndef __NOTIFY_SETUP_H +#define __NOTIFY_SETUP_H + +void notifylist_add_config(NOTIFYLIST_REC *rec); +void notifylist_remove_config(NOTIFYLIST_REC *rec); + +void notifylist_read_config(void); + +#endif diff --git a/src/irc/notifylist/notify-whois.c b/src/irc/notifylist/notify-whois.c new file mode 100644 index 00000000..439a8af8 --- /dev/null +++ b/src/irc/notifylist/notify-whois.c @@ -0,0 +1,186 @@ +/* + notify-whois.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "special-vars.h" + +#include "irc.h" +#include "irc-server.h" +#include "masks.h" + +#include "notifylist.h" + +static char *last_notify_nick; + +static void event_whois(const char *data, IRC_SERVER_REC *server) +{ + char *params, *nick, *user, *host, *realname; + NOTIFY_NICK_REC *nickrec; + NOTIFYLIST_REC *notify; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname); + + notify = notifylist_find(nick, server->connrec->ircnet); + if (notify != NULL && !irc_mask_match(notify->mask, nick, user, host)) { + /* user or host didn't match */ + g_free(params); + return; + } + + nickrec = notify_nick_find(server, nick); + if (nickrec != NULL) { + g_free_not_null(last_notify_nick); + last_notify_nick = g_strdup(nick); + + g_free_not_null(nickrec->user); + g_free_not_null(nickrec->host); + g_free_not_null(nickrec->realname); + g_free_and_null(nickrec->awaymsg); + nickrec->user = g_strdup(user); + nickrec->host = g_strdup(host); + nickrec->realname = g_strdup(realname); + + nickrec->away = FALSE; + nickrec->host_ok = TRUE; + nickrec->idle_ok = TRUE; + } + g_free(params); +} + +static void event_whois_idle(const char *data, IRC_SERVER_REC *server) +{ + NOTIFY_NICK_REC *nickrec; + NOTIFYLIST_REC *notify; + char *params, *nick, *secstr; + long secs; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &secstr); + secs = atol(secstr); + + notify = notifylist_find(nick, server->connrec->ircnet); + nickrec = notify_nick_find(server, nick); + if (notify != NULL && nickrec != NULL) { + time_t now = time(NULL); + nickrec->idle_changed = secs < now-nickrec->idle_time && + now-nickrec->idle_time > notify->idle_check_time; + + nickrec->idle_time = now-secs; + } + + g_free(params); +} + +static void event_whois_away(const char *data, IRC_SERVER_REC *server) +{ + NOTIFY_NICK_REC *nickrec; + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + + nickrec = notify_nick_find(server, nick); + if (nickrec != NULL) { + nickrec->awaymsg = g_strdup(awaymsg); + nickrec->away = TRUE; + } + + g_free(params); +} + +/* All WHOIS replies got, now announce all the changes at once. */ +static void event_whois_end(const char *data, IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + GSList *tmp; + const char *event; + int away_ok; + time_t now; + + now = time(NULL); + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (rec->realname == NULL) + continue; + + notify = notifylist_find(rec->nick, server->connrec->ircnet); + if (notify == NULL) continue; + + away_ok = !notify->away_check || !rec->away; + + event = NULL; + if (!rec->join_announced) { + rec->join_announced = TRUE; + rec->idle_time = now; + if (away_ok) event = "notifylist joined"; + } else if (notify->away_check && rec->away_ok == rec->away) + event = "notifylist away changed"; + else if (notify->idle_check_time > 0 && rec->idle_changed) + event = "notifylist unidle"; + + if (event != NULL) { + signal_emit(event, 6, server, rec->nick, + rec->user, rec->host, + rec->realname, rec->awaymsg); + } + rec->idle_ok = notify->idle_check_time <= 0 || + now-rec->idle_time <= notify->idle_check_time; + rec->idle_changed = FALSE; + rec->away_ok = away_ok; + } +} + +/* last person that NOTIFY detected a signon for */ +static char *expando_lastnotify(void *server, void *item, int *free_ret) +{ + return last_notify_nick; +} + +void notifylist_whois_init(void) +{ + last_notify_nick = NULL; + + signal_add("notifylist event whois", (SIGNAL_FUNC) event_whois); + signal_add("notifylist event whois away", (SIGNAL_FUNC) event_whois_away); + signal_add("notifylist event whois idle", (SIGNAL_FUNC) event_whois_idle); + signal_add("notifylist event whois end", (SIGNAL_FUNC) event_whois_end); + expando_create("D", expando_lastnotify); +} + +void notifylist_whois_deinit(void) +{ + g_free_not_null(last_notify_nick); + + signal_remove("notifylist event whois", (SIGNAL_FUNC) event_whois); + signal_remove("notifylist event whois away", (SIGNAL_FUNC) event_whois_away); + signal_remove("notifylist event whois idle", (SIGNAL_FUNC) event_whois_idle); + signal_remove("notifylist event whois end", (SIGNAL_FUNC) event_whois_end); + expando_destroy("D", expando_lastnotify); +} diff --git a/src/irc/notifylist/notifylist.c b/src/irc/notifylist/notifylist.c new file mode 100644 index 00000000..00561dc9 --- /dev/null +++ b/src/irc/notifylist/notifylist.c @@ -0,0 +1,356 @@ +/* + notifylist.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" + +#include "irc.h" +#include "irc-server.h" +#include "server-redirect.h" +#include "masks.h" +#include "nicklist.h" + +#include "notifylist.h" +#include "notify-setup.h" + +GSList *notifies; + +NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets, + int away_check, int idle_check_time) +{ + NOTIFYLIST_REC *rec; + + g_return_val_if_fail(mask != NULL, NULL); + + rec = g_new0(NOTIFYLIST_REC, 1); + rec->mask = g_strdup(mask); + rec->ircnets = ircnets == NULL || *ircnets == '\0' ? NULL : + g_strsplit(ircnets, " ", -1); + rec->away_check = away_check; + rec->idle_check_time = idle_check_time; + + notifylist_add_config(rec); + + notifies = g_slist_append(notifies, rec); + signal_emit("notifylist new", 1, rec); + return rec; +} + +static void notify_destroy(NOTIFYLIST_REC *rec) +{ + if (rec->ircnets != NULL) g_strfreev(rec->ircnets); + g_free(rec->mask); + g_free(rec); +} + +void notifylist_destroy_all(void) +{ + g_slist_foreach(notifies, (GFunc) notify_destroy, NULL); + g_slist_free(notifies); + + notifies = NULL; +} + +void notifylist_remove(const char *mask) +{ + NOTIFYLIST_REC *rec; + + g_return_if_fail(mask != NULL); + + rec = notifylist_find(mask, "*"); + if (rec == NULL) return; + + notifylist_remove_config(rec); + notifies = g_slist_remove(notifies, rec); + signal_emit("notifylist remove", 1, rec); + + notify_destroy(rec); +} + +int notify_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet) +{ + char **tmp; + + if (rec->ircnets == NULL) return TRUE; + if (ircnet == NULL) return FALSE; + if (strcmp(ircnet, "*") == 0) return TRUE; + + for (tmp = rec->ircnets; *tmp != NULL; tmp++) { + if (g_strcasecmp(*tmp, ircnet) == 0) + return TRUE; + } + + return FALSE; +} + +NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet) +{ + NOTIFYLIST_REC *best; + GSList *tmp; + int len; + + best = NULL; + len = strlen(mask); + for (tmp = notifies; tmp != NULL; tmp = tmp->next) { + NOTIFYLIST_REC *rec = tmp->data; + + /* check mask */ + if (g_strncasecmp(rec->mask, mask, len) != 0 || + (rec->mask[len] != '\0' && rec->mask[len] != '!')) continue; + + /* check ircnet */ + if (rec->ircnets == NULL) { + best = rec; + continue; + } + + if (notify_ircnets_match(rec, ircnet)) + return rec; + } + + return best; +} + +int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick) +{ + NOTIFY_NICK_REC *rec; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(server != NULL, FALSE); + + rec = notify_nick_find(server, nick); + return rec != NULL && rec->host_ok && rec->away_ok && rec->idle_ok; +} + +static IRC_SERVER_REC *notifylist_ison_serverlist(const char *nick, const char *taglist) +{ + IRC_SERVER_REC *server; + char **list, **tmp; + + list = g_strsplit(taglist, " ", -1); + + server = NULL; + for (tmp = list; *tmp != NULL; tmp++) { + server = (IRC_SERVER_REC *) server_find_ircnet(*tmp); + + if (server != NULL && notifylist_ison_server(server, nick)) + break; + } + g_strfreev(list); + + return tmp == NULL ? NULL : server; +} + +IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(serverlist != NULL, FALSE); + + if (*serverlist != '\0') + return notifylist_ison_serverlist(nick, serverlist); + + /* any server.. */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + if (notifylist_ison_server(tmp->data, nick)) + return tmp->data; + } + + return NULL; +} + +static void notifylist_init_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *rec; + + g_return_if_fail(server != NULL); + + rec = g_new0(MODULE_SERVER_REC,1 ); + MODULE_DATA_SET(server, rec); + + server_redirect_init((SERVER_REC *) server, "command ison", 1, ISON_EVENT, NULL); +} + +static void notifylist_deinit_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + while (mserver->notify_users != NULL) { + rec = mserver->notify_users->data; + + mserver->notify_users = g_slist_remove(mserver->notify_users, rec); + notify_nick_destroy(rec); + } + g_free(mserver); +} + +void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec) +{ + MODULE_SERVER_REC *mserver; + + mserver = MODULE_DATA(server); + mserver->notify_users = g_slist_remove(mserver->notify_users, rec); + + if (rec->host_ok && rec->away_ok) { + signal_emit("notifylist left", 6, + server, rec->nick, + rec->user, rec->host, + rec->realname, rec->awaymsg); + } +} + +static void notifylist_idle_reset(IRC_SERVER_REC *server, const char *nick) +{ + NOTIFY_NICK_REC *rec; + NOTIFYLIST_REC *notify; + + notify = notifylist_find(nick, server->connrec->ircnet); + rec = notify_nick_find(server, nick); + + if (notify != NULL && rec != NULL && notify->idle_check_time > 0 && + time(NULL)-rec->idle_time > notify->idle_check_time) { + rec->idle_time = time(NULL); + signal_emit("notifylist unidle", 6, + server, rec->nick, + rec->user, rec->host, + rec->realname, rec->awaymsg); + } +} + +static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick) +{ + NOTIFY_NICK_REC *rec; + + if (*data == ':') data++; /* quit message */ + + rec = notify_nick_find(server, nick); + if (rec != NULL) notifylist_left(server, rec); +} + +static void notifylist_check_join(IRC_SERVER_REC *server, const char *nick, + const char *userhost, const char *realname, int away) +{ + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + char *user, *host; + + notify = notifylist_find(nick, server->connrec->ircnet); + if (notify == NULL) return; + + rec = notify_nick_find(server, nick); + if (rec != NULL && rec->join_announced) return; + if (rec == NULL) rec = notify_nick_create(server, nick); + + user = g_strdup(userhost); + host = strchr(user, '@'); + if (host != NULL) *host++ = '\0'; else host = ""; + + if (!irc_mask_match(notify->mask, nick, user, host)) { + g_free(user); + return; + } + + if (notify->away_check && away == -1) { + /* we need to know if the nick is away */ + g_free(user); + return; + } + + g_free_not_null(rec->user); + g_free_not_null(rec->host); + g_free_not_null(rec->realname); + rec->user = g_strdup(user); + rec->host = g_strdup(host); + rec->realname = *realname == '\0' ? NULL : g_strdup(realname); + + if (away != -1) rec->away = away; + rec->host_ok = TRUE; + rec->join_announced = TRUE; + rec->idle_time = time(NULL); + + signal_emit("notifylist joined", 6, + server, rec->nick, rec->user, rec->host, realname, NULL); + g_free(user); +} + +static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + if (nick != NULL) { + notifylist_check_join(server, nick, address, "", -1); + notifylist_idle_reset(server, nick); + } +} + +static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + notifylist_check_join(server, nick, address, "", -1); +} + +static void sig_channel_wholist(CHANNEL_REC *channel) +{ + GSList *nicks, *tmp; + + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + notifylist_check_join(channel->server, rec->nick, rec->host, rec->realname, rec->gone); + } + g_slist_free(nicks); +} + +void notifylist_init(void) +{ + notifylist_read_config(); + + notifylist_commands_init(); + notifylist_ison_init(); + notifylist_whois_init(); + signal_add("server connected", (SIGNAL_FUNC) notifylist_init_server); + signal_add("server disconnected", (SIGNAL_FUNC) notifylist_deinit_server); + signal_add("event quit", (SIGNAL_FUNC) event_quit); + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("channel wholist", (SIGNAL_FUNC) sig_channel_wholist); + signal_add("setup reread", (SIGNAL_FUNC) notifylist_read_config); +} + +void notifylist_deinit(void) +{ + notifylist_commands_deinit(); + notifylist_ison_deinit(); + notifylist_whois_deinit(); + + signal_remove("server connected", (SIGNAL_FUNC) notifylist_init_server); + signal_remove("server disconnected", (SIGNAL_FUNC) notifylist_deinit_server); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("channel wholist", (SIGNAL_FUNC) sig_channel_wholist); + signal_remove("setup reread", (SIGNAL_FUNC) notifylist_read_config); + + notifylist_destroy_all(); +} diff --git a/src/irc/notifylist/notifylist.h b/src/irc/notifylist/notifylist.h new file mode 100644 index 00000000..0d4f3739 --- /dev/null +++ b/src/irc/notifylist/notifylist.h @@ -0,0 +1,32 @@ +#ifndef __NOTIFYLIST_H +#define __NOTIFYLIST_H + +typedef struct { + char *mask; /* nick part must not contain wildcards */ + char **ircnets; /* if non-NULL, check only from these irc networks */ + + /* notify when AWAY status changes (uses /USERHOST) */ + int away_check:1; + /* notify when idle time is reset and it was bigger than this + (uses /WHOIS and PRIVMSG events) */ + int idle_check_time; +} NOTIFYLIST_REC; + +extern GSList *notifies; + +void notifylist_init(void); +void notifylist_deinit(void); + +NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets, + int away_check, int idle_check_time); +void notifylist_remove(const char *mask); + +IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist); +int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick); + +/* If `ircnet' is "*", it doesn't matter at all. */ +NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet); + +int notify_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet); + +#endif |