summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/Makefile.am59
-rw-r--r--src/core/args.c60
-rw-r--r--src/core/args.h15
-rw-r--r--src/core/commands.c462
-rw-r--r--src/core/commands.h74
-rw-r--r--src/core/core.c68
-rw-r--r--src/core/core.h17
-rw-r--r--src/core/levels.c160
-rw-r--r--src/core/levels.h44
-rw-r--r--src/core/line-split.c147
-rw-r--r--src/core/line-split.h13
-rw-r--r--src/core/log.c438
-rw-r--r--src/core/log.h49
-rw-r--r--src/core/memdebug.c356
-rw-r--r--src/core/memdebug.h31
-rw-r--r--src/core/misc.c467
-rw-r--r--src/core/misc.h57
-rw-r--r--src/core/module.h3
-rw-r--r--src/core/modules.c185
-rw-r--r--src/core/modules.h33
-rw-r--r--src/core/net-disconnect.c155
-rw-r--r--src/core/net-disconnect.h7
-rw-r--r--src/core/net-internal.h6
-rw-r--r--src/core/net-nonblock.c210
-rw-r--r--src/core/net-nonblock.h26
-rw-r--r--src/core/network.c451
-rw-r--r--src/core/network.h71
-rw-r--r--src/core/pidwait.c74
-rw-r--r--src/core/pidwait.h12
-rw-r--r--src/core/rawlog.c167
-rw-r--r--src/core/rawlog.h28
-rw-r--r--src/core/server-redirect.c323
-rw-r--r--src/core/server-redirect.h38
-rw-r--r--src/core/server.c273
-rw-r--r--src/core/server.h66
-rw-r--r--src/core/settings.c336
-rw-r--r--src/core/settings.h56
-rw-r--r--src/core/signals.c356
-rw-r--r--src/core/signals.h30
-rw-r--r--src/core/special-vars.c635
-rw-r--r--src/core/special-vars.h27
-rw-r--r--src/fe-common/Makefile.am1
-rw-r--r--src/fe-common/core/Makefile.am38
-rw-r--r--src/fe-common/core/autorun.c62
-rw-r--r--src/fe-common/core/command-history.c182
-rw-r--r--src/fe-common/core/command-history.h16
-rw-r--r--src/fe-common/core/fe-common-core.c132
-rw-r--r--src/fe-common/core/fe-common-core.h8
-rw-r--r--src/fe-common/core/fe-core-commands.c266
-rw-r--r--src/fe-common/core/fe-log.c402
-rw-r--r--src/fe-common/core/fe-server.c96
-rw-r--r--src/fe-common/core/fe-settings.c215
-rw-r--r--src/fe-common/core/hilight-text.c354
-rw-r--r--src/fe-common/core/hilight-text.h22
-rw-r--r--src/fe-common/core/keyboard.c297
-rw-r--r--src/fe-common/core/keyboard.h40
-rw-r--r--src/fe-common/core/module-formats.c87
-rw-r--r--src/fe-common/core/module-formats.h62
-rw-r--r--src/fe-common/core/module.h3
-rw-r--r--src/fe-common/core/nick-hilight.c115
-rw-r--r--src/fe-common/core/printtext.c858
-rw-r--r--src/fe-common/core/printtext.h61
-rw-r--r--src/fe-common/core/themes.c278
-rw-r--r--src/fe-common/core/themes.h40
-rw-r--r--src/fe-common/core/translation.c122
-rw-r--r--src/fe-common/core/translation.h12
-rw-r--r--src/fe-common/core/window-items.c224
-rw-r--r--src/fe-common/core/window-items.h22
-rw-r--r--src/fe-common/core/windows.c466
-rw-r--r--src/fe-common/core/windows.h67
-rw-r--r--src/fe-common/irc/Makefile.am32
-rw-r--r--src/fe-common/irc/completion.c628
-rw-r--r--src/fe-common/irc/completion.h13
-rw-r--r--src/fe-common/irc/dcc/Makefile.am17
-rw-r--r--src/fe-common/irc/dcc/fe-dcc.c457
-rw-r--r--src/fe-common/irc/dcc/module-formats.c57
-rw-r--r--src/fe-common/irc/dcc/module-formats.h37
-rw-r--r--src/fe-common/irc/fe-channels.c123
-rw-r--r--src/fe-common/irc/fe-common-irc.c172
-rw-r--r--src/fe-common/irc/fe-common-irc.h8
-rw-r--r--src/fe-common/irc/fe-ctcp.c110
-rw-r--r--src/fe-common/irc/fe-events-numeric.c707
-rw-r--r--src/fe-common/irc/fe-events.c682
-rw-r--r--src/fe-common/irc/fe-ignore.c248
-rw-r--r--src/fe-common/irc/fe-irc-commands.c541
-rw-r--r--src/fe-common/irc/fe-query.c133
-rw-r--r--src/fe-common/irc/flood/Makefile.am17
-rw-r--r--src/fe-common/irc/flood/fe-flood.c54
-rw-r--r--src/fe-common/irc/flood/module-formats.c33
-rw-r--r--src/fe-common/irc/flood/module-formats.h13
-rw-r--r--src/fe-common/irc/irc-hilight-text.c54
-rw-r--r--src/fe-common/irc/irc-hilight-text.h6
-rw-r--r--src/fe-common/irc/irc-nick-hilight.c89
-rw-r--r--src/fe-common/irc/module-formats.c174
-rw-r--r--src/fe-common/irc/module-formats.h146
-rw-r--r--src/fe-common/irc/module.h3
-rw-r--r--src/fe-common/irc/notifylist/Makefile.am17
-rw-r--r--src/fe-common/irc/notifylist/fe-notifylist.c241
-rw-r--r--src/fe-common/irc/notifylist/module-formats.c39
-rw-r--r--src/fe-common/irc/notifylist/module-formats.h19
-rw-r--r--src/fe-none/Makefile.am24
-rw-r--r--src/fe-none/irssi.c94
-rw-r--r--src/fe-none/module.h3
-rw-r--r--src/fe-text/Makefile.am46
-rw-r--r--src/fe-text/gui-entry.c177
-rw-r--r--src/fe-text/gui-entry.h22
-rw-r--r--src/fe-text/gui-mainwindows.c66
-rw-r--r--src/fe-text/gui-mainwindows.h21
-rw-r--r--src/fe-text/gui-printtext.c334
-rw-r--r--src/fe-text/gui-printtext.h28
-rw-r--r--src/fe-text/gui-readline.c374
-rw-r--r--src/fe-text/gui-readline.h10
-rw-r--r--src/fe-text/gui-special-vars.c61
-rw-r--r--src/fe-text/gui-special-vars.h7
-rw-r--r--src/fe-text/gui-statusbar-items.c691
-rw-r--r--src/fe-text/gui-statusbar-items.h7
-rw-r--r--src/fe-text/gui-statusbar.c237
-rw-r--r--src/fe-text/gui-statusbar.h21
-rw-r--r--src/fe-text/gui-textwidget.c390
-rw-r--r--src/fe-text/gui-textwidget.h7
-rw-r--r--src/fe-text/gui-windows.c780
-rw-r--r--src/fe-text/gui-windows.h93
-rw-r--r--src/fe-text/irssi.c156
-rw-r--r--src/fe-text/module-formats.c30
-rw-r--r--src/fe-text/module-formats.h11
-rw-r--r--src/fe-text/module.h6
-rw-r--r--src/fe-text/screen.c254
-rw-r--r--src/fe-text/screen.h31
-rw-r--r--src/irc/Makefile.am5
-rw-r--r--src/irc/core/Makefile.am61
-rw-r--r--src/irc/core/bans.c218
-rw-r--r--src/irc/core/bans.h15
-rw-r--r--src/irc/core/channel-events.c241
-rw-r--r--src/irc/core/channel-events.h7
-rw-r--r--src/irc/core/channels-query.c594
-rw-r--r--src/irc/core/channels-query.h7
-rw-r--r--src/irc/core/channels-setup.c215
-rw-r--r--src/irc/core/channels-setup.h27
-rw-r--r--src/irc/core/channels.c231
-rw-r--r--src/irc/core/channels.h73
-rw-r--r--src/irc/core/ctcp.c193
-rw-r--r--src/irc/core/ctcp.h10
-rw-r--r--src/irc/core/ignore.c296
-rw-r--r--src/irc/core/ignore.h30
-rw-r--r--src/irc/core/irc-commands.c878
-rw-r--r--src/irc/core/irc-commands.h7
-rw-r--r--src/irc/core/irc-core.c63
-rw-r--r--src/irc/core/irc-core.h7
-rw-r--r--src/irc/core/irc-log.c70
-rw-r--r--src/irc/core/irc-rawlog.c77
-rw-r--r--src/irc/core/irc-rawlog.h7
-rw-r--r--src/irc/core/irc-server.c457
-rw-r--r--src/irc/core/irc-server.h146
-rw-r--r--src/irc/core/irc-special-vars.c332
-rw-r--r--src/irc/core/irc-special-vars.h7
-rw-r--r--src/irc/core/irc.c440
-rw-r--r--src/irc/core/irc.h90
-rw-r--r--src/irc/core/ircnet-setup.c116
-rw-r--r--src/irc/core/ircnet-setup.h23
-rw-r--r--src/irc/core/lag.c178
-rw-r--r--src/irc/core/lag.h7
-rw-r--r--src/irc/core/masks.c170
-rw-r--r--src/irc/core/masks.h15
-rw-r--r--src/irc/core/massjoin.c254
-rw-r--r--src/irc/core/massjoin.h7
-rw-r--r--src/irc/core/mode-lists.c234
-rw-r--r--src/irc/core/mode-lists.h24
-rw-r--r--src/irc/core/modes.c451
-rw-r--r--src/irc/core/modes.h18
-rw-r--r--src/irc/core/module.h4
-rw-r--r--src/irc/core/netsplit.c270
-rw-r--r--src/irc/core/netsplit.h27
-rw-r--r--src/irc/core/nicklist.c566
-rw-r--r--src/irc/core/nicklist.h41
-rw-r--r--src/irc/core/query.c158
-rw-r--r--src/irc/core/query.h30
-rw-r--r--src/irc/core/server-idle.c252
-rw-r--r--src/irc/core/server-idle.h24
-rw-r--r--src/irc/core/server-reconnect.c398
-rw-r--r--src/irc/core/server-reconnect.h16
-rw-r--r--src/irc/core/server-setup.c317
-rw-r--r--src/irc/core/server-setup.h40
-rw-r--r--src/irc/dcc/Makefile.am14
-rw-r--r--src/irc/dcc/dcc-chat.c371
-rw-r--r--src/irc/dcc/dcc-chat.h7
-rw-r--r--src/irc/dcc/dcc-files.c577
-rw-r--r--src/irc/dcc/dcc-files.h7
-rw-r--r--src/irc/dcc/dcc.c550
-rw-r--r--src/irc/dcc/dcc.h91
-rw-r--r--src/irc/dcc/module.h3
-rw-r--r--src/irc/flood/Makefile.am12
-rw-r--r--src/irc/flood/autoignore.c250
-rw-r--r--src/irc/flood/autoignore.h18
-rw-r--r--src/irc/flood/flood.c212
-rw-r--r--src/irc/flood/flood.h7
-rw-r--r--src/irc/flood/module.h12
-rw-r--r--src/irc/irc.c27
-rw-r--r--src/irc/notifylist/Makefile.am15
-rw-r--r--src/irc/notifylist/module.h44
-rw-r--r--src/irc/notifylist/notify-commands.c81
-rw-r--r--src/irc/notifylist/notify-ison.c354
-rw-r--r--src/irc/notifylist/notify-setup.c84
-rw-r--r--src/irc/notifylist/notify-setup.h9
-rw-r--r--src/irc/notifylist/notify-whois.c186
-rw-r--r--src/irc/notifylist/notifylist.c356
-rw-r--r--src/irc/notifylist/notifylist.h32
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", &ltime) != 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, &times);
+
+ 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