imported.
authorPekka Riikonen <priikone@silcnet.org>
Thu, 24 May 2001 12:27:08 +0000 (12:27 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Thu, 24 May 2001 12:27:08 +0000 (12:27 +0000)
144 files changed:
apps/irssi/src/core/Makefile.am [new file with mode: 0644]
apps/irssi/src/core/args.c [new file with mode: 0644]
apps/irssi/src/core/args.h [new file with mode: 0644]
apps/irssi/src/core/channel-rec.h [new file with mode: 0644]
apps/irssi/src/core/channel-setup-rec.h [new file with mode: 0644]
apps/irssi/src/core/channels-setup.c [new file with mode: 0644]
apps/irssi/src/core/channels-setup.h [new file with mode: 0644]
apps/irssi/src/core/channels.c [new file with mode: 0644]
apps/irssi/src/core/channels.h [new file with mode: 0644]
apps/irssi/src/core/chat-commands.c [new file with mode: 0644]
apps/irssi/src/core/chat-protocols.c [new file with mode: 0644]
apps/irssi/src/core/chat-protocols.h [new file with mode: 0644]
apps/irssi/src/core/chatnet-rec.h [new file with mode: 0644]
apps/irssi/src/core/chatnets.c [new file with mode: 0644]
apps/irssi/src/core/chatnets.h [new file with mode: 0644]
apps/irssi/src/core/commands.c [new file with mode: 0644]
apps/irssi/src/core/commands.h [new file with mode: 0644]
apps/irssi/src/core/core.c [new file with mode: 0644]
apps/irssi/src/core/core.h [new file with mode: 0644]
apps/irssi/src/core/expandos.c [new file with mode: 0644]
apps/irssi/src/core/expandos.h [new file with mode: 0644]
apps/irssi/src/core/ignore.c [new file with mode: 0644]
apps/irssi/src/core/ignore.h [new file with mode: 0644]
apps/irssi/src/core/levels.c [new file with mode: 0644]
apps/irssi/src/core/levels.h [new file with mode: 0644]
apps/irssi/src/core/line-split.c [new file with mode: 0644]
apps/irssi/src/core/line-split.h [new file with mode: 0644]
apps/irssi/src/core/log.c [new file with mode: 0644]
apps/irssi/src/core/log.h [new file with mode: 0644]
apps/irssi/src/core/masks.c [new file with mode: 0644]
apps/irssi/src/core/masks.h [new file with mode: 0644]
apps/irssi/src/core/memdebug.c [new file with mode: 0644]
apps/irssi/src/core/memdebug.h [new file with mode: 0644]
apps/irssi/src/core/misc.c [new file with mode: 0644]
apps/irssi/src/core/misc.h [new file with mode: 0644]
apps/irssi/src/core/module.h [new file with mode: 0644]
apps/irssi/src/core/modules.c [new file with mode: 0644]
apps/irssi/src/core/modules.h [new file with mode: 0644]
apps/irssi/src/core/net-disconnect.c [new file with mode: 0644]
apps/irssi/src/core/net-disconnect.h [new file with mode: 0644]
apps/irssi/src/core/net-nonblock.c [new file with mode: 0644]
apps/irssi/src/core/net-nonblock.h [new file with mode: 0644]
apps/irssi/src/core/net-sendbuffer.c [new file with mode: 0644]
apps/irssi/src/core/net-sendbuffer.h [new file with mode: 0644]
apps/irssi/src/core/network.c [new file with mode: 0644]
apps/irssi/src/core/network.h [new file with mode: 0644]
apps/irssi/src/core/nick-rec.h [new file with mode: 0644]
apps/irssi/src/core/nicklist.c [new file with mode: 0644]
apps/irssi/src/core/nicklist.h [new file with mode: 0644]
apps/irssi/src/core/nickmatch-cache.c [new file with mode: 0644]
apps/irssi/src/core/nickmatch-cache.h [new file with mode: 0644]
apps/irssi/src/core/pidwait.c [new file with mode: 0644]
apps/irssi/src/core/pidwait.h [new file with mode: 0644]
apps/irssi/src/core/queries.c [new file with mode: 0644]
apps/irssi/src/core/queries.h [new file with mode: 0644]
apps/irssi/src/core/query-rec.h [new file with mode: 0644]
apps/irssi/src/core/rawlog.c [new file with mode: 0644]
apps/irssi/src/core/rawlog.h [new file with mode: 0644]
apps/irssi/src/core/server-connect-rec.h [new file with mode: 0644]
apps/irssi/src/core/server-rec.h [new file with mode: 0644]
apps/irssi/src/core/server-setup-rec.h [new file with mode: 0644]
apps/irssi/src/core/servers-reconnect.c [new file with mode: 0644]
apps/irssi/src/core/servers-reconnect.h [new file with mode: 0644]
apps/irssi/src/core/servers-redirect.c [new file with mode: 0644]
apps/irssi/src/core/servers-redirect.h [new file with mode: 0644]
apps/irssi/src/core/servers-setup.c [new file with mode: 0644]
apps/irssi/src/core/servers-setup.h [new file with mode: 0644]
apps/irssi/src/core/servers.c [new file with mode: 0644]
apps/irssi/src/core/servers.h [new file with mode: 0644]
apps/irssi/src/core/settings.c [new file with mode: 0644]
apps/irssi/src/core/settings.h [new file with mode: 0644]
apps/irssi/src/core/signals.c [new file with mode: 0644]
apps/irssi/src/core/signals.h [new file with mode: 0644]
apps/irssi/src/core/special-vars.c [new file with mode: 0644]
apps/irssi/src/core/special-vars.h [new file with mode: 0644]
apps/irssi/src/core/window-item-def.h [new file with mode: 0644]
apps/irssi/src/core/window-item-rec.h [new file with mode: 0644]
apps/irssi/src/core/write-buffer.c [new file with mode: 0644]
apps/irssi/src/core/write-buffer.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/Makefile.am [new file with mode: 0644]
apps/irssi/src/fe-common/core/autorun.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/chat-completion.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/chat-completion.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/command-history.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/command-history.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/completion.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/completion.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-channels.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-channels.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-common-core.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-common-core.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-core-commands.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-exec.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-exec.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-expandos.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-help.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-ignore-messages.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-ignore.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-log.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-messages.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-messages.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-modules.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-queries.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-queries.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-server.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-settings.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-windows.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/fe-windows.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/formats.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/formats.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/hilight-text.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/hilight-text.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/keyboard.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/keyboard.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/module-formats.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/module-formats.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/module.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/printtext.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/printtext.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/themes.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/themes.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/translation.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/translation.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/window-activity.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/window-commands.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/window-items.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/window-items.h [new file with mode: 0644]
apps/irssi/src/fe-common/core/windows-layout.c [new file with mode: 0644]
apps/irssi/src/fe-common/core/windows-layout.h [new file with mode: 0644]
apps/irssi/src/silc/core/Makefile.am
apps/irssi/src/silc/core/clientutil.c [new file with mode: 0644]
apps/irssi/src/silc/core/clientutil.h [new file with mode: 0644]
apps/irssi/src/silc/core/module.h [new file with mode: 0644]
apps/irssi/src/silc/core/silc-channels.c
apps/irssi/src/silc/core/silc-channels.h [new file with mode: 0644]
apps/irssi/src/silc/core/silc-core.c
apps/irssi/src/silc/core/silc-core.h [new file with mode: 0644]
apps/irssi/src/silc/core/silc-nicklist.c [new file with mode: 0644]
apps/irssi/src/silc/core/silc-nicklist.h [new file with mode: 0644]
apps/irssi/src/silc/core/silc-queries.c [new file with mode: 0644]
apps/irssi/src/silc/core/silc-queries.h [new file with mode: 0644]
apps/irssi/src/silc/core/silc-servers-reconnect.c [new file with mode: 0644]
apps/irssi/src/silc/core/silc-servers.c
apps/irssi/src/silc/core/silc-servers.h [new file with mode: 0644]

diff --git a/apps/irssi/src/core/Makefile.am b/apps/irssi/src/core/Makefile.am
new file mode 100644 (file)
index 0000000..1dc2f5f
--- /dev/null
@@ -0,0 +1,102 @@
+noinst_LIBRARIES = libcore.a
+
+INCLUDES = \
+       $(GLIB_CFLAGS) \
+       -DSYSCONFDIR=\""$(sysconfdir)"\" \
+       -DMODULEDIR=\""$(libdir)/irssi/modules"\" \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/core
+
+if BUILD_MEMDEBUG
+memdebug_src=memdebug.c
+else
+memdebug_src=
+endif
+
+libcore_a_SOURCES = \
+       args.c \
+       channels.c \
+       channels-setup.c \
+       commands.c \
+       chat-commands.c \
+       chat-protocols.c \
+       chatnets.c \
+       core.c \
+       expandos.c \
+        ignore.c \
+        levels.c \
+       line-split.c \
+       log.c \
+       masks.c \
+        $(memdebug_src) \
+       misc.c \
+       modules.c \
+       net-disconnect.c \
+       net-nonblock.c \
+       net-sendbuffer.c \
+       network.c \
+       nicklist.c \
+       nickmatch-cache.c \
+       pidwait.c \
+       queries.c \
+       rawlog.c \
+       servers.c \
+       servers-reconnect.c \
+       servers-redirect.c \
+       servers-setup.c \
+       settings.c \
+       signals.c \
+       special-vars.c \
+       write-buffer.c
+
+structure_headers = \
+       channel-rec.h \
+       channel-setup-rec.h \
+       chatnet-rec.h \
+       query-rec.h \
+       server-rec.h \
+       server-setup-rec.h \
+       server-connect-rec.h \
+       window-item-rec.h
+
+noinst_HEADERS = \
+       args.h \
+       channels.h \
+       channels-setup.h \
+       commands.h \
+       chat-protocols.h \
+       chatnets.h \
+       core.h \
+       expandos.h \
+        ignore.h \
+        levels.h \
+       line-split.h \
+       log.h \
+       masks.h \
+        memdebug.h \
+       misc.h \
+       module.h \
+       modules.h \
+       net-disconnect.h \
+       net-nonblock.h \
+       net-sendbuffer.h \
+       network.h \
+       nick-rec.h \
+       nicklist.h \
+       nickmatch-cache.h \
+       pidwait.h \
+       queries.h \
+       rawlog.h \
+       servers.h \
+       servers-reconnect.h \
+       servers-redirect.h \
+       servers-setup.h \
+       settings.h \
+       signals.h \
+       special-vars.h \
+       window-item-def.h \
+       write-buffer.h \
+       $(structure_headers)
+
+EXTRA_DIST = \
+       memdebug.c
diff --git a/apps/irssi/src/core/args.c b/apps/irssi/src/core/args.c
new file mode 100644 (file)
index 0000000..093a8d5
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ args.c : small frontend to libPopt command line argument parser
+
+    Copyright (C) 1999-2001 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.\n"
+                      "Run '%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/apps/irssi/src/core/args.h b/apps/irssi/src/core/args.h
new file mode 100644 (file)
index 0000000..4d1b82f
--- /dev/null
@@ -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/apps/irssi/src/core/channel-rec.h b/apps/irssi/src/core/channel-rec.h
new file mode 100644 (file)
index 0000000..2ff6a53
--- /dev/null
@@ -0,0 +1,30 @@
+/* CHANNEL_REC definition, used for inheritance */
+
+#include "window-item-rec.h"
+
+char *topic;
+char *topic_by;
+time_t topic_time;
+
+GHashTable *nicks; /* list of nicks */
+NICK_REC *ownnick; /* our own nick */
+
+unsigned int no_modes:1; /* channel doesn't support modes */
+char *mode;
+int limit; /* user limit */
+char *key; /* password key */
+
+unsigned int chanop:1; /* You're a channel operator */
+unsigned int names_got:1; /* Received /NAMES list */
+unsigned int wholist:1; /* WHO list got */
+unsigned int synced:1; /* Channel synced - all queries done */
+
+unsigned int joined:1; /* Have we even received JOIN event for this channel? */
+unsigned int left:1; /* You just left the channel */
+unsigned int kicked:1; /* You just got kicked */
+unsigned int destroying:1;
+
+/* Return the information needed to call SERVER_REC->channels_join() for
+   this channel. Usually just the channel name, but may contain also the
+   channel key. */
+char *(*get_join_data)(CHANNEL_REC *channel);
diff --git a/apps/irssi/src/core/channel-setup-rec.h b/apps/irssi/src/core/channel-setup-rec.h
new file mode 100644 (file)
index 0000000..a0b2897
--- /dev/null
@@ -0,0 +1,12 @@
+int type; /* module_get_uniq_id("CHANNEL SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+char *chatnet;
+char *password;
+
+char *botmasks;
+char *autosendcmd;
+
+unsigned int autojoin:1;
+GHashTable *module_data;
diff --git a/apps/irssi/src/core/channels-setup.c b/apps/irssi/src/core/channels-setup.c
new file mode 100644 (file)
index 0000000..a53bd3f
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ 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 "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers-setup.h"
+#include "channels-setup.h"
+
+GSList *setupchannels;
+
+static void channel_setup_save(CHANNEL_SETUP_REC *channel)
+{
+       CONFIG_NODE *parentnode, *node;
+       int index;
+
+       index = g_slist_index(setupchannels, channel);
+
+       parentnode = iconfig_node_traverse("(channels", TRUE);
+       node = config_node_index(parentnode, index);
+       if (node == NULL)
+               node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK);
+
+        iconfig_node_clear(node);
+       iconfig_node_set_str(node, "name", channel->name);
+       iconfig_node_set_str(node, "chatnet", channel->chatnet);
+       if (channel->autojoin)
+               iconfig_node_set_bool(node, "autojoin", TRUE);
+       iconfig_node_set_str(node, "password", channel->password);
+       iconfig_node_set_str(node, "botmasks", channel->botmasks);
+       iconfig_node_set_str(node, "autosendcmd", channel->autosendcmd);
+}
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel)
+{
+       if (g_slist_find(setupchannels, channel) == NULL)
+               setupchannels = g_slist_append(setupchannels, channel);
+       channel_setup_save(channel);
+
+       signal_emit("channel setup created", 1, channel);
+}
+
+static void channel_config_remove(CHANNEL_SETUP_REC *channel)
+{
+       CONFIG_NODE *node;
+
+       node = iconfig_node_traverse("channels", FALSE);
+       if (node != NULL) iconfig_node_list_remove(node, g_slist_index(setupchannels, channel));
+}
+
+static void channel_setup_destroy(CHANNEL_SETUP_REC *channel)
+{
+       g_return_if_fail(channel != NULL);
+
+       setupchannels = g_slist_remove(setupchannels, channel);
+       signal_emit("channel setup destroyed", 1, channel);
+
+       g_free_not_null(channel->chatnet);
+       g_free_not_null(channel->password);
+       g_free_not_null(channel->botmasks);
+       g_free_not_null(channel->autosendcmd);
+       g_free(channel->name);
+       g_free(channel);
+}
+
+void channel_setup_remove(CHANNEL_SETUP_REC *channel)
+{
+        channel_config_remove(channel);
+        channel_setup_destroy(channel);
+}
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+                                     const char *chatnet)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(channel != NULL, NULL);
+
+       for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_SETUP_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, channel) == 0 &&
+                   channel_chatnet_match(rec->chatnet, chatnet))
+                       return rec;
+       }
+
+       return NULL;
+}
+
+static CHANNEL_SETUP_REC *channel_setup_read(CONFIG_NODE *node)
+{
+       CHANNEL_SETUP_REC *rec;
+        CHATNET_REC *chatnetrec;
+       char *channel, *chatnet;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       channel = config_node_get_str(node, "name", NULL);
+        chatnet = config_node_get_str(node, "chatnet", NULL);
+       if (chatnet == NULL) /* FIXME: remove this after .98... */ {
+               chatnet = g_strdup(config_node_get_str(node, "ircnet", NULL));
+               if (chatnet != NULL) {
+                        iconfig_node_set_str(node, "chatnet", chatnet);
+                        iconfig_node_set_str(node, "ircnet", NULL);
+               }
+       }
+
+       chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+       if (channel == NULL || chatnetrec == NULL) {
+               /* missing information.. */
+               return NULL;
+       }
+
+       rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+       rec->type = module_get_uniq_id("CHANNEL SETUP", 0);
+       rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+       rec->autojoin = config_node_get_bool(node, "autojoin", FALSE);
+       rec->name = g_strdup(channel);
+       rec->chatnet = g_strdup(chatnetrec != NULL ? chatnetrec->name : chatnet);
+       rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+       rec->botmasks = g_strdup(config_node_get_str(node, "botmasks", NULL));
+       rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+       setupchannels = g_slist_append(setupchannels, rec);
+       signal_emit("channel setup created", 2, rec, node);
+       return rec;
+}
+
+static void channels_read_config(void)
+{
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+       while (setupchannels != NULL)
+               channel_setup_destroy(setupchannels->data);
+
+       /* Read channels */
+       node = iconfig_node_traverse("channels", FALSE);
+       if (node != NULL) {
+               for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+                       channel_setup_read(tmp->data);
+       }
+}
+
+void channels_setup_init(void)
+{
+        setupchannels = NULL;
+       source_host_ok = FALSE;
+
+        signal_add("setup reread", (SIGNAL_FUNC) channels_read_config);
+        signal_add("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
+
+void channels_setup_deinit(void)
+{
+       while (setupchannels != NULL)
+               channel_setup_destroy(setupchannels->data);
+
+        signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config);
+        signal_remove("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
diff --git a/apps/irssi/src/core/channels-setup.h b/apps/irssi/src/core/channels-setup.h
new file mode 100644 (file)
index 0000000..423bccb
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef __CHANNELS_SETUP_H
+#define __CHANNELS_SETUP_H
+
+#include "modules.h"
+
+#define CHANNEL_SETUP(server) \
+       MODULE_CHECK_CAST(server, CHANNEL_SETUP_REC, type, "CHANNEL SETUP")
+
+#define IS_CHANNEL_SETUP(server) \
+       (CHANNEL_SETUP(server) ? TRUE : FALSE)
+
+struct _CHANNEL_SETUP_REC {
+#include "channel-setup-rec.h"
+};
+
+extern GSList *setupchannels;
+
+void channels_setup_init(void);
+void channels_setup_deinit(void);
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel);
+void channel_setup_remove(CHANNEL_SETUP_REC *channel);
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+                                     const char *chatnet);
+
+#define channel_chatnet_match(rec, chatnet) \
+       ((rec) == NULL || (rec)[0] == '\0' || \
+        ((chatnet) != NULL && g_strcasecmp(rec, chatnet) == 0))
+
+#endif
diff --git a/apps/irssi/src/core/channels.c b/apps/irssi/src/core/channels.c
new file mode 100644 (file)
index 0000000..f3d3a7a
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ channel.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 "special-vars.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "nicklist.h"
+
+GSList *channels; /* List of all channels */
+
+static char *get_join_data(CHANNEL_REC *channel)
+{
+       return g_strdup(channel->name);
+}
+
+void channel_init(CHANNEL_REC *channel, int automatic)
+{
+       g_return_if_fail(channel != NULL);
+       g_return_if_fail(channel->name != NULL);
+
+       channels = g_slist_append(channels, channel);
+       if (channel->server != NULL) {
+               channel->server->channels =
+                       g_slist_append(channel->server->channels, channel);
+       }
+
+        MODULE_DATA_INIT(channel);
+       channel->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL");
+        channel->mode = g_strdup("");
+       channel->createtime = time(NULL);
+        channel->get_join_data = get_join_data;
+
+       signal_emit("channel created", 2, channel, GINT_TO_POINTER(automatic));
+}
+
+void channel_destroy(CHANNEL_REC *channel)
+{
+       g_return_if_fail(IS_CHANNEL(channel));
+
+       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);
+
+        MODULE_DATA_DEINIT(channel);
+       g_free_not_null(channel->hilight_color);
+       g_free_not_null(channel->topic);
+       g_free_not_null(channel->topic_by);
+       g_free_not_null(channel->key);
+       g_free(channel->mode);
+       g_free(channel->name);
+       g_free(channel);
+}
+
+static CHANNEL_REC *channel_find_server(SERVER_REC *server,
+                                       const char *name)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+
+       if (server->channel_find_func != NULL) {
+               /* use the server specific channel find function */
+               return server->channel_find_func(server, name);
+       }
+
+       for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *rec = tmp->data;
+
+               if (g_strcasecmp(name, rec->name) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+CHANNEL_REC *channel_find(SERVER_REC *server, const char *name)
+{
+       g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+       g_return_val_if_fail(name != NULL, NULL);
+
+       if (server != NULL)
+               return channel_find_server(server, name);
+
+       /* find from any server */
+       return gslist_foreach_find(servers,
+                                  (FOREACH_FIND_FUNC) channel_find_server,
+                                  (void *) name);
+}
+
+/* connected to server, autojoin to channels. */
+static void event_connected(SERVER_REC *server)
+{
+       GString *chans;
+       GSList *tmp;
+
+       g_return_if_fail(SERVER(server));
+
+       if (server->connrec->reconnection)
+               return;
+
+       /* join to the channels marked with autojoin in setup */
+       chans = g_string_new(NULL);
+       for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_SETUP_REC *rec = tmp->data;
+
+               if (!rec->autojoin ||
+                   !channel_chatnet_match(rec->chatnet,
+                                          server->connrec->chatnet))
+                       continue;
+
+               g_string_sprintfa(chans, "%s,", rec->name);
+       }
+
+       if (chans->len > 0) {
+               g_string_truncate(chans, chans->len-1);
+               server->channels_join(server, chans->str, TRUE);
+       }
+
+       g_string_free(chans, TRUE);
+}
+
+static int match_nick_flags(SERVER_REC *server, NICK_REC *nick, char flag)
+{
+       const char *flags = server->get_nick_flags();
+
+       return strchr(flags, flag) == NULL ||
+               (flag == flags[0] && nick->op) ||
+               (flag == flags[1] && (nick->voice || nick->halfop ||
+                                     nick->op)) ||
+               (flag == flags[2] && (nick->halfop || nick->op));
+}
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel)
+{
+       CHANNEL_SETUP_REC *rec;
+       NICK_REC *nick;
+       char **bots, **bot;
+
+       g_return_if_fail(IS_CHANNEL(channel));
+
+       rec = channel_setup_find(channel->name, channel->server->connrec->chatnet);
+       if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd)
+               return;
+
+       if (rec->botmasks == NULL || !*rec->botmasks) {
+               /* just send the command. */
+               eval_special_string(rec->autosendcmd, "", channel->server, channel);
+               return;
+       }
+
+       /* find first available bot.. */
+       bots = g_strsplit(rec->botmasks, " ", -1);
+       for (bot = bots; *bot != NULL; bot++) {
+               const char *botnick = *bot;
+
+               nick = nicklist_find_mask(channel,
+                                         channel->server->isnickflag(*botnick) ?
+                                         botnick+1 : botnick);
+               if (nick != NULL &&
+                   match_nick_flags(channel->server, nick, *botnick)) {
+                       eval_special_string(rec->autosendcmd, nick->nick,
+                                           channel->server, channel);
+                       break;
+               }
+       }
+       g_strfreev(bots);
+}
+
+void channels_init(void)
+{
+       channels_setup_init();
+
+       signal_add("event connected", (SIGNAL_FUNC) event_connected);
+}
+
+void channels_deinit(void)
+{
+       channels_setup_deinit();
+
+       signal_remove("event connected", (SIGNAL_FUNC) event_connected);
+       module_uniq_destroy("CHANNEL");
+}
diff --git a/apps/irssi/src/core/channels.h b/apps/irssi/src/core/channels.h
new file mode 100644 (file)
index 0000000..98b75ee
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __CHANNELS_H
+#define __CHANNELS_H
+
+#include "modules.h"
+
+/* Returns CHANNEL_REC if it's channel, NULL if it isn't. */
+#define CHANNEL(channel) \
+       MODULE_CHECK_CAST_MODULE(channel, CHANNEL_REC, type, \
+                             "WINDOW ITEM TYPE", "CHANNEL")
+
+#define IS_CHANNEL(channel) \
+       (CHANNEL(channel) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _CHANNEL_REC {
+#include "channel-rec.h"
+};
+
+extern GSList *channels;
+
+/* Create new channel record */
+void channel_init(CHANNEL_REC *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(SERVER_REC *server, const char *name);
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel);
+
+void channels_init(void);
+void channels_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/chat-commands.c b/apps/irssi/src/core/chat-commands.c
new file mode 100644 (file)
index 0000000..78a05a7
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ chat-commands.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 "network.h"
+#include "signals.h"
+#include "commands.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+#include "channels.h"
+#include "queries.h"
+#include "window-item-def.h"
+
+static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr)
+{
+        CHAT_PROTOCOL_REC *proto;
+       SERVER_CONNECT_REC *conn;
+       GHashTable *optlist;
+       char *addr, *portstr, *password, *nick, *chatnet, *host;
+       void *free_arg;
+
+       g_return_val_if_fail(data != NULL, NULL);
+
+       if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
+                           "connect", &optlist, &addr, &portstr,
+                           &password, &nick))
+               return NULL;
+       if (plus_addr != NULL) *plus_addr = *addr == '+';
+       if (*addr == '+') addr++;
+       if (*addr == '\0') {
+               signal_emit("error command", 1,
+                           GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
+               cmd_params_free(free_arg);
+               return NULL;
+       }
+
+       if (strcmp(password, "-") == 0)
+               *password = '\0';
+
+        /* check if -<chatnet> option is used to specify chat protocol */
+       proto = chat_protocol_find_net(optlist);
+
+       /* connect to server */
+       chatnet = proto == NULL ? NULL :
+               g_hash_table_lookup(optlist, proto->chatnet);
+       conn = server_create_conn(proto != NULL ? proto->id : -1, addr,
+                                 atoi(portstr), chatnet, password, nick);
+        if (proto == NULL)
+               proto = chat_protocol_find_id(conn->chat_type);
+
+       if (proto->not_initialized) {
+               /* trying to use protocol that isn't yet initialized */
+               signal_emit("chat protocol unknown", 1, proto->name);
+               server_connect_free(conn);
+                cmd_params_free(free_arg);
+               return NULL;
+       }
+
+       if (g_hash_table_lookup(optlist, "6") != NULL)
+               conn->family = AF_INET6;
+       else if (g_hash_table_lookup(optlist, "4") != NULL)
+               conn->family = AF_INET;
+
+        host = g_hash_table_lookup(optlist, "host");
+       if (host != NULL && *host != '\0') {
+               IPADDR ip4, ip6;
+
+               if (net_gethostbyname(host, &ip4, &ip6) == 0)
+                        server_connect_own_ip_save(conn, &ip4, &ip6);
+       }
+
+       cmd_params_free(free_arg);
+        return conn;
+}
+
+/* SYNTAX: CONNECT [-4 | -6] [-ircnet <ircnet>] [-host <hostname>]
+                   <address>|<chatnet> [<port> [<password> [<nick>]]] */
+static void cmd_connect(const char *data)
+{
+       SERVER_CONNECT_REC *conn;
+
+       conn = get_server_connect(data, NULL);
+        if (conn != NULL)
+               CHAT_PROTOCOL(conn)->server_connect(conn);
+}
+
+static RECONNECT_REC *find_reconnect_server(int chat_type,
+                                           const char *addr, int port)
+{
+       RECONNECT_REC *match, *last_proto_match;
+       GSList *tmp;
+        int count;
+
+       g_return_val_if_fail(addr != NULL, NULL);
+
+       /* check if there's a reconnection to the same host and maybe even
+          the same port */
+        match = last_proto_match = NULL; count = 0;
+       for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+               RECONNECT_REC *rec = tmp->data;
+
+               if (rec->conn->chat_type == chat_type) {
+                       count++; last_proto_match = rec;
+                       if (g_strcasecmp(rec->conn->address, addr) == 0) {
+                               if (rec->conn->port == port)
+                                       return rec;
+                               match = rec;
+                       }
+               }
+       }
+
+       if (count == 1) {
+               /* only one reconnection with wanted protocol,
+                  we probably want to use it */
+                return last_proto_match;
+       }
+
+       return match;
+}
+
+static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+       SERVER_CONNECT_REC *oldconn;
+       RECONNECT_REC *recon;
+
+       if (server != NULL) {
+               oldconn = server->connrec;
+                reconnect_save_status(conn, server);
+       } else {
+               /* maybe we can reconnect some server from
+                  reconnection queue */
+               recon = find_reconnect_server(conn->chat_type,
+                                             conn->address, conn->port);
+               if (recon == NULL) return;
+
+               oldconn = recon->conn;
+               server_reconnect_destroy(recon, FALSE);
+
+               conn->away_reason = g_strdup(oldconn->away_reason);
+               conn->channels = g_strdup(oldconn->channels);
+       }
+
+       conn->reconnection = TRUE;
+
+       if (conn->chatnet == NULL && oldconn->chatnet != NULL)
+               conn->chatnet = g_strdup(oldconn->chatnet);
+
+       if (server != NULL) {
+               signal_emit("command disconnect", 2,
+                           "* Changing server", server);
+       } else {
+               server_connect_free(oldconn);
+       }
+}
+
+/* SYNTAX: SERVER [-4 | -6] [-ircnet <ircnet>] [-host <hostname>]
+                  [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
+static void cmd_server(const char *data, SERVER_REC *server)
+{
+       SERVER_CONNECT_REC *conn;
+       int plus_addr;
+
+       g_return_if_fail(data != NULL);
+
+        /* create connection record */
+       conn = get_server_connect(data, &plus_addr);
+       if (conn != NULL) {
+               if (!plus_addr)
+                       update_reconnection(conn, server);
+               CHAT_PROTOCOL(conn)->server_connect(conn);
+       }
+}
+
+/* SYNTAX: DISCONNECT *|<tag> [<message>] */
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+       char *tag, *msg;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
+               return;
+
+       if (*tag != '\0' && strcmp(tag, "*") != 0)
+               server = server_find_tag(tag);
+       if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
+
+       if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
+       signal_emit("server quit", 2, server, msg);
+
+       cmd_params_free(free_arg);
+       server_disconnect(server);
+}
+
+/* SYNTAX: QUIT [<message>] */
+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("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);
+}
+
+/* SYNTAX: JOIN [-invite] [-<server tag>] <channels> [<keys>] */
+static void cmd_join(const char *data, SERVER_REC *server)
+{
+       GHashTable *optlist;
+       char *channels;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+       if (!IS_SERVER(server) || !server->connected)
+               cmd_return_error(CMDERR_NOT_CONNECTED);
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+                           "join", &optlist, &channels))
+               return;
+
+       if (g_hash_table_lookup(optlist, "invite"))
+               channels = server->last_invite;
+       else {
+               if (*channels == '\0')
+                       cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+               /* -<server tag> */
+               server = cmd_options_get_server("join", optlist, server);
+       }
+
+       if (server != NULL && channels != NULL)
+               server->channels_join(server, channels, FALSE);
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: MSG [-<server tag>] <targets> <message> */
+static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       GHashTable *optlist;
+       char *target, *origtarget, *msg;
+       void *free_arg;
+       int free_ret;
+
+       g_return_if_fail(data != NULL);
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+                           "msg", &optlist, &target, &msg))
+               return;
+       if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       server = cmd_options_get_server("msg", optlist, SERVER(server));
+       if (server == NULL || !server->connected)
+               cmd_param_error(CMDERR_NOT_CONNECTED);
+
+        origtarget = target;
+       free_ret = FALSE;
+       if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) {
+               target = parse_special(&target, server, item,
+                                      NULL, &free_ret, NULL, 0);
+               if (target != NULL && *target == '\0')
+                       target = NULL;
+       } else if (strcmp(target, "*") == 0 && item != NULL)
+               target = item->name;
+
+       if (target != NULL)
+               server->send_message(server, target, msg);
+
+       signal_emit(target != NULL && server->ischannel(target) ?
+                   "message own_public" : "message own_private", 4,
+                   server, msg, target, origtarget);
+
+       if (free_ret && target != NULL) g_free(target);
+       cmd_params_free(free_arg);
+}
+
+static void cmd_foreach(const char *data, SERVER_REC *server,
+                       WI_ITEM_REC *item)
+{
+       command_runsub("foreach", data, server, item);
+}
+
+/* SYNTAX: FOREACH SERVER <command> */
+static void cmd_foreach_server(const char *data, SERVER_REC *server)
+{
+       GSList *tmp;
+
+       for (tmp = servers; tmp != NULL; tmp = tmp->next)
+               signal_emit("send command", 3, data, tmp->data, NULL);
+}
+
+/* SYNTAX: FOREACH CHANNEL <command> */
+static void cmd_foreach_channel(const char *data)
+{
+       GSList *tmp;
+
+       for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *rec = tmp->data;
+
+               signal_emit("send command", 3, data, rec->server, rec);
+       }
+}
+
+/* SYNTAX: FOREACH QUERY <command> */
+static void cmd_foreach_query(const char *data)
+{
+       GSList *tmp;
+
+       for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+               QUERY_REC *rec = tmp->data;
+
+               signal_emit("send command", 3, data, rec->server, rec);
+       }
+}
+
+void chat_commands_init(void)
+{
+       settings_add_str("misc", "quit_message", "leaving");
+
+       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("quit", NULL, (SIGNAL_FUNC) cmd_quit);
+       command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+       command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
+       command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
+       command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
+       command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
+       command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
+
+       command_set_options("connect", "4 6 +host");
+       command_set_options("join", "invite");
+}
+
+void chat_commands_deinit(void)
+{
+       command_unbind("server", (SIGNAL_FUNC) cmd_server);
+       command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
+       command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+       command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
+       command_unbind("join", (SIGNAL_FUNC) cmd_join);
+       command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
+       command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
+       command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
+       command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
+       command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
+}
diff --git a/apps/irssi/src/core/chat-protocols.c b/apps/irssi/src/core/chat-protocols.c
new file mode 100644 (file)
index 0000000..ab7c09d
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ chat-protocol.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 "modules.h"
+#include "signals.h"
+#include "chat-protocols.h"
+
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "channels-setup.h"
+
+GSList *chat_protocols;
+
+static CHAT_PROTOCOL_REC *default_proto;
+
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id)
+{
+       return object == NULL ||
+               chat_protocol_lookup(id) !=
+               G_STRUCT_MEMBER(int, object, type_pos) ? NULL : object;
+}
+
+/* Return the ID for the specified chat protocol. */
+int chat_protocol_lookup(const char *name)
+{
+       CHAT_PROTOCOL_REC *rec;
+
+       g_return_val_if_fail(name != NULL, -1);
+
+       rec = chat_protocol_find(name);
+       return rec == NULL ? -1 : rec->id;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+               CHAT_PROTOCOL_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, name) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(id > 0, NULL);
+
+       for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+               CHAT_PROTOCOL_REC *rec = tmp->data;
+
+               if (rec->id == id)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(optlist != NULL, NULL);
+
+       for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+               CHAT_PROTOCOL_REC *rec = tmp->data;
+
+               if (rec->chatnet != NULL &&
+                   g_hash_table_lookup(optlist, rec->chatnet) != NULL)
+                        return rec;
+       }
+
+       return NULL;
+}
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec)
+{
+       CHAT_PROTOCOL_REC *newrec;
+        int created;
+
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       newrec = chat_protocol_find(rec->name);
+        created = newrec == NULL;
+       if (newrec == NULL) {
+               newrec = g_new0(CHAT_PROTOCOL_REC, 1);
+               chat_protocols = g_slist_append(chat_protocols, newrec);
+       } else {
+               /* updating existing protocol */
+                g_free(newrec->name);
+       }
+
+       memcpy(newrec, rec, sizeof(CHAT_PROTOCOL_REC));
+       newrec->id = module_get_uniq_id_str("PROTOCOL", rec->name);
+       newrec->name = g_strdup(rec->name);
+
+       if (default_proto == NULL)
+                chat_protocol_set_default(newrec);
+
+        if (created)
+               signal_emit("chat protocol created", 1, newrec);
+        else
+               signal_emit("chat protocol updated", 1, newrec);
+        return newrec;
+}
+
+static void chat_protocol_destroy(CHAT_PROTOCOL_REC *rec)
+{
+       g_return_if_fail(rec != NULL);
+
+       chat_protocols = g_slist_remove(chat_protocols, rec);
+
+       if (default_proto == rec) {
+               chat_protocol_set_default(chat_protocols == NULL ? NULL :
+                                         chat_protocols->data);
+       }
+
+       signal_emit("chat protocol destroyed", 1, rec);
+
+       g_free(rec->name);
+       g_free(rec);
+}
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name)
+{
+       CHAT_PROTOCOL_REC *rec;
+
+       g_return_if_fail(name != NULL);
+
+       rec = chat_protocol_find(name);
+       if (rec != NULL) chat_protocol_destroy(rec);
+}
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec)
+{
+        default_proto = rec;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void)
+{
+        return default_proto;
+}
+
+static CHATNET_REC *create_chatnet(void)
+{
+        return g_new0(CHATNET_REC, 1);
+}
+
+static SERVER_SETUP_REC *create_server_setup(void)
+{
+        return g_new0(SERVER_SETUP_REC, 1);
+}
+
+static CHANNEL_SETUP_REC *create_channel_setup(void)
+{
+        return g_new0(CHANNEL_SETUP_REC, 1);
+}
+
+static SERVER_CONNECT_REC *create_server_connect(void)
+{
+        return g_new0(SERVER_CONNECT_REC, 1);
+}
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+   specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name)
+{
+       CHAT_PROTOCOL_REC *rec, *newrec;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+        rec = chat_protocol_find(name);
+       if (rec != NULL)
+                return rec;
+
+       rec = g_new0(CHAT_PROTOCOL_REC, 1);
+        rec->not_initialized = TRUE;
+       rec->name = (char *) name;
+       rec->create_chatnet = create_chatnet;
+        rec->create_server_setup = create_server_setup;
+        rec->create_channel_setup = create_channel_setup;
+       rec->create_server_connect = create_server_connect;
+
+       newrec = chat_protocol_register(rec);
+       g_free(rec);
+        return newrec;
+}
+
+void chat_protocols_init(void)
+{
+       default_proto = NULL;
+       chat_protocols = NULL;
+}
+
+void chat_protocols_deinit(void)
+{
+       while (chat_protocols != NULL)
+                chat_protocol_destroy(chat_protocols->data);
+}
diff --git a/apps/irssi/src/core/chat-protocols.h b/apps/irssi/src/core/chat-protocols.h
new file mode 100644 (file)
index 0000000..cc7eaad
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef __CHAT_PROTOCOLS_H
+#define __CHAT_PROTOCOLS_H
+
+typedef struct {
+       int id;
+
+       unsigned int not_initialized:1;
+
+       char *name;
+       char *fullname;
+       char *chatnet;
+
+        CHATNET_REC *(*create_chatnet) (void);
+       SERVER_SETUP_REC *(*create_server_setup) (void);
+        CHANNEL_SETUP_REC *(*create_channel_setup) (void);
+       SERVER_CONNECT_REC *(*create_server_connect) (void);
+
+        SERVER_REC *(*server_connect) (SERVER_CONNECT_REC *);
+        CHANNEL_REC *(*channel_create) (SERVER_REC *, const char *, int);
+        QUERY_REC *(*query_create) (const char *, const char *, int);
+} CHAT_PROTOCOL_REC;
+
+extern GSList *chat_protocols;
+
+#define PROTO_CHECK_CAST(object, cast, type_field, id) \
+       ((cast *) chat_protocol_check_cast(object, \
+                               offsetof(cast, type_field), id))
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id);
+
+#define CHAT_PROTOCOL(object) \
+       ((object) == NULL ? chat_protocol_get_default() : \
+       chat_protocol_find_id((object)->chat_type))
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec);
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name);
+
+/* Find functions */
+int chat_protocol_lookup(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id);
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist);
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec);
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void);
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+   specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name);
+
+void chat_protocols_init(void);
+void chat_protocols_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/chatnet-rec.h b/apps/irssi/src/core/chatnet-rec.h
new file mode 100644 (file)
index 0000000..e3ed8aa
--- /dev/null
@@ -0,0 +1,12 @@
+int type; /* module_get_uniq_id("CHATNET", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+
+char *nick;
+char *username;
+char *realname;
+
+char *own_host; /* address to use when connecting this server */
+char *autosendcmd; /* command to send after connecting to this ircnet */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
diff --git a/apps/irssi/src/core/chatnets.c b/apps/irssi/src/core/chatnets.c
new file mode 100644 (file)
index 0000000..a2cff52
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ chatnets.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 "special-vars.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+
+GSList *chatnets; /* list of available chat networks */
+
+static void chatnet_config_save(CHATNET_REC *chatnet)
+{
+       CONFIG_NODE *node;
+
+       node = iconfig_node_traverse("chatnets", TRUE);
+       node = config_node_section(node, chatnet->name, NODE_TYPE_BLOCK);
+       iconfig_node_clear(node);
+
+       iconfig_node_set_str(node, "type", chat_protocol_find_id(chatnet->chat_type)->name);
+       iconfig_node_set_str(node, "nick", chatnet->nick);
+       iconfig_node_set_str(node, "username", chatnet->username);
+       iconfig_node_set_str(node, "realname", chatnet->realname);
+       iconfig_node_set_str(node, "host", chatnet->own_host);
+       iconfig_node_set_str(node, "autosendcmd", chatnet->autosendcmd);
+
+        signal_emit("chatnet saved", 2, chatnet, node);
+}
+
+static void chatnet_config_remove(CHATNET_REC *chatnet)
+{
+       CONFIG_NODE *node;
+
+       node = iconfig_node_traverse("chatnets", FALSE);
+       if (node != NULL) iconfig_node_set_str(node, chatnet->name, NULL);
+}
+
+void chatnet_create(CHATNET_REC *chatnet)
+{
+       g_return_if_fail(chatnet != NULL);
+
+        chatnet->type = module_get_uniq_id("CHATNET", 0);
+       if (g_slist_find(chatnets, chatnet) == NULL)
+               chatnets = g_slist_append(chatnets, chatnet);
+
+       chatnet_config_save(chatnet);
+       signal_emit("chatnet created", 1, chatnet);
+}
+
+void chatnet_remove(CHATNET_REC *chatnet)
+{
+       g_return_if_fail(IS_CHATNET(chatnet));
+
+       signal_emit("chatnet removed", 1, chatnet);
+
+       chatnet_config_remove(chatnet);
+       chatnet_destroy(chatnet);
+}
+
+void chatnet_destroy(CHATNET_REC *chatnet)
+{
+       g_return_if_fail(IS_CHATNET(chatnet));
+
+       chatnets = g_slist_remove(chatnets, chatnet);
+       signal_emit("chatnet destroyed", 1, chatnet);
+
+       g_free_not_null(chatnet->nick);
+       g_free_not_null(chatnet->username);
+       g_free_not_null(chatnet->realname);
+       g_free_not_null(chatnet->own_host);
+       g_free_not_null(chatnet->autosendcmd);
+       g_free(chatnet->name);
+       g_free(chatnet);
+}
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+               CHATNET_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, name) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+       CHATNET_REC *rec;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       if (server->connrec->chatnet == NULL)
+               return;
+
+       rec = chatnet_find(server->connrec->chatnet);
+       if (rec != NULL && rec->autosendcmd)
+               eval_special_string(rec->autosendcmd, "", server, NULL);
+}
+
+static void chatnet_read(CONFIG_NODE *node)
+{
+        CHAT_PROTOCOL_REC *proto;
+       CHATNET_REC *rec;
+        char *type;
+
+       if (node == NULL || node->key == NULL)
+               return;
+
+       type = config_node_get_str(node, "type", NULL);
+       proto = type == NULL ? NULL : chat_protocol_find(type);
+       if (proto == NULL) {
+               proto = type == NULL ? chat_protocol_get_default() :
+                       chat_protocol_get_unknown(type);
+       }
+
+       if (type == NULL)
+               iconfig_node_set_str(node, "type", proto->name);
+
+       rec = proto->create_chatnet();
+       rec->type = module_get_uniq_id("CHATNET", 0);
+       rec->chat_type = proto->id;
+       rec->name = g_strdup(node->key);
+       rec->nick = g_strdup(config_node_get_str(node, "nick", NULL));
+       rec->username = g_strdup(config_node_get_str(node, "username", NULL));
+       rec->realname = g_strdup(config_node_get_str(node, "realname", NULL));
+       rec->own_host = g_strdup(config_node_get_str(node, "host", NULL));
+       rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+       chatnets = g_slist_append(chatnets, rec);
+        signal_emit("chatnet read", 2, rec, node);
+}
+
+static void read_chatnets(void)
+{
+       CONFIG_NODE *node;
+
+       while (chatnets != NULL)
+                chatnet_destroy(chatnets->data);
+
+       node = iconfig_node_traverse("chatnets", FALSE);
+       if (node == NULL) {
+               /* FIXME: remove after .98 */
+               node = iconfig_node_traverse("ircnets", FALSE);
+               if (node != NULL) {
+                       /* very dirty method - doesn't update hashtables
+                          but this will do temporarily.. */
+                       g_free(node->key);
+                        node->key = g_strdup("chatnets");
+               }
+       }
+
+       if (node != NULL)
+                g_slist_foreach(node->value, (GFunc) chatnet_read, NULL);
+}
+
+void chatnets_init(void)
+{
+       chatnets = NULL;
+
+       signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+       signal_add("setup reread", (SIGNAL_FUNC) read_chatnets);
+        signal_add_first("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
+
+void chatnets_deinit(void)
+{
+       while (chatnets != NULL)
+               chatnet_destroy(chatnets->data);
+       module_uniq_destroy("CHATNET");
+
+       signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+       signal_remove("setup reread", (SIGNAL_FUNC) read_chatnets);
+        signal_remove("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
diff --git a/apps/irssi/src/core/chatnets.h b/apps/irssi/src/core/chatnets.h
new file mode 100644 (file)
index 0000000..2b78f64
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef __CHATNETS_H
+#define __CHATNETS_H
+
+#include "modules.h"
+
+/* Returns CHATNET_REC if it's chatnet, NULL if it isn't. */
+#define CHATNET(chatnet) \
+       MODULE_CHECK_CAST(chatnet, CHATNET_REC, type, "CHATNET")
+
+#define IS_CHATNET(chatnet) \
+       (CHATNET(chatnet) ? TRUE : FALSE)
+
+struct _CHATNET_REC {
+#include "chatnet-rec.h"
+};
+
+extern GSList *chatnets; /* list of available chat networks */
+
+/* add the chatnet to chat networks list */
+void chatnet_create(CHATNET_REC *chatnet);
+/* remove the chatnet from chat networks list */
+void chatnet_remove(CHATNET_REC *chatnet);
+/* destroy the chatnet structure. doesn't remove from config file */
+void chatnet_destroy(CHATNET_REC *chatnet);
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name);
+
+void chatnets_init(void);
+void chatnets_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/commands.c b/apps/irssi/src/core/commands.c
new file mode 100644 (file)
index 0000000..b6fb8a7
--- /dev/null
@@ -0,0 +1,909 @@
+/*
+ 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 "special-vars.h"
+
+#include "servers.h"
+#include "servers-redirect.h"
+#include "channels.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+GSList *commands;
+char *current_command;
+
+static int signal_default_command;
+
+static GSList *alias_runstack;
+
+COMMAND_REC *command_find(const char *cmd)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(cmd != NULL, NULL);
+
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->cmd, cmd) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
+                                              const char *module)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(rec != NULL, NULL);
+       g_return_val_if_fail(module != NULL, NULL);
+
+       for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+               COMMAND_MODULE_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, module) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec,
+                                                   SIGNAL_FUNC func)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(rec != NULL, NULL);
+       g_return_val_if_fail(func != NULL, NULL);
+
+       for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+               COMMAND_MODULE_REC *rec = tmp->data;
+
+                if (g_slist_find(rec->signals, func) != NULL)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+int command_have_sub(const char *command)
+{
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(command != NULL, FALSE);
+
+       /* find "command "s */
+        len = strlen(command);
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->cmd, command, len) == 0 &&
+                   rec->cmd[len] == ' ')
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static COMMAND_MODULE_REC *command_module_get(COMMAND_REC *rec,
+                                             const char *module)
+{
+        COMMAND_MODULE_REC *modrec;
+
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       modrec = command_module_find(rec, module);
+       if (modrec == NULL) {
+               modrec = g_new0(COMMAND_MODULE_REC, 1);
+               modrec->name = g_strdup(module);
+               rec->modules = g_slist_append(rec->modules, modrec);
+       }
+
+        return modrec;
+}
+
+void command_bind_to(const char *module, int pos, const char *cmd,
+                    const char *category, SIGNAL_FUNC func)
+{
+       COMMAND_REC *rec;
+        COMMAND_MODULE_REC *modrec;
+       char *str;
+
+       g_return_if_fail(module != NULL);
+       g_return_if_fail(cmd != NULL);
+
+       rec = command_find(cmd);
+       if (rec == 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);
+       }
+        modrec = command_module_get(rec, module);
+
+        modrec->signals = g_slist_append(modrec->signals, func);
+
+       if (func != NULL) {
+               str = g_strconcat("command ", cmd, NULL);
+               signal_add_to(module, pos, str, func);
+               g_free(str);
+       }
+
+       signal_emit("commandlist new", 1, rec);
+}
+
+static 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_strfreev(rec->options);
+       g_free(rec->cmd);
+       g_free(rec);
+}
+
+static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
+{
+       rec->modules = g_slist_remove(rec->modules, modrec);
+
+       g_slist_free(modrec->signals);
+        g_free(modrec->name);
+        g_free_not_null(modrec->options);
+        g_free(modrec);
+}
+
+static void command_module_destroy(COMMAND_REC *rec,
+                                  COMMAND_MODULE_REC *modrec)
+{
+       GSList *tmp, *freelist;
+
+        command_module_free(modrec, rec);
+
+       /* command_set_options() might have added module declaration of it's
+          own without any signals .. check if they're the only ones left
+          and if so, destroy them. */
+        freelist = NULL;
+       for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+               COMMAND_MODULE_REC *rec = tmp->data;
+
+               if (rec->signals == NULL)
+                       freelist = g_slist_append(freelist, rec);
+               else {
+                        g_slist_free(freelist);
+                        freelist = NULL;
+                       break;
+               }
+       }
+
+       g_slist_foreach(freelist, (GFunc) command_module_free, rec);
+       g_slist_free(freelist);
+
+       if (rec->modules == NULL)
+               command_free(rec);
+}
+
+void command_unbind(const char *cmd, SIGNAL_FUNC func)
+{
+       COMMAND_REC *rec;
+        COMMAND_MODULE_REC *modrec;
+       char *str;
+
+       g_return_if_fail(cmd != NULL);
+       g_return_if_fail(func != NULL);
+
+       rec = command_find(cmd);
+       if (rec != NULL) {
+               modrec = command_module_find_func(rec, func);
+               modrec->signals = g_slist_remove(modrec->signals, func);
+               if (modrec->signals == NULL)
+                       command_module_destroy(rec, modrec);
+       }
+
+       str = g_strconcat("command ", cmd, NULL);
+       signal_remove(str, func);
+       g_free(str);
+}
+
+/* Expand `cmd' - returns `cmd' if not found, NULL if more than one
+   match is found */
+static const char *command_expand(char *cmd)
+{
+       GSList *tmp;
+       const char *match;
+       int len, multiple;
+
+       g_return_val_if_fail(cmd != NULL, NULL);
+
+       multiple = FALSE;
+       match = NULL;
+       len = strlen(cmd);
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
+                   strchr(rec->cmd+len, ' ') == NULL) {
+                       if (rec->cmd[len] == '\0') {
+                               /* full match */
+                               return rec->cmd;
+                       }
+
+                       if (match != NULL) {
+                               /* multiple matches, we still need to check
+                                  if there's some command left that is a
+                                  full match.. */
+                               multiple = TRUE;
+                       }
+
+                       /* check that this is the only match */
+                       match = rec->cmd;
+               }
+       }
+
+       if (multiple) {
+               signal_emit("error command", 2,
+                           GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
+               return NULL;
+       }
+
+       return match != NULL ? match : cmd;
+}
+
+void command_runsub(const char *cmd, const char *data,
+                   void *server, void *item)
+{
+       const char *newcmd;
+       char *orig, *subcmd, *defcmd, *args;
+
+       g_return_if_fail(data != NULL);
+
+       if (*data == '\0') {
+                /* no subcommand given - list the subcommands */
+               signal_emit("list subcommands", 2, cmd);
+               return;
+       }
+
+       /* get command.. */
+       orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
+       args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
+       if (args != NULL) *args++ = '\0'; else args = "";
+       while (*args == ' ') args++;
+
+       /* check if this command can be expanded */
+       newcmd = command_expand(subcmd+8);
+       if (newcmd == NULL) {
+                /* ambiguous command */
+               g_free(orig);
+               return;
+       }
+
+       subcmd = g_strconcat("command ", newcmd, NULL);
+
+       g_strdown(subcmd);
+       if (!signal_emit(subcmd, 3, args, server, item)) {
+               defcmd = g_strdup_printf("default command %s", cmd);
+               if (!signal_emit(defcmd, 3, data, server, item)) {
+                       signal_emit("error command", 2,
+                                   GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
+               }
+                g_free(defcmd);
+       }
+
+       g_free(subcmd);
+       g_free(orig);
+}
+
+static GSList *optlist_find(GSList *optlist, const char *option)
+{
+       while (optlist != NULL) {
+               char *name = optlist->data;
+               if (iscmdtype(*name)) name++;
+
+               if (g_strcasecmp(name, option) == 0)
+                       return optlist;
+
+               optlist = optlist->next;
+       }
+
+       return NULL;
+}
+
+int command_have_option(const char *cmd, const char *option)
+{
+       COMMAND_REC *rec;
+       char **tmp;
+
+       g_return_val_if_fail(cmd != NULL, FALSE);
+       g_return_val_if_fail(option != NULL, FALSE);
+
+        rec = command_find(cmd);
+       g_return_val_if_fail(rec != NULL, FALSE);
+
+       if (rec->options == NULL)
+               return FALSE;
+
+       for (tmp = rec->options; *tmp != NULL; tmp++) {
+               char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
+
+               if (g_strcasecmp(name, option) == 0)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void command_calc_options(COMMAND_REC *rec, const char *options)
+{
+       char **optlist, **tmp, *name, *str;
+       GSList *list, *oldopt;
+
+       optlist = g_strsplit(options, " ", -1);
+
+       if (rec->options == NULL) {
+                /* first call - use specified args directly */
+               rec->options = optlist;
+               return;
+       }
+
+       /* save old options to linked list */
+       list = NULL;
+       for (tmp = rec->options; *tmp != NULL; tmp++)
+                list = g_slist_append(list, g_strdup(*tmp));
+       g_strfreev(rec->options);
+
+       /* merge the options */
+       for (tmp = optlist; *tmp != NULL; tmp++) {
+               name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
+
+               oldopt = optlist_find(list, name);
+               if (oldopt != NULL) {
+                        /* already specified - overwrite old defination */
+                       g_free(oldopt->data);
+                       oldopt->data = g_strdup(*tmp);
+               } else {
+                       /* new option, append to list */
+                        list = g_slist_append(list, g_strdup(*tmp));
+               }
+       }
+       g_strfreev(optlist);
+
+       /* linked list -> string[] */
+       str = gslist_to_string(list, " ");
+       rec->options = g_strsplit(str, " ", -1);
+        g_free(str);
+
+        g_slist_foreach(list, (GFunc) g_free, NULL);
+       g_slist_free(list);
+}
+
+/* recalculate options to command from options in all modules */
+static void command_update_options(COMMAND_REC *rec)
+{
+       GSList *tmp;
+
+       g_strfreev(rec->options);
+       rec->options = NULL;
+
+       for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+               COMMAND_MODULE_REC *modrec = tmp->data;
+
+               if (modrec->options != NULL)
+                       command_calc_options(rec, modrec->options);
+       }
+}
+
+void command_set_options_module(const char *module,
+                               const char *cmd, const char *options)
+{
+       COMMAND_REC *rec;
+       COMMAND_MODULE_REC *modrec;
+        int reload;
+
+       g_return_if_fail(module != NULL);
+       g_return_if_fail(cmd != NULL);
+       g_return_if_fail(options != NULL);
+
+        rec = command_find(cmd);
+       g_return_if_fail(rec != NULL);
+        modrec = command_module_get(rec, module);
+
+       reload = modrec->options != NULL;
+        if (reload) {
+               /* options already set for the module ..
+                  we need to recalculate everything */
+               g_free(modrec->options);
+       }
+
+       modrec->options = g_strdup(options);
+
+        if (reload)
+               command_update_options(rec);
+        else
+               command_calc_options(rec, options);
+}
+
+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;
+}
+
+static 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;
+}
+
+/* Find specified option from list of options - the `option' might be
+   shortened version of the full command. Returns index where the
+   option was found, -1 if not found or -2 if there was multiple matches. */
+static int option_find(char **array, const char *option)
+{
+       char **tmp;
+       int index, found, len, multiple;
+
+       g_return_val_if_fail(array != NULL, -1);
+       g_return_val_if_fail(option != NULL, -1);
+
+       len = strlen(option);
+
+       found = -1; index = 0; multiple = FALSE;
+       for (tmp = array; *tmp != NULL; tmp++, index++) {
+               const char *text = *tmp + iscmdtype(**tmp);
+
+               if (g_strncasecmp(text, option, len) == 0) {
+                       if (text[len] == '\0') {
+                               /* full match */
+                               return index;
+                       }
+
+                       if (found != -1) {
+                               /* multiple matches - we still need to check
+                                  if there's a full match left.. */
+                               multiple = TRUE;
+                       }
+
+                       /* partial match, check that it's the only one */
+                       found = index;
+               }
+       }
+
+       if (multiple)
+               return -2;
+
+       return found;
+}
+
+static int get_cmd_options(char **data, int ignore_unknown,
+                          const char *cmd, GHashTable *options)
+{
+       COMMAND_REC *rec;
+       char *option, *arg, **optlist;
+       int pos;
+
+       /* get option definations */
+       rec = cmd == NULL ? NULL : command_find(cmd);
+       optlist = rec == NULL ? NULL : rec->options;
+
+       option = NULL; pos = -1;
+       for (;;) {
+               if (**data == '-') {
+                       if (option != NULL && *optlist[pos] == '+') {
+                               /* required argument missing! */
+                                *data = optlist[pos] + 1;
+                               return CMDERR_OPTION_ARG_MISSING;
+                       }
+
+                       (*data)++;
+                       if (**data == '-' && isspace((*data)[1])) {
+                               /* -- option means end of options even
+                                  if next word starts with - */
+                               (*data)++;
+                               while (isspace(**data)) (*data)++;
+                               break;
+                       }
+
+                       if (!isspace(**data))
+                               option = cmd_get_param(data);
+                       else {
+                               option = "-";
+                               (*data)++;
+                       }
+
+                       /* check if this option can have argument */
+                       pos = optlist == NULL ? -1 :
+                               option_find(optlist, option);
+                       if (pos == -1 && !ignore_unknown) {
+                               /* unknown option! */
+                                *data = option;
+                               return CMDERR_OPTION_UNKNOWN;
+                       }
+                       if (pos == -2 && !ignore_unknown) {
+                                /* multiple matches */
+                               *data = option;
+                               return CMDERR_OPTION_AMBIGUOUS;
+                       }
+                       if (pos >= 0) {
+                               /* if we used a shortcut of parameter, put
+                                  the whole parameter name in options table */
+                               option = optlist[pos] +
+                                       iscmdtype(*optlist[pos]);
+                       }
+                       if (options != NULL)
+                               g_hash_table_insert(options, option, "");
+
+                       if (pos < 0 || !iscmdtype(*optlist[pos]) ||
+                           *optlist[pos] == '!')
+                               option = NULL;
+
+                       while (isspace(**data)) (*data)++;
+                       continue;
+               }
+
+               if (option == NULL)
+                       break;
+
+               if (*optlist[pos] == '@' && !isdigit(**data))
+                       break; /* expected a numeric argument */
+
+               /* save the argument */
+               arg = cmd_get_quoted_param(data);
+               if (options != NULL) {
+                       g_hash_table_remove(options, option);
+                       g_hash_table_insert(options, option, arg);
+               }
+               option = NULL;
+
+               while (isspace(**data)) (*data)++;
+       }
+
+       return 0;
+}
+
+typedef struct {
+       char *data;
+        GHashTable *options;
+} CMD_TEMP_REC;
+
+static char *get_optional_channel(CHANNEL_REC *active_channel, char **data)
+{
+        CHANNEL_REC *chanrec;
+       char *tmp, *origtmp, *channel, *ret;
+
+       if (active_channel == NULL) {
+                /* no active channel in window, channel required */
+               return cmd_get_param(data);
+       }
+
+       origtmp = tmp = g_strdup(*data);
+       channel = cmd_get_param(&tmp);
+
+       if (strcmp(channel, "*") == 0 ||
+           !active_channel->server->ischannel(channel))
+               ret = active_channel->name;
+       else {
+               /* Find the channel first and use it's name if found.
+                  This allows automatic !channel -> !XXXXXchannel replaces. */
+               chanrec = channel_find(active_channel->server, channel);
+               ret = chanrec == NULL ? channel : chanrec->name;
+                cmd_get_param(data);
+       }
+
+       g_free(origtmp);
+        return ret;
+}
+
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
+{
+        CHANNEL_REC *chanrec;
+       CMD_TEMP_REC *rec;
+       GHashTable **opthash;
+       char **str, *arg, *datad;
+       va_list args;
+       int cnt, error, ignore_unknown;
+
+       g_return_val_if_fail(data != NULL, FALSE);
+
+       va_start(args, count);
+
+       rec = g_new0(CMD_TEMP_REC, 1);
+       rec->data = g_strdup(data);
+       *free_me = rec;
+
+        datad = rec->data;
+       error = FALSE;
+
+       chanrec = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
+               (CHANNEL_REC *) va_arg(args, CHANNEL_REC *);
+
+       if (count & PARAM_FLAG_OPTIONS) {
+               arg = (char *) va_arg(args, char *);
+               opthash = (GHashTable **) va_arg(args, GHashTable **);
+
+               rec->options = *opthash =
+                       g_hash_table_new((GHashFunc) g_istr_hash,
+                                        (GCompareFunc) g_istr_equal);
+
+               ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
+               error = get_cmd_options(&datad, ignore_unknown,
+                                       arg, *opthash);
+       }
+
+       if (!error) {
+               /* and now handle the string */
+               cnt = PARAM_WITHOUT_FLAGS(count);
+               if (count & PARAM_FLAG_OPTCHAN) {
+                       /* optional channel as first parameter */
+                       arg = get_optional_channel(chanrec, &datad);
+
+                       str = (char **) va_arg(args, char **);
+                       if (str != NULL) *str = arg;
+                       cnt--;
+               }
+
+               while (cnt-- > 0) {
+                       if (cnt == 0 && count & PARAM_FLAG_GETREST) {
+                               /* get rest */
+                               arg = datad;
+                       } else {
+                               arg = (count & PARAM_FLAG_NOQUOTES) ?
+                                       cmd_get_param(&datad) :
+                                       cmd_get_quoted_param(&datad);
+                       }
+
+                       str = (char **) va_arg(args, char **);
+                       if (str != NULL) *str = arg;
+               }
+       }
+       va_end(args);
+
+       if (error) {
+                signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
+               signal_stop();
+
+                cmd_params_free(rec);
+               *free_me = NULL;
+       }
+
+       return !error;
+}
+
+void cmd_params_free(void *free_me)
+{
+       CMD_TEMP_REC *rec = free_me;
+
+       if (rec->options != NULL) g_hash_table_destroy(rec->options);
+       g_free(rec->data);
+       g_free(rec);
+}
+
+static void command_module_unbind_all(COMMAND_REC *rec,
+                                     COMMAND_MODULE_REC *modrec)
+{
+       GSList *tmp, *next;
+
+       for (tmp = modrec->signals; tmp != NULL; tmp = next) {
+               next = tmp->next;
+
+               command_unbind(rec->cmd, tmp->data);
+       }
+
+       if (g_slist_find(commands, rec) != NULL) {
+               /* this module might have removed some options
+                  from command, update them. */
+               command_update_options(rec);
+       }
+}
+
+void commands_remove_module(const char *module)
+{
+       GSList *tmp, *next, *modlist;
+
+       g_return_if_fail(module != NULL);
+
+       for (tmp = commands; tmp != NULL; tmp = next) {
+               COMMAND_REC *rec = tmp->data;
+
+                next = tmp->next;
+               modlist = gslist_find_string(rec->modules, module);
+               if (modlist != NULL)
+                       command_module_unbind_all(rec, modlist->data);
+       }
+}
+
+#define alias_runstack_push(alias) \
+       alias_runstack = g_slist_append(alias_runstack, alias)
+
+#define alias_runstack_pop(alias) \
+       alias_runstack = g_slist_remove(alias_runstack, alias)
+
+#define alias_runstack_find(alias) \
+        (gslist_find_icase_string(alias_runstack, alias) != NULL)
+
+static void parse_command(const char *command, int expand_aliases,
+                         SERVER_REC *server, void *item)
+{
+       const char *alias, *newcmd;
+       char *cmd, *orig, *args, *oldcmd;
+
+       g_return_if_fail(command != NULL);
+
+       cmd = orig = g_strconcat("command ", command, NULL);
+       args = strchr(cmd+8, ' ');
+       if (args != NULL) *args++ = '\0'; else args = "";
+
+       /* check if there's an alias for command. Don't allow
+          recursive aliases */
+       alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
+               alias_find(cmd+8);
+       if (alias != NULL) {
+                alias_runstack_push(cmd+8);
+               eval_special_string(alias, args, server, item);
+                alias_runstack_pop(cmd+8);
+               g_free(orig);
+               return;
+       }
+
+       /* check if this command can be expanded */
+       newcmd = command_expand(cmd+8);
+       if (newcmd == NULL) {
+                /* ambiguous command */
+               g_free(orig);
+               return;
+       }
+
+       cmd = g_strconcat("command ", newcmd, NULL);
+       if (server != NULL)
+               server_redirect_default(SERVER(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,
+                              command, server, item);
+       }
+       current_command = oldcmd;
+
+       g_free(cmd);
+       g_free(orig);
+}
+
+static void event_command(const char *line, SERVER_REC *server, void *item)
+{
+       char *cmdchar;
+       int expand_aliases = TRUE;
+
+       g_return_if_fail(line != NULL);
+
+       if (*line == '\0') {
+               /* empty line, forget it. */
+                signal_stop();
+               return;
+       }
+
+       cmdchar = strchr(settings_get_str("cmdchars"), *line);
+       if (cmdchar != NULL && line[1] == ' ') {
+               /* "/ text" = same as sending "text" to active channel. */
+               line += 2;
+               cmdchar = NULL;
+       }
+       if (cmdchar == NULL) {
+               /* non-command - let someone else handle this */
+               signal_emit("send text", 3, line, server, item);
+               return;
+       }
+
+       /* same cmdchar twice ignores aliases ignores aliases */
+       line++;
+       if (*line == *cmdchar) {
+               line++;
+               expand_aliases = FALSE;
+       }
+
+       /* ^command hides the output - we'll do this at fe-common but
+          we have to skip the ^ char here.. */
+       if (*line == '^') line++;
+
+       parse_command(line, expand_aliases, server, item);
+}
+
+/* SYNTAX: EVAL <command(s)> */
+static void cmd_eval(const char *data, SERVER_REC *server, void *item)
+{
+       g_return_if_fail(data != NULL);
+
+       eval_special_string(data, "", server, item);
+}
+
+/* SYNTAX: CD <directory> */
+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)
+{
+       commands = NULL;
+       current_command = NULL;
+       alias_runstack = NULL;
+
+       signal_default_command = signal_get_uniq_id("default command");
+
+       settings_add_str("misc", "cmdchars", "/");
+       signal_add("send command", (SIGNAL_FUNC) event_command);
+
+       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);
+
+       signal_remove("send command", (SIGNAL_FUNC) event_command);
+
+       command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
+       command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
+}
diff --git a/apps/irssi/src/core/commands.h b/apps/irssi/src/core/commands.h
new file mode 100644 (file)
index 0000000..9dee2e8
--- /dev/null
@@ -0,0 +1,147 @@
+#ifndef __COMMANDS_H
+#define __COMMANDS_H
+
+#include "signals.h"
+
+typedef struct {
+       char *name;
+       char *options;
+        GSList *signals;
+} COMMAND_MODULE_REC;
+
+typedef struct {
+        GSList *modules;
+       char *category;
+       char *cmd;
+       char **options; /* combined from modules[..]->options */
+} COMMAND_REC;
+
+enum {
+       CMDERR_OPTION_UNKNOWN = -3, /* unknown -option */
+       CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */
+       CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */
+
+       CMDERR_UNKNOWN, /* unknown command */
+       CMDERR_AMBIGUOUS, /* ambiguous command */
+
+        CMDERR_ERRNO, /* get the error from errno */
+       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_CHAN_NOT_FOUND, /* channel not found */
+       CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */
+       CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */
+};
+
+/* Return the full command for `alias' */
+#define alias_find(alias) \
+       iconfig_get_str("aliases", alias, NULL)
+
+/* Returning from command function with error */
+#define cmd_return_error(a) \
+       G_STMT_START { \
+         signal_emit("error command", 1, GINT_TO_POINTER(a)); \
+         signal_stop(); \
+         return; \
+       } G_STMT_END
+
+#define cmd_param_error(a) \
+       G_STMT_START { \
+         cmd_params_free(free_arg); \
+         cmd_return_error(a); \
+       } G_STMT_END
+
+extern GSList *commands;
+extern char *current_command; /* the command we're right now. */
+
+/* Bind command to specified function. */
+void command_bind_to(const char *module, int pos, const char *cmd,
+                    const char *category, SIGNAL_FUNC func);
+#define command_bind(a, b, c) command_bind_to(MODULE_NAME, 1, a, b, c)
+#define command_bind_first(a, b, c) command_bind_to(MODULE_NAME, 0, a, b, c)
+#define command_bind_last(a, b, c) command_bind_to(MODULE_NAME, 2, a, b, c)
+
+void command_unbind(const char *cmd, SIGNAL_FUNC func);
+
+/* Run subcommand, `cmd' contains the base command, first word in `data'
+   contains the subcommand */
+void command_runsub(const char *cmd, const char *data,
+                   void *server, void *item);
+
+COMMAND_REC *command_find(const char *cmd);
+int command_have_sub(const char *command);
+
+/* Specify options that command can accept. `options' contains list of
+   options separated with space, each option can contain a special
+   char in front of it:
+
+   '!': no argument (default)
+   '-': optional argument
+   '+': argument required
+   '@': optional numeric argument
+
+   for example if options = "save -file +nick", you can use
+   /command -save -file [<filename>] -nick <nickname>
+
+   You can call this command multiple times for same command, options
+   will be merged. If there's any conflicts with option types, the last
+   call will override the previous */
+#define iscmdtype(c) \
+        ((c) == '!' || (c) == '-' || (c) == '+' || (c) == '@')
+void command_set_options_module(const char *module,
+                               const char *cmd, const char *options);
+#define command_set_options(cmd, options) \
+       command_set_options_module(MODULE_NAME, cmd, options)
+
+/* Returns TRUE if command has specified option. */
+int command_have_option(const char *cmd, const char *option);
+
+/* count can have these flags: */
+#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00000fff)
+/* don't check for quotes - "arg1 arg2" is NOT treated as one argument */
+#define PARAM_FLAG_NOQUOTES 0x00001000
+/* final argument gets all the rest of the arguments */
+#define PARAM_FLAG_GETREST 0x00002000
+/* command contains options - first you need to specify them with
+   command_set_options() function. Example:
+
+     -cmd requiredarg -noargcmd -cmd2 "another arg" -optnumarg rest of text
+
+   You would call this with:
+
+   // only once in init
+   command_set_options("mycmd", "+cmd noargcmd -cmd2 @optnumarg");
+
+   GHashTable *optlist;
+
+   cmd_get_params(data, &free_me, 1 | PARAM_FLAG_OPTIONS |
+                  PARAM_FLAG_GETREST, "mycmd", &optlist, &rest);
+
+   The optlist hash table is filled:
+
+   "cmd" = "requiredarg"
+   "noargcmd" = ""
+   "cmd2" = "another arg"
+   "optnumarg" = "" - this is because "rest" isn't a numeric value
+*/
+#define PARAM_FLAG_OPTIONS 0x00004000
+/* don't complain about unknown options */
+#define PARAM_FLAG_UNKNOWN_OPTIONS 0x00008000
+/* optional channel in first argument */
+#define PARAM_FLAG_OPTCHAN 0x00010000
+
+char *cmd_get_param(char **data);
+/* get parameters from command - you should point free_me somewhere and
+   cmd_params_free() it after you don't use any of the parameters anymore.
+
+   Returns TRUE if all ok, FALSE if error occured. */
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...);
+
+void cmd_params_free(void *free_me);
+
+void commands_remove_module(const char *module);
+
+void commands_init(void);
+void commands_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/core.c b/apps/irssi/src/core/core.c
new file mode 100644 (file)
index 0000000..fd7459d
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ 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 "pidwait.h"
+
+#include "net-disconnect.h"
+#include "net-sendbuffer.h"
+#include "signals.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "chatnets.h"
+#include "commands.h"
+#include "expandos.h"
+#include "write-buffer.h"
+#include "log.h"
+#include "rawlog.h"
+#include "ignore.h"
+
+#include "channels.h"
+#include "queries.h"
+#include "nicklist.h"
+#include "nickmatch-cache.h"
+
+void chat_commands_init(void);
+void chat_commands_deinit(void);
+
+int irssi_gui;
+
+void core_init(void)
+{
+       modules_init();
+#ifndef WIN32
+       pidwait_init();
+#endif
+
+       net_disconnect_init();
+       net_sendbuffer_init();
+       signals_init();
+       settings_init();
+       commands_init();
+        nickmatch_cache_init();
+
+       chat_protocols_init();
+       chatnets_init();
+        expandos_init();
+       ignore_init();
+       servers_init();
+        write_buffer_init();
+       log_init();
+       rawlog_init();
+
+       channels_init();
+       queries_init();
+       nicklist_init();
+
+       chat_commands_init();
+        settings_check();
+}
+
+void core_deinit(void)
+{
+       chat_commands_deinit();
+
+       nicklist_deinit();
+       queries_deinit();
+       channels_deinit();
+
+       rawlog_deinit();
+       log_deinit();
+        write_buffer_deinit();
+       servers_deinit();
+       ignore_deinit();
+        expandos_deinit();
+       chatnets_deinit();
+       chat_protocols_deinit();
+
+        nickmatch_cache_deinit();
+       commands_deinit();
+       settings_deinit();
+       signals_deinit();
+       net_sendbuffer_deinit();
+       net_disconnect_deinit();
+
+#ifndef WIN32
+       pidwait_deinit();
+#endif
+       modules_deinit();
+}
diff --git a/apps/irssi/src/core/core.h b/apps/irssi/src/core/core.h
new file mode 100644 (file)
index 0000000..61d6ef7
--- /dev/null
@@ -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/apps/irssi/src/core/expandos.c b/apps/irssi/src/core/expandos.c
new file mode 100644 (file)
index 0000000..e657231
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ expandos.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 "modules.h"
+#include "signals.h"
+#include "expandos.h"
+#include "settings.h"
+#include "commands.h"
+#include "misc.h"
+#include "irssi-version.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "queries.h"
+#include "window-item-def.h"
+
+#ifdef HAVE_SYS_UTSNAME_H
+#  include <sys/utsname.h>
+#endif
+
+#define MAX_EXPANDO_SIGNALS 10
+
+typedef struct {
+       EXPANDO_FUNC func;
+
+        int signals;
+       int signal_ids[MAX_EXPANDO_SIGNALS];
+        int signal_args[MAX_EXPANDO_SIGNALS];
+} EXPANDO_REC;
+
+static int timer_tag;
+
+static EXPANDO_REC *char_expandos[127];
+static GHashTable *expandos;
+static time_t client_start_time;
+static char *last_sent_msg, *last_sent_msg_body;
+static char *last_privmsg_from, *last_public_from;
+static char *sysname, *sysrelease, *sysarch;
+static const char *timestamp_format;
+
+#define CHAR_EXPANDOS_COUNT \
+       ((int) (sizeof(char_expandos) / sizeof(char_expandos[0])))
+
+/* Create expando - overrides any existing ones. */
+void expando_create(const char *key, EXPANDO_FUNC func, ...)
+{
+        EXPANDO_REC *rec;
+        const char *signal;
+       va_list va;
+
+       g_return_if_fail(key != NULL || *key == '\0');
+       g_return_if_fail(func != NULL);
+
+       if (key[1] != '\0')
+               rec = g_hash_table_lookup(expandos, key);
+       else {
+               /* single character expando */
+               rec = char_expandos[(int) *key];
+       }
+
+       if (rec != NULL)
+               rec->signals = 0;
+       else {
+               rec = g_new0(EXPANDO_REC, 1);
+                if (key[1] != '\0')
+                       g_hash_table_insert(expandos, g_strdup(key), rec);
+               else
+                       char_expandos[(int) *key] = rec;
+       }
+
+       rec->func = func;
+
+       va_start(va, func);
+       while ((signal = (const char *) va_arg(va, const char *)) != NULL)
+               expando_add_signal(key, signal, (int) va_arg(va, int));
+        va_end(va);
+}
+
+static EXPANDO_REC *expando_find(const char *key)
+{
+       if (key[1] != '\0')
+               return g_hash_table_lookup(expandos, key);
+        else
+               return char_expandos[(int) *key];
+}
+
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg)
+{
+       EXPANDO_REC *rec;
+
+       g_return_if_fail(key != NULL);
+       g_return_if_fail(signal != NULL);
+
+        rec = expando_find(key);
+        g_return_if_fail(rec != NULL);
+
+       if (arg == EXPANDO_NEVER) {
+                /* expando changes never */
+               rec->signals = -1;
+       } else if (rec->signals < MAX_EXPANDO_SIGNALS) {
+               g_return_if_fail(rec->signals != -1);
+
+               rec->signal_ids[rec->signals] = signal_get_uniq_id(signal);
+               rec->signal_args[rec->signals] = arg;
+                rec->signals++;
+       }
+}
+
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func)
+{
+       gpointer origkey;
+        EXPANDO_REC *rec;
+
+       g_return_if_fail(key != NULL || *key == '\0');
+       g_return_if_fail(func != NULL);
+
+       if (key[1] == '\0') {
+               /* single character expando */
+               rec = char_expandos[(int) *key];
+               if (rec != NULL && rec->func == func) {
+                       char_expandos[(int) *key] = NULL;
+                       g_free(rec);
+               }
+       } else if (g_hash_table_lookup_extended(expandos, key, &origkey,
+                                               (gpointer *) &rec)) {
+               if (rec->func == func) {
+                       g_free(origkey);
+                       g_hash_table_remove(expandos, key);
+                       g_free(rec);
+               }
+       }
+}
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+       SIGNAL_FUNC func;
+       EXPANDO_REC *rec;
+        int n, arg;
+
+       g_return_if_fail(key != NULL);
+       g_return_if_fail(funccount >= 1);
+       g_return_if_fail(funcs != NULL);
+       g_return_if_fail(funcs[0] != NULL);
+
+        rec = expando_find(key);
+       g_return_if_fail(rec != NULL);
+
+       if (rec->signals == 0) {
+               /* it's unknown when this expando changes..
+                  check it once in a second */
+                signal_add("expando timer", funcs[EXPANDO_ARG_NONE]);
+       }
+
+       for (n = 0; n < rec->signals; n++) {
+               arg = rec->signal_args[n];
+               func = arg < funccount ? funcs[arg] : NULL;
+               if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+               signal_add_to_id(MODULE_NAME, 1, rec->signal_ids[n], func);
+       }
+}
+
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+       SIGNAL_FUNC func;
+       EXPANDO_REC *rec;
+        int n, arg;
+
+       g_return_if_fail(key != NULL);
+       g_return_if_fail(funccount >= 1);
+       g_return_if_fail(funcs != NULL);
+       g_return_if_fail(funcs[0] != NULL);
+
+        rec = expando_find(key);
+       g_return_if_fail(rec != NULL);
+
+       if (rec->signals == 0) {
+               /* it's unknown when this expando changes..
+                  check it once in a second */
+                signal_remove("expando timer", funcs[EXPANDO_ARG_NONE]);
+       }
+
+       for (n = 0; n < rec->signals; n++) {
+               arg = rec->signal_args[n];
+               func = arg < funccount ? funcs[arg] : NULL;
+               if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+               signal_remove_id(rec->signal_ids[n], func);
+       }
+}
+
+EXPANDO_FUNC expando_find_char(char chr)
+{
+       g_return_val_if_fail(chr < CHAR_EXPANDOS_COUNT, NULL);
+
+       return char_expandos[(int) chr] == NULL ? NULL :
+               char_expandos[(int) chr]->func;
+}
+
+EXPANDO_FUNC expando_find_long(const char *key)
+{
+       EXPANDO_REC *rec = g_hash_table_lookup(expandos, key);
+       return rec == NULL ? NULL : rec->func;
+}
+
+/* last person who sent you a MSG */
+static char *expando_lastmsg(SERVER_REC *server, void *item, int *free_ret)
+{
+       return last_privmsg_from;
+}
+
+/* last person to whom you sent a MSG */
+static char *expando_lastmymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+       return last_sent_msg;
+}
+
+/* last person to send a public message to a channel you are on */
+static char *expando_lastpublic(SERVER_REC *server, void *item, int *free_ret)
+{
+       return last_public_from;
+}
+
+/* text of your AWAY message, if any */
+static char *expando_awaymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->away_reason;
+}
+
+/* body of last MSG you sent */
+static char *expando_lastmymsg_body(SERVER_REC *server, void *item, int *free_ret)
+{
+       return last_sent_msg_body;
+}
+
+/* current channel */
+static char *expando_channel(SERVER_REC *server, void *item, int *free_ret)
+{
+       return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->name;
+}
+
+/* time client was started, $time() format */
+static char *expando_clientstarted(SERVER_REC *server, void *item, int *free_ret)
+{
+        *free_ret = TRUE;
+       return g_strdup_printf("%ld", (long) client_start_time);
+}
+
+/* channel you were last INVITEd to */
+static char *expando_last_invite(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->last_invite;
+}
+
+/* client version text string */
+static char *expando_version(SERVER_REC *server, void *item, int *free_ret)
+{
+       return IRSSI_VERSION;
+}
+
+/* current value of CMDCHARS */
+static char *expando_cmdchars(SERVER_REC *server, void *item, int *free_ret)
+{
+       return (char *) settings_get_str("cmdchars");
+}
+
+/* modes of current channel, if any */
+static char *expando_chanmode(SERVER_REC *server, void *item, int *free_ret)
+{
+       return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->mode;
+}
+
+/* current nickname */
+static char *expando_nick(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->nick;
+}
+
+/* value of STATUS_OPER if you are an irc operator */
+static char *expando_statusoper(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL || !server->server_operator ? "" :
+               (char *) settings_get_str("STATUS_OPER");
+}
+
+/* if you are a channel operator in $C, expands to a '@' */
+static char *expando_chanop(SERVER_REC *server, void *item, int *free_ret)
+{
+       return IS_CHANNEL(item) && CHANNEL(item)->chanop ? "@" : "";
+}
+
+/* nickname of whomever you are QUERYing */
+static char *expando_query(SERVER_REC *server, void *item, int *free_ret)
+{
+       return !IS_QUERY(item) ? "" : QUERY(item)->name;
+}
+
+/* version of current server */
+static char *expando_serverversion(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->version;
+}
+
+/* target of current input (channel or QUERY nickname) */
+static char *expando_target(SERVER_REC *server, void *item, int *free_ret)
+{
+       return item == NULL ? "" : ((WI_ITEM_REC *) item)->name;
+}
+
+/* client release date (numeric version string) */
+static char *expando_releasedate(SERVER_REC *server, void *item, int *free_ret)
+{
+       return IRSSI_VERSION_DATE;
+}
+
+/* current working directory */
+static char *expando_workdir(SERVER_REC *server, void *item, int *free_ret)
+{
+       *free_ret = TRUE;
+       return g_get_current_dir();
+}
+
+/* value of REALNAME */
+static char *expando_realname(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->connrec->realname;
+}
+
+/* time of day (hh:mm) */
+static char *expando_time(SERVER_REC *server, void *item, int *free_ret)
+{
+       time_t now;
+       struct tm *tm;
+        char str[256];
+
+        now = time(NULL);
+       tm = localtime(&now);
+
+       if (strftime(str, sizeof(str), timestamp_format, tm) == 0)
+                return "";
+
+       *free_ret = TRUE;
+        return g_strdup(str);
+}
+
+/* a literal '$' */
+static char *expando_dollar(SERVER_REC *server, void *item, int *free_ret)
+{
+       return "$";
+}
+
+/* system name */
+static char *expando_sysname(SERVER_REC *server, void *item, int *free_ret)
+{
+       return sysname;
+}
+
+/* system release */
+static char *expando_sysrelease(SERVER_REC *server, void *item, int *free_ret)
+{
+        return sysrelease;
+}
+
+/* system architecture */
+static char *expando_sysarch(SERVER_REC *server, void *item, int *free_ret)
+{
+        return sysarch;
+}
+
+/* Topic of active channel (or address of queried nick) */
+static char *expando_topic(SERVER_REC *server, void *item, int *free_ret)
+{
+       return IS_CHANNEL(item) ? CHANNEL(item)->topic :
+               IS_QUERY(item) ? QUERY(item)->address : "";
+}
+
+/* Server tag */
+static char *expando_servertag(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->tag;
+}
+
+/* Server chatnet */
+static char *expando_chatnet(SERVER_REC *server, void *item, int *free_ret)
+{
+       return server == NULL ? "" : server->connrec->chatnet;
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+                              const char *nick, const char *address,
+                              const char *target)
+{
+       g_free_not_null(last_public_from);
+       last_public_from = g_strdup(nick);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+                               const char *nick, const char *address)
+{
+       g_free_not_null(last_privmsg_from);
+       last_privmsg_from = g_strdup(nick);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+                                   const char *target, const char *origtarget)
+{
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(msg != NULL);
+
+       if (target != NULL) {
+               if (target != last_sent_msg) {
+                       g_free_not_null(last_sent_msg);
+                       last_sent_msg = g_strdup(target);
+               }
+               g_free_not_null(last_sent_msg_body);
+               last_sent_msg_body = g_strdup(msg);
+       }
+}
+
+static int sig_timer(void)
+{
+        signal_emit("expando timer", 0);
+        return 1;
+}
+
+static void read_settings(void)
+{
+        timestamp_format = settings_get_str("timestamp_format");
+}
+
+void expandos_init(void)
+{
+#ifdef HAVE_SYS_UTSNAME_H
+       struct utsname un;
+#endif
+       settings_add_str("misc", "STATUS_OPER", "*");
+       settings_add_str("misc", "timestamp_format", "%H:%M");
+
+       client_start_time = time(NULL);
+       last_sent_msg = NULL; last_sent_msg_body = NULL;
+       last_privmsg_from = NULL; last_public_from = NULL;
+
+        sysname = sysrelease = sysarch = NULL;
+#ifdef HAVE_SYS_UTSNAME_H
+       if (uname(&un) == 0) {
+               sysname = g_strdup(un.sysname);
+               sysrelease = g_strdup(un.release);
+               sysarch = g_strdup(un.machine);
+       }
+#endif
+
+       memset(char_expandos, 0, sizeof(char_expandos));
+       expandos = g_hash_table_new((GHashFunc) g_str_hash,
+                                   (GCompareFunc) g_str_equal);
+
+       expando_create(",", expando_lastmsg,
+                      "message private", EXPANDO_ARG_SERVER, NULL);
+       expando_create(".", expando_lastmymsg,
+                      "command msg", EXPANDO_ARG_NONE, NULL);
+       expando_create(";", expando_lastpublic,
+                      "message public", EXPANDO_ARG_SERVER, NULL);
+       expando_create("A", expando_awaymsg,
+                      "away mode changed", EXPANDO_ARG_NONE, NULL);
+       expando_create("B", expando_lastmymsg_body,
+                      "command msg", EXPANDO_ARG_NONE, NULL);
+       expando_create("C", expando_channel,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("F", expando_clientstarted,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("I", expando_last_invite, NULL);
+       expando_create("J", expando_version,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("K", expando_cmdchars,
+                      "setup changed", EXPANDO_ARG_NONE, NULL);
+       expando_create("M", expando_chanmode,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW,
+                      "channel mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+       expando_create("N", expando_nick,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW,
+                       "server nick changed", EXPANDO_ARG_SERVER, NULL);
+       expando_create("O", expando_statusoper,
+                      "setup changed", EXPANDO_ARG_NONE,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW,
+                      "user mode changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("P", expando_chanop,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW,
+                      "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+       expando_create("Q", expando_query,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("R", expando_serverversion,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("T", expando_target,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("V", expando_releasedate,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("W", expando_workdir, NULL);
+       expando_create("Y", expando_realname,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("Z", expando_time, NULL);
+       expando_create("$", expando_dollar,
+                      "", EXPANDO_NEVER, NULL);
+
+       expando_create("sysname", expando_sysname,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("sysrelease", expando_sysrelease,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("sysarch", expando_sysarch,
+                      "", EXPANDO_NEVER, NULL);
+       expando_create("topic", expando_topic,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window item changed", EXPANDO_ARG_WINDOW,
+                      "channel topic changed", EXPANDO_ARG_WINDOW_ITEM,
+                      "query address changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+       expando_create("tag", expando_servertag,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("chatnet", expando_chatnet,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window server changed", EXPANDO_ARG_WINDOW, NULL);
+
+       read_settings();
+
+        timer_tag = g_timeout_add(1000, (GSourceFunc) sig_timer, NULL);
+       signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void expandos_deinit(void)
+{
+       int n;
+
+       for (n = 0; n < CHAR_EXPANDOS_COUNT; n++)
+               g_free_not_null(char_expandos[n]);
+
+       expando_destroy("sysname", expando_sysname);
+       expando_destroy("sysrelease", expando_sysrelease);
+       expando_destroy("sysarch", expando_sysarch);
+       expando_destroy("topic", expando_topic);
+       expando_destroy("tag", expando_servertag);
+       expando_destroy("chatnet", expando_chatnet);
+
+        g_hash_table_destroy(expandos);
+
+       g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body);
+       g_free_not_null(last_privmsg_from); g_free_not_null(last_public_from);
+       g_free_not_null(sysname); g_free_not_null(sysrelease);
+        g_free_not_null(sysarch);
+
+        g_source_remove(timer_tag);
+       signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/core/expandos.h b/apps/irssi/src/core/expandos.h
new file mode 100644 (file)
index 0000000..3dcb527
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef __EXPANDOS_H
+#define __EXPANDOS_H
+
+#include "signals.h"
+
+/* first argument of signal must match to active .. */
+typedef enum {
+        EXPANDO_ARG_NONE,
+        EXPANDO_ARG_SERVER,
+        EXPANDO_ARG_WINDOW,
+       EXPANDO_ARG_WINDOW_ITEM,
+
+       EXPANDO_NEVER /* special: expando never changes */
+} ExpandoArg;
+
+typedef char* (*EXPANDO_FUNC)
+       (SERVER_REC *server, void *item, int *free_ret);
+
+/* Create expando - overrides any existing ones.
+   ... = signal, type, ..., NULL - list of signals that might change the
+   value of this expando */
+void expando_create(const char *key, EXPANDO_FUNC func, ...);
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg);
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func);
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+
+/* internal: */
+EXPANDO_FUNC expando_find_char(char chr);
+EXPANDO_FUNC expando_find_long(const char *key);
+
+void expandos_init(void);
+void expandos_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/ignore.c b/apps/irssi/src/core/ignore.c
new file mode 100644 (file)
index 0000000..69460c5
--- /dev/null
@@ -0,0 +1,484 @@
+/*
+ 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 "masks.h"
+#include "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "nickmatch-cache.h"
+
+#include "ignore.h"
+
+GSList *ignores;
+
+static NICKMATCH_REC *nickmatch;
+static int time_tag;
+
+/* check if `text' contains ignored nick at the start of the line. */
+static int ignore_check_replies_rec(IGNORE_REC *rec, CHANNEL_REC *channel,
+                                   const char *text)
+{
+       GSList *nicks, *tmp;
+
+       nicks = nicklist_find_multiple(channel, rec->mask);
+       if (nicks == NULL) return FALSE;
+
+       for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+               NICK_REC *nick = tmp->data;
+
+               if (nick_match_msg(channel, text, nick->nick))
+                       return TRUE;
+       }
+       g_slist_free(nicks);
+
+       return FALSE;
+}
+
+static int ignore_check_replies(CHANNEL_REC *chanrec, const char *text)
+{
+       GSList *tmp;
+
+       if (text == NULL || chanrec == NULL)
+               return FALSE;
+
+        /* check reply ignores */
+       for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+               IGNORE_REC *rec = tmp->data;
+
+               if (rec->mask != NULL && rec->replies &&
+                   ignore_check_replies_rec(rec, chanrec, text))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static int ignore_match_pattern(IGNORE_REC *rec, const char *text)
+{
+       if (rec->pattern == NULL)
+               return TRUE;
+
+        if (text == NULL)
+               return FALSE;
+
+       if (rec->regexp) {
+#ifdef HAVE_REGEX_H
+               return rec->regexp_compiled &&
+                       regexec(&rec->preg, text, 0, NULL, 0) == 0;
+#else
+                return FALSE;
+#endif
+       }
+
+       return rec->fullword ?
+               stristr_full(text, rec->pattern) != NULL :
+               stristr(text, rec->pattern) != NULL;
+}
+
+#define ignore_match_level(rec, level) \
+        ((level & (rec)->level) != 0)
+
+#define ignore_match_nickmask(rec, nick, nickmask) \
+       ((rec)->mask == NULL || \
+       (strchr((rec)->mask, '!') != NULL ? \
+               match_wildcards((rec)->mask, nickmask) : \
+               match_wildcards((rec)->mask, nick)))
+
+#define ignore_match_server(rec, server) \
+       ((rec)->servertag == NULL || \
+       g_strcasecmp((server)->tag, (rec)->servertag) == 0)
+
+#define ignore_match_channel(rec, channel) \
+       ((rec)->channels == NULL || ((channel) != NULL && \
+               strarray_find((rec)->channels, (channel)) != -1))
+
+static int ignore_check_without_mask(GSList *list, CHANNEL_REC *channel,
+                                    int level, const char *text)
+{
+       GSList *tmp;
+        int len, best_mask, best_match, best_patt;
+
+        best_mask = best_patt = -1; best_match = FALSE;
+       for (tmp = list; tmp != NULL; tmp = tmp->next) {
+               IGNORE_REC *rec = tmp->data;
+
+               if (ignore_match_level(rec, level) &&
+                   ignore_match_pattern(rec, text)) {
+                       len = rec->mask == NULL ? 0 : strlen(rec->mask);
+                       if (len > best_mask) {
+                               best_mask = len;
+                               best_match = !rec->exception;
+                       } else if (len == best_mask && rec->pattern != NULL) {
+                               len = strlen(rec->pattern);
+                               if (len > best_patt) {
+                                       best_patt = len;
+                                       best_match = !rec->exception;
+                               }
+                       }
+               }
+       }
+
+       if (best_match || (level & MSGLEVEL_PUBLIC) == 0)
+               return best_match;
+
+        return ignore_check_replies(channel, text);
+}
+
+int ignore_check(SERVER_REC *server, const char *nick, const char *host,
+                const char *channel, const char *text, int level)
+{
+       CHANNEL_REC *chanrec;
+       NICK_REC *nickrec;
+        IGNORE_REC *rec;
+       GSList *tmp, *list;
+        char *nickmask;
+        int len, best_mask, best_match, best_patt;
+
+       g_return_val_if_fail(server != NULL, 0);
+        if (nick == NULL) nick = "";
+
+       chanrec = (channel != NULL && server != NULL &&
+                  server->ischannel(channel)) ?
+               channel_find(server, channel) : NULL;
+       if (chanrec != NULL && nick != NULL &&
+           (nickrec = nicklist_find(chanrec, nick)) != NULL) {
+                /* nick found - check only ignores in nickmatch cache */
+               if (nickrec->host == NULL)
+                       nicklist_set_host(chanrec, nickrec, host);
+
+               list = nickmatch_find(nickmatch, nickrec);
+               return ignore_check_without_mask(list, chanrec, level, text);
+       }
+
+       nickmask = g_strconcat(nick, "!", host, NULL);
+
+        best_mask = best_patt = -1; best_match = FALSE;
+       for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+               rec = tmp->data;
+
+               if (ignore_match_level(rec, level) &&
+                   ignore_match_server(rec, server) &&
+                   ignore_match_channel(rec, channel) &&
+                   ignore_match_nickmask(rec, nick, nickmask) &&
+                   ignore_match_pattern(rec, text)) {
+                       len = rec->mask == NULL ? 0 : strlen(rec->mask);
+                       if (len > best_mask) {
+                               best_mask = len;
+                               best_match = !rec->exception;
+                       } else if (len == best_mask && rec->pattern != NULL) {
+                               len = strlen(rec->pattern);
+                               if (len > best_patt) {
+                                       best_patt = len;
+                                       best_match = !rec->exception;
+                               }
+                       }
+               }
+       }
+        g_free(nickmask);
+
+       if (best_match || (level & MSGLEVEL_PUBLIC) == 0)
+               return best_match;
+
+        return ignore_check_replies(chanrec, text);
+}
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask,
+                       char **channels)
+{
+       GSList *tmp;
+       char **chan;
+       int ignore_servertag;
+
+       if (mask != NULL && (*mask == '\0' || strcmp(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->unignore_time > 0)
+               return;
+
+       node = iconfig_node_traverse("(ignores", TRUE);
+       node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+       if (rec->mask != NULL) iconfig_node_set_str(node, "mask", rec->mask);
+       if (rec->level) {
+               levelstr = bits2level(rec->level);
+               iconfig_node_set_str(node, "level", levelstr);
+               g_free(levelstr);
+       }
+       iconfig_node_set_str(node, "pattern", rec->pattern);
+       if (rec->exception) iconfig_node_set_bool(node, "exception", TRUE);
+       if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+       if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+       if (rec->replies) iconfig_node_set_bool(node, "replies", TRUE);
+
+       if (rec->channels != NULL && *rec->channels != NULL) {
+               node = config_node_section(node, "channels", NODE_TYPE_LIST);
+               iconfig_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) iconfig_node_list_remove(node, ignore_index(rec));
+}
+
+void ignore_add_rec(IGNORE_REC *rec)
+{
+#ifdef HAVE_REGEX_H
+       rec->regexp_compiled = !rec->regexp || rec->pattern == NULL ? FALSE :
+               regcomp(&rec->preg, rec->pattern,
+                       REG_EXTENDED|REG_ICASE|REG_NOSUB) == 0;
+#endif
+
+       ignores = g_slist_append(ignores, rec);
+       ignore_set_config(rec);
+
+       signal_emit("ignore created", 1, rec);
+}
+
+static void ignore_destroy(IGNORE_REC *rec, int send_signal)
+{
+       ignores = g_slist_remove(ignores, rec);
+       if (send_signal)
+               signal_emit("ignore destroyed", 1, rec);
+
+#ifdef HAVE_REGEX_H
+       if (rec->regexp_compiled) regfree(&rec->preg);
+#endif
+       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);
+
+       nickmatch_rebuild(nickmatch);
+}
+
+void ignore_update_rec(IGNORE_REC *rec)
+{
+       if (rec->level == 0) {
+               /* unignored everything */
+               ignore_remove_config(rec);
+               ignore_destroy(rec, TRUE);
+       } 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);
+
+               signal_emit("ignore changed", 1, rec);
+               nickmatch_rebuild(nickmatch);
+       }
+}
+
+static int unignore_timeout(void)
+{
+       GSList *tmp, *next;
+        time_t now;
+
+        now = time(NULL);
+       for (tmp = ignores; tmp != NULL; tmp = next) {
+               IGNORE_REC *rec = tmp->data;
+
+               next = tmp->next;
+               if (rec->unignore_time > 0 && now >= rec->unignore_time) {
+                       rec->level = 0;
+                       ignore_update_rec(rec);
+               }
+       }
+
+       return TRUE;
+}
+
+static void read_ignores(void)
+{
+       IGNORE_REC *rec;
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+       while (ignores != NULL)
+                ignore_destroy(ignores->data, FALSE);
+
+       node = iconfig_node_traverse("ignores", FALSE);
+       if (node == NULL) {
+               nickmatch_rebuild(nickmatch);
+               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));
+               if (rec->mask != NULL && strcmp(rec->mask, "*") == 0) {
+                       /* FIXME: remove after .98 */
+                        g_free(rec->mask);
+                       rec->mask = NULL;
+               }
+               rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL));
+               rec->level = level2bits(config_node_get_str(node, "level", ""));
+                rec->exception = config_node_get_bool(node, "exception", FALSE);
+               if (*config_node_get_str(node, "except_level", "") != '\0') {
+                       /* FIXME: remove after .98 */
+                       rec->level = level2bits(config_node_get_str(node, "except_level", ""));
+                        rec->exception = TRUE;
+               }
+               rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+               rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+               rec->replies = config_node_get_bool(node, "replies", FALSE);
+
+               node = config_node_section(node, "channels", -1);
+               if (node != NULL) rec->channels = config_node_get_list(node);
+       }
+
+       nickmatch_rebuild(nickmatch);
+}
+
+static void ignore_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+                             NICK_REC *nick)
+{
+       GSList *tmp, *matches;
+        char *nickmask;
+
+       if (nick->host == NULL)
+               return; /* don't check until host is known */
+
+        matches = NULL;
+       nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+       for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+               IGNORE_REC *rec = tmp->data;
+
+               if (ignore_match_nickmask(rec, nick->nick, nickmask) &&
+                   ignore_match_server(rec, channel->server) &&
+                   ignore_match_channel(rec, channel->name))
+                       matches = g_slist_append(matches, rec);
+       }
+       g_free_not_null(nickmask);
+
+       if (matches == NULL)
+               g_hash_table_remove(list, nick);
+        else
+                g_hash_table_insert(list, nick, matches);
+}
+
+void ignore_init(void)
+{
+       ignores = NULL;
+       nickmatch = nickmatch_init(ignore_nick_cache);
+       time_tag = g_timeout_add(1000, (GSourceFunc) unignore_timeout, NULL);
+
+        read_ignores();
+        signal_add("setup reread", (SIGNAL_FUNC) read_ignores);
+}
+
+void ignore_deinit(void)
+{
+       g_source_remove(time_tag);
+       while (ignores != NULL)
+                ignore_destroy(ignores->data, TRUE);
+        nickmatch_deinit(nickmatch);
+
+       signal_remove("setup reread", (SIGNAL_FUNC) read_ignores);
+}
diff --git a/apps/irssi/src/core/ignore.h b/apps/irssi/src/core/ignore.h
new file mode 100644 (file)
index 0000000..4056062
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __IGNORE_H
+#define __IGNORE_H
+
+#ifdef HAVE_REGEX_H
+#  include <regex.h>
+#endif
+
+typedef struct {
+       int level; /* ignore these levels */
+       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 */
+
+        time_t unignore_time; /* time in sec for temp ignores */
+
+       unsigned int exception:1; /* *don't* ignore */
+       unsigned int regexp:1;
+       unsigned int fullword:1;
+       unsigned int replies:1; /* ignore replies to nick in channel */
+#ifdef HAVE_REGEX_H
+       unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */
+       regex_t preg;
+#endif
+} IGNORE_REC;
+
+extern GSList *ignores;
+
+int ignore_check(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/apps/irssi/src/core/levels.c b/apps/irssi/src/core/levels.c
new file mode 100644 (file)
index 0000000..aae506a
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ 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 const char *levels[] = {
+       "CRAP",
+       "MSGS",
+       "PUBLICS",
+       "NOTICES",
+       "SNOTES",
+       "CTCPS",
+       "ACTIONS",
+       "JOINS",
+       "PARTS",
+       "QUITS",
+       "KICKS",
+       "MODES",
+       "TOPICS",
+       "WALLOPS",
+       "INVITES",
+       "NICKS",
+       "DCC",
+       "DCCMSGS",
+       "CLIENTNOTICES",
+       "CLIENTCRAP",
+       "CLIENTERRORS",
+       "HILIGHT",
+
+       "NOHILIGHT",
+       NULL
+};
+
+int level_get(const char *level)
+{
+       int n, len, match;
+
+       if (g_strcasecmp(level, "ALL") == 0 || strcmp(level, "*") == 0)
+               return MSGLEVEL_ALL;
+
+       if (g_strcasecmp(level, "NEVER") == 0)
+               return MSGLEVEL_NEVER;
+
+       len = strlen(level);
+       if (len == 0) return 0;
+
+       /* partial match allowed, as long as it's the only one that matches */
+       match = 0;
+       for (n = 0; levels[n] != NULL; n++) {
+               if (g_strncasecmp(levels[n], level, len) == 0) {
+                       if ((int)strlen(levels[n]) == len) {
+                               /* full match */
+                               return 1L << n;
+                       }
+                       if (match > 0) {
+                               /* ambiguous - abort */
+                               return 0;
+                       }
+                       match = 1L << n;
+               }
+       }
+
+       return match;
+}
+
+int level2bits(const char *level)
+{
+       char *orig, *str, *ptr;
+       int ret, singlelevel, negative;
+
+       g_return_val_if_fail(level != NULL, 0);
+
+       if (*level == '\0')
+               return 0;
+
+       orig = str = g_strdup(level);
+
+       ret = 0;
+       for (ptr = str; ; str++) {
+               if (*str == ' ')
+                       *str++ = '\0';
+               else if (*str != '\0')
+                       continue;
+
+               negative = *ptr == '-';
+               if (*ptr == '-' || *ptr == '+') ptr++;
+
+               singlelevel = level_get(ptr);
+               if (singlelevel != 0) {
+                       ret = !negative ? (ret | singlelevel) :
+                               (ret & ~singlelevel);
+               }
+
+                       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 == 0)
+               return g_strdup("");
+
+       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 & (1L << n))
+                       g_string_sprintfa(str, "%s ", levels[n]);
+       }
+        if (str->len > 0)
+               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, *itemname;
+       int itemlevel;
+
+       g_return_val_if_fail(src != NULL, dest);
+
+       list = g_strsplit(src, " ", -1);
+       for (item = list; *item != NULL; item++) {
+               itemname = *item + (**item == '+' || **item == '-' ? 1 : 0);
+                g_strup(itemname);
+               itemlevel = level_get(itemname);
+
+               if (strcmp(itemname, "NONE") == 0)
+                        dest = 0;
+               else if (**item == '-')
+                       dest &= ~(itemlevel);
+               else
+                       dest |= itemlevel;
+       }
+       g_strfreev(list);
+
+       return dest;
+}
diff --git a/apps/irssi/src/core/levels.h b/apps/irssi/src/core/levels.h
new file mode 100644 (file)
index 0000000..2d7288f
--- /dev/null
@@ -0,0 +1,45 @@
+#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_WALLOPS      0x0002000
+#define MSGLEVEL_INVITES      0x0004000
+#define MSGLEVEL_NICKS        0x0008000
+#define MSGLEVEL_DCC          0x0010000
+#define MSGLEVEL_DCCMSGS      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 highlight 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 level_get(const char *level);
+int level2bits(const char *level);
+char *bits2level(int bits);
+int combine_level(int dest, const char *src);
+
+#endif
diff --git a/apps/irssi/src/core/line-split.c b/apps/irssi/src/core/line-split.c
new file mode 100644 (file)
index 0000000..b7daf27
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ 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"
+#include "misc.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
+
+struct _LINEBUF_REC {
+        int len;
+       int alloc;
+       int remove;
+        char *str;
+};
+
+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->remove;
+               g_memmove(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/apps/irssi/src/core/line-split.h b/apps/irssi/src/core/line-split.h
new file mode 100644 (file)
index 0000000..7c101dd
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef __LINE_SPLIT_H
+#define __LINE_SPLIT_H
+
+/* 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/apps/irssi/src/core/log.c b/apps/irssi/src/core/log.c
new file mode 100644 (file)
index 0000000..368de25
--- /dev/null
@@ -0,0 +1,565 @@
+/*
+ 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 "servers.h"
+#include "log.h"
+#include "write-buffer.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#define DEFAULT_LOG_FILE_CREATE_MODE 644
+
+#ifdef HAVE_FCNTL
+static struct flock lock;
+#endif
+
+GSList *logs;
+
+static const char *log_item_types[] = {
+       "target",
+       "window",
+
+       NULL
+};
+
+const char *log_timestamp;
+static int log_file_create_mode;
+static int rotate_tag;
+
+static int log_item_str2type(const char *type)
+{
+       int n;
+
+       for (n = 0; log_item_types[n] != NULL; n++) {
+               if (g_strcasecmp(log_item_types[n], type) == 0)
+                       return n;
+       }
+
+       return -1;
+}
+
+static void log_write_timestamp(int handle, const char *format,
+                               const char *text, time_t stamp)
+{
+       struct tm *tm;
+       char str[256];
+
+       g_return_if_fail(format != NULL);
+       if (*format == '\0') return;
+
+       tm = localtime(&stamp);
+       if (strftime(str, sizeof(str), format, tm) > 0)
+               write_buffer(handle, str, strlen(str));
+       if (text != NULL) write_buffer(handle, text, strlen(text));
+}
+
+static char *log_filename(LOG_REC *log)
+{
+       char *str, fname[1024];
+       struct tm *tm;
+        size_t ret;
+       time_t now;
+
+       now = time(NULL);
+       tm = localtime(&now);
+
+       str = convert_home(log->fname);
+       ret = strftime(fname, sizeof(fname), str, tm);
+       g_free(str);
+
+       if (ret <= 0) {
+               g_warning("log_filename() : strftime() failed");
+                return NULL;
+       }
+
+       return g_strdup(fname);
+}
+
+int log_start_logging(LOG_REC *log)
+{
+       g_return_val_if_fail(log != NULL, FALSE);
+
+       if (log->handle != -1)
+               return TRUE;
+
+       /* Append/create log file */
+       g_free_not_null(log->real_fname);
+       log->real_fname = log_filename(log);
+       log->handle = log->real_fname == NULL ? -1 :
+               open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT,
+                    log_file_create_mode);
+       if (log->handle == -1) {
+               signal_emit("log create failed", 1, log);
+               log->failed = TRUE;
+               return FALSE;
+       }
+#ifdef HAVE_FCNTL
+        memset(&lock, 0, sizeof(lock));
+       lock.l_type = F_WRLCK;
+       if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) {
+               close(log->handle);
+               log->handle = -1;
+               signal_emit("log locked", 1, log);
+               log->failed = TRUE;
+               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);
+       log->failed = FALSE;
+       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
+
+       write_buffer_flush();
+       close(log->handle);
+       log->handle = -1;
+}
+
+static void log_rotate_check(LOG_REC *log)
+{
+       char *new_fname;
+
+       g_return_if_fail(log != NULL);
+
+       if (log->handle == -1 || log->real_fname == NULL)
+               return;
+
+       new_fname = log_filename(log);
+       if (strcmp(new_fname, log->real_fname) != 0) {
+               /* rotate log */
+               log_stop_logging(log);
+               log_start_logging(log);
+       }
+       g_free(new_fname);
+}
+
+void log_write_rec(LOG_REC *log, const char *str, int level)
+{
+       struct tm *tm;
+       time_t now;
+       int hour, day;
+
+       g_return_if_fail(log != NULL);
+       g_return_if_fail(str != NULL);
+
+       if (log->handle == -1)
+               return;
+
+       now = time(NULL);
+       tm = localtime(&now);
+       hour = tm->tm_hour;
+       day = tm->tm_mday;
+
+       tm = localtime(&log->last);
+       day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */
+       if (tm->tm_hour != hour) {
+               /* hour changed, check if we need to rotate log file */
+                log_rotate_check(log);
+       }
+
+       if (day != 0) {
+               /* day changed */
+               log_write_timestamp(log->handle,
+                                   settings_get_str("log_day_changed"),
+                                   "\n", now);
+       }
+
+       log->last = now;
+
+        if ((level & MSGLEVEL_LASTLOG) == 0)
+               log_write_timestamp(log->handle, log_timestamp, str, now);
+       else
+               write_buffer(log->handle, str, strlen(str));
+       write_buffer(log->handle, "\n", 1);
+
+       signal_emit("log written", 2, log, str);
+}
+
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+                           const char *servertag)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(log != NULL, NULL);
+       g_return_val_if_fail(item != NULL, NULL);
+
+       for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+               LOG_ITEM_REC *rec = tmp->data;
+
+               if (rec->type == type && g_strcasecmp(rec->name, item) == 0 &&
+                   (rec->servertag == NULL || (servertag != NULL &&
+                       g_strcasecmp(rec->servertag, servertag) == 0)))
+                       return rec;
+       }
+
+       return NULL;
+}
+
+void log_file_write(SERVER_REC *server, const char *item, int level,
+                   const char *str, int no_fallbacks)
+{
+       GSList *tmp, *fallbacks;
+       char *tmpstr, *servertag;
+       int found;
+
+       g_return_if_fail(str != NULL);
+
+       if (logs == NULL)
+               return;
+
+       servertag = server == NULL ? NULL : server->tag;
+       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 &&
+                        log_item_find(rec, LOG_ITEM_TARGET, item,
+                                      servertag) != NULL)
+                       log_write_rec(rec, str, level);
+       }
+
+       if (!found && !no_fallbacks && fallbacks != NULL) {
+               /* not found from any items, so write it to all main logs */
+               tmpstr = (level & MSGLEVEL_PUBLIC) ?
+                       g_strconcat(item, ": ", str, NULL) :
+                       g_strdup(str);
+
+               for (tmp = fallbacks; tmp != NULL; tmp = tmp->next)
+                        log_write_rec(tmp->data, tmpstr, level);
+
+               g_free(tmpstr);
+       }
+        g_slist_free(fallbacks);
+}
+
+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;
+}
+
+static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent)
+{
+       GSList *tmp;
+       CONFIG_NODE *node;
+
+       parent = config_node_section(parent, "items", NODE_TYPE_LIST);
+       for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+               LOG_ITEM_REC *rec = tmp->data;
+
+                node = config_node_section(parent, NULL, NODE_TYPE_BLOCK);
+               iconfig_node_set_str(node, "type", log_item_types[rec->type]);
+               iconfig_node_set_str(node, "name", rec->name);
+               iconfig_node_set_str(node, "server", rec->servertag);
+       }
+}
+
+static void log_update_config(LOG_REC *log)
+{
+       CONFIG_NODE *node;
+       char *levelstr;
+
+       if (log->temp)
+               return;
+
+       node = iconfig_node_traverse("logs", TRUE);
+       node = config_node_section(node, log->fname, NODE_TYPE_BLOCK);
+
+       if (log->autoopen)
+               iconfig_node_set_bool(node, "auto_open", TRUE);
+       else
+               iconfig_node_set_str(node, "auto_open", NULL);
+
+       levelstr = bits2level(log->level);
+       iconfig_node_set_str(node, "level", levelstr);
+       g_free(levelstr);
+
+       iconfig_node_set_str(node, "items", NULL);
+
+       if (log->items != NULL)
+               log_items_update_config(log, node);
+}
+
+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)
+{
+       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->real_fname = log_filename(rec);
+               rec->handle = -1;
+       }
+
+       rec->level = level;
+       return rec;
+}
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+                 const char *servertag)
+{
+       LOG_ITEM_REC *rec;
+
+       g_return_if_fail(log != NULL);
+       g_return_if_fail(name != NULL);
+
+       if (log_item_find(log, type, name, servertag))
+               return;
+
+       rec = g_new0(LOG_ITEM_REC, 1);
+       rec->type = type;
+       rec->name = g_strdup(name);
+       rec->servertag = g_strdup(servertag);
+
+       log->items = g_slist_append(log->items, 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_update_config(log);
+       signal_emit("log new", 1, log);
+}
+
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item)
+{
+       log->items = g_slist_remove(log->items, item);
+
+       g_free(item->name);
+       g_free_not_null(item->servertag);
+       g_free(item);
+}
+
+static void log_destroy(LOG_REC *log)
+{
+       g_return_if_fail(log != NULL);
+
+       if (log->handle != -1)
+               log_stop_logging(log);
+
+       logs = g_slist_remove(logs, log);
+       signal_emit("log remove", 1, log);
+
+       while (log->items != NULL)
+               log_item_destroy(log, log->items->data);
+       g_free(log->fname);
+       g_free_not_null(log->real_fname);
+       g_free(log);
+}
+
+void log_close(LOG_REC *log)
+{
+       g_return_if_fail(log != NULL);
+
+       log_remove_config(log);
+       log_destroy(log);
+}
+
+static int sig_rotate_check(void)
+{
+       static int last_hour = -1;
+       struct tm tm;
+       time_t now;
+
+       /* don't do anything until hour is changed */
+       now = time(NULL);
+       memcpy(&tm, localtime(&now), sizeof(tm));
+       if (tm.tm_hour != last_hour) {
+               last_hour = tm.tm_hour;
+               g_slist_foreach(logs, (GFunc) log_rotate_check, NULL);
+       }
+       return 1;
+}
+
+static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log)
+{
+       LOG_ITEM_REC *rec;
+       GSList *tmp;
+       char *item;
+       int type;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               if (node->type != NODE_TYPE_BLOCK)
+                       continue;
+
+               item = config_node_get_str(node, "name", NULL);
+               type = log_item_str2type(config_node_get_str(node, "type", NULL));
+               if (item == NULL || type == -1)
+                       continue;
+
+               rec = g_new0(LOG_ITEM_REC, 1);
+               rec->type = type;
+               rec->name = g_strdup(item);
+               rec->servertag = g_strdup(config_node_get_str(node, "server", NULL));
+
+               log->items = g_slist_append(log->items, rec);
+       }
+}
+
+static void log_read_config(void)
+{
+       CONFIG_NODE *node;
+       LOG_REC *log;
+       GSList *tmp, *next, *fnames;
+
+       /* close old logs, save list of open logs */
+       fnames = NULL;
+       for (tmp = logs; tmp != NULL; tmp = next) {
+               log = tmp->data;
+
+               next = tmp->next;
+               if (log->temp)
+                       continue;
+
+               if (log->handle != -1)
+                       fnames = g_slist_append(fnames, g_strdup(log->fname));
+               log_destroy(log);
+       }
+
+       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));
+
+               node = config_node_section(node, "items", -1);
+               if (node != NULL)
+                       log_items_read_config(node, log);
+
+               if (log->autoopen || gslist_find_string(fnames, log->fname))
+                       log_start_logging(log);
+       }
+
+       g_slist_foreach(fnames, (GFunc) g_free, NULL);
+       g_slist_free(fnames);
+}
+
+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",
+                        DEFAULT_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);
+}
+
+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);
+}
diff --git a/apps/irssi/src/core/log.h b/apps/irssi/src/core/log.h
new file mode 100644 (file)
index 0000000..7361b6a
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef __LOG_H
+#define __LOG_H
+
+enum {
+       LOG_ITEM_TARGET, /* channel, query, .. */
+       LOG_ITEM_WINDOW_REFNUM
+};
+
+typedef struct {
+       int type;
+        char *name;
+       char *servertag;
+} LOG_ITEM_REC;
+
+typedef struct {
+       char *fname; /* file name, in strftime() format */
+       char *real_fname; /* the current expanded file name */
+       int handle; /* file handle */
+       time_t opened;
+
+       int level; /* log only these levels */
+       GSList *items; /* log only on these items */
+
+       time_t last; /* when last message was written */
+
+       unsigned int autoopen:1; /* automatically start logging at startup */
+       unsigned int failed:1; /* opening log failed last time */
+       unsigned 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);
+void log_update(LOG_REC *log);
+void log_close(LOG_REC *log);
+LOG_REC *log_find(const char *fname);
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+                 const char *servertag);
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item);
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+                           const char *servertag);
+
+void log_file_write(SERVER_REC *server, const char *item, int level,
+                   const char *str, int no_fallbacks);
+void log_write_rec(LOG_REC *log, const char *str, int level);
+
+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/apps/irssi/src/core/masks.c b/apps/irssi/src/core/masks.c
new file mode 100644 (file)
index 0000000..7014e43
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ 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 "servers.h"
+
+/* Returns TRUE if mask contains '!' ie. address should be checked too.
+   Also checks if mask contained any wildcards. */
+static int check_address(const char *mask, int *wildcards)
+{
+       int ret;
+
+       ret = FALSE;
+       while (*mask != '\0') {
+               if (*mask == '!') {
+                       if (*wildcards) return TRUE;
+                       ret = TRUE;
+               }
+
+               if (*mask == '?' || *mask == '*') {
+                       *wildcards = TRUE;
+                       if (ret) return TRUE;
+               }
+               mask++;
+       }
+
+       return ret;
+}
+
+static int check_mask(SERVER_REC *server, const char *mask,
+                     const char *str, int wildcards)
+{
+       if (server != NULL && server->mask_match_func != NULL) {
+               /* use server specified mask match function */
+               return server->mask_match_func(mask, str);
+       }
+
+       return wildcards ? match_wildcards(mask, str) :
+               g_strcasecmp(mask, str) == 0;
+}
+
+int mask_match(SERVER_REC *server, const char *mask,
+              const char *nick, const char *user, const char *host)
+{
+       char *str;
+       int ret, wildcards;
+
+       g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+       g_return_val_if_fail(mask != NULL && nick != NULL &&
+                            nick != NULL && host != NULL, FALSE);
+
+       str = !check_address(mask, &wildcards) ? (char *) nick :
+               g_strdup_printf("%s!%s@%s", nick, user, host);
+       ret = check_mask(server, mask, str, wildcards);
+       if (str != nick) g_free(str);
+
+       return ret;
+}
+
+int mask_match_address(SERVER_REC *server, const char *mask,
+                      const char *nick, const char *address)
+{
+       char *str;
+       int ret, wildcards;
+
+       g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+       g_return_val_if_fail(mask != NULL && nick != NULL, FALSE);
+       if (address == NULL) address = "";
+
+       str = !check_address(mask, &wildcards) ? (char *) nick :
+               g_strdup_printf("%s!%s", nick, address);
+       ret = check_mask(server, mask, str, wildcards);
+       if (str != nick) g_free(str);
+
+       return ret;
+}
+
+int masks_match(SERVER_REC *server, const char *masks,
+               const char *nick, const char *address)
+{
+       int (*mask_match_func)(const char *, const char *);
+       char **list, **tmp, *mask;
+       int found;
+
+       g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+       g_return_val_if_fail(masks != NULL &&
+                            nick != NULL && address != NULL, FALSE);
+
+       if (*masks == '\0')
+                return FALSE;
+
+       mask_match_func = server != NULL && server->mask_match_func != NULL ?
+               server->mask_match_func : match_wildcards;
+
+       found = FALSE;
+       mask = g_strdup_printf("%s!%s", nick, address);
+       list = g_strsplit(masks, " ", -1);
+       for (tmp = list; *tmp != NULL; tmp++) {
+               if (g_strcasecmp(*tmp, nick) == 0) {
+                        found = TRUE;
+                       break;
+               }
+
+               if (mask_match_func(*tmp, mask)) {
+                       found = TRUE;
+                       break;
+               }
+       }
+       g_strfreev(list);
+       g_free(mask);
+
+       return found;
+}
diff --git a/apps/irssi/src/core/masks.h b/apps/irssi/src/core/masks.h
new file mode 100644 (file)
index 0000000..b6ce20d
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef __MASKS_H
+#define __MASKS_H
+
+int mask_match(SERVER_REC *server, const char *mask,
+              const char *nick, const char *user, const char *host);
+int mask_match_address(SERVER_REC *server, const char *mask,
+                      const char *nick, const char *address);
+int masks_match(SERVER_REC *server, const char *masks,
+               const char *nick, const char *address);
+
+#endif
diff --git a/apps/irssi/src/core/memdebug.c b/apps/irssi/src/core/memdebug.c
new file mode 100644 (file)
index 0000000..213d454
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ 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>
+#include <gmodule.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(char *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(char *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 = g_strdup(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_clear(char *p)
+{
+       MEM_REC *rec;
+
+       if (g_hash_table_lookup(preallocs, p) != NULL)
+               p += BUFFER_CHECK_SIZE;
+
+       rec = g_hash_table_lookup(data, p);
+       if (rec != NULL && rec->size > 0)
+               memset(p, 'F', rec->size);
+}
+
+static void *data_remove(char *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->file);
+       g_free(rec->comment);
+       g_free(rec);
+
+       return p;
+}
+
+void *ig_malloc(int size, const char *file, int line)
+{
+       char *p;
+
+       size += BUFFER_CHECK_SIZE*2;
+       p = g_malloc(size);
+       data_add(p, size, file, line);
+       return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+void *ig_malloc0(int size, const char *file, int line)
+{
+       char *p;
+
+       size += BUFFER_CHECK_SIZE*2;
+       p = g_malloc0(size);
+       data_add(p, size, file, line);
+       return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+void *ig_realloc(void *mem, unsigned long size, const char *file, int line)
+{
+       char *p, *oldmem = mem;
+
+       size += BUFFER_CHECK_SIZE*2;
+       oldmem -= BUFFER_CHECK_SIZE;
+       data_remove(oldmem, file, line);
+       p = g_realloc(oldmem, size);
+       data_add(p, size, file, line);
+       return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+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)
+{
+       char *cp = p;
+
+       if (cp == NULL) g_error("ig_free() : trying to free NULL");
+
+       cp -= BUFFER_CHECK_SIZE;
+       data_clear(cp);
+       cp = data_remove(cp, "??", 0);
+       if (cp != NULL) g_free(cp);
+}
+
+GString *ig_string_new(const char *file, int line, const char *str)
+{
+       GString *ret;
+
+       ret = g_string_new(str);
+       data_add((void *) ret, INT_MIN, file, line);
+       return ret;
+}
+
+void ig_string_free(const char *file, int line, GString *str, gboolean freeit)
+{
+       data_remove((void *) 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;
+}
+
+char *ig_dirname(const char *file, int line, const char *fname)
+{
+       char *ret;
+
+       ret = g_dirname(fname);
+       data_add(ret, INT_MIN, file, line);
+       return ret;
+}
+
+char *ig_module_build_path(const char *file, int line, const char *dir, const char *module)
+{
+       char *ret;
+
+       ret = g_module_build_path(dir, module);
+       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 = (char *) 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/apps/irssi/src/core/memdebug.h b/apps/irssi/src/core/memdebug.h
new file mode 100644 (file)
index 0000000..3f32880
--- /dev/null
@@ -0,0 +1,38 @@
+#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);
+char *ig_dirname(const char *file, int line, const char *fname);
+char *ig_module_build_path(const char *file, int line, const char *dir, const char *module);
+
+#define g_malloc(a) ig_malloc(a, __FILE__, __LINE__)
+#define g_malloc0(a) ig_malloc0(a, __FILE__, __LINE__)
+#define g_free ig_free
+#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_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)
+#define g_dirname(a) ig_dirname(__FILE__, __LINE__, a)
+#define g_module_build_path(a, b) ig_module_build_path(__FILE__, __LINE__, a, b)
+
+#ifndef __STRICT_ANSI__
+#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)
+#endif
+#endif
diff --git a/apps/irssi/src/core/misc.c b/apps/irssi/src/core/misc.c
new file mode 100644 (file)
index 0000000..7a2c54d
--- /dev/null
@@ -0,0 +1,727 @@
+/*
+ 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>
+#ifdef HAVE_REGEX_H
+#  include <regex.h>
+#endif
+
+typedef struct {
+       int condition;
+       GInputFunction function;
+        void *data;
+} IRSSI_INPUT_REC;
+
+static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
+                          void *data)
+{
+       IRSSI_INPUT_REC *rec = data;
+       int icond = 0;
+
+       if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+               /* error, we have to call the function.. */
+               if (rec->condition & G_IO_IN)
+                       icond |= G_INPUT_READ;
+               else
+                       icond |= G_INPUT_WRITE;
+       }
+
+       if (condition & (G_IO_IN | G_IO_PRI))
+               icond |= G_INPUT_READ;
+       if (condition & G_IO_OUT)
+               icond |= G_INPUT_WRITE;
+
+       if (rec->condition & icond)
+               rec->function(rec->data, source, icond);
+
+       return TRUE;
+}
+
+int g_input_add_full(GIOChannel *source, int priority, int condition,
+                    GInputFunction function, void *data)
+{
+        IRSSI_INPUT_REC *rec;
+       unsigned int result;
+       GIOCondition cond;
+
+       rec = g_new(IRSSI_INPUT_REC, 1);
+       rec->condition = condition;
+       rec->function = function;
+       rec->data = data;
+
+       cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
+       if (condition & G_INPUT_READ)
+               cond |= G_IO_IN|G_IO_PRI;
+       if (condition & G_INPUT_WRITE)
+               cond |= G_IO_OUT;
+
+       result = g_io_add_watch_full(source, priority, cond,
+                                    irssi_io_invoke, rec, g_free);
+
+       return result;
+}
+
+int g_input_add(GIOChannel *source, int condition,
+               GInputFunction function, void *data)
+{
+       return g_input_add_full(source, G_PRIORITY_DEFAULT, condition,
+                               function, data);
+}
+
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+       if (tv1->tv_sec < tv2->tv_sec)
+               return -1;
+       if (tv1->tv_sec > tv2->tv_sec)
+               return 1;
+
+       return tv1->tv_usec < tv2->tv_usec ? -1 :
+               tv1->tv_usec > tv2->tv_usec ? 1 : 0;
+}
+
+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 execute(const char *cmd)
+{
+       char **args;
+#ifndef WIN32
+       int pid;
+#endif
+
+       g_return_val_if_fail(cmd != NULL, -1);
+
+#ifndef WIN32
+       pid = fork();
+       if (pid == -1) return FALSE;
+       if (pid != 0) {
+               pidwait_add(pid);
+               return pid;
+       }
+
+       args = g_strsplit(cmd, " ", -1);
+       execvp(args[0], args);
+       g_strfreev(args);
+
+       _exit(99);
+       return -1;
+#else
+       args = g_strsplit(cmd, " ", -1);
+       _spawnvp(_P_DETACH, args[0], args);
+       g_strfreev(args);
+       return 0;
+#endif
+}
+
+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, const void *data)
+{
+       void *ret;
+
+       while (list != NULL) {
+               ret = func(list->data, (void *) data);
+                if (ret != NULL) return ret;
+
+               list = list->next;
+       }
+
+       return NULL;
+}
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_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;
+}
+
+/* `list' contains char* */
+char *gslist_to_string(GSList *list, const char *delimiter)
+{
+       GString *str;
+       char *ret;
+
+       str = g_string_new(NULL);
+       while (list != NULL) {
+               if (str->len != 0) g_string_append(str, delimiter);
+               g_string_append(str, list->data);
+
+               list = list->next;
+       }
+
+        ret = str->str;
+       g_string_free(str, FALSE);
+       return ret;
+}
+
+void hash_save_key(char *key, void *value, GSList **list)
+{
+        *list = g_slist_append(*list, key);
+}
+
+/* save all keys in hash table to linked list - you shouldn't remove any
+   items while using this list, use g_slist_free() after you're done with it */
+GSList *hashtable_get_keys(GHashTable *hash)
+{
+       GSList *list;
+
+       list = NULL;
+       g_hash_table_foreach(hash, (GHFunc) hash_save_key, &list);
+       return list;
+}
+
+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 *max;
+       int keylen, datalen, pos;
+
+       keylen = strlen(key);
+       datalen = strlen(data);
+
+       if (keylen > datalen)
+               return NULL;
+       if (keylen == 0)
+               return (char *) data;
+
+       max = data+datalen-keylen;
+       pos = 0;
+       while (data <= max) {
+               if (key[pos] == '\0')
+                        return (char *) data;
+
+               if (toupper(data[pos]) == toupper(key[pos]))
+                       pos++;
+               else {
+                       data++;
+                        pos = 0;
+               }
+       }
+
+       return NULL;
+}
+
+#define isbound(c) \
+       ((unsigned char) (c) < 128 && \
+       (isspace((int) (c)) || ispunct((int) (c))))
+
+char *strstr_full_case(const char *data, const char *key, int icase)
+{
+       const char *start, *max;
+       int keylen, datalen, pos, match;
+
+       keylen = strlen(key);
+       datalen = strlen(data);
+
+       if (keylen > datalen)
+               return NULL;
+       if (keylen == 0)
+               return (char *) data;
+
+       max = data+datalen-keylen;
+       start = data; pos = 0;
+       while (data <= max) {
+               if (key[pos] == '\0') {
+                       if (data[pos] != '\0' && !isbound(data[pos])) {
+                               data++;
+                               pos = 0;
+                                continue;
+                       }
+                       return (char *) data;
+               }
+
+               match = icase ? (toupper(data[pos]) == toupper(key[pos])) :
+                                data[pos] == key[pos];
+
+               if (match && (pos != 0 || data == start || isbound(data[-1])))
+                       pos++;
+               else {
+                       data++;
+                        pos = 0;
+               }
+       }
+
+       return NULL;
+}
+
+char *strstr_full(const char *data, const char *key)
+{
+        return strstr_full_case(data, key, FALSE);
+}
+
+char *stristr_full(const char *data, const char *key)
+{
+        return strstr_full_case(data, key, TRUE);
+}
+
+int regexp_match(const char *str, const char *regexp)
+{
+#ifdef HAVE_REGEX_H
+       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;
+#else
+       return FALSE;
+#endif
+}
+
+/* Create the directory and all it's parent directories */
+int mkpath(const char *path, int mode)
+{
+       struct stat statbuf;
+       const char *p;
+       char *dir;
+
+       g_return_val_if_fail(path != NULL, -1);
+
+       p = g_path_skip_root((char *) path);
+       if (p == NULL) {
+               /* not a full path, maybe not what we wanted
+                  but continue anyway.. */
+                p = path;
+       }
+       for (;;) {
+               if (*p != G_DIR_SEPARATOR && *p != '\0') {
+                       p++;
+                       continue;
+               }
+
+               dir = g_strndup(path, (int) (p-path));
+               if (stat(dir, &statbuf) != 0) {
+#ifndef WIN32
+                       if (mkdir(dir, mode) == -1) {
+#else
+                       if (_mkdir(dir) == -1) {
+#endif
+                               g_free(dir);
+                               return -1;
+                       }
+               }
+               g_free(dir);
+
+               if (*p++ == '\0')
+                       break;
+       }
+
+       return 0;
+}
+
+/* convert ~/ to $HOME */
+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;
+}
+
+int g_istr_cmp(gconstpointer v, gconstpointer v2)
+{
+       return g_strcasecmp((const char *) v, (const char *) v2);
+}
+
+/* a char* hash function from ASU */
+unsigned int g_istr_hash(gconstpointer v)
+{
+       const char *s = (const char *) v;
+       unsigned int h = 0, g;
+
+       while (*s != '\0') {
+               h = (h << 4) + toupper(*s);
+               if ((g = h & 0xf0000000UL)) {
+                       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 = g_strdup(cmask);
+       for (; *mask != '\0' && *data != '\0'; mask++) {
+               if (*mask != '*') {
+                       if (*mask != '?' && toupper(*mask) != toupper(*data))
+                               break;
+
+                       data++;
+                       continue;
+               }
+
+               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 ? '?' : '*';
+       }
+
+       while (*mask == '*') mask++;
+
+       ret = data != NULL && *data == '\0' && *mask == '\0';
+       g_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);
+
+       if (*str == '\0' || *str == end_char)
+               return 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;
+}
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *channel)
+{
+       char *str, *p;
+
+       str = p = g_malloc(strlen(channel)*2+1);
+       while (*channel != '\0') {
+               if ((unsigned char) *channel >= 32)
+                       *p++ = *channel;
+               else {
+                       *p++ = '^';
+                       *p++ = *channel + 'A'-1;
+               }
+               channel++;
+       }
+       *p = '\0';
+
+       return str;
+}
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t)
+{
+       struct tm *tm;
+       char *str;
+        int len;
+
+       tm = localtime(&t);
+       str = g_strdup(asctime(tm));
+
+       len = strlen(str);
+       if (len > 0) str[len-1] = '\0';
+        return str;
+}
+
+/* Returns number of columns needed to print items.
+   save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+                        int max_width, int max_columns,
+                        int item_extra, int item_min_size,
+                        int **save_column_widths, int *rows)
+{
+        GSList *tmp;
+       int **columns, *columns_width, *columns_rows;
+       int item_pos, items_count;
+       int ret, len, max_len, n, col;
+
+       items_count = g_slist_length(items);
+       if (items_count == 0) {
+               *save_column_widths = NULL;
+                *rows = 0;
+               return 0;
+       }
+
+       len = max_width/(item_extra+item_min_size);
+        if (len <= 0) len = 1;
+       if (max_columns <= 0 || len < max_columns)
+                max_columns = len;
+
+       columns = g_new0(int *, max_columns);
+       columns_width = g_new0(int, max_columns);
+       columns_rows = g_new0(int, max_columns);
+
+       for (n = 1; n < max_columns; n++) {
+               columns[n] = g_new0(int, n+1);
+               columns_rows[n] = items_count <= n+1 ? 1 :
+                        (items_count+n)/(n+1);
+       }
+
+       /* for each possible column count, save the column widths and
+          find the biggest column count that fits to screen. */
+        item_pos = 0; max_len = 0;
+       for (tmp = items; tmp != NULL; tmp = tmp->next) {
+               len = item_extra+len_func(tmp->data);
+               if (max_len < len)
+                       max_len = len;
+
+               for (n = 1; n < max_columns; n++) {
+                       if (columns_width[n] > max_width)
+                               continue; /* too wide */
+
+                       col = item_pos/columns_rows[n];
+                       if (columns[n][col] < len) {
+                               columns_width[n] += len-columns[n][col];
+                                columns[n][col] = len;
+                       }
+               }
+
+                item_pos++;
+       }
+
+       for (n = max_columns-1; n > 1; n--) {
+               if (columns_width[n] <= max_width &&
+                   columns[n][n] > 0)
+                        break;
+       }
+        ret = n+1;
+
+       *save_column_widths = g_new(int, ret);
+       if (ret == 1) {
+                **save_column_widths = max_len;
+                *rows = 1;
+       } else {
+               memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
+               *rows = columns_rows[ret-1];
+       }
+
+       for (n = 1; n < max_columns; n++)
+                g_free(columns[n]);
+       g_free(columns_width);
+       g_free(columns_rows);
+       g_free(columns);
+
+        return ret;
+}
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows)
+{
+        GSList *tmp, *sorted;
+       int row, skip;
+
+       if (list == NULL || rows == 0)
+                return list;
+
+       sorted = NULL;
+
+       for (row = 0; row < rows; row++) {
+                tmp = g_slist_nth(list, row);
+                skip = 1;
+               for (; tmp != NULL; tmp = tmp->next) {
+                       if (--skip == 0) {
+                                skip = rows;
+                               sorted = g_slist_append(sorted, tmp->data);
+                       }
+               }
+       }
+
+       g_return_val_if_fail(g_slist_length(sorted) ==
+                            g_slist_length(list), sorted);
+        return sorted;
+}
diff --git a/apps/irssi/src/core/misc.h b/apps/irssi/src/core/misc.h
new file mode 100644 (file)
index 0000000..45c8ce1
--- /dev/null
@@ -0,0 +1,104 @@
+#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);
+typedef int (*COLUMN_LEN_FUNC)(void *data);
+
+static inline int nearest_power(int num)
+{
+       int n = 1;
+
+       while (n < num) n <<= 1;
+       return n;
+}
+
+/* Returns 1 if tv1 > tv2, -1 if tv2 > tv1 or 0 if they're equal. */
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2);
+/* Returns "tv1 - tv2", returns the result in milliseconds. Note that
+   if the difference is too large, the result might be invalid. */
+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 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, const void *data);
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_to_string(GSList *list, int offset, const char *delimiter);
+/* `list' contains char* */
+char *gslist_to_string(GSList *list, const char *delimiter);
+
+/* save all keys in hash table to linked list - you shouldn't remove any
+   items while using this list, use g_slist_free() after you're done with it */
+GSList *hashtable_get_keys(GHashTable *hash);
+
+/* strstr() with case-ignoring */
+char *stristr(const char *data, const char *key);
+
+/* like strstr(), but matches only for full words.
+   `icase' specifies if match is case sensitive */
+char *strstr_full_case(const char *data, const char *key, int icase);
+char *strstr_full(const char *data, const char *key);
+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);
+
+/* Create the directory and all it's parent directories */
+int mkpath(const char *path, int mode);
+/* convert ~/ to $HOME */
+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);
+
+/* Case-insensitive GCompareFunc func */
+int g_istr_cmp(gconstpointer v, gconstpointer v2);
+
+/* 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);
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *channel);
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t);
+
+/* Returns number of columns needed to print items.
+   save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+                        int max_width, int max_columns,
+                        int item_extra, int item_min_size,
+                        int **save_column_widths, int *rows);
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows);
+
+#endif
diff --git a/apps/irssi/src/core/module.h b/apps/irssi/src/core/module.h
new file mode 100644 (file)
index 0000000..8978438
--- /dev/null
@@ -0,0 +1,3 @@
+#include "common.h"
+
+#define MODULE_NAME "core"
diff --git a/apps/irssi/src/core/modules.c b/apps/irssi/src/core/modules.c
new file mode 100644 (file)
index 0000000..0dac04a
--- /dev/null
@@ -0,0 +1,444 @@
+/*
+ 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"
+#include "signals.h"
+
+#include "commands.h"
+#include "settings.h"
+
+GSList *modules;
+
+static GHashTable *uniqids, *uniqstrids;
+static GHashTable *idlookup, *stridlookup;
+static int next_uniq_id;
+
+void *module_check_cast(void *object, int type_pos, const char *id)
+{
+       return object == NULL || module_find_id(id,
+               G_STRUCT_MEMBER(int, object, type_pos)) == -1 ? NULL : object;
+}
+
+void *module_check_cast_module(void *object, int type_pos,
+                              const char *module, const char *id)
+{
+       const char *str;
+
+       if (object == NULL)
+               return NULL;
+
+       str = module_find_id_str(module,
+                                G_STRUCT_MEMBER(int, object, type_pos));
+       return str == NULL || strcmp(str, id) != 0 ? NULL : object;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id)
+{
+        GHashTable *ids;
+       gpointer origkey, uniqid, idp;
+       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);
+       }
+
+       idp = GINT_TO_POINTER(id);
+       if (!g_hash_table_lookup_extended(ids, idp, &origkey, &uniqid)) {
+               /* not found */
+               ret = next_uniq_id++;
+                g_hash_table_insert(ids, idp, GINT_TO_POINTER(ret));
+                g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), idp);
+       } 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 *idlist;
+       gpointer origkey, id;
+       int ret;
+
+       g_return_val_if_fail(module != NULL, -1);
+
+       if (!g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid),
+                                         &origkey, &id))
+               return -1;
+
+       /* check that module matches */
+       idlist = g_hash_table_lookup(idlookup, module);
+       if (idlist == NULL)
+               return -1;
+
+       ret = GPOINTER_TO_INT(id);
+       if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+           GPOINTER_TO_INT(id) != uniqid)
+               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 *idlist;
+       gpointer origkey, id;
+       const char *ret;
+
+       g_return_val_if_fail(module != NULL, NULL);
+
+       if (!g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid),
+                                         &origkey, &id))
+               return NULL;
+
+       /* check that module matches */
+       idlist = g_hash_table_lookup(stridlookup, module);
+       if (idlist == NULL)
+               return NULL;
+
+       ret = id;
+       if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+           GPOINTER_TO_INT(id) != uniqid)
+               ret = NULL;
+
+       return ret;
+}
+
+static void uniq_destroy(gpointer key, gpointer value)
+{
+        g_hash_table_remove(uniqids, value);
+}
+
+static void 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 *idlist;
+       gpointer key;
+
+       if (g_hash_table_lookup_extended(idlookup, module, &key,
+                                        (gpointer *) &idlist)) {
+               g_hash_table_remove(idlookup, key);
+               g_free(key);
+
+               g_hash_table_foreach(idlist, (GHFunc) uniq_destroy, NULL);
+               g_hash_table_destroy(idlist);
+       }
+
+       if (g_hash_table_lookup_extended(stridlookup, module, &key,
+                                        (gpointer *) &idlist)) {
+               g_hash_table_remove(stridlookup, key);
+               g_free(key);
+
+               g_hash_table_foreach(idlist, (GHFunc) uniq_destroy_str, NULL);
+               g_hash_table_destroy(idlist);
+       }
+}
+
+MODULE_REC *module_find(const char *name)
+{
+       GSList *tmp;
+
+       for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+               MODULE_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, name) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+#ifdef HAVE_GMODULE
+static char *module_get_name(const char *path, int *start, int *end)
+{
+       const char *name;
+       char *module_name, *ptr;
+
+        name = NULL;
+       if (g_path_is_absolute(path)) {
+               name = strrchr(path, G_DIR_SEPARATOR);
+                if (name != NULL) name++;
+       }
+
+       if (name == NULL)
+               name = path;
+
+       if (strncmp(name, "lib", 3) == 0)
+               name += 3;
+
+       module_name = g_strdup(name);
+       ptr = strchr(module_name, '.');
+       if (ptr != NULL) *ptr = '\0';
+
+       *start = (int) (name-path);
+       *end = *start + (ptr == NULL ? strlen(name) :
+                        (int) (module_name-ptr));
+
+       return module_name;
+}
+
+static GModule *module_open(const char *name)
+{
+       struct stat statbuf;
+       GModule *module;
+       char *path, *str;
+
+       if (g_path_is_absolute(name) ||
+           (*name == '.' && name[1] == G_DIR_SEPARATOR))
+               path = g_strdup(name);
+       else {
+               /* first try from home dir */
+               str = g_strdup_printf("%s/.irssi/modules", g_get_home_dir());
+               path = g_module_build_path(str, name);
+               g_free(str);
+
+               if (stat(path, &statbuf) == 0) {
+                       module = g_module_open(path, (GModuleFlags) 0);
+                       g_free(path);
+                       return module;
+               }
+
+               /* module not found from home dir, try global module dir */
+               g_free(path);
+               path = g_module_build_path(MODULEDIR, name);
+       }
+
+       module = g_module_open(path, (GModuleFlags) 0);
+       g_free(path);
+       return module;
+}
+
+#define module_error(error, module, text) \
+       signal_emit("module error", 3, GINT_TO_POINTER(error), module, text)
+
+static int module_load_name(const char *path, const char *name, int silent)
+{
+       void (*module_init) (void);
+       GModule *gmodule;
+       MODULE_REC *rec;
+       char *initfunc;
+
+       gmodule = module_open(path);
+       if (gmodule == NULL) {
+               if (!silent) {
+                       module_error(MODULE_ERROR_LOAD, name,
+                                    g_module_error());
+               }
+               return FALSE;
+       }
+
+       /* get the module's init() function */
+       initfunc = g_strconcat(name, "_init", NULL);
+       if (!g_module_symbol(gmodule, initfunc, (gpointer *) &module_init)) {
+               if (!silent)
+                       module_error(MODULE_ERROR_INVALID, name, NULL);
+               g_module_close(gmodule);
+               g_free(initfunc);
+               return FALSE;
+       }
+       g_free(initfunc);
+
+       rec = g_new0(MODULE_REC, 1);
+       rec->name = g_strdup(name);
+        rec->gmodule = gmodule;
+       modules = g_slist_append(modules, rec);
+
+       module_init();
+       settings_check_module(name);
+
+       signal_emit("module loaded", 1, rec);
+       return TRUE;
+}
+#endif
+
+/* Load module - automatically tries to load also the related non-core
+   modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes)
+{
+#ifdef HAVE_GMODULE
+        GString *realpath;
+       char *name, *pname;
+       int ret, start, end;
+
+       g_return_val_if_fail(path != NULL, FALSE);
+
+       if (!g_module_supported())
+               return FALSE;
+
+       name = module_get_name(path, &start, &end);
+       if (module_find(name)) {
+               module_error(MODULE_ERROR_ALREADY_LOADED, name, NULL);
+                g_free(name);
+               return FALSE;
+       }
+
+        /* load "module_core" instead of "module" if it exists */
+       realpath = g_string_new(path);
+       g_string_insert(realpath, end, "_core");
+
+        pname = g_strconcat(name, "_core", NULL);
+       ret = module_load_name(realpath->str, pname, TRUE);
+       g_free(pname);
+
+       if (!ret) {
+                /* load "module" - complain if it's not found */
+               ret = module_load_name(path, name, FALSE);
+       } else if (prefixes != NULL) {
+               /* load all the "prefix modules", like the fe-common, irc,
+                  etc. part of the module */
+               while (*prefixes != NULL) {
+                        g_string_assign(realpath, path);
+                       g_string_insert(realpath, start, "_");
+                       g_string_insert(realpath, start, *prefixes);
+
+                        pname = g_strconcat(*prefixes, "_", name, NULL);
+                       module_load_name(realpath->str, pname, TRUE);
+                       g_free(pname);
+
+                        prefixes++;
+               }
+       }
+
+        g_string_free(realpath, TRUE);
+       g_free(name);
+       return ret;
+#else
+        return FALSE;
+#endif
+}
+
+void module_unload(MODULE_REC *module)
+{
+#ifdef HAVE_GMODULE
+       void (*module_deinit) (void);
+       char *deinitfunc;
+
+       g_return_if_fail(module != NULL);
+
+       modules = g_slist_remove(modules, module);
+
+       signal_emit("module unloaded", 1, module);
+
+       /* call the module's deinit() function */
+       deinitfunc = g_strconcat(module->name, "_deinit", NULL);
+       if (g_module_symbol(module->gmodule, deinitfunc,
+                           (gpointer *) &module_deinit))
+               module_deinit();
+       g_free(deinitfunc);
+
+        settings_remove_module(module->name);
+       commands_remove_module(module->name);
+       signals_remove_module(module->name);
+
+       g_module_close(module->gmodule);
+       g_free(module->name);
+       g_free(module);
+#endif
+}
+
+static void uniq_get_modules(char *key, void *value, GSList **list)
+{
+        *list = g_slist_append(*list, key);
+}
+
+void modules_init(void)
+{
+       modules = NULL;
+
+       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)
+{
+       GSList *list;
+
+       list = NULL;
+       g_hash_table_foreach(idlookup, (GHFunc) uniq_get_modules, &list);
+       g_hash_table_foreach(stridlookup, (GHFunc) uniq_get_modules, &list);
+
+       while (list != NULL) {
+               module_uniq_destroy(list->data);
+               list = g_slist_remove(list, list->data);
+       }
+
+       g_hash_table_destroy(idlookup);
+       g_hash_table_destroy(stridlookup);
+       g_hash_table_destroy(uniqids);
+       g_hash_table_destroy(uniqstrids);
+}
diff --git a/apps/irssi/src/core/modules.h b/apps/irssi/src/core/modules.h
new file mode 100644 (file)
index 0000000..7a83819
--- /dev/null
@@ -0,0 +1,64 @@
+#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)
+
+enum {
+       MODULE_ERROR_ALREADY_LOADED,
+       MODULE_ERROR_LOAD,
+       MODULE_ERROR_INVALID
+};
+
+typedef struct {
+       char *name;
+#ifdef HAVE_GMODULE
+       GModule *gmodule;
+#endif
+} MODULE_REC;
+
+extern GSList *modules;
+
+MODULE_REC *module_find(const char *name);
+
+/* Load module - automatically tries to load also the related non-core
+   modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes);
+void module_unload(MODULE_REC *module);
+
+#define MODULE_CHECK_CAST(object, cast, type_field, id) \
+       ((cast *) module_check_cast(object, offsetof(cast, type_field), id))
+#define MODULE_CHECK_CAST_MODULE(object, cast, type_field, module, id) \
+       ((cast *) module_check_cast_module(object, \
+                               offsetof(cast, type_field), module, id))
+void *module_check_cast(void *object, int type_pos, const char *id);
+void *module_check_cast_module(void *object, int type_pos,
+                              const char *module, const char *id);
+
+/* 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/apps/irssi/src/core/net-disconnect.c b/apps/irssi/src/core/net-disconnect.c
new file mode 100644 (file)
index 0000000..b0e9535
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ 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;
+       GIOChannel *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[512];
+       int count, ret;
+
+       /* check if there's any data waiting in socket. read max. 5kB so
+          if server just keeps sending us stuff we won't get stuck */
+       count = 0;
+       do {
+               ret = net_receive(rec->handle, buf, sizeof(buf));
+               if (ret == -1) {
+                       /* socket was closed */
+                       net_disconnect_remove(rec);
+               }
+                count++;
+       } while (ret == sizeof(buf) && count < 10);
+}
+
+static int sig_timeout_disconnect(void)
+{
+       NET_DISCONNECT_REC *rec;
+       GSList *tmp, *next;
+       time_t now;
+
+       /* 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)
+                       net_disconnect_remove(rec);
+       }
+
+       if (disconnects == NULL) {
+               /* no more sockets in disconnect queue, stop calling this
+                  function */
+               timeout_tag = -1;
+       }
+       return disconnects != NULL;
+}
+
+/* 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(GIOChannel *handle)
+{
+       NET_DISCONNECT_REC *rec;
+
+       rec = g_new(NET_DISCONNECT_REC, 1);
+       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);
+       }
+
+       disconnects = g_slist_append(disconnects, rec);
+}
+
+void net_disconnect_init(void)
+{
+       disconnects = NULL;
+       timeout_tag = -1;
+}
+
+void net_disconnect_deinit(void)
+{
+#ifndef WIN32
+       NET_DISCONNECT_REC *rec;
+       time_t now, max;
+       int first, fd;
+       struct timeval tv;
+       fd_set set;
+
+       /* 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 = g_io_channel_unix_get_fd(rec->handle);
+               FD_ZERO(&set);
+               FD_SET(fd, &set);
+               tv.tv_sec = first ? 0 : max-now;
+               tv.tv_usec = first ? 100000 : 0;
+               if (select(fd+1, &set, NULL, NULL, &tv) > 0 &&
+                   FD_ISSET(fd, &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;
+               }
+       }
+#endif
+}
diff --git a/apps/irssi/src/core/net-disconnect.h b/apps/irssi/src/core/net-disconnect.h
new file mode 100644 (file)
index 0000000..a1ca064
--- /dev/null
@@ -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/apps/irssi/src/core/net-nonblock.c b/apps/irssi/src/core/net-nonblock.c
new file mode 100644 (file)
index 0000000..7392f42
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ 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;
+
+       GIOChannel *pipes[2];
+       int port;
+       IPADDR *my_ip;
+       int tag;
+} SIMPLE_THREAD_REC;
+
+#define is_fatal_error(err) \
+       (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
+
+static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
+{
+        unsigned int ret;
+       int err, sent;
+
+       sent = 0;
+       do {
+               err = g_io_channel_write(channel, (char *) data + sent,
+                                        len-sent, &ret);
+                sent += ret;
+       } while (sent < len && !is_fatal_error(err));
+
+       return err != 0 ? -1 : 0;
+}
+
+static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
+{
+       time_t maxwait;
+        unsigned int ret;
+       int err, received;
+
+       maxwait = time(NULL)+2;
+       received = 0;
+       do {
+               err = g_io_channel_read(channel, (char *) data + received,
+                                       len-received, &ret);
+               received += ret;
+       } while (received < len && time(NULL) < maxwait &&
+                (ret != 0 || !is_fatal_error(err)));
+
+       return received < len ? -1 : 0;
+}
+
+/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
+   written to pipe when found PID of the resolver child is returned */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe)
+{
+       RESOLVED_IP_REC rec;
+       const char *errorstr;
+#ifndef WIN32
+       int pid;
+#endif
+
+       g_return_val_if_fail(addr != NULL, FALSE);
+
+#ifndef WIN32
+       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");
+       }
+#endif
+
+       /* child */
+        memset(&rec, 0, sizeof(rec));
+       rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
+       if (rec.error == 0) {
+               errorstr = NULL;
+       } else {
+               errorstr = net_gethosterror(rec.error);
+               rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
+       }
+
+        g_io_channel_write_block(pipe, &rec, sizeof(rec));
+       if (rec.errlen != 0)
+               g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
+
+#ifndef WIN32
+       if (pid == 0)
+               _exit(99);
+#endif
+
+       /* we used blocking lookup */
+       return 0;
+}
+
+/* get the resolved IP address */
+int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
+{
+       rec->error = -1;
+       rec->errorstr = NULL;
+
+#ifndef WIN32
+       fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
+#endif
+
+       /* get ip+error */
+       if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
+               rec->errorstr = g_strdup_printf("Host name lookup: %s",
+                                               g_strerror(errno));
+               return -1;
+       }
+
+       if (rec->error) {
+               /* read error string, if we can't read everything for some
+                  reason, just ignore it. */
+               rec->errorstr = g_malloc0(rec->errlen+1);
+                g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
+       }
+
+       return 0;
+}
+
+/* Get host name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
+{
+       /* FIXME: not implemented */
+       return FALSE;
+}
+
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid)
+{
+       g_return_if_fail(pid > 0);
+
+#ifndef WIN32
+       kill(pid, SIGKILL);
+#endif
+}
+
+static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle)
+{
+       g_return_if_fail(rec != NULL);
+
+       g_source_remove(rec->tag);
+
+       if (net_geterror(handle) != 0) {
+               /* failed */
+               g_io_channel_close(handle);
+                g_io_channel_unref(handle);
+               handle = NULL;
+       }
+
+       rec->func(handle, rec->data);
+       g_free(rec);
+}
+
+static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
+{
+       RESOLVED_IP_REC iprec;
+       GIOChannel *handle;
+        IPADDR *ip;
+
+       g_return_if_fail(rec != NULL);
+
+       g_source_remove(rec->tag);
+
+       net_gethostbyname_return(pipe, &iprec);
+       g_free_not_null(iprec.errorstr);
+
+       g_io_channel_close(rec->pipes[0]);
+       g_io_channel_unref(rec->pipes[0]);
+       g_io_channel_close(rec->pipes[1]);
+       g_io_channel_unref(rec->pipes[1]);
+
+       ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
+       handle = iprec.error == -1 ? NULL :
+               net_connect_ip(ip, rec->port, rec->my_ip);
+
+       g_free_not_null(rec->my_ip);
+
+       if (handle == NULL) {
+               /* failed */
+               rec->func(NULL, rec->data);
+               g_free(rec);
+               return;
+       }
+
+       rec->tag = g_input_add(handle, G_INPUT_READ | 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;
+       }
+
+       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] = g_io_channel_unix_new(fd[0]);
+       rec->pipes[1] = g_io_channel_unix_new(fd[1]);
+
+       /* start nonblocking host name lookup */
+       net_gethostbyname_nonblock(server, rec->pipes[1]);
+       rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ,
+                              (GInputFunction) simple_readpipe, rec);
+
+       return TRUE;
+}
diff --git a/apps/irssi/src/core/net-nonblock.h b/apps/irssi/src/core/net-nonblock.h
new file mode 100644 (file)
index 0000000..a0e5cdd
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef __NET_NONBLOCK_H
+#define __NET_NONBLOCK_H
+
+#include "network.h"
+
+typedef struct {
+       IPADDR ip4, ip6; /* resolved ip addresses */
+       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 struct {
+        int namelen;
+       char *name;
+
+       int error;
+       int errlen;
+       char *errorstr;
+} RESOLVED_NAME_REC;
+
+typedef void (*NET_CALLBACK) (GIOChannel *, void *);
+typedef void (*NET_HOST_CALLBACK) (RESOLVED_NAME_REC *, void *);
+
+/* nonblocking gethostbyname(), PID of the resolver child is returned. */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe);
+/* Get host's name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data);
+/* get the resolved IP address. returns -1 if some error occured with read() */
+int net_gethostbyname_return(GIOChannel *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/apps/irssi/src/core/net-sendbuffer.c b/apps/irssi/src/core/net-sendbuffer.c
new file mode 100644 (file)
index 0000000..04eab80
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ net-sendbuffer.c : Buffered send()
+
+    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 "network.h"
+#include "net-sendbuffer.h"
+
+struct _NET_SENDBUF_REC {
+       GIOChannel *handle;
+
+        int send_tag;
+       int bufsize;
+       int bufpos;
+       char *buffer; /* Buffer is NULL until it's actually needed. */
+};
+
+static GSList *buffers;
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+   is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize)
+{
+       NET_SENDBUF_REC *rec;
+
+       g_return_val_if_fail(handle != NULL, NULL);
+
+       rec = g_new0(NET_SENDBUF_REC, 1);
+        rec->send_tag = -1;
+       rec->handle = handle;
+       rec->bufsize = bufsize > 0 ? bufsize : DEFAULT_BUFFER_SIZE;
+
+       buffers = g_slist_append(buffers, rec);
+       return rec;
+}
+
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close)
+{
+       buffers = g_slist_remove(buffers, rec);
+
+        if (rec->send_tag != -1) g_source_remove(rec->send_tag);
+       if (close) net_disconnect(rec->handle);
+       g_free_not_null(rec->buffer);
+       g_free(rec);
+}
+
+/* Transmit all data from buffer - return TRUE if successful */
+static int buffer_send(NET_SENDBUF_REC *rec)
+{
+       int ret;
+
+       ret = net_transmit(rec->handle, rec->buffer, rec->bufpos);
+       if (ret < 0 || rec->bufpos == ret) {
+               /* error/all sent - don't try to send it anymore */
+                g_free_and_null(rec->buffer);
+               return TRUE;
+       }
+
+       if (ret > 0) {
+                rec->bufpos -= ret;
+               g_memmove(rec->buffer, rec->buffer+ret, rec->bufpos);
+       }
+       return FALSE;
+}
+
+static void sig_sendbuffer(NET_SENDBUF_REC *rec)
+{
+       if (rec->buffer != NULL) {
+               if (!buffer_send(rec))
+                        return;
+       }
+
+       g_source_remove(rec->send_tag);
+       rec->send_tag = -1;
+}
+
+/* Add `data' to transmit buffer - return FALSE if buffer is full */
+static int buffer_add(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+       if (rec->buffer == NULL) {
+               rec->buffer = g_malloc(rec->bufsize);
+               rec->bufpos = 0;
+       }
+
+       if (rec->bufpos+size > rec->bufsize)
+               return FALSE;
+
+       memcpy(rec->buffer+rec->bufpos, data, size);
+       rec->bufpos += size;
+       return TRUE;
+}
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+   automatically after a while. Returns -1 if some unrecoverable error
+   occured. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+       int ret;
+
+       g_return_val_if_fail(rec != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+       if (size <= 0) return 0;
+
+       if (rec->buffer == NULL) {
+                /* nothing in buffer - transmit immediately */
+               ret = net_transmit(rec->handle, data, size);
+               if (ret < 0) return -1;
+               size -= ret;
+               data = ((const char *) data) + ret;
+       }
+
+       if (size <= 0)
+               return 0;
+
+       /* everything couldn't be sent. */
+       if (rec->send_tag == -1) {
+               rec->send_tag =
+                       g_input_add(rec->handle, G_INPUT_WRITE,
+                                   (GInputFunction) sig_sendbuffer, rec);
+       }
+
+       return buffer_add(rec, data, size) ? 0 : -1;
+}
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec)
+{
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       return rec->handle;
+}
+
+void net_sendbuffer_init(void)
+{
+       buffers = NULL;
+}
+
+void net_sendbuffer_deinit(void)
+{
+}
diff --git a/apps/irssi/src/core/net-sendbuffer.h b/apps/irssi/src/core/net-sendbuffer.h
new file mode 100644 (file)
index 0000000..bb6d8e0
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef __NET_SENDBUFFER_H
+#define __NET_SENDBUFFER_H
+
+#define DEFAULT_BUFFER_SIZE 8192
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+   is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize);
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close);
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+   automatically after a while. Returns -1 if some unrecoverable error
+   occured. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size);
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec);
+
+void net_sendbuffer_init(void);
+void net_sendbuffer_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/network.c b/apps/irssi/src/core/network.c
new file mode 100644 (file)
index 0000000..4fc06c0
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ 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"
+
+#ifndef INADDR_NONE
+#  define INADDR_NONE INADDR_BROADCAST
+#endif
+
+union sockaddr_union {
+       struct sockaddr sa;
+       struct sockaddr_in sin;
+#ifdef HAVE_IPV6
+       struct sockaddr_in6 sin6;
+#endif
+};
+
+#ifdef HAVE_IPV6
+#  define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \
+       sizeof(so.sin6) : sizeof(so.sin))
+#else
+#  define SIZEOF_SOCKADDR(so) (sizeof(so.sin))
+#endif
+
+#ifdef WIN32
+#  define g_io_channel_new(handle) g_io_channel_win32_new_stream_socket(handle)
+#else
+#  define g_io_channel_new(handle) g_io_channel_unix_new(handle)
+#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->ip, &ip2->ip, sizeof(ip1->ip)) == 0;
+#endif
+
+       return memcmp(&ip1->ip, &ip2->ip, 4) == 0;
+}
+
+
+/* copy IP to sockaddr */
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
+{
+       if (ip == NULL) {
+#ifdef HAVE_IPV6
+               so->sin6.sin6_family = AF_INET6;
+               so->sin6.sin6_addr = in6addr_any;
+#else
+               so->sin.sin_family = AF_INET;
+               so->sin.sin_addr.s_addr = INADDR_ANY;
+#endif
+               return;
+       }
+
+       so->sin.sin_family = ip->family;
+#ifdef HAVE_IPV6
+       if (ip->family == AF_INET6)
+               memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip));
+       else
+#endif
+               memcpy(&so->sin.sin_addr, &ip->ip, 4);
+}
+
+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->ip, &so->sin6.sin6_addr, sizeof(ip->ip));
+       else
+#endif
+               memcpy(&ip->ip, &so->sin.sin_addr, 4);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+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((unsigned short)port);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+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 */
+GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip)
+{
+       IPADDR ip4, ip6, *ip;
+        int family;
+
+       g_return_val_if_fail(addr != NULL, NULL);
+
+        family = my_ip == NULL ? 0 : my_ip->family;
+       if (net_gethostbyname(addr, &ip4, &ip6) == -1)
+               return NULL;
+
+       if (my_ip == NULL) {
+                /* prefer IPv4 addresses */
+               ip = ip4.family != 0 ? &ip4 : &ip6;
+       } else if (IPADDR_IS_V6(my_ip)) {
+                /* my_ip is IPv6 address, use it if possible */
+               if (ip6.family != 0)
+                       ip = &ip6;
+               else {
+                       my_ip = NULL;
+                        ip = &ip4;
+               }
+       } else {
+                /* my_ip is IPv4 address, use it if possible */
+               if (ip4.family != 0)
+                       ip = &ip4;
+               else {
+                       my_ip = NULL;
+                        ip = &ip6;
+               }
+       }
+
+       return net_connect_ip(ip, port, my_ip);
+}
+
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+       union sockaddr_union so;
+       int handle, ret, opt = 1;
+
+       if (my_ip != NULL && ip->family != my_ip->family) {
+               g_warning("net_connect_ip(): ip->family != my_ip->family");
+                my_ip = NULL;
+       }
+
+       /* 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 NULL;
+
+       /* set socket options */
+#ifndef WIN32
+       fcntl(handle, F_SETFL, O_NONBLOCK);
+#endif
+       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_SOCKADDR(so));
+       }
+
+       /* connect */
+       sin_set_ip(&so, ip);
+       sin_set_port(&so, port);
+       ret = connect(handle, &so.sa, SIZEOF_SOCKADDR(so));
+
+#ifndef WIN32
+       if (ret < 0 && errno != EINPROGRESS) {
+#else
+       if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) {
+#endif
+               close(handle);
+               return NULL;
+       }
+
+       return g_io_channel_new(handle);
+}
+
+/* Disconnect socket */
+void net_disconnect(GIOChannel *handle)
+{
+       g_return_if_fail(handle != NULL);
+
+       g_io_channel_close(handle);
+       g_io_channel_unref(handle);
+}
+
+/* Listen for connections on a socket. if `my_ip' is NULL, listen in any
+   address. */
+GIOChannel *net_listen(IPADDR *my_ip, int *port)
+{
+       union sockaddr_union so;
+       int ret, handle, opt = 1;
+       socklen_t len;
+
+       g_return_val_if_fail(port != NULL, NULL);
+
+       memset(&so, 0, sizeof(so));
+       sin_set_port(&so, *port);
+       sin_set_ip(&so, my_ip);
+
+       /* create the socket */
+       handle = socket(so.sin.sin_family, SOCK_STREAM, 0);
+#ifdef HAVE_IPV6
+       if (handle == -1 && errno == EINVAL) {
+               /* IPv6 is not supported by OS */
+               so.sin.sin_family = AF_INET;
+               so.sin.sin_addr.s_addr = INADDR_ANY;
+
+               handle = socket(AF_INET, SOCK_STREAM, 0);
+       }
+#endif
+       if (handle == -1)
+               return NULL;
+
+       /* set socket options */
+#ifndef WIN32
+       fcntl(handle, F_SETFL, O_NONBLOCK);
+#endif
+       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 */
+       ret = bind(handle, &so.sa, SIZEOF_SOCKADDR(so));
+       if (ret >= 0) {
+               /* get the actual port we started listen */
+               len = SIZEOF_SOCKADDR(so);
+               ret = getsockname(handle, &so.sa, &len);
+               if (ret >= 0) {
+                       *port = sin_get_port(&so);
+
+                       /* start listening */
+                       if (listen(handle, 1) >= 0)
+                                return g_io_channel_new(handle);
+               }
+
+       }
+
+        /* error */
+       close(handle);
+       return NULL;
+}
+
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port)
+{
+       union sockaddr_union so;
+       int ret;
+       socklen_t addrlen;
+
+       g_return_val_if_fail(handle != NULL, NULL);
+
+       addrlen = sizeof(so);
+       ret = accept(g_io_channel_unix_get_fd(handle), &so.sa, &addrlen);
+
+       if (ret < 0)
+               return NULL;
+
+       if (addr != NULL) sin_get_ip(&so, addr);
+       if (port != NULL) *port = sin_get_port(&so);
+
+#ifndef WIN32
+       fcntl(ret, F_SETFL, O_NONBLOCK);
+#endif
+       return g_io_channel_new(ret);
+}
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len)
+{
+        unsigned int ret;
+       int err;
+
+       g_return_val_if_fail(handle != NULL, -1);
+       g_return_val_if_fail(buf != NULL, -1);
+
+       err = g_io_channel_read(handle, buf, len, &ret);
+       if (err == 0 && ret == 0)
+               return -1; /* disconnected */
+
+       if (err == G_IO_ERROR_AGAIN || (err != 0 && errno == EINTR))
+               return 0; /* no bytes received */
+
+       return err == 0 ? (int)ret : -1;
+}
+
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len)
+{
+        unsigned int ret;
+       int err;
+
+       g_return_val_if_fail(handle != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+
+       err = g_io_channel_write(handle, (char *) data, len, &ret);
+       if (err == G_IO_ERROR_AGAIN ||
+           (err != 0 && (errno == EINTR || errno == EPIPE)))
+               return 0;
+
+       return err == 0 ? (int)ret : -1;
+}
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port)
+{
+       union sockaddr_union so;
+       socklen_t addrlen;
+
+       g_return_val_if_fail(handle != NULL, -1);
+       g_return_val_if_fail(addr != NULL, -1);
+
+       addrlen = sizeof(so);
+       if (getsockname(g_io_channel_unix_get_fd(handle),
+                       (struct sockaddr *) &so, &addrlen) == -1)
+               return -1;
+
+        sin_get_ip(&so, addr);
+       if (port) *port = sin_get_port(&so);
+
+       return 0;
+}
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+   If ip->family is 0, the address wasn't found.
+   Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6)
+{
+#ifdef HAVE_IPV6
+       union sockaddr_union *so;
+       struct addrinfo hints, *ai, *origai;
+       char hbuf[NI_MAXHOST];
+       int host_error, count;
+#else
+       struct hostent *hp;
+#endif
+
+       g_return_val_if_fail(addr != NULL, -1);
+
+       memset(ip4, 0, sizeof(IPADDR));
+       memset(ip6, 0, sizeof(IPADDR));
+
+#ifdef HAVE_IPV6
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_socktype = SOCK_STREAM;
+
+       /* save error to host_error for later use */
+       host_error = getaddrinfo(addr, NULL, &hints, &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;
+
+        origai = ai; count = 0;
+       while (ai != NULL && count < 2) {
+               so = (union sockaddr_union *) ai->ai_addr;
+
+               if (ai->ai_family == AF_INET6 && ip6->family == 0) {
+                       sin_get_ip(so, ip6);
+                        count++;
+               } else if (ai->ai_family == AF_INET && ip4->family == 0) {
+                       sin_get_ip(so, ip4);
+                        count++;
+               }
+                ai = ai->ai_next;
+       }
+       freeaddrinfo(origai);
+#else
+       hp = gethostbyname(addr);
+       if (hp == NULL) return h_errno;
+
+       ip4->family = AF_INET;
+       memcpy(&ip4->ip, hp->h_addr, 4);
+#endif
+
+       return 0;
+}
+
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+   Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name)
+{
+#ifdef HAVE_IPV6
+       struct addrinfo req, *ai;
+       int host_error;
+#else
+       struct hostent *hp;
+#endif
+       char ipname[MAX_IP_LEN];
+
+       g_return_val_if_fail(ip != NULL, -1);
+       g_return_val_if_fail(name != NULL, -1);
+
+       net_ip2host(ip, ipname);
+
+       *name = NULL;
+#ifdef HAVE_IPV6
+       memset(&req, 0, sizeof(struct addrinfo));
+       req.ai_socktype = SOCK_STREAM;
+       req.ai_flags = AI_CANONNAME;
+
+       /* save error to host_error for later use */
+       host_error = getaddrinfo(ipname, NULL, &req, &ai);
+       if (host_error != 0)
+               return host_error;
+       *name = g_strdup(ai->ai_canonname);
+
+       freeaddrinfo(ai);
+#else
+       hp = gethostbyaddr(ipname, strlen(ipname), AF_INET);
+       if (hp == NULL) return -1;
+
+       *name = g_strdup(hp->h_name);
+#endif
+
+       return 0;
+}
+
+int net_ip2host(IPADDR *ip, char *host)
+{
+#ifdef HAVE_IPV6
+       if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN))
+               return -1;
+#else
+       unsigned long ip4;
+
+       ip4 = ntohl(ip->ip.s_addr);
+       g_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu",
+                  (ip4 & 0xff000000UL) >> 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->ip) == 0)
+                       return -1;
+       } else
+#endif
+       {
+               /* IPv4 */
+               ip->family = AF_INET;
+#ifdef HAVE_INET_ATON
+               if (inet_aton(host, &ip->ip.s_addr) == 0)
+                       return -1;
+#else
+               addr = inet_addr(host);
+               if (addr == INADDR_NONE)
+                       return -1;
+
+               memcpy(&ip->ip, &addr, 4);
+#endif
+       }
+
+       return 0;
+}
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle)
+{
+       int data;
+       socklen_t len = sizeof(data);
+
+       if (getsockopt(g_io_channel_unix_get_fd(handle),
+                      SOL_SOCKET, SO_ERROR, (void *) &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 (error) {
+       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
+}
+
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+   some error with name server) */
+int net_hosterror_notfound(int error)
+{
+#ifdef HAVE_IPV6
+       return error != 1 && (error == EAI_NONAME || error == EAI_NODATA);
+#else
+       return error == HOST_NOT_FOUND || error == NO_ADDRESS;
+#endif
+}
+
+/* Get name of TCP service */
+char *net_getservbyport(int port)
+{
+       struct servent *entry;
+
+       entry = getservbyport(htons((unsigned short) port), "tcp");
+       return entry == NULL ? NULL : entry->s_name;
+}
+
+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/apps/irssi/src/core/network.h b/apps/irssi/src/core/network.h
new file mode 100644 (file)
index 0000000..4c25740
--- /dev/null
@@ -0,0 +1,95 @@
+#ifndef __NETWORK_H
+#define __NETWORK_H
+
+#ifdef HAVE_SOCKS_H
+#include <socks.h>
+#endif
+
+#include <sys/types.h>
+#ifndef WIN32
+#  include <sys/socket.h>
+#  include <netinet/in.h>
+#  include <netdb.h>
+#  include <arpa/inet.h>
+#endif
+
+#ifndef AF_INET6
+#  ifdef PF_INET6
+#    define AF_INET6 PF_INET6
+#  else
+#    define AF_INET6 10
+#  endif
+#endif
+
+struct _IPADDR {
+       unsigned short family;
+#ifdef HAVE_IPV6
+       struct in6_addr ip;
+#else
+       struct in_addr ip;
+#endif
+};
+
+/* maxmimum string length of IP address */
+#ifdef HAVE_IPV6
+#  define MAX_IP_LEN INET6_ADDRSTRLEN
+#else
+#  define MAX_IP_LEN 20
+#endif
+
+#define IPADDR_IS_V6(ip) ((ip)->family != AF_INET)
+
+/* returns 1 if IPADDRs are the same */
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2);
+
+/* Connect to socket */
+GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip);
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+/* Disconnect socket */
+void net_disconnect(GIOChannel *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(GIOChannel *handle);
+
+/* Listen for connections on a socket */
+GIOChannel *net_listen(IPADDR *my_ip, int *port);
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len);
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len);
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+   If ip->family is 0, the address wasn't found.
+   Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6);
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+   Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name);
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error);
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+   some error with name server) */
+int net_hosterror_notfound(int error);
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */
+int net_ip2host(IPADDR *ip, char *host);
+/* char* -> IPADDR translation. */
+int net_host2ip(const char *host, IPADDR *ip);
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle);
+
+/* Get name of TCP service */
+char *net_getservbyport(int port);
+
+int is_ipv4_address(const char *host);
+int is_ipv6_address(const char *host);
+
+#endif
diff --git a/apps/irssi/src/core/nick-rec.h b/apps/irssi/src/core/nick-rec.h
new file mode 100644 (file)
index 0000000..7dff6f3
--- /dev/null
@@ -0,0 +1,27 @@
+/* NICK_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("NICK", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+time_t last_check; /* last time gone was checked */
+
+char *nick;
+char *host;
+char *realname;
+int hops;
+
+/* status in server */
+unsigned int gone:1;
+unsigned int serverop:1;
+
+/* status in channel */
+unsigned int send_massjoin:1; /* Waiting to be sent in massjoin signal */
+unsigned int op:1;
+unsigned int halfop:1;
+unsigned int voice:1;
+
+GHashTable *module_data;
+
+void *unique_id; /* unique ID to use for comparing if one nick is in another channels,
+                   or NULL = nicks are unique, just keep comparing them. */
+NICK_REC *next; /* support for multiple identically named nicks */
diff --git a/apps/irssi/src/core/nicklist.c b/apps/irssi/src/core/nicklist.c
new file mode 100644 (file)
index 0000000..f9539ff
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ 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 "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "masks.h"
+
+#define isalnumhigh(a) \
+        (isalnum(a) || (unsigned char) (a) >= 128)
+
+static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       NICK_REC *list;
+
+       nick->next = NULL;
+
+       list = g_hash_table_lookup(channel->nicks, nick->nick);
+        if (list == NULL)
+               g_hash_table_insert(channel->nicks, nick->nick, nick);
+       else {
+                /* multiple nicks with same name */
+               while (list->next != NULL)
+                       list = list->next;
+               list->next = nick;
+       }
+
+       if (nick == channel->ownnick) {
+                /* move our own nick to beginning of the nick list.. */
+               nicklist_set_own(channel, nick);
+       }
+}
+
+static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       NICK_REC *list;
+
+       list = g_hash_table_lookup(channel->nicks, nick->nick);
+       if (list == NULL)
+               return;
+
+       if (list == nick || list->next == NULL) {
+               g_hash_table_remove(channel->nicks, nick->nick);
+               if (list->next != NULL) {
+                       g_hash_table_insert(channel->nicks, nick->next->nick,
+                                           nick->next);
+               }
+       } else {
+               while (list->next != nick)
+                       list = list->next;
+               list->next = nick->next;
+       }
+}
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       MODULE_DATA_INIT(nick);
+
+       nick->type = module_get_uniq_id("NICK", 0);
+        nick->chat_type = channel->chat_type;
+
+        nick_hash_add(channel, nick);
+       signal_emit("nicklist new", 2, channel, nick);
+}
+
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
+{
+        g_return_if_fail(channel != NULL);
+        g_return_if_fail(nick != NULL);
+       g_return_if_fail(host != NULL);
+
+        g_free_not_null(nick->host);
+       nick->host = g_strdup(host);
+
+        signal_emit("nicklist host changed", 2, channel, nick);
+}
+
+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(IS_CHANNEL(channel));
+       g_return_if_fail(nick != NULL);
+
+        nick_hash_remove(channel, nick);
+       nicklist_destroy(channel, nick);
+}
+
+static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
+                                const char *old_nick, const char *new_nick,
+                                GSList *nicks)
+{
+       CHANNEL_REC *channel;
+       NICK_REC *nickrec;
+       GSList *tmp;
+
+       for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+               channel = tmp->data;
+               nickrec = tmp->next->data;
+
+               /* remove old nick from hash table */
+                nick_hash_remove(channel, nickrec);
+
+               if (new_nick_id != NULL)
+                       nickrec->unique_id = new_nick_id;
+
+               g_free(nickrec->nick);
+               nickrec->nick = g_strdup(new_nick);
+
+               /* add new nick to hash table */
+                nick_hash_add(channel, nickrec);
+
+               signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
+       }
+       g_slist_free(nicks);
+}
+
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+                    const char *new_nick)
+{
+       nicklist_rename_list(server, NULL, old_nick, new_nick,
+                            nicklist_get_same(server, old_nick));
+}
+
+void nicklist_rename_unique(SERVER_REC *server,
+                           void *old_nick_id, const char *old_nick,
+                           void *new_nick_id, const char *new_nick)
+{
+       nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
+                            nicklist_get_same_unique(server, old_nick_id));
+}
+
+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 (mask_match_address(channel->server, mask,
+                                      nick->nick, nick->host))
+                       break;
+       }
+       g_slist_free(nicks);
+       return tmp == NULL ? NULL : nick;
+}
+
+GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
+{
+       GSList *nicks, *tmp, *next;
+
+       g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+       g_return_val_if_fail(mask != NULL, NULL);
+
+       nicks = nicklist_getnicks(channel);
+       for (tmp = nicks; tmp != NULL; tmp = next) {
+               NICK_REC *nick = tmp->data;
+
+               next = tmp->next;
+               if (!mask_match_address(channel->server, mask,
+                                       nick->nick, nick->host))
+                        nicks = g_slist_remove(nicks, tmp->data);
+       }
+
+       return nicks;
+}
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
+{
+       g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+       g_return_val_if_fail(nick != NULL, NULL);
+
+       return g_hash_table_lookup(channel->nicks, nick);
+}
+
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+                              void *id)
+{
+       NICK_REC *rec;
+
+       g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+       g_return_val_if_fail(nick != NULL, NULL);
+
+       rec = g_hash_table_lookup(channel->nicks, nick);
+       while (rec != NULL && rec->unique_id != id)
+                rec = rec->next;
+
+        return rec;
+}
+
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
+{
+       NICK_REC *nickrec;
+       char *nick, *host;
+
+       g_return_val_if_fail(IS_CHANNEL(channel), 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 (host != NULL) {
+               while (nickrec != NULL) {
+                       if (nickrec->host != NULL &&
+                           match_wildcards(host, nickrec->host))
+                               break; /* match */
+                       nickrec = nickrec->next;
+               }
+       }
+       g_free(nick);
+       return nickrec;
+}
+
+static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
+{
+       while (rec != NULL) {
+               *list = g_slist_append(*list, rec);
+                rec = rec->next;
+       }
+}
+
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel)
+{
+       GSList *list;
+
+       g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+
+       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)
+{
+       while (nick != NULL) {
+               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);
+               }
+
+               nick = nick->next;
+       }
+}
+
+GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
+{
+       NICKLIST_GET_SAME_REC rec;
+       GSList *tmp;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+
+       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;
+}
+
+typedef struct {
+       CHANNEL_REC *channel;
+        void *id;
+       GSList *list;
+} NICKLIST_GET_SAME_UNIQUE_REC;
+
+static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
+                                      NICKLIST_GET_SAME_UNIQUE_REC *rec)
+{
+       while (nick != NULL) {
+               if (nick->unique_id == rec->id) {
+                       rec->list = g_slist_append(rec->list, rec->channel);
+                       rec->list = g_slist_append(rec->list, nick);
+                        break;
+               }
+
+                nick = nick->next;
+       }
+}
+
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
+{
+       NICKLIST_GET_SAME_UNIQUE_REC rec;
+       GSList *tmp;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+       g_return_val_if_fail(id != NULL, NULL);
+
+        rec.id = id;
+       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_unique,
+                                    &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);
+}
+
+static void nicklist_update_flags_list(SERVER_REC *server, int gone,
+                                      int serverop, GSList *nicks)
+{
+       GSList *tmp;
+       CHANNEL_REC *channel;
+       NICK_REC *rec;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+               channel = tmp->data;
+               rec = tmp->next->data;
+
+               rec->last_check = time(NULL);
+
+               if (gone != -1 && (int)rec->gone != gone) {
+                       rec->gone = gone;
+                       signal_emit("nicklist gone changed", 2, channel, rec);
+               }
+
+               if (serverop != -1 && (int)rec->serverop != serverop) {
+                       rec->serverop = serverop;
+                       signal_emit("nicklist serverop changed", 2, channel, rec);
+               }
+       }
+       g_slist_free(nicks);
+}
+
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+                          int gone, int serverop)
+{
+       nicklist_update_flags_list(server, gone, serverop,
+                                  nicklist_get_same(server, nick));
+}
+
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+                                 int gone, int serverop)
+{
+       nicklist_update_flags_list(server, gone, serverop,
+                                  nicklist_get_same_unique(server, id));
+}
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       NICK_REC *first, *next;
+
+        channel->ownnick = nick;
+
+       /* move our nick in the list to first, makes some things easier
+          (like handling multiple identical nicks in fe-messages.c) */
+       first = g_hash_table_lookup(channel->nicks, nick->nick);
+       if (first->next == NULL)
+               return;
+
+       next = nick->next;
+       nick->next = first;
+
+       while (first->next != nick)
+                first = first->next;
+       first->next = next;
+
+        g_hash_table_insert(channel->nicks, nick->nick, nick);
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+       g_return_if_fail(IS_CHANNEL(channel));
+
+       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)
+{
+       NICK_REC *next;
+
+       while (nick != NULL) {
+                next = nick->next;
+               nicklist_destroy(channel, nick);
+                nick = next;
+       }
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+       g_return_if_fail(IS_CHANNEL(channel));
+
+       g_hash_table_foreach(channel->nicks,
+                            (GHFunc) nicklist_remove_hash, channel);
+       g_hash_table_destroy(channel->nicks);
+}
+
+static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
+{
+        NICK_REC *rec;
+       char *tmpnick;
+
+       tmpnick = g_strndup(nick, len);
+       rec = g_hash_table_lookup(channel->nicks, tmpnick);
+
+       if (rec != NULL) {
+               /* if there's multiple, get the one with identical case */
+               while (rec->next != NULL) {
+                       if (strcmp(rec->nick, tmpnick) == 0)
+                               break;
+                        rec = rec->next;
+               }
+       }
+
+        g_free(tmpnick);
+       return rec;
+}
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
+{
+       const char *msgstart, *orignick;
+       int len, fullmatch;
+
+       g_return_val_if_fail(nick != NULL, FALSE);
+       g_return_val_if_fail(msg != NULL, FALSE);
+
+       if (channel != NULL && channel->server->nick_match_msg != NULL)
+               return channel->server->nick_match_msg(msg, nick);
+
+       /* first check for identical match */
+       len = strlen(nick);
+       if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len]))
+               return TRUE;
+
+       orignick = nick;
+       for (;;) {
+               nick = orignick;
+               msgstart = msg;
+                fullmatch = TRUE;
+
+               /* check if it matches for alphanumeric parts of nick */
+               while (*nick != '\0' && *msg != '\0') {
+                       if (toupper(*nick) == toupper(*msg)) {
+                               /* total match */
+                               msg++;
+                       } else if (isalnum(*msg) && !isalnum(*nick)) {
+                               /* some strange char in your nick, pass it */
+                                fullmatch = FALSE;
+                       } else
+                               break;
+
+                       nick++;
+               }
+
+               if (msg != msgstart && !isalnumhigh(*msg)) {
+                       /* at least some of the chars in line matched the
+                          nick, and msg continue with non-alphanum character,
+                          this might be for us.. */
+                       if (*nick != '\0') {
+                               /* remove the rest of the non-alphanum chars
+                                  from nick and check if it then matches. */
+                                fullmatch = FALSE;
+                               while (*nick != '\0' && !isalnum(*nick))
+                                       nick++;
+                       }
+
+                       if (*nick == '\0') {
+                               /* yes, match! */
+                                break;
+                       }
+               }
+
+               /* no match. check if this is a message to multiple people
+                  (like nick1,nick2: text) */
+               while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
+
+               if (*msg != ',') {
+                        nick = orignick;
+                       break;
+               }
+
+                msg++;
+       }
+
+       if (*nick != '\0')
+               return FALSE; /* didn't match */
+
+       if (fullmatch)
+               return TRUE; /* matched without fuzzyness */
+
+       /* matched with some fuzzyness .. check if there's an exact match
+          for some other nick in the same channel. */
+        return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
+}
+
+void nicklist_init(void)
+{
+       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("channel created", (SIGNAL_FUNC) sig_channel_created);
+       signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+       module_uniq_destroy("NICK");
+}
diff --git a/apps/irssi/src/core/nicklist.h b/apps/irssi/src/core/nicklist.h
new file mode 100644 (file)
index 0000000..a968350
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef __NICKLIST_H
+#define __NICKLIST_H
+
+/* Returns NICK_REC if it's nick, NULL if it isn't. */
+#define NICK(server) \
+       MODULE_CHECK_CAST(server, NICK_REC, type, "NICK")
+
+#define IS_NICK(server) \
+       (NICK(server) ? TRUE : FALSE)
+
+struct _NICK_REC {
+#include "nick-rec.h"
+};
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick);
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host);
+/* Remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick);
+/* Change nick */
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+                    const char *new_nick);
+void nicklist_rename_unique(SERVER_REC *server,
+                           void *old_nick_id, const char *old_nick,
+                           void *new_nick_id, const char *new_nick);
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick);
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+                              void *id);
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks that match the mask */
+GSList *nicklist_find_multiple(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(SERVER_REC *server, const char *nick);
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id);
+
+/* Update specified nick's status in server. */
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+                          int gone, int ircop);
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+                          int gone, int ircop);
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick);
+
+/* Nick record comparision for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2);
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick);
+
+void nicklist_init(void);
+void nicklist_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/nickmatch-cache.c b/apps/irssi/src/core/nickmatch-cache.c
new file mode 100644 (file)
index 0000000..6605a2f
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ nickmatch-cache.c : irssi
+
+    Copyright (C) 2001 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 "nicklist.h"
+
+#include "nickmatch-cache.h"
+
+static GSList *lists;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func)
+{
+       NICKMATCH_REC *rec;
+
+       rec = g_new0(NICKMATCH_REC, 1);
+       rec->func = func;
+
+       lists = g_slist_append(lists, rec);
+        return rec;
+}
+
+void nickmatch_deinit(NICKMATCH_REC *rec)
+{
+       lists = g_slist_remove(lists, rec);
+
+        g_hash_table_destroy(rec->nicks);
+        g_free(rec);
+}
+
+static void nickmatch_check_channel(CHANNEL_REC *channel, NICKMATCH_REC *rec)
+{
+       GSList *nicks, *tmp;
+
+       nicks = nicklist_getnicks(channel);
+       for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+               NICK_REC *nick = tmp->data;
+
+               rec->func(rec->nicks, channel, nick);
+       }
+        g_slist_free(nicks);
+}
+
+void nickmatch_rebuild(NICKMATCH_REC *rec)
+{
+       if (rec->nicks != NULL)
+               g_hash_table_destroy(rec->nicks);
+
+       rec->nicks = g_hash_table_new((GHashFunc) g_direct_hash,
+                                     (GCompareFunc) g_direct_equal);
+
+       g_slist_foreach(channels, (GFunc) nickmatch_check_channel, rec);
+}
+
+static void sig_nick_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       GSList *tmp;
+
+        g_return_if_fail(channel != NULL);
+       g_return_if_fail(nick != NULL);
+
+       for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+               NICKMATCH_REC *rec = tmp->data;
+
+               rec->func(rec->nicks, channel, nick);
+       }
+}
+
+static void sig_nick_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       GSList *tmp;
+
+        g_return_if_fail(channel != NULL);
+       g_return_if_fail(nick != NULL);
+
+       for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+               NICKMATCH_REC *rec = tmp->data;
+
+                g_hash_table_remove(rec->nicks, nick);
+       }
+}
+
+void nickmatch_cache_init(void)
+{
+       lists = NULL;
+        signal_add("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+        signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+        signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+        signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
+
+void nickmatch_cache_deinit(void)
+{
+       g_slist_foreach(lists, (GFunc) nickmatch_deinit, NULL);
+        g_slist_free(lists);
+
+       signal_remove("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+        signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+        signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+        signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
diff --git a/apps/irssi/src/core/nickmatch-cache.h b/apps/irssi/src/core/nickmatch-cache.h
new file mode 100644 (file)
index 0000000..c4140a4
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef __NICKMATCH_CACHE_H
+#define __NICKMATCH_CACHE_H
+
+typedef void (*NICKMATCH_REBUILD_FUNC) (GHashTable *list,
+                                       CHANNEL_REC *channel, NICK_REC *nick);
+
+typedef struct {
+        GHashTable *nicks;
+       NICKMATCH_REBUILD_FUNC func;
+} NICKMATCH_REC;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func);
+void nickmatch_deinit(NICKMATCH_REC *rec);
+
+/* Calls rebuild function for all nicks in all channels.
+   This must be called soon after nickmatch_init(), before any nicklist
+   signals get sent. */
+void nickmatch_rebuild(NICKMATCH_REC *rec);
+
+#define nickmatch_find(rec, nick) \
+        g_hash_table_lookup((rec)->nicks, nick)
+
+void nickmatch_cache_init(void);
+void nickmatch_cache_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/pidwait.c b/apps/irssi/src/core/pidwait.c
new file mode 100644 (file)
index 0000000..f54aa34
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ 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) {
+               int pid = GPOINTER_TO_INT(tmp->data);
+
+               next = tmp->next;
+               if (waitpid(pid, &status, WNOHANG) > 0) {
+                       /* process terminated, remove from list */
+                       signal_emit_id(signal_pidwait, 2, tmp->data,
+                                      GINT_TO_POINTER(status));
+                       pids = g_slist_remove(pids, tmp->data);
+               }
+       }
+       return 1;
+}
+
+void pidwait_init(void)
+{
+       pids = NULL;
+       childcheck_tag = g_timeout_add(1000, (GSourceFunc) child_check, NULL);
+
+       signal_pidwait = signal_get_uniq_id("pidwait");
+}
+
+void pidwait_deinit(void)
+{
+       g_slist_free(pids);
+
+       g_source_remove(childcheck_tag);
+}
diff --git a/apps/irssi/src/core/pidwait.h b/apps/irssi/src/core/pidwait.h
new file mode 100644 (file)
index 0000000..3f6b84c
--- /dev/null
@@ -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/apps/irssi/src/core/queries.c b/apps/irssi/src/core/queries.c
new file mode 100644 (file)
index 0000000..d1c5135
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ queries.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 "servers.h"
+#include "queries.h"
+
+GSList *queries;
+
+void query_init(QUERY_REC *query, int automatic)
+{
+       g_return_if_fail(query != NULL);
+       g_return_if_fail(query->name != NULL);
+
+       queries = g_slist_append(queries, query);
+
+        MODULE_DATA_INIT(query);
+       query->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY");
+       if (query->server_tag != NULL) {
+               query->server = server_find_tag(query->server_tag);
+               if (query->server != NULL) {
+                       query->server->queries =
+                               g_slist_append(query->server->queries, query);
+               }
+       }
+
+       signal_emit("query created", 2, query, GINT_TO_POINTER(automatic));
+}
+
+void query_destroy(QUERY_REC *query)
+{
+       g_return_if_fail(IS_QUERY(query));
+
+        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_not_null(query->hilight_color);
+        g_free_not_null(query->server_tag);
+        g_free_not_null(query->address);
+       g_free(query->name);
+       g_free(query);
+}
+
+static QUERY_REC *query_find_server(SERVER_REC *server, const char *nick)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+
+       if (server->query_find_func != NULL) {
+               /* use the server specific query find function */
+               return server->query_find_func(server, nick);
+       }
+
+       for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+               QUERY_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, nick) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+QUERY_REC *query_find(SERVER_REC *server, const char *nick)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+       g_return_val_if_fail(nick != NULL, NULL);
+
+       if (server != NULL)
+               return query_find_server(server, nick);
+
+       for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+               QUERY_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, nick) == 0)
+                       return rec;
+       }
+
+        return NULL;
+}
+
+void query_change_nick(QUERY_REC *query, const char *nick)
+{
+       char *oldnick;
+
+       g_return_if_fail(IS_QUERY(query));
+
+        oldnick = query->name;
+       query->name = g_strdup(nick);
+       signal_emit("query nick changed", 2, query, oldnick);
+        g_free(oldnick);
+}
+
+void query_change_address(QUERY_REC *query, const char *address)
+{
+       g_return_if_fail(IS_QUERY(query));
+
+        g_free_not_null(query->address);
+       query->address = g_strdup(address);
+       signal_emit("query address changed", 1, query);
+}
+
+void query_change_server(QUERY_REC *query, SERVER_REC *server)
+{
+       g_return_if_fail(IS_QUERY(query));
+
+       if (query->server != NULL) {
+               query->server->queries =
+                        g_slist_remove(query->server->queries, query);
+       }
+       if (server != NULL)
+                server->queries = g_slist_append(server->queries, query);
+
+       query->server = server;
+       signal_emit("query server changed", 1, query);
+}
+
+void queries_init(void)
+{
+}
+
+void queries_deinit(void)
+{
+       module_uniq_destroy("QUERY");
+}
diff --git a/apps/irssi/src/core/queries.h b/apps/irssi/src/core/queries.h
new file mode 100644 (file)
index 0000000..77ef9c3
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __QUERIES_H
+#define __QUERIES_H
+
+#include "modules.h"
+
+/* Returns QUERY_REC if it's query, NULL if it isn't. */
+#define QUERY(query) \
+       MODULE_CHECK_CAST_MODULE(query, QUERY_REC, type, \
+                             "WINDOW ITEM TYPE", "QUERY")
+
+#define IS_QUERY(query) \
+       (QUERY(query) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _QUERY_REC {
+#include "query-rec.h"
+};
+
+extern GSList *queries;
+
+void query_init(QUERY_REC *query, int automatic);
+void query_destroy(QUERY_REC *query);
+
+/* Find query by name, if `server' is NULL, search from all servers */
+QUERY_REC *query_find(SERVER_REC *server, const char *nick);
+
+void query_change_nick(QUERY_REC *query, const char *nick);
+void query_change_address(QUERY_REC *query, const char *address);
+void query_change_server(QUERY_REC *query, SERVER_REC *server);
+
+void queries_init(void);
+void queries_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/query-rec.h b/apps/irssi/src/core/query-rec.h
new file mode 100644 (file)
index 0000000..12ef491
--- /dev/null
@@ -0,0 +1,9 @@
+/* QUERY_REC definition, used for inheritance */
+
+#include "window-item-rec.h"
+
+char *address;
+char *server_tag;
+unsigned int unwanted:1; /* TRUE if the other side closed or
+                           some error occured (DCC chats!) */
+unsigned int destroying:1;
diff --git a/apps/irssi/src/core/rawlog.c b/apps/irssi/src/core/rawlog.c
new file mode 100644 (file)
index 0000000..4e47040
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ 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 "write-buffer.h"
+#include "settings.h"
+
+static int rawlog_lines;
+static int signal_rawlog;
+static int log_file_create_mode;
+
+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) {
+               write_buffer_flush();
+               close(rawlog->handle);
+       }
+       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_buffer(rawlog->handle, str, strlen(str));
+               write_buffer(rawlog->handle, "\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->handle = open(path, O_WRONLY | O_APPEND | O_CREAT,
+                             log_file_create_mode);
+       g_free(path);
+
+       rawlog_dump(rawlog, rawlog->handle);
+       rawlog->logging = rawlog->handle != -1;
+}
+
+void rawlog_close(RAWLOG_REC *rawlog)
+{
+       if (rawlog->logging) {
+               write_buffer_flush();
+               close(rawlog->handle);
+               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"));
+       log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
+}
+
+void rawlog_init(void)
+{
+       signal_rawlog = signal_get_uniq_id("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/apps/irssi/src/core/rawlog.h b/apps/irssi/src/core/rawlog.h
new file mode 100644 (file)
index 0000000..9e460a8
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef __RAWLOG_H
+#define __RAWLOG_H
+
+struct _RAWLOG_REC {
+       int logging;
+       int handle;
+
+        int nlines;
+       GSList *lines;
+};
+
+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/apps/irssi/src/core/server-connect-rec.h b/apps/irssi/src/core/server-connect-rec.h
new file mode 100644 (file)
index 0000000..f1b3d07
--- /dev/null
@@ -0,0 +1,26 @@
+/* SERVER_CONNECT_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER CONNECT", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+/* if we're connecting via proxy, or just NULLs */
+char *proxy;
+int proxy_port;
+char *proxy_string, *proxy_password;
+
+unsigned short family; /* 0 = don't care, AF_INET or AF_INET6 */
+char *address;
+int port;
+char *chatnet;
+
+IPADDR *own_ip4, *own_ip6;
+
+char *password;
+char *nick;
+char *username;
+char *realname;
+
+/* when reconnecting, the old server status */
+unsigned int reconnection:1; /* we're trying to reconnect */
+char *channels;
+char *away_reason;
diff --git a/apps/irssi/src/core/server-rec.h b/apps/irssi/src/core/server-rec.h
new file mode 100644 (file)
index 0000000..7cdc66e
--- /dev/null
@@ -0,0 +1,72 @@
+/* SERVER_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+STRUCT_SERVER_CONNECT_REC *connrec;
+time_t connect_time; /* connection time */
+time_t real_connect_time; /* time when server replied that we really are connected */
+
+char *tag; /* tag name for addressing server */
+char *nick; /* current nick */
+
+unsigned int connected:1; /* connected to server */
+unsigned int connection_lost:1; /* Connection lost unintentionally */
+
+NET_SENDBUF_REC *handle;
+int readtag; /* input tag */
+
+/* for net_connect_nonblock() */
+GIOChannel *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* */
+
+RAWLOG_REC *rawlog;
+LINEBUF_REC *buffer; /* receive buffer */
+GHashTable *module_data;
+
+char *version; /* server version */
+char *away_reason;
+char *last_invite; /* channel where you were last invited */
+unsigned int server_operator:1;
+unsigned int usermode_away:1;
+unsigned int banned:1; /* not allowed to connect to this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+
+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;
+
+/* -- support for multiple server types -- */
+
+/* -- must not be NULL: -- */
+/* join to a number of channels, channels are specified in `data' separated
+   with commas. there can exist other information after first space like
+   channel keys etc. */
+void (*channels_join)(SERVER_REC *server, const char *data, int automatic);
+/* returns true if `flag' indicates a nick flag (op/voice/halfop) */
+int (*isnickflag)(char flag);
+/* returns true if `data' indicates a channel */
+int (*ischannel)(const char *data);
+/* returns all nick flag characters in order op, voice, halfop. If some
+   of them aren't supported '\0' can be used. */
+const char *(*get_nick_flags)(void);
+/* send public or private message to server */
+void (*send_message)(SERVER_REC *server, const char *target, const char *msg);
+
+/* -- Default implementations are used if NULL -- */
+CHANNEL_REC *(*channel_find_func)(SERVER_REC *server, const char *name);
+QUERY_REC *(*query_find_func)(SERVER_REC *server, const char *nick);
+int (*mask_match_func)(const char *mask, const char *data);
+/* returns true if `msg' was meant for `nick' */
+int (*nick_match_msg)(const char *nick, const char *msg);
+
+#undef STRUCT_SERVER_CONNECT_REC
diff --git a/apps/irssi/src/core/server-setup-rec.h b/apps/irssi/src/core/server-setup-rec.h
new file mode 100644 (file)
index 0000000..4352bef
--- /dev/null
@@ -0,0 +1,21 @@
+int type; /* module_get_uniq_id("SERVER SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *chatnet;
+
+unsigned short family; /* 0 = default, AF_INET or AF_INET6 */
+char *address;
+int port;
+char *password;
+
+char *own_host; /* address to use when connecting this server */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
+
+time_t last_connect; /* to avoid reconnecting too fast.. */
+
+unsigned int autoconnect:1;
+unsigned int last_failed:1; /* if last connection attempt failed */
+unsigned int banned:1; /* if we're banned from this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+
+GHashTable *module_data;
diff --git a/apps/irssi/src/core/servers-reconnect.c b/apps/irssi/src/core/servers-reconnect.c
new file mode 100644 (file)
index 0000000..a9491f9
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ servers-reconnect.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 "network.h"
+#include "signals.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+
+#include "settings.h"
+
+GSList *reconnects;
+static int last_reconnect_tag;
+static int reconnect_timeout_tag;
+static int reconnect_time;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+       g_free_not_null(conn->away_reason);
+       conn->away_reason = !server->usermode_away ? NULL :
+               g_strdup(server->away_reason);
+
+       signal_emit("server reconnect save status", 2, conn, server);
+}
+
+static void server_reconnect_add(SERVER_CONNECT_REC *conn,
+                                time_t next_connect)
+{
+       RECONNECT_REC *rec;
+
+       g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+       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);
+}
+
+void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn)
+{
+       g_return_if_fail(rec != NULL);
+
+       reconnects = g_slist_remove(reconnects, rec);
+
+       signal_emit("server reconnect remove", 1, rec);
+       if (free_conn) server_connect_free(rec->conn);
+       g_free(rec);
+
+       if (reconnects == NULL)
+           last_reconnect_tag = 0;
+}
+
+static int server_reconnect_timeout(void)
+{
+       SERVER_CONNECT_REC *conn;
+       GSList *list, *tmp;
+       time_t now;
+
+       /* If server_connect() removes the next reconnection in queue,
+          we're screwed. I don't think this should happen anymore, but just
+          to be sure we don't crash, do this safely. */
+       list = g_slist_copy(reconnects);
+       now = time(NULL);
+       for (tmp = list; tmp != NULL; tmp = tmp->next) {
+               RECONNECT_REC *rec = tmp->data;
+
+               if (g_slist_find(reconnects, rec) == NULL)
+                       continue;
+
+               if (rec->next_connect <= now) {
+                       conn = rec->conn;
+                       server_reconnect_destroy(rec, FALSE);
+                       CHAT_PROTOCOL(conn)->server_connect(conn);
+               }
+       }
+
+       g_slist_free(list);
+       return 1;
+}
+
+static void sserver_connect(SERVER_SETUP_REC *rec, SERVER_CONNECT_REC *conn)
+{
+        conn->family = rec->family;
+       conn->address = g_strdup(rec->address);
+       if (conn->port == 0) conn->port = rec->port;
+
+       server_setup_fill_reconn(conn, rec);
+       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.. */
+               CHAT_PROTOCOL(conn)->server_connect(conn);
+       }
+}
+
+static SERVER_CONNECT_REC *
+server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
+{
+       SERVER_CONNECT_REC *dest;
+
+        dest = NULL;
+       signal_emit("server connect copy", 2, &dest, src);
+       g_return_val_if_fail(dest != NULL, NULL);
+
+       dest->type = module_get_uniq_id("SERVER CONNECT", 0);
+       dest->reconnection = src->reconnection;
+       dest->proxy = g_strdup(src->proxy);
+        dest->proxy_port = src->proxy_port;
+       dest->proxy_string = g_strdup(src->proxy_string);
+       dest->proxy_password = g_strdup(src->proxy_password);
+
+       if (connect_info) {
+                dest->family = src->family;
+               dest->address = g_strdup(src->address);
+               dest->port = src->port;
+               dest->password = g_strdup(src->password);
+       }
+
+       dest->chatnet = g_strdup(src->chatnet);
+       dest->nick = g_strdup(src->nick);
+       dest->username = g_strdup(src->username);
+       dest->realname = g_strdup(src->realname);
+
+       if (src->own_ip4 != NULL) {
+               dest->own_ip4 = g_new(IPADDR, 1);
+               memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR));
+       }
+       if (src->own_ip6 != NULL) {
+               dest->own_ip6 = g_new(IPADDR, 1);
+               memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR));
+       }
+
+       dest->channels = g_strdup(src->channels);
+       dest->away_reason = g_strdup(src->away_reason);
+
+       return dest;
+}
+
+#define server_should_reconnect(server) \
+       ((server)->connection_lost && ((server)->connrec->chatnet != NULL || \
+                               (!(server)->banned && !(server)->dns_error)))
+
+#define sserver_connect_ok(rec, net) \
+       (!(rec)->banned && !(rec)->dns_error && (rec)->chatnet != NULL && \
+       g_strcasecmp((rec)->chatnet, (net)) == 0)
+
+static void sig_reconnect(SERVER_REC *server)
+{
+       SERVER_CONNECT_REC *conn;
+       SERVER_SETUP_REC *sserver;
+       GSList *tmp;
+       int use_next, through;
+       time_t now;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       if (reconnect_time == -1 || !server_should_reconnect(server))
+               return;
+
+       conn = server_connect_copy_skeleton(server->connrec, FALSE);
+        g_return_if_fail(conn != NULL);
+
+       /* save the server status */
+       if (server->connected) {
+               conn->reconnection = TRUE;
+
+                reconnect_save_status(conn, server);
+       }
+
+       sserver = server_setup_find(server->connrec->address,
+                                   server->connrec->port);
+
+       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 (server->banned) sserver->banned = TRUE;
+                if (server->dns_error) sserver->dns_error = TRUE;
+       }
+
+       if (sserver == NULL || conn->chatnet == NULL) {
+               /* not in any chatnet, just reconnect back to same server */
+                conn->family = server->connrec->family;
+               conn->address = g_strdup(server->connrec->address);
+               conn->port = server->connrec->port;
+               conn->password = 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 */
+                       CHAT_PROTOCOL(conn)->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) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (sserver_connect_ok(rec, conn->chatnet) &&
+                   (!rec->last_connect || !rec->last_failed ||
+                    rec->last_connect < now-FAILED_RECONNECT_WAIT)) {
+                       if (rec == sserver)
+                                conn->port = server->connrec->port;
+                       sserver_connect(rec, conn);
+                       return;
+               }
+       }
+
+       /* just try the next server in list */
+       use_next = through = FALSE;
+       for (tmp = setupservers; tmp != NULL; ) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (!use_next && server->connrec->port == rec->port &&
+                   g_strcasecmp(rec->address, server->connrec->address) == 0)
+                       use_next = TRUE;
+               else if (use_next && sserver_connect_ok(rec, conn->chatnet)) {
+                       if (rec == sserver)
+                                conn->port = server->connrec->port;
+                       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 chatnet in setup.. */
+                        server_connect_free(conn);
+                       break;
+               }
+
+               tmp = setupservers;
+               use_next = through = TRUE;
+       }
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+       g_return_if_fail(IS_SERVER(server));
+       if (!server->connrec->reconnection)
+               return;
+
+       if (server->connrec->channels != NULL)
+               server->channels_join(server, server->connrec->channels, TRUE);
+}
+
+/* Remove all servers from reconnect list */
+/* SYNTAX: RMRECONNS */
+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;
+}
+
+static void reconnect_all(void)
+{
+       GSList *list;
+       SERVER_CONNECT_REC *conn;
+       RECONNECT_REC *rec;
+
+       /* first move reconnects to another list so if server_connect()
+          fails and goes to reconnection list again, we won't get stuck
+          here forever */
+       list = NULL;
+       while (reconnects != NULL) {
+               rec = reconnects->data;
+
+               list = g_slist_append(list, rec->conn);
+               server_reconnect_destroy(rec, FALSE);
+       }
+
+
+       while (list != NULL) {
+               conn = list->data;
+
+               CHAT_PROTOCOL(conn)->server_connect(conn);
+                list = g_slist_remove(list, conn);
+       }
+}
+
+/* SYNTAX: RECONNECT <tag> */
+static void cmd_reconnect(const char *data, SERVER_REC *server)
+{
+       SERVER_CONNECT_REC *conn;
+       RECONNECT_REC *rec;
+       int tag;
+
+       if (*data == '\0' && server != NULL) {
+               /* reconnect back to same server */
+               conn = server_connect_copy_skeleton(server->connrec, TRUE);
+
+               if (server->connected) {
+                       reconnect_save_status(conn, server);
+                       signal_emit("command disconnect", 2,
+                                   "* Reconnecting", server);
+               }
+
+               conn->reconnection = TRUE;
+               CHAT_PROTOCOL(conn)->server_connect(conn);
+                return;
+       }
+
+       if (g_strcasecmp(data, "all") == 0) {
+               /* reconnect all servers in reconnect queue */
+                reconnect_all();
+                return;
+       }
+
+       if (*data == '\0') {
+               /* reconnect to first server in reconnection list */
+               if (reconnects == NULL)
+                       cmd_return_error(CMDERR_NOT_CONNECTED);
+                rec = reconnects->data;
+       } else {
+               if (g_strncasecmp(data, "RECON-", 6) == 0)
+                       data += 6;
+
+               tag = atoi(data);
+               rec = tag <= 0 ? NULL : reconnect_find_tag(tag);
+
+               if (rec == NULL) {
+                       signal_emit("server reconnect not found", 1, data);
+                        return;
+               }
+       }
+
+       conn = rec->conn;
+       server_reconnect_destroy(rec, FALSE);
+       CHAT_PROTOCOL(conn)->server_connect(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);
+       signal_stop();
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+       GSList *tmp, *next;
+
+       for (tmp = reconnects; tmp != NULL; tmp = next) {
+               RECONNECT_REC *rec = tmp->data;
+
+                next = tmp->next;
+                if (rec->conn->chat_type == proto->id)
+                       server_reconnect_destroy(rec, TRUE);
+       }
+}
+
+static void read_settings(void)
+{
+       reconnect_time = settings_get_int("server_reconnect_time");
+}
+
+void servers_reconnect_init(void)
+{
+       settings_add_int("server", "server_reconnect_time", 300);
+
+       reconnects = NULL;
+       last_reconnect_tag = 0;
+
+       reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL);
+       read_settings();
+
+       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);
+       signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns);
+       command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect);
+       command_bind_first("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+}
+
+void servers_reconnect_deinit(void)
+{
+       g_source_remove(reconnect_timeout_tag);
+
+       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);
+       signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns);
+       command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect);
+       command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+}
diff --git a/apps/irssi/src/core/servers-reconnect.h b/apps/irssi/src/core/servers-reconnect.h
new file mode 100644 (file)
index 0000000..b51486f
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef __SERVER_RECONNECT_H
+#define __SERVER_RECONNECT_H
+
+/* wait for half an hour before trying to reconnect to host where last
+   connection failed */
+#define FAILED_RECONNECT_WAIT (60*30)
+
+typedef struct {
+       int tag;
+       time_t next_connect;
+
+       SERVER_CONNECT_REC *conn;
+} RECONNECT_REC;
+
+extern GSList *reconnects;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server);
+void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn);
+
+void servers_reconnect_init(void);
+void servers_reconnect_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/servers-redirect.c b/apps/irssi/src/core/servers-redirect.c
new file mode 100644 (file)
index 0000000..ca340fc
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ 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 "servers.h"
+#include "servers-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(IS_SERVER(server));
+
+       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(IS_SERVER(server));
+       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(IS_SERVER(server), 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(IS_SERVER(server));
+
+       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(IS_SERVER(server));
+       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) {
+               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);
+
+               last--;
+       }
+
+       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(IS_SERVER(server));
+       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;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+       g_return_val_if_fail(event != NULL, NULL);
+
+       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 || args == 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/apps/irssi/src/core/servers-redirect.h b/apps/irssi/src/core/servers-redirect.h
new file mode 100644 (file)
index 0000000..c9e45ce
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef __SERVERS_REDIRECT_H
+#define __SERVERS_REDIRECT_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/apps/irssi/src/core/servers-setup.c b/apps/irssi/src/core/servers-setup.c
new file mode 100644 (file)
index 0000000..deaf3cc
--- /dev/null
@@ -0,0 +1,532 @@
+/*
+ servers-setup.c : irssi
+
+    Copyright (C) 1999-2001 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 "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+
+GSList *setupservers;
+
+char *old_source_host;
+int source_host_ok; /* Use source_host_ip .. */
+IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+
+static void save_ips(IPADDR *ip4, IPADDR *ip6,
+                    IPADDR **save_ip4, IPADDR **save_ip6)
+{
+       if (ip4->family == 0)
+               g_free_and_null(*save_ip4);
+       else {
+                if (*save_ip4 == NULL)
+                       *save_ip4 = g_new(IPADDR, 1);
+               memcpy(*save_ip4, ip4, sizeof(IPADDR));
+       }
+
+       if (ip6->family == 0)
+               g_free_and_null(*save_ip6);
+       else {
+                if (*save_ip6 == NULL)
+                       *save_ip6 = g_new(IPADDR, 1);
+               memcpy(*save_ip6, ip6, sizeof(IPADDR));
+       }
+}
+
+static void get_source_host_ip(void)
+{
+        const char *hostname;
+       IPADDR ip4, ip6;
+
+       if (source_host_ok)
+               return;
+
+       /* FIXME: This will block! */
+        hostname = settings_get_str("hostname");
+       source_host_ok = *hostname != '\0' &&
+               net_gethostbyname(hostname, &ip4, &ip6) == 0;
+
+       if (source_host_ok)
+               save_ips(&ip4, &ip6, &source_host_ip4, &source_host_ip6);
+       else {
+                g_free_and_null(source_host_ip4);
+                g_free_and_null(source_host_ip6);
+       }
+}
+
+static void conn_set_ip(SERVER_CONNECT_REC *conn, const char *own_host,
+                       IPADDR **own_ip4, IPADDR **own_ip6)
+{
+       IPADDR ip4, ip6;
+
+       if (*own_ip4 == NULL && *own_ip6 == NULL) {
+               /* resolve the IP */
+               if (net_gethostbyname(own_host, &ip4, &ip6) == 0)
+                        save_ips(&ip4, &ip6, own_ip4, own_ip6);
+       }
+
+       server_connect_own_ip_save(conn, *own_ip4, *own_ip6);
+}
+
+/* Fill information to connection from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+                             SERVER_SETUP_REC *sserver)
+{
+       g_return_if_fail(IS_SERVER_CONNECT(conn));
+       g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+       if (sserver->own_host != NULL) {
+               conn_set_ip(conn, sserver->own_host,
+                           &sserver->own_ip4, &sserver->own_ip6);
+       }
+
+       if (sserver->chatnet != NULL && conn->chatnet == NULL)
+               conn->chatnet = g_strdup(sserver->chatnet);
+
+       if (sserver->password != NULL && conn->password == NULL)
+               conn->password = g_strdup(sserver->password);
+
+       signal_emit("server setup fill reconn", 2, conn, sserver);
+}
+
+static void server_setup_fill(SERVER_CONNECT_REC *conn,
+                             const char *address, int port)
+{
+       g_return_if_fail(conn != NULL);
+       g_return_if_fail(address != NULL);
+
+       conn->type = module_get_uniq_id("SERVER CONNECT", 0);
+
+       conn->address = g_strdup(address);
+       if (port > 0) conn->port = port;
+
+       if (!conn->nick) conn->nick = g_strdup(settings_get_str("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("use_proxy")) {
+               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"));
+               conn->proxy_password = g_strdup(settings_get_str("proxy_password"));
+       }
+
+       /* source IP */
+       if (source_host_ip4 != NULL) {
+               conn->own_ip4 = g_new(IPADDR, 1);
+               memcpy(conn->own_ip4, source_host_ip4, sizeof(IPADDR));
+       }
+       if (source_host_ip6 != NULL) {
+               conn->own_ip6 = g_new(IPADDR, 1);
+               memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR));
+       }
+}
+
+static void server_setup_fill_server(SERVER_CONNECT_REC *conn,
+                                    SERVER_SETUP_REC *sserver)
+{
+       g_return_if_fail(IS_SERVER_CONNECT(conn));
+       g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+       sserver->last_connect = time(NULL);
+
+       if (sserver->family != 0 && conn->family == 0)
+                conn->family = sserver->family;
+       if (sserver->port > 0 && conn->port <= 0)
+               conn->port = sserver->port;
+       server_setup_fill_reconn(conn, sserver);
+
+       signal_emit("server setup fill server", 2, conn, sserver);
+}
+
+static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn,
+                                     CHATNET_REC *chatnet)
+{
+       g_return_if_fail(IS_SERVER_CONNECT(conn));
+       g_return_if_fail(IS_CHATNET(chatnet));
+
+       if (chatnet->nick) {
+               g_free(conn->nick);
+               conn->nick = g_strdup(chatnet->nick);;
+       }
+       if (chatnet->username) {
+                g_free(conn->username);
+               conn->username = g_strdup(chatnet->username);;
+       }
+       if (chatnet->realname) {
+                g_free(conn->realname);
+               conn->realname = g_strdup(chatnet->realname);;
+       }
+       if (chatnet->own_host != NULL) {
+               conn_set_ip(conn, chatnet->own_host,
+                           &chatnet->own_ip4, &chatnet->own_ip6);
+       }
+
+       signal_emit("server setup fill chatnet", 2, conn, chatnet);
+}
+
+static SERVER_CONNECT_REC *
+create_addr_conn(int chat_type, const char *address, int port,
+                const char *chatnet, const char *password,
+                const char *nick)
+{
+        CHAT_PROTOCOL_REC *proto;
+       SERVER_CONNECT_REC *conn;
+       SERVER_SETUP_REC *sserver;
+       CHATNET_REC *chatnetrec;
+
+       g_return_val_if_fail(address != NULL, NULL);
+
+       sserver = server_setup_find(address, port);
+       if (sserver != NULL) {
+               if (chat_type < 0)
+                       chat_type = sserver->chat_type;
+               else if (chat_type != sserver->chat_type)
+                        sserver = NULL;
+       }
+
+       proto = chat_type >= 0 ? chat_protocol_find_id(chat_type) :
+                chat_protocol_get_default();
+
+       conn = proto->create_server_connect();
+       conn->chat_type = proto->id;
+        if (chatnet != NULL && *chatnet != '\0')
+               conn->chatnet = g_strdup(chatnet);
+
+       /* fill in the defaults */
+       server_setup_fill(conn, address, port);
+
+       /* fill the rest from chat network settings */
+       chatnetrec = chatnet != NULL ? chatnet_find(chatnet) :
+               (sserver == NULL || sserver->chatnet == NULL ? NULL :
+                chatnet_find(sserver->chatnet));
+       if (chatnetrec != NULL)
+               server_setup_fill_chatnet(conn, chatnetrec);
+
+       /* fill the information from setup */
+       if (sserver != NULL)
+               server_setup_fill_server(conn, sserver);
+
+       /* nick / password given in command line overrides all settings */
+       if (password && *password) {
+               g_free_not_null(conn->password);
+               conn->password = g_strdup(password);
+       }
+       if (nick && *nick) {
+               g_free_not_null(conn->nick);
+               conn->nick = g_strdup(nick);
+       }
+
+       signal_emit("server setup fill connect", 1, conn);
+       return conn;
+}
+
+/* Connect to server where last connect succeeded (or we haven't tried to
+   connect yet). If there's no such server, connect to server where we
+   haven't connected for the longest time */
+static SERVER_CONNECT_REC *
+create_chatnet_conn(const char *dest, int port,
+                   const char *password, const char *nick)
+{
+       SERVER_SETUP_REC *bestrec;
+       GSList *tmp;
+       time_t now, besttime;
+
+       now = time(NULL);
+       bestrec = NULL; besttime = now;
+       for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (rec->chatnet == NULL ||
+                   g_strcasecmp(rec->chatnet, dest) != 0)
+                       continue;
+
+               if (!rec->last_failed) {
+                       bestrec = rec;
+                       break;
+               }
+
+               if (bestrec == NULL || besttime > rec->last_connect) {
+                       bestrec = rec;
+                       besttime = rec->last_connect;
+               }
+       }
+
+       return bestrec == NULL ? NULL :
+               create_addr_conn(bestrec->chat_type, bestrec->address, 0,
+                                dest, NULL, nick);
+}
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+   `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *
+server_create_conn(int chat_type, const char *dest, int port,
+                  const char *chatnet, const char *password,
+                  const char *nick)
+{
+       SERVER_CONNECT_REC *rec;
+
+       g_return_val_if_fail(dest != NULL, NULL);
+
+       if (chatnet_find(dest) != NULL) {
+               rec = create_chatnet_conn(dest, port, password, nick);
+               if (rec != NULL)
+                       return rec;
+       }
+
+       return create_addr_conn(chat_type, dest, port,
+                               chatnet, password, nick);
+}
+
+/* Find matching server from setup. Try to find record with a same port,
+   but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port)
+{
+       SERVER_SETUP_REC *server;
+       GSList *tmp;
+
+       g_return_val_if_fail(address != NULL, NULL);
+
+       server = NULL;
+       for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->address, address) == 0) {
+                       server = rec;
+                       if (rec->port == port)
+                               break;
+               }
+       }
+
+       return server;
+}
+
+/* Find matching server from setup. Ports must match or NULL is returned. */
+SERVER_SETUP_REC *server_setup_find_port(const char *address, int port)
+{
+       SERVER_SETUP_REC *rec;
+
+       rec = server_setup_find(address, port);
+       return rec == NULL || rec->port != port ? NULL : rec;
+}
+
+static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node)
+{
+       SERVER_SETUP_REC *rec;
+        CHATNET_REC *chatnetrec;
+       char *server, *chatnet, *family;
+       int port;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       server = config_node_get_str(node, "address", NULL);
+       if (server == NULL)
+               return NULL;
+
+       port = config_node_get_int(node, "port", 0);
+       if (server_setup_find_port(server, port) != NULL) {
+               /* already exists - don't let it get there twice or
+                  server reconnects will screw up! */
+               return NULL;
+       }
+
+       rec = NULL;
+       chatnet = config_node_get_str(node, "chatnet", NULL);
+       if (chatnet == NULL) /* FIXME: remove this after .98... */ {
+               chatnet = config_node_get_str(node, "ircnet", NULL);
+               if (chatnet != NULL) {
+                        iconfig_node_set_str(node, "chatnet", chatnet);
+                        iconfig_node_set_str(node, "ircnet", NULL);
+                       chatnet = config_node_get_str(node, "chatnet", NULL);
+               }
+       }
+
+       chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+       if (chatnetrec == NULL && chatnet != NULL) {
+                /* chat network not found, create it. */
+               chatnetrec = chat_protocol_get_default()->create_chatnet();
+               chatnetrec->chat_type = chat_protocol_get_default()->id;
+               chatnetrec->name = g_strdup(chatnet);
+               chatnet_create(chatnetrec);
+       }
+
+        family = config_node_get_str(node, "family", "");
+
+       rec = CHAT_PROTOCOL(chatnetrec)->create_server_setup();
+       rec->type = module_get_uniq_id("SERVER SETUP", 0);
+        rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+       rec->chatnet = chatnetrec == NULL ? NULL : g_strdup(chatnetrec->name);
+       rec->family = g_strcasecmp(family, "inet6") == 0 ? AF_INET6 :
+               (g_strcasecmp(family, "inet") == 0 ? AF_INET : 0);
+       rec->address = g_strdup(server);
+       rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+       rec->port = port;
+       rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
+       rec->own_host = g_strdup(config_node_get_str(node, "own_host", NULL));
+
+       signal_emit("server setup read", 2, rec, node);
+
+       setupservers = g_slist_append(setupservers, rec);
+       return rec;
+}
+
+static void server_setup_save(SERVER_SETUP_REC *rec)
+{
+       CONFIG_NODE *parentnode, *node;
+       int index;
+
+       index = g_slist_index(setupservers, rec);
+
+       parentnode = iconfig_node_traverse("(servers", TRUE);
+       node = config_node_index(parentnode, index);
+       if (node == NULL)
+               node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK);
+
+        iconfig_node_clear(node);
+       iconfig_node_set_str(node, "address", rec->address);
+       iconfig_node_set_str(node, "chatnet", rec->chatnet);
+
+       iconfig_node_set_int(node, "port", rec->port);
+       iconfig_node_set_str(node, "password", rec->password);
+       iconfig_node_set_str(node, "own_host", rec->own_host);
+
+       iconfig_node_set_str(node, "family",
+                            rec->family == AF_INET6 ? "inet6" :
+                            rec->family == AF_INET ? "inet" : NULL);
+
+       if (rec->autoconnect)
+               iconfig_node_set_bool(node, "autoconnect", TRUE);
+
+       signal_emit("server setup saved", 2, rec, node);
+}
+
+static void server_setup_remove_config(SERVER_SETUP_REC *rec)
+{
+       CONFIG_NODE *node;
+       int index;
+
+       node = iconfig_node_traverse("servers", FALSE);
+       if (node != NULL) {
+               index = g_slist_index(setupservers, rec);
+               iconfig_node_list_remove(node, index);
+       }
+}
+
+static void server_setup_destroy(SERVER_SETUP_REC *rec)
+{
+       setupservers = g_slist_remove(setupservers, rec);
+       signal_emit("server setup destroyed", 1, rec);
+
+       g_free_not_null(rec->own_host);
+       g_free_not_null(rec->own_ip4);
+       g_free_not_null(rec->own_ip6);
+       g_free_not_null(rec->chatnet);
+       g_free_not_null(rec->password);
+       g_free(rec->address);
+       g_free(rec);
+}
+
+void server_setup_add(SERVER_SETUP_REC *rec)
+{
+       rec->type = module_get_uniq_id("SERVER SETUP", 0);
+       if (g_slist_find(setupservers, rec) == NULL)
+               setupservers = g_slist_append(setupservers, rec);
+       server_setup_save(rec);
+}
+
+void server_setup_remove(SERVER_SETUP_REC *rec)
+{
+       server_setup_remove_config(rec);
+       server_setup_destroy(rec);
+}
+
+static void read_servers(void)
+{
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+       while (setupservers != NULL)
+               server_setup_destroy(setupservers->data);
+
+       /* Read servers */
+       node = iconfig_node_traverse("servers", FALSE);
+       if (node != NULL) {
+               for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+                       server_setup_read(tmp->data);
+       }
+}
+
+static void read_settings(void)
+{
+       if (old_source_host == NULL ||
+           strcmp(old_source_host, settings_get_str("hostname")) != 0) {
+                g_free_not_null(old_source_host);
+               old_source_host = g_strdup(settings_get_str("hostname"));
+
+               source_host_ok = FALSE;
+               get_source_host_ip();
+       }
+}
+
+void servers_setup_init(void)
+{
+       settings_add_str("server", "hostname", "");
+
+       settings_add_str("server", "nick", NULL);
+       settings_add_str("server", "user_name", NULL);
+       settings_add_str("server", "real_name", NULL);
+
+       settings_add_bool("proxy", "use_proxy", FALSE);
+       settings_add_str("proxy", "proxy_address", "");
+       settings_add_int("proxy", "proxy_port", 6667);
+       settings_add_str("proxy", "proxy_string", "CONNECT %s %d");
+       settings_add_str("proxy", "proxy_password", "");
+
+        setupservers = NULL;
+       source_host_ip4 = source_host_ip6 = NULL;
+        old_source_host = NULL;
+       read_settings();
+
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+       signal_add("setup reread", (SIGNAL_FUNC) read_servers);
+        signal_add("irssi init read settings", (SIGNAL_FUNC) read_servers);
+}
+
+void servers_setup_deinit(void)
+{
+       g_free_not_null(source_host_ip4);
+       g_free_not_null(source_host_ip6);
+       g_free_not_null(old_source_host);
+
+       while (setupservers != NULL)
+               server_setup_destroy(setupservers->data);
+
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+       signal_remove("setup reread", (SIGNAL_FUNC) read_servers);
+        signal_remove("irssi init read settings", (SIGNAL_FUNC) read_servers);
+
+       module_uniq_destroy("SERVER SETUP");
+}
diff --git a/apps/irssi/src/core/servers-setup.h b/apps/irssi/src/core/servers-setup.h
new file mode 100644 (file)
index 0000000..d0807d1
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef __SERVERS_SETUP_H
+#define __SERVERS_SETUP_H
+
+#include "modules.h"
+
+#define SERVER_SETUP(server) \
+       MODULE_CHECK_CAST(server, SERVER_SETUP_REC, type, "SERVER SETUP")
+
+#define IS_SERVER_SETUP(server) \
+       (SERVER_SETUP(server) ? TRUE : FALSE)
+
+/* servers */
+struct _SERVER_SETUP_REC {
+#include "server-setup-rec.h"
+};
+
+extern GSList *setupservers;
+
+extern IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+extern int source_host_ok; /* Use source_host_ip .. */
+
+/* Fill reconnection specific information to connection
+   from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+                             SERVER_SETUP_REC *sserver);
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+   `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *
+server_create_conn(int chat_type, const char *dest, int port,
+                  const char *chatnet, const char *password,
+                  const char *nick);
+
+/* Find matching server from setup. Try to find record with a same port,
+   but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port);
+/* Find matching server from setup. Ports must match or NULL is returned. */
+SERVER_SETUP_REC *server_setup_find_port(const char *address, int port);
+
+void server_setup_add(SERVER_SETUP_REC *rec);
+void server_setup_remove(SERVER_SETUP_REC *rec);
+
+void servers_setup_init(void);
+void servers_setup_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/servers.c b/apps/irssi/src/core/servers.c
new file mode 100644 (file)
index 0000000..f74e7f5
--- /dev/null
@@ -0,0 +1,544 @@
+/*
+ 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 "signals.h"
+#include "commands.h"
+#include "line-split.h"
+#include "net-nonblock.h"
+#include "net-sendbuffer.h"
+#include "misc.h"
+#include "rawlog.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-reconnect.h"
+#include "servers-redirect.h"
+#include "servers-setup.h"
+#include "channels.h"
+#include "queries.h"
+
+GSList *servers, *lookup_servers;
+
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg)
+{
+       g_return_if_fail(IS_SERVER(server));
+
+       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->handle != NULL)
+               net_sendbuffer_destroy(server->handle, TRUE);
+
+       if (server->connect_pipe[0] != NULL) {
+               g_io_channel_close(server->connect_pipe[0]);
+               g_io_channel_unref(server->connect_pipe[0]);
+               g_io_channel_close(server->connect_pipe[1]);
+               g_io_channel_unref(server->connect_pipe[1]);
+       }
+
+       MODULE_DATA_DEINIT(server);
+       server_connect_free(server->connrec);
+       g_free_not_null(server->nick);
+       g_free(server->tag);
+       g_free(server);
+}
+
+/* generate tag from server's address */
+static char *server_create_address_tag(const char *address)
+{
+       const char *start, *end;
+
+       g_return_val_if_fail(address != NULL, NULL);
+
+       /* try to generate a reasonable server tag */
+       if (strchr(address, '.') == NULL) {
+               start = end = NULL;
+       } else 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;
+
+        g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
+
+       tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
+               g_strdup(conn->chatnet) :
+               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;
+}
+
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server)
+{
+       server->connect_time = time(NULL);
+       server->rawlog = rawlog_create();
+
+       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);
+
+       servers = g_slist_append(servers, server);
+       signal_emit("server connected", 1, server);
+}
+
+static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
+{
+       int error;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       error = net_geterror(handle);
+       if (error != 0) {
+               server->connection_lost = TRUE;
+               server_connect_failed(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_finished(server);
+}
+
+static void server_connect_callback_readpipe(SERVER_REC *server)
+{
+       SERVER_CONNECT_REC *conn;
+       RESOLVED_IP_REC iprec;
+       GIOChannel *handle;
+        IPADDR *ip, *own_ip;
+       const char *errormsg;
+        int port;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       g_source_remove(server->connect_tag);
+       server->connect_tag = -1;
+
+       net_gethostbyname_return(server->connect_pipe[0], &iprec);
+
+       g_io_channel_close(server->connect_pipe[0]);
+       g_io_channel_unref(server->connect_pipe[0]);
+       g_io_channel_close(server->connect_pipe[1]);
+       g_io_channel_unref(server->connect_pipe[1]);
+
+       server->connect_pipe[0] = NULL;
+       server->connect_pipe[1] = NULL;
+
+       /* figure out if we should use IPv4 or v6 address */
+       ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
+               (server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
+               &iprec.ip4 : &iprec.ip6;
+       if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
+           !settings_get_bool("resolve_prefer_ipv6"))
+                ip = &iprec.ip4;
+
+        conn = server->connrec;
+       port = conn->proxy != NULL ? conn->proxy_port : conn->port;
+       own_ip = ip == NULL ? NULL :
+               (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
+
+       if (ip != NULL)
+               signal_emit("server connecting", 2, server, ip);
+
+       handle = ip == NULL ? NULL : net_connect_ip(ip, port, own_ip);
+       if (handle == NULL) {
+               /* failed */
+               if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
+                       /* IP wasn't found for the host, don't try to reconnect
+                          back to this server */
+                       server->dns_error = TRUE;
+               }
+
+               if (iprec.error == 0) {
+                       /* connect() failed */
+                       errormsg = g_strerror(errno);
+               } else {
+                       /* gethostbyname() failed */
+                       errormsg = iprec.errorstr != NULL ? iprec.errorstr :
+                               "Host lookup failed";
+               }
+               server->connection_lost = TRUE;
+               server_connect_failed(server, errormsg);
+               g_free_not_null(iprec.errorstr);
+               return;
+       }
+
+       server->handle = net_sendbuffer_create(handle, 0);
+       server->connect_tag =
+               g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
+                           (GInputFunction) server_connect_callback_init,
+                           server);
+}
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server)
+{
+       g_return_if_fail(server != NULL);
+
+       MODULE_DATA_INIT(server);
+       server->type = module_get_uniq_id("SERVER", 0);
+
+       server->nick = g_strdup(server->connrec->nick);
+       if (server->connrec->username == NULL || *server->connrec->username == '\0') {
+               g_free_not_null(server->connrec->username);
+
+               server->connrec->username = g_get_user_name();
+               if (*server->connrec->username == '\0') server->connrec->username = "-";
+               server->connrec->username = g_strdup(server->connrec->username);
+       }
+       if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
+               g_free_not_null(server->connrec->realname);
+
+               server->connrec->realname = g_get_real_name();
+               if (*server->connrec->realname == '\0') server->connrec->realname = "-";
+               server->connrec->realname = g_strdup(server->connrec->realname);
+       }
+
+       server->tag = server_create_tag(server->connrec);
+}
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server)
+{
+       const char *connect_address;
+        int fd[2];
+
+       g_return_val_if_fail(server != NULL, FALSE);
+       if (server->connrec->port <= 0) return FALSE;
+
+       server_connect_init(server);
+
+       if (pipe(fd) != 0) {
+               g_warning("server_connect(): pipe() failed.");
+                g_free(server->tag);
+               g_free(server->nick);
+               return FALSE;
+       }
+
+        server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
+       server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
+
+       connect_address = server->connrec->proxy != NULL ?
+               server->connrec->proxy : server->connrec->address;
+       server->connect_pid =
+               net_gethostbyname_nonblock(connect_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;
+}
+
+static int server_remove_channels(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;
+       }
+
+       while (server->queries != NULL)
+               query_change_server(server->queries->data, NULL);
+
+       g_slist_free(server->channels);
+       g_slist_free(server->queries);
+
+       return found;
+}
+
+void server_disconnect(SERVER_REC *server)
+{
+       int chans;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       if (server->connect_tag != -1) {
+               /* still connecting to server.. */
+               if (server->connect_pid != -1)
+                       net_disconnect_nonblock(server->connect_pid);
+               server_connect_failed(server, NULL);
+               return;
+       }
+
+       servers = g_slist_remove(servers, server);
+
+       signal_emit("server disconnected", 1, server);
+
+       /* close all channels */
+       chans = server_remove_channels(server);
+
+       if (server->handle != NULL) {
+               if (!chans || server->connection_lost)
+                       net_sendbuffer_destroy(server->handle, TRUE);
+               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(net_sendbuffer_handle(server->handle));
+                       net_sendbuffer_destroy(server->handle, FALSE);
+               }
+               server->handle = NULL;
+       }
+
+       if (server->readtag > 0)
+               g_source_remove(server->readtag);
+
+        MODULE_DATA_DEINIT(server);
+       server_connect_free(server->connrec);
+       rawlog_destroy(server->rawlog);
+       line_split_free(server->buffer);
+       g_free_not_null(server->version);
+       g_free_not_null(server->away_reason);
+       g_free(server->nick);
+       g_free(server->tag);
+       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 (g_strcasecmp(server->tag, tag) == 0)
+                       return server;
+       }
+
+       for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *server = tmp->data;
+
+               if (g_strcasecmp(server->tag, tag) == 0)
+                       return server;
+       }
+
+       return NULL;
+}
+
+SERVER_REC *server_find_chatnet(const char *chatnet)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(chatnet != NULL, NULL);
+       if (*chatnet == '\0') return NULL;
+
+       for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *server = tmp->data;
+
+               if (server->connrec->chatnet != NULL &&
+                   g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
+                       return server;
+       }
+
+       return NULL;
+}
+
+void server_connect_free(SERVER_CONNECT_REC *conn)
+{
+       g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+       signal_emit("server connect free", 1, conn);
+        g_free_not_null(conn->proxy);
+       g_free_not_null(conn->proxy_string);
+       g_free_not_null(conn->proxy_password);
+
+       g_free_not_null(conn->address);
+       g_free_not_null(conn->chatnet);
+
+       g_free_not_null(conn->own_ip4);
+       g_free_not_null(conn->own_ip6);
+
+        g_free_not_null(conn->password);
+        g_free_not_null(conn->nick);
+        g_free_not_null(conn->username);
+       g_free_not_null(conn->realname);
+
+       g_free_not_null(conn->channels);
+        g_free_not_null(conn->away_reason);
+        g_free(conn);
+}
+
+void server_change_nick(SERVER_REC *server, const char *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);
+}
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+                               IPADDR *ip4, IPADDR *ip6)
+{
+       if (ip4 == NULL || ip4->family == 0)
+               g_free_and_null(conn->own_ip4);
+       if (ip6 == NULL || ip6->family == 0)
+               g_free_and_null(conn->own_ip6);
+
+       if (ip4 != NULL && ip4->family != 0) {
+               /* IPv4 address was found */
+               if (conn->own_ip4 == NULL)
+                       conn->own_ip4 = g_new0(IPADDR, 1);
+               memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
+       }
+
+       if (ip6 != NULL && ip6->family != 0) {
+               /* IPv6 address was found */
+               if (conn->own_ip6 == NULL)
+                       conn->own_ip6 = g_new0(IPADDR, 1);
+               memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
+       }
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+   returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+                                  GHashTable *optlist,
+                                  SERVER_REC *defserver)
+{
+       SERVER_REC *server;
+       GSList *list, *tmp, *next;
+
+       /* get all the options, then remove the known ones. there should
+          be only one left - the server tag. */
+       list = hashtable_get_keys(optlist);
+       if (cmd != NULL) {
+               for (tmp = list; tmp != NULL; tmp = next) {
+                       char *option = tmp->data;
+                       next = tmp->next;
+
+                       if (command_have_option(cmd, option))
+                               list = g_slist_remove(list, option);
+               }
+       }
+
+       if (list == NULL)
+               return defserver;
+
+       server = server_find_tag(list->data);
+       if (server == NULL || list->next != NULL) {
+               /* unknown option (not server tag) */
+               signal_emit("error command", 2,
+                           GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+                           server == NULL ? list->data : list->next->data);
+               signal_stop();
+
+               server = NULL;
+       }
+
+       g_slist_free(list);
+       return server;
+}
+
+static void disconnect_servers(GSList *servers, int chat_type)
+{
+       GSList *tmp, *next;
+
+       for (tmp = servers; tmp != NULL; tmp = next) {
+               SERVER_REC *rec = tmp->data;
+
+                next = tmp->next;
+                if (rec->chat_type == chat_type)
+                       server_disconnect(rec);
+       }
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+        disconnect_servers(servers, proto->id);
+        disconnect_servers(lookup_servers, proto->id);
+}
+
+void servers_init(void)
+{
+       settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
+       lookup_servers = servers = NULL;
+
+       signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+       servers_reconnect_init();
+       servers_redirect_init();
+       servers_setup_init();
+}
+
+void servers_deinit(void)
+{
+       signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+       servers_setup_deinit();
+       servers_redirect_deinit();
+       servers_reconnect_deinit();
+
+       module_uniq_destroy("SERVER");
+       module_uniq_destroy("SERVER CONNECT");
+}
diff --git a/apps/irssi/src/core/servers.h b/apps/irssi/src/core/servers.h
new file mode 100644 (file)
index 0000000..75e4cbf
--- /dev/null
@@ -0,0 +1,66 @@
+#ifndef __SERVERS_H
+#define __SERVERS_H
+
+#include "modules.h"
+
+/* Returns SERVER_REC if it's server, NULL if it isn't. */
+#define SERVER(server) \
+       MODULE_CHECK_CAST(server, SERVER_REC, type, "SERVER")
+
+/* Returns SERVER_CONNECT_REC if it's server connection, NULL if it isn't. */
+#define SERVER_CONNECT(conn) \
+       MODULE_CHECK_CAST(conn, SERVER_CONNECT_REC, type, "SERVER CONNECT")
+
+#define IS_SERVER(server) \
+       (SERVER(server) ? TRUE : FALSE)
+
+#define IS_SERVER_CONNECT(conn) \
+       (SERVER_CONNECT(conn) ? TRUE : FALSE)
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+struct _SERVER_CONNECT_REC {
+#include "server-connect-rec.h"
+};
+
+#define STRUCT_SERVER_CONNECT_REC SERVER_CONNECT_REC
+struct _SERVER_REC {
+#include "server-rec.h"
+};
+
+extern GSList *servers, *lookup_servers;
+
+void servers_init(void);
+void servers_deinit(void);
+
+/* Disconnect from server */
+void server_disconnect(SERVER_REC *server);
+
+SERVER_REC *server_find_tag(const char *tag);
+SERVER_REC *server_find_chatnet(const char *chatnet);
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server);
+void server_connect_free(SERVER_CONNECT_REC *conn);
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server);
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server);
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg);
+
+/* Change your nick */
+void server_change_nick(SERVER_REC *server, const char *nick);
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+                               IPADDR *ip4, IPADDR *ip6);
+
+/* `optlist' should contain only one unknown key - the server tag.
+   returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+                                  GHashTable *optlist,
+                                  SERVER_REC *defserver);
+
+#endif
diff --git a/apps/irssi/src/core/settings.c b/apps/irssi/src/core/settings.c
new file mode 100644 (file)
index 0000000..0169b26
--- /dev/null
@@ -0,0 +1,680 @@
+/*
+ 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 "misc.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+#include "default-config.h"
+
+#include <signal.h>
+
+CONFIG_REC *mainconfig;
+
+static GString *last_errors;
+static char *last_config_error_msg;
+static GSList *last_invalid_modules;
+static int fe_initialized;
+static int config_changed; /* FIXME: remove after .98 (unless needed again) */
+
+static GHashTable *settings;
+static int timeout_tag;
+
+static int config_last_modifycounter;
+static time_t config_last_mtime;
+static long config_last_size;
+static unsigned int config_last_checksum;
+
+static SETTINGS_REC *settings_find(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;
+}
+
+const char *settings_get_str(const char *key)
+{
+       SETTINGS_REC *rec;
+       CONFIG_NODE *setnode, *node;
+
+       rec = settings_find(key);
+        g_return_val_if_fail(rec != NULL, NULL);
+
+       setnode = iconfig_node_traverse("settings", FALSE);
+       if (setnode == NULL)
+               return rec->def;
+
+       node = config_node_section(setnode, rec->module, -1);
+       return node == NULL ? rec->def :
+               config_node_get_str(node, key, rec->def);
+}
+
+int settings_get_int(const char *key)
+{
+       SETTINGS_REC *rec;
+       CONFIG_NODE *setnode, *node;
+        int def;
+
+       rec = settings_find(key);
+        g_return_val_if_fail(rec != NULL, 0);
+        def = GPOINTER_TO_INT(rec->def);
+
+       setnode = iconfig_node_traverse("settings", FALSE);
+       if (setnode == NULL)
+               return def;
+
+       node = config_node_section(setnode, rec->module, -1);
+       return node == NULL ? def :
+               config_node_get_int(node, key, def);
+}
+
+int settings_get_bool(const char *key)
+{
+       SETTINGS_REC *rec;
+       CONFIG_NODE *setnode, *node;
+        int def;
+
+       rec = settings_find(key);
+        g_return_val_if_fail(rec != NULL, 0);
+        def = GPOINTER_TO_INT(rec->def);
+
+       setnode = iconfig_node_traverse("settings", FALSE);
+       if (setnode == NULL)
+               return def;
+
+       node = config_node_section(setnode, rec->module, -1);
+       return node == NULL ? def :
+               config_node_get_bool(node, key, def);
+}
+
+void settings_add_str_module(const char *module, 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->module = g_strdup(module);
+       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_module(const char *module, 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->module = g_strdup(module);
+       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_module(const char *module, 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->module = g_strdup(module);
+       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->module);
+        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);
+}
+
+static int settings_remove_hash(const char *key, SETTINGS_REC *rec,
+                               const char *module)
+{
+       if (strcmp(rec->module, module) == 0) {
+               settings_destroy(rec);
+                return TRUE;
+       }
+
+        return FALSE;
+}
+
+void settings_remove_module(const char *module)
+{
+       g_hash_table_foreach_remove(settings,
+                                   (GHRFunc) settings_remove_hash,
+                                   (void *) module);
+}
+
+static CONFIG_NODE *settings_get_node(const char *key)
+{
+       SETTINGS_REC *rec;
+        CONFIG_NODE *node;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       rec = g_hash_table_lookup(settings, key);
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       node = iconfig_node_traverse("settings", TRUE);
+       return config_node_section(node, rec->module, NODE_TYPE_BLOCK);
+}
+
+void settings_set_str(const char *key, const char *value)
+{
+        iconfig_node_set_str(settings_get_node(key), key, value);
+}
+
+void settings_set_int(const char *key, int value)
+{
+        iconfig_node_set_int(settings_get_node(key), key, value);
+}
+
+void settings_set_bool(const char *key, int value)
+{
+        iconfig_node_set_bool(settings_get_node(key), key, value);
+}
+
+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 void sig_init_finished(void)
+{
+       fe_initialized = TRUE;
+       if (last_errors != NULL) {
+               signal_emit("settings errors", 1, last_errors->str);
+               g_string_free(last_errors, TRUE);
+       }
+
+       if (last_config_error_msg != NULL) {
+               signal_emit("gui dialog", 2, "error", last_config_error_msg);
+               g_free_and_null(last_config_error_msg);
+       }
+
+       if (config_changed) {
+               /* some backwards compatibility changes were made to
+                  config file, reload it */
+               signal_emit("setup changed", 0);
+       }
+}
+
+/* FIXME: remove after 0.7.98 - only for backward compatibility */
+static void settings_move(SETTINGS_REC *rec, char *value)
+{
+       CONFIG_NODE *setnode, *node;
+
+       setnode = iconfig_node_traverse("settings", TRUE);
+       node = config_node_section(setnode, rec->module, NODE_TYPE_BLOCK);
+
+       iconfig_node_set_str(node, rec->key, value);
+       iconfig_node_set_str(setnode, rec->key, NULL);
+
+        config_changed = TRUE;
+}
+
+static void settings_clean_invalid_module(const char *module)
+{
+        CONFIG_NODE *node;
+        SETTINGS_REC *set;
+       GSList *tmp, *next;
+
+       node = iconfig_node_traverse("settings", FALSE);
+       if (node == NULL) return;
+
+       node = config_node_section(node, module, -1);
+       if (node == NULL) return;
+
+       for (tmp = node->value; tmp != NULL; tmp = next) {
+               CONFIG_NODE *subnode = tmp->data;
+                next = tmp->next;
+
+               set = g_hash_table_lookup(settings, subnode->key);
+               if (set == NULL || strcmp(set->module, module) != 0)
+                        iconfig_node_remove(node, subnode);
+       }
+}
+
+/* remove all invalid settings from config file. works only with the
+   modules that have already called settings_check() */
+void settings_clean_invalid(void)
+{
+       while (last_invalid_modules != NULL) {
+               char *module = last_invalid_modules->data;
+
+                settings_clean_invalid_module(module);
+
+                g_free(module);
+               last_invalid_modules =
+                       g_slist_remove(last_invalid_modules, module);
+       }
+}
+
+/* verify that all settings in config file for `module' are actually found
+   from /SET list */
+void settings_check_module(const char *module)
+{
+        SETTINGS_REC *set;
+       CONFIG_NODE *node;
+        GString *errors;
+       GSList *tmp, *next;
+        int count;
+
+        g_return_if_fail(module != NULL);
+
+       node = iconfig_node_traverse("settings", FALSE);
+       if (node != NULL) {
+               /* FIXME: remove after 0.7.98 */
+               for (tmp = node->value; tmp != NULL; tmp = next) {
+                       CONFIG_NODE *node = tmp->data;
+
+                        next = tmp->next;
+                       if (node->type != NODE_TYPE_KEY)
+                               continue;
+                       set = g_hash_table_lookup(settings, node->key);
+                        if (set != NULL)
+                               settings_move(set, node->value);
+               }
+       }
+       node = node == NULL ? NULL : config_node_section(node, module, -1);
+       if (node == NULL) return;
+
+        errors = g_string_new(NULL);
+       g_string_sprintf(errors, "Unknown settings in configuration "
+                        "file for module %s:", module);
+
+        count = 0;
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               set = g_hash_table_lookup(settings, node->key);
+               if (set == NULL || strcmp(set->module, module) != 0) {
+                       g_string_sprintfa(errors, " %s", node->key);
+                        count++;
+               }
+       }
+       if (count > 0) {
+               if (gslist_find_icase_string(last_invalid_modules,
+                                            module) == NULL) {
+                        /* mark this module having invalid settings */
+                       last_invalid_modules =
+                               g_slist_append(last_invalid_modules,
+                                              g_strdup(module));
+               }
+               if (fe_initialized)
+                        signal_emit("settings errors", 1, errors->str);
+               else {
+                       if (last_errors == NULL)
+                               last_errors = g_string_new(NULL);
+                       else
+                               g_string_append_c(last_errors, '\n');
+                        g_string_append(last_errors, errors->str);
+               }
+       }
+        g_string_free(errors, TRUE);
+}
+
+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);
+}
+
+/* Yes, this is my own stupid checksum generator, some "real" algorithm
+   would be nice but would just take more space without much real benefit */
+static unsigned int file_checksum(const char *fname)
+{
+        char buf[512];
+        int f, ret, n;
+       unsigned int checksum = 0;
+
+       f = open(fname, O_RDONLY);
+       if (f == -1) return 0;
+
+        n = 0;
+       while ((ret = read(f, buf, sizeof(buf))) > 0) {
+               while (ret-- > 0)
+                       checksum += buf[ret] << ((n++ & 3)*8);
+       }
+       close(f);
+       return checksum;
+}
+
+static void irssi_config_save_state(const char *fname)
+{
+       struct stat statbuf;
+
+       g_return_if_fail(fname != NULL);
+
+       if (stat(fname, &statbuf) != 0)
+               return;
+
+       /* save modify time, file size and checksum */
+       config_last_mtime = statbuf.st_mtime;
+       config_last_size = statbuf.st_size;
+       config_last_checksum = file_checksum(fname);
+}
+
+int irssi_config_is_changed(const char *fname)
+{
+       struct stat statbuf;
+
+       if (fname == NULL)
+               fname = mainconfig->fname;
+
+       if (stat(fname, &statbuf) != 0)
+               return FALSE;
+
+       return config_last_mtime != statbuf.st_mtime &&
+               (config_last_size != statbuf.st_size ||
+                config_last_checksum != file_checksum(fname));
+}
+
+static CONFIG_REC *parse_configfile(const char *fname)
+{
+       CONFIG_REC *config;
+       struct stat statbuf;
+        const char *path;
+       char *real_fname;
+
+       real_fname = fname != NULL ? g_strdup(fname) :
+               g_strdup_printf("%s"G_DIR_SEPARATOR_S".irssi"
+                               G_DIR_SEPARATOR_S"config", g_get_home_dir());
+
+       if (stat(real_fname, &statbuf) == 0)
+               path = real_fname;
+       else {
+               /* user configuration file not found, use the default one
+                  from sysconfdir */
+                path = SYSCONFDIR"/irssi/config";
+               if (stat(path, &statbuf) != 0) {
+                       /* no configuration file in sysconfdir ..
+                          use the build-in configuration */
+                        path = NULL;
+               }
+       }
+
+       config = config_open(path, -1);
+       if (config == NULL) {
+               last_config_error_msg =
+                       g_strdup_printf("Error opening configuration file %s: %s",
+                                       path, g_strerror(errno));
+               config = config_open(NULL, -1);
+       }
+
+        if (path != NULL)
+               config_parse(config);
+        else
+               config_parse_data(config, default_config, "internal");
+
+       config_change_file_name(config, real_fname, 0660);
+        irssi_config_save_state(real_fname);
+       g_free(real_fname);
+       return config;
+}
+
+static void init_configfile(void)
+{
+       struct stat statbuf;
+       char *str;
+
+       str = g_strdup_printf("%s"G_DIR_SEPARATOR_S".irssi", g_get_home_dir());
+       if (stat(str, &statbuf) != 0) {
+               /* ~/.irssi not found, create it. */
+               if (mkpath(str, 0700) != 0) {
+                       g_error("Couldn't create %s directory", str);
+               }
+       } else if (!S_ISDIR(statbuf.st_mode)) {
+               g_error("%s is not a directory.\n"
+                       "You should remove it with command: rm ~/.irssi", str);
+       }
+       g_free(str);
+
+       mainconfig = parse_configfile(NULL);
+       config_last_modifycounter = mainconfig->modifycounter;
+
+       /* any errors? */
+       if (config_last_error(mainconfig) != NULL) {
+               last_config_error_msg =
+                       g_strdup_printf("Ignored errors in configuration "
+                                       "file:\n%s",
+                                       config_last_error(mainconfig));
+       }
+
+       signal(SIGTERM, sig_term);
+}
+
+int settings_reread(const char *fname)
+{
+       CONFIG_REC *tempconfig;
+       char *str;
+
+       if (fname == NULL) fname = "~/.irssi/config";
+
+       str = convert_home(fname);
+       tempconfig = parse_configfile(str);
+       g_free(str);
+
+       if (tempconfig == NULL) {
+               signal_emit("gui dialog", 2, "error", g_strerror(errno));
+               return FALSE;
+       }
+
+       if (config_last_error(tempconfig) != NULL) {
+               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 FALSE;
+       }
+
+       config_close(mainconfig);
+       mainconfig = tempconfig;
+       config_last_modifycounter = mainconfig->modifycounter;
+
+       signal_emit("setup changed", 0);
+       signal_emit("setup reread", 0);
+        return TRUE;
+}
+
+int settings_save(const char *fname)
+{
+       char *str;
+       int error;
+
+       if (fname == NULL)
+               fname = mainconfig->fname;
+
+       error = config_write(mainconfig, fname, 0660) != 0;
+       irssi_config_save_state(fname);
+       config_last_modifycounter = mainconfig->modifycounter;
+       if (error) {
+               str = g_strdup_printf("Couldn't save configuration file: %s",
+                                     config_last_error(mainconfig));
+               signal_emit("gui dialog", 2, "error", str);
+               g_free(str);
+       }
+        return !error;
+}
+
+static void sig_autosave(void)
+{
+       char *fname, *str;
+
+       if (!settings_get_bool("settings_autosave") ||
+           config_last_modifycounter == mainconfig->modifycounter)
+               return;
+
+       if (!irssi_config_is_changed(NULL))
+               settings_save(NULL);
+       else {
+               fname = g_strconcat(mainconfig->fname, ".autosave", NULL);
+               str = g_strdup_printf("Configuration file was modified "
+                                     "while irssi was running. Saving "
+                                     "configuration to file '%s' instead. "
+                                     "Use /SAVE or /RELOAD to get rid of "
+                                     "this message.", fname);
+               signal_emit("gui dialog", 2, "warning", str);
+               g_free(str);
+
+                settings_save(fname);
+               g_free(fname);
+       }
+}
+
+void settings_init(void)
+{
+       settings = g_hash_table_new((GHashFunc) g_str_hash,
+                                   (GCompareFunc) g_str_equal);
+
+       last_errors = NULL;
+       last_config_error_msg = NULL;
+        last_invalid_modules = NULL;
+       fe_initialized = FALSE;
+        config_changed = FALSE;
+
+       config_last_mtime = 0;
+       config_last_modifycounter = 0;
+       init_configfile();
+
+       settings_add_bool("misc", "settings_autosave", TRUE);
+       timeout_tag = g_timeout_add(1000*60*60, (GSourceFunc) sig_autosave, NULL);
+       signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+       signal_add("gui exit", (SIGNAL_FUNC) sig_autosave);
+}
+
+static void settings_hash_free(const char *key, SETTINGS_REC *rec)
+{
+       settings_destroy(rec);
+}
+
+void settings_deinit(void)
+{
+        g_source_remove(timeout_tag);
+       signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+       signal_remove("gui exit", (SIGNAL_FUNC) sig_autosave);
+
+       g_slist_foreach(last_invalid_modules, (GFunc) g_free, NULL);
+       g_slist_free(last_invalid_modules);
+
+       g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL);
+       g_hash_table_destroy(settings);
+
+       if (mainconfig != NULL) config_close(mainconfig);
+}
diff --git a/apps/irssi/src/core/settings.h b/apps/irssi/src/core/settings.h
new file mode 100644 (file)
index 0000000..cea8c4d
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef __SETTINGS_H
+#define __SETTINGS_H
+
+enum {
+       SETTING_TYPE_STRING,
+       SETTING_TYPE_INT,
+       SETTING_TYPE_BOOLEAN
+};
+
+typedef struct {
+        char *module;
+       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)
+#define iconfig_node_set_str(a, b, c) config_node_set_str(mainconfig, a, b, c)
+#define iconfig_node_set_int(a, b, c) config_node_set_int(mainconfig, a, b, c)
+#define iconfig_node_set_bool(a, b, c) config_node_set_bool(mainconfig, a, b, c)
+#define iconfig_node_list_remove(a, b) config_node_list_remove(mainconfig, a, b)
+#define iconfig_node_remove(a, b) config_node_remove(mainconfig, a, b)
+#define iconfig_node_clear(a) config_node_clear(mainconfig, a)
+#define iconfig_node_add_list(a, b) config_node_add_list(mainconfig, a, b)
+
+extern CONFIG_REC *mainconfig;
+
+/* Functions for handling the "settings" node of Irssi configuration */
+const char *settings_get_str(const char *key);
+int settings_get_int(const char *key);
+int settings_get_bool(const char *key);
+
+/* Functions to add/remove settings */
+void settings_add_str_module(const char *module, const char *section,
+                            const char *key, const char *def);
+void settings_add_int_module(const char *module, const char *section,
+                            const char *key, int def);
+void settings_add_bool_module(const char *module, const char *section,
+                             const char *key, int def);
+void settings_remove(const char *key);
+void settings_remove_module(const char *module);
+
+#define settings_add_str(section, key, def) \
+       settings_add_str_module(MODULE_NAME, section, key, def)
+#define settings_add_int(section, key, def) \
+       settings_add_int_module(MODULE_NAME, section, key, def)
+#define settings_add_bool(section, key, def) \
+       settings_add_bool_module(MODULE_NAME, section, key, def)
+
+void settings_set_str(const char *key, const char *value);
+void settings_set_int(const char *key, int value);
+void settings_set_bool(const char *key, int value);
+
+/* 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);
+
+/* verify that all settings in config file for `module' are actually found
+   from /SET list */
+void settings_check_module(const char *module);
+#define settings_check() settings_check_module(MODULE_NAME)
+
+/* remove all invalid settings from config file. works only with the
+   modules that have already called settings_check() */
+void settings_clean_invalid(void);
+
+/* if `fname' is NULL, the default is used */
+int settings_reread(const char *fname);
+int settings_save(const char *fname);
+int irssi_config_is_changed(const char *fname);
+
+void settings_init(void);
+void settings_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/core/signals.c b/apps/irssi/src/core/signals.c
new file mode 100644 (file)
index 0000000..cb964eb
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ 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 id; /* signal id */
+
+       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 *current_emitted_signal;
+
+void signal_add_to(const char *module, int pos,
+                  const char *signal, SIGNAL_FUNC func)
+{
+       g_return_if_fail(signal != NULL);
+
+       signal_add_to_id(module, pos, signal_get_uniq_id(signal), func);
+}
+
+/* bind a signal */
+void signal_add_to_id(const char *module, int pos,
+                     int signal_id, SIGNAL_FUNC func)
+{
+       SIGNAL_REC *rec;
+
+       g_return_if_fail(signal_id >= 0);
+       g_return_if_fail(func != NULL);
+       g_return_if_fail(pos >= 0 && pos < SIGNAL_LISTS);
+
+       rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+       if (rec == NULL) {
+               rec = g_mem_chunk_alloc0(signals_chunk);
+                rec->id = signal_id;
+               g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), 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);
+       }
+}
+
+static int signal_list_find(GPtrArray *array, void *data)
+{
+       unsigned 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;
+}
+
+void signal_remove_id(int signal_id, SIGNAL_FUNC func)
+{
+       SIGNAL_REC *rec;
+
+       g_return_if_fail(signal_id >= 0);
+       g_return_if_fail(func != NULL);
+
+       rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+        if (rec != NULL)
+               signal_remove_from_lists(rec, signal_id, func);
+}
+
+/* unbind signal */
+void signal_remove(const char *signal, SIGNAL_FUNC func)
+{
+       g_return_if_fail(signal != NULL);
+
+       signal_remove_id(signal_get_uniq_id(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, stop_emit_count;
+
+       /* signal_stop_by_name("signal"); signal_emit("signal", ...);
+          fails if we compare rec->stop_emit against 0. */
+       stop_emit_count = rec->stop_emit;
+
+       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;
+#if SIGNAL_MAX_ARGUMENTS != 6
+#  error SIGNAL_MAX_ARGS changed - update code
+#endif
+                               func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5]);
+                               current_emitted_signal = prev_emitted_signal;
+                       }
+
+                       if (rec->stop_emit != stop_emit_count) {
+                               stopped = TRUE;
+                               rec->stop_emit--;
+                               n = SIGNAL_LISTS;
+                               break;
+                       }
+               }
+       }
+       rec->emitting--;
+
+       if (!rec->emitting) {
+               if (rec->stop_emit != 0) {
+                       /* signal_stop() used too many times */
+                        rec->stop_emit = 0;
+               }
+               if (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[SIGNAL_MAX_ARGUMENTS];
+       SIGNAL_REC *rec;
+       int n;
+
+       g_return_val_if_fail(signal_id >= 0, FALSE);
+       g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
+
+       for (n = 0; n < SIGNAL_MAX_ARGUMENTS; n++)
+               arglist[n] = n >= params ? NULL : va_arg(va, gconstpointer);
+
+       rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+       if (rec != NULL && signal_emit_real(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 = signal_get_uniq_id(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)
+               g_warning("signal_stop() : no signals are being emitted currently");
+       else if (rec->emitting > rec->stop_emit)
+               rec->stop_emit++;
+}
+
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal)
+{
+       SIGNAL_REC *rec;
+       int signal_id;
+
+       signal_id = signal_get_uniq_id(signal);
+       rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+       if (rec == NULL)
+               g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal);
+       else if (rec->emitting > rec->stop_emit)
+               rec->stop_emit++;
+}
+
+/* return the name of the signal that is currently being emitted */
+const char *signal_get_emitted(void)
+{
+       return signal_get_id_str(signal_get_emitted_id());
+}
+
+/* return the ID of the signal that is currently being emitted */
+int signal_get_emitted_id(void)
+{
+       SIGNAL_REC *rec;
+
+       rec = current_emitted_signal;
+        g_return_val_if_fail(rec != NULL, -1);
+       return rec->id;
+}
+
+/* return TRUE if specified signal was stopped */
+int signal_is_stopped(int signal_id)
+{
+       SIGNAL_REC *rec;
+
+       rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+       g_return_val_if_fail(rec != NULL, FALSE);
+
+        return rec->emitting <= rec->stop_emit;
+}
+
+static void signal_remove_module(void *signal, SIGNAL_REC *rec,
+                                const char *module)
+{
+       unsigned int index;
+       int signal_id, list;
+
+       signal_id = GPOINTER_TO_INT(signal);
+
+       for (list = 0; list < SIGNAL_LISTS; list++) {
+               if (rec->modulelist[list] == NULL)
+                       continue;
+
+               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);
+}
+
+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/apps/irssi/src/core/signals.h b/apps/irssi/src/core/signals.h
new file mode 100644 (file)
index 0000000..795f732
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef __SIGNAL_H
+#define __SIGNAL_H
+
+#define SIGNAL_MAX_ARGUMENTS 6
+typedef void (*SIGNAL_FUNC) (gconstpointer, gconstpointer,
+                            gconstpointer, gconstpointer,
+                            gconstpointer, gconstpointer);
+
+void signals_init(void);
+void signals_deinit(void);
+
+/* signal name -> ID */
+#define signal_get_uniq_id(signal) \
+        module_get_uniq_id_str("signals", signal)
+/* signal ID -> name */
+#define signal_get_id_str(signal_id) \
+       module_find_id_str("signals", signal_id)
+
+/* bind a signal */
+void signal_add_to(const char *module, int pos,
+                  const char *signal, SIGNAL_FUNC func);
+void signal_add_to_id(const char *module, int pos,
+                     int 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);
+void signal_remove_id(int signal_id, 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);
+
+/* return the name of the signal that is currently being emitted */
+const char *signal_get_emitted(void);
+/* return the ID of the signal that is currently being emitted */
+int signal_get_emitted_id(void);
+/* return TRUE if specified signal was stopped */
+int signal_is_stopped(int signal_id);
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module);
+
+#endif
diff --git a/apps/irssi/src/core/special-vars.c b/apps/irssi/src/core/special-vars.c
new file mode 100644 (file)
index 0000000..40372f9
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ 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 "expandos.h"
+#include "settings.h"
+#include "misc.h"
+
+#define ALIGN_RIGHT 0x01
+#define ALIGN_CUT   0x02
+#define ALIGN_PAD   0x04
+
+#define isvarchar(c) \
+        (isalnum(c) || (c) == '_')
+
+static SPECIAL_HISTORY_FUNC history_func = NULL;
+
+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, SERVER_REC *server,
+                                    void *item, int *free_ret)
+{
+       EXPANDO_FUNC func;
+       char *ret;
+       int type;
+
+       *free_ret = FALSE;
+
+       /* expando? */
+        func = expando_find_long(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)
+               return ret;
+
+       return NULL;
+}
+
+static char *get_long_variable(char **cmd, SERVER_REC *server,
+                              void *item, int *free_ret, int getname)
+{
+       char *start, *var, *ret;
+
+       /* get variable name */
+       start = *cmd;
+       while (isvarchar((*cmd)[1])) (*cmd)++;
+
+       var = g_strndup(start, (int) (*cmd-start)+1);
+       if (getname) {
+               *free_ret = TRUE;
+                return var;
+       }
+       ret = get_long_variable_value(var, server, item, free_ret);
+       g_free(var);
+       return ret;
+}
+
+/* return the value of the variable found from `cmd'.
+   if 'getname' is TRUE, return the name of the variable instead it's value */
+static char *get_variable(char **cmd, SERVER_REC *server, void *item,
+                         char **arglist, int *free_ret, int *arg_used,
+                         int getname)
+{
+       EXPANDO_FUNC func;
+
+       if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') {
+               /* argument */
+               *free_ret = TRUE;
+               if (arg_used != NULL) *arg_used = TRUE;
+               return getname ? g_strdup_printf("%c", **cmd) :
+                       get_argument(cmd, arglist);
+       }
+
+       if (isalpha(**cmd) && isvarchar((*cmd)[1])) {
+               /* long variable name.. */
+               return get_long_variable(cmd, server, item, free_ret, getname);
+       }
+
+       /* single character variable. */
+       if (getname) {
+               *free_ret = TRUE;
+                return g_strdup_printf("%c", **cmd);
+       }
+       *free_ret = FALSE;
+       func = expando_find_char(**cmd);
+       return func == NULL ? NULL : func(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, SERVER_REC *server, void *item,
+                              char **arglist, int *free_ret, int *arg_used,
+                              int flags)
+{
+       char command, *value, *p;
+       int len;
+
+       if (**cmd == '!') {
+               /* find text from command history */
+               if (flags & PARSE_FLAG_GETNAME)
+                       return "!";
+
+               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 = "*";
+
+                       if (flags & PARSE_FLAG_GETNAME)
+                                return "*";
+
+                       *free_ret = TRUE;
+                       return get_argument(&temp_cmd, arglist);
+               }
+       }
+
+       value = get_variable(cmd, server, item, arglist, free_ret,
+                            arg_used, flags & PARSE_FLAG_GETNAME);
+
+       if (flags & PARSE_FLAG_GETNAME)
+               return value;
+
+       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|ALIGN_PAD;
+        *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;
+               else if (*str == '.')
+                         *flags &= ~ALIGN_PAD;
+               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 */
+       if (flags & ALIGN_PAD) {
+               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, SERVER_REC *server, void *item,
+                   char **arglist, int *free_ret, int *arg_used, int flags)
+{
+       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,
+                                                  flags);
+               }
+
+               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, flags);
+       if (**cmd == '\0')
+               g_error("parse_special() : buffer overflow!");
+
+       if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
+               *arg_used = TRUE;
+
+       if (brackets) {
+               while (**cmd != '}' && (*cmd)[1] != '\0')
+                       (*cmd)++;
+       }
+
+       if (nest_free) g_free(nest_value);
+
+       if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
+               /* 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;
+}
+
+static void gstring_append_escaped(GString *str, const char *text)
+{
+       while (*text != '\0') {
+               if (*text == '%')
+                        g_string_append_c(str, '%');
+               g_string_append_c(str, *text);
+               text++;
+       }
+}
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+                          const char *data, int *arg_used, int flags)
+{
+       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 == '\\'){
+                       switch (*cmd) {
+                       case 't':
+                               g_string_append_c(str, '\t');
+                               break;
+                       case 'n':
+                               g_string_append_c(str, '\n');
+                               break;
+                       default:
+                               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,
+                                           flags);
+                       if (ret != NULL) {
+                                if ((flags & PARSE_FLAG_ESCAPE_VARS) == 0)
+                                       g_string_append(str, ret);
+                               else
+                                        gstring_append_escaped(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;
+}
+
+#define is_split_char(str, start) \
+       ((str)[0] == ';' && ((start) == (str) || \
+               ((str)[-1] != '\\' && (str)[-1] != '$')))
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+                        SERVER_REC *server, void *item)
+{
+       const char *cmdchars;
+       char *orig, *str, *start, *ret;
+       int arg_used, arg_used_ever;
+       GSList *commands;
+
+       commands = NULL;
+       arg_used_ever = FALSE;
+       cmdchars = settings_get_str("cmdchars");
+
+       /* get a list of all the commands to run */
+       orig = start = str = g_strdup(cmd);
+       do {
+               if (is_split_char(str, start))
+                       *str++ = '\0';
+               else if (*str != '\0') {
+                       str++;
+                       continue;
+               }
+
+               ret = parse_special_string(start, server, item,
+                                          data, &arg_used, 0);
+               if (arg_used) arg_used_ever = TRUE;
+
+               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);
+               }
+               commands = g_slist_append(commands, ret);
+               start = str;
+       } while (*start != '\0');
+
+       /* run the command, if no arguments were ever used, append all of them
+          after each command */
+       while (commands != NULL) {
+               ret = commands->data;
+
+               if (!arg_used_ever && *data != '\0') {
+                       char *old = ret;
+
+                       ret = g_strconcat(old, " ", data, NULL);
+                       g_free(old);
+               }
+               signal_emit("send command", 3, ret, server, item);
+
+               g_free(ret);
+               commands = g_slist_remove(commands, commands->data);
+       }
+       g_free(orig);
+}
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func)
+{
+       history_func = func;
+}
+
+static void special_vars_signals_do(const char *text, int funccount,
+                                   SIGNAL_FUNC *funcs, int bind)
+{
+       char *ret;
+        int need_free;
+
+       while (*text != '\0') {
+               if (*text == '\\' && text[1] != '\0') {
+                       text += 2;
+               } else if (*text == '$' && text[1] != '\0') {
+                       text++;
+                       ret = parse_special((char **) &text, NULL, NULL,
+                                           NULL, &need_free, NULL,
+                                           PARSE_FLAG_GETNAME);
+                       if (ret != NULL) {
+                                if (bind)
+                                       expando_bind(ret, funccount, funcs);
+                                else
+                                       expando_unbind(ret, funccount, funcs);
+                               if (need_free) g_free(ret);
+                       }
+
+               }
+                else text++;
+       }
+}
+
+void special_vars_add_signals(const char *text,
+                             int funccount, SIGNAL_FUNC *funcs)
+{
+        special_vars_signals_do(text, funccount, funcs, TRUE);
+}
+
+void special_vars_remove_signals(const char *text,
+                                int funccount, SIGNAL_FUNC *funcs)
+{
+        special_vars_signals_do(text, funccount, funcs, FALSE);
+}
diff --git a/apps/irssi/src/core/special-vars.h b/apps/irssi/src/core/special-vars.h
new file mode 100644 (file)
index 0000000..af02e12
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef __SPECIAL_VARS_H
+#define __SPECIAL_VARS_H
+
+#include "signals.h"
+
+#define PARSE_FLAG_GETNAME     0x01 /* return argument name instead of it's value */
+#define PARSE_FLAG_ISSET_ANY   0x02 /* arg_used field specifies that at least one of the $variables was non-empty */
+#define PARSE_FLAG_ESCAPE_VARS  0x04 /* if any arguments/variables contain % chars, escape them with another % */
+
+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, SERVER_REC *server, void *item,
+                   char **arglist, int *free_ret, int *arg_used, int flags);
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+                          const char *data, int *arg_used, int flags);
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+                        SERVER_REC *server, void *item);
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func);
+
+void special_vars_add_signals(const char *text,
+                             int funccount, SIGNAL_FUNC *funcs);
+void special_vars_remove_signals(const char *text,
+                                int funccount, SIGNAL_FUNC *funcs);
+
+#endif
diff --git a/apps/irssi/src/core/window-item-def.h b/apps/irssi/src/core/window-item-def.h
new file mode 100644 (file)
index 0000000..4364c66
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef __WINDOW_ITEM_DEF_H
+#define __WINDOW_ITEM_DEF_H
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _WI_ITEM_REC {
+#include "window-item-rec.h"
+};
+
+#endif
diff --git a/apps/irssi/src/core/window-item-rec.h b/apps/irssi/src/core/window-item-rec.h
new file mode 100644 (file)
index 0000000..5c09a5b
--- /dev/null
@@ -0,0 +1,15 @@
+/* WI_ITEM_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("CHANNEL/QUERY/xxx", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+GHashTable *module_data;
+
+void *window;
+STRUCT_SERVER_REC *server;
+char *name;
+
+time_t createtime;
+int data_level;
+char *hilight_color;
+
+#undef STRUCT_SERVER_REC
diff --git a/apps/irssi/src/core/write-buffer.c b/apps/irssi/src/core/write-buffer.c
new file mode 100644 (file)
index 0000000..762fc24
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ write-buffer.c : irssi
+
+    Copyright (C) 2001 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 "write-buffer.h"
+
+#define BUFFER_BLOCK_SIZE 2048
+
+typedef struct {
+       char *active_block;
+        int active_block_pos;
+
+       GSList *blocks;
+} BUFFER_REC;
+
+static GSList *empty_blocks;
+static GHashTable *buffers;
+static int block_count;
+
+static int write_buffer_max_blocks;
+static int timeout_tag;
+
+static void write_buffer_new_block(BUFFER_REC *rec)
+{
+       char *block;
+
+       if (empty_blocks == NULL)
+               block = g_malloc(BUFFER_BLOCK_SIZE);
+       else {
+               block = empty_blocks->data;
+                empty_blocks = g_slist_remove(empty_blocks, block);
+       }
+
+        block_count++;
+       rec->active_block = block;
+        rec->active_block_pos = 0;
+       rec->blocks = g_slist_append(rec->blocks, block);
+}
+
+int write_buffer(int handle, const void *data, int size)
+{
+       BUFFER_REC *rec;
+        const char *cdata = data;
+       int next_size;
+
+       if (write_buffer_max_blocks <= 0) {
+               /* no write buffer */
+                return write(handle, data, size);
+       }
+
+       if (size <= 0)
+               return size;
+
+       rec = g_hash_table_lookup(buffers, GINT_TO_POINTER(handle));
+       if (rec == NULL) {
+               rec = g_new0(BUFFER_REC, 1);
+                write_buffer_new_block(rec);
+               g_hash_table_insert(buffers, GINT_TO_POINTER(handle), rec);
+       }
+
+       while (size > 0) {
+                if (rec->active_block_pos == BUFFER_BLOCK_SIZE)
+                       write_buffer_new_block(rec);
+
+               next_size = size < BUFFER_BLOCK_SIZE-rec->active_block_pos ?
+                       size : BUFFER_BLOCK_SIZE-rec->active_block_pos;
+               memcpy(rec->active_block+rec->active_block_pos,
+                      cdata, next_size);
+
+               rec->active_block_pos += next_size;
+               cdata += next_size;
+                size -= next_size;
+       }
+
+       if (block_count > write_buffer_max_blocks)
+                write_buffer_flush();
+
+        return size;
+}
+
+static int write_buffer_flush_rec(void *handlep, BUFFER_REC *rec)
+{
+       GSList *tmp;
+        int handle, size;
+
+        handle = GPOINTER_TO_INT(handlep);
+       for (tmp = rec->blocks; tmp != NULL; tmp = tmp->next) {
+               size = tmp->data != rec->active_block ? BUFFER_BLOCK_SIZE :
+                       rec->active_block_pos;
+               write(handle, tmp->data, size);
+       }
+
+        empty_blocks = g_slist_concat(empty_blocks, rec->blocks);
+       g_free(rec);
+        return TRUE;
+}
+
+void write_buffer_flush(void)
+{
+       g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+       g_slist_free(empty_blocks);
+        empty_blocks = NULL;
+
+       g_hash_table_foreach_remove(buffers,
+                                   (GHRFunc) write_buffer_flush_rec, NULL);
+        block_count = 0;
+}
+
+static void read_settings(void)
+{
+       int msecs;
+
+       if (timeout_tag != -1)
+                g_source_remove(timeout_tag);
+
+       write_buffer_flush();
+
+       write_buffer_max_blocks = settings_get_int("write_buffer_kb") *
+               1024/BUFFER_BLOCK_SIZE;
+
+       if (settings_get_int("write_buffer_mins") > 0) {
+                msecs = settings_get_int("write_buffer_mins")*60*1000;
+               timeout_tag = g_timeout_add(msecs,
+                                           (GSourceFunc) write_buffer_flush,
+                                           NULL);
+       }
+}
+
+static void cmd_flushbuffer(void)
+{
+        write_buffer_flush();
+}
+
+void write_buffer_init(void)
+{
+       settings_add_int("misc", "write_buffer_mins", 0);
+       settings_add_int("misc", "write_buffer_kb", 0);
+
+       buffers = g_hash_table_new((GHashFunc) g_direct_hash,
+                                  (GCompareFunc) g_direct_equal);
+
+        empty_blocks = NULL;
+        block_count = 0;
+
+       timeout_tag = -1;
+       read_settings();
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+        command_bind("flushbuffer", NULL, (SIGNAL_FUNC) cmd_flushbuffer);
+}
+
+void write_buffer_deinit(void)
+{
+       if (timeout_tag != -1)
+               g_source_remove(timeout_tag);
+
+        write_buffer_flush();
+        g_hash_table_destroy(buffers);
+
+       g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+        g_slist_free(empty_blocks);
+
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+       command_unbind("flushbuffer",  (SIGNAL_FUNC) cmd_flushbuffer);
+}
diff --git a/apps/irssi/src/core/write-buffer.h b/apps/irssi/src/core/write-buffer.h
new file mode 100644 (file)
index 0000000..ef52744
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __WRITE_BUFFER_H
+#define __WRITE_BUFFER_H
+
+int write_buffer(int handle, const void *data, int size);
+void write_buffer_flush(void);
+
+void write_buffer_init(void);
+void write_buffer_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/Makefile.am b/apps/irssi/src/fe-common/core/Makefile.am
new file mode 100644 (file)
index 0000000..9913cfe
--- /dev/null
@@ -0,0 +1,60 @@
+noinst_LIBRARIES = libfe_common_core.a
+
+INCLUDES = \
+       $(GLIB_CFLAGS) \
+       -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ \
+       -DHELPDIR=\""$(datadir)/irssi/help"\" \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_common_core_a_SOURCES = \
+       autorun.c \
+       chat-completion.c \
+       command-history.c \
+       completion.c \
+       fe-channels.c \
+       fe-common-core.c \
+       fe-core-commands.c \
+       fe-exec.c \
+       fe-expandos.c \
+       fe-help.c \
+       fe-ignore.c \
+       fe-ignore-messages.c \
+       fe-log.c \
+       fe-messages.c \
+       fe-modules.c \
+       fe-queries.c \
+       fe-server.c \
+       fe-settings.c \
+       formats.c \
+       hilight-text.c \
+       keyboard.c \
+       module-formats.c \
+       printtext.c \
+       themes.c \
+       translation.c \
+       window-activity.c \
+       window-commands.c \
+       window-items.c \
+       windows-layout.c \
+       fe-windows.c
+
+noinst_HEADERS = \
+       command-history.h \
+       chat-completion.h \
+       completion.h \
+       fe-channels.h \
+       fe-common-core.h \
+       fe-exec.h \
+       fe-messages.h \
+       fe-queries.h \
+       formats.h \
+       hilight-text.h \
+       keyboard.h \
+       module-formats.h \
+       module.h \
+       printtext.h \
+       themes.h \
+       translation.h \
+       window-items.h \
+       windows-layout.h \
+       fe-windows.h
diff --git a/apps/irssi/src/fe-common/core/autorun.c b/apps/irssi/src/fe-common/core/autorun.c
new file mode 100644 (file)
index 0000000..b305b82
--- /dev/null
@@ -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 "fe-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);
+               if (ret > 0) 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/apps/irssi/src/fe-common/core/chat-completion.c b/apps/irssi/src/fe-common/core/chat-completion.c
new file mode 100644 (file)
index 0000000..3cbcbf0
--- /dev/null
@@ -0,0 +1,862 @@
+/*
+ chat-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 "settings.h"
+
+#include "chatnets.h"
+#include "servers-setup.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "queries.h"
+#include "nicklist.h"
+
+#include "completion.h"
+#include "window-items.h"
+
+static int keep_privates_count, keep_publics_count;
+static int completion_lowercase;
+static const char *completion_char, *cmdchars;
+static GSList *global_lastmsgs;
+static int completion_auto, completion_strict;
+
+#define SERVER_LAST_MSG_ADD(server, nick) \
+       last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \
+                    nick, TRUE, keep_privates_count)
+
+#define CHANNEL_LAST_MSG_ADD(channel, nick, own) \
+       last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \
+                    nick, own, keep_publics_count)
+
+static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick)
+{
+       while (list != NULL) {
+               LAST_MSG_REC *rec = list->data;
+
+               if (g_strcasecmp(rec->nick, nick) == 0)
+                       return rec;
+               list = list->next;
+       }
+
+       return NULL;
+}
+
+static void last_msg_dec_owns(GSList *list)
+{
+       LAST_MSG_REC *rec;
+
+       while (list != NULL) {
+               rec = list->data;
+               if (rec->own) rec->own--;
+
+               list = list->next;
+       }
+}
+
+static void last_msg_add(GSList **list, const char *nick, int own, int max)
+{
+       LAST_MSG_REC *rec;
+
+       rec = last_msg_find(*list, nick);
+       if (rec != NULL) {
+               /* msg already exists, update it */
+               *list = g_slist_remove(*list, rec);
+               if (own)
+                       rec->own = max;
+               else if (rec->own)
+                        rec->own--;
+       } else {
+               rec = g_new(LAST_MSG_REC, 1);
+               rec->nick = g_strdup(nick);
+
+               if ((int)g_slist_length(*list) == max) {
+                       *list = g_slist_remove(*list,
+                                              g_slist_last(*list)->data);
+               }
+
+               rec->own = own ? max : 0;
+       }
+       rec->time = time(NULL);
+
+        last_msg_dec_owns(*list);
+
+       *list = g_slist_prepend(*list, rec);
+}
+
+static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
+{
+       *list = g_slist_remove(*list, rec);
+
+       g_free(rec->nick);
+       g_free(rec);
+}
+
+void completion_last_message_add(const char *nick)
+{
+       g_return_if_fail(nick != NULL);
+
+       last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count);
+}
+
+void completion_last_message_remove(const char *nick)
+{
+       LAST_MSG_REC *rec;
+
+       g_return_if_fail(nick != NULL);
+
+       rec = last_msg_find(global_lastmsgs, nick);
+        if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec);
+}
+
+void completion_last_message_rename(const char *oldnick, const char *newnick)
+{
+       LAST_MSG_REC *rec;
+
+       g_return_if_fail(oldnick != NULL);
+       g_return_if_fail(newnick != NULL);
+
+       rec = last_msg_find(global_lastmsgs, oldnick);
+       if (rec != NULL) {
+               g_free(rec->nick);
+                rec->nick = g_strdup(newnick);
+       }
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+                              const char *nick, const char *address,
+                              const char *target)
+{
+       CHANNEL_REC *channel;
+        int own;
+
+       channel = channel_find(server, target);
+       if (channel != NULL) {
+                own = nick_match_msg(channel, msg, server->nick);
+               CHANNEL_LAST_MSG_ADD(channel, nick, own);
+       }
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+                               const char *nick, const char *address)
+{
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(nick != NULL);
+
+       SERVER_LAST_MSG_ADD(server, nick);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+                                  const char *target, const char *origtarget)
+{
+       CHANNEL_REC *channel;
+       NICK_REC *nick;
+       char *p, *msgnick;
+
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(msg != NULL);
+        if (target == NULL) return;
+
+        channel = channel_find(server, target);
+       if (channel == NULL)
+               return;
+
+       /* channel msg - if first word in line is nick,
+          add it to lastmsgs */
+       p = strchr(msg, ' ');
+       if (p != NULL && p != msg) {
+               msgnick = g_strndup(msg, (int) (p-msg));
+               nick = nicklist_find(channel, msgnick);
+               if (nick == NULL && msgnick[1] != '\0') {
+                       /* probably ':' or ',' or some other
+                          char after nick, try without it */
+                       msgnick[strlen(msgnick)-1] = '\0';
+                       nick = nicklist_find(channel, msgnick);
+               }
+                g_free(msgnick);
+               if (nick != NULL && nick != channel->ownnick)
+                       CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE);
+       }
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+                                   const char *target, const char *origtarget)
+{
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(target != NULL);
+
+       if (target != NULL && query_find(server, target) == NULL)
+               SERVER_LAST_MSG_ADD(server, target);
+}
+
+static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+        MODULE_CHANNEL_REC *mchannel;
+       LAST_MSG_REC *rec;
+
+        mchannel = MODULE_DATA(channel);
+       rec = last_msg_find(mchannel->lastmsgs, nick->nick);
+       if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec);
+}
+
+static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick,
+                            const char *oldnick)
+{
+        MODULE_CHANNEL_REC *mchannel;
+       LAST_MSG_REC *rec;
+
+        mchannel = MODULE_DATA(channel);
+       rec = last_msg_find(mchannel->lastmsgs, oldnick);
+       if (rec != NULL) {
+               g_free(rec->nick);
+               rec->nick = g_strdup(nick->nick);
+       }
+}
+
+static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2)
+{
+       return m1->time < m2->time ? 1 : -1;
+}
+
+/* Complete /MSG from specified server, or from
+   global_lastmsgs if server is NULL */
+static void completion_msg_server(GSList **list, SERVER_REC *server,
+                                 const char *nick, const char *prefix)
+{
+       LAST_MSG_REC *msg;
+       GSList *tmp;
+       int len;
+
+       g_return_if_fail(nick != NULL);
+
+       len = strlen(nick);
+       tmp = server == NULL ? global_lastmsgs :
+               ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs;
+       for (; tmp != NULL; tmp = tmp->next) {
+               LAST_MSG_REC *rec = tmp->data;
+
+               if (len != 0 && g_strncasecmp(rec->nick, nick, len) != 0)
+                       continue;
+
+               msg = g_new(LAST_MSG_REC, 1);
+               msg->time = rec->time;
+               msg->nick = prefix == NULL || *prefix == '\0' ?
+                       g_strdup(rec->nick) :
+                       g_strconcat(prefix, " ", rec->nick, NULL);
+               *list = g_slist_insert_sorted(*list, msg,
+                                             (GCompareFunc) last_msg_cmp);
+       }
+}
+
+/* convert list of LAST_MSG_REC's to list of char* nicks. */
+static GList *convert_msglist(GSList *msglist)
+{
+       GList *list;
+
+       list = NULL;
+       while (msglist != NULL) {
+               LAST_MSG_REC *rec = msglist->data;
+
+                list = g_list_append(list, rec->nick);
+               msglist = g_slist_remove(msglist, rec);
+               g_free(rec);
+       }
+
+       return list;
+}
+
+/* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
+static GList *completion_msg(SERVER_REC *win_server,
+                            SERVER_REC *find_server,
+                            const char *nick, const char *prefix)
+{
+       GSList *tmp, *list;
+       char *newprefix;
+
+       g_return_val_if_fail(nick != NULL, NULL);
+       if (servers == NULL) return NULL;
+
+       list = NULL;
+       if (find_server != NULL) {
+               completion_msg_server(&list, find_server, nick, prefix);
+               return convert_msglist(list);
+       }
+
+       completion_msg_server(&list, NULL, nick, prefix);
+       for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *rec = tmp->data;
+
+               if (rec == win_server)
+                       newprefix = g_strdup(prefix);
+               else {
+                       newprefix = prefix == NULL ?
+                               g_strdup_printf("-%s", rec->tag) :
+                               g_strdup_printf("%s -%s", prefix, rec->tag);
+               }
+
+               completion_msg_server(&list, rec, nick, newprefix);
+               g_free_not_null(newprefix);
+       }
+
+       return convert_msglist(list);
+}
+
+static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel,
+                                  const char *nick, const char *suffix)
+{
+        MODULE_CHANNEL_REC *mchannel;
+       GSList *tmp;
+        GList *ownlist;
+       char *str;
+       int len;
+
+       /* go through the last x nicks who have said something in the channel.
+          nicks of all the "own messages" are placed before others */
+        ownlist = NULL;
+       len = strlen(nick);
+        mchannel = MODULE_DATA(channel);
+       for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) {
+               LAST_MSG_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->nick, nick, len) == 0 &&
+                   glist_find_icase_string(*outlist, rec->nick) == NULL) {
+                       str = g_strconcat(rec->nick, suffix, NULL);
+                       if (completion_lowercase) g_strdown(str);
+                       if (rec->own)
+                               ownlist = g_list_append(ownlist, str);
+                        else
+                               *outlist = g_list_append(*outlist, str);
+               }
+       }
+
+        *outlist = g_list_concat(ownlist, *outlist);
+}
+
+static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
+                                        const char *nick,
+                                        const char *suffix)
+{
+       GSList *nicks, *tmp;
+       GList *list;
+       char *tnick, *str, *in, *out;
+       int len, str_len, tmplen;
+
+       g_return_val_if_fail(channel != NULL, NULL);
+
+       list = NULL;
+
+       /* get all nicks from current channel, strip non alnum chars,
+          compare again and add to completion list on matching */
+       len = strlen(nick);
+       nicks = nicklist_getnicks(channel);
+
+       str_len = 80; str = g_malloc(str_len+1);
+       for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+               NICK_REC *rec = tmp->data;
+
+                tmplen = strlen(rec->nick);
+               if (tmplen > str_len) {
+                        str_len = tmplen*2;
+                        str = g_realloc(str, str_len+1);
+               }
+
+               /* remove non alnum chars from nick */
+               in = rec->nick; out = str;
+               while (*in != '\0') {
+                       if (isalnum(*in))
+                               *out++ = *in;
+                        in++;
+               }
+                *out = '\0';
+
+               /* add to list if 'cleaned' nick matches */
+               if (g_strncasecmp(str, nick, len) == 0) {
+                       tnick = g_strconcat(rec->nick, suffix, NULL);
+                       if (completion_lowercase)
+                               g_strdown(tnick);
+
+                       if (glist_find_icase_string(list, tnick) == NULL)
+                               list = g_list_append(list, tnick);
+                       else
+                                g_free(tnick);
+               }
+
+       }
+        g_free(str);
+       g_slist_free(nicks);
+
+       return list;
+}
+
+static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick,
+                                      const char *suffix)
+{
+       GSList *nicks, *tmp;
+       GList *list;
+       char *str;
+       int len;
+
+       g_return_val_if_fail(channel != NULL, NULL);
+       g_return_val_if_fail(nick != NULL, NULL);
+       if (*nick == '\0') return NULL;
+
+       if (suffix != NULL && *suffix == '\0')
+               suffix = NULL;
+
+       /* put first the nicks who have recently said something */
+       list = NULL;
+       complete_from_nicklist(&list, channel, nick, suffix);
+
+       /* and add the rest of the nicks too */
+       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 &&
+                   rec != channel->ownnick) {
+                       str = g_strconcat(rec->nick, suffix, NULL);
+                       if (completion_lowercase)
+                               g_strdown(str);
+                        if (glist_find_icase_string(list, str) == NULL)
+                               list = g_list_append(list, str);
+                       else
+                                g_free(str);
+               }
+       }
+       g_slist_free(nicks);
+
+       /* remove non alphanum chars from nick and search again in case
+          list is still NULL ("foo<tab>" would match "_foo_" f.e.) */
+       if (!completion_strict)
+               list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix));
+       return list;
+}
+
+/* append all strings in list2 to list1 that already aren't there and
+   free list2 */
+static GList *completion_joinlist(GList *list1, GList *list2)
+{
+       GList *old;
+
+       old = 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(old);
+       return list1;
+}
+
+GList *completion_get_channels(SERVER_REC *server, const char *word)
+{
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+       g_return_val_if_fail(*word != '\0', NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       /* first get the joined channels */
+       tmp = server == NULL ? NULL : server->channels;
+       for (; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->name, word, len) == 0)
+                       list = g_list_append(list, g_strdup(rec->name));
+       }
+
+       /* get channels from setup */
+       for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_SETUP_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->name, word, len) == 0 &&
+                   glist_find_icase_string(list, rec->name) == NULL)
+                       list = g_list_append(list, g_strdup(rec->name));
+
+       }
+
+       return list;
+}
+
+static void complete_window_nicks(GList **list, WINDOW_REC *window,
+                                  const char *word, const char *linestart)
+{
+        CHANNEL_REC *channel;
+        GList *tmplist;
+        GSList *tmp;
+        const char *nicksuffix;
+
+        nicksuffix = *linestart != '\0' ? NULL : completion_char;
+
+        channel = CHANNEL(window->active);
+
+        /* first the active channel */
+        if (channel != NULL) {
+                tmplist = completion_channel_nicks(channel, word, nicksuffix);
+                *list = completion_joinlist(*list, tmplist);
+        }
+
+        if (nicksuffix != NULL) {
+                /* completing nick at the start of line - probably answering
+                   to some other nick, don't even try to complete from
+                   non-active channels */
+                return;
+        }
+
+        /* then the rest */
+        for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+                channel = CHANNEL(tmp->data);
+                if (channel != NULL && tmp->data != window->active) {
+                        tmplist = completion_channel_nicks(channel, word,
+                                                           nicksuffix);
+                        *list = completion_joinlist(*list, tmplist);
+                }
+        }
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+                             const char *word, const char *linestart)
+{
+       SERVER_REC *server;
+       CHANNEL_REC *channel;
+       QUERY_REC *query;
+       char *prefix;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(window != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(linestart != NULL);
+
+       server = window->active_server;
+       if (server == NULL && servers != NULL)
+               server = servers->data;
+
+       if (server != NULL && server->ischannel(word)) {
+               /* probably completing a channel name */
+               *list = completion_get_channels(window->active_server, word);
+                return;
+       }
+
+       server = window->active_server;
+       if (server == NULL || !server->connected)
+               return;
+
+       if (*linestart == '\0' && *word == '\0') {
+               /* pressed TAB at the start of line - add /MSG */
+                prefix = g_strdup_printf("%cmsg", *cmdchars);
+               *list = completion_msg(server, NULL, "", prefix);
+               if (*list == NULL)
+                       *list = g_list_append(*list, g_strdup(prefix));
+               g_free(prefix);
+
+               signal_stop();
+               return;
+       }
+
+       channel = CHANNEL(window->active);
+       query = QUERY(window->active);
+       if (channel == NULL && query != NULL &&
+           g_strncasecmp(word, query->name, strlen(word)) == 0) {
+               /* completion in query */
+                *list = g_list_append(*list, g_strdup(query->name));
+       } else if (channel != NULL) {
+               /* nick completion .. we could also be completing a nick
+                  after /MSG from nicks in channel */
+                complete_window_nicks(list, window, word, linestart);
+       }
+
+       if (*list != NULL) signal_stop();
+}
+
+static SERVER_REC *line_get_server(const char *line)
+{
+       SERVER_REC *server;
+       char *tag, *ptr;
+
+       g_return_val_if_fail(line != NULL, NULL);
+       if (*line != '-') return NULL;
+
+       /* -option found - should be server tag */
+       tag = g_strdup(line+1);
+       ptr = strchr(tag, ' ');
+       if (ptr != NULL) *ptr = '\0';
+
+       server = server_find_tag(tag);
+
+       g_free(tag);
+       return server;
+}
+
+static void sig_complete_msg(GList **list, WINDOW_REC *window,
+                            const char *word, const char *line,
+                            int *want_space)
+{
+       SERVER_REC *server, *msgserver;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       server = window->active_server;
+       if (server == NULL || !server->connected)
+               return;
+
+       msgserver = line_get_server(line);
+       *list = completion_msg(server, msgserver, word, NULL);
+       if (*list != NULL) signal_stop();
+}
+
+GList *completion_get_chatnets(const char *word)
+{
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+               CHATNET_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->name, word, len) == 0)
+                       list = g_list_append(list, g_strdup(rec->name));
+       }
+
+       return list;
+}
+
+GList *completion_get_servers(const char *word)
+{
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       g_return_val_if_fail(word != NULL, NULL);
+
+       len = strlen(word);
+       list = NULL;
+
+       for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->address, word, len) == 0) 
+                       list = g_list_append(list, g_strdup(rec->address));
+       }
+
+       return list;
+}
+
+static void sig_complete_connect(GList **list, WINDOW_REC *window,
+                                const char *word, const char *line, 
+                                int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+
+       *list = completion_get_chatnets(word);
+       *list = g_list_concat(*list, completion_get_servers(word));
+       if (*list != NULL) signal_stop();
+}
+
+/* expand \n, \t and \\ */
+static char *expand_escapes(const char *line, SERVER_REC *server,
+                           WI_ITEM_REC *item)
+{
+       char *ptr, *ret;
+
+       ret = ptr = g_malloc(strlen(line)+1);
+       for (; *line != '\0'; line++) {
+               if (*line != '\\') {
+                       *ptr++ = *line;
+                       continue;
+               }
+
+               line++;
+               if (*line == '\0') {
+                       *ptr++ = '\\';
+                       break;
+               }
+
+               switch (*line) {
+               case 'n':
+                       /* newline .. we need to send another "send text"
+                          event to handle it (or actually the text before
+                          the newline..) */
+                       *ptr = '\0';
+                       signal_emit("send text", 3, ret, server, item);
+                       ptr = ret;
+                       break;
+               case 't':
+                       *ptr++ = '\t';
+                       break;
+               case '\\':
+                       *ptr++ = '\\';
+                       break;
+               default:
+                       *ptr++ = '\\';
+                       *ptr++ = *line;
+                       break;
+               }
+       }
+
+       *ptr = '\0';
+       return ret;
+}
+
+static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       CHANNEL_REC *channel;
+       GList *comp;
+       char *line, *str, *ptr, comp_char;
+
+       g_return_if_fail(data != NULL);
+       if (item == NULL) return;
+
+       line = settings_get_bool("expand_escapes") ?
+               expand_escapes(data, server, item) : g_strdup(data);
+       comp_char = *completion_char;
+
+       /* check for automatic nick completion */
+        ptr = NULL;
+       comp = NULL;
+       channel = CHANNEL(item);
+
+       if (completion_auto && channel != NULL && comp_char != '\0') {
+               ptr = strchr(line, comp_char);
+               if (ptr != NULL) {
+                       *ptr++ = '\0';
+                       if (nicklist_find(channel, line) == NULL) {
+                               comp = completion_channel_nicks(channel,
+                                                               line, NULL);
+                       }
+               }
+       }
+
+       str = g_strdup_printf(ptr == NULL ? "%s %s" : "%s %s%c%s", item->name,
+                             comp != NULL ? (char *) comp->data : line,
+                             comp_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 void sig_server_disconnected(SERVER_REC *server)
+{
+       MODULE_SERVER_REC *mserver;
+
+       g_return_if_fail(server != NULL);
+
+        mserver = MODULE_DATA(server);
+       while (mserver->lastmsgs)
+                last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data);
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+       MODULE_CHANNEL_REC *mchannel;
+
+       g_return_if_fail(channel != NULL);
+
+        mchannel = MODULE_DATA(channel);
+       while (mchannel->lastmsgs != NULL) {
+               last_msg_destroy(&mchannel->lastmsgs,
+                                mchannel->lastmsgs->data);
+       }
+}
+
+static void read_settings(void)
+{
+       keep_privates_count = settings_get_int("completion_keep_privates");
+       keep_publics_count = settings_get_int("completion_keep_publics");
+       completion_lowercase = settings_get_bool("completion_nicks_lowercase");
+       completion_char = settings_get_str("completion_char");
+       cmdchars = settings_get_str("cmdchars");
+       completion_auto = settings_get_bool("completion_auto");
+       completion_strict = settings_get_bool("completion_strict");
+}
+
+void chat_completion_init(void)
+{
+       settings_add_str("completion", "completion_char", ":");
+       settings_add_bool("completion", "completion_auto", FALSE);
+       settings_add_int("completion", "completion_keep_publics", 50);
+       settings_add_int("completion", "completion_keep_privates", 10);
+       settings_add_bool("completion", "expand_escapes", FALSE);
+       settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
+       settings_add_bool("completion", "completion_strict", FALSE);
+
+       read_settings();
+       signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
+       signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+       signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+       signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+       signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+       signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+       signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+       signal_add("send text", (SIGNAL_FUNC) event_text);
+       signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+       signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void chat_completion_deinit(void)
+{
+       while (global_lastmsgs != NULL)
+               last_msg_destroy(&global_lastmsgs, global_lastmsgs->data);
+
+       signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+       signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+       signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+       signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+       signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+       signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+       signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+       signal_remove("send text", (SIGNAL_FUNC) event_text);
+       signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+       signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/chat-completion.h b/apps/irssi/src/fe-common/core/chat-completion.h
new file mode 100644 (file)
index 0000000..3cb70ca
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __CHAT_COMPLETION_H
+#define __CHAT_COMPLETION_H
+
+void completion_last_message_add(const char *nick);
+void completion_last_message_remove(const char *nick);
+void completion_last_message_rename(const char *oldnick, const char *newnick);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/command-history.c b/apps/irssi/src/fe-common/core/command-history.c
new file mode 100644 (file)
index 0000000..cdf4b5a
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ 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 "fe-windows.h"
+#include "window-items.h"
+
+/* command history */
+static GList *history, *history_pos;
+static int history_lines, history_over_counter;
+static int window_history;
+
+void command_history_add(WINDOW_REC *window, const char *text)
+{
+       GList **phistory, *link;
+       int *phistory_lines;
+
+       g_return_if_fail(text != NULL);
+
+       if (window_history) {
+               /* window specific command history */
+               phistory = &window->history;
+               phistory_lines = &window->history_lines;
+       } else {
+               /* global command history */
+               phistory = &history;
+               phistory_lines = &history_lines;
+       }
+
+       if (settings_get_int("max_command_history") < 1 || *phistory_lines < settings_get_int("max_command_history"))
+               (*phistory_lines)++;
+       else {
+               link = *phistory;
+               g_free(link->data);
+               *phistory = g_list_remove_link(*phistory, link);
+               g_list_free_1(link);
+       }
+
+       *phistory = g_list_append(*phistory, g_strdup(text));
+}
+
+const char *command_history_prev(WINDOW_REC *window, const char *text)
+{
+       GList *pos, **phistory_pos;
+        int *phistory_over_counter;
+
+       if (window_history) {
+               phistory_pos = &window->history_pos;
+               phistory_over_counter = &window->history_over_counter;
+       } else {
+               phistory_pos = &history_pos;
+               phistory_over_counter = &history_over_counter;
+       }
+
+       pos = *phistory_pos;
+       if (*phistory_pos != NULL) {
+               *phistory_pos = (*phistory_pos)->prev;
+               if (*phistory_pos == NULL)
+                        (*phistory_over_counter)++;
+       } else {
+               *phistory_pos = g_list_last(window_history ?
+                                           window->history : history);
+       }
+
+       if (*text != '\0' &&
+           (pos == NULL || strcmp(pos->data, text) != 0)) {
+               /* save the old entry to history */
+               command_history_add(window, text);
+       }
+
+       return *phistory_pos == NULL ? "" : (*phistory_pos)->data;
+}
+
+const char *command_history_next(WINDOW_REC *window, const char *text)
+{
+       GList *pos, **phistory_pos;
+        int *phistory_over_counter;
+
+       if (window_history) {
+               phistory_pos = &window->history_pos;
+               phistory_over_counter = &window->history_over_counter;
+       } else {
+               phistory_pos = &history_pos;
+               phistory_over_counter = &history_over_counter;
+       }
+
+       pos = *phistory_pos;
+
+       if (pos != NULL)
+               *phistory_pos = (*phistory_pos)->next;
+       else if (*phistory_over_counter > 0) {
+               (*phistory_over_counter)--;
+               *phistory_pos = window_history ? window->history : history;
+       }
+
+       if (*text != '\0' &&
+           (pos == NULL || strcmp(pos->data, text) != 0)) {
+               /* save the old entry to history */
+               command_history_add(window, text);
+       }
+       return *phistory_pos == NULL ? "" : (*phistory_pos)->data;
+}
+
+void command_history_clear_pos(WINDOW_REC *window)
+{
+       window->history_over_counter = 0;
+       window->history_pos = NULL;
+        history_over_counter = 0;
+       history_pos = NULL;
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+       g_list_foreach(window->history, (GFunc) g_free, NULL);
+       g_list_free(window->history);
+}
+
+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->history : history;
+       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("window_history");
+}
+
+void command_history_init(void)
+{
+       settings_add_int("history", "max_command_history", 100);
+       settings_add_bool("history", "window_history", FALSE);
+
+       special_history_func_set(special_history_func);
+
+       history_lines = 0; history_over_counter = 0;
+       history = NULL; history_pos = NULL;
+
+       read_settings();
+       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 destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+       g_list_foreach(history, (GFunc) g_free, NULL);
+       g_list_free(history);
+}
diff --git a/apps/irssi/src/fe-common/core/command-history.h b/apps/irssi/src/fe-common/core/command-history.h
new file mode 100644 (file)
index 0000000..9f37f06
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __COMMAND_HISTORY_H
+#define __COMMAND_HISTORY_H
+
+#include "fe-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/apps/irssi/src/fe-common/core/completion.c b/apps/irssi/src/fe-common/core/completion.c
new file mode 100644 (file)
index 0000000..1642761
--- /dev/null
@@ -0,0 +1,681 @@
+/*
+ completion.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 "commands.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "completion.h"
+
+#define wordreplace_find(word) \
+       iconfig_list_find("replaces", "text", word, "replace")
+
+#define completion_find(completion) \
+       iconfig_list_find("completions", "short", completion, "long")
+
+static GList *complist; /* list of commands we're currently completing */
+static char *last_line;
+static int last_want_space, last_line_pos;
+
+#define isseparator_notspace(c) \
+        ((c) == ',')
+
+#define isseparator(c) \
+       (isspace((int) (c)) || isseparator_notspace(c))
+
+void chat_completion_init(void);
+void chat_completion_deinit(void);
+
+/* Return whole word at specified position in string */
+char *get_word_at(const char *str, int pos, char **startpos)
+{
+       const char *start, *end;
+
+       g_return_val_if_fail(str != NULL, NULL);
+       g_return_val_if_fail(pos >= 0, NULL);
+
+       /* get previous word if char at `pos' is space */
+       start = str+pos;
+       while (start > str && isseparator(start[-1])) start--;
+
+       end = start;
+       while (start > str && !isseparator(start[-1])) start--;
+       while (*end != '\0' && !isseparator(*end)) end++;
+       while (*end != '\0' && isseparator_notspace(*end)) end++;
+
+       *startpos = (char *) start;
+       return g_strndup(start, (int) (end-start));
+}
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos)
+{
+       GString *result;
+       const char *replace;
+       char *word, *wordstart, *ret;
+       int startpos;
+
+       g_return_val_if_fail(line != NULL, NULL);
+       g_return_val_if_fail(pos != NULL, NULL);
+
+       word = get_word_at(line, *pos, &wordstart);
+       startpos = (int) (wordstart-line);
+
+       result = g_string_new(line);
+       g_string_erase(result, startpos, strlen(word));
+
+       /* check for words in autocompletion list */
+       replace = wordreplace_find(word);
+       if (replace == NULL) {
+               ret = NULL;
+               g_string_free(result, TRUE);
+       } else {
+               *pos = startpos+strlen(replace);
+
+               g_string_insert(result, startpos, replace);
+               ret = result->str;
+               g_string_free(result, FALSE);
+       }
+
+       g_free(word);
+       return ret;
+}
+
+static void free_completions(void)
+{
+       complist = g_list_first(complist);
+
+       g_list_foreach(complist, (GFunc) g_free, NULL);
+       g_list_free(complist);
+        complist = NULL;
+
+       g_free_and_null(last_line);
+}
+
+/* manual word completion - called when TAB is pressed */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos)
+{
+       static int startpos = 0, wordlen = 0;
+
+       GString *result;
+       char *word, *wordstart, *linestart, *ret;
+       int want_space;
+
+       g_return_val_if_fail(line != NULL, NULL);
+       g_return_val_if_fail(pos != NULL, NULL);
+
+       if (complist != NULL && *pos == last_line_pos &&
+           strcmp(line, last_line) == 0) {
+               /* complete from old list */
+               complist = complist->next != NULL ? complist->next :
+                       g_list_first(complist);
+               want_space = last_want_space;
+       } else {
+               /* get new completion list */
+               free_completions();
+
+               /* get the word we want to complete */
+               word = get_word_at(line, *pos, &wordstart);
+               startpos = (int) (wordstart-line);
+               wordlen = strlen(word);
+
+               /* get the start of line until the word we're completing */
+               if (isseparator(*line)) {
+                       /* empty space at the start of line */
+                       if (wordstart == line)
+                               wordstart += strlen(wordstart);
+               } else {
+                       while (wordstart > line && isseparator(wordstart[-1]))
+                               wordstart--;
+               }
+               linestart = g_strndup(line, (int) (wordstart-line));
+
+               /* completions usually add space after the word, that makes
+                  things a bit harder. When continuing a completion
+                  "/msg nick1 "<tab> we have to cycle to nick2, etc.
+                  BUT if we start completion with "/msg "<tab>, we don't
+                  want to complete the /msg word, but instead complete empty
+                  word with /msg being in linestart. */
+               if (*pos > 0 && line[*pos-1] == ' ') {
+                       char *old;
+
+                        old = linestart;
+                       linestart = *linestart == '\0' ?
+                               g_strdup(word) :
+                               g_strconcat(linestart, " ", word, NULL);
+                       g_free(old);
+
+                       g_free(word);
+                       word = g_strdup("");
+                       startpos = strlen(linestart)+1;
+                       wordlen = 0;
+               }
+
+               want_space = TRUE;
+               signal_emit("complete word", 5, &complist, window, word, linestart, &want_space);
+               last_want_space = want_space;
+
+               g_free(linestart);
+               g_free(word);
+       }
+
+       if (complist == NULL)
+               return NULL;
+
+       /* word completed */
+       *pos = startpos+strlen(complist->data);
+
+       /* replace the word in line - we need to return
+          a full new line */
+       result = g_string_new(line);
+       g_string_erase(result, startpos, wordlen);
+       g_string_insert(result, startpos, complist->data);
+
+       if (want_space) {
+               if (!isseparator(result->str[*pos]))
+                       g_string_insert_c(result, *pos, ' ');
+               (*pos)++;
+       }
+
+       wordlen = strlen(complist->data);
+       last_line_pos = *pos;
+       g_free_not_null(last_line);
+       last_line = g_strdup(result->str);
+
+       ret = result->str;
+       g_string_free(result, FALSE);
+       return ret;
+}
+
+GList *list_add_file(GList *list, const char *name)
+{
+       struct stat statbuf;
+       char *fname;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       fname = convert_home(name);
+       if (stat(fname, &statbuf) == 0) {
+               list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) :
+                                    g_strconcat(name, G_DIR_SEPARATOR_S, NULL));
+       }
+
+        g_free(fname);
+       return list;
+}
+
+GList *filename_complete(const char *path)
+{
+        GList *list;
+       DIR *dirp;
+       struct dirent *dp;
+       char *realpath, *dir, *basename, *name;
+       int len;
+
+       g_return_val_if_fail(path != NULL, NULL);
+
+       list = NULL;
+
+       /* get directory part of the path - expand ~/ */
+       realpath = convert_home(path);
+       dir = g_dirname(realpath);
+       g_free(realpath);
+
+       /* open directory for reading */
+       dirp = opendir(dir);
+       g_free(dir);
+       if (dirp == NULL) return NULL;
+
+       dir = g_dirname(path);
+       if (*dir == G_DIR_SEPARATOR && dir[1] == '\0')
+               *dir = '\0'; /* completing file in root directory */
+       basename = g_basename(path);
+       len = strlen(basename);
+
+       /* add all files in directory to completion list */
+       while ((dp = readdir(dirp)) != NULL) {
+               if (dp->d_name[0] == '.') {
+                       if (dp->d_name[1] == '\0' ||
+                           (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
+                               continue; /* skip . and .. */
+
+                       if (basename[0] != '.')
+                               continue;
+               }
+
+               if (len == 0 || strncmp(dp->d_name, basename, len) == 0) {
+                       name = g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name);
+                       list = list_add_file(list, name);
+                       g_free(name);
+               }
+       }
+       closedir(dirp);
+
+       g_free(dir);
+        return list;
+}
+
+static GList *completion_get_settings(const char *key)
+{
+       GList *complist;
+       GSList *tmp, *sets;
+       int len;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       sets = settings_get_sorted();
+
+       len = strlen(key);
+       complist = NULL;
+       for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+               SETTINGS_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->key, key, len) == 0)
+                       complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
+       }
+       g_slist_free(sets);
+       return complist;
+}
+
+static GList *completion_get_bool_settings(const char *key)
+{
+       GList *complist;
+       GSList *tmp, *sets;
+       int len;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       sets = settings_get_sorted();
+
+       len = strlen(key);
+       complist = NULL;
+       for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+               SETTINGS_REC *rec = tmp->data;
+
+               if (rec->type == SETTING_TYPE_BOOLEAN &&
+                   g_strncasecmp(rec->key, key, len) == 0)
+                       complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
+       }
+       g_slist_free(sets);
+       return complist;
+}
+
+static GList *completion_get_aliases(const char *alias, char cmdchar)
+{
+       CONFIG_NODE *node;
+       GList *complist;
+       GSList *tmp;
+       char *word;
+       int len;
+
+       g_return_val_if_fail(alias != NULL, NULL);
+
+       /* get list of aliases from mainconfig */
+       node = iconfig_node_traverse("aliases", FALSE);
+       tmp = node == NULL ? NULL : node->value;
+
+       len = strlen(alias);
+       complist = NULL;
+       for (; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *node = tmp->data;
+
+               if (node->type != NODE_TYPE_KEY)
+                       continue;
+
+               if (g_strncasecmp(node->key, alias, len) == 0) {
+                       word = g_strdup_printf("%c%s", cmdchar, node->key);
+                       /* add matching alias to completion list, aliases will
+                          be appended after command completions and kept in
+                          uppercase to show it's an alias */
+                       if (glist_find_icase_string(complist, word) == NULL)
+                               complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
+                       else
+                               g_free(word);
+               }
+       }
+       return complist;
+}
+
+static GList *completion_get_commands(const char *cmd, char cmdchar)
+{
+       GList *complist;
+       GSList *tmp;
+       char *word;
+       int len;
+
+       g_return_val_if_fail(cmd != NULL, NULL);
+
+       len = strlen(cmd);
+       complist = NULL;
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if (strchr(rec->cmd, ' ') != NULL)
+                       continue;
+
+               if (g_strncasecmp(rec->cmd, cmd, len) == 0) {
+                       word = cmdchar == '\0' ? g_strdup(rec->cmd) :
+                               g_strdup_printf("%c%s", cmdchar, rec->cmd);
+                       if (glist_find_icase_string(complist, word) == NULL)
+                               complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
+                       else
+                               g_free(word);
+               }
+       }
+       return complist;
+}
+
+static GList *completion_get_subcommands(const char *cmd)
+{
+       GList *complist;
+       GSList *tmp;
+       char *spacepos;
+       int len, skip;
+
+       g_return_val_if_fail(cmd != NULL, NULL);
+
+       /* get the number of chars to skip at the start of command. */
+       spacepos = strrchr(cmd, ' ');
+       skip = spacepos == NULL ? strlen(cmd)+1 :
+               ((int) (spacepos-cmd) + 1);
+
+       len = strlen(cmd);
+       complist = NULL;
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if ((int)strlen(rec->cmd) < len)
+                       continue;
+
+               if (strchr(rec->cmd+len, ' ') != NULL)
+                       continue;
+
+               if (g_strncasecmp(rec->cmd, cmd, len) == 0)
+                       complist = g_list_insert_sorted(complist, g_strdup(rec->cmd+skip), (GCompareFunc) g_istr_cmp);
+       }
+       return complist;
+}
+
+GList *completion_get_options(const char *cmd, const char *option)
+{
+       COMMAND_REC *rec;
+       GList *list;
+       char **tmp;
+       int len;
+
+       g_return_val_if_fail(cmd != NULL, NULL);
+       g_return_val_if_fail(option != NULL, NULL);
+
+       rec = command_find(cmd);
+       if (rec == NULL || rec->options == NULL) return NULL;
+
+       list = NULL;
+       len = strlen(option);
+       for (tmp = rec->options; *tmp != NULL; tmp++) {
+               const char *optname = *tmp + iscmdtype(**tmp);
+
+               if (len == 0 || g_strncasecmp(optname, option, len) == 0)
+                        list = g_list_append(list, g_strconcat("-", optname, NULL));
+       }
+
+       return list;
+}
+
+/* split the line to command and arguments */
+static char *line_get_command(const char *line, char **args, int aliases)
+{
+       const char *ptr, *cmdargs;
+       char *cmd, *checkcmd;
+
+       g_return_val_if_fail(line != NULL, NULL);
+       g_return_val_if_fail(args != NULL, NULL);
+
+       cmd = checkcmd = NULL; *args = "";
+       cmdargs = NULL; ptr = line;
+
+       do {
+               ptr = strchr(ptr, ' ');
+               if (ptr == NULL) {
+                       checkcmd = g_strdup(line);
+                       cmdargs = "";
+               } else {
+                       checkcmd = g_strndup(line, (int) (ptr-line));
+
+                       while (isspace(*ptr)) ptr++;
+                       cmdargs = ptr;
+               }
+
+               if (aliases ? !alias_find(checkcmd) :
+                   !command_find(checkcmd)) {
+                       /* not found, use the previous */
+                       g_free(checkcmd);
+                       break;
+               }
+
+               /* found, check if it has subcommands */
+               g_free_not_null(cmd);
+               if (!aliases)
+                       cmd = checkcmd;
+               else {
+                        cmd = g_strdup(alias_find(checkcmd));
+                       g_free(checkcmd);
+               }
+               *args = (char *) cmdargs;
+       } while (ptr != NULL);
+
+       return cmd;
+}
+
+static char *expand_aliases(const char *line)
+{
+        char *cmd, *args, *ret;
+
+       g_return_val_if_fail(line != NULL, NULL);
+
+       cmd = line_get_command(line, &args, TRUE);
+       if (cmd == NULL) return g_strdup(line);
+       if (*args == '\0') return cmd;
+
+       ret = g_strconcat(cmd, " ", args, NULL);
+       g_free(cmd);
+       return ret;
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+                             const char *word, const char *linestart, int *want_space)
+{
+       const char *newword, *cmdchars;
+       char *signal, *cmd, *args, *line;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(linestart != NULL);
+
+       /* check against "completion words" list */
+       newword = completion_find(word);
+       if (newword != NULL) {
+               *list = g_list_append(*list, g_strdup(newword));
+
+               signal_stop();
+               return;
+       }
+
+       /* command completion? */
+       cmdchars = settings_get_str("cmdchars");
+       if (strchr(cmdchars, *word) && *linestart == '\0') {
+               /* complete /command */
+               *list = completion_get_commands(word+1, *word);
+
+               /* complete aliases, too */
+               *list = g_list_concat(*list,
+                                     completion_get_aliases(word+1, *word));
+
+               if (*list != NULL) signal_stop();
+               return;
+       }
+
+       /* check only for /command completions from now on */
+        cmdchars = strchr(cmdchars, *linestart);
+       if (cmdchars == NULL) return;
+
+        /* check if there's aliases */
+       line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
+               expand_aliases(linestart+1);
+
+       cmd = line_get_command(line, &args, FALSE);
+       if (cmd == NULL) {
+               g_free(line);
+               return;
+       }
+
+       /* we're completing -option? */
+       if (*word == '-') {
+               *list = completion_get_options(cmd, word+1);
+               g_free(cmd);
+               g_free(line);
+               return;
+       }
+
+       /* complete parameters */
+       signal = g_strconcat("complete command ", cmd, NULL);
+       signal_emit(signal, 5, list, window, word, args, want_space);
+
+       if (command_have_sub(line)) {
+               /* complete subcommand */
+               g_free(cmd);
+               cmd = g_strconcat(line, " ", word, NULL);
+               *list = g_list_concat(completion_get_subcommands(cmd), *list);
+
+               if (*list != NULL) signal_stop();
+       }
+
+       g_free(signal);
+       g_free(cmd);
+
+       g_free(line);
+}
+
+static void sig_complete_set(GList **list, WINDOW_REC *window,
+                            const char *word, const char *line, int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line != '\0') return;
+
+       *list = completion_get_settings(word);
+       if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_toggle(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line, int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line != '\0') return;
+
+       *list = completion_get_bool_settings(word);
+       if (*list != NULL) signal_stop();
+}
+
+/* first argument of command is file name - complete it */
+static void sig_complete_filename(GList **list, WINDOW_REC *window,
+                                 const char *word, const char *line, int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line != '\0') return;
+
+       *list = filename_complete(word);
+       if (*list != NULL) {
+               *want_space = FALSE;
+               signal_stop();
+       }
+}
+
+/* first argument of command is .. command :) (/HELP command) */
+static void sig_complete_command(GList **list, WINDOW_REC *window,
+                                 const char *word, const char *line, int *want_space)
+{
+       char *cmd;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line == '\0') {
+               /* complete base command */
+               *list = completion_get_commands(word, '\0');
+       } else if (command_have_sub(line)) {
+               /* complete subcommand */
+                cmd = g_strconcat(line, " ", word, NULL);
+               *list = completion_get_subcommands(cmd);
+               g_free(cmd);
+       }
+
+       if (*list != NULL) signal_stop();
+}
+
+void completion_init(void)
+{
+       complist = NULL;
+       last_line = NULL; last_line_pos = -1;
+
+       chat_completion_init();
+
+       signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word);
+       signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set);
+       signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+       signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command run", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+       signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command);
+}
+
+void completion_deinit(void)
+{
+        free_completions();
+
+       chat_completion_deinit();
+
+       signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+       signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set);
+       signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+       signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command run", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+       signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command);
+}
+
+
diff --git a/apps/irssi/src/fe-common/core/completion.h b/apps/irssi/src/fe-common/core/completion.h
new file mode 100644 (file)
index 0000000..ef0fe06
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __COMPLETION_H
+#define __COMPLETION_H
+
+#include "window-items.h"
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos);
+/* manual word completion - called when TAB is pressed */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos);
+
+GList *filename_complete(const char *path);
+
+void completion_init(void);
+void completion_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/fe-channels.c b/apps/irssi/src/fe-common/core/fe-channels.c
new file mode 100644 (file)
index 0000000..67557b9
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ 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 "misc.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "nicklist.h"
+
+#include "fe-windows.h"
+#include "fe-channels.h"
+#include "window-items.h"
+#include "printtext.h"
+
+static void signal_channel_created(CHANNEL_REC *channel, void *automatic)
+{
+       if (window_item_window(channel) == NULL) {
+               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_item_add(active_win, (WI_ITEM_REC *) channel, FALSE);
+}
+
+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)
+               return;
+
+       window_item_destroy((WI_ITEM_REC *) channel);
+
+       if (channel->joined && !channel->left &&
+           channel->server != NULL) {
+               /* kicked out from channel */
+               window_bind_add(window, channel->server->tag,
+                               channel->name);
+       } else if (!channel->joined || channel->left)
+               window_auto_destroy(window);
+}
+
+static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+       CHANNEL_REC *channel;
+
+       g_return_if_fail(window != NULL);
+
+       channel = CHANNEL(item);
+        if (channel != NULL) channel_destroy(channel);
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+       WINDOW_REC *window;
+       GSList *tmp;
+
+       g_return_if_fail(IS_SERVER(server));
+
+       for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *channel = tmp->data;
+
+               window = window_item_window((WI_ITEM_REC *) channel);
+               window_bind_add(window, server->tag, channel->name);
+       }
+}
+
+static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+       g_return_if_fail(window != NULL);
+       if (item == NULL) return;
+
+       if (g_slist_length(window->items) > 1 && IS_CHANNEL(item)) {
+               printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE,
+                           TXT_TALKING_IN, item->name);
+                signal_stop();
+       }
+}
+
+static void cmd_wjoin_pre(const char *data)
+{
+       GHashTable *optlist;
+       char *nick;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+                           "join", &optlist, &nick))
+                return;
+
+       if (g_hash_table_lookup(optlist, "window") != NULL) {
+               signal_add("channel created",
+                          (SIGNAL_FUNC) signal_channel_created_curwin);
+        }
+       cmd_params_free(free_arg);
+}
+
+static void cmd_join(const char *data, SERVER_REC *server)
+{
+       WINDOW_REC *window;
+        CHANNEL_REC *channel;
+
+        if (strchr(data, ' ') != NULL || strchr(data, ',') != NULL)
+                return;
+
+        channel = channel_find(server, data);
+       if (channel == NULL)
+               return;
+
+        window = window_item_window(channel);
+
+       if (window == active_win) {
+               /* channel is in active window, set it active */
+               window_item_set_active(active_win,
+                                      (WI_ITEM_REC *) channel);
+       } else {
+               /* notify user how to move the channel to active
+                  window. this was used to be done automatically
+                  but it just confused everyone who did it
+                  accidentally */
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_CHANNEL_MOVE_NOTIFY, channel->name,
+                                  window->refnum);
+       }
+}
+
+static void cmd_wjoin_post(const char *data)
+{
+       GHashTable *optlist;
+       char *nick;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+                           "join", &optlist, &nick))
+               return;
+
+       if (g_hash_table_lookup(optlist, "window") != NULL) {
+               signal_remove("channel created",
+                          (SIGNAL_FUNC) signal_channel_created_curwin);
+       }
+       cmd_params_free(free_arg);
+}
+
+static void cmd_channel_list_joined(void)
+{
+       CHANNEL_REC *channel;
+       GString *nicks;
+       GSList *nicklist, *tmp, *ntmp;
+
+       if (channels == NULL) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_IN_CHANNELS);
+               return;
+       }
+
+       /* print active channel */
+       channel = CHANNEL(active_win->active);
+       if (channel != NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CURRENT_CHANNEL, channel->name);
+
+       /* print list of all channels, their modes, server tags and nicks */
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_HEADER);
+       for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+               channel = tmp->data;
+
+               nicklist = nicklist_getnicks(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);
+               }
+
+               if (nicks->len > 1) g_string_truncate(nicks, nicks->len-1);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_LINE,
+                           channel->name, channel->mode, channel->server->tag, nicks->str);
+
+               g_slist_free(nicklist);
+               g_string_free(nicks, TRUE);
+       }
+}
+
+/* SYNTAX: CHANNEL LIST */
+static void cmd_channel_list(void)
+{
+       GString *str;
+       GSList *tmp;
+
+       str = g_string_new(NULL);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_HEADER);
+       for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_SETUP_REC *rec = tmp->data;
+
+               g_string_truncate(str, 0);
+               if (rec->autojoin)
+                       g_string_append(str, "autojoin, ");
+               if (rec->botmasks != NULL && *rec->botmasks != '\0')
+                       g_string_sprintfa(str, "bots: %s, ", rec->botmasks);
+               if (rec->autosendcmd != NULL && *rec->autosendcmd != '\0')
+                       g_string_sprintfa(str, "botcmd: %s, ", rec->autosendcmd);
+
+               if (str->len > 2) g_string_truncate(str, str->len-2);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_LINE,
+                           rec->name, rec->chatnet == NULL ? "" : rec->chatnet,
+                           rec->password == NULL ? "" : rec->password, str->str);
+       }
+       g_string_free(str, TRUE);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_FOOTER);
+}
+
+static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       if (*data == '\0')
+               cmd_channel_list_joined();
+       else
+               command_runsub("channel", data, server, item);
+}
+
+/* SYNTAX: CHANNEL ADD [-auto | -noauto] [-bots <masks>] [-botcmd <command>]
+                       <channel> <chatnet> [<password>] */
+static void cmd_channel_add(const char *data)
+{
+       GHashTable *optlist;
+        CHATNET_REC *chatnetrec;
+       CHANNEL_SETUP_REC *rec;
+       char *botarg, *botcmdarg, *chatnet, *channel, *password;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+                           "channel add", &optlist, &channel, &chatnet, &password))
+               return;
+
+       if (*chatnet == '\0' || *channel == '\0')
+               cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       chatnetrec = chatnet_find(chatnet);
+       if (chatnetrec == NULL) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           TXT_UNKNOWN_CHATNET, chatnet);
+               cmd_params_free(free_arg);
+                return;
+       }
+
+       botarg = g_hash_table_lookup(optlist, "bots");
+       botcmdarg = g_hash_table_lookup(optlist, "botcmd");
+
+       rec = channel_setup_find(channel, chatnet);
+       if (rec == NULL) {
+               rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+               rec->name = g_strdup(channel);
+               rec->chatnet = g_strdup(chatnet);
+       } else {
+               if (g_hash_table_lookup(optlist, "bots")) g_free_and_null(rec->botmasks);
+               if (g_hash_table_lookup(optlist, "botcmd")) g_free_and_null(rec->autosendcmd);
+               if (*password != '\0') g_free_and_null(rec->password);
+       }
+       if (g_hash_table_lookup(optlist, "auto")) rec->autojoin = TRUE;
+       if (g_hash_table_lookup(optlist, "noauto")) rec->autojoin = FALSE;
+       if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg);
+       if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg);
+       if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
+
+       signal_emit("channel add fill", 2, rec, optlist);
+
+       channel_setup_create(rec);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_CHANSETUP_ADDED, channel, chatnet);
+
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CHANNEL REMOVE <channel> <chatnet> */
+static void cmd_channel_remove(const char *data)
+{
+       CHANNEL_SETUP_REC *rec;
+       char *chatnet, *channel;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 2, &channel, &chatnet))
+               return;
+       if (*chatnet == '\0' || *channel == '\0')
+               cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       rec = channel_setup_find(channel, chatnet);
+       if (rec == NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
+       else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet);
+               channel_setup_remove(rec);
+       }
+       cmd_params_free(free_arg);
+}
+
+static int get_nick_length(void *data)
+{
+        return strlen(((NICK_REC *) data)->nick);
+}
+
+static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
+{
+        WINDOW_REC *window;
+       TEXT_DEST_REC dest;
+       GString *str;
+       GSList *tmp;
+        char *format, *stripped;
+       char *linebuf, nickmode[2] = { 0, 0 };
+       int *columns, cols, rows, last_col_rows, col, row, max_width;
+        int item_extra, linebuf_size;
+
+       window = window_find_closest(channel->server, channel->name,
+                                    MSGLEVEL_CLIENTCRAP);
+        max_width = window->width;
+
+        /* get the length of item extra stuff ("[ ] ") */
+       format = format_get_text(MODULE_NAME, NULL,
+                                channel->server, channel->name,
+                                TXT_NAMES_NICK, " ", "");
+       stripped = strip_codes(format);
+       item_extra = strlen(stripped);
+        g_free(stripped);
+       g_free(format);
+
+       if (settings_get_int("names_max_width") > 0 &&
+           max_width > settings_get_int("names_max_width"))
+               max_width = settings_get_int("names_max_width");
+
+        /* remove width of timestamp from max_width */
+       format_create_dest(&dest, channel->server, channel->name,
+                          MSGLEVEL_CLIENTCRAP, NULL);
+       format = format_get_line_start(current_theme, &dest, time(NULL));
+       if (format != NULL) {
+               stripped = strip_codes(format);
+               max_width -= strlen(stripped);
+               g_free(stripped);
+               g_free(format);
+       }
+
+       /* calculate columns */
+       cols = get_max_column_count(nicklist, get_nick_length, max_width,
+                                   settings_get_int("names_max_columns"),
+                                   item_extra, 3, &columns, &rows);
+       nicklist = columns_sort_list(nicklist, rows);
+
+        /* rows in last column */
+       last_col_rows = rows-(cols*rows-g_slist_length(nicklist));
+       if (last_col_rows == 0)
+                last_col_rows = rows;
+
+       str = g_string_new(NULL);
+       linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size);
+
+        col = 0; row = 0;
+       for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+               NICK_REC *rec = tmp->data;
+
+               nickmode[0] = rec->op ? '@' : rec->voice ? '+' : ' ';
+
+               if (linebuf_size < columns[col]-item_extra+1) {
+                       linebuf_size = (columns[col]-item_extra+1)*2;
+                        linebuf = g_realloc(linebuf, linebuf_size);
+               }
+               memset(linebuf, ' ', columns[col]-item_extra);
+               linebuf[columns[col]-item_extra] = '\0';
+               memcpy(linebuf, rec->nick, strlen(rec->nick));
+
+               format = format_get_text(MODULE_NAME, NULL,
+                                        channel->server, channel->name,
+                                        TXT_NAMES_NICK, nickmode, linebuf);
+               g_string_append(str, format);
+               g_free(format);
+
+               if (++col == cols) {
+                       printtext(channel->server, channel->name,
+                                 MSGLEVEL_CLIENTCRAP, "%s", str->str);
+                       g_string_truncate(str, 0);
+                       col = 0; row++;
+
+                       if (row == last_col_rows)
+                                cols--;
+               }
+       }
+
+       if (str->len != 0) {
+               printtext(channel->server, channel->name,
+                         MSGLEVEL_CLIENTCRAP, "%s", str->str);
+       }
+
+       g_slist_free(nicklist);
+       g_string_free(str, TRUE);
+       g_free_not_null(columns);
+       g_free(linebuf);
+}
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags)
+{
+       NICK_REC *nick;
+       GSList *tmp, *nicklist, *sorted;
+       int nicks, normal, voices, ops;
+
+       nicks = normal = voices = ops = 0;
+       nicklist = nicklist_getnicks(channel);
+       sorted = NULL;
+
+       /* sort the nicklist */
+       for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+               nick = tmp->data;
+
+               nicks++;
+               if (nick->op) {
+                       ops++;
+                       if ((flags & CHANNEL_NICKLIST_FLAG_OPS) == 0)
+                                continue;
+               } else if (nick->halfop) {
+                       if ((flags & CHANNEL_NICKLIST_FLAG_HALFOPS) == 0)
+                               continue;
+               } else if (nick->voice) {
+                       voices++;
+                       if ((flags & CHANNEL_NICKLIST_FLAG_VOICES) == 0)
+                               continue;
+               } else {
+                       normal++;
+                       if ((flags & CHANNEL_NICKLIST_FLAG_NORMAL) == 0)
+                               continue;
+               }
+
+               sorted = g_slist_insert_sorted(sorted, nick, (GCompareFunc)
+                                              nicklist_compare);
+       }
+       g_slist_free(nicklist);
+
+       /* display the nicks */
+       printformat(channel->server, channel->name,
+                   MSGLEVEL_CRAP, TXT_NAMES, channel->name, "");
+       display_sorted_nicks(channel, sorted);
+       g_slist_free(sorted);
+
+       printformat(channel->server, channel->name,
+                   MSGLEVEL_CRAP, TXT_ENDOFNAMES,
+                   channel->name, nicks, ops, voices, normal);
+}
+
+/* SYNTAX: NAMES [-ops -halfops -voices -normal] [<channels> | **] */
+static void cmd_names(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       CHANNEL_REC *chanrec;
+       GHashTable *optlist;
+        GString *unknowns;
+       char *channel, **channels, **tmp;
+        int flags;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+       if (!IS_SERVER(server) || !server->connected)
+               cmd_return_error(CMDERR_NOT_CONNECTED);
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+                           "names", &optlist, &channel))
+               return;
+
+       if (strcmp(channel, "*") == 0 || *channel == '\0') {
+               if (!IS_CHANNEL(item))
+                        cmd_param_error(CMDERR_NOT_JOINED);
+
+               channel = item->name;
+       }
+
+       flags = 0;
+       if (g_hash_table_lookup(optlist, "ops") != NULL)
+               flags |= CHANNEL_NICKLIST_FLAG_OPS;
+       if (g_hash_table_lookup(optlist, "halfops") != NULL)
+               flags |= CHANNEL_NICKLIST_FLAG_HALFOPS;
+       if (g_hash_table_lookup(optlist, "voices") != NULL)
+               flags |= CHANNEL_NICKLIST_FLAG_VOICES;
+       if (g_hash_table_lookup(optlist, "normal") != NULL)
+               flags |= CHANNEL_NICKLIST_FLAG_NORMAL;
+
+        if (flags == 0) flags = CHANNEL_NICKLIST_FLAG_ALL;
+
+        unknowns = g_string_new(NULL);
+
+       channels = g_strsplit(channel, ",", -1);
+       for (tmp = channels; *tmp != NULL; tmp++) {
+               chanrec = channel_find(server, *tmp);
+               if (chanrec == NULL)
+                       g_string_sprintfa(unknowns, "%s,", *tmp);
+               else {
+                       fe_channels_nicklist(chanrec, flags);
+                       signal_stop();
+               }
+       }
+       g_strfreev(channels);
+
+       if (unknowns->len > 1)
+                g_string_truncate(unknowns, unknowns->len-1);
+
+       if (unknowns->len > 0 && strcmp(channel, unknowns->str) != 0)
+                signal_emit("command names", 3, unknowns->str, server, item);
+        g_string_free(unknowns, TRUE);
+
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CYCLE [<channel>] [<message>] */
+static void cmd_cycle(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       CHANNEL_REC *chanrec;
+       char *channame, *msg, *joindata;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+       if (!IS_SERVER(server) || !server->connected)
+               cmd_return_error(CMDERR_NOT_CONNECTED);
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN,
+                           item, &channame, &msg))
+               return;
+       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);
+
+       joindata = chanrec->get_join_data(chanrec);
+       window_bind_add(window_item_window(chanrec),
+                       chanrec->server->tag, chanrec->name);
+        channel_destroy(chanrec);
+
+       server->channels_join(server, joindata, FALSE);
+       g_free(joindata);
+
+       cmd_params_free(free_arg);
+}
+
+void fe_channels_init(void)
+{
+       settings_add_bool("lookandfeel", "autoclose_windows", TRUE);
+       settings_add_int("lookandfeel", "names_max_columns", 6);
+       settings_add_int("lookandfeel", "names_max_width", 0);
+
+       signal_add("channel created", (SIGNAL_FUNC) signal_channel_created);
+       signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
+       signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+       signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+       signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+
+       command_bind_first("join", NULL, (SIGNAL_FUNC) cmd_wjoin_pre);
+       command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+       command_bind_last("join", NULL, (SIGNAL_FUNC) cmd_wjoin_post);
+       command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel);
+       command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add);
+       command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove);
+       command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list);
+       command_bind("names", NULL, (SIGNAL_FUNC) cmd_names);
+       command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle);
+
+       command_set_options("channel add", "auto noauto -bots -botcmd");
+       command_set_options("names", "ops halfops voices normal");
+       command_set_options("join", "window");
+}
+
+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 destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+       signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+       signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+
+       command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_pre);
+       command_unbind("join", (SIGNAL_FUNC) cmd_join);
+       command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_post);
+       command_unbind("channel", (SIGNAL_FUNC) cmd_channel);
+       command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add);
+       command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove);
+       command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list);
+       command_unbind("names", (SIGNAL_FUNC) cmd_names);
+       command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-channels.h b/apps/irssi/src/fe-common/core/fe-channels.h
new file mode 100644 (file)
index 0000000..a8aa697
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __FE_CHANNELS_H
+#define __FE_CHANNELS_H
+
+#define CHANNEL_NICKLIST_FLAG_OPS       0x01
+#define CHANNEL_NICKLIST_FLAG_HALFOPS   0x02
+#define CHANNEL_NICKLIST_FLAG_VOICES    0x04
+#define CHANNEL_NICKLIST_FLAG_NORMAL    0x08
+#define CHANNEL_NICKLIST_FLAG_ALL       0x0f
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags);
+
+void fe_channels_init(void);
+void fe_channels_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/fe-common-core.c b/apps/irssi/src/fe-common/core/fe-common-core.c
new file mode 100644 (file)
index 0000000..8cfb3a8
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ 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 "module-formats.h"
+#include "args.h"
+#include "misc.h"
+#include "levels.h"
+#include "settings.h"
+
+#include "channels.h"
+#include "servers-setup.h"
+
+#include "fe-queries.h"
+#include "hilight-text.h"
+#include "command-history.h"
+#include "completion.h"
+#include "keyboard.h"
+#include "printtext.h"
+#include "formats.h"
+#include "themes.h"
+#include "translation.h"
+#include "fe-channels.h"
+#include "fe-windows.h"
+#include "window-items.h"
+#include "windows-layout.h"
+
+#include <signal.h>
+
+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 autorun_init(void);
+void autorun_deinit(void);
+
+void fe_core_log_init(void);
+void fe_core_log_deinit(void);
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+void fe_expandos_init(void);
+void fe_expandos_deinit(void);
+
+void fe_help_init(void);
+void fe_help_deinit(void);
+
+void fe_ignore_init(void);
+void fe_ignore_deinit(void);
+
+void fe_ignore_messages_init(void);
+void fe_ignore_messages_deinit(void);
+
+void fe_log_init(void);
+void fe_log_deinit(void);
+
+void fe_messages_init(void);
+void fe_messages_deinit(void);
+
+void fe_modules_init(void);
+void fe_modules_deinit(void);
+
+void fe_server_init(void);
+void fe_server_deinit(void);
+
+void fe_settings_init(void);
+void fe_settings_deinit(void);
+
+void window_activity_init(void);
+void window_activity_deinit(void);
+
+void window_commands_init(void);
+void window_commands_deinit(void);
+
+void fe_core_commands_init(void);
+void fe_core_commands_deinit(void);
+
+static void sig_connected(SERVER_REC *server)
+{
+       MODULE_DATA_SET(server, g_new0(MODULE_SERVER_REC, 1));
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+       g_free(MODULE_DATA(server));
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+       MODULE_DATA_SET(channel, g_new0(MODULE_CHANNEL_REC, 1));
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+       g_free(MODULE_DATA(channel));
+}
+
+void fe_common_core_init(void)
+{
+       static struct poptOption options[] = {
+               { "connect", 'c', POPT_ARG_STRING, &autocon_server, 0, "Automatically connect to server/ircnet", "SERVER" },
+               { "password", 'w', POPT_ARG_STRING, &autocon_password, 0, "Autoconnect password", "SERVER" },
+               { "port", 'p', POPT_ARG_INT, &autocon_port, 0, "Autoconnect port", "PORT" },
+               { "noconnect", '!', POPT_ARG_NONE, &no_autoconnect, 0, "Disable autoconnecting", NULL },
+               { "nick", 'n', POPT_ARG_STRING, &cmdline_nick, 0, "Specify nick to use", NULL },
+               { "hostname", 'h', POPT_ARG_STRING, &cmdline_hostname, 0, "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_bool("lookandfeel", "timestamps", TRUE);
+       settings_add_bool("lookandfeel", "msgs_timestamps", FALSE);
+       settings_add_int("lookandfeel", "timestamp_timeout", 0);
+
+       settings_add_bool("lookandfeel", "bell_beeps", FALSE);
+       settings_add_str("lookandfeel", "beep_msg_level", "");
+       settings_add_bool("lookandfeel", "beep_when_window_active", TRUE);
+       settings_add_bool("lookandfeel", "beep_when_away", TRUE);
+
+       settings_add_bool("lookandfeel", "hide_text_style", FALSE);
+       settings_add_bool("lookandfeel", "hide_server_tags", FALSE);
+
+       settings_add_bool("lookandfeel", "use_status_window", TRUE);
+       settings_add_bool("lookandfeel", "use_msgs_window", FALSE);
+
+       themes_init();
+        theme_register(fecommon_core_formats);
+
+       autorun_init();
+       command_history_init();
+       completion_init();
+       keyboard_init();
+       printtext_init();
+       formats_init();
+#ifndef WIN32
+        fe_exec_init();
+#endif
+        fe_expandos_init();
+       fe_help_init();
+       fe_ignore_init();
+       fe_log_init();
+       fe_modules_init();
+       fe_server_init();
+       fe_settings_init();
+       translation_init();
+       windows_init();
+       window_activity_init();
+       window_commands_init();
+       window_items_init();
+       windows_layout_init();
+       fe_core_commands_init();
+
+        fe_channels_init();
+        fe_queries_init();
+
+       fe_messages_init();
+       hilight_text_init();
+       fe_ignore_messages_init();
+
+       settings_check();
+
+        signal_add_first("server connected", (SIGNAL_FUNC) sig_connected);
+        signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+        signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+        signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void fe_common_core_deinit(void)
+{
+       autorun_deinit();
+       hilight_text_deinit();
+       command_history_deinit();
+       completion_deinit();
+       keyboard_deinit();
+       printtext_deinit();
+       formats_deinit();
+#ifndef WIN32
+        fe_exec_deinit();
+#endif
+        fe_expandos_deinit();
+       fe_help_deinit();
+       fe_ignore_deinit();
+       fe_log_deinit();
+       fe_modules_deinit();
+       fe_server_deinit();
+       fe_settings_deinit();
+       translation_deinit();
+       windows_deinit();
+       window_activity_deinit();
+       window_commands_deinit();
+       window_items_deinit();
+       windows_layout_deinit();
+       fe_core_commands_deinit();
+
+        fe_channels_deinit();
+        fe_queries_deinit();
+
+       fe_messages_deinit();
+       fe_ignore_messages_init();
+
+        theme_unregister();
+       themes_deinit();
+
+        signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+        signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+        signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+        signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void glog_func(const char *log_domain, GLogLevelFlags log_level,
+              const char *message)
+{
+       const char *reason;
+
+       switch (log_level) {
+       case G_LOG_LEVEL_WARNING:
+                reason = "warning";
+                break;
+       case G_LOG_LEVEL_CRITICAL:
+                reason = "critical";
+               break;
+       default:
+               reason = "error";
+                break;
+       }
+
+       if (windows == NULL)
+               fprintf(stderr, "GLib %s: %s", reason, message);
+       else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_GLIB_ERROR, reason, message);
+       }
+}
+
+static void create_windows(void)
+{
+       WINDOW_REC *window;
+
+       windows_layout_restore();
+       if (windows != NULL)
+               return;
+
+       if (settings_get_bool("use_status_window")) {
+               window = window_create(NULL, TRUE);
+               window_set_name(window, "(status)");
+               window_set_level(window, MSGLEVEL_ALL ^
+                                (settings_get_bool("use_msgs_window") ?
+                                 (MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS) : 0));
+       }
+
+       if (settings_get_bool("use_msgs_window")) {
+               window = window_create(NULL, TRUE);
+               window_set_name(window, "(msgs)");
+               window_set_level(window, MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS);
+       }
+
+       if (windows == NULL) {
+               /* we have to have at least one window.. */
+                window = window_create(NULL, TRUE);
+       }
+}
+
+static void autoconnect_servers(void)
+{
+       GSList *tmp, *chatnets;
+       char *str;
+
+       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 */
+       chatnets = NULL;
+       for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+               SERVER_SETUP_REC *rec = tmp->data;
+
+               if (rec->autoconnect &&
+                   (rec->chatnet == NULL ||
+                    gslist_find_icase_string(chatnets, rec->chatnet) == NULL)) {
+                       if (rec->chatnet != NULL)
+                               chatnets = g_slist_append(chatnets, rec->chatnet);
+
+                       str = g_strdup_printf("%s %d", rec->address, rec->port);
+                       signal_emit("command connect", 1, str);
+                       g_free(str);
+               }
+       }
+
+       g_slist_free(chatnets);
+}
+
+void fe_common_core_finish_init(void)
+{
+       signal_emit("irssi init read settings", 0);
+
+#ifdef SIGPIPE
+       signal(SIGPIPE, SIG_IGN);
+#endif
+
+       if (cmdline_nick != NULL) {
+               /* override nick found from setup */
+               settings_set_str("nick", cmdline_nick);
+       }
+
+       if (cmdline_hostname != NULL) {
+               /* override host name found from setup */
+               settings_set_str("hostname", cmdline_hostname);
+       }
+
+       create_windows();
+
+        /* _after_ windows are created.. */
+       g_log_set_handler(G_LOG_DOMAIN,
+                         (GLogLevelFlags) (G_LOG_LEVEL_CRITICAL |
+                                           G_LOG_LEVEL_WARNING),
+                         (GLogFunc) glog_func, NULL);
+
+       autoconnect_servers();
+}
diff --git a/apps/irssi/src/fe-common/core/fe-common-core.h b/apps/irssi/src/fe-common/core/fe-common-core.h
new file mode 100644 (file)
index 0000000..1c12047
--- /dev/null
@@ -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/apps/irssi/src/fe-common/core/fe-core-commands.c b/apps/irssi/src/fe-common/core/fe-core-commands.c
new file mode 100644 (file)
index 0000000..7b8f7f9
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ fe-core-commands.c : irssi
+
+    Copyright (C) 1999-2001 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 "line-split.h"
+#include "settings.h"
+#include "irssi-version.h"
+
+#include "fe-windows.h"
+#include "printtext.h"
+
+#define PASTE_CHECK_SPEED 200 /* 0.2 sec */
+
+static int ret_texts[] = {
+       TXT_OPTION_UNKNOWN,
+       TXT_OPTION_AMBIGUOUS,
+       TXT_OPTION_MISSING_ARG,
+       TXT_COMMAND_UNKNOWN,
+       TXT_COMMAND_AMBIGUOUS,
+        -1,
+       TXT_NOT_ENOUGH_PARAMS,
+       TXT_NOT_CONNECTED,
+       TXT_NOT_JOINED,
+       TXT_CHAN_NOT_FOUND,
+       TXT_CHAN_NOT_SYNCED,
+       TXT_NOT_GOOD_IDEA
+};
+
+/* keep the whole command line here temporarily. we need it in
+   "default command" event handler, but there we don't know if the start of
+   the line had one or two command chars, and which one.. */
+static const char *current_cmdline;
+static int hide_output;
+
+static GTimeVal time_command_last, time_command_now;
+static int last_command_cmd, command_cmd;
+
+/* SYNTAX: ECHO [-current] [-window <name>] [-level <level>] <text> */
+static void cmd_echo(const char *data, void *server, WI_ITEM_REC *item)
+{
+        WINDOW_REC *window;
+       GHashTable *optlist;
+       char *msg, *levelstr, *winname;
+       void *free_arg;
+       int level;
+
+       g_return_if_fail(data != NULL);
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_GETREST, "echo", &optlist, &msg))
+               return;
+
+        levelstr = g_hash_table_lookup(optlist, "level");
+       level = levelstr == NULL ? 0 :
+               level2bits(g_hash_table_lookup(optlist, "level"));
+       if (level == 0) level = MSGLEVEL_CRAP;
+
+       winname = g_hash_table_lookup(optlist, "window");
+       window = winname == NULL ? NULL :
+               is_numeric(winname, '\0') ?
+               window_find_refnum(atoi(winname)) :
+               window_find_item(NULL, winname);
+       if (window == NULL) window = active_win;
+
+       printtext_window(window, level, "%s", msg);
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: VERSION */
+static void cmd_version(char *data)
+{
+       g_return_if_fail(data != NULL);
+
+       if (*data == '\0') {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                         "Client: "PACKAGE" " IRSSI_VERSION);
+       }
+}
+
+/* SYNTAX: CAT <file> */
+static void cmd_cat(const char *data)
+{
+       LINEBUF_REC *buffer = NULL;
+       char *fname, *fposstr;
+       char tmpbuf[1024], *str;
+       void *free_arg;
+       int f, ret, recvlen, fpos;
+
+       if (!cmd_get_params(data, &free_arg, 2, &fname, &fposstr))
+               return;
+
+       fname = convert_home(fname);
+       fpos = atoi(fposstr);
+        cmd_params_free(free_arg);
+
+       f = open(fname, O_RDONLY);
+       g_free(fname);
+
+       if (f == -1) {
+               /* file not found */
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                         "%s", g_strerror(errno));
+               return;
+       }
+
+        lseek(f, fpos, SEEK_SET);
+       do {
+               recvlen = read(f, tmpbuf, sizeof(tmpbuf));
+
+               ret = line_split(tmpbuf, recvlen, &str, &buffer);
+               if (ret > 0)
+                       printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str);
+       } while (ret > 0);
+       line_split_free(buffer);
+
+       close(f);
+}
+
+/* SYNTAX: BEEP */
+static void cmd_beep(void)
+{
+        signal_emit("beep", 0);
+}
+
+static void sig_stop(void)
+{
+       signal_stop();
+}
+
+static void event_command(const char *data)
+{
+       const char *cmdchar;
+
+       /* save current command line */
+       current_cmdline = data;
+
+        /* for detecting if we're pasting text */
+       time_command_last = time_command_now;
+       last_command_cmd = command_cmd;
+
+       g_get_current_time(&time_command_now);
+       command_cmd = strchr(settings_get_str("cmdchars"), *data) != NULL;
+
+       /* /^command hides the output of the command */
+       cmdchar = strchr(settings_get_str("cmdchars"), *data);
+       if (cmdchar != NULL && (data[1] == '^' ||
+                               (data[1] == *cmdchar && data[2] == '^'))) {
+                hide_output = TRUE;
+               signal_add_first("print starting", (SIGNAL_FUNC) sig_stop);
+               signal_add_first("print format", (SIGNAL_FUNC) sig_stop);
+               signal_add_first("print text stripped", (SIGNAL_FUNC) sig_stop);
+               signal_add_first("print text", (SIGNAL_FUNC) sig_stop);
+       }
+}
+
+static void event_command_last(const char *data)
+{
+       if (hide_output) {
+               hide_output = FALSE;
+               signal_remove("print starting", (SIGNAL_FUNC) sig_stop);
+               signal_remove("print format", (SIGNAL_FUNC) sig_stop);
+               signal_remove("print text stripped", (SIGNAL_FUNC) sig_stop);
+               signal_remove("print text", (SIGNAL_FUNC) sig_stop);
+       }
+}
+
+static void event_default_command(const char *data, void *server,
+                                 WI_ITEM_REC *item)
+{
+       const char *cmdchars, *ptr;
+       char *cmd, *p;
+       long diff;
+
+       cmdchars = settings_get_str("cmdchars");
+
+       ptr = data;
+       while (*ptr != '\0' && *ptr != ' ') {
+               if (strchr(cmdchars, *ptr)) {
+                       /* command character inside command .. we probably
+                          want to send this text to channel. for example
+                          when pasting a path /usr/bin/xxx. */
+                       signal_emit("send text", 3, current_cmdline, server, item);
+                       return;
+               }
+               ptr++;
+       }
+
+       /* maybe we're copy+pasting text? check how long it was since the
+          last line */
+       diff = get_timeval_diff(&time_command_now, &time_command_last);
+       if (item != NULL && !last_command_cmd && diff < PASTE_CHECK_SPEED) {
+               signal_emit("send text", 3, current_cmdline, active_win->active_server, active_win->active);
+               command_cmd = FALSE;
+               return;
+       }
+
+       /* get the command part of the line, send "error command" signal */
+       cmd = g_strdup(data);
+       p = strchr(cmd, ' ');
+       if (p != NULL) *p = '\0';
+
+       signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd);
+
+       g_free(cmd);
+}
+
+static void event_cmderror(void *errorp, const char *arg)
+{
+       int error;
+
+       error = GPOINTER_TO_INT(errorp);
+       if (error == CMDERR_ERRNO) {
+                /* errno is special */
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", g_strerror(errno));
+       } else {
+                /* others */
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error + -CMDERR_OPTION_UNKNOWN], arg);
+       }
+}
+
+static void event_list_subcommands(const char *command)
+{
+        GSList *tmp;
+        GString *str;
+       int len;
+
+       str = g_string_new(NULL);
+
+        len = strlen(command);
+       for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->cmd, command, len) == 0 &&
+                   rec->cmd[len] == ' ' &&
+                   strchr(rec->cmd+len+1, ' ') == NULL) {
+                        g_string_sprintfa(str, "%s ", rec->cmd+len+1);
+               }
+       }
+
+       if (str->len != 0) {
+               g_string_truncate(str, str->len-1);
+                printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str->str);
+       }
+
+        g_string_free(str, TRUE);
+}
+
+void fe_core_commands_init(void)
+{
+       hide_output = FALSE;
+
+       command_cmd = FALSE;
+       memset(&time_command_now, 0, sizeof(GTimeVal));
+
+       command_bind("echo", NULL, (SIGNAL_FUNC) cmd_echo);
+       command_bind("version", NULL, (SIGNAL_FUNC) cmd_version);
+       command_bind("cat", NULL, (SIGNAL_FUNC) cmd_cat);
+       command_bind("beep", NULL, (SIGNAL_FUNC) cmd_beep);
+
+       signal_add("send command", (SIGNAL_FUNC) event_command);
+       signal_add_last("send command", (SIGNAL_FUNC) event_command_last);
+       signal_add("default command", (SIGNAL_FUNC) event_default_command);
+       signal_add("error command", (SIGNAL_FUNC) event_cmderror);
+       signal_add("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+
+       command_set_options("echo", "current +level +window");
+}
+
+void fe_core_commands_deinit(void)
+{
+       command_unbind("echo", (SIGNAL_FUNC) cmd_echo);
+       command_unbind("version", (SIGNAL_FUNC) cmd_version);
+       command_unbind("cat", (SIGNAL_FUNC) cmd_cat);
+       command_unbind("beep", (SIGNAL_FUNC) cmd_beep);
+
+       signal_remove("send command", (SIGNAL_FUNC) event_command);
+       signal_remove("send command", (SIGNAL_FUNC) event_command_last);
+       signal_remove("default command", (SIGNAL_FUNC) event_default_command);
+       signal_remove("error command", (SIGNAL_FUNC) event_cmderror);
+       signal_remove("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-exec.c b/apps/irssi/src/fe-common/core/fe-exec.c
new file mode 100644 (file)
index 0000000..bc8f469
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+ fe-exec.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 "commands.h"
+#include "pidwait.h"
+#include "line-split.h"
+#include "net-sendbuffer.h"
+#include "misc.h"
+#include "levels.h"
+
+#include "printtext.h"
+#include "fe-exec.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+#include <signal.h>
+#include <sys/wait.h>
+
+static GSList *processes;
+static int signal_exec_input;
+
+static EXEC_WI_REC *exec_wi_create(WINDOW_REC *window, PROCESS_REC *rec)
+{
+       EXEC_WI_REC *item;
+
+        g_return_val_if_fail(window != NULL, NULL);
+        g_return_val_if_fail(rec != NULL, NULL);
+
+       item = g_new0(EXEC_WI_REC, 1);
+       item->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "EXEC");
+       item->name = rec->name != NULL ?
+               g_strdup_printf("%%%s", rec->name) :
+               g_strdup_printf("%%%d", rec->id);
+
+       item->window = window;
+       item->createtime = time(NULL);
+        item->process = rec;
+
+       MODULE_DATA_INIT(item);
+       window_item_add(window, (WI_ITEM_REC *) item, FALSE);
+        return item;
+}
+
+static void exec_wi_destroy(EXEC_WI_REC *rec)
+{
+        g_return_if_fail(rec != NULL);
+
+       if (rec->destroying) return;
+        rec->destroying = TRUE;
+
+       if (window_item_window((WI_ITEM_REC *) rec) != NULL)
+               window_item_destroy((WI_ITEM_REC *) rec);
+
+       MODULE_DATA_DEINIT(rec);
+       g_free(rec->name);
+        g_free(rec);
+}
+
+static int process_get_new_id(void)
+{
+        PROCESS_REC *rec;
+       GSList *tmp;
+       int id;
+
+       id = 0;
+       tmp = processes;
+       while (tmp != NULL) {
+               rec = tmp->data;
+
+               if (id != rec->id) {
+                       tmp = tmp->next;
+                       continue;
+               }
+
+               id++;
+               tmp = processes;
+       }
+
+       return id;
+}
+
+static PROCESS_REC *process_find_pid(int pid)
+{
+       GSList *tmp;
+
+        g_return_val_if_fail(pid > 0, NULL);
+
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               if (rec->pid == pid)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+static PROCESS_REC *process_find_id(int id, int verbose)
+{
+       GSList *tmp;
+
+        g_return_val_if_fail(id != -1, NULL);
+
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               if (rec->id == id)
+                       return rec;
+       }
+
+       if (verbose) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                         "Unknown process id: %d", id);
+       }
+
+       return NULL;
+}
+
+static PROCESS_REC *process_find(const char *name, int verbose)
+{
+       GSList *tmp;
+
+        g_return_val_if_fail(name != NULL, NULL);
+
+       if (*name == '%' && is_numeric(name+1, 0))
+                return process_find_id(atoi(name+1), verbose);
+
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               if (rec->name != NULL && strcmp(rec->name, name) == 0)
+                       return rec;
+       }
+
+       if (verbose) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                         "Unknown process name: %s", name);
+       }
+
+       return NULL;
+}
+
+static void process_destroy(PROCESS_REC *rec, int status)
+{
+       processes = g_slist_remove(processes, rec);
+
+       signal_emit("exec remove", 2, rec, GINT_TO_POINTER(status));
+
+       if (rec->read_tag != -1)
+               g_source_remove(rec->read_tag);
+       if (rec->target_item != NULL)
+                exec_wi_destroy(rec->target_item);
+
+       line_split_free(rec->databuf);
+        g_io_channel_close(rec->in);
+        g_io_channel_unref(rec->in);
+        net_sendbuffer_destroy(rec->out, TRUE);
+
+       g_free_not_null(rec->name);
+       g_free_not_null(rec->target);
+        g_free(rec->args);
+        g_free(rec);
+}
+
+static void processes_killall(int signum)
+{
+       GSList *tmp;
+
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               kill(rec->pid, signum);
+       }
+}
+
+static int signal_name_to_id(const char *name)
+{
+       /* check only the few most common signals, too much job to check
+          them all. if we sometimes want more, procps-sources/proc/sig.c
+          would be useful for copypasting */
+       if (g_strcasecmp(name, "hup") == 0)
+                return SIGHUP;
+       if (g_strcasecmp(name, "int") == 0)
+                return SIGINT;
+       if (g_strcasecmp(name, "term") == 0)
+                return SIGTERM;
+       if (g_strcasecmp(name, "kill") == 0)
+                return SIGKILL;
+       if (g_strcasecmp(name, "usr1") == 0)
+                return SIGUSR1;
+       if (g_strcasecmp(name, "usr2") == 0)
+                return SIGUSR2;
+        return -1;
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+   returns NULL if there was unknown -option */
+static int cmd_options_get_signal(const char *cmd,
+                                 GHashTable *optlist)
+{
+       GSList *list, *tmp, *next;
+       char *signame;
+        int signum;
+
+       /* get all the options, then remove the known ones. there should
+          be only one left - the signal */
+       list = hashtable_get_keys(optlist);
+       if (cmd != NULL) {
+               for (tmp = list; tmp != NULL; tmp = next) {
+                       char *option = tmp->data;
+                       next = tmp->next;
+
+                       if (command_have_option(cmd, option))
+                               list = g_slist_remove(list, option);
+               }
+       }
+
+       if (list == NULL)
+               return -1;
+
+       signame = list->data;
+       signum = -1;
+
+       signum = is_numeric(signame, 0) ? atol(signame) :
+               signal_name_to_id(signame);
+
+       if (signum == -1 || list->next != NULL) {
+               /* unknown option (not a signal) */
+               signal_emit("error command", 2,
+                           GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+                           signum == -1 ? list->data : list->next->data);
+               signal_stop();
+                return -2;
+       }
+
+       g_slist_free(list);
+       return signum;
+}
+
+static void exec_show_list(void)
+{
+       GSList *tmp;
+
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+                         "%d (%s): %s", rec->id, rec->name, rec->args);
+       }
+}
+
+static void process_exec(PROCESS_REC *rec, const char *cmd)
+{
+       const char *shell_args[4] = { "/bin/sh", "-c", NULL, NULL };
+        char **args;
+       int in[2], out[2];
+        int n;
+
+       if (pipe(in) == -1)
+                return;
+       if (pipe(out) == -1)
+               return;
+
+       shell_args[2] = cmd;
+       rec->pid = fork();
+       if (rec->pid == -1) {
+                /* error */
+               close(in[0]); close(in[1]);
+                close(out[0]); close(out[1]);
+               return;
+       }
+
+       if (rec->pid != 0) {
+               /* parent process */
+                GIOChannel *outio = g_io_channel_unix_new(in[1]);
+
+               rec->in = g_io_channel_unix_new(out[0]);
+               rec->out = net_sendbuffer_create(outio, 0);
+
+                close(out[1]);
+               close(in[0]);
+               pidwait_add(rec->pid);
+                return;
+       }
+
+       /* child process, try to clean up everything */
+       setsid();
+       setuid(getuid());
+       setgid(getgid());
+       signal(SIGINT, SIG_IGN);
+       signal(SIGQUIT, SIG_DFL);
+
+       putenv("TERM=tty");
+
+       /* set stdin, stdout and stderr */
+        dup2(in[0], STDIN_FILENO);
+        dup2(out[1], STDOUT_FILENO);
+       dup2(out[1], STDERR_FILENO);
+
+        /* don't let child see our files */
+       for (n = 3; n < 256; n++)
+                close(n);
+
+       if (rec->shell) {
+               execvp(shell_args[0], (char **) shell_args);
+
+               fprintf(stderr, "Exec: /bin/sh: %s\n", g_strerror(errno));
+       } else {
+               args = g_strsplit(cmd, " ", -1);
+                execvp(args[0], args);
+
+               fprintf(stderr, "Exec: %s: %s\n", args[0], g_strerror(errno));
+       }
+
+       _exit(-1);
+}
+
+static void sig_exec_input_reader(PROCESS_REC *rec)
+{
+        char tmpbuf[512], *str;
+        unsigned int recvlen;
+       int err, ret;
+
+       g_return_if_fail(rec != NULL);
+
+       recvlen = 0;
+       err = g_io_channel_read(rec->in, tmpbuf,
+                               sizeof(tmpbuf), &recvlen);
+       if (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
+               recvlen = -1;
+
+       do {
+               ret = line_split(tmpbuf, recvlen, &str, &rec->databuf);
+               if (ret == -1) {
+                       /* link to terminal closed? */
+                       g_source_remove(rec->read_tag);
+                        rec->read_tag = -1;
+                       break;
+               }
+
+               if (ret > 0) {
+                       signal_emit_id(signal_exec_input, 2, rec, str);
+                        if (recvlen > 0) recvlen = 0;
+               }
+       } while (ret > 0);
+}
+
+static void handle_exec(const char *args, GHashTable *optlist,
+                       WI_ITEM_REC *item)
+{
+       PROCESS_REC *rec;
+        char *target;
+       int notice, signum, interactive;
+
+       /* check that there's no unknown options. we allowed them
+          because signals can be used as options, but there should be
+          only one unknown option: the signal name/number. */
+       signum = cmd_options_get_signal("exec", optlist);
+       if (signum == -2)
+                return;
+
+       if (*args == '\0') {
+               exec_show_list();
+                return;
+       }
+
+       target = NULL;
+       notice = FALSE;
+
+       if (g_hash_table_lookup(optlist, "in") != NULL) {
+               rec = process_find(g_hash_table_lookup(optlist, "in"), TRUE);
+               if (rec != NULL) {
+                       net_sendbuffer_send(rec->out, args, strlen(args));
+                       net_sendbuffer_send(rec->out, "\n", 1);
+               }
+               return;
+       }
+
+       /* check if args is a process ID or name. if it's ID but not found,
+          complain about it and fail immediately */
+       rec = process_find(args, *args == '%');
+       if (*args == '%' && rec == NULL)
+               return;
+
+        /* common options */
+       if (g_hash_table_lookup(optlist, "out") != NULL) {
+                /* redirect output to active channel/query */
+               if (item == NULL)
+                       cmd_return_error(CMDERR_NOT_JOINED);
+                target = item->name;
+       } else if (g_hash_table_lookup(optlist, "msg") != NULL) {
+                /* redirect output to /msg <nick> */
+               target = g_hash_table_lookup(optlist, "msg");
+       } else if (g_hash_table_lookup(optlist, "notice") != NULL) {
+               target = g_hash_table_lookup(optlist, "notice");
+                notice = TRUE;
+       }
+
+        /* options that require process ID/name as argument */
+       if (rec == NULL &&
+           (signum != -1 || g_hash_table_lookup(optlist, "close") != NULL)) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                         "Unknown process name: %s", args);
+               return;
+       }
+       if (g_hash_table_lookup(optlist, "close") != NULL) {
+               /* forcibly close the process */
+                process_destroy(rec, -1);
+                return;
+       }
+
+       if (signum != -1) {
+               /* send a signal to process */
+                kill(rec->pid, signum);
+                return;
+       }
+
+        interactive = g_hash_table_lookup(optlist, "interactive") != NULL;
+       if (*args == '%') {
+               /* do something to already existing process */
+               char *name;
+
+               if (target != NULL) {
+                        /* redirect output to target */
+                       g_free_and_null(rec->target);
+                       rec->target = g_strdup(target);
+                        rec->notice = notice;
+               }
+
+                name = g_hash_table_lookup(optlist, "name");
+               if (name != NULL) {
+                       /* change window name */
+                       g_free_not_null(rec->name);
+                       rec->name = *name == '\0' ? NULL : g_strdup(name);
+               } else if (target == NULL &&
+                          (rec->target_item == NULL || interactive)) {
+                       /* no parameters given,
+                          redirect output to the active window */
+                       g_free_and_null(rec->target);
+                       rec->target_win = active_win;
+
+                       if (rec->target_item != NULL) {
+                               exec_wi_destroy(rec->target_item);
+                                rec->target_item = NULL;
+                       }
+
+                       if (interactive) {
+                               rec->target_item =
+                                       exec_wi_create(active_win, rec);
+                       }
+               }
+                return;
+       }
+
+        /* starting a new process */
+       rec = g_new0(PROCESS_REC, 1);
+       rec->pid = -1;
+        rec->shell = g_hash_table_lookup(optlist, "nosh") == NULL;
+
+       process_exec(rec, args);
+       if (rec->pid == -1) {
+                /* pipe() or fork() failed */
+               g_free(rec);
+               cmd_return_error(CMDERR_ERRNO);
+       }
+
+        rec->id = process_get_new_id();
+       rec->target = g_strdup(target);
+       rec->target_win = active_win;
+        rec->args = g_strdup(args);
+       rec->notice = notice;
+        rec->silent = g_hash_table_lookup(optlist, "-") != NULL;
+       rec->name = g_strdup(g_hash_table_lookup(optlist, "name"));
+
+       rec->read_tag = g_input_add(rec->in, G_INPUT_READ,
+                                   (GInputFunction) sig_exec_input_reader,
+                                   rec);
+       processes = g_slist_append(processes, rec);
+
+       if (rec->target == NULL && interactive)
+               rec->target_item = exec_wi_create(active_win, rec);
+
+       signal_emit("exec new", 1, rec);
+}
+
+/* SYNTAX: EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>]
+               [-name <name>] <cmd line>
+          EXEC -out | -window | -msg <target> | -notice <target> |
+               -close | -<signal> <id>
+          EXEC -in <id> <text to send to process> */
+static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       GHashTable *optlist;
+        char *args;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+
+       if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                          PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+                          "exec", &optlist, &args)) {
+               handle_exec(args, optlist, item);
+               cmd_params_free(free_arg);
+       }
+}
+
+static void sig_pidwait(void *pid, void *statusp)
+{
+       PROCESS_REC *rec;
+       int status = GPOINTER_TO_INT(statusp);
+
+        rec = process_find_pid(GPOINTER_TO_INT(pid));
+       if (rec == NULL) return;
+
+       /* process exited */
+       if (!rec->silent) {
+               if (WIFSIGNALED(status)) {
+                       status = WTERMSIG(status);
+                       printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                 "process %d (%s) terminated with signal %d (%s)",
+                                 rec->id, rec->args,
+                                 status, g_strsignal(status));
+               } else {
+                        status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+                       printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                 "process %d (%s) terminated with return code %d",
+                                 rec->id, rec->args, status);
+               }
+       }
+       process_destroy(rec, status);
+}
+
+static void sig_exec_input(PROCESS_REC *rec, const char *text)
+{
+       WI_ITEM_REC *item;
+       SERVER_REC *server;
+        char *str;
+
+        item = NULL;
+       server = NULL;
+
+       if (rec->target != NULL) {
+               item = window_item_find(NULL, rec->target);
+               server = item != NULL ? item->server :
+                       active_win->active_server;
+
+                str = g_strconcat(rec->target, " ", text, NULL);
+               signal_emit(rec->notice ? "command notice" : "command msg",
+                           3, str, server, item);
+                g_free(str);
+       } else if (rec->target_item != NULL) {
+               printtext(NULL, rec->target_item->name, MSGLEVEL_CLIENTCRAP,
+                         "%s", text);
+       } else {
+               printtext_window(rec->target_win, MSGLEVEL_CLIENTCRAP,
+                                "%s", text);
+       }
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+       GSList *tmp;
+
+       /* window is being closed, if there's any /exec targets for it,
+          change them to active window. */
+       for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+               PROCESS_REC *rec = tmp->data;
+
+               if (rec->target_win == window)
+                       rec->target_win = active_win;
+       }
+}
+
+static void sig_window_item_destroyed(WINDOW_REC *window, EXEC_WI_REC *item)
+{
+       if (IS_EXEC_WI(item)) {
+                item->process->target_item = NULL;
+               exec_wi_destroy(item);
+       }
+}
+
+static void event_text(const char *data, SERVER_REC *server, EXEC_WI_REC *item)
+{
+       if (!IS_EXEC_WI(item)) return;
+
+       net_sendbuffer_send(item->process->out, data, strlen(data));
+       net_sendbuffer_send(item->process->out, "\n", 1);
+        signal_stop();
+}
+
+void fe_exec_init(void)
+{
+       command_bind("exec", NULL, (SIGNAL_FUNC) cmd_exec);
+       command_set_options("exec", "!- interactive nosh +name out +msg +notice +in window close");
+
+        signal_exec_input = signal_get_uniq_id("exec input");
+        signal_add("pidwait", (SIGNAL_FUNC) sig_pidwait);
+        signal_add("exec input", (SIGNAL_FUNC) sig_exec_input);
+        signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+       signal_add("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed);
+       signal_add_first("send text", (SIGNAL_FUNC) event_text);
+}
+
+void fe_exec_deinit(void)
+{
+       if (processes != NULL) {
+               processes_killall(SIGTERM);
+               sleep(1);
+               processes_killall(SIGKILL);
+
+               while (processes != NULL)
+                       process_destroy(processes->data, -1);
+       }
+
+       command_unbind("exec", (SIGNAL_FUNC) cmd_exec);
+
+        signal_remove("pidwait", (SIGNAL_FUNC) sig_pidwait);
+        signal_remove("exec input", (SIGNAL_FUNC) sig_exec_input);
+        signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+       signal_remove("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed);
+       signal_remove("send text", (SIGNAL_FUNC) event_text);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-exec.h b/apps/irssi/src/fe-common/core/fe-exec.h
new file mode 100644 (file)
index 0000000..7f569ec
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef __FE_EXEC_H
+#define __FE_EXEC_H
+
+#include "fe-windows.h"
+
+#define EXEC_WI(query) \
+       MODULE_CHECK_CAST_MODULE(query, EXEC_WI_REC, type, \
+                             "WINDOW ITEM TYPE", "EXEC")
+
+#define IS_EXEC_WI(query) \
+       (EXEC_WI(query) ? TRUE : FALSE)
+
+typedef struct PROCESS_REC PROCESS_REC;
+
+#define STRUCT_SERVER_REC void
+typedef struct {
+#include "window-item-rec.h"
+       PROCESS_REC *process;
+       unsigned int destroying:1;
+} EXEC_WI_REC;
+
+struct PROCESS_REC {
+        int id;
+       char *name;
+        char *args;
+
+       int pid;
+       GIOChannel *in;
+        NET_SENDBUF_REC *out;
+        LINEBUF_REC *databuf;
+       int read_tag;
+
+        char *target; /* send text with /msg <target> ... */
+       WINDOW_REC *target_win; /* print text to this window */
+        EXEC_WI_REC *target_item; /* print text to this exec window item */
+
+       unsigned int shell:1; /* start the program via /bin/sh */
+       unsigned int notice:1; /* send text with /notice, not /msg if target is set */
+       unsigned int silent:1; /* don't print "process exited with level xx" */
+};
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/fe-expandos.c b/apps/irssi/src/fe-common/core/fe-expandos.c
new file mode 100644 (file)
index 0000000..7eb145d
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ fe-expandos.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 "expandos.h"
+#include "fe-windows.h"
+
+/* Window ref# */
+static char *expando_winref(SERVER_REC *server, void *item, int *free_ret)
+{
+        *free_ret = TRUE;
+       return g_strdup_printf("%d", active_win->refnum);
+}
+
+/* Window name */
+static char *expando_winname(SERVER_REC *server, void *item, int *free_ret)
+{
+       return active_win->name;
+}
+
+void fe_expandos_init(void)
+{
+       expando_create("winref", expando_winref,
+                      "window changed", EXPANDO_ARG_NONE,
+                      "window refnum changed", EXPANDO_ARG_WINDOW, NULL);
+       expando_create("winname", expando_winname,
+                      "window name changed", EXPANDO_ARG_WINDOW, NULL);
+}
+
+void fe_expandos_deinit(void)
+{
+       expando_destroy("winref", expando_winref);
+       expando_destroy("winname", expando_winname);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-help.c b/apps/irssi/src/fe-common/core/fe-help.c
new file mode 100644 (file)
index 0000000..fa90473
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ fe-help.c : irssi
+
+    Copyright (C) 1999-2001 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 "line-split.h"
+#include "settings.h"
+
+#include "printtext.h"
+#include "formats.h"
+
+static int commands_equal(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 int get_cmd_length(void *data)
+{
+        return strlen(((COMMAND_REC *) data)->cmd);
+}
+
+static void help_category(GSList *cmdlist, int items)
+{
+        WINDOW_REC *window;
+       TEXT_DEST_REC dest;
+       GString *str;
+       GSList *tmp;
+       int *columns, cols, rows, col, row, last_col_rows, max_width;
+       char *linebuf, *format, *stripped;
+
+       window = window_find_closest(NULL, NULL, MSGLEVEL_CLIENTCRAP);
+        max_width = window->width;
+
+        /* remove width of timestamp from max_width */
+       format_create_dest(&dest, NULL, NULL, MSGLEVEL_CLIENTCRAP, NULL);
+       format = format_get_line_start(current_theme, &dest, time(NULL));
+       if (format != NULL) {
+               stripped = strip_codes(format);
+               max_width -= strlen(stripped);
+               g_free(stripped);
+               g_free(format);
+       }
+
+        /* calculate columns */
+       cols = get_max_column_count(cmdlist, get_cmd_length,
+                                   max_width, 6, 1, 3, &columns, &rows);
+       cmdlist = columns_sort_list(cmdlist, rows);
+
+        /* rows in last column */
+       last_col_rows = rows-(cols*rows-g_slist_length(cmdlist));
+       if (last_col_rows == 0)
+                last_col_rows = rows;
+
+       str = g_string_new(NULL);
+       linebuf = g_malloc(max_width+1);
+
+        col = 0; row = 0;
+       for (tmp = cmdlist; tmp != NULL; tmp = tmp->next) {
+               COMMAND_REC *rec = tmp->data;
+
+               memset(linebuf, ' ', columns[col]);
+               linebuf[columns[col]] = '\0';
+               memcpy(linebuf, rec->cmd, strlen(rec->cmd));
+               g_string_append(str, linebuf);
+
+               if (++col == cols) {
+                       printtext(NULL, NULL,
+                                 MSGLEVEL_CLIENTCRAP, "%s", str->str);
+                       g_string_truncate(str, 0);
+                       col = 0; row++;
+
+                       if (row == last_col_rows)
+                                cols--;
+               }
+       }
+       if (str->len != 0)
+               printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str->str);
+
+       g_slist_free(cmdlist);
+       g_string_free(str, TRUE);
+       g_free(columns);
+       g_free(linebuf);
+}
+
+static int show_help_file(const char *file)
+{
+        const char *helppath;
+       char tmpbuf[1024], *str, *path;
+       LINEBUF_REC *buffer = NULL;
+       int f, ret, recvlen;
+
+        helppath = settings_get_str("help_path");
+
+       /* helpdir/command or helpdir/category/command */
+       path = g_strdup_printf("%s/%s", helppath, file);
+       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);
+               if (ret > 0) {
+                       str = g_strconcat("%|", str, NULL);
+                       printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, str);
+                       g_free(str);
+               }
+       }
+       while (ret > 0);
+       line_split_free(buffer);
+
+       close(f);
+       return TRUE;
+}
+
+static void show_help(const char *data)
+{
+       COMMAND_REC *rec, *last;
+       GSList *tmp, *cmdlist;
+       int items, findlen;
+       int header, found, fullmatch;
+
+       g_return_if_fail(data != NULL);
+
+       /* sort the commands list */
+       commands = g_slist_sort(commands, (GCompareFunc) commands_equal);
+
+       /* print command, sort by category */
+       cmdlist = NULL; last = NULL; header = FALSE; fullmatch = FALSE;
+       items = 0; findlen = strlen(data); found = FALSE;
+       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);
+                       }
+
+                       g_slist_free(cmdlist); cmdlist = NULL;
+                       items = 0;
+               }
+
+               if (last != NULL && g_strcasecmp(rec->cmd, last->cmd) == 0)
+                       continue; /* don't display same command twice */
+
+               if ((int)strlen(rec->cmd) >= findlen &&
+                   g_strncasecmp(rec->cmd, data, findlen) == 0) {
+                       if (rec->cmd[findlen] == '\0') {
+                               fullmatch = TRUE;
+                               found = TRUE;
+                               break;
+                       }
+                       else if (strchr(rec->cmd+findlen+1, ' ') == NULL) {
+                               /* not a subcommand (and matches the query) */
+                               items++;
+                               cmdlist = g_slist_append(cmdlist, rec);
+                               found = TRUE;
+                       }
+               }
+       }
+
+       if ((!found || fullmatch) && !show_help_file(data)) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+                         "No help for %s", data);
+       }
+
+       if (*data != '\0' && data[strlen(data)-1] != ' ' &&
+           command_have_sub(data)) {
+               char *cmd;
+
+               cmd = g_strconcat(data, " ", NULL);
+               show_help(cmd);
+               g_free(cmd);
+       }
+
+       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);
+               g_slist_free(cmdlist);
+       }
+}
+
+/* SYNTAX: HELP [<command>] */
+static void cmd_help(const char *data)
+{
+       char *cmd, *ptr;
+
+       cmd = g_strdup(data);
+       ptr = cmd+strlen(cmd);
+       while (ptr[-1] == ' ') ptr--; *ptr = '\0';
+
+       show_help(cmd);
+        g_free(cmd);
+}
+
+void fe_help_init(void)
+{
+        settings_add_str("misc", "help_path", HELPDIR);
+       command_bind("help", NULL, (SIGNAL_FUNC) cmd_help);
+}
+
+void fe_help_deinit(void)
+{
+       command_unbind("help", (SIGNAL_FUNC) cmd_help);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-ignore-messages.c b/apps/irssi/src/fe-common/core/fe-ignore-messages.c
new file mode 100644 (file)
index 0000000..770f4a4
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ fe-ignore-messages.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 "levels.h"
+#include "ignore.h"
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+                              const char *nick, const char *address,
+                              const char *target)
+{
+       if (ignore_check(server, nick, address, target, msg, MSGLEVEL_PUBLIC))
+               signal_stop();
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+                               const char *nick, const char *address)
+{
+       if (ignore_check(server, nick, address, NULL, msg, MSGLEVEL_MSGS))
+               signal_stop();
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *address)
+{
+       if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_JOINS))
+               signal_stop();
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *address,
+                            const char *reason)
+{
+       if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_PARTS))
+               signal_stop();
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+                            const char *address, const char *reason)
+{
+       if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
+               signal_stop();
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *kicker,
+                            const char *address, const char *reason)
+{
+       if (ignore_check(server, kicker, address,
+                        channel, reason, MSGLEVEL_KICKS))
+               signal_stop();
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+                            const char *oldnick, const char *address)
+{
+       if (ignore_check(server, oldnick, address,
+                        NULL, NULL, MSGLEVEL_NICKS) ||
+           ignore_check(server, newnick, address,
+                        NULL, NULL, MSGLEVEL_NICKS))
+               signal_stop();
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+                                const char *oldnick, const char *address)
+{
+       if (ignore_check(server, oldnick, address, NULL, NULL, MSGLEVEL_NICKS))
+               signal_stop();
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+                              const char *nick, const char *address)
+{
+       if (*channel == '\0' ||
+           ignore_check(server, nick, address,
+                        channel, NULL, MSGLEVEL_INVITES))
+               signal_stop();
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+                             const char *topic,
+                             const char *nick, const char *address)
+{
+       if (ignore_check(server, nick, address,
+                        channel, topic, MSGLEVEL_TOPICS))
+               signal_stop();
+}
+
+void fe_ignore_messages_init(void)
+{
+       signal_add_first("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_add_first("message join", (SIGNAL_FUNC) sig_message_join);
+       signal_add_first("message part", (SIGNAL_FUNC) sig_message_part);
+       signal_add_first("message quit", (SIGNAL_FUNC) sig_message_quit);
+       signal_add_first("message kick", (SIGNAL_FUNC) sig_message_kick);
+       signal_add_first("message nick", (SIGNAL_FUNC) sig_message_nick);
+       signal_add_first("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+       signal_add_first("message invite", (SIGNAL_FUNC) sig_message_invite);
+       signal_add_first("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
+
+void fe_ignore_messages_deinit(void)
+{
+       signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+       signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+       signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+       signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+       signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+       signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+       signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+       signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-ignore.c b/apps/irssi/src/fe-common/core/fe-ignore.c
new file mode 100644 (file)
index 0000000..3523d52
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ 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 "servers.h"
+#include "ignore.h"
+#include "printtext.h"
+
+static char *ignore_get_key(IGNORE_REC *rec)
+{
+       char *chans, *ret;
+
+       if (rec->channels == NULL)
+               return g_strdup(rec->mask != NULL ? rec->mask : "*" );
+
+       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 void ignore_print(int index, IGNORE_REC *rec)
+{
+       GString *options;
+       char *key, *levels;
+
+       key = ignore_get_key(rec);
+       levels = bits2level(rec->level);
+
+       options = g_string_new(NULL);
+       if (rec->exception) g_string_sprintfa(options, "-except ");
+       if (rec->regexp) g_string_sprintfa(options, "-regexp ");
+       if (rec->fullword) g_string_sprintfa(options, "-full ");
+       if (rec->replies) g_string_sprintfa(options, "-replies ");
+       if (options->len > 1) g_string_truncate(options, options->len-1);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+                   TXT_IGNORE_LINE, index,
+                   key != NULL ? key : "",
+                   levels != NULL ? levels : "", options->str);
+       g_string_free(options, TRUE);
+        g_free(key);
+       g_free(levels);
+}
+
+static void cmd_ignore_show(void)
+{
+       GSList *tmp;
+       int index;
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_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, TXT_IGNORE_FOOTER);
+}
+
+/* SYNTAX: IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+                  [-channels <channel>] [-time <secs>] <mask> [<levels>]
+           IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+                 [-time <secs>] <channels> [<levels>] */
+static void cmd_ignore(const char *data)
+{
+       GHashTable *optlist;
+       IGNORE_REC *rec;
+       char *patternarg, *chanarg, *mask, *levels, *timestr;
+       char **channels;
+       void *free_arg;
+       int new_ignore;
+
+       if (*data == '\0') {
+               cmd_ignore_show();
+               return;
+       }
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST,
+                           "ignore", &optlist, &mask, &levels))
+               return;
+
+       patternarg = g_hash_table_lookup(optlist, "pattern");
+        chanarg = g_hash_table_lookup(optlist, "channels");
+
+       if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+        if (*levels == '\0') levels = "ALL";
+
+       if (active_win->active_server != NULL &&
+           active_win->active_server->ischannel(mask)) {
+               chanarg = mask;
+               mask = NULL;
+       }
+       channels = (chanarg == NULL || *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 == NULL || *mask == '\0' ||
+                       strcmp(mask, "*") == 0 ? NULL : g_strdup(mask);
+               rec->channels = channels;
+       } else {
+                g_free_and_null(rec->pattern);
+               g_strfreev(channels);
+       }
+
+       rec->level = combine_level(rec->level, levels);
+       rec->pattern = (patternarg == NULL || *patternarg == '\0') ?
+               NULL : g_strdup(patternarg);
+       rec->exception = g_hash_table_lookup(optlist, "except") != NULL;
+       rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+       rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+       rec->replies = g_hash_table_lookup(optlist, "replies") != NULL;
+       timestr = g_hash_table_lookup(optlist, "time");
+        if (timestr != NULL)
+               rec->unignore_time = time(NULL)+atoi(timestr);
+
+       if (rec->level == 0) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED,
+                           rec->mask == NULL ? "*" : rec->mask);
+       }
+
+       if (new_ignore)
+               ignore_add_rec(rec);
+       else
+               ignore_update_rec(rec);
+
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNIGNORE <id>|<mask> */
+static void cmd_unignore(const char *data)
+{
+       IGNORE_REC *rec;
+       GSList *tmp;
+
+       if (*data == '\0')
+                cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       if (is_numeric(data, ' ')) {
+               /* with index number */
+               tmp = g_slist_nth(ignores, atoi(data)-1);
+               rec = tmp == NULL ? NULL : tmp->data;
+       } else {
+               /* with mask */
+               const char *chans[2] = { "*", NULL };
+
+               if (active_win->active_server != NULL &&
+                   active_win->active_server->ischannel(data)) {
+                       chans[0] = data;
+                       data = NULL;
+               }
+               rec = ignore_find("*", data, (char **) chans);
+       }
+
+       if (rec != NULL) {
+               rec->level = 0;
+               ignore_update_rec(rec);
+       } else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           TXT_IGNORE_NOT_FOUND, data);
+       }
+}
+
+static void sig_ignore_created(IGNORE_REC *rec)
+{
+       char *key, *levels;
+
+       key = ignore_get_key(rec);
+        levels = bits2level(rec->level);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_IGNORED, key, levels);
+       g_free(key);
+       g_free(levels);
+}
+
+static void sig_ignore_destroyed(IGNORE_REC *rec)
+{
+       char *key;
+
+       key = ignore_get_key(rec);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED, key);
+       g_free(key);
+}
+
+void fe_ignore_init(void)
+{
+       command_bind("ignore", NULL, (SIGNAL_FUNC) cmd_ignore);
+       command_bind("unignore", NULL, (SIGNAL_FUNC) cmd_unignore);
+
+       signal_add("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+       signal_add("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+       signal_add("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+
+       command_set_options("ignore", "regexp full except replies -time -pattern -channels");
+}
+
+void fe_ignore_deinit(void)
+{
+       command_unbind("ignore", (SIGNAL_FUNC) cmd_ignore);
+       command_unbind("unignore", (SIGNAL_FUNC) cmd_unignore);
+
+       signal_remove("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+       signal_remove("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+       signal_remove("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-log.c b/apps/irssi/src/fe-common/core/fe-log.c
new file mode 100644 (file)
index 0000000..60d9632
--- /dev/null
@@ -0,0 +1,669 @@
+/*
+ fe-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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "servers.h"
+#include "levels.h"
+#include "misc.h"
+#include "log.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "formats.h"
+#include "themes.h"
+#include "printtext.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 THEME_REC *log_theme;
+static int skip_next_printtext;
+static const char *log_theme_name;
+
+static void log_add_targets(LOG_REC *log, const char *targets, const char *tag)
+{
+       char **tmp, **items;
+
+        g_return_if_fail(log != NULL);
+        g_return_if_fail(targets != NULL);
+
+       items = g_strsplit(targets, " ", -1);
+
+       for (tmp = items; *tmp != NULL; tmp++)
+               log_item_add(log, LOG_ITEM_TARGET, *tmp, tag);
+
+       g_strfreev(items);
+}
+
+/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-window] [-<server tag>]
+                    [-targets <targets>] <fname> [<levels>] */
+static void cmd_log_open(const char *data)
+{
+        SERVER_REC *server;
+        GHashTable *optlist;
+       char *targetarg, *fname, *levels, *servertag;
+       void *free_arg;
+       char window[MAX_INT_STRLEN];
+       LOG_REC *log;
+       int level;
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+                           PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_OPTIONS,
+                           "log open", &optlist, &fname, &levels))
+               return;
+       if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       level = level2bits(levels);
+       log = log_create_rec(fname, level != 0 ? level : MSGLEVEL_ALL);
+
+       /* -<server tag> */
+       server = cmd_options_get_server("log open", optlist, NULL);
+       servertag = server == NULL ? NULL : server->tag;
+
+       if (g_hash_table_lookup(optlist, "window")) {
+               /* log by window ref# */
+               ltoa(window, active_win->refnum);
+               log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window,
+                            servertag);
+       } else {
+               targetarg = g_hash_table_lookup(optlist, "targets");
+               if (targetarg != NULL && *targetarg != '\0')
+                       log_add_targets(log, targetarg, servertag);
+       }
+
+       if (g_hash_table_lookup(optlist, "autoopen"))
+               log->autoopen = TRUE;
+
+       log_update(log);
+
+       if (log->handle == -1 && g_hash_table_lookup(optlist, "noopen") == NULL) {
+               /* start logging */
+               if (log_start_logging(log)) {
+                       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                   TXT_LOG_OPENED, fname);
+               } else {
+                       log_close(log);
+               }
+       }
+
+        cmd_params_free(free_arg);
+}
+
+static LOG_REC *log_find_from_data(const char *data)
+{
+       GSList *tmp;
+
+       if (!is_numeric(data, ' '))
+               return log_find(data);
+
+       /* with index number */
+       tmp = g_slist_nth(logs, atoi(data)-1);
+       return tmp == NULL ? NULL : tmp->data;
+}
+
+/* SYNTAX: LOG CLOSE <id>|<file> */
+static void cmd_log_close(const char *data)
+{
+       LOG_REC *log;
+
+       log = log_find_from_data(data);
+       if (log == NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+       else {
+               log_close(log);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+       }
+}
+
+/* SYNTAX: LOG START <id>|<file> */
+static void cmd_log_start(const char *data)
+{
+       LOG_REC *log;
+
+       log = log_find_from_data(data);
+       if (log != NULL) {
+               log_start_logging(log);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, data);
+       }
+}
+
+/* SYNTAX: LOG STOP <id>|<file> */
+static void cmd_log_stop(const char *data)
+{
+       LOG_REC *log;
+
+       log = log_find_from_data(data);
+       if (log == NULL || log->handle == -1)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+       else {
+               log_stop_logging(log);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+       }
+}
+
+static char *log_items_get_list(LOG_REC *log)
+{
+       GSList *tmp;
+       GString *str;
+       char *ret;
+
+       g_return_val_if_fail(log != NULL, NULL);
+       g_return_val_if_fail(log->items != NULL, NULL);
+
+       str = g_string_new(NULL);
+       for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+               LOG_ITEM_REC *rec = tmp->data;
+
+                g_string_sprintfa(str, "%s, ", rec->name);
+       }
+       g_string_truncate(str, str->len-2);
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+       return ret;
+}
+
+static void cmd_log_list(void)
+{
+       GSList *tmp;
+       char *levelstr, *items;
+       int index;
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_HEADER);
+       for (tmp = logs, index = 1; tmp != NULL; tmp = tmp->next, index++) {
+               LOG_REC *rec = tmp->data;
+
+               levelstr = bits2level(rec->level);
+               items = rec->items == NULL ? NULL :
+                        log_items_get_list(rec);
+
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST,
+                           index, rec->fname, items != NULL ? items : "",
+                           levelstr, rec->autoopen ? " -autoopen" : "");
+
+               g_free_not_null(items);
+               g_free(levelstr);
+       }
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_FOOTER);
+}
+
+static void cmd_log(const char *data, SERVER_REC *server, void *item)
+{
+       if (*data == '\0')
+               cmd_log_list();
+       else
+               command_runsub("log", data, server, item);
+}
+
+static LOG_REC *logs_find_item(int type, const char *item,
+                              const char *servertag, LOG_ITEM_REC **ret_item)
+{
+       LOG_ITEM_REC *logitem;
+       GSList *tmp;
+
+       for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+               LOG_REC *log = tmp->data;
+
+               logitem = log_item_find(log, type, item, servertag);
+               if (logitem != NULL) {
+                       if (ret_item != NULL) *ret_item = logitem;
+                       return log;
+               }
+       }
+
+       return NULL;
+}
+
+/* SYNTAX: WINDOW LOG on|off|toggle [<filename>] */
+static void cmd_window_log(const char *data)
+{
+       LOG_REC *log;
+       char *set, *fname, window[MAX_INT_STRLEN];
+       void *free_arg;
+       int open_log, close_log;
+
+       if (!cmd_get_params(data, &free_arg, 2, &set, &fname))
+               return;
+
+        ltoa(window, active_win->refnum);
+       log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+        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_CLIENTNOTICE, TXT_NOT_TOGGLE);
+               cmd_params_free(free_arg);
+               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);
+                log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+               log_update(log);
+               g_free(fname);
+       }
+
+       if (open_log && log != NULL) {
+               log_start_logging(log);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, log->fname);
+       } else if (close_log && log != NULL && log->handle != -1) {
+               log_stop_logging(log);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, log->fname);
+       }
+
+        cmd_params_free(free_arg);
+}
+
+/* Create log file entry to window, but don't start logging */
+/* SYNTAX: WINDOW LOGFILE <file> */
+static void cmd_window_logfile(const char *data)
+{
+       LOG_REC *log;
+       char window[MAX_INT_STRLEN];
+
+        ltoa(window, active_win->refnum);
+       log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+       if (log != NULL) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE_LOGGING);
+               return;
+       }
+
+       log = log_create_rec(data, MSGLEVEL_ALL);
+       log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+       log_update(log);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE, data);
+}
+
+/* window's refnum changed - update the logs to log the new window refnum */
+static void sig_window_refnum_changed(WINDOW_REC *window, gpointer old_refnum)
+{
+       char winnum[MAX_INT_STRLEN];
+       LOG_REC *log;
+       LOG_ITEM_REC *item;
+
+        ltoa(winnum, GPOINTER_TO_INT(old_refnum));
+       log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, winnum, NULL, &item);
+
+       if (log != NULL) {
+               ltoa(winnum, window->refnum);
+
+               g_free(item->name);
+               item->name = g_strdup(winnum);
+       }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+        LOG_ITEM_REC *logitem;
+       GSList *tmp, *next;
+
+       for (tmp = logs; tmp != NULL; tmp = next) {
+               LOG_REC *log = tmp->data;
+               next = tmp->next;
+
+               if (!log->temp || log->items == NULL)
+                        continue;
+
+               logitem = log->items->data;
+               if (logitem->type == LOG_ITEM_TARGET &&
+                   g_strcasecmp(logitem->servertag, server->tag) == 0)
+                       log_close(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_open(SERVER_REC *server, const char *target)
+{
+       LOG_REC *log;
+       char *fname, *dir, *fixed_target, *tag;
+
+        tag = server == NULL ? NULL : server->tag;
+       log = logs_find_item(LOG_ITEM_TARGET, target, tag, NULL);
+       if (log != NULL && !log->failed) {
+               log_start_logging(log);
+               return;
+       }
+
+       /* '/' -> '_' - don't even accidentally try to log to
+          #../../../file if you happen to join to such channel.. */
+       fixed_target = g_strdup(target);
+        replace_chars(fixed_target, '/', '_');
+       fname = parse_special_string(autolog_path, server, NULL,
+                                    fixed_target, NULL, 0);
+       g_free(fixed_target);
+
+       if (log_find(fname) == NULL) {
+               log = log_create_rec(fname, autolog_level);
+               log_item_add(log, LOG_ITEM_TARGET, target, tag);
+
+               dir = g_dirname(log->real_fname);
+               mkpath(dir, LOG_DIR_CREATE_MODE);
+               g_free(dir);
+
+               log->temp = TRUE;
+               log_update(log);
+               log_start_logging(log);
+       }
+       g_free(fname);
+}
+
+static void autolog_open_check(SERVER_REC *server, const char *target,
+                              int level)
+{
+       char **targets, **tmp;
+
+       if (level == MSGLEVEL_PARTS || /* FIXME: kind of a kludge, but we don't want to reopen logs when we're parting the channel with /WINDOW CLOSE.. */
+           (autolog_level & level) == 0 || target == NULL || *target == '\0')
+               return;
+
+       /* there can be multiple targets separated with comma */
+       targets = g_strsplit(target, ",", -1);
+       for (tmp = targets; *tmp != NULL; tmp++)
+               autolog_open(server, *tmp);
+       g_strfreev(targets);
+}
+
+static void log_single_line(WINDOW_REC *window, void *server,
+                           const char *target, int level, const char *text)
+{
+       char windownum[MAX_INT_STRLEN];
+       char **targets, **tmp;
+       LOG_REC *log;
+
+       /* save to log created with /WINDOW LOG */
+       ltoa(windownum, window->refnum);
+       log = logs_find_item(LOG_ITEM_WINDOW_REFNUM,
+                            windownum, NULL, NULL);
+       if (log != NULL) log_write_rec(log, text, level);
+
+       if (target == NULL)
+               log_file_write(server, NULL, level, text, FALSE);
+       else {
+               /* there can be multiple items separated with comma */
+               targets = g_strsplit(target, ",", -1);
+               for (tmp = targets; *tmp != NULL; tmp++)
+                       log_file_write(server, *tmp, level, text, FALSE);
+               g_strfreev(targets);
+       }
+}
+
+static void log_line(WINDOW_REC *window, SERVER_REC *server,
+                    const char *target, int level, const char *text)
+{
+       char **lines, **tmp;
+
+       if (level == MSGLEVEL_NEVER)
+               return;
+
+       /* let autolog open the log records */
+       autolog_open_check(server, target, level);
+
+       if (logs == NULL)
+               return;
+
+       /* text may contain one or more lines, log wants to eat them one
+          line at a time */
+       lines = g_strsplit(text, "\n", -1);
+       for (tmp = lines; *tmp != NULL; tmp++)
+               log_single_line(window, server, target, level, *tmp);
+       g_strfreev(lines);
+}
+
+static void sig_printtext_stripped(TEXT_DEST_REC *dest, const char *text)
+{
+       if (skip_next_printtext) {
+               skip_next_printtext = FALSE;
+               return;
+       }
+
+       log_line(dest->window, dest->server, dest->target,
+                dest->level, text);
+}
+
+static void sig_print_format(THEME_REC *theme, const char *module,
+                            TEXT_DEST_REC *dest, void *formatnum, char **args)
+{
+       char *str, *stripped, *linestart, *tmp;
+
+       if (log_theme == NULL) {
+               /* theme isn't loaded for some reason (/reload destroys it),
+                  reload it. */
+               log_theme = theme_load(log_theme_name);
+               if (log_theme == NULL) return;
+       }
+
+       if (theme == log_theme)
+               return;
+
+       str = format_get_text_theme_charargs(log_theme, module, dest,
+                                            GPOINTER_TO_INT(formatnum), args);
+       skip_next_printtext = TRUE;
+
+       if (*str != '\0') {
+                /* add the line start format */
+               linestart = format_get_level_tag(log_theme, dest);
+                tmp = str;
+               str = format_add_linestart(tmp, linestart);
+               g_free_not_null(linestart);
+               g_free(tmp);
+
+               /* strip colors from text, log it. */
+               stripped = strip_codes(str);
+               log_line(dest->window, dest->server, dest->target,
+                        dest->level, stripped);
+               g_free(stripped);
+       }
+       g_free(str);
+
+}
+
+static int sig_autoremove(void)
+{
+       SERVER_REC *server;
+       LOG_ITEM_REC *logitem;
+       GSList *tmp, *next;
+       time_t removetime;
+
+        removetime = time(NULL)-AUTOLOG_INACTIVITY_CLOSE;
+       for (tmp = logs; tmp != NULL; tmp = next) {
+               LOG_REC *log = tmp->data;
+
+               next = tmp->next;
+
+               if (!log->temp || log->last > removetime || log->items == NULL)
+                        continue;
+
+               /* Close only logs with private messages */
+               logitem = log->items->data;
+               if (logitem->servertag == NULL)
+                       continue;
+
+               server = server_find_tag(logitem->servertag);
+               if (logitem->type == LOG_ITEM_TARGET &&
+                   server != NULL && !server->ischannel(logitem->name))
+                       log_close(log);
+       }
+       return 1;
+}
+
+static void sig_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+       LOG_REC *log;
+
+       log = logs_find_item(LOG_ITEM_TARGET, item->name,
+                            item->server == NULL ? NULL :
+                            item->server->tag, NULL);
+       if (log != NULL && log->temp)
+               log_close(log);
+}
+
+static void sig_log_locked(LOG_REC *log)
+{
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_LOG_LOCKED, log->fname);
+}
+
+static void sig_log_create_failed(LOG_REC *log)
+{
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_LOG_CREATE_FAILED, log->fname, g_strerror(errno));
+}
+
+static void sig_awaylog_show(LOG_REC *log, gpointer pmsgs, gpointer pfilepos)
+{
+       char *str;
+       int msgs, filepos;
+
+       msgs = GPOINTER_TO_INT(pmsgs);
+       filepos = GPOINTER_TO_INT(pfilepos);
+
+       if (msgs == 0)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_NO_AWAY_MSGS, log->fname);
+       else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_AWAY_MSGS, log->fname, msgs);
+
+                str = g_strdup_printf("\"%s\" %d", log->fname, filepos);
+               signal_emit("command cat", 1, str);
+               g_free(str);
+       }
+}
+
+static void sig_theme_destroyed(THEME_REC *theme)
+{
+       if (theme == log_theme)
+               log_theme = NULL;
+}
+
+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();
+
+       /* write to log files with different theme? */
+       if (log_theme_name != NULL)
+               signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+       log_theme_name = settings_get_str("log_theme");
+       if (*log_theme_name == '\0')
+               log_theme_name = NULL;
+       else
+               signal_add("print format", (SIGNAL_FUNC) sig_print_format);
+
+       log_theme = log_theme_name == NULL ? NULL :
+               theme_load(log_theme_name);
+}
+
+void fe_log_init(void)
+{
+       autoremove_tag = g_timeout_add(60000, (GSourceFunc) sig_autoremove, NULL);
+       skip_next_printtext = FALSE;
+
+        settings_add_str("log", "autolog_path", "~/irclogs/$tag/$0.log");
+       settings_add_str("log", "autolog_level", "all -crap -clientcrap -ctcps");
+        settings_add_bool("log", "autolog", FALSE);
+        settings_add_str("log", "log_theme", "");
+
+       autolog_level = 0;
+       log_theme_name = NULL;
+       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("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 destroy", (SIGNAL_FUNC) sig_window_item_destroy);
+       signal_add("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+       signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+       signal_add("log locked", (SIGNAL_FUNC) sig_log_locked);
+       signal_add("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+       signal_add("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+       signal_add("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_set_options("log open", "noopen autoopen -targets window");
+}
+
+void fe_log_deinit(void)
+{
+       g_source_remove(autoremove_tag);
+       if (log_theme_name != NULL)
+                signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+
+       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("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 destroy", (SIGNAL_FUNC) sig_window_item_destroy);
+       signal_remove("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+       signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+       signal_remove("log locked", (SIGNAL_FUNC) sig_log_locked);
+       signal_remove("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+       signal_remove("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+       signal_remove("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-messages.c b/apps/irssi/src/fe-common/core/fe-messages.c
new file mode 100644 (file)
index 0000000..fa5e88a
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ fe-messages.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 "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "window-items.h"
+#include "fe-queries.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "hilight-text.h"
+#include "ignore.h"
+#include "printtext.h"
+
+#define ishighalnum(c) ((unsigned char) (c) >= 128 || isalnum(c))
+
+static GHashTable *printnicks;
+
+/* convert _underlined_ and *bold* words (and phrases) to use real
+   underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text)
+{
+       GString *str;
+       char *ret;
+       int pos;
+
+        g_return_val_if_fail(text != NULL, NULL);
+
+       str = g_string_new(text);
+
+       for (pos = 0; pos < str->len; pos++) {
+               char type, *bgn, *end;
+
+               bgn = str->str + pos;
+
+               if (*bgn == '*') 
+                       type = 2; /* bold */
+               else if (*bgn == '_') 
+                       type = 31; /* underlined */
+               else
+                       continue;
+
+               /* check that the beginning marker starts a word, and
+                  that the matching end marker ends a word */
+               if ((pos > 0 && !isspace(bgn[-1])) || !ishighalnum(bgn[1]))
+                       continue;
+               if ((end = strchr(bgn+1, *bgn)) == NULL)
+                       continue;
+               if (!ishighalnum(end[-1]) || ishighalnum(end[1]) ||
+                   end[1] == type || end[1] == '*' || end[1] == '_')
+                       continue;
+
+               if (IS_CHANNEL(item)) {
+                       /* check that this isn't a _nick_, we don't want to
+                          use emphasis on them. */
+                       int found;
+                        char c;
+
+                       c = end[1];
+                        end[1] = '\0';
+                        found = nicklist_find(CHANNEL(item), bgn) != NULL;
+                       end[1] = c;
+                       if (found) continue;
+               }
+
+               /* allow only *word* emphasis, not *multiple words* */
+               if (!settings_get_bool("emphasis_multiword")) {
+                       char *c;
+                       for (c = bgn+1; c != end; c++) {
+                               if (!ishighalnum(*c))
+                                       break;
+                       }
+                       if (c != end) continue;
+               }
+
+               if (settings_get_bool("emphasis_replace")) {
+                       *bgn = *end = type;
+                        pos += (end-bgn);
+               } else {
+                       g_string_insert_c(str, pos, type);
+                        pos += (end - bgn) + 2;
+                       g_string_insert_c(str, pos++, type);
+               }
+       }
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+       return ret;
+}
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick)
+{
+        NICK_REC *nickrec;
+        char *emptystr;
+
+       g_return_val_if_fail(nick != NULL, NULL);
+
+       if (!settings_get_bool("show_nickmode"))
+                return "";
+
+        emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
+
+       nickrec = channel == NULL ? NULL :
+               nicklist_find(channel, nick);
+       return nickrec == NULL ? emptystr :
+               (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr));
+}
+
+static char *channel_get_nickmode_rec(NICK_REC *nickrec)
+{
+        char *emptystr;
+
+       if (!settings_get_bool("show_nickmode"))
+                return "";
+
+        emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
+
+       return nickrec == NULL ? emptystr :
+               (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr));
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+                              const char *nick, const char *address,
+                              const char *target, NICK_REC *nickrec)
+{
+       CHANNEL_REC *chanrec;
+       const char *nickmode, *printnick;
+       int for_me, print_channel, level;
+       char *color, *freemsg = NULL;
+
+       /* NOTE: this may return NULL if some channel is just closed with
+          /WINDOW CLOSE and server still sends the few last messages */
+       chanrec = channel_find(server, target);
+       if (nickrec == NULL && chanrec != NULL)
+                nickrec = nicklist_find(chanrec, nick);
+
+       for_me = nick_match_msg(chanrec, msg, server->nick);
+       color = for_me ? NULL :
+               hilight_match_nick(server, target, nick, address, MSGLEVEL_PUBLIC, msg);
+
+       print_channel = chanrec == NULL ||
+               !window_item_is_active((WI_ITEM_REC *) chanrec);
+       if (!print_channel && settings_get_bool("print_active_channel") &&
+           window_item_window((WI_ITEM_REC *) chanrec)->items->next != NULL)
+               print_channel = TRUE;
+
+       level = MSGLEVEL_PUBLIC | (for_me || color != NULL ?
+                                  MSGLEVEL_HILIGHT : MSGLEVEL_NOHILIGHT);
+
+       if (settings_get_bool("emphasis"))
+               msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg);
+
+       /* get nick mode & nick what to print the msg with
+          (in case there's multiple identical nicks) */
+       nickmode = channel_get_nickmode_rec(nickrec);
+       printnick = nickrec == NULL ? nick :
+               g_hash_table_lookup(printnicks, nickrec);
+       if (printnick == NULL)
+               printnick = nick;
+
+       if (!print_channel) {
+               /* message to active channel in window */
+               if (color != NULL) {
+                       /* highlighted nick */
+                       printformat(server, target, level,
+                                   TXT_PUBMSG_HILIGHT,
+                                   color, printnick, msg, nickmode);
+               } else {
+                       printformat(server, target, level,
+                                   for_me ? TXT_PUBMSG_ME : TXT_PUBMSG,
+                                   printnick, msg, nickmode);
+               }
+       } else {
+               /* message to not existing/active channel */
+               if (color != NULL) {
+                       /* highlighted nick */
+                       printformat(server, target, level,
+                                   TXT_PUBMSG_HILIGHT_CHANNEL,
+                                   color, printnick, target, msg, nickmode);
+               } else {
+                       printformat(server, target, level,
+                                   for_me ? TXT_PUBMSG_ME_CHANNEL :
+                                   TXT_PUBMSG_CHANNEL,
+                                   printnick, target, msg, nickmode);
+               }
+       }
+
+        g_free_not_null(freemsg);
+       g_free_not_null(color);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+                               const char *nick, const char *address)
+{
+       QUERY_REC *query;
+        char *freemsg = NULL;
+
+       query = query_find(server, nick);
+
+       if (settings_get_bool("emphasis"))
+               msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+       printformat(server, nick, MSGLEVEL_MSGS,
+                   query == NULL ? TXT_MSG_PRIVATE :
+                   TXT_MSG_PRIVATE_QUERY, nick, address, msg);
+
+       g_free_not_null(freemsg);
+}
+
+static void print_own_channel_message(SERVER_REC *server, CHANNEL_REC *channel,
+                                     const char *target, const char *msg)
+{
+       WINDOW_REC *window;
+       const char *nickmode;
+        char *freemsg = NULL;
+       int print_channel;
+
+       nickmode = channel_get_nickmode(channel, server->nick);
+
+       window = channel == NULL ? NULL :
+               window_item_window((WI_ITEM_REC *) channel);
+
+       print_channel = window == NULL ||
+               window->active != (WI_ITEM_REC *) channel;
+
+       if (!print_channel && settings_get_bool("print_active_channel") &&
+           window != NULL && g_slist_length(window->items) > 1)
+               print_channel = TRUE;
+
+       if (settings_get_bool("emphasis"))
+               msg = freemsg = expand_emphasis((WI_ITEM_REC *) channel, msg);
+
+       if (!print_channel) {
+               printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+                           TXT_OWN_MSG, server->nick, msg, nickmode);
+       } else {
+               printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+                           TXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode);
+       }
+
+       g_free_not_null(freemsg);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+                                  const char *target)
+{
+       CHANNEL_REC *channel;
+
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(msg != NULL);
+
+       channel = channel_find(server, target);
+       print_own_channel_message(server, channel, target, msg);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+                                   const char *target, const char *origtarget)
+{
+       QUERY_REC *query;
+        char *freemsg = NULL;
+
+       g_return_if_fail(server != NULL);
+       g_return_if_fail(msg != NULL);
+
+       if (target == NULL) {
+               /* this should only happen if some special target failed and
+                  we should display some error message. currently the special
+                  targets are only ',' and '.'. */
+               g_return_if_fail(strcmp(origtarget, ",") == 0 ||
+                                strcmp(origtarget, ".") == 0);
+
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           *origtarget == ',' ? TXT_NO_MSGS_GOT :
+                           TXT_NO_MSGS_SENT);
+               signal_stop();
+               return;
+       }
+
+       query = privmsg_get_query(server, target, TRUE, MSGLEVEL_MSGS);
+
+       if (settings_get_bool("emphasis"))
+               msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+       printformat(server, target,
+                   MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+                   query == NULL ? TXT_OWN_MSG_PRIVATE :
+                   TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick);
+
+       g_free_not_null(freemsg);
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *address)
+{
+       printformat(server, channel, MSGLEVEL_JOINS,
+                   TXT_JOIN, nick, address, channel);
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *address,
+                            const char *reason)
+{
+       printformat(server, channel, MSGLEVEL_PARTS,
+                   TXT_PART, nick, address, channel, reason);
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+                            const char *address, const char *reason)
+{
+       WINDOW_REC *window;
+       GString *chans;
+       GSList *tmp, *windows;
+       char *print_channel;
+       int once, count;
+
+       if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
+               return;
+
+       print_channel = NULL;
+       once = settings_get_bool("show_quit_once");
+
+       count = 0; windows = NULL;
+       chans = g_string_new(NULL);
+       for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *rec = tmp->data;
+
+               if (!nicklist_find(rec, nick))
+                       continue;
+
+               if (ignore_check(server, nick, address, rec->name,
+                                reason, MSGLEVEL_QUITS)) {
+                       count++;
+                       continue;
+               }
+
+               if (print_channel == NULL ||
+                   active_win->active == (WI_ITEM_REC *) rec)
+                       print_channel = rec->name;
+
+               if (once)
+                       g_string_sprintfa(chans, "%s,", rec->name);
+               else {
+                       window = window_item_window((WI_ITEM_REC *) rec);
+                       if (g_slist_find(windows, window) == NULL) {
+                               windows = g_slist_append(windows, window);
+                               printformat(server, rec->name, MSGLEVEL_QUITS,
+                                           TXT_QUIT, nick, address, reason,
+                                           rec->name);
+                       }
+               }
+               count++;
+       }
+       g_slist_free(windows);
+
+       if (!once) {
+               /* check if you had query with the nick and
+                  display the quit there too */
+               QUERY_REC *query = query_find(server, nick);
+               if (query != NULL) {
+                       printformat(server, nick, MSGLEVEL_QUITS,
+                                   TXT_QUIT, nick, address, reason, "");
+               }
+       }
+
+       if (once || count == 0) {
+               if (chans->len > 0)
+                       g_string_truncate(chans, chans->len-1);
+               printformat(server, print_channel, MSGLEVEL_QUITS,
+                           count <= 1 ? TXT_QUIT : TXT_QUIT_ONCE,
+                           nick, address, reason, chans->str);
+       }
+       g_string_free(chans, TRUE);
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+                            const char *nick, const char *kicker,
+                            const char *address, const char *reason)
+{
+       printformat(server, channel, MSGLEVEL_KICKS,
+                   TXT_KICK, nick, channel, kicker, reason);
+}
+
+static void print_nick_change_channel(SERVER_REC *server, const char *channel,
+                                     const char *newnick, const char *oldnick,
+                                     const char *address,
+                                     int ownnick)
+{
+       if (ignore_check(server, oldnick, address,
+                        channel, newnick, MSGLEVEL_NICKS))
+               return;
+
+       printformat(server, channel, MSGLEVEL_NICKS,
+                   ownnick ? TXT_YOUR_NICK_CHANGED : TXT_NICK_CHANGED,
+                   oldnick, newnick, channel);
+}
+
+static void print_nick_change(SERVER_REC *server, const char *newnick,
+                             const char *oldnick, const char *address,
+                             int ownnick)
+{
+       GSList *tmp, *windows;
+       int msgprint;
+
+       msgprint = FALSE;
+
+       /* Print to each channel/query where the nick is.
+          Don't print more than once to the same window. */
+       windows = NULL;
+       for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+               CHANNEL_REC *channel = tmp->data;
+               WINDOW_REC *window =
+                       window_item_window((WI_ITEM_REC *) channel);
+
+               if (nicklist_find(channel, newnick) == NULL ||
+                   g_slist_find(windows, window) != NULL)
+                       continue;
+
+               windows = g_slist_append(windows, window);
+               print_nick_change_channel(server, channel->name, newnick,
+                                         oldnick, address, ownnick);
+               msgprint = TRUE;
+       }
+
+       for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+               QUERY_REC *query = tmp->data;
+               WINDOW_REC *window =
+                       window_item_window((WI_ITEM_REC *) query);
+
+               if (g_strcasecmp(query->name, oldnick) != 0 ||
+                   g_slist_find(windows, window) != NULL)
+                       continue;
+
+               windows = g_slist_append(windows, window);
+               print_nick_change_channel(server, query->name, newnick,
+                                         oldnick, address, ownnick);
+               msgprint = TRUE;
+       }
+       g_slist_free(windows);
+
+       if (!msgprint && ownnick) {
+               printformat(server, NULL, MSGLEVEL_NICKS,
+                           TXT_YOUR_NICK_CHANGED, oldnick, newnick, "");
+       }
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+                            const char *oldnick, const char *address)
+{
+       print_nick_change(server, newnick, oldnick, address, FALSE);
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+                                const char *oldnick, const char *address)
+{
+       print_nick_change(server, newnick, oldnick, address, TRUE);
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+                              const char *nick, const char *address)
+{
+       char *str;
+
+       str = show_lowascii(channel);
+       printformat(server, NULL, MSGLEVEL_INVITES,
+                   TXT_INVITE, nick, str);
+       g_free(str);
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+                             const char *topic,
+                             const char *nick, const char *address)
+{
+       printformat(server, channel, MSGLEVEL_TOPICS,
+                   *topic != '\0' ? TXT_NEW_TOPIC : TXT_TOPIC_UNSET,
+                   nick, channel, topic);
+}
+
+static int printnick_exists(NICK_REC *first, NICK_REC *ignore,
+                           const char *nick)
+{
+       char *printnick;
+
+       while (first != NULL) {
+               if (first != ignore) {
+                       printnick = g_hash_table_lookup(printnicks, first);
+                       if (printnick != NULL && strcmp(printnick, nick) == 0)
+                               return TRUE;
+               }
+
+               first = first->next;
+       }
+
+        return FALSE;
+}
+
+static NICK_REC *printnick_find_original(NICK_REC *nick)
+{
+       while (nick != NULL) {
+               if (g_hash_table_lookup(printnicks, nick) == NULL)
+                        return nick;
+
+               nick = nick->next;
+       }
+
+       return NULL;
+}
+
+static void sig_nicklist_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       NICK_REC *firstnick;
+       GString *newnick;
+       char *nickhost, *p;
+       int n;
+
+       if (nick->host == NULL)
+                return;
+
+       firstnick = g_hash_table_lookup(channel->nicks, nick->nick);
+       if (firstnick->next == NULL)
+               return;
+
+       if (nick == channel->ownnick) {
+               /* own nick is being added, might be a nick change and
+                  someone else having the original nick already in use.. */
+               nick = printnick_find_original(firstnick->next);
+               if (nick == NULL)
+                        return; /* nope, we have it */
+       }
+
+       /* identical nick already exists, have to change it somehow.. */
+       p = strchr(nick->host, '@');
+       if (p == NULL) p = nick->host; else p++;
+
+       nickhost = g_strdup_printf("%s@%s", nick->nick, p);
+       p = strchr(nickhost+strlen(nick->nick), '.');
+       if (p != NULL) *p = '\0';
+
+       if (!printnick_exists(firstnick, nick, nickhost)) {
+                /* use nick@host */
+               g_hash_table_insert(printnicks, nick, nickhost);
+                return;
+       }
+
+       newnick = g_string_new(NULL);
+        n = 2;
+       do {
+               g_string_sprintf(newnick, "%s%d", nickhost, n);
+                n++;
+       } while (printnick_exists(firstnick, nick, newnick->str));
+
+       g_hash_table_insert(printnicks, nick, newnick->str);
+       g_string_free(newnick, FALSE);
+        g_free(nickhost);
+}
+
+static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+       char *nickname;
+
+       nickname = g_hash_table_lookup(printnicks, nick);
+       if (nickname != NULL) {
+                g_free(nickname);
+               g_hash_table_remove(printnicks, nick);
+       }
+}
+
+static void sig_nicklist_changed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+        sig_nicklist_remove(channel, nick);
+        sig_nicklist_new(channel, nick);
+}
+
+static void sig_channel_joined(CHANNEL_REC *channel)
+{
+        NICK_REC *nick;
+       char *nickname;
+
+       /* channel->ownnick is set at this point - check if our own nick
+          has been changed, if it was set it back to the original nick and
+          change the previous original to something else */
+
+        nickname = g_hash_table_lookup(printnicks, channel->ownnick);
+       if (nickname == NULL)
+               return;
+
+        g_free(nickname);
+       g_hash_table_remove(printnicks, channel->ownnick);
+
+        /* our own nick is guaranteed to be the first in list */
+        nick = channel->ownnick->next;
+       while (nick != NULL) {
+               if (g_hash_table_lookup(printnicks, nick) == NULL) {
+                       sig_nicklist_new(channel, nick);
+                        break;
+               }
+                nick = nick->next;
+       }
+}
+
+static void g_hash_free_value(void *key, void *value)
+{
+        g_free(value);
+}
+
+void fe_messages_init(void)
+{
+       printnicks = g_hash_table_new((GHashFunc) g_direct_hash,
+                                     (GCompareFunc) g_direct_equal);
+
+       settings_add_bool("lookandfeel", "emphasis", TRUE);
+       settings_add_bool("lookandfeel", "emphasis_replace", FALSE);
+       settings_add_bool("lookandfeel", "emphasis_multiword", FALSE);
+       settings_add_bool("lookandfeel", "show_nickmode", TRUE);
+       settings_add_bool("lookandfeel", "show_nickmode_empty", TRUE);
+       settings_add_bool("lookandfeel", "print_active_channel", FALSE);
+       settings_add_bool("lookandfeel", "show_quit_once", FALSE);
+
+       signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+       signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_add("message join", (SIGNAL_FUNC) sig_message_join);
+       signal_add("message part", (SIGNAL_FUNC) sig_message_part);
+       signal_add("message quit", (SIGNAL_FUNC) sig_message_quit);
+       signal_add("message kick", (SIGNAL_FUNC) sig_message_kick);
+       signal_add("message nick", (SIGNAL_FUNC) sig_message_nick);
+       signal_add("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+       signal_add("message invite", (SIGNAL_FUNC) sig_message_invite);
+       signal_add("message topic", (SIGNAL_FUNC) sig_message_topic);
+
+       signal_add("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+       signal_add("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+       signal_add("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+       signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+       signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
+
+void fe_messages_deinit(void)
+{
+        g_hash_table_foreach(printnicks, (GHFunc) g_hash_free_value, NULL);
+       g_hash_table_destroy(printnicks);
+
+       signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+       signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+       signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+       signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+       signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+       signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+       signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+       signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+       signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+       signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+       signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+
+       signal_remove("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+       signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+       signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+       signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+       signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-messages.h b/apps/irssi/src/fe-common/core/fe-messages.h
new file mode 100644 (file)
index 0000000..afe7644
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __FE_MESSAGES_H
+#define __FE_MESSAGES_H
+
+/* convert _underlined_ and *bold* words (and phrases) to use real
+   underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text);
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/fe-modules.c b/apps/irssi/src/fe-common/core/fe-modules.c
new file mode 100644 (file)
index 0000000..e351dc7
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ 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 "modules.h"
+#include "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "chat-protocols.h"
+
+#include "printtext.h"
+
+static void sig_module_error(void *number, const char *module,
+                            const char *data)
+{
+       switch (GPOINTER_TO_INT(number)) {
+       case MODULE_ERROR_ALREADY_LOADED:
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_MODULE_ALREADY_LOADED, module);
+               break;
+       case MODULE_ERROR_LOAD:
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_MODULE_LOAD_ERROR, module, data);
+               break;
+       case MODULE_ERROR_INVALID:
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_MODULE_INVALID, module);
+               break;
+       }
+}
+
+static void sig_module_loaded(MODULE_REC *rec)
+{
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_MODULE_LOADED, rec->name);
+}
+
+static void sig_module_unloaded(MODULE_REC *rec)
+{
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_MODULE_UNLOADED, rec->name);
+}
+
+static void cmd_load_list(void)
+{
+       GSList *tmp;
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_HEADER);
+       for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+               MODULE_REC *rec = tmp->data;
+
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           TXT_MODULE_LINE, rec->name);
+       }
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_FOOTER);
+}
+
+static char **module_prefixes_get(void)
+{
+        GSList *tmp;
+        char **list, *name;
+        int count;
+
+       list = g_new(char *, 2 + 2*g_slist_length(chat_protocols));
+       list[0] = "fe";
+
+       count = 1;
+       for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+               CHAT_PROTOCOL_REC *rec = tmp->data;
+
+               name = g_strdup(rec->name);
+                g_strdown(name);
+
+               list[count++] = name;
+                list[count++] = g_strconcat("fe_", name, NULL);
+       }
+       list[count] = NULL;
+
+        return list;
+}
+
+static void module_prefixes_free(char **list)
+{
+       char **pos = list+1;
+
+       while (*pos != NULL) {
+                g_free(*pos);
+                pos++;
+       }
+        g_free(list);
+}
+
+/* SYNTAX: LOAD <module> */
+static void cmd_load(const char *data)
+{
+#ifdef HAVE_GMODULE
+       char **module_prefixes;
+
+       g_return_if_fail(data != NULL);
+       if (*data == '\0')
+               cmd_load_list();
+       else {
+               module_prefixes = module_prefixes_get();
+               module_load(data, module_prefixes);
+                module_prefixes_free(module_prefixes);
+       }
+#else
+       printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                 "Dynamic modules loading not supported");
+#endif
+}
+
+/* SYNTAX: UNLOAD <module> */
+static void cmd_unload(const char *data)
+{
+       MODULE_REC *rec;
+
+       g_return_if_fail(data != NULL);
+       if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       rec = module_find(data);
+       if (rec != NULL) module_unload(rec);
+}
+
+void fe_modules_init(void)
+{
+       signal_add("module error", (SIGNAL_FUNC) sig_module_error);
+       signal_add("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+       signal_add("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+       command_bind("load", NULL, (SIGNAL_FUNC) cmd_load);
+       command_bind("unload", NULL, (SIGNAL_FUNC) cmd_unload);
+}
+
+void fe_modules_deinit(void)
+{
+       signal_remove("module error", (SIGNAL_FUNC) sig_module_error);
+       signal_remove("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+       signal_remove("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+       command_unbind("load", (SIGNAL_FUNC) cmd_load);
+       command_unbind("unload", (SIGNAL_FUNC) cmd_unload);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-queries.c b/apps/irssi/src/fe-common/core/fe-queries.c
new file mode 100644 (file)
index 0000000..1924464
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ fe-queries.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 "settings.h"
+
+#include "chat-protocols.h"
+#include "queries.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "printtext.h"
+
+static int queryclose_tag, query_auto_close, querycreate_level;
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+                            int own, int level)
+{
+       QUERY_REC *query;
+
+       g_return_val_if_fail(IS_SERVER(server), NULL);
+        g_return_val_if_fail(nick != NULL, NULL);
+
+       query = query_find(server, nick);
+       if (query == NULL && (querycreate_level & level) != 0 &&
+           (!own || settings_get_bool("autocreate_own_query"))) {
+               query = CHAT_PROTOCOL(server)->
+                       query_create(server->tag, nick, TRUE);
+       }
+
+       return query;
+}
+
+static void signal_query_created(QUERY_REC *query, gpointer automatic)
+{
+       g_return_if_fail(IS_QUERY(query));
+
+       if (window_item_window(query) == NULL) {
+               window_item_create((WI_ITEM_REC *) query,
+                                  GPOINTER_TO_INT(automatic));
+               printformat(query->server, query->name, MSGLEVEL_CLIENTNOTICE,
+                           TXT_QUERY_STARTED, query->name);
+       }
+}
+
+static void signal_query_created_curwin(QUERY_REC *query)
+{
+       g_return_if_fail(IS_QUERY(query));
+
+       window_item_add(active_win, (WI_ITEM_REC *) query, FALSE);
+}
+
+static void signal_query_destroyed(QUERY_REC *query)
+{
+       WINDOW_REC *window;
+
+       g_return_if_fail(IS_QUERY(query));
+
+       window = window_item_window((WI_ITEM_REC *) query);
+       if (window != NULL) {
+               window_item_destroy((WI_ITEM_REC *) query);
+
+               if (!query->unwanted)
+                       window_auto_destroy(window);
+       }
+}
+
+static void signal_query_server_changed(QUERY_REC *query)
+{
+       WINDOW_REC *window;
+
+       g_return_if_fail(query != NULL);
+
+       window = window_item_window((WI_ITEM_REC *) query);
+       if (window->active == (WI_ITEM_REC *) query)
+               window_change_server(window, query->server);
+}
+
+static void signal_query_nick_changed(QUERY_REC *query, const char *oldnick)
+{
+       g_return_if_fail(query != NULL);
+
+       signal_emit("window item changed", 2,
+                   window_item_window((WI_ITEM_REC *) query), query);
+}
+
+static void signal_window_item_server_changed(WINDOW_REC *window,
+                                             QUERY_REC *query)
+{
+       if (IS_QUERY(query)) {
+               g_free_and_null(query->server_tag);
+                if (query->server != NULL)
+                       query->server_tag = g_strdup(query->server->tag);
+       }
+}
+
+static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+       QUERY_REC *query;
+
+       g_return_if_fail(window != NULL);
+
+       query = QUERY(item);
+       if (query != NULL) query_destroy(query);
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+       GSList *tmp;
+
+       if (!IS_SERVER(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 &&
+                   (rec->server_tag == 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;
+        QUERY_REC *query;
+
+       g_return_if_fail(data != NULL);
+
+       server = server_find_tag(data);
+        query = QUERY(active_win->active);
+       if (server == NULL || query == NULL)
+               return;
+
+       /* /WINDOW SERVER used in a query window */
+       query_change_server(query, server);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_QUERY_SERVER_CHANGED,
+                   query->name, server->tag);
+       signal_stop();
+}
+
+/* SYNTAX: UNQUERY [<nick>] */
+static void cmd_unquery(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       QUERY_REC *query;
+
+       g_return_if_fail(data != NULL);
+
+       if (*data == '\0') {
+               /* remove current query */
+               query = QUERY(item);
+               if (query == NULL) return;
+       } else {
+               query = query_find(server, data);
+               if (query == NULL) {
+                       printformat(server, NULL, MSGLEVEL_CLIENTERROR,
+                                   TXT_NO_QUERY, data);
+                       return;
+               }
+       }
+
+       query_destroy(query);
+}
+
+/* SYNTAX: QUERY [-window] [-<server tag>] <nick> [<message>] */
+static void cmd_query(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       GHashTable *optlist;
+       QUERY_REC *query;
+       char *nick, *msg;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+
+       if (*data == '\0') {
+               /* remove current query */
+               cmd_unquery("", server, item);
+               return;
+       }
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+                           PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS,
+                           "query", &optlist, &nick, &msg))
+               return;
+       if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       server = cmd_options_get_server("query", optlist, server);
+       if (server == NULL) {
+               cmd_params_free(free_arg);
+                return;
+       }
+
+       if (*nick != '=' && (server == NULL || !server->connected))
+               cmd_param_error(CMDERR_NOT_CONNECTED);
+
+       if (g_hash_table_lookup(optlist, "window") != NULL) {
+               signal_add("query created",
+                          (SIGNAL_FUNC) signal_query_created_curwin);
+       }
+
+       query = query_find(server, nick);
+       if (query == NULL)
+               CHAT_PROTOCOL(server)->query_create(server->tag, nick, FALSE);
+       else {
+               /* query already exists */
+               WINDOW_REC *window = window_item_window(query);
+
+               if (window == active_win) {
+                        /* query is in active window, set it active */
+                       window_item_set_active(active_win,
+                                              (WI_ITEM_REC *) query);
+               } else {
+                       /* notify user how to move the query to active
+                          window. this was used to be done automatically
+                          but it just confused everyone who did it
+                          accidentally */
+                       printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                          TXT_QUERY_MOVE_NOTIFY, query->name,
+                                          window->refnum);
+               }
+       }
+
+       if (g_hash_table_lookup(optlist, "window") != NULL) {
+               signal_remove("query created",
+                             (SIGNAL_FUNC) signal_query_created_curwin);
+       }
+
+       if (*msg != '\0') {
+               /* FIXME: we'll need some function that does both
+                  of these. and separate the , and . target handling
+                  from own_private messagge.. */
+               server->send_message(server, nick, msg);
+
+               signal_emit("message own_private", 4,
+                           server, msg, nick, nick);
+       }
+
+       cmd_params_free(free_arg);
+}
+
+static int window_has_query(WINDOW_REC *window)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(window != NULL, FALSE);
+
+       for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+               if (IS_QUERY(tmp->data))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void sig_window_changed(WINDOW_REC *window, WINDOW_REC *old_window)
+{
+       if (query_auto_close <= 0)
+               return;
+
+       /* reset the window's last_line timestamp so that query doesn't get
+          closed immediately after switched to the window, or after changed
+          to some other window from it */
+       if (window != NULL && window_has_query(window))
+               window->last_line = time(NULL);
+       if (old_window != NULL && window_has_query(old_window))
+               old_window->last_line = time(NULL);
+}
+
+static int sig_query_autoclose(void)
+{
+       WINDOW_REC *window;
+       GSList *tmp, *next;
+       time_t now;
+
+       now = time(NULL);
+       for (tmp = queries; tmp != NULL; tmp = next) {
+               QUERY_REC *rec = tmp->data;
+
+               next = tmp->next;
+               window = window_item_window((WI_ITEM_REC *) rec);
+               if (window != active_win && rec->data_level == 0 &&
+                   now-window->last_line > query_auto_close)
+                       query_destroy(rec);
+       }
+        return 1;
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+                               const char *nick, const char *address)
+{
+       /* create query window if needed */
+       privmsg_get_query(server, nick, FALSE, MSGLEVEL_MSGS);
+}
+
+static void read_settings(void)
+{
+       querycreate_level = level2bits(settings_get_str("autocreate_query_level"));
+       query_auto_close = settings_get_int("autoclose_query");
+       if (query_auto_close > 0 && queryclose_tag == -1)
+               queryclose_tag = g_timeout_add(5000, (GSourceFunc) sig_query_autoclose, NULL);
+       else if (query_auto_close <= 0 && queryclose_tag != -1) {
+               g_source_remove(queryclose_tag);
+               queryclose_tag = -1;
+       }
+}
+
+void fe_queries_init(void)
+{
+       settings_add_str("lookandfeel", "autocreate_query_level", "MSGS DCCMSGS");
+       settings_add_bool("lookandfeel", "autocreate_own_query", TRUE);
+       settings_add_int("lookandfeel", "autoclose_query", 0);
+
+       queryclose_tag = -1;
+       read_settings();
+
+       signal_add("query created", (SIGNAL_FUNC) signal_query_created);
+       signal_add("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+       signal_add("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+       signal_add("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+        signal_add("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+       signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+       signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+       signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
+       signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_bind("query", NULL, (SIGNAL_FUNC) cmd_query);
+       command_bind("unquery", NULL, (SIGNAL_FUNC) cmd_unquery);
+       command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+
+       command_set_options("query", "window");
+}
+
+void fe_queries_deinit(void)
+{
+       if (queryclose_tag != -1) g_source_remove(queryclose_tag);
+
+       signal_remove("query created", (SIGNAL_FUNC) signal_query_created);
+       signal_remove("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+       signal_remove("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+       signal_remove("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+        signal_remove("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+       signal_remove("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+       signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+       signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
+       signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_unbind("query", (SIGNAL_FUNC) cmd_query);
+       command_unbind("unquery", (SIGNAL_FUNC) cmd_unquery);
+       command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-queries.h b/apps/irssi/src/fe-common/core/fe-queries.h
new file mode 100644 (file)
index 0000000..6db9cb4
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __FE_QUERIES_H
+#define __FE_QUERIES_H
+
+#include "queries.h"
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+                            int own, int level);
+
+void fe_queries_init(void);
+void fe_queries_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/fe-server.c b/apps/irssi/src/fe-common/core/fe-server.c
new file mode 100644 (file)
index 0000000..f2327c5
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ fe-server.c : irssi
+
+    Copyright (C) 1999-2001 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 "levels.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+
+#include "module-formats.h"
+#include "printtext.h"
+
+static void print_servers(void)
+{
+       GSList *tmp;
+
+       for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *rec = tmp->data;
+
+               printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LIST,
+                           rec->tag, rec->connrec->address, rec->connrec->port,
+                           rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick);
+       }
+}
+
+static void print_lookup_servers(void)
+{
+       GSList *tmp;
+       for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+               SERVER_REC *rec = tmp->data;
+
+               printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LOOKUP_LIST,
+                           rec->tag, rec->connrec->address, rec->connrec->port,
+                           rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, 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;
+               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, TXT_SERVER_RECONNECT_LIST,
+                           tag, conn->address, conn->port,
+                           conn->chatnet == NULL ? "" : conn->chatnet,
+                           conn->nick, next_connect);
+               g_free(next_connect);
+               g_free(tag);
+       }
+}
+
+static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist)
+{
+       CHAT_PROTOCOL_REC *rec;
+        SERVER_SETUP_REC *server;
+        char *chatnet;
+
+       rec = chat_protocol_find_net(optlist);
+       if (rec == NULL)
+                rec = chat_protocol_get_default();
+       else {
+               chatnet = g_hash_table_lookup(optlist, rec->chatnet);
+               if (chatnet_find(chatnet) == NULL) {
+                       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                   TXT_UNKNOWN_CHATNET, chatnet);
+                       return NULL;
+               }
+       }
+
+        server = rec->create_server_setup();
+        server->chat_type = rec->id;
+       return server;
+}
+
+static void cmd_server_add(const char *data)
+{
+        GHashTable *optlist;
+       SERVER_SETUP_REC *rec;
+       char *addr, *portstr, *password, *value;
+       void *free_arg;
+       int port;
+
+       if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+                           "server add", &optlist, &addr, &portstr, &password))
+               return;
+
+       if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+       port = *portstr == '\0' ? 6667 : atoi(portstr);
+
+       rec = server_setup_find_port(addr, port);
+       if (rec == NULL) {
+               rec = create_server_setup(optlist);
+               if (rec == NULL) {
+                       cmd_params_free(free_arg);
+                       return;
+               }
+               rec->address = g_strdup(addr);
+               rec->port = port;
+       } else {
+               value = g_hash_table_lookup(optlist, "port");
+               if (value != NULL && *value != '\0') rec->port = atoi(value);
+
+               if (*password != '\0') g_free_and_null(rec->password);
+               if (g_hash_table_lookup(optlist, "host")) {
+                       g_free_and_null(rec->own_host);
+                       rec->own_ip4 = rec->own_ip6 = NULL;
+               }
+       }
+
+       if (g_hash_table_lookup(optlist, "6"))
+               rec->family = AF_INET6;
+        else if (g_hash_table_lookup(optlist, "4"))
+               rec->family = AF_INET;
+
+       if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE;
+       if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE;
+
+       if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
+       value = g_hash_table_lookup(optlist, "host");
+       if (value != NULL && *value != '\0') {
+               rec->own_host = g_strdup(value);
+               rec->own_ip4 = rec->own_ip6 = NULL;
+       }
+
+       signal_emit("server add fill", 2, rec, optlist);
+
+       server_setup_add(rec);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_SETUPSERVER_ADDED, addr, port);
+
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: SERVER REMOVE <address> [<port>] */
+static void cmd_server_remove(const char *data)
+{
+       SERVER_SETUP_REC *rec;
+       char *addr, *port;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 2, &addr, &port))
+               return;
+       if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+        if (*port == '\0')
+               rec = server_setup_find(addr, -1);
+       else
+               rec = server_setup_find_port(addr, atoi(port));
+
+       if (rec == NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND, addr, port);
+       else {
+               server_setup_remove(rec);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_REMOVED, addr, port);
+       }
+
+       cmd_params_free(free_arg);
+}
+
+static void cmd_server(const char *data, SERVER_REC *server, void *item)
+{
+       GHashTable *optlist;
+       char *addr;
+       void *free_arg;
+
+       if (*data == '\0') {
+               if (servers == NULL && lookup_servers == NULL &&
+                   reconnects == NULL) {
+                       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                    TXT_NO_CONNECTED_SERVERS);
+               } else {
+                       print_servers();
+                       print_lookup_servers();
+                       print_reconnects();
+               }
+
+               signal_stop();
+               return;
+       }
+
+       if (g_strncasecmp(data, "add ", 4) == 0 ||
+           g_strncasecmp(data, "remove ", 7) == 0 ||
+           g_strcasecmp(data, "list") == 0 ||
+           g_strncasecmp(data, "list ", 5) == 0) {
+               command_runsub("server", data, server, item);
+               signal_stop();
+               return;
+       }
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+                           "connect", &optlist, &addr))
+               return;
+
+       if (*addr == '\0' || strcmp(addr, "+") == 0)
+               cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+       if (*addr == '+') window_create(NULL, FALSE);
+
+       cmd_params_free(free_arg);
+}
+
+static void sig_server_looking(SERVER_REC *server)
+{
+       g_return_if_fail(server != NULL);
+
+       printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_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, TXT_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,
+                   TXT_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,
+                           TXT_CONNECTION_LOST, server->connrec->address);
+       } else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_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,
+                   TXT_CONNECTION_LOST, server->connrec->address);
+}
+
+static void sig_server_quit(SERVER_REC *server, const char *msg)
+{
+       g_return_if_fail(server != NULL);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_SERVER_QUIT, server->connrec->address, msg);
+}
+
+static void sig_server_lag_disconnected(SERVER_REC *server)
+{
+       g_return_if_fail(server != NULL);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_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,
+                   TXT_RECONNECT_REMOVED, reconnect->conn->address, reconnect->conn->port,
+                   reconnect->conn->chatnet == NULL ? "" : reconnect->conn->chatnet);
+}
+
+static void sig_server_reconnect_not_found(const char *tag)
+{
+       g_return_if_fail(tag != NULL);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_RECONNECT_NOT_FOUND, tag);
+}
+
+static void sig_chat_protocol_unknown(const char *protocol)
+{
+       g_return_if_fail(protocol != NULL);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                    TXT_UNKNOWN_CHAT_PROTOCOL, protocol);
+}
+
+void fe_server_init(void)
+{
+       command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+       command_bind("server add", NULL, (SIGNAL_FUNC) cmd_server_add);
+       command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove);
+       command_set_options("server add", "4 6 auto noauto -host -port");
+
+       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);
+       signal_add("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+       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("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
+
+void fe_server_deinit(void)
+{
+       command_unbind("server", (SIGNAL_FUNC) cmd_server);
+       command_unbind("server add", (SIGNAL_FUNC) cmd_server_add);
+       command_unbind("server remove", (SIGNAL_FUNC) cmd_server_remove);
+
+       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);
+       signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+       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("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-settings.c b/apps/irssi/src/fe-common/core/fe-settings.c
new file mode 100644 (file)
index 0000000..90262ee
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ 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 "servers.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "levels.h"
+#include "printtext.h"
+#include "keyboard.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:
+               ltoa(value_int, 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)
+               settings_set_bool(key, TRUE);
+       else if (g_strcasecmp(value, "OFF") == 0)
+               settings_set_bool(key, FALSE);
+       else if (g_strcasecmp(value, "TOGGLE") == 0)
+               settings_set_bool(key, !settings_get_bool(key));
+       else
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_NOT_TOGGLE);
+}
+
+/* SYNTAX: SET [-clear] [<key> [<value>]] */
+static void cmd_set(char *data)
+{
+        GHashTable *optlist;
+       GSList *sets, *tmp;
+       const char *last_section;
+       char *key, *value;
+       void *free_arg;
+       int found, clear;
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+                           "set", &optlist, &key, &value))
+               return;
+
+       clear = g_hash_table_lookup(optlist, "clear") != NULL;
+
+       last_section = ""; found = 0;
+       sets = settings_get_sorted();
+       for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+               SETTINGS_REC *rec = tmp->data;
+
+               if (((clear || *value != '\0') && g_strcasecmp(rec->key, key) != 0) ||
+                   (*value == '\0' && *key != '\0' && stristr(rec->key, key) == NULL))
+                       continue;
+
+               if (strcmp(last_section, rec->section) != 0) {
+                       /* print section */
+                       printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%_[ %s ]", rec->section);
+                       last_section = rec->section;
+               }
+
+               if (clear || *value != '\0') {
+                       /* change the setting */
+                       switch (rec->type) {
+                       case SETTING_TYPE_BOOLEAN:
+                                if (clear)
+                                       settings_set_bool(key, FALSE);
+                                else
+                                       set_boolean(key, value);
+                               break;
+                       case SETTING_TYPE_INT:
+                               settings_set_int(key, clear ? 0 : atoi(value));
+                               break;
+                       case SETTING_TYPE_STRING:
+                               settings_set_str(key, clear ? "" : value);
+                               break;
+                       }
+                       signal_emit("setup changed", 0);
+               }
+
+                set_print(rec);
+               found = TRUE;
+
+               if (clear || *value != '\0')
+                       break;
+       }
+       g_slist_free(sets);
+
+        if (!found)
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %s", key);
+
+        cmd_params_free(free_arg);
+}
+
+/* SYNTAX: TOGGLE <key> [on|off|toggle] */
+static void cmd_toggle(const char *data)
+{
+       char *key, *value;
+       void *free_arg;
+       int type;
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &key, &value))
+               return;
+
+        if (*key == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       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));
+       }
+
+        cmd_params_free(free_arg);
+}
+
+static int config_key_compare(CONFIG_NODE *node1, CONFIG_NODE *node2)
+{
+       return g_strcasecmp(node1->key, node2->key);
+}
+
+static void show_aliases(const char *alias)
+{
+       CONFIG_NODE *node;
+       GSList *tmp, *list;
+       int aliaslen;
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_HEADER);
+
+       node = iconfig_node_traverse("aliases", FALSE);
+       tmp = node == NULL ? NULL : node->value;
+
+       /* first get the list of aliases sorted */
+       list = NULL;
+       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;
+
+               list = g_slist_insert_sorted(list, node, (GCompareFunc) config_key_compare);
+       }
+
+       /* print the aliases */
+       for (tmp = list; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *node = tmp->data;
+
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_LINE,
+                           node->key, node->value);
+       }
+       g_slist_free(list);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_FOOTER);
+}
+
+static void alias_remove(const char *alias)
+{
+       if (iconfig_get_str("aliases", alias, NULL) == NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_NOT_FOUND, alias);
+       else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_REMOVED, alias);
+               iconfig_set_str("aliases", alias, NULL);
+       }
+}
+
+/* SYNTAX: ALIAS [[-]<alias> [<command>]] */
+static void cmd_alias(const char *data)
+{
+       char *alias, *value;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+
+       if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &alias, &value))
+               return;
+
+       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, TXT_ALIAS_ADDED, alias);
+               iconfig_set_str("aliases", alias, value);
+       }
+        cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNALIAS <alias> */
+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);
+}
+
+/* SYNTAX: RELOAD [<file>] */
+static void cmd_reload(const char *data)
+{
+       char *fname;
+
+       fname = *data != '\0' ? g_strdup(data) :
+               g_strdup_printf("%s/.irssi/config", g_get_home_dir());
+       if (settings_reread(fname)) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           TXT_CONFIG_RELOADED, fname);
+       }
+       g_free(fname);
+}
+
+static void settings_save_fe(const char *fname)
+{
+       if (settings_save(fname)) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                           TXT_CONFIG_SAVED, fname);
+       }
+}
+
+static void settings_save_confirm(const char *line, char *fname)
+{
+       if (line[0] == 'Y')
+               settings_save_fe(fname);
+       g_free(fname);
+}
+
+/* SYNTAX: SAVE [<file>] */
+static void cmd_save(const char *data)
+{
+       char *format;
+
+       if (*data == '\0')
+               data = mainconfig->fname;
+
+       if (!irssi_config_is_changed(data)) {
+               settings_save_fe(data);
+               return;
+       }
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_CONFIG_MODIFIED, data);
+
+       format = format_get_text(MODULE_NAME, NULL, NULL, NULL,
+                                TXT_OVERWRITE_CONFIG);
+       keyboard_entry_redirect((SIGNAL_FUNC) settings_save_confirm,
+                               format, 0, g_strdup(data));
+        g_free(format);
+}
+
+static void settings_clean_confirm(const char *line)
+{
+       if (line[0] == 'Y')
+                settings_clean_invalid();
+}
+
+static void sig_settings_errors(const char *msg)
+{
+        printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", msg);
+       keyboard_entry_redirect((SIGNAL_FUNC) settings_clean_confirm,
+                               "Remove unknown settings from config file (Y/n)?",
+                               0, NULL);
+}
+
+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);
+       command_bind("reload", NULL, (SIGNAL_FUNC) cmd_reload);
+       command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+       command_set_options("set", "clear");
+
+        signal_add("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
+
+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);
+       command_unbind("reload", (SIGNAL_FUNC) cmd_reload);
+       command_unbind("save", (SIGNAL_FUNC) cmd_save);
+
+       signal_remove("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-windows.c b/apps/irssi/src/fe-common/core/fe-windows.c
new file mode 100644 (file)
index 0000000..3c48261
--- /dev/null
@@ -0,0 +1,583 @@
+/*
+ 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 "servers.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "printtext.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+GSList *windows; /* first in the list is the active window,
+                    next is the last active, etc. */
+WINDOW_REC *active_win;
+
+static int daytag;
+static int daycheck; /* 0 = don't check, 1 = time is 00:00, check,
+                        2 = time is 00:00, already checked */
+
+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_prepend(windows, rec);
+       signal_emit("window created", 2, rec, GINT_TO_POINTER(automatic));
+
+       if (item != NULL) window_item_add(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;
+}
+
+/* removed_refnum was removed from the windows list, pack the windows so
+   there won't be any holes. If there is any holes after removed_refnum,
+   leave the windows behind it alone. */
+static void windows_pack(int removed_refnum)
+{
+       WINDOW_REC *window;
+       int refnum;
+
+       for (refnum = removed_refnum+1;; refnum++) {
+               window = window_find_refnum(refnum);
+               if (window == NULL || window->sticky_refnum)
+                       break;
+
+               window_set_refnum(window, refnum-1);
+       }
+}
+
+void window_destroy(WINDOW_REC *window)
+{
+       g_return_if_fail(window != NULL);
+
+       if (window->destroying) return;
+       window->destroying = TRUE;
+       windows = g_slist_remove(windows, window);
+
+       if (active_win == window && windows != NULL) {
+                active_win = NULL; /* it's corrupted */
+               window_set_active(windows->data);
+       }
+
+       while (window->items != NULL)
+               window_item_destroy(window->items->data);
+
+        if (settings_get_bool("windows_auto_renumber"))
+               windows_pack(window->refnum);
+
+       signal_emit("window destroyed", 1, window);
+
+       while (window->bound_items != NULL)
+                window_bind_destroy(window, window->bound_items->data);
+
+       g_free_not_null(window->hilight_color);
+       g_free_not_null(window->servertag);
+       g_free_not_null(window->theme_name);
+       g_free_not_null(window->name);
+       g_free(window);
+}
+
+void window_auto_destroy(WINDOW_REC *window)
+{
+       if (settings_get_bool("autoclose_windows") && windows->next != NULL &&
+           window->items == NULL && window->level == 0)
+                window_destroy(window);
+}
+
+void window_set_active(WINDOW_REC *window)
+{
+       WINDOW_REC *old_window;
+
+       if (window == active_win)
+               return;
+
+       old_window = active_win;
+       active_win = window;
+       if (active_win != NULL) {
+               windows = g_slist_remove(windows, active_win);
+               windows = g_slist_prepend(windows, active_win);
+       }
+
+        if (active_win != NULL)
+               signal_emit("window changed", 2, active_win, old_window);
+}
+
+void window_change_server(WINDOW_REC *window, void *server)
+{
+       window->active_server = server;
+       signal_emit("window server changed", 2, window, server);
+}
+
+void window_set_refnum(WINDOW_REC *window, int refnum)
+{
+       GSList *tmp;
+       int old_refnum;
+
+       g_return_if_fail(window != NULL);
+       g_return_if_fail(refnum >= 1);
+       if (window->refnum == refnum) return;
+
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum == refnum) {
+                       rec->refnum = window->refnum;
+                        signal_emit("window refnum changed", 2, rec, GINT_TO_POINTER(refnum));
+                       break;
+               }
+       }
+
+       old_refnum = window->refnum;
+       window->refnum = refnum;
+       signal_emit("window refnum changed", 2, window, GINT_TO_POINTER(old_refnum));
+}
+
+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);
+}
+
+/* return active item's name, or if none is active, window's name */
+char *window_get_active_name(WINDOW_REC *window)
+{
+       g_return_val_if_fail(window != NULL, NULL);
+
+       if (window->active != NULL)
+               return window->active->name;
+
+       return window->name;
+}
+
+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;
+
+       /* match by level - ignore server */
+       window = window_find_level(NULL, level);
+       if (window != NULL) return window;
+
+       /* fallback to active */
+       return active_win;
+}
+
+WINDOW_REC *window_find_refnum(int refnum)
+{
+       GSList *tmp;
+
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum == refnum)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+WINDOW_REC *window_find_name(const char *name)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->name != NULL && g_strcasecmp(rec->name, name) == 0)
+                       return rec;
+       }
+
+       return NULL;
+}
+
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name)
+{
+       WINDOW_REC *rec;
+       WI_ITEM_REC *item;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       rec = window_find_name(name);
+       if (rec != NULL) return rec;
+
+       item = server == NULL ? NULL :
+               window_item_find(server, name);
+       if (item == NULL && 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 = server == NULL ? NULL :
+                       window_item_find(server, chan);
+               if (item == NULL) item = window_item_find(NULL, chan);
+               g_free(chan);
+       }
+
+       if (item == NULL)
+               return 0;
+
+       return window_item_window(item);
+}
+
+int window_refnum_prev(int refnum, int wrap)
+{
+       GSList *tmp;
+       int prev, max;
+
+       max = prev = -1;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum < refnum && (prev == -1 || rec->refnum > prev))
+                       prev = rec->refnum;
+               if (wrap && (max == -1 || rec->refnum > max))
+                       max = rec->refnum;
+       }
+
+       return prev != -1 ? prev : max;
+}
+
+int window_refnum_next(int refnum, int wrap)
+{
+       GSList *tmp;
+       int min, next;
+
+       min = next = -1;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum > refnum && (next == -1 || rec->refnum < next))
+                       next = rec->refnum;
+               if (wrap && (min == -1 || rec->refnum < min))
+                       min = rec->refnum;
+       }
+
+       return next != -1 ? next : min;
+}
+
+int windows_refnum_last(void)
+{
+       GSList *tmp;
+       int max;
+
+       max = -1;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum > max)
+                       max = rec->refnum;
+       }
+
+       return max;
+}
+
+static int window_refnum_cmp(WINDOW_REC *w1, WINDOW_REC *w2)
+{
+       return w1->refnum < w2->refnum ? -1 : 1;
+}
+
+GSList *windows_get_sorted(void)
+{
+       GSList *tmp, *sorted;
+
+        sorted = NULL;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               sorted = g_slist_insert_sorted(sorted, rec, (GCompareFunc)
+                                              window_refnum_cmp);
+       }
+
+        return sorted;
+}
+
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+                                const char *name)
+{
+       WINDOW_BIND_REC *rec;
+
+        g_return_val_if_fail(window != NULL, NULL);
+        g_return_val_if_fail(servertag != NULL, NULL);
+        g_return_val_if_fail(name != NULL, NULL);
+
+       rec = g_new0(WINDOW_BIND_REC, 1);
+        rec->name = g_strdup(name);
+        rec->servertag = g_strdup(servertag);
+
+       window->bound_items = g_slist_append(window->bound_items, rec);
+        return rec;
+}
+
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec)
+{
+       g_return_if_fail(window != NULL);
+        g_return_if_fail(rec != NULL);
+
+       window->bound_items = g_slist_remove(window->bound_items, rec);
+
+        g_free(rec->servertag);
+        g_free(rec->name);
+        g_free(rec);
+}
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+                                 const char *name)
+{
+       GSList *tmp;
+
+        g_return_val_if_fail(window != NULL, NULL);
+        g_return_val_if_fail(servertag != NULL, NULL);
+        g_return_val_if_fail(name != NULL, NULL);
+
+       for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+               WINDOW_BIND_REC *rec = tmp->data;
+
+               if (g_strcasecmp(rec->name, name) == 0 &&
+                   g_strcasecmp(rec->servertag, servertag) == 0)
+                        return rec;
+       }
+
+        return NULL;
+}
+
+void window_bind_remove_unsticky(WINDOW_REC *window)
+{
+       GSList *tmp, *next;
+
+       for (tmp = window->bound_items; tmp != NULL; tmp = next) {
+               WINDOW_BIND_REC *rec = tmp->data;
+
+               next = tmp->next;
+               if (!rec->sticky)
+                        window_bind_destroy(window, rec);
+       }
+}
+
+static void sig_server_looking(SERVER_REC *server)
+{
+       GSList *tmp;
+
+       g_return_if_fail(server != NULL);
+
+       /* Try to keep some server assigned to windows..
+          Also change active window's server if the window is empty */
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if ((rec->servertag == NULL ||
+                    g_strcasecmp(rec->servertag, server->tag) == 0) &&
+                   (rec->active_server == NULL ||
+                    (rec == active_win && rec->items == NULL)))
+                       window_change_server(rec, server);
+       }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+       GSList *tmp;
+        SERVER_REC *new_server;
+
+       g_return_if_fail(server != NULL);
+
+       new_server = servers == NULL ? NULL : servers->data;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->active_server == server) {
+                       window_change_server(rec, rec->servertag != NULL ?
+                                            NULL : new_server);
+               }
+       }
+}
+
+static void sig_print_text(void)
+{
+       GSList *tmp;
+       char month[100];
+       time_t t;
+       struct tm *tm;
+
+       t = time(NULL);
+       tm = localtime(&t);
+       if (strftime(month, sizeof(month), "%b", tm) <= 0)
+               month[0] = '\0';
+
+       if (tm->tm_hour != 0 || tm->tm_min != 0)
+               return;
+
+       daycheck = 2;
+       signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+       /* day changed, print notice about it to every window */
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               printformat_window(tmp->data, MSGLEVEL_NEVER, TXT_DAYCHANGE,
+                                  tm->tm_mday, tm->tm_mon+1,
+                                  1900+tm->tm_year, month);
+       }
+}
+
+static int sig_check_daychange(void)
+{
+       time_t t;
+       struct tm *tm;
+
+       t = time(NULL);
+       tm = localtime(&t);
+
+       if (daycheck == 1 && tm->tm_hour == 0 && tm->tm_min == 0) {
+               sig_print_text();
+               return TRUE;
+       }
+
+       if (tm->tm_hour != 23 || tm->tm_min != 59) {
+               daycheck = 0;
+               return TRUE;
+       }
+
+       /* time is 23:59 */
+       if (daycheck == 0) {
+               daycheck = 1;
+               signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+       }
+       return TRUE;
+}
+
+static void read_settings(void)
+{
+       if (daytag != -1) {
+               g_source_remove(daytag);
+               daytag = -1;
+       }
+
+       if (settings_get_bool("timestamps"))
+               daytag = g_timeout_add(30000, (GSourceFunc) sig_check_daychange, NULL);
+}
+
+void windows_init(void)
+{
+       active_win = NULL;
+       daycheck = 0; daytag = -1;
+       settings_add_bool("lookandfeel", "window_auto_change", FALSE);
+       settings_add_bool("lookandfeel", "windows_auto_renumber", TRUE);
+
+       read_settings();
+       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);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void windows_deinit(void)
+{
+       if (daytag != -1) g_source_remove(daytag);
+       if (daycheck == 1) signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+       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);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/fe-windows.h b/apps/irssi/src/fe-common/core/fe-windows.h
new file mode 100644 (file)
index 0000000..c9ffe69
--- /dev/null
@@ -0,0 +1,96 @@
+#ifndef __WINDOWS_H
+#define __WINDOWS_H
+
+#include "servers.h"
+
+#define STRUCT_SERVER_REC SERVER_REC
+#include "window-item-def.h"
+
+enum {
+        DATA_LEVEL_NONE = 0,
+       DATA_LEVEL_TEXT,
+       DATA_LEVEL_MSG,
+        DATA_LEVEL_HILIGHT
+};
+
+typedef struct {
+       char *servertag;
+        char *name;
+       unsigned int sticky:1;
+} WINDOW_BIND_REC;
+
+typedef struct {
+       int refnum;
+       char *name;
+
+        int width, height;
+
+       GSList *items;
+       WI_ITEM_REC *active;
+       SERVER_REC *active_server;
+        char *servertag; /* active_server must be either NULL or have this tag (unless there's items in this window) */
+
+       int level; /* message level */
+       GSList *bound_items; /* list of WINDOW_BIND_RECs */
+
+       unsigned int sticky_refnum:1;
+       unsigned int destroying:1;
+
+       /* window-specific command line history */
+       GList *history, *history_pos;
+       int history_lines, history_over_counter;
+
+       int data_level; /* current data level */
+       char *hilight_color; /* current hilight color in %format */
+
+       time_t last_timestamp; /* When was last timestamp printed */
+       time_t last_line; /* When was last line printed */
+
+        char *theme_name; /* active theme in window, NULL = default */
+       void *theme; /* THEME_REC */
+
+       void *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_auto_destroy(WINDOW_REC *window);
+
+void window_set_active(WINDOW_REC *window);
+void window_change_server(WINDOW_REC *window, void *server);
+
+void window_set_refnum(WINDOW_REC *window, int refnum);
+void window_set_name(WINDOW_REC *window, const char *name);
+void window_set_level(WINDOW_REC *window, int level);
+
+/* return active item's name, or if none is active, window's name */
+char *window_get_active_name(WINDOW_REC *window);
+
+WINDOW_REC *window_find_level(void *server, int level);
+WINDOW_REC *window_find_closest(void *server, const char *name, int level);
+WINDOW_REC *window_find_refnum(int refnum);
+WINDOW_REC *window_find_name(const char *name);
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name);
+
+int window_refnum_prev(int refnum, int wrap);
+int window_refnum_next(int refnum, int wrap);
+int windows_refnum_last(void);
+
+GSList *windows_get_sorted(void);
+
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+                                const char *name);
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec);
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+                                 const char *name);
+void window_bind_remove_unsticky(WINDOW_REC *window);
+
+void windows_init(void);
+void windows_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/formats.c b/apps/irssi/src/fe-common/core/formats.c
new file mode 100644 (file)
index 0000000..b933c06
--- /dev/null
@@ -0,0 +1,941 @@
+/*
+ formats.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 "special-vars.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "fe-windows.h"
+#include "formats.h"
+#include "themes.h"
+#include "translation.h"
+
+static const char *format_backs = "04261537";
+static const char *format_fores = "kbgcrmyw";
+static const char *format_boldfores = "KBGCRMYW";
+
+static int signal_gui_print_text;
+static int hide_text_style, hide_server_tags;
+
+static int timestamps, msgs_timestamps;
+static int timestamp_timeout;
+
+int format_find_tag(const char *module, const char *tag)
+{
+       FORMAT_REC *formats;
+       int n;
+
+       formats = g_hash_table_lookup(default_formats, module);
+       if (formats == NULL)
+               return -1;
+
+       for (n = 0; formats[n].def != NULL; n++) {
+               if (formats[n].tag != NULL &&
+                   g_strcasecmp(formats[n].tag, tag) == 0)
+                       return n;
+       }
+
+       return -1;
+}
+
+int format_expand_styles(GString *out, char format)
+{
+       char *p;
+
+       switch (format) {
+       case 'U':
+               /* Underline on/off */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_UNDERLINE);
+               break;
+       case '9':
+       case '_':
+               /* bold on/off */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_BOLD);
+               break;
+       case '8':
+               /* reverse */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_REVERSE);
+               break;
+       case '%':
+               g_string_append_c(out, '%');
+               break;
+       case ':':
+               /* Newline */
+               g_string_append_c(out, '\n');
+               break;
+       case '|':
+               /* Indent here */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_INDENT);
+               break;
+       case 'F':
+               /* blink */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_BLINK);
+               break;
+       case 'n':
+       case 'N':
+               /* default color */
+               g_string_append_c(out, 4);
+               g_string_append_c(out, FORMAT_STYLE_DEFAULTS);
+               break;
+       default:
+               /* check if it's a background color */
+               p = strchr(format_backs, format);
+               if (p != NULL) {
+                       g_string_append_c(out, 4);
+                       g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+                       g_string_append_c(out, (char) ((int) (p-format_backs)+'0'));
+                       break;
+               }
+
+               /* check if it's a foreground color */
+               if (format == 'p') format = 'm';
+               p = strchr(format_fores, format);
+               if (p != NULL) {
+                       g_string_append_c(out, 4);
+                       g_string_append_c(out, (char) ((int) (p-format_fores)+'0'));
+                       g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+                       break;
+               }
+
+               /* check if it's a bold foreground color */
+               if (format == 'P') format = 'M';
+               p = strchr(format_boldfores, format);
+               if (p != NULL) {
+                       g_string_append_c(out, 4);
+                       g_string_append_c(out, (char) (8+(int) (p-format_boldfores)+'0'));
+                       g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+                       break;
+               }
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+void format_read_arglist(va_list va, FORMAT_REC *format,
+                        char **arglist, int arglist_size,
+                        char *buffer, int buffer_size)
+{
+       int num, len, bufpos;
+
+        g_return_if_fail(format->params < arglist_size);
+
+       bufpos = 0;
+        arglist[format->params] = NULL;
+       for (num = 0; num < format->params; num++) {
+               switch (format->paramtypes[num]) {
+               case FORMAT_STRING:
+                       arglist[num] = (char *) va_arg(va, char *);
+                       if (arglist[num] == NULL) {
+                               g_warning("format_read_arglist() : 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;
+               }
+               }
+       }
+}
+
+void format_create_dest(TEXT_DEST_REC *dest,
+                       void *server, const char *target,
+                       int level, WINDOW_REC *window)
+{
+       dest->server = server;
+       dest->target = target;
+       dest->level = level;
+       dest->window = window != NULL ? window :
+               window_find_closest(server, target, level);
+
+       dest->hilight_priority = 0;
+        dest->hilight_color = NULL;
+}
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str)
+{
+        GString *tmp;
+       int len;
+
+        g_return_val_if_fail(str != NULL, 0);
+
+        tmp = g_string_new(NULL);
+       len = 0;
+       while (*str != '\0') {
+               if (*str == '%' && str[1] != '\0') {
+                       str++;
+                       if (*str != '%' && format_expand_styles(tmp, *str)) {
+                                str++;
+                               continue;
+                       }
+
+                       /* %% or unknown %code, written as-is */
+                       if (*str != '%')
+                               len++;
+               }
+
+                len++;
+               str++;
+       }
+
+       g_string_free(tmp, TRUE);
+        return len;
+}
+
+/* Return how many characters in `str' must be skipped before `len'
+   characters of text is skipped. Like strip_real_length(), except this
+   handles %codes. */
+int format_real_length(const char *str, int len)
+{
+       GString *tmp;
+       const char *start;
+
+        g_return_val_if_fail(str != NULL, 0);
+        g_return_val_if_fail(len >= 0, 0);
+
+        start = str;
+        tmp = g_string_new(NULL);
+       while (*str != '\0' && len > 0) {
+               if (*str == '%' && str[1] != '\0') {
+                       str++;
+                       if (*str != '%' && format_expand_styles(tmp, *str)) {
+                                str++;
+                               continue;
+                       }
+
+                       /* %% or unknown %code, written as-is */
+                       if (*str != '%') {
+                               if (--len == 0)
+                                        break;
+                       }
+               }
+
+                len--;
+               str++;
+       }
+
+       g_string_free(tmp, TRUE);
+        return (int) (str-start);
+}
+
+char *format_string_expand(const char *text)
+{
+       GString *out;
+       char code, *ret;
+
+        g_return_val_if_fail(text != NULL, NULL);
+
+       out = g_string_new(NULL);
+
+       code = 0;
+       while (*text != '\0') {
+               if (code == '%') {
+                       /* color code */
+                       if (!format_expand_styles(out, *text)) {
+                               g_string_append_c(out, '%');
+                               g_string_append_c(out, '%');
+                               g_string_append_c(out, *text);
+                       }
+                       code = 0;
+               } else {
+                       if (*text == '%')
+                               code = *text;
+                       else
+                               g_string_append_c(out, *text);
+               }
+
+               text++;
+       }
+
+       ret = out->str;
+       g_string_free(out, FALSE);
+       return ret;
+}
+
+static char *format_get_text_args(TEXT_DEST_REC *dest,
+                                 const char *text, char **arglist)
+{
+       GString *out;
+       char code, *ret;
+       int need_free;
+
+       out = g_string_new(NULL);
+
+       code = 0;
+       while (*text != '\0') {
+               if (code == '%') {
+                       /* color code */
+                       if (!format_expand_styles(out, *text)) {
+                               g_string_append_c(out, '%');
+                               g_string_append_c(out, '%');
+                               g_string_append_c(out, *text);
+                       }
+                       code = 0;
+               } else if (code == '$') {
+                       /* argument */
+                       char *ret;
+
+                       ret = parse_special((char **) &text,
+                                           active_win == NULL ? NULL :
+                                           active_win->active_server,
+                                           active_win == NULL ? NULL :
+                                           active_win->active, arglist,
+                                           &need_free, NULL, 0);
+
+                       if (ret != NULL) {
+                               /* string shouldn't end with \003 or it could
+                                  mess up the next one or two characters */
+                                int diff;
+                               int len = strlen(ret);
+                               while (len > 0 && ret[len-1] == 3) len--;
+                               diff = strlen(ret)-len;
+
+                               g_string_append(out, ret);
+                               if (diff > 0)
+                                       g_string_truncate(out, out->len-diff);
+                               if (need_free) g_free(ret);
+                       }
+                       code = 0;
+               } else {
+                       if (*text == '%' || *text == '$')
+                               code = *text;
+                       else
+                               g_string_append_c(out, *text);
+               }
+
+               text++;
+       }
+
+       ret = out->str;
+       g_string_free(out, FALSE);
+       return ret;
+}
+
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+                           TEXT_DEST_REC *dest, int formatnum, ...)
+{
+       va_list va;
+       char *str;
+
+       if (theme == NULL) {
+               theme = dest->window->theme == NULL ? current_theme :
+                       dest->window->theme;
+       }
+
+       va_start(va, formatnum);
+       str = format_get_text_theme_args(theme, module, dest, formatnum, va);
+       va_end(va);
+
+       return str;
+}
+
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+                                TEXT_DEST_REC *dest, int formatnum,
+                                va_list va)
+{
+       char *arglist[MAX_FORMAT_PARAMS];
+       char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+       FORMAT_REC *formats;
+
+       formats = g_hash_table_lookup(default_formats, module);
+       format_read_arglist(va, &formats[formatnum],
+                           arglist, sizeof(arglist)/sizeof(char *),
+                           buffer, sizeof(buffer));
+
+       return format_get_text_theme_charargs(theme, module, dest,
+                                             formatnum, arglist);
+}
+
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+                                    TEXT_DEST_REC *dest, int formatnum,
+                                    char **args)
+{
+       MODULE_THEME_REC *module_theme;
+       char *text;
+
+       module_theme = g_hash_table_lookup(theme->modules, module);
+       if (module_theme == NULL)
+               return NULL;
+
+        text = module_theme->expanded_formats[formatnum];
+       return format_get_text_args(dest, text, args);
+}
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+                     void *server, const char *target,
+                     int formatnum, ...)
+{
+       TEXT_DEST_REC dest;
+       THEME_REC *theme;
+       va_list va;
+       char *str;
+
+       format_create_dest(&dest, server, target, 0, window);
+       theme = dest.window->theme == NULL ? current_theme :
+               dest.window->theme;
+
+       va_start(va, formatnum);
+       str = format_get_text_theme_args(theme, module, &dest, formatnum, va);
+       va_end(va);
+
+       return str;
+}
+
+/* add `linestart' to start of each line in `text'. `text' may contain
+   multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart)
+{
+       GString *str;
+       char *ret;
+
+       if (linestart == NULL)
+               return g_strdup(text);
+
+       if (strchr(text, '\n') == NULL)
+               return g_strconcat(linestart, text, NULL);
+
+       str = g_string_new(linestart);
+       while (*text != '\0') {
+               g_string_append_c(str, *text);
+               if (*text == '\n')
+                       g_string_append(str, linestart);
+               text++;
+       }
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+        return ret;
+}
+
+#define LINE_START_IRSSI_LEVEL \
+       (MSGLEVEL_CLIENTERROR | MSGLEVEL_CLIENTNOTICE)
+
+#define NOT_LINE_START_LEVEL \
+       (MSGLEVEL_NEVER | MSGLEVEL_LASTLOG | MSGLEVEL_CLIENTCRAP | \
+       MSGLEVEL_MSGS | MSGLEVEL_PUBLIC | MSGLEVEL_DCC | MSGLEVEL_DCCMSGS | \
+       MSGLEVEL_ACTIONS | MSGLEVEL_NOTICES | MSGLEVEL_SNOTES | MSGLEVEL_CTCPS)
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+       int format;
+
+       if (dest->level & LINE_START_IRSSI_LEVEL)
+               format = TXT_LINE_START_IRSSI;
+       else if ((dest->level & NOT_LINE_START_LEVEL) == 0)
+               format = TXT_LINE_START;
+       else
+               return NULL;
+
+       return format_get_text_theme(theme, MODULE_NAME, dest, format);
+}
+
+#define show_timestamp(level) \
+       ((level & (MSGLEVEL_NEVER|MSGLEVEL_LASTLOG)) == 0 && \
+       (timestamps || (msgs_timestamps && ((level) & MSGLEVEL_MSGS))))
+
+static char *get_timestamp(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+       struct tm *tm;
+       int diff;
+
+       if (!show_timestamp(dest->level))
+               return NULL;
+
+       if (timestamp_timeout > 0) {
+               diff = t - dest->window->last_timestamp;
+               dest->window->last_timestamp = t;
+               if (diff < timestamp_timeout)
+                       return NULL;
+       }
+
+       tm = localtime(&t);
+       return format_get_text_theme(theme, MODULE_NAME, dest, TXT_TIMESTAMP,
+                                    tm->tm_year+1900,
+                                    tm->tm_mon+1, tm->tm_mday,
+                                    tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static char *get_server_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+       SERVER_REC *server;
+       int count = 0;
+
+       server = dest->server;
+
+       if (server == NULL || hide_server_tags ||
+           (dest->window->active != NULL &&
+            dest->window->active->server == server))
+               return NULL;
+
+       if (servers != NULL) {
+               count++;
+               if (servers->next != NULL)
+                       count++;
+       }
+       if (count < 2 && lookup_servers != NULL) {
+                count++;
+               if (lookup_servers->next != NULL)
+                       count++;
+       }
+
+       return count < 2 ? NULL :
+               format_get_text_theme(theme, MODULE_NAME, dest,
+                                     TXT_SERVERTAG, server->tag);
+}
+
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+       char *timestamp, *servertag;
+       char *linestart;
+
+       timestamp = get_timestamp(theme, dest, t);
+       servertag = get_server_tag(theme, dest);
+
+       if (timestamp == NULL && servertag == NULL)
+               return NULL;
+
+       linestart = g_strconcat(timestamp != NULL ? timestamp : "",
+                               servertag, NULL);
+
+       g_free_not_null(timestamp);
+       g_free_not_null(servertag);
+       return linestart;
+}
+
+void format_newline(WINDOW_REC *window)
+{
+       g_return_if_fail(window != NULL);
+
+       signal_emit_id(signal_gui_print_text, 6, window,
+                      GINT_TO_POINTER(-1), GINT_TO_POINTER(-1),
+                      GINT_TO_POINTER(PRINTFLAG_NEWLINE),
+                      "", GINT_TO_POINTER(-1));
+}
+
+/* parse ANSI color string */
+static char *get_ansi_color(THEME_REC *theme, char *str,
+                           int *fg_ret, int *bg_ret, int *flags_ret)
+{
+       static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+       char *start;
+       int fg, bg, flags, num;
+
+       if (*str != '[')
+               return str;
+       start = str++;
+
+       fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret;
+       bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret;
+       flags = flags_ret == NULL ? 0 : *flags_ret;
+
+       num = 0;
+       for (;; str++) {
+               if (*str == '\0') return start;
+
+               if (isdigit((int) *str)) {
+                       num = num*10 + (*str-'0');
+                       continue;
+               }
+
+               if (*str != ';' && *str != 'm')
+                       return start;
+
+               switch (num) {
+               case 0:
+                       /* reset colors back to default */
+                       fg = theme->default_color;
+                       bg = -1;
+                       flags &= ~PRINTFLAG_INDENT;
+                       break;
+               case 1:
+                       /* hilight */
+                       flags |= PRINTFLAG_BOLD;
+                       break;
+               case 5:
+                       /* blink */
+                       flags |= PRINTFLAG_BLINK;
+                       break;
+               case 7:
+                       /* reverse */
+                       flags |= 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 (fg_ret != NULL) *fg_ret = fg;
+                       if (bg_ret != NULL) *bg_ret = bg;
+                       if (flags_ret != NULL) *flags_ret = flags;
+
+                       str++;
+                       break;
+               }
+       }
+
+       return str;
+}
+
+/* parse MIRC color string */
+static void get_mirc_color(const char **str, int *fg_ret, int *bg_ret)
+{
+       int fg, bg;
+
+       fg = fg_ret == NULL ? -1 : *fg_ret;
+       bg = bg_ret == NULL ? -1 : *bg_ret;
+
+       if (!isdigit((int) **str) && **str != ',') {
+               fg = -1;
+               bg = -1;
+       } else {
+               /* foreground color */
+               if (**str != ',') {
+                       fg = **str-'0';
+                        (*str)++;
+                       if (isdigit((int) **str)) {
+                               fg = fg*10 + (**str-'0');
+                               (*str)++;
+                       }
+               }
+               if (**str == ',') {
+                       /* background color */
+                       (*str)++;
+                       if (!isdigit((int) **str))
+                               bg = -1;
+                       else {
+                               bg = **str-'0';
+                               (*str)++;
+                               if (isdigit((int) **str)) {
+                                       bg = bg*10 + (**str-'0');
+                                       (*str)++;
+                               }
+                       }
+               }
+       }
+
+       if (fg_ret) *fg_ret = fg;
+       if (bg_ret) *bg_ret = bg;
+}
+
+#define IS_COLOR_CODE(c) \
+       ((c) == 2 || (c) == 3 || (c) == 4 || (c) == 6 || (c) == 7 || \
+       (c) == 15 || (c) == 22 || (c) == 27 || (c) == 31)
+
+/* Return how many characters in `str' must be skipped before `len'
+   characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+                     int *last_color_pos, int *last_color_len)
+{
+       const char *start = str;
+
+        if (last_color_pos != NULL)
+               *last_color_pos = -1;
+       if (last_color_len != NULL)
+               *last_color_len = -1;
+
+       while (*str != '\0') {
+               if (*str == 3) {
+                       const char *mircstart = str;
+
+                       if (last_color_pos != NULL)
+                               *last_color_pos = (int) (str-start);
+                        str++;
+                       get_mirc_color(&str, NULL, NULL);
+                        if (last_color_len != NULL)
+                               *last_color_len = (int) (str-mircstart);
+
+               } else if (*str == 4 && str[1] != '\0') {
+                       if (str[1] < FORMAT_STYLE_SPECIAL && str[2] != '\0') {
+                               if (last_color_pos != NULL)
+                                       *last_color_pos = (int) (str-start);
+                                if (last_color_len != NULL)
+                                        *last_color_len = 3;
+                               str++;
+                       } else if (str[1] == FORMAT_STYLE_DEFAULTS) {
+                               if (last_color_pos != NULL)
+                                       *last_color_pos = (int) (str-start);
+                                if (last_color_len != NULL)
+                                        *last_color_len = 2;
+                       }
+                        str += 2;
+               } else {
+                       if (!IS_COLOR_CODE(*str)) {
+                               if (len-- == 0)
+                                       break;
+                       }
+                       str++;
+               }
+       }
+
+       return (int) (str-start);
+}
+
+char *strip_codes(const char *input)
+{
+        const char *p;
+        char *str, *out;
+
+        out = str = g_strdup(input);
+        for (p = input; *p != '\0'; p++) {
+                if (*p == 3) {
+                        p++;  
+
+                        /* mirc color */
+                        get_mirc_color(&p, NULL, NULL);
+                        p--;
+                        continue;
+                }
+
+                if (*p == 4 && p[1] != '\0') {
+                        if (p[1] >= FORMAT_STYLE_SPECIAL) {
+                                p++;
+                                continue;
+                        }
+
+                        /* irssi color */
+                        if (p[2] != '\0') {
+                                p += 2;
+                                continue;
+                        }
+                }
+
+                if (!IS_COLOR_CODE(*p))
+                        *out++ = *p;   
+        }
+
+        *out = '\0';
+        return str; 
+}
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text)
+{
+       char *dup, *str, *ptr, type;
+       int fgcolor, bgcolor;
+       int flags;
+
+       dup = str = g_strdup(text);
+
+       flags = 0; fgcolor = -1; bgcolor = -1;
+       while (*str != '\0') {
+                type = '\0';
+               for (ptr = str; *ptr != '\0'; ptr++) {
+                       if (IS_COLOR_CODE(*ptr) || *ptr == '\n') {
+                               type = *ptr;
+                               *ptr++ = '\0';
+                               break;
+                       }
+
+                       *ptr = (char) translation_in[(int) (unsigned char) *ptr];
+               }
+
+               if (type == 7) {
+                       /* bell */
+                       if (settings_get_bool("bell_beeps"))
+                                signal_emit("beep", 0);
+               }
+
+               if (*str != '\0') {
+                        /* send the text to gui handler */
+                       signal_emit_id(signal_gui_print_text, 6, dest->window,
+                                      GINT_TO_POINTER(fgcolor),
+                                      GINT_TO_POINTER(bgcolor),
+                                      GINT_TO_POINTER(flags), str,
+                                      dest->level);
+                       flags &= ~PRINTFLAG_INDENT;
+               }
+
+               if (type == '\n')
+                       format_newline(dest->window);
+
+               if (*ptr == '\0')
+                       break;
+
+               switch (type)
+               {
+               case 2:
+                       /* bold */
+                       if (!hide_text_style)
+                               flags ^= PRINTFLAG_BOLD;
+                       break;
+               case 3:
+                       /* MIRC color */
+                       get_mirc_color((const char **) &ptr,
+                                      hide_text_style ? NULL : &fgcolor,
+                                      hide_text_style ? NULL : &bgcolor);
+                       if (!hide_text_style)
+                               flags |= PRINTFLAG_MIRC_COLOR;
+                       break;
+               case 4:
+                       /* user specific colors */
+                       flags &= ~PRINTFLAG_MIRC_COLOR;
+                       switch (*ptr) {
+                       case FORMAT_STYLE_BLINK:
+                               flags ^= PRINTFLAG_BLINK;
+                               break;
+                       case FORMAT_STYLE_UNDERLINE:
+                               flags ^= PRINTFLAG_UNDERLINE;
+                               break;
+                       case FORMAT_STYLE_BOLD:
+                               flags ^= PRINTFLAG_BOLD;
+                               break;
+                       case FORMAT_STYLE_REVERSE:
+                               flags ^= PRINTFLAG_REVERSE;
+                               break;
+                       case FORMAT_STYLE_INDENT:
+                               flags |= PRINTFLAG_INDENT;
+                               break;
+                       case FORMAT_STYLE_DEFAULTS:
+                               fgcolor = bgcolor = -1;
+                               flags &= PRINTFLAG_INDENT;
+                               break;
+                       default:
+                               if (*ptr != FORMAT_COLOR_NOCHANGE) {
+                                       fgcolor = (unsigned char) *ptr-'0';
+                                       if (fgcolor <= 7)
+                                               flags &= ~PRINTFLAG_BOLD;
+                                       else {
+                                               /* bold */
+                                               if (fgcolor != 8) fgcolor -= 8;
+                                               flags |= PRINTFLAG_BOLD;
+                                       }
+                               }
+                               ptr++;
+                               if (*ptr != FORMAT_COLOR_NOCHANGE)
+                                       bgcolor = *ptr-'0';
+                       }
+                       ptr++;
+                       break;
+               case 6:
+                       /* blink */
+                       if (!hide_text_style)
+                               flags ^= PRINTFLAG_BLINK;
+                       break;
+               case 15:
+                       /* remove all styling */
+                       fgcolor = bgcolor = -1;
+                       flags &= PRINTFLAG_INDENT;
+                       break;
+               case 22:
+                       /* reverse */
+                       if (!hide_text_style)
+                               flags ^= PRINTFLAG_REVERSE;
+                       break;
+               case 31:
+                       /* underline */
+                       if (!hide_text_style)
+                               flags ^= PRINTFLAG_UNDERLINE;
+                       break;
+               case 27:
+                       /* ansi color code */
+                       ptr = get_ansi_color(dest->window == NULL || dest->window->theme == NULL ?
+                                            current_theme : dest->window->theme,
+                                            ptr,
+                                            hide_text_style ? NULL : &fgcolor,
+                                            hide_text_style ? NULL : &bgcolor,
+                                            hide_text_style ? NULL : &flags);
+                       break;
+               }
+
+               str = ptr;
+       }
+
+       g_free(dup);
+}
+
+static void read_settings(void)
+{
+       hide_server_tags = settings_get_bool("hide_server_tags");
+       hide_text_style = settings_get_bool("hide_text_style");
+       timestamps = settings_get_bool("timestamps");
+       timestamp_timeout = settings_get_int("timestamp_timeout");
+       msgs_timestamps = settings_get_bool("msgs_timestamps");
+}
+
+void formats_init(void)
+{
+       signal_gui_print_text = signal_get_uniq_id("gui print text");
+
+       read_settings();
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void formats_deinit(void)
+{
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/formats.h b/apps/irssi/src/fe-common/core/formats.h
new file mode 100644 (file)
index 0000000..86c53e6
--- /dev/null
@@ -0,0 +1,115 @@
+#ifndef __FORMATS_H
+#define __FORMATS_H
+
+#include "themes.h"
+#include "fe-windows.h"
+
+#define PRINTFLAG_BOLD          0x01
+#define PRINTFLAG_REVERSE       0x02
+#define PRINTFLAG_UNDERLINE     0x04
+#define PRINTFLAG_BLINK         0x08
+#define PRINTFLAG_MIRC_COLOR    0x10
+#define PRINTFLAG_INDENT        0x20
+#define PRINTFLAG_NEWLINE       0x40
+
+#define MAX_FORMAT_PARAMS 10
+#define DEFAULT_FORMAT_ARGLIST_SIZE 200
+
+enum {
+       FORMAT_STRING,
+       FORMAT_INT,
+       FORMAT_LONG,
+       FORMAT_FLOAT
+};
+
+struct _FORMAT_REC {
+       char *tag;
+       char *def;
+
+       int params;
+       int paramtypes[MAX_FORMAT_PARAMS];
+};
+
+typedef struct {
+       WINDOW_REC *window;
+       SERVER_REC *server;
+       const char *target;
+       int level;
+
+       int hilight_priority;
+       char *hilight_color;
+} TEXT_DEST_REC;
+
+int format_find_tag(const char *module, const char *tag);
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str);
+/* Return how many characters in `str' must be skipped before `len'
+   characters of text is skipped. Like strip_real_length(), except this
+   handles %codes. */
+int format_real_length(const char *str, int len);
+
+char *format_string_expand(const char *text);
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+                     void *server, const char *target,
+                     int formatnum, ...);
+
+/* good size for buffer is DEFAULT_FORMAT_ARGLIST_SIZE */
+void format_read_arglist(va_list va, FORMAT_REC *format,
+                        char **arglist, int arglist_size,
+                        char *buffer, int buffer_size);
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+                           TEXT_DEST_REC *dest, int formatnum, ...);
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+                                TEXT_DEST_REC *dest, int formatnum,
+                                va_list va);
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+                                    TEXT_DEST_REC *dest, int formatnum,
+                                    char **args);
+
+/* add `linestart' to start of each line in `text'. `text' may contain
+   multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart);
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest);
+
+/* return timestamp + server tag */
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t);
+
+
+/* "private" functions for printtext */
+void format_create_dest(TEXT_DEST_REC *dest,
+                       void *server, const char *target,
+                       int level, WINDOW_REC *window);
+
+void format_newline(WINDOW_REC *window);
+
+/* Return how many characters in `str' must be skipped before `len'
+   characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+                     int *last_color_pos, int *last_color_len);
+
+/* strip all color (etc.) codes from `input'.
+   Returns newly allocated string. */
+char *strip_codes(const char *input);
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text);
+
+#define FORMAT_COLOR_NOCHANGE  ('0'-1) /* don't change this, at least hilighting depends this value */
+
+#define FORMAT_STYLE_SPECIAL   0x60
+#define FORMAT_STYLE_BLINK     (0x01 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_UNDERLINE (0x02 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_BOLD      (0x03 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_REVERSE   (0x04 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_INDENT    (0x05 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_DEFAULTS  (0x06 + FORMAT_STYLE_SPECIAL)
+int format_expand_styles(GString *out, char format);
+
+void formats_init(void);
+void formats_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/hilight-text.c b/apps/irssi/src/fe-common/core/hilight-text.c
new file mode 100644 (file)
index 0000000..197957f
--- /dev/null
@@ -0,0 +1,697 @@
+/*
+ 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 "levels.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+
+#include "hilight-text.h"
+#include "nickmatch-cache.h"
+#include "printtext.h"
+#include "formats.h"
+
+static NICKMATCH_REC *nickmatch;
+static HILIGHT_REC *next_nick_hilight, *next_line_hilight;
+static int next_hilight_start, next_hilight_end;
+static int never_hilight_level, default_hilight_level;
+GSList *hilights;
+
+static void reset_cache(void)
+{
+       GSList *tmp;
+
+        never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level;
+       for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+               HILIGHT_REC *rec = tmp->data;
+
+               if (never_hilight_level & rec->level)
+                        never_hilight_level &= ~rec->level;
+       }
+
+       nickmatch_rebuild(nickmatch);
+}
+
+static void hilight_add_config(HILIGHT_REC *rec)
+{
+       CONFIG_NODE *node;
+
+       g_return_if_fail(rec != NULL);
+
+       node = iconfig_node_traverse("(hilights", TRUE);
+       node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+        iconfig_node_set_str(node, "text", rec->text);
+        if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level);
+        if (rec->color) iconfig_node_set_str(node, "color", rec->color);
+        if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color);
+        if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority);
+        iconfig_node_set_bool(node, "nick", rec->nick);
+        iconfig_node_set_bool(node, "word", rec->word);
+        if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE);
+        if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+        if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+
+       if (rec->channels != NULL && *rec->channels != NULL) {
+               node = config_node_section(node, "channels", NODE_TYPE_LIST);
+               iconfig_node_add_list(node, rec->channels);
+       }
+}
+
+static void hilight_remove_config(HILIGHT_REC *rec)
+{
+       CONFIG_NODE *node;
+
+       g_return_if_fail(rec != NULL);
+
+       node = iconfig_node_traverse("hilights", FALSE);
+       if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec));
+}
+
+static void hilight_destroy(HILIGHT_REC *rec)
+{
+       g_return_if_fail(rec != NULL);
+
+#ifdef HAVE_REGEX_H
+       if (rec->regexp_compiled) regfree(&rec->preg);
+#endif
+       if (rec->channels != NULL) g_strfreev(rec->channels);
+       g_free_not_null(rec->color);
+       g_free_not_null(rec->act_color);
+       g_free(rec->text);
+       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)
+{
+       g_return_if_fail(rec != NULL);
+
+       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 int hilight_match_text(HILIGHT_REC *rec, const char *text,
+                             int *match_beg, int *match_end)
+{
+       char *match;
+
+       if (rec->regexp) {
+#ifdef HAVE_REGEX_H
+               regmatch_t rmatch[1];
+
+               if (rec->regexp_compiled &&
+                   regexec(&rec->preg, text, 1, rmatch, 0) == 0) {
+                       if (rmatch[0].rm_so > 0 &&
+                           match_beg != NULL && match_end != NULL) {
+                               *match_beg = rmatch[0].rm_so;
+                               *match_end = rmatch[0].rm_eo;
+                       }
+                       return TRUE;
+               }
+#endif
+       } else {
+               match = rec->fullword ?
+                       stristr_full(text, rec->text) :
+                       stristr(text, rec->text);
+               if (match != NULL) {
+                       if (match_beg != NULL && match_end != NULL) {
+                               *match_beg = (int) (match-text);
+                               *match_end = *match_beg + strlen(rec->text);
+                       }
+                       return TRUE;
+               }
+       }
+
+        return FALSE;
+}
+
+#define hilight_match_level(rec, level) \
+       (level & (((rec)->level != 0 ? rec->level : default_hilight_level)))
+
+#define hilight_match_channel(rec, channel) \
+       ((rec)->channels == NULL || ((channel) != NULL && \
+               strarray_find((rec)->channels, (channel)) != -1))
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+                          const char *nick, const char *address,
+                          int level, const char *str,
+                           int *match_beg, int *match_end)
+{
+       GSList *tmp;
+        CHANNEL_REC *chanrec;
+       NICK_REC *nickrec;
+
+       g_return_val_if_fail(str != NULL, NULL);
+
+       if ((never_hilight_level & level) == level)
+               return NULL;
+
+       if (nick != NULL) {
+                /* check nick mask hilights */
+               chanrec = channel_find(server, channel);
+               nickrec = chanrec == NULL ? NULL :
+                       nicklist_find(chanrec, nick);
+               if (nickrec != NULL) {
+                        HILIGHT_REC *rec;
+
+                       if (nickrec->host == NULL)
+                               nicklist_set_host(chanrec, nickrec, address);
+
+                       rec = nickmatch_find(nickmatch, nickrec);
+                       if (rec != NULL && hilight_match_level(rec, level))
+                               return rec;
+               }
+       }
+
+       for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+               HILIGHT_REC *rec = tmp->data;
+
+               if (!rec->nickmask && hilight_match_level(rec, level) &&
+                   hilight_match_channel(rec, channel) &&
+                   hilight_match_text(rec, str, match_beg, match_end))
+                       return rec;
+       }
+
+        return NULL;
+}
+
+static char *hilight_get_act_color(HILIGHT_REC *rec)
+{
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       return g_strdup(rec->act_color != NULL ? rec->act_color :
+                       rec->color != NULL ? rec->color :
+                       settings_get_str("hilight_act_color"));
+}
+
+static char *hilight_get_color(HILIGHT_REC *rec)
+{
+       const char *color;
+
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       color = rec->color != NULL ? rec->color :
+               settings_get_str("hilight_color");
+
+       return format_string_expand(color);
+}
+
+static void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec)
+{
+       dest->level |= MSGLEVEL_HILIGHT;
+
+       if (rec->priority > 0)
+               dest->hilight_priority = rec->priority;
+
+        dest->hilight_color = hilight_get_act_color(rec);
+}
+
+static void sig_print_text_stripped(TEXT_DEST_REC *dest, const char *str)
+{
+        HILIGHT_REC *hilight;
+
+       g_return_if_fail(str != NULL);
+
+       if (next_nick_hilight != NULL) {
+               if (!next_nick_hilight->nick) {
+                        /* non-nick hilight wanted */
+                       hilight = next_nick_hilight;
+                       next_nick_hilight = NULL;
+                       if (!hilight_match_text(hilight, str,
+                                               &next_hilight_start,
+                                               &next_hilight_end)) {
+                                next_hilight_start = 0;
+                                next_hilight_end = strlen(str);
+                       }
+               } else {
+                       /* nick is highlighted, just set priority */
+                       hilight_update_text_dest(dest, next_nick_hilight);
+                       next_nick_hilight = NULL;
+                       return;
+               }
+       } else {
+               if (dest->level & (MSGLEVEL_NOHILIGHT|MSGLEVEL_HILIGHT))
+                       return;
+
+               hilight = hilight_match(dest->server, dest->target,
+                                       NULL, NULL, dest->level, str,
+                                       &next_hilight_start,
+                                       &next_hilight_end);
+       }
+
+       if (hilight != NULL) {
+               /* update the level / hilight info */
+               hilight_update_text_dest(dest, hilight);
+
+               next_line_hilight = hilight;
+       }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *str)
+{
+       char *color, *newstr;
+        int next_hilight_len;
+
+       if (next_line_hilight == NULL)
+                return;
+
+       color = hilight_get_color(next_line_hilight);
+       next_hilight_len = next_hilight_end-next_hilight_start;
+
+       if (!next_line_hilight->word) {
+               /* hilight whole line */
+               char *tmp = strip_codes(str);
+               newstr = g_strconcat(color, tmp, NULL);
+                g_free(tmp);
+       } else {
+               /* hilight part of the line */
+                GString *tmp;
+                char *middle, *lastcolor;
+               int pos, color_pos, color_len;
+
+                tmp = g_string_new(NULL);
+
+                /* start of the line */
+               pos = strip_real_length(str, next_hilight_start, NULL, NULL);
+               g_string_append(tmp, str);
+                g_string_truncate(tmp, pos);
+
+               /* color */
+                g_string_append(tmp, color);
+
+               /* middle of the line, stripped */
+               middle = strip_codes(str+pos);
+                pos = tmp->len;
+               g_string_append(tmp, middle);
+                g_string_truncate(tmp, pos+next_hilight_len);
+                g_free(middle);
+
+               /* end of the line */
+               pos = strip_real_length(str, next_hilight_end,
+                                       &color_pos, &color_len);
+               if (color_pos > 0)
+                       lastcolor = g_strndup(str+color_pos, color_len);
+                else {
+                        /* no colors in line, change back to default */
+                       lastcolor = g_malloc0(3);
+                       lastcolor[0] = 4;
+                        lastcolor[1] = FORMAT_STYLE_DEFAULTS;
+               }
+               g_string_append(tmp, lastcolor);
+               g_string_append(tmp, str+pos);
+               g_free(lastcolor);
+
+                newstr = tmp->str;
+                g_string_free(tmp, FALSE);
+       }
+
+       next_line_hilight = NULL;
+       signal_emit("print text", 2, dest, newstr);
+
+       g_free(color);
+       g_free(newstr);
+
+       signal_stop();
+}
+
+char *hilight_match_nick(SERVER_REC *server, const char *channel,
+                        const char *nick, const char *address,
+                        int level, const char *msg)
+{
+        HILIGHT_REC *rec;
+       char *color;
+
+       rec = hilight_match(server, channel, nick, address,
+                           level, msg, NULL, NULL);
+       color = rec == NULL || !rec->nick ? NULL :
+               hilight_get_color(rec);
+
+        next_nick_hilight = rec;
+       return color;
+}
+
+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) {
+                reset_cache();
+               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);
+
+               rec->text = g_strdup(text);
+
+               color = config_node_get_str(node, "color", NULL);
+               rec->color = color == NULL || *color == '\0' ? NULL :
+                       g_strdup(color);
+
+               color = config_node_get_str(node, "act_color", NULL);
+               rec->act_color = color == NULL || *color == '\0' ? NULL :
+                       g_strdup(color);
+
+               rec->level = config_node_get_int(node, "level", 0);
+               rec->priority = config_node_get_int(node, "priority", 0);
+               rec->nick = config_node_get_bool(node, "nick", TRUE);
+               rec->word = config_node_get_bool(node, "word", TRUE);
+
+               rec->nickmask = config_node_get_bool(node, "mask", FALSE);
+               rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+               rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+
+#ifdef HAVE_REGEX_H
+               rec->regexp_compiled = !rec->regexp ? FALSE :
+                       regcomp(&rec->preg, rec->text,
+                               REG_EXTENDED|REG_ICASE) == 0;
+#endif
+
+               node = config_node_section(node, "channels", -1);
+               if (node != NULL) rec->channels = config_node_get_list(node);
+       }
+
+        reset_cache();
+}
+
+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,
+                   TXT_HILIGHT_LINE, index, rec->text,
+                   chans != NULL ? chans : "",
+                   levelstr != NULL ? levelstr : "",
+                   rec->nickmask ? " -mask" : "",
+                   rec->fullword ? " -full" : "",
+                   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, TXT_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, TXT_HILIGHT_FOOTER);
+}
+
+/* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -regexp]
+                   [-color <color>] [-actcolor <color>] [-level <level>]
+                  [-channels <channels>] <text> */
+static void cmd_hilight(const char *data)
+{
+        GHashTable *optlist;
+       HILIGHT_REC *rec;
+       char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text;
+       char **channels;
+       void *free_arg;
+
+       g_return_if_fail(data != NULL);
+
+       if (*data == '\0') {
+               cmd_hilight_show();
+               return;
+       }
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+                           PARAM_FLAG_GETREST, "hilight", &optlist, &text))
+               return;
+
+       chanarg = g_hash_table_lookup(optlist, "channels");
+       levelarg = g_hash_table_lookup(optlist, "level");
+       priorityarg = g_hash_table_lookup(optlist, "priority");
+       colorarg = g_hash_table_lookup(optlist, "color");
+       actcolorarg = g_hash_table_lookup(optlist, "actcolor");
+
+       if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
+               g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1);
+
+       rec = hilight_find(text, channels);
+       if (rec == NULL) {
+               rec = g_new0(HILIGHT_REC, 1);
+
+               /* default to nick/word hilighting */
+                rec->nick = TRUE;
+               rec->word = TRUE;
+
+               rec->text = g_strdup(text);
+               rec->channels = channels;
+       } else {
+               g_strfreev(channels);
+
+                hilight_remove_config(rec);
+               hilights = g_slist_remove(hilights, rec);
+       }
+
+       rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 :
+               level2bits(replace_chars(levelarg, ',', ' '));
+       rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg);
+
+       if (g_hash_table_lookup(optlist, "line") != NULL) {
+               rec->word = FALSE;
+               rec->nick = FALSE;
+       }
+
+       if (g_hash_table_lookup(optlist, "word") != NULL) {
+               rec->word = TRUE;
+                rec->nick = FALSE;
+       }
+
+       if (g_hash_table_lookup(optlist, "nick") != NULL)
+                rec->nick = TRUE;
+
+       rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL;
+       rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+       rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+
+       if (colorarg != NULL) {
+               if (*colorarg != '\0')
+                       rec->color = g_strdup(colorarg);
+               else
+                       g_free_and_null(rec->color);
+       }
+       if (actcolorarg != NULL) {
+               if (*actcolorarg != '\0')
+                       rec->act_color = g_strdup(actcolorarg);
+               else
+                       g_free_and_null(rec->act_color);
+       }
+
+#ifdef HAVE_REGEX_H
+       if (rec->regexp_compiled)
+               regfree(&rec->preg);
+       rec->regexp_compiled = !rec->regexp ? FALSE :
+               regcomp(&rec->preg, rec->text, REG_EXTENDED|REG_ICASE) == 0;
+#endif
+
+       hilights = g_slist_append(hilights, rec);
+       hilight_add_config(rec);
+
+       hilight_print(g_slist_index(hilights, rec)+1, rec);
+        cmd_params_free(free_arg);
+
+       reset_cache();
+}
+
+/* SYNTAX: DEHILIGHT <id>|<mask> */
+static void cmd_dehilight(const char *data)
+{
+       HILIGHT_REC *rec;
+       GSList *tmp;
+
+       if (is_numeric(data, ' ')) {
+               /* with index number */
+               tmp = g_slist_nth(hilights, atoi(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, TXT_HILIGHT_NOT_FOUND, data);
+       else {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text);
+               hilight_remove(rec);
+                reset_cache();
+       }
+}
+
+static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+                              NICK_REC *nick)
+{
+       GSList *tmp;
+       HILIGHT_REC *match;
+        char *nickmask;
+       int len, best_match;
+
+       if (nick->host == NULL)
+                return; /* don't check until host is known */
+
+       nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+
+       best_match = 0; match = NULL;
+       for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+               HILIGHT_REC *rec = tmp->data;
+
+               if (rec->nickmask &&
+                   hilight_match_channel(rec, channel->name) &&
+                   match_wildcards(rec->text, nickmask)) {
+                       len = strlen(rec->text);
+                       if (best_match < len) {
+                               best_match = len;
+                               match = rec;
+                       }
+               }
+       }
+       g_free_not_null(nickmask);
+
+       if (match != NULL)
+                g_hash_table_insert(list, nick, match);
+}
+
+static void read_settings(void)
+{
+       default_hilight_level = level2bits(settings_get_str("hilight_level"));
+}
+
+void hilight_text_init(void)
+{
+       settings_add_str("lookandfeel", "hilight_color", "%Y");
+       settings_add_str("lookandfeel", "hilight_act_color", "%M");
+       settings_add_str("lookandfeel", "hilight_level", "PUBLIC DCCMSGS");
+
+       next_nick_hilight = NULL;
+       next_line_hilight = NULL;
+
+        read_settings();
+
+       nickmatch = nickmatch_init(hilight_nick_cache);
+       read_hilight_config();
+
+       signal_add_first("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
+       signal_add_first("print text", (SIGNAL_FUNC) sig_print_text);
+        signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config);
+        signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight);
+       command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight);
+       command_set_options("hilight", "-color -actcolor -level -priority -channels nick word line mask full regexp");
+}
+
+void hilight_text_deinit(void)
+{
+       hilights_destroy_all();
+        nickmatch_deinit(nickmatch);
+
+       signal_remove("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
+       signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+        signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config);
+        signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+       command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight);
+       command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight);
+}
diff --git a/apps/irssi/src/fe-common/core/hilight-text.h b/apps/irssi/src/fe-common/core/hilight-text.h
new file mode 100644 (file)
index 0000000..92093bb
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef __HILIGHT_TEXT_H
+#define __HILIGHT_TEXT_H
+
+#ifdef HAVE_REGEX_H
+#  include <regex.h>
+#endif
+
+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. */
+        char *act_color; /* color for window activity */
+       int priority;
+
+       unsigned int nick:1; /* hilight only nick if possible */
+       unsigned int word:1; /* hilight only word, not full line */
+
+       unsigned int nickmask:1; /* `text' is a nick mask */
+       unsigned int fullword:1; /* match `text' only for full words */
+       unsigned int regexp:1; /* `text' is a regular expression */
+#ifdef HAVE_REGEX_H
+       unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */
+       regex_t preg;
+#endif
+} HILIGHT_REC;
+
+extern GSList *hilights;
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+                          const char *nick, const char *address,
+                          int level, const char *str,
+                          int *match_beg, int *match_end);
+
+char *hilight_match_nick(SERVER_REC *server, const char *channel,
+                        const char *nick, const char *address,
+                        int level, const char *msg);
+
+void hilight_text_init(void);
+void hilight_text_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/keyboard.c b/apps/irssi/src/fe-common/core/keyboard.c
new file mode 100644 (file)
index 0000000..0b7ac71
--- /dev/null
@@ -0,0 +1,820 @@
+/*
+ keyboard.c : irssi
+
+    Copyright (C) 1999-2001 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 "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "keyboard.h"
+#include "fe-windows.h"
+#include "printtext.h"
+
+GSList *keyinfos;
+static GHashTable *keys, *default_keys;
+
+/* A cache of some sort for key presses that generate a single char only.
+   If the key isn't used, used_keys[key] is zero. */
+static char used_keys[256];
+
+/* contains list of all key bindings of which command is "key" -
+   this can be used to check fast if some command queue exists or not.
+   Format is _always_ in key1-key2-key3 format (like ^W-^N,
+   not ^W^N) */
+static GTree *key_states;
+/* List of all key combo names */
+static GSList *key_combos;
+static int key_config_frozen;
+
+struct KEYBOARD_REC {
+       /* example:
+          /BIND ^[ key meta
+          /BIND meta-O key meta2
+          /BIND meta-[ key meta2
+
+          /BIND meta2-C key right
+          /BIND ^W-meta-right /echo ^W Meta-right key pressed
+
+          When ^W Meta-Right is pressed, the full char combination
+          is "^W^[^[[C".
+
+          We'll get there with key states:
+            ^W - key_prev_state = NULL, key_state = NULL -> ^W
+            ^[ - key_prev_state = NULL, key_state = ^W -> meta
+            ^[ - key_prev_state = ^W, key_state = meta -> meta
+            [ - key_prev_state = ^W-meta, key_state = meta -> meta2
+            C - key_prev_state = ^W-meta, key_state = meta2 -> right
+            key_prev_state = ^W-meta, key_state = right -> ^W-meta-right
+
+          key_state is moved to key_prev_state if there's nothing else in
+          /BINDs matching for key_state-newkey.
+
+          ^X^Y equals to ^X-^Y, ABC equals to A-B-C unless there's ABC
+          named key. ^ can be used with ^^ and - with -- */
+       char *key_state, *key_prev_state;
+
+        /* GUI specific data sent in "key pressed" signal */
+        void *gui_data;
+};
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+   key combo states and sending the gui_data parameter in "key pressed"
+   signal */
+KEYBOARD_REC *keyboard_create(void *data)
+{
+       KEYBOARD_REC *rec;
+
+       rec = g_new0(KEYBOARD_REC, 1);
+       rec->gui_data = data;
+
+       signal_emit("keyboard created", 1, rec);
+        return rec;
+}
+
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard)
+{
+       signal_emit("keyboard destroyed", 1, keyboard);
+
+        g_free_not_null(keyboard->key_state);
+        g_free_not_null(keyboard->key_prev_state);
+        g_free(keyboard);
+}
+
+static void key_destroy(KEY_REC *rec, GHashTable *hash)
+{
+       g_hash_table_remove(hash, rec->key);
+
+       g_free_not_null(rec->data);
+       g_free(rec->key);
+       g_free(rec);
+}
+
+static void key_default_add(const char *id, const char *key, const char *data)
+{
+        KEYINFO_REC *info;
+       KEY_REC *rec;
+
+       info = key_info_find(id);
+       if (info == NULL)
+               return;
+
+       rec = g_hash_table_lookup(default_keys, key);
+       if (rec != NULL) {
+               /* key already exists, replace */
+               rec->info->default_keys =
+                       g_slist_remove(rec->info->default_keys, rec);
+               key_destroy(rec, default_keys);
+       }
+
+       rec = g_new0(KEY_REC, 1);
+       rec->key = g_strdup(key);
+       rec->info = info;
+       rec->data = g_strdup(data);
+        info->default_keys = g_slist_append(info->default_keys, rec);
+       g_hash_table_insert(default_keys, rec->key, rec);
+}
+
+static CONFIG_NODE *key_config_find(const char *key)
+{
+       CONFIG_NODE *node;
+        GSList *tmp;
+
+       /* remove old keyboard settings */
+       node = iconfig_node_traverse("(keyboard", TRUE);
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               if (strcmp(config_node_get_str(node, "key", ""), key) == 0)
+                        return node;
+       }
+
+        return NULL;
+}
+
+static void keyconfig_save(const char *id, const char *key, const char *data)
+{
+       CONFIG_NODE *node;
+
+       g_return_if_fail(id != NULL);
+       g_return_if_fail(key != NULL);
+
+       node = key_config_find(key);
+       if (node == NULL) {
+               node = iconfig_node_traverse("(keyboard", TRUE);
+               node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+       }
+
+       iconfig_node_set_str(node, "key", key);
+       iconfig_node_set_str(node, "id", id);
+       iconfig_node_set_str(node, "data", data);
+}
+
+static void keyconfig_clear(const char *key)
+{
+       CONFIG_NODE *node;
+
+       g_return_if_fail(key != NULL);
+
+       /* remove old keyboard settings */
+       node = key_config_find(key);
+        if (node != NULL)
+               iconfig_node_clear(node);
+}
+
+KEYINFO_REC *key_info_find(const char *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;
+}
+
+static KEY_REC *key_combo_find(const char *key)
+{
+       KEYINFO_REC *info;
+        GSList *tmp;
+
+       info = key_info_find("key");
+       if (info == NULL)
+               return NULL;
+
+       for (tmp = info->keys; tmp != NULL; tmp = tmp->next) {
+               KEY_REC *rec = tmp->data;
+
+               if (strcmp(rec->data, key) == 0)
+                        return rec;
+       }
+
+        return NULL;
+}
+
+static void key_states_scan_key(const char *key, KEY_REC *rec, GString *temp)
+{
+       char **keys, **tmp, *p;
+
+       g_string_truncate(temp, 0);
+
+       /* meta-^W^Gfoo -> meta-^W-^G-f-o-o */
+       keys = g_strsplit(key, "-", -1);
+       for (tmp = keys; *tmp != NULL; tmp++) {
+               if (key_combo_find(*tmp)) {
+                        /* key combo */
+                       g_string_append(temp, *tmp);
+                        g_string_append_c(temp, '-');
+                        continue;
+               }
+
+               if (**tmp == '\0') {
+                        /* '-' */
+                       g_string_append(temp, "--");
+                        continue;
+               }
+
+               for (p = *tmp; *p != '\0'; p++) {
+                       g_string_append_c(temp, *p);
+
+                       if (*p == '^') {
+                                /* ctrl-code */
+                               if (p[1] != '\0')
+                                       p++;
+                               g_string_append_c(temp, *p);
+                       }
+
+                       g_string_append_c(temp, '-');
+               }
+       }
+       g_strfreev(keys);
+
+       if (temp->len > 0) {
+               g_string_truncate(temp, temp->len-1);
+
+               if (temp->str[1] == '-' || temp->str[1] == '\0')
+                        used_keys[(int) (unsigned char) temp->str[0]] = 1;
+               g_tree_insert(key_states, g_strdup(temp->str), rec);
+       }
+}
+
+static int key_state_destroy(char *key)
+{
+       g_free(key);
+        return FALSE;
+}
+
+/* Rescan all the key combos and figure out which characters are supposed
+   to be treated as characters and which as key combos.
+   Yes, this is pretty slow function... */
+static void key_states_rescan(void)
+{
+       GString *temp;
+
+       memset(used_keys, 0, sizeof(used_keys));
+
+       g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy,
+                       G_IN_ORDER, NULL);
+       g_tree_destroy(key_states);
+       key_states = g_tree_new((GCompareFunc) strcmp);
+
+        temp = g_string_new(NULL);
+       g_hash_table_foreach(keys, (GHFunc) key_states_scan_key, temp);
+        g_string_free(temp, TRUE);
+}
+
+void key_configure_freeze(void)
+{
+        key_config_frozen++;
+}
+
+void key_configure_thaw(void)
+{
+        g_return_if_fail(key_config_frozen > 0);
+
+       if (--key_config_frozen == 0)
+               key_states_rescan();
+}
+
+static void key_configure_destroy(KEY_REC *rec)
+{
+       g_return_if_fail(rec != NULL);
+
+       rec->info->keys = g_slist_remove(rec->info->keys, rec);
+       g_hash_table_remove(keys, rec->key);
+
+       if (!key_config_frozen)
+                key_states_rescan();
+
+       g_free_not_null(rec->data);
+       g_free(rec->key);
+       g_free(rec);
+}
+
+/* Configure new key */
+static void key_configure_create(const char *id, const char *key,
+                                const char *data)
+{
+       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_hash_table_lookup(keys, key);
+       if (rec != NULL)
+               key_configure_destroy(rec);
+
+       rec = g_new0(KEY_REC, 1);
+       rec->key = g_strdup(key);
+       rec->info = info;
+       rec->data = g_strdup(data);
+       info->keys = g_slist_append(info->keys, rec);
+       g_hash_table_insert(keys, rec->key, rec);
+
+       if (!key_config_frozen)
+                key_states_rescan();
+}
+
+/* Bind a key for function */
+void key_bind(const char *id, const char *description,
+             const char *key_default, const char *data, SIGNAL_FUNC func)
+{
+       KEYINFO_REC *info;
+       char *key;
+
+       g_return_if_fail(id != NULL);
+
+       /* create key info record */
+       info = key_info_find(id);
+       if (info == NULL) {
+               g_return_if_fail(func != NULL);
+
+               if (description == NULL)
+                       g_warning("key_bind(%s) should have description!", id);
+               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 */
+               key = g_strconcat("key ", id, NULL);
+               signal_add(key, func);
+               g_free(key);
+
+               signal_emit("keyinfo created", 1, info);
+       }
+
+       if (key_default != NULL && *key_default != '\0') {
+                key_default_add(id, key_default, data);
+               key_configure_create(id, key_default, data);
+       }
+}
+
+static void keyinfo_remove(KEYINFO_REC *info)
+{
+       g_return_if_fail(info != NULL);
+
+       keyinfos = g_slist_remove(keyinfos, info);
+       signal_emit("keyinfo destroyed", 1, info);
+
+       /* destroy all keys */
+        g_slist_foreach(info->keys, (GFunc) key_destroy, keys);
+        g_slist_foreach(info->default_keys, (GFunc) key_destroy, default_keys);
+
+       /* destroy key info */
+       g_slist_free(info->keys);
+       g_slist_free(info->default_keys);
+       g_free_not_null(info->description);
+       g_free(info->id);
+       g_free(info);
+}
+
+/* Unbind key */
+void key_unbind(const char *id, SIGNAL_FUNC func)
+{
+       KEYINFO_REC *info;
+       char *key;
+
+       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 */
+       key = g_strconcat("key ", id, NULL);
+       signal_remove(key, func);
+       g_free(key);
+}
+
+/* Configure new key */
+void key_configure_add(const char *id, const char *key, const char *data)
+{
+       g_return_if_fail(id != NULL);
+       g_return_if_fail(key != NULL && *key != '\0');
+
+       key_configure_create(id, key, data);
+       keyconfig_save(id, key, data);
+}
+
+/* Remove key */
+void key_configure_remove(const char *key)
+{
+       KEY_REC *rec;
+
+       g_return_if_fail(key != NULL);
+
+       rec = g_hash_table_lookup(keys, key);
+       if (rec == NULL) return;
+
+        keyconfig_clear(key);
+       key_configure_destroy(rec);
+}
+
+static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key)
+{
+       int consumed;
+        char *str;
+
+       str = g_strconcat("key ", key->info->id, NULL);
+       consumed = signal_emit(str, 3, key->data, keyboard->gui_data, key->info);
+       g_free(str);
+
+        return consumed;
+}
+
+int key_states_search(const char *combo, const char *search)
+{
+       while (*search != '\0') {
+               if (*combo != *search)
+                       return *search - *combo;
+                search++; combo++;
+       }
+
+       return *combo == '\0' || *combo == '-' ? 0 : -1;
+}
+
+/* Returns TRUE if key press was consumed. Control characters should be sent
+   as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */
+int key_pressed(KEYBOARD_REC *keyboard, const char *key)
+{
+       KEY_REC *rec;
+       char *str;
+        int consumed;
+
+       g_return_val_if_fail(keyboard != NULL, FALSE);
+       g_return_val_if_fail(key != NULL && *key != '\0', FALSE);
+
+       if (keyboard->key_state == NULL) {
+               if (key[1] == '\0' &&
+                   !used_keys[(int) (unsigned char) key[0]]) {
+                        /* fast check - key not used */
+                       return FALSE;
+               }
+
+               rec = g_tree_search(key_states,
+                                   (GSearchFunc) key_states_search,
+                                   (void *) key);
+               if (rec == NULL ||
+                   (g_tree_lookup(key_states, (void *) key) != NULL &&
+                    strcmp(rec->info->id, "key") != 0)) {
+                       /* a single non-combo key was pressed */
+                       rec = g_hash_table_lookup(keys, key);
+                       if (rec == NULL)
+                               return FALSE;
+                       consumed = key_emit_signal(keyboard, rec);
+
+                       /* never consume non-control characters */
+                       return consumed && key[1] != '\0';
+               }
+       }
+
+       if (keyboard->key_state == NULL) {
+                /* first key in combo */
+               rec = g_tree_lookup(key_states, (void *) key);
+       } else {
+               /* continuing key combination */
+               str = g_strconcat(keyboard->key_state, "-", key, NULL);
+               rec = g_tree_lookup(key_states, str);
+               g_free(str);
+       }
+
+       if (rec != NULL && strcmp(rec->info->id, "key") == 0) {
+               /* combo has a specified name, use it */
+               g_free_not_null(keyboard->key_state);
+               keyboard->key_state = g_strdup(rec->data);
+       } else {
+               /* some unnamed key - move key_state after key_prev_state
+                  and replace key_state with this new key */
+               if (keyboard->key_prev_state == NULL)
+                       keyboard->key_prev_state = keyboard->key_state;
+               else {
+                       str = g_strconcat(keyboard->key_prev_state, "-",
+                                         keyboard->key_state, NULL);
+                       g_free(keyboard->key_prev_state);
+                       g_free(keyboard->key_state);
+                       keyboard->key_prev_state = str;
+               }
+
+               keyboard->key_state = g_strdup(key);
+       }
+
+        /* what to do with the key combo? */
+       str = keyboard->key_prev_state == NULL ?
+               g_strdup(keyboard->key_state) :
+               g_strconcat(keyboard->key_prev_state, "-",
+                           keyboard->key_state, NULL);
+
+       rec = g_tree_lookup(key_states, str);
+       if (rec != NULL) {
+               if (strcmp(rec->info->id, "key") == 0)
+                       rec = g_tree_lookup(key_states, rec->data);
+
+               if (rec != NULL) {
+                       /* full key combo */
+                       key_emit_signal(keyboard, rec);
+                       rec = NULL;
+               }
+       } else {
+                /* check that combo is possible */
+               rec = g_tree_search(key_states,
+                                   (GSearchFunc) key_states_search, str);
+       }
+
+       if (rec == NULL) {
+               /* a) key combo finished, b) unknown key combo, abort */
+               g_free_and_null(keyboard->key_prev_state);
+               g_free_and_null(keyboard->key_state);
+       }
+
+       g_free(str);
+        return TRUE;
+}
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+                            int flags, void *data)
+{
+       signal_emit("gui entry redirect", 4, func, entry,
+                   GINT_TO_POINTER(flags), data);
+}
+
+static void sig_command(const char *data)
+{
+       const char *cmdchars;
+       char *str;
+
+       cmdchars = settings_get_str("cmdchars");
+       str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+               g_strdup_printf("%c%s", *cmdchars, data);
+
+       signal_emit("send command", 3, str, active_win->active_server, active_win->active);
+
+       g_free(str);
+}
+
+static void sig_key(const char *data)
+{
+        /* we should never get here */
+}
+
+static void sig_multi(const char *data, void *gui_data)
+{
+        KEYINFO_REC *info;
+       char **list, **tmp, *p, *str;
+
+       list = g_strsplit(data, ";", -1);
+       for (tmp = list; *tmp != NULL; tmp++) {
+               p = strchr(*tmp, ' ');
+               if (p != NULL) *p++ = '\0'; else p = "";
+
+               info = key_info_find(*tmp);
+               if (info != NULL) {
+                       str = g_strconcat("key ", info->id, NULL);
+                       signal_emit(str, 3, p, gui_data, info);
+                       g_free(str);
+               }
+       }
+        g_strfreev(list);
+}
+
+static void cmd_show_keys(const char *searchkey, int full)
+{
+       GSList *info, *key;
+        int len;
+
+       len = searchkey == NULL ? 0 : strlen(searchkey);
+       for (info = keyinfos; info != NULL; info = info->next) {
+               KEYINFO_REC *rec = info->data;
+
+               for (key = rec->keys; key != NULL; key = key->next) {
+                       KEY_REC *rec = key->data;
+
+                       if ((len == 0 || g_strncasecmp(rec->key, searchkey, len) == 0) &&
+                           (!full || rec->key[len] == '\0')) {
+                               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_KEY,
+                                           rec->key, rec->info->id, rec->data == NULL ? "" : rec->data);
+                       }
+               }
+       }
+}
+
+/* SYNTAX: BIND [-delete] [<key> [<command> [<data>]]] */
+static void cmd_bind(const char *data)
+{
+       GHashTable *optlist;
+       char *key, *id, *keydata;
+       void *free_arg;
+       int command_id;
+
+       if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+                           "bind", &optlist, &key, &id, &keydata))
+               return;
+
+       if (*key != '\0' && g_hash_table_lookup(optlist, "delete")) {
+                /* delete key */
+               key_configure_remove(key);
+               cmd_params_free(free_arg);
+               return;
+       }
+
+       if (*id == '\0') {
+               /* show some/all keys */
+               cmd_show_keys(key, FALSE);
+               cmd_params_free(free_arg);
+               return;
+       }
+
+       command_id = strchr(settings_get_str("cmdchars"), *id) != NULL;
+       if (command_id) {
+               /* using shortcut to command id */
+               keydata = g_strconcat(id, " ", keydata, NULL);
+               id = "command";
+       }
+
+       if (key_info_find(id) == NULL)
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_BIND_UNKNOWN_ID, id);
+       else {
+               key_configure_add(id, key, keydata);
+               cmd_show_keys(key, TRUE);
+       }
+
+       if (command_id) g_free(keydata);
+        cmd_params_free(free_arg);
+}
+
+static GList *completion_get_keyinfos(const char *info)
+{
+       GList *list;
+       GSList *tmp;
+       int len;
+
+       list = NULL; len = strlen(info);
+       for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+               KEYINFO_REC *rec = tmp->data;
+
+               if (g_strncasecmp(rec->id, info, len) == 0)
+                        list = g_list_append(list, g_strdup(rec->id));
+       }
+
+       return list;
+}
+
+static void sig_complete_bind(GList **list, WINDOW_REC *window,
+                             const char *word, const char *line,
+                             int *want_space)
+{
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+       if (*line == '\0' || strchr(line, ' ') != NULL)
+               return;
+
+       *list = completion_get_keyinfos(word);
+       if (*list != NULL) signal_stop();
+}
+
+static int key_destroy_hash(const char *key, KEY_REC *rec)
+{
+       rec->info->keys = g_slist_remove(rec->info->keys, rec);
+
+       g_free_not_null(rec->data);
+       g_free(rec->key);
+       g_free(rec);
+        return TRUE;
+}
+
+static void key_copy_default(const char *key, KEY_REC *orig)
+{
+       KEY_REC *rec;
+
+        rec = g_new0(KEY_REC, 1);
+       rec->key = g_strdup(orig->key);
+       rec->info = orig->info;
+       rec->data = g_strdup(orig->data);
+
+       rec->info->keys = g_slist_append(rec->info->keys, rec);
+       g_hash_table_insert(keys, rec->key, rec);
+}
+
+static void keyboard_reset_defaults(void)
+{
+       g_hash_table_foreach_remove(keys, (GHRFunc) key_destroy_hash, NULL);
+        g_hash_table_foreach(default_keys, (GHFunc) key_copy_default, NULL);
+}
+
+static void key_config_read(CONFIG_NODE *node)
+{
+       char *key, *id, *data;
+
+       g_return_if_fail(node != NULL);
+
+       key = config_node_get_str(node, "key", NULL);
+       id = config_node_get_str(node, "id", NULL);
+       data = config_node_get_str(node, "data", NULL);
+
+       if (key != NULL && id != NULL)
+               key_configure_create(id, key, data);
+}
+
+static void read_keyboard_config(void)
+{
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+        key_configure_freeze();
+
+       keyboard_reset_defaults();
+
+       node = iconfig_node_traverse("keyboard", FALSE);
+       if (node == NULL) {
+               key_configure_thaw();
+               return;
+       }
+
+       /* FIXME: backward "compatibility" - remove after irssi .99 */
+       if (node->type != NODE_TYPE_LIST) {
+                iconfig_node_remove(NULL, node);
+               key_configure_thaw();
+               return;
+       }
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+               key_config_read(tmp->data);
+
+        key_configure_thaw();
+}
+
+void keyboard_init(void)
+{
+       keys = g_hash_table_new((GHashFunc) g_str_hash,
+                               (GCompareFunc) g_str_equal);
+       default_keys = g_hash_table_new((GHashFunc) g_str_hash,
+                                       (GCompareFunc) g_str_equal);
+       keyinfos = NULL;
+       key_states = g_tree_new((GCompareFunc) strcmp);
+       key_combos = NULL;
+        key_config_frozen = 0;
+       memset(used_keys, 0, sizeof(used_keys));
+
+       key_bind("command", "Run any IRC command", NULL, NULL, (SIGNAL_FUNC) sig_command);
+       key_bind("key", "Specify name for key binding", NULL, NULL, (SIGNAL_FUNC) sig_key);
+       key_bind("multi", "Run multiple commands", NULL, NULL, (SIGNAL_FUNC) sig_multi);
+
+       /* read the keyboard config when all key binds are known */
+       signal_add("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+       signal_add("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+       signal_add("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+
+       command_bind("bind", NULL, (SIGNAL_FUNC) cmd_bind);
+       command_set_options("bind", "delete");
+}
+
+void keyboard_deinit(void)
+{
+       while (keyinfos != NULL)
+               keyinfo_remove(keyinfos->data);
+       g_hash_table_destroy(keys);
+       g_hash_table_destroy(default_keys);
+
+       g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy,
+                       G_IN_ORDER, NULL);
+       g_tree_destroy(key_states);
+
+       signal_remove("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+        signal_remove("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+       signal_remove("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+       command_unbind("bind", (SIGNAL_FUNC) cmd_bind);
+}
diff --git a/apps/irssi/src/fe-common/core/keyboard.h b/apps/irssi/src/fe-common/core/keyboard.h
new file mode 100644 (file)
index 0000000..9f2652e
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef __KEYBOARD_H
+#define __KEYBOARD_H
+
+#include "signals.h"
+
+typedef struct KEYBOARD_REC KEYBOARD_REC;
+
+typedef struct {
+       char *id;
+       char *description;
+
+       GSList *keys, *default_keys;
+} KEYINFO_REC;
+
+typedef struct {
+       KEYINFO_REC *info;
+
+       char *key;
+       char *data;
+} KEY_REC;
+
+extern GSList *keyinfos;
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+   key combo states and sending the gui_data parameter in "key pressed"
+   signal */
+KEYBOARD_REC *keyboard_create(void *gui_data);
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard);
+/* Returns TRUE if key press was consumed. Control characters should be sent
+   as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */
+int key_pressed(KEYBOARD_REC *keyboard, const char *key);
+
+void key_bind(const char *id, const char *description,
+             const char *key_default, const char *data, SIGNAL_FUNC func);
+void key_unbind(const char *id, SIGNAL_FUNC func);
+
+void key_configure_freeze(void);
+void key_configure_thaw(void);
+
+void key_configure_add(const char *id, const char *key, const char *data);
+void key_configure_remove(const char *key);
+
+KEYINFO_REC *key_info_find(const char *id);
+
+#define ENTRY_REDIRECT_FLAG_HOTKEY     0x01
+#define ENTRY_REDIRECT_FLAG_HIDDEN     0x02
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+                            int flags, void *data);
+
+void keyboard_init(void);
+void keyboard_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/module-formats.c b/apps/irssi/src/fe-common/core/module-formats.c
new file mode 100644 (file)
index 0000000..9ccdd10
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ 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 "formats.h"
+
+FORMAT_REC fecommon_core_formats[] = {
+       { MODULE_NAME, "Core", 0 },
+
+       /* ---- */
+       { NULL, "Windows", 0 },
+
+       { "line_start", "{line_start}", 0 },
+       { "line_start_irssi", "{line_start}{hilight Irssi:} ", 0 },
+        { "timestamp", "{timestamp $Z} ", 6, { 1, 1, 1, 1, 1, 1 } },
+       { "servertag", "[$0] ", 1, { 0 } },
+       { "daychange", "Day changed to $[-2.0]{0} $3 $2", 4, { 1, 1, 1, 0 } },
+       { "talking_with", "You are now talking with {nick $0}", 1, { 0 } },
+       { "refnum_too_low", "Window number must be greater than 1", 0 },
+       { "error_server_sticky", "Window's server is sticky and it cannot be changed without -unsticky option", 0 },
+       { "set_server_sticky", "Window's server set sticky", 1, { 0 } },
+       { "unset_server_sticky", "Window's server isn't sticky anymore", 1, { 0 } },
+       { "window_level", "Window level is now $0", 1, { 0 } },
+       { "windowlist_header", "Ref Name                 Active item     Server          Level", 0 },
+       { "windowlist_line", "$[3]0 %|$[20]1 $[15]2 $[15]3 $4", 5, { 1, 0, 0, 0, 0 } },
+       { "windowlist_footer", "", 0 },
+       { "windows_layout_saved", "Layout of windows is now remembered next time you start irssi", 0 },
+       { "windows_layout_reset", "Layout of windows reset to defaults", 0 },
+
+       /* ---- */
+       { NULL, "Server", 0 },
+
+       { "looking_up", "Looking up {server $0}", 1, { 0 } },
+       { "connecting", "Connecting to {server $0} [$1] port {hilight $2}", 3, { 0, 0, 1 } },
+       { "connection_established", "Connection to {server $0} established", 1, { 0 } },
+       { "cant_connect", "Unable to connect server {server $0} port {hilight $1} {reason $2}", 3, { 0, 1, 0 } },
+       { "connection_lost", "Connection lost to {server $0}", 1, { 0 } },
+       { "lag_disconnected", "No PONG reply from server {server $0} in $1 seconds, disconnecting", 2, { 0, 1 } },
+       { "disconnected", "Disconnected from {server $0} {reason $1}", 2, { 0, 0 } },
+       { "server_quit", "Disconnecting from server {server $0}: {reason $1}", 2, { 0, 0 } },
+       { "server_changed", "Changed to {hilight $2} server {server $1}", 3, { 0, 0, 0 } },
+       { "unknown_server_tag", "Unknown server tag {server $0}", 1, { 0 } },
+       { "no_connected_servers", "Not connected to any servers", 0 },
+       { "server_list", "{server $0}: $1:$2 ($3)", 5, { 0, 0, 1, 0, 0 } },
+       { "server_lookup_list", "{server $0}: $1:$2 ($3) (connecting...)", 5, { 0, 0, 1, 0, 0 } },
+       { "server_reconnect_list", "{server $0}: $1:$2 ($3) ($5 left before reconnecting)", 6, { 0, 0, 1, 0, 0, 0 } },
+       { "server_reconnect_removed", "Removed reconnection to server {server $0} port {hilight $1}", 3, { 0, 1, 0 } },
+       { "server_reconnect_not_found", "Reconnection tag {server $0} not found", 1, { 0 } },
+       { "setupserver_added", "Server {server $0} saved", 2, { 0, 1 } },
+       { "setupserver_removed", "Server {server $0} removed", 2, { 0, 1 } },
+       { "setupserver_not_found", "Server {server $0} not found", 2, { 0, 1 } },
+
+       /* ---- */
+       { NULL, "Channels", 0 },
+
+       { "join", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}", 3, { 0, 0, 0 } },
+       { "part", "{channick $0} {chanhost $1} has left {channel $2} {reason $3}", 4, { 0, 0, 0, 0 } },
+       { "kick", "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}", 4, { 0, 0, 0, 0 } },
+       { "quit", "{channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+       { "quit_once", "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+       { "invite", "{nick $0} invites you to {channel $1}", 2, { 0, 0 } },
+       { "new_topic", "{nick $0} changed the topic of {channel $1} to: $2", 3, { 0, 0, 0 } },
+       { "topic_unset", "Topic unset by {nick $0} on {channel $1}", 2, { 0, 0 } },
+       { "your_nick_changed", "You're now known as {nick $1}", 3, { 0, 0, 0 } },
+       { "nick_changed", "{channick $0} is now known as {channick_hilight $1}", 3, { 0, 0, 0 } },
+       { "talking_in", "You are now talking in {channel $0}", 1, { 0 } },
+       { "not_in_channels", "You are not on any channels", 0 },
+       { "current_channel", "Current channel {channel $0}", 1, { 0 } },
+       { "names", "{names_users Users {names_channel $0}} $1", 2, { 0, 0 } },
+        { "names_nick", "{names_nick $0 $1}", 2, { 0, 0 } },
+        { "endofnames", "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} voices, {hilight $4} normal}", 5, { 0, 1, 1, 1, 1 } },
+       { "chanlist_header", "You are on the following channels:", 0 },
+       { "chanlist_line", "{channel $[-10]0} %|+$1 ($2): $3", 4, { 0, 0, 0, 0 } },
+       { "chansetup_not_found", "Channel {channel $0} not found", 2, { 0, 0 } },
+       { "chansetup_added", "Channel {channel $0} saved", 2, { 0, 0 } },
+       { "chansetup_removed", "Channel {channel $0} removed", 2, { 0, 0 } },
+       { "chansetup_header", "Channel         IRC net    Password   Settings", 0 },
+       { "chansetup_line", "{channel $[15]0} %|$[10]1 $[10]2 $3", 4, { 0, 0, 0, 0 } },
+       { "chansetup_footer", "", 0 },
+       { "channel_move_notify", "{channel $0} is already joined in window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } },
+
+       /* ---- */
+       { NULL, "Messages", 0 },
+
+       { "own_msg", "{ownmsgnick $2 {ownnick $0}}$1", 3, { 0, 0, 0 } },
+       { "own_msg_channel", "{ownmsgnick $3 {ownnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+       { "own_msg_private", "{ownprivmsg msg $0}$1", 2, { 0, 0 } },
+       { "own_msg_private_query", "{ownprivmsgnick {ownprivnick $2}}$1", 3, { 0, 0, 0 } },
+       { "pubmsg_me", "{pubmsgmenick $2 {menick $0}}$1", 3, { 0, 0, 0 } },
+       { "pubmsg_me_channel", "{pubmsgmenick $3 {menick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+       { "pubmsg_hilight", "{pubmsghinick $0 $3 $1}$2", 4, { 0, 0, 0, 0 } },
+       { "pubmsg_hilight_channel", "{pubmsghinick $0 $4 $1{msgchannel $2}}$3", 5, { 0, 0, 0, 0, 0 } },
+       { "pubmsg", "{pubmsgnick $2 {pubnick $0}}$1", 3, { 0, 0, 0 } },
+       { "pubmsg_channel", "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+       { "msg_private", "{privmsg $0 $1}$2", 3, { 0, 0, 0 } },
+       { "msg_private_query", "{privmsgnick $0}$2", 3, { 0, 0, 0 } },
+       { "no_msgs_got", "You have not received a message from anyone yet", 0 },
+       { "no_msgs_sent", "You have not sent a message to anyone yet", 0 },
+
+       /* ---- */
+       { NULL, "Queries", 0 },
+
+       { "query_start", "Starting query with {nick $0}", 1, { 0 } },
+       { "no_query", "No query with {nick $0}", 1, { 0 } },
+       { "query_server_changed", "Query with {nick $0} changed to server {server $1}", 2, { 0, 0 } },
+       { "query_move_notify", "Query with {nick $0} is already created to window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } },
+
+       /* ---- */
+       { NULL, "Highlighting", 0 },
+
+       { "hilight_header", "Highlights:", 0 },
+       { "hilight_line", "$[-4]0 $1 $2 $3$4$5", 7, { 1, 0, 0, 0, 0, 0, 0 } },
+       { "hilight_footer", "", 0 },
+       { "hilight_not_found", "Highlight not found: $0", 1, { 0 } },
+       { "hilight_removed", "Highlight removed: $0", 1, { 0 } },
+
+       /* ---- */
+       { NULL, "Aliases", 0 },
+
+       { "alias_added", "Alias $0 added", 1, { 0 } },
+       { "alias_removed", "Alias $0 removed", 1, { 0 } },
+       { "alias_not_found", "No such alias: $0", 1, { 0 } },
+       { "aliaslist_header", "Aliases:", 0 },
+       { "aliaslist_line", "$[10]0 $1", 2, { 0, 0 } },
+       { "aliaslist_footer", "", 0 },
+
+       /* ---- */
+       { NULL, "Logging", 0 },
+
+       { "log_opened", "Log file {hilight $0} opened", 1, { 0 } },
+       { "log_closed", "Log file {hilight $0} closed", 1, { 0 } },
+       { "log_create_failed", "Couldn't create log file {hilight $0}: $1", 2, { 0, 0 } },
+       { "log_locked", "Log file {hilight $0} is locked, probably by another running Irssi", 1, { 0 } },
+       { "log_not_open", "Log file {hilight $0} not open", 1, { 0 } },
+       { "log_started", "Started logging to file {hilight $0}", 1, { 0 } },
+       { "log_stopped", "Stopped logging to file {hilight $0}", 1, { 0 } },
+       { "log_list_header", "Logs:", 0 },
+       { "log_list", "$0 $1: $2 $3$4", 5, { 1, 0, 0, 0, 0, 0 } },
+       { "log_list_footer", "", 0 },
+       { "windowlog_file", "Window LOGFILE set to $0", 1, { 0 } },
+       { "windowlog_file_logging", "Can't change window's logfile while log is on", 0 },
+       { "no_away_msgs", "No new messages in awaylog", 1, { 0 } },
+       { "away_msgs", "{hilight $1} new messages in awaylog:", 2, { 0, 1 } },
+
+       /* ---- */
+       { NULL, "Modules", 0 },
+
+       { "module_header", "Loaded modules:", 0, },
+       { "module_line", "  $0", 1, { 0 } },
+       { "module_footer", "", 0, },
+       { "module_already_loaded", "Module {hilight $0} already loaded", 1, { 0 } },
+       { "module_load_error", "Error loading module {hilight $0}: $1", 2, { 0, 0 } },
+       { "module_invalid", "{hilight $0} isn't Irssi module", 1, { 0 } },
+       { "module_loaded", "Loaded module {hilight $0}", 1, { 0 } },
+       { "module_unloaded", "Unloaded module {hilight $0}", 1, { 0 } },
+
+       /* ---- */
+       { NULL, "Commands", 0 },
+
+       { "command_unknown", "Unknown command: $0", 1, { 0 } },
+       { "command_ambiguous", "Ambiguous command: $0", 1, { 0 } },
+       { "option_unknown", "Unknown option: $0", 1, { 0 } },
+       { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } },
+       { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } },
+       { "not_enough_params", "Not enough parameters given", 0 },
+       { "not_connected", "Not connected to server", 0 },
+       { "not_joined", "Not joined to any channel", 0 },
+       { "chan_not_found", "Not joined to such channel", 0 },
+       { "chan_not_synced", "Channel not fully synchronized yet, try again after a while", 0 },
+       { "not_good_idea", "Doing this is not a good idea. Add -YES if you really mean it", 0 },
+
+       /* ---- */
+       { NULL, "Themes", 0 },
+
+       { "theme_saved", "Theme saved to $0", 1, { 0 } },
+       { "theme_save_failed", "Error saving theme to $0: $1", 2, { 0, 0 } },
+       { "theme_not_found", "Theme {hilight $0} not found", 1, { 0 } },
+       { "theme_changed", "Using now theme {hilight $0} ($1)", 2, { 0, 0 } },
+       { "window_theme_changed", "Using theme {hilight $0} ($1) in this window", 2, { 0, 0 } },
+       { "format_title", "%:[{hilight $0}] - [{hilight $1}]%:", 2, { 0, 0 } },
+       { "format_subtitle", "[{hilight $0}]", 1, { 0 } },
+       { "format_item", "$0 = $1", 2, { 0, 0 } },
+
+       /* ---- */
+       { NULL, "Ignores", 0 },
+
+       { "ignored", "Ignoring {hilight $1} from {nick $0}", 2, { 0, 0 } },
+       { "unignored", "Unignored {nick $0}", 1, { 0 } },
+       { "ignore_not_found", "{nick $0} is not being ignored", 1, { 0 } },
+       { "ignore_no_ignores", "There are no ignores", 0 },
+       { "ignore_header", "Ignorance List:", 0 },
+       { "ignore_line", "$[-4]0 $1: $2 $3 $4", 4, { 1, 0, 0, 0 } },
+       { "ignore_footer", "", 0 },
+
+       /* ---- */
+       { NULL, "Misc", 0 },
+
+       { "unknown_chat_protocol", "Unknown chat protocol: $0", 1, { 0 } },
+       { "unknown_chatnet", "Unknown chat network: $0", 1, { 0 } },
+       { "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 },
+       { "perl_error", "Perl error: $0", 1, { 0 } },
+       { "bind_key", "$[10]0 $1 $2", 3, { 0, 0, 0 } },
+       { "bind_unknown_id", "Unknown bind action: $0", 1, { 0 } },
+       { "config_saved", "Saved configuration to file $0", 1, { 0 } },
+       { "config_reloaded", "Reloaded configuration", 1, { 0 } },
+       { "config_modified", "Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?", 1, { 0 } },
+       { "glib_error", "{error GLib $0} $1", 2, { 0, 0 } },
+       { "overwrite_config", "Overwrite config (y/N)?", 0 },
+
+       { NULL, NULL, 0 }
+};
diff --git a/apps/irssi/src/fe-common/core/module-formats.h b/apps/irssi/src/fe-common/core/module-formats.h
new file mode 100644 (file)
index 0000000..e0f31f9
--- /dev/null
@@ -0,0 +1,194 @@
+#include "formats.h"
+
+enum {
+       TXT_MODULE_NAME,
+
+       TXT_FILL_1,
+
+       TXT_LINE_START,
+       TXT_LINE_START_IRSSI,
+       TXT_TIMESTAMP,
+       TXT_SERVERTAG,
+       TXT_DAYCHANGE,
+       TXT_TALKING_WITH,
+       TXT_REFNUM_TOO_LOW,
+        TXT_ERROR_SERVER_STICKY,
+        TXT_SET_SERVER_STICKY,
+        TXT_UNSET_SERVER_STICKY,
+        TXT_WINDOW_LEVEL,
+       TXT_WINDOWLIST_HEADER,
+       TXT_WINDOWLIST_LINE,
+       TXT_WINDOWLIST_FOOTER,
+       TXT_WINDOWS_LAYOUT_SAVED,
+       TXT_WINDOWS_LAYOUT_RESET,
+
+       TXT_FILL_2,
+
+       TXT_LOOKING_UP,
+       TXT_CONNECTING,
+       TXT_CONNECTION_ESTABLISHED,
+       TXT_CANT_CONNECT,
+       TXT_CONNECTION_LOST,
+       TXT_LAG_DISCONNECTED,
+       TXT_DISCONNECTED,
+       TXT_SERVER_QUIT,
+       TXT_SERVER_CHANGED,
+       TXT_UNKNOWN_SERVER_TAG,
+       TXT_NO_CONNECTED_SERVERS,
+       TXT_SERVER_LIST,
+       TXT_SERVER_LOOKUP_LIST,
+       TXT_SERVER_RECONNECT_LIST,
+       TXT_RECONNECT_REMOVED,
+       TXT_RECONNECT_NOT_FOUND,
+       TXT_SETUPSERVER_ADDED,
+       TXT_SETUPSERVER_REMOVED,
+       TXT_SETUPSERVER_NOT_FOUND,
+
+       TXT_FILL_3,
+
+       TXT_JOIN,
+       TXT_PART,
+       TXT_KICK,
+       TXT_QUIT,
+       TXT_QUIT_ONCE,
+       TXT_INVITE,
+       TXT_NEW_TOPIC,
+       TXT_TOPIC_UNSET,
+       TXT_YOUR_NICK_CHANGED,
+       TXT_NICK_CHANGED,
+       TXT_TALKING_IN,
+       TXT_NOT_IN_CHANNELS,
+       TXT_CURRENT_CHANNEL,
+       TXT_NAMES,
+       TXT_NAMES_NICK,
+       TXT_ENDOFNAMES,
+       TXT_CHANLIST_HEADER,
+       TXT_CHANLIST_LINE,
+       TXT_CHANSETUP_NOT_FOUND,
+       TXT_CHANSETUP_ADDED,
+       TXT_CHANSETUP_REMOVED,
+       TXT_CHANSETUP_HEADER,
+       TXT_CHANSETUP_LINE,
+       TXT_CHANSETUP_FOOTER,
+        TXT_CHANNEL_MOVE_NOTIFY,
+
+        TXT_FILL_4,
+
+       TXT_OWN_MSG,
+       TXT_OWN_MSG_CHANNEL,
+       TXT_OWN_MSG_PRIVATE,
+       TXT_OWN_MSG_PRIVATE_QUERY,
+       TXT_PUBMSG_ME,
+       TXT_PUBMSG_ME_CHANNEL,
+       TXT_PUBMSG_HILIGHT,
+       TXT_PUBMSG_HILIGHT_CHANNEL,
+       TXT_PUBMSG,
+       TXT_PUBMSG_CHANNEL,
+       TXT_MSG_PRIVATE,
+       TXT_MSG_PRIVATE_QUERY,
+       TXT_NO_MSGS_GOT,
+       TXT_NO_MSGS_SENT,
+
+       TXT_FILL_5,
+
+       TXT_QUERY_STARTED,
+       TXT_NO_QUERY,
+       TXT_QUERY_SERVER_CHANGED,
+        TXT_QUERY_MOVE_NOTIFY,
+
+       TXT_FILL_6,
+
+       TXT_HILIGHT_HEADER,
+       TXT_HILIGHT_LINE,
+       TXT_HILIGHT_FOOTER,
+       TXT_HILIGHT_NOT_FOUND,
+       TXT_HILIGHT_REMOVED,
+
+       TXT_FILL_7,
+
+       TXT_ALIAS_ADDED,
+       TXT_ALIAS_REMOVED,
+       TXT_ALIAS_NOT_FOUND,
+       TXT_ALIASLIST_HEADER,
+       TXT_ALIASLIST_LINE,
+       TXT_ALIASLIST_FOOTER,
+
+       TXT_FILL_8,
+
+       TXT_LOG_OPENED,
+       TXT_LOG_CLOSED,
+       TXT_LOG_CREATE_FAILED,
+       TXT_LOG_LOCKED,
+       TXT_LOG_NOT_OPEN,
+       TXT_LOG_STARTED,
+       TXT_LOG_STOPPED,
+       TXT_LOG_LIST_HEADER,
+       TXT_LOG_LIST,
+       TXT_LOG_LIST_FOOTER,
+       TXT_WINDOWLOG_FILE,
+       TXT_WINDOWLOG_FILE_LOGGING,
+       TXT_LOG_NO_AWAY_MSGS,
+       TXT_LOG_AWAY_MSGS,
+
+       TXT_FILL_9,
+
+       TXT_MODULE_HEADER,
+       TXT_MODULE_LINE,
+        TXT_MODULE_FOOTER,
+       TXT_MODULE_ALREADY_LOADED,
+       TXT_MODULE_LOAD_ERROR,
+       TXT_MODULE_INVALID,
+       TXT_MODULE_LOADED,
+       TXT_MODULE_UNLOADED,
+
+       TXT_FILL_10,
+
+       TXT_COMMAND_UNKNOWN,
+       TXT_COMMAND_AMBIGUOUS,
+       TXT_OPTION_UNKNOWN,
+       TXT_OPTION_AMBIGUOUS,
+       TXT_OPTION_MISSING_ARG,
+       TXT_NOT_ENOUGH_PARAMS,
+       TXT_NOT_CONNECTED,
+       TXT_NOT_JOINED,
+       TXT_CHAN_NOT_FOUND,
+       TXT_CHAN_NOT_SYNCED,
+       TXT_NOT_GOOD_IDEA,
+
+       TXT_FILL_11,
+
+       TXT_THEME_SAVED,
+       TXT_THEME_SAVE_FAILED,
+       TXT_THEME_NOT_FOUND,
+       TXT_THEME_CHANGED,
+       TXT_WINDOW_THEME_CHANGED,
+       TXT_FORMAT_TITLE,
+       TXT_FORMAT_SUBTITLE,
+       TXT_FORMAT_ITEM,
+
+       TXT_FILL_12,
+
+       TXT_IGNORED,
+       TXT_UNIGNORED,
+       TXT_IGNORE_NOT_FOUND,
+       TXT_IGNORE_NO_IGNORES,
+       TXT_IGNORE_HEADER,
+       TXT_IGNORE_LINE,
+       TXT_IGNORE_FOOTER,
+
+       TXT_FILL_13,
+
+        TXT_UNKNOWN_CHAT_PROTOCOL,
+        TXT_UNKNOWN_CHATNET,
+       TXT_NOT_TOGGLE,
+       TXT_PERL_ERROR,
+       TXT_BIND_KEY,
+       TXT_BIND_UNKNOWN_ID,
+       TXT_CONFIG_SAVED,
+       TXT_CONFIG_RELOADED,
+       TXT_CONFIG_MODIFIED,
+       TXT_GLIB_ERROR,
+        TXT_OVERWRITE_CONFIG
+};
+
+extern FORMAT_REC fecommon_core_formats[];
diff --git a/apps/irssi/src/fe-common/core/module.h b/apps/irssi/src/fe-common/core/module.h
new file mode 100644 (file)
index 0000000..203e3a3
--- /dev/null
@@ -0,0 +1,34 @@
+#include "common.h"
+
+#define MODULE_NAME "fe-common/core"
+
+typedef struct {
+       time_t time;
+       char *nick;
+
+       /* channel specific msg to/from me - this is actually a reference
+          count. it begins from `completion_keep_publics' and is decreased
+          every time some nick is added to lastmsgs list.
+
+          this is because of how the nick completion works. the same nick
+          is never in the lastmsgs list twice, but every time it's used
+          it's just moved to the beginning of the list. if this would be
+          just a boolean value the own-status would never be removed
+          from the nick if it didn't keep quiet for long enough.
+
+          so, the own-status is rememberd only for the last
+           `completion_keep_publics' lines */
+       int own;
+} LAST_MSG_REC;
+
+typedef struct {
+       /* /MSG completion: */
+       GSList *lastmsgs; /* list of nicks who sent you msg or
+                            to who you send msg */
+} MODULE_SERVER_REC;
+
+typedef struct {
+       /* nick completion: */
+       GSList *lastmsgs; /* list of nicks who sent latest msgs and
+                            list of nicks who you sent msgs to */
+} MODULE_CHANNEL_REC;
diff --git a/apps/irssi/src/fe-common/core/printtext.c b/apps/irssi/src/fe-common/core/printtext.c
new file mode 100644 (file)
index 0000000..38de158
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ printtext.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 "settings.h"
+
+#include "levels.h"
+#include "servers.h"
+
+#include "themes.h"
+#include "fe-windows.h"
+#include "printtext.h"
+
+static int beep_msg_level, beep_when_away, beep_when_window_active;
+
+static int signal_gui_print_text;
+static int signal_print_text_stripped;
+static int signal_print_text;
+static int signal_print_text_finished;
+static int signal_print_format;
+static int signal_print_starting;
+
+static int sending_print_starting;
+
+static void print_line(TEXT_DEST_REC *dest, const char *text);
+
+static void printformat_module_dest(const char *module, TEXT_DEST_REC *dest,
+                                   int formatnum, va_list va)
+{
+       char *arglist[MAX_FORMAT_PARAMS];
+       char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+       FORMAT_REC *formats;
+       THEME_REC *theme;
+       char *str;
+
+       theme = dest->window->theme == NULL ? current_theme :
+               dest->window->theme;
+
+       formats = g_hash_table_lookup(default_formats, module);
+       format_read_arglist(va, &formats[formatnum],
+                           arglist, sizeof(arglist)/sizeof(char *),
+                           buffer, sizeof(buffer));
+
+       if (!sending_print_starting) {
+               sending_print_starting = TRUE;
+               signal_emit_id(signal_print_starting, 1, dest);
+                sending_print_starting = FALSE;
+       }
+
+       signal_emit_id(signal_print_format, 5, theme, module,
+                      dest, GINT_TO_POINTER(formatnum), arglist);
+
+       str = format_get_text_theme_charargs(theme, module, dest,
+                                            formatnum, arglist);
+       if (*str != '\0') print_line(dest, str);
+       g_free(str);
+}
+
+void printformat_module_args(const char *module, void *server,
+                            const char *target, int level,
+                            int formatnum, va_list va)
+{
+       TEXT_DEST_REC dest;
+
+       format_create_dest(&dest, server, target, level, NULL);
+       printformat_module_dest(module, &dest, formatnum, va);
+}
+
+void printformat_module(const char *module, void *server, const char *target,
+                       int level, int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+       printformat_module_args(module, server, target, level, formatnum, va);
+       va_end(va);
+}
+
+void printformat_module_window_args(const char *module, WINDOW_REC *window,
+                                   int level, int formatnum, va_list va)
+{
+       TEXT_DEST_REC dest;
+
+       format_create_dest(&dest, NULL, NULL, level, window);
+       printformat_module_dest(module, &dest, formatnum, va);
+}
+
+void printformat_module_window(const char *module, WINDOW_REC *window,
+                              int level, int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+       printformat_module_window_args(module, window, level, formatnum, va);
+       va_end(va);
+}
+
+void printformat_module_gui_args(const char *module, int formatnum, va_list va)
+{
+       TEXT_DEST_REC dest;
+       char *arglist[MAX_FORMAT_PARAMS];
+       char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+       FORMAT_REC *formats;
+        char *str;
+
+       g_return_if_fail(module != NULL);
+
+        memset(&dest, 0, sizeof(dest));
+
+       formats = g_hash_table_lookup(default_formats, module);
+       format_read_arglist(va, &formats[formatnum],
+                           arglist, sizeof(arglist)/sizeof(char *),
+                           buffer, sizeof(buffer));
+
+       str = format_get_text_theme_charargs(current_theme, module, &dest,
+                                            formatnum, arglist);
+       if (*str != '\0') format_send_to_gui(&dest, str);
+       g_free(str);
+}
+
+void printformat_module_gui(const char *module, int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+        printformat_module_gui_args(module, formatnum, va);
+       va_end(va);
+}
+
+static void print_line(TEXT_DEST_REC *dest, const char *text)
+{
+       char *str, *tmp;
+
+       g_return_if_fail(dest != NULL);
+       g_return_if_fail(text != NULL);
+
+       tmp = format_get_level_tag(current_theme, dest);
+       str = format_add_linestart(text, tmp);
+       g_free_not_null(tmp);
+
+       /* send the plain text version for logging etc.. */
+       tmp = strip_codes(str);
+       signal_emit_id(signal_print_text_stripped, 2, dest, tmp);
+       g_free(tmp);
+
+       signal_emit_id(signal_print_text, 2, dest, str);
+       g_free(str);
+}
+
+/* append string to `out', expand newlines. */
+static void printtext_append_str(TEXT_DEST_REC *dest, GString *out,
+                                const char *str)
+{
+       while (*str != '\0') {
+               if (*str != '\n')
+                       g_string_append_c(out, *str);
+               else {
+                       print_line(dest, out->str);
+                       g_string_truncate(out, 0);
+               }
+               str++;
+       }
+}
+
+static char *printtext_get_args(TEXT_DEST_REC *dest, const char *str,
+                               va_list va)
+{
+       GString *out;
+       char *ret;
+
+       out = g_string_new(NULL);
+       for (; *str != '\0'; str++) {
+               if (*str != '%') {
+                       g_string_append_c(out, *str);
+                       continue;
+               }
+
+               if (*++str == '\0')
+                       break;
+
+               /* standard parameters */
+               switch (*str) {
+               case 's': {
+                       char *s = (char *) va_arg(va, char *);
+                       if (s && *s) printtext_append_str(dest, out, s);
+                       break;
+               }
+               case 'd': {
+                       int d = (int) va_arg(va, int);
+                       g_string_sprintfa(out, "%d", d);
+                       break;
+               }
+               case 'f': {
+                       double f = (double) va_arg(va, double);
+                       g_string_sprintfa(out, "%0.2f", f);
+                       break;
+               }
+               case 'u': {
+                       unsigned int d =
+                               (unsigned int) va_arg(va, unsigned int);
+                       g_string_sprintfa(out, "%u", d);
+                       break;
+                }
+               case 'l': {
+                       long d = (long) va_arg(va, long);
+
+                       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 (!format_expand_styles(out, *str)) {
+                               g_string_append_c(out, '%');
+                               g_string_append_c(out, *str);
+                       }
+                       break;
+               }
+       }
+
+       ret = out->str;
+       g_string_free(out, FALSE);
+       return ret;
+}
+
+static char *printtext_expand_formats(const char *str)
+{
+       GString *out;
+       char *ret;
+
+       out = g_string_new(NULL);
+       for (; *str != '\0'; str++) {
+               if (*str != '%') {
+                       g_string_append_c(out, *str);
+                       continue;
+               }
+
+               if (*++str == '\0')
+                       break;
+
+               if (!format_expand_styles(out, *str)) {
+                       g_string_append_c(out, '%');
+                       g_string_append_c(out, *str);
+               }
+       }
+
+       ret = out->str;
+       g_string_free(out, FALSE);
+       return ret;
+}
+
+void printtext_dest(TEXT_DEST_REC *dest, const char *text, va_list va)
+{
+       char *str;
+
+       if (!sending_print_starting) {
+               sending_print_starting = TRUE;
+               signal_emit_id(signal_print_starting, 1, dest);
+                sending_print_starting = FALSE;
+       }
+
+       str = printtext_get_args(dest, text, va);
+       print_line(dest, str);
+       g_free(str);
+}
+
+/* Write text to target - convert color codes */
+void printtext(void *server, const char *target, int level, const char *text, ...)
+{
+       TEXT_DEST_REC dest;
+       va_list va;
+
+       g_return_if_fail(text != NULL);
+
+        format_create_dest(&dest, server, target, level, NULL);
+
+       va_start(va, text);
+       printtext_dest(&dest, text, va);
+       va_end(va);
+}
+
+/* Like printtext(), but don't handle %s etc. */
+void printtext_string(void *server, const char *target, int level, const char *text)
+{
+       TEXT_DEST_REC dest;
+        char *str;
+
+       g_return_if_fail(text != NULL);
+
+        format_create_dest(&dest, server, target, level, NULL);
+
+       if (!sending_print_starting) {
+               sending_print_starting = TRUE;
+               signal_emit_id(signal_print_starting, 1, dest);
+                sending_print_starting = FALSE;
+       }
+
+        str = printtext_expand_formats(text);
+       print_line(&dest, str);
+        g_free(str);
+}
+
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...)
+{
+       TEXT_DEST_REC dest;
+       va_list va;
+
+       g_return_if_fail(text != NULL);
+
+       format_create_dest(&dest, NULL, NULL, level,
+                          window != NULL ? window : active_win);
+
+       va_start(va, text);
+       printtext_dest(&dest, text, va);
+       va_end(va);
+}
+
+void printtext_gui(const char *text)
+{
+       TEXT_DEST_REC dest;
+        char *str;
+
+       g_return_if_fail(text != NULL);
+
+        memset(&dest, 0, sizeof(dest));
+
+       str = printtext_expand_formats(text);
+       format_send_to_gui(&dest, str);
+       g_free(str);
+}
+
+static void msg_beep_check(TEXT_DEST_REC *dest)
+{
+       if (dest->level != 0 && (dest->level & MSGLEVEL_NOHILIGHT) == 0 &&
+           (beep_msg_level & dest->level) &&
+           (beep_when_away || (dest->server != NULL &&
+                               !dest->server->usermode_away)) &&
+           (beep_when_window_active || dest->window != active_win)) {
+                signal_emit("beep", 0);
+       }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *text)
+{
+       char *str, *tmp;
+
+       g_return_if_fail(dest != NULL);
+       g_return_if_fail(text != NULL);
+
+       msg_beep_check(dest);
+
+       dest->window->last_line = time(NULL);
+
+       /* add timestamp/server tag here - if it's done in print_line()
+          it would be written to log files too */
+       tmp = format_get_line_start(current_theme, dest, time(NULL));
+       str = format_add_linestart(text, tmp);
+       g_free_not_null(tmp);
+
+       format_send_to_gui(dest, str);
+       g_free(str);
+
+       signal_emit_id(signal_print_text_finished, 1, dest->window);
+}
+
+static void sig_print_text_free(TEXT_DEST_REC *dest, const char *text)
+{
+        g_free_and_null(dest->hilight_color);
+}
+
+void printtext_multiline(void *server, const char *target, int level,
+                        const char *format, const char *text)
+{
+       char **lines, **tmp;
+
+       g_return_if_fail(format != NULL);
+       g_return_if_fail(text != NULL);
+
+       lines = g_strsplit(text, "\n", -1);
+        for (tmp = lines; *tmp != NULL; tmp++)
+               printtext(NULL, NULL, level, format, *tmp);
+       g_strfreev(lines);
+}
+
+static void sig_gui_dialog(const char *type, const char *text)
+{
+       char *format;
+
+       if (g_strcasecmp(type, "warning") == 0)
+               format = "%_Warning:%_ %s";
+       else if (g_strcasecmp(type, "error") == 0)
+               format = "%_Error:%_ %s";
+       else
+               format = "%s";
+
+        printtext_multiline(NULL, NULL, MSGLEVEL_NEVER, format, text);
+}
+
+static void read_settings(void)
+{
+       beep_msg_level = level2bits(settings_get_str("beep_msg_level"));
+       beep_when_away = settings_get_bool("beep_when_away");
+        beep_when_window_active = settings_get_bool("beep_when_window_active");
+}
+
+void printtext_init(void)
+{
+       sending_print_starting = FALSE;
+       signal_gui_print_text = signal_get_uniq_id("gui print text");
+       signal_print_text_stripped = signal_get_uniq_id("print text stripped");
+       signal_print_text = signal_get_uniq_id("print text");
+       signal_print_text_finished = signal_get_uniq_id("print text finished");
+       signal_print_format = signal_get_uniq_id("print format");
+       signal_print_starting = signal_get_uniq_id("print starting");
+
+       read_settings();
+       signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+       signal_add_last("print text", (SIGNAL_FUNC) sig_print_text_free);
+       signal_add("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void printtext_deinit(void)
+{
+       signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+       signal_remove("print text", (SIGNAL_FUNC) sig_print_text_free);
+       signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/printtext.h b/apps/irssi/src/fe-common/core/printtext.h
new file mode 100644 (file)
index 0000000..14f4656
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef __PRINTTEXT_H
+#define __PRINTTEXT_H
+
+#include "fe-windows.h"
+
+void printformat_module(const char *module, void *server, const char *target, int level, int formatnum, ...);
+void printformat_module_window(const char *module, WINDOW_REC *window, int level, int formatnum, ...);
+
+void printformat_module_args(const char *module, void *server, const char *target, int level, int formatnum, va_list va);
+void printformat_module_window_args(const char *module, WINDOW_REC *window, int level, int formatnum, va_list va);
+
+void printtext(void *server, const char *target, int level, const char *text, ...);
+void printtext_string(void *server, const char *target, int level, const char *text);
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...);
+void printtext_multiline(void *server, const char *target, int level, const char *format, const char *text);
+
+/* only GUI should call these - used for printing text to somewhere else
+   than windows */
+void printtext_gui(const char *text);
+void printformat_module_gui(const char *module, int formatnum, ...);
+void printformat_module_gui_args(const char *module, int formatnum, va_list va);
+
+void printtext_init(void);
+void printtext_deinit(void);
+
+/* printformat(...) = printformat_format(MODULE_NAME, ...)
+
+   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..
+ */
+#if defined (__GNUC__) && !defined (__STRICT_ANSI__)
+/* GCC */
+#  define printformat(server, target, level, formatnum...) \
+       printformat_module(MODULE_NAME, server, target, level, ##formatnum)
+#  define printformat_window(window, level, formatnum...) \
+       printformat_module_window(MODULE_NAME, window, level, ##formatnum)
+#  define printformat_gui(formatnum...) \
+       printformat_module_gui(MODULE_NAME, ##formatnum)
+#elif defined (_ISOC99_SOURCE)
+/* C99 */
+#  define printformat(server, target, level, formatnum, ...) \
+       printformat_module(MODULE_NAME, server, target, level, formatnum, __VA_ARGS__)
+#  define printformat_window(window, level, formatnum, ...) \
+       printformat_module_window(MODULE_NAME, window, level, formatnum, __VA_ARGS__)
+#  define printformat_gui(formatnum, ...) \
+       printformat_module_gui(MODULE_NAME, formatnum, __VA_ARGS__)
+#else
+/* inline/static */
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat(void *server, const char *target, int level, int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+       printformat_module_args(MODULE_NAME, server, target, level, formatnum, va);
+       va_end(va);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat_window(WINDOW_REC *window, int level, int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+       printformat_module_window_args(MODULE_NAME, window, level, formatnum, va);
+       va_end(va);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat_gui(int formatnum, ...)
+{
+       va_list va;
+
+       va_start(va, formatnum);
+       printformat_module_gui_args(MODULE_NAME, formatnum, va);
+       va_end(va);
+}
+#endif
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/themes.c b/apps/irssi/src/fe-common/core/themes.c
new file mode 100644 (file)
index 0000000..de9b6de
--- /dev/null
@@ -0,0 +1,1184 @@
+/*
+ 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "special-vars.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "themes.h"
+#include "printtext.h"
+
+#include "default-theme.h"
+
+GSList *themes;
+THEME_REC *current_theme;
+GHashTable *default_formats;
+
+static int init_finished;
+static char *init_errors;
+
+static int theme_read(THEME_REC *theme, const char *path, const char *data);
+
+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->abstracts = g_hash_table_new((GHashFunc) g_str_hash,
+                                         (GCompareFunc) g_str_equal);
+       rec->modules = g_hash_table_new((GHashFunc) g_istr_hash,
+                                       (GCompareFunc) g_istr_equal);
+       themes = g_slist_append(themes, rec);
+       signal_emit("theme created", 1, rec);
+
+       return rec;
+}
+
+static void theme_abstract_destroy(char *key, char *value)
+{
+       g_free(key);
+        g_free(value);
+}
+
+static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
+{
+       int n;
+
+       for (n = 0; n < rec->count; n++) {
+               g_free_not_null(rec->formats[n]);
+               g_free_not_null(rec->expanded_formats[n]);
+       }
+       g_free(rec->formats);
+       g_free(rec->expanded_formats);
+
+       g_free(rec->name);
+       g_free(rec);
+}
+
+void theme_destroy(THEME_REC *rec)
+{
+       themes = g_slist_remove(themes, rec);
+
+       signal_emit("theme destroyed", 1, rec);
+
+       g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL);
+       g_hash_table_destroy(rec->abstracts);
+       g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL);
+       g_hash_table_destroy(rec->modules);
+
+       g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL);
+       g_slist_free(rec->replace_values);
+
+       g_free(rec->path);
+       g_free(rec->name);
+       g_free(rec);
+}
+
+static char *theme_replace_expand(THEME_REC *theme, int index,
+                                 char default_fg, char default_bg,
+                                 char *last_fg, char *last_bg,
+                                 char chr, int flags)
+{
+       GSList *rec;
+       char *ret, *abstract, data[2];
+
+       rec = g_slist_nth(theme->replace_values, index);
+       g_return_val_if_fail(rec != NULL, NULL);
+
+       data[0] = chr; data[1] = '\0';
+
+       abstract = rec->data;
+       abstract = theme_format_expand_data(theme, (const char **) &abstract,
+                                           default_fg, default_bg,
+                                           last_fg, last_bg, flags);
+       ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+       g_free(abstract);
+       return ret;
+}
+
+static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW";
+static const char *bgcolorformats = "n01234567";
+
+#define IS_FGCOLOR_FORMAT(c) \
+        ((c) != '\0' && strchr(fgcolorformats, c) != NULL)
+#define IS_BGCOLOR_FORMAT(c) \
+        ((c) != '\0' && strchr(bgcolorformats, c) != NULL)
+
+/* append "variable" part in $variable, ie. not the contents of the variable */
+static void theme_format_append_variable(GString *str, const char **format)
+{
+       const char *orig;
+       char *value, *args[1] = { NULL };
+       int free_ret;
+
+       orig = *format;
+       (*format)++;
+
+       value = parse_special((char **) format, NULL, NULL,
+                             args, &free_ret, NULL, 0);
+       if (free_ret) g_free(value);
+       (*format)++;
+
+       /* append the variable name */
+       value = g_strndup(orig, (int) (*format-orig));
+       g_string_append(str, value);
+       g_free(value);
+}
+
+/* append next "item", either a character, $variable or %format */
+static void theme_format_append_next(THEME_REC *theme, GString *str,
+                                    const char **format,
+                                    char default_fg, char default_bg,
+                                    char *last_fg, char *last_bg,
+                                    int flags)
+{
+       int index;
+       unsigned char chr;
+
+       chr = **format;
+       if ((chr == '$' || chr == '%') &&
+           (*format)[1] == '\0') {
+               /* last char, always append */
+               g_string_append_c(str, chr);
+               (*format)++;
+                return;
+       }
+
+       if (chr == '$') {
+               /* $variable .. we'll always need to skip this, since it
+                  may contain characters that are in replace chars. */
+               theme_format_append_variable(str, format);
+               return;
+       }
+
+       if (**format == '%') {
+               /* format */
+               (*format)++;
+               if (**format != '{' && **format != '}') {
+                        chr = **format;
+                       if (**format == 'n') {
+                               /* %n = change to default color */
+                               g_string_append(str, "%n");
+
+                               if (default_bg != 'n') {
+                                       g_string_append_c(str, '%');
+                                       g_string_append_c(str, default_bg);
+                               }
+                               if (default_fg != 'n') {
+                                       g_string_append_c(str, '%');
+                                       g_string_append_c(str, default_fg);
+                               }
+
+                               *last_fg = default_fg;
+                               *last_bg = default_bg;
+                       } else {
+                               if (IS_FGCOLOR_FORMAT(chr))
+                                       *last_fg = chr;
+                               if (IS_BGCOLOR_FORMAT(chr))
+                                       *last_bg = chr;
+                               g_string_append_c(str, '%');
+                               g_string_append_c(str, chr);
+                       }
+                       (*format)++;
+                       return;
+               }
+
+               /* %{ or %} gives us { or } char */
+               chr = **format;
+       }
+
+       index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
+               theme->replace_keys[(int) chr];
+       if (index == -1)
+               g_string_append_c(str, chr);
+       else {
+               char *value;
+
+               value = theme_replace_expand(theme, index,
+                                            default_fg, default_bg,
+                                            last_fg, last_bg, chr, flags);
+               g_string_append(str, value);
+               g_free(value);
+       }
+
+        (*format)++;
+}
+
+/* expand a single {abstract ...data... } */
+static char *theme_format_expand_abstract(THEME_REC *theme,
+                                         const char **formatp,
+                                         char default_fg, char default_bg,
+                                         int flags)
+{
+       const char *p, *format;
+       char *abstract, *data, *ret;
+       int len;
+
+       format = *formatp;
+
+       /* get abstract name first */
+       p = format;
+       while (*p != '\0' && *p != ' ' &&
+              *p != '{' && *p != '}') p++;
+       if (*p == '\0' || p == format)
+               return NULL; /* error */
+
+       len = (int) (p-format);
+       abstract = g_strndup(format, len);
+
+       /* skip the following space, if there's any more spaces they're
+          treated as arguments */
+       if (*p == ' ') {
+               len++;
+               if ((flags & EXPAND_FLAG_IGNORE_EMPTY)) {
+                        /* if the data is empty, ignore the abstract */
+                       p = format+len;
+                       while (*p == ' ') p++;
+                       if (*p == '}') {
+                                *formatp = p+1;
+                               g_free(abstract);
+                               return NULL;
+                       }
+               }
+
+       }
+       *formatp = format+len;
+
+       /* get the abstract data */
+       data = g_hash_table_lookup(theme->abstracts, abstract);
+       g_free(abstract);
+       if (data == NULL) {
+               /* unknown abstract, just display the data */
+               data = "$0-";
+       }
+       abstract = g_strdup(data);
+
+       /* we'll need to get the data part. it may contain
+          more abstracts, they are automatically expanded. */
+       data = theme_format_expand_data(theme, formatp, default_fg, default_bg,
+                                       NULL, NULL, flags);
+       len = strlen(data);
+
+       if (len > 1 && isdigit(data[len-1]) && data[len-2] == '$') {
+               /* ends with $<digit> .. this breaks things if next
+                  character is digit or '-' */
+                char digit, *tmp;
+
+               tmp = data;
+               digit = tmp[len-1];
+               tmp[len-1] = '\0';
+
+               data = g_strdup_printf("%s{%c}", tmp, digit);
+               g_free(tmp);
+       }
+
+       ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+       g_free(abstract);
+        g_free(data);
+       abstract = ret;
+
+       /* abstract may itself contain abstracts or replaces */
+       p = abstract;
+       ret = theme_format_expand_data(theme, &p, default_fg, default_bg,
+                                      &default_fg, &default_bg,
+                                      flags | EXPAND_FLAG_LASTCOLOR_ARG);
+       g_free(abstract);
+       return ret;
+}
+
+/* expand the data part in {abstract data} */
+char *theme_format_expand_data(THEME_REC *theme, const char **format,
+                              char default_fg, char default_bg,
+                              char *save_last_fg, char *save_last_bg,
+                              int flags)
+{
+       GString *str;
+       char *ret, *abstract;
+       char last_fg, last_bg;
+        int recurse_flags;
+
+       last_fg = default_fg;
+       last_bg = default_bg;
+        recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;
+
+       str = g_string_new(NULL);
+       while (**format != '\0') {
+               if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
+                       /* ignore } if we're expanding original string */
+                       (*format)++;
+                       break;
+               }
+
+               if (**format != '{') {
+                       if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
+                           **format == '$' && (*format)[1] == '0') {
+                               /* save the color before $0 ..
+                                  this is for the %n replacing */
+                               if (save_last_fg != NULL) {
+                                       *save_last_fg = last_fg;
+                                       save_last_fg = NULL;
+                               }
+                               if (save_last_bg != NULL) {
+                                       *save_last_bg = last_bg;
+                                       save_last_bg = NULL;
+                               }
+                       }
+
+                       theme_format_append_next(theme, str, format,
+                                                default_fg, default_bg,
+                                                &last_fg, &last_bg,
+                                                recurse_flags);
+                       continue;
+               }
+
+               (*format)++;
+               if (**format == '\0' || **format == '}')
+                       break; /* error */
+
+               /* get a single {...} */
+               abstract = theme_format_expand_abstract(theme, format,
+                                                       last_fg, last_bg,
+                                                       recurse_flags);
+               if (abstract != NULL) {
+                       g_string_append(str, abstract);
+                       g_free(abstract);
+               }
+       }
+
+       if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) == 0) {
+               /* save the last color */
+               if (save_last_fg != NULL)
+                       *save_last_fg = last_fg;
+               if (save_last_bg != NULL)
+                       *save_last_bg = last_bg;
+       }
+
+       ret = str->str;
+        g_string_free(str, FALSE);
+        return ret;
+}
+
+#define IS_OLD_FORMAT(code, last_fg, last_bg) \
+       (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \
+       ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg))))
+
+static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
+{
+       GString *str;
+       char *ret, last_fg, last_bg;
+
+       str = g_string_new(NULL);
+
+       last_fg = last_bg = 'n';
+       while (*format != '\0') {
+               if (*format == '$') {
+                        /* $variable, skrip it entirely */
+                       theme_format_append_variable(str, &format);
+                        last_fg = last_bg = '\0';
+               } else if (*format != '%') {
+                       /* a normal character */
+                       g_string_append_c(str, *format);
+                       format++;
+               } else {
+                       /* %format */
+                       format++;
+                       if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
+                               /* active color set again */
+                       } else if (IS_FGCOLOR_FORMAT(*format) &&
+                                  (*format != 'n' || format[2] == 'n') &&
+                                  format[1] == '%' &&
+                                  IS_FGCOLOR_FORMAT(format[2])) {
+                               /* two fg colors in a row. bg colors are
+                                  so rare that we don't bother checking
+                                  them */
+                       } else {
+                               /* some format, add it */
+                               g_string_append_c(str, '%');
+                               g_string_append_c(str, *format);
+
+                               if (IS_FGCOLOR_FORMAT(*format))
+                                       last_fg = *format;
+                               if (IS_BGCOLOR_FORMAT(*format))
+                                       last_bg = *format;
+                       }
+                       format++;
+               }
+       }
+
+       ret = str->str;
+        g_string_free(str, FALSE);
+        return ret;
+}
+
+char *theme_format_expand(THEME_REC *theme, const char *format)
+{
+       char *data, *ret;
+
+       g_return_val_if_fail(theme != NULL, NULL);
+       g_return_val_if_fail(format != NULL, NULL);
+
+       data = theme_format_expand_data(theme, &format, 'n', 'n', NULL, NULL,
+                                       EXPAND_FLAG_ROOT);
+       ret = theme_format_compress_colors(theme, data);
+        g_free(data);
+       return ret;
+}
+
+static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module)
+{
+       MODULE_THEME_REC *rec;
+       FORMAT_REC *formats;
+
+       rec = g_hash_table_lookup(theme->modules, module);
+       if (rec != NULL) return rec;
+
+       formats = g_hash_table_lookup(default_formats, module);
+        g_return_val_if_fail(formats != NULL, NULL);
+
+       rec = g_new0(MODULE_THEME_REC, 1);
+       rec->name = g_strdup(module);
+
+       for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ;
+       rec->formats = g_new0(char *, rec->count);
+       rec->expanded_formats = g_new0(char *, rec->count);
+
+       g_hash_table_insert(theme->modules, rec->name, rec);
+       return rec;
+}
+
+static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
+{
+       GSList *tmp;
+       CONFIG_NODE *node;
+       const char *p;
+        int index;
+
+        /* reset replace keys */
+       for (index = 0; index < 256; index++)
+                theme->replace_keys[index] = -1;
+       index = 0;
+
+       node = config_node_traverse(config, "replaces", FALSE);
+       if (node == NULL || node->type !=  NODE_TYPE_BLOCK) return;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               if (node->key != NULL && node->value != NULL) {
+                       for (p = node->key; *p != '\0'; p++)
+                                theme->replace_keys[(int) *p] = index;
+
+                       theme->replace_values =
+                               g_slist_append(theme->replace_values,
+                                              g_strdup(node->value));
+                        index++;
+               }
+       }
+}
+
+static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme)
+{
+       GSList *tmp;
+       CONFIG_NODE *node;
+        gpointer oldkey, oldvalue;
+
+       node = config_node_traverse(config, "abstracts", FALSE);
+       if (node == NULL || node->type !=  NODE_TYPE_BLOCK) return;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               if (node->key == NULL || node->value == NULL)
+                       continue;
+
+               if (g_hash_table_lookup_extended(theme->abstracts, node->key,
+                                                &oldkey, &oldvalue)) {
+                        /* new values override old ones */
+                        g_hash_table_remove(theme->abstracts, oldkey);
+                       g_free(oldkey);
+                       g_free(oldvalue);
+               }
+
+               g_hash_table_insert(theme->abstracts, g_strdup(node->key),
+                                   g_strdup(node->value));
+       }
+}
+
+static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec,
+                            const char *module,
+                            const char *key, const char *value)
+{
+       int num;
+
+        num = format_find_tag(module, key);
+       if (num != -1) {
+               rec->formats[num] = g_strdup(value);
+               rec->expanded_formats[num] = theme_format_expand(theme, value);
+       }
+}
+
+static void theme_read_formats(THEME_REC *theme, const char *module,
+                              CONFIG_REC *config, MODULE_THEME_REC *rec)
+{
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+       node = config_node_traverse(config, "formats", FALSE);
+       if (node == NULL) return;
+       node = config_node_section(node, module, -1);
+       if (node == NULL) return;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               node = tmp->data;
+
+               if (node->key != NULL && node->value != NULL) {
+                       theme_set_format(theme, rec, module,
+                                        node->key, node->value);
+               }
+       }
+}
+
+static void theme_init_module(THEME_REC *theme, const char *module,
+                             CONFIG_REC *config)
+{
+       MODULE_THEME_REC *rec;
+       FORMAT_REC *formats;
+       int n;
+
+       formats = g_hash_table_lookup(default_formats, module);
+       g_return_if_fail(formats != NULL);
+
+       rec = theme_module_create(theme, module);
+
+       if (config != NULL)
+               theme_read_formats(theme, module, config, rec);
+
+       /* expand the remaining formats */
+       for (n = 0; n < rec->count; n++) {
+               if (rec->expanded_formats[n] == NULL) {
+                       rec->expanded_formats[n] =
+                               theme_format_expand(theme, formats[n].def);
+               }
+       }
+}
+
+static void sig_print_errors(void)
+{
+       init_finished = TRUE;
+
+       if (init_errors != NULL) {
+               signal_emit("gui dialog", 2, "error", init_errors);
+                g_free(init_errors);
+       }
+}
+
+static void theme_read_module(THEME_REC *theme, const char *module)
+{
+       CONFIG_REC *config;
+
+       config = config_open(theme->path, -1);
+       if (config != NULL)
+               config_parse(config);
+
+       theme_init_module(theme, module, config);
+
+       if (config != NULL) config_close(config);
+}
+
+static void themes_read_module(const char *module)
+{
+        g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module);
+}
+
+static void theme_remove_module(THEME_REC *theme, const char *module)
+{
+       MODULE_THEME_REC *rec;
+
+       rec = g_hash_table_lookup(theme->modules, module);
+       if (rec == NULL) return;
+
+       g_hash_table_remove(theme->modules, module);
+       theme_module_destroy(module, rec);
+}
+
+static void themes_remove_module(const char *module)
+{
+        g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module);
+}
+
+void theme_register_module(const char *module, FORMAT_REC *formats)
+{
+       if (g_hash_table_lookup(default_formats, module) != NULL)
+               return;
+
+        g_hash_table_insert(default_formats, g_strdup(module), formats);
+       themes_read_module(module);
+}
+
+void theme_unregister_module(const char *module)
+{
+       gpointer key, value;
+
+       if (default_formats == NULL)
+               return; /* already uninitialized */
+
+       if (!g_hash_table_lookup_extended(default_formats, module, &key, &value))
+               return;
+
+       g_hash_table_remove(default_formats, key);
+       g_free(key);
+
+       themes_remove_module(module);
+}
+
+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;
+}
+
+static void window_themes_update(void)
+{
+       GSList *tmp;
+
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->theme_name != NULL)
+                        rec->theme = theme_load(rec->theme_name);
+       }
+}
+
+THEME_REC *theme_load(const char *setname)
+{
+       THEME_REC *theme, *oldtheme;
+       struct stat statbuf;
+       char *fname, *name, *p;
+
+        name = g_strdup(setname);
+       p = strrchr(name, '.');
+       if (p != NULL && strcmp(p, ".theme") == 0) {
+               /* remove the trailing .theme */
+                *p = '\0';
+       }
+
+       theme = theme_find(name);
+
+       /* check home dir */
+       fname = g_strdup_printf("%s/.irssi/%s.theme", g_get_home_dir(), name);
+       if (stat(fname, &statbuf) != 0) {
+               /* check global config dir */
+               g_free(fname);
+               fname = g_strdup_printf(SYSCONFDIR"/irssi/%s.theme", name);
+               if (stat(fname, &statbuf) != 0) {
+                       /* theme not found */
+                       g_free(fname);
+                       g_free(name);
+                       return theme; /* use the one in memory if possible */
+               }
+       }
+
+       if (theme != NULL && theme->last_modify == statbuf.st_mtime) {
+               /* theme not modified, use the one already in memory */
+               g_free(fname);
+               g_free(name);
+               return theme;
+       }
+
+        oldtheme = theme;
+       theme = theme_create(fname, name);
+       theme->last_modify = statbuf.st_mtime;
+       if (!theme_read(theme, theme->path, NULL)) {
+                /* error reading .theme file */
+               theme_destroy(theme);
+               theme = NULL;
+       }
+
+       if (oldtheme != NULL && theme != NULL) {
+               theme_destroy(oldtheme);
+               window_themes_update();
+       }
+
+       g_free(fname);
+       g_free(name);
+       return theme;
+}
+
+typedef struct {
+        THEME_REC *theme;
+       CONFIG_REC *config;
+} THEME_READ_REC;
+
+static void theme_read_modules(const char *module, void *value,
+                              THEME_READ_REC *rec)
+{
+       theme_init_module(rec->theme, module, rec->config);
+}
+
+static void read_error(const char *str)
+{
+       char *old;
+
+       if (init_finished)
+                printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
+       else if (init_errors == NULL)
+               init_errors = g_strdup(str);
+       else {
+                old = init_errors;
+               init_errors = g_strconcat(init_errors, "\n", str, NULL);
+                g_free(old);
+       }
+}
+
+static int theme_read(THEME_REC *theme, const char *path, const char *data)
+{
+       CONFIG_REC *config;
+       THEME_READ_REC rec;
+        char *str;
+
+       config = config_open(data == NULL ? path : NULL, -1) ;
+       if (config == NULL) {
+               /* didn't exist or no access? */
+               str = g_strdup_printf("Error reading theme file %s: %s",
+                                     path, g_strerror(errno));
+               read_error(str);
+               g_free(str);
+               return FALSE;
+       }
+
+       if (data != NULL)
+               config_parse_data(config, data, "internal");
+        else
+               config_parse(config);
+
+       if (config_last_error(config) != NULL) {
+               str = g_strdup_printf("Ignored errors in theme %s:\n%s",
+                                     theme->name, config_last_error(config));
+               read_error(str);
+                g_free(str);
+       }
+
+       theme->default_color =
+               config_get_int(config, NULL, "default_color", 0);
+       theme->default_real_color =
+               config_get_int(config, NULL, "default_real_color", 7);
+       theme_read_replaces(config, theme);
+
+       if (data == NULL) {
+               /* get the default abstracts from default theme. */
+               CONFIG_REC *default_config;
+
+               default_config = config_open(NULL, -1);
+               config_parse_data(default_config, default_theme, "internal");
+               theme_read_abstracts(default_config, theme);
+               config_close(default_config);
+       }
+       theme_read_abstracts(config, theme);
+
+       rec.theme = theme;
+       rec.config = config;
+       g_hash_table_foreach(default_formats,
+                            (GHFunc) theme_read_modules, &rec);
+       config_close(config);
+
+        return TRUE;
+}
+
+typedef struct {
+       char *name;
+       char *short_name;
+} THEME_SEARCH_REC;
+
+static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2)
+{
+       return g_strcasecmp(r1->short_name, r2->short_name);
+}
+
+static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list)
+{
+       THEME_SEARCH_REC *rec;
+
+       rec = g_new(THEME_SEARCH_REC, 1);
+       rec->name = module;
+       rec->short_name = strrchr(module, '/');
+       if (rec->short_name != NULL)
+               rec->short_name++; else rec->short_name = module;
+       *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal);
+}
+
+static GSList *get_sorted_modules(void)
+{
+       GSList *list;
+
+       list = NULL;
+       g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list);
+       return list;
+}
+
+static THEME_SEARCH_REC *theme_search(GSList *list, const char *module)
+{
+       THEME_SEARCH_REC *rec;
+
+       while (list != NULL) {
+               rec = list->data;
+
+               if (g_strcasecmp(rec->short_name, module) == 0)
+                       return rec;
+               list = list->next;
+       }
+
+       return NULL;
+}
+
+static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset)
+{
+       MODULE_THEME_REC *theme;
+       FORMAT_REC *formats;
+       const char *text, *last_title;
+       int n, first;
+
+       formats = g_hash_table_lookup(default_formats, rec->name);
+       theme = g_hash_table_lookup(current_theme->modules, rec->name);
+
+       last_title = NULL; first = TRUE;
+       for (n = 1; formats[n].def != NULL; n++) {
+               text = theme != NULL && theme->formats[n] != NULL ?
+                       theme->formats[n] : formats[n].def;
+
+               if (formats[n].tag == NULL)
+                       last_title = text;
+               else if ((value != NULL && key != NULL && g_strcasecmp(formats[n].tag, key) == 0) ||
+                        (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) {
+                       if (first) {
+                               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def);
+                               first = FALSE;
+                       }
+                       if (last_title != NULL)
+                               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title);
+                       if (reset || value != NULL) {
+                               theme = theme_module_create(current_theme, rec->name);
+                                g_free_not_null(theme->formats[n]);
+                                g_free_not_null(theme->expanded_formats[n]);
+
+                               text = reset ? formats[n].def : value;
+                               theme->formats[n] = reset ? NULL : g_strdup(value);
+                               theme->expanded_formats[n] = theme_format_expand(current_theme, text);
+                       }
+                       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text);
+                       last_title = NULL;
+               }
+       }
+}
+
+/* SYNTAX: FORMAT [-delete | -reset] [<module>] [<key> [<value>]] */
+static void cmd_format(const char *data)
+{
+        GHashTable *optlist;
+       GSList *tmp, *modules;
+       char *module, *key, *value;
+       void *free_arg;
+       int reset;
+
+       if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+                           "format", &optlist, &module, &key, &value))
+               return;
+
+       modules = get_sorted_modules();
+       if (*module == '\0')
+               module = NULL;
+       else if (theme_search(modules, module) == NULL) {
+               /* first argument isn't module.. */
+               cmd_params_free(free_arg);
+               if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+                                   "format", &optlist, &key, &value))
+                       return;
+               module = NULL;
+       }
+
+       reset = FALSE;
+       if (*key == '\0') key = NULL;
+       if (g_hash_table_lookup(optlist, "reset"))
+               reset = TRUE;
+       else if (g_hash_table_lookup(optlist, "delete"))
+               value = "";
+       else if (*value == '\0')
+               value = NULL;
+
+       for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+               THEME_SEARCH_REC *rec = tmp->data;
+
+               if (module == NULL || g_strcasecmp(rec->short_name, module) == 0)
+                       theme_show(rec, key, value, reset);
+       }
+       g_slist_foreach(modules, (GFunc) g_free, NULL);
+       g_slist_free(modules);
+
+        cmd_params_free(free_arg);
+}
+
+static void module_save(const char *module, MODULE_THEME_REC *rec,
+                        CONFIG_REC *config)
+{
+       CONFIG_NODE *fnode, *node;
+       FORMAT_REC *formats;
+       int n;
+
+        formats = g_hash_table_lookup(default_formats, rec->name);
+       if (formats == NULL) return;
+
+       fnode = config_node_traverse(config, "formats", TRUE);
+
+       node = config_node_section(fnode, rec->name, NODE_TYPE_BLOCK);
+       for (n = 0; formats[n].def != NULL; n++) {
+                if (rec->formats[n] != NULL) {
+                        config_node_set_str(config, node, formats[n].tag,
+                                            rec->formats[n]);
+                }
+        }
+
+        if (node->value == NULL) {
+                /* not modified, don't keep the empty section */
+                config_node_remove(config, fnode, node);
+                if (fnode->value == NULL)
+                        config_node_remove(config, config->mainnode, fnode);
+        }
+}
+
+static void theme_save(THEME_REC *theme)
+{
+       CONFIG_REC *config;
+       char *path;
+       int ok;
+
+       config = config_open(theme->path, -1);
+        if (config != NULL)
+                config_parse(config);
+        else {
+                if (g_strcasecmp(theme->name, "default") == 0) {
+                        config = config_open(NULL, -1);
+                        config_parse_data(config, default_theme, "internal");
+                        config_change_file_name(config, theme->path, 0660);
+                } else {
+                        config = config_open(theme->path, 0660);
+                        if (config == NULL)
+                                return;
+                        config_parse(config);
+                }
+        }
+
+       g_hash_table_foreach(theme->modules, (GHFunc) module_save, config);
+
+        /* always save the theme to ~/.irssi/ */
+       path = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(),
+                              g_basename(theme->path));
+       ok = config_write(config, path, 0660) == 0;
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED,
+                   path, config_last_error(config));
+
+       g_free(path);
+       config_close(config);
+}
+
+/* save changed formats */
+static void cmd_save(void)
+{
+       GSList *tmp;
+
+       for (tmp = themes; tmp != NULL; tmp = tmp->next) {
+               THEME_REC *theme = tmp->data;
+
+               theme_save(theme);
+       }
+}
+
+static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
+{
+       FORMAT_REC *formats;
+       int n, len;
+
+       formats = g_hash_table_lookup(default_formats, rec->name);
+
+       len = strlen(key);
+       for (n = 1; formats[n].def != NULL; n++) {
+               const char *item = formats[n].tag;
+
+               if (item != NULL && g_strncasecmp(item, key, len) == 0)
+                        *list = g_list_append(*list, g_strdup(item));
+       }
+}
+
+static GList *completion_get_formats(const char *module, const char *key)
+{
+       GSList *modules, *tmp;
+       GList *list;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       list = NULL;
+
+       modules = get_sorted_modules();
+       if (*module == '\0' || theme_search(modules, module) != NULL) {
+               for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+                       THEME_SEARCH_REC *rec = tmp->data;
+
+                       if (*module == '\0' || g_strcasecmp(rec->short_name, module) == 0)
+                               complete_format_list(rec, key, &list);
+               }
+       }
+       g_slist_foreach(modules, (GFunc) g_free, NULL);
+       g_slist_free(modules);
+
+       return list;
+}
+
+static void sig_complete_format(GList **list, WINDOW_REC *window,
+                               const char *word, const char *line, int *want_space)
+{
+       const char *ptr;
+       int words;
+
+       g_return_if_fail(list != NULL);
+       g_return_if_fail(word != NULL);
+       g_return_if_fail(line != NULL);
+
+        ptr = line;
+
+       words = 0;
+       do {
+               words++;
+                ptr = strchr(ptr, ' ');
+       } while (ptr != NULL);
+
+       if (words > 2)
+               return;
+
+       *list = completion_get_formats(line, word);
+       if (*list != NULL) signal_stop();
+}
+
+static void change_theme(const char *name, int verbose)
+{
+       THEME_REC *rec;
+
+       rec = theme_load(name);
+       if (rec != NULL) {
+               current_theme = rec;
+               if (verbose) {
+                       printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                          TXT_THEME_CHANGED,
+                                          rec->name, rec->path);
+               }
+       } else if (verbose) {
+               printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                           TXT_THEME_NOT_FOUND, name);
+       }
+}
+
+static void read_settings(void)
+{
+       const char *theme;
+
+       theme = settings_get_str("theme");
+       if (strcmp(current_theme->name, theme) != 0)
+               change_theme(theme, TRUE);
+}
+
+static void themes_read(void)
+{
+       char *fname;
+
+       while (themes != NULL)
+               theme_destroy(themes->data);
+
+       /* first there's default theme.. */
+       current_theme = theme_load("default");
+       if (current_theme == NULL) {
+               fname = g_strdup_printf("%s/.irssi/default.theme",
+                                       g_get_home_dir());
+               current_theme = theme_create(fname, "default");
+               current_theme->default_color = 0;
+               current_theme->default_real_color = 7;
+                theme_read(current_theme, NULL, default_theme);
+               g_free(fname);
+       }
+
+        window_themes_update();
+        change_theme(settings_get_str("theme"), FALSE);
+}
+
+void themes_init(void)
+{
+       settings_add_str("lookandfeel", "theme", "default");
+
+       default_formats = g_hash_table_new((GHashFunc) g_str_hash,
+                                          (GCompareFunc) g_str_equal);
+
+        init_finished = FALSE;
+        init_errors = NULL;
+
+       themes = NULL;
+       themes_read();
+
+       command_bind("format", NULL, (SIGNAL_FUNC) cmd_format);
+       command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+       signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format);
+       signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+        signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+       signal_add("setup reread", (SIGNAL_FUNC) themes_read);
+
+       command_set_options("format", "delete reset");
+}
+
+void themes_deinit(void)
+{
+       while (themes != NULL)
+               theme_destroy(themes->data);
+
+       g_hash_table_destroy(default_formats);
+       default_formats = NULL;
+
+       command_unbind("format", (SIGNAL_FUNC) cmd_format);
+       command_unbind("save", (SIGNAL_FUNC) cmd_save);
+       signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format);
+       signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+        signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+        signal_remove("setup reread", (SIGNAL_FUNC) themes_read);
+}
diff --git a/apps/irssi/src/fe-common/core/themes.h b/apps/irssi/src/fe-common/core/themes.h
new file mode 100644 (file)
index 0000000..96f2340
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef __THEMES_H
+#define __THEMES_H
+
+typedef struct {
+       char *name;
+
+       int count;
+       char **formats; /* in same order as in module's default formats */
+       char **expanded_formats; /* this contains the formats after
+                                   expanding {templates} */
+} MODULE_THEME_REC;
+
+typedef struct {
+       char *path;
+       char *name;
+        time_t last_modify;
+
+       int default_color; /* default color to use with text with default
+                             background. default is 0 which means the default
+                             color set by terminal */
+       int default_real_color; /* default color to use with background set.
+                                  this shouldn't be 0, unless black is really
+                                  wanted. default is 7 (white). */
+       GHashTable *modules;
+
+        int replace_keys[256]; /* index to replace_values for each char */
+       GSList *replace_values;
+       GHashTable *abstracts;
+
+       void *gui_data;
+} THEME_REC;
+
+typedef struct _FORMAT_REC FORMAT_REC;
+
+extern GSList *themes;
+extern THEME_REC *current_theme;
+extern GHashTable *default_formats;
+
+THEME_REC *theme_create(const char *path, const char *name);
+void theme_destroy(THEME_REC *rec);
+
+THEME_REC *theme_load(const char *name);
+
+#define theme_register(formats) theme_register_module(MODULE_NAME, formats)
+#define theme_unregister() theme_unregister_module(MODULE_NAME)
+void theme_register_module(const char *module, FORMAT_REC *formats);
+void theme_unregister_module(const char *module);
+
+#define EXPAND_FLAG_IGNORE_REPLACES     0x01 /* don't use the character replaces when expanding */
+#define EXPAND_FLAG_IGNORE_EMPTY        0x02 /* if abstract's argument is empty, don't try to expand it (ie. {xx }, but not {xx}) */
+#define EXPAND_FLAG_RECURSIVE_MASK      0x0f
+/* private */
+#define EXPAND_FLAG_ROOT               0x10
+#define EXPAND_FLAG_LASTCOLOR_ARG      0x20
+
+char *theme_format_expand(THEME_REC *theme, const char *format);
+char *theme_format_expand_data(THEME_REC *theme, const char **format,
+                              char default_fg, char default_bg,
+                              char *save_last_fg, char *save_last_bg,
+                              int flags);
+
+void themes_init(void);
+void themes_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/translation.c b/apps/irssi/src/fe-common/core/translation.c
new file mode 100644 (file)
index 0000000..2713cc7
--- /dev/null
@@ -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/apps/irssi/src/fe-common/core/translation.h b/apps/irssi/src/fe-common/core/translation.h
new file mode 100644 (file)
index 0000000..48b2c3d
--- /dev/null
@@ -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/apps/irssi/src/fe-common/core/window-activity.c b/apps/irssi/src/fe-common/core/window-activity.c
new file mode 100644 (file)
index 0000000..3e4cd80
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ window-activity.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 "servers.h"
+#include "channels.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "nicklist.h"
+#include "hilight-text.h"
+#include "formats.h"
+
+static char **hide_targets;
+static int hide_level, msg_level, hilight_level;
+
+static void window_activity(WINDOW_REC *window, int data_level,
+                           const char *hilight_color)
+{
+       int old_data_level;
+
+       old_data_level = window->data_level;
+       if (data_level == 0 || window->data_level < data_level) {
+               window->data_level = data_level;
+                g_free_not_null(window->hilight_color);
+               window->hilight_color = g_strdup(hilight_color);
+               signal_emit("window hilight", 1, window);
+       }
+
+       signal_emit("window activity", 2, window,
+                   GINT_TO_POINTER(old_data_level));
+}
+
+static void window_item_activity(WI_ITEM_REC *item, int data_level,
+                                const char *hilight_color)
+{
+       int old_data_level;
+
+       old_data_level = item->data_level;
+       if (data_level == 0 || item->data_level < data_level) {
+               item->data_level = data_level;
+                g_free_not_null(item->hilight_color);
+               item->hilight_color = g_strdup(hilight_color);
+               signal_emit("window item hilight", 1, item);
+       }
+
+       signal_emit("window item activity", 2, item,
+                   GINT_TO_POINTER(old_data_level));
+}
+
+#define hide_target_activity(data_level, target) \
+       ((target) != NULL && hide_targets != NULL && \
+       strarray_find(hide_targets, target) != -1)
+
+static void sig_hilight_text(TEXT_DEST_REC *dest, const char *msg)
+{
+       WI_ITEM_REC *item;
+       int data_level;
+
+       if (dest->window == active_win || (dest->level & hide_level))
+               return;
+
+       if (dest->level & hilight_level) {
+               data_level = DATA_LEVEL_HILIGHT+dest->hilight_priority;
+       } else {
+               data_level = (dest->level & msg_level) ?
+                       DATA_LEVEL_MSG : DATA_LEVEL_TEXT;
+       }
+
+       if ((dest->level & MSGLEVEL_HILIGHT) == 0 &&
+           hide_target_activity(data_level, dest->target))
+               return;
+
+       if (dest->target != NULL) {
+               item = window_item_find(dest->server, dest->target);
+               if (item != NULL) {
+                       window_item_activity(item, data_level,
+                                            dest->hilight_color);
+               }
+       }
+       window_activity(dest->window, data_level, dest->hilight_color);
+}
+
+static void sig_dehilight_window(WINDOW_REC *window)
+{
+        GSList *tmp;
+
+       g_return_if_fail(window != NULL);
+
+       if (window->data_level != 0) {
+               window_activity(window, 0, NULL);
+               for (tmp = window->items; tmp != NULL; tmp = tmp->next)
+                       window_item_activity(tmp->data, 0, NULL);
+       }
+}
+
+static void read_settings(void)
+{
+       const char *targets;
+
+       if (hide_targets != NULL)
+               g_strfreev(hide_targets);
+
+        targets = settings_get_str("activity_hide_targets");
+       hide_targets = *targets == '\0' ? NULL :
+               g_strsplit(targets, " ", -1);
+
+       hide_level = MSGLEVEL_NEVER | MSGLEVEL_NO_ACT |
+               level2bits(settings_get_str("activity_hide_level"));
+       msg_level = level2bits(settings_get_str("activity_msg_level"));
+       hilight_level = MSGLEVEL_HILIGHT |
+               level2bits(settings_get_str("activity_hilight_level"));
+}
+
+void window_activity_init(void)
+{
+       settings_add_str("lookandfeel", "activity_hide_targets", "");
+       settings_add_str("lookandfeel", "activity_hide_level", "");
+       settings_add_str("lookandfeel", "activity_msg_level", "PUBLIC");
+       settings_add_str("lookandfeel", "activity_hilight_level", "MSGS DCCMSGS");
+
+       read_settings();
+       signal_add("print text", (SIGNAL_FUNC) sig_hilight_text);
+       signal_add("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+       signal_add("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+       signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void window_activity_deinit(void)
+{
+       if (hide_targets != NULL)
+               g_strfreev(hide_targets);
+
+       signal_remove("print text", (SIGNAL_FUNC) sig_hilight_text);
+       signal_remove("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+       signal_remove("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+       signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/apps/irssi/src/fe-common/core/window-commands.c b/apps/irssi/src/fe-common/core/window-commands.c
new file mode 100644 (file)
index 0000000..17cf65e
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ window-commands.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 "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "servers.h"
+
+#include "levels.h"
+
+#include "themes.h"
+#include "fe-windows.h"
+#include "window-items.h"
+#include "windows-layout.h"
+#include "printtext.h"
+
+static void cmd_window(const char *data, void *server, WI_ITEM_REC *item)
+{
+       if (is_numeric(data, 0)) {
+                signal_emit("command window refnum", 3, data, server, item);
+               return;
+       }
+
+       command_runsub("window", data, server, item);
+}
+
+/* SYNTAX: WINDOW NEW [hide] */
+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_strncasecmp(data, "hid", 3) == 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);
+}
+
+/* SYNTAX: WINDOW CLOSE [<first> [<last>] */
+static void cmd_window_close(const char *data)
+{
+        GSList *tmp, *destroys;
+       char *first, *last;
+        int first_num, last_num;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 2, &first, &last))
+               return;
+
+       if ((*first != '\0' && !is_numeric(first, '\0')) ||
+           ((*last != '\0') && !is_numeric(last, '\0'))) {
+               cmd_params_free(free_arg);
+                return;
+       }
+
+       first_num = *first == '\0' ? active_win->refnum : atoi(first);
+       last_num = *last == '\0' ? active_win->refnum : atoi(last);
+
+        /* get list of windows to destroy */
+        destroys = NULL;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               if (rec->refnum >= first_num && rec->refnum <= last_num)
+                       destroys = g_slist_append(destroys, rec);
+       }
+
+        /* really destroy the windows */
+       while (destroys != NULL) {
+               WINDOW_REC *rec = destroys->data;
+
+               if (windows->next != NULL)
+                       window_destroy(rec);
+
+                destroys = g_slist_remove(destroys, rec);
+       }
+
+       cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW REFNUM <number> */
+static void cmd_window_refnum(const char *data)
+{
+       WINDOW_REC *window;
+
+       if (!is_numeric(data, 0))
+               return;
+
+       window = window_find_refnum(atoi(data));
+       if (window != NULL)
+               window_set_active(window);
+}
+
+/* return the first window number with the highest activity */
+static WINDOW_REC *window_highest_activity(WINDOW_REC *window)
+{
+       WINDOW_REC *rec, *max_win;
+       GSList *tmp;
+       int max_act, through;
+
+       g_return_val_if_fail(window != NULL, NULL);
+
+       max_win = NULL; 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->data_level > 0 && max_act < rec->data_level) {
+                       max_act = rec->data_level;
+                       max_win = rec;
+               }
+       }
+
+       return max_win;
+}
+
+/* SYNTAX: WINDOW GOTO active|<number>|<name> */
+static void cmd_window_goto(const char *data)
+{
+       WINDOW_REC *window;
+
+       g_return_if_fail(data != NULL);
+
+       if (is_numeric(data, 0)) {
+               cmd_window_refnum(data);
+               return;
+       }
+
+       if (g_strcasecmp(data, "active") == 0)
+                window = window_highest_activity(active_win);
+       else
+                window = window_find_item(active_win->active_server, data);
+
+       if (window != NULL)
+               window_set_active(window);
+}
+
+/* SYNTAX: WINDOW NEXT */
+static void cmd_window_next(void)
+{
+       int num;
+
+       num = window_refnum_next(active_win->refnum, TRUE);
+       if (num < 1) num = windows_refnum_last();
+
+       window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LAST */
+static void cmd_window_last(void)
+{
+       if (windows->next != NULL)
+               window_set_active(windows->next->data);
+}
+
+/* SYNTAX: WINDOW PREVIOUS */
+static void cmd_window_previous(void)
+{
+       int num;
+
+       num = window_refnum_prev(active_win->refnum, TRUE);
+       if (num < 1) num = window_refnum_next(0, TRUE);
+
+       window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LEVEL [<level>] */
+static void cmd_window_level(const char *data)
+{
+       char *level;
+
+       g_return_if_fail(data != NULL);
+
+       window_set_level(active_win, combine_level(active_win->level, data));
+
+       level = active_win->level == 0 ? g_strdup("NONE") :
+               bits2level(active_win->level);
+       printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                          TXT_WINDOW_LEVEL, level);
+       g_free(level);
+}
+
+/* SYNTAX: WINDOW SERVER [-sticky | -unsticky] <tag> */
+static void cmd_window_server(const char *data)
+{
+       GHashTable *optlist;
+       SERVER_REC *server;
+        char *tag;
+       void *free_arg;
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+                           "window server", &optlist, &tag))
+               return;
+
+       if (*tag == '\0' &&
+           (g_hash_table_lookup(optlist, "sticky") != NULL ||
+            g_hash_table_lookup(optlist, "unsticky") != NULL)) {
+               tag = active_win->active_server->tag;
+       }
+
+       if (*tag == '\0')
+               cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+       server = server_find_tag(tag);
+
+       if (g_hash_table_lookup(optlist, "unsticky") != NULL &&
+           active_win->servertag != NULL) {
+               g_free_and_null(active_win->servertag);
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_UNSET_SERVER_STICKY, server->tag);
+       }
+
+       if (active_win->servertag != NULL &&
+           g_hash_table_lookup(optlist, "sticky") == NULL) {
+               printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+                                  TXT_ERROR_SERVER_STICKY);
+       } else if (server == NULL) {
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_UNKNOWN_SERVER_TAG, tag);
+       } else if (active_win->active == NULL) {
+               window_change_server(active_win, server);
+               if (g_hash_table_lookup(optlist, "sticky") != NULL) {
+                        g_free_not_null(active_win->servertag);
+                       active_win->servertag = g_strdup(server->tag);
+                       printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                          TXT_SET_SERVER_STICKY, server->tag);
+               }
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_SERVER_CHANGED,
+                                  server->tag, server->connrec->address,
+                                  server->connrec->chatnet == NULL ? "" :
+                                  server->connrec->chatnet);
+       }
+
+       cmd_params_free(free_arg);
+}
+
+static void cmd_window_item(const char *data, void *server, WI_ITEM_REC *item)
+{
+       command_runsub("window item", data, server, item);
+}
+
+/* SYNTAX: WINDOW ITEM PREV */
+static void cmd_window_item_prev(void)
+{
+       window_item_prev(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM NEXT */
+static void cmd_window_item_next(void)
+{
+       window_item_next(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM GOTO <name> */
+static void cmd_window_item_goto(const char *data, SERVER_REC *server)
+{
+        WI_ITEM_REC *item;
+
+        item = window_item_find_window(active_win, server, data);
+        if (item != NULL)
+                window_item_set_active(active_win, item);
+}
+
+/* SYNTAX: WINDOW ITEM MOVE <number>|<name> */
+static void cmd_window_item_move(const char *data, SERVER_REC *server,
+                                 WI_ITEM_REC *item)
+{
+        WINDOW_REC *window;
+
+        if (is_numeric(data, '\0')) {
+                /* move current window item to specified window */
+                window = window_find_refnum(atoi(data));
+        } else {
+                /* move specified window item to current window */
+                item = window_item_find(server, data);
+                window = active_win;
+        }
+        if (window != NULL && item != NULL)
+                window_item_set_active(window, item);
+}
+
+/* SYNTAX: WINDOW NUMBER [-sticky] <number> */
+static void cmd_window_number(const char *data)
+{
+       GHashTable *optlist;
+        char *refnum;
+       void *free_arg;
+        int num;
+
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+                           "window number", &optlist, &refnum))
+               return;
+
+       if (*refnum == '\0')
+               cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+       num = atoi(refnum);
+       if (num < 1) {
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_REFNUM_TOO_LOW);
+       } else {
+               window_set_refnum(active_win, num);
+               active_win->sticky_refnum =
+                       g_hash_table_lookup(optlist, "sticky") != NULL;
+       }
+
+        cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW NAME <name> */
+static void cmd_window_name(const char *data)
+{
+        window_set_name(active_win, data);
+}
+
+/* we're moving the first window to last - move the first contiguous block
+   of refnums to left. Like if there's windows 1..5 and 7..10, move 1 to
+   11, 2..5 to 1..4 and leave 7..10 alone  */
+static void windows_move_left(WINDOW_REC *move_window)
+{
+       WINDOW_REC *window;
+       int refnum;
+
+       window_set_refnum(move_window, windows_refnum_last()+1);
+       for (refnum = 2;; refnum++) {
+               window = window_find_refnum(refnum);
+               if (window == NULL) break;
+
+               window_set_refnum(window, refnum-1);
+       }
+}
+
+/* we're moving the last window to first - make some space so we can use the
+   refnum 1 */
+static void windows_move_right(WINDOW_REC *move_window)
+{
+       WINDOW_REC *window;
+       int refnum;
+
+       /* find the first unused refnum, like if there's windows
+          1..5 and 7..10, we only need to move 1..5 to 2..6 */
+       refnum = 1;
+       while (window_find_refnum(refnum) != NULL) refnum++;
+
+       refnum--;
+       while (refnum > 0) {
+               window = window_find_refnum(refnum);
+               g_return_if_fail(window != NULL);
+               window_set_refnum(window, window == move_window ? 1 : refnum+1);
+
+               refnum--;
+       }
+}
+
+static void cmd_window_move_left(void)
+{
+       int refnum;
+
+       refnum = window_refnum_prev(active_win->refnum, TRUE);
+       if (refnum != -1) {
+               window_set_refnum(active_win, refnum);
+               return;
+       }
+
+       windows_move_left(active_win);
+}
+
+static void cmd_window_move_right(void)
+{
+       int refnum;
+
+       refnum = window_refnum_next(active_win->refnum, TRUE);
+       if (refnum != -1) {
+               window_set_refnum(active_win, refnum);
+               return;
+       }
+
+        windows_move_right(active_win);
+}
+
+/* SYNTAX: WINDOW MOVE <number>|left|right */
+static void cmd_window_move(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       int new_refnum, refnum;
+
+       if (!is_numeric(data, 0)) {
+               command_runsub("window move", data, server, item);
+                return;
+       }
+
+       new_refnum = atoi(data);
+       if (new_refnum > active_win->refnum) {
+               for (;;) {
+                       refnum = window_refnum_next(active_win->refnum, FALSE);
+                       if (refnum == -1 || refnum > new_refnum)
+                               break;
+
+                       window_set_refnum(active_win, refnum);
+               }
+       } else {
+               for (;;) {
+                       refnum = window_refnum_prev(active_win->refnum, FALSE);
+                       if (refnum == -1 || refnum < new_refnum)
+                               break;
+
+                       window_set_refnum(active_win, refnum);
+               }
+       }
+}
+
+/* SYNTAX: WINDOW LIST */
+static void cmd_window_list(void)
+{
+       GSList *tmp, *sorted;
+       char *levelstr;
+
+       sorted = windows_get_sorted();
+       printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_HEADER);
+       for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+               levelstr = bits2level(rec->level);
+               printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_LINE,
+                           rec->refnum, rec->name == NULL ? "" : rec->name,
+                           rec->active == NULL ? "" : rec->active->name,
+                           rec->active_server == NULL ? "" : ((SERVER_REC *) rec->active_server)->tag,
+                           levelstr);
+               g_free(levelstr);
+       }
+       g_slist_free(sorted);
+        printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_FOOTER);
+}
+
+/* SYNTAX: WINDOW THEME <name> */
+static void cmd_window_theme(const char *data)
+{
+       THEME_REC *theme;
+
+       g_free_not_null(active_win->theme_name);
+       active_win->theme_name = g_strdup(data);
+
+       active_win->theme = theme = theme_load(data);
+       if (theme != NULL) {
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_WINDOW_THEME_CHANGED,
+                                  theme->name, theme->path);
+       } else {
+               printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+                                  TXT_THEME_NOT_FOUND, data);
+       }
+}
+
+static void cmd_layout(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+       command_runsub("layout", data, server, item);
+}
+
+/* SYNTAX: FOREACH WINDOW <command> */
+static void cmd_foreach_window(const char *data)
+{
+        WINDOW_REC *old;
+       GSList *tmp;
+
+        old = active_win;
+       for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+                active_win = rec;
+               signal_emit("send command", 3, data, rec->active_server,
+                           rec->active);
+       }
+        active_win = old;
+}
+
+void window_commands_init(void)
+{
+       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 kill", NULL, (SIGNAL_FUNC) cmd_window_close);
+       command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+       command_bind("window refnum", NULL, (SIGNAL_FUNC) cmd_window_refnum);
+       command_bind("window goto", NULL, (SIGNAL_FUNC) cmd_window_goto);
+       command_bind("window previous", NULL, (SIGNAL_FUNC) cmd_window_previous);
+       command_bind("window next", NULL, (SIGNAL_FUNC) cmd_window_next);
+       command_bind("window last", NULL, (SIGNAL_FUNC) cmd_window_last);
+       command_bind("window level", NULL, (SIGNAL_FUNC) cmd_window_level);
+       command_bind("window item", NULL, (SIGNAL_FUNC) cmd_window_item);
+       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 item goto", NULL, (SIGNAL_FUNC) cmd_window_item_goto);
+       command_bind("window item move", NULL, (SIGNAL_FUNC) cmd_window_item_move);
+       command_bind("window number", NULL, (SIGNAL_FUNC) cmd_window_number);
+       command_bind("window name", NULL, (SIGNAL_FUNC) cmd_window_name);
+       command_bind("window move", NULL, (SIGNAL_FUNC) cmd_window_move);
+       command_bind("window move left", NULL, (SIGNAL_FUNC) cmd_window_move_left);
+       command_bind("window move right", NULL, (SIGNAL_FUNC) cmd_window_move_right);
+       command_bind("window list", NULL, (SIGNAL_FUNC) cmd_window_list);
+       command_bind("window theme", NULL, (SIGNAL_FUNC) cmd_window_theme);
+       command_bind("layout", NULL, (SIGNAL_FUNC) cmd_layout);
+       /* SYNTAX: LAYOUT SAVE */
+       command_bind("layout save", NULL, (SIGNAL_FUNC) windows_layout_save);
+       /* SYNTAX: LAYOUT RESET */
+       command_bind("layout reset", NULL, (SIGNAL_FUNC) windows_layout_reset);
+       command_bind("foreach window", NULL, (SIGNAL_FUNC) cmd_foreach_window);
+
+       command_set_options("window number", "sticky");
+       command_set_options("window server", "sticky unsticky");
+}
+
+void window_commands_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 kill", (SIGNAL_FUNC) cmd_window_close);
+       command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+       command_unbind("window refnum", (SIGNAL_FUNC) cmd_window_refnum);
+       command_unbind("window goto", (SIGNAL_FUNC) cmd_window_goto);
+       command_unbind("window previous", (SIGNAL_FUNC) cmd_window_previous);
+       command_unbind("window next", (SIGNAL_FUNC) cmd_window_next);
+       command_unbind("window last", (SIGNAL_FUNC) cmd_window_last);
+       command_unbind("window level", (SIGNAL_FUNC) cmd_window_level);
+       command_unbind("window item", (SIGNAL_FUNC) cmd_window_item);
+       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 item goto", (SIGNAL_FUNC) cmd_window_item_goto);
+       command_unbind("window item move", (SIGNAL_FUNC) cmd_window_item_move);
+       command_unbind("window number", (SIGNAL_FUNC) cmd_window_number);
+       command_unbind("window name", (SIGNAL_FUNC) cmd_window_name);
+       command_unbind("window move", (SIGNAL_FUNC) cmd_window_move);
+       command_unbind("window move left", (SIGNAL_FUNC) cmd_window_move_left);
+       command_unbind("window move right", (SIGNAL_FUNC) cmd_window_move_right);
+       command_unbind("window list", (SIGNAL_FUNC) cmd_window_list);
+       command_unbind("window theme", (SIGNAL_FUNC) cmd_window_theme);
+       command_unbind("layout", (SIGNAL_FUNC) cmd_layout);
+       command_unbind("layout save", (SIGNAL_FUNC) windows_layout_save);
+       command_unbind("layout reset", (SIGNAL_FUNC) windows_layout_reset);
+       command_unbind("foreach window", (SIGNAL_FUNC) cmd_foreach_window);
+}
diff --git a/apps/irssi/src/fe-common/core/window-items.c b/apps/irssi/src/fe-common/core/window-items.c
new file mode 100644 (file)
index 0000000..d772652
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ 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 "servers.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "printtext.h"
+
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic)
+{
+       g_return_if_fail(window != NULL);
+       g_return_if_fail(item != NULL);
+
+        item->window = window;
+
+       if (window->items == NULL) {
+               window->active = item;
+               window->active_server = item->server;
+       }
+
+       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_item_remove(WI_ITEM_REC *item)
+{
+       WINDOW_REC *window;
+
+       g_return_if_fail(item != NULL);
+
+       window = window_item_window(item);
+
+       if (g_slist_find(window->items, item) == NULL)
+               return;
+
+        item->window = NULL;
+       window->items = g_slist_remove(window->items, item);
+
+       if (window->active == item) {
+               window_item_set_active(window, window->items == NULL ? NULL :
+                                      window->items->data);
+       }
+
+       signal_emit("window item remove", 2, window, item);
+}
+
+void window_item_destroy(WI_ITEM_REC *item)
+{
+       WINDOW_REC *window;
+
+       window = window_item_window(item);
+        window_item_remove(item);
+
+       signal_emit("window item destroy", 2, window, item);
+}
+
+void window_item_change_server(WI_ITEM_REC *item, void *server)
+{
+       WINDOW_REC *window;
+
+       g_return_if_fail(item != NULL);
+
+       window = window_item_window(item);
+       item->server = server;
+
+        signal_emit("window item server changed", 2, window, item);
+       if (window->active == item) window_change_server(window, item->server);
+}
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+        g_return_if_fail(window != NULL);
+
+        if (item != NULL && window_item_window(item) != window) {
+                /* move item to different window */
+                window_item_remove(item);
+                window_item_add(window, item, FALSE);
+        }
+
+       if (window->active != item) {
+               window->active = item;
+               if (item != NULL && window->active_server != item->server)
+                       window_change_server(window, item->server);
+               signal_emit("window item changed", 2, window, item);
+       }
+}
+
+/* Return TRUE if `item' is the active window item in the window.
+   `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item)
+{
+       WINDOW_REC *window;
+
+       if (item == NULL)
+               return FALSE;
+
+       window = window_item_window(item);
+       if (window == NULL)
+               return FALSE;
+
+       return window->active == item;
+}
+
+void window_item_prev(WINDOW_REC *window)
+{
+       WI_ITEM_REC *last;
+       GSList *tmp;
+
+       g_return_if_fail(window != NULL);
+
+       last = NULL;
+       for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+               WI_ITEM_REC *rec = tmp->data;
+
+               if (rec != window->active)
+                       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);
+}
+
+void window_item_next(WINDOW_REC *window)
+{
+       WI_ITEM_REC *next;
+       GSList *tmp;
+       int gone;
+
+       g_return_if_fail(window != NULL);
+
+       next = NULL; gone = FALSE;
+       for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+               WI_ITEM_REC *rec = tmp->data;
+
+               if (rec == window->active)
+                       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);
+}
+
+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 window_bind_has_sticky(WINDOW_REC *window)
+{
+       GSList *tmp;
+
+       for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+               WINDOW_BIND_REC *rec = tmp->data;
+
+               if (rec->sticky)
+                        return TRUE;
+       }
+
+        return FALSE;
+}
+
+void window_item_create(WI_ITEM_REC *item, int automatic)
+{
+       WINDOW_REC *window;
+       GSList *tmp, *sorted;
+       int clear_waiting, reuse_unused_windows;
+
+       g_return_if_fail(item != NULL);
+
+       reuse_unused_windows =
+               !settings_get_bool("autoclose_windows") ||
+               settings_get_bool("reuse_unused_windows");
+
+       clear_waiting = TRUE;
+       window = NULL;
+        sorted = windows_get_sorted();
+       for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+               WINDOW_REC *rec = tmp->data;
+
+                /* is item bound to this window? */
+               if (item->server != NULL &&
+                   window_bind_find(rec, item->server->tag, item->name)) {
+                       window = rec;
+                       clear_waiting = FALSE;
+                       break;
+               }
+
+               /* use this window IF:
+                    - reuse_unused_windows is ON
+                    - window has no existing items
+                    - window has no name
+                    - window has no sticky binds (/LAYOUT SAVEd)
+                    - we already haven't found "good enough" window,
+                      except if
+                         - this is the active window
+                         - old window had some temporary bounds and this
+                          one doesn't
+                    */
+               if (reuse_unused_windows && rec->items == NULL &&
+                   rec->name == NULL && !window_bind_has_sticky(rec) &&
+                   (window == NULL || rec == active_win ||
+                    window->bound_items != NULL))
+                       window = rec;
+       }
+        g_slist_free(sorted);
+
+        if (window == NULL && !settings_get_bool("autocreate_windows")) {
+                /* never create new windows automatically */
+                window = active_win;
+        }
+
+       if (window == NULL) {
+               /* create new window to use */
+               window = window_create(item, automatic);
+       } else {
+               /* use existing window */
+               window_item_add(window, item, automatic);
+       }
+
+       if (clear_waiting)
+                window_bind_remove_unsticky(window);
+}
+
+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,
+                           TXT_TALKING_WITH, item->name);
+       }
+}
+
+void window_items_init(void)
+{
+       settings_add_bool("lookandfeel", "reuse_unused_windows", FALSE);
+       settings_add_bool("lookandfeel", "autocreate_windows", TRUE);
+
+       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/apps/irssi/src/fe-common/core/window-items.h b/apps/irssi/src/fe-common/core/window-items.h
new file mode 100644 (file)
index 0000000..f8db3c3
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __WINDOW_ITEMS_H
+#define __WINDOW_ITEMS_H
+
+#include "fe-windows.h"
+
+/* Add/remove/destroy window item from `window' */
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic);
+void window_item_remove(WI_ITEM_REC *item);
+void window_item_destroy(WI_ITEM_REC *item);
+
+/* Find a window for `item' and call window_item_add(). */
+void window_item_create(WI_ITEM_REC *item, int automatic);
+
+#define window_item_window(item) \
+       ((WINDOW_REC *) ((WI_ITEM_REC *) (item))->window)
+void window_item_change_server(WI_ITEM_REC *item, void *server);
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item);
+/* Return TRUE if `item' is the active window item in the window.
+   `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item);
+
+void window_item_prev(WINDOW_REC *window);
+void window_item_next(WINDOW_REC *window);
+
+/* Find wanted window item by name. `server' can be NULL. */
+WI_ITEM_REC *window_item_find(void *server, const char *name);
+WI_ITEM_REC *window_item_find_window(WINDOW_REC *window,
+                                     void *server, const char *name);
+
+void window_items_init(void);
+void window_items_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/fe-common/core/windows-layout.c b/apps/irssi/src/fe-common/core/windows-layout.c
new file mode 100644 (file)
index 0000000..814127f
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ windows-layout.c : irssi
+
+    Copyright (C) 2000-2001 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 "chat-protocols.h"
+#include "servers.h"
+#include "queries.h"
+
+#include "module-formats.h"
+#include "printtext.h"
+#include "themes.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+static void sig_window_restore_item(WINDOW_REC *window, const char *type,
+                                   CONFIG_NODE *node)
+{
+       char *name, *tag, *chat_type;
+
+       chat_type = config_node_get_str(node, "chat_type", NULL);
+       name = config_node_get_str(node, "name", NULL);
+       tag = config_node_get_str(node, "tag", NULL);
+
+       if (name == NULL || tag == NULL)
+               return;
+
+       if (g_strcasecmp(type, "CHANNEL") == 0) {
+               /* bind channel to window */
+               WINDOW_BIND_REC *rec = window_bind_add(window, tag, name);
+                rec->sticky = TRUE;
+       } else if (g_strcasecmp(type, "QUERY") == 0 && chat_type != NULL) {
+               /* create query immediately */
+               chat_protocol_find(chat_type)->query_create(tag, name, TRUE);
+       }
+}
+
+static void window_add_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+       GSList *tmp;
+       char *type;
+
+       if (node == NULL)
+               return;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *node = tmp->data;
+
+               type = config_node_get_str(node, "type", NULL);
+               if (type != NULL) {
+                       signal_emit("window restore item", 3,
+                                   window, type, node);
+               }
+       }
+}
+
+void windows_layout_restore(void)
+{
+       WINDOW_REC *window;
+       CONFIG_NODE *node;
+       GSList *tmp;
+
+       node = iconfig_node_traverse("windows", FALSE);
+       if (node == NULL) return;
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *node = tmp->data;
+
+               window = window_create(NULL, TRUE);
+               window_set_refnum(window, atoi(node->key));
+                window->sticky_refnum = config_node_get_bool(node, "sticky_refnum", FALSE);
+               window_set_name(window, config_node_get_str(node, "name", NULL));
+               window_set_level(window, level2bits(config_node_get_str(node, "level", "")));
+
+               window->servertag = g_strdup(config_node_get_str(node, "servertag", NULL));
+               window->theme_name = g_strdup(config_node_get_str(node, "theme", NULL));
+               if (window->theme_name != NULL)
+                       window->theme = theme_load(window->theme_name);
+
+               window_add_items(window, config_node_section(node, "items", -1));
+               signal_emit("window restore", 2, window, node);
+       }
+
+       signal_emit("windows restored", 0);
+}
+
+static void window_save_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+       CONFIG_NODE *subnode;
+       GSList *tmp;
+       const char *type;
+
+       node = config_node_section(node, "items", NODE_TYPE_LIST);
+       for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+               WI_ITEM_REC *rec = tmp->data;
+               SERVER_REC *server = rec->server;
+
+               type = module_find_id_str("WINDOW ITEM TYPE", rec->type);
+               if (type == NULL) continue;
+
+               subnode = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+               iconfig_node_set_str(subnode, "type", type);
+               type = chat_protocol_find_id(rec->chat_type)->name;
+               iconfig_node_set_str(subnode, "chat_type", type);
+               iconfig_node_set_str(subnode, "name", rec->name);
+
+               if (server != NULL)
+                       iconfig_node_set_str(subnode, "tag", server->tag);
+               else if (IS_QUERY(rec)) {
+                       iconfig_node_set_str(subnode, "tag",
+                                            QUERY(rec)->server_tag);
+               }
+       }
+}
+
+static void window_save(WINDOW_REC *window, CONFIG_NODE *node)
+{
+       char refnum[MAX_INT_STRLEN];
+
+        ltoa(refnum, window->refnum);
+       node = config_node_section(node, refnum, NODE_TYPE_BLOCK);
+
+       if (window->sticky_refnum)
+               iconfig_node_set_bool(node, "sticky_refnum", TRUE);
+
+       if (window->name != NULL)
+               iconfig_node_set_str(node, "name", window->name);
+       if (window->servertag != NULL)
+               iconfig_node_set_str(node, "servertag", window->servertag);
+       if (window->level != 0) {
+                char *level = bits2level(window->level);
+               iconfig_node_set_str(node, "level", level);
+               g_free(level);
+       }
+       if (window->theme_name != NULL)
+               iconfig_node_set_str(node, "theme", window->theme_name);
+
+       if (window->items != NULL)
+               window_save_items(window, node);
+
+       signal_emit("window save", 2, window, node);
+}
+
+void windows_layout_save(void)
+{
+       CONFIG_NODE *node;
+
+       iconfig_set_str(NULL, "windows", NULL);
+       node = iconfig_node_traverse("windows", TRUE);
+
+       g_slist_foreach(windows, (GFunc) window_save, node);
+       signal_emit("windows saved", 0);
+
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_WINDOWS_LAYOUT_SAVED);
+}
+
+void windows_layout_reset(void)
+{
+       iconfig_set_str(NULL, "windows", NULL);
+       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                   TXT_WINDOWS_LAYOUT_RESET);
+}
+
+void windows_layout_init(void)
+{
+       signal_add("window restore item", (SIGNAL_FUNC) sig_window_restore_item);
+}
+
+void windows_layout_deinit(void)
+{
+       signal_remove("window restore item", (SIGNAL_FUNC) sig_window_restore_item);
+}
diff --git a/apps/irssi/src/fe-common/core/windows-layout.h b/apps/irssi/src/fe-common/core/windows-layout.h
new file mode 100644 (file)
index 0000000..d33fda5
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef __WINDOWS_LAYOUT_H
+#define __WINDOWS_LAYOUT_H
+
+void windows_layout_restore(void);
+void windows_layout_save(void);
+void windows_layout_reset(void);
+
+void windows_layout_init(void);
+void windows_layout_deinit(void);
+
+#endif
index f794ae8fe0568a4d30b699fb39b51e1587071df9..b25d74302a8f2dc18ed64348be896d5d1d822dd1 100644 (file)
@@ -1,27 +1,30 @@
+moduledir = $(libdir)/irssi/modules
+
 INCLUDES = $(GLIB_CFLAGS) -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src
 
-SILC_INCLUDE=../../../..
-IRSSI_INCLUDE=../../..
+module_LTLIBRARIES = libsilc_core.la
+
+libsilc_core_la_LDFLAGS = -avoid-version
 
 INCLUDES = \
-        $(GLIB_CFLAGS) \
-        -DSYSCONFDIR=\""$(sysconfdir)"\" \
-        -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \
-        -I$(IRSSI_INCLUDE)/src/core \
-        -I$(SILC_INCLUDE)/includes \
-        -I$(SILC_INCLUDE)/lib/silccore \
-        -I$(SILC_INCLUDE)/lib/silccrypt \
-        -I$(SILC_INCLUDE)/lib/silcmath \
-        -I$(SILC_INCLUDE)/lib/silcske \
-        -I$(SILC_INCLUDE)/lib/silcsim \
-        -I$(SILC_INCLUDE)/lib/silcutil \
-        -I$(SILC_INCLUDE)/lib/silcclient \
-        -I$(SILC_INCLUDE)/lib/silcmath/gmp \
-        -I$(SILC_INCLUDE)/lib/trq
-
-noinst_LIBRARIES=libsilc_core.a
-
-libsilc_core_a_SOURCES = \
+       $(GLIB_CFLAGS) \
+       -DSYSCONFDIR=\""$(sysconfdir)"\" \
+       -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \
+       -I$(IRSSI_INCLUDE)/src/core \
+       -I$(SILC_INCLUDE)/includes \
+       -I$(SILC_INCLUDE)/lib/silccore \
+       -I$(SILC_INCLUDE)/lib/silccrypt \
+       -I$(SILC_INCLUDE)/lib/silcmath \
+       -I$(SILC_INCLUDE)/lib/silcske \
+       -I$(SILC_INCLUDE)/lib/silcsim \
+       -I$(SILC_INCLUDE)/lib/silcutil \
+       -I$(SILC_INCLUDE)/lib/silcclient \
+       -I$(SILC_INCLUDE)/lib/silcmath/gmp \
+       -I$(SILC_INCLUDE)/lib/trq
+
+libsilc_core_la_DEPENDENCIES = .libs/libsilcclient.a .libs/libsilcorig.a
+
+libsilc_core_la_SOURCES = \
        silc-channels.c \
        silc-core.c \
        silc-nicklist.c \
@@ -36,3 +39,20 @@ noinst_HEADERS = \
        silc-nicklist.h \
        silc-queries.h \
        silc-servers.h
+
+SILC_LIBS = \
+       libsilcclient.la \
+       libsilcorig.la
+
+libsilc_core_la_LIBADD = \
+       $(SILC_LIBS)
+
+EXTRA_DIST = \
+       $(SILC_LIBS)
+
+.libs/libsilcclient.a:
+       if [ ! -d .libs ]; then mkdir .libs; fi
+       cd .libs && ln -sf ../../../libsilcclient.a . && cd ..
+
+.libs/libsilcorig.a: .libs/libsilcclient.a
+       cd .libs && ln -sf ../../../libsilcorig.a . && cd ..
diff --git a/apps/irssi/src/silc/core/clientutil.c b/apps/irssi/src/silc/core/clientutil.c
new file mode 100644 (file)
index 0000000..a9b0434
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ clientutil.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 "silc-servers.h"
+
+/* Verifies received public key. If user decides to trust the key it is
+   saved as trusted server key for later use. If user does not trust the
+   key this returns FALSE. */
+
+int silc_client_verify_server_key(SILC_SERVER_REC *server,
+                                 unsigned char *pk, unsigned int pk_len,
+                                 SilcSKEPKType pk_type)
+{
+  char filename[256];
+  char file[256];
+  char *hostname, *fingerprint;
+  struct stat st;
+
+  hostname = server->connrec->address;
+
+  if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+    //silc_say(client, "We don't support server %s key type", hostname);
+    return FALSE;
+  }
+
+  memset(filename, 0, sizeof(filename));
+  memset(file, 0, sizeof(file));
+  snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
+          server->connrec->port);
+  snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s", 
+          g_get_home_dir(), file);
+
+  /* Check wheter this key already exists */
+  if (stat(filename, &st) < 0) {
+
+    fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+    /*silc_say(client, "Received server %s public key", hostname);
+    silc_say(client, "Fingerprint for the server %s key is", hostname);
+    silc_say(client, "%s", fingerprint);*/
+    silc_free(fingerprint);
+
+    /* Ask user to verify the key and save it */
+    /*if (silc_client_ask_yes_no(client,
+       "Would you like to accept the key (y/n)? "))*/
+      {
+       /* Save the key for future checking */
+       silc_pkcs_save_public_key_data(filename, pk, pk_len, 
+                                      SILC_PKCS_FILE_PEM);
+       return TRUE;
+      }
+  } else {
+    /* The key already exists, verify it. */
+    SilcPublicKey public_key;
+    unsigned char *encpk;
+    unsigned int encpk_len;
+
+    /* Load the key file */
+    if (!silc_pkcs_load_public_key(filename, &public_key, 
+                                  SILC_PKCS_FILE_PEM))
+      if (!silc_pkcs_load_public_key(filename, &public_key, 
+                                    SILC_PKCS_FILE_BIN)) {
+       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+       /*silc_say(client, "Received server %s public key", hostname);
+       silc_say(client, "Fingerprint for the server %s key is", hostname);
+       silc_say(client, "%s", fingerprint);*/
+       silc_free(fingerprint);
+       /*silc_say(client, "Could not load your local copy of the server %s key",
+                hostname);
+       if (silc_client_ask_yes_no(client, 
+          "Would you like to accept the key anyway (y/n)? "))*/
+         {
+           /* Save the key for future checking */
+           unlink(filename);
+           silc_pkcs_save_public_key_data(filename, pk, pk_len,
+                                          SILC_PKCS_FILE_PEM);
+           return TRUE;
+         }
+       
+       return FALSE;
+      }
+  
+    /* Encode the key data */
+    encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+    if (!encpk) {
+      fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+      /*silc_say(client, "Received server %s public key", hostname);
+      silc_say(client, "Fingerprint for the server %s key is", hostname);
+      silc_say(client, "%s", fingerprint);*/
+      silc_free(fingerprint);
+      /*silc_say(client, "Your local copy of the server %s key is malformed",
+              hostname);
+      if (silc_client_ask_yes_no(client,
+         "Would you like to accept the key anyway (y/n)? "))*/
+       {
+         /* Save the key for future checking */
+         unlink(filename);
+         silc_pkcs_save_public_key_data(filename, pk, pk_len,
+                                        SILC_PKCS_FILE_PEM);
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+
+    if (memcmp(encpk, pk, encpk_len)) {
+      fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+      /*silc_say(client, "Received server %s public key", hostname);
+      silc_say(client, "Fingerprint for the server %s key is", hostname);
+      silc_say(client, "%s", fingerprint);*/
+      silc_free(fingerprint);
+      /*silc_say(client, "Server %s key does not match with your local copy",
+              hostname);
+      silc_say(client, "It is possible that the key has expired or changed");
+      silc_say(client, "It is also possible that some one is performing "
+                      "man-in-the-middle attack");*/
+      
+      /* Ask user to verify the key and save it */
+      /*if (silc_client_ask_yes_no(client,
+         "Would you like to accept the key anyway (y/n)? "))*/
+       {
+         /* Save the key for future checking */
+         unlink(filename);
+         silc_pkcs_save_public_key_data(filename, pk, pk_len,
+                                        SILC_PKCS_FILE_PEM);
+         return TRUE;
+       }
+
+      //silc_say(client, "Will not accept server %s key", hostname);
+      return FALSE;
+    }
+
+    /* Local copy matched */
+    return TRUE;
+  }
+
+  //silc_say(client, "Will not accept server %s key", hostname);
+  return FALSE;
+}
diff --git a/apps/irssi/src/silc/core/clientutil.h b/apps/irssi/src/silc/core/clientutil.h
new file mode 100644 (file)
index 0000000..4af7425
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __CLIENTUTIL_H
+#define __CLIENTUTIL_H
+
+int silc_client_verify_server_key(SILC_SERVER_REC *server,
+                                 unsigned char *pk, unsigned int pk_len,
+                                 SilcSKEPKType pk_type);
+
+#endif
diff --git a/apps/irssi/src/silc/core/module.h b/apps/irssi/src/silc/core/module.h
new file mode 100644 (file)
index 0000000..aadd816
--- /dev/null
@@ -0,0 +1,11 @@
+#include "common.h"
+
+#define MODULE_NAME "silc"
+
+#undef PACKAGE
+#undef VERSION
+#include "silcincludes.h"
+#include "clientlibincludes.h"
+#include "silc-core.h"
+
+#define SILC_PROTOCOL (chat_protocol_lookup("SILC"))
index 014ed1d020aea9eb5b4a75705c50db5486f5e6dd..b1f82f863970d8e14e0c274fb9c42cdfac7b2cc6 100644 (file)
@@ -225,70 +225,68 @@ static void event_nick(SILC_SERVER_REC *server, va_list va)
 
 static void event_cmode(SILC_SERVER_REC *server, va_list va)
 {
-  SILC_CHANNEL_REC *chanrec;
-  SilcClientEntry client;
-  SilcChannelEntry channel;
-  char *mode;
-  uint32 modei;
-
-  client = va_arg(va, SilcClientEntry);
-  modei = va_arg(va, uint32);
-  channel = va_arg(va, SilcChannelEntry);
-  mode = silc_client_chmode(modei, channel);
-  
-  chanrec = silc_channel_find_entry(server, channel);
-  if (chanrec != NULL) {
-    g_free_not_null(chanrec->mode);
-    chanrec->mode = g_strdup(mode == NULL ? "" : mode);
-    signal_emit("channel mode changed", 1, chanrec);
-  }
-  
-  /*signal_emit("message mode", 5, server, chanrec->name,
-    client->nickname, client->username, mode);*/
-  printtext(server, channel->channel_name, MSGLEVEL_MODES,
-           "mode/%s [%s] by %s", channel->channel_name, mode,
-           client->nickname);
-  
-  g_free(mode);
+       SILC_CHANNEL_REC *chanrec;
+       SilcClientEntry client;
+       SilcChannelEntry channel;
+       char *mode;
+
+       client = va_arg(va, SilcClientEntry);
+       mode = silc_client_chmode(va_arg(va, unsigned int));
+       channel = va_arg(va, SilcChannelEntry);
+
+       chanrec = silc_channel_find_entry(server, channel);
+       if (chanrec != NULL) {
+               g_free_not_null(chanrec->mode);
+               chanrec->mode = g_strdup(mode == NULL ? "" : mode);
+               signal_emit("channel mode changed", 1, chanrec);
+       }
+
+       /*signal_emit("message mode", 5, server, chanrec->name,
+                   client->nickname, client->username, mode);*/
+       printtext(server, channel->channel_name, MSGLEVEL_MODES,
+                 "mode/%s [%s] by %s", channel->channel_name, mode,
+                 client->nickname);
+
+       g_free(mode);
 }
 
 static void event_cumode(SILC_SERVER_REC *server, va_list va)
 {
-  SILC_CHANNEL_REC *chanrec;
-  SilcClientEntry client, destclient;
-  SilcChannelEntry channel;
-  int mode;
-  char *modestr;
-  
-  client = va_arg(va, SilcClientEntry);
-  mode = va_arg(va, uint32);
-  destclient = va_arg(va, SilcClientEntry);
-  channel = va_arg(va, SilcChannelEntry);
-  
-  modestr = silc_client_chumode(mode);
-  chanrec = silc_channel_find_entry(server, channel);
-  if (chanrec != NULL) {
-    SILC_NICK_REC *nick;
-    
-    if (destclient == server->conn->local_entry) {
-      chanrec->chanop =
-       (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
-    }
-    
-    nick = silc_nicklist_find(chanrec, client);
-    if (nick != NULL) {
-      nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
-      signal_emit("nick mode changed", 2, chanrec, nick);
-    }
-  }
-  
-  /*signal_emit("message mode", 5, server, chanrec->name,
-    client->nickname, client->username, modestr);*/
-  printtext(server, channel->channel_name, MSGLEVEL_MODES,
-           "mode/%s [%s] by %s", channel->channel_name, modestr,
-           client->nickname);
-  
-  g_free(modestr);
+       SILC_CHANNEL_REC *chanrec;
+       SilcClientEntry client, destclient;
+       SilcChannelEntry channel;
+        int mode;
+       char *modestr;
+
+       client = va_arg(va, SilcClientEntry);
+       mode = va_arg(va, unsigned int);
+       destclient = va_arg(va, SilcClientEntry);
+       channel = va_arg(va, SilcChannelEntry);
+
+       modestr = silc_client_chumode(mode);
+       chanrec = silc_channel_find_entry(server, channel);
+       if (chanrec != NULL) {
+               SILC_NICK_REC *nick;
+
+               if (destclient == server->conn->local_entry) {
+                       chanrec->chanop =
+                               (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+               }
+
+               nick = silc_nicklist_find(chanrec, client);
+               if (nick != NULL) {
+                        nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+                       signal_emit("nick mode changed", 2, chanrec, nick);
+               }
+       }
+
+       /*signal_emit("message mode", 5, server, chanrec->name,
+                   client->nickname, client->username, modestr);*/
+       printtext(server, channel->channel_name, MSGLEVEL_MODES,
+                 "mode/%s [%s] by %s", channel->channel_name, modestr,
+                 client->nickname);
+
+       g_free(modestr);
 }
 
 static void command_part(const char *data, SILC_SERVER_REC *server,
diff --git a/apps/irssi/src/silc/core/silc-channels.h b/apps/irssi/src/silc/core/silc-channels.h
new file mode 100644 (file)
index 0000000..de6db8b
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef __SILC_CHANNELS_H
+#define __SILC_CHANNELS_H
+
+#include "chat-protocols.h"
+#include "channels.h"
+#include "silc-servers.h"
+
+/* Returns SILC_CHANNEL_REC if it's SILC channel, NULL if it isn't. */
+#define SILC_CHANNEL(channel) \
+       PROTO_CHECK_CAST(CHANNEL(channel), SILC_CHANNEL_REC, chat_type, "SILC")
+
+#define IS_SILC_CHANNEL(channel) \
+       (SILC_CHANNEL(channel) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SILC_SERVER_REC
+typedef struct {
+#include "channel-rec.h"
+
+       GSList *banlist; /* list of bans */
+       GSList *ebanlist; /* list of ban exceptions */
+       GSList *invitelist; /* invite list */
+
+        SilcChannelEntry entry;
+} SILC_CHANNEL_REC;
+
+void silc_channels_init(void);
+void silc_channels_deinit(void);
+
+/* Create new SILC channel record */
+SILC_CHANNEL_REC *silc_channel_create(SILC_SERVER_REC *server,
+                                     const char *name, int automatic);
+
+#define silc_channel_find(server, name) \
+       SILC_CHANNEL(channel_find(SERVER(server), name))
+
+SILC_CHANNEL_REC *silc_channel_find_entry(SILC_SERVER_REC *server,
+                                         SilcChannelEntry entry);
+
+#endif
index 43198042099fba6a187e0e19774f49c9863843c1..7afa379ba7805c3267e8c7d38cbd72b9641a8090 100644 (file)
@@ -1,23 +1,3 @@
-/*
-
-  silc-core.c
-
-  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
-
-  Copyright (C) 2001 Pekka Riikonen
-
-  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.
-
-*/
-
 #include "module.h"
 #include "chat-protocols.h"
 
@@ -52,135 +32,110 @@ extern SilcClientOperations ops;
 static void silc_say(SilcClient client, SilcClientConnection conn,
                     char *msg, ...)
 {
-  SILC_SERVER_REC *server;
-  va_list va;
-  char *str;
-
-  server = conn == NULL ? NULL : conn->context;
-  
-  va_start(va, msg);
-  str = g_strdup_vprintf(msg, va);
-  printtext(server, "#silc", MSGLEVEL_CRAP, "%s", str);
-  g_free(str);
-  va_end(va);
-}
+       SILC_SERVER_REC *server;
+       va_list va;
+       char *str;
 
-/* Message for a channel. The `sender' is the nickname of the sender 
-   received in the packet. The `channel_name' is the name of the channel. */
+       server = conn == NULL ? NULL : conn->context;
 
-static void 
-silc_channel_message(SilcClient client, SilcClientConnection conn,
-                    SilcClientEntry sender, SilcChannelEntry channel,
-                    SilcMessageFlags flags, char *msg)
-{
-  SILC_SERVER_REC *server;
-  SILC_NICK_REC *nick;
-  SILC_CHANNEL_REC *chanrec;
-  
-  server = conn == NULL ? NULL : conn->context;
-  chanrec = silc_channel_find_entry(server, channel);
-  
-  nick = silc_nicklist_find(chanrec, sender);
-  signal_emit("message public", 6, server, msg,
-             nick == NULL ? "(unknown)" : nick->nick,
-             nick == NULL ? NULL : nick->host,
-             chanrec->name, nick);
+       va_start(va, msg);
+       str = g_strdup_vprintf(msg, va);
+       printtext(server, "#silc", MSGLEVEL_CRAP, "%s", str);
+       g_free(str);
+       va_end(va);
 }
 
-/* Private message to the client. The `sender' is the nickname of the
-   sender received in the packet. */
-
-static void 
-silc_private_message(SilcClient client, SilcClientConnection conn,
-                    SilcClientEntry sender, SilcMessageFlags flags,
-                         char *msg)
+static void silc_channel_message(SilcClient c, SilcClientConnection conn,
+                                SilcClientEntry client,
+                                SilcChannelEntry channel, char *msg)
 {
-  SILC_SERVER_REC *server;
-  
-  server = conn == NULL ? NULL : conn->context;
-  signal_emit("message private", 4, server, msg,
-             sender->nickname ? sender->nickname : "(unknown)",
-             sender->username ? sender->username : NULL);
+       SILC_SERVER_REC *server;
+       SILC_NICK_REC *nick;
+        SILC_CHANNEL_REC *chanrec;
+
+       server = conn == NULL ? NULL : conn->context;
+       chanrec = silc_channel_find_entry(server, channel);
+
+       nick = client == NULL ? NULL : silc_nicklist_find(chanrec, client);
+       signal_emit("message public", 6, server, msg,
+                   nick == NULL ? "(unknown)" : nick->nick,
+                   nick == NULL ? NULL : nick->host,
+                   chanrec->name, nick);
 }
 
-/* Notify message to the client. The notify arguments are sent in the
-   same order as servers sends them. The arguments are same as received
-   from the server except for ID's.  If ID is received application receives
-   the corresponding entry to the ID. For example, if Client ID is received
-   application receives SilcClientEntry.  Also, if the notify type is
-   for channel the channel entry is sent to application (even if server
-   does not send it). */
+static void silc_private_message(SilcClient c, SilcClientConnection conn,
+                                SilcClientEntry client, char *msg)
+{
+       SILC_SERVER_REC *server;
+
+       server = conn == NULL ? NULL : conn->context;
+       signal_emit("message private", 4, server, msg,
+                   client == NULL ? "(unknown)" : client->nickname,
+                   client == NULL ? NULL : client->username);
+}
 
 typedef struct {
-  int type;
-  const char *name;
+       int type;
+       const char *name;
 } NOTIFY_REC;
 
 #define MAX_NOTIFY (sizeof(notifies)/sizeof(notifies[0]))
 static NOTIFY_REC notifies[] = {
-  { SILC_NOTIFY_TYPE_NONE,             NULL },
-  { SILC_NOTIFY_TYPE_INVITE,           "invite" },
-  { SILC_NOTIFY_TYPE_JOIN,             "join" },
-  { SILC_NOTIFY_TYPE_LEAVE,            "leave" },
-  { SILC_NOTIFY_TYPE_SIGNOFF,          "signoff" },
-  { SILC_NOTIFY_TYPE_TOPIC_SET,                "topic" },
-  { SILC_NOTIFY_TYPE_NICK_CHANGE,              "nick" },
-  { SILC_NOTIFY_TYPE_CMODE_CHANGE,     "cmode" },
-  { SILC_NOTIFY_TYPE_CUMODE_CHANGE,    "cumode" },
-  { SILC_NOTIFY_TYPE_MOTD,             "motd" }
+       { SILC_NOTIFY_TYPE_NONE,                NULL },
+       { SILC_NOTIFY_TYPE_INVITE,              "invite" },
+       { SILC_NOTIFY_TYPE_JOIN,                "join" },
+       { SILC_NOTIFY_TYPE_LEAVE,               "leave" },
+       { SILC_NOTIFY_TYPE_SIGNOFF,             "signoff" },
+       { SILC_NOTIFY_TYPE_TOPIC_SET,           "topic" },
+       { SILC_NOTIFY_TYPE_NICK_CHANGE,         "nick" },
+       { SILC_NOTIFY_TYPE_CMODE_CHANGE,        "cmode" },
+       { SILC_NOTIFY_TYPE_CUMODE_CHANGE,       "cumode" },
+       { SILC_NOTIFY_TYPE_MOTD,                "motd" }
 };
 
 static void silc_notify(SilcClient client, SilcClientConnection conn,
-                       SilcNotifyType type, ...)
+                        SilcNotifyType type, ...)
 {
-  SILC_SERVER_REC *server;
-  va_list va;
-  
-  server = conn == NULL ? NULL : conn->context;
-  va_start(va, type);
-  
-  if (type == SILC_NOTIFY_TYPE_NONE) {
-    /* some generic notice from server */
-    printtext(server, NULL, MSGLEVEL_CRAP, "%s",
-             (char *) va_arg(va, char *));
-  } else if (type < MAX_NOTIFY) {
-    /* send signal about the notify event */
-    char signal[50];
-    
-    g_snprintf(signal, sizeof(signal), "silc event %s",
-              notifies[type].name);
-    signal_emit(signal, 2, server, va);
-  } else {
-    /* unknown notify */
-    printtext(server, NULL, MSGLEVEL_CRAP,
-             "Unknown notify %d", type);
-  }
-  va_end(va);
+       SILC_SERVER_REC *server;
+       va_list va;
+
+       server = conn == NULL ? NULL : conn->context;
+       va_start(va, type);
+
+       if (type == SILC_NOTIFY_TYPE_NONE) {
+               /* some generic notice from server */
+               printtext(server, NULL, MSGLEVEL_CRAP, "%s",
+                         (char *) va_arg(va, char *));
+       } else if (type < MAX_NOTIFY) {
+               /* send signal about the notify event */
+               char signal[50];
+
+               g_snprintf(signal, sizeof(signal), "silc event %s",
+                          notifies[type].name);
+               signal_emit(signal, 2, server, va);
+       } else {
+               /* unknown notify */
+               printtext(server, NULL, MSGLEVEL_CRAP,
+                         "Unknown notify %d", type);
+       }
+       va_end(va);
 }
 
-/* Called to indicate that connection was either successfully established
-   or connecting failed.  This is also the first time application receives
-   the SilcClientConnection objecet which it should save somewhere. */
-
-static void 
-silc_connect(SilcClient client, SilcClientConnection conn, int success)
+static void silc_connect(SilcClient client, SilcClientConnection conn, int success)
 {
-  SILC_SERVER_REC *server = conn->context;
-  
-  if (success) {
-    server->connected = TRUE;
-    signal_emit("event connected", 1, server);
-  } else {
-    server->connection_lost = TRUE;
-    server->conn->context = NULL;
-    server_disconnect(SERVER(server));
-  }
-}
+       SILC_SERVER_REC *server = conn->context;
 
-/* Called to indicate that connection was disconnected to the server. */
+       if (success) {
+               server->connected = TRUE;
+               signal_emit("event connected", 1, server);
+       } else {
+               server->connection_lost = TRUE;
+                server->conn->context = NULL;
+               server_disconnect(SERVER(server));
+       }
+}
 
-static void 
-silc_disconnect(SilcClient client, SilcClientConnection conn)
+static void silc_disconnect(SilcClient client, SilcClientConnection conn)
 {
        SILC_SERVER_REC *server = conn->context;
 
@@ -190,252 +145,135 @@ silc_disconnect(SilcClient client, SilcClientConnection conn)
        server_disconnect(SERVER(server));
 }
 
-/* Command handler. This function is called always in the command function.
-   If error occurs it will be called as well. `conn' is the associated
-   client connection. `cmd_context' is the command context that was
-   originally sent to the command. `success' is FALSE if error occured
-   during command. `command' is the command being processed. It must be
-   noted that this is not reply from server. This is merely called just
-   after application has called the command. Just to tell application
-   that the command really was processed. */
-
-static void 
-silc_command(SilcClient client, SilcClientConnection conn, 
-            SilcClientCommandContext cmd_context, int success,
-            SilcCommand command)
+static void silc_command(SilcClient client, SilcClientConnection conn,
+                        SilcClientCommandContext cmd_context, int success,
+                        SilcCommand command)
 {
 }
 
-/* Command reply handler. This function is called always in the command reply
-   function. If error occurs it will be called as well. Normal scenario
-   is that it will be called after the received command data has been parsed
-   and processed. The function is used to pass the received command data to
-   the application. 
-
-   `conn' is the associated client connection. `cmd_payload' is the command
-   payload data received from server and it can be ignored. It is provided
-   if the application would like to re-parse the received command data,
-   however, it must be noted that the data is parsed already by the library
-   thus the payload can be ignored. `success' is FALSE if error occured.
-   In this case arguments are not sent to the application. `command' is the
-   command reply being processed. The function has variable argument list
-   and each command defines the number and type of arguments it passes to the
-   application (on error they are not sent). */
-
-static void 
-silc_command_reply(SilcClient client, SilcClientConnection conn,
-                  SilcCommandPayload cmd_payload, int success,
-                  SilcCommand command, SilcCommandStatus status, ...)
-
+static void silc_command_reply(SilcClient client, SilcClientConnection conn,
+                              SilcCommandPayload cmd_payload, int success,
+                              SilcCommand command,
+                              SilcCommandStatus status, ...)
 {
-  SILC_SERVER_REC *server = conn->context;
-  SILC_CHANNEL_REC *chanrec;
-  va_list va;
-
-  va_start(va, status);
-
-  /*g_snprintf(signal, sizeof(signal), "silc command reply %s",
-    silc_commands[type]);
-    signal_emit(signal, 2, server, va);*/
-
-  switch(command) {
-  case SILC_COMMAND_JOIN: 
-    {
-      char *channel, *mode;
-      uint32 modei;
-      SilcChannelEntry channel_entry;
-      
-      channel = va_arg(va, char *);
-      channel_entry = va_arg(va, SilcChannelEntry);
-      modei = va_arg(va, uint32);
-      mode = silc_client_chmode(modei, channel_entry);
-      
-      chanrec = silc_channel_find(server, channel);
-      if (chanrec != NULL && !success)
-       channel_destroy(CHANNEL(chanrec));
-      else if (chanrec == NULL && success)
-       chanrec = silc_channel_create(server, channel, TRUE);
-      
-      g_free_not_null(chanrec->mode);
-      chanrec->mode = g_strdup(mode == NULL ? "" : mode);
-      signal_emit("channel mode changed", 1, chanrec);
-      break;
-    }
-  case SILC_COMMAND_NICK: 
-    {
-      SilcClientEntry client = va_arg(va, SilcClientEntry);
-      char *old;
-      
-      old = g_strdup(server->nick);
-      server_change_nick(SERVER(server), client->nickname);
-      nicklist_rename_unique(SERVER(server),
-                            server->conn->local_entry, server->nick,
-                            client, client->nickname);
-      
-      signal_emit("message own_nick", 4,
-                 server, server->nick, old, "");
-      g_free(old);
-      break;
-    }
-  case SILC_COMMAND_USERS: 
-    {
-      SilcChannelEntry channel;
-      SilcChannelUser user;
-      NICK_REC *ownnick;
-      
-      channel = va_arg(va, SilcChannelEntry);
-      chanrec = silc_channel_find_entry(server, channel);
-      if (chanrec == NULL)
-       break;
-      
-      silc_list_start(channel->clients);
-      while ((user = silc_list_get(channel->clients)) != NULL)
-       silc_nicklist_insert(chanrec, user, FALSE);
-      
-      ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
-      nicklist_set_own(CHANNEL(chanrec), ownnick);
-      signal_emit("channel joined", 1, chanrec);
-      fe_channels_nicklist(CHANNEL(chanrec),
-                          CHANNEL_NICKLIST_FLAG_ALL);
-      break;
-    }
-  }
-  
-  va_end(va);
-}
+        SILC_SERVER_REC *server = conn->context;
+       SILC_CHANNEL_REC *chanrec;
+       va_list va;
+
+       va_start(va, status);
+
+       /*g_snprintf(signal, sizeof(signal), "silc command reply %s",
+                  silc_commands[type]);
+       signal_emit(signal, 2, server, va);*/
+
+       switch(command) {
+       case SILC_COMMAND_JOIN: {
+               char *channel, *mode;
+
+               channel = va_arg(va, char *);
+               (void)va_arg(va, SilcChannelEntry);
+               mode = silc_client_chmode(va_arg(va, unsigned int));
+
+               chanrec = silc_channel_find(server, channel);
+               if (chanrec != NULL && !success)
+                       channel_destroy(CHANNEL(chanrec));
+               else if (chanrec == NULL && success)
+                       chanrec = silc_channel_create(server, channel, TRUE);
+
+               g_free_not_null(chanrec->mode);
+               chanrec->mode = g_strdup(mode == NULL ? "" : mode);
+               signal_emit("channel mode changed", 1, chanrec);
+               break;
+       }
+       case SILC_COMMAND_NICK: {
+               SilcClientEntry client = va_arg(va, SilcClientEntry);
+                char *old;
+
+                old = g_strdup(server->nick);
+               server_change_nick(SERVER(server), client->nickname);
+               nicklist_rename_unique(SERVER(server),
+                                      server->conn->local_entry, server->nick,
+                                       client, client->nickname);
+
+               signal_emit("message own_nick", 4,
+                           server, server->nick, old, "");
+                g_free(old);
+               break;
+       }
+       case SILC_COMMAND_USERS: {
+               SilcChannelEntry channel;
+               SilcChannelUser user;
+                NICK_REC *ownnick;
+
+               channel = va_arg(va, SilcChannelEntry);
+               chanrec = silc_channel_find_entry(server, channel);
+               if (chanrec == NULL)
+                       break;
+
+               silc_list_start(channel->clients);
+               while ((user = silc_list_get(channel->clients)) != NULL)
+                       silc_nicklist_insert(chanrec, user, FALSE);
+
+                ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
+               nicklist_set_own(CHANNEL(chanrec), ownnick);
+                signal_emit("channel joined", 1, chanrec);
+               fe_channels_nicklist(CHANNEL(chanrec),
+                                    CHANNEL_NICKLIST_FLAG_ALL);
+               break;
+       }
+       }
 
-/* Verifies received public key. If user decides to trust the key it is
-   saved as public server key for later use. If user does not trust the
-   key this returns FALSE. */
+       va_end(va);
+}
 
-static int silc_verify_public_key(SilcClient client,
-                                 SilcClientConnection conn, 
-                                 SilcSocketType conn_type,
-                                 unsigned char *pk, uint32 pk_len,
+static int silc_verify_server_key(SilcClient client, SilcClientConnection conn,
+                                 unsigned char *pk, unsigned int pk_len,
                                  SilcSKEPKType pk_type)
 {
-  return TRUE;
+       return TRUE;
 }
 
-/* Asks passphrase from user on the input line. */
-
 static unsigned char *silc_ask_passphrase(SilcClient client,
                                          SilcClientConnection conn)
 {
        return NULL;
 }
 
-/* Find authentication method and authentication data by hostname and
-   port. The hostname may be IP address as well. The found authentication
-   method and authentication data is returned to `auth_meth', `auth_data'
-   and `auth_data_len'. The function returns TRUE if authentication method
-   is found and FALSE if not. `conn' may be NULL. */
-
-static int 
-silc_get_auth_method(SilcClient client, SilcClientConnection conn,
-                    char *hostname, uint16 port,
-                    SilcProtocolAuthMeth *auth_meth,
-                    unsigned char **auth_data,
-                    uint32 *auth_data_len)
+static int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+                               char *hostname, unsigned short port,
+                               SilcProtocolAuthMeth *auth_meth,
+                               unsigned char **auth_data,
+                               unsigned int *auth_data_len)
 {
-  return FALSE;
+       return FALSE;
 }
 
-/* Notifies application that failure packet was received.  This is called
-   if there is some protocol active in the client.  The `protocol' is the
-   protocol context.  The `failure' is opaque pointer to the failure
-   indication.  Note, that the `failure' is protocol dependant and application
-   must explicitly cast it to correct type.  Usually `failure' is 32 bit
-   failure type (see protocol specs for all protocol failure types). */
-
-static void 
-silc_failure(SilcClient client, SilcClientConnection conn, 
-            SilcProtocol protocol, void *failure)
+static void silc_failure(SilcClient client, SilcClientConnection conn,
+                        SilcProtocol protocol, void *failure)
 {
-  if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
-    SilcSKEStatus status = (SilcSKEStatus)failure;
-    
-    if (status == SILC_SKE_STATUS_BAD_VERSION)
-      silc_say(client, conn, 
-              "You are running incompatible client version (it may be "
-              "too old or too new)");
-    if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
-      silc_say(client, conn, "Server does not support your public key type");
-    if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
-      silc_say(client, conn, 
-              "Server does not support one of your proposed KE group");
-    if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
-      silc_say(client, conn, 
-              "Server does not support one of your proposed cipher");
-    if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
-      silc_say(client, conn, 
-              "Server does not support one of your proposed PKCS");
-    if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
-      silc_say(client, conn, 
-              "Server does not support one of your proposed hash function");
-    if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
-      silc_say(client, conn, 
-              "Server does not support one of your proposed HMAC");
-    if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
-      silc_say(client, conn, "Incorrect signature");
-  }
-
-  if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
-    uint32 err = (uint32)failure;
-
-    if (err == SILC_AUTH_FAILED)
-      silc_say(client, conn, "Authentication failed");
-  }
 }
 
-/* Asks whether the user would like to perform the key agreement protocol.
-   This is called after we have received an key agreement packet or an
-   reply to our key agreement packet. This returns TRUE if the user wants
-   the library to perform the key agreement protocol and FALSE if it is not
-   desired (application may start it later by calling the function
-   silc_client_perform_key_agreement). */
-
-static int 
-silc_key_agreement(SilcClient client, SilcClientConnection conn,
-                  SilcClientEntry client_entry, char *hostname,
-                  int port,
-                  SilcKeyAgreementCallback *completion,
-                  void **context)
+static int key_agreement(SilcClient client, SilcClientConnection conn,
+                        SilcClientEntry client_entry, char *hostname,
+                        int port)
 {
-  char host[256];
-
-  /* We will just display the info on the screen and return FALSE and user
-     will have to start the key agreement with a command. */
-
-  if (hostname) {
-    memset(host, 0, sizeof(host));
-    snprintf(host, sizeof(host) - 1, "(%s on port %d)", hostname, port); 
-  }
-
-  silc_say(client, conn, "%s wants to perform key agreement %s",
-          client_entry->nickname, hostname ? host : "");
-
-  *completion = NULL;
-  *context = NULL;
-
-  return FALSE;
+        return FALSE;
 }
 
-/* SILC client operations */
 SilcClientOperations ops = {
-  silc_say,
-  silc_channel_message,
-  silc_private_message,
-  silc_notify,
-  silc_command,
-  silc_command_reply,
-  silc_connect,
-  silc_disconnect,
-  silc_get_auth_method,
-  silc_verify_public_key,
-  silc_ask_passphrase,
-  silc_failure,
-  silc_key_agreement,
+       silc_say,
+       silc_channel_message,
+       silc_private_message,
+       silc_notify,
+       silc_command,
+       silc_command_reply,
+       silc_connect,
+       silc_disconnect,
+       silc_get_auth_method,
+       silc_verify_server_key,
+       silc_ask_passphrase,
+       silc_failure,
+        key_agreement
 };
 
 /* Loads public and private key from files. */
@@ -448,7 +286,7 @@ static void silc_client_create_key_pair(char *pkcs_name, int bits,
        SilcPKCS pkcs;
        SilcRng rng;
        unsigned char *key;
-       uint32 key_len;
+       unsigned int key_len;
 
        rng = silc_rng_alloc();
        silc_rng_init(rng);
diff --git a/apps/irssi/src/silc/core/silc-core.h b/apps/irssi/src/silc/core/silc-core.h
new file mode 100644 (file)
index 0000000..968e15c
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __SILC_CORE_H
+#define __SILC_CORE_H
+
+extern SilcClient silc_client;
+
+#define IS_SILC_ITEM(rec) (IS_SILC_CHANNEL(rec) || IS_SILC_QUERY(rec))
+
+#endif
diff --git a/apps/irssi/src/silc/core/silc-nicklist.c b/apps/irssi/src/silc/core/silc-nicklist.c
new file mode 100644 (file)
index 0000000..bef2521
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ silc-nicklist.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 "misc.h"
+#include "servers.h"
+
+#include "silc-channels.h"
+#include "silc-nicklist.h"
+
+SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel,
+                                   SilcChannelUser user, int send_massjoin)
+{
+       SILC_NICK_REC *rec;
+
+       g_return_val_if_fail(IS_SILC_CHANNEL(channel), NULL);
+       g_return_val_if_fail(user != NULL, NULL);
+
+       rec = g_new0(SILC_NICK_REC, 1);
+       rec->nick = g_strdup(user->client->nickname);
+        rec->host = g_strdup(user->client->username);
+       rec->silc_user = user;
+       rec->unique_id = user->client;
+
+       if (user->mode & SILC_CHANNEL_UMODE_CHANOP) rec->op = TRUE;
+       if (user->mode & SILC_CHANNEL_UMODE_CHANFO) rec->founder = TRUE;
+       rec->send_massjoin = send_massjoin;
+
+       nicklist_insert(CHANNEL(channel), (NICK_REC *) rec);
+       return rec;
+}
+
+SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel,
+                                 SilcClientEntry client)
+{
+       return (SILC_NICK_REC *)
+               nicklist_find_unique(CHANNEL(channel),
+                                    client->nickname, client);
+}
+
+#define isnickchar(a) \
+    (isalnum((int) (a)) || (a) == '`' || (a) == '-' || (a) == '_' || \
+    (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \
+    (a) == '|' || (a) == '\\' || (a) == '^')
+
+/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */
+char *silc_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((int) *nick))
+                       *spos++ = *nick;
+               nick++;
+       }
+       if ((unsigned char) *nick >= 128)
+               *spos++ = *nick; /* just add it so that nicks won't match.. */
+       *spos = '\0';
+       return stripped;
+}
+
+/* Check is `msg' is meant for `nick'. */
+int silc_nick_match(const char *nick, const char *msg)
+{
+       char *stripnick, *stripmsg;
+       int ret, len;
+
+       g_return_val_if_fail(nick != NULL, FALSE);
+       g_return_val_if_fail(msg != NULL, FALSE);
+
+       len = strlen(nick);
+       if (g_strncasecmp(msg, nick, len) == 0 && !isalnum((int) msg[len]))
+               return TRUE;
+
+       stripnick = silc_nick_strip(nick);
+       stripmsg = silc_nick_strip(msg);
+
+       len = strlen(stripnick);
+       ret = len > 0 && g_strncasecmp(stripmsg, stripnick, len) == 0 &&
+               !isalnum((int) stripmsg[len]) &&
+               (unsigned char) stripmsg[len] < 128;
+
+       g_free(stripnick);
+       g_free(stripmsg);
+       return ret;
+}
+
+static const char *get_nick_flags(void)
+{
+       static char flags[3] = { '@', '+', '\0' };
+       return flags;
+}
+
+static void sig_connected(SILC_SERVER_REC *server)
+{
+       if (IS_SILC_SERVER(server))
+               server->get_nick_flags = (void *) get_nick_flags;
+}
+
+void silc_nicklist_init(void)
+{
+       signal_add("server connected", (SIGNAL_FUNC) sig_connected);
+}
+
+void silc_nicklist_deinit(void)
+{
+       signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+}
diff --git a/apps/irssi/src/silc/core/silc-nicklist.h b/apps/irssi/src/silc/core/silc-nicklist.h
new file mode 100644 (file)
index 0000000..8d690a2
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef __SILC_NICKLIST_H
+#define __SILC_NICKLIST_H
+
+#include "nicklist.h"
+
+typedef struct {
+#include "nick-rec.h"
+       SilcChannelUser silc_user;
+
+       unsigned int founder:1;
+} SILC_NICK_REC;
+
+SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel,
+                                   SilcChannelUser user, int send_massjoin);
+
+SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel,
+                                  SilcClientEntry client);
+
+/* Check if `msg' is meant for `nick'. */
+int silc_nick_match(const char *nick, const char *msg);
+
+void silc_nicklist_init(void);
+void silc_nicklist_deinit(void);
+
+#endif
diff --git a/apps/irssi/src/silc/core/silc-queries.c b/apps/irssi/src/silc/core/silc-queries.c
new file mode 100644 (file)
index 0000000..0d3bbe0
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ silc-queries.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 "misc.h"
+
+#include "silc-queries.h"
+
+QUERY_REC *silc_query_create(SILC_SERVER_REC *server,
+                           const char *nick, int automatic)
+{
+       QUERY_REC *rec;
+
+       g_return_val_if_fail(server == NULL || IS_SILC_SERVER(server), NULL);
+       g_return_val_if_fail(nick != NULL, NULL);
+
+       rec = g_new0(QUERY_REC, 1);
+       rec->chat_type = SILC_PROTOCOL;
+       rec->name = g_strdup(nick);
+       rec->server = (SERVER_REC *) server;
+       query_init(rec, automatic);
+       return rec;
+}
+
+void silc_queries_init(void)
+{
+}
+
+void silc_queries_deinit(void)
+{
+}
diff --git a/apps/irssi/src/silc/core/silc-queries.h b/apps/irssi/src/silc/core/silc-queries.h
new file mode 100644 (file)
index 0000000..bd024f6
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef __SILC_QUERIES_H
+#define __SILC_QUERIES_H
+
+#include "chat-protocols.h"
+#include "queries.h"
+#include "silc-servers.h"
+
+/* Returns SILC_QUERY_REC if it's SILC query, NULL if it isn't. */
+#define SILC_QUERY(query) \
+       PROTO_CHECK_CAST(QUERY(query), QUERY_REC, chat_type, "SILC")
+
+#define IS_SILC_QUERY(query) \
+       (SILC_QUERY(query) ? TRUE : FALSE)
+
+void silc_queries_init(void);
+void silc_queries_deinit(void);
+
+#define silc_query_find(server, name) \
+       query_find(SERVER(server), name)
+
+QUERY_REC *silc_query_create(SILC_SERVER_REC *server,
+                           const char *nick, int automatic);
+
+#endif
diff --git a/apps/irssi/src/silc/core/silc-servers-reconnect.c b/apps/irssi/src/silc/core/silc-servers-reconnect.c
new file mode 100644 (file)
index 0000000..79a7b69
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ silc-servers-reconnect.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 "silc-servers.h"
+
+static void sig_server_reconnect_save_status(SILC_SERVER_CONNECT_REC *conn,
+                                            SILC_SERVER_REC *server)
+{
+       if (!IS_SILC_SERVER_CONNECT(conn) || !IS_SILC_SERVER(server))
+               return;
+
+       g_free_not_null(conn->channels);
+       conn->channels = silc_server_get_channels(server);
+}
+
+static void sig_server_connect_copy(SERVER_CONNECT_REC **dest,
+                                   SILC_SERVER_CONNECT_REC *src)
+{
+       SILC_SERVER_CONNECT_REC *rec;
+
+       g_return_if_fail(dest != NULL);
+       if (!IS_SILC_SERVER_CONNECT(src))
+               return;
+
+       rec = g_new0(SILC_SERVER_CONNECT_REC, 1);
+       rec->chat_type = SILC_PROTOCOL;
+       *dest = (SERVER_CONNECT_REC *) rec;
+}
+
+void silc_servers_reconnect_init(void)
+{
+       signal_add("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status);
+       signal_add("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy);
+}
+
+void silc_servers_reconnect_deinit(void)
+{
+       signal_remove("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status);
+       signal_remove("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy);
+}
index b2abf157471dbc1ec8e267aa3f826c6080a52e7e..21d562147f662aab3bfc5eea7f50c8f5beebc66b 100644 (file)
@@ -46,14 +46,14 @@ void silc_servers_reconnect_deinit(void);
 static void silc_send_channel(SILC_SERVER_REC *server,
                              char *channel, char *msg)
 {
-  SILC_CHANNEL_REC *rec;
-  
-  rec = silc_channel_find(server, channel);
-  if (rec == NULL)
-    return;
-  
-  silc_client_send_channel_message(silc_client, server->conn, rec->entry, 
-                                  NULL, 0, msg, strlen(msg), TRUE);
+        SILC_CHANNEL_REC *rec;
+
+       rec = silc_channel_find(server, channel);
+       if (rec == NULL)
+               return;
+
+       silc_client_send_channel_message(silc_client, server->conn,
+                                         rec->entry, msg, strlen(msg), TRUE);
 }
 
 typedef struct {
@@ -64,38 +64,38 @@ typedef struct {
 static void silc_send_msg_clients(SilcClient client,
                                  SilcClientConnection conn,
                                  SilcClientEntry *clients,
-                                 uint32 clients_count,
+                                 unsigned int clients_count,
                                  void *context)
 {
-  PRIVMSG_REC *rec = context;
-  SilcClientEntry target;
-  
-  if (clients_count == 0) {
-    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
-             "Unknown nick: %s", rec->nick);
-  } else {
-    target = clients[0]; /* FIXME: not a good idea :) */
-    
-    silc_client_send_private_message(client, conn, target, 0,
-                                    rec->msg, strlen(rec->msg),
-                                    TRUE);
-  }
-  
-  g_free(rec->nick);
-  g_free(rec->msg);
-  g_free(rec);
+       PRIVMSG_REC *rec = context;
+       SilcClientEntry target;
+
+       if (clients_count == 0) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                         "Unknown nick: %s", rec->nick);
+       } else {
+               target = clients[0]; /* FIXME: not a good idea :) */
+
+               silc_client_send_private_message(client, conn, target,
+                                                rec->msg, strlen(rec->msg),
+                                                TRUE);
+       }
+
+        g_free(rec->nick);
+        g_free(rec->msg);
+        g_free(rec);
 }
 
 static void silc_send_msg(SILC_SERVER_REC *server, char *nick, char *msg)
 {
-  PRIVMSG_REC *rec;
-  
-  rec = g_new0(PRIVMSG_REC, 1);
-  rec->nick = g_strdup(nick);
-  rec->msg = g_strdup(msg);
-  
-  silc_client_get_clients(silc_client, server->conn,
-                         nick, "", silc_send_msg_clients, rec);
+       PRIVMSG_REC *rec;
+
+       rec = g_new0(PRIVMSG_REC, 1);
+       rec->nick = g_strdup(nick);
+       rec->msg = g_strdup(msg);
+
+       silc_client_get_clients(silc_client, server->conn,
+                               nick, "", silc_send_msg_clients, rec);
 }
 
 static int isnickflag_func(char flag)
@@ -155,17 +155,17 @@ static void sig_connected(SILC_SERVER_REC *server)
 
 static void sig_disconnected(SILC_SERVER_REC *server)
 {
-  if (!IS_SILC_SERVER(server) || server->conn == NULL)
-    return;
-  
-  if (server->conn->sock != NULL) {
-    silc_client_close_connection(silc_client, NULL, server->conn);
-    
-    /* SILC closes the handle */
-    g_io_channel_unref(net_sendbuffer_handle(server->handle));
-    net_sendbuffer_destroy(server->handle, FALSE);
-    server->handle = NULL;
-  }
+       if (!IS_SILC_SERVER(server) || server->conn == NULL)
+               return;
+
+       if (server->conn->sock != NULL) {
+               silc_client_close_connection(silc_client, server->conn);
+
+                /* SILC closes the handle */
+               g_io_channel_unref(net_sendbuffer_handle(server->handle));
+               net_sendbuffer_destroy(server->handle, FALSE);
+                server->handle = NULL;
+       }
 }
 
 SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn)
@@ -219,9 +219,9 @@ char *silc_server_get_channels(SILC_SERVER_REC *server)
 void silc_command_exec(SILC_SERVER_REC *server,
                       const char *command, const char *args)
 {
-       uint32 argc = 0;
+       unsigned int argc = 0;
        unsigned char **argv;
-       uint32 *argv_lens, *argv_types;
+       unsigned int *argv_lens, *argv_types;
        char *data, *tmpcmd;
        SilcClientCommand *cmd;
        SilcClientCommandContext ctx;
diff --git a/apps/irssi/src/silc/core/silc-servers.h b/apps/irssi/src/silc/core/silc-servers.h
new file mode 100644 (file)
index 0000000..8ce42c3
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef __SILC_SERVER_H
+#define __SILC_SERVER_H
+
+#include "chat-protocols.h"
+#include "servers.h"
+
+/* returns SILC_SERVER_REC if it's SILC server, NULL if it isn't */
+#define SILC_SERVER(server) \
+       PROTO_CHECK_CAST(SERVER(server), SILC_SERVER_REC, chat_type, "SILC")
+
+#define SILC_SERVER_CONNECT(conn) \
+       PROTO_CHECK_CAST(SERVER_CONNECT(conn), SILC_SERVER_CONNECT_REC, \
+                        chat_type, "SILC")
+
+#define IS_SILC_SERVER(server) \
+       (SILC_SERVER(server) ? TRUE : FALSE)
+
+#define IS_SILC_SERVER_CONNECT(conn) \
+       (SILC_SERVER_CONNECT(conn) ? TRUE : FALSE)
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+typedef struct {
+#include "server-connect-rec.h"
+} SILC_SERVER_CONNECT_REC;
+
+#define STRUCT_SERVER_CONNECT_REC SILC_SERVER_CONNECT_REC
+typedef struct {
+#include "server-rec.h"
+       /* 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 */
+
+       GSList *idles; /* Idle queue - send these commands to server
+                         if there's nothing else to do */
+
+       gpointer chanqueries;
+       SilcClientConnection conn;
+} SILC_SERVER_REC;
+
+SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn);
+
+/* Return a string of all channels in server in
+   server->channels_join() format */
+char *silc_server_get_channels(SILC_SERVER_REC *server);
+
+void silc_command_exec(SILC_SERVER_REC *server,
+                      const char *command, const char *args);
+
+void silc_server_init(void);
+void silc_server_deinit(void);
+
+#endif