summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am14
-rw-r--r--src/common/Makefile.am61
-rw-r--r--src/common/cfgfiles.c1105
-rw-r--r--src/common/cfgfiles.h55
-rw-r--r--src/common/chanopt.c431
-rw-r--r--src/common/chanopt.h6
-rw-r--r--src/common/ctcp.c191
-rw-r--r--src/common/ctcp.h6
-rw-r--r--src/common/dbus/Makefile.am56
-rw-r--r--src/common/dbus/README198
-rw-r--r--src/common/dbus/apps_xchat_url_handler.schemas37
-rw-r--r--src/common/dbus/dbus-client.c118
-rw-r--r--src/common/dbus/dbus-client.h27
-rw-r--r--src/common/dbus/dbus-plugin.c1087
-rw-r--r--src/common/dbus/dbus-plugin.h31
-rw-r--r--src/common/dbus/example.c201
-rw-r--r--src/common/dbus/example.py28
-rw-r--r--src/common/dbus/marshallers.list1
-rw-r--r--src/common/dbus/org.xchat.service.service.in3
-rw-r--r--src/common/dbus/remote-object.xml142
-rw-r--r--src/common/dcc.c2587
-rw-r--r--src/common/dcc.h117
-rw-r--r--src/common/fe.h162
-rw-r--r--src/common/history.c121
-rw-r--r--src/common/history.h18
-rw-r--r--src/common/identd.c89
-rw-r--r--src/common/ignore.c424
-rw-r--r--src/common/ignore.h38
-rw-r--r--src/common/inbound.c1336
-rw-r--r--src/common/inbound.h39
-rw-r--r--src/common/inet.h43
-rw-r--r--src/common/make-te.c58
-rw-r--r--src/common/modes.c836
-rw-r--r--src/common/modes.h12
-rw-r--r--src/common/msproxy.c467
-rw-r--r--src/common/msproxy.h257
-rw-r--r--src/common/network.c383
-rw-r--r--src/common/network.h34
-rw-r--r--src/common/notify.c634
-rw-r--r--src/common/notify.h44
-rw-r--r--src/common/outbound.c4403
-rw-r--r--src/common/outbound.h20
-rw-r--r--src/common/plugin-timer.c208
-rw-r--r--src/common/plugin-timer.h7
-rw-r--r--src/common/plugin.c1569
-rw-r--r--src/common/plugin.h132
-rw-r--r--src/common/proto-irc.c1260
-rw-r--r--src/common/proto-irc.h6
-rw-r--r--src/common/server.c2047
-rw-r--r--src/common/server.h26
-rw-r--r--src/common/servlist.c1311
-rw-r--r--src/common/servlist.h62
-rw-r--r--src/common/ssl.c323
-rw-r--r--src/common/ssl.h65
-rw-r--r--src/common/text.c2309
-rw-r--r--src/common/text.h42
-rw-r--r--src/common/textenums.h76
-rw-r--r--src/common/textevents.h424
-rw-r--r--src/common/textevents.in840
-rw-r--r--src/common/tree.c215
-rw-r--r--src/common/tree.h16
-rw-r--r--src/common/url.c280
-rw-r--r--src/common/url.h19
-rw-r--r--src/common/userlist.c454
-rw-r--r--src/common/userlist.h41
-rw-r--r--src/common/util.c1729
-rw-r--r--src/common/util.h54
-rw-r--r--src/common/xchat-plugin.h334
-rw-r--r--src/common/xchat.c951
-rw-r--r--src/common/xchat.h575
-rw-r--r--src/common/xchatc.h39
-rw-r--r--src/fe-gtk/Makefile.am32
-rw-r--r--src/fe-gtk/about.c161
-rw-r--r--src/fe-gtk/about.h1
-rw-r--r--src/fe-gtk/ascii.c177
-rw-r--r--src/fe-gtk/ascii.h1
-rw-r--r--src/fe-gtk/banlist.c418
-rw-r--r--src/fe-gtk/banlist.h1
-rw-r--r--src/fe-gtk/chanlist.c943
-rw-r--r--src/fe-gtk/chanlist.h1
-rw-r--r--src/fe-gtk/chanview-tabs.c779
-rw-r--r--src/fe-gtk/chanview-tree.c364
-rw-r--r--src/fe-gtk/chanview.c643
-rw-r--r--src/fe-gtk/chanview.h31
-rw-r--r--src/fe-gtk/custom-list.c753
-rw-r--r--src/fe-gtk/custom-list.h85
-rw-r--r--src/fe-gtk/dccgui.c1098
-rw-r--r--src/fe-gtk/editlist.c409
-rw-r--r--src/fe-gtk/editlist.h1
-rw-r--r--src/fe-gtk/fe-gtk.c1063
-rw-r--r--src/fe-gtk/fe-gtk.h197
-rw-r--r--src/fe-gtk/fkeys.c1814
-rw-r--r--src/fe-gtk/fkeys.h5
-rw-r--r--src/fe-gtk/gtkutil.c675
-rw-r--r--src/fe-gtk/gtkutil.h39
-rw-r--r--src/fe-gtk/ignoregui.c449
-rw-r--r--src/fe-gtk/joind.c257
-rw-r--r--src/fe-gtk/joind.h2
-rw-r--r--src/fe-gtk/maingui.c3811
-rw-r--r--src/fe-gtk/maingui.h33
-rw-r--r--src/fe-gtk/menu.c2270
-rw-r--r--src/fe-gtk/menu.h41
-rw-r--r--src/fe-gtk/mmx_cmod.S530
-rw-r--r--src/fe-gtk/mmx_cmod.h4
-rw-r--r--src/fe-gtk/notifygui.c442
-rw-r--r--src/fe-gtk/notifygui.h2
-rw-r--r--src/fe-gtk/palette.c226
-rw-r--r--src/fe-gtk/palette.h15
-rw-r--r--src/fe-gtk/pixmaps.c123
-rw-r--r--src/fe-gtk/pixmaps.h19
-rw-r--r--src/fe-gtk/plugin-tray.c852
-rw-r--r--src/fe-gtk/plugin-tray.h4
-rw-r--r--src/fe-gtk/plugingui.c239
-rw-r--r--src/fe-gtk/plugingui.h2
-rw-r--r--src/fe-gtk/rawlog.c152
-rw-r--r--src/fe-gtk/rawlog.h1
-rw-r--r--src/fe-gtk/search.c159
-rw-r--r--src/fe-gtk/search.h1
-rw-r--r--src/fe-gtk/servlistgui.c1918
-rw-r--r--src/fe-gtk/setup.c2137
-rw-r--r--src/fe-gtk/sexy-spell-entry.c1329
-rw-r--r--src/fe-gtk/sexy-spell-entry.h87
-rw-r--r--src/fe-gtk/textgui.c456
-rw-r--r--src/fe-gtk/textgui.h2
-rw-r--r--src/fe-gtk/urlgrab.c208
-rw-r--r--src/fe-gtk/urlgrab.h2
-rw-r--r--src/fe-gtk/userlistgui.c718
-rw-r--r--src/fe-gtk/userlistgui.h8
-rw-r--r--src/fe-gtk/xtext.c5478
-rw-r--r--src/fe-gtk/xtext.h275
-rw-r--r--src/fe-text/Makefile.am9
-rw-r--r--src/fe-text/README5
-rw-r--r--src/fe-text/fe-text.c862
-rw-r--r--src/fe-text/fe-text.h29
-rw-r--r--src/pixmaps/Makefile.am20
-rw-r--r--src/pixmaps/book.pngbin0 -> 841 bytes
-rw-r--r--src/pixmaps/fileoffer.pngbin0 -> 5637 bytes
-rw-r--r--src/pixmaps/highlight.pngbin0 -> 5544 bytes
-rw-r--r--src/pixmaps/hop.pngbin0 -> 262 bytes
-rw-r--r--src/pixmaps/message.pngbin0 -> 5152 bytes
-rw-r--r--src/pixmaps/op.pngbin0 -> 246 bytes
-rw-r--r--src/pixmaps/purple.pngbin0 -> 240 bytes
-rw-r--r--src/pixmaps/red.pngbin0 -> 212 bytes
-rw-r--r--src/pixmaps/voice.pngbin0 -> 267 bytes
-rw-r--r--src/version-script34
145 files changed, 64236 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..78856692
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,14 @@
+## Process this file with automake to produce Makefile.in
+
+EXTRA_DIST = fe-text/fe-text.c \
+	fe-text/README fe-text/fe-text.h version-script
+
+if DO_TEXT
+text_fe = fe-text
+endif
+
+if DO_GTK
+gtk_fe = fe-gtk
+endif
+
+SUBDIRS = pixmaps common $(gtk_fe) $(text_fe)
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
new file mode 100644
index 00000000..6a35da91
--- /dev/null
+++ b/src/common/Makefile.am
@@ -0,0 +1,61 @@
+## Process this file with automake to produce Makefile.in
+
+noinst_LIBRARIES = libxchatcommon.a
+
+INCLUDES = $(COMMON_CFLAGS)
+
+EXTRA_DIST = \
+	cfgfiles.h \
+	chanopt.h \
+	ctcp.h \
+	dcc.h \
+	fe.h \
+	history.h \
+	identd.c \
+	ignore.h \
+	inbound.h \
+	inet.h \
+	make-te.c \
+	modes.h \
+	msproxy.h \
+	network.h \
+	notify.h \
+	outbound.h \
+	plugin.h \
+	plugin-timer.h \
+	proto-irc.h \
+	server.h \
+	servlist.h \
+	ssl.h \
+	ssl.c	\
+	text.h \
+	textenums.h \
+	textevents.h \
+	textevents.in \
+	tree.h \
+	url.h \
+	userlist.h \
+	util.h \
+	xchat.h \
+	xchatc.h \
+	xchat-plugin.h
+
+if USE_OPENSSL
+ssl_c = ssl.c
+endif
+
+if USE_DBUS
+dbusdir = dbus
+libxchatcommon_a_LIBADD =				\
+	$(top_builddir)/src/common/dbus/dbus-*.$(OBJEXT)
+endif
+SUBDIRS = $(dbusdir) .
+
+libxchatcommon_a_SOURCES = cfgfiles.c chanopt.c ctcp.c dcc.c history.c ignore.c \
+	inbound.c modes.c msproxy.c network.c notify.c outbound.c \
+	plugin.c plugin-timer.c proto-irc.c server.c servlist.c $(ssl_c) \
+	text.c tree.c url.c userlist.c util.c xchat.c
+libxchatcommon_a_CFLAGS = $(LIBPROXY_CFLAGS)
+
+textevents: make-te
+	./make-te < textevents.in > textevents.h 2> textenums.h
diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c
new file mode 100644
index 00000000..8bb2a61b
--- /dev/null
+++ b/src/common/cfgfiles.c
@@ -0,0 +1,1105 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "util.h"
+#include "fe.h"
+#include "text.h"
+#include "xchatc.h"
+
+#ifdef WIN32
+#define XCHAT_DIR "X-Chat 2"
+#else
+#define XCHAT_DIR ".xchat2"
+#endif
+#define DEF_FONT "Monospace 9"
+
+void
+list_addentry (GSList ** list, char *cmd, char *name)
+{
+	struct popup *pop;
+	int cmd_len = 1, name_len;
+
+	/* remove <2.8.0 stuff */
+	if (!strcmp (cmd, "away") && !strcmp (name, "BACK"))
+		return;
+
+	if (cmd)
+		cmd_len = strlen (cmd) + 1;
+	name_len = strlen (name) + 1;
+
+	pop = malloc (sizeof (struct popup) + cmd_len + name_len);
+	pop->name = (char *) pop + sizeof (struct popup);
+	pop->cmd = pop->name + name_len;
+
+	memcpy (pop->name, name, name_len);
+	if (cmd)
+		memcpy (pop->cmd, cmd, cmd_len);
+	else
+		pop->cmd[0] = 0;
+
+	*list = g_slist_append (*list, pop);
+}
+
+/* read it in from a buffer to our linked list */
+
+static void
+list_load_from_data (GSList ** list, char *ibuf, int size)
+{
+	char cmd[384];
+	char name[128];
+	char *buf;
+	int pnt = 0;
+
+	cmd[0] = 0;
+	name[0] = 0;
+
+	while (buf_get_line (ibuf, &buf, &pnt, size))
+	{
+		if (*buf != '#')
+		{
+			if (!strncasecmp (buf, "NAME ", 5))
+			{
+				safe_strcpy (name, buf + 5, sizeof (name));
+			}
+			else if (!strncasecmp (buf, "CMD ", 4))
+			{
+				safe_strcpy (cmd, buf + 4, sizeof (cmd));
+				if (*name)
+				{
+					list_addentry (list, cmd, name);
+					cmd[0] = 0;
+					name[0] = 0;
+				}
+			}
+		}
+	}
+}
+
+void
+list_loadconf (char *file, GSList ** list, char *defaultconf)
+{
+	char filebuf[256];
+	char *ibuf;
+	int fh;
+	struct stat st;
+
+	snprintf (filebuf, sizeof (filebuf), "%s/%s", get_xdir_fs (), file);
+	fh = open (filebuf, O_RDONLY | OFLAGS);
+	if (fh == -1)
+	{
+		if (defaultconf)
+			list_load_from_data (list, defaultconf, strlen (defaultconf));
+		return;
+	}
+	if (fstat (fh, &st) != 0)
+	{
+		perror ("fstat");
+		abort ();
+	}
+
+	ibuf = malloc (st.st_size);
+	read (fh, ibuf, st.st_size);
+	close (fh);
+
+	list_load_from_data (list, ibuf, st.st_size);
+
+	free (ibuf);
+}
+
+void
+list_free (GSList ** list)
+{
+	void *data;
+	while (*list)
+	{
+		data = (void *) (*list)->data;
+		free (data);
+		*list = g_slist_remove (*list, data);
+	}
+}
+
+int
+list_delentry (GSList ** list, char *name)
+{
+	struct popup *pop;
+	GSList *alist = *list;
+
+	while (alist)
+	{
+		pop = (struct popup *) alist->data;
+		if (!strcasecmp (name, pop->name))
+		{
+			*list = g_slist_remove (*list, pop);
+			free (pop);
+			return 1;
+		}
+		alist = alist->next;
+	}
+	return 0;
+}
+
+char *
+cfg_get_str (char *cfg, char *var, char *dest, int dest_len)
+{
+	while (1)
+	{
+		if (!strncasecmp (var, cfg, strlen (var)))
+		{
+			char *value, t;
+			cfg += strlen (var);
+			while (*cfg == ' ')
+				cfg++;
+			if (*cfg == '=')
+				cfg++;
+			while (*cfg == ' ')
+				cfg++;
+			/*while (*cfg == ' ' || *cfg == '=')
+			   cfg++; */
+			value = cfg;
+			while (*cfg != 0 && *cfg != '\n')
+				cfg++;
+			t = *cfg;
+			*cfg = 0;
+			safe_strcpy (dest, value, dest_len);
+			*cfg = t;
+			return cfg;
+		}
+		while (*cfg != 0 && *cfg != '\n')
+			cfg++;
+		if (*cfg == 0)
+			return 0;
+		cfg++;
+		if (*cfg == 0)
+			return 0;
+	}
+}
+
+static int
+cfg_put_str (int fh, char *var, char *value)
+{
+	char buf[512];
+	int len;
+
+	snprintf (buf, sizeof buf, "%s = %s\n", var, value);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_put_color (int fh, int r, int g, int b, char *var)
+{
+	char buf[400];
+	int len;
+
+	snprintf (buf, sizeof buf, "%s = %04x %04x %04x\n", var, r, g, b);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_put_int (int fh, int value, char *var)
+{
+	char buf[400];
+	int len;
+
+	if (value == -1)
+		value = 1;
+
+	snprintf (buf, sizeof buf, "%s = %d\n", var, value);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_get_color (char *cfg, char *var, int *r, int *g, int *b)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+		return 0;
+
+	sscanf (str, "%04x %04x %04x", r, g, b);
+	return 1;
+}
+
+int
+cfg_get_int_with_result (char *cfg, char *var, int *result)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+	{
+		*result = 0;
+		return 0;
+	}
+
+	*result = 1;
+	return atoi (str);
+}
+
+int
+cfg_get_int (char *cfg, char *var)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+		return 0;
+
+	return atoi (str);
+}
+
+char *xdir_fs = NULL;	/* file system encoding */
+char *xdir_utf = NULL;	/* utf-8 encoding */
+
+#ifdef WIN32
+
+#include <windows.h>
+
+static gboolean
+get_reg_str (const char *sub, const char *name, char *out, DWORD len)
+{
+	HKEY hKey;
+	DWORD t;
+
+	if (RegOpenKeyEx (HKEY_CURRENT_USER, sub, 0, KEY_READ, &hKey) ==
+			ERROR_SUCCESS)
+	{
+		if (RegQueryValueEx (hKey, name, NULL, &t, out, &len) != ERROR_SUCCESS ||
+			 t != REG_SZ)
+		{
+			RegCloseKey (hKey);
+			return FALSE;
+		}
+		out[len-1] = 0;
+		RegCloseKey (hKey);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+char *
+get_xdir_fs (void)
+{
+	if (!xdir_fs)
+	{
+		char out[256];
+
+		if (!get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\"
+				"Explorer\\Shell Folders", "AppData", out, sizeof (out)))
+			return "./config";
+		xdir_fs = g_strdup_printf ("%s\\" XCHAT_DIR, out);
+	}
+	return xdir_fs;
+}
+
+#else
+
+char *
+get_xdir_fs (void)
+{
+	if (!xdir_fs)
+		xdir_fs = g_strdup_printf ("%s/" XCHAT_DIR, g_get_home_dir ());
+
+	return xdir_fs;
+}
+
+#endif	/* !WIN32 */
+
+char *
+get_xdir_utf8 (void)
+{
+	if (!xdir_utf)	/* never free this, keep it for program life time */
+		xdir_utf = xchat_filename_to_utf8 (get_xdir_fs (), -1, 0, 0, 0);
+
+	return xdir_utf;
+}
+
+static void
+check_prefs_dir (void)
+{
+	char *dir = get_xdir_fs ();
+	if (access (dir, F_OK) != 0)
+	{
+#ifdef WIN32
+		if (mkdir (dir) != 0)
+#else
+		if (mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
+#endif
+			fe_message (_("Cannot create ~/.xchat2"), FE_MSG_ERROR);
+	}
+}
+
+static char *
+default_file (void)
+{
+	static char *dfile = 0;
+
+	if (!dfile)
+	{
+		dfile = malloc (strlen (get_xdir_fs ()) + 12);
+		sprintf (dfile, "%s/xchat.conf", get_xdir_fs ());
+	}
+	return dfile;
+}
+
+/* Keep these sorted!! */
+
+const struct prefs vars[] = {
+	{"auto_save", P_OFFINT (autosave), TYPE_BOOL},
+	{"auto_save_url", P_OFFINT (autosave_url), TYPE_BOOL},
+
+	{"away_auto_unmark", P_OFFINT (auto_unmark_away), TYPE_BOOL},
+	{"away_reason", P_OFFSET (awayreason), TYPE_STR},
+	{"away_show_message", P_OFFINT (show_away_message), TYPE_BOOL},
+	{"away_show_once", P_OFFINT (show_away_once), TYPE_BOOL},
+	{"away_size_max", P_OFFINT (away_size_max), TYPE_INT},
+	{"away_timeout", P_OFFINT (away_timeout), TYPE_INT},
+	{"away_track", P_OFFINT (away_track), TYPE_BOOL},
+
+	{"completion_amount", P_OFFINT (completion_amount), TYPE_INT},
+	{"completion_auto", P_OFFINT (nickcompletion), TYPE_BOOL},
+	{"completion_sort", P_OFFINT (completion_sort), TYPE_INT},
+	{"completion_suffix", P_OFFSET (nick_suffix), TYPE_STR},
+
+	{"dcc_auto_chat", P_OFFINT (autodccchat), TYPE_INT},
+	{"dcc_auto_resume", P_OFFINT (autoresume), TYPE_BOOL},
+	{"dcc_auto_send", P_OFFINT (autodccsend), TYPE_INT},
+	{"dcc_blocksize", P_OFFINT (dcc_blocksize), TYPE_INT},
+	{"dcc_completed_dir", P_OFFSET (dcc_completed_dir), TYPE_STR},
+	{"dcc_dir", P_OFFSET (dccdir), TYPE_STR},
+	{"dcc_fast_send", P_OFFINT (fastdccsend), TYPE_BOOL},
+	{"dcc_global_max_get_cps", P_OFFINT (dcc_global_max_get_cps), TYPE_INT},
+	{"dcc_global_max_send_cps", P_OFFINT (dcc_global_max_send_cps), TYPE_INT},
+	{"dcc_ip", P_OFFSET (dcc_ip_str), TYPE_STR},
+	{"dcc_ip_from_server", P_OFFINT (ip_from_server), TYPE_BOOL},
+	{"dcc_max_get_cps", P_OFFINT (dcc_max_get_cps), TYPE_INT},
+	{"dcc_max_send_cps", P_OFFINT (dcc_max_send_cps), TYPE_INT},
+	{"dcc_permissions", P_OFFINT (dccpermissions), TYPE_INT},
+	{"dcc_port_first", P_OFFINT (first_dcc_send_port), TYPE_INT},
+	{"dcc_port_last", P_OFFINT (last_dcc_send_port), TYPE_INT},
+	{"dcc_remove", P_OFFINT (dcc_remove), TYPE_BOOL},
+	{"dcc_save_nick", P_OFFINT (dccwithnick), TYPE_BOOL},
+	{"dcc_send_fillspaces", P_OFFINT (dcc_send_fillspaces), TYPE_BOOL},
+	{"dcc_stall_timeout", P_OFFINT (dccstalltimeout), TYPE_INT},
+	{"dcc_timeout", P_OFFINT (dcctimeout), TYPE_INT},
+
+	{"dnsprogram", P_OFFSET (dnsprogram), TYPE_STR},
+
+	{"flood_ctcp_num", P_OFFINT (ctcp_number_limit), TYPE_INT},
+	{"flood_ctcp_time", P_OFFINT (ctcp_time_limit), TYPE_INT},
+	{"flood_msg_num", P_OFFINT (msg_number_limit), TYPE_INT},
+	{"flood_msg_time", P_OFFINT (msg_time_limit), TYPE_INT},
+
+	{"gui_auto_open_chat", P_OFFINT (autoopendccchatwindow), TYPE_BOOL},
+	{"gui_auto_open_dialog", P_OFFINT (autodialog), TYPE_BOOL},
+	{"gui_auto_open_recv", P_OFFINT (autoopendccrecvwindow), TYPE_BOOL},
+	{"gui_auto_open_send", P_OFFINT (autoopendccsendwindow), TYPE_BOOL},
+	{"gui_dialog_height", P_OFFINT (dialog_height), TYPE_INT},
+	{"gui_dialog_left", P_OFFINT (dialog_left), TYPE_INT},
+	{"gui_dialog_top", P_OFFINT (dialog_top), TYPE_INT},
+	{"gui_dialog_width", P_OFFINT (dialog_width), TYPE_INT},
+	{"gui_hide_menu", P_OFFINT (hidemenu), TYPE_BOOL},
+	{"gui_input_spell", P_OFFINT (gui_input_spell), TYPE_BOOL},
+	{"gui_input_style", P_OFFINT (style_inputbox), TYPE_BOOL},
+	{"gui_join_dialog", P_OFFINT (gui_join_dialog), TYPE_BOOL},
+	{"gui_lagometer", P_OFFINT (lagometer), TYPE_INT},
+	{"gui_mode_buttons", P_OFFINT (chanmodebuttons), TYPE_BOOL},
+	{"gui_pane_left_size", P_OFFINT (gui_pane_left_size), TYPE_INT},
+	{"gui_pane_right_size", P_OFFINT (gui_pane_right_size), TYPE_INT},
+	{"gui_quit_dialog", P_OFFINT (gui_quit_dialog), TYPE_BOOL},
+	{"gui_slist_fav", P_OFFINT (slist_fav), TYPE_INT},
+	{"gui_slist_select", P_OFFINT (slist_select), TYPE_INT},
+	{"gui_slist_skip", P_OFFINT (slist_skip), TYPE_BOOL},
+	{"gui_throttlemeter", P_OFFINT (throttlemeter), TYPE_INT},
+	{"gui_topicbar", P_OFFINT (topicbar), TYPE_BOOL},
+	{"gui_tray", P_OFFINT (gui_tray), TYPE_BOOL},
+	{"gui_tray_flags", P_OFFINT (gui_tray_flags), TYPE_INT},
+	{"gui_tweaks", P_OFFINT (gui_tweaks), TYPE_INT},
+	{"gui_ulist_buttons", P_OFFINT (userlistbuttons), TYPE_BOOL},
+	{"gui_ulist_doubleclick", P_OFFSET (doubleclickuser), TYPE_STR},
+	{"gui_ulist_hide", P_OFFINT (hideuserlist), TYPE_BOOL},
+	{"gui_ulist_left", P_OFFINT (_gui_ulist_left), TYPE_BOOL},	/* obsolete */
+	{"gui_ulist_pos", P_OFFINT (gui_ulist_pos), TYPE_INT},
+	{"gui_ulist_resizable", P_OFFINT (paned_userlist), TYPE_BOOL},
+	{"gui_ulist_show_hosts", P_OFFINT(showhostname_in_userlist), TYPE_BOOL},
+	{"gui_ulist_sort", P_OFFINT (userlist_sort), TYPE_INT},
+	{"gui_ulist_style", P_OFFINT (style_namelistgad), TYPE_BOOL},
+	{"gui_url_mod", P_OFFINT (gui_url_mod), TYPE_INT},
+	{"gui_usermenu", P_OFFINT (gui_usermenu), TYPE_BOOL},
+	{"gui_win_height", P_OFFINT (mainwindow_height), TYPE_INT},
+	{"gui_win_left", P_OFFINT (mainwindow_left), TYPE_INT},
+	{"gui_win_save", P_OFFINT (mainwindow_save), TYPE_BOOL},
+	{"gui_win_state", P_OFFINT (gui_win_state), TYPE_INT},
+	{"gui_win_top", P_OFFINT (mainwindow_top), TYPE_INT},
+	{"gui_win_width", P_OFFINT (mainwindow_width), TYPE_INT},
+
+#ifdef WIN32
+	{"identd", P_OFFINT (identd), TYPE_BOOL},
+#endif
+	{"input_balloon_chans", P_OFFINT (input_balloon_chans), TYPE_BOOL},
+	{"input_balloon_hilight", P_OFFINT (input_balloon_hilight), TYPE_BOOL},
+	{"input_balloon_priv", P_OFFINT (input_balloon_priv), TYPE_BOOL},
+	{"input_balloon_time", P_OFFINT (input_balloon_time), TYPE_INT},
+	{"input_beep_chans", P_OFFINT (input_beep_chans), TYPE_BOOL},
+	{"input_beep_hilight", P_OFFINT (input_beep_hilight), TYPE_BOOL},
+	{"input_beep_msg", P_OFFINT (input_beep_priv), TYPE_BOOL},
+	{"input_command_char", P_OFFSET (cmdchar), TYPE_STR},
+	{"input_filter_beep", P_OFFINT (filterbeep), TYPE_BOOL},
+	{"input_flash_chans", P_OFFINT (input_flash_chans), TYPE_BOOL},
+	{"input_flash_hilight", P_OFFINT (input_flash_hilight), TYPE_BOOL},
+	{"input_flash_priv", P_OFFINT (input_flash_priv), TYPE_BOOL},
+	{"input_perc_ascii", P_OFFINT (perc_ascii), TYPE_BOOL},
+	{"input_perc_color", P_OFFINT (perc_color), TYPE_BOOL},
+	{"input_tray_chans", P_OFFINT (input_tray_chans), TYPE_BOOL},
+	{"input_tray_hilight", P_OFFINT (input_tray_hilight), TYPE_BOOL},
+	{"input_tray_priv", P_OFFINT (input_tray_priv), TYPE_BOOL},
+
+	{"irc_auto_rejoin", P_OFFINT (autorejoin), TYPE_BOOL},
+	{"irc_ban_type", P_OFFINT (bantype), TYPE_INT},
+	{"irc_conf_mode", P_OFFINT (confmode), TYPE_BOOL},
+	{"irc_extra_hilight", P_OFFSET (irc_extra_hilight), TYPE_STR},
+	{"irc_hide_version", P_OFFINT (hidever), TYPE_BOOL},
+	{"irc_id_ntext", P_OFFSET (irc_id_ntext), TYPE_STR},
+	{"irc_id_ytext", P_OFFSET (irc_id_ytext), TYPE_STR},
+	{"irc_invisible", P_OFFINT (invisible), TYPE_BOOL},
+	{"irc_join_delay", P_OFFINT (irc_join_delay), TYPE_INT},
+	{"irc_logging", P_OFFINT (logging), TYPE_BOOL},
+	{"irc_logmask", P_OFFSET (logmask), TYPE_STR},
+	{"irc_nick1", P_OFFSET (nick1), TYPE_STR},
+	{"irc_nick2", P_OFFSET (nick2), TYPE_STR},
+	{"irc_nick3", P_OFFSET (nick3), TYPE_STR},
+	{"irc_nick_hilight", P_OFFSET (irc_nick_hilight), TYPE_STR},
+	{"irc_no_hilight", P_OFFSET (irc_no_hilight), TYPE_STR},
+	{"irc_part_reason", P_OFFSET (partreason), TYPE_STR},
+	{"irc_quit_reason", P_OFFSET (quitreason), TYPE_STR},
+	{"irc_raw_modes", P_OFFINT (raw_modes), TYPE_BOOL},
+	{"irc_real_name", P_OFFSET (realname), TYPE_STR},
+	{"irc_servernotice", P_OFFINT (servernotice), TYPE_BOOL},
+	{"irc_skip_motd", P_OFFINT (skipmotd), TYPE_BOOL},
+	{"irc_user_name", P_OFFSET (username), TYPE_STR},
+	{"irc_wallops", P_OFFINT (wallops), TYPE_BOOL},
+	{"irc_who_join", P_OFFINT (userhost), TYPE_BOOL},
+	{"irc_whois_front", P_OFFINT (irc_whois_front), TYPE_BOOL},
+
+	{"net_auto_reconnect", P_OFFINT (autoreconnect), TYPE_BOOL},
+	{"net_auto_reconnectonfail", P_OFFINT (autoreconnectonfail), TYPE_BOOL},
+	{"net_bind_host", P_OFFSET (hostname), TYPE_STR},
+	{"net_ping_timeout", P_OFFINT (pingtimeout), TYPE_INT},
+	{"net_proxy_auth", P_OFFINT (proxy_auth), TYPE_BOOL},
+	{"net_proxy_host", P_OFFSET (proxy_host), TYPE_STR},
+	{"net_proxy_pass", P_OFFSET (proxy_pass), TYPE_STR},
+	{"net_proxy_port", P_OFFINT (proxy_port), TYPE_INT},
+	{"net_proxy_type", P_OFFINT (proxy_type), TYPE_INT},
+	{"net_proxy_use", P_OFFINT (proxy_use), TYPE_INT},
+	{"net_proxy_user", P_OFFSET (proxy_user), TYPE_STR},
+
+	{"net_reconnect_delay", P_OFFINT (recon_delay), TYPE_INT},
+	{"net_throttle", P_OFFINT (throttle), TYPE_BOOL},
+
+	{"notify_timeout", P_OFFINT (notify_timeout), TYPE_INT},
+	{"notify_whois_online", P_OFFINT (whois_on_notifyonline), TYPE_BOOL},
+
+	{"perl_warnings", P_OFFINT (perlwarnings), TYPE_BOOL},
+
+	{"sound_command", P_OFFSET (soundcmd), TYPE_STR},
+	{"sound_dir", P_OFFSET (sounddir), TYPE_STR},
+	{"stamp_log", P_OFFINT (timestamp_logs), TYPE_BOOL},
+	{"stamp_log_format", P_OFFSET (timestamp_log_format), TYPE_STR},
+	{"stamp_text", P_OFFINT (timestamp), TYPE_BOOL},
+	{"stamp_text_format", P_OFFSET (stamp_format), TYPE_STR},
+
+	{"tab_chans", P_OFFINT (tabchannels), TYPE_BOOL},
+	{"tab_dialogs", P_OFFINT (privmsgtab), TYPE_BOOL},
+	{"tab_layout", P_OFFINT (tab_layout), TYPE_INT},
+	{"tab_new_to_front", P_OFFINT (newtabstofront), TYPE_INT},
+	{"tab_notices", P_OFFINT (notices_tabs), TYPE_BOOL},
+	{"tab_pos", P_OFFINT (tab_pos), TYPE_INT},
+	{"tab_position", P_OFFINT (_tabs_position), TYPE_INT}, /* obsolete */
+	{"tab_server", P_OFFINT (use_server_tab), TYPE_BOOL},
+	{"tab_small", P_OFFINT (tab_small), TYPE_INT},
+	{"tab_sort", P_OFFINT (tab_sort), TYPE_BOOL},
+	{"tab_trunc", P_OFFINT (truncchans), TYPE_INT},
+	{"tab_utils", P_OFFINT (windows_as_tabs), TYPE_BOOL},
+
+	{"text_background", P_OFFSET (background), TYPE_STR},
+	{"text_color_nicks", P_OFFINT (colorednicks), TYPE_BOOL},
+	{"text_font", P_OFFSET (font_normal), TYPE_STR},
+	{"text_indent", P_OFFINT (indent_nicks), TYPE_BOOL},
+	{"text_max_indent", P_OFFINT (max_auto_indent), TYPE_INT},
+	{"text_max_lines", P_OFFINT (max_lines), TYPE_INT},
+	{"text_replay", P_OFFINT (text_replay), TYPE_BOOL},
+	{"text_show_marker", P_OFFINT (show_marker), TYPE_BOOL},
+	{"text_show_sep", P_OFFINT (show_separator), TYPE_BOOL},
+	{"text_stripcolor", P_OFFINT (stripcolor), TYPE_BOOL},
+	{"text_thin_sep", P_OFFINT (thin_separator), TYPE_BOOL},
+	{"text_tint_blue", P_OFFINT (tint_blue), TYPE_INT},
+	{"text_tint_green", P_OFFINT (tint_green), TYPE_INT},
+	{"text_tint_red", P_OFFINT (tint_red), TYPE_INT},
+	{"text_transparent", P_OFFINT (transparent), TYPE_BOOL},
+	{"text_wordwrap", P_OFFINT (wordwrap), TYPE_BOOL},
+
+	{0, 0, 0},
+};
+
+static char *
+convert_with_fallback (const char *str, const char *fallback)
+{
+	char *utf;
+
+	utf = g_locale_to_utf8 (str, -1, 0, 0, 0);
+	if (!utf)
+	{
+		/* this can happen if CHARSET envvar is set wrong */
+		/* maybe it's already utf8 (breakage!) */
+		if (!g_utf8_validate (str, -1, NULL))
+			utf = g_strdup (fallback);
+		else
+			utf = g_strdup (str);
+	}
+
+	return utf;
+}
+
+void
+load_config (void)
+{
+	struct stat st;
+	char *cfg, *sp;
+	const char *username, *realname;
+	int res, val, i, fh;
+
+	check_prefs_dir ();
+	username = g_get_user_name ();
+	if (!username)
+		username = "root";
+
+	realname = g_get_real_name ();
+	if ((realname && realname[0] == 0) || !realname)
+		realname = username;
+
+	username = convert_with_fallback (username, "username");
+	realname = convert_with_fallback (realname, "realname");
+
+	memset (&prefs, 0, sizeof (struct xchatprefs));
+
+	/* put in default values, anything left out is automatically zero */
+	prefs.local_ip = 0xffffffff;
+	prefs.irc_join_delay = 3;
+	prefs.show_marker = 1;
+	prefs.newtabstofront = 2;
+	prefs.completion_amount = 5;
+	prefs.away_timeout = 60;
+	prefs.away_size_max = 300;
+	prefs.away_track = 1;
+	prefs.timestamp_logs = 1;
+	prefs.truncchans = 20;
+	prefs.autoresume = 1;
+	prefs.show_away_once = 1;
+	prefs.indent_nicks = 1;
+	prefs.thin_separator = 1;
+	prefs._tabs_position = 2; /* 2 = left */
+	prefs.fastdccsend = 1;
+	prefs.wordwrap = 1;
+	prefs.autosave = 1;
+	prefs.autodialog = 1;
+	prefs.gui_input_spell = 1;
+	prefs.autoreconnect = 1;
+	prefs.recon_delay = 10;
+	prefs.text_replay = 1;
+	prefs.tabchannels = 1;
+	prefs.tab_layout = 2;	/* 0=Tabs 1=Reserved 2=Tree */
+	prefs.tab_sort = 1;
+	prefs.paned_userlist = 1;
+	prefs.newtabstofront = 2;
+	prefs.use_server_tab = 1;
+	prefs.privmsgtab = 1;
+	/*prefs.style_inputbox = 1;*/
+	prefs.dccpermissions = 0600;
+	prefs.max_lines = 500;
+	prefs.mainwindow_width = 640;
+	prefs.mainwindow_height = 400;
+	prefs.dialog_width = 500;
+	prefs.dialog_height = 256;
+	prefs.gui_join_dialog = 1;
+	prefs.gui_quit_dialog = 1;
+	prefs.dcctimeout = 180;
+	prefs.dccstalltimeout = 60;
+	prefs.notify_timeout = 15;
+	prefs.tint_red =
+		prefs.tint_green =
+		prefs.tint_blue = 195;
+	prefs.auto_indent = 1;
+	prefs.max_auto_indent = 256;
+	prefs.show_separator = 1;
+	prefs.dcc_blocksize = 1024;
+	prefs.throttle = 1;
+	 /*FIXME*/ prefs.msg_time_limit = 30;
+	prefs.msg_number_limit = 5;
+	prefs.ctcp_time_limit = 30;
+	prefs.ctcp_number_limit = 5;
+	prefs.topicbar = 1;
+	prefs.lagometer = 1;
+	prefs.throttlemeter = 1;
+	prefs.autoopendccrecvwindow = 1;
+	prefs.autoopendccsendwindow = 1;
+	prefs.autoopendccchatwindow = 1;
+	prefs.userhost = 1;
+	prefs.gui_url_mod = 4;	/* ctrl */
+	prefs.gui_tray = 1;
+	prefs.gui_pane_left_size = 100;
+	prefs.gui_pane_right_size = 100;
+	prefs.mainwindow_save = 1;
+	prefs.bantype = 2;
+	prefs.input_balloon_time = 20;
+	prefs.input_flash_priv = prefs.input_flash_hilight = 1;
+	prefs.input_tray_priv = prefs.input_tray_hilight = 1;
+	prefs.autodccsend = 2;	/* browse mode */
+#ifdef WIN32
+	prefs.identd = 1;
+#endif
+	strcpy (prefs.stamp_format, "[%H:%M] ");
+	strcpy (prefs.timestamp_log_format, "%b %d %H:%M:%S ");
+	strcpy (prefs.logmask, "%n-%c.log");
+	strcpy (prefs.nick_suffix, ",");
+	strcpy (prefs.cmdchar, "/");
+	strcpy (prefs.nick1, username);
+	strcpy (prefs.nick2, username);
+	strcat (prefs.nick2, "_");
+	strcpy (prefs.nick3, username);
+	strcat (prefs.nick3, "__");
+	strcpy (prefs.realname, realname);
+	strcpy (prefs.username, username);
+#ifdef WIN32
+	strcpy (prefs.sounddir, "./sounds");
+	{
+		char out[256];
+
+		if (get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\"
+						 "Explorer\\Shell Folders", "Personal", out, sizeof (out)))
+			snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", out);
+		else
+			snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", get_xdir_utf8 ());
+	}
+#else
+	snprintf (prefs.sounddir, sizeof (prefs.sounddir), "%s/sounds", get_xdir_utf8 ());
+	snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s/downloads", get_xdir_utf8 ());
+#endif
+	strcpy (prefs.doubleclickuser, "QUOTE WHOIS %s %s");
+	strcpy (prefs.awayreason, _("I'm busy"));
+	strcpy (prefs.quitreason, _("Leaving"));
+	strcpy (prefs.partreason, prefs.quitreason);
+	strcpy (prefs.font_normal, DEF_FONT);
+	strcpy (prefs.dnsprogram, "host");
+	strcpy (prefs.irc_no_hilight, "NickServ,ChanServ");
+
+	g_free ((char *)username);
+	g_free ((char *)realname);
+
+	fh = open (default_file (), OFLAGS | O_RDONLY);
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		cfg = malloc (st.st_size + 1);
+		cfg[0] = '\0';
+		i = read (fh, cfg, st.st_size);
+		if (i >= 0)
+			cfg[i] = '\0';					/* make sure cfg is NULL terminated */
+		close (fh);
+		i = 0;
+		do
+		{
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				cfg_get_str (cfg, vars[i].name, (char *) &prefs + vars[i].offset,
+								 vars[i].len);
+				break;
+			case TYPE_BOOL:
+			case TYPE_INT:
+				val = cfg_get_int_with_result (cfg, vars[i].name, &res);
+				if (res)
+					*((int *) &prefs + vars[i].offset) = val;
+				break;
+			}
+			i++;
+		}
+		while (vars[i].name);
+
+		free (cfg);
+
+	} else
+	{
+#ifndef WIN32
+#ifndef __EMX__
+		/* OS/2 uses UID 0 all the time */
+		if (getuid () == 0)
+			fe_message (_("* Running IRC as root is stupid! You should\n"
+							"  create a User Account and use that to login.\n"), FE_MSG_WARN|FE_MSG_WAIT);
+#endif
+#endif /* !WIN32 */
+
+		mkdir_utf8 (prefs.dccdir);
+		mkdir_utf8 (prefs.dcc_completed_dir);
+	}
+	if (prefs.mainwindow_height < 138)
+		prefs.mainwindow_height = 138;
+	if (prefs.mainwindow_width < 106)
+		prefs.mainwindow_width = 106;
+
+	sp = strchr (prefs.username, ' ');
+	if (sp)
+		sp[0] = 0;	/* spaces in username would break the login */
+
+	/* try to make sense of old ulist/tree position settings */
+	if (prefs.gui_ulist_pos == 0)
+	{
+		prefs.gui_ulist_pos = 3;	/* top right */
+		if (prefs._gui_ulist_left)
+			prefs.gui_ulist_pos = 2;	/* bottom left */
+
+		switch (prefs._tabs_position)
+		{
+		case 0:
+			prefs.tab_pos = 6; /* bottom */
+			break;
+		case 1:
+			prefs.tab_pos = 5;	/* top */
+			break;
+		case 2:
+			prefs.tab_pos = 1; 	/* left */
+			break;
+		case 3:
+			prefs.tab_pos = 4; 	/* right */
+			break;
+		case 4:
+			prefs.tab_pos = 1;	/* (hidden)left */
+			break;
+		case 5:
+			if (prefs._gui_ulist_left)
+			{
+				prefs.tab_pos = 1; 	/* above ulist left */
+				prefs.gui_ulist_pos = 2;
+			}
+			else
+			{
+				prefs.tab_pos = 3; 	/* above ulist right */
+				prefs.gui_ulist_pos = 4;
+			}
+			break;
+		}
+	}
+}
+
+int
+save_config (void)
+{
+	int fh, i;
+	char *new_config, *config;
+
+	check_prefs_dir ();
+
+	config = default_file ();
+	new_config = malloc (strlen (config) + 5);
+	strcpy (new_config, config);
+	strcat (new_config, ".new");
+	
+	fh = open (new_config, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, 0600);
+	if (fh == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+
+	if (!cfg_put_str (fh, "version", PACKAGE_VERSION))
+	{
+		free (new_config);
+		return 0;
+	}
+		
+	i = 0;
+	do
+	{
+		switch (vars[i].type)
+		{
+		case TYPE_STR:
+			if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset))
+			{
+				free (new_config);
+				return 0;
+			}
+			break;
+		case TYPE_INT:
+		case TYPE_BOOL:
+			if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name))
+			{
+				free (new_config);
+				return 0;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	if (close (fh) == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+
+#ifdef WIN32
+	unlink (config);	/* win32 can't rename to an existing file */
+#endif
+	if (rename (new_config, config) == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+	free (new_config);
+
+	return 1;
+}
+
+static void
+set_showval (session *sess, const struct prefs *var, char *tbuf)
+{
+	int len, dots, j;
+
+	len = strlen (var->name);
+	memcpy (tbuf, var->name, len);
+	dots = 29 - len;
+	if (dots < 0)
+		dots = 0;
+	tbuf[len++] = '\003';
+	tbuf[len++] = '2';
+	for (j=0;j<dots;j++)
+		tbuf[j + len] = '.';
+	len += j;
+	switch (var->type)
+	{
+	case TYPE_STR:
+		sprintf (tbuf + len, "\0033:\017 %s\n",
+					(char *) &prefs + var->offset);
+		break;
+	case TYPE_INT:
+		sprintf (tbuf + len, "\0033:\017 %d\n",
+					*((int *) &prefs + var->offset));
+		break;
+	case TYPE_BOOL:
+		if (*((int *) &prefs + var->offset))
+			sprintf (tbuf + len, "\0033:\017 %s\n", "ON");
+		else
+			sprintf (tbuf + len, "\0033:\017 %s\n", "OFF");
+		break;
+	}
+	PrintText (sess, tbuf);
+}
+
+static void
+set_list (session * sess, char *tbuf)
+{
+	int i;
+
+	i = 0;
+	do
+	{
+		set_showval (sess, &vars[i], tbuf);
+		i++;
+	}
+	while (vars[i].name);
+}
+
+int
+cfg_get_bool (char *var)
+{
+	int i = 0;
+
+	do
+	{
+		if (!strcasecmp (var, vars[i].name))
+		{
+			return *((int *) &prefs + vars[i].offset);
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	return -1;
+}
+
+int
+cmd_set (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int wild = FALSE;
+	int or = FALSE;
+	int off = FALSE;
+	int quiet = FALSE;
+	int erase = FALSE;
+	int i = 0, finds = 0, found;
+	int idx = 2;
+	char *var, *val;
+
+	if (strcasecmp (word[2], "-e") == 0)
+	{
+		idx++;
+		erase = TRUE;
+	}
+
+	/* turn a bit OFF */
+	if (strcasecmp (word[idx], "-off") == 0)
+	{
+		idx++;
+		off = TRUE;
+	}
+
+	/* turn a bit ON */
+	if (strcasecmp (word[idx], "-or") == 0 || strcasecmp (word[idx], "-on") == 0)
+	{
+		idx++;
+		or = TRUE;
+	}
+
+	if (strcasecmp (word[idx], "-quiet") == 0)
+	{
+		idx++;
+		quiet = TRUE;
+	}
+
+	var = word[idx];
+	val = word_eol[idx+1];
+
+	if (!*var)
+	{
+		set_list (sess, tbuf);
+		return TRUE;
+	}
+
+	if ((strchr (var, '*') || strchr (var, '?')) && !*val)
+		wild = TRUE;
+
+	if (*val == '=')
+		val++;
+
+	do
+	{
+		if (wild)
+			found = !match (var, vars[i].name);
+		else
+			found = strcasecmp (var, vars[i].name);
+
+		if (found == 0)
+		{
+			finds++;
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				if (erase || *val)
+				{
+					strncpy ((char *) &prefs + vars[i].offset, val, vars[i].len);
+					((char *) &prefs)[vars[i].offset + vars[i].len - 1] = 0;
+					if (!quiet)
+						PrintTextf (sess, "%s set to: %s\n", var, (char *) &prefs + vars[i].offset);
+				} else
+				{
+					set_showval (sess, &vars[i], tbuf);
+				}
+				break;
+			case TYPE_INT:
+			case TYPE_BOOL:
+				if (*val)
+				{
+					if (vars[i].type == TYPE_BOOL)
+					{
+						if (atoi (val))
+							*((int *) &prefs + vars[i].offset) = 1;
+						else
+							*((int *) &prefs + vars[i].offset) = 0;
+						if (!strcasecmp (val, "YES") || !strcasecmp (val, "ON"))
+							*((int *) &prefs + vars[i].offset) = 1;
+						if (!strcasecmp (val, "NO") || !strcasecmp (val, "OFF"))
+							*((int *) &prefs + vars[i].offset) = 0;
+					} else
+					{
+						if (or)
+							*((int *) &prefs + vars[i].offset) |= atoi (val);
+						else if (off)
+							*((int *) &prefs + vars[i].offset) &= ~(atoi (val));
+						else
+							*((int *) &prefs + vars[i].offset) = atoi (val);
+					}
+					if (!quiet)
+						PrintTextf (sess, "%s set to: %d\n", var,
+										*((int *) &prefs + vars[i].offset));
+				} else
+				{
+					set_showval (sess, &vars[i], tbuf);
+				}
+				break;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	if (!finds && !quiet)
+		PrintText (sess, "No such variable.\n");
+
+	return TRUE;
+}
+
+int
+xchat_open_file (char *file, int flags, int mode, int xof_flags)
+{
+	char buf[1024];
+
+	if (xof_flags & XOF_FULLPATH)
+	{
+		if (xof_flags & XOF_DOMODE)
+			return open (file, flags | OFLAGS, mode);
+		else
+			return open (file, flags | OFLAGS);
+	}
+
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file);
+	if (xof_flags & XOF_DOMODE)
+		return open (buf, flags | OFLAGS, mode);
+	else
+		return open (buf, flags | OFLAGS);
+}
+
+FILE *
+xchat_fopen_file (const char *file, const char *mode, int xof_flags)
+{
+	char buf[1024];
+
+	if (xof_flags & XOF_FULLPATH)
+		return fopen (file, mode);
+
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file);
+	return fopen (buf, mode);
+}
diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h
new file mode 100644
index 00000000..984b9472
--- /dev/null
+++ b/src/common/cfgfiles.h
@@ -0,0 +1,55 @@
+/* cfgfiles.h */
+
+#ifndef XCHAT_CFGFILES_H
+#define XCHAT_CFGFILES_H
+
+#include "xchat.h"
+
+extern char *xdir_fs;
+extern char *xdir_utf;
+
+char *cfg_get_str (char *cfg, char *var, char *dest, int dest_len);
+int cfg_get_bool (char *var);
+int cfg_get_int_with_result (char *cfg, char *var, int *result);
+int cfg_get_int (char *cfg, char *var);
+int cfg_put_int (int fh, int value, char *var);
+int cfg_get_color (char *cfg, char *var, int *r, int *g, int *b);
+int cfg_put_color (int fh, int r, int g, int b, char *var);
+char *get_xdir_fs (void);
+char *get_xdir_utf8 (void);
+void load_config (void);
+int save_config (void);
+void list_free (GSList ** list);
+void list_loadconf (char *file, GSList ** list, char *defaultconf);
+int list_delentry (GSList ** list, char *name);
+void list_addentry (GSList ** list, char *cmd, char *name);
+int cmd_set (session *sess, char *tbuf, char *word[], char *word_eol[]);
+int xchat_open_file (char *file, int flags, int mode, int xof_flags);
+FILE *xchat_fopen_file (const char *file, const char *mode, int xof_flags);
+#define XOF_DOMODE 1
+#define XOF_FULLPATH 2
+
+#define STRUCT_OFFSET_STR(type,field) \
+( (unsigned int) (((char *) (&(((type *) NULL)->field)))- ((char *) NULL)) )
+
+#define STRUCT_OFFSET_INT(type,field) \
+( (unsigned int) (((int *) (&(((type *) NULL)->field)))- ((int *) NULL)) )
+
+#define P_OFFSET(field) STRUCT_OFFSET_STR(struct xchatprefs, field),sizeof(prefs.field)
+#define P_OFFSETNL(field) STRUCT_OFFSET_STR(struct xchatprefs, field)
+#define P_OFFINT(field) STRUCT_OFFSET_INT(struct xchatprefs, field),0
+#define P_OFFINTNL(field) STRUCT_OFFSET_INT(struct xchatprefs, field)
+
+struct prefs
+{
+	char *name;
+	unsigned short offset;
+	unsigned short len;
+	unsigned short type;
+};
+
+#define TYPE_STR 0
+#define TYPE_INT 1
+#define TYPE_BOOL 2
+
+#endif
diff --git a/src/common/chanopt.c b/src/common/chanopt.c
new file mode 100644
index 00000000..a4fd8faa
--- /dev/null
+++ b/src/common/chanopt.c
@@ -0,0 +1,431 @@
+/* per-channel/dialog settings :: /CHANOPT */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "xchat.h"
+
+#include "cfgfiles.h"
+#include "server.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+static GSList *chanopt_list = NULL;
+static gboolean chanopt_open = FALSE;
+static gboolean chanopt_changed = FALSE;
+
+
+typedef struct
+{
+	char *name;
+	char *alias;	/* old names from 2.8.4 */
+	int offset;
+} channel_options;
+
+#define S_F(xx) STRUCT_OFFSET_STR(struct session,xx)
+
+static const channel_options chanopt[] =
+{
+	{"alert_beep", "BEEP", S_F(alert_beep)},
+	{"alert_taskbar", NULL, S_F(alert_taskbar)},
+	{"alert_tray", "TRAY", S_F(alert_tray)},
+
+	{"text_hidejoinpart", "CONFMODE", S_F(text_hidejoinpart)},
+	{"text_logging", NULL, S_F(text_logging)},
+	{"text_scrollback", NULL, S_F(text_scrollback)},
+};
+
+#undef S_F
+
+static char *
+chanopt_value (guint8 val)
+{
+	switch (val)
+	{
+	case SET_OFF:
+		return "OFF";
+	case SET_ON:
+		return "ON";
+	default:
+		return "{unset}";
+	}
+}
+
+/* handle the /CHANOPT command */
+
+int
+chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int dots, i = 0, j, p = 0;
+	guint8 val;
+	int offset = 2;
+	char *find;
+	gboolean quiet = FALSE;
+	int newval = -1;
+
+	if (!strcmp (word[2], "-quiet"))
+	{
+		quiet = TRUE;
+		offset++;
+	}
+
+	find = word[offset++];
+
+	if (word[offset][0])
+	{
+		if (!strcasecmp (word[offset], "ON"))
+			newval = 1;
+		else if (!strcasecmp (word[offset], "OFF"))
+			newval = 0;
+		else if (word[offset][0] == 'u')
+			newval = SET_DEFAULT;
+		else
+			newval = atoi (word[offset]);
+	}
+
+	if (!quiet)
+		PrintTextf (sess, "\002Network\002: %s \002Channel\002: %s\n",
+						sess->server->network ? server_get_network (sess->server, TRUE) : _("<none>"),
+						sess->channel[0] ? sess->channel : _("<none>"));
+
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		if (find[0] == 0 || match (find, chanopt[i].name) || (chanopt[i].alias && match (find, chanopt[i].alias)))
+		{
+			if (newval != -1)	/* set new value */
+			{
+				*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = newval;
+			}
+
+			if (!quiet)	/* print value */
+			{
+				strcpy (tbuf, chanopt[i].name);
+				p = strlen (tbuf);
+
+				tbuf[p++] = 3;
+				tbuf[p++] = '2';
+
+				dots = 20 - strlen (chanopt[i].name);
+
+				for (j = 0; j < dots; j++)
+					tbuf[p++] = '.';
+				tbuf[p++] = 0;
+
+				val = G_STRUCT_MEMBER (guint8, sess, chanopt[i].offset);
+				PrintTextf (sess, "%s\0033:\017 %s", tbuf, chanopt_value (val));
+			}
+		}
+		i++;
+	}
+
+	return TRUE;
+}
+
+/* is a per-channel setting set? Or is it UNSET and
+ * the global version is set? */
+
+gboolean
+chanopt_is_set (unsigned int global, guint8 per_chan_setting)
+{
+	if (per_chan_setting == SET_DEFAULT)
+		return global;
+
+	return per_chan_setting;
+}
+
+/* additive version */
+
+gboolean
+chanopt_is_set_a (unsigned int global, guint8 per_chan_setting)
+{
+	if (per_chan_setting == SET_DEFAULT)
+		return global;
+
+	return per_chan_setting || global;
+}
+
+/* === below is LOADING/SAVING stuff only === */
+
+typedef struct
+{
+	/* Per-Channel Alerts */
+	/* use a byte, because we need a pointer to each element */
+	guint8 alert_beep;
+	guint8 alert_taskbar;
+	guint8 alert_tray;
+
+	/* Per-Channel Settings */
+	guint8 text_hidejoinpart;
+	guint8 text_logging;
+	guint8 text_scrollback;
+
+	char *network;
+	char *channel;
+
+} chanopt_in_memory;
+
+
+static chanopt_in_memory *
+chanopt_find (char *network, char *channel, gboolean add_new)
+{
+	GSList *list;
+	chanopt_in_memory *co;
+	int i;
+
+	for (list = chanopt_list; list; list = list->next)
+	{
+		co = list->data;
+		if (!strcasecmp (co->channel, channel) &&
+			 !strcasecmp (co->network, network))
+			return co;
+	}
+
+	if (!add_new)
+		return NULL;
+
+	/* allocate a new one */
+	co = g_malloc0 (sizeof (chanopt_in_memory));
+	co->channel = g_strdup (channel);
+	co->network = g_strdup (network);
+
+	/* set all values to SET_DEFAULT */
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = SET_DEFAULT;
+		i++;
+	}
+
+	chanopt_list = g_slist_prepend (chanopt_list, co);
+	chanopt_changed = TRUE;
+
+	return co;
+}
+
+static void
+chanopt_add_opt (chanopt_in_memory *co, char *var, int new_value)
+{
+	int i;
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		if (!strcmp (var, chanopt[i].name))
+		{
+			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = new_value;
+
+		}
+		i++;
+	}
+}
+
+/* load chanopt.conf from disk into our chanopt_list GSList */
+
+static void
+chanopt_load_all (void)
+{
+	int fh;
+	char buf[256];
+	char *eq;
+	char *network = NULL;
+	chanopt_in_memory *current = NULL;
+
+	/* 1. load the old file into our GSList */
+	fh = xchat_open_file ("chanopt.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
+		{
+			eq = strchr (buf, '=');
+			if (!eq)
+				continue;
+			eq[0] = 0;
+
+			if (eq != buf && eq[-1] == ' ')
+				eq[-1] = 0;
+
+			if (!strcmp (buf, "network"))
+			{
+				g_free (network);
+				network = g_strdup (eq + 2);
+			}
+			else if (!strcmp (buf, "channel"))
+			{
+				current = chanopt_find (network, eq + 2, TRUE);
+				chanopt_changed = FALSE;
+			}
+			else
+			{
+				if (current)
+					chanopt_add_opt (current, buf, atoi (eq + 2));
+			}
+
+		}
+		close (fh);
+		g_free (network);
+	}
+}
+
+void
+chanopt_load (session *sess)
+{
+	int i;
+	guint8 val;
+	chanopt_in_memory *co;
+	char *network;
+
+	if (sess->channel[0] == 0)
+		return;
+
+	network = server_get_network (sess->server, FALSE);
+	if (!network)
+		return;
+
+	if (!chanopt_open)
+	{
+		chanopt_open = TRUE;
+		chanopt_load_all ();
+	}
+
+	co = chanopt_find (network, sess->channel, FALSE);
+	if (!co)
+		return;
+
+	/* fill in all the sess->xxxxx fields */
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		val = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
+		*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = val;
+		i++;
+	}
+}
+
+void
+chanopt_save (session *sess)
+{
+	int i;
+	guint8 vals;
+	guint8 valm;
+	chanopt_in_memory *co;
+	char *network;
+
+	if (sess->channel[0] == 0)
+		return;
+
+	network = server_get_network (sess->server, FALSE);
+	if (!network)
+		return;
+
+	/* 2. reconcile sess with what we loaded from disk */
+
+	co = chanopt_find (network, sess->channel, TRUE);
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		vals = G_STRUCT_MEMBER(guint8, sess, chanopt[i].offset);
+		valm = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
+
+		if (vals != valm)
+		{
+			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = vals;
+			chanopt_changed = TRUE;
+		}
+
+		i++;
+	}
+}
+
+static void
+chanopt_save_one_channel (chanopt_in_memory *co, int fh)
+{
+	int i;
+	char buf[256];
+	guint8 val;
+
+	snprintf (buf, sizeof (buf), "%s = %s\n", "network", co->network);
+	write (fh, buf, strlen (buf));
+
+	snprintf (buf, sizeof (buf), "%s = %s\n", "channel", co->channel);
+	write (fh, buf, strlen (buf));
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
+		if (val != SET_DEFAULT)
+		{
+			snprintf (buf, sizeof (buf), "%s = %d\n", chanopt[i].name, val);
+			write (fh, buf, strlen (buf));
+		}
+		i++;
+	}
+}
+
+void
+chanopt_save_all (void)
+{
+	int i;
+	int num_saved;
+	int fh;
+	GSList *list;
+	chanopt_in_memory *co;
+	guint8 val;
+
+	if (!chanopt_list || !chanopt_changed)
+	{
+		return;
+	}
+
+	fh = xchat_open_file ("chanopt.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh == -1)
+	{
+		return;
+	}
+
+	for (num_saved = 0, list = chanopt_list; list; list = list->next)
+	{
+		co = list->data;
+
+		i = 0;
+		while (i < sizeof (chanopt) / sizeof (channel_options))
+		{
+			val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
+			/* not using global/default setting, must save */
+			if (val != SET_DEFAULT)
+			{
+				if (num_saved != 0)
+					write (fh, "\n", 1);
+
+				chanopt_save_one_channel (co, fh);
+				num_saved++;
+				goto cont;
+			}
+			i++;
+		}
+
+cont:
+		g_free (co->network);
+		g_free (co->channel);
+		g_free (co);
+	}
+
+	close (fh);
+
+	/* we're quiting, no need to free */
+
+	/*g_slist_free (chanopt_list);
+	chanopt_list = NULL;
+
+	chanopt_open = FALSE;
+	chanopt_changed = FALSE;*/
+}
diff --git a/src/common/chanopt.h b/src/common/chanopt.h
new file mode 100644
index 00000000..90ca86c3
--- /dev/null
+++ b/src/common/chanopt.h
@@ -0,0 +1,6 @@
+int chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]);
+gboolean chanopt_is_set (unsigned int global, guint8 per_chan_setting);
+gboolean chanopt_is_set_a (unsigned int global, guint8 per_chan_setting);
+void chanopt_save_all (void);
+void chanopt_save (session *sess);
+void chanopt_load (session *sess);
diff --git a/src/common/ctcp.c b/src/common/ctcp.c
new file mode 100644
index 00000000..574cda79
--- /dev/null
+++ b/src/common/ctcp.c
@@ -0,0 +1,191 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "util.h"
+#include "modes.h"
+#include "outbound.h"
+#include "ignore.h"
+#include "inbound.h"
+#include "dcc.h"
+#include "text.h"
+#include "ctcp.h"
+#include "server.h"
+#include "xchatc.h"
+
+
+static void
+ctcp_reply (session *sess, char *nick, char *word[], char *word_eol[],
+				char *conf)
+{
+	char tbuf[4096];	/* can receive 2048 from IRC, so this is enough */
+
+	conf = strdup (conf);
+	/* process %C %B etc */
+	check_special_chars (conf, TRUE);
+	auto_insert (tbuf, sizeof (tbuf), conf, word, word_eol, "", "", word_eol[5],
+					 server_get_network (sess->server, TRUE), "", "", nick);
+	free (conf);
+	handle_command (sess, tbuf, FALSE);
+}
+
+static int
+ctcp_check (session *sess, char *nick, char *word[], char *word_eol[],
+				char *ctcp)
+{
+	int ret = 0;
+	char *po;
+	struct popup *pop;
+	GSList *list = ctcp_list;
+
+	po = strchr (ctcp, '\001');
+	if (po)
+		*po = 0;
+
+	po = strchr (word_eol[5], '\001');
+	if (po)
+		*po = 0;
+
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (!strcasecmp (ctcp, pop->name))
+		{
+			ctcp_reply (sess, nick, word, word_eol, pop->cmd);
+			ret = 1;
+		}
+		list = list->next;
+	}
+	return ret;
+}
+
+void
+ctcp_handle (session *sess, char *to, char *nick, char *ip,
+				 char *msg, char *word[], char *word_eol[], int id)
+{
+	char *po;
+	session *chansess;
+	server *serv = sess->server;
+	char outbuf[1024];
+	int ctcp_offset = 2;
+
+	if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') )
+			ctcp_offset = 3;
+
+	/* consider DCC to be different from other CTCPs */
+	if (!strncasecmp (msg, "DCC", 3))
+	{
+		/* but still let CTCP replies override it */
+		if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+		{
+			if (!ignore_check (word[1], IG_DCC))
+				handle_dcc (sess, nick, word, word_eol);
+		}
+		return;
+	}
+
+	/* consider ACTION to be different from other CTCPs. Check
+      ignore as if it was a PRIV/CHAN. */
+	if (!strncasecmp (msg, "ACTION ", 7))
+	{
+		if (is_channel (serv, to))
+		{
+			/* treat a channel action as a CHAN */
+			if (ignore_check (word[1], IG_CHAN))
+				return;
+		} else
+		{
+			/* treat a private action as a PRIV */
+			if (ignore_check (word[1], IG_PRIV))
+				return;
+		}
+
+		/* but still let CTCP replies override it */
+		if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+			goto generic;
+
+		inbound_action (sess, to, nick, ip, msg + 7, FALSE, id);
+		return;
+	}
+
+	if (ignore_check (word[1], IG_CTCP))
+		return;
+
+	if (!strcasecmp (msg, "VERSION") && !prefs.hidever)
+	{
+		snprintf (outbuf, sizeof (outbuf), "VERSION xchat "PACKAGE_VERSION" %s",
+					 get_cpu_str ());
+		serv->p_nctcp (serv, nick, outbuf);
+	}
+
+	if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+	{
+		if (!strncasecmp (msg, "SOUND", 5))
+		{
+			po = strchr (word[5], '\001');
+			if (po)
+				po[0] = 0;
+
+			if (is_channel (sess->server, to))
+			{
+				chansess = find_channel (sess->server, to);
+				if (!chansess)
+					chansess = sess;
+
+				EMIT_SIGNAL (XP_TE_CTCPSNDC, chansess, word[5],
+								 nick, to, NULL, 0);
+			} else
+			{
+				EMIT_SIGNAL (XP_TE_CTCPSND, sess->server->front_session, word[5],
+								 nick, NULL, NULL, 0);
+			}
+
+			/* don't let IRCers specify path */
+#ifdef WIN32
+			if (strchr (word[5], '/') == NULL && strchr (word[5], '\\') == NULL)
+#else
+			if (strchr (word[5], '/') == NULL)
+#endif
+				sound_play (word[5], TRUE);
+			return;
+		}
+	}
+
+generic:
+	po = strchr (msg, '\001');
+	if (po)
+		po[0] = 0;
+
+	if (!is_channel (sess->server, to))
+	{
+		EMIT_SIGNAL (XP_TE_CTCPGEN, sess->server->front_session, msg, nick,
+						 NULL, NULL, 0);
+	} else
+	{
+		chansess = find_channel (sess->server, to);
+		if (!chansess)
+			chansess = sess;
+		EMIT_SIGNAL (XP_TE_CTCPGENC, chansess, msg, nick, to, NULL, 0);
+	}
+}
diff --git a/src/common/ctcp.h b/src/common/ctcp.h
new file mode 100644
index 00000000..9acd80e3
--- /dev/null
+++ b/src/common/ctcp.h
@@ -0,0 +1,6 @@
+#ifndef XCHAT_CTCP_H
+#define XCHAT_CTCP_H
+
+void ctcp_handle (session *sess, char *to, char *nick, char *ip, char *msg, char *word[], char *word_eol[], int id);
+
+#endif
diff --git a/src/common/dbus/Makefile.am b/src/common/dbus/Makefile.am
new file mode 100644
index 00000000..05ee9de6
--- /dev/null
+++ b/src/common/dbus/Makefile.am
@@ -0,0 +1,56 @@
+noinst_LIBRARIES = libxchatdbus.a
+libxchatdbus_a_SOURCES =			\
+	dbus-plugin.c				\
+	dbus-plugin.h				\
+	dbus-client.c				\
+	dbus-client.h
+
+EXTRA_DIST =				\
+	remote-object.xml		\
+	apps_xchat_url_handler.schemas	\
+	marshallers.list		\
+	example.py			\
+	org.xchat.service.service.in \
+	README
+
+BUILT_SOURCES =				\
+	marshallers.h			\
+	remote-object-glue.h
+
+CLEANFILES = $(BUILT_SOURCES)
+
+INCLUDES = $(COMMON_CFLAGS) $(DBUS_CFLAGS)
+
+noinst_PROGRAMS = example
+example_SOURCES = example.c 
+example_LDADD = $(DBUS_LIBS) $(GLIB_LIBS)
+
+remote-object-glue.h: remote-object.xml
+	$(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=remote_object --mode=glib-server --output=$@ $<
+
+marshallers.h: marshallers.list
+	$(LIBTOOL) --mode=execute $(GLIB_GENMARSHAL)  --header --body $< > $@
+
+# Dbus service file
+servicedir = $(DBUS_SERVICES_DIR)
+service_in_files = org.xchat.service.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+	@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
+
+if DO_GCONF
+GCONF_SCHEMA_CONFIG_SOURCE = `$(GCONFTOOL) --get-default-source`
+GCONF_SCHEMA_FILE_DIR = $(sysconfdir)/gconf/schemas
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
+schema_DATA = apps_xchat_url_handler.schemas
+install-data-local:
+	if test -z "$(DESTDIR)" ; then \
+		for p in $(schema_DATA) ; do \
+			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p; \
+		done \
+	fi
+else
+install-data-local:
+endif
diff --git a/src/common/dbus/README b/src/common/dbus/README
new file mode 100644
index 00000000..d61cf4e0
--- /dev/null
+++ b/src/common/dbus/README
@@ -0,0 +1,198 @@
+For more help you can see the xchat plugin interface documentation.
+http://www.xchat.org/docs/plugin20.html
+WARNING: The dbus interface may change in the future.
+
+You can use the "/org/xchat/Remote" object with interface "org.xchat.plugin",
+but his context can be changed by other clients at any moment and
+you may receive signal asked by other clients. So for more complex usage it's
+better to get your own remote object. Using "Connect" method on interface
+"org.xchat.connection"
+
+Available methods on org.xchat.connection interface:
+
+"Connect"
+  - Parameters:
+    - gchar*: filename
+    - gchar*: name
+    - gchar*: description
+    - gchar*: version
+  - Returns:
+    - gchar*: Your own object's path.
+
+"Disconnect"
+  No parameter, no return value. It frees your remote object.
+
+Available methods on org.xchat.plugin interface:
+
+"Command"
+  - Parameters:
+    - gchar*: the command name without the "/". (e.g. "nick pseudo")
+
+"Print"
+  - Parameters:
+    - gchar*: text to print on the xchat window.
+
+"FindContext"
+  - Parameters:
+    - gchar*: the server name. Can be NULL.
+    - gchar*: the channel name. Can be NULL.
+  - Returns:
+    - guint: context ID
+
+"GetContext"
+  - Returns:
+    - guint: current context's ID
+
+"SetContext"
+  - Parameters:
+    - guint: context ID to switch, returned by "FindContext" or "GetContext"
+  - Returns:
+    - gboolean: 1 for success, 0 for failure.
+
+"GetInfo"
+  - Parameters:
+    - gchar*: ID of the information you want.
+  - Returns:
+    - gchar*: information you requested.
+
+"GetPrefs"
+  - Parameters:
+    - gchar*: Setting name required.
+  - Returns:
+    - int: 0-Failed 1-Returned a string 2-Returned an Integer
+           3-Returned a Boolean.
+    - gchar*: the information requested if it's a string.
+    - int: the information requested if it's a integer or boolean.
+
+"HookCommand"
+  - Parameters:
+    - gchar*: Name of the command (without the forward slash).
+    - int: Priority of this command.
+    - gchar*: String of text to display when the user executes /help
+              for this command. May be NULL if you're lazy.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"HookServer"
+  - Parameters:
+    - gchar*: Name of the server event.
+    - int: Priority of this command.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"HookPrint"
+  - Parameters:
+    - gchar*: Name of the print event.
+    - int: Priority of this command.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"Unhook"
+  - Parameters:
+    - guint: ID of the hook to unhook.
+      (the return value of "HookCommand", "HookServer" or "HookPrint")
+
+"ListGet"
+  - Parameters:
+    - gchar*: The list name.
+  - Returns:
+    - guint: List ID.
+
+"ListNext"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+  - Returns:
+    - gboolean: says if there is no more item in the list.
+
+"ListStr"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - gchar*: The information requested.
+Warning: "context" attribut of "channels" list should be get with "ListInt"
+
+"ListInt"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - guint: The information requested.
+
+"ListTime"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - guint64: The information requested.
+
+"ListFields"
+  - Parameters:
+    - gchar*: The list name.
+  - Returns:
+    - gchar**: information names in this list.
+
+"ListFree"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+
+"EmitPrint"
+  - Parameters:
+    - gchar*: Text event to print.
+    - gchar**: NULL terminated array of string.
+  - Returns:
+    - gboolean: 1-Success 0-Failure.
+
+"Nickcmp"
+  - Parameters:
+    - gchar*: String to compare.
+    - gchar*: String to compare.
+  - Returns:
+    - int: An integer less than, equal to, or greater than zero if s1 is found,
+           respectively, to be less than, to match, or be greater than s2. 
+
+"Strip"
+  - Parameters:
+    - gchar*: String to strip.
+    - int: Length of the string (or -1 for NULL terminated).
+    - int: Bit-field of flags: 0-Strip mIRC colors, 1-Strip text attributes. 
+  - Returns:
+    - gchar*: striped string.
+
+"SendModes"
+  - Parameters:
+    - gchar**: NULL terminated array of targets (strings). The names of people
+               whom the action will be performed on.
+    - int: Maximum modes to send per line.
+    - gchar: Mode sign, '-' or '+'.
+    - gchar: Mode char, e.g. 'o' for Ops.
+
+
+Available signals:
+
+"ServerSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - gchar**: word_eol returned bu xchat.
+    - guint: the ID of the hook. (the return value of "HookServer").
+    - guint: the ID of the context where the event come from.
+
+"CommandSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - gchar**: word_eol returned bu xchat.
+    - guint: the ID of the hook. (the return value of "HookCommand").
+    - guint: the ID of the context where the event come from.
+
+"PrintSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - guint: the ID of the hook. (the return value of "HookPrint").
+    - guint: the ID of the context where the event come from.
+
+"UnloadSignal"
+  emited when the user asks to unload your program.
+  Please exit(0); when received !
diff --git a/src/common/dbus/apps_xchat_url_handler.schemas b/src/common/dbus/apps_xchat_url_handler.schemas
new file mode 100644
index 00000000..10ac948d
--- /dev/null
+++ b/src/common/dbus/apps_xchat_url_handler.schemas
@@ -0,0 +1,37 @@
+<gconfschemafile>
+	<schemalist>
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/command</key>
+			<applyto>/desktop/gnome/url-handlers/irc/command</applyto>
+			<owner>xchat</owner>
+			<type>string</type>
+			<default>xchat --existing --url=%u</default>
+			<locale name="C">
+				<short>The handler for "irc://" URLs</short>
+			</locale>
+		</schema>
+
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/enabled</key>
+			<applyto>/desktop/gnome/url-handlers/irc/enabled</applyto>
+			<owner>xchat</owner>
+			<type>bool</type>
+			<default>true</default>
+			<locale name="C">
+				<short>Set it at TRUE if you want it activated</short>
+			</locale>
+		</schema>
+
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/needs_terminal</key>
+			<applyto>/desktop/gnome/url-handlers/irc/needs_terminal</applyto>
+			<owner>xchat</owner>
+			<type>bool</type>
+			<default>false</default>
+			<locale name="C">
+				<short>Run xchat in a terminal?</short>
+			</locale>
+		</schema>
+
+	</schemalist>
+</gconfschemafile>
diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c
new file mode 100644
index 00000000..ac6bf6dc
--- /dev/null
+++ b/src/common/dbus/dbus-client.c
@@ -0,0 +1,118 @@
+/* dbus-client.c - XChat command-line options for D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#include <dbus/dbus-glib.h>
+#include "dbus-client.h"
+#include "../xchat.h"
+#include "../xchatc.h"
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_REMOTE "/org/xchat/Remote"
+#define DBUS_REMOTE_INTERFACE "org.xchat.plugin"
+
+static void
+write_error (char *message,
+	     GError **error)
+{
+	if (error == NULL || *error == NULL) {
+		return;
+	}
+	g_printerr ("%s: %s\n", message, (*error)->message);
+	g_clear_error (error);
+}
+
+void
+xchat_remote (void)
+/* TODO: dbus_g_connection_unref (connection) are commented because it makes
+ * dbus to crash. Fixed in dbus >=0.70 ?!?
+ * https://launchpad.net/distros/ubuntu/+source/dbus/+bug/54375
+ */
+{
+	DBusGConnection *connection;
+	DBusGProxy *dbus = NULL;
+	DBusGProxy *remote_object = NULL;
+	gboolean xchat_running;
+	GError *error = NULL;
+	char *command = NULL;
+
+	/* GnomeVFS >=2.15 uses D-Bus and threads, so threads should be
+	 * initialised before opening for the first time a D-Bus connection */
+	if (!g_thread_supported ()) {
+		g_thread_init (NULL);
+	}
+	dbus_g_thread_init ();
+
+	/* if there is nothing to do, return now. */
+	if (!arg_existing || !(arg_url || arg_command)) {
+		return;
+	}
+
+	arg_dont_autoconnect = TRUE;
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		write_error (_("Couldn't connect to session bus"), &error);
+		return;
+	}
+
+	/* Checks if xchat is already running */
+	dbus = dbus_g_proxy_new_for_name (connection,
+					  DBUS_SERVICE_DBUS,
+					  DBUS_PATH_DBUS,
+					  DBUS_INTERFACE_DBUS);
+	if (!dbus_g_proxy_call (dbus, "NameHasOwner", &error,
+				G_TYPE_STRING, DBUS_SERVICE,
+				G_TYPE_INVALID,
+				G_TYPE_BOOLEAN, &xchat_running,
+				G_TYPE_INVALID)) {
+		write_error (_("Failed to complete NameHasOwner"), &error);
+		xchat_running = FALSE;
+	}
+	g_object_unref (dbus);
+
+	if (!xchat_running) {
+		//dbus_g_connection_unref (connection);
+		return;
+	}
+
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   DBUS_REMOTE,
+						   DBUS_REMOTE_INTERFACE);
+
+	if (arg_url) {
+		command = g_strdup_printf ("url %s", arg_url);
+	} else if (arg_command) {
+		command = g_strdup (arg_command);
+	}
+
+	if (command) {
+		if (!dbus_g_proxy_call (remote_object, "Command",
+					&error,
+					G_TYPE_STRING, command,
+					G_TYPE_INVALID,G_TYPE_INVALID)) {
+			write_error (_("Failed to complete Command"), &error);
+		}
+		g_free (command);
+	}
+
+	exit (0);
+}
diff --git a/src/common/dbus/dbus-client.h b/src/common/dbus/dbus-client.h
new file mode 100644
index 00000000..cafc2b71
--- /dev/null
+++ b/src/common/dbus/dbus-client.h
@@ -0,0 +1,27 @@
+/* dbus-client.h - XChat command-line options for D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#ifndef __DBUS_PLUGIN_H__
+#define __DBUS_PLUGIN_H__
+
+void xchat_remote (void);
+
+#endif /* __DBUS_PLUGIN_H__ */
diff --git a/src/common/dbus/dbus-plugin.c b/src/common/dbus/dbus-plugin.c
new file mode 100644
index 00000000..012812cc
--- /dev/null
+++ b/src/common/dbus/dbus-plugin.c
@@ -0,0 +1,1087 @@
+/* dbus-plugin.c - xchat plugin for remote access using D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib/gi18n.h>
+#include "../xchat-plugin.h"
+
+#define PNAME _("remote access")
+#define PDESC _("plugin for remote access using DBUS")
+#define PVERSION ""
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_OBJECT_PATH "/org/xchat"
+
+static xchat_plugin *ph;
+static guint last_context_id = 0;
+static GList *contexts = NULL;
+static GHashTable *clients = NULL;
+static DBusGConnection *connection;
+
+typedef struct RemoteObject RemoteObject;
+typedef struct RemoteObjectClass RemoteObjectClass;
+
+GType Remote_object_get_type (void);
+
+struct RemoteObject
+{
+	GObject parent;
+
+	guint last_hook_id;
+	guint last_list_id;
+	xchat_context *context;
+	char *dbus_path;
+	char *filename;
+	GHashTable *hooks;
+	GHashTable *lists;
+	void *handle;
+};
+
+struct RemoteObjectClass
+{
+	GObjectClass parent;
+};
+
+typedef struct 
+{
+	guint id;
+	int return_value;
+	xchat_hook *hook;
+	RemoteObject *obj;
+} HookInfo;
+
+typedef struct
+{
+	guint id;
+	xchat_context *context;
+} ContextInfo;
+
+enum
+{
+	SERVER_SIGNAL,
+	COMMAND_SIGNAL,
+	PRINT_SIGNAL,
+	UNLOAD_SIGNAL,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define REMOTE_TYPE_OBJECT              (remote_object_get_type ())
+#define REMOTE_OBJECT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), REMOTE_TYPE_OBJECT, RemoteObject))
+#define REMOTE_OBJECT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), REMOTE_TYPE_OBJECT, RemoteObjectClass))
+#define REMOTE_IS_OBJECT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), REMOTE_TYPE_OBJECT))
+#define REMOTE_IS_OBJECT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), REMOTE_TYPE_OBJECT))
+#define REMOTE_OBJECT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), REMOTE_TYPE_OBJECT, RemoteObjectClass))
+#define REMOTE_OBJECT_ERROR (remote_object_error_quark ())
+#define REMOTE_TYPE_ERROR (remote_object_error_get_type ()) 
+
+G_DEFINE_TYPE (RemoteObject, remote_object, G_TYPE_OBJECT)
+
+/* Available services */
+
+static gboolean		remote_object_connect		(RemoteObject *obj,
+							 const char *filename,
+							 const char *name,
+							 const char *desc,
+							 const char *version,
+							 DBusGMethodInvocation *context);
+
+static gboolean		remote_object_disconnect	(RemoteObject *obj,
+							 DBusGMethodInvocation *context);
+
+static gboolean		remote_object_command		(RemoteObject *obj,
+							 const char *command,
+							 GError **error);
+
+static gboolean		remote_object_print		(RemoteObject *obj,
+							 const char *text,
+							 GError **error);
+
+static gboolean		remote_object_find_context	(RemoteObject *obj,
+							 const char *server,
+							 const char *channel,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_get_context	(RemoteObject *obj,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_set_context	(RemoteObject *obj,
+							 guint id,
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_print		(RemoteObject *obj,
+							 const char *text,
+							 GError **error);
+
+static gboolean		remote_object_get_info		(RemoteObject *obj,
+							 const char *id,
+							 char **ret_info,
+							 GError **error);
+
+static gboolean		remote_object_get_prefs		(RemoteObject *obj,
+							 const char *name,
+							 int *ret_type,
+							 char **ret_str,
+							 int *ret_int,
+							 GError **error);
+
+static gboolean		remote_object_hook_command	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 const char *help_text,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_hook_server	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_hook_print	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_unhook		(RemoteObject *obj,
+							 guint id,
+							 GError **error);
+
+static gboolean		remote_object_list_get		(RemoteObject *obj,
+							 const char *name,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_list_next		(RemoteObject *obj,
+							 guint id,
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_list_str		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 char **ret_str,
+							 GError **error);
+
+static gboolean		remote_object_list_int		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 int *ret_int,
+							 GError **error);
+
+static gboolean		remote_object_list_time		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 guint64 *ret_time,
+							 GError **error);
+
+static gboolean		remote_object_list_fields	(RemoteObject *obj,
+							 const char *name,
+							 char ***ret,
+							 GError **error);
+
+static gboolean		remote_object_list_free		(RemoteObject *obj,
+							 guint id,
+							 GError **error);
+
+static gboolean		remote_object_emit_print	(RemoteObject *obj,
+							 const char *event_name,
+							 const char *args[],
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_nickcmp		(RemoteObject *obj,
+							 const char *nick1,
+							 const char *nick2,
+							 int *ret,
+							 GError **error);
+
+static gboolean		remote_object_strip		(RemoteObject *obj,
+							 const char *str,
+							 int len,
+							 int flag,
+							 char **ret_str,
+							 GError **error);
+
+static gboolean		remote_object_send_modes	(RemoteObject *obj,
+							 const char *targets[],
+							 int modes_per_line,
+							 char sign,
+							 char mode,
+							 GError **error);
+
+#include "remote-object-glue.h"
+#include "marshallers.h"
+
+/* Useful functions */
+
+static char**		build_list			(char *word[]);
+static guint		context_list_find_id		(xchat_context *context);
+static xchat_context*	context_list_find_context	(guint id);
+
+/* Remote Object */
+
+static void
+hook_info_destroy (gpointer data)
+{
+	HookInfo *info = (HookInfo*)data;
+
+	if (info == NULL) {
+		return;
+	}
+	xchat_unhook (ph, info->hook);
+	g_free (info);
+}
+
+static void
+list_info_destroy (gpointer data)
+{
+	xchat_list_free (ph, (xchat_list*)data);
+}
+
+static void
+remote_object_finalize (GObject *obj)
+{
+	RemoteObject *self = (RemoteObject*)obj;
+
+	g_hash_table_destroy (self->lists);
+	g_hash_table_destroy (self->hooks);
+	g_free (self->dbus_path);
+	g_free (self->filename);
+	xchat_plugingui_remove (ph, self->handle);
+
+	G_OBJECT_CLASS (remote_object_parent_class)->finalize (obj);
+}
+
+static void
+remote_object_init (RemoteObject *obj)
+{
+	obj->hooks =
+		g_hash_table_new_full (g_int_hash,
+				       g_int_equal,
+				       NULL,
+				       hook_info_destroy);
+
+	obj->lists =
+		g_hash_table_new_full (g_int_hash,
+				       g_int_equal,
+				       g_free,
+				       list_info_destroy);
+	obj->dbus_path = NULL;
+	obj->filename = NULL;
+	obj->last_hook_id = 0;
+	obj->last_list_id = 0;
+	obj->context = xchat_get_context (ph);
+}
+
+static void
+remote_object_class_init (RemoteObjectClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = remote_object_finalize;
+
+	signals[SERVER_SIGNAL] =
+		g_signal_new ("server_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[COMMAND_SIGNAL] =
+		g_signal_new ("command_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[PRINT_SIGNAL] =
+		g_signal_new ("print_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      3, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[UNLOAD_SIGNAL] =
+		g_signal_new ("unload_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+/* Implementation of services */
+
+static gboolean
+remote_object_connect (RemoteObject *obj,
+		       const char *filename,
+		       const char *name,
+		       const char *desc,
+		       const char *version,
+		       DBusGMethodInvocation *context)
+{
+	static guint count = 0;
+	char *sender, *path;
+	RemoteObject *remote_object;
+	
+	sender = dbus_g_method_get_sender (context);
+	remote_object = g_hash_table_lookup (clients, sender);
+	if (remote_object != NULL) {
+		dbus_g_method_return (context, remote_object->dbus_path);
+		g_free (sender);
+		return TRUE;
+	}
+	path = g_strdup_printf (DBUS_OBJECT_PATH"/%d", count++);
+	remote_object = g_object_new (REMOTE_TYPE_OBJECT, NULL);
+	remote_object->dbus_path = path;
+	remote_object->filename = g_path_get_basename (filename);
+	remote_object->handle = xchat_plugingui_add (ph,
+						     remote_object->filename,
+						     name,
+						     desc,
+						     version, NULL);
+	dbus_g_connection_register_g_object (connection,
+					     path,
+					     G_OBJECT (remote_object));
+
+	g_hash_table_insert (clients,
+			     sender,
+			     remote_object);
+
+	dbus_g_method_return (context, path);
+	return TRUE;
+}
+
+static gboolean
+remote_object_disconnect (RemoteObject *obj,
+			  DBusGMethodInvocation *context)
+{
+	char *sender;
+	
+	sender = dbus_g_method_get_sender (context);
+	g_hash_table_remove (clients, sender);
+	g_free (sender);
+
+	dbus_g_method_return (context);
+	return TRUE;
+}
+
+static gboolean
+remote_object_command (RemoteObject *obj,
+		       const char *command,
+		       GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_command (ph, command);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_print (RemoteObject *obj,
+		     const char *text,
+		     GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_print (ph, text);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_find_context (RemoteObject *obj,
+			    const char *server,
+			    const char *channel,
+			    guint *ret_id,
+			    GError **error)
+{
+	xchat_context *context;
+
+	if (*server == '\0') {
+		server = NULL;
+	}
+	if (*channel == '\0') {
+		channel = NULL;
+	}
+	context = xchat_find_context (ph, server, channel);
+	*ret_id = context_list_find_id (context);
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_context (RemoteObject *obj,
+			   guint *ret_id,
+			   GError **error)
+{
+	*ret_id = context_list_find_id (obj->context);
+	return TRUE;
+}
+
+static gboolean
+remote_object_set_context (RemoteObject *obj,
+			   guint id,
+			   gboolean *ret,
+			   GError **error)
+{
+	xchat_context *context;
+	
+	context = context_list_find_context (id);
+	if (context == NULL) {
+		*ret = FALSE;
+		return TRUE;
+	}
+	obj->context = context;
+	*ret = TRUE;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_info (RemoteObject *obj,
+			const char *id,
+			char **ret_info,
+			GError **error)
+{
+	/* win_ptr is a GtkWindow* casted to char* and will crash
+	 * D-Bus if we send it as a string */
+	if (!xchat_set_context (ph, obj->context) ||
+	    g_str_equal (id, "win_ptr")) {
+		*ret_info = NULL;
+		return TRUE;
+	}
+	*ret_info = g_strdup (xchat_get_info (ph, id));
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_prefs (RemoteObject *obj,
+			 const char *name,
+			 int *ret_type,
+			 char **ret_str,
+			 int *ret_int,
+			 GError **error)
+{
+	const char *str;
+
+	if (!xchat_set_context (ph, obj->context)) {
+		*ret_type = 0;
+		return TRUE;
+	}
+	*ret_type = xchat_get_prefs (ph, name, &str, ret_int);
+	*ret_str = g_strdup (str);
+
+	return TRUE;
+}
+
+static int
+server_hook_cb (char *word[],
+		char *word_eol[],
+		void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+	char **arg2;
+
+	arg1 = build_list (word + 1);
+	arg2 = build_list (word_eol + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[SERVER_SIGNAL],
+		       0,
+		       arg1, arg2, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+	g_strfreev (arg2);
+  
+	return info->return_value;
+}
+
+static int
+command_hook_cb (char *word[],
+		 char *word_eol[],
+		 void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+	char **arg2;
+
+	arg1 = build_list (word + 1);
+	arg2 = build_list (word_eol + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[COMMAND_SIGNAL],
+		       0,
+		       arg1, arg2, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+	g_strfreev (arg2);
+  
+	return info->return_value;
+}
+
+static int
+print_hook_cb (char *word[],
+	       void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+
+	arg1 = build_list (word + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[PRINT_SIGNAL],
+		       0,
+		       arg1, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+  
+	return info->return_value;
+}
+
+static gboolean
+remote_object_hook_command (RemoteObject *obj,
+			    const char *name,
+			    int priority,
+			    const char *help_text,
+			    int return_value,
+			    guint *ret_id,
+			    GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_command (ph,
+					 name,
+					 priority,
+					 command_hook_cb,
+					 help_text,
+					 info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_hook_server (RemoteObject *obj,
+			   const char *name,
+			   int priority,
+			   int return_value,
+			   guint *ret_id,
+			   GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_server (ph,
+					name,
+					priority,
+					server_hook_cb,
+					info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_hook_print (RemoteObject *obj,
+			  const char *name,
+			  int priority,
+			  int return_value,
+			  guint *ret_id,
+			  GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_print (ph,
+				       name,
+				       priority,
+				       print_hook_cb,
+				       info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_unhook (RemoteObject *obj,
+		      guint id,
+		      GError **error)
+{
+	g_hash_table_remove (obj->hooks, &id);
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_get (RemoteObject *obj,
+			const char *name,
+			guint *ret_id,
+			GError **error)
+{
+	xchat_list *xlist;
+	guint *id;
+
+	if (!xchat_set_context (ph, obj->context)) {
+		*ret_id = 0;
+		return TRUE;
+	}
+	xlist = xchat_list_get (ph, name);
+	if (xlist == NULL) {
+		*ret_id = 0;
+		return TRUE;
+	}
+	id = g_new (guint, 1);
+	*id = ++obj->last_list_id;
+	*ret_id = *id;
+	g_hash_table_insert (obj->lists,
+			     id,
+			     xlist);
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_next	(RemoteObject *obj,
+			 guint id,
+			 gboolean *ret,
+			 GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL) {
+		*ret = FALSE;
+		return TRUE;
+	}
+	*ret = xchat_list_next (ph, xlist);
+
+	return TRUE;
+}			 
+
+static gboolean
+remote_object_list_str (RemoteObject *obj,
+			guint id,
+			const char *name,
+			char **ret_str,
+			GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL && !xchat_set_context (ph, obj->context)) {
+		*ret_str = NULL;
+		return TRUE;
+	}
+	if (g_str_equal (name, "context")) {
+		*ret_str = NULL;
+		return TRUE;
+	}
+	*ret_str = g_strdup (xchat_list_str (ph, xlist, name));
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_int (RemoteObject *obj,
+			guint id,
+			const char *name,
+			int *ret_int,
+			GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL && !xchat_set_context (ph, obj->context)) {
+		*ret_int = -1;
+		return TRUE;
+	}
+	if (g_str_equal (name, "context")) {
+		xchat_context *context;
+		context = (xchat_context*)xchat_list_str (ph, xlist, name);
+		*ret_int = context_list_find_id (context);
+	} else {
+		*ret_int = xchat_list_int (ph, xlist, name);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_time (RemoteObject *obj,
+			 guint id,
+			 const char *name,
+			 guint64 *ret_time,
+			 GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL) {
+		*ret_time = (guint64) -1;
+		return TRUE;
+	}
+	*ret_time = xchat_list_time (ph, xlist, name);
+	
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_fields (RemoteObject *obj,
+			   const char *name,
+			   char ***ret,
+			   GError **error)
+{
+	*ret = g_strdupv ((char**)xchat_list_fields (ph, name));
+	if (*ret == NULL) {
+		*ret = g_new0 (char*, 1);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_free (RemoteObject *obj,
+			 guint id,
+			 GError **error)
+{
+	g_hash_table_remove (obj->lists, &id);
+	return TRUE;
+}
+
+static gboolean
+remote_object_emit_print (RemoteObject *obj,
+			  const char *event_name,
+			  const char *args[],
+			  gboolean *ret,
+			  GError **error)
+{
+	const char *argv[4] = {NULL, NULL, NULL, NULL};
+	int i;
+	
+	for (i = 0; i < 4 && args[i] != NULL; i++) {
+		argv[i] = args[i];
+	}
+
+	*ret = xchat_set_context (ph, obj->context);
+	if (*ret) {
+		*ret = xchat_emit_print (ph, event_name, argv[0], argv[1],
+							 argv[2], argv[3]);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_nickcmp (RemoteObject *obj,
+		       const char *nick1,
+		       const char *nick2,
+		       int *ret,
+		       GError **error)
+{
+	xchat_set_context (ph, obj->context);
+	*ret = xchat_nickcmp (ph, nick1, nick2);
+	return TRUE;
+}
+
+static gboolean
+remote_object_strip (RemoteObject *obj,
+		     const char *str,
+		     int len,
+		     int flag,
+		     char **ret_str,
+		     GError **error)
+{
+	*ret_str = xchat_strip (ph, str, len, flag);
+	return TRUE;
+}
+
+static gboolean
+remote_object_send_modes (RemoteObject *obj,
+			  const char *targets[],
+			  int modes_per_line,
+			  char sign,
+			  char mode,
+			  GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_send_modes (ph, targets,
+				  g_strv_length ((char**)targets),
+				  modes_per_line,
+				  sign, mode);
+	}
+	return TRUE;
+}
+
+/* DBUS stuffs */
+
+static void
+name_owner_changed (DBusGProxy *driver_proxy,
+		    const char *name,
+		    const char *old_owner,
+		    const char *new_owner,
+		    void       *user_data)
+{
+	if (*new_owner == '\0') {
+		/* this name has vanished */
+		g_hash_table_remove (clients, name);
+	}
+}
+
+static gboolean
+init_dbus (void)
+{
+	DBusGProxy *proxy;
+	RemoteObject *remote;
+	guint request_name_result;
+	GError *error = NULL;
+
+	dbus_g_object_type_install_info (REMOTE_TYPE_OBJECT,
+					 &dbus_glib_remote_object_object_info);
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (connection == NULL) {
+		xchat_printf (ph, _("Couldn't connect to session bus: %s\n"),
+			      error->message);
+		g_error_free (error);
+		return FALSE;
+	}
+
+	proxy = dbus_g_proxy_new_for_name (connection,
+					   DBUS_SERVICE_DBUS,
+					   DBUS_PATH_DBUS,
+					   DBUS_INTERFACE_DBUS);
+
+	if (!dbus_g_proxy_call (proxy, "RequestName", &error,
+				G_TYPE_STRING, DBUS_SERVICE,
+				G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
+				G_TYPE_INVALID,
+				G_TYPE_UINT, &request_name_result,
+				G_TYPE_INVALID)) {
+		xchat_printf (ph, _("Failed to acquire %s: %s\n"),
+			      DBUS_SERVICE,
+			      error->message);
+		g_error_free (error);
+
+		return FALSE;
+	}
+
+	dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
+				 G_TYPE_STRING,
+				 G_TYPE_STRING,
+				 G_TYPE_STRING,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (proxy, "NameOwnerChanged", 
+				     G_CALLBACK (name_owner_changed),
+				     NULL, NULL);
+
+	remote = g_object_new (REMOTE_TYPE_OBJECT, NULL);
+	dbus_g_connection_register_g_object (connection,
+					     DBUS_OBJECT_PATH"/Remote",
+					     G_OBJECT (remote));
+
+	return TRUE;
+}
+
+/* xchat_plugin stuffs */
+
+static char**
+build_list (char *word[])
+{
+	guint i;
+	guint num = 0;
+	char **result;
+
+	if (word == NULL) {
+		return NULL;
+	}
+  
+	while (word[num] && word[num][0]) {
+		num++;
+	}
+	
+	result = g_new0 (char*, num + 1);
+	for (i = 0; i < num; i++) {
+		result[i] = g_strdup (word[i]);
+	}
+
+	return result;
+}
+
+static guint
+context_list_find_id (xchat_context *context)
+{
+	GList *l = NULL;
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->context == context) {
+			return ((ContextInfo*)l->data)->id;
+		}
+	}
+
+	return 0;
+}
+
+static xchat_context*
+context_list_find_context (guint id)
+{
+	GList *l = NULL;
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->id == id) {
+			return ((ContextInfo*)l->data)->context;
+		}
+	}
+
+	return NULL;
+}
+
+static int
+open_context_cb (char *word[],
+		 void *userdata)
+{
+	ContextInfo *info;
+	
+	info = g_new0 (ContextInfo, 1);
+	info->id = ++last_context_id;
+	info->context = xchat_get_context (ph);
+	contexts = g_list_prepend (contexts, info);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+close_context_cb (char *word[],
+		  void *userdata)
+{
+	GList *l;
+	xchat_context *context = xchat_get_context (ph);
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->context == context) {
+			g_free (l->data);
+			contexts = g_list_delete_link (contexts, l);
+			break;
+		}
+	}
+
+	return XCHAT_EAT_NONE;
+}
+
+static gboolean
+clients_find_filename_foreach (gpointer key,
+			       gpointer value,
+			       gpointer user_data)
+{
+	RemoteObject *obj = REMOTE_OBJECT (value);
+	return g_str_equal (obj->filename, (char*)user_data);
+}
+
+static int
+unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
+{
+	RemoteObject *obj;
+	
+	obj = g_hash_table_find (clients,
+				 clients_find_filename_foreach,
+				 word[2]);
+	if (obj != NULL) {
+		g_signal_emit (obj, 
+			       signals[UNLOAD_SIGNAL],
+			       0);
+		return XCHAT_EAT_ALL;
+	}
+	
+	return XCHAT_EAT_NONE;
+}
+
+int
+dbus_plugin_init (xchat_plugin *plugin_handle,
+		  char **plugin_name,
+		  char **plugin_desc,
+		  char **plugin_version,
+		  char *arg)
+{
+	ph = plugin_handle;
+	*plugin_name = PNAME;
+	*plugin_desc = PDESC;
+	*plugin_version = PVERSION;
+
+	if (init_dbus()) {
+		/*xchat_printf (ph, _("%s loaded successfully!\n"), PNAME);*/
+
+		clients = g_hash_table_new_full (g_str_hash,
+						 g_str_equal,
+						 g_free,
+						 g_object_unref);
+
+		xchat_hook_print (ph, "Open Context",
+				  XCHAT_PRI_NORM,
+				  open_context_cb,
+				  NULL);
+
+		xchat_hook_print (ph, "Close Context",
+				  XCHAT_PRI_NORM,
+				  close_context_cb,
+				  NULL);
+
+		xchat_hook_command (ph, "unload",
+				    XCHAT_PRI_HIGHEST,
+				    unload_plugin_cb, NULL, NULL);
+	}
+
+	return TRUE; 
+}
diff --git a/src/common/dbus/dbus-plugin.h b/src/common/dbus/dbus-plugin.h
new file mode 100644
index 00000000..bda8f61c
--- /dev/null
+++ b/src/common/dbus/dbus-plugin.h
@@ -0,0 +1,31 @@
+/* dbus-plugin.c - xchat plugin for remote access using DBUS
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#ifndef XCHAT_DBUS_PLUGIN_H
+#define XCHAT_DBUS_PLUGIN_H
+
+int	dbus_plugin_init	(xchat_plugin *plugin_handle,
+				 char **plugin_name,
+				 char **plugin_desc,
+				 char **plugin_version,
+				 char *arg);
+
+#endif
diff --git a/src/common/dbus/example.c b/src/common/dbus/example.c
new file mode 100644
index 00000000..1d072785
--- /dev/null
+++ b/src/common/dbus/example.c
@@ -0,0 +1,201 @@
+/* example.c - program to demonstrate some D-BUS stuffs.
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#include <config.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+#include "marshallers.h"
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_REMOTE "/org/xchat/Remote"
+#define DBUS_REMOTE_CONNECTION_INTERFACE "org.xchat.connection"
+#define DBUS_REMOTE_PLUGIN_INTERFACE "org.xchat.plugin"
+
+guint command_id;
+guint server_id;
+
+static void
+write_error (char *message,
+	     GError **error)
+{
+	if (error == NULL || *error == NULL) {
+		return;
+	}
+	g_printerr ("%s: %s\n", message, (*error)->message);
+	g_clear_error (error);
+}
+
+static void
+test_server_cb (DBusGProxy *proxy,
+		char *word[],
+		char *word_eol[],
+		guint hook_id,
+		guint context_id,
+		gpointer user_data)
+{
+	if (hook_id == server_id) {
+		g_print ("message: %s\n", word_eol[0]);
+	}
+}
+
+static void
+test_command_cb (DBusGProxy *proxy,
+		 char *word[],
+		 char *word_eol[],
+		 guint hook_id,
+		 guint context_id,
+		 gpointer user_data)
+{
+	GError *error = NULL;
+
+	if (hook_id == command_id) {
+		if (!dbus_g_proxy_call (proxy, "Unhook",
+					&error,
+					G_TYPE_UINT, hook_id,
+					G_TYPE_INVALID, G_TYPE_INVALID)) {
+			write_error ("Failed to complete unhook", &error);
+		}
+		/* Now if you write "/test blah" again in the xchat window
+		 * you'll get a "Unknown command" error message */
+		g_print ("test command received: %s\n", word_eol[1]);
+		if (!dbus_g_proxy_call (proxy, "Print",
+					&error,
+					G_TYPE_STRING, "test command succeed",
+					G_TYPE_INVALID,
+					G_TYPE_INVALID)) {
+			write_error ("Failed to complete Print", &error);
+		}
+	}
+}
+
+static void
+unload_cb (void)
+{
+	g_print ("Good bye !\n");
+	exit (EXIT_SUCCESS);
+}
+
+int
+main (int argc, char **argv)
+{
+	DBusGConnection *connection;
+	DBusGProxy *remote_object;
+	GMainLoop *mainloop;
+	gchar *path;
+	GError *error = NULL;
+
+	g_type_init ();
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (connection == NULL) {
+		write_error ("Couldn't connect to session bus", &error);
+		return EXIT_FAILURE;
+	}
+  
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   DBUS_REMOTE,
+						   DBUS_REMOTE_CONNECTION_INTERFACE);
+	if (!dbus_g_proxy_call (remote_object, "Connect",
+				&error,
+				G_TYPE_STRING, argv[0],
+				G_TYPE_STRING, "example",
+				G_TYPE_STRING, "Example of a D-Bus client",
+				G_TYPE_STRING, "1.0",
+				G_TYPE_INVALID,
+				G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		write_error ("Failed to complete Connect", &error);
+		return EXIT_FAILURE;
+	}
+	g_object_unref (remote_object);
+
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   path,
+						   DBUS_REMOTE_PLUGIN_INTERFACE);
+	g_free (path);
+
+	if (!dbus_g_proxy_call (remote_object, "HookCommand",
+				&error,
+				G_TYPE_STRING, "test",
+				G_TYPE_INT, 0,
+				G_TYPE_STRING, "Simple D-BUS example",
+				G_TYPE_INT, 1, G_TYPE_INVALID,
+				G_TYPE_UINT, &command_id, G_TYPE_INVALID)) {
+		write_error ("Failed to complete HookCommand", &error);
+		return EXIT_FAILURE;
+	}
+	g_print ("Command hook id=%d\n", command_id);
+
+	if (!dbus_g_proxy_call (remote_object, "HookServer",
+				&error,
+				G_TYPE_STRING, "RAW LINE",
+				G_TYPE_INT, 0,
+				G_TYPE_INT, 0, G_TYPE_INVALID,
+				G_TYPE_UINT, &server_id, G_TYPE_INVALID)) {
+		write_error ("Failed to complete HookServer", &error);
+		return EXIT_FAILURE;
+	}
+	g_print ("Server hook id=%d\n", server_id);
+
+	dbus_g_object_register_marshaller (
+		g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+		G_TYPE_NONE,
+		G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT,
+		G_TYPE_INVALID);
+
+	dbus_g_object_register_marshaller (
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE,
+		G_TYPE_INVALID);
+
+	dbus_g_proxy_add_signal (remote_object, "CommandSignal",
+				 G_TYPE_STRV,
+				 G_TYPE_STRV,
+				 G_TYPE_UINT,
+				 G_TYPE_UINT,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "CommandSignal",
+				     G_CALLBACK (test_command_cb),
+				     NULL, NULL);
+
+	dbus_g_proxy_add_signal (remote_object, "ServerSignal",
+				 G_TYPE_STRV,
+				 G_TYPE_STRV,
+				 G_TYPE_UINT,
+				 G_TYPE_UINT,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "ServerSignal",
+				     G_CALLBACK (test_server_cb),
+				     NULL, NULL);
+
+	dbus_g_proxy_add_signal (remote_object, "UnloadSignal",
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "UnloadSignal",
+				     G_CALLBACK (unload_cb),
+				     NULL, NULL);
+
+	/* Now you can write on the xchat windows: "/test arg1 arg2 ..." */
+	mainloop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (mainloop);
+
+	return EXIT_SUCCESS;
+}
diff --git a/src/common/dbus/example.py b/src/common/dbus/example.py
new file mode 100644
index 00000000..08bfdac3
--- /dev/null
+++ b/src/common/dbus/example.py
@@ -0,0 +1,28 @@
+#! /usr/bin/python
+
+import dbus
+
+bus = dbus.SessionBus()
+proxy = bus.get_object('org.xchat.service', '/org/xchat/Remote')
+remote = dbus.Interface(proxy, 'org.xchat.connection')
+path = remote.Connect ("example.py",
+		       "Python example",
+		       "Example of a D-Bus client written in python",
+		       "1.0")
+proxy = bus.get_object('org.xchat.service', path)
+xchat = dbus.Interface(proxy, 'org.xchat.plugin')
+
+channels = xchat.ListGet ("channels")
+while xchat.ListNext (channels):
+	name = xchat.ListStr (channels, "channel")
+	print "------- " + name + " -------"
+	xchat.SetContext (xchat.ListInt (channels, "context"))
+	xchat.EmitPrint ("Channel Message", ["John", "Hi there", "@"])
+	users = xchat.ListGet ("users")
+	while xchat.ListNext (users):
+		print "Nick: " + xchat.ListStr (users, "nick")
+	xchat.ListFree (users)
+xchat.ListFree (channels)
+
+print xchat.Strip ("\00312Blue\003 \002Bold!\002", -1, 1|2)
+
diff --git a/src/common/dbus/marshallers.list b/src/common/dbus/marshallers.list
new file mode 100644
index 00000000..bc3c4ad5
--- /dev/null
+++ b/src/common/dbus/marshallers.list
@@ -0,0 +1 @@
+VOID:POINTER,POINTER,UINT,UINT
diff --git a/src/common/dbus/org.xchat.service.service.in b/src/common/dbus/org.xchat.service.service.in
new file mode 100644
index 00000000..19490121
--- /dev/null
+++ b/src/common/dbus/org.xchat.service.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.xchat.service
+Exec=@bindir@/xchat
diff --git a/src/common/dbus/remote-object.xml b/src/common/dbus/remote-object.xml
new file mode 100644
index 00000000..88a8ae7c
--- /dev/null
+++ b/src/common/dbus/remote-object.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+
+  <interface name="org.xchat.connection">
+    <method name="Connect">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="filename" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="s" name="desc" direction="in"/>
+      <arg type="s" name="version" direction="in"/>
+      <arg type="s" name="path" direction="out"/>
+    </method>  
+    <method name="Disconnect">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    </method>
+  </interface>
+
+  <interface name="org.xchat.plugin">
+    <method name="Command">
+      <arg type="s" name="command" direction="in"/>
+    </method>
+    <method name="Print">
+      <arg type="s" name="text" direction="in"/>
+    </method>
+    <method name="FindContext">
+      <arg type="s" name="server" direction="in"/>
+      <arg type="s" name="channel" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="GetContext">
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="SetContext">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="GetInfo">
+      <arg type="s" name="id" direction="in"/>
+      <arg type="s" name="ret_info" direction="out"/>
+    </method>
+    <method name="GetPrefs">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="ret_type" direction="out"/>
+      <arg type="s" name="ret_str" direction="out"/>
+      <arg type="i" name="ret_int" direction="out"/>
+    </method>
+    <method name="HookCommand">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="s" name="help_text" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="HookServer">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="HookPrint">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="Unhook">
+      <arg type="u" name="id" direction="in"/>
+    </method>
+    <method name="ListGet">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="ListNext">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="ListStr">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="s" name="ret_str" direction="out"/>
+    </method>
+    <method name="ListInt">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="ret_int" direction="out"/>
+    </method>
+    <method name="ListTime">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="t" name="ret_time" direction="out"/>
+    </method>
+    <method name="ListFields">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="as" name="ret" direction="out"/>
+    </method>
+    <method name="ListFree">
+      <arg type="u" name="id" direction="in"/>
+    </method>
+    <method name="EmitPrint">
+      <arg type="s" name="event_name" direction="in"/>
+      <arg type="as" name="args" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="Nickcmp">
+      <arg type="s" name="nick1" direction="in"/>
+      <arg type="s" name="nick2" direction="in"/>
+      <arg type="i" name="ret" direction="out"/>
+    </method>
+    <method name="Strip">
+      <arg type="s" name="str" direction="in"/>
+      <arg type="i" name="len" direction="in"/>
+      <arg type="i" name="flag" direction="in"/>
+      <arg type="s" name="ret_str" direction="out"/>
+    </method>
+    <method name="SendModes">
+      <arg type="as" name="targets" direction="in"/>
+      <arg type="i" name="modes_per_line" direction="in"/>
+      <arg type="y" name="sign" direction="in"/>
+      <arg type="y" name="mode" direction="in"/>
+    </method>
+
+    <signal name="CommandSignal">
+      <arg type="as" name="word"/>
+      <arg type="as" name="word_eol"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="ServerSignal">
+      <arg type="as" name="word"/>
+      <arg type="as" name="word_eol"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="PrintSignal">
+      <arg type="as" name="word"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="UnloadSignal"/>
+  </interface>
+</node>
diff --git a/src/common/dcc.c b/src/common/dcc.c
new file mode 100644
index 00000000..8f289342
--- /dev/null
+++ b/src/common/dcc.c
@@ -0,0 +1,2587 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ * Copyright (C) 2006 Damjan Jovanovic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows
+ * Bernhard Valenti <bernhard.valenti@gmx.net> 2000-11-21: Fixed DCC send behind nat
+ *
+ * 2001-03-08 Added support for getting "dcc_ip" config parameter.
+ * Jim Seymour (jseymour@LinxNet.com)
+ */
+
+/* we only use 32 bits, but without this define, you get only 31! */
+#define _FILE_OFFSET_BITS 64
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "xchat.h"
+#include "util.h"
+#include "fe.h"
+#include "outbound.h"
+#include "inbound.h"
+#include "network.h"
+#include "plugin.h"
+#include "server.h"
+#include "text.h"
+#include "url.h"
+#include "xchatc.h"
+
+#ifdef USE_DCC64
+#define BIG_STR_TO_INT(x) strtoull(x,NULL,10)
+#else
+#define BIG_STR_TO_INT(x) strtoul(x,NULL,10)
+#endif
+
+static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" };
+
+struct dccstat_info dccstat[] = {
+	{N_("Waiting"), 1 /*black */ },
+	{N_("Active"), 12 /*cyan */ },
+	{N_("Failed"), 4 /*red */ },
+	{N_("Done"), 3 /*green */ },
+	{N_("Connect"), 1 /*black */ },
+	{N_("Aborted"), 4 /*red */ },
+};
+
+static int dcc_global_throttle;	/* 0x1 = sends, 0x2 = gets */
+/*static*/ int dcc_sendcpssum, dcc_getcpssum;
+
+static struct DCC *new_dcc (void);
+static void dcc_close (struct DCC *dcc, int dccstat, int destroy);
+static gboolean dcc_send_data (GIOChannel *, GIOCondition, struct DCC *);
+static gboolean dcc_read (GIOChannel *, GIOCondition, struct DCC *);
+static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc);
+
+static int new_id()
+{
+	static int id = 0;
+	if (id == 0)
+	{
+		/* start the first ID at a random number for pseudo security */
+		/* 1 - 255 */
+		id = RAND_INT(255) + 1;
+		/* ignore overflows, since it can go to 2 billion */
+	}
+	return id++;
+}
+
+static double
+timeval_diff (GTimeVal *greater,
+				 GTimeVal *less)
+{
+	long usecdiff;
+	double result;
+	
+	result = greater->tv_sec - less->tv_sec;
+	usecdiff = (long) greater->tv_usec - less->tv_usec;
+	result += (double) usecdiff / 1000000;
+	
+	return result;
+}
+
+static void
+dcc_unthrottle (struct DCC *dcc)
+{
+	/* don't unthrottle here, but delegate to funcs */
+	if (dcc->type == TYPE_RECV)
+		dcc_read (NULL, 0, dcc);
+	else
+		dcc_send_data (NULL, 0, dcc);
+}
+
+static void
+dcc_calc_cps (struct DCC *dcc)
+{
+	GTimeVal now;
+	int oldcps;
+	double timediff, startdiff;
+	int glob_throttle_bit, wasthrottled;
+	int *cpssum, glob_limit;
+	DCC_SIZE pos, posdiff;
+
+	g_get_current_time (&now);
+
+	/* the pos we use for sends is an average
+		between pos and ack */
+	if (dcc->type == TYPE_SEND)
+	{
+		/* carefull to avoid 32bit overflow */
+		pos = dcc->pos - ((dcc->pos - dcc->ack) / 2);
+		glob_throttle_bit = 0x1;
+		cpssum = &dcc_sendcpssum;
+		glob_limit = prefs.dcc_global_max_send_cps;
+	}
+	else
+	{
+		pos = dcc->pos;
+		glob_throttle_bit = 0x2;
+		cpssum = &dcc_getcpssum;
+		glob_limit = prefs.dcc_global_max_get_cps;
+	}
+
+	if (!dcc->firstcpstv.tv_sec && !dcc->firstcpstv.tv_usec)
+		dcc->firstcpstv = now;
+	else
+	{
+		startdiff = timeval_diff (&now, &dcc->firstcpstv);
+		if (startdiff < 1)
+			startdiff = 1;
+		else if (startdiff > CPS_AVG_WINDOW)
+			startdiff = CPS_AVG_WINDOW;
+
+		timediff = timeval_diff (&now, &dcc->lastcpstv);
+		if (timediff > startdiff)
+			timediff = startdiff = 1;
+
+		posdiff = pos - dcc->lastcpspos;
+		oldcps = dcc->cps;
+		dcc->cps = ((double) posdiff / timediff) * (timediff / startdiff) +
+			(double) dcc->cps * (1.0 - (timediff / startdiff));
+
+		*cpssum += dcc->cps - oldcps;
+	}
+
+	dcc->lastcpspos = pos;
+	dcc->lastcpstv = now;
+
+	/* now check cps against set limits... */
+	wasthrottled = dcc->throttled;
+
+	/* check global limits first */
+	dcc->throttled &= ~0x2;
+	if (glob_limit > 0 && *cpssum >= glob_limit)
+	{
+		dcc_global_throttle |= glob_throttle_bit;
+		if (dcc->maxcps >= 0)
+			dcc->throttled |= 0x2;
+	}
+	else
+		dcc_global_throttle &= ~glob_throttle_bit;
+
+	/* now check per-connection limit */
+	if (dcc->maxcps > 0 && dcc->cps > dcc->maxcps)
+		dcc->throttled |= 0x1;
+	else
+		dcc->throttled &= ~0x1;
+
+	/* take action */
+	if (wasthrottled && !dcc->throttled)
+		dcc_unthrottle (dcc);
+}
+
+static void
+dcc_remove_from_sum (struct DCC *dcc)
+{
+	if (dcc->dccstat != STAT_ACTIVE)
+		return;
+	if (dcc->type == TYPE_SEND)
+		dcc_sendcpssum -= dcc->cps;
+	else if (dcc->type == TYPE_RECV)
+		dcc_getcpssum -= dcc->cps;
+}
+
+gboolean
+is_dcc (struct DCC *dcc)
+{
+	GSList *list = dcc_list;
+	while (list)
+	{
+		if (list->data == dcc)
+			return TRUE;
+		list = list->next;
+	}
+	return FALSE;
+}
+
+/* this is called from xchat.c:xchat_misc_checks() every 1 second. */
+
+void
+dcc_check_timeouts (void)
+{
+	struct DCC *dcc;
+	time_t tim = time (0);
+	GSList *next, *list = dcc_list;
+
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		next = list->next;
+
+		switch (dcc->dccstat)
+		{
+		case STAT_ACTIVE:
+			dcc_calc_cps (dcc);
+			fe_dcc_update (dcc);
+
+			if (dcc->type == TYPE_SEND || dcc->type == TYPE_RECV)
+			{
+				if (prefs.dccstalltimeout > 0)
+				{
+					if (!dcc->throttled
+						&& tim - dcc->lasttime > prefs.dccstalltimeout)
+					{
+						EMIT_SIGNAL (XP_TE_DCCSTALL, dcc->serv->front_session,
+										 dcctypes[dcc->type],
+										 file_part (dcc->file), dcc->nick, NULL, 0);
+						dcc_close (dcc, STAT_ABORTED, FALSE);
+					}
+				}
+			}
+			break;
+		case STAT_QUEUED:
+			if (dcc->type == TYPE_SEND || dcc->type == TYPE_CHATSEND)
+			{
+				if (tim - dcc->offertime > prefs.dcctimeout)
+				{
+					if (prefs.dcctimeout > 0)
+					{
+						EMIT_SIGNAL (XP_TE_DCCTOUT, dcc->serv->front_session,
+										 dcctypes[dcc->type],
+										 file_part (dcc->file), dcc->nick, NULL, 0);
+						dcc_close (dcc, STAT_ABORTED, FALSE);
+					}
+				}
+			}
+			break;
+		case STAT_DONE:
+		case STAT_FAILED:
+		case STAT_ABORTED:
+			if (prefs.dcc_remove)
+				dcc_close (dcc, 0, TRUE);
+			break;
+		}
+		list = next;
+	}
+}
+
+static int
+dcc_lookup_proxy (char *host, struct sockaddr_in *addr)
+{
+	struct hostent *h;
+	static char *cache_host = NULL;
+	static guint32 cache_addr;
+
+	/* too lazy to thread this, so we cache results */
+	if (cache_host)
+	{
+		if (strcmp (host, cache_host) == 0)
+		{
+			memcpy (&addr->sin_addr, &cache_addr, 4);
+			return TRUE;
+		}
+		free (cache_host);
+		cache_host = NULL;
+	}
+
+	h = gethostbyname (host);
+	if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL)
+	{
+		memcpy (&addr->sin_addr, h->h_addr, 4);
+		memcpy (&cache_addr, h->h_addr, 4);
+		cache_host = strdup (host);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+#define DCC_USE_PROXY() (prefs.proxy_host[0] && prefs.proxy_type>0 && prefs.proxy_type<5 && prefs.proxy_use!=1)
+
+static int
+dcc_connect_sok (struct DCC *dcc)
+{
+	int sok;
+	struct sockaddr_in addr;
+
+	sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (sok == -1)
+		return -1;
+
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_family = AF_INET;
+	if (DCC_USE_PROXY ())
+	{
+		if (!dcc_lookup_proxy (prefs.proxy_host, &addr))
+		{
+			closesocket (sok);
+			return -1;
+		}
+		addr.sin_port = htons (prefs.proxy_port);
+	}
+	else
+	{
+		addr.sin_port = htons (dcc->port);
+		addr.sin_addr.s_addr = htonl (dcc->addr);
+	}
+
+	set_nonblocking (sok);
+	connect (sok, (struct sockaddr *) &addr, sizeof (addr));
+
+	return sok;
+}
+
+static void
+dcc_close (struct DCC *dcc, int dccstat, int destroy)
+{
+	if (dcc->wiotag)
+	{
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+	}
+
+	if (dcc->iotag)
+	{
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+	}
+
+	if (dcc->sok != -1)
+	{
+		closesocket (dcc->sok);
+		dcc->sok = -1;
+	}
+
+	dcc_remove_from_sum (dcc);
+
+	if (dcc->fp != -1)
+	{
+		close (dcc->fp);
+		dcc->fp = -1;
+
+		if(dccstat == STAT_DONE)
+		{
+			/* if we just completed a dcc recieve, move the */
+			/* completed file to the completed directory */
+			if(dcc->type == TYPE_RECV)
+			{			
+				/* mgl: change this to use destfile_fs for correctness and to */
+				/* handle the case where dccwithnick is set */
+				move_file_utf8 (prefs.dccdir, prefs.dcc_completed_dir, 
+									 file_part (dcc->destfile), prefs.dccpermissions);
+			}
+
+		}
+	}
+
+	dcc->dccstat = dccstat;
+	if (dcc->dccchat)
+	{
+		free (dcc->dccchat);
+		dcc->dccchat = NULL;
+	}
+
+	if (destroy)
+	{
+		dcc_list = g_slist_remove (dcc_list, dcc);
+		fe_dcc_remove (dcc);
+		if (dcc->proxy)
+			free (dcc->proxy);
+		if (dcc->file)
+			free (dcc->file);
+		if (dcc->destfile)
+			g_free (dcc->destfile);
+		if (dcc->destfile_fs)
+			g_free (dcc->destfile_fs);
+		free (dcc->nick);
+		free (dcc);
+		return;
+	}
+
+	fe_dcc_update (dcc);
+}
+
+void
+dcc_abort (session *sess, struct DCC *dcc)
+{
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_QUEUED:
+		case STAT_CONNECTING:
+		case STAT_ACTIVE:
+			dcc_close (dcc, STAT_ABORTED, FALSE);
+			switch (dcc->type)
+			{
+			case TYPE_CHATSEND:
+			case TYPE_CHATRECV:
+				EMIT_SIGNAL (XP_TE_DCCCHATABORT, sess, dcc->nick, NULL, NULL,
+								 NULL, 0);
+				break;
+			case TYPE_SEND:
+				EMIT_SIGNAL (XP_TE_DCCSENDABORT, sess, dcc->nick,
+								 file_part (dcc->file), NULL, NULL, 0);
+				break;
+			case TYPE_RECV:
+				EMIT_SIGNAL (XP_TE_DCCRECVABORT, sess, dcc->nick,
+								 dcc->file, NULL, NULL, 0);
+			}
+			break;
+		default:
+			dcc_close (dcc, 0, TRUE);
+		}
+	}
+}
+
+void
+dcc_notify_kill (struct server *serv)
+{
+	struct server *replaceserv = 0;
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	if (serv_list)
+		replaceserv = (struct server *) serv_list->data;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->serv == serv)
+			dcc->serv = replaceserv;
+		list = list->next;
+	}
+}
+
+struct DCC *
+dcc_write_chat (char *nick, char *text)
+{
+	struct DCC *dcc;
+	int len;
+
+	dcc = find_dcc (nick, "", TYPE_CHATRECV);
+	if (!dcc)
+		dcc = find_dcc (nick, "", TYPE_CHATSEND);
+	if (dcc && dcc->dccstat == STAT_ACTIVE)
+	{
+		len = strlen (text);
+		tcp_send_real (NULL, dcc->sok, dcc->serv->encoding, dcc->serv->using_irc,
+							text, len);
+		send (dcc->sok, "\n", 1, 0);
+		dcc->size += len;
+		fe_dcc_update (dcc);
+		return dcc;
+	}
+	return 0;
+}
+
+/* returns: 0 - ok
+				1 - the dcc is closed! */
+
+static int
+dcc_chat_line (struct DCC *dcc, char *line)
+{
+	session *sess;
+	char *word[PDIWORDS];
+	char *po;
+	char *utf;
+	char *conv;
+	int ret, i;
+	int len;
+	gsize utf_len;
+	char portbuf[32];
+
+	len = strlen (line);
+	if (dcc->serv->using_cp1255)
+		len++;	/* include the NUL terminator */
+
+	if (dcc->serv->using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */
+		utf = NULL;
+	else if (dcc->serv->encoding == NULL)     /* system */
+		utf = g_locale_to_utf8 (line, len, NULL, &utf_len, NULL);
+	else
+		utf = g_convert (line, len, "UTF-8", dcc->serv->encoding, 0, &utf_len, 0);
+
+	if (utf)
+	{
+		line = utf;
+		len = utf_len;
+	}
+
+	if (dcc->serv->using_cp1255 && len > 0)
+		len--;
+
+	/* we really need valid UTF-8 now */
+	conv = text_validate (&line, &len);
+
+	sess = find_dialog (dcc->serv, dcc->nick);
+	if (!sess)
+		sess = dcc->serv->front_session;
+
+	sprintf (portbuf, "%d", dcc->port);
+
+	word[0] = "DCC Chat Text";
+	word[1] = net_ip (dcc->addr);
+	word[2] = portbuf;
+	word[3] = dcc->nick;
+	word[4] = line;
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	ret = plugin_emit_print (sess, word);
+
+	/* did the plugin close it? */
+	if (!g_slist_find (dcc_list, dcc))
+	{
+		if (utf)
+			g_free (utf);
+		if (conv)
+			g_free (conv);
+		return 1;
+	}
+
+	/* did the plugin eat the event? */
+	if (ret)
+	{
+		if (utf)
+			g_free (utf);
+		if (conv)
+			g_free (conv);
+		return 0;
+	}
+
+	url_check_line (line, len);
+
+	if (line[0] == 1 && !strncasecmp (line + 1, "ACTION", 6))
+	{
+		po = strchr (line + 8, '\001');
+		if (po)
+			po[0] = 0;
+		inbound_action (sess, dcc->serv->nick, dcc->nick, "", line + 8, FALSE, FALSE);
+	} else
+	{
+		inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE);
+	}
+	if (utf)
+		g_free (utf);
+	if (conv)
+		g_free (conv);
+	return 0;
+}
+
+static gboolean
+dcc_read_chat (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int i, len, dead;
+	char portbuf[32];
+	char lbuf[2050];
+
+	while (1)
+	{
+		if (dcc->throttled)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			return FALSE;
+		}
+
+		if (!dcc->iotag)
+			dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+
+		len = recv (dcc->sok, lbuf, sizeof (lbuf) - 2, 0);
+		if (len < 1)
+		{
+			if (len < 0)
+			{
+				if (would_block ())
+					return TRUE;
+			}
+			sprintf (portbuf, "%d", dcc->port);
+			EMIT_SIGNAL (XP_TE_DCCCHATF, dcc->serv->front_session, dcc->nick,
+							 net_ip (dcc->addr), portbuf,
+							 errorstring ((len < 0) ? sock_error () : 0), 0);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+		i = 0;
+		lbuf[len] = 0;
+		while (i < len)
+		{
+			switch (lbuf[i])
+			{
+			case '\r':
+				break;
+			case '\n':
+				dcc->dccchat->linebuf[dcc->dccchat->pos] = 0;
+				dead = dcc_chat_line (dcc, dcc->dccchat->linebuf);
+
+				if (dead || !dcc->dccchat) /* the dcc has been closed, don't use (DCC *)! */
+					return TRUE;
+
+				dcc->pos += dcc->dccchat->pos;
+				dcc->dccchat->pos = 0;
+				fe_dcc_update (dcc);
+				break;
+			default:
+				dcc->dccchat->linebuf[dcc->dccchat->pos] = lbuf[i];
+				if (dcc->dccchat->pos < (sizeof (dcc->dccchat->linebuf) - 1))
+					dcc->dccchat->pos++;
+			}
+			i++;
+		}
+	}
+}
+
+static void
+dcc_calc_average_cps (struct DCC *dcc)
+{
+	time_t sec;
+
+	sec = time (0) - dcc->starttime;
+	if (sec < 1)
+		sec = 1;
+	if (dcc->type == TYPE_SEND)
+		dcc->cps = (dcc->ack - dcc->resumable) / sec;
+	else
+		dcc->cps = (dcc->pos - dcc->resumable) / sec;
+}
+
+static void
+dcc_send_ack (struct DCC *dcc)
+{
+	/* send in 32-bit big endian */
+	guint32 pos = htonl (dcc->pos & 0xffffffff);
+	send (dcc->sok, (char *) &pos, 4, 0);
+}
+
+static gboolean
+dcc_read (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char *old;
+	char buf[4096];
+	int n;
+	gboolean need_ack = FALSE;
+
+	if (dcc->fp == -1)
+	{
+
+		/* try to create the download dir (even if it exists, no harm) */
+		mkdir_utf8 (prefs.dccdir);
+
+		if (dcc->resumable)
+		{
+			dcc->fp = open (dcc->destfile_fs, O_WRONLY | O_APPEND | OFLAGS);
+			dcc->pos = dcc->resumable;
+			dcc->ack = dcc->resumable;
+		} else
+		{
+			if (access (dcc->destfile_fs, F_OK) == 0)
+			{
+				n = 0;
+				do
+				{
+					n++;
+					snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile_fs, n);
+				}
+				while (access (buf, F_OK) == 0);
+
+				g_free (dcc->destfile_fs);
+				dcc->destfile_fs = g_strdup (buf);
+
+				old = dcc->destfile;
+				dcc->destfile = xchat_filename_to_utf8 (buf, -1, 0, 0, 0);
+
+				EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session,
+								 old, dcc->destfile, NULL, NULL, 0);
+				g_free (old);
+			}
+			dcc->fp =
+				open (dcc->destfile_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT,
+						prefs.dccpermissions);
+		}
+	}
+	if (dcc->fp == -1)
+	{
+		/* the last executed function is open(), errno should be valid */
+		EMIT_SIGNAL (XP_TE_DCCFILEERR, dcc->serv->front_session, dcc->destfile,
+						 errorstring (errno), NULL, NULL, 0);
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	while (1)
+	{
+		if (dcc->throttled)
+		{
+			if (need_ack)
+				dcc_send_ack (dcc);
+
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			return FALSE;
+		}
+
+		if (!dcc->iotag)
+			dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc);
+
+		n = recv (dcc->sok, buf, sizeof (buf), 0);
+		if (n < 1)
+		{
+			if (n < 0)
+			{
+				if (would_block ())
+				{
+					if (need_ack)
+						dcc_send_ack (dcc);
+					return TRUE;
+				}
+			}
+			EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file,
+							 dcc->destfile, dcc->nick,
+							 errorstring ((n < 0) ? sock_error () : 0), 0);
+			/* send ack here? but the socket is dead */
+			/*if (need_ack)
+				dcc_send_ack (dcc);*/
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		if (write (dcc->fp, buf, n) == -1) /* could be out of hdd space */
+		{
+			EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file,
+							 dcc->destfile, dcc->nick, errorstring (errno), 0);
+			if (need_ack)
+				dcc_send_ack (dcc);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		dcc->lasttime = time (0);
+		dcc->pos += n;
+		need_ack = TRUE;	/* send ack when we're done recv()ing */
+
+		if (dcc->pos >= dcc->size)
+		{
+			dcc_send_ack (dcc);
+			dcc_close (dcc, STAT_DONE, FALSE);
+			dcc_calc_average_cps (dcc);	/* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */
+			sprintf (buf, "%d", dcc->cps);
+			EMIT_SIGNAL (XP_TE_DCCRECVCOMP, dcc->serv->front_session,
+							 dcc->file, dcc->destfile, dcc->nick, buf, 0);
+			return TRUE;
+		}
+	}
+}
+
+static void
+dcc_open_query (server *serv, char *nick)
+{
+	if (prefs.autodialog)
+		open_query (serv, nick, FALSE);
+}
+
+static gboolean
+dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int er;
+	struct sockaddr_in addr;
+	
+#ifdef WIN32
+	if (condition & G_IO_ERR)
+	{
+		int len;
+
+		/* find the last errno for this socket */
+		len = sizeof (er);
+		getsockopt (dcc->sok, SOL_SOCKET, SO_ERROR, (char *)&er, &len);
+		EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session,
+						 dcctypes[dcc->type], dcc->nick, errorstring (er),
+						 NULL, 0);
+		dcc->dccstat = STAT_FAILED;
+		fe_dcc_update (dcc);
+		return FALSE;
+	}
+
+#else
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_port = htons (dcc->port);
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl (dcc->addr);
+
+	/* check if it's already connected; This always fails on winXP */
+	if (connect (dcc->sok, (struct sockaddr *) &addr, sizeof (addr)) != 0)
+	{
+		er = sock_error ();
+		if (er != EISCONN)
+		{
+			EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session,
+							 dcctypes[dcc->type], dcc->nick, errorstring (er),
+							 NULL, 0);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return FALSE;
+		}
+	}
+#endif
+	
+	return TRUE;
+}
+
+static gboolean
+dcc_connect_finished (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char host[128];
+
+	if (dcc->iotag)
+	{
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+	}
+
+	if (!dcc_did_connect (source, condition, dcc))
+		return TRUE;
+
+	dcc->dccstat = STAT_ACTIVE;
+	snprintf (host, sizeof host, "%s:%d", net_ip (dcc->addr), dcc->port);
+
+	switch (dcc->type)
+	{
+	case TYPE_RECV:
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONRECV, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+	case TYPE_SEND:
+		/* passive send */
+		dcc->fastsend = prefs.fastdccsend;
+		if (dcc->fastsend)
+			dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE, dcc_send_data, dcc);
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_ack, dcc);
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+	case TYPE_CHATSEND:	/* pchat */
+		dcc_open_query (dcc->serv, dcc->nick);
+	case TYPE_CHATRECV:	/* normal chat */
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+		dcc->dccchat = malloc (sizeof (struct dcc_chat));
+		dcc->dccchat->pos = 0;
+		EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session,
+						 dcc->nick, host, NULL, NULL, 0);
+		break;
+	}
+	dcc->starttime = time (0);
+	dcc->lasttime = dcc->starttime;
+	fe_dcc_update (dcc);
+
+	return TRUE;
+}
+
+static gboolean
+read_proxy (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (proxy->bufferused < proxy->buffersize)
+	{
+		int ret = recv (dcc->sok, &proxy->buffer[proxy->bufferused],
+						proxy->buffersize - proxy->bufferused, 0);
+		if (ret > 0)
+			proxy->bufferused += ret;
+		else
+		{
+			if (would_block ())
+				return FALSE;
+			else
+			{
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				if (dcc->iotag)
+				{
+					fe_input_remove (dcc->iotag);
+					dcc->iotag = 0;
+				}
+				return FALSE;
+			}
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+write_proxy (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (proxy->bufferused < proxy->buffersize)
+	{
+		int ret = send (dcc->sok, &proxy->buffer[proxy->bufferused],
+						proxy->buffersize - proxy->bufferused, 0);
+		if (ret >= 0)
+			proxy->bufferused += ret;
+		else
+		{
+			if (would_block ())
+				return FALSE;
+			else
+			{
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				if (dcc->wiotag)
+				{
+					fe_input_remove (dcc->wiotag);
+					dcc->wiotag = 0;
+				}
+				return FALSE;
+			}
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+proxy_read_line (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (1)
+	{
+		proxy->buffersize = proxy->bufferused + 1;
+		if (!read_proxy (dcc))
+			return FALSE;
+		if (proxy->buffer[proxy->bufferused - 1] == '\n'
+			|| proxy->bufferused == MAX_PROXY_BUFFER)
+		{
+			proxy->buffer[proxy->bufferused - 1] = 0;
+			return TRUE;
+		}
+	}
+}
+
+static gboolean
+dcc_wingate_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	if (proxy->phase == 0)
+	{
+		proxy->buffersize = snprintf ((char*) proxy->buffer, MAX_PROXY_BUFFER,
+										"%s %d\r\n", net_ip(dcc->addr),
+										dcc->port);
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_wingate_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+	if (proxy->phase == 1)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		dcc_connect_finished (source, 0, dcc);
+	}
+	return TRUE;
+}
+
+struct sock_connect
+{
+	char version;
+	char type;
+	guint16 port;
+	guint32 address;
+	char username[10];
+};
+static gboolean
+dcc_socks_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+
+	if (proxy->phase == 0)
+	{
+		struct sock_connect sc;
+		sc.version = 4;
+		sc.type = 1;
+		sc.port = htons (dcc->port);
+		sc.address = htonl (dcc->addr);
+		strncpy (sc.username, prefs.username, 9);
+		memcpy (proxy->buffer, &sc, sizeof (sc));
+		proxy->buffersize = 8 + strlen (sc.username) + 1;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		proxy->buffersize = 8;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+		if (proxy->buffer[1] == 90)
+			dcc_connect_finished (source, 0, dcc);
+		else
+		{
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+		}
+	}
+
+	return TRUE;
+}
+
+struct sock5_connect1
+{
+        char version;
+        char nmethods;
+        char method;
+};
+static gboolean
+dcc_socks5_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0];
+
+	if (proxy->phase == 0)
+	{
+		struct sock5_connect1 sc1;
+
+		sc1.version = 5;
+		sc1.nmethods = 1;
+		sc1.method = 0;
+		if (auth)
+			sc1.method = 2;
+		memcpy (proxy->buffer, &sc1, 3);
+		proxy->buffersize = 3;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		proxy->buffersize = 2;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+
+		/* did the server say no auth required? */
+		if (proxy->buffer[0] == 5 && proxy->buffer[1] == 0)
+			auth = 0;
+
+		/* Set up authentication I/O */
+		if (auth)
+		{
+			int len_u=0, len_p=0;
+
+			/* authentication sub-negotiation (RFC1929) */
+			if ( proxy->buffer[0] != 5 || proxy->buffer[1] != 2 )  /* UPA not supported by server */
+			{
+				PrintText (dcc->serv->front_session, "SOCKS\tServer doesn't support UPA authentication.\n");
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				return TRUE;
+			}
+
+			memset (proxy->buffer, 0, MAX_PROXY_BUFFER);
+
+			/* form the UPA request */
+			len_u = strlen (prefs.proxy_user);
+			len_p = strlen (prefs.proxy_pass);
+			proxy->buffer[0] = 1;
+			proxy->buffer[1] = len_u;
+			memcpy (proxy->buffer + 2, prefs.proxy_user, len_u);
+			proxy->buffer[2 + len_u] = len_p;
+			memcpy (proxy->buffer + 3 + len_u, prefs.proxy_pass, len_p);
+
+			proxy->buffersize = 3 + len_u + len_p;
+			proxy->bufferused = 0;
+			dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+										dcc_socks5_proxy_traverse, dcc);
+			++proxy->phase;
+		}
+		else
+		{
+			if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0)
+			{
+				PrintText (dcc->serv->front_session, "SOCKS\tAuthentication required but disabled in settings.\n");
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				return TRUE;
+			}
+			proxy->phase += 2;
+		}
+	}
+	
+	if (proxy->phase == 3)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->buffersize = 2;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 4)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		if (dcc->iotag)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+		}
+		if (proxy->buffer[1] != 0)
+		{
+			PrintText (dcc->serv->front_session, "SOCKS\tAuthentication failed. "
+							 "Is username and password correct?\n");
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 5)
+	{
+		proxy->buffer[0] = 5;	/* version (socks 5) */
+		proxy->buffer[1] = 1;	/* command (connect) */
+		proxy->buffer[2] = 0;	/* reserved */
+		proxy->buffer[3] = 1;	/* address type (IPv4) */
+		proxy->buffer[4] = (dcc->addr >> 24) & 0xFF;	/* IP address */
+		proxy->buffer[5] = (dcc->addr >> 16) & 0xFF;
+		proxy->buffer[6] = (dcc->addr >> 8) & 0xFF;
+		proxy->buffer[7] = (dcc->addr & 0xFF);
+		proxy->buffer[8] = (dcc->port >> 8) & 0xFF;		/* port */
+		proxy->buffer[9] = (dcc->port & 0xFF);
+		proxy->buffersize = 10;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 6)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->buffersize = 4;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 7)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			if (proxy->buffer[1] == 2)
+				PrintText (dcc->serv->front_session, "SOCKS\tProxy refused to connect to host (not allowed).\n");
+			else
+				PrintTextf (dcc->serv->front_session, "SOCKS\tProxy failed to connect to host (error %d).\n", proxy->buffer[1]);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		switch (proxy->buffer[3])
+		{
+			case 1: proxy->buffersize += 6; break;
+			case 3: proxy->buffersize += 1; break;
+			case 4: proxy->buffersize += 18; break;
+		};
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 8)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		/* handle domain name case */
+		if (proxy->buffer[3] == 3)
+		{
+			proxy->buffersize = 5 + proxy->buffer[4] + 2;
+		}
+		/* everything done? */
+		if (proxy->bufferused == proxy->buffersize)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			dcc_connect_finished (source, 0, dcc);
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+dcc_http_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+
+	if (proxy->phase == 0)
+	{
+		char buf[256];
+		char auth_data[128];
+		char auth_data2[68];
+		int n, n2;
+
+		n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n",
+                                          net_ip(dcc->addr), dcc->port);
+		if (prefs.proxy_auth)
+		{
+			n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s",
+							prefs.proxy_user, prefs.proxy_pass);
+			base64_encode (auth_data, auth_data2, n2);
+			n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data);
+		}
+		n += snprintf (buf+n, sizeof (buf)-n, "\r\n");
+		proxy->buffersize = n;
+		proxy->bufferused = 0;
+		memcpy (proxy->buffer, buf, proxy->buffersize);
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_http_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+										dcc_http_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!proxy_read_line (dcc))
+			return TRUE;
+		/* "HTTP/1.0 200 OK" */
+		if (proxy->bufferused < 12 ||
+			 memcmp (proxy->buffer, "HTTP/", 5) || memcmp (proxy->buffer + 9, "200", 3))
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			PrintText (dcc->serv->front_session, proxy->buffer);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		proxy->bufferused = 0;
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 3)
+	{
+		while (1)
+		{
+			/* read until blank line */
+			if (proxy_read_line (dcc))
+			{
+				if (proxy->bufferused < 1 ||
+					(proxy->bufferused == 2 && proxy->buffer[0] == '\r'))
+				{
+					break;
+				}
+				if (proxy->bufferused > 1)
+					PrintText (dcc->serv->front_session, proxy->buffer);
+				proxy->bufferused = 0;
+			}
+			else
+				return TRUE;
+		}
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+		dcc_connect_finished (source, 0, dcc);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	fe_input_remove (dcc->iotag);
+	dcc->iotag = 0;
+
+	if (!dcc_did_connect (source, condition, dcc))
+		return TRUE;
+
+	dcc->proxy = malloc (sizeof (struct proxy_state));
+	if (!dcc->proxy)
+	{
+		dcc->dccstat = STAT_FAILED;
+		fe_dcc_update (dcc);
+		return TRUE;
+	}
+	memset (dcc->proxy, 0, sizeof (struct proxy_state));
+
+	switch (prefs.proxy_type)
+	{
+		case 1: return dcc_wingate_proxy_traverse (source, condition, dcc);
+		case 2: return dcc_socks_proxy_traverse (source, condition, dcc);
+		case 3: return dcc_socks5_proxy_traverse (source, condition, dcc);
+		case 4: return dcc_http_proxy_traverse (source, condition, dcc);
+	}
+	return TRUE;
+}
+
+static int dcc_listen_init (struct DCC *, struct session *);
+
+static void
+dcc_connect (struct DCC *dcc)
+{
+	int ret;
+	char tbuf[400];
+
+	if (dcc->dccstat == STAT_CONNECTING)
+		return;
+	dcc->dccstat = STAT_CONNECTING;
+
+	if (dcc->pasvid && dcc->port == 0)
+	{
+		/* accepted a passive dcc send */
+		ret = dcc_listen_init (dcc, dcc->serv->front_session);
+		if (!ret)
+		{
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return;
+		}
+		/* possible problems with filenames containing spaces? */
+		if (dcc->type == TYPE_RECV)
+			snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ?
+					"DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" :
+					"DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file,
+					dcc->addr, dcc->port, dcc->size, dcc->pasvid);
+		else
+			snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d",
+				dcc->addr, dcc->port, dcc->pasvid);
+		dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+	}
+	else
+	{
+		dcc->sok = dcc_connect_sok (dcc);
+		if (dcc->sok == -1)
+		{
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return;
+		}
+		if (DCC_USE_PROXY ())
+			dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc);
+		else
+			dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc);
+	}
+	
+	fe_dcc_update (dcc);
+}
+
+static gboolean
+dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char *buf;
+	int len, sent, sok = dcc->sok;
+
+	if (prefs.dcc_blocksize < 1) /* this is too little! */
+		prefs.dcc_blocksize = 1024;
+
+	if (prefs.dcc_blocksize > 102400)	/* this is too much! */
+		prefs.dcc_blocksize = 102400;
+
+	if (dcc->throttled)
+	{
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		return FALSE;
+	}
+
+	if (!dcc->fastsend)
+	{
+		if (dcc->ack < dcc->pos)
+			return TRUE;
+	}
+	else if (!dcc->wiotag)
+		dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc);
+
+	buf = malloc (prefs.dcc_blocksize);
+	if (!buf)
+		return TRUE;
+
+	lseek (dcc->fp, dcc->pos, SEEK_SET);
+	len = read (dcc->fp, buf, prefs.dcc_blocksize);
+	if (len < 1)
+		goto abortit;
+	sent = send (sok, buf, len, 0);
+
+	if (sent < 0 && !(would_block ()))
+	{
+abortit:
+		free (buf);
+		EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session,
+						 file_part (dcc->file), dcc->nick,
+						 errorstring (sock_error ()), NULL, 0);
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	if (sent > 0)
+	{
+		dcc->pos += sent;
+		dcc->lasttime = time (0);
+	}
+
+	/* have we sent it all yet? */
+	if (dcc->pos >= dcc->size)
+	{
+		/* it's all sent now, so remove the WRITE/SEND handler */
+		if (dcc->wiotag)
+		{
+			fe_input_remove (dcc->wiotag);
+			dcc->wiotag = 0;
+		}
+	}
+
+	free (buf);
+
+	return TRUE;
+}
+
+static gboolean
+dcc_handle_new_ack (struct DCC *dcc)
+{
+	guint32 ack;
+	char buf[16];
+	gboolean done = FALSE;
+
+	memcpy (&ack, dcc->ack_buf, 4);
+	dcc->ack = ntohl (ack);
+
+	/* this could mess up when xfering >32bit files */
+	if (dcc->size <= 0xffffffff)
+	{
+		/* fix for BitchX */
+		if (dcc->ack < dcc->resumable)
+			dcc->ackoffset = TRUE;
+		if (dcc->ackoffset)
+			dcc->ack += dcc->resumable;
+	}
+
+	/* DCC complete check */
+	if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff))
+	{
+		dcc->ack = dcc->size;	/* force 100% ack for >4 GB */
+		dcc_close (dcc, STAT_DONE, FALSE);
+		dcc_calc_average_cps (dcc);	/* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */
+		sprintf (buf, "%d", dcc->cps);
+		EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session,
+						 file_part (dcc->file), dcc->nick, buf, NULL, 0);
+		done = TRUE;
+	}
+	else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff)))
+	{
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+	}
+
+#ifdef USE_DCC64
+	/* take the top 32 of "bytes send" and bottom 32 of "ack" */
+	dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) |
+					(dcc->ack & 0xffffffff);
+	/* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */
+#endif
+
+	return done;
+}
+
+static gboolean
+dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int len;
+
+	while (1)
+	{
+		/* try to fill up 4 bytes */
+		len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0);
+		if (len < 1)
+		{
+			if (len < 0)
+			{
+				if (would_block ())	/* ok - keep waiting */
+					return TRUE;
+			}
+			EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session,
+							 file_part (dcc->file), dcc->nick,
+							 errorstring ((len < 0) ? sock_error () : 0), NULL, 0);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		dcc->ack_pos += len;
+		if (dcc->ack_pos >= 4)
+		{
+			dcc->ack_pos = 0;
+			if (dcc_handle_new_ack (dcc))
+				return TRUE;
+		}
+		/* loop again until would_block() returns true */
+	}
+}
+
+static gboolean
+dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char host[128];
+	struct sockaddr_in CAddr;
+	int sok;
+	socklen_t len;
+
+	len = sizeof (CAddr);
+	sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len);
+	fe_input_remove (dcc->iotag);
+	dcc->iotag = 0;
+	closesocket (dcc->sok);
+	if (sok < 0)
+	{
+		dcc->sok = -1;
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	set_nonblocking (sok);
+	dcc->sok = sok;
+	dcc->addr = ntohl (CAddr.sin_addr.s_addr);
+
+	if (dcc->pasvid)
+		return dcc_connect_finished (NULL, 0, dcc);
+
+	dcc->dccstat = STAT_ACTIVE;
+	dcc->lasttime = dcc->starttime = time (0);
+	dcc->fastsend = prefs.fastdccsend;
+
+	snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port);
+
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+		if (dcc->fastsend)
+			dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc);
+		dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc);
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+
+	case TYPE_CHATSEND:
+		dcc_open_query (dcc->serv, dcc->nick);
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+		dcc->dccchat = malloc (sizeof (struct dcc_chat));
+		dcc->dccchat->pos = 0;
+		EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session,
+						 dcc->nick, host, NULL, NULL, 0);
+		break;
+	}
+
+	fe_dcc_update (dcc);
+
+	return TRUE;
+}
+
+guint32
+dcc_get_my_address (void)	/* the address we'll tell the other person */
+{
+	struct hostent *dns_query;
+	guint32 addr = 0;
+
+	if (prefs.ip_from_server && prefs.dcc_ip)
+		addr = prefs.dcc_ip;
+	else if (prefs.dcc_ip_str[0])
+	{
+	   dns_query = gethostbyname ((const char *) prefs.dcc_ip_str);
+
+	   if (dns_query != NULL &&
+	       dns_query->h_length == 4 &&
+	       dns_query->h_addr_list[0] != NULL)
+		{
+			/*we're offered at least one IPv4 address: we take the first*/
+			addr = *((guint32*) dns_query->h_addr_list[0]);
+		}
+	}
+
+	return addr;
+}
+
+static int
+dcc_listen_init (struct DCC *dcc, session *sess)
+{
+	guint32 my_addr;
+	struct sockaddr_in SAddr;
+	int i, bindretval = -1;
+	socklen_t len;
+
+	dcc->sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (dcc->sok == -1)
+		return FALSE;
+
+	memset (&SAddr, 0, sizeof (struct sockaddr_in));
+
+	len = sizeof (SAddr);
+	getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len);
+
+	SAddr.sin_family = AF_INET;
+
+	/*if local_ip is specified use that*/
+	if (prefs.local_ip != 0xffffffff)
+	{
+		my_addr = prefs.local_ip;
+		SAddr.sin_addr.s_addr = prefs.local_ip;
+	}
+	/*otherwise use the default*/
+	else
+		my_addr = SAddr.sin_addr.s_addr;
+
+	/*if we have a valid portrange try to use that*/
+	if (prefs.first_dcc_send_port > 0)
+	{
+		SAddr.sin_port = 0;
+		i = 0;
+		while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) &&
+				(bindretval == -1))
+		{
+			SAddr.sin_port = htons (prefs.first_dcc_send_port + i);
+			i++;
+			/*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/
+			bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr));
+		}
+
+		/* with a small port range, reUseAddr is needed */
+		len = 1;
+		setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len));
+
+	} else
+	{
+		/* try random port */
+		SAddr.sin_port = 0;
+		bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr));
+	}
+
+	if (bindretval == -1)
+	{
+		/* failed to bind */
+		PrintText (sess, "Failed to bind to any address or port.\n");
+		return FALSE;
+	}
+
+	len = sizeof (SAddr);
+	getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len);
+
+	dcc->port = ntohs (SAddr.sin_port);
+
+	/*if we have a dcc_ip, we use that, so the remote client can connect*/
+	/*else we try to take an address from dcc_ip_str*/
+	/*if something goes wrong we tell the client to connect to our LAN ip*/
+	dcc->addr = dcc_get_my_address ();
+
+	/*if nothing else worked we use the address we bound to*/
+	if (dcc->addr == 0)
+	   dcc->addr = my_addr;
+
+	dcc->addr = ntohl (dcc->addr);
+
+	set_nonblocking (dcc->sok);
+	listen (dcc->sok, 1);
+	set_blocking (dcc->sok);
+
+	dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc);
+
+	return TRUE;
+}
+
+static struct session *dccsess;
+static char *dccto;				  /* lame!! */
+static int dccmaxcps;
+static int recursive = FALSE;
+
+static void
+dcc_send_wild (char *file)
+{
+	dcc_send (dccsess, dccto, file, dccmaxcps, 0);
+}
+
+void
+dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive)
+{
+	char outbuf[512];
+	struct stat st;
+	struct DCC *dcc;
+	char *file_fs;
+
+	/* this is utf8 */
+	file = expand_homedir (file);
+
+	if (!recursive && (strchr (file, '*') || strchr (file, '?')))
+	{
+		char path[256];
+		char wild[256];
+		char *path_fs;	/* local filesystem encoding */
+
+		safe_strcpy (wild, file_part (file), sizeof (wild));
+		path_part (file, path, sizeof (path));
+		if (path[0] != '/' || path[1] != '\0')
+			path[strlen (path) - 1] = 0;	/* remove trailing slash */
+
+		dccsess = sess;
+		dccto = to;
+		dccmaxcps = maxcps;
+
+		free (file);
+
+		/* for_files() will use opendir, so we need local FS encoding */
+		path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0);
+		if (path_fs)
+		{
+			recursive = TRUE;
+			for_files (path_fs, wild, dcc_send_wild);
+			recursive = FALSE;
+			g_free (path_fs);
+		}
+
+		return;
+	}
+
+	dcc = new_dcc ();
+	if (!dcc)
+		return;
+	dcc->file = file;
+	dcc->maxcps = maxcps;
+
+	/* get the local filesystem encoding */
+	file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0);
+
+	if (stat (file_fs, &st) != -1)
+	{
+
+#ifndef USE_DCC64
+		if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U)
+		{
+			PrintText (sess, "Cannot send files larger than 4 GB.\n");
+			goto xit;
+		}
+#endif
+
+		if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1)
+		{
+			PrintText (sess, "Cannot send directories or empty files.\n");
+			goto xit;
+		}
+
+		dcc->starttime = dcc->offertime = time (0);
+		dcc->serv = sess->server;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->size = st.st_size;
+		dcc->type = TYPE_SEND;
+		dcc->fp = open (file_fs, OFLAGS | O_RDONLY);
+		if (dcc->fp != -1)
+		{
+			g_free (file_fs);
+			if (passive || dcc_listen_init (dcc, sess))
+			{
+				char havespaces = 0;
+				file = dcc->file;
+				while (*file)
+				{
+					if (*file == ' ')
+					{
+						if (prefs.dcc_send_fillspaces)
+				    		*file = '_';
+					  	else
+					   	havespaces = 1;
+					}
+					file++;
+				}
+				dcc->nick = strdup (to);
+				if (prefs.autoopendccsendwindow)
+				{
+					if (fe_dcc_open_send_win (TRUE))	/* already open? add */
+						fe_dcc_add (dcc);
+				} else
+					fe_dcc_add (dcc);
+
+				if (passive)
+				{
+					dcc->pasvid = new_id();
+					snprintf (outbuf, sizeof (outbuf), (havespaces) ?
+							"DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" :
+							"DCC SEND %s 199 0 %"DCC_SFMT" %d",
+							file_part (dcc->file),
+							dcc->size, dcc->pasvid);
+				} else
+				{
+					snprintf (outbuf, sizeof (outbuf), (havespaces) ?
+							"DCC SEND \"%s\" %u %d %"DCC_SFMT :
+							"DCC SEND %s %u %d %"DCC_SFMT,
+							file_part (dcc->file), dcc->addr,
+							dcc->port, dcc->size);
+				}
+				sess->server->p_ctcp (sess->server, to, outbuf);
+
+				EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file),
+								 to, dcc->file, NULL, 0);
+			} else
+			{
+				dcc_close (dcc, 0, TRUE);
+			}
+			return;
+		}
+	}
+	PrintTextf (sess, _("Cannot access %s\n"), dcc->file);
+	PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno));
+xit:
+	g_free (file_fs);
+	dcc_close (dcc, 0, TRUE);
+}
+
+static struct DCC *
+find_dcc_from_id (int id, int type)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->pasvid == id &&
+			 dcc->dccstat == STAT_QUEUED && dcc->type == type)
+		return dcc;
+		list = list->next;
+	}
+	return 0;
+}
+
+static struct DCC *
+find_dcc_from_port (int port, int type)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->port == port &&
+			 dcc->dccstat == STAT_QUEUED && dcc->type == type)
+			return dcc;
+		list = list->next;
+	}
+	return 0;
+}
+
+struct DCC *
+find_dcc (char *nick, char *file, int type)
+{
+	GSList *list = dcc_list;
+	struct DCC *dcc;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (nick == NULL || !rfc_casecmp (nick, dcc->nick))
+		{
+			if (type == -1 || dcc->type == type)
+			{
+				if (!file[0])
+					return dcc;
+				if (!strcasecmp (file, file_part (dcc->file)))
+					return dcc;
+				if (!strcasecmp (file, dcc->file))
+					return dcc;
+			}
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+/* called when we receive a NICK change from server */
+
+void
+dcc_change_nick (struct server *serv, char *oldnick, char *newnick)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->serv == serv)
+		{
+			if (!serv->p_cmp (dcc->nick, oldnick))
+			{
+				if (dcc->nick)
+					free (dcc->nick);
+				dcc->nick = strdup (newnick);
+			}
+		}
+		list = list->next;
+	}
+}
+
+/* is the destination file the same? new_dcc is not opened yet */
+
+static int
+is_same_file (struct DCC *dcc, struct DCC *new_dcc)
+{
+	struct stat st_a, st_b;
+
+	/* if it's the same filename, must be same */
+	if (strcmp (dcc->destfile, new_dcc->destfile) == 0)
+		return TRUE;
+
+	/* now handle case-insensitive Filesystems: HFS+, FAT */
+#ifdef WIN32
+#warning no win32 implementation - behaviour may be unreliable
+#else
+	/* this fstat() shouldn't really fail */
+	if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1)
+		return FALSE;
+	if (stat (new_dcc->destfile_fs, &st_b) == -1)
+		return FALSE;
+
+	/* same inode, same device, same file! */
+	if (st_a.st_ino == st_b.st_ino &&
+		 st_a.st_dev == st_b.st_dev)
+		return TRUE;
+#endif
+
+	return FALSE;
+}
+
+static int
+is_resumable (struct DCC *dcc)
+{
+	dcc->resumable = 0;
+
+	/* Check the file size */
+	if (access (dcc->destfile_fs, W_OK) == 0)
+	{
+		struct stat st;
+
+		if (stat (dcc->destfile_fs, &st) != -1)
+		{
+			if (st.st_size < dcc->size)
+			{
+				dcc->resumable = st.st_size;
+				dcc->pos = st.st_size;
+			}
+			else
+				dcc->resume_error = 2;
+		} else
+		{
+			dcc->resume_errno = errno;
+			dcc->resume_error = 1;
+		}
+	} else
+	{
+		dcc->resume_errno = errno;
+		dcc->resume_error = 1;
+	}
+
+	/* Now verify that this DCC is not already in progress from someone else */
+
+	if (dcc->resumable)
+	{
+		GSList *list = dcc_list;
+		struct DCC *d;
+		while (list)
+		{
+			d = list->data;
+			if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED &&
+				 d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED)
+			{
+				if (d != dcc && is_same_file (d, dcc))
+				{
+					dcc->resume_error = 3;	/* dccgui.c uses it */
+					dcc->resumable = 0;
+					dcc->pos = 0;
+					break;
+				}
+			}
+			list = list->next;
+		}
+	}
+
+	return dcc->resumable;
+}
+
+void
+dcc_get (struct DCC *dcc)
+{
+	switch (dcc->dccstat)
+	{
+	case STAT_QUEUED:
+		if (dcc->type != TYPE_CHATSEND)
+		{
+			if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable)
+			{
+				dcc_resume (dcc);
+			}
+			else
+			{
+				dcc->resumable = 0;
+				dcc->pos = 0;
+				dcc_connect (dcc);
+			}
+		}
+		break;
+	case STAT_DONE:
+	case STAT_FAILED:
+	case STAT_ABORTED:
+		dcc_close (dcc, 0, TRUE);
+		break;
+	}
+}
+
+/* for "Save As..." dialog */
+
+void
+dcc_get_with_destfile (struct DCC *dcc, char *file)
+{
+	g_free (dcc->destfile);
+	dcc->destfile = g_strdup (file);	/* utf-8 */
+
+	/* get the local filesystem encoding */
+	g_free (dcc->destfile_fs);
+	dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0);
+
+	/* since destfile changed, must check resumability again */
+	is_resumable (dcc);
+
+	dcc_get (dcc);
+}
+
+void
+dcc_get_nick (struct session *sess, char *nick)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (!sess->server->p_cmp (nick, dcc->nick))
+		{
+			if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
+			{
+				dcc->resumable = 0;
+				dcc->pos = 0;
+				dcc->ack = 0;
+				dcc_connect (dcc);
+				return;
+			}
+		}
+		list = list->next;
+	}
+	if (sess)
+		EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0);
+}
+
+static struct DCC *
+new_dcc (void)
+{
+	struct DCC *dcc = malloc (sizeof (struct DCC));
+	if (!dcc)
+		return 0;
+	memset (dcc, 0, sizeof (struct DCC));
+	dcc->sok = -1;
+	dcc->fp = -1;
+	dcc_list = g_slist_prepend (dcc_list, dcc);
+	return (dcc);
+}
+
+void
+dcc_chat (struct session *sess, char *nick, int passive)
+{
+	char outbuf[512];
+	struct DCC *dcc;
+
+	dcc = find_dcc (nick, "", TYPE_CHATSEND);
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_ACTIVE:
+		case STAT_QUEUED:
+		case STAT_CONNECTING:
+			EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case STAT_ABORTED:
+		case STAT_FAILED:
+			dcc_close (dcc, 0, TRUE);
+		}
+	}
+	dcc = find_dcc (nick, "", TYPE_CHATRECV);
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_QUEUED:
+			dcc_connect (dcc);
+			break;
+		case STAT_FAILED:
+		case STAT_ABORTED:
+			dcc_close (dcc, 0, TRUE);
+		}
+		return;
+	}
+	/* offer DCC CHAT */
+
+	dcc = new_dcc ();
+	if (!dcc)
+		return;
+	dcc->starttime = dcc->offertime = time (0);
+	dcc->serv = sess->server;
+	dcc->dccstat = STAT_QUEUED;
+	dcc->type = TYPE_CHATSEND;
+	dcc->nick = strdup (nick);
+	if (passive || dcc_listen_init (dcc, sess))
+	{
+		if (prefs.autoopendccchatwindow)
+		{
+			if (fe_dcc_open_chat_win (TRUE))	/* already open? add only */
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+
+		if (passive)
+		{
+			dcc->pasvid = new_id ();
+			snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d",
+						 dcc->port, dcc->pasvid);
+		} else
+		{
+			snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d",
+						 dcc->addr, dcc->port);
+		}
+		dcc->serv->p_ctcp (dcc->serv, nick, outbuf);
+		EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0);
+	} else
+	{
+		dcc_close (dcc, 0, TRUE);
+	}
+}
+
+static void
+dcc_malformed (struct session *sess, char *nick, char *data)
+{
+	EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0);
+}
+
+int
+dcc_resume (struct DCC *dcc)
+{
+	char tbuf[500];
+
+	if (dcc->dccstat == STAT_QUEUED && dcc->resumable)
+	{
+		dcc->resume_sent = 1;
+		/* filename contains spaces? Quote them! */
+		snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ?
+					  "DCC RESUME \"%s\" %d %"DCC_SFMT :
+					  "DCC RESUME %s %d %"DCC_SFMT,
+					  dcc->file, dcc->port, dcc->resumable);
+
+		if (dcc->pasvid)
+ 			sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid);
+
+		dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void
+dcc_confirm_send (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_get (dcc);
+}
+
+static void
+dcc_deny_send (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_abort (dcc->serv->front_session, dcc);
+}
+
+static void
+dcc_confirm_chat (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_connect (dcc);
+}
+
+static void
+dcc_deny_chat (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_abort (dcc->serv->front_session, dcc);
+}
+
+static struct DCC *
+dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid)
+{
+	struct DCC *dcc;
+
+	dcc = new_dcc ();
+	if (dcc)
+	{
+		dcc->serv = sess->server;
+		dcc->type = TYPE_CHATRECV;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->addr = addr;
+		dcc->port = port;
+		dcc->pasvid = pasvid;
+		dcc->nick = strdup (nick);
+		dcc->starttime = time (0);
+
+		EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick,
+						 NULL, NULL, NULL, 0);
+
+		if (prefs.autoopendccchatwindow)
+		{
+			if (fe_dcc_open_chat_win (TRUE))	/* already open? add only */
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+
+		if (prefs.autodccchat == 1)
+			dcc_connect (dcc);
+		else if (prefs.autodccchat == 2)
+		{
+			char buff[128];
+			snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick);
+			fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc);
+		}
+	}
+
+	return dcc;
+}
+
+static struct DCC *
+dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid)
+{
+	struct DCC *dcc;
+	char tbuf[512];
+
+	dcc = new_dcc ();
+	if (dcc)
+	{
+		dcc->file = strdup (file);
+
+		dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) +
+										  strlen (file) + 4);
+
+		strcpy (dcc->destfile, prefs.dccdir);
+		if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/')
+			strcat (dcc->destfile, "/");
+		if (prefs.dccwithnick)
+		{
+#ifdef WIN32
+			char *t = strlen (dcc->destfile) + dcc->destfile;
+			strcpy (t, nick);
+			while (*t)
+			{
+				if (*t == '\\' || *t == '|')
+					*t = '_';
+				t++;
+			}
+#else
+			strcat (dcc->destfile, nick);
+#endif
+			strcat (dcc->destfile, ".");
+		}
+		strcat (dcc->destfile, file);
+
+		/* get the local filesystem encoding */
+		dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0);
+
+		dcc->resumable = 0;
+		dcc->pos = 0;
+		dcc->serv = sess->server;
+		dcc->type = TYPE_RECV;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->addr = addr;
+		dcc->port = port;
+		dcc->pasvid = pasvid;
+		dcc->size = size;
+		dcc->nick = strdup (nick);
+		dcc->maxcps = prefs.dcc_max_get_cps;
+
+		is_resumable (dcc);
+
+		/* autodccsend is really autodccrecv.. right? */
+
+		if (prefs.autodccsend == 1)
+		{
+			dcc_get (dcc);
+		}
+		else if (prefs.autodccsend == 2)
+		{
+			snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file);
+			fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc);
+		}
+		if (prefs.autoopendccrecvwindow)
+		{
+			if (fe_dcc_open_recv_win (TRUE))	/* was already open? just add*/
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+	}
+	sprintf (tbuf, "%"DCC_SFMT, size);
+	snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port);
+	EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick,
+					 file, tbuf, tbuf + 24, 0);
+
+	return dcc;
+}
+
+void
+handle_dcc (struct session *sess, char *nick, char *word[],
+				char *word_eol[])
+{
+	char tbuf[512];
+	struct DCC *dcc;
+	char *type = word[5];
+	int port, pasvid = 0;
+	guint32 addr;
+	DCC_SIZE size;
+	int psend = 0;
+
+	if (!strcasecmp (type, "CHAT"))
+	{
+		port = atoi (word[8]);
+		addr = strtoul (word[7], NULL, 10);
+
+		if (port == 0)
+			pasvid = atoi (word[9]);
+		else if (word[9][0] != 0)
+		{
+			pasvid = atoi (word[9]);
+			psend = 1;
+		}
+
+		if (!addr /*|| (port < 1024 && port != 0)*/
+			|| port > 0xffff || (port == 0 && pasvid == 0))
+		{
+			dcc_malformed (sess, nick, word_eol[4] + 2);
+			return;
+		}
+
+		if (psend)
+		{
+			dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND);
+			if (dcc)
+			{
+				dcc->addr = addr;
+				dcc->port = port;
+				dcc_connect (dcc);
+			} else
+			{
+				dcc_malformed (sess, nick, word_eol[4] + 2);
+			}
+			return;
+		}
+
+		dcc = find_dcc (nick, "", TYPE_CHATSEND);
+		if (dcc)
+			dcc_close (dcc, 0, TRUE);
+
+		dcc = find_dcc (nick, "", TYPE_CHATRECV);
+		if (dcc)
+			dcc_close (dcc, 0, TRUE);
+
+		dcc_add_chat (sess, nick, port, addr, pasvid);
+		return;
+	}
+
+	if (!strcasecmp (type, "Resume"))
+	{
+		port = atoi (word[7]);
+
+		if (port == 0)
+		{ /* PASSIVE */
+			pasvid = atoi(word[9]);
+			dcc = find_dcc_from_id(pasvid, TYPE_SEND);
+		} else
+		{
+			dcc = find_dcc_from_port (port, TYPE_SEND);
+		}
+		if (!dcc)
+			dcc = find_dcc (nick, word[6], TYPE_SEND);
+		if (dcc)
+		{
+			size = BIG_STR_TO_INT (word[8]);
+			dcc->resumable = size;
+			if (dcc->resumable < dcc->size)
+			{
+				dcc->pos = dcc->resumable;
+				dcc->ack = dcc->resumable;
+				lseek (dcc->fp, dcc->pos, SEEK_SET);
+
+				/* Checking if dcc is passive and if filename contains spaces */
+				if (dcc->pasvid)
+					snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ?
+							"DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" :
+							"DCC ACCEPT %s %d %"DCC_SFMT" %d",
+							file_part (dcc->file), port, dcc->resumable, dcc->pasvid);
+				else
+					snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ?
+							"DCC ACCEPT \"%s\" %d %"DCC_SFMT :
+							"DCC ACCEPT %s %d %"DCC_SFMT,
+							file_part (dcc->file), port, dcc->resumable);
+
+				dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+			}
+			sprintf (tbuf, "%"DCC_SFMT, dcc->pos);
+			EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick,
+							 file_part (dcc->file), tbuf, NULL, 0);
+		}
+		return;
+	}
+	if (!strcasecmp (type, "Accept"))
+	{
+		port = atoi (word[7]);
+		dcc = find_dcc_from_port (port, TYPE_RECV);
+		if (dcc && dcc->dccstat == STAT_QUEUED)
+		{
+			dcc_connect (dcc);
+		}
+		return;
+	}
+	if (!strcasecmp (type, "SEND"))
+	{
+		char *file = file_part (word[6]);
+
+		port = atoi (word[8]);
+		addr = strtoul (word[7], NULL, 10);
+		size = BIG_STR_TO_INT (word[9]);
+
+		if (port == 0) /* Passive dcc requested */
+			pasvid = atoi (word[10]);
+		else if (word[10][0] != 0)
+		{
+			/* Requesting passive dcc.
+			 * Destination user of an active dcc is giving his
+			 * TRUE address/port/pasvid data.
+			 * This information will be used later to
+			 * establish the connection to the user.
+			 * We can recognize this type of dcc using word[10]
+			 * because this field is always null (no pasvid)
+			 * in normal dcc sends.
+			 */
+			pasvid = atoi (word[10]);
+			psend = 1;
+		}
+
+
+		if (!addr || !size /*|| (port < 1024 && port != 0)*/
+			|| port > 0xffff || (port == 0 && pasvid == 0))
+		{
+			dcc_malformed (sess, nick, word_eol[4] + 2);
+			return;
+		}
+
+		if (psend)
+		{
+			/* Third Step of Passive send.
+			 * Connecting to the destination and finally
+			 * sending file.
+			 */
+			dcc = find_dcc_from_id (pasvid, TYPE_SEND);
+			if (dcc)
+			{
+				dcc->addr = addr;
+				dcc->port = port;
+				dcc_connect (dcc);
+			} else
+			{
+				dcc_malformed (sess, nick, word_eol[4] + 2);
+			}
+			return;
+		}
+
+		dcc_add_file (sess, file, size, port, nick, addr, pasvid);
+
+	} else
+	{
+		EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session,
+						 word_eol[4] + 2, nick, NULL, NULL, 0);
+	}
+}
+
+void
+dcc_show_list (struct session *sess)
+{
+	int i = 0;
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+
+	EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0);
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		i++;
+		PrintTextf (sess, " %s  %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n",
+					 dcctypes[dcc->type], dcc->nick,
+					 _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos,
+					 file_part (dcc->file));
+		list = list->next;
+	}
+	if (!i)
+		PrintText (sess, _("No active DCCs\n"));
+}
diff --git a/src/common/dcc.h b/src/common/dcc.h
new file mode 100644
index 00000000..da4ce979
--- /dev/null
+++ b/src/common/dcc.h
@@ -0,0 +1,117 @@
+/* dcc.h */
+
+#include <time.h>						/* for time_t */
+
+#ifndef XCHAT_DCC_H
+#define XCHAT_DCC_H
+
+#define STAT_QUEUED 0
+#define STAT_ACTIVE 1
+#define STAT_FAILED 2
+#define STAT_DONE 3
+#define STAT_CONNECTING 4
+#define STAT_ABORTED 5
+
+#define TYPE_SEND 0
+#define TYPE_RECV 1
+#define TYPE_CHATRECV 2
+#define TYPE_CHATSEND 3
+
+#define CPS_AVG_WINDOW 10
+
+/* can we do 64-bit dcc? */
+#if defined(G_GINT64_FORMAT) && defined(HAVE_STRTOULL)
+#define USE_DCC64
+/* we really get only 63 bits, since st_size is signed */
+#define DCC_SIZE gint64
+#define DCC_SFMT G_GINT64_FORMAT
+#else
+#define DCC_SIZE unsigned int
+#define DCC_SFMT "u"
+#endif
+
+struct DCC
+{
+	struct server *serv;
+	struct dcc_chat *dccchat;
+	struct proxy_state *proxy;
+	guint32 addr;					/* the 32bit IP number, host byte order */
+	int fp;							/* file pointer */
+	int sok;
+	int iotag;						/* reading io tag */
+	int wiotag;						/* writing/sending io tag */
+	int port;
+	int pasvid;						/* mIRC's passive DCC id */
+	int cps;
+	int resume_error;
+	int resume_errno;
+
+	GTimeVal lastcpstv, firstcpstv;
+	DCC_SIZE lastcpspos;
+	int maxcps;
+
+	unsigned char ack_buf[4];	/* buffer for reading 4-byte ack */
+	int ack_pos;
+
+	DCC_SIZE size;
+	DCC_SIZE resumable;
+	DCC_SIZE ack;
+	DCC_SIZE pos;
+	time_t starttime;
+	time_t offertime;
+	time_t lasttime;
+	char *file;					/* utf8 */
+	char *destfile;			/* utf8 */
+	char *destfile_fs;		/* local filesystem encoding */
+	char *nick;
+	unsigned char type;		  /* 0 = SEND  1 = RECV  2 = CHAT */
+	unsigned char dccstat;	  /* 0 = QUEUED  1 = ACTIVE  2 = FAILED  3 = DONE */
+	unsigned int resume_sent:1;	/* resume request sent */
+	unsigned int fastsend:1;
+	unsigned int ackoffset:1;	/* is reciever sending acks as an offset from */
+										/* the resume point? */
+	unsigned int throttled:2;	/* 0x1 = per send/get throttle
+											0x2 = global throttle */
+};
+
+#define MAX_PROXY_BUFFER 1024
+struct proxy_state
+{
+	int phase;
+	unsigned char buffer[MAX_PROXY_BUFFER];
+	int buffersize;
+	int bufferused;
+};
+
+struct dcc_chat
+{
+	char linebuf[2048];
+	int pos;
+};
+
+struct dccstat_info
+{
+	char *name;						  /* Display name */
+	int color;						  /* Display color (index into colors[] ) */
+};
+
+extern struct dccstat_info dccstat[];
+
+gboolean is_dcc (struct DCC *dcc);
+void dcc_abort (session *sess, struct DCC *dcc);
+void dcc_get (struct DCC *dcc);
+int dcc_resume (struct DCC *dcc);
+void dcc_check_timeouts (void);
+void dcc_change_nick (server *serv, char *oldnick, char *newnick);
+void dcc_notify_kill (struct server *serv);
+struct DCC *dcc_write_chat (char *nick, char *text);
+void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive);
+struct DCC *find_dcc (char *nick, char *file, int type);
+void dcc_get_nick (struct session *sess, char *nick);
+void dcc_chat (session *sess, char *nick, int passive);
+void handle_dcc (session *sess, char *nick, char *word[], char *word_eol[]);
+void dcc_show_list (session *sess);
+guint32 dcc_get_my_address (void);
+void dcc_get_with_destfile (struct DCC *dcc, char *utf8file);
+
+#endif
diff --git a/src/common/fe.h b/src/common/fe.h
new file mode 100644
index 00000000..16526581
--- /dev/null
+++ b/src/common/fe.h
@@ -0,0 +1,162 @@
+#include "userlist.h"
+#include "dcc.h"
+
+#ifndef XCHAT_FE_H
+#define XCHAT_FE_H
+
+/* for storage of /menu entries */
+typedef struct
+{
+	gint32 pos;	/* position */
+	gint16 modifier;	/* keybinding */
+	gint16 root_offset;	/* bytes to offset ->path */
+
+	char is_main;	/* is part of the Main menu? (not a popup) */
+	char state;	/* state of toggle items */
+	char markup;	/* use pango markup? */
+	char enable;	/* enabled? sensitivity */
+
+	int key;
+	char *path;
+	char *label;
+	char *cmd;
+	char *ucmd;	/* unselect command (toggles) */
+	char *group;	/* for radio items or NULL */
+	char *icon;	/* filename */
+} menu_entry;
+
+int fe_args (int argc, char *argv[]);
+void fe_init (void);
+void fe_main (void);
+void fe_cleanup (void);
+void fe_exit (void);
+int fe_timeout_add (int interval, void *callback, void *userdata);
+void fe_timeout_remove (int tag);
+void fe_new_window (struct session *sess, int focus);
+void fe_new_server (struct server *serv);
+void fe_add_rawlog (struct server *serv, char *text, int len, int outbound);
+#define FE_MSG_WAIT 1
+#define FE_MSG_INFO 2
+#define FE_MSG_WARN 4
+#define FE_MSG_ERROR 8
+#define FE_MSG_MARKUP 16
+void fe_message (char *msg, int flags);
+#define FIA_READ 1
+#define FIA_WRITE 2
+#define FIA_EX 4
+#define FIA_FD 8
+int fe_input_add (int sok, int flags, void *func, void *data);
+void fe_input_remove (int tag);
+void fe_idle_add (void *func, void *data);
+void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
+void fe_set_hilight (struct session *sess);
+void fe_set_tab_color (struct session *sess, int col);
+void fe_flash_window (struct session *sess);
+void fe_update_mode_buttons (struct session *sess, char mode, char sign);
+void fe_update_channel_key (struct session *sess);
+void fe_update_channel_limit (struct session *sess);
+int fe_is_chanwindow (struct server *serv);
+void fe_add_chan_list (struct server *serv, char *chan, char *users,
+							  char *topic);
+void fe_chan_list_end (struct server *serv);
+int fe_is_banwindow (struct session *sess);
+void fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption);
+void fe_ban_list_end (struct session *sess, int is_exemption);
+void fe_notify_update (char *name);
+void fe_notify_ask (char *name, char *networks);
+void fe_text_clear (struct session *sess, int lines);
+void fe_close_window (struct session *sess);
+void fe_progressbar_start (struct session *sess);
+void fe_progressbar_end (struct server *serv);
+void fe_print_text (struct session *sess, char *text, time_t stamp);
+void fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel);
+int fe_userlist_remove (struct session *sess, struct User *user);
+void fe_userlist_rehash (struct session *sess, struct User *user);
+void fe_userlist_update (struct session *sess, struct User *user);
+void fe_userlist_move (struct session *sess, struct User *user, int new_row);
+void fe_userlist_numbers (struct session *sess);
+void fe_userlist_clear (struct session *sess);
+void fe_userlist_set_selected (struct session *sess);
+void fe_uselect (session *sess, char *word[], int do_clear, int scroll_to);
+void fe_dcc_add (struct DCC *dcc);
+void fe_dcc_update (struct DCC *dcc);
+void fe_dcc_remove (struct DCC *dcc);
+int fe_dcc_open_recv_win (int passive);
+int fe_dcc_open_send_win (int passive);
+int fe_dcc_open_chat_win (int passive);
+void fe_clear_channel (struct session *sess);
+void fe_session_callback (struct session *sess);
+void fe_server_callback (struct server *serv);
+void fe_url_add (const char *text);
+void fe_pluginlist_update (void);
+void fe_buttons_update (struct session *sess);
+void fe_dlgbuttons_update (struct session *sess);
+void fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive);
+void fe_set_channel (struct session *sess);
+void fe_set_title (struct session *sess);
+void fe_set_nonchannel (struct session *sess, int state);
+void fe_set_nick (struct server *serv, char *newnick);
+void fe_ignore_update (int level);
+void fe_beep (void);
+void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp);
+void fe_set_lag (server *serv, int lag);
+void fe_set_throttle (server *serv);
+void fe_set_away (server *serv);
+void fe_serverlist_open (session *sess);
+void fe_get_str (char *prompt, char *def, void *callback, void *ud);
+void fe_get_int (char *prompt, int def, void *callback, void *ud);
+#define FRF_WRITE 1	/* save file */
+#define FRF_MULTIPLE 2	/* multi-select */
+#define FRF_ADDFOLDER 4	/* add ~/.xchat2 to favourites */
+#define FRF_CHOOSEFOLDER 8	/* choosing a folder only */
+#define FRF_FILTERISINITIAL 16	/* unused */
+#define FRF_NOASKOVERWRITE 32	/* don't ask to overwrite existing files */
+void fe_get_file (const char *title, char *initial,
+				 void (*callback) (void *userdata, char *file), void *userdata,
+				 int flags);
+typedef enum {
+	FE_GUI_HIDE,
+	FE_GUI_SHOW,
+	FE_GUI_FOCUS,
+	FE_GUI_FLASH,
+	FE_GUI_COLOR,
+	FE_GUI_ICONIFY,
+	FE_GUI_MENU,
+	FE_GUI_ATTACH,
+	FE_GUI_APPLY,
+} fe_gui_action;
+void fe_ctrl_gui (session *sess, fe_gui_action action, int arg);
+int fe_gui_info (session *sess, int info_type);
+void *fe_gui_info_ptr (session *sess, int info_type);
+void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud);
+char *fe_get_inputbox_contents (struct session *sess);
+int fe_get_inputbox_cursor (struct session *sess);
+void fe_set_inputbox_contents (struct session *sess, char *text);
+void fe_set_inputbox_cursor (struct session *sess, int delta, int pos);
+void fe_open_url (const char *url);
+void fe_menu_del (menu_entry *);
+char *fe_menu_add (menu_entry *);
+void fe_menu_update (menu_entry *);
+#define FE_SE_CONNECT 0
+#define FE_SE_LOGGEDIN 1
+#define FE_SE_DISCONNECT 2
+#define FE_SE_RECONDELAY 3
+#define FE_SE_CONNECTING 4
+void fe_server_event (server *serv, int type, int arg);
+/* pass NULL filename2 for default xchat icon */
+void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout);
+/* pass NULL filename for default xchat icon */
+void fe_tray_set_file (const char *filename);
+typedef enum
+{
+	FE_ICON_NORMAL = 0,
+	FE_ICON_MESSAGE = 2,
+	FE_ICON_HIGHLIGHT = 5,
+	FE_ICON_PRIVMSG = 8,
+	FE_ICON_FILEOFFER = 11
+} feicon;
+void fe_tray_set_icon (feicon icon);
+void fe_tray_set_tooltip (const char *text);
+void fe_tray_set_balloon (const char *title, const char *text);
+
+#endif
diff --git a/src/common/history.c b/src/common/history.c
new file mode 100644
index 00000000..1cd6b508
--- /dev/null
+++ b/src/common/history.c
@@ -0,0 +1,121 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include "history.h"
+
+void
+history_add (struct history *his, char *text)
+{
+	if (his->lines[his->realpos])
+		free (his->lines[his->realpos]);
+	his->lines[his->realpos] = strdup (text);
+	his->realpos++;
+	if (his->realpos == HISTORY_SIZE)
+		his->realpos = 0;
+	his->pos = his->realpos;
+}
+
+void
+history_free (struct history *his)
+{
+	int i;
+	for (i = 0; i < HISTORY_SIZE; i++)
+	{
+		if (his->lines[i])
+		{
+			free (his->lines[i]);
+			his->lines[i] = 0;
+		}
+	}
+}
+
+char *
+history_down (struct history *his)
+{
+	int next;
+
+	if (his->pos == his->realpos)	/* allow down only after up */
+		return 0;
+	if (his->realpos == 0)
+	{
+		if (his->pos == HISTORY_SIZE - 1)
+		{
+			his->pos = 0;
+			return "";
+		}
+	} else
+	{
+		if (his->pos == his->realpos - 1)
+		{
+			his->pos++;
+			return "";
+		}
+	}
+
+	next = 0;
+	if (his->pos < HISTORY_SIZE - 1)
+		next = his->pos + 1;
+
+	if (his->lines[next])
+	{
+		his->pos = next;
+		return his->lines[his->pos];
+	}
+
+	return 0;
+}
+
+char *
+history_up (struct history *his, char *current_text)
+{
+	int next;
+
+	if (his->realpos == HISTORY_SIZE - 1)
+	{
+		if (his->pos == 0)
+			return 0;
+	} else
+	{
+		if (his->pos == his->realpos + 1)
+			return 0;
+	}
+
+	next = HISTORY_SIZE - 1;
+	if (his->pos != 0)
+		next = his->pos - 1;
+
+	if (his->lines[next])
+	{
+		if
+		(
+			current_text[0] && strcmp(current_text, his->lines[next]) &&
+			(!his->lines[his->pos] || strcmp(current_text, his->lines[his->pos])) &&
+			(!his->lines[his->realpos] || strcmp(current_text, his->lines[his->pos]))
+		)
+		{
+			history_add (his, current_text);
+		}
+		
+		his->pos = next;
+		return his->lines[his->pos];
+	}
+
+	return 0;
+}
diff --git a/src/common/history.h b/src/common/history.h
new file mode 100644
index 00000000..5267f1fc
--- /dev/null
+++ b/src/common/history.h
@@ -0,0 +1,18 @@
+#ifndef XCHAT_HISTORY_H
+#define XCHAT_HISTORY_H
+
+#define HISTORY_SIZE 100
+
+struct history
+{
+	char *lines[HISTORY_SIZE];
+	int pos;
+	int realpos;
+};
+
+void history_add (struct history *his, char *text);
+void history_free (struct history *his);
+char *history_up (struct history *his, char *current_text);
+char *history_down (struct history *his);
+
+#endif
diff --git a/src/common/identd.c b/src/common/identd.c
new file mode 100644
index 00000000..919282ea
--- /dev/null
+++ b/src/common/identd.c
@@ -0,0 +1,89 @@
+/* simple identd server for xchat under win32 */
+
+
+static int identd_is_running = FALSE;
+
+
+static int
+identd (char *username)
+{
+	int sok, read_sok, len;
+	char *p;
+	char buf[256];
+	char outbuf[256];
+	struct sockaddr_in addr;
+
+	sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (sok == INVALID_SOCKET)
+	{
+		free (username);
+		return 0;
+	}
+
+	len = 1;
+	setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len));
+
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons (113);
+
+	if (bind (sok, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR)
+	{
+		closesocket (sok);
+		free (username);
+		return 0;
+	}
+
+	if (listen (sok, 1) == SOCKET_ERROR)
+	{
+		closesocket (sok);
+		free (username);
+		return 0;
+	}
+
+	len = sizeof (addr);
+	read_sok = accept (sok, (struct sockaddr *) &addr, &len);
+	closesocket (sok);
+	if (read_sok == INVALID_SOCKET)
+	{
+		free (username);
+		return 0;
+	}
+
+	identd_is_running = FALSE;
+
+	snprintf (outbuf, sizeof (outbuf), "%%\tServicing ident request from %s\n",
+				 inet_ntoa (addr.sin_addr));
+	PrintText (current_sess, outbuf);
+
+	recv (read_sok, buf, sizeof (buf) - 1, 0);
+	buf[sizeof (buf) - 1] = 0;	  /* ensure null termination */
+
+	p = strchr (buf, ',');
+	if (p)
+	{
+		snprintf (outbuf, sizeof (outbuf) - 1, "%d, %d : USERID : UNIX : %s\r\n",
+					 atoi (buf), atoi (p + 1), username);
+		outbuf[sizeof (outbuf) - 1] = 0;	/* ensure null termination */
+		send (read_sok, outbuf, strlen (outbuf), 0);
+	}
+
+	sleep (1);
+	closesocket (read_sok);
+	free (username);
+
+	return 0;
+}
+
+static void
+identd_start (char *username)
+{
+	DWORD tid;
+
+	if (identd_is_running == FALSE)
+	{
+		identd_is_running = TRUE;
+		CloseHandle (CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) identd,
+						 strdup (username), 0, &tid));
+	}
+}
diff --git a/src/common/ignore.c b/src/common/ignore.c
new file mode 100644
index 00000000..c3544f30
--- /dev/null
+++ b/src/common/ignore.c
@@ -0,0 +1,424 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "xchat.h"
+#include "ignore.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+int ignored_ctcp = 0;			  /* keep a count of all we ignore */
+int ignored_priv = 0;
+int ignored_chan = 0;
+int ignored_noti = 0;
+int ignored_invi = 0;
+static int ignored_total = 0;
+
+/* ignore_exists ():
+ * returns: struct ig, if this mask is in the ignore list already
+ *          NULL, otherwise
+ */
+struct ignore *
+ignore_exists (char *mask)
+{
+	struct ignore *ig = 0;
+	GSList *list;
+
+	list = ignore_list;
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+		if (!rfc_casecmp (ig->mask, mask))
+			return ig;
+		list = list->next;
+	}
+	return NULL;
+
+}
+
+/* ignore_add(...)
+
+ * returns:
+ *            0 fail
+ *            1 success
+ *            2 success (old ignore has been changed)
+ */
+
+int
+ignore_add (char *mask, int type)
+{
+	struct ignore *ig = 0;
+	int change_only = FALSE;
+
+	/* first check if it's already ignored */
+	ig = ignore_exists (mask);
+	if (ig)
+		change_only = TRUE;
+
+	if (!change_only)
+		ig = malloc (sizeof (struct ignore));
+
+	if (!ig)
+		return 0;
+
+	ig->mask = strdup (mask);
+	ig->type = type;
+
+	if (!change_only)
+		ignore_list = g_slist_prepend (ignore_list, ig);
+	fe_ignore_update (1);
+
+	if (change_only)
+		return 2;
+
+	return 1;
+}
+
+void
+ignore_showlist (session *sess)
+{
+	struct ignore *ig;
+	GSList *list = ignore_list;
+	char tbuf[256];
+	int i = 0;
+
+	EMIT_SIGNAL (XP_TE_IGNOREHEADER, sess, 0, 0, 0, 0, 0);
+
+	while (list)
+	{
+		ig = list->data;
+		i++;
+
+		snprintf (tbuf, sizeof (tbuf), " %-25s ", ig->mask);
+		if (ig->type & IG_PRIV)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_NOTI)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_CHAN)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_CTCP)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_DCC)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_INVI)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_UNIG)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		strcat (tbuf, "\n");
+		PrintText (sess, tbuf);
+		/*EMIT_SIGNAL (XP_TE_IGNORELIST, sess, ig->mask, 0, 0, 0, 0); */
+		/* use this later, when TE's support 7 args */
+		list = list->next;
+	}
+
+	if (!i)
+		EMIT_SIGNAL (XP_TE_IGNOREEMPTY, sess, 0, 0, 0, 0, 0);
+
+	EMIT_SIGNAL (XP_TE_IGNOREFOOTER, sess, 0, 0, 0, 0, 0);
+}
+
+/* ignore_del()
+
+ * one of the args must be NULL, use mask OR *ig, not both
+ *
+ */
+
+int
+ignore_del (char *mask, struct ignore *ig)
+{
+	if (!ig)
+	{
+		GSList *list = ignore_list;
+
+		while (list)
+		{
+			ig = (struct ignore *) list->data;
+			if (!rfc_casecmp (ig->mask, mask))
+				break;
+			list = list->next;
+			ig = 0;
+		}
+	}
+	if (ig)
+	{
+		ignore_list = g_slist_remove (ignore_list, ig);
+		free (ig->mask);
+		free (ig);
+		fe_ignore_update (1);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* check if a msg should be ignored by browsing our ignore list */
+
+int
+ignore_check (char *host, int type)
+{
+	struct ignore *ig;
+	GSList *list = ignore_list;
+
+	/* check if there's an UNIGNORE first, they take precendance. */
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+		if (ig->type & IG_UNIG)
+		{
+			if (ig->type & type)
+			{
+				if (match (ig->mask, host))
+					return FALSE;
+			}
+		}
+		list = list->next;
+	}
+
+	list = ignore_list;
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+
+		if (ig->type & type)
+		{
+			if (match (ig->mask, host))
+			{
+				ignored_total++;
+				if (type & IG_PRIV)
+					ignored_priv++;
+				if (type & IG_NOTI)
+					ignored_noti++;
+				if (type & IG_CHAN)
+					ignored_chan++;
+				if (type & IG_CTCP)
+					ignored_ctcp++;
+				if (type & IG_INVI)
+					ignored_invi++;
+				fe_ignore_update (2);
+				return TRUE;
+			}
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+static char *
+ignore_read_next_entry (char *my_cfg, struct ignore *ignore)
+{
+	char tbuf[1024];
+
+	/* Casting to char * done below just to satisfy compiler */
+
+	if (my_cfg)
+	{
+		my_cfg = cfg_get_str (my_cfg, "mask", tbuf, sizeof (tbuf));
+		if (!my_cfg)
+			return NULL;
+		ignore->mask = strdup (tbuf);
+	}
+	if (my_cfg)
+	{
+		my_cfg = cfg_get_str (my_cfg, "type", tbuf, sizeof (tbuf));
+		ignore->type = atoi (tbuf);
+	}
+	return my_cfg;
+}
+
+void
+ignore_load ()
+{
+	struct ignore *ignore;
+	struct stat st;
+	char *cfg, *my_cfg;
+	int fh, i;
+
+	fh = xchat_open_file ("ignore.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		if (st.st_size)
+		{
+			cfg = malloc (st.st_size + 1);
+			cfg[0] = '\0';
+			i = read (fh, cfg, st.st_size);
+			if (i >= 0)
+				cfg[i] = '\0';
+			my_cfg = cfg;
+			while (my_cfg)
+			{
+				ignore = malloc (sizeof (struct ignore));
+				memset (ignore, 0, sizeof (struct ignore));
+				if ((my_cfg = ignore_read_next_entry (my_cfg, ignore)))
+					ignore_list = g_slist_prepend (ignore_list, ignore);
+				else
+					free (ignore);
+			}
+			free (cfg);
+		}
+		close (fh);
+	}
+}
+
+void
+ignore_save ()
+{
+	char buf[1024];
+	int fh;
+	GSList *temp = ignore_list;
+	struct ignore *ig;
+
+	fh = xchat_open_file ("ignore.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (temp)
+		{
+			ig = (struct ignore *) temp->data;
+			if (!(ig->type & IG_NOSAVE))
+			{
+				snprintf (buf, sizeof (buf), "mask = %s\ntype = %d\n\n",
+							 ig->mask, ig->type);
+				write (fh, buf, strlen (buf));
+			}
+			temp = temp->next;
+		}
+		close (fh);
+	}
+
+}
+
+static gboolean
+flood_autodialog_timeout (gpointer data)
+{
+	prefs.autodialog = 1;
+	return FALSE;
+}
+
+int
+flood_check (char *nick, char *ip, server *serv, session *sess, int what)	/*0=ctcp  1=priv */
+{
+	/*
+	   serv
+	   int ctcp_counter; 
+	   time_t ctcp_last_time;
+	   prefs
+	   unsigned int ctcp_number_limit;
+	   unsigned int ctcp_time_limit;
+	 */
+	char buf[512];
+	char real_ip[132];
+	int i;
+	time_t current_time;
+	current_time = time (NULL);
+
+	if (what == 0)
+	{
+		if (serv->ctcp_last_time == 0)	/*first ctcp in this server */
+		{
+			serv->ctcp_last_time = time (NULL);
+			serv->ctcp_counter++;
+		} else
+		{
+			if (difftime (current_time, serv->ctcp_last_time) < prefs.ctcp_time_limit)	/*if we got the ctcp in the seconds limit */
+			{
+				serv->ctcp_counter++;
+				if (serv->ctcp_counter == prefs.ctcp_number_limit)	/*if we reached the maximun numbers of ctcp in the seconds limits */
+				{
+					serv->ctcp_last_time = current_time;	/*we got the flood, restore all the vars for next one */
+					serv->ctcp_counter = 0;
+					for (i = 0; i < 128; i++)
+						if (ip[i] == '@')
+							break;
+					snprintf (real_ip, sizeof (real_ip), "*!*%s", &ip[i]);
+					/*ignore_add (char *mask, int priv, int noti, int chan,
+					   int ctcp, int invi, int unignore, int no_save) */
+
+					snprintf (buf, sizeof (buf),
+								 _("You are being CTCP flooded from %s, ignoring %s\n"),
+								 nick, real_ip);
+					PrintText (sess, buf);
+
+					/*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */
+					ignore_add (real_ip, IG_CTCP);
+					return 0;
+				}
+			}
+		}
+	} else
+	{
+		if (serv->msg_last_time == 0)
+		{
+			serv->msg_last_time = time (NULL);
+			serv->ctcp_counter++;
+		} else
+		{
+			if (difftime (current_time, serv->msg_last_time) <
+				 prefs.msg_time_limit)
+			{
+				serv->msg_counter++;
+				if (serv->msg_counter == prefs.msg_number_limit)	/*if we reached the maximun numbers of ctcp in the seconds limits */
+				{
+					snprintf (buf, sizeof (buf),
+					 _("You are being MSG flooded from %s, setting gui_auto_open_dialog OFF.\n"),
+								 ip);
+					PrintText (sess, buf);
+					serv->msg_last_time = current_time;	/*we got the flood, restore all the vars for next one */
+					serv->msg_counter = 0;
+					/*ignore_add (char *mask, int priv, int noti, int chan,
+					   int ctcp, int invi, int unignore, int no_save) */
+
+					if (prefs.autodialog)
+					{
+						/*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */
+						prefs.autodialog = 0;
+						/* turn it back on in 30 secs */
+						fe_timeout_add (30000, flood_autodialog_timeout, NULL);
+					}
+					return 0;
+				}
+			}
+		}
+	}
+	return 1;
+}
+
diff --git a/src/common/ignore.h b/src/common/ignore.h
new file mode 100644
index 00000000..3a971a86
--- /dev/null
+++ b/src/common/ignore.h
@@ -0,0 +1,38 @@
+#ifndef XCHAT_IGNORE_H
+#define XCHAT_IGNORE_H
+
+extern GSList *ignore_list;
+
+extern int ignored_ctcp;
+extern int ignored_priv;
+extern int ignored_chan;
+extern int ignored_noti;
+extern int ignored_invi;
+
+#define IG_PRIV	1
+#define IG_NOTI	2
+#define IG_CHAN	4
+#define IG_CTCP	8
+#define IG_INVI	16
+#define IG_UNIG	32
+#define IG_NOSAVE	64
+#define IG_DCC		128
+
+struct ignore
+{
+	char *mask;
+	unsigned int type;	/* one of more of IG_* ORed together */
+};
+
+struct ignore *ignore_exists (char *mask);
+int ignore_add (char *mask, int type);
+void ignore_showlist (session *sess);
+int ignore_del (char *mask, struct ignore *ig);
+int ignore_check (char *mask, int type);
+void ignore_load (void);
+void ignore_save (void);
+void ignore_gui_open (void);
+void ignore_gui_update (int level);
+int flood_check (char *nick, char *ip, server *serv, session *sess, int what);
+
+#endif
diff --git a/src/common/inbound.c b/src/common/inbound.c
new file mode 100644
index 00000000..ec7dd9d0
--- /dev/null
+++ b/src/common/inbound.c
@@ -0,0 +1,1336 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#include "xchat.h"
+#include "util.h"
+#include "ignore.h"
+#include "fe.h"
+#include "modes.h"
+#include "notify.h"
+#include "outbound.h"
+#include "inbound.h"
+#include "server.h"
+#include "servlist.h"
+#include "text.h"
+#include "ctcp.h"
+#include "plugin.h"
+#include "xchatc.h"
+
+
+void
+clear_channel (session *sess)
+{
+	if (sess->channel[0])
+		strcpy (sess->waitchannel, sess->channel);
+	sess->channel[0] = 0;
+	sess->doing_who = FALSE;
+	sess->done_away_check = FALSE;
+
+	log_close (sess);
+
+	if (sess->current_modes)
+	{
+		free (sess->current_modes);
+		sess->current_modes = NULL;
+	}
+
+	if (sess->mode_timeout_tag)
+	{
+		fe_timeout_remove (sess->mode_timeout_tag);
+		sess->mode_timeout_tag = 0;
+	}
+
+	fe_clear_channel (sess);
+	userlist_clear (sess);
+	fe_set_nonchannel (sess, FALSE);
+	fe_set_title (sess);
+}
+
+void
+set_topic (session *sess, char *topic, char *stripped_topic)
+{
+	if (sess->topic)
+		free (sess->topic);
+	sess->topic = strdup (stripped_topic);
+	fe_set_topic (sess, topic, stripped_topic);
+}
+
+static session *
+find_session_from_nick (char *nick, server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+
+	sess = find_dialog (serv, nick);
+	if (sess)
+		return sess;
+
+	if (serv->front_session)
+	{
+		if (userlist_find (serv->front_session, nick))
+			return serv->front_session;
+	}
+
+	if (current_sess && current_sess->server == serv)
+	{
+		if (userlist_find (current_sess, nick))
+			return current_sess;
+	}
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (userlist_find (sess, nick))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static session *
+inbound_open_dialog (server *serv, char *from)
+{
+	session *sess;
+
+	sess = new_ircwindow (serv, from, SESS_DIALOG, 0);
+	/* for playing sounds */
+	EMIT_SIGNAL (XP_TE_OPENDIALOG, sess, NULL, NULL, NULL, NULL, 0);
+
+	return sess;
+}
+
+static void
+inbound_make_idtext (server *serv, char *idtext, int max, int id)
+{
+	idtext[0] = 0;
+	if (serv->have_idmsg)
+	{
+		if (id)
+		{
+			safe_strcpy (idtext, prefs.irc_id_ytext, max);
+		} else
+		{
+			safe_strcpy (idtext, prefs.irc_id_ntext, max);
+		}
+		/* convert codes like %C,%U to the proper ones */
+		check_special_chars (idtext, TRUE);
+	}
+}
+
+void
+inbound_privmsg (server *serv, char *from, char *ip, char *text, int id)
+{
+	session *sess;
+	char idtext[64];
+
+	sess = find_dialog (serv, from);
+
+	if (sess || prefs.autodialog)
+	{
+		/*0=ctcp  1=priv will set autodialog=0 here is flud detected */
+		if (!sess)
+		{
+			if (flood_check (from, ip, serv, current_sess, 1))
+				/* Create a dialog session */
+				sess = inbound_open_dialog (serv, from);
+			else
+				sess = serv->server_session;
+			if (!sess)
+				return; /* ?? */
+		}
+
+		if (ip && ip[0])
+		{
+			if (prefs.logging && sess->logfd != -1 &&
+				(!sess->topic || strcmp(sess->topic, ip)))
+			{
+				char tbuf[1024];
+				snprintf (tbuf, sizeof (tbuf), "[%s has address %s]\n", from, ip);
+				write (sess->logfd, tbuf, strlen (tbuf));
+			}
+			set_topic (sess, ip, ip);
+		}
+		inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id);
+		return;
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	sess = find_session_from_nick (from, serv);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0);
+		return;
+	}
+
+	if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0);
+}
+
+/* used for Alerts section. Masks can be separated by commas and spaces. */
+
+gboolean
+alert_match_word (char *word, char *masks)
+{
+	char *p = masks;
+	char endchar;
+	int res;
+
+	if (masks[0] == 0)
+		return FALSE;
+
+	while (1)
+	{
+		/* if it's a 0, space or comma, the word has ended. */
+		if (*p == 0 || *p == ' ' || *p == ',')
+		{
+			endchar = *p;
+			*p = 0;
+			res = match (masks, word);
+			*p = endchar;
+
+			if (res)
+				return TRUE;	/* yes, matched! */
+
+			masks = p + 1;
+			if (*p == 0)
+				return FALSE;
+		}
+		p++;
+	}
+}
+
+gboolean
+alert_match_text (char *text, char *masks)
+{
+	unsigned char *p = text;
+	unsigned char endchar;
+	int res;
+
+	if (masks[0] == 0)
+		return FALSE;
+
+	while (1)
+	{
+		if (*p >= '0' && *p <= '9')
+		{
+			p++;
+			continue;
+		}
+
+		/* if it's RFC1459 <special>, it can be inside a word */
+		switch (*p)
+		{
+		case '-': case '[': case ']': case '\\':
+		case '`': case '^': case '{': case '}':
+		case '_': case '|':
+			p++;
+			continue;
+		}
+
+		/* if it's a 0, space or comma, the word has ended. */
+		if (*p == 0 || *p == ' ' || *p == ',' ||
+			/* if it's anything BUT a letter, the word has ended. */
+			 (!g_unichar_isalpha (g_utf8_get_char (p))))
+		{
+			endchar = *p;
+			*p = 0;
+			res = alert_match_word (text, masks);
+			*p = endchar;
+
+			if (res)
+				return TRUE;	/* yes, matched! */
+
+			text = p + g_utf8_skip [p[0]];
+			if (*p == 0)
+				return FALSE;
+		}
+
+		p += g_utf8_skip [p[0]];
+	}
+}
+
+static int
+is_hilight (char *from, char *text, session *sess, server *serv)
+{
+	if (alert_match_word (from, prefs.irc_no_hilight))
+		return 0;
+
+	text = strip_color (text, -1, STRIP_ALL);
+
+	if (alert_match_text (text, serv->nick) ||
+		 alert_match_text (text, prefs.irc_extra_hilight) ||
+		 alert_match_word (from, prefs.irc_nick_hilight))
+	{
+		g_free (text);
+		if (sess != current_tab)
+			sess->nick_said = TRUE;
+		fe_set_hilight (sess);
+		return 1;
+	}
+
+	g_free (text);
+	return 0;
+}
+
+void
+inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id)
+{
+	session *def = sess;
+	server *serv = sess->server;
+	struct User *user;
+	char nickchar[2] = "\000";
+	char idtext[64];
+	int privaction = FALSE;
+
+	if (!fromme)
+	{
+		if (is_channel (serv, chan))
+		{
+			sess = find_channel (serv, chan);
+		} else
+		{
+			/* it's a private action! */
+			privaction = TRUE;
+			/* find a dialog tab for it */
+			sess = find_dialog (serv, from);
+			/* if non found, open a new one */
+			if (!sess && prefs.autodialog)
+			{
+				/* but only if it wouldn't flood */
+				if (flood_check (from, ip, serv, current_sess, 1))
+					sess = inbound_open_dialog (serv, from);
+				else
+					sess = serv->server_session;
+			}
+			if (!sess)
+			{
+				sess = find_session_from_nick (from, serv);
+				/* still not good? */
+				if (!sess)
+					sess = serv->front_session;
+			}
+		}
+	}
+
+	if (!sess)
+		sess = def;
+
+	if (sess != current_tab)
+	{
+		if (fromme)
+		{
+			sess->msg_said = FALSE;
+			sess->new_data = TRUE;
+		} else
+		{
+			sess->msg_said = TRUE;
+			sess->new_data = FALSE;
+		}
+	}
+
+	user = userlist_find (sess, from);
+	if (user)
+	{
+		nickchar[0] = user->prefix[0];
+		user->lasttalk = time (0);
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	if (!fromme && !privaction)
+	{
+		if (is_hilight (from, text, sess, serv))
+		{
+			EMIT_SIGNAL (XP_TE_HCHANACTION, sess, from, text, nickchar, idtext, 0);
+			return;
+		}
+	}
+
+	if (fromme)
+		EMIT_SIGNAL (XP_TE_UACTION, sess, from, text, nickchar, idtext, 0);
+	else if (!privaction)
+		EMIT_SIGNAL (XP_TE_CHANACTION, sess, from, text, nickchar, idtext, 0);
+	else if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0);
+}
+
+void
+inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id)
+{
+	struct User *user;
+	int hilight = FALSE;
+	char nickchar[2] = "\000";
+	char idtext[64];
+
+	if (!sess)
+	{
+		if (chan)
+		{
+			sess = find_channel (serv, chan);
+			if (!sess && !is_channel (serv, chan))
+				sess = find_dialog (serv, chan);
+		} else
+		{
+			sess = find_dialog (serv, from);
+		}
+		if (!sess)
+			return;
+	}
+
+	if (sess != current_tab)
+	{
+		sess->msg_said = TRUE;
+		sess->new_data = FALSE;
+	}
+
+	user = userlist_find (sess, from);
+	if (user)
+	{
+		nickchar[0] = user->prefix[0];
+		user->lasttalk = time (0);
+	}
+
+	if (fromme)
+	{
+  		if (prefs.auto_unmark_away && serv->is_away)
+			sess->server->p_set_back (sess->server);
+		EMIT_SIGNAL (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, 0);
+		return;
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	if (is_hilight (from, text, sess, serv))
+		hilight = TRUE;
+
+	if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0);
+	else if (hilight)
+		EMIT_SIGNAL (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, 0);
+	else
+		EMIT_SIGNAL (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, 0);
+}
+
+void
+inbound_newnick (server *serv, char *nick, char *newnick, int quiet)
+{
+	int me = FALSE;
+	session *sess;
+	GSList *list = sess_list;
+
+	if (!serv->p_cmp (nick, serv->nick))
+	{
+		me = TRUE;
+		safe_strcpy (serv->nick, newnick, NICKLEN);
+	}
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (userlist_change (sess, nick, newnick) || (me && sess->type == SESS_SERVER))
+			{
+				if (!quiet)
+				{
+					if (me)
+						EMIT_SIGNAL (XP_TE_UCHANGENICK, sess, nick, newnick, NULL,
+										 NULL, 0);
+					else
+						EMIT_SIGNAL (XP_TE_CHANGENICK, sess, nick, newnick, NULL,
+										 NULL, 0);
+				}
+			}
+			if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
+			{
+				safe_strcpy (sess->channel, newnick, CHANLEN);
+				fe_set_channel (sess);
+			}
+			fe_set_title (sess);
+		}
+		list = list->next;
+	}
+
+	dcc_change_nick (serv, nick, newnick);
+
+	if (me)
+		fe_set_nick (serv, newnick);
+}
+
+/* find a "<none>" tab */
+static session *
+find_unused_session (server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] == 0 &&
+			 sess->server == serv)
+		{
+			if (sess->waitchannel[0] == 0)
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static session *
+find_session_from_waitchannel (char *chan, struct server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv && sess->channel[0] == 0 && sess->type == SESS_CHANNEL)
+		{
+			if (!serv->p_cmp (chan, sess->waitchannel))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+inbound_ujoin (server *serv, char *chan, char *nick, char *ip)
+{
+	session *sess;
+
+	/* already joined? probably a bnc */
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		/* see if a window is waiting to join this channel */
+		sess = find_session_from_waitchannel (chan, serv);
+		if (!sess)
+		{
+			/* find a "<none>" tab and use that */
+			sess = find_unused_session (serv);
+			if (!sess)
+				/* last resort, open a new tab/window */
+				sess = new_ircwindow (serv, chan, SESS_CHANNEL, 1);
+		}
+	}
+
+	safe_strcpy (sess->channel, chan, CHANLEN);
+
+	fe_set_channel (sess);
+	fe_set_title (sess);
+	fe_set_nonchannel (sess, TRUE);
+	userlist_clear (sess);
+
+	log_open_or_close (sess);
+
+	sess->waitchannel[0] = 0;
+	sess->ignore_date = TRUE;
+	sess->ignore_mode = TRUE;
+	sess->ignore_names = TRUE;
+	sess->end_of_names = FALSE;
+
+	/* sends a MODE */
+	serv->p_join_info (sess->server, chan);
+
+	EMIT_SIGNAL (XP_TE_UJOIN, sess, nick, chan, ip, NULL, 0);
+
+	if (prefs.userhost)
+	{
+		/* sends WHO #channel */
+		serv->p_user_list (sess->server, chan);
+		sess->doing_who = TRUE;
+	}
+}
+
+void
+inbound_ukick (server *serv, char *chan, char *kicker, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_UKICK, sess, serv->nick, chan, kicker, reason, 0);
+		clear_channel (sess);
+		if (prefs.autorejoin)
+		{
+			serv->p_join (serv, chan, sess->channelkey);
+			safe_strcpy (sess->waitchannel, chan, CHANLEN);
+		}
+	}
+}
+
+void
+inbound_upart (server *serv, char *chan, char *ip, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		if (*reason)
+			EMIT_SIGNAL (XP_TE_UPARTREASON, sess, serv->nick, ip, chan, reason,
+							 0);
+		else
+			EMIT_SIGNAL (XP_TE_UPART, sess, serv->nick, ip, chan, NULL, 0);
+		clear_channel (sess);
+	}
+}
+
+void
+inbound_nameslist (server *serv, char *chan, char *names)
+{
+	session *sess;
+	char name[NICKLEN];
+	int pos = 0;
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		EMIT_SIGNAL (XP_TE_USERSONCHAN, serv->server_session, chan, names, NULL,
+						 NULL, 0);
+		return;
+	}
+	if (!sess->ignore_names)
+		EMIT_SIGNAL (XP_TE_USERSONCHAN, sess, chan, names, NULL, NULL, 0);
+
+	if (sess->end_of_names)
+	{
+		sess->end_of_names = FALSE;
+		userlist_clear (sess);
+	}
+
+	while (1)
+	{
+		switch (*names)
+		{
+		case 0:
+			name[pos] = 0;
+			if (pos != 0)
+				userlist_add (sess, name, 0);
+			return;
+		case ' ':
+			name[pos] = 0;
+			pos = 0;
+			userlist_add (sess, name, 0);
+			break;
+		default:
+			name[pos] = *names;
+			if (pos < (NICKLEN-1))
+				pos++;
+		}
+		names++;
+	}
+}
+
+void
+inbound_topic (server *serv, char *chan, char *topic_text)
+{
+	session *sess = find_channel (serv, chan);
+	char *stripped_topic;
+
+	if (sess)
+	{
+		stripped_topic = strip_color (topic_text, -1, STRIP_ALL);
+		set_topic (sess, topic_text, stripped_topic);
+		g_free (stripped_topic);
+	} else
+		sess = serv->server_session;
+
+	EMIT_SIGNAL (XP_TE_TOPIC, sess, chan, topic_text, NULL, NULL, 0);
+}
+
+void
+inbound_topicnew (server *serv, char *nick, char *chan, char *topic)
+{
+	session *sess;
+	char *stripped_topic;
+
+	sess = find_channel (serv, chan);
+	if (sess)
+	{
+		stripped_topic = strip_color (topic, -1, STRIP_ALL);
+		set_topic (sess, topic, stripped_topic);
+		g_free (stripped_topic);
+		EMIT_SIGNAL (XP_TE_NEWTOPIC, sess, nick, topic, chan, NULL, 0);
+	}
+}
+
+void
+inbound_join (server *serv, char *chan, char *user, char *ip)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_JOIN, sess, user, chan, ip, NULL, 0);
+		userlist_add (sess, user, ip);
+	}
+}
+
+void
+inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_KICK, sess, kicker, user, chan, reason, 0);
+		userlist_remove (sess, user);
+	}
+}
+
+void
+inbound_part (server *serv, char *chan, char *user, char *ip, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		if (*reason)
+			EMIT_SIGNAL (XP_TE_PARTREASON, sess, user, ip, chan, reason, 0);
+		else
+			EMIT_SIGNAL (XP_TE_PART, sess, user, ip, chan, NULL, 0);
+		userlist_remove (sess, user);
+	}
+}
+
+void
+inbound_topictime (server *serv, char *chan, char *nick, time_t stamp)
+{
+	char *tim = ctime (&stamp);
+	session *sess = find_channel (serv, chan);
+
+	if (!sess)
+		sess = serv->server_session;
+
+	tim[24] = 0;	/* get rid of the \n */
+	EMIT_SIGNAL (XP_TE_TOPICDATE, sess, chan, nick, tim, NULL, 0);
+}
+
+void
+inbound_quit (server *serv, char *nick, char *ip, char *reason)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int was_on_front_session = FALSE;
+
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+		{
+ 			if (sess == current_sess)
+ 				was_on_front_session = TRUE;
+			if (userlist_remove (sess, nick))
+			{
+				EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0);
+			} else if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
+			{
+				EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0);
+			}
+		}
+		list = list->next;
+	}
+
+	notify_set_offline (serv, nick, was_on_front_session);
+}
+
+void
+inbound_ping_reply (session *sess, char *timestring, char *from)
+{
+	unsigned long tim, nowtim, dif;
+	int lag = 0;
+	char outbuf[64];
+
+	if (strncmp (timestring, "LAG", 3) == 0)
+	{
+		timestring += 3;
+		lag = 1;
+	}
+
+	tim = strtoul (timestring, NULL, 10);
+	nowtim = make_ping_time ();
+	dif = nowtim - tim;
+
+	sess->server->ping_recv = time (0);
+
+	if (lag)
+	{
+		sess->server->lag_sent = 0;
+		sess->server->lag = dif / 1000;
+		fe_set_lag (sess->server, dif / 100000);
+		return;
+	}
+
+	if (atol (timestring) == 0)
+	{
+		if (sess->server->lag_sent)
+			sess->server->lag_sent = 0;
+		else
+			EMIT_SIGNAL (XP_TE_PINGREP, sess, from, "?", NULL, NULL, 0);
+	} else
+	{
+		snprintf (outbuf, sizeof (outbuf), "%ld.%ld%ld", dif / 1000000, (dif / 100000) % 10, dif % 10);
+		EMIT_SIGNAL (XP_TE_PINGREP, sess, from, outbuf, NULL, NULL, 0);
+	}
+}
+
+static session *
+find_session_from_type (int type, server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == type && serv == sess->server)
+			return sess;
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id)
+{
+	char *po,*ptr=to;
+	session *sess = 0;
+	int server_notice = FALSE;
+
+	if (is_channel (serv, ptr))
+		sess = find_channel (serv, ptr);
+
+	if (!sess && ptr[0] == '@')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (!sess && ptr[0] == '%')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (!sess && ptr[0] == '+')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (strcmp (nick, ip) == 0)
+		server_notice = TRUE;
+
+	if (!sess)
+	{
+		ptr = 0;
+		if (prefs.notices_tabs)
+		{
+			int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES;
+			sess = find_session_from_type (stype, serv);
+			if (!sess)
+			{
+				if (stype == SESS_NOTICES)
+					sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0);
+				else
+					sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0);
+				fe_set_channel (sess);
+				fe_set_title (sess);
+				fe_set_nonchannel (sess, FALSE);
+				userlist_clear (sess);
+				log_open_or_close (sess);
+			}
+			/* Avoid redundancy with some Undernet notices */
+			if (!strncmp (msg, "*** Notice -- ", 14))
+				msg += 14;
+		} else
+		{
+											/* paranoia check */
+			if (msg[0] == '[' && (!serv->have_idmsg || id))
+			{
+				/* guess where chanserv meant to post this -sigh- */
+				if (!strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick))
+				{
+					char *dest = strdup (msg + 1);
+					char *end = strchr (dest, ']');
+					if (end)
+					{
+						*end = 0;
+						sess = find_channel (serv, dest);
+					}
+					free (dest);
+				}
+			}
+			if (!sess)
+				sess = find_session_from_nick (nick, serv);
+		}
+		if (!sess)
+		{
+			if (server_notice)	
+				sess = serv->server_session;
+			else
+				sess = serv->front_session;
+		}
+	}
+
+	if (msg[0] == 1)
+	{
+		msg++;
+		if (!strncmp (msg, "PING", 4))
+		{
+			inbound_ping_reply (sess, msg + 5, nick);
+			return;
+		}
+	}
+	po = strchr (msg, '\001');
+	if (po)
+		po[0] = 0;
+
+	if (server_notice)
+		EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0);
+	else if (ptr)
+		EMIT_SIGNAL (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0);
+}
+
+void
+inbound_away (server *serv, char *nick, char *msg)
+{
+	struct away_msg *away = server_away_find_message (serv, nick);
+	session *sess = NULL;
+	GSList *list;
+
+	if (away && !strcmp (msg, away->message))	/* Seen the msg before? */
+	{
+		if (prefs.show_away_once && !serv->inside_whois)
+			return;
+	} else
+	{
+		server_away_save_message (serv, nick, msg);
+	}
+
+	if (prefs.irc_whois_front)
+		sess = serv->front_session;
+	else
+	{
+		if (!serv->inside_whois)
+			sess = find_session_from_nick (nick, serv);
+		if (!sess)
+			sess = serv->server_session;
+	}
+
+	/* possibly hide the output */
+	if (!serv->inside_whois || !serv->skip_next_whois)
+		EMIT_SIGNAL (XP_TE_WHOIS5, sess, nick, msg, NULL, NULL, 0);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+			userlist_set_away (sess, nick, TRUE);
+		list = list->next;
+	}
+}
+
+int
+inbound_nameslist_end (server *serv, char *chan)
+{
+	session *sess;
+	GSList *list;
+
+	if (!strcmp (chan, "*"))
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->server == serv)
+			{
+				sess->end_of_names = TRUE;
+				sess->ignore_names = FALSE;
+			}
+			list = list->next;
+		}
+		return TRUE;
+	}
+	sess = find_channel (serv, chan);
+	if (sess)
+	{
+		sess->end_of_names = TRUE;
+		sess->ignore_names = FALSE;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+check_autojoin_channels (server *serv)
+{
+	char *po;
+	session *sess;
+	GSList *list = sess_list;
+	int i = 0;
+	GSList *channels, *keys;
+
+	/* shouldnt really happen, the io tag is destroyed in server.c */
+	if (!is_server (serv))
+		return FALSE;
+
+	/* send auto join list */
+	if (serv->autojoin)
+	{
+		joinlist_split (serv->autojoin, &channels, &keys);
+		serv->p_join_list (serv, channels, keys);
+		joinlist_free (channels, keys);
+
+		free (serv->autojoin);
+		serv->autojoin = NULL;
+	}
+
+	/* this is really only for re-connects when you
+    * join channels not in the auto-join list. */
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->willjoinchannel[0] != 0)
+			{
+				strcpy (sess->waitchannel, sess->willjoinchannel);
+				sess->willjoinchannel[0] = 0;
+				serv->p_join (serv, sess->waitchannel, sess->channelkey);
+				po = strchr (sess->waitchannel, ',');
+				if (po)
+					*po = 0;
+				po = strchr (sess->waitchannel, ' ');
+				if (po)
+					*po = 0;
+				i++;
+			}
+		}
+		list = list->next;
+	}
+	serv->joindelay_tag = 0;
+	fe_server_event (serv, FE_SE_LOGGEDIN, i);
+	return FALSE;
+}
+
+void
+inbound_next_nick (session *sess, char *nick)
+{
+	char *newnick;
+	server *serv = sess->server;
+	ircnet *net;
+
+	serv->nickcount++;
+
+	switch (serv->nickcount)
+	{
+	case 2:
+		newnick = prefs.nick2;
+		net = serv->network;
+		/* use network specific "Second choice"? */
+		if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2)
+			newnick = net->nick2;
+		serv->p_change_nick (serv, newnick);
+		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0);
+		break;
+
+	case 3:
+		serv->p_change_nick (serv, prefs.nick3);
+		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.nick3, NULL, NULL, 0);
+		break;
+
+	default:
+		EMIT_SIGNAL (XP_TE_NICKFAIL, sess, NULL, NULL, NULL, NULL, 0);
+	}
+}
+
+void
+do_dns (session *sess, char *nick, char *host)
+{
+	char *po;
+	char tbuf[1024];
+
+	po = strrchr (host, '@');
+	if (po)
+		host = po + 1;
+	EMIT_SIGNAL (XP_TE_RESOLVINGUSER, sess, nick, host, NULL, NULL, 0);
+	snprintf (tbuf, sizeof (tbuf), "exec -d %s %s", prefs.dnsprogram, host);
+	handle_command (sess, tbuf, FALSE);
+}
+
+static void
+set_default_modes (server *serv)
+{
+	char modes[8];
+
+	modes[0] = '+';
+	modes[1] = '\0';
+
+	if (prefs.wallops)
+		strcat (modes, "w");
+	if (prefs.servernotice)
+		strcat (modes, "s");
+	if (prefs.invisible)
+		strcat (modes, "i");
+
+	if (modes[1] != '\0')
+	{
+		serv->p_mode (serv, serv->nick, modes);
+	}
+}
+
+void
+inbound_login_start (session *sess, char *nick, char *servname)
+{
+	inbound_newnick (sess->server, sess->server->nick, nick, TRUE);
+	server_set_name (sess->server, servname);
+	if (sess->type == SESS_SERVER)
+		log_open_or_close (sess);
+	/* reset our away status */
+	if (sess->server->reconnect_away)
+	{
+		handle_command (sess->server->server_session, "away", FALSE);
+		sess->server->reconnect_away = FALSE;
+	}
+}
+
+static void
+inbound_set_all_away_status (server *serv, char *nick, unsigned int status)
+{
+	GSList *list;
+	session *sess;
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+			userlist_set_away (sess, nick, status);
+		list = list->next;
+	}
+}
+
+void
+inbound_uaway (server *serv)
+{
+	serv->is_away = TRUE;
+	serv->away_time = time (NULL);
+	fe_set_away (serv);
+
+	inbound_set_all_away_status (serv, serv->nick, 1);
+}
+
+void
+inbound_uback (server *serv)
+{
+	serv->is_away = FALSE;
+	serv->reconnect_away = FALSE;
+	fe_set_away (serv);
+
+	inbound_set_all_away_status (serv, serv->nick, 0);
+}
+
+void
+inbound_foundip (session *sess, char *ip)
+{
+	struct hostent *HostAddr;
+
+	HostAddr = gethostbyname (ip);
+	if (HostAddr)
+	{
+		prefs.dcc_ip = ((struct in_addr *) HostAddr->h_addr)->s_addr;
+		EMIT_SIGNAL (XP_TE_FOUNDIP, sess,
+						 inet_ntoa (*((struct in_addr *) HostAddr->h_addr)),
+						 NULL, NULL, NULL, 0);
+	}
+}
+
+void
+inbound_user_info_start (session *sess, char *nick)
+{
+	/* set away to FALSE now, 301 may turn it back on */
+	inbound_set_all_away_status (sess->server, nick, 0);
+}
+
+/* reporting new information found about this user. chan may be NULL.
+ * away may be 0xff to indicate UNKNOWN. */
+
+void
+inbound_user_info (session *sess, char *chan, char *user, char *host,
+						 char *servname, char *nick, char *realname,
+						 unsigned int away)
+{
+	server *serv = sess->server;
+	session *who_sess;
+	GSList *list;
+	char *uhost = NULL;
+
+	if (user && host)
+	{
+		uhost = g_malloc (strlen (user) + strlen (host) + 2);
+		sprintf (uhost, "%s@%s", user, host);
+	}
+
+	if (chan)
+	{
+		who_sess = find_channel (serv, chan);
+		if (who_sess)
+			userlist_add_hostname (who_sess, nick, uhost, realname, servname, away);
+		else
+		{
+			if (serv->doing_dns && nick && host)
+				do_dns (sess, nick, host);
+		}
+	}
+	else
+	{
+		/* came from WHOIS, not channel specific */
+		for (list = sess_list; list; list = list->next)
+		{
+			sess = list->data;
+			if (sess->type == SESS_CHANNEL && sess->server == serv)
+			{
+				userlist_add_hostname (sess, nick, uhost, realname, servname, away);
+			}
+		}
+	}
+
+	g_free (uhost);
+}
+
+int
+inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption)
+{
+	char *time_str = ctime (&stamp);
+	server *serv = sess->server;
+
+	time_str[19] = 0;	/* get rid of the \n */
+	if (stamp == 0)
+		time_str = "";
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		goto nowindow;
+	}
+
+   if (!fe_is_banwindow (sess))
+	{
+nowindow:
+		/* let proto-irc.c do the 'goto def' for exemptions */
+		if (is_exemption)
+			return FALSE;
+
+		EMIT_SIGNAL (XP_TE_BANLIST, sess, chan, mask, banner, time_str, 0);
+		return TRUE;
+	}
+
+	fe_add_ban_list (sess, mask, banner, time_str, is_exemption);
+	return TRUE;
+}
+
+/* execute 1 end-of-motd command */
+
+static int
+inbound_exec_eom_cmd (char *str, void *sess)
+{
+	handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE);
+	return 1;
+}
+
+void
+inbound_login_end (session *sess, char *text)
+{
+	server *serv = sess->server;
+
+	if (!serv->end_of_motd)
+	{
+		if (prefs.ip_from_server && serv->use_who)
+		{
+			serv->skip_next_userhost = TRUE;
+			serv->p_get_ip_uh (serv, serv->nick);	/* sends USERHOST mynick */
+		}
+		set_default_modes (serv);
+
+		if (serv->network)
+		{
+			/* there may be more than 1, separated by \n */
+			if (((ircnet *)serv->network)->command)
+				token_foreach (((ircnet *)serv->network)->command, '\n',
+									inbound_exec_eom_cmd, sess);
+
+			/* send nickserv password */
+			if (((ircnet *)serv->network)->nickserv)
+				serv->p_ns_identify (serv, ((ircnet *)serv->network)->nickserv);
+		}
+
+		/* send JOIN now or wait? */
+		if (serv->network && ((ircnet *)serv->network)->nickserv &&
+			 prefs.irc_join_delay)
+			serv->joindelay_tag = fe_timeout_add (prefs.irc_join_delay * 1000,
+															  check_autojoin_channels, serv);
+		else
+			check_autojoin_channels (serv);
+		if (serv->supports_watch)
+			notify_send_watches (serv);
+		serv->end_of_motd = TRUE;
+	}
+	if (prefs.skipmotd && !serv->motd_skipped)
+	{
+		serv->motd_skipped = TRUE;
+		EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL,
+						 NULL, NULL, 0);
+		return;
+	}
+	EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL,
+					 NULL, NULL, 0);
+}
+
+void
+inbound_identified (server *serv)	/* 'MODE +e MYSELF' on freenode */
+{
+	if (serv->joindelay_tag)
+	{
+		/* stop waiting, just auto JOIN now */
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+		check_autojoin_channels (serv);
+	}
+}
diff --git a/src/common/inbound.h b/src/common/inbound.h
new file mode 100644
index 00000000..b972981f
--- /dev/null
+++ b/src/common/inbound.h
@@ -0,0 +1,39 @@
+#ifndef XCHAT_INBOUND_H
+#define XCHAT_INBOUND_H
+
+void inbound_next_nick (session *sess, char *nick);
+void inbound_uback (server *serv);
+void inbound_uaway (server *serv);
+void inbound_part (server *serv, char *chan, char *user, char *ip, char *reason);
+void inbound_upart (server *serv, char *chan, char *ip, char *reason);
+void inbound_ukick (server *serv, char *chan, char *kicker, char *reason);
+void inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason);
+void inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id);
+void inbound_quit (server *serv, char *nick, char *ip, char *reason);
+void inbound_topicnew (server *serv, char *nick, char *chan, char *topic);
+void inbound_join (server *serv, char *chan, char *user, char *ip);
+void inbound_ujoin (server *serv, char *chan, char *nick, char *ip);
+void inbound_topictime (server *serv, char *chan, char *nick, time_t stamp);
+void inbound_topic (server *serv, char *chan, char *topic_text);
+void inbound_user_info_start (session *sess, char *nick);
+void inbound_user_info (session *sess, char *chan, char *user, char *host, char *servname, char *nick, char *realname, unsigned int away);
+void inbound_foundip (session *sess, char *ip);
+int inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption);
+void inbound_ping_reply (session *sess, char *timestring, char *from);
+void inbound_nameslist (server *serv, char *chan, char *names);
+int inbound_nameslist_end (server *serv, char *chan);
+void inbound_away (server *serv, char *nick, char *msg);
+void inbound_login_start (session *sess, char *nick, char *servname);
+void inbound_login_end (session *sess, char *text);
+void inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id);
+void clear_channel (session *sess);
+void set_topic (session *sess, char *topic, char *stripped_topic);
+void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id);
+void inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id);
+void inbound_newnick (server *serv, char *nick, char *newnick, int quiet);
+void do_dns (session *sess, char *nick, char *host);
+void inbound_identified (server *serv);
+gboolean alert_match_word (char *word, char *masks);
+gboolean alert_match_text (char *text, char *masks);
+
+#endif
diff --git a/src/common/inet.h b/src/common/inet.h
new file mode 100644
index 00000000..b420c9c6
--- /dev/null
+++ b/src/common/inet.h
@@ -0,0 +1,43 @@
+/* include stuff for internet */
+
+#ifndef WIN32
+
+#ifdef WANTSOCKET
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+#ifdef WANTARPA
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#ifdef WANTDNS
+#include <netdb.h>
+#endif
+#define closesocket close
+#define set_blocking(sok) fcntl(sok, F_SETFL, 0)
+#define set_nonblocking(sok) fcntl(sok, F_SETFL, O_NONBLOCK)
+#define would_block() (errno == EAGAIN || errno == EWOULDBLOCK)
+#define sock_error() (errno)
+
+#else
+
+#ifdef USE_IPV6
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <tpipv6.h>
+#else
+#include <winsock.h>
+#endif
+
+#define set_blocking(sok)	{ \
+									unsigned long zero = 0; \
+									ioctlsocket (sok, FIONBIO, &zero); \
+									}
+#define set_nonblocking(sok)	{ \
+										unsigned long one = 1; \
+										ioctlsocket (sok, FIONBIO, &one); \
+										}
+#define would_block() (WSAGetLastError() == WSAEWOULDBLOCK)
+#define sock_error WSAGetLastError
+
+#endif
diff --git a/src/common/make-te.c b/src/common/make-te.c
new file mode 100644
index 00000000..ed87df3d
--- /dev/null
+++ b/src/common/make-te.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+	char name[512];
+	char num[512];
+	char help[512];
+	char def[512];
+	char args[512];
+	char buf[512];
+	char *defines[512];
+  	int i = 0, max;
+
+	printf("/* this file is auto generated, edit textevents.in instead! */\n\nconst struct text_event te[] = {\n");
+	while(fgets(name, sizeof(name), stdin))
+	{
+		name[strlen(name)-1] = 0;
+		fgets(num, sizeof(num), stdin);
+		num[strlen(num)-1] = 0;
+		fgets(help, sizeof(help), stdin);
+		help[strlen(help)-1] = 0;
+		fgets(def, sizeof(def), stdin);
+		def[strlen(def)-1] = 0;
+		fgets(args, sizeof(args), stdin);
+		args[strlen(args)-1] = 0;
+		fgets(buf, sizeof(buf), stdin);
+
+		if (args[0] == 'n')
+			printf("\n{\"%s\", %s, %d, \n\"%s\"},\n",
+							 name, help, atoi(args+1) | 128, def);
+		else
+			printf("\n{\"%s\", %s, %d, \nN_(\"%s\")},\n",
+							 name, help, atoi(args), def);
+		defines[i] = strdup (num);
+		i++;
+	}
+
+	printf("};\n");
+	
+	fprintf(stderr, "/* this file is auto generated, edit textevents.in instead! */\n\nenum\n{\n");
+	max = i;
+	i = 0;
+	while (i < max)
+	{
+		if (i + 1 < max)
+		{
+			fprintf(stderr, "\t%s,\t\t%s,\n", defines[i], defines[i+1]);
+			i++;
+		} else
+			fprintf(stderr, "\t%s,\n", defines[i]);
+		i++;
+	}
+	fprintf(stderr, "\tNUM_XP\n};\n");
+
+	return 0;
+}
diff --git a/src/common/modes.c b/src/common/modes.c
new file mode 100644
index 00000000..1acf7f54
--- /dev/null
+++ b/src/common/modes.c
@@ -0,0 +1,836 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "xchat.h"
+#include "xchatc.h"
+#include "modes.h"
+#include "server.h"
+#include "text.h"
+#include "fe.h"
+#include "util.h"
+#include "inbound.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+typedef struct
+{
+	server *serv;
+	char *op;
+	char *deop;
+	char *voice;
+	char *devoice;
+} mode_run;
+
+static int is_prefix_char (server * serv, char c);
+static void record_chan_mode (session *sess, char sign, char mode, char *arg);
+static char *mode_cat (char *str, char *addition);
+static void handle_single_mode (mode_run *mr, char sign, char mode, char *nick, char *chan, char *arg, int quiet, int is_324);
+static int mode_has_arg (server *serv, char sign, char mode);
+static void mode_print_grouped (session *sess, char *nick, mode_run *mr);
+static int mode_chanmode_type (server * serv, char mode);
+
+
+/* word[] - list of nicks.
+   wpos   - index into word[]. Where nicks really start.
+   end    - index into word[]. Last entry plus one.
+   sign   - a char, e.g. '+' or '-'
+   mode   - a mode, e.g. 'o' or 'v'	*/
+void
+send_channel_modes (session *sess, char *tbuf, char *word[], int wpos,
+						  int end, char sign, char mode, int modes_per_line)
+{
+	int usable_modes, orig_len, len, wlen, i, max;
+	server *serv = sess->server;
+
+	/* sanity check. IRC RFC says three per line. */
+	if (serv->modes_per_line < 3)
+		serv->modes_per_line = 3;
+	if (modes_per_line < 1)
+		modes_per_line = serv->modes_per_line;
+
+	/* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */
+	/* 512 - 6 - 2 - 1 - strlen(chan) */
+	max = 503 - strlen (sess->channel);
+
+	while (wpos < end)
+	{
+		tbuf[0] = '\0';
+		orig_len = len = 0;
+
+		/* we'll need this many modechars too */
+		len += modes_per_line;
+
+		/* how many can we fit? */
+		for (i = 0; i < modes_per_line; i++)
+		{
+			/* no more nicks left? */
+			if (wpos + i >= end)
+				break;
+			wlen = strlen (word[wpos + i]) + 1;
+			if (wlen + len > max)
+				break;
+			len += wlen; /* length of our whole string so far */
+		}
+		if (i < 1)
+			return;
+		usable_modes = i;	/* this is how many we'll send on this line */
+
+		/* add the +/-modemodemodemode */
+		len = orig_len;
+		tbuf[len] = sign;
+		len++;
+		for (i = 0; i < usable_modes; i++)
+		{
+			tbuf[len] = mode;
+			len++;
+		}
+		tbuf[len] = 0;	/* null terminate for the strcat() to work */
+
+		/* add all the nicknames */
+		for (i = 0; i < usable_modes; i++)
+		{
+			strcat (tbuf, " ");
+			strcat (tbuf, word[wpos + i]);
+		}
+		serv->p_mode (serv, sess->channel, tbuf);
+
+		wpos += usable_modes;
+	}
+}
+
+/* does 'chan' have a valid prefix? e.g. # or & */
+
+int
+is_channel (server * serv, char *chan)
+{
+	if (strchr (serv->chantypes, chan[0]))
+		return 1;
+	return 0;
+}
+
+/* is the given char a valid nick mode char? e.g. @ or + */
+
+static int
+is_prefix_char (server * serv, char c)
+{
+	int pos = 0;
+	char *np = serv->nick_prefixes;
+
+	while (np[0])
+	{
+		if (np[0] == c)
+			return pos;
+		pos++;
+		np++;
+	}
+
+	if (serv->bad_prefix)
+	{
+		if (strchr (serv->bad_nick_prefixes, c))
+		/* valid prefix char, but mode unknown */
+			return -2;
+	}
+
+	return -1;
+}
+
+/* returns '@' for ops etc... */
+
+char
+get_nick_prefix (server * serv, unsigned int access)
+{
+	int pos;
+	char c;
+
+	for (pos = 0; pos < USERACCESS_SIZE; pos++)
+	{
+		c = serv->nick_prefixes[pos];
+		if (c == 0)
+			break;
+		if (access & (1 << pos))
+			return c;
+	}
+
+	return 0;
+}
+
+/* returns the access bitfield for a nickname. E.g.
+	@nick would return 000010 in binary
+	%nick would return 000100 in binary
+	+nick would return 001000 in binary */
+
+unsigned int
+nick_access (server * serv, char *nick, int *modechars)
+{
+	int i;
+	unsigned int access = 0;
+	char *orig = nick;
+
+	while (*nick)
+	{
+		i = is_prefix_char (serv, *nick);
+		if (i == -1)
+			break;
+
+		/* -2 == valid prefix char, but mode unknown */
+		if (i != -2)
+			access |= (1 << i);
+
+		nick++;
+	}
+
+	*modechars = nick - orig;
+
+	return access;
+}
+
+/* returns the access number for a particular mode. e.g.
+	mode 'a' returns 0
+	mode 'o' returns 1
+	mode 'h' returns 2
+	mode 'v' returns 3
+	Also puts the nick-prefix-char in 'prefix' */
+
+int
+mode_access (server * serv, char mode, char *prefix)
+{
+	int pos = 0;
+
+	while (serv->nick_modes[pos])
+	{
+		if (serv->nick_modes[pos] == mode)
+		{
+			*prefix = serv->nick_prefixes[pos];
+			return pos;
+		}
+		pos++;
+	}
+
+	*prefix = 0;
+
+	return -1;
+}
+
+static void
+record_chan_mode (session *sess, char sign, char mode, char *arg)
+{
+	/* Somebody needed to acutally update sess->current_modes, needed to
+		play nice with bouncers, and less mode calls. Also keeps modes up
+		to date for scripts */
+	server *serv = sess->server;
+	GString *current = g_string_new(sess->current_modes);
+	gint mode_pos = -1;
+	gchar *current_char = current->str;
+	gint modes_length;
+	gint argument_num = 0;
+	gint argument_offset = 0;
+	gint argument_length = 0;
+	int i = 0;
+	gchar *arguments_start;
+
+	/* find out if the mode currently exists */
+	arguments_start = g_strstr_len(current->str	, -1, " ");
+	if (arguments_start) {
+		modes_length = arguments_start - current->str;
+	}
+	else {
+		modes_length = current->len;
+		/* set this to the end of the modes */
+		arguments_start = current->str + current->len;
+	}
+
+	while (mode_pos == -1 && i < modes_length)
+	{
+		if (*current_char == mode)
+		{
+			mode_pos = i;
+		}
+		else
+		{
+			i++;
+			current_char++;
+		}
+	}
+
+	/* if the mode currently exists and has an arg, need to know where
+	 * (including leading space) */
+	if (mode_pos != -1 && mode_has_arg(serv, '+', mode))
+	{
+		current_char = current->str;
+
+		i = 0;
+		while (i <= mode_pos)
+		{
+			if (mode_has_arg(serv, '+', *current_char))
+				argument_num++;
+			current_char++;
+			i++;
+		}
+
+		/* check through arguments for where to start */
+		current_char = arguments_start;
+		i = 0;
+		while (i < argument_num && *current_char != '\0')
+		{
+			if (*current_char == ' ')
+				i++;
+			if (i != argument_num)
+				current_char++;
+		}
+		argument_offset = current_char - current->str;
+
+		/* how long the existing argument is for this key
+		 * important for malloc and strncpy */
+		if (i == argument_num)
+		{
+			argument_length++;
+			current_char++;
+			while (*current_char != '\0' && *current_char != ' ')
+			{
+				argument_length++;
+				current_char++;
+			}
+		}
+	}
+
+	/* two cases, adding and removing a mode, handled differently */
+	if (sign == '+')
+	{
+		if (mode_pos != -1)
+		{
+			/* if it already exists, only need to do something (change)
+			 * if there should be a param */
+			if (mode_has_arg(serv, sign, mode))
+			{
+				/* leave the old space there */
+				current = g_string_erase(current, argument_offset+1, argument_length-1);
+				current = g_string_insert(current, argument_offset+1, arg);
+
+				free(sess->current_modes);
+				sess->current_modes = g_string_free(current, FALSE);
+			}
+		}
+		/* mode wasn't there before */
+		else
+		{
+			/* insert the new mode character */
+			current = g_string_insert_c(current, modes_length, mode);
+
+			/* add the argument, with space if there is one */
+			if (mode_has_arg(serv, sign, mode))
+			{
+				current = g_string_append_c(current, ' ');
+				current = g_string_append(current, arg);
+			}
+
+			free(sess->current_modes);
+			sess->current_modes = g_string_free(current, FALSE);
+		}
+	}
+	else if (sign == '-' && mode_pos != -1)
+	{
+		/* remove the argument first if it has one*/
+		if (mode_has_arg(serv, '+', mode))
+			current = g_string_erase(current, argument_offset, argument_length);
+
+		/* remove the mode character */
+		current = g_string_erase(current, mode_pos, 1);
+
+		free(sess->current_modes);
+		sess->current_modes = g_string_free(current, FALSE);
+	}
+}
+
+static char *
+mode_cat (char *str, char *addition)
+{
+	int len;
+
+	if (str)
+	{
+		len = strlen (str) + strlen (addition) + 2;
+		str = realloc (str, len);
+		strcat (str, " ");
+		strcat (str, addition);
+	} else
+	{
+		str = strdup (addition);
+	}
+
+	return str;
+}
+
+/* handle one mode, e.g.
+   handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */
+
+static void
+handle_single_mode (mode_run *mr, char sign, char mode, char *nick,
+						  char *chan, char *arg, int quiet, int is_324)
+{
+	session *sess;
+	server *serv = mr->serv;
+	char outbuf[4];
+
+	outbuf[0] = sign;
+	outbuf[1] = 0;
+	outbuf[2] = mode;
+	outbuf[3] = 0;
+
+	sess = find_channel (serv, chan);
+	if (!sess || !is_channel (serv, chan))
+	{
+		/* got modes for a chan we're not in! probably nickmode +isw etc */
+		sess = serv->front_session;
+		goto genmode;
+	}
+
+	/* is this a nick mode? */
+	if (strchr (serv->nick_modes, mode))
+	{
+		/* update the user in the userlist */
+		userlist_update_mode (sess, /*nickname */ arg, mode, sign);
+	} else
+	{
+		if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1)
+			record_chan_mode (sess, sign, mode, arg);
+	}
+
+	switch (sign)
+	{
+	case '+':
+		switch (mode)
+		{
+		case 'k':
+			safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey));
+			fe_update_channel_key (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANSETKEY, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'l':
+			sess->limit = atoi (arg);
+			fe_update_channel_limit (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'o':
+			if (!quiet)
+				mr->op = mode_cat (mr->op, arg);
+			return;
+		case 'h':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'v':
+			if (!quiet)
+				mr->voice = mode_cat (mr->voice, arg);
+			return;
+		case 'b':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'e':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANEXEMPT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'I':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL, 0);
+			return;
+		}
+		break;
+	case '-':
+		switch (mode)
+		{
+		case 'k':
+			sess->channelkey[0] = 0;
+			fe_update_channel_key (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMKEY, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case 'l':
+			sess->limit = 0;
+			fe_update_channel_limit (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case 'o':
+			if (!quiet)
+				mr->deop = mode_cat (mr->deop, arg);
+			return;
+		case 'h':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANDEHOP, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'v':
+			if (!quiet)
+				mr->devoice = mode_cat (mr->devoice, arg);
+			return;
+		case 'b':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'e':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'I':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMINVITE, sess, nick, arg, NULL, NULL, 0);
+			return;
+		}
+	}
+
+	fe_update_mode_buttons (sess, mode, sign);
+
+ genmode:
+	/* Received umode +e. If we're waiting to send JOIN then send now! */
+	if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick))
+		inbound_identified (serv);
+
+	if (!quiet)
+	{
+		if (*arg)
+		{
+			char *buf = malloc (strlen (chan) + strlen (arg) + 2);
+			sprintf (buf, "%s %s", chan, arg);
+			EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, buf, 0);
+			free (buf);
+		} else
+			EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, chan, 0);
+	}
+}
+
+/* does this mode have an arg? like +b +l +o */
+
+static int
+mode_has_arg (server * serv, char sign, char mode)
+{
+	int type;
+
+	/* if it's a nickmode, it must have an arg */
+	if (strchr (serv->nick_modes, mode))
+		return 1;
+
+	type = mode_chanmode_type (serv, mode);
+	switch (type)
+	{
+	case 0:					  /* type A */
+	case 1:					  /* type B */
+		return 1;
+	case 2:					  /* type C */
+		if (sign == '+')
+			return 1;
+	case 3:					  /* type D */
+		return 0;
+	default:
+		return 0;
+	}
+
+}
+
+/* what type of chanmode is it? -1 for not in chanmode */
+static int
+mode_chanmode_type (server * serv, char mode)
+{
+	/* see what numeric 005 CHANMODES=xxx said */
+	char *cm = serv->chanmodes;
+	int type = 0;
+	int found = 0;
+
+	while (*cm && !found)
+	{
+		if (*cm == ',')
+		{
+			type++;
+		} else if (*cm == mode)
+		{
+			found = 1;
+		}
+		cm++;
+	}
+	if (found)
+		return type;
+	/* not found? -1 */
+	else
+		return -1;
+}
+
+static void
+mode_print_grouped (session *sess, char *nick, mode_run *mr)
+{
+	/* print all the grouped Op/Deops */
+	if (mr->op)
+	{
+		EMIT_SIGNAL (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0);
+		free (mr->op);
+		mr->op = NULL;
+	}
+
+	if (mr->deop)
+	{
+		EMIT_SIGNAL (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL, 0);
+		free (mr->deop);
+		mr->deop = NULL;
+	}
+
+	if (mr->voice)
+	{
+		EMIT_SIGNAL (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL, 0);
+		free (mr->voice);
+		mr->voice = NULL;
+	}
+
+	if (mr->devoice)
+	{
+		EMIT_SIGNAL (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL, NULL, 0);
+		free (mr->devoice);
+		mr->devoice = NULL;
+	}
+}
+
+
+/* handle a MODE or numeric 324 from server */
+
+void
+handle_mode (server * serv, char *word[], char *word_eol[],
+				 char *nick, int numeric_324)
+{
+	session *sess;
+	char *chan;
+	char *modes;
+	char *argstr;
+	char sign;
+	int len;
+	int arg;
+	int i, num_args;
+	int num_modes;
+	int offset = 3;
+	int all_modes_have_args = FALSE;
+	int using_front_tab = FALSE;
+	mode_run mr;
+
+	mr.serv = serv;
+	mr.op = mr.deop = mr.voice = mr.devoice = NULL;
+
+	/* numeric 324 has everything 1 word later (as opposed to MODE) */
+	if (numeric_324)
+		offset++;
+
+	chan = word[offset];
+	modes = word[offset + 1];
+	if (*modes == ':')
+		modes++;
+
+	if (*modes == 0)
+		return;	/* beyondirc's blank modes */
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		using_front_tab = TRUE;
+	}
+	/* remove trailing space */
+	len = strlen (word_eol[offset]) - 1;
+	if (word_eol[offset][len] == ' ')
+		word_eol[offset][len] = 0;
+
+	if (prefs.raw_modes && !numeric_324)
+		EMIT_SIGNAL (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0);
+
+	if (numeric_324 && !using_front_tab)
+	{
+		if (sess->current_modes)
+			free (sess->current_modes);
+		sess->current_modes = strdup (word_eol[offset+1]);
+	}
+
+	sign = *modes;
+	modes++;
+	arg = 1;
+
+	/* count the number of arguments (e.g. after the -o+v) */
+	num_args = 0;
+	i = 1;
+	while ((i + offset + 1) < PDIWORDS)
+	{
+		i++;
+		if (!(*word[i + offset]))
+			break;
+		num_args++;
+	}
+
+	/* count the number of modes (without the -/+ chars */
+	num_modes = 0;
+	i = 0;
+	while (i < strlen (modes))
+	{
+		if (modes[i] != '+' && modes[i] != '-')
+			num_modes++;
+		i++;
+	}
+
+	if (num_args == num_modes)
+		all_modes_have_args = TRUE;
+
+	while (*modes)
+	{
+		switch (*modes)
+		{
+		case '-':
+		case '+':
+			/* print all the grouped Op/Deops */
+			mode_print_grouped (sess, nick, &mr);
+			sign = *modes;
+			break;
+		default:
+			argstr = "";
+			if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args+1))
+			{
+				arg++;
+				argstr = word[arg + offset];
+			}
+			handle_single_mode (&mr, sign, *modes, nick, chan,
+									  argstr, numeric_324 || prefs.raw_modes,
+									  numeric_324);
+		}
+
+		modes++;
+	}
+
+	/* update the title at the end, now that the mode update is internal now */
+	if (!using_front_tab)
+		fe_set_title (sess);
+
+	/* print all the grouped Op/Deops */
+	mode_print_grouped (sess, nick, &mr);
+}
+
+/* handle the 005 numeric */
+
+void
+inbound_005 (server * serv, char *word[])
+{
+	int w;
+	char *pre;
+
+	w = 4;							  /* start at the 4th word */
+	while (w < PDIWORDS && *word[w])
+	{
+		if (strncmp (word[w], "MODES=", 6) == 0)
+		{
+			serv->modes_per_line = atoi (word[w] + 6);
+		} else if (strncmp (word[w], "CHANTYPES=", 10) == 0)
+		{
+			free (serv->chantypes);
+			serv->chantypes = strdup (word[w] + 10);
+		} else if (strncmp (word[w], "CHANMODES=", 10) == 0)
+		{
+			free (serv->chanmodes);
+			serv->chanmodes = strdup (word[w] + 10);
+		} else if (strncmp (word[w], "PREFIX=", 7) == 0)
+		{
+			pre = strchr (word[w] + 7, ')');
+			if (pre)
+			{
+				pre[0] = 0;			  /* NULL out the ')' */
+				free (serv->nick_prefixes);
+				free (serv->nick_modes);
+				serv->nick_prefixes = strdup (pre + 1);
+				serv->nick_modes = strdup (word[w] + 8);
+			} else
+			{
+				/* bad! some ircds don't give us the modes. */
+				/* in this case, we use it only to strip /NAMES */
+				serv->bad_prefix = TRUE;
+				if (serv->bad_nick_prefixes)
+					free (serv->bad_nick_prefixes);
+				serv->bad_nick_prefixes = strdup (word[w] + 7);
+			}
+		} else if (strncmp (word[w], "WATCH=", 6) == 0)
+		{
+			serv->supports_watch = TRUE;
+		} else if (strncmp (word[w], "NETWORK=", 8) == 0)
+		{
+/*			if (serv->networkname)
+				free (serv->networkname);
+			serv->networkname = strdup (word[w] + 8);*/
+
+			if (serv->server_session->type == SESS_SERVER)
+			{
+				safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN);
+				fe_set_channel (serv->server_session);
+			}
+
+			/* use /NICKSERV */
+			if (strcasecmp (word[w] + 8, "RusNet") == 0)
+				serv->nickservtype = 1;
+			else if (strcasecmp (word[w] + 8, "UniBG") == 0)
+				serv->nickservtype = 3;
+			else if (strcasecmp (word[w] + 8, "QuakeNet") == 0)
+				serv->nickservtype = 4;
+
+		} else if (strncmp (word[w], "CASEMAPPING=", 12) == 0)
+		{
+			if (strcmp (word[w] + 12, "ascii") == 0)	/* bahamut */
+				serv->p_cmp = (void *)strcasecmp;
+		} else if (strncmp (word[w], "CHARSET=", 8) == 0)
+		{
+			if (strcasecmp (word[w] + 8, "UTF-8") == 0)
+			{
+				server_set_encoding (serv, "UTF-8");
+			}
+		} else if (strcmp (word[w], "NAMESX") == 0)
+		{
+									/* 12345678901234567 */
+			tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
+		} else if (strcmp (word[w], "WHOX") == 0)
+		{
+			serv->have_whox = TRUE;
+		} else if (strcmp (word[w], "CAPAB") == 0)
+		{
+			serv->have_capab = TRUE;
+									/* 12345678901234567890 */
+			tcp_send_len (serv, "CAPAB IDENTIFY-MSG\r\n", 20);
+			/* now wait for numeric 290 */	
+		} else if (strcmp (word[w], "EXCEPTS") == 0)
+		{
+#ifndef WIN32
+			serv->have_except = TRUE;
+#endif
+		} else if (strncmp (word[w], "ELIST=", 6) == 0)
+		{
+			/* supports LIST >< min/max user counts? */
+			if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u'))
+				serv->use_listargs = TRUE;
+		}
+
+		w++;
+	}
+}
diff --git a/src/common/modes.h b/src/common/modes.h
new file mode 100644
index 00000000..3f9c4a72
--- /dev/null
+++ b/src/common/modes.h
@@ -0,0 +1,12 @@
+#ifndef XCHAT_MODES_H
+#define XCHAT_MODES_H
+
+int is_channel (server *serv, char *chan);
+char get_nick_prefix (server *serv, unsigned int access);
+unsigned int nick_access (server *serv, char *nick, int *modechars);
+int mode_access (server *serv, char mode, char *prefix);
+void inbound_005 (server *serv, char *word[]);
+void handle_mode (server *serv, char *word[], char *word_eol[], char *nick, int numeric_324);
+void send_channel_modes (session *sess, char *tbuf, char *word[], int start, int end, char sign, char mode, int modes_per_line);
+
+#endif
diff --git a/src/common/msproxy.c b/src/common/msproxy.c
new file mode 100644
index 00000000..9c85394d
--- /dev/null
+++ b/src/common/msproxy.c
@@ -0,0 +1,467 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+/*#define DEBUG_MSPROXY*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#include "xchat.h"
+#include "network.h"
+#include "xchatc.h"
+#include "server.h"
+#include "msproxy.h"
+
+
+#ifdef USE_MSPROXY
+#include <ntlm.h>
+
+static int
+send_msprequest(s, state, request, end)
+	int s;
+	struct msproxy_state_t *state;
+	struct msproxy_request_t *request;
+	char *end;
+{
+	ssize_t w;
+	size_t l;
+
+	request->magic25 = htonl(MSPROXY_VERSION);
+	request->serverack = state->seq_recv;
+	/* don't start incrementing sequence until we are acking packet #2. */
+	request->sequence	= (unsigned char)(request->serverack >= 2 ? state->seq_sent + 1 : 0);
+
+	memcpy(request->RWSP, "RWSP", sizeof(request->RWSP));
+
+	l = end - (char *)request;
+	/* all requests must be atleast MSPROXY_MINLENGTH it seems. */
+	if (l < MSPROXY_MINLENGTH) {
+		bzero(end, (size_t)(MSPROXY_MINLENGTH - l));
+		l = MSPROXY_MINLENGTH;
+	}
+
+	if ((w = send(s, request, l, 0)) != l) {
+#ifdef DEBUG_MSPROXY
+		printf ("send_msprequest(): send() failed (%d bytes sent instead of %d\n", w, l);
+		perror ("Error is");
+#endif
+		return -1;
+	}
+	state->seq_sent = request->sequence;
+
+	return w;
+}
+
+static int
+recv_mspresponse(s, state, response)
+	int s;
+	struct msproxy_state_t *state;
+	struct msproxy_response_t *response;
+{
+	ssize_t r;
+
+	do {
+		if ((r = recv (s, response, sizeof (*response), 0)) < MSPROXY_MINLENGTH) {
+#ifdef DEBUG_MSPROXY
+			printf ("recv_mspresponse(): expected to read atleast %d, read %d\n", MSPROXY_MINLENGTH, r);
+#endif
+			return -1;
+		}
+		if (state->seq_recv == 0)
+			break; /* not started incrementing yet. */
+#ifdef DEBUG_MSPROXY
+		if (response->sequence == state->seq_recv)
+			printf ("seq_recv: %d, dup response, seqnumber: 0x%x\n", state->seq_recv, response->sequence);
+#endif
+	} while (response->sequence == state->seq_recv);
+
+	state->seq_recv = response->sequence;
+
+	return r;
+}
+
+int
+traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound)
+{
+	struct msproxy_request_t req;
+	struct msproxy_response_t res;
+	char *data, *p;
+	char hostname[NT_MAXNAMELEN];
+	char ntdomain[NT_MAXNAMELEN];
+	char challenge[8];
+	netstore *ns_client;
+	int clientport;
+	guint32 destaddr;
+	guint32 flags;
+
+	if (!prefs.proxy_auth || !prefs.proxy_user[0] || !prefs.proxy_pass[0] )
+		return 1;
+
+	/* MS proxy protocol implementation currently doesn't support IPv6 */
+	destaddr = net_getsockaddr_v4 (ns_proxy);
+	if (!destaddr)
+		return 1;
+
+	state->seq_recv = 0;
+	state->seq_sent = 0;
+
+#ifdef DEBUG_MSPROXY
+	printf ("Connecting to %s:%d via MS proxy\n", serverAddr, port);
+#endif
+
+	gethostname (hostname, NT_MAXNAMELEN);
+	p = strchr (hostname, '.');
+        if (p)
+        	*p = '\0';
+
+	bzero (&req, sizeof(req));
+	req.clientid		= htonl(0x0a000000);	/* Initial client ID is always 0x0a		*/
+	req.command		= htons(MSPROXY_HELLO);	/* HELLO command					*/
+	req.packet.hello.magic5	= htons(0x4b00);	/* Fill in magic values				*/
+	req.packet.hello.magic10	= htons(0x1400);
+	req.packet.hello.magic15	= htons(0x0400);
+	req.packet.hello.magic20	= htons(0x5704);
+	req.packet.hello.magic25	= htons(0x0004);
+	req.packet.hello.magic30	= htons(0x0100);
+	req.packet.hello.magic35	= htons(0x4a02);
+	req.packet.hello.magic40	= htons(0x3000);
+	req.packet.hello.magic45	= htons(0x4400);
+	req.packet.hello.magic50	= htons(0x3900);
+	data = req.packet.hello.data;
+	strcpy (data, prefs.proxy_user);		/* Append a username				*/
+	data += strlen (prefs.proxy_user)+2;		/* +2 automatically creates second empty string	*/
+	strcpy (data, MSPROXY_EXECUTABLE);		/* Append an application name			*/
+	data += strlen (MSPROXY_EXECUTABLE)+1;
+	strcpy (data, hostname);				/* Append a hostname				*/
+	data += strlen (hostname)+1;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (strcmp(res.RWSP, "RWSP") != 0) {
+#ifdef DEBUG_MSPROXY
+		printf ("Received mailformed packet (no RWSP signature)\n");
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) >> 8 != 0x10) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 10??, is %x", ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	state->clientid	= htonl(rand());
+	state->serverid	= res.serverid;
+
+#ifdef DEBUG_MSPROXY
+	printf ("clientid: 0x%x, serverid: 0x%0x\n", state->clientid, state->serverid);
+	printf ("packet #2\n");
+#endif
+
+	/* almost identical. */
+	req.clientid	= state->clientid;
+	req.serverid	= state->serverid;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected serverid = 0x%x, is 0x%x\n",state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (res.sequence != 0x01) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.sequence = 0x01, is 0x%x\n", res.sequence);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) != MSPROXY_USERINFO_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+#ifdef DEBUG_MSPROXY
+	printf ("packet #3\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_AUTHENTICATE);
+	memcpy(req.packet.auth.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP"));
+	req.packet.auth.bindaddr	= htonl(0x02000000);
+	req.packet.auth.msgtype		= htonl(0x01000000);
+	/* NTLM flags:	0x80000000 Negotiate LAN Manager key
+			0x10000000 Negotiate sign
+			0x04000000 Request target
+			0x02000000 Negotiate OEM
+			0x00800000 Always sign
+			0x00020000 Negotiate NTLM
+	*/
+	req.packet.auth.flags		= htonl(0x06020000);
+
+	if (send_msprequest(sok, state, &req, &req.packet.auth.data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) != MSPROXY_AUTHENTICATE_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_AUTHENTICATE_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	flags = res.packet.auth.flags & htonl(0x00020000);			/* Remember if the server supports NTLM */
+	memcpy(challenge, &res.packet.auth.challenge, sizeof(challenge));
+	memcpy(ntdomain, &res.packet.auth.NTLMSSP[res.packet.auth.target.offset], res.packet.auth.target.len);
+	ntdomain[res.packet.auth.target.len] = 0;
+
+#ifdef DEBUG_MSPROXY
+	printf ("ntdomain: \"%s\"\n", ntdomain);
+	printf ("packet #4\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_AUTHENTICATE_2);	/* Authentication response			*/
+	req.packet.auth2.magic3		= htons(0x0200);			/* Something					*/
+	memcpy(req.packet.auth2.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP"));		/* Start of NTLM message				*/
+	req.packet.auth2.msgtype		= htonl(0x03000000);			/* Message type 2				*/
+	req.packet.auth2.flags		= flags | htonl(0x02000000);		/* Choose authentication method			*/
+	data = req.packet.auth2.data;
+	if (flags) {
+		req.packet.auth2.lm_resp.len	= 0;				/* We are here if NTLM is supported,		*/
+		req.packet.auth2.lm_resp.alloc	= 0;				/* Do not fill in insecure LM response		*/
+		req.packet.auth2.lm_resp.offset	= data - req.packet.auth2.NTLMSSP;
+		req.packet.auth2.ntlm_resp.len = 24;				/* Fill in NTLM response security buffer	*/
+		req.packet.auth2.ntlm_resp.alloc = 24;
+		req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP;
+		ntlm_smb_nt_encrypt(prefs.proxy_pass, challenge, data);		/* Append an NTLM response			*/
+		data += 24;	
+	} else {
+		req.packet.auth2.lm_resp.len	= 24;				/* Fill in LM response security buffer		*/
+		req.packet.auth2.lm_resp.alloc	= 24;
+		req.packet.auth2.lm_resp.offset	= data - req.packet.auth2.NTLMSSP;
+		ntlm_smb_encrypt(prefs.proxy_pass, challenge, data);		/* Append an LM response			*/
+		data += 24;
+		req.packet.auth2.ntlm_resp.len = 0;				/* NTLM response is empty			*/
+		req.packet.auth2.ntlm_resp.alloc = 0;
+		req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP;
+	}
+	req.packet.auth2.ntdomain_buf.len = strlen(ntdomain);			/* Domain name					*/
+	req.packet.auth2.ntdomain_buf.alloc = req.packet.auth2.ntdomain_buf.len;
+	req.packet.auth2.ntdomain_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, ntdomain);
+	data += req.packet.auth2.ntdomain_buf.len;
+	req.packet.auth2.username_buf.len = strlen(prefs.proxy_user);		/* Username					*/
+	req.packet.auth2.username_buf.alloc = req.packet.auth2.username_buf.len;
+	req.packet.auth2.username_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, prefs.proxy_user);
+	data += req.packet.auth2.username_buf.len;
+	req.packet.auth2.clienthost_buf.len = strlen(hostname);			/* Hostname					*/
+	req.packet.auth2.clienthost_buf.alloc = req.packet.auth2.clienthost_buf.len;
+	req.packet.auth2.clienthost_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, hostname);
+	data += req.packet.auth2.clienthost_buf.len;
+	req.packet.auth2.sessionkey_buf.len = 0;				/* Session key (we don't use it)		*/
+	req.packet.auth2.sessionkey_buf.alloc = 0;
+	req.packet.auth2.sessionkey_buf.offset = data - req.packet.auth2.NTLMSSP;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (res.clientack != 0x01) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.clientack = 0x01, is 0x%x\n", res.clientack);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) >> 8 != 0x47) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 47??, is 0x%x\n", ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) ==  MSPROXY_AUTHENTICATE_2_NAK) {
+#ifdef DEBUG_MSPROXY
+		printf ("Authentication failed\n");
+#endif
+		return -1;
+	}
+
+#ifdef DEBUG_MSPROXY
+	printf ("packet #5\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_CONNECT);
+	req.packet.connect.magic2	= htons(0x0200);
+	req.packet.connect.magic6	= htons(0x0200);
+	req.packet.connect.destport	= htons(port);
+	req.packet.connect.destaddr	= destaddr;
+	data = req.packet.connect.executable;
+	strcpy(data, MSPROXY_EXECUTABLE);
+	data += strlen(MSPROXY_EXECUTABLE) + 1;
+
+	/*
+	 * need to tell server what port we will connect from, so we bind our sockets.
+	 */
+	ns_client = net_store_new ();
+	if (!bound) {
+		net_store_fill_any (ns_client);
+		net_bind(ns_client, csok4, csok6);
+#ifdef DEBUG_MSPROXY
+		perror ("bind() result");
+#endif
+	}
+ 	clientport = net_getsockport(csok4, csok6);
+	if (clientport == -1) {
+#ifdef DEBUG_MSPROXY
+		printf ("Unable to obtain source port\n");
+#endif
+		return 1;
+	}
+	req.packet.connect.srcport = clientport;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (ntohs(res.command) != MSPROXY_CONNECT_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n",MSPROXY_CONNECT_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	net_store_fill_v4 (ns_client, res.packet.connect.clientaddr, res.packet.connect.clientport);
+
+#ifdef DEBUG_MSPROXY
+	printf ("Connecting...\n");
+#endif
+	if (net_connect (ns_client, csok4, csok6, csok) != 0) {
+#ifdef DEBUG_MSPROXY
+		printf ("Failed to connect to port %d\n", htons(res.packet.connect.clientport));
+#endif
+		net_store_destroy (ns_client);
+		return 1;
+	}
+	net_store_destroy (ns_client);
+#ifdef DEBUG_MSPROXY
+	printf ("packet #6\n");
+#endif
+
+	req.clientid	= state->clientid;
+	req.serverid	= state->serverid;
+	req.command	= htons(MSPROXY_USERINFO_ACK);		
+
+	if (send_msprequest(sok, state, &req, req.packet.connack.data) == -1)
+		return 1;
+
+	return 0;
+}
+
+void
+msproxy_keepalive (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	struct msproxy_request_t req;
+	struct msproxy_response_t res;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && (serv->proxy_sok != -1))
+		{
+#ifdef DEBUG_MSPROXY
+			printf ("sending MS proxy keepalive packet\n");
+#endif
+
+			bzero(&req, sizeof(req));
+			req.clientid	= serv->msp_state.clientid;
+			req.serverid	= serv->msp_state.serverid;
+			req.command	= htons(MSPROXY_HELLO);
+			
+			if (send_msprequest(serv->proxy_sok, &serv->msp_state, &req, req.packet.hello.data) == -1)
+				continue;
+
+			recv_mspresponse(serv->proxy_sok, &serv->msp_state, &res);
+
+#ifdef DEBUG_MSPROXY
+			if (ntohs(res.command) != MSPROXY_USERINFO_ACK)
+				printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command));
+#endif
+		}
+		list = list->next;
+	}
+}
+
+#endif
diff --git a/src/common/msproxy.h b/src/common/msproxy.h
new file mode 100644
index 00000000..d37c81c5
--- /dev/null
+++ b/src/common/msproxy.h
@@ -0,0 +1,257 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+#include "network.h"
+
+#define MSPROXY_EXECUTABLE 		"xchat.exe"	/* This probably can be used for access control on the server side */
+
+#define MSPROXY_MINLENGTH		172		/* minimum length of packet.				*/
+#define NT_MAXNAMELEN			17		/* maximum name length (domain etc), comes from NetBIOS */
+#define MSPROXY_VERSION			0x00010200	/* MS Proxy v2 ?					*/
+
+/* Commands / responses */
+#define MSPROXY_HELLO			0x0500	/* packet 1 from client.			*/
+#define MSPROXY_HELLO_ACK		0x1000	/* packet 1 from server.			*/
+
+#define MSPROXY_USERINFO_ACK		0x0400	/* packet 2 from server.			*/
+
+#define MSPROXY_AUTHENTICATE		0x4700	/* authentication request		*/
+#define MSPROXY_AUTHENTICATE_ACK	0x4714	/* authentication challenge		*/
+
+#define MSPROXY_AUTHENTICATE_2		0x4701	/* authentication response		*/
+#define MSPROXY_AUTHENTICATE_2_ACK	0x4715	/* authentication passed		*/
+#define MSPROXY_AUTHENTICATE_2_NAK	0x4716	/* authentication failure		*/
+
+#define MSPROXY_CONNECT			0x071e	/* connect request.			*/
+#define MSPROXY_CONNECT_ACK		0x0703	/* connect request accepted.		*/
+
+#pragma pack(1)
+
+struct ntlm_buffer {
+	guint16	len;
+	guint16	alloc;
+	guint32	offset;
+};
+
+struct msproxy_request_t {
+	guint32					clientid;			/* 1-4							*/
+	guint32					magic25;				/* 5-8							*/
+	guint32					serverid;			/* 9-12							*/
+	unsigned char				serverack;			/* 13: ack of last server packet			*/
+	char					pad10[3];			/* 14-16						*/
+	unsigned char				sequence;			/* 17: sequence # of this packet.			*/
+	char					pad11[7];			/* 18-24						*/
+	char					RWSP[4];			/* 25-28: 0x52,0x57,0x53,0x50				*/
+	char					pad15[8];			/* 29-36						*/
+	guint16					command;				/* 37-38						*/
+
+	/* packet specifics start at 39. */
+	union {
+		struct {
+			char			pad1[18];			/* 39-56						*/
+			guint16			magic3;				/* 57-58						*/
+			char           		pad3[114];			/* 59-172						*/
+			guint16			magic5;				/* 173-174: 0x4b, 0x00					*/
+			char			pad5[2];			/* 175-176						*/
+			guint16			magic10;				/* 177-178: 0x14, 0x00					*/
+			char			pad6[2];			/* 179-180						*/
+			guint16			magic15;				/* 181-182: 0x04, 0x00					*/
+			char			pad10[2];			/* 183-184						*/
+			guint16			magic16;				/* 185-186						*/
+			char			pad11[2];			/* 187-188						*/
+			guint16			magic20;				/* 189-190: 0x57, 0x04					*/
+			guint16			magic25;				/* 191-192: 0x00, 0x04					*/
+			guint16			magic30;				/* 193-194: 0x01, 0x00					*/
+			char			pad20[2];			/* 195-196: 0x4a, 0x02					*/
+			guint16			magic35;				/* 197-198: 0x4a, 0x02					*/
+			char			pad30[10];			/* 199-208						*/
+			guint16			magic40;				/* 209-210: 0x30, 0x00					*/
+			char			pad40[2];			/* 211-212						*/
+			guint16			magic45;				/* 213-214: 0x44, 0x00					*/
+			char			pad45[2];			/* 215-216						*/
+			guint16			magic50;				/* 217-218: 0x39, 0x00					*/
+			char			pad50[2];			/* 219-220						*/
+			char			data[256];			/* 221-EOP: a sequence of NULL-terminated strings:
+											- username;
+											- empty string (just a NULL);
+											- application name;
+											- hostname					*/
+		} hello;
+
+		struct {
+			char			pad1[4];			/* 39-42						*/
+			guint16			magic2;				/* 43-44						*/
+			char			pad10[12];			/* 45-56						*/
+			guint32			bindaddr;			/* 57-60: address to bind.				*/
+			guint16			bindport;			/* 61-62: port to bind.					*/
+			char           		pad15[2];			/* 63-64						*/
+			guint16			magic3;				/* 65-66						*/
+			guint16			boundport;			/* 67-68						*/
+			char           		pad20[104];			/* 69-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 1			*/
+			guint32			flags;				/* 185-188: NTLM message flags				*/
+			guint16			magic20;				/* 189-190: 0x28, 0x00					*/
+			char			pad30[2];			/* 191-192						*/
+			guint16			magic25;				/* 193-194: 0x96, 0x82					*/
+			guint16			magic30;				/* 195-196: 0x01, 0x00					*/
+			char			pad40[12];			/* 197-208						*/
+			guint16			magic50;				/* 209-210: 0x30, 0x00					*/
+			char			pad50[6];			/* 211-216						*/
+			guint16			magic55;				/* 217-218: 0x30, 0x00					*/
+			char			pad55[2];			/* 219-220						*/
+			char			data[0];			/* Dummy end marker, no real data required		*/
+		} auth;
+
+		struct {
+			char			pad1[4];			/* 39-42						*/
+			guint16			magic1;				/* 43-44						*/
+			guint32			magic2;				/* 45-48						*/
+			char			pad2[8];			/* 49-56						*/
+			guint16			magic3;				/* 57-58						*/
+			char			pad3[6];			/* 59-64						*/
+			guint16			magic4;				/* 65-66						*/
+			guint16			boundport;			/* 67-68						*/
+			char           		pad4[104];			/* 69-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 3			*/
+			struct ntlm_buffer	lm_resp;				/* 185-192: LM response security buffer			*/
+			struct ntlm_buffer	ntlm_resp;			/* 193-200: NTLM response security buffer		*/
+			struct ntlm_buffer	ntdomain_buf;			/* 201-208: domain name security buffer			*/
+			struct ntlm_buffer	username_buf;			/* 209-216: username security buffer			*/
+			struct ntlm_buffer	clienthost_buf;			/* 217-224: hostname security buffer			*/
+			struct ntlm_buffer	sessionkey_buf;			/* 225-232: session key security buffer			*/
+			guint32			flags;				/* 233-236: message flags				*/
+			char			data[1024];			/* 237-EOP: data area					*/
+		} auth2;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad1[2];			/* 41-42						*/
+			guint16			magic2;				/* 43-44						*/
+			guint32			magic3;				/* 45-48						*/
+			char			pad5[8];			/* 48-56						*/
+			guint16			magic6;				/* 57-58: 0x0200					*/
+			guint16			destport;			/* 59-60						*/
+			guint32			destaddr;			/* 61-64						*/
+			char			pad10[4];			/* 65-68						*/
+			guint16			magic10;				/* 69-70						*/
+			char			pad15[2];			/* 71-72						*/
+			guint16			srcport;			/* 73-74: port client connects from			*/
+			char			pad20[82];			/* 75-156						*/
+			char			executable[256];		/* 76-EOP: application name				*/
+		} connect;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad5[2];			/* 41-42						*/
+			guint16			magic5;				/* 43-44						*/
+			guint32			magic10;				/* 45-48						*/
+			char			pad10[2];			/* 49-50						*/
+			guint16			magic15;				/* 51-52						*/
+			guint32			magic16;				/* 53-56						*/
+			guint16			magic20;				/* 57-58						*/
+			guint16			clientport;			/* 59-60: forwarded port.				*/
+			guint32			clientaddr;			/* 61-64: forwarded address.				*/
+			guint32			magic30;				/* 65-68						*/
+			guint32			magic35;				/* 69-72						*/
+			guint16			serverport;			/* 73-74: port server will connect to us from.		*/
+			guint16			srcport;			/* 75-76: connect request; port used on client behalf.	*/
+			guint16			boundport;			/* 77-78: bind request; port used on client behalf.	*/
+			guint32			boundaddr;			/* 79-82: addr used on client behalf			*/
+			char			pad30[90];			/* 83-172						*/
+			char			data[0];			/* End marker						*/
+		} connack;
+
+	} packet;
+};
+
+struct msproxy_response_t {
+	guint32					packetid;			/* 1-4							*/
+	guint32					magic5;				/* 5-8							*/
+	guint32             			serverid;			/* 9-12							*/
+	char					clientack;			/* 13: ack of last client packet.			*/
+	char					pad5[3];			/* 14-16						*/
+	unsigned char				sequence;			/* 17: sequence # of this packet.			*/
+	char					pad10[7];			/* 18-24						*/
+	char					RWSP[4];			/* 25-28: 0x52,0x57,0x53,0x50				*/
+	char					pad15[8];			/* 29-36						*/
+	guint16					command;				/* 37-38						*/
+
+	union {
+		struct {
+			char			pad5[18];			/* 39-56						*/
+			guint16			magic20;				/* 57-58: 0x02, 0x00					*/
+			char			pad10[6];			/* 59-64						*/
+			guint16			magic30;				/* 65-66: 0x74, 0x01					*/
+			char			pad15[2];			/* 67-68						*/
+			guint16			magic35;				/* 69-70: 0x0c, 0x00					*/
+			char			pad20[6];			/* 71-76						*/
+			guint16			magic50;				/* 77-78: 0x04, 0x00					*/
+			char			pad30[6];			/* 79-84						*/
+			guint16			magic60;				/* 85-86: 0x65, 0x05					*/
+			char			pad35[2];			/* 87-88						*/
+			guint16			magic65;				/* 89-90: 0x02, 0x00					*/
+			char			pad40[8];			/* 91-98						*/
+			guint16			udpport;			/* 99-100						*/
+			guint32			udpaddr;			/* 101-104						*/
+		} hello;
+
+		struct {
+			char			pad1[6];			/* 39-44						*/
+			guint32			magic10;				/* 45-48						*/
+			char			pad3[10];			/* 49-58						*/
+			guint16			boundport;			/* 59-60: port server bound for us.			*/
+			guint32			boundaddr;			/* 61-64: addr server bound for us.			*/
+			char			pad10[4];			/* 65-68						*/
+			guint16			magic15;				/* 69-70						*/
+			char			pad15[102];			/* 70-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 2			*/
+			struct ntlm_buffer	target;				/* 185-192: target security buffer			*/
+			guint32			flags;				/* 193-196: NTLM message flags				*/
+			char			challenge[8];			/* 197-204: NTLM challenge request			*/
+			char			context[8];			/* 205-212: NTLM context				*/
+			char			data[1024];			/* 213-EOP: target information data			*/
+		} auth;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad5[18];			/* 41-58						*/
+			guint16			clientport;			/* 59-60: forwarded port.				*/
+			guint32			clientaddr;			/* 61-64: forwarded address.				*/
+			guint32			magic10;				/* 65-68						*/
+			guint32			magic15;				/* 69-72						*/
+			guint16			serverport;			/* 73-74: port server will connect to us from.		*/
+			guint16			srcport;			/* 75-76: connect request; port used on client behalf.	*/
+			guint16			boundport;			/* 77-78: bind request; port used on client behalf.	*/
+			guint32			boundaddr;			/* 79-82: addr used on client behalf			*/
+			char			pad10[90];			/* 83-172						*/
+		} connect;
+	} packet;
+};
+
+#pragma pack()
+
+int traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound);
+void msproxy_keepalive (void);
diff --git a/src/common/network.c b/src/common/network.c
new file mode 100644
index 00000000..0c409506
--- /dev/null
+++ b/src/common/network.c
@@ -0,0 +1,383 @@
+/* X-Chat
+ * Copyright (C) 2001 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* ipv4 and ipv6 networking functions with a common interface */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "../../config.h"				  /* grab USE_IPV6 and LOOKUPD defines */
+
+#define WANTSOCKET
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#define NETWORK_PRIVATE
+#include "network.h"
+
+#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n)))
+
+
+/* ================== COMMON ================= */
+
+static void
+net_set_socket_options (int sok)
+{
+	socklen_t sw;
+
+	sw = 1;
+	setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw));
+	sw = 1;
+	setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw));
+}
+
+char *
+net_ip (guint32 addr)
+{
+	struct in_addr ia;
+
+	ia.s_addr = htonl (addr);
+	return inet_ntoa (ia);
+}
+
+void
+net_store_destroy (netstore * ns)
+{
+#ifdef USE_IPV6
+	if (ns->ip6_hostent)
+		freeaddrinfo (ns->ip6_hostent);
+#endif
+	free (ns);
+}
+
+netstore *
+net_store_new (void)
+{
+	netstore *ns;
+
+	ns = malloc (sizeof (netstore));
+	memset (ns, 0, sizeof (netstore));
+
+	return ns;
+}
+
+#ifndef USE_IPV6
+
+/* =================== IPV4 ================== */
+
+/*
+	A note about net_resolve and lookupd:
+
+	Many IRC networks rely on round-robin DNS for load balancing, rotating the list
+	of IP address on each query. However, this method breaks when DNS queries are
+	cached. Mac OS X and Darwin handle DNS lookups through the lookupd daemon, which
+	caches queries in its default configuration: thus, if we always pick the first
+	address, we will be stuck with the same host (which might be down!) until the
+	TTL reaches 0 or lookupd is reset (typically, at reboot). Therefore, we need to
+	pick a random address from the result list, instead of always using the first.
+*/
+
+char *
+net_resolve (netstore * ns, char *hostname, int port, char **real_host)
+{
+	ns->ip4_hostent = gethostbyname (hostname);
+	if (!ns->ip4_hostent)
+		return NULL;
+
+	memset (&ns->addr, 0, sizeof (ns->addr));
+#ifdef LOOKUPD
+	int count = 0;
+	while (ns->ip4_hostent->h_addr_list[count]) count++;
+	memcpy (&ns->addr.sin_addr,
+			ns->ip4_hostent->h_addr_list[RAND_INT(count)],
+			ns->ip4_hostent->h_length);
+#else
+	memcpy (&ns->addr.sin_addr, ns->ip4_hostent->h_addr,
+			  ns->ip4_hostent->h_length);
+#endif
+	ns->addr.sin_port = htons (port);
+	ns->addr.sin_family = AF_INET;
+
+	*real_host = strdup (ns->ip4_hostent->h_name);
+	return strdup (inet_ntoa (ns->addr.sin_addr));
+}
+
+int
+net_connect (netstore * ns, int sok4, int sok6, int *sok_return)
+{
+	*sok_return = sok4;
+	return connect (sok4, (struct sockaddr *) &ns->addr, sizeof (ns->addr));
+}
+
+void
+net_bind (netstore * tobindto, int sok4, int sok6)
+{
+	bind (sok4, (struct sockaddr *) &tobindto->addr, sizeof (tobindto->addr));
+}
+
+void
+net_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_STREAM, 0);
+	*sok6 = -1;
+	net_set_socket_options (*sok4);
+}
+
+void
+udp_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_DGRAM, 0);
+	*sok6 = -1;
+}
+
+void
+net_store_fill_any (netstore *ns)
+{
+	ns->addr.sin_family = AF_INET;
+	ns->addr.sin_addr.s_addr = INADDR_ANY;
+	ns->addr.sin_port = 0;
+}
+
+void
+net_store_fill_v4 (netstore *ns, guint32 addr, int port)
+{
+	ns->addr.sin_family = AF_INET;
+	ns->addr.sin_addr.s_addr = addr;
+	ns->addr.sin_port = port;
+}
+
+guint32
+net_getsockaddr_v4 (netstore *ns)
+{
+	return ns->addr.sin_addr.s_addr;
+}
+
+int
+net_getsockport (int sok4, int sok6)
+{
+	struct sockaddr_in addr;
+	int len = sizeof (addr);
+
+	if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1)
+		return -1;
+	return addr.sin_port;
+}
+
+#else
+
+/* =================== IPV6 ================== */
+
+char *
+net_resolve (netstore * ns, char *hostname, int port, char **real_host)
+{
+	struct addrinfo hints;
+	char ipstring[MAX_HOSTNAME];
+	char portstring[MAX_HOSTNAME];
+	int ret;
+
+/*	if (ns->ip6_hostent)
+		freeaddrinfo (ns->ip6_hostent);*/
+
+	sprintf (portstring, "%d", port);
+
+	memset (&hints, 0, sizeof (struct addrinfo));
+	hints.ai_family = PF_UNSPEC; /* support ipv6 and ipv4 */
+	hints.ai_flags = AI_CANONNAME;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if (port == 0)
+		ret = getaddrinfo (hostname, NULL, &hints, &ns->ip6_hostent);
+	else
+		ret = getaddrinfo (hostname, portstring, &hints, &ns->ip6_hostent);
+	if (ret != 0)
+		return NULL;
+
+#ifdef LOOKUPD	/* See note about lookupd above the IPv4 version of net_resolve. */
+	struct addrinfo *tmp;
+	int count = 0;
+
+	for (tmp = ns->ip6_hostent; tmp; tmp = tmp->ai_next)
+		count ++;
+
+	count = RAND_INT(count);
+	
+	while (count--) ns->ip6_hostent = ns->ip6_hostent->ai_next;
+#endif
+
+	/* find the numeric IP number */
+	ipstring[0] = 0;
+	getnameinfo (ns->ip6_hostent->ai_addr, ns->ip6_hostent->ai_addrlen,
+					 ipstring, sizeof (ipstring), NULL, 0, NI_NUMERICHOST);
+
+	if (ns->ip6_hostent->ai_canonname)
+		*real_host = strdup (ns->ip6_hostent->ai_canonname);
+	else
+		*real_host = strdup (hostname);
+
+	return strdup (ipstring);
+}
+
+/* the only thing making this interface unclean, this shitty sok4, sok6 business */
+
+int
+net_connect (netstore * ns, int sok4, int sok6, int *sok_return)
+{
+	struct addrinfo *res, *res0;
+	int error = -1;
+
+	res0 = ns->ip6_hostent;
+
+	for (res = res0; res; res = res->ai_next)
+	{
+/*		sok = socket (res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (sok < 0)
+			continue;*/
+		switch (res->ai_family)
+		{
+		case AF_INET:
+			error = connect (sok4, res->ai_addr, res->ai_addrlen);
+			*sok_return = sok4;
+			break;
+		case AF_INET6:
+			error = connect (sok6, res->ai_addr, res->ai_addrlen);
+			*sok_return = sok6;
+			break;
+		default:
+			error = 1;
+		}
+
+		if (error == 0)
+			break;
+	}
+
+	return error;
+}
+
+void
+net_bind (netstore * tobindto, int sok4, int sok6)
+{
+	bind (sok4, tobindto->ip6_hostent->ai_addr,
+			tobindto->ip6_hostent->ai_addrlen);
+	bind (sok6, tobindto->ip6_hostent->ai_addr,
+			tobindto->ip6_hostent->ai_addrlen);
+}
+
+void
+net_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	*sok6 = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+	net_set_socket_options (*sok4);
+	net_set_socket_options (*sok6);
+}
+
+void
+udp_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	*sok6 = socket (AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+}
+
+/* the following functions are used only by MSPROXY and are not
+   proper ipv6 implementations - do not use in new code! */
+
+void
+net_store_fill_any (netstore *ns)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+	if (!ai) {
+		ai = malloc (sizeof (struct addrinfo));
+		memset (ai, 0, sizeof (struct addrinfo));
+		ns->ip6_hostent = ai;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	if (!sin) {
+		sin = malloc (sizeof (struct sockaddr_in));
+		memset (sin, 0, sizeof (struct sockaddr_in));
+		ai->ai_addr = (struct sockaddr *)sin;
+	}
+	ai->ai_family = AF_INET;
+	ai->ai_addrlen = sizeof(struct sockaddr_in);
+	sin->sin_family = AF_INET;
+	sin->sin_addr.s_addr = INADDR_ANY;
+	sin->sin_port = 0;
+	ai->ai_next = NULL;
+}
+
+void
+net_store_fill_v4 (netstore *ns, guint32 addr, int port)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+	if (!ai) {
+		ai = malloc (sizeof (struct addrinfo));
+		memset (ai, 0, sizeof (struct addrinfo));
+		ns->ip6_hostent = ai;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	if (!sin) {
+		sin = malloc (sizeof (struct sockaddr_in));
+		memset (sin, 0, sizeof (struct sockaddr_in));
+		ai->ai_addr = (struct sockaddr *)sin;
+	}
+	ai->ai_family = AF_INET;
+	ai->ai_addrlen = sizeof(struct sockaddr_in);
+	sin->sin_family = AF_INET;
+	sin->sin_addr.s_addr = addr;
+	sin->sin_port = port;
+	ai->ai_next = NULL;
+}
+
+guint32
+net_getsockaddr_v4 (netstore *ns)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+
+	while (ai->ai_family != AF_INET) {
+		ai = ai->ai_next;
+		if (!ai)
+			return 0;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	return sin->sin_addr.s_addr;
+}
+
+int
+net_getsockport (int sok4, int sok6)
+{
+	struct sockaddr_in addr;
+	int len = sizeof (addr);
+
+	if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1)
+		return -1;
+	return addr.sin_port;
+}
+
+#endif
diff --git a/src/common/network.h b/src/common/network.h
new file mode 100644
index 00000000..f45f210a
--- /dev/null
+++ b/src/common/network.h
@@ -0,0 +1,34 @@
+#ifndef XCHAT_NETWORK_H
+#define XCHAT_NETWORK_H
+
+typedef struct netstore_
+{
+#ifdef NETWORK_PRIVATE
+#ifdef USE_IPV6
+	struct addrinfo *ip6_hostent;
+#else
+	struct hostent *ip4_hostent;
+	struct sockaddr_in addr;
+#endif
+#else
+	int _dummy;	/* some compilers don't like empty structs */
+#endif
+} netstore;
+
+#define MAX_HOSTNAME 128
+
+netstore *net_store_new (void);
+void net_store_destroy (netstore *ns);
+int net_connect (netstore *ns, int sok4, int sok6, int *sok_return);
+char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
+void net_bind (netstore *tobindto, int sok4, int sok6);
+char *net_ip (guint32 addr);
+void net_sockets (int *sok4, int *sok6);
+/* functions for MSPROXY only! */
+void udp_sockets (int *sok4, int *sok6);
+void net_store_fill_any (netstore *ns);
+void net_store_fill_v4 (netstore *ns, guint32 addr, int port);
+guint32 net_getsockaddr_v4 (netstore *ns);
+int net_getsockport(int sok4, int sok6);
+
+#endif
diff --git a/src/common/notify.c b/src/common/notify.c
new file mode 100644
index 00000000..04795849
--- /dev/null
+++ b/src/common/notify.c
@@ -0,0 +1,634 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "xchat.h"
+#include "notify.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "server.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+GSList *notify_list = 0;
+int notify_tag = 0;
+
+
+static char *
+despacify_dup (char *str)
+{
+	char *p, *res = malloc (strlen (str) + 1);
+
+	p = res;
+	while (1)
+	{
+		if (*str != ' ')
+		{
+			*p = *str;
+			if (*p == 0)
+				return res;
+			p++;
+		}
+		str++;
+	}
+}
+
+static int
+notify_netcmp (char *str, void *serv)
+{
+	char *net = despacify_dup (server_get_network (serv, TRUE));
+
+	if (rfc_casecmp (str, net) == 0)
+	{
+		free (net);
+		return 0;	/* finish & return FALSE from token_foreach() */
+	}
+
+	free (net);
+	return 1;	/* keep going... */
+}
+
+/* monitor this nick on this particular network? */
+
+static gboolean
+notify_do_network (struct notify *notify, server *serv)
+{
+	if (!notify->networks)	/* ALL networks for this nick */
+		return TRUE;
+
+	if (token_foreach (notify->networks, ',', notify_netcmp, serv))
+		return FALSE;	/* network list doesn't contain this one */
+
+	return TRUE;
+}
+
+struct notify_per_server *
+notify_find_server_entry (struct notify *notify, struct server *serv)
+{
+	GSList *list = notify->server_list;
+	struct notify_per_server *servnot;
+
+	while (list)
+	{
+		servnot = (struct notify_per_server *) list->data;
+		if (servnot->server == serv)
+			return servnot;
+		list = list->next;
+	}
+
+	/* not found, should we add it, or is this not a network where
+      we're monitoring this nick? */
+	if (!notify_do_network (notify, serv))
+		return NULL;
+
+	servnot = malloc (sizeof (struct notify_per_server));
+	if (servnot)
+	{
+		memset (servnot, 0, sizeof (struct notify_per_server));
+		servnot->server = serv;
+		servnot->notify = notify;
+		notify->server_list = g_slist_prepend (notify->server_list, servnot);
+	}
+	return servnot;
+}
+
+void
+notify_save (void)
+{
+	int fh;
+	struct notify *notify;
+	GSList *list = notify_list;
+
+	fh = xchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (list)
+		{
+			notify = (struct notify *) list->data;
+			write (fh, notify->name, strlen (notify->name));
+			if (notify->networks)
+			{
+				write (fh, " ", 1);
+				write (fh, notify->networks, strlen (notify->networks));
+			}
+			write (fh, "\n", 1);
+			list = list->next;
+		}
+		close (fh);
+	}
+}
+
+void
+notify_load (void)
+{
+	int fh;
+	char buf[256];
+	char *sep;
+
+	fh = xchat_open_file ("notify.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
+		{
+			if (buf[0] != '#' && buf[0] != 0)
+			{
+				sep = strchr (buf, ' ');
+				if (sep)
+				{
+					sep[0] = 0;
+					notify_adduser (buf, sep + 1);					
+				}
+				else
+					notify_adduser (buf, NULL);
+			}
+		}
+		close (fh);
+	}
+}
+
+static struct notify_per_server *
+notify_find (server *serv, char *nick)
+{
+	GSList *list = notify_list;
+	struct notify_per_server *servnot;
+	struct notify *notify;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+
+		servnot = notify_find_server_entry (notify, serv);
+		if (!servnot)
+		{
+			list = list->next;
+			continue;
+		}
+
+		if (!serv->p_cmp (notify->name, nick))
+			return servnot;
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+static void
+notify_announce_offline (server * serv, struct notify_per_server *servnot,
+								char *nick, int quiet)
+{
+	session *sess;
+
+	sess = serv->front_session;
+
+	servnot->ison = FALSE;
+	servnot->lastoff = time (0);
+	if (!quiet)
+		EMIT_SIGNAL (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername,
+						 server_get_network (serv, TRUE), NULL, 0);
+	fe_notify_update (nick);
+	fe_notify_update (0);
+}
+
+static void
+notify_announce_online (server * serv, struct notify_per_server *servnot,
+								char *nick)
+{
+	session *sess;
+
+	sess = serv->front_session;
+
+	servnot->lastseen = time (0);
+	if (servnot->ison)
+		return;
+
+	servnot->ison = TRUE;
+	servnot->laston = time (0);
+	EMIT_SIGNAL (XP_TE_NOTIFYONLINE, sess, nick, serv->servername,
+					 server_get_network (serv, TRUE), NULL, 0);
+	fe_notify_update (nick);
+	fe_notify_update (0);
+
+	if (prefs.whois_on_notifyonline)
+	{
+
+	    /* Let's do whois with idle time (like in /quote WHOIS %s %s) */
+
+	    char *wii_str = malloc (strlen (nick) * 2 + 2);
+	    sprintf (wii_str, "%s %s", nick, nick);
+	    serv->p_whois (serv, wii_str);
+	    free (wii_str);
+	}
+}
+
+/* handles numeric 601 */
+
+void
+notify_set_offline (server * serv, char *nick, int quiet)
+{
+	struct notify_per_server *servnot;
+
+	servnot = notify_find (serv, nick);
+	if (!servnot)
+		return;
+
+	notify_announce_offline (serv, servnot, nick, quiet);
+}
+
+/* handles numeric 604 and 600 */
+
+void
+notify_set_online (server * serv, char *nick)
+{
+	struct notify_per_server *servnot;
+
+	servnot = notify_find (serv, nick);
+	if (!servnot)
+		return;
+
+	notify_announce_online (serv, servnot, nick);
+}
+
+static void
+notify_watch (server * serv, char *nick, int add)
+{
+	char tbuf[256];
+
+	snprintf (tbuf, sizeof (tbuf), "WATCH +%s", nick);
+	if (!add)
+		tbuf[6] = '-';
+	serv->p_raw (serv, tbuf);
+}
+
+static void
+notify_watch_all (struct notify *notify, int add)
+{
+	server *serv;
+	GSList *list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd && serv->supports_watch &&
+			 notify_do_network (notify, serv))
+			notify_watch (serv, notify->name, add);
+		list = list->next;
+	}
+}
+
+static void
+notify_flush_watches (server * serv, GSList *from, GSList *end)
+{
+	char tbuf[512];
+	GSList *list;
+	struct notify *notify;
+
+	strcpy (tbuf, "WATCH");
+
+	list = from;
+	while (list != end)
+	{
+		notify = list->data;
+		strcat (tbuf, " +");
+		strcat (tbuf, notify->name);
+		list = list->next;
+	}
+	serv->p_raw (serv, tbuf);
+}
+
+/* called when logging in. e.g. when End of motd. */
+
+void
+notify_send_watches (server * serv)
+{
+	struct notify *notify;
+	GSList *list;
+	GSList *point;
+	int len;
+
+	len = 0;
+	point = list = notify_list;
+	while (list)
+	{
+		notify = list->data;
+
+		if (notify_do_network (notify, serv))
+		{
+			len += strlen (notify->name) + 2 /* + and space */;
+			if (len > 500)
+			{
+				notify_flush_watches (serv, point, list);
+				len = strlen (notify->name) + 2;
+				point = list;
+			}
+		}
+
+		list = list->next;
+	}
+
+	if (point)
+		notify_flush_watches (serv, point, NULL);
+}
+
+/* called when receiving a ISON 303 - should this func go? */
+
+void
+notify_markonline (server *serv, char *word[])
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+	int i, seen;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		servnot = notify_find_server_entry (notify, serv);
+		if (!servnot)
+		{
+			list = list->next;
+			continue;
+		}
+		i = 4;
+		seen = FALSE;
+		while (*word[i])
+		{
+			if (!serv->p_cmp (notify->name, word[i]))
+			{
+				seen = TRUE;
+				notify_announce_online (serv, servnot, notify->name);
+				break;
+			}
+			i++;
+			/* FIXME: word[] is only a 32 element array, limits notify list to
+			   about 27 people */
+			if (i > PDIWORDS - 5)
+			{
+				/*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/
+				break;
+			}
+		}
+		if (!seen && servnot->ison)
+		{
+			notify_announce_offline (serv, servnot, notify->name, FALSE);
+		}
+		list = list->next;
+	}
+	fe_notify_update (0);
+}
+
+/* yuck! Old routine for ISON notify */
+
+static void
+notify_checklist_for_server (server *serv)
+{
+	char outbuf[512];
+	struct notify *notify;
+	GSList *list = notify_list;
+	int i = 0;
+
+	strcpy (outbuf, "ISON ");
+	while (list)
+	{
+		notify = list->data;
+		if (notify_do_network (notify, serv))
+		{
+			i++;
+			strcat (outbuf, notify->name);
+			strcat (outbuf, " ");
+			if (strlen (outbuf) > 460)
+			{
+				/* LAME: we can't send more than 512 bytes to the server, but     *
+				 * if we split it in two packets, our offline detection wouldn't  *
+				 work                                                           */
+				/*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/
+				break;
+			}
+		}
+		list = list->next;
+	}
+
+	if (i)
+		serv->p_raw (serv, outbuf);
+}
+
+int
+notify_checklist (void)	/* check ISON list */
+{
+	struct server *serv;
+	GSList *list = serv_list;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd && !serv->supports_watch)
+		{
+			notify_checklist_for_server (serv);
+		}
+		list = list->next;
+	}
+	return 1;
+}
+
+void
+notify_showlist (struct session *sess)
+{
+	char outbuf[256];
+	struct notify *notify;
+	GSList *list = notify_list;
+	struct notify_per_server *servnot;
+	int i = 0;
+
+	EMIT_SIGNAL (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0);
+	while (list)
+	{
+		i++;
+		notify = (struct notify *) list->data;
+		servnot = notify_find_server_entry (notify, sess->server);
+		if (servnot && servnot->ison)
+			snprintf (outbuf, sizeof (outbuf), _("  %-20s online\n"), notify->name);
+		else
+			snprintf (outbuf, sizeof (outbuf), _("  %-20s offline\n"), notify->name);
+		PrintText (sess, outbuf);
+		list = list->next;
+	}
+	if (i)
+	{
+		sprintf (outbuf, "%d", i);
+		EMIT_SIGNAL (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL, 0);
+	} else
+		EMIT_SIGNAL (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0);
+}
+
+int
+notify_deluser (char *name)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!rfc_casecmp (notify->name, name))
+		{
+			fe_notify_update (notify->name);
+			/* Remove the records for each server */
+			while (notify->server_list)
+			{
+				servnot = (struct notify_per_server *) notify->server_list->data;
+				notify->server_list =
+					g_slist_remove (notify->server_list, servnot);
+				free (servnot);
+			}
+			notify_list = g_slist_remove (notify_list, notify);
+			notify_watch_all (notify, FALSE);
+			if (notify->networks)
+				free (notify->networks);
+			free (notify->name);
+			free (notify);
+			fe_notify_update (0);
+			return 1;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+notify_adduser (char *name, char *networks)
+{
+	struct notify *notify = malloc (sizeof (struct notify));
+	if (notify)
+	{
+		memset (notify, 0, sizeof (struct notify));
+		if (strlen (name) >= NICKLEN)
+		{
+			notify->name = malloc (NICKLEN);
+			safe_strcpy (notify->name, name, NICKLEN);
+		} else
+		{
+			notify->name = strdup (name);
+		}
+		if (networks)
+			notify->networks = despacify_dup (networks);
+		notify->server_list = 0;
+		notify_list = g_slist_prepend (notify_list, notify);
+		notify_checklist ();
+		fe_notify_update (notify->name);
+		fe_notify_update (0);
+		notify_watch_all (notify, TRUE);
+	}
+}
+
+gboolean
+notify_is_in_list (server *serv, char *name)
+{
+	struct notify *notify;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!serv->p_cmp (notify->name, name))
+			return TRUE;
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+int
+notify_isnotify (struct session *sess, char *name)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!sess->server->p_cmp (notify->name, name))
+		{
+			servnot = notify_find_server_entry (notify, sess->server);
+			if (servnot && servnot->ison)
+				return TRUE;
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+void
+notify_cleanup ()
+{
+	GSList *list = notify_list;
+	GSList *nslist, *srvlist;
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	struct server *serv;
+	int valid;
+
+	while (list)
+	{
+		/* Traverse the list of notify structures */
+		notify = (struct notify *) list->data;
+		nslist = notify->server_list;
+		while (nslist)
+		{
+			/* Look at each per-server structure */
+			servnot = (struct notify_per_server *) nslist->data;
+
+			/* Check the server is valid */
+			valid = FALSE;
+			srvlist = serv_list;
+			while (srvlist)
+			{
+				serv = (struct server *) srvlist->data;
+				if (servnot->server == serv)
+				{
+					valid = serv->connected;	/* Only valid if server is too */
+					break;
+				}
+				srvlist = srvlist->next;
+			}
+			if (!valid)
+			{
+				notify->server_list =
+					g_slist_remove (notify->server_list, servnot);
+				free (servnot);
+				nslist = notify->server_list;
+			} else
+			{
+				nslist = nslist->next;
+			}
+		}
+		list = list->next;
+	}
+	fe_notify_update (0);
+}
diff --git a/src/common/notify.h b/src/common/notify.h
new file mode 100644
index 00000000..37674fe5
--- /dev/null
+++ b/src/common/notify.h
@@ -0,0 +1,44 @@
+#ifndef XCHAT_NOTIFY_H
+#define XCHAT_NOTIFY_H
+
+struct notify
+{
+	char *name;
+	char *networks;	/* network names, comma sep */
+	GSList *server_list;
+};
+
+struct notify_per_server
+{
+	struct server *server;
+	struct notify *notify;
+	time_t laston;
+	time_t lastseen;
+	time_t lastoff;
+	unsigned int ison:1;
+};
+
+extern GSList *notify_list;
+extern int notify_tag;
+
+/* the WATCH stuff */
+void notify_set_online (server * serv, char *nick);
+void notify_set_offline (server * serv, char *nick, int quiet);
+void notify_send_watches (server * serv);
+
+/* the general stuff */
+void notify_adduser (char *name, char *networks);
+int notify_deluser (char *name);
+void notify_cleanup (void);
+void notify_load (void);
+void notify_save (void);
+void notify_showlist (session *sess);
+gboolean notify_is_in_list (server *serv, char *name);
+int notify_isnotify (session *sess, char *name);
+struct notify_per_server *notify_find_server_entry (struct notify *notify, struct server *serv);
+
+/* the old ISON stuff - remove me? */
+void notify_markonline (server *serv, char *word[]);
+int notify_checklist (void);
+
+#endif
diff --git a/src/common/outbound.c b/src/common/outbound.c
new file mode 100644
index 00000000..7c6e5e6a
--- /dev/null
+++ b/src/common/outbound.c
@@ -0,0 +1,4403 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#define _GNU_SOURCE	/* for memrchr */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#ifndef WIN32
+#include <sys/wait.h>
+#endif
+
+#include <unistd.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "xchat.h"
+#include "plugin.h"
+#include "ignore.h"
+#include "util.h"
+#include "fe.h"
+#include "cfgfiles.h"			  /* xchat_fopen_file() */
+#include "network.h"				/* net_ip() */
+#include "modes.h"
+#include "notify.h"
+#include "inbound.h"
+#include "text.h"
+#include "xchatc.h"
+#include "servlist.h"
+#include "server.h"
+#include "tree.h"
+#include "outbound.h"
+
+
+#ifdef USE_DEBUG
+extern int current_mem_usage;
+#endif
+#define TBUFSIZE 4096
+
+static void help (session *sess, char *tbuf, char *helpcmd, int quiet);
+static int cmd_server (session *sess, char *tbuf, char *word[], char *word_eol[]);
+static void handle_say (session *sess, char *text, int check_spch);
+
+
+static void
+notj_msg (struct session *sess)
+{
+	PrintText (sess, _("No channel joined. Try /join #<channel>\n"));
+}
+
+void
+notc_msg (struct session *sess)
+{
+	PrintText (sess, _("Not connected. Try /server <host> [<port>]\n"));
+}
+
+static char *
+random_line (char *file_name)
+{
+	FILE *fh;
+	char buf[512];
+	int lines, ran;
+
+	if (!file_name[0])
+		goto nofile;
+
+	fh = xchat_fopen_file (file_name, "r", 0);
+	if (!fh)
+	{
+	 nofile:
+		/* reason is not a file, an actual reason! */
+		return strdup (file_name);
+	}
+
+	/* count number of lines in file */
+	lines = 0;
+	while (fgets (buf, sizeof (buf), fh))
+		lines++;
+
+	if (lines < 1)
+		goto nofile;
+
+	/* go down a random number */
+	rewind (fh);
+	ran = RAND_INT (lines);
+	do
+	{
+		fgets (buf, sizeof (buf), fh);
+		lines--;
+	}
+	while (lines > ran);
+	fclose (fh);
+	buf[strlen (buf) - 1] = 0;	  /* remove the trailing '\n' */
+	return strdup (buf);
+}
+
+void
+server_sendpart (server * serv, char *channel, char *reason)
+{
+	if (!reason)
+	{
+		reason = random_line (prefs.partreason);
+		serv->p_part (serv, channel, reason);
+		free (reason);
+	} else
+	{
+		/* reason set by /quit, /close argument */
+		serv->p_part (serv, channel, reason);
+	}
+}
+
+void
+server_sendquit (session * sess)
+{
+	char *rea, *colrea;
+
+	if (!sess->quitreason)
+	{
+		colrea = strdup (prefs.quitreason);
+		check_special_chars (colrea, FALSE);
+		rea = random_line (colrea);
+		free (colrea);
+		sess->server->p_quit (sess->server, rea);
+		free (rea);
+	} else
+	{
+		/* reason set by /quit, /close argument */
+		sess->server->p_quit (sess->server, sess->quitreason);
+	}
+}
+
+void
+process_data_init (char *buf, char *cmd, char *word[],
+						 char *word_eol[], gboolean handle_quotes,
+						 gboolean allow_escape_quotes)
+{
+	int wordcount = 2;
+	int space = FALSE;
+	int quote = FALSE;
+	int j = 0;
+	int len;
+
+	word[0] = "\000\000";
+	word_eol[0] = "\000\000";
+	word[1] = (char *)buf;
+	word_eol[1] = (char *)cmd;
+
+	while (1)
+	{
+		switch (*cmd)
+		{
+		case 0:
+			buf[j] = 0;
+			for (j = wordcount; j < PDIWORDS; j++)
+			{
+				word[j] = "\000\000";
+				word_eol[j] = "\000\000";
+			}
+			return;
+		case '\042':
+			if (!handle_quotes)
+				goto def;
+			/* two quotes turn into 1 */
+			if (allow_escape_quotes && cmd[1] == '\042')
+			{
+				cmd++;
+				goto def;
+			}
+			if (quote)
+			{
+				quote = FALSE;
+				space = FALSE;
+			} else
+				quote = TRUE;
+			cmd++;
+			break;
+		case ' ':
+			if (!quote)
+			{
+				if (!space)
+				{
+					buf[j] = 0;
+					j++;
+
+					if (wordcount < PDIWORDS)
+					{
+						word[wordcount] = &buf[j];
+						word_eol[wordcount] = cmd + 1;
+						wordcount++;
+					}
+
+					space = TRUE;
+				}
+				cmd++;
+				break;
+			}
+		default:
+def:
+			space = FALSE;
+			len = g_utf8_skip[((unsigned char *)cmd)[0]];
+			if (len == 1)
+			{
+				buf[j] = *cmd;
+				j++;
+				cmd++;
+			} else
+			{
+				/* skip past a multi-byte utf8 char */
+				memcpy (buf + j, cmd, len);
+				j += len;
+				cmd += len;
+			}
+		}
+	}
+}
+
+static int
+cmd_addbutton (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (*word[2] && *word_eol[3])
+	{
+		if (sess->type == SESS_DIALOG)
+		{
+			list_addentry (&dlgbutton_list, word_eol[3], word[2]);
+			fe_dlgbuttons_update (sess);
+		} else
+		{
+			list_addentry (&button_list, word_eol[3], word[2]);
+			fe_buttons_update (sess);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_allchannels (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] && sess->server->connected)
+		{
+			handle_command (sess, word_eol[2], FALSE);
+		}
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_allchannelslocal (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+	server *serv = sess->server;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] &&
+			 sess->server->connected && sess->server == serv)
+		{
+			handle_command (sess, word_eol[2], FALSE);
+		}
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_allservers (struct session *sess, char *tbuf, char *word[],
+					 char *word_eol[])
+{
+	GSList *list;
+	server *serv;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected)
+			handle_command (serv->front_session, word_eol[2], FALSE);
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+	char *reason = word_eol[2];
+
+	if (!(*reason))
+	{
+		if (sess->server->is_away)
+		{
+			if (sess->server->last_away_reason)
+				PrintTextf (sess, _("Already marked away: %s\n"), sess->server->last_away_reason);
+			return FALSE;
+		}
+
+		if (sess->server->reconnect_away)
+			reason = sess->server->last_away_reason;
+		else
+			/* must manage memory pointed to by random_line() */
+			reason = random_line (prefs.awayreason);
+	}
+	sess->server->p_set_away (sess->server, reason);
+
+	if (prefs.show_away_message)
+	{
+		snprintf (tbuf, TBUFSIZE, "me is away: %s", reason);
+		for (list = sess_list; list; list = list->next)
+		{
+			/* am I the right server and not a dialog box */
+			if (((struct session *) list->data)->server == sess->server
+				 && ((struct session *) list->data)->type == SESS_CHANNEL
+				 && ((struct session *) list->data)->channel[0])
+			{
+				handle_command ((session *) list->data, tbuf, TRUE);
+			}
+		}
+	}
+
+	if (sess->server->last_away_reason != reason)
+	{
+		if (sess->server->last_away_reason)
+			free (sess->server->last_away_reason);
+
+		if (reason == word_eol[2])
+			sess->server->last_away_reason = strdup (reason);
+		else
+			sess->server->last_away_reason = reason;
+	}
+
+	if (!sess->server->connected)
+		sess->server->reconnect_away = 1;
+
+	return TRUE;
+}
+
+static int
+cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+	unsigned int gone;
+
+	if (sess->server->is_away)
+	{
+		sess->server->p_set_back (sess->server);
+
+		if (prefs.show_away_message)
+		{
+			gone = time (NULL) - sess->server->away_time;
+			sprintf (tbuf, "me is back (gone %.2d:%.2d:%.2d)", gone / 3600,
+						(gone / 60) % 60, gone % 60);
+			for (list = sess_list; list; list = list->next)
+			{
+				/* am I the right server and not a dialog box */
+				if (((struct session *) list->data)->server == sess->server
+					 && ((struct session *) list->data)->type == SESS_CHANNEL
+					 && ((struct session *) list->data)->channel[0])
+				{
+					handle_command ((session *) list->data, tbuf, TRUE);
+				}
+			}
+		}
+	}
+	else
+	{
+		PrintText (sess, _("Already marked back.\n"));
+	}
+
+	if (sess->server->last_away_reason)
+		free (sess->server->last_away_reason);
+	sess->server->last_away_reason = NULL;
+
+	return TRUE;
+}
+
+static void
+ban (session * sess, char *tbuf, char *mask, char *bantypestr, int deop)
+{
+	int bantype;
+	struct User *user;
+	char *at, *dot, *lastdot;
+	char username[64], fullhost[128], domain[128], *mode, *p2;
+	server *serv = sess->server;
+
+	user = userlist_find (sess, mask);
+	if (user && user->hostname)  /* it's a nickname, let's find a proper ban mask */
+	{
+		if (deop)
+		{
+			mode = "-o+b ";
+			p2 = user->nick;
+		} else
+		{
+			mode = "+b";
+			p2 = "";
+		}
+
+		mask = user->hostname;
+
+		at = strchr (mask, '@');	/* FIXME: utf8 */
+		if (!at)
+			return;					  /* can't happen? */
+		*at = 0;
+
+		if (mask[0] == '~' || mask[0] == '+' ||
+		    mask[0] == '=' || mask[0] == '^' || mask[0] == '-')
+		{
+			/* the ident is prefixed with something, we replace that sign with an * */
+			safe_strcpy (username+1, mask+1, sizeof (username)-1);
+			username[0] = '*';
+		} else if (at - mask < USERNAMELEN)
+		{
+			/* we just add an * in the begining of the ident */
+			safe_strcpy (username+1, mask, sizeof (username)-1);
+			username[0] = '*';
+		} else
+		{
+			/* ident might be too long, we just ban what it gives and add an * in the end */
+			safe_strcpy (username, mask, sizeof (username));
+		}
+		*at = '@';
+		safe_strcpy (fullhost, at + 1, sizeof (fullhost));
+
+		dot = strchr (fullhost, '.');
+		if (dot)
+		{
+			safe_strcpy (domain, dot, sizeof (domain));
+		} else
+		{
+			safe_strcpy (domain, fullhost, sizeof (domain));
+		}
+
+		if (*bantypestr)
+			bantype = atoi (bantypestr);
+		else
+			bantype = prefs.bantype;
+
+		tbuf[0] = 0;
+		if (inet_addr (fullhost) != -1)	/* "fullhost" is really a IP number */
+		{
+			lastdot = strrchr (fullhost, '.');
+			if (!lastdot)
+				return;				  /* can't happen? */
+
+			*lastdot = 0;
+			strcpy (domain, fullhost);
+			*lastdot = '.';
+
+			switch (bantype)
+			{
+			case 0:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s.*", mode, p2, domain);
+				break;
+
+			case 1:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost);
+				break;
+
+			case 2:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s.*", mode, p2, username, domain);
+				break;
+
+			case 3:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost);
+				break;
+			}
+		} else
+		{
+			switch (bantype)
+			{
+			case 0:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@*%s", mode, p2, domain);
+				break;
+
+			case 1:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost);
+				break;
+
+			case 2:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@*%s", mode, p2, username, domain);
+				break;
+
+			case 3:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost);
+				break;
+			}
+		}
+
+	} else
+	{
+		snprintf (tbuf, TBUFSIZE, "+b %s", mask);
+	}
+	serv->p_mode (serv, sess->channel, tbuf);
+}
+
+static int
+cmd_ban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *mask = word[2];
+
+	if (*mask)
+	{
+		ban (sess, tbuf, mask, word[3], 0);
+	} else
+	{
+		sess->server->p_mode (sess->server, sess->channel, "+b");	/* banlist */
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_unban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* Allow more than one mask in /unban -- tvk */
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'b', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_chanopt (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* chanopt.c */
+	return chanopt_command (sess, tbuf, word, word_eol);
+}
+
+static int
+cmd_charset (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	server *serv = sess->server;
+	const char *locale = NULL;
+	int offset = 0;
+
+	if (strcmp (word[2], "-quiet") == 0)
+		offset++;
+
+	if (!word[2 + offset][0])
+	{
+		g_get_charset (&locale);
+		PrintTextf (sess, "Current charset: %s\n",
+						serv->encoding ? serv->encoding : locale);
+		return TRUE;
+	}
+
+	if (servlist_check_encoding (word[2 + offset]))
+	{
+		server_set_encoding (serv, word[2 + offset]);
+		if (offset < 1)
+			PrintTextf (sess, "Charset changed to: %s\n", word[2 + offset]);
+	} else
+	{
+		PrintTextf (sess, "\0034Unknown charset:\017 %s\n", word[2 + offset]);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_clear (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+	char *reason = word_eol[2];
+
+	if (strcasecmp (reason, "HISTORY") == 0)
+	{
+		history_free (&sess->history);
+		return TRUE;
+	}
+
+	if (strncasecmp (reason, "all", 3) == 0)
+	{
+		while (list)
+		{
+			sess = list->data;
+			if (!sess->nick_said)
+				fe_text_clear (list->data, 0);
+			list = list->next;
+		}
+		return TRUE;
+	}
+
+	if (reason[0] != '-' && !isdigit (reason[0]) && reason[0] != 0)
+		return FALSE;
+
+	fe_text_clear (sess, atoi (reason));
+	return TRUE;
+}
+
+static int
+cmd_close (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+
+	if (strcmp (word[2], "-m") == 0)
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			list = list->next;
+			if (sess->type == SESS_DIALOG)
+				fe_close_window (sess);
+		}
+	} else
+	{
+		if (*word_eol[2])
+			sess->quitreason = word_eol[2];
+		fe_close_window (sess);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_ctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int mbl;
+	char *to = word[2];
+	if (*to)
+	{
+		char *msg = word_eol[3];
+		if (*msg)
+		{
+			unsigned char *cmd = (unsigned char *)msg;
+
+			/* make the first word upper case (as per RFC) */
+			while (1)
+			{
+				if (*cmd == ' ' || *cmd == 0)
+					break;
+				mbl = g_utf8_skip[*cmd];
+				if (mbl == 1)
+					*cmd = toupper (*cmd);
+				cmd += mbl;
+			}
+
+			sess->server->p_ctcp (sess->server, to, msg);
+
+			EMIT_SIGNAL (XP_TE_CTCPSEND, sess, to, msg, NULL, NULL, 0);
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static int
+cmd_country (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *code = word[2];
+	if (*code)
+	{
+		/* search? */
+		if (strcmp (code, "-s") == 0)
+		{
+			country_search (word[3], sess, (void *)PrintTextf);
+			return TRUE;
+		}
+
+		/* search, but forgot the -s */
+		if (strchr (code, '*'))
+		{
+			country_search (code, sess, (void *)PrintTextf);
+			return TRUE;
+		}
+
+		sprintf (tbuf, "%s = %s\n", code, country (code));
+		PrintText (sess, tbuf);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_cycle (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *key = sess->channelkey;
+	char *chan = word[2];
+	if (!*chan)
+		chan = sess->channel;
+	if (*chan && sess->type == SESS_CHANNEL)
+	{
+		sess->server->p_cycle (sess->server, chan, key);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_dcc (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int goodtype;
+	struct DCC *dcc = 0;
+	char *type = word[2];
+	if (*type)
+	{
+		if (!strcasecmp (type, "HELP"))
+			return FALSE;
+		if (!strcasecmp (type, "CLOSE"))
+		{
+			if (*word[3] && *word[4])
+			{
+				goodtype = 0;
+				if (!strcasecmp (word[3], "SEND"))
+				{
+					dcc = find_dcc (word[4], word[5], TYPE_SEND);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+				if (!strcasecmp (word[3], "GET"))
+				{
+					dcc = find_dcc (word[4], word[5], TYPE_RECV);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+				if (!strcasecmp (word[3], "CHAT"))
+				{
+					dcc = find_dcc (word[4], "", TYPE_CHATRECV);
+					if (!dcc)
+						dcc = find_dcc (word[4], "", TYPE_CHATSEND);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+
+				if (!goodtype)
+					return FALSE;
+
+				if (!dcc)
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+
+				return TRUE;
+
+			}
+			return FALSE;
+		}
+		if ((!strcasecmp (type, "CHAT")) || (!strcasecmp (type, "PCHAT")))
+		{
+			char *nick = word[3];
+			int passive = (!strcasecmp(type, "PCHAT")) ? 1 : 0;
+			if (*nick)
+				dcc_chat (sess, nick, passive);
+			return TRUE;
+		}
+		if (!strcasecmp (type, "LIST"))
+		{
+			dcc_show_list (sess);
+			return TRUE;
+		}
+		if (!strcasecmp (type, "GET"))
+		{
+			char *nick = word[3];
+			char *file = word[4];
+			if (!*file)
+			{
+				if (*nick)
+					dcc_get_nick (sess, nick);
+			} else
+			{
+				dcc = find_dcc (nick, file, TYPE_RECV);
+				if (dcc)
+					dcc_get (dcc);
+				else
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+			}
+			return TRUE;
+		}
+		if ((!strcasecmp (type, "SEND")) || (!strcasecmp (type, "PSEND")))
+		{
+			int i = 3, maxcps;
+			char *nick, *file;
+			int passive = (!strcasecmp(type, "PSEND")) ? 1 : 0;
+
+			nick = word[i];
+			if (!*nick)
+				return FALSE;
+
+			maxcps = prefs.dcc_max_send_cps;
+			if (!strncasecmp(nick, "-maxcps=", 8))
+			{
+				maxcps = atoi(nick + 8);
+				i++;
+				nick = word[i];
+				if (!*nick)
+					return FALSE;
+			}
+
+			i++;
+
+			file = word[i];
+			if (!*file)
+			{
+				fe_dcc_send_filereq (sess, nick, maxcps, passive);
+				return TRUE;
+			}
+
+			do
+			{
+				dcc_send (sess, nick, file, maxcps, passive);
+				i++;
+				file = word[i];
+			}
+			while (*file);
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	dcc_show_list (sess);
+	return TRUE;
+}
+
+static int
+cmd_debug (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	struct session *s;
+	struct server *v;
+	GSList *list = sess_list;
+
+	PrintText (sess, "Session   T Channel    WaitChan  WillChan  Server\n");
+	while (list)
+	{
+		s = (struct session *) list->data;
+		sprintf (tbuf, "%p %1x %-10.10s %-10.10s %-10.10s %p\n",
+					s, s->type, s->channel, s->waitchannel,
+					s->willjoinchannel, s->server);
+		PrintText (sess, tbuf);
+		list = list->next;
+	}
+
+	list = serv_list;
+	PrintText (sess, "Server    Sock  Name\n");
+	while (list)
+	{
+		v = (struct server *) list->data;
+		sprintf (tbuf, "%p %-5d %s\n",
+					v, v->sok, v->servername);
+		PrintText (sess, tbuf);
+		list = list->next;
+	}
+
+	sprintf (tbuf,
+				"\nfront_session: %p\n"
+				"current_tab: %p\n\n",
+				sess->server->front_session, current_tab);
+	PrintText (sess, tbuf);
+#ifdef USE_DEBUG
+	sprintf (tbuf, "current mem: %d\n\n", current_mem_usage);
+	PrintText (sess, tbuf);
+#endif  /* !MEMORY_DEBUG */
+
+	return TRUE;
+}
+
+static int
+cmd_delbutton (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (*word[2])
+	{
+		if (sess->type == SESS_DIALOG)
+		{
+			if (list_delentry (&dlgbutton_list, word[2]))
+				fe_dlgbuttons_update (sess);
+		} else
+		{
+			if (list_delentry (&button_list, word[2]))
+				fe_buttons_update (sess);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_dehop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'h', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_deop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'o', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+typedef struct
+{
+	char **nicks;
+	int i;
+	session *sess;
+	char *reason;
+	char *tbuf;
+} multidata;
+
+static int
+mdehop_cb (struct User *user, multidata *data)
+{
+	if (user->hop && !user->me)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mdehop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * sess->hops);
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mdehop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'h', 0);
+	free (nicks);
+
+	return TRUE;
+}
+
+static int
+mdeop_cb (struct User *user, multidata *data)
+{
+	if (user->op && !user->me)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mdeop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * sess->ops);
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mdeop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'o', 0);
+	free (nicks);
+
+	return TRUE;
+}
+
+GSList *menu_list = NULL;
+
+static void
+menu_free (menu_entry *me)
+{
+	free (me->path);
+	if (me->label)
+		free (me->label);
+	if (me->cmd)
+		free (me->cmd);
+	if (me->ucmd)
+		free (me->ucmd);
+	if (me->group)
+		free (me->group);
+	if (me->icon)
+		free (me->icon);
+	free (me);
+}
+
+/* strings equal? but ignore underscores */
+
+int
+menu_streq (const char *s1, const char *s2, int def)
+{
+	/* for separators */
+	if (s1 == NULL && s2 == NULL)
+		return 0;
+	if (s1 == NULL || s2 == NULL)
+		return 1;
+	while (*s1)
+	{
+		if (*s1 == '_')
+			s1++;
+		if (*s2 == '_')
+			s2++;
+		if (*s1 != *s2)
+			return 1;
+		s1++;
+		s2++;
+	}
+	if (!*s2)
+		return 0;
+	return def;
+}
+
+static menu_entry *
+menu_entry_find (char *path, char *label)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		if (!strcmp (path, me->path))
+		{
+			if (me->label && label && !strcmp (label, me->label))
+				return me;
+		}
+		list = list->next;
+	}
+	return NULL;
+}
+
+static void
+menu_del_children (char *path, char *label)
+{
+	GSList *list, *next;
+	menu_entry *me;
+	char buf[512];
+
+	if (!label)
+		label = "";
+	if (path[0])
+		snprintf (buf, sizeof (buf), "%s/%s", path, label);
+	else
+		snprintf (buf, sizeof (buf), "%s", label);
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		next = list->next;
+		if (!menu_streq (buf, me->path, 0))
+		{
+			menu_list = g_slist_remove (menu_list, me);
+			menu_free (me);
+		}
+		list = next;
+	}
+}
+
+static int
+menu_del (char *path, char *label)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		if (!menu_streq (me->label, label, 1) && !menu_streq (me->path, path, 1))
+		{
+			menu_list = g_slist_remove (menu_list, me);
+			fe_menu_del (me);
+			menu_free (me);
+			/* delete this item's children, if any */
+			menu_del_children (path, label);
+			return 1;
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
+static char
+menu_is_mainmenu_root (char *path, gint16 *offset)
+{
+	static const char *menus[] = {"\x4$TAB","\x5$TRAY","\x4$URL","\x5$NICK","\x5$CHAN"};
+	int i;
+
+	for (i = 0; i < 5; i++)
+	{
+		if (!strncmp (path, menus[i] + 1, menus[i][0]))
+		{
+			*offset = menus[i][0] + 1;	/* number of bytes to offset the root */
+			return 0;	/* is not main menu */
+		}
+	}
+
+	*offset = 0;
+	return 1;	/* is main menu */
+}
+
+static void
+menu_add (char *path, char *label, char *cmd, char *ucmd, int pos, int state, int markup, int enable, int mod, int key, char *group, char *icon)
+{
+	menu_entry *me;
+
+	/* already exists? */
+	me = menu_entry_find (path, label);
+	if (me)
+	{
+		/* update only */
+		me->state = state;
+		me->enable = enable;
+		fe_menu_update (me);
+		return;
+	}
+
+	me = malloc (sizeof (menu_entry));
+	me->pos = pos;
+	me->modifier = mod;
+	me->is_main = menu_is_mainmenu_root (path, &me->root_offset);
+	me->state = state;
+	me->markup = markup;
+	me->enable = enable;
+	me->key = key;
+	me->path = strdup (path);
+	me->label = NULL;
+	me->cmd = NULL;
+	me->ucmd = NULL;
+	me->group = NULL;
+	me->icon = NULL;
+
+	if (label)
+		me->label = strdup (label);
+	if (cmd)
+		me->cmd = strdup (cmd);
+	if (ucmd)
+		me->ucmd = strdup (ucmd);
+	if (group)
+		me->group = strdup (group);
+	if (icon)
+		me->icon = strdup (icon);
+
+	menu_list = g_slist_append (menu_list, me);
+	label = fe_menu_add (me);
+	if (label)
+	{
+		/* FE has given us a stripped label */
+		free (me->label);
+		me->label = strdup (label);
+		g_free (label); /* this is from pango */
+	}
+}
+
+static int
+cmd_menu (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int len;
+	int pos = 0xffff;
+	int state;
+	int toggle = FALSE;
+	int enable = TRUE;
+	int markup = FALSE;
+	int key = 0;
+	int mod = 0;
+	char *label;
+	char *group = NULL;
+	char *icon = NULL;
+
+	if (!word[2][0] || !word[3][0])
+		return FALSE;
+
+	/* -eX enabled or not? */
+	if (word[idx][0] == '-' && word[idx][1] == 'e')
+	{
+		enable = atoi (word[idx] + 2);
+		idx++;
+	}
+
+	/* -i<ICONFILE> */
+	if (word[idx][0] == '-' && word[idx][1] == 'i')
+	{
+		icon = word[idx] + 2;
+		idx++;
+	}
+
+	/* -k<mod>,<key> key binding */
+	if (word[idx][0] == '-' && word[idx][1] == 'k')
+	{
+		char *comma = strchr (word[idx], ',');
+		if (!comma)
+			return FALSE;
+		mod = atoi (word[idx] + 2);
+		key = atoi (comma + 1);
+		idx++;
+	}
+
+	/* -m to specify PangoMarkup language */
+	if (word[idx][0] == '-' && word[idx][1] == 'm')
+	{
+		markup = TRUE;
+		idx++;
+	}
+
+	/* -pX to specify menu position */
+	if (word[idx][0] == '-' && word[idx][1] == 'p')
+	{
+		pos = atoi (word[idx] + 2);
+		idx++;
+	}
+
+	/* -rSTATE,GROUP to specify a radio item */
+	if (word[idx][0] == '-' && word[idx][1] == 'r')
+	{
+		state = atoi (word[idx] + 2);
+		group = word[idx] + 4;
+		idx++;
+	}
+
+	/* -tX to specify toggle item with default state */
+	if (word[idx][0] == '-' && word[idx][1] == 't')
+	{
+		state = atoi (word[idx] + 2);
+		idx++;
+		toggle = TRUE;
+	}
+
+	if (word[idx+1][0] == 0)
+		return FALSE;
+
+	/* the path */
+	path_part (word[idx+1], tbuf, 512);
+	len = strlen (tbuf);
+	if (len)
+		tbuf[len - 1] = 0;
+
+	/* the name of the item */
+	label = file_part (word[idx + 1]);
+	if (label[0] == '-' && label[1] == 0)
+		label = NULL;	/* separator */
+
+	if (markup)
+	{
+		char *p;	/* to force pango closing tags through */
+		for (p = label; *p; p++)
+			if (*p == 3)
+				*p = '/';
+	}
+
+	if (!strcasecmp (word[idx], "ADD"))
+	{
+		if (toggle)
+		{
+			menu_add (tbuf, label, word[idx + 2], word[idx + 3], pos, state, markup, enable, mod, key, NULL, NULL);
+		} else
+		{
+			if (word[idx + 2][0])
+				menu_add (tbuf, label, word[idx + 2], NULL, pos, state, markup, enable, mod, key, group, icon);
+			else
+				menu_add (tbuf, label, NULL, NULL, pos, state, markup, enable, mod, key, group, icon);
+		}
+		return TRUE;
+	}
+
+	if (!strcasecmp (word[idx], "DEL"))
+	{
+		menu_del (tbuf, label);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+mkick_cb (struct User *user, multidata *data)
+{
+	if (!user->op && !user->me)
+		data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason);
+	return TRUE;
+}
+
+static int
+mkickops_cb (struct User *user, multidata *data)
+{
+	if (user->op && !user->me)
+		data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason);
+	return TRUE;
+}
+
+static int
+cmd_mkick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	multidata data;
+
+	data.sess = sess;
+	data.reason = word_eol[2];
+	tree_foreach (sess->usertree, (tree_traverse_func *)mkickops_cb, &data);
+	tree_foreach (sess->usertree, (tree_traverse_func *)mkick_cb, &data);
+
+	return TRUE;
+}
+
+static int
+cmd_devoice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'v', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_discon (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sess->server->disconnect (sess, TRUE, -1);
+	return TRUE;
+}
+
+static int
+cmd_dns (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+#ifdef WIN32
+	PrintText (sess, "DNS is not implemented in Windows.\n");
+	return TRUE;
+#else
+	char *nick = word[2];
+	struct User *user;
+
+	if (*nick)
+	{
+		if (strchr (nick, '.') == NULL)
+		{
+			user = userlist_find (sess, nick);
+			if (user && user->hostname)
+			{
+				do_dns (sess, user->nick, user->hostname);
+			} else
+			{
+				sess->server->p_get_ip (sess->server, nick);
+				sess->server->doing_dns = TRUE;
+			}
+		} else
+		{
+			snprintf (tbuf, TBUFSIZE, "exec -d %s %s", prefs.dnsprogram, nick);
+			handle_command (sess, tbuf, FALSE);
+		}
+		return TRUE;
+	}
+	return FALSE;
+#endif
+}
+
+static int
+cmd_echo (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	PrintText (sess, word_eol[2]);
+	return TRUE;
+}
+
+#ifndef WIN32
+
+static void
+exec_check_process (struct session *sess)
+{
+	int val;
+
+	if (sess->running_exec == NULL)
+		return;
+	val = waitpid (sess->running_exec->childpid, NULL, WNOHANG);
+	if (val == -1 || val > 0)
+	{
+		close (sess->running_exec->myfd);
+		fe_input_remove (sess->running_exec->iotag);
+		free (sess->running_exec);
+		sess->running_exec = NULL;
+	}
+}
+
+#ifndef __EMX__
+static int
+cmd_execs (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	r = kill (sess->running_exec->childpid, SIGSTOP);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+static int
+cmd_execc (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	r = kill (sess->running_exec->childpid, SIGCONT);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+static int
+cmd_execk (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	if (strcmp (word[2], "-9") == 0)
+		r = kill (sess->running_exec->childpid, SIGKILL);
+	else
+		r = kill (sess->running_exec->childpid, SIGTERM);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+/* OS/2 Can't have the /EXECW command because it uses pipe(2) not socketpair
+   and thus it is simplex --AGL */
+static int
+cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int len;
+	char *temp;
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	len = strlen(word_eol[2]);
+	temp = malloc(len + 2);
+	sprintf(temp, "%s\n", word_eol[2]);
+	PrintText(sess, temp);
+	write(sess->running_exec->myfd, temp, len + 1);
+	free(temp);
+
+	return TRUE;
+}
+#endif /* !__EMX__ */
+
+/* convert ANSI escape color codes to mIRC codes */
+
+static short escconv[] =
+/* 0 1 2 3 4 5  6 7  0 1 2 3 4  5  6  7 */
+{  1,4,3,5,2,10,6,1, 1,7,9,8,12,11,13,1 };
+
+static void
+exec_handle_colors (char *buf, int len)
+{
+	char numb[16];
+	char *nbuf;
+	int i = 0, j = 0, k = 0, firstn = 0, col, colf = 0, colb = 0;
+	int esc = FALSE, backc = FALSE, bold = FALSE;
+
+	/* any escape codes in this text? */
+	if (strchr (buf, 27) == 0)
+		return;
+
+	nbuf = malloc (len + 1);
+
+	while (i < len)
+	{
+		switch (buf[i])
+		{
+		case '\r':
+			break;
+		case 27:
+			esc = TRUE;
+			break;
+		case ';':
+			if (!esc)
+				goto norm;
+			backc = TRUE;
+			numb[k] = 0;
+			firstn = atoi (numb);
+			k = 0;
+			break;
+		case '[':
+			if (!esc)
+				goto norm;
+			break;
+		default:
+			if (esc)
+			{
+				if (buf[i] >= 'A' && buf[i] <= 'z')
+				{
+					if (buf[i] == 'm')
+					{
+						/* ^[[0m */
+						if (k == 0 || (numb[0] == '0' && k == 1))
+						{
+							nbuf[j] = '\017';
+							j++;
+							bold = FALSE;
+							goto cont;
+						}
+
+						numb[k] = 0;
+						col = atoi (numb);
+						backc = FALSE;
+
+						if (firstn == 1)
+							bold = TRUE;
+
+						if (firstn >= 30 && firstn <= 37)
+							colf = firstn - 30;
+
+						if (col >= 40)
+						{
+							colb = col - 40;
+							backc = TRUE;
+						}
+
+						if (col >= 30 && col <= 37)
+							colf = col - 30;
+
+						if (bold)
+							colf += 8;
+
+						if (backc)
+						{
+							colb = escconv[colb % 14];
+							colf = escconv[colf % 14];
+							j += sprintf (&nbuf[j], "\003%d,%02d", colf, colb);
+						} else
+						{
+							colf = escconv[colf % 14];
+							j += sprintf (&nbuf[j], "\003%02d", colf);
+						}
+					}
+cont:				esc = FALSE;
+					backc = FALSE;
+					k = 0;
+				} else
+				{
+					if (isdigit ((unsigned char) buf[i]) && k < (sizeof (numb) - 1))
+					{
+						numb[k] = buf[i];
+						k++;
+					}
+				}
+			} else
+			{
+norm:			nbuf[j] = buf[i];
+				j++;
+			}
+		}
+		i++;
+	}
+
+	nbuf[j] = 0;
+	memcpy (buf, nbuf, j + 1);
+	free (nbuf);
+}
+
+#ifndef HAVE_MEMRCHR
+static void *
+memrchr (const void *block, int c, size_t size)
+{
+	unsigned char *p;
+
+	for (p = (unsigned char *)block + size; p != block; p--)
+		if (*p == c)
+			return p;
+	return 0;
+}
+#endif
+
+static gboolean
+exec_data (GIOChannel *source, GIOCondition condition, struct nbexec *s)
+{
+	char *buf, *readpos, *rest;
+	int rd, len;
+	int sok = s->myfd;
+
+	len = s->buffill;
+	if (len) {
+		/* append new data to buffered incomplete line */
+		buf = malloc(len + 2050);
+		memcpy(buf, s->linebuf, len);
+		readpos = buf + len;
+		free(s->linebuf);
+		s->linebuf = NULL;
+	}
+	else
+		readpos = buf = malloc(2050);
+
+	rd = read (sok, readpos, 2048);
+	if (rd < 1)
+	{
+		/* The process has died */
+		kill(s->childpid, SIGKILL);
+		if (len) {
+			buf[len] = '\0';
+			exec_handle_colors(buf, len);
+			if (s->tochannel)
+			{
+				/* must turn off auto-completion temporarily */
+				unsigned int old = prefs.nickcompletion;
+				prefs.nickcompletion = 0;
+				handle_multiline (s->sess, buf, FALSE, TRUE);
+				prefs.nickcompletion = old;
+			}
+			else
+				PrintText (s->sess, buf);
+		}
+		free(buf);
+		waitpid (s->childpid, NULL, 0);
+		s->sess->running_exec = NULL;
+		fe_input_remove (s->iotag);
+		close (sok);
+		free (s);
+		return TRUE;
+	}
+	len += rd;
+	buf[len] = '\0';
+
+	rest = memrchr(buf, '\n', len);
+	if (rest)
+		rest++;
+	else
+		rest = buf;
+	if (*rest) {
+		s->buffill = len - (rest - buf); /* = strlen(rest) */
+		s->linebuf = malloc(s->buffill);
+		memcpy(s->linebuf, rest, s->buffill);
+		*rest = '\0';
+		len -= s->buffill; /* possibly 0 */
+	}
+	else
+		s->buffill = 0;
+
+	if (len) {
+		exec_handle_colors (buf, len);
+		if (s->tochannel)
+			handle_multiline (s->sess, buf, FALSE, TRUE);
+		else
+			PrintText (s->sess, buf);
+	}
+
+	free(buf);
+	return TRUE;
+}
+
+static int
+cmd_exec (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int tochannel = FALSE;
+	char *cmd = word_eol[2];
+	int fds[2], pid = 0;
+	struct nbexec *s;
+	int shell = TRUE;
+	int fd;
+
+	if (*cmd)
+	{
+		exec_check_process (sess);
+		if (sess->running_exec != NULL)
+		{
+			EMIT_SIGNAL (XP_TE_ALREADYPROCESS, sess, NULL, NULL, NULL, NULL, 0);
+			return TRUE;
+		}
+
+		if (!strcmp (word[2], "-d"))
+		{
+			if (!*word[3])
+				return FALSE;
+			cmd = word_eol[3];
+			shell = FALSE;
+		}
+		else if (!strcmp (word[2], "-o"))
+		{
+			if (!*word[3])
+				return FALSE;
+			cmd = word_eol[3];
+			tochannel = TRUE;
+		}
+
+		if (shell)
+		{
+			if (access ("/bin/sh", X_OK) != 0)
+			{
+				fe_message (_("I need /bin/sh to run!\n"), FE_MSG_ERROR);
+				return TRUE;
+			}
+		}
+
+#ifdef __EMX__						  /* if os/2 */
+		if (pipe (fds) < 0)
+		{
+			PrintText (sess, "Pipe create error\n");
+			return FALSE;
+		}
+		setmode (fds[0], O_BINARY);
+		setmode (fds[1], O_BINARY);
+#else
+		if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) == -1)
+		{
+			PrintText (sess, "socketpair(2) failed\n");
+			return FALSE;
+		}
+#endif
+		s = (struct nbexec *) malloc (sizeof (struct nbexec));
+		memset(s, 0, sizeof(*s));
+		s->myfd = fds[0];
+		s->tochannel = tochannel;
+		s->sess = sess;
+
+		pid = fork ();
+		if (pid == 0)
+		{
+			/* This is the child's context */
+			close (0);
+			close (1);
+			close (2);
+			/* Close parent's end of pipe */
+			close(s->myfd);
+			/* Copy the child end of the pipe to stdout and stderr */
+			dup2 (fds[1], 1);
+			dup2 (fds[1], 2);
+			/* Also copy it to stdin so we can write to it */
+			dup2 (fds[1], 0);
+			/* Now close all open file descriptors except stdin, stdout and stderr */
+			for (fd = 3; fd < 1024; fd++) close(fd);
+			/* Now we call /bin/sh to run our cmd ; made it more friendly -DC1 */
+			if (shell)
+			{
+				execl ("/bin/sh", "sh", "-c", cmd, NULL);
+			} else
+			{
+				char **argv;
+				int argc;
+
+				my_poptParseArgvString (cmd, &argc, &argv);
+				execvp (argv[0], argv);
+			}
+			/* not reached unless error */
+			/*printf("exec error\n");*/
+			fflush (stdout);
+			fflush (stdin);
+			_exit (0);
+		}
+		if (pid == -1)
+		{
+			/* Parent context, fork() failed */
+
+			PrintText (sess, "Error in fork(2)\n");
+			close(fds[0]);
+			close(fds[1]);
+		} else
+		{
+			/* Parent path */
+			close(fds[1]);
+			s->childpid = pid;
+			s->iotag = fe_input_add (s->myfd, FIA_READ|FIA_EX, exec_data, s);
+			sess->running_exec = s;
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+#endif
+
+static int
+cmd_flushq (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sprintf (tbuf, "Flushing server send queue, %d bytes.\n", sess->server->sendq_len);
+	PrintText (sess, tbuf);
+	sess->server->flush_queue (sess->server);
+	return TRUE;
+}
+
+static int
+cmd_quit (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+		sess->quitreason = word_eol[2];
+	sess->server->disconnect (sess, TRUE, -1);
+	sess->quitreason = NULL;
+	return 2;
+}
+
+static int
+cmd_gate (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *server_name = word[2];
+	server *serv = sess->server;
+	if (*server_name)
+	{
+		char *port = word[3];
+#ifdef USE_OPENSSL
+		serv->use_ssl = FALSE;
+#endif
+		server_fill_her_up (serv);
+		if (*port)
+			serv->connect (serv, server_name, atoi (port), TRUE);
+		else
+			serv->connect (serv, server_name, 23, TRUE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+typedef struct
+{
+	char *cmd;
+	session *sess;
+} getvalinfo;
+
+static void
+get_int_cb (int cancel, int val, getvalinfo *info)
+{
+	char buf[512];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "%s %d", info->cmd, val);
+		if (is_session (info->sess))
+			handle_command (info->sess, buf, FALSE);
+	}
+
+	free (info->cmd);
+	free (info);
+}
+
+static int
+cmd_getint (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	getvalinfo *info;
+
+	if (!word[4][0])
+		return FALSE;
+
+	info = malloc (sizeof (*info));
+	info->cmd = strdup (word[3]);
+	info->sess = sess;
+
+	fe_get_int (word[4], atoi (word[2]), get_int_cb, info);
+
+	return TRUE;
+}
+
+static void
+get_file_cb (char *cmd, char *file)
+{
+	char buf[1024 + 128];
+
+	/* execute the command once per file, then once more with
+      no args */
+	if (file)
+	{
+		snprintf (buf, sizeof (buf), "%s %s", cmd, file);
+		handle_command (current_sess, buf, FALSE);
+	}
+	else
+	{
+		handle_command (current_sess, cmd, FALSE);
+		free (cmd);
+	}
+}
+
+static int
+cmd_getfile (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int flags = 0;
+
+	if (!word[3][0])
+		return FALSE;
+
+	if (!strcmp (word[2], "-folder"))
+	{
+		flags |= FRF_CHOOSEFOLDER;
+		idx++;
+	}
+
+	if (!strcmp (word[idx], "-multi"))
+	{
+		flags |= FRF_MULTIPLE;
+		idx++;
+	}
+
+	if (!strcmp (word[idx], "-save"))
+	{
+		flags |= FRF_WRITE;
+		idx++;
+	}
+
+	fe_get_file (word[idx+1], word[idx+2], (void *)get_file_cb, strdup (word[idx]), flags);
+
+	return TRUE;
+}
+
+static void
+get_str_cb (int cancel, char *val, getvalinfo *info)
+{
+	char buf[512];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "%s %s", info->cmd, val);
+		if (is_session (info->sess))
+			handle_command (info->sess, buf, FALSE);
+	}
+
+	free (info->cmd);
+	free (info);
+}
+
+static int
+cmd_getstr (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	getvalinfo *info;
+
+	if (!word[4][0])
+		return FALSE;
+
+	info = malloc (sizeof (*info));
+	info->cmd = strdup (word[3]);
+	info->sess = sess;
+
+	fe_get_str (word[4], word[2], get_str_cb, info);
+
+	return TRUE;
+}
+
+static int
+cmd_ghost (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (!word[2][0])
+		return FALSE;
+
+	sess->server->p_ns_ghost (sess->server, word[2], word[3]);
+	return TRUE;
+}
+
+static int
+cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	switch (str_ihash (word[2]))
+	{
+	case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */
+	case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */
+	case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */
+	case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */
+	case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */
+	case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */
+	case 0x0030dd42: fe_ctrl_gui (sess, 0, 0); break; /* HIDE */
+	case 0x61addbe3: fe_ctrl_gui (sess, 5, 0); break; /* ICONIFY */
+	case 0xc0851aaa: fe_message (word[3], FE_MSG_INFO|FE_MSG_MARKUP); break; /* MSGBOX */
+	case 0x0035dafd: fe_ctrl_gui (sess, 1, 0); break; /* SHOW */
+	case 0x0033155f: /* MENU */
+		if (!strcasecmp (word[3], "TOGGLE"))
+			fe_ctrl_gui (sess, 6, 0);
+		else
+			return FALSE;
+		break;
+	default:
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+typedef struct
+{
+	int longfmt;
+	int i, t;
+	char *buf;
+} help_list;
+
+static void
+show_help_line (session *sess, help_list *hl, char *name, char *usage)
+{
+	int j, len, max;
+	char *p;
+
+	if (name[0] == '.')	/* hidden command? */
+		return;
+
+	if (hl->longfmt)	/* long format for /HELP -l */
+	{
+		if (!usage || usage[0] == 0)
+			PrintTextf (sess, "   \0034%s\003 :\n", name);
+		else
+			PrintTextf (sess, "   \0034%s\003 : %s\n", name, _(usage));
+		return;
+	}
+
+	/* append the name into buffer, but convert to uppercase */
+	len = strlen (hl->buf);
+	p = name;
+	while (*p)
+	{
+		hl->buf[len] = toupper ((unsigned char) *p);
+		len++;
+		p++;
+	}
+	hl->buf[len] = 0;
+
+	hl->t++;
+	if (hl->t == 5)
+	{
+		hl->t = 0;
+		strcat (hl->buf, "\n");
+		PrintText (sess, hl->buf);
+		hl->buf[0] = ' ';
+		hl->buf[1] = ' ';
+		hl->buf[2] = 0;
+	} else
+	{
+		/* append some spaces after the command name */
+		max = strlen (name);
+		if (max < 10)
+		{
+			max = 10 - max;
+			for (j = 0; j < max; j++)
+			{
+				hl->buf[len] = ' ';
+				len++;
+				hl->buf[len] = 0;
+			}
+		}
+	}
+}
+
+static int
+cmd_help (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 0, longfmt = 0;
+	char *helpcmd = "";
+	GSList *list;
+
+	if (tbuf)
+		helpcmd = word[2];
+	if (*helpcmd && strcmp (helpcmd, "-l") == 0)
+		longfmt = 1;
+
+	if (*helpcmd && !longfmt)
+	{
+		help (sess, tbuf, helpcmd, FALSE);
+	} else
+	{
+		struct popup *pop;
+		char *buf = malloc (4096);
+		help_list hl;
+
+		hl.longfmt = longfmt;
+		hl.buf = buf;
+
+		PrintTextf (sess, "\n%s\n\n", _("Commands Available:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		while (xc_cmds[i].name)
+		{
+			show_help_line (sess, &hl, xc_cmds[i].name, xc_cmds[i].help);
+			i++;
+		}
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("User defined commands:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		list = command_list;
+		while (list)
+		{
+			pop = list->data;
+			show_help_line (sess, &hl, pop->name, pop->cmd);
+			list = list->next;
+		}
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("Plugin defined commands:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		plugin_command_foreach (sess, &hl, (void *)show_help_line);
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+		free (buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("Type /HELP <command> for more information, or /HELP -l"));
+	}
+	return TRUE;
+}
+
+static int
+cmd_id (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0])
+	{
+		sess->server->p_ns_identify (sess->server, word[2]);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_ignore (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i;
+	int type = 0;
+	int quiet = 0;
+	char *mask;
+
+	if (!*word[2])
+	{
+		ignore_showlist (sess);
+		return TRUE;
+	}
+	if (!*word[3])
+		return FALSE;
+
+	i = 3;
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (type == 0)
+				return FALSE;
+
+			mask = word[2];
+			if (strchr (mask, '?') == NULL &&
+			    strchr (mask, '*') == NULL &&
+			    userlist_find (sess, mask))
+			{
+				mask = tbuf;
+				snprintf (tbuf, TBUFSIZE, "%s!*@*", word[2]);
+			}
+
+			i = ignore_add (mask, type);
+			if (quiet)
+				return TRUE;
+			switch (i)
+			{
+			case 1:
+				EMIT_SIGNAL (XP_TE_IGNOREADD, sess, mask, NULL, NULL, NULL, 0);
+				break;
+			case 2:	/* old ignore changed */
+				EMIT_SIGNAL (XP_TE_IGNORECHANGE, sess, mask, NULL, NULL, NULL, 0);
+			}
+			return TRUE;
+		}
+		if (!strcasecmp (word[i], "UNIGNORE"))
+			type |= IG_UNIG;
+		else if (!strcasecmp (word[i], "ALL"))
+			type |= IG_PRIV | IG_NOTI | IG_CHAN | IG_CTCP | IG_INVI | IG_DCC;
+		else if (!strcasecmp (word[i], "PRIV"))
+			type |= IG_PRIV;
+		else if (!strcasecmp (word[i], "NOTI"))
+			type |= IG_NOTI;
+		else if (!strcasecmp (word[i], "CHAN"))
+			type |= IG_CHAN;
+		else if (!strcasecmp (word[i], "CTCP"))
+			type |= IG_CTCP;
+		else if (!strcasecmp (word[i], "INVI"))
+			type |= IG_INVI;
+		else if (!strcasecmp (word[i], "QUIET"))
+			quiet = 1;
+		else if (!strcasecmp (word[i], "NOSAVE"))
+			type |= IG_NOSAVE;
+		else if (!strcasecmp (word[i], "DCC"))
+			type |= IG_DCC;
+		else
+		{
+			sprintf (tbuf, _("Unknown arg '%s' ignored."), word[i]);
+			PrintText (sess, tbuf);
+		}
+		i++;
+	}
+}
+
+static int
+cmd_invite (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (!*word[2])
+		return FALSE;
+	if (*word[3])
+		sess->server->p_invite (sess->server, word[3], word[2]);
+	else
+		sess->server->p_invite (sess->server, sess->channel, word[2]);
+	return TRUE;
+}
+
+static int
+cmd_join (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *chan = word[2];
+	if (*chan)
+	{
+		char *po, *pass = word[3];
+		sess->server->p_join (sess->server, chan, pass);
+		if (sess->channel[0] == 0 && sess->waitchannel[0])
+		{
+			po = strchr (chan, ',');
+			if (po)
+				*po = 0;
+			safe_strcpy (sess->waitchannel, chan, CHANLEN);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_kick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *reason = word_eol[3];
+	if (*nick)
+	{
+		sess->server->p_kick (sess->server, sess->channel, nick, reason);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_kickban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *reason = word_eol[3];
+	struct User *user;
+
+	if (*nick)
+	{
+		/* if the reason is a 1 digit number, treat it as a bantype */
+
+		user = userlist_find (sess, nick);
+
+		if (isdigit ((unsigned char) reason[0]) && reason[1] == 0)
+		{
+			ban (sess, tbuf, nick, reason, (user && user->op));
+			reason[0] = 0;
+		} else
+			ban (sess, tbuf, nick, "", (user && user->op));
+
+		sess->server->p_kick (sess->server, sess->channel, nick, reason);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_killall (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	xchat_exit();
+	return 2;
+}
+
+static int
+cmd_lagcheck (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	lag_check ();
+	return TRUE;
+}
+
+static void
+lastlog (session *sess, char *search, gboolean regexp)
+{
+	session *lastlog_sess;
+
+	if (!is_session (sess))
+		return;
+
+	lastlog_sess = find_dialog (sess->server, "(lastlog)");
+	if (!lastlog_sess)
+		lastlog_sess = new_ircwindow (sess->server, "(lastlog)", SESS_DIALOG, 0);
+
+	lastlog_sess->lastlog_sess = sess;
+	lastlog_sess->lastlog_regexp = regexp;	/* remember the search type */
+
+	fe_text_clear (lastlog_sess, 0);
+	fe_lastlog (sess, lastlog_sess, search, regexp);
+}
+
+static int
+cmd_lastlog (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		if (!strcmp (word[2], "-r"))
+			lastlog (sess, word_eol[3], TRUE);
+		else
+			lastlog (sess, word_eol[2], FALSE);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_list (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sess->server->p_list_channels (sess->server, word_eol[2], 1);
+
+	return TRUE;
+}
+
+gboolean
+load_perform_file (session *sess, char *file)
+{
+	char tbuf[1024 + 4];
+	char *nl;
+	FILE *fp;
+
+	fp = xchat_fopen_file (file, "r", XOF_FULLPATH);
+	if (!fp)
+		return FALSE;
+
+	tbuf[1024] = 0;
+	while (fgets (tbuf, 1024, fp))
+	{
+		nl = strchr (tbuf, '\n');
+		if (nl == tbuf) /* skip empty commands */
+			continue;
+		if (nl)
+			*nl = 0;
+		if (tbuf[0] == prefs.cmdchar[0])
+			handle_command (sess, tbuf + 1, TRUE);
+		else
+			handle_command (sess, tbuf, TRUE);
+	}
+	fclose (fp);
+	return TRUE;
+}
+
+static int
+cmd_load (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *error, *arg, *file;
+	int len;
+
+	if (!word[2][0])
+		return FALSE;
+
+	if (strcmp (word[2], "-e") == 0)
+	{
+		file = expand_homedir (word[3]);
+		if (!load_perform_file (sess, file))
+		{
+			PrintTextf (sess, _("Cannot access %s\n"), file);
+			PrintText (sess, errorstring (errno));
+		}
+		free (file);
+		return TRUE;
+	}
+
+#ifdef USE_PLUGIN
+	len = strlen (word[2]);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (".dll", word[2] + len - 4) == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (".sl", word[2] + len - 3) == 0)
+#else
+	if (len > 3 && strcasecmp (".so", word[2] + len - 3) == 0)
+#endif
+#endif
+	{
+		arg = NULL;
+		if (word_eol[3][0])
+			arg = word_eol[3];
+
+		file = expand_homedir (word[2]);
+		error = plugin_load (sess, file, arg);
+		free (file);
+
+		if (error)
+			PrintText (sess, error);
+
+		return TRUE;
+	}
+#endif
+
+	sprintf (tbuf, "Unknown file type %s. Maybe you need to install the Perl or Python plugin?\n", word[2]);
+	PrintText (sess, tbuf);
+
+	return FALSE;
+}
+
+static int
+cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *act = word_eol[2];
+
+	if (!(*act))
+		return FALSE;
+
+	if (sess->type == SESS_SERVER)
+	{
+		notj_msg (sess);
+		return TRUE;
+	}
+
+	snprintf (tbuf, TBUFSIZE, "\001ACTION %s\001\r", act);
+	/* first try through DCC CHAT */
+	if (dcc_write_chat (sess->channel, tbuf))
+	{
+		/* print it to screen */
+		inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE);
+	} else
+	{
+		/* DCC CHAT failed, try through server */
+		if (sess->server->connected)
+		{
+			sess->server->p_action (sess->server, sess->channel, act);
+			/* print it to screen */
+			inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE);
+		} else
+		{
+			notc_msg (sess);
+		}
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_mode (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* +channel channels are dying, let those servers whine about modes.
+	 * return info about current channel if available and no info is given */
+	if ((*word[2] == '+') || (*word[2] == 0) || (!is_channel(sess->server, word[2]) &&
+				!(rfc_casecmp(sess->server->nick, word[2]) == 0)))
+	{
+		if(sess->channel[0] == 0)
+			return FALSE;
+		sess->server->p_mode (sess->server, sess->channel, word_eol[2]);
+	}
+	else
+		sess->server->p_mode (sess->server, word[2], word_eol[3]);
+	return TRUE;
+}
+
+static int
+mop_cb (struct User *user, multidata *data)
+{
+	if (!user->op)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * (sess->total - sess->ops));
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '+', 'o', 0);
+
+	free (nicks);
+
+	return TRUE;
+}
+
+static int
+cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *msg = word_eol[3];
+	struct session *newsess;
+
+	if (*nick)
+	{
+		if (*msg)
+		{
+			if (strcmp (nick, ".") == 0)
+			{							  /* /msg the last nick /msg'ed */
+				if (sess->lastnick[0])
+					nick = sess->lastnick;
+			} else
+			{
+				safe_strcpy (sess->lastnick, nick, NICKLEN);	/* prime the last nick memory */
+			}
+
+			if (*nick == '=')
+			{
+				nick++;
+				if (!dcc_write_chat (nick, msg))
+				{
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+					return TRUE;
+				}
+			} else
+			{
+				if (!sess->server->connected)
+				{
+					notc_msg (sess);
+					return TRUE;
+				}
+				sess->server->p_message (sess->server, nick, msg);
+			}
+			newsess = find_dialog (sess->server, nick);
+			if (!newsess)
+				newsess = find_channel (sess->server, nick);
+			if (newsess)
+				inbound_chanmsg (newsess->server, NULL, newsess->channel,
+									  newsess->server->nick, msg, TRUE, FALSE);
+			else
+			{
+				/* mask out passwords */
+				if (strcasecmp (nick, "nickserv") == 0 &&
+					 strncasecmp (msg, "identify ", 9) == 0)
+					msg = "identify ****";
+				EMIT_SIGNAL (XP_TE_MSGSEND, sess, nick, msg, NULL, NULL, 0);
+			}
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static int
+cmd_names (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2])
+	  	sess->server->p_names (sess->server, word[2]);
+	else
+		sess->server->p_names (sess->server, sess->channel);
+	return TRUE;
+}
+
+static int
+cmd_nctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[3])
+	{
+		sess->server->p_nctcp (sess->server, word[2], word_eol[3]);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_newserver (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (strcmp (word[2], "-noconnect") == 0)
+	{
+		new_ircwindow (NULL, word[3], SESS_SERVER, 0);
+		return TRUE;
+	}
+	
+	sess = new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	cmd_server (sess, tbuf, word, word_eol);
+	return TRUE;
+}
+
+static int
+cmd_nick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	if (*nick)
+	{
+		if (sess->server->connected)
+			sess->server->p_change_nick (sess->server, nick);
+		else
+			inbound_newnick (sess->server, sess->server->nick, nick, TRUE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_notice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2] && *word_eol[3])
+	{
+		sess->server->p_notice (sess->server, word[2], word_eol[3]);
+		EMIT_SIGNAL (XP_TE_NOTICESEND, sess, word[2], word_eol[3], NULL, NULL, 0);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_notify (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 1;
+	char *net = NULL;
+
+	if (*word[2])
+	{
+		if (strcmp (word[2], "-n") == 0)	/* comma sep network list */
+		{
+			net = word[3];
+			i += 2;
+		}
+
+		while (1)
+		{
+			i++;
+			if (!*word[i])
+				break;
+			if (notify_deluser (word[i]))
+			{
+				EMIT_SIGNAL (XP_TE_DELNOTIFY, sess, word[i], NULL, NULL, NULL, 0);
+				return TRUE;
+			}
+
+			if (net && strcmp (net, "ASK") == 0)
+				fe_notify_ask (word[i], NULL);
+			else
+			{
+				notify_adduser (word[i], net);
+				EMIT_SIGNAL (XP_TE_ADDNOTIFY, sess, word[i], NULL, NULL, NULL, 0);
+			}
+		}
+	} else
+		notify_showlist (sess);
+	return TRUE;
+}
+
+static int
+cmd_op (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'o', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_part (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *chan = word[2];
+	char *reason = word_eol[3];
+	if (!*chan)
+		chan = sess->channel;
+	if ((*chan) && is_channel (sess->server, chan))
+	{
+		if (reason[0] == 0)
+			reason = NULL;
+		server_sendpart (sess->server, chan, reason);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_ping (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char timestring[64];
+	unsigned long tim;
+	char *to = word[2];
+
+	tim = make_ping_time ();
+
+	snprintf (timestring, sizeof (timestring), "%lu", tim);
+	sess->server->p_ping (sess->server, to, timestring);
+
+	return TRUE;
+}
+
+void
+open_query (server *serv, char *nick, gboolean focus_existing)
+{
+	session *sess;
+
+	sess = find_dialog (serv, nick);
+	if (!sess)
+		new_ircwindow (serv, nick, SESS_DIALOG, 1);
+	else if (focus_existing)
+		fe_ctrl_gui (sess, 2, 0);	/* bring-to-front */
+}
+
+static int
+cmd_query (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	gboolean focus = TRUE;
+
+	if (strcmp (word[2], "-nofocus") == 0)
+	{
+		nick = word[3];
+		focus = FALSE;
+	}
+
+	if (*nick && !is_channel (sess->server, nick))
+	{
+		open_query (sess->server, nick, focus);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_quote (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *raw = word_eol[2];
+
+	return sess->server->p_raw (sess->server, raw);
+}
+
+static int
+cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int tmp = prefs.recon_delay;
+	GSList *list;
+	server *serv = sess->server;
+
+	prefs.recon_delay = 0;
+
+	if (!strcasecmp (word[2], "ALL"))
+	{
+		list = serv_list;
+		while (list)
+		{
+			serv = list->data;
+			if (serv->connected)
+				serv->auto_reconnect (serv, TRUE, -1);
+			list = list->next;
+		}
+	}
+	/* If it isn't "ALL" and there is something
+	there it *should* be a server they are trying to connect to*/
+	else if (*word[2])
+	{
+		int offset = 0;
+#ifdef USE_OPENSSL
+		int use_ssl = FALSE;
+
+		if (strcmp (word[2], "-ssl") == 0)
+		{
+			use_ssl = TRUE;
+			offset++;	/* args move up by 1 word */
+		}
+		serv->use_ssl = use_ssl;
+		serv->accept_invalid_cert = TRUE;
+#endif
+
+		if (*word[4+offset])
+			safe_strcpy (serv->password, word[4+offset], sizeof (serv->password));
+		if (*word[3+offset])
+			serv->port = atoi (word[3+offset]);
+		safe_strcpy (serv->hostname, word[2+offset], sizeof (serv->hostname));
+		serv->auto_reconnect (serv, TRUE, -1);
+	}
+	else
+	{
+		serv->auto_reconnect (serv, TRUE, -1);
+	}
+	prefs.recon_delay = tmp;
+
+	return TRUE;
+}
+
+static int
+cmd_recv (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		sess->server->p_inline (sess->server, word_eol[2], strlen (word_eol[2]));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_say (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *speech = word_eol[2];
+	if (*speech)
+	{
+		handle_say (sess, speech, FALSE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	guint32 addr;
+	socklen_t len;
+	struct sockaddr_in SAddr;
+
+	if (!word[2][0])
+		return FALSE;
+
+	addr = dcc_get_my_address ();
+	if (addr == 0)
+	{
+		/* use the one from our connected server socket */
+		memset (&SAddr, 0, sizeof (struct sockaddr_in));
+		len = sizeof (SAddr);
+		getsockname (sess->server->sok, (struct sockaddr *) &SAddr, &len);
+		addr = SAddr.sin_addr.s_addr;
+	}
+	addr = ntohl (addr);
+
+	if ((addr & 0xffff0000) == 0xc0a80000 ||	/* 192.168.x.x */
+		 (addr & 0xff000000) == 0x0a000000)		/* 10.x.x.x */
+		/* we got a private net address, let's PSEND or it'll fail */
+		snprintf (tbuf, 512, "DCC PSEND %s", word_eol[2]);
+	else
+		snprintf (tbuf, 512, "DCC SEND %s", word_eol[2]);
+
+	handle_command (sess, tbuf, FALSE);
+
+	return TRUE;
+}
+
+static int
+cmd_setcursor (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int delta = FALSE;
+
+	if (*word[2])
+	{
+		if (word[2][0] == '-' || word[2][0] == '+')
+			delta = TRUE;
+		fe_set_inputbox_cursor (sess, delta, atoi (word[2]));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_settab (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		strcpy (tbuf, sess->channel);
+		safe_strcpy (sess->channel, word_eol[2], CHANLEN);
+		fe_set_channel (sess);
+		strcpy (sess->channel, tbuf);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_settext (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	fe_set_inputbox_contents (sess, word_eol[2]);
+	return TRUE;
+}
+
+static int
+cmd_splay (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2])
+	{
+		sound_play (word[2], FALSE);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+parse_irc_url (char *url, char *server_name[], char *port[], char *channel[], int *use_ssl)
+{
+	char *co;
+#ifdef USE_OPENSSL
+	if (strncasecmp ("ircs://", url, 7) == 0)
+	{
+		*use_ssl = TRUE;
+		*server_name = url + 7;
+		goto urlserv;
+	}
+#endif
+
+	if (strncasecmp ("irc://", url, 6) == 0)
+	{
+		*server_name = url + 6;
+#ifdef USE_OPENSSL
+urlserv:
+#endif
+		/* check for port */
+		co = strchr (*server_name, ':');
+		if (co)
+		{
+			*port = co + 1;
+			*co = 0;
+		} else
+			co = *server_name;
+		/* check for channel - mirc style */
+		co = strchr (co + 1, '/');
+		if (co)
+		{
+			*co = 0;
+			co++;
+			if (*co == '#')
+				*channel = co+1;
+			else
+				*channel = co;
+			
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int offset = 0;
+	char *server_name = NULL;
+	char *port = NULL;
+	char *pass = NULL;
+	char *channel = NULL;
+	int use_ssl = FALSE;
+	int is_url = TRUE;
+	server *serv = sess->server;
+
+#ifdef USE_OPENSSL
+	/* BitchX uses -ssl, mIRC uses -e, let's support both */
+	if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0)
+	{
+		use_ssl = TRUE;
+		offset++;	/* args move up by 1 word */
+	}
+#endif
+
+	if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &use_ssl))
+	{
+		is_url = FALSE;
+		server_name = word[2 + offset];
+	}
+	if (port)
+		pass = word[3 + offset];
+	else
+	{
+		port = word[3 + offset];
+		pass = word[4 + offset];
+	}
+	
+	if (!(*server_name))
+		return FALSE;
+
+	sess->server->network = NULL;
+
+	/* dont clear it for /servchan */
+	if (strncasecmp (word_eol[1], "SERVCHAN ", 9))
+		sess->willjoinchannel[0] = 0;
+
+	if (channel)
+	{
+		sess->willjoinchannel[0] = '#';
+		safe_strcpy ((sess->willjoinchannel + 1), channel, (CHANLEN - 1));
+	}
+
+	/* support +7000 style ports like mIRC */
+	if (port[0] == '+')
+	{
+		port++;
+#ifdef USE_OPENSSL
+		use_ssl = TRUE;
+#endif
+	}
+
+	if (*pass)
+	{
+		safe_strcpy (serv->password, pass, sizeof (serv->password));
+	}
+#ifdef USE_OPENSSL
+	serv->use_ssl = use_ssl;
+	serv->accept_invalid_cert = TRUE;
+#endif
+
+	/* try to connect by Network name */
+	if (servlist_connect_by_netname (sess, server_name, !is_url))
+		return TRUE;
+
+	if (*port)
+	{
+		serv->connect (serv, server_name, atoi (port), FALSE);
+	} else
+	{
+		/* -1 for default port */
+		serv->connect (serv, server_name, -1, FALSE);
+	}
+
+	/* try to associate this connection with a listed network */
+	if (!serv->network)
+		/* search for this hostname in the entire server list */
+		serv->network = servlist_net_find_from_server (server_name);
+		/* may return NULL, but that's OK */
+
+	return TRUE;
+}
+
+static int
+cmd_servchan (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	int offset = 0;
+
+#ifdef USE_OPENSSL
+	if (strcmp (word[2], "-ssl") == 0)
+		offset++;
+#endif
+
+	if (*word[4 + offset])
+	{
+		safe_strcpy (sess->willjoinchannel, word[4 + offset], CHANLEN);
+		return cmd_server (sess, tbuf, word, word_eol);
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_topic (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0] && is_channel (sess->server, word[2]))
+		sess->server->p_topic (sess->server, word[2], word_eol[3]);
+	else
+		sess->server->p_topic (sess->server, sess->channel, word_eol[2]);
+	return TRUE;
+}
+
+static int
+cmd_tray (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (strcmp (word[2], "-b") == 0)
+	{
+		fe_tray_set_balloon (word[3], word[4][0] ? word[4] : NULL);
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-t") == 0)
+	{
+		fe_tray_set_tooltip (word[3][0] ? word[3] : NULL);
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-i") == 0)
+	{
+		fe_tray_set_icon (atoi (word[3]));
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-f") != 0)
+		return FALSE;
+
+	if (!word[3][0])
+	{
+		fe_tray_set_file (NULL);	/* default xchat icon */
+		return TRUE;
+	}
+
+	if (!word[4][0])
+	{
+		fe_tray_set_file (word[3]);	/* fixed custom icon */
+		return TRUE;
+	}
+
+	/* flash between 2 icons */
+	fe_tray_set_flash (word[4], word[5][0] ? word[5] : NULL, atoi (word[3]));
+	return TRUE;
+}
+
+static int
+cmd_unignore (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	char *mask = word[2];
+	char *arg = word[3];
+	if (*mask)
+	{
+		if (ignore_del (mask, NULL))
+		{
+			if (strcasecmp (arg, "QUIET"))
+				EMIT_SIGNAL (XP_TE_IGNOREREMOVE, sess, mask, NULL, NULL, NULL, 0);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+#ifdef USE_PLUGIN
+	int len, by_file = FALSE;
+
+	len = strlen (word[2]);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (word[2] + len - 4, ".dll") == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (word[2] + len - 3, ".sl") == 0)
+#else
+	if (len > 3 && strcasecmp (word[2] + len - 3, ".so") == 0)
+#endif
+#endif
+		by_file = TRUE;
+
+	switch (plugin_kill (word[2], by_file))
+	{
+	case 0:
+			PrintText (sess, _("No such plugin found.\n"));
+			break;
+	case 1:
+			return TRUE;
+	case 2:
+			PrintText (sess, _("That plugin is refusing to unload.\n"));
+			break;
+	}
+#endif
+
+	return FALSE;
+}
+
+static server *
+find_server_from_hostname (char *hostname)
+{
+	GSList *list = serv_list;
+	server *serv;
+
+	while (list)
+	{
+		serv = list->data;
+		if (!strcasecmp (hostname, serv->hostname) && serv->connected)
+			return serv;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+static server *
+find_server_from_net (void *net)
+{
+	GSList *list = serv_list;
+	server *serv;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->network == net && serv->connected)
+			return serv;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+static void
+url_join_only (server *serv, char *tbuf, char *channel)
+{
+	/* already connected, JOIN only. FIXME: support keys? */
+	tbuf[0] = '#';
+	/* tbuf is 4kb */
+	safe_strcpy ((tbuf + 1), channel, 256);
+	serv->p_join (serv, tbuf, "");
+}
+
+static int
+cmd_url (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0])
+	{
+		char *server_name = NULL;
+		char *port = NULL;
+		char *channel = NULL;
+		char *url = g_strdup (word[2]);
+		int use_ssl = FALSE;
+		void *net;
+		server *serv;
+
+		if (parse_irc_url (url, &server_name, &port, &channel, &use_ssl))
+		{
+			/* maybe we're already connected to this net */
+
+			/* check for "FreeNode" */
+			net = servlist_net_find (server_name, NULL, strcasecmp);
+			/* check for "irc.eu.freenode.net" */
+			if (!net)
+				net = servlist_net_find_from_server (server_name);
+
+			if (net)
+			{
+				/* found the network, but are we connected? */
+				serv = find_server_from_net (net);
+				if (serv)
+				{
+					url_join_only (serv, tbuf, channel);
+					g_free (url);
+					return TRUE;
+				}
+			}
+			else
+			{
+				/* an un-listed connection */
+				serv = find_server_from_hostname (server_name);
+				if (serv)
+				{
+					url_join_only (serv, tbuf, channel);
+					g_free (url);
+					return TRUE;
+				}
+			}
+
+			/* not connected to this net, open new window */
+			cmd_newserver (sess, tbuf, word, word_eol);
+
+		} else
+			fe_open_url (word[2]);
+		g_free (url);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+userlist_cb (struct User *user, session *sess)
+{
+	time_t lt;
+
+	if (!user->lasttalk)
+		lt = 0;
+	else
+		lt = time (0) - user->lasttalk;
+	PrintTextf (sess,
+				"\00306%s\t\00314[\00310%-38s\00314] \017ov\0033=\017%d%d away=%u lt\0033=\017%d\n",
+				user->nick, user->hostname, user->op, user->voice, user->away, lt);
+
+	return TRUE;
+}
+
+static int
+cmd_uselect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int clear = TRUE;
+	int scroll = FALSE;
+
+	if (strcmp (word[2], "-a") == 0)	/* ADD (don't clear selections) */
+	{
+		clear = FALSE;
+		idx++;
+	}
+	if (strcmp (word[idx], "-s") == 0)	/* SCROLL TO */
+	{
+		scroll = TRUE;
+		idx++;
+	}
+	/* always valid, no args means clear the selection list */
+	fe_uselect (sess, word + idx, clear, scroll);
+	return TRUE;
+}
+
+static int
+cmd_userlist (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	tree_foreach (sess->usertree, (tree_traverse_func *)userlist_cb, sess);
+	return TRUE;
+}
+
+static int
+wallchop_cb (struct User *user, multidata *data)
+{
+	if (user->op)
+	{
+		if (data->i)
+			strcat (data->tbuf, ",");
+		strcat (data->tbuf, user->nick);
+		data->i++;
+	}
+	if (data->i == 5)
+	{
+		data->i = 0;
+		sprintf (data->tbuf + strlen (data->tbuf),
+					" :[@%s] %s", data->sess->channel, data->reason);
+		data->sess->server->p_raw (data->sess->server, data->tbuf);
+		strcpy (data->tbuf, "NOTICE ");
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_wallchop (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	multidata data;
+
+	if (!(*word_eol[2]))
+		return FALSE;
+
+	strcpy (tbuf, "NOTICE ");
+
+	data.reason = word_eol[2];
+	data.tbuf = tbuf;
+	data.i = 0;
+	data.sess = sess;
+	tree_foreach (sess->usertree, (tree_traverse_func*)wallchop_cb, &data);
+
+	if (data.i)
+	{
+		sprintf (tbuf + strlen (tbuf),
+					" :[@%s] %s", sess->channel, word_eol[2]);
+		sess->server->p_raw (sess->server, tbuf);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_wallchan (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	GSList *list;
+
+	if (*word_eol[2])
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->type == SESS_CHANNEL)
+			{
+				inbound_chanmsg (sess->server, NULL, sess->channel,
+									  sess->server->nick, word_eol[2], TRUE, FALSE);
+				sess->server->p_message (sess->server, sess->channel, word_eol[2]);
+			}
+			list = list->next;
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'h', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'v', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+/* *MUST* be kept perfectly sorted for the bsearch to work */
+const struct commands xc_cmds[] = {
+	{"ADDBUTTON", cmd_addbutton, 0, 0, 1,
+	 N_("ADDBUTTON <name> <action>, adds a button under the user-list")},
+	{"ALLCHAN", cmd_allchannels, 0, 0, 1,
+	 N_("ALLCHAN <cmd>, sends a command to all channels you're in")},
+	{"ALLCHANL", cmd_allchannelslocal, 0, 0, 1,
+	 N_("ALLCHANL <cmd>, sends a command to all channels you're in")},
+	{"ALLSERV", cmd_allservers, 0, 0, 1,
+	 N_("ALLSERV <cmd>, sends a command to all servers you're in")},
+	{"AWAY", cmd_away, 1, 0, 1, N_("AWAY [<reason>], sets you away")},
+	{"BACK", cmd_back, 1, 0, 1, N_("BACK, sets you back (not away)")},
+	{"BAN", cmd_ban, 1, 1, 1,
+	 N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")},
+	{"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")},
+	{"CHARSET", cmd_charset, 0, 0, 1, 0},
+	{"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")},
+	{"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")},
+
+	{"COUNTRY", cmd_country, 0, 0, 1,
+	 N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")},
+	{"CTCP", cmd_ctcp, 1, 0, 1,
+	 N_("CTCP <nick> <message>, send the CTCP message to nick, common messages are VERSION and USERINFO")},
+	{"CYCLE", cmd_cycle, 1, 1, 1,
+	 N_("CYCLE [<channel>], parts the current or given channel and immediately rejoins")},
+	{"DCC", cmd_dcc, 0, 0, 1,
+	 N_("\n"
+	 "DCC GET <nick>                      - accept an offered file\n"
+	 "DCC SEND [-maxcps=#] <nick> [file]  - send a file to someone\n"
+	 "DCC PSEND [-maxcps=#] <nick> [file] - send a file using passive mode\n"
+	 "DCC LIST                            - show DCC list\n"
+	 "DCC CHAT <nick>                     - offer DCC CHAT to someone\n"
+	 "DCC PCHAT <nick>                    - offer DCC CHAT using passive mode\n"
+	 "DCC CLOSE <type> <nick> <file>         example:\n"
+	 "         /dcc close send johnsmith file.tar.gz")},
+	{"DEBUG", cmd_debug, 0, 0, 1, 0},
+
+	{"DEHOP", cmd_dehop, 1, 1, 1,
+	 N_("DEHOP <nick>, removes chanhalf-op status from the nick on the current channel (needs chanop)")},
+	{"DELBUTTON", cmd_delbutton, 0, 0, 1,
+	 N_("DELBUTTON <name>, deletes a button from under the user-list")},
+	{"DEOP", cmd_deop, 1, 1, 1,
+	 N_("DEOP <nick>, removes chanop status from the nick on the current channel (needs chanop)")},
+	{"DEVOICE", cmd_devoice, 1, 1, 1,
+	 N_("DEVOICE <nick>, removes voice status from the nick on the current channel (needs chanop)")},
+	{"DISCON", cmd_discon, 0, 0, 1, N_("DISCON, Disconnects from server")},
+	{"DNS", cmd_dns, 0, 0, 1, N_("DNS <nick|host|ip>, Finds a users IP number")},
+	{"ECHO", cmd_echo, 0, 0, 1, N_("ECHO <text>, Prints text locally")},
+#ifndef WIN32
+	{"EXEC", cmd_exec, 0, 0, 1,
+	 N_("EXEC [-o] <command>, runs the command. If -o flag is used then output is sent to current channel, else is printed to current text box")},
+#ifndef __EMX__
+	{"EXECCONT", cmd_execc, 0, 0, 1, N_("EXECCONT, sends the process SIGCONT")},
+#endif
+	{"EXECKILL", cmd_execk, 0, 0, 1,
+	 N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")},
+#ifndef __EMX__
+	{"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")},
+	{"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")},
+#endif
+#endif
+	{"FLUSHQ", cmd_flushq, 0, 0, 1,
+	 N_("FLUSHQ, flushes the current server's send queue")},
+	{"GATE", cmd_gate, 0, 0, 1,
+	 N_("GATE <host> [<port>], proxies through a host, port defaults to 23")},
+	{"GETFILE", cmd_getfile, 0, 0, 1, "GETFILE [-folder] [-multi] [-save] <command> <title> [<initial>]"},
+	{"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"},
+	{"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"},
+	{"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")},
+	{"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n"
+									  "       GUI [MSGBOX <text>|MENU TOGGLE]"},
+	{"HELP", cmd_help, 0, 0, 1, 0},
+	{"HOP", cmd_hop, 1, 1, 1,
+	 N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")},
+	{"ID", cmd_id, 1, 0, 1, N_("ID <password>, identifies yourself to nickserv")},
+	{"IGNORE", cmd_ignore, 0, 0, 1,
+	 N_("IGNORE <mask> <types..> <options..>\n"
+	 "    mask - host mask to ignore, eg: *!*@*.aol.com\n"
+	 "    types - types of data to ignore, one or all of:\n"
+	 "            PRIV, CHAN, NOTI, CTCP, DCC, INVI, ALL\n"
+	 "    options - NOSAVE, QUIET")},
+
+	{"INVITE", cmd_invite, 1, 0, 1,
+	 N_("INVITE <nick> [<channel>], invites someone to a channel, by default the current channel (needs chanop)")},
+	{"JOIN", cmd_join, 1, 0, 0, N_("JOIN <channel>, joins the channel")},
+	{"KICK", cmd_kick, 1, 1, 1,
+	 N_("KICK <nick>, kicks the nick from the current channel (needs chanop)")},
+	{"KICKBAN", cmd_kickban, 1, 1, 1,
+	 N_("KICKBAN <nick>, bans then kicks the nick from the current channel (needs chanop)")},
+	{"KILLALL", cmd_killall, 0, 0, 1, "KILLALL, immediately exit"},
+	{"LAGCHECK", cmd_lagcheck, 0, 0, 1,
+	 N_("LAGCHECK, forces a new lag check")},
+	{"LASTLOG", cmd_lastlog, 0, 0, 1,
+	 N_("LASTLOG <string>, searches for a string in the buffer")},
+	{"LIST", cmd_list, 1, 0, 1, 0},
+	{"LOAD", cmd_load, 0, 0, 1, N_("LOAD [-e] <file>, loads a plugin or script")},
+
+	{"MDEHOP", cmd_mdehop, 1, 1, 1,
+	 N_("MDEHOP, Mass deop's all chanhalf-ops in the current channel (needs chanop)")},
+	{"MDEOP", cmd_mdeop, 1, 1, 1,
+	 N_("MDEOP, Mass deop's all chanops in the current channel (needs chanop)")},
+	{"ME", cmd_me, 0, 0, 1,
+	 N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")},
+	{"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n"
+										 "       See http://xchat.org/docs/menu/ for more details."},
+	{"MKICK", cmd_mkick, 1, 1, 1,
+	 N_("MKICK, Mass kicks everyone except you in the current channel (needs chanop)")},
+	{"MODE", cmd_mode, 1, 0, 1, 0},
+	{"MOP", cmd_mop, 1, 1, 1,
+	 N_("MOP, Mass op's all users in the current channel (needs chanop)")},
+	{"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")},
+
+	{"NAMES", cmd_names, 1, 0, 1,
+	 N_("NAMES, Lists the nicks on the current channel")},
+	{"NCTCP", cmd_nctcp, 1, 0, 1,
+	 N_("NCTCP <nick> <message>, Sends a CTCP notice")},
+	{"NEWSERVER", cmd_newserver, 0, 0, 1, N_("NEWSERVER [-noconnect] <hostname> [<port>]")},
+	{"NICK", cmd_nick, 0, 0, 1, N_("NICK <nickname>, sets your nick")},
+
+	{"NOTICE", cmd_notice, 1, 0, 1,
+	 N_("NOTICE <nick/channel> <message>, sends a notice. Notices are a type of message that should be auto reacted to")},
+	{"NOTIFY", cmd_notify, 0, 0, 1,
+	 N_("NOTIFY [-n network1[,network2,...]] [<nick>], displays your notify list or adds someone to it")},
+	{"OP", cmd_op, 1, 1, 1,
+	 N_("OP <nick>, gives chanop status to the nick (needs chanop)")},
+	{"PART", cmd_part, 1, 1, 0,
+	 N_("PART [<channel>] [<reason>], leaves the channel, by default the current one")},
+	{"PING", cmd_ping, 1, 0, 1,
+	 N_("PING <nick | channel>, CTCP pings nick or channel")},
+	{"QUERY", cmd_query, 0, 0, 1,
+	 N_("QUERY [-nofocus] <nick>, opens up a new privmsg window to someone")},
+	{"QUIT", cmd_quit, 0, 0, 1,
+	 N_("QUIT [<reason>], disconnects from the current server")},
+	{"QUOTE", cmd_quote, 1, 0, 1,
+	 N_("QUOTE <text>, sends the text in raw form to the server")},
+#ifdef USE_OPENSSL
+	{"RECONNECT", cmd_reconnect, 0, 0, 1,
+	 N_("RECONNECT [-ssl] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
+#else
+	{"RECONNECT", cmd_reconnect, 0, 0, 1,
+	 N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
+#endif
+	{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to xchat, as if it was received from the irc server")},
+
+	{"SAY", cmd_say, 0, 0, 1,
+	 N_("SAY <text>, sends the text to the object in the current window")},
+	{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
+#ifdef USE_OPENSSL
+	{"SERVCHAN", cmd_servchan, 0, 0, 1,
+	 N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")},
+#else
+	{"SERVCHAN", cmd_servchan, 0, 0, 1,
+	 N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")},
+#endif
+#ifdef USE_OPENSSL
+	{"SERVER", cmd_server, 0, 0, 1,
+	 N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 9999 for ssl connections")},
+#else
+	{"SERVER", cmd_server, 0, 0, 1,
+	 N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")},
+#endif
+	{"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")},
+	{"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>")},
+	{"SETTAB", cmd_settab, 0, 0, 1, 0},
+	{"SETTEXT", cmd_settext, 0, 0, 1, 0},
+	{"SPLAY", cmd_splay, 0, 0, 1, "SPLAY <soundfile>"},
+	{"TOPIC", cmd_topic, 1, 1, 1,
+	 N_("TOPIC [<topic>], sets the topic if one is given, else shows the current topic")},
+	{"TRAY", cmd_tray, 0, 0, 1,
+	 N_("\nTRAY -f <timeout> <file1> [<file2>] Blink tray between two icons.\n"
+		   "TRAY -f <filename>                  Set tray to a fixed icon.\n"
+			"TRAY -i <number>                    Blink tray with an internal icon.\n"
+			"TRAY -t <text>                      Set the tray tooltip.\n"
+			"TRAY -b <title> <text>              Set the tray balloon."
+			)},
+	{"UNBAN", cmd_unban, 1, 1, 1,
+	 N_("UNBAN <mask> [<mask>...], unbans the specified masks.")},
+	{"UNIGNORE", cmd_unignore, 0, 0, 1, N_("UNIGNORE <mask> [QUIET]")},
+	{"UNLOAD", cmd_unload, 0, 0, 1, N_("UNLOAD <name>, unloads a plugin or script")},
+	{"URL", cmd_url, 0, 0, 1, N_("URL <url>, opens a URL in your browser")},
+	{"USELECT", cmd_uselect, 0, 1, 0,
+	 N_("USELECT [-a] [-s] <nick1> <nick2> etc, highlights nick(s) in channel userlist")},
+	{"USERLIST", cmd_userlist, 1, 1, 1, 0},
+	{"VOICE", cmd_voice, 1, 1, 1,
+	 N_("VOICE <nick>, gives voice status to someone (needs chanop)")},
+	{"WALLCHAN", cmd_wallchan, 1, 1, 1,
+	 N_("WALLCHAN <message>, writes the message to all channels")},
+	{"WALLCHOP", cmd_wallchop, 1, 1, 1,
+	 N_("WALLCHOP <message>, sends the message to all chanops on the current channel")},
+	{0, 0, 0, 0, 0, 0}
+};
+
+
+static int
+command_compare (const void *a, const void *b)
+{
+	return strcasecmp (a, ((struct commands *)b)->name);
+}
+
+static struct commands *
+find_internal_command (char *name)
+{
+	/* the "-1" is to skip the NULL terminator */
+	return bsearch (name, xc_cmds, (sizeof (xc_cmds) /
+				sizeof (xc_cmds[0])) - 1, sizeof (xc_cmds[0]), command_compare);
+}
+
+static void
+help (session *sess, char *tbuf, char *helpcmd, int quiet)
+{
+	struct commands *cmd;
+
+	if (plugin_show_help (sess, helpcmd))
+		return;
+
+	cmd = find_internal_command (helpcmd);
+
+	if (cmd)
+	{
+		if (cmd->help)
+		{
+			snprintf (tbuf, TBUFSIZE, _("Usage: %s\n"), _(cmd->help));
+			PrintText (sess, tbuf);
+		} else
+		{
+			if (!quiet)
+				PrintText (sess, _("\nNo help available on that command.\n"));
+		}
+		return;
+	}
+
+	if (!quiet)
+		PrintText (sess, _("No such command.\n"));
+}
+
+/* inserts %a, %c, %d etc into buffer. Also handles &x %x for word/word_eol. *
+ *   returns 2 on buffer overflow
+ *   returns 1 on success                                                    *
+ *   returns 0 on bad-args-for-user-command                                  *
+ * - word/word_eol args might be NULL                                        *
+ * - this beast is used for UserCommands, UserlistButtons and CTCP replies   */
+
+int
+auto_insert (char *dest, int destlen, unsigned char *src, char *word[],
+				 char *word_eol[], char *a, char *c, char *d, char *e, char *h,
+				 char *n, char *s)
+{
+	int num;
+	char buf[32];
+	time_t now;
+	struct tm *tm_ptr;
+	char *utf;
+	gsize utf_len;
+	char *orig = dest;
+
+	destlen--;
+
+	while (src[0])
+	{
+		if (src[0] == '%' || src[0] == '&')
+		{
+			if (isdigit ((unsigned char) src[1]))
+			{
+				if (isdigit ((unsigned char) src[2]) && isdigit ((unsigned char) src[3]))
+				{
+					buf[0] = src[1];
+					buf[1] = src[2];
+					buf[2] = src[3];
+					buf[3] = 0;
+					dest[0] = atoi (buf);
+					utf = g_locale_to_utf8 (dest, 1, 0, &utf_len, 0);
+					if (utf)
+					{
+						if ((dest - orig) + utf_len >= destlen)
+						{
+							g_free (utf);
+							return 2;
+						}
+
+						memcpy (dest, utf, utf_len);
+						g_free (utf);
+						dest += utf_len;
+					}
+					src += 3;
+				} else
+				{
+					if (word)
+					{
+						src++;
+						num = src[0] - '0';	/* ascii to decimal */
+						if (*word[num] == 0)
+							return 0;
+
+						if (src[-1] == '%')
+							utf = word[num];
+						else
+							utf = word_eol[num];
+
+						/* avoid recusive usercommand overflow */
+						if ((dest - orig) + strlen (utf) >= destlen)
+							return 2;
+
+						strcpy (dest, utf);
+						dest += strlen (dest);
+					}
+				}
+			} else
+			{
+				if (src[0] == '&')
+					goto lamecode;
+				src++;
+				utf = NULL;
+				switch (src[0])
+				{
+				case '%':
+					if ((dest - orig) + 2 >= destlen)
+						return 2;
+					dest[0] = '%';
+					dest[1] = 0;
+					dest++;
+					break;
+				case 'a':
+					utf = a; break;
+				case 'c':
+					utf = c; break;
+				case 'd':
+					utf = d; break;
+				case 'e':
+					utf = e; break;
+				case 'h':
+					utf = h; break;
+				case 'm':
+					utf = get_cpu_str (); break;
+				case 'n':
+					utf = n; break;
+				case 's':
+					utf = s; break;
+				case 't':
+					now = time (0);
+					utf = ctime (&now);
+					utf[19] = 0;
+					break;
+				case 'v':
+					utf = PACKAGE_VERSION; break;
+					break;
+				case 'y':
+					now = time (0);
+					tm_ptr = localtime (&now);
+					snprintf (buf, sizeof (buf), "%4d%02d%02d", 1900 +
+								 tm_ptr->tm_year, 1 + tm_ptr->tm_mon, tm_ptr->tm_mday);
+					utf = buf;
+					break;
+				default:
+					src--;
+					goto lamecode;
+				}
+
+				if (utf)
+				{
+					if ((dest - orig) + strlen (utf) >= destlen)
+						return 2;
+					strcpy (dest, utf);
+					dest += strlen (dest);
+				}
+
+			}
+			src++;
+		} else
+		{
+			utf_len = g_utf8_skip[src[0]];
+
+			if ((dest - orig) + utf_len >= destlen)
+				return 2;
+
+			if (utf_len == 1)
+			{
+		 lamecode:
+				dest[0] = src[0];
+				dest++;
+				src++;
+			} else
+			{
+				memcpy (dest, src, utf_len);
+				dest += utf_len;
+				src += utf_len;
+			}
+		}
+	}
+
+	dest[0] = 0;
+
+	return 1;
+}
+
+void
+check_special_chars (char *cmd, int do_ascii) /* check for %X */
+{
+	int occur = 0;
+	int len = strlen (cmd);
+	char *buf, *utf;
+	char tbuf[4];
+	int i = 0, j = 0;
+	gsize utf_len;
+
+	if (!len)
+		return;
+
+	buf = malloc (len + 1);
+
+	if (buf)
+	{
+		while (cmd[j])
+		{
+			switch (cmd[j])
+			{
+			case '%':
+				occur++;
+				if (	do_ascii &&
+						j + 3 < len &&
+						(isdigit ((unsigned char) cmd[j + 1]) && isdigit ((unsigned char) cmd[j + 2]) &&
+						isdigit ((unsigned char) cmd[j + 3])))
+				{
+					tbuf[0] = cmd[j + 1];
+					tbuf[1] = cmd[j + 2];
+					tbuf[2] = cmd[j + 3];
+					tbuf[3] = 0;
+					buf[i] = atoi (tbuf);
+					utf = g_locale_to_utf8 (buf + i, 1, 0, &utf_len, 0);
+					if (utf)
+					{
+						memcpy (buf + i, utf, utf_len);
+						g_free (utf);
+						i += (utf_len - 1);
+					}
+					j += 3;
+				} else
+				{
+					switch (cmd[j + 1])
+					{
+					case 'R':
+						buf[i] = '\026';
+						break;
+					case 'U':
+						buf[i] = '\037';
+						break;
+					case 'B':
+						buf[i] = '\002';
+						break;
+					case 'C':
+						buf[i] = '\003';
+						break;
+					case 'O':
+						buf[i] = '\017';
+						break;
+					case 'H':	/* CL: invisible text code */
+						buf[i] = HIDDEN_CHAR;
+						break;
+					case '%':
+						buf[i] = '%';
+						break;
+					default:
+						buf[i] = '%';
+						j--;
+						break;
+					}
+					j++;
+					break;
+			default:
+					buf[i] = cmd[j];
+				}
+			}
+			j++;
+			i++;
+		}
+		buf[i] = 0;
+		if (occur)
+			strcpy (cmd, buf);
+		free (buf);
+	}
+}
+
+typedef struct
+{
+	char *nick;
+	int len;
+	struct User *best;
+	int bestlen;
+	char *space;
+	char *tbuf;
+} nickdata;
+
+static int
+nick_comp_cb (struct User *user, nickdata *data)
+{
+	int lenu;
+
+	if (!rfc_ncasecmp (user->nick, data->nick, data->len))
+	{
+		lenu = strlen (user->nick);
+		if (lenu == data->len)
+		{
+			snprintf (data->tbuf, TBUFSIZE, "%s%s", user->nick, data->space);
+			data->len = -1;
+			return FALSE;
+		} else if (lenu < data->bestlen)
+		{
+			data->bestlen = lenu;
+			data->best = user;
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+perform_nick_completion (struct session *sess, char *cmd, char *tbuf)
+{
+	int len;
+	char *space = strchr (cmd, ' ');
+	if (space && space != cmd)
+	{
+		if (space[-1] == prefs.nick_suffix[0] && space - 1 != cmd)
+		{
+			len = space - cmd - 1;
+			if (len < NICKLEN)
+			{
+				char nick[NICKLEN];
+				nickdata data;
+
+				memcpy (nick, cmd, len);
+				nick[len] = 0;
+
+				data.nick = nick;
+				data.len = len;
+				data.bestlen = INT_MAX;
+				data.best = NULL;
+				data.tbuf = tbuf;
+				data.space = space - 1;
+				tree_foreach (sess->usertree, (tree_traverse_func *)nick_comp_cb, &data);
+
+				if (data.len == -1)
+					return;
+
+				if (data.best)
+				{
+					snprintf (tbuf, TBUFSIZE, "%s%s", data.best->nick, space - 1);
+					return;
+				}
+			}
+		}
+	}
+
+	strcpy (tbuf, cmd);
+}
+
+static void
+user_command (session * sess, char *tbuf, char *cmd, char *word[],
+				  char *word_eol[])
+{
+	if (!auto_insert (tbuf, 2048, cmd, word, word_eol, "", sess->channel, "",
+							server_get_network (sess->server, TRUE), "",
+							sess->server->nick, ""))
+	{
+		PrintText (sess, _("Bad arguments for user command.\n"));
+		return;
+	}
+
+	handle_command (sess, tbuf, TRUE);
+}
+
+/* handle text entered without a CMDchar prefix */
+
+static void
+handle_say (session *sess, char *text, int check_spch)
+{
+	struct DCC *dcc;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	char pdibuf_static[1024];
+	char newcmd_static[1024];
+	char *pdibuf = pdibuf_static;
+	char *newcmd = newcmd_static;
+	int len;
+	int newcmdlen = sizeof newcmd_static;
+
+	if (strcmp (sess->channel, "(lastlog)") == 0)
+	{
+		lastlog (sess->lastlog_sess, text, sess->lastlog_regexp);
+		return;
+	}
+
+	len = strlen (text);
+	if (len >= sizeof pdibuf_static)
+		pdibuf = malloc (len + 1);
+
+	if (len + NICKLEN >= newcmdlen)
+		newcmd = malloc (newcmdlen = len + NICKLEN + 1);
+
+	if (check_spch && prefs.perc_color)
+		check_special_chars (text, prefs.perc_ascii);
+
+	/* Python relies on this */
+	word[PDIWORDS] = NULL;
+	word_eol[PDIWORDS] = NULL;
+
+	/* split the text into words and word_eol */
+	process_data_init (pdibuf, text, word, word_eol, TRUE, FALSE);
+
+	/* a command of "" can be hooked for non-commands */
+	if (plugin_emit_command (sess, "", word, word_eol))
+		goto xit;
+
+	/* incase a plugin did /close */
+	if (!is_session (sess))
+		goto xit;
+
+	if (!sess->channel[0] || sess->type == SESS_SERVER || sess->type == SESS_NOTICES || sess->type == SESS_SNOTICES)
+	{
+		notj_msg (sess);
+		goto xit;
+	}
+
+	if (prefs.nickcompletion)
+		perform_nick_completion (sess, text, newcmd);
+	else
+		safe_strcpy (newcmd, text, newcmdlen);
+
+	text = newcmd;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* try it via dcc, if possible */
+		dcc = dcc_write_chat (sess->channel, text);
+		if (dcc)
+		{
+			inbound_chanmsg (sess->server, NULL, sess->channel,
+								  sess->server->nick, text, TRUE, FALSE);
+			set_topic (sess, net_ip (dcc->addr), net_ip (dcc->addr));
+			goto xit;
+		}
+	}
+
+	if (sess->server->connected)
+	{
+		unsigned int max;
+		unsigned char t = 0;
+
+		/* maximum allowed message text */
+		/* :nickname!username@host.com PRIVMSG #channel :text\r\n */
+		max = 512;
+		max -= 16;	/* :, !, @, " PRIVMSG ", " ", :, \r, \n */
+		max -= strlen (sess->server->nick);
+		max -= strlen (sess->channel);
+		if (sess->me && sess->me->hostname)
+			max -= strlen (sess->me->hostname);
+		else
+		{
+			max -= 9;	/* username */
+			max -= 65;	/* max possible hostname and '@' */
+		}
+
+		if (strlen (text) > max)
+		{
+			int i = 0, size;
+
+			/* traverse the utf8 string and find the nearest cut point that
+				doesn't split 1 char in half */
+			while (1)
+			{
+				size = g_utf8_skip[((unsigned char *)text)[i]];
+				if ((i + size) >= max)
+					break;
+				i += size;
+			}
+			max = i;
+			t = text[max];
+			text[max] = 0;			  /* insert a NULL terminator to shorten it */
+		}
+
+		inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
+							  text, TRUE, FALSE);
+		sess->server->p_message (sess->server, sess->channel, text);
+
+		if (t)
+		{
+			text[max] = t;
+			handle_say (sess, text + max, FALSE);
+		}
+
+	} else
+	{
+		notc_msg (sess);
+	}
+
+xit:
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+
+	if (newcmd != newcmd_static)
+		free (newcmd);
+}
+
+/* handle a command, without the '/' prefix */
+
+int
+handle_command (session *sess, char *cmd, int check_spch)
+{
+	struct popup *pop;
+	int user_cmd = FALSE;
+	GSList *list;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	static int command_level = 0;
+	struct commands *int_cmd;
+	char pdibuf_static[1024];
+	char tbuf_static[TBUFSIZE];
+	char *pdibuf;
+	char *tbuf;
+	int len;
+	int ret = TRUE;
+
+	if (command_level > 99)
+	{
+		fe_message (_("Too many recursive usercommands, aborting."), FE_MSG_ERROR);
+		return TRUE;
+	}
+	command_level++;
+	/* anything below MUST DEC command_level before returning */
+
+	len = strlen (cmd);
+	if (len >= sizeof (pdibuf_static))
+		pdibuf = malloc (len + 1);
+	else
+		pdibuf = pdibuf_static;
+
+	if ((len * 2) >= sizeof (tbuf_static))
+		tbuf = malloc ((len * 2) + 1);
+	else
+		tbuf = tbuf_static;
+
+	/* split the text into words and word_eol */
+	process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE);
+
+	/* ensure an empty string at index 32 for cmd_deop etc */
+	/* (internal use only, plugins can still only read 1-31). */
+	word[PDIWORDS] = "\000\000";
+	word_eol[PDIWORDS] = "\000\000";
+
+	int_cmd = find_internal_command (word[1]);
+	/* redo it without quotes processing, for some commands like /JOIN */
+	if (int_cmd && !int_cmd->handle_quotes)
+		process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE);
+
+	if (check_spch && prefs.perc_color)
+		check_special_chars (cmd, prefs.perc_ascii);
+
+	if (plugin_emit_command (sess, word[1], word, word_eol))
+		goto xit;
+
+	/* incase a plugin did /close */
+	if (!is_session (sess))
+		goto xit;
+
+	/* first see if it's a userCommand */
+	list = command_list;
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (!strcasecmp (pop->name, word[1]))
+		{
+			user_command (sess, tbuf, pop->cmd, word, word_eol);
+			user_cmd = TRUE;
+		}
+		list = list->next;
+	}
+
+	if (user_cmd)
+		goto xit;
+
+	/* now check internal commands */
+	int_cmd = find_internal_command (word[1]);
+
+	if (int_cmd)
+	{
+		if (int_cmd->needserver && !sess->server->connected)
+		{
+			notc_msg (sess);
+		} else if (int_cmd->needchannel && !sess->channel[0])
+		{
+			notj_msg (sess);
+		} else
+		{
+			switch (int_cmd->callback (sess, tbuf, word, word_eol))
+			{
+			case FALSE:
+				help (sess, tbuf, int_cmd->name, TRUE);
+				break;
+			case 2:
+				ret = FALSE;
+				goto xit;
+			}
+		}
+	} else
+	{
+		/* unknown command, just send it to the server and hope */
+		if (!sess->server->connected)
+			PrintText (sess, _("Unknown Command. Try /help\n"));
+		else
+			sess->server->p_raw (sess->server, cmd);
+	}
+
+xit:
+	command_level--;
+
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+
+	if (tbuf != tbuf_static)
+		free (tbuf);
+
+	return ret;
+}
+
+/* handle one line entered into the input box */
+
+static int
+handle_user_input (session *sess, char *text, int history, int nocommand)
+{
+	if (*text == '\0')
+		return 1;
+
+	if (history)
+		history_add (&sess->history, text);
+
+	/* is it NOT a command, just text? */
+	if (nocommand || text[0] != prefs.cmdchar[0])
+	{
+		handle_say (sess, text, TRUE);
+		return 1;
+	}
+
+	/* check for // */
+	if (text[0] == prefs.cmdchar[0] && text[1] == prefs.cmdchar[0])
+	{
+		handle_say (sess, text + 1, TRUE);
+		return 1;
+	}
+
+	if (prefs.cmdchar[0] == '/')
+	{
+		int i;
+		const char *unix_dirs [] = {
+			"/bin/", "/boot/", "/dev/",
+			"/etc/", "/home/", "/lib/",
+			"/lost+found/", "/mnt/", "/opt/",
+			"/proc/", "/root/", "/sbin/",
+			"/tmp/", "/usr/", "/var/",
+			"/gnome/", NULL};
+		for (i = 0; unix_dirs[i] != NULL; i++)
+			if (strncmp (text, unix_dirs[i], strlen (unix_dirs[i]))==0)
+			{
+				handle_say (sess, text, TRUE);
+				return 1;
+			}
+	}
+
+	return handle_command (sess, text + 1, TRUE);
+}
+
+/* changed by Steve Green. Macs sometimes paste with imbedded \r */
+void
+handle_multiline (session *sess, char *cmd, int history, int nocommand)
+{
+	while (*cmd)
+	{
+		char *cr = cmd + strcspn (cmd, "\n\r");
+		int end_of_string = *cr == 0;
+		*cr = 0;
+		if (!handle_user_input (sess, cmd, history, nocommand))
+			return;
+		if (end_of_string)
+			break;
+		cmd = cr + 1;
+	}
+}
+
+/*void
+handle_multiline (session *sess, char *cmd, int history, int nocommand)
+{
+	char *cr;
+
+	cr = strchr (cmd, '\n');
+	if (cr)
+	{
+		while (1)
+		{
+			if (cr)
+				*cr = 0;
+			if (!handle_user_input (sess, cmd, history, nocommand))
+				return;
+			if (!cr)
+				break;
+			cmd = cr + 1;
+			if (*cmd == 0)
+				break;
+			cr = strchr (cmd, '\n');
+		}
+	} else
+	{
+		handle_user_input (sess, cmd, history, nocommand);
+	}
+}*/
diff --git a/src/common/outbound.h b/src/common/outbound.h
new file mode 100644
index 00000000..a2047845
--- /dev/null
+++ b/src/common/outbound.h
@@ -0,0 +1,20 @@
+#ifndef XCHAT_OUTBOUND_H
+#define XCHAT_OUTBOUND_H
+
+extern const struct commands xc_cmds[];
+extern GSList *menu_list;
+
+int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[],
+				 char *a, char *c, char *d, char *e, char *h, char *n, char *s);
+int handle_command (session *sess, char *cmd, int check_spch);
+void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes);
+void handle_multiline (session *sess, char *cmd, int history, int nocommand);
+void check_special_chars (char *cmd, int do_ascii);
+void notc_msg (session *sess);
+void server_sendpart (server * serv, char *channel, char *reason);
+void server_sendquit (session * sess);
+int menu_streq (const char *s1, const char *s2, int def);
+void open_query (server *serv, char *nick, gboolean focus_existing);
+gboolean load_perform_file (session *sess, char *file);
+
+#endif
diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c
new file mode 100644
index 00000000..f09074a8
--- /dev/null
+++ b/src/common/plugin-timer.c
@@ -0,0 +1,208 @@
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include "xchat-plugin.h"
+
+#ifdef WIN32
+#define strcasecmp stricmp
+#endif
+
+static xchat_plugin *ph;	/* plugin handle */
+static GSList *timer_list = NULL;
+
+#define STATIC
+#define HELP \
+"Usage: TIMER [-refnum <num>] [-repeat <num>] <seconds> <command>\n" \
+"       TIMER [-quiet] -delete <num>"
+
+typedef struct
+{
+	xchat_hook *hook;
+	xchat_context *context;
+	char *command;
+	int ref;
+	int repeat;
+	float timeout;
+	unsigned int forever:1;
+} timer;
+
+static void
+timer_del (timer *tim)
+{
+	timer_list = g_slist_remove (timer_list, tim);
+	free (tim->command);
+	xchat_unhook (ph, tim->hook);
+	free (tim);
+}
+
+static void
+timer_del_ref (int ref, int quiet)
+{
+	GSList *list;
+	timer *tim;
+
+	list = timer_list;
+	while (list)
+	{
+		tim = list->data;
+		if (tim->ref == ref)
+		{
+			timer_del (tim);
+			if (!quiet)
+				xchat_printf (ph, "Timer %d deleted.\n", ref);
+			return;
+		}
+		list = list->next;
+	}
+	if (!quiet)
+		xchat_print (ph, "No such ref number found.\n");
+}
+
+static int
+timeout_cb (timer *tim)
+{
+	if (xchat_set_context (ph, tim->context))
+	{
+		xchat_command (ph, tim->command);
+
+		if (tim->forever)
+			return 1;
+
+		tim->repeat--;
+		if (tim->repeat > 0)
+			return 1;
+	}
+
+	timer_del (tim);
+	return 0;
+}
+
+static void
+timer_add (int ref, float timeout, int repeat, char *command)
+{
+	timer *tim;
+	GSList *list;
+
+	if (ref == 0)
+	{
+		ref = 1;
+		list = timer_list;
+		while (list)
+		{
+			tim = list->data;
+			if (tim->ref >= ref)
+				ref = tim->ref + 1;
+			list = list->next;
+		}
+	}
+
+	tim = malloc (sizeof (timer));
+	tim->ref = ref;
+	tim->repeat = repeat;
+	tim->timeout = timeout;
+	tim->command = strdup (command);
+	tim->context = xchat_get_context (ph);
+	tim->forever = FALSE;
+
+	if (repeat == 0)
+		tim->forever = TRUE;
+
+	tim->hook = xchat_hook_timer (ph, timeout * 1000.0, (void *)timeout_cb, tim);
+	timer_list = g_slist_append (timer_list, tim);
+}
+
+static void
+timer_showlist (void)
+{
+	GSList *list;
+	timer *tim;
+
+	if (timer_list == NULL)
+	{
+		xchat_print (ph, "No timers installed.\n");
+		xchat_print (ph, HELP);
+		return;
+	}
+							 /*  00000 00000000 0000000 abc */
+	xchat_print (ph, "\026 Ref#  Seconds  Repeat  Command \026\n");
+	list = timer_list;
+	while (list)
+	{
+		tim = list->data;
+		xchat_printf (ph, "%5d %8.1f %7d  %s\n", tim->ref, tim->timeout,
+						  tim->repeat, tim->command);
+		list = list->next;
+	}
+}
+
+static int
+timer_cb (char *word[], char *word_eol[], void *userdata)
+{
+	int repeat = 1;
+	float timeout;
+	int offset = 0;
+	int ref = 0;
+	int quiet = FALSE;
+	char *command;
+
+	if (!word[2][0])
+	{
+		timer_showlist ();
+		return XCHAT_EAT_XCHAT;
+	}
+
+	if (strcasecmp (word[2], "-quiet") == 0)
+	{
+		quiet = TRUE;
+		offset++;
+	}
+
+	if (strcasecmp (word[2 + offset], "-delete") == 0)
+	{
+		timer_del_ref (atoi (word[3 + offset]), quiet);
+		return XCHAT_EAT_XCHAT;
+	}
+
+	if (strcasecmp (word[2 + offset], "-refnum") == 0)
+	{
+		ref = atoi (word[3 + offset]);
+		offset += 2;
+	}
+
+	if (strcasecmp (word[2 + offset], "-repeat") == 0)
+	{
+		repeat = atoi (word[3 + offset]);
+		offset += 2;
+	}
+
+	timeout = atof (word[2 + offset]);
+	command = word_eol[3 + offset];
+
+	if (timeout < 0.1 || !command[0])
+		xchat_print (ph, HELP);
+	else
+		timer_add (ref, timeout, repeat, command);
+
+	return XCHAT_EAT_XCHAT;
+}
+
+int
+#ifdef STATIC
+timer_plugin_init
+#else
+xchat_plugin_init
+#endif
+				(xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg)
+{
+	/* we need to save this for use with any xchat_* functions */
+	ph = plugin_handle;
+
+	*plugin_name = "Timer";
+	*plugin_desc = "IrcII style /TIMER command";
+	*plugin_version = "";
+
+	xchat_hook_command (ph, "TIMER", XCHAT_PRI_NORM, timer_cb, HELP, 0);
+
+	return 1;       /* return 1 for success */
+}
diff --git a/src/common/plugin-timer.h b/src/common/plugin-timer.h
new file mode 100644
index 00000000..e3530c8d
--- /dev/null
+++ b/src/common/plugin-timer.h
@@ -0,0 +1,7 @@
+#ifndef XCHAT_PLUGIN_TIMER_H
+#define XCHAT_PLUGIN_TIMER_H
+
+int timer_plugin_init (xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg);
+
+#endif
diff --git a/src/common/plugin.c b/src/common/plugin.c
new file mode 100644
index 00000000..ada4d3be
--- /dev/null
+++ b/src/common/plugin.c
@@ -0,0 +1,1569 @@
+/* X-Chat
+ * Copyright (C) 2002 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "xchat.h"
+#include "fe.h"
+#include "util.h"
+#include "outbound.h"
+#include "cfgfiles.h"
+#include "ignore.h"
+#include "server.h"
+#include "servlist.h"
+#include "modes.h"
+#include "notify.h"
+#include "text.h"
+#define PLUGIN_C
+typedef struct session xchat_context;
+#include "xchat-plugin.h"
+#include "plugin.h"
+
+
+#include "xchatc.h"
+
+/* the USE_PLUGIN define only removes libdl stuff */
+
+#ifdef USE_PLUGIN
+#ifdef USE_GMODULE
+#include <gmodule.h>
+#else
+#include <dlfcn.h>
+#endif
+#endif
+
+#define DEBUG(x) {x;}
+
+/* crafted to be an even 32 bytes */
+struct _xchat_hook
+{
+	xchat_plugin *pl;	/* the plugin to which it belongs */
+	char *name;			/* "xdcc" */
+	void *callback;	/* pointer to xdcc_callback */
+	char *help_text;	/* help_text for commands only */
+	void *userdata;	/* passed to the callback */
+	int tag;				/* for timers & FDs only */
+	int type;			/* HOOK_* */
+	int pri;	/* fd */	/* priority / fd for HOOK_FD only */
+};
+
+struct _xchat_list
+{
+	int type;			/* LIST_* */
+	GSList *pos;		/* current pos */
+	GSList *next;		/* next pos */
+	GSList *head;		/* for LIST_USERS only */
+	struct notify_per_server *notifyps;	/* notify_per_server * */
+};
+
+typedef int (xchat_cmd_cb) (char *word[], char *word_eol[], void *user_data);
+typedef int (xchat_serv_cb) (char *word[], char *word_eol[], void *user_data);
+typedef int (xchat_print_cb) (char *word[], void *user_data);
+typedef int (xchat_fd_cb) (int fd, int flags, void *user_data);
+typedef int (xchat_timer_cb) (void *user_data);
+typedef int (xchat_init_func) (xchat_plugin *, char **, char **, char **, char *);
+typedef int (xchat_deinit_func) (xchat_plugin *);
+
+enum
+{
+	LIST_CHANNELS,
+	LIST_DCC,
+	LIST_IGNORE,
+	LIST_NOTIFY,
+	LIST_USERS
+};
+
+enum
+{
+	HOOK_COMMAND,	/* /command */
+	HOOK_SERVER,	/* PRIVMSG, NOTICE, numerics */
+	HOOK_PRINT,		/* All print events */
+	HOOK_TIMER,		/* timeouts */
+	HOOK_FD,			/* sockets & fds */
+	HOOK_DELETED	/* marked for deletion */
+};
+
+GSList *plugin_list = NULL;	/* export for plugingui.c */
+static GSList *hook_list = NULL;
+
+extern const struct prefs vars[];	/* cfgfiles.c */
+
+
+/* unload a plugin and remove it from our linked list */
+
+static int
+plugin_free (xchat_plugin *pl, int do_deinit, int allow_refuse)
+{
+	GSList *list, *next;
+	xchat_hook *hook;
+	xchat_deinit_func *deinit_func;
+
+	/* fake plugin added by xchat_plugingui_add() */
+	if (pl->fake)
+		goto xit;
+
+	/* run the plugin's deinit routine, if any */
+	if (do_deinit && pl->deinit_callback != NULL)
+	{
+		deinit_func = pl->deinit_callback;
+		if (!deinit_func (pl) && allow_refuse)
+			return FALSE;
+	}
+
+	/* remove all of this plugin's hooks */
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		next = list->next;
+		if (hook->pl == pl)
+			xchat_unhook (NULL, hook);
+		list = next;
+	}
+
+#ifdef USE_PLUGIN
+	if (pl->handle)
+#ifdef USE_GMODULE
+		g_module_close (pl->handle);
+#else
+		dlclose (pl->handle);
+#endif
+#endif
+
+xit:
+	if (pl->free_strings)
+	{
+		if (pl->name)
+			free (pl->name);
+		if (pl->desc)
+			free (pl->desc);
+		if (pl->version)
+			free (pl->version);
+	}
+	if (pl->filename)
+		free ((char *)pl->filename);
+	free (pl);
+
+	plugin_list = g_slist_remove (plugin_list, pl);
+
+#ifdef USE_PLUGIN
+	fe_pluginlist_update ();
+#endif
+
+	return TRUE;
+}
+
+static xchat_plugin *
+plugin_list_add (xchat_context *ctx, char *filename, const char *name,
+					  const char *desc, const char *version, void *handle,
+					  void *deinit_func, int fake, int free_strings)
+{
+	xchat_plugin *pl;
+
+	pl = malloc (sizeof (xchat_plugin));
+	pl->handle = handle;
+	pl->filename = filename;
+	pl->context = ctx;
+	pl->name = (char *)name;
+	pl->desc = (char *)desc;
+	pl->version = (char *)version;
+	pl->deinit_callback = deinit_func;
+	pl->fake = fake;
+	pl->free_strings = free_strings;	/* free() name,desc,version? */
+
+	plugin_list = g_slist_prepend (plugin_list, pl);
+
+	return pl;
+}
+
+static void *
+xchat_dummy (xchat_plugin *ph)
+{
+	return NULL;
+}
+
+#ifdef WIN32
+static int
+xchat_read_fd (xchat_plugin *ph, GIOChannel *source, char *buf, int *len)
+{
+	return g_io_channel_read (source, buf, *len, len);
+}
+#endif
+
+/* Load a static plugin */
+
+void
+plugin_add (session *sess, char *filename, void *handle, void *init_func,
+				void *deinit_func, char *arg, int fake)
+{
+	xchat_plugin *pl;
+	char *file;
+
+	file = NULL;
+	if (filename)
+		file = strdup (filename);
+
+	pl = plugin_list_add (sess, file, file, NULL, NULL, handle, deinit_func,
+								 fake, FALSE);
+
+	if (!fake)
+	{
+		/* win32 uses these because it doesn't have --export-dynamic! */
+		pl->xchat_hook_command = xchat_hook_command;
+		pl->xchat_hook_server = xchat_hook_server;
+		pl->xchat_hook_print = xchat_hook_print;
+		pl->xchat_hook_timer = xchat_hook_timer;
+		pl->xchat_hook_fd = xchat_hook_fd;
+		pl->xchat_unhook = xchat_unhook;
+		pl->xchat_print = xchat_print;
+		pl->xchat_printf = xchat_printf;
+		pl->xchat_command = xchat_command;
+		pl->xchat_commandf = xchat_commandf;
+		pl->xchat_nickcmp = xchat_nickcmp;
+		pl->xchat_set_context = xchat_set_context;
+		pl->xchat_find_context = xchat_find_context;
+		pl->xchat_get_context = xchat_get_context;
+		pl->xchat_get_info = xchat_get_info;
+		pl->xchat_get_prefs = xchat_get_prefs;
+		pl->xchat_list_get = xchat_list_get;
+		pl->xchat_list_free = xchat_list_free;
+		pl->xchat_list_fields = xchat_list_fields;
+		pl->xchat_list_str = xchat_list_str;
+		pl->xchat_list_next = xchat_list_next;
+		pl->xchat_list_int = xchat_list_int;
+		pl->xchat_plugingui_add = xchat_plugingui_add;
+		pl->xchat_plugingui_remove = xchat_plugingui_remove;
+		pl->xchat_emit_print = xchat_emit_print;
+#ifdef WIN32
+		pl->xchat_read_fd = (void *) xchat_read_fd;
+#else
+		pl->xchat_read_fd = xchat_dummy;
+#endif
+		pl->xchat_list_time = xchat_list_time;
+		pl->xchat_gettext = xchat_gettext;
+		pl->xchat_send_modes = xchat_send_modes;
+		pl->xchat_strip = xchat_strip;
+		pl->xchat_free = xchat_free;
+
+		/* incase new plugins are loaded on older xchat */
+		pl->xchat_dummy4 = xchat_dummy;
+		pl->xchat_dummy3 = xchat_dummy;
+		pl->xchat_dummy2 = xchat_dummy;
+		pl->xchat_dummy1 = xchat_dummy;
+
+		/* run xchat_plugin_init, if it returns 0, close the plugin */
+		if (((xchat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
+		{
+			plugin_free (pl, FALSE, FALSE);
+			return;
+		}
+	}
+
+#ifdef USE_PLUGIN
+	fe_pluginlist_update ();
+#endif
+}
+
+/* kill any plugin by the given (file) name (used by /unload) */
+
+int
+plugin_kill (char *name, int by_filename)
+{
+	GSList *list;
+	xchat_plugin *pl;
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		/* static-plugins (plugin-timer.c) have a NULL filename */
+		if ((by_filename && pl->filename && strcasecmp (name, pl->filename) == 0) ||
+			 (by_filename && pl->filename && strcasecmp (name, file_part (pl->filename)) == 0) ||
+			(!by_filename && strcasecmp (name, pl->name) == 0))
+		{
+			/* statically linked plugins have a NULL filename */
+			if (pl->filename != NULL && !pl->fake)
+			{
+				if (plugin_free (pl, TRUE, TRUE))
+					return 1;
+				return 2;
+			}
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
+/* kill all running plugins (at shutdown) */
+
+void
+plugin_kill_all (void)
+{
+	GSList *list, *next;
+	xchat_plugin *pl;
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		next = list->next;
+		if (!pl->fake)
+			plugin_free (list->data, TRUE, FALSE);
+		list = next;
+	}
+}
+
+#ifdef USE_PLUGIN
+
+/* load a plugin from a filename. Returns: NULL-success or an error string */
+
+char *
+plugin_load (session *sess, char *filename, char *arg)
+{
+	void *handle;
+	xchat_init_func *init_func;
+	xchat_deinit_func *deinit_func;
+
+#ifdef USE_GMODULE
+	/* load the plugin */
+	handle = g_module_open (filename, 0);
+	if (handle == NULL)
+		return (char *)g_module_error ();
+
+	/* find the init routine xchat_plugin_init */
+	if (!g_module_symbol (handle, "xchat_plugin_init", (gpointer *)&init_func))
+	{
+		g_module_close (handle);
+		return _("No xchat_plugin_init symbol; is this really an xchat plugin?");
+	}
+
+	/* find the plugin's deinit routine, if any */
+	if (!g_module_symbol (handle, "xchat_plugin_deinit", (gpointer *)&deinit_func))
+		deinit_func = NULL;
+
+#else
+	char *error;
+	char *filepart;
+
+/* OpenBSD lacks this! */
+#ifndef RTLD_GLOBAL
+#define RTLD_GLOBAL 0
+#endif
+
+#ifndef RTLD_NOW
+#define RTLD_NOW 0
+#endif
+
+	/* get the filename without path */
+	filepart = file_part (filename);
+
+	/* load the plugin */
+	if (filepart &&
+		 /* xsys draws in libgtk-1.2, causing crashes, so force RTLD_LOCAL */
+		 (strstr (filepart, "local") || strncmp (filepart, "libxsys-1", 9) == 0)
+		)
+		handle = dlopen (filename, RTLD_NOW);
+	else
+		handle = dlopen (filename, RTLD_GLOBAL | RTLD_NOW);
+	if (handle == NULL)
+		return (char *)dlerror ();
+	dlerror ();		/* Clear any existing error */
+
+	/* find the init routine xchat_plugin_init */
+	init_func = dlsym (handle, "xchat_plugin_init");
+	error = (char *)dlerror ();
+	if (error != NULL)
+	{
+		dlclose (handle);
+		return _("No xchat_plugin_init symbol; is this really an xchat plugin?");
+	}
+
+	/* find the plugin's deinit routine, if any */
+	deinit_func = dlsym (handle, "xchat_plugin_deinit");
+	error = (char *)dlerror ();
+#endif
+
+	/* add it to our linked list */
+	plugin_add (sess, filename, handle, init_func, deinit_func, arg, FALSE);
+
+	return NULL;
+}
+
+static session *ps;
+
+static void
+plugin_auto_load_cb (char *filename)
+{
+	char *pMsg;
+
+#ifndef WIN32	/* black listed */
+	if (!strcmp (file_part (filename), "dbus.so"))
+		return;
+#endif
+
+	pMsg = plugin_load (ps, filename, NULL);
+	if (pMsg)
+	{
+		PrintTextf (ps, "AutoLoad failed for: %s\n", filename);
+		PrintText (ps, pMsg);
+	}
+}
+
+void
+plugin_auto_load (session *sess)
+{
+	ps = sess;
+#ifdef WIN32
+	for_files ("./plugins", "*.dll", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.dll", plugin_auto_load_cb);
+#else
+#if defined(__hpux)
+	for_files (XCHATLIBDIR"/plugins", "*.sl", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.sl", plugin_auto_load_cb);
+#else
+	for_files (XCHATLIBDIR"/plugins", "*.so", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.so", plugin_auto_load_cb);
+#endif
+#endif
+}
+
+#endif
+
+static GSList *
+plugin_hook_find (GSList *list, int type, char *name)
+{
+	xchat_hook *hook;
+
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == type)
+		{
+			if (strcasecmp (hook->name, name) == 0)
+				return list;
+
+			if (type == HOOK_SERVER)
+			{
+				if (strcasecmp (hook->name, "RAW LINE") == 0)
+					return list;
+			}
+		}
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+/* check for plugin hooks and run them */
+
+static int
+plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], int type)
+{
+	GSList *list, *next;
+	xchat_hook *hook;
+	int ret, eat = 0;
+
+	list = hook_list;
+	while (1)
+	{
+		list = plugin_hook_find (list, type, name);
+		if (!list)
+			goto xit;
+
+		hook = list->data;
+		next = list->next;
+		hook->pl->context = sess;
+
+		/* run the plugin's callback function */
+		switch (type)
+		{
+		case HOOK_COMMAND:
+			ret = ((xchat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata);
+			break;
+		case HOOK_SERVER:
+			ret = ((xchat_serv_cb *)hook->callback) (word, word_eol, hook->userdata);
+			break;
+		default: /*case HOOK_PRINT:*/
+			ret = ((xchat_print_cb *)hook->callback) (word, hook->userdata);
+			break;
+		}
+
+		if ((ret & XCHAT_EAT_XCHAT) && (ret & XCHAT_EAT_PLUGIN))
+		{
+			eat = 1;
+			goto xit;
+		}
+		if (ret & XCHAT_EAT_PLUGIN)
+			goto xit;	/* stop running plugins */
+		if (ret & XCHAT_EAT_XCHAT)
+			eat = 1;	/* eventually we'll return 1, but continue running plugins */
+
+		list = next;
+	}
+
+xit:
+	/* really remove deleted hooks now */
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		next = list->next;
+		if (hook->type == HOOK_DELETED)
+		{
+			hook_list = g_slist_remove (hook_list, hook);
+			free (hook);
+		}
+		list = next;
+	}
+
+	return eat;
+}
+
+/* execute a plugged in command. Called from outbound.c */
+
+int
+plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[])
+{
+	return plugin_hook_run (sess, name, word, word_eol, HOOK_COMMAND);
+}
+
+/* got a server PRIVMSG, NOTICE, numeric etc... */
+
+int
+plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[])
+{
+	return plugin_hook_run (sess, name, word, word_eol, HOOK_SERVER);
+}
+
+/* see if any plugins are interested in this print event */
+
+int
+plugin_emit_print (session *sess, char *word[])
+{
+	return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT);
+}
+
+int
+plugin_emit_dummy_print (session *sess, char *name)
+{
+	char *word[32];
+	int i;
+
+	word[0] = name;
+	for (i = 1; i < 32; i++)
+		word[i] = "\000";
+
+	return plugin_hook_run (sess, name, word, NULL, HOOK_PRINT);
+}
+
+int
+plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval,
+							 int len, char *string)
+{
+	char *word[PDIWORDS];
+	char keyval_str[16];
+	char state_str[16];
+	char len_str[16];
+	int i;
+
+	if (!hook_list)
+		return 0;
+
+	sprintf (keyval_str, "%u", keyval);
+	sprintf (state_str, "%u", state);
+	sprintf (len_str, "%d", len);
+
+	word[0] = "Key Press";
+	word[1] = keyval_str;
+	word[2] = state_str;
+	word[3] = string;
+	word[4] = len_str;
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT);
+}
+
+static int
+plugin_timeout_cb (xchat_hook *hook)
+{
+	int ret;
+
+	/* timer_cb's context starts as front-most-tab */
+	hook->pl->context = current_sess;
+
+	/* call the plugin's timeout function */
+	ret = ((xchat_timer_cb *)hook->callback) (hook->userdata);
+
+	/* the callback might have already unhooked it! */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return 0;
+
+	if (ret == 0)
+	{
+		hook->tag = 0;	/* avoid fe_timeout_remove, returning 0 is enough! */
+		xchat_unhook (hook->pl, hook);
+	}
+
+	return ret;
+}
+
+/* insert a hook into hook_list according to its priority */
+
+static void
+plugin_insert_hook (xchat_hook *new_hook)
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == new_hook->type && hook->pri <= new_hook->pri)
+		{
+			hook_list = g_slist_insert_before (hook_list, list, new_hook);
+			return;
+		}
+		list = list->next;
+	}
+
+	hook_list = g_slist_append (hook_list, new_hook);
+}
+
+static gboolean
+plugin_fd_cb (GIOChannel *source, GIOCondition condition, xchat_hook *hook)
+{
+	int flags = 0, ret;
+	typedef int (xchat_fd_cb2) (int fd, int flags, void *user_data, GIOChannel *);
+
+	if (condition & G_IO_IN)
+		flags |= XCHAT_FD_READ;
+	if (condition & G_IO_OUT)
+		flags |= XCHAT_FD_WRITE;
+	if (condition & G_IO_PRI)
+		flags |= XCHAT_FD_EXCEPTION;
+
+	ret = ((xchat_fd_cb2 *)hook->callback) (hook->pri, flags, hook->userdata, source);
+
+	/* the callback might have already unhooked it! */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return 0;
+
+	if (ret == 0)
+	{
+		hook->tag = 0; /* avoid fe_input_remove, returning 0 is enough! */
+		xchat_unhook (hook->pl, hook);
+	}
+
+	return ret;
+}
+
+/* allocate and add a hook to our list. Used for all 4 types */
+
+static xchat_hook *
+plugin_add_hook (xchat_plugin *pl, int type, int pri, const char *name,
+					  const  char *help_text, void *callb, int timeout, void *userdata)
+{
+	xchat_hook *hook;
+
+	hook = malloc (sizeof (xchat_hook));
+	memset (hook, 0, sizeof (xchat_hook));
+
+	hook->type = type;
+	hook->pri = pri;
+	if (name)
+		hook->name = strdup (name);
+	if (help_text)
+		hook->help_text = strdup (help_text);
+	hook->callback = callb;
+	hook->pl = pl;
+	hook->userdata = userdata;
+
+	/* insert it into the linked list */
+	plugin_insert_hook (hook);
+
+	if (type == HOOK_TIMER)
+		hook->tag = fe_timeout_add (timeout, plugin_timeout_cb, hook);
+
+	return hook;
+}
+
+GList *
+plugin_command_list(GList *tmp_list)
+{
+	xchat_hook *hook;
+	GSList *list = hook_list;
+
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == HOOK_COMMAND)
+			tmp_list = g_list_prepend(tmp_list, hook->name);
+		list = list->next;
+	}
+	return tmp_list;
+}
+
+void
+plugin_command_foreach (session *sess, void *userdata,
+			void (*cb) (session *sess, void *userdata, char *name, char *help))
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == HOOK_COMMAND && hook->name[0])
+		{
+			cb (sess, userdata, hook->name, hook->help_text);
+		}
+		list = list->next;
+	}
+}
+
+int
+plugin_show_help (session *sess, char *cmd)
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = plugin_hook_find (hook_list, HOOK_COMMAND, cmd);
+	if (list)
+	{
+		hook = list->data;
+		if (hook->help_text)
+		{
+			PrintText (sess, hook->help_text);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/* ========================================================= */
+/* ===== these are the functions plugins actually call ===== */
+/* ========================================================= */
+
+void *
+xchat_unhook (xchat_plugin *ph, xchat_hook *hook)
+{
+	/* perl.c trips this */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return NULL;
+
+	if (hook->type == HOOK_TIMER && hook->tag != 0)
+		fe_timeout_remove (hook->tag);
+
+	if (hook->type == HOOK_FD && hook->tag != 0)
+		fe_input_remove (hook->tag);
+
+	hook->type = HOOK_DELETED;	/* expunge later */
+
+	if (hook->name)
+		free (hook->name);	/* NULL for timers & fds */
+	if (hook->help_text)
+		free (hook->help_text);	/* NULL for non-commands */
+
+	return hook->userdata;
+}
+
+xchat_hook *
+xchat_hook_command (xchat_plugin *ph, const char *name, int pri,
+						  xchat_cmd_cb *callb, const char *help_text, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_COMMAND, pri, name, help_text, callb, 0,
+									userdata);
+}
+
+xchat_hook *
+xchat_hook_server (xchat_plugin *ph, const char *name, int pri,
+						 xchat_serv_cb *callb, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_SERVER, pri, name, 0, callb, 0, userdata);
+}
+
+xchat_hook *
+xchat_hook_print (xchat_plugin *ph, const char *name, int pri,
+						xchat_print_cb *callb, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_PRINT, pri, name, 0, callb, 0, userdata);
+}
+
+xchat_hook *
+xchat_hook_timer (xchat_plugin *ph, int timeout, xchat_timer_cb *callb,
+					   void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_TIMER, 0, 0, 0, callb, timeout, userdata);
+}
+
+xchat_hook *
+xchat_hook_fd (xchat_plugin *ph, int fd, int flags,
+					xchat_fd_cb *callb, void *userdata)
+{
+	xchat_hook *hook;
+
+	hook = plugin_add_hook (ph, HOOK_FD, 0, 0, 0, callb, 0, userdata);
+	hook->pri = fd;
+	/* plugin hook_fd flags correspond exactly to FIA_* flags (fe.h) */
+	hook->tag = fe_input_add (fd, flags, plugin_fd_cb, hook);
+
+	return hook;
+}
+
+void
+xchat_print (xchat_plugin *ph, const char *text)
+{
+	if (!is_session (ph->context))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_print called without a valid context.\n", ph->name));
+		return;
+	}
+
+	PrintText (ph->context, (char *)text);
+}
+
+void
+xchat_printf (xchat_plugin *ph, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	xchat_print (ph, buf);
+	g_free (buf);
+}
+
+void
+xchat_command (xchat_plugin *ph, const char *command)
+{
+	char *conv;
+	int len = -1;
+
+	if (!is_session (ph->context))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_command called without a valid context.\n", ph->name));
+		return;
+	}
+
+	/* scripts/plugins continue to send non-UTF8... *sigh* */
+	conv = text_validate ((char **)&command, &len);
+	handle_command (ph->context, (char *)command, FALSE);
+	g_free (conv);
+}
+
+void
+xchat_commandf (xchat_plugin *ph, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	xchat_command (ph, buf);
+	g_free (buf);
+}
+
+int
+xchat_nickcmp (xchat_plugin *ph, const char *s1, const char *s2)
+{
+	return ((session *)ph->context)->server->p_cmp (s1, s2);
+}
+
+xchat_context *
+xchat_get_context (xchat_plugin *ph)
+{
+	return ph->context;
+}
+
+int
+xchat_set_context (xchat_plugin *ph, xchat_context *context)
+{
+	if (is_session (context))
+	{
+		ph->context = context;
+		return 1;
+	}
+	return 0;
+}
+
+xchat_context *
+xchat_find_context (xchat_plugin *ph, const char *servname, const char *channel)
+{
+	GSList *slist, *clist, *sessions = NULL;
+	server *serv;
+	session *sess;
+	char *netname;
+
+	if (servname == NULL && channel == NULL)
+		return current_sess;
+
+	slist = serv_list;
+	while (slist)
+	{
+		serv = slist->data;
+		netname = server_get_network (serv, TRUE);
+
+		if (servname == NULL ||
+			 rfc_casecmp (servname, serv->servername) == 0 ||
+			 strcasecmp (servname, serv->hostname) == 0 ||
+			 strcasecmp (servname, netname) == 0)
+		{
+			if (channel == NULL)
+				return serv->front_session;
+
+			clist = sess_list;
+			while (clist)
+			{
+				sess = clist->data;
+				if (sess->server == serv)
+				{
+					if (rfc_casecmp (channel, sess->channel) == 0)
+					{
+						if (sess->server == ph->context->server)
+						{
+							g_slist_free (sessions);
+							return sess;
+						} else
+						{
+							sessions = g_slist_prepend (sessions, sess);
+						}
+					}
+				}
+				clist = clist->next;
+			}
+		}
+		slist = slist->next;
+	}
+
+	if (sessions)
+	{
+		sessions = g_slist_reverse (sessions);
+		sess = sessions->data;
+		g_slist_free (sessions);
+		return sess;
+	}
+
+	return NULL;
+}
+
+const char *
+xchat_get_info (xchat_plugin *ph, const char *id)
+{
+	session *sess;
+	guint32 hash;
+
+	/*                 1234567890 */
+	if (!strncmp (id, "event_text", 10))
+	{
+		char *e = (char *)id + 10;
+		if (*e == ' ') e++;	/* 2.8.0 only worked without a space */
+		return text_find_format_string (e);
+	}
+
+	hash = str_hash (id);
+	/* do the session independant ones first */
+	switch (hash)
+	{
+	case 0x325acab5:	/* libdirfs */
+		return XCHATLIBDIR;
+
+	case 0x14f51cd8: /* version */
+		return PACKAGE_VERSION;
+
+	case 0xdd9b1abd:	/* xchatdir */
+		return get_xdir_utf8 ();
+
+	case 0xe33f6c4a:	/* xchatdirfs */
+		return get_xdir_fs ();
+	}
+
+	sess = ph->context;
+	if (!is_session (sess))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_get_info called without a valid context.\n", ph->name));
+		return NULL;
+	}
+
+	switch (hash)
+	{
+	case 0x2de2ee: /* away */
+		if (sess->server->is_away)
+			return sess->server->last_away_reason;
+		return NULL;
+
+  	case 0x2c0b7d03: /* channel */
+		return sess->channel;
+
+	case 0x2c0d614c: /* charset */
+		{
+			const char *locale;
+
+			if (sess->server->encoding)
+				return sess->server->encoding;
+
+			locale = NULL;
+			g_get_charset (&locale);
+			return locale;
+		}
+
+	case 0x30f5a8: /* host */
+		return sess->server->hostname;
+
+	case 0x1c0e99c1: /* inputbox */
+		return fe_get_inputbox_contents (sess);
+
+	case 0x633fb30:	/* modes */
+		return sess->current_modes;
+
+	case 0x6de15a2e:	/* network */
+		return server_get_network (sess->server, FALSE);
+
+	case 0x339763: /* nick */
+		return sess->server->nick;
+
+	case 0x438fdf9: /* nickserv */
+		if (sess->server->network)
+			return ((ircnet *)sess->server->network)->nickserv;
+		return NULL;
+
+	case 0xca022f43: /* server */
+		if (!sess->server->connected)
+			return NULL;
+		return sess->server->servername;
+
+	case 0x696cd2f: /* topic */
+		return sess->topic;
+
+	case 0x3419f12d: /* gtkwin_ptr */
+		return fe_gui_info_ptr (sess, 1);
+
+	case 0x506d600b: /* native win_ptr */
+		return fe_gui_info_ptr (sess, 0);
+
+	case 0x6d3431b5: /* win_status */
+		switch (fe_gui_info (sess, 0))	/* check window status */
+		{
+		case 0: return "normal";
+		case 1: return "active";
+		case 2: return "hidden";
+		}
+		return NULL;
+	}
+
+	return NULL;
+}
+
+int
+xchat_get_prefs (xchat_plugin *ph, const char *name, const char **string, int *integer)
+{
+	int i = 0;
+
+	/* some special run-time info (not really prefs, but may aswell throw it in here) */
+	switch (str_hash (name))
+	{
+		case 0xf82136c4: /* state_cursor */
+			*integer = fe_get_inputbox_cursor (ph->context);
+			return 2;
+
+		case 0xd1b: /* id */
+			*integer = ph->context->server->id;
+			return 2;
+	}
+	
+	do
+	{
+		if (!strcasecmp (name, vars[i].name))
+		{
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				*string = ((char *) &prefs + vars[i].offset);
+				return 1;
+
+			case TYPE_INT:
+				*integer = *((int *) &prefs + vars[i].offset);
+				return 2;
+
+			default:
+			/*case TYPE_BOOL:*/
+				if (*((int *) &prefs + vars[i].offset))
+					*integer = 1;
+				else
+					*integer = 0;
+				return 3;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	return 0;
+}
+
+xchat_list *
+xchat_list_get (xchat_plugin *ph, const char *name)
+{
+	xchat_list *list;
+
+	list = malloc (sizeof (xchat_list));
+	list->pos = NULL;
+
+	switch (str_hash (name))
+	{
+	case 0x556423d0: /* channels */
+		list->type = LIST_CHANNELS;
+		list->next = sess_list;
+		break;
+
+	case 0x183c4:	/* dcc */
+		list->type = LIST_DCC;
+		list->next = dcc_list;
+		break;
+
+	case 0xb90bfdd2:	/* ignore */
+		list->type = LIST_IGNORE;
+		list->next = ignore_list;
+		break;
+
+	case 0xc2079749:	/* notify */
+		list->type = LIST_NOTIFY;
+		list->next = notify_list;
+		list->head = (void *)ph->context;	/* reuse this pointer */
+		break;
+
+	case 0x6a68e08: /* users */
+		if (is_session (ph->context))
+		{
+			list->type = LIST_USERS;
+			list->head = list->next = userlist_flat_list (ph->context);
+			fe_userlist_set_selected (ph->context);
+			break;
+		}	/* fall through */
+
+	default:
+		free (list);
+		return NULL;
+	}
+
+	return list;
+}
+
+void
+xchat_list_free (xchat_plugin *ph, xchat_list *xlist)
+{
+	if (xlist->type == LIST_USERS)
+		g_slist_free (xlist->head);
+	free (xlist);
+}
+
+int
+xchat_list_next (xchat_plugin *ph, xchat_list *xlist)
+{
+	if (xlist->next == NULL)
+		return 0;
+
+	xlist->pos = xlist->next;
+	xlist->next = xlist->pos->next;
+
+	/* NOTIFY LIST: Find the entry which matches the context
+		of the plugin when list_get was originally called. */
+	if (xlist->type == LIST_NOTIFY)
+	{
+		xlist->notifyps = notify_find_server_entry (xlist->pos->data,
+													((session *)xlist->head)->server);
+		if (!xlist->notifyps)
+			return 0;
+	}
+
+	return 1;
+}
+
+const char * const *
+xchat_list_fields (xchat_plugin *ph, const char *name)
+{
+	static const char * const dcc_fields[] =
+	{
+		"iaddress32","icps",		"sdestfile","sfile",		"snick",	"iport",
+		"ipos", "iposhigh", "iresume", "iresumehigh", "isize", "isizehigh", "istatus", "itype", NULL
+	};
+	static const char * const channels_fields[] =
+	{
+		"schannel",	"schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes",
+		"snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers",
+		NULL
+	};
+	static const char * const ignore_fields[] =
+	{
+		"iflags", "smask", NULL
+	};
+	static const char * const notify_fields[] =
+	{
+		"iflags", "snetworks", "snick", "toff", "ton", "tseen", NULL
+	};
+	static const char * const users_fields[] =
+	{
+		"iaway", "shost", "tlasttalk", "snick", "sprefix", "srealname", "iselected", NULL
+	};
+	static const char * const list_of_lists[] =
+	{
+		"channels",	"dcc", "ignore", "notify", "users", NULL
+	};
+
+	switch (str_hash (name))
+	{
+	case 0x556423d0:	/* channels */
+		return channels_fields;
+	case 0x183c4:		/* dcc */
+		return dcc_fields;
+	case 0xb90bfdd2:	/* ignore */
+		return ignore_fields;
+	case 0xc2079749:	/* notify */
+		return notify_fields;
+	case 0x6a68e08:	/* users */
+		return users_fields;
+	case 0x6236395:	/* lists */
+		return list_of_lists;
+	}
+
+	return NULL;
+}
+
+time_t
+xchat_list_time (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data;
+
+	switch (xlist->type)
+	{
+	case LIST_NOTIFY:
+		if (!xlist->notifyps)
+			return (time_t) -1;
+		switch (hash)
+		{
+		case 0x1ad6f:	/* off */
+			return xlist->notifyps->lastoff;
+		case 0xddf:	/* on */
+			return xlist->notifyps->laston;
+		case 0x35ce7b:	/* seen */
+			return xlist->notifyps->lastseen;
+		}
+		break;
+
+	case LIST_USERS:
+		data = xlist->pos->data;
+		switch (hash)
+		{
+		case 0xa9118c42:	/* lasttalk */
+			return ((struct User *)data)->lasttalk;
+		}
+	}
+
+	return (time_t) -1;
+}
+
+const char *
+xchat_list_str (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data = ph->context;
+	int type = LIST_CHANNELS;
+
+	/* a NULL xlist is a shortcut to current "channels" context */
+	if (xlist)
+	{
+		data = xlist->pos->data;
+		type = xlist->type;
+	}
+
+	switch (type)
+	{
+	case LIST_CHANNELS:
+		switch (hash)
+		{
+		case 0x2c0b7d03: /* channel */
+			return ((session *)data)->channel;
+		case 0x577e0867: /* chantypes */
+			return ((session *)data)->server->chantypes;
+		case 0x38b735af: /* context */
+			return data;	/* this is a session * */
+		case 0x6de15a2e: /* network */
+			return server_get_network (((session *)data)->server, FALSE);
+		case 0x8455e723: /* nickprefixes */
+			return ((session *)data)->server->nick_prefixes;
+		case 0x829689ad: /* nickmodes */
+			return ((session *)data)->server->nick_modes;
+		case 0xca022f43: /* server */
+			return ((session *)data)->server->servername;
+		}
+		break;
+
+	case LIST_DCC:
+		switch (hash)
+		{
+		case 0x3d9ad31e:	/* destfile */
+			return ((struct DCC *)data)->destfile;
+		case 0x2ff57c:	/* file */
+			return ((struct DCC *)data)->file;
+		case 0x339763: /* nick */
+			return ((struct DCC *)data)->nick;
+		}
+		break;
+
+	case LIST_IGNORE:
+		switch (hash)
+		{
+		case 0x3306ec:	/* mask */
+			return ((struct ignore *)data)->mask;
+		}
+		break;
+
+	case LIST_NOTIFY:
+		switch (hash)
+		{
+		case 0x4e49ec05:	/* networks */
+			return ((struct notify *)data)->networks;
+		case 0x339763: /* nick */
+			return ((struct notify *)data)->name;
+		}
+		break;
+
+	case LIST_USERS:
+		switch (hash)
+		{
+		case 0x339763: /* nick */
+			return ((struct User *)data)->nick;
+		case 0x30f5a8: /* host */
+			return ((struct User *)data)->hostname;
+		case 0xc594b292: /* prefix */
+			return ((struct User *)data)->prefix;
+		case 0xccc6d529: /* realname */
+			return ((struct User *)data)->realname;
+		}
+		break;
+	}
+
+	return NULL;
+}
+
+int
+xchat_list_int (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data = ph->context;
+	int tmp, type = LIST_CHANNELS;
+
+	/* a NULL xlist is a shortcut to current "channels" context */
+	if (xlist)
+	{
+		data = xlist->pos->data;
+		type = xlist->type;
+	}
+
+	switch (type)
+	{
+	case LIST_DCC:
+		switch (hash)
+		{
+		case 0x34207553: /* address32 */
+			return ((struct DCC *)data)->addr;
+		case 0x181a6: /* cps */
+			return ((struct DCC *)data)->cps;
+		case 0x349881: /* port */
+			return ((struct DCC *)data)->port;
+		case 0x1b254: /* pos */
+			return ((struct DCC *)data)->pos & 0xffffffff;
+		case 0xe8a945f6: /* poshigh */
+			return (((struct DCC *)data)->pos >> 32) & 0xffffffff;
+		case 0xc84dc82d: /* resume */
+			return ((struct DCC *)data)->resumable & 0xffffffff;
+		case 0xded4c74f: /* resumehigh */
+			return (((struct DCC *)data)->resumable >> 32) & 0xffffffff;
+		case 0x35e001: /* size */
+			return ((struct DCC *)data)->size & 0xffffffff;
+		case 0x3284d523: /* sizehigh */
+			return (((struct DCC *)data)->size >> 32) & 0xffffffff;
+		case 0xcacdcff2: /* status */
+			return ((struct DCC *)data)->dccstat;
+		case 0x368f3a: /* type */
+			return ((struct DCC *)data)->type;
+		}
+		break;
+
+	case LIST_IGNORE:
+		switch (hash)
+		{
+		case 0x5cfee87:	/* flags */
+			return ((struct ignore *)data)->type;
+		}
+		break;
+
+	case LIST_CHANNELS:
+		switch (hash)
+		{
+		case 0xd1b:	/* id */
+			return ((struct session *)data)->server->id;
+		case 0x5cfee87:	/* flags */
+			tmp = ((struct session *)data)->alert_taskbar;   /* bit 10 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->alert_tray;         /* 9 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->alert_beep;         /* 8 */
+			tmp <<= 1;
+			/*tmp |= ((struct session *)data)->color_paste;*/    /* 7 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->text_hidejoinpart;   /* 6 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->have_idmsg; /* 5 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->have_whox;  /* 4 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->end_of_motd;/* 3 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->is_away;    /* 2 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->connecting; /* 1 */ 
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->connected;  /* 0 */
+			return tmp;
+		case 0x1a192: /* lag */
+			return ((struct session *)data)->server->lag;
+		case 0x1916144c: /* maxmodes */
+			return ((struct session *)data)->server->modes_per_line;
+		case 0x66f1911: /* queue */
+			return ((struct session *)data)->server->sendq_len;
+		case 0x368f3a:	/* type */
+			return ((struct session *)data)->type;
+		case 0x6a68e08: /* users */
+			return ((struct session *)data)->total;
+		}
+		break;
+
+	case LIST_NOTIFY:
+		if (!xlist->notifyps)
+			return -1;
+		switch (hash)
+		{
+		case 0x5cfee87: /* flags */
+			return xlist->notifyps->ison;
+		}
+
+	case LIST_USERS:
+		switch (hash)
+		{
+		case 0x2de2ee:	/* away */
+			return ((struct User *)data)->away;
+		case 0x4705f29b: /* selected */
+			return ((struct User *)data)->selected;
+		}
+		break;
+
+	}
+
+	return -1;
+}
+
+void *
+xchat_plugingui_add (xchat_plugin *ph, const char *filename,
+							const char *name, const char *desc,
+							const char *version, char *reserved)
+{
+#ifdef USE_PLUGIN
+	ph = plugin_list_add (NULL, strdup (filename), strdup (name), strdup (desc),
+								 strdup (version), NULL, NULL, TRUE, TRUE);
+	fe_pluginlist_update ();
+#endif
+
+	return ph;
+}
+
+void
+xchat_plugingui_remove (xchat_plugin *ph, void *handle)
+{
+#ifdef USE_PLUGIN
+	plugin_free (handle, FALSE, FALSE);
+#endif
+}
+
+int
+xchat_emit_print (xchat_plugin *ph, const char *event_name, ...)
+{
+	va_list args;
+	/* currently only 4 because no events use more than 4.
+		This can be easily expanded without breaking the API. */
+	char *argv[4] = {NULL, NULL, NULL, NULL};
+	int i = 0;
+
+	va_start (args, event_name);
+	while (1)
+	{
+		argv[i] = va_arg (args, char *);
+		if (!argv[i])
+			break;
+		i++;
+		if (i >= 4)
+			break;
+	}
+
+	i = text_emit_by_name ((char *)event_name, ph->context, argv[0], argv[1],
+								  argv[2], argv[3]);
+	va_end (args);
+
+	return i;
+}
+
+char *
+xchat_gettext (xchat_plugin *ph, const char *msgid)
+{
+	/* so that plugins can use xchat's internal gettext strings. */
+	/* e.g. The EXEC plugin uses this on Windows. */
+	return _(msgid);
+}
+
+void
+xchat_send_modes (xchat_plugin *ph, const char **targets, int ntargets, int modes_per_line, char sign, char mode)
+{
+	char tbuf[514];	/* modes.c needs 512 + null */
+
+	send_channel_modes (ph->context, tbuf, (char **)targets, 0, ntargets, sign, mode, modes_per_line);
+}
+
+char *
+xchat_strip (xchat_plugin *ph, const char *str, int len, int flags)
+{
+	return strip_color ((char *)str, len, flags);
+}
+
+void
+xchat_free (xchat_plugin *ph, void *ptr)
+{
+	g_free (ptr);
+}
diff --git a/src/common/plugin.h b/src/common/plugin.h
new file mode 100644
index 00000000..b0c89d1b
--- /dev/null
+++ b/src/common/plugin.h
@@ -0,0 +1,132 @@
+#ifndef XCHAT_COMMONPLUGIN_H
+#define XCHAT_COMMONPLUGIN_H
+
+#ifdef PLUGIN_C
+struct _xchat_plugin
+{
+	/* Keep these insync with xchat-plugin.h */
+	/* !!don't change the order, to keep binary compat!! */
+	xchat_hook *(*xchat_hook_command) (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+	xchat_hook *(*xchat_hook_server) (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+	xchat_hook *(*xchat_hook_print) (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph,
+		   int fd,
+		   int flags,
+		   int (*callback) (int fd, int flags, void *user_data),
+		   void *userdata);
+	void *(*xchat_unhook) (xchat_plugin *ph,
+	      xchat_hook *hook);
+	void (*xchat_print) (xchat_plugin *ph,
+	     const char *text);
+	void (*xchat_printf) (xchat_plugin *ph,
+	      const char *format, ...);
+	void (*xchat_command) (xchat_plugin *ph,
+	       const char *command);
+	void (*xchat_commandf) (xchat_plugin *ph,
+		const char *format, ...);
+	int (*xchat_nickcmp) (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+	int (*xchat_set_context) (xchat_plugin *ph,
+		   xchat_context *ctx);
+	xchat_context *(*xchat_find_context) (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+	xchat_context *(*xchat_get_context) (xchat_plugin *ph);
+	const char *(*xchat_get_info) (xchat_plugin *ph,
+		const char *id);
+	int (*xchat_get_prefs) (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+	xchat_list * (*xchat_list_get) (xchat_plugin *ph,
+		const char *name);
+	void (*xchat_list_free) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * const * (*xchat_list_fields) (xchat_plugin *ph,
+		   const char *name);
+	int (*xchat_list_next) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * (*xchat_list_str) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	int (*xchat_list_int) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	void * (*xchat_plugingui_add) (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+	void (*xchat_plugingui_remove) (xchat_plugin *ph,
+			void *handle);
+	int (*xchat_emit_print) (xchat_plugin *ph,
+			const char *event_name, ...);
+	void *(*xchat_read_fd) (xchat_plugin *ph);
+	time_t (*xchat_list_time) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	char *(*xchat_gettext) (xchat_plugin *ph,
+		const char *msgid);
+	void (*xchat_send_modes) (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+	char *(*xchat_strip) (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+	void (*xchat_free) (xchat_plugin *ph,
+	    void *ptr);
+	void *(*xchat_dummy4) (xchat_plugin *ph);
+	void *(*xchat_dummy3) (xchat_plugin *ph);
+	void *(*xchat_dummy2) (xchat_plugin *ph);
+	void *(*xchat_dummy1) (xchat_plugin *ph);
+	/* PRIVATE FIELDS! */
+	void *handle;		/* from dlopen */
+	char *filename;	/* loaded from */
+	char *name;
+	char *desc;
+	char *version;
+	session *context;
+	void *deinit_callback;	/* pointer to xchat_plugin_deinit */
+	unsigned int fake:1;		/* fake plugin. Added by xchat_plugingui_add() */
+	unsigned int free_strings:1;		/* free name,desc,version? */
+};
+#endif
+
+char *plugin_load (session *sess, char *filename, char *arg);
+void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake);
+int plugin_kill (char *name, int by_filename);
+void plugin_kill_all (void);
+void plugin_auto_load (session *sess);
+int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]);
+int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]);
+int plugin_emit_print (session *sess, char *word[]);
+int plugin_emit_dummy_print (session *sess, char *name);
+int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, int len, char *string);
+GList* plugin_command_list(GList *tmp_list);
+int plugin_show_help (session *sess, char *cmd);
+void plugin_command_foreach (session *sess, void *userdata, void (*cb) (session *sess, void *userdata, char *name, char *usage));
+
+#endif
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
new file mode 100644
index 00000000..f1df6ccd
--- /dev/null
+++ b/src/common/proto-irc.c
@@ -0,0 +1,1260 @@
+/* X-Chat
+ * Copyright (C) 2002 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* IRC RFC1459(+commonly used extensions) protocol implementation */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "xchat.h"
+#include "ctcp.h"
+#include "fe.h"
+#include "ignore.h"
+#include "inbound.h"
+#include "modes.h"
+#include "notify.h"
+#include "plugin.h"
+#include "server.h"
+#include "text.h"
+#include "outbound.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+static void
+irc_login (server *serv, char *user, char *realname)
+{
+	if (serv->password[0])
+		tcp_sendf (serv, "PASS %s\r\n", serv->password);
+
+	tcp_sendf (serv,
+				  "NICK %s\r\n"
+				  "USER %s %s %s :%s\r\n",
+				  serv->nick, user, user, serv->servername, realname);
+}
+
+static void
+irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
+{
+	/* are all ircd authors idiots? */
+	switch (serv->nickservtype)
+	{
+	case 0:
+		tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 1:
+		tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 2:
+		tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 3:
+		tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 4:
+		/* why couldn't QuakeNet implement one of the existing ones? */
+		tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3);
+	}
+}
+
+static void
+irc_ns_identify (server *serv, char *pass)
+{
+	irc_nickserv (serv, "IDENTIFY", pass, "", "");
+}
+
+static void
+irc_ns_ghost (server *serv, char *usname, char *pass)
+{
+	if (serv->nickservtype != 4)
+		irc_nickserv (serv, "GHOST", usname, " ", pass);
+}
+
+static void
+irc_join (server *serv, char *channel, char *key)
+{
+	if (key[0])
+		tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
+	else
+		tcp_sendf (serv, "JOIN %s\r\n", channel);
+}
+
+static void
+irc_join_list_flush (server *serv, GString *c, GString *k)
+{
+	char *chanstr, *keystr;
+
+	chanstr = g_string_free (c, FALSE);
+	keystr = g_string_free (k, FALSE);
+	if (chanstr[0])
+	{
+		if (keystr[0])
+			tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
+		else
+			tcp_sendf (serv, "JOIN %s\r\n", chanstr);
+	}
+	g_free (chanstr);
+	g_free (keystr);
+}
+
+/* join a whole list of channels & keys, split to multiple lines
+ * to get around 512 limit */
+
+static void
+irc_join_list (server *serv, GSList *channels, GSList *keys)
+{
+	GSList *clist;
+	GSList *klist;
+	GString *c = g_string_new (NULL);
+	GString *k = g_string_new (NULL);
+	int len;
+	int add;
+	int i, j;
+
+	i = j = 0;
+	len = 9; /* "JOIN<space><space>\r\n" */
+	clist = channels;
+	klist = keys;
+
+	while (clist)
+	{
+		/* measure how many bytes this channel would add... */
+		if (1)
+		{
+			add = strlen (clist->data);
+			if (i != 0)
+				add++;	/* comma */
+		}
+
+		if (klist->data)
+		{
+			add += strlen (klist->data);
+		}
+		else
+		{
+			add++;	/* 'x' filler */
+		}
+
+		if (j != 0)
+			add++;	/* comma */
+
+		/* too big? dump buffer and start a fresh one */
+		if (len + add > 512)
+		{
+			irc_join_list_flush (serv, c, k);
+
+			c = g_string_new (NULL);
+			k = g_string_new (NULL);
+			i = j = 0;
+			len = 9;
+		}
+
+		/* now actually add it to our GStrings */
+		if (1)
+		{
+			add = strlen (clist->data);
+			if (i != 0)
+			{
+				add++;
+				g_string_append_c (c, ',');
+			}
+			g_string_append (c, clist->data);
+			i++;
+		}
+
+		if (klist->data)
+		{
+			add += strlen (klist->data);
+			if (j != 0)
+			{
+				add++;
+				g_string_append_c (k, ',');
+			}
+			g_string_append (k, klist->data);
+			j++;
+		}
+		else
+		{
+			add++;
+			if (j != 0)
+			{
+				add++;
+				g_string_append_c (k, ',');
+			}
+			g_string_append_c (k, 'x');
+			j++;
+		}
+
+		len += add;
+
+		klist = klist->next;
+		clist = clist->next;
+	}
+
+	irc_join_list_flush (serv, c, k);
+}
+
+static void
+irc_part (server *serv, char *channel, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
+	else
+		tcp_sendf (serv, "PART %s\r\n", channel);
+}
+
+static void
+irc_quit (server *serv, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "QUIT :%s\r\n", reason);
+	else
+		tcp_send_len (serv, "QUIT\r\n", 6);
+}
+
+static void
+irc_set_back (server *serv)
+{
+	tcp_send_len (serv, "AWAY\r\n", 6);
+}
+
+static void
+irc_set_away (server *serv, char *reason)
+{
+	if (reason)
+	{
+		if (!reason[0])
+			reason = " ";
+	}
+	else
+	{
+		reason = " ";
+	}
+
+	tcp_sendf (serv, "AWAY :%s\r\n", reason);
+}
+
+static void
+irc_ctcp (server *serv, char *to, char *msg)
+{
+	tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
+}
+
+static void
+irc_nctcp (server *serv, char *to, char *msg)
+{
+	tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
+}
+
+static void
+irc_cycle (server *serv, char *channel, char *key)
+{
+	tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
+}
+
+static void
+irc_kick (server *serv, char *channel, char *nick, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
+	else
+		tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
+}
+
+static void
+irc_invite (server *serv, char *channel, char *nick)
+{
+	tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
+}
+
+static void
+irc_mode (server *serv, char *target, char *mode)
+{
+	tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
+}
+
+/* find channel info when joined */
+
+static void
+irc_join_info (server *serv, char *channel)
+{
+	tcp_sendf (serv, "MODE %s\r\n", channel);
+}
+
+/* initiate userlist retreival */
+
+static void
+irc_user_list (server *serv, char *channel)
+{
+	tcp_sendf (serv, "WHO %s\r\n", channel);
+}
+
+/* userhost */
+
+static void
+irc_userhost (server *serv, char *nick)
+{
+	tcp_sendf (serv, "USERHOST %s\r\n", nick);
+}
+
+static void
+irc_away_status (server *serv, char *channel)
+{
+	if (serv->have_whox)
+		tcp_sendf (serv, "WHO %s %%ctnf,152\r\n", channel);
+	else
+		tcp_sendf (serv, "WHO %s\r\n", channel);
+}
+
+/*static void
+irc_get_ip (server *serv, char *nick)
+{
+	tcp_sendf (serv, "WHO %s\r\n", nick);
+}*/
+
+
+/*
+ *  Command: WHOIS
+ *     Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
+ */
+static void
+irc_user_whois (server *serv, char *nicks)
+{
+	tcp_sendf (serv, "WHOIS %s\r\n", nicks);
+}
+
+static void
+irc_message (server *serv, char *channel, char *text)
+{
+	tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
+}
+
+static void
+irc_action (server *serv, char *channel, char *act)
+{
+	tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
+}
+
+static void
+irc_notice (server *serv, char *channel, char *text)
+{
+	tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
+}
+
+static void
+irc_topic (server *serv, char *channel, char *topic)
+{
+	if (!topic)
+		tcp_sendf (serv, "TOPIC %s :\r\n", channel);
+	else if (topic[0])
+		tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
+	else
+		tcp_sendf (serv, "TOPIC %s\r\n", channel);
+}
+
+static void
+irc_list_channels (server *serv, char *arg, int min_users)
+{
+	if (arg[0])
+	{
+		tcp_sendf (serv, "LIST %s\r\n", arg);
+		return;
+	}
+
+	if (serv->use_listargs)
+		tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
+	else
+		tcp_send_len (serv, "LIST\r\n", 6);
+}
+
+static void
+irc_names (server *serv, char *channel)
+{
+	tcp_sendf (serv, "NAMES %s\r\n", channel);
+}
+
+static void
+irc_change_nick (server *serv, char *new_nick)
+{
+	tcp_sendf (serv, "NICK %s\r\n", new_nick);
+}
+
+static void
+irc_ping (server *serv, char *to, char *timestring)
+{
+	if (*to)
+		tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
+	else
+		tcp_sendf (serv, "PING %s\r\n", timestring);
+}
+
+static int
+irc_raw (server *serv, char *raw)
+{
+	int len;
+	char tbuf[4096];
+	if (*raw)
+	{
+		len = strlen (raw);
+		if (len < sizeof (tbuf) - 3)
+		{
+			len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
+			tcp_send_len (serv, tbuf, len);
+		} else
+		{
+			tcp_send_len (serv, raw, len);
+			tcp_send_len (serv, "\r\n", 2);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* ============================================================== */
+/* ======================= IRC INPUT ============================ */
+/* ============================================================== */
+
+
+static void
+channel_date (session *sess, char *chan, char *timestr)
+{
+	time_t timestamp = (time_t) atol (timestr);
+	char *tim = ctime (&timestamp);
+	tim[24] = 0;	/* get rid of the \n */
+	EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
+}
+
+static void
+process_numeric (session * sess, int n,
+					  char *word[], char *word_eol[], char *text)
+{
+	server *serv = sess->server;
+	/* show whois is the server tab */
+	session *whois_sess = serv->server_session;
+
+	/* unless this setting is on */
+	if (prefs.irc_whois_front)
+		whois_sess = serv->front_session;
+
+	switch (n)
+	{
+	case 1:
+		inbound_login_start (sess, word[3], word[1]);
+		/* if network is PTnet then you must get your IP address
+			from "001" server message */
+		if ((strncmp(word[7], "PTnet", 5) == 0) &&
+			(strncmp(word[8], "IRC", 3) == 0) &&
+			(strncmp(word[9], "Network", 7) == 0) &&
+			(strrchr(word[10], '@') != NULL))
+		{
+			serv->use_who = FALSE;
+			if (prefs.ip_from_server)
+				inbound_foundip (sess, strrchr(word[10], '@')+1);
+		}
+
+		/* use /NICKSERV */
+		if (strcasecmp (word[7], "DALnet") == 0 ||
+			 strcasecmp (word[7], "BRASnet") == 0)
+			serv->nickservtype = 1;
+
+		/* use /NS */
+		else if (strcasecmp (word[7], "FreeNode") == 0)
+			serv->nickservtype = 2;
+
+		goto def;
+
+	case 4:	/* check the ircd type */
+		serv->use_listargs = FALSE;
+		serv->modes_per_line = 3;		/* default to IRC RFC */
+		if (strncmp (word[5], "bahamut", 7) == 0)				/* DALNet */
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+		} else if (strncmp (word[5], "u2.10.", 6) == 0)		/* Undernet */
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+			serv->modes_per_line = 6;		/* allow 6 modes per line */
+		} else if (strncmp (word[5], "glx2", 4) == 0)
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+		}
+		goto def;
+
+	case 5:
+		inbound_005 (serv, word);
+		goto def;
+
+	case 263:	/*Server load is temporarily too heavy */
+		if (fe_is_chanwindow (sess->server))
+		{
+			fe_chan_list_end (sess->server);
+			fe_message (word_eol[5] + 1, FE_MSG_ERROR);
+		}
+		goto def;
+
+	case 290:	/* CAPAB reply */
+		if (strstr (word_eol[1], "IDENTIFY-MSG"))
+		{
+			serv->have_idmsg = TRUE;
+			break;
+		}
+		goto def;
+
+	case 301:
+		inbound_away (serv, word[4],
+						(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
+		break;
+
+	case 302:
+		if (serv->skip_next_userhost)
+		{
+			char *eq = strchr (word[4], '=');
+			if (eq)
+			{
+				*eq = 0;
+				if (!serv->p_cmp (word[4] + 1, serv->nick))
+				{
+					char *at = strrchr (eq + 1, '@');
+					if (at)
+						inbound_foundip (sess, at + 1);
+				}
+			}
+
+			serv->skip_next_userhost = FALSE;
+			break;
+		}
+		else goto def;
+
+	case 303:
+		word[4]++;
+		notify_markonline (serv, word);
+		break;
+
+	case 305:
+		inbound_uback (serv);
+		goto def;
+
+	case 306:
+		inbound_uaway (serv);
+		goto def;
+
+	case 312:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
+		else
+			inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, 0xff);
+		break;
+
+	case 311:	/* WHOIS 1st line */
+		serv->inside_whois = 1;
+		inbound_user_info_start (sess, word[4]);
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
+							 word[6], word_eol[8] + 1, 0);
+		else
+			inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
+									 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], 0xff);
+		break;
+
+	case 314:	/* WHOWAS */
+		inbound_user_info_start (sess, word[4]);
+		EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
+						 word[6], word_eol[8] + 1, 0);
+		break;
+
+	case 317:
+		if (!serv->skip_next_whois)
+		{
+			time_t timestamp = (time_t) atol (word[6]);
+			long idle = atol (word[5]);
+			char *tim;
+			char outbuf[64];
+
+			snprintf (outbuf, sizeof (outbuf),
+						"%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
+						idle % 60);
+			if (timestamp == 0)
+				EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
+								 outbuf, NULL, NULL, 0);
+			else
+			{
+				tim = ctime (&timestamp);
+				tim[19] = 0; 	/* get rid of the \n */
+				EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
+								 outbuf, tim, NULL, 0);
+			}
+		}
+		break;
+
+	case 318:	/* END OF WHOIS */
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
+							 NULL, NULL, 0);
+		serv->skip_next_whois = 0;
+		serv->inside_whois = 0;
+		break;
+
+	case 313:
+	case 319:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
+							 word_eol[5] + 1, NULL, NULL, 0);
+		break;
+
+	case 307:	/* dalnet version */
+	case 320:	/* :is an identified user */
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
+							 word_eol[5] + 1, NULL, NULL, 0);
+		break;
+
+	case 321:
+		if (!fe_is_chanwindow (sess->server))
+			EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
+		break;
+
+	case 322:
+		if (fe_is_chanwindow (sess->server))
+		{
+			fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
+		} else
+		{
+			PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
+							word[4], atoi (word[5]), word_eol[6] + 1);
+		}
+		break;
+
+	case 323:
+		if (!fe_is_chanwindow (sess->server))
+			EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
+		else
+			fe_chan_list_end (sess->server);
+		break;
+
+	case 324:
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+			sess = serv->server_session;
+		if (sess->ignore_mode)
+			sess->ignore_mode = FALSE;
+		else
+			EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
+							 NULL, NULL, 0);
+		fe_update_mode_buttons (sess, 't', '-');
+		fe_update_mode_buttons (sess, 'n', '-');
+		fe_update_mode_buttons (sess, 's', '-');
+		fe_update_mode_buttons (sess, 'i', '-');
+		fe_update_mode_buttons (sess, 'p', '-');
+		fe_update_mode_buttons (sess, 'm', '-');
+		fe_update_mode_buttons (sess, 'l', '-');
+		fe_update_mode_buttons (sess, 'k', '-');
+		handle_mode (serv, word, word_eol, "", TRUE);
+		break;
+
+	case 329:
+		sess = find_channel (serv, word[4]);
+		if (sess)
+		{
+			if (sess->ignore_date)
+				sess->ignore_date = FALSE;
+			else
+				channel_date (sess, word[4], word[5]);
+		}
+		break;
+
+	case 330:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
+							 word_eol[6] + 1, word[5], NULL, 0);
+		break;
+
+	case 332:
+		inbound_topic (serv, word[4],
+						(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
+		break;
+
+	case 333:
+		inbound_topictime (serv, word[4], word[5], atol (word[6]));
+		break;
+
+#if 0
+	case 338:  /* Undernet Real user@host, Real IP */
+		EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6], 
+			(word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
+		break;
+#endif
+
+	case 341:						  /* INVITE ACK */
+		EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
+						 NULL, 0);
+		break;
+
+	case 352:						  /* WHO */
+		{
+			unsigned int away = 0;
+			session *who_sess = find_channel (serv, word[4]);
+
+			if (*word[9] == 'G')
+				away = 1;
+
+			inbound_user_info (sess, word[4], word[5], word[6], word[7],
+									 word[8], word_eol[11], away);
+
+			/* try to show only user initiated whos */
+			if (!who_sess || !who_sess->doing_who)
+				EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
+								 word[2], NULL, 0);
+		}
+		break;
+
+	case 354:	/* undernet WHOX: used as a reply for irc_away_status */
+		{
+			unsigned int away = 0;
+			session *who_sess;
+
+			/* irc_away_status sends out a "152" */
+			if (!strcmp (word[4], "152"))
+			{
+				who_sess = find_channel (serv, word[5]);
+
+				if (*word[7] == 'G')
+					away = 1;
+
+				/* :SanJose.CA.us.undernet.org 354 z1 152 #zed1 z1 H@ */
+				inbound_user_info (sess, word[5], 0, 0, 0, word[6], 0, away);
+
+				/* try to show only user initiated whos */
+				if (!who_sess || !who_sess->doing_who)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+			} else
+				goto def;
+		}
+		break;
+
+	case 315:						  /* END OF WHO */
+		{
+			session *who_sess;
+			who_sess = find_channel (serv, word[4]);
+			if (who_sess)
+			{
+				if (!who_sess->doing_who)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+				who_sess->doing_who = FALSE;
+			} else
+			{
+				if (!serv->doing_dns)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+				serv->doing_dns = FALSE;
+			}
+		}
+		break;
+
+	case 348:	/* +e-list entry */
+		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
+			goto def;
+		break;
+
+	case 349:	/* end of exemption list */
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+		{
+			sess = serv->front_session;
+			goto def;
+		}
+		if (!fe_is_banwindow (sess))
+			goto def;
+		fe_ban_list_end (sess, TRUE);
+		break;
+
+	case 353:						  /* NAMES */
+		inbound_nameslist (serv, word[5],
+							(word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
+		break;
+
+	case 366:
+		if (!inbound_nameslist_end (serv, word[4]))
+			goto def;
+		break;
+
+	case 367: /* banlist entry */
+		inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
+		break;
+
+	case 368:
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+		{
+			sess = serv->front_session;
+			goto def;
+		}
+		if (!fe_is_banwindow (sess))
+			goto def;
+		fe_ban_list_end (sess, FALSE);
+		break;
+
+	case 369:	/* WHOWAS end */
+	case 406:	/* WHOWAS error */
+		EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
+		serv->inside_whois = 0;
+		break;
+
+	case 372:	/* motd text */
+	case 375:	/* motd start */
+		if (!prefs.skipmotd || serv->motd_skipped)
+			EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
+							 NULL, 0);
+		break;
+
+	case 376:	/* end of motd */
+	case 422:	/* motd file is missing */
+		inbound_login_end (sess, text);
+		break;
+
+	case 433:	/* nickname in use */
+	case 432:	/* erroneous nickname */
+		if (serv->end_of_motd)
+			goto def;
+		inbound_next_nick (sess,  word[4]);
+		break;
+
+	case 437:
+		if (serv->end_of_motd || is_channel (serv, word[4]))
+			goto def;
+		inbound_next_nick (sess, word[4]);
+		break;
+
+	case 471:
+		EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 473:
+		EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 474:
+		EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 475:
+		EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 601:
+		notify_set_offline (serv, word[4], FALSE);
+		break;
+
+	case 605:
+		notify_set_offline (serv, word[4], TRUE);
+		break;
+
+	case 600:
+	case 604:
+		notify_set_online (serv, word[4]);
+		break;
+
+	default:
+
+		if (serv->inside_whois && word[4][0])
+		{
+			/* some unknown WHOIS reply, ircd coders make them up weekly */
+			if (!serv->skip_next_whois)
+				EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
+								(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
+								 word[2], NULL, 0);
+			return;
+		}
+
+	def:
+		if (is_channel (serv, word[4]))
+		{
+			session *realsess = find_channel (serv, word[4]);
+			if (!realsess)
+				realsess = serv->server_session;
+			EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
+		} else
+		{
+			EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
+							 word[2], NULL, 0);
+		}
+	}
+}
+
+/* handle named messages that starts with a ':' */
+
+static void
+process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
+{
+	server *serv = sess->server;
+	char ip[128], nick[NICKLEN];
+	char *text, *ex;
+	int len = strlen (type);
+
+	/* fill in the "ip" and "nick" buffers */
+	ex = strchr (word[1], '!');
+	if (!ex)							  /* no '!', must be a server message */
+	{
+		safe_strcpy (ip, word[1], sizeof (ip));
+		safe_strcpy (nick, word[1], sizeof (nick));
+	} else
+	{
+		safe_strcpy (ip, ex + 1, sizeof (ip));
+		ex[0] = 0;
+		safe_strcpy (nick, word[1], sizeof (nick));
+		ex[0] = '!';
+	}
+
+	if (len == 4)
+	{
+		guint32 t;
+
+		t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); 	
+		/* this should compile to a bunch of: CMP.L, JE ... nice & fast */
+		switch (t)
+		{
+		case WORDL('J','O','I','N'):
+			{
+				char *chan = word[3];
+
+				if (*chan == ':')
+					chan++;
+				if (!serv->p_cmp (nick, serv->nick))
+					inbound_ujoin (serv, chan, nick, ip);
+				else
+					inbound_join (serv, chan, nick, ip);
+			}
+			return;
+
+		case WORDL('K','I','C','K'):
+			{
+				char *kicked = word[4];
+				char *reason = word_eol[5];
+				if (*kicked)
+				{
+					if (*reason == ':')
+						reason++;
+					if (!strcmp (kicked, serv->nick))
+	 					inbound_ukick (serv, word[3], nick, reason);
+					else
+						inbound_kick (serv, word[3], kicked, nick, reason);
+				}
+			}
+			return;
+
+		case WORDL('K','I','L','L'):
+			EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
+			return;
+
+		case WORDL('M','O','D','E'):
+			handle_mode (serv, word, word_eol, nick, FALSE);	/* modes.c */
+			return;
+
+		case WORDL('N','I','C','K'):
+			inbound_newnick (serv, nick, (word_eol[3][0] == ':')
+									? word_eol[3] + 1 : word_eol[3], FALSE);
+			return;
+
+		case WORDL('P','A','R','T'):
+			{
+				char *chan = word[3];
+				char *reason = word_eol[4];
+
+				if (*chan == ':')
+					chan++;
+				if (*reason == ':')
+					reason++;
+				if (!strcmp (nick, serv->nick))
+					inbound_upart (serv, chan, ip, reason);
+				else
+					inbound_part (serv, chan, nick, ip, reason);
+			}
+			return;
+
+		case WORDL('P','O','N','G'):
+			inbound_ping_reply (serv->server_session,
+								 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
+			return;
+
+		case WORDL('Q','U','I','T'):
+			inbound_quit (serv, nick, ip,
+							  (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
+			return;
+		}
+
+		goto garbage;
+	}
+
+	else if (len >= 5)
+	{
+		guint32 t;
+
+		t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); 	
+		/* this should compile to a bunch of: CMP.L, JE ... nice & fast */
+		switch (t)
+		{
+		case WORDL('I','N','V','I'):
+			if (ignore_check (word[1], IG_INVI))
+				return;
+			
+			if (word[4][0] == ':')
+				EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
+								 serv->servername, NULL, 0);
+			else
+				EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
+								 serv->servername, NULL, 0);
+				
+			return;
+
+		case WORDL('N','O','T','I'):
+			{
+				int id = FALSE;	/* identified */
+
+				text = word_eol[4];
+				if (*text == ':')
+					text++;
+
+				if (serv->have_idmsg)
+				{
+					if (*text == '+')
+					{
+						id = TRUE;
+						text++;
+					} else if (*text == '-')
+						text++;
+				}
+
+				if (!ignore_check (word[1], IG_NOTI))
+					inbound_notice (serv, word[3], nick, text, ip, id);
+			}
+			return;
+
+		case WORDL('P','R','I','V'):
+			{
+				char *to = word[3];
+				int len;
+				int id = FALSE;	/* identified */
+				if (*to)
+				{
+					text = word_eol[4];
+					if (*text == ':')
+						text++;
+					if (serv->have_idmsg)
+					{
+						if (*text == '+')
+						{
+							id = TRUE;
+							text++;
+						} else if (*text == '-')
+							text++;
+					}
+					len = strlen (text);
+					if (text[0] == 1 && text[len - 1] == 1)	/* ctcp */
+					{
+						text[len - 1] = 0;
+						text++;
+						if (strncasecmp (text, "ACTION", 6) != 0)
+							flood_check (nick, ip, serv, sess, 0);
+						if (strncasecmp (text, "DCC ", 4) == 0)
+							/* redo this with handle_quotes TRUE */
+							process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
+						ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
+					} else
+					{
+						if (is_channel (serv, to))
+						{
+							if (ignore_check (word[1], IG_CHAN))
+								return;
+							inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
+						} else
+						{
+							if (ignore_check (word[1], IG_PRIV))
+								return;
+							inbound_privmsg (serv, nick, ip, text, id);
+						}
+					}
+				}
+			}
+			return;
+
+		case WORDL('T','O','P','I'):
+			inbound_topicnew (serv, nick, word[3],
+									(word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
+			return;
+
+		case WORDL('W','A','L','L'):
+			text = word_eol[3];
+			if (*text == ':')
+				text++;
+			EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
+			return;
+		}
+	}
+
+garbage:
+	/* unknown message */
+	PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
+}
+
+/* handle named messages that DON'T start with a ':' */
+
+static void
+process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
+{
+	sess = sess->server->server_session;
+
+	if (!strncmp (buf, "PING ", 5))
+	{
+		tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
+		return;
+	}
+	if (!strncmp (buf, "ERROR", 5))
+	{
+		EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
+		return;
+	}
+	if (!strncmp (buf, "NOTICE ", 7))
+	{
+		buf = word_eol[3];
+		if (*buf == ':')
+			buf++;
+		EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
+		return;
+	}
+
+	EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
+}
+
+/* irc_inline() - 1 single line received from serv */
+
+static void
+irc_inline (server *serv, char *buf, int len)
+{
+	session *sess, *tmp;
+	char *type, *text;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
+	char *pdibuf = pdibuf_static;
+
+	/* need more than 522? fall back to malloc */
+	if (len >= sizeof (pdibuf_static))
+		pdibuf = malloc (len + 1);
+
+	sess = serv->front_session;
+
+	/* Python relies on this */
+	word[PDIWORDS] = NULL;
+	word_eol[PDIWORDS] = NULL;
+
+	if (buf[0] == ':')
+	{
+		/* split line into words and words_to_end_of_line */
+		process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
+
+		/* find a context for this message */
+		if (is_channel (serv, word[3]))
+		{
+			tmp = find_channel (serv, word[3]);
+			if (tmp)
+				sess = tmp;
+		}
+
+		/* for server messages, the 2nd word is the "message type" */
+		type = word[2];
+
+		word[0] = type;
+		word_eol[1] = buf;	/* keep the ":" for plugins */
+		if (plugin_emit_server (sess, type, word, word_eol))
+			goto xit;
+		word[1]++;
+		word_eol[1] = buf + 1;	/* but not for xchat internally */
+
+	} else
+	{
+		process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
+		word[0] = type = word[1];
+		if (plugin_emit_server (sess, type, word, word_eol))
+			goto xit;
+	}
+
+	if (buf[0] != ':')
+	{
+		process_named_servermsg (sess, buf, word[0], word_eol);
+		goto xit;
+	}
+
+	/* see if the second word is a numeric */
+	if (isdigit ((unsigned char) word[2][0]))
+	{
+		text = word_eol[4];
+		if (*text == ':')
+			text++;
+
+		process_numeric (sess, atoi (word[2]), word, word_eol, text);
+	} else
+	{
+		process_named_msg (sess, type, word, word_eol);
+	}
+
+xit:
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+}
+
+void
+proto_fill_her_up (server *serv)
+{
+	serv->p_inline = irc_inline;
+	serv->p_invite = irc_invite;
+	serv->p_cycle = irc_cycle;
+	serv->p_ctcp = irc_ctcp;
+	serv->p_nctcp = irc_nctcp;
+	serv->p_quit = irc_quit;
+	serv->p_kick = irc_kick;
+	serv->p_part = irc_part;
+	serv->p_ns_identify = irc_ns_identify;
+	serv->p_ns_ghost = irc_ns_ghost;
+	serv->p_join = irc_join;
+	serv->p_join_list = irc_join_list;
+	serv->p_login = irc_login;
+	serv->p_join_info = irc_join_info;
+	serv->p_mode = irc_mode;
+	serv->p_user_list = irc_user_list;
+	serv->p_away_status = irc_away_status;
+	/*serv->p_get_ip = irc_get_ip;*/
+	serv->p_whois = irc_user_whois;
+	serv->p_get_ip = irc_user_list;
+	serv->p_get_ip_uh = irc_userhost;
+	serv->p_set_back = irc_set_back;
+	serv->p_set_away = irc_set_away;
+	serv->p_message = irc_message;
+	serv->p_action = irc_action;
+	serv->p_notice = irc_notice;
+	serv->p_topic = irc_topic;
+	serv->p_list_channels = irc_list_channels;
+	serv->p_change_nick = irc_change_nick;
+	serv->p_names = irc_names;
+	serv->p_ping = irc_ping;
+	serv->p_raw = irc_raw;
+	serv->p_cmp = rfc_casecmp;	/* can be changed by 005 in modes.c */
+}
diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h
new file mode 100644
index 00000000..90803594
--- /dev/null
+++ b/src/common/proto-irc.h
@@ -0,0 +1,6 @@
+#ifndef XCHAT_PROTO_H
+#define XCHAT_PROTO_H
+
+void proto_fill_her_up (server *serv);
+
+#endif
diff --git a/src/common/server.c b/src/common/server.c
new file mode 100644
index 00000000..dd43adff
--- /dev/null
+++ b/src/common/server.c
@@ -0,0 +1,2047 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+/*#define DEBUG_MSPROXY*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#ifndef WIN32
+#include <signal.h>
+#include <sys/wait.h>
+#else
+#include <winbase.h>
+#endif
+
+#include "xchat.h"
+#include "fe.h"
+#include "cfgfiles.h"
+#include "network.h"
+#include "notify.h"
+#include "xchatc.h"
+#include "inbound.h"
+#include "outbound.h"
+#include "text.h"
+#include "util.h"
+#include "url.h"
+#include "proto-irc.h"
+#include "servlist.h"
+#include "server.h"
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#include <openssl/err.h>		  /* ERR_() */
+#include "ssl.h"
+#endif
+
+#ifdef USE_MSPROXY
+#include "msproxy.h"
+#endif
+
+#ifdef WIN32
+#include "identd.c"
+#endif
+
+#ifdef USE_LIBPROXY
+#include <proxy.h>
+#endif
+
+#ifdef USE_OPENSSL
+extern SSL_CTX *ctx;				  /* xchat.c */
+/* local variables */
+static struct session *g_sess = NULL;
+#endif
+
+static GSList *away_list = NULL;
+GSList *serv_list = NULL;
+
+static void auto_reconnect (server *serv, int send_quit, int err);
+static void server_disconnect (session * sess, int sendquit, int err);
+static int server_cleanup (server * serv);
+static void server_connect (server *serv, char *hostname, int port, int no_login);
+
+#ifdef USE_LIBPROXY
+extern pxProxyFactory *libproxy_factory;
+#endif
+
+/* actually send to the socket. This might do a character translation or
+   send via SSL. server/dcc both use this function. */
+
+int
+tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len)
+{
+	int ret;
+	char *locale;
+	gsize loc_len;
+
+	if (encoding == NULL)	/* system */
+	{
+		locale = NULL;
+		if (!prefs.utf8_locale)
+		{
+			const gchar *charset;
+
+			g_get_charset (&charset);
+			locale = g_convert_with_fallback (buf, len, charset, "UTF-8",
+														 "?", 0, &loc_len, 0);
+		}
+	} else
+	{
+		if (using_irc)	/* using "IRC" encoding (CP1252/UTF-8 hybrid) */
+			/* if all chars fit inside CP1252, use that. Otherwise this
+			   returns NULL and we send UTF-8. */
+			locale = g_convert (buf, len, "CP1252", "UTF-8", 0, &loc_len, 0);
+		else
+			locale = g_convert_with_fallback (buf, len, encoding, "UTF-8",
+														 "?", 0, &loc_len, 0);
+	}
+
+	if (locale)
+	{
+		len = loc_len;
+#ifdef USE_OPENSSL
+		if (!ssl)
+			ret = send (sok, locale, len, 0);
+		else
+			ret = _SSL_send (ssl, locale, len);
+#else
+		ret = send (sok, locale, len, 0);
+#endif
+		g_free (locale);
+	} else
+	{
+#ifdef USE_OPENSSL
+		if (!ssl)
+			ret = send (sok, buf, len, 0);
+		else
+			ret = _SSL_send (ssl, buf, len);
+#else
+		ret = send (sok, buf, len, 0);
+#endif
+	}
+
+	return ret;
+}
+
+static int
+server_send_real (server *serv, char *buf, int len)
+{
+	fe_add_rawlog (serv, buf, len, TRUE);
+
+	return tcp_send_real (serv->ssl, serv->sok, serv->encoding, serv->using_irc,
+								 buf, len);
+}
+
+/* new throttling system, uses the same method as the Undernet
+   ircu2.10 server; under test, a 200-line paste didn't flood
+   off the client */
+
+static int
+tcp_send_queue (server *serv)
+{
+	char *buf, *p;
+	int len, i, pri;
+	GSList *list;
+	time_t now = time (0);
+
+	/* did the server close since the timeout was added? */
+	if (!is_server (serv))
+		return 0;
+
+	/* try priority 2,1,0 */
+	pri = 2;
+	while (pri >= 0)
+	{
+		list = serv->outbound_queue;
+		while (list)
+		{
+			buf = (char *) list->data;
+			if (buf[0] == pri)
+			{
+				buf++;	/* skip the priority byte */
+				len = strlen (buf);
+
+				if (serv->next_send < now)
+					serv->next_send = now;
+				if (serv->next_send - now >= 10)
+				{
+					/* check for clock skew */
+					if (now >= serv->prev_now)
+						return 1;		  /* don't remove the timeout handler */
+					/* it is skewed, reset to something sane */
+					serv->next_send = now;
+				}
+
+				for (p = buf, i = len; i && *p != ' '; p++, i--);
+				serv->next_send += (2 + i / 120);
+				serv->sendq_len -= len;
+				serv->prev_now = now;
+				fe_set_throttle (serv);
+
+				server_send_real (serv, buf, len);
+
+				buf--;
+				serv->outbound_queue = g_slist_remove (serv->outbound_queue, buf);
+				free (buf);
+				list = serv->outbound_queue;
+			} else
+			{
+				list = list->next;
+			}
+		}
+		/* now try pri 0 */
+		pri--;
+	}
+	return 0;						  /* remove the timeout handler */
+}
+
+int
+tcp_send_len (server *serv, char *buf, int len)
+{
+	char *dbuf;
+	int noqueue = !serv->outbound_queue;
+
+	if (!prefs.throttle)
+		return server_send_real (serv, buf, len);
+
+	dbuf = malloc (len + 2);	/* first byte is the priority */
+	dbuf[0] = 2;	/* pri 2 for most things */
+	memcpy (dbuf + 1, buf, len);
+	dbuf[len + 1] = 0;
+
+	/* privmsg and notice get a lower priority */
+	if (strncasecmp (dbuf + 1, "PRIVMSG", 7) == 0 ||
+		 strncasecmp (dbuf + 1, "NOTICE", 6) == 0)
+	{
+		dbuf[0] = 1;
+	}
+	else
+	{
+		/* WHO/MODE get the lowest priority */
+		if (strncasecmp (dbuf + 1, "WHO ", 4) == 0 ||
+		/* but only MODE queries, not changes */
+			(strncasecmp (dbuf + 1, "MODE", 4) == 0 &&
+			 strchr (dbuf, '-') == NULL &&
+			 strchr (dbuf, '+') == NULL))
+			dbuf[0] = 0;
+	}
+
+	serv->outbound_queue = g_slist_append (serv->outbound_queue, dbuf);
+	serv->sendq_len += len; /* tcp_send_queue uses strlen */
+
+	if (tcp_send_queue (serv) && noqueue)
+		fe_timeout_add (500, tcp_send_queue, serv);
+
+	return 1;
+}
+
+/*int
+tcp_send (server *serv, char *buf)
+{
+	return tcp_send_len (serv, buf, strlen (buf));
+}*/
+
+void
+tcp_sendf (server *serv, char *fmt, ...)
+{
+	va_list args;
+	/* keep this buffer in BSS. Converting UTF-8 to ISO-8859-x might make the
+      string shorter, so allow alot more than 512 for now. */
+	static char send_buf[1540];	/* good code hey (no it's not overflowable) */
+	int len;
+
+	va_start (args, fmt);
+	len = vsnprintf (send_buf, sizeof (send_buf) - 1, fmt, args);
+	va_end (args);
+
+	send_buf[sizeof (send_buf) - 1] = '\0';
+	if (len < 0 || len > (sizeof (send_buf) - 1))
+		len = strlen (send_buf);
+
+	tcp_send_len (serv, send_buf, len);
+}
+
+static int
+close_socket_cb (gpointer sok)
+{
+	closesocket (GPOINTER_TO_INT (sok));
+	return 0;
+}
+
+static void
+close_socket (int sok)
+{
+	/* close the socket in 5 seconds so the QUIT message is not lost */
+	fe_timeout_add (5000, close_socket_cb, GINT_TO_POINTER (sok));
+}
+
+/* handle 1 line of text received from the server */
+
+static void
+server_inline (server *serv, char *line, int len)
+{
+	char *utf_line_allocated = NULL;
+
+	/* Checks whether we're set to use UTF-8 charset */
+	if (serv->using_irc ||				/* 1. using CP1252/UTF-8 Hybrid */
+		(serv->encoding == NULL && prefs.utf8_locale) || /* OR 2. using system default->UTF-8 */
+	    (serv->encoding != NULL &&				/* OR 3. explicitly set to UTF-8 */
+		 (strcasecmp (serv->encoding, "UTF8") == 0 ||
+		  strcasecmp (serv->encoding, "UTF-8") == 0)))
+	{
+		/* The user has the UTF-8 charset set, either via /charset
+		command or from his UTF-8 locale. Thus, we first try the
+		UTF-8 charset, and if we fail to convert, we assume
+		it to be ISO-8859-1 (see text_validate). */
+
+		utf_line_allocated = text_validate (&line, &len);
+
+	} else
+	{
+		/* Since the user has an explicit charset set, either
+		via /charset command or from his non-UTF8 locale,
+		we don't fallback to ISO-8859-1 and instead try to remove
+		errnoeous octets till the string is convertable in the
+		said charset. */
+
+		const char *encoding = NULL;
+
+		if (serv->encoding != NULL)
+			encoding = serv->encoding;
+		else
+			g_get_charset (&encoding);
+
+		if (encoding != NULL)
+		{
+			char *conv_line; /* holds a copy of the original string */
+			int conv_len; /* tells g_convert how much of line to convert */
+			gsize utf_len;
+			gsize read_len;
+			GError *err;
+			gboolean retry;
+
+			conv_line = g_malloc (len + 1);
+			memcpy (conv_line, line, len);
+			conv_line[len] = 0;
+			conv_len = len;
+
+			/* if CP1255, convert it with the NUL terminator.
+				Works around SF bug #1122089 */
+			if (serv->using_cp1255)
+				conv_len++;
+
+			do
+			{
+				err = NULL;
+				retry = FALSE;
+				utf_line_allocated = g_convert_with_fallback (conv_line, conv_len, "UTF-8", encoding, "?", &read_len, &utf_len, &err);
+				if (err != NULL)
+				{
+					if (err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && conv_len > (read_len + 1))
+					{
+						/* Make our best bet by removing the erroneous char.
+						   This will work for casual 8-bit strings with non-standard chars. */
+						memmove (conv_line + read_len, conv_line + read_len + 1, conv_len - read_len -1);
+						conv_len--;
+						retry = TRUE;
+					}
+					g_error_free (err);
+				}
+			} while (retry);
+
+			g_free (conv_line);
+
+			/* If any conversion has occured at all. Conversion might fail
+			due to errors other than invalid sequences, e.g. unknown charset. */
+			if (utf_line_allocated != NULL)
+			{
+				line = utf_line_allocated;
+				len = utf_len;
+				if (serv->using_cp1255 && len > 0)
+					len--;
+			}
+			else
+			{
+				/* If all fails, treat as UTF-8 with fallback to ISO-8859-1. */
+				utf_line_allocated = text_validate (&line, &len);
+			}
+		}
+	}
+
+	fe_add_rawlog (serv, line, len, FALSE);
+	url_check_line (line, len);
+
+	/* let proto-irc.c handle it */
+	serv->p_inline (serv, line, len);
+
+	if (utf_line_allocated != NULL) /* only if a special copy was allocated */
+		g_free (utf_line_allocated);
+}
+
+/* read data from socket */
+
+static gboolean
+server_read (GIOChannel *source, GIOCondition condition, server *serv)
+{
+	int sok = serv->sok;
+	int error, i, len;
+	char lbuf[2050];
+
+	while (1)
+	{
+#ifdef USE_OPENSSL
+		if (!serv->ssl)
+#endif
+			len = recv (sok, lbuf, sizeof (lbuf) - 2, 0);
+#ifdef USE_OPENSSL
+		else
+			len = _SSL_recv (serv->ssl, lbuf, sizeof (lbuf) - 2);
+#endif
+		if (len < 1)
+		{
+			error = 0;
+			if (len < 0)
+			{
+				if (would_block ())
+					return TRUE;
+				error = sock_error ();
+			}
+			if (!serv->end_of_motd)
+			{
+				server_disconnect (serv->server_session, FALSE, error);
+				if (!servlist_cycle (serv))
+				{
+					if (prefs.autoreconnect)
+						auto_reconnect (serv, FALSE, error);
+				}
+			} else
+			{
+				if (prefs.autoreconnect)
+					auto_reconnect (serv, FALSE, error);
+				else
+					server_disconnect (serv->server_session, FALSE, error);
+			}
+			return TRUE;
+		}
+
+		i = 0;
+
+		lbuf[len] = 0;
+
+		while (i < len)
+		{
+			switch (lbuf[i])
+			{
+			case '\r':
+				break;
+
+			case '\n':
+				serv->linebuf[serv->pos] = 0;
+				server_inline (serv, serv->linebuf, serv->pos);
+				serv->pos = 0;
+				break;
+
+			default:
+				serv->linebuf[serv->pos] = lbuf[i];
+				if (serv->pos >= (sizeof (serv->linebuf) - 1))
+					fprintf (stderr,
+								"*** XCHAT WARNING: Buffer overflow - shit server!\n");
+				else
+					serv->pos++;
+			}
+			i++;
+		}
+	}
+}
+
+static void
+server_connected (server * serv)
+{
+	prefs.wait_on_exit = TRUE;
+	serv->ping_recv = time (0);
+	serv->connected = TRUE;
+	set_nonblocking (serv->sok);
+	serv->iotag = fe_input_add (serv->sok, FIA_READ|FIA_EX, server_read, serv);
+	if (!serv->no_login)
+	{
+		EMIT_SIGNAL (XP_TE_CONNECTED, serv->server_session, NULL, NULL, NULL,
+						 NULL, 0);
+		if (serv->network)
+		{
+			serv->p_login (serv,
+								(!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) &&
+								 (((ircnet *)serv->network)->user)) ?
+								(((ircnet *)serv->network)->user) :
+								prefs.username,
+								(!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) &&
+								 (((ircnet *)serv->network)->real)) ?
+								(((ircnet *)serv->network)->real) :
+								prefs.realname);
+		} else
+		{
+			serv->p_login (serv, prefs.username, prefs.realname);
+		}
+	} else
+	{
+		EMIT_SIGNAL (XP_TE_SERVERCONNECTED, serv->server_session, NULL, NULL,
+						 NULL, NULL, 0);
+	}
+
+	server_set_name (serv, serv->servername);
+	fe_server_event (serv, FE_SE_CONNECT, 0);
+}
+
+#ifdef WIN32
+
+static gboolean
+server_close_pipe (int *pipefd)	/* see comments below */
+{
+	close (pipefd[0]);	/* close WRITE end first to cause an EOF on READ */
+	close (pipefd[1]);	/* in giowin32, and end that thread. */
+	free (pipefd);
+	return FALSE;
+}
+
+#endif
+
+static void
+server_stopconnecting (server * serv)
+{
+	if (serv->iotag)
+	{
+		fe_input_remove (serv->iotag);
+		serv->iotag = 0;
+	}
+
+	if (serv->joindelay_tag)
+	{
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+	}
+
+#ifndef WIN32
+	/* kill the child process trying to connect */
+	kill (serv->childpid, SIGKILL);
+	waitpid (serv->childpid, NULL, 0);
+
+	close (serv->childwrite);
+	close (serv->childread);
+#else
+	PostThreadMessage (serv->childpid, WM_QUIT, 0, 0);
+
+	{
+		/* if we close the pipe now, giowin32 will crash. */
+		int *pipefd = malloc (sizeof (int) * 2);
+		pipefd[0] = serv->childwrite;
+		pipefd[1] = serv->childread;
+		g_idle_add ((GSourceFunc)server_close_pipe, pipefd);
+	}
+#endif
+
+#ifdef USE_OPENSSL
+	if (serv->ssl_do_connect_tag)
+	{
+		fe_timeout_remove (serv->ssl_do_connect_tag);
+		serv->ssl_do_connect_tag = 0;
+	}
+#endif
+
+	fe_progressbar_end (serv);
+
+	serv->connecting = FALSE;
+	fe_server_event (serv, FE_SE_DISCONNECT, 0);
+}
+
+#ifdef USE_OPENSSL
+#define	SSLTMOUT	90				  /* seconds */
+static void
+ssl_cb_info (SSL * s, int where, int ret)
+{
+/*	char buf[128];*/
+
+
+	return;							  /* FIXME: make debug level adjustable in serverlist or settings */
+
+/*	snprintf (buf, sizeof (buf), "%s (%d)", SSL_state_string_long (s), where);
+	if (g_sess)
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+	else
+		fprintf (stderr, "%s\n", buf);*/
+}
+
+static int
+ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
+{
+	char subject[256];
+	char issuer[256];
+	char buf[512];
+
+
+	X509_NAME_oneline (X509_get_subject_name (ctx->current_cert), subject,
+							 sizeof (subject));
+	X509_NAME_oneline (X509_get_issuer_name (ctx->current_cert), issuer,
+							 sizeof (issuer));
+
+	snprintf (buf, sizeof (buf), "* Subject: %s", subject);
+	EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+	snprintf (buf, sizeof (buf), "* Issuer: %s", issuer);
+	EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+
+	return (TRUE);					  /* always ok */
+}
+
+static int
+ssl_do_connect (server * serv)
+{
+	char buf[128];
+
+	g_sess = serv->server_session;
+	if (SSL_connect (serv->ssl) <= 0)
+	{
+		char err_buf[128];
+		int err;
+
+		g_sess = NULL;
+		if ((err = ERR_get_error ()) > 0)
+		{
+			ERR_error_string (err, err_buf);
+			snprintf (buf, sizeof (buf), "(%d) %s", err, err_buf);
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL,
+							 NULL, NULL, 0);
+
+			if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER)
+				PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n"));
+
+			server_cleanup (serv);
+
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+
+			return (0);				  /* remove it (0) */
+		}
+	}
+	g_sess = NULL;
+
+	if (SSL_is_init_finished (serv->ssl))
+	{
+		struct cert_info cert_info;
+		struct chiper_info *chiper_info;
+		int verify_error;
+		int i;
+
+		if (!_SSL_get_cert_info (&cert_info, serv->ssl))
+		{
+			snprintf (buf, sizeof (buf), "* Certification info:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			snprintf (buf, sizeof (buf), "  Subject:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			for (i = 0; cert_info.subject_word[i]; i++)
+			{
+				snprintf (buf, sizeof (buf), "    %s", cert_info.subject_word[i]);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}
+			snprintf (buf, sizeof (buf), "  Issuer:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			for (i = 0; cert_info.issuer_word[i]; i++)
+			{
+				snprintf (buf, sizeof (buf), "    %s", cert_info.issuer_word[i]);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}
+			snprintf (buf, sizeof (buf), "  Public key algorithm: %s (%d bits)",
+						 cert_info.algorithm, cert_info.algorithm_bits);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			/*if (cert_info.rsa_tmp_bits)
+			{
+				snprintf (buf, sizeof (buf),
+							 "  Public key algorithm uses ephemeral key with %d bits",
+							 cert_info.rsa_tmp_bits);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}*/
+			snprintf (buf, sizeof (buf), "  Sign algorithm %s",
+						 cert_info.sign_algorithm/*, cert_info.sign_algorithm_bits*/);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			snprintf (buf, sizeof (buf), "  Valid since %s to %s",
+						 cert_info.notbefore, cert_info.notafter);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+		} else
+		{
+			snprintf (buf, sizeof (buf), " * No Certificate");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+		}
+
+		chiper_info = _SSL_get_cipher_info (serv->ssl);	/* static buffer */
+		snprintf (buf, sizeof (buf), "* Cipher info:");
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL,
+						 0);
+		snprintf (buf, sizeof (buf), "  Version: %s, cipher %s (%u bits)",
+					 chiper_info->version, chiper_info->chiper,
+					 chiper_info->chiper_bits);
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL,
+						 0);
+
+		verify_error = SSL_get_verify_result (serv->ssl);
+		switch (verify_error)
+		{
+		case X509_V_OK:
+			/* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */
+			/* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */
+			break;
+		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+		case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+		case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+		case X509_V_ERR_CERT_HAS_EXPIRED:
+			if (serv->accept_invalid_cert)
+			{
+				snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored",
+							 X509_verify_cert_error_string (verify_error),
+							 verify_error);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+				break;
+			}
+		default:
+			snprintf (buf, sizeof (buf), "%s.? (%d)",
+						 X509_verify_cert_error_string (verify_error),
+						 verify_error);
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+
+			server_cleanup (serv);
+
+			return (0);
+		}
+
+		server_stopconnecting (serv);
+
+		/* activate gtk poll */
+		server_connected (serv);
+
+		return (0);					  /* remove it (0) */
+	} else
+	{
+		if (serv->ssl->session && serv->ssl->session->time + SSLTMOUT < time (NULL))
+		{
+			snprintf (buf, sizeof (buf), "SSL handshake timed out");
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL,
+							 NULL, NULL, 0);
+			server_cleanup (serv); /* ->connecting = FALSE */
+
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+
+			return (0);				  /* remove it (0) */
+		}
+
+		return (1);					  /* call it more (1) */
+	}
+}
+#endif
+
+static int
+timeout_auto_reconnect (server *serv)
+{
+	if (is_server (serv))  /* make sure it hasnt been closed during the delay */
+	{
+		serv->recondelay_tag = 0;
+		if (!serv->connected && !serv->connecting && serv->server_session)
+		{
+			server_connect (serv, serv->hostname, serv->port, FALSE);
+		}
+	}
+	return 0;			  /* returning 0 should remove the timeout handler */
+}
+
+static void
+auto_reconnect (server *serv, int send_quit, int err)
+{
+	session *s;
+	GSList *list;
+	int del;
+
+	if (serv->server_session == NULL)
+		return;
+
+	list = sess_list;
+	while (list)				  /* make sure auto rejoin can work */
+	{
+		s = list->data;
+		if (s->type == SESS_CHANNEL && s->channel[0])
+		{
+			strcpy (s->waitchannel, s->channel);
+			strcpy (s->willjoinchannel, s->channel);
+		}
+		list = list->next;
+	}
+
+	if (serv->connected)
+		server_disconnect (serv->server_session, send_quit, err);
+
+	del = prefs.recon_delay * 1000;
+	if (del < 1000)
+		del = 500;				  /* so it doesn't block the gui */
+
+#ifndef WIN32
+	if (err == -1 || err == 0 || err == ECONNRESET || err == ETIMEDOUT)
+#else
+	if (err == -1 || err == 0 || err == WSAECONNRESET || err == WSAETIMEDOUT)
+#endif
+		serv->reconnect_away = serv->is_away;
+
+	/* is this server in a reconnect delay? remove it! */
+	if (serv->recondelay_tag)
+	{
+		fe_timeout_remove (serv->recondelay_tag);
+		serv->recondelay_tag = 0;
+	}
+
+	serv->recondelay_tag = fe_timeout_add (del, timeout_auto_reconnect, serv);
+	fe_server_event (serv, FE_SE_RECONDELAY, del);
+}
+
+static void
+server_flush_queue (server *serv)
+{
+	list_free (&serv->outbound_queue);
+	serv->sendq_len = 0;
+	fe_set_throttle (serv);
+}
+
+#ifdef WIN32
+
+static int
+waitline2 (GIOChannel *source, char *buf, int bufsize)
+{
+	int i = 0;
+	int len;
+
+	while (1)
+	{
+		if (g_io_channel_read (source, &buf[i], 1, &len) != G_IO_ERROR_NONE)
+			return -1;
+		if (buf[i] == '\n' || bufsize == i + 1)
+		{
+			buf[i] = 0;
+			return i;
+		}
+		i++;
+	}
+}
+
+#else
+
+#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0)
+
+#endif
+
+/* connect() successed */
+
+static void
+server_connect_success (server *serv)
+{
+#ifdef USE_OPENSSL
+#define	SSLDOCONNTMOUT	300
+	if (serv->use_ssl)
+	{
+		char *err;
+
+		/* it'll be a memory leak, if connection isn't terminated by
+		   server_cleanup() */
+		serv->ssl = _SSL_socket (ctx, serv->sok);
+		if ((err = _SSL_set_verify (ctx, ssl_cb_verify, NULL)))
+		{
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL,
+							 NULL, NULL, 0);
+			server_cleanup (serv);	/* ->connecting = FALSE */
+			return;
+		}
+		/* FIXME: it'll be needed by new servers */
+		/* send(serv->sok, "STLS\r\n", 6, 0); sleep(1); */
+		set_nonblocking (serv->sok);
+		serv->ssl_do_connect_tag = fe_timeout_add (SSLDOCONNTMOUT,
+																 ssl_do_connect, serv);
+		return;
+	}
+
+	serv->ssl = NULL;
+#endif
+	server_stopconnecting (serv);	/* ->connecting = FALSE */
+	/* activate glib poll */
+	server_connected (serv);
+}
+
+/* receive info from the child-process about connection progress */
+
+static gboolean
+server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
+{
+	session *sess = serv->server_session;
+	char tbuf[128];
+	char outbuf[512];
+	char host[100];
+	char ip[100];
+	char *p;
+
+	waitline2 (source, tbuf, sizeof tbuf);
+
+	switch (tbuf[0])
+	{
+	case '0':	/* print some text */
+		waitline2 (source, tbuf, sizeof tbuf);
+		PrintText (serv->server_session, tbuf);
+		break;
+	case '1':						  /* unknown host */
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+#ifdef USE_IPV6
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+#endif
+		EMIT_SIGNAL (XP_TE_UKNHOST, sess, NULL, NULL, NULL, NULL, 0);
+		if (!servlist_cycle (serv))
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+		break;
+	case '2':						  /* connection failed */
+		waitline2 (source, tbuf, sizeof tbuf);
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+#ifdef USE_IPV6
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+#endif
+		EMIT_SIGNAL (XP_TE_CONNFAIL, sess, errorstring (atoi (tbuf)), NULL,
+						 NULL, NULL, 0);
+		if (!servlist_cycle (serv))
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+		break;
+	case '3':						  /* gethostbyname finished */
+		waitline2 (source, host, sizeof host);
+		waitline2 (source, ip, sizeof ip);
+		waitline2 (source, outbuf, sizeof outbuf);
+		EMIT_SIGNAL (XP_TE_CONNECT, sess, host, ip, outbuf, NULL, 0);
+#ifdef WIN32
+		if (prefs.identd)
+		{
+			if (serv->network)
+				identd_start ((((ircnet *)serv->network)->user) ?
+									(((ircnet *)serv->network)->user) :
+									prefs.username);
+			else
+				identd_start (prefs.username);
+		}
+#else
+		snprintf (outbuf, sizeof (outbuf), "%s/auth/xchat_auth",
+					 g_get_home_dir ());
+		if (access (outbuf, X_OK) == 0)
+		{
+			snprintf (outbuf, sizeof (outbuf), "exec -d %s/auth/xchat_auth %s",
+						 g_get_home_dir (), prefs.username);
+			handle_command (serv->server_session, outbuf, FALSE);
+		}
+#endif
+		break;
+	case '4':						  /* success */
+		waitline2 (source, tbuf, sizeof (tbuf));
+#ifdef USE_MSPROXY
+		serv->sok = strtol (tbuf, &p, 10);
+		if (*p++ == ' ')
+		{
+			serv->proxy_sok = strtol (p, &p, 10);
+			serv->msp_state.clientid = strtol (++p, &p, 10);
+			serv->msp_state.serverid = strtol (++p, &p, 10);
+			serv->msp_state.seq_sent = atoi (++p);
+		} else
+			serv->proxy_sok = -1;
+#ifdef DEBUG_MSPROXY
+		printf ("Parent got main socket: %d, proxy socket: %d\n", serv->sok, serv->proxy_sok);
+		printf ("Client ID 0x%08x server ID 0x%08x seq_sent %d\n", serv->msp_state.clientid, serv->msp_state.serverid, serv->msp_state.seq_sent);
+#endif
+#else
+		serv->sok = atoi (tbuf);
+#endif
+#ifdef USE_IPV6
+		/* close the one we didn't end up using */
+		if (serv->sok == serv->sok4)
+			closesocket (serv->sok6);
+		else
+			closesocket (serv->sok4);
+		if (serv->proxy_sok != -1)
+		{
+			if (serv->proxy_sok == serv->proxy_sok4)
+				closesocket (serv->proxy_sok6);
+			else
+				closesocket (serv->proxy_sok4);
+		}
+#endif
+		server_connect_success (serv);
+		break;
+	case '5':						  /* prefs ip discovered */
+		waitline2 (source, tbuf, sizeof tbuf);
+		prefs.local_ip = inet_addr (tbuf);
+		break;
+	case '7':						  /* gethostbyname (prefs.hostname) failed */
+		sprintf (outbuf,
+					_("Cannot resolve hostname %s\nCheck your IP Settings!\n"),
+					prefs.hostname);
+		PrintText (sess, outbuf);
+		break;
+	case '8':
+		PrintText (sess, _("Proxy traversal failed.\n"));
+		server_disconnect (sess, FALSE, -1);
+		break;
+	case '9':
+		waitline2 (source, tbuf, sizeof tbuf);
+		EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, tbuf, NULL, NULL, NULL, 0);
+		break;
+	}
+
+	return TRUE;
+}
+
+/* kill all sockets & iotags of a server. Stop a connection attempt, or
+   disconnect if already connected. */
+
+static int
+server_cleanup (server * serv)
+{
+	fe_set_lag (serv, 0.0);
+
+	if (serv->iotag)
+	{
+		fe_input_remove (serv->iotag);
+		serv->iotag = 0;
+	}
+
+	if (serv->joindelay_tag)
+	{
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+	}
+
+#ifdef USE_OPENSSL
+	if (serv->ssl)
+	{
+		_SSL_close (serv->ssl);
+		serv->ssl = NULL;
+	}
+#endif
+
+	if (serv->connecting)
+	{
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+		return 1;
+	}
+
+	if (serv->connected)
+	{
+		close_socket (serv->sok);
+		if (serv->proxy_sok)
+			close_socket (serv->proxy_sok);
+		serv->connected = FALSE;
+		serv->end_of_motd = FALSE;
+		return 2;
+	}
+
+	/* is this server in a reconnect delay? remove it! */
+	if (serv->recondelay_tag)
+	{
+		fe_timeout_remove (serv->recondelay_tag);
+		serv->recondelay_tag = 0;
+		return 3;
+	}
+
+	return 0;
+}
+
+static void
+server_disconnect (session * sess, int sendquit, int err)
+{
+	server *serv = sess->server;
+	GSList *list;
+	char tbuf[64];
+	gboolean shutup = FALSE;
+
+	/* send our QUIT reason */
+	if (sendquit && serv->connected)
+	{
+		server_sendquit (sess);
+	}
+
+	fe_server_event (serv, FE_SE_DISCONNECT, 0);
+
+	/* close all sockets & io tags */
+	switch (server_cleanup (serv))
+	{
+	case 0:							  /* it wasn't even connected! */
+		notc_msg (sess);
+		return;
+	case 1:							  /* it was in the process of connecting */
+		sprintf (tbuf, "%d", sess->server->childpid);
+		EMIT_SIGNAL (XP_TE_STOPCONNECT, sess, tbuf, NULL, NULL, NULL, 0);
+		return;
+	case 3:
+		shutup = TRUE;	/* won't print "disconnected" in channels */
+	}
+
+	server_flush_queue (serv);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = (struct session *) list->data;
+		if (sess->server == serv)
+		{
+			if (!shutup || sess->type == SESS_SERVER)
+				/* print "Disconnected" to each window using this server */
+				EMIT_SIGNAL (XP_TE_DISCON, sess, errorstring (err), NULL, NULL, NULL, 0);
+
+			if (!sess->channel[0] || sess->type == SESS_CHANNEL)
+				clear_channel (sess);
+		}
+		list = list->next;
+	}
+
+	serv->pos = 0;
+	serv->motd_skipped = FALSE;
+	serv->no_login = FALSE;
+	serv->servername[0] = 0;
+	serv->lag_sent = 0;
+
+	notify_cleanup ();
+}
+
+/* send a "print text" command to the parent process - MUST END IN \n! */
+
+static void
+proxy_error (int fd, char *msg)
+{
+	write (fd, "0\n", 2);
+	write (fd, msg, strlen (msg));
+}
+
+struct sock_connect
+{
+	char version;
+	char type;
+	guint16 port;
+	guint32 address;
+	char username[10];
+};
+
+/* traverse_socks() returns:
+ *				0 success                *
+ *          1 socks traversal failed */
+
+static int
+traverse_socks (int print_fd, int sok, char *serverAddr, int port)
+{
+	struct sock_connect sc;
+	unsigned char buf[256];
+
+	sc.version = 4;
+	sc.type = 1;
+	sc.port = htons (port);
+	sc.address = inet_addr (serverAddr);
+	strncpy (sc.username, prefs.username, 9);
+
+	send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
+	buf[1] = 0;
+	recv (sok, buf, 10, 0);
+	if (buf[1] == 90)
+		return 0;
+
+	snprintf (buf, sizeof (buf), "SOCKS\tServer reported error %d,%d.\n", buf[0], buf[1]);
+	proxy_error (print_fd, buf);
+	return 1;
+}
+
+struct sock5_connect1
+{
+	char version;
+	char nmethods;
+	char method;
+};
+
+static int
+traverse_socks5 (int print_fd, int sok, char *serverAddr, int port)
+{
+	struct sock5_connect1 sc1;
+	unsigned char *sc2;
+	unsigned int packetlen, addrlen;
+	unsigned char buf[260];
+	int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0];
+
+	sc1.version = 5;
+	sc1.nmethods = 1;
+	if (auth)
+		sc1.method = 2;  /* Username/Password Authentication (UPA) */
+	else
+		sc1.method = 0;  /* NO Authentication */
+	send (sok, (char *) &sc1, 3, 0);
+	if (recv (sok, buf, 2, 0) != 2)
+		goto read_error;
+
+	if (buf[0] != 5)
+	{
+		proxy_error (print_fd, "SOCKS\tServer is not socks version 5.\n");
+		return 1;
+	}
+
+	/* did the server say no auth required? */
+	if (buf[1] == 0)
+		auth = 0;
+
+	if (auth)
+	{
+		int len_u=0, len_p=0;
+
+		/* authentication sub-negotiation (RFC1929) */
+		if (buf[1] != 2)  /* UPA not supported by server */
+		{
+			proxy_error (print_fd, "SOCKS\tServer doesn't support UPA authentication.\n");
+			return 1;
+		}
+
+		memset (buf, 0, sizeof(buf));
+
+		/* form the UPA request */
+		len_u = strlen (prefs.proxy_user);
+		len_p = strlen (prefs.proxy_pass);
+		buf[0] = 1;
+		buf[1] = len_u;
+		memcpy (buf + 2, prefs.proxy_user, len_u);
+		buf[2 + len_u] = len_p;
+		memcpy (buf + 3 + len_u, prefs.proxy_pass, len_p);
+
+		send (sok, buf, 3 + len_u + len_p, 0);
+		if ( recv (sok, buf, 2, 0) != 2 )
+			goto read_error;
+		if ( buf[1] != 0 )
+		{
+			proxy_error (print_fd, "SOCKS\tAuthentication failed. "
+							 "Is username and password correct?\n");
+			return 1; /* UPA failed! */
+		}
+	}
+	else
+	{
+		if (buf[1] != 0)
+		{
+			proxy_error (print_fd, "SOCKS\tAuthentication required but disabled in settings.\n");
+			return 1;
+		}
+	}
+
+	addrlen = strlen (serverAddr);
+	packetlen = 4 + 1 + addrlen + 2;
+	sc2 = malloc (packetlen);
+	sc2[0] = 5;						  /* version */
+	sc2[1] = 1;						  /* command */
+	sc2[2] = 0;						  /* reserved */
+	sc2[3] = 3;						  /* address type */
+	sc2[4] = (unsigned char) addrlen;	/* hostname length */
+	memcpy (sc2 + 5, serverAddr, addrlen);
+	*((unsigned short *) (sc2 + 5 + addrlen)) = htons (port);
+	send (sok, sc2, packetlen, 0);
+	free (sc2);
+
+	/* consume all of the reply */
+	if (recv (sok, buf, 4, 0) != 4)
+		goto read_error;
+	if (buf[0] != 5 || buf[1] != 0)
+	{
+		if (buf[1] == 2)
+			snprintf (buf, sizeof (buf), "SOCKS\tProxy refused to connect to host (not allowed).\n");
+		else
+			snprintf (buf, sizeof (buf), "SOCKS\tProxy failed to connect to host (error %d).\n", buf[1]);
+		proxy_error (print_fd, buf);
+		return 1;
+	}
+	if (buf[3] == 1)	/* IPV4 32bit address */
+	{
+		if (recv (sok, buf, 6, 0) != 6)
+			goto read_error;
+	} else if (buf[3] == 4)	/* IPV6 128bit address */
+	{
+		if (recv (sok, buf, 18, 0) != 18)
+			goto read_error;
+	} else if (buf[3] == 3)	/* string, 1st byte is size */
+	{
+		if (recv (sok, buf, 1, 0) != 1)	/* read the string size */
+			goto read_error;
+		packetlen = buf[0] + 2;	/* can't exceed 260 */
+		if (recv (sok, buf, packetlen, 0) != packetlen)
+			goto read_error;
+	}
+
+	return 0;	/* success */
+
+read_error:
+	proxy_error (print_fd, "SOCKS\tRead error from server.\n");
+	return 1;
+}
+
+static int
+traverse_wingate (int print_fd, int sok, char *serverAddr, int port)
+{
+	char buf[128];
+
+	snprintf (buf, sizeof (buf), "%s %d\r\n", serverAddr, port);
+	send (sok, buf, strlen (buf), 0);
+
+	return 0;
+}
+
+/* stuff for HTTP auth is here */
+
+static void
+three_to_four (char *from, char *to)
+{
+	static const char tab64[64]=
+	{
+		'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+		'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+		'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+		'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+	};
+
+	to[0] = tab64 [ (from[0] >> 2) & 63 ];
+	to[1] = tab64 [ ((from[0] << 4) | (from[1] >> 4)) & 63 ];
+	to[2] = tab64 [ ((from[1] << 2) | (from[2] >> 6)) & 63 ];
+	to[3] = tab64 [ from[2] & 63 ];
+};
+
+void
+base64_encode (char *to, char *from, unsigned int len)
+{
+	while (len >= 3)
+	{
+		three_to_four (from, to);
+		len -= 3;
+		from += 3;
+		to += 4;
+	}
+	if (len)
+	{
+		char three[3]={0,0,0};
+		int i=0;
+		for (i=0;i<len;i++)
+			three[i] = *from++;
+		three_to_four (three, to);
+		if (len == 1)
+			to[2] = to[3] = '=';
+		else if (len == 2)
+			to[3] = '=';
+		to += 4;
+	};
+	to[0] = 0;
+}
+
+static int
+http_read_line (int print_fd, int sok, char *buf, int len)
+{
+#ifdef WIN32
+	/* make sure waitline() uses recv() or it'll fail on win32 */
+	len = waitline (sok, buf, len, FALSE);
+#else
+	len = waitline (sok, buf, len, TRUE);
+#endif
+	if (len >= 1)
+	{
+		/* print the message out (send it to the parent process) */
+		write (print_fd, "0\n", 2);
+
+		if (buf[len-1] == '\r')
+		{
+			buf[len-1] = '\n';
+			write (print_fd, buf, len);
+		} else
+		{
+			write (print_fd, buf, len);
+			write (print_fd, "\n", 1);
+		}
+	}
+
+	return len;
+}
+
+static int
+traverse_http (int print_fd, int sok, char *serverAddr, int port)
+{
+	char buf[512];
+	char auth_data[256];
+	char auth_data2[252];
+	int n, n2;
+
+	n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n",
+					  serverAddr, port);
+	if (prefs.proxy_auth)
+	{
+		n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s",
+							prefs.proxy_user, prefs.proxy_pass);
+		base64_encode (auth_data, auth_data2, n2);
+		n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data);
+	}
+	n += snprintf (buf+n, sizeof (buf)-n, "\r\n");
+	send (sok, buf, n, 0);
+
+	n = http_read_line (print_fd, sok, buf, sizeof (buf));
+	/* "HTTP/1.0 200 OK" */
+	if (n < 12)
+		return 1;
+	if (memcmp (buf, "HTTP/", 5) || memcmp (buf + 9, "200", 3))
+		return 1;
+	while (1)
+	{
+		/* read until blank line */
+		n = http_read_line (print_fd, sok, buf, sizeof (buf));
+		if (n < 1 || (n == 1 && buf[0] == '\n'))
+			break;
+	}
+	return 0;
+}
+
+static int
+traverse_proxy (int proxy_type, int print_fd, int sok, char *ip, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound)
+{
+	switch (proxy_type)
+	{
+	case 1:
+		return traverse_wingate (print_fd, sok, ip, port);
+	case 2:
+		return traverse_socks (print_fd, sok, ip, port);
+	case 3:
+		return traverse_socks5 (print_fd, sok, ip, port);
+	case 4:
+		return traverse_http (print_fd, sok, ip, port);
+#ifdef USE_MSPROXY
+	case 5:
+		return traverse_msproxy (sok, ip, port, state, ns_proxy, csok4, csok6, csok, bound);
+#endif
+	}
+
+	return 1;
+}
+
+/* this is the child process making the connection attempt */
+
+static int
+server_child (server * serv)
+{
+	netstore *ns_server;
+	netstore *ns_proxy = NULL;
+	netstore *ns_local;
+	int port = serv->port;
+	int error;
+	int sok, psok;
+	char *hostname = serv->hostname;
+	char *real_hostname = NULL;
+	char *ip;
+	char *proxy_ip = NULL;
+	char *local_ip;
+	int connect_port;
+	char buf[512];
+	char bound = 0;
+	int proxy_type = 0;
+	char *proxy_host = NULL;
+	int proxy_port;
+
+	ns_server = net_store_new ();
+
+	/* is a hostname set? - bind to it */
+	if (prefs.hostname[0])
+	{
+		ns_local = net_store_new ();
+		local_ip = net_resolve (ns_local, prefs.hostname, 0, &real_hostname);
+		if (local_ip != NULL)
+		{
+			snprintf (buf, sizeof (buf), "5\n%s\n", local_ip);
+			write (serv->childwrite, buf, strlen (buf));
+			net_bind (ns_local, serv->sok4, serv->sok6);
+			bound = 1;
+		} else
+		{
+			write (serv->childwrite, "7\n", 2);
+		}
+		net_store_destroy (ns_local);
+	}
+
+	if (!serv->dont_use_proxy) /* blocked in serverlist? */
+	{
+		if (FALSE)
+			;
+#ifdef USE_LIBPROXY
+		else if (prefs.proxy_type == 5)
+		{
+			char **proxy_list;
+			char *url, *proxy;
+
+			url = g_strdup_printf ("irc://%s:%d", hostname, port);
+			proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url);
+
+			if (proxy_list) {
+				/* can use only one */
+				proxy = proxy_list[0];
+				if (!strncmp (proxy, "direct", 6))
+					proxy_type = 0;
+				else if (!strncmp (proxy, "http", 4))
+					proxy_type = 4;
+				else if (!strncmp (proxy, "socks5", 6))
+					proxy_type = 3;
+				else if (!strncmp (proxy, "socks", 5))
+					proxy_type = 2;
+			}
+
+			if (proxy_type) {
+				char *c;
+				c = strchr (proxy, ':') + 3;
+				proxy_host = strdup (c);
+				c = strchr (proxy_host, ':');
+				*c = '\0';
+				proxy_port = atoi (c + 1);
+			}
+
+			g_strfreev (proxy_list);
+			g_free (url);
+#endif
+		} else if (prefs.proxy_host[0] &&
+			   prefs.proxy_type > 0 &&
+			   prefs.proxy_use != 2) /* proxy is NOT dcc-only */
+		{
+			proxy_type = prefs.proxy_type;
+			proxy_host = strdup (prefs.proxy_host);
+			proxy_port = prefs.proxy_port;
+		}
+	}
+
+	serv->proxy_type = proxy_type;
+
+	/* first resolve where we want to connect to */
+	if (proxy_type > 0)
+	{
+		snprintf (buf, sizeof (buf), "9\n%s\n", proxy_host);
+		write (serv->childwrite, buf, strlen (buf));
+		ip = net_resolve (ns_server, proxy_host, proxy_port, &real_hostname);
+		free (proxy_host);
+		if (!ip)
+		{
+			write (serv->childwrite, "1\n", 2);
+			goto xit;
+		}
+		connect_port = proxy_port;
+
+		/* if using socks4 or MS Proxy, attempt to resolve ip for irc server */
+		if ((proxy_type == 2) || (proxy_type == 5))
+		{
+			ns_proxy = net_store_new ();
+			proxy_ip = net_resolve (ns_proxy, hostname, port, &real_hostname);
+			if (!proxy_ip)
+			{
+				write (serv->childwrite, "1\n", 2);
+				goto xit;
+			}
+		} else						  /* otherwise we can just use the hostname */
+			proxy_ip = strdup (hostname);
+	} else
+	{
+		ip = net_resolve (ns_server, hostname, port, &real_hostname);
+		if (!ip)
+		{
+			write (serv->childwrite, "1\n", 2);
+			goto xit;
+		}
+		connect_port = port;
+	}
+
+	snprintf (buf, sizeof (buf), "3\n%s\n%s\n%d\n",
+				 real_hostname, ip, connect_port);
+	write (serv->childwrite, buf, strlen (buf));
+
+	if (!serv->dont_use_proxy && (proxy_type == 5))
+		error = net_connect (ns_server, serv->proxy_sok4, serv->proxy_sok6, &psok);
+	else
+	{
+		error = net_connect (ns_server, serv->sok4, serv->sok6, &sok);
+		psok = sok;
+	}
+
+	if (error != 0)
+	{
+		snprintf (buf, sizeof (buf), "2\n%d\n", sock_error ());
+		write (serv->childwrite, buf, strlen (buf));
+	} else
+	{
+		/* connect succeeded */
+		if (proxy_ip)
+		{
+			switch (traverse_proxy (proxy_type, serv->childwrite, psok, proxy_ip, port, &serv->msp_state, ns_proxy, serv->sok4, serv->sok6, &sok, bound))
+			{
+			case 0:	/* success */
+#ifdef USE_MSPROXY
+				if (!serv->dont_use_proxy && (proxy_type == 5))
+					snprintf (buf, sizeof (buf), "4\n%d %d %d %d %d\n", sok, psok, serv->msp_state.clientid, serv->msp_state.serverid,
+						serv->msp_state.seq_sent);
+				else
+#endif
+					snprintf (buf, sizeof (buf), "4\n%d\n", sok);	/* success */
+				write (serv->childwrite, buf, strlen (buf));
+				break;
+			case 1:	/* socks traversal failed */
+				write (serv->childwrite, "8\n", 2);
+				break;
+			}
+		} else
+		{
+			snprintf (buf, sizeof (buf), "4\n%d\n", sok);	/* success */
+			write (serv->childwrite, buf, strlen (buf));
+		}
+	}
+
+xit:
+
+#if defined (USE_IPV6) || defined (WIN32)
+	/* this is probably not needed */
+	net_store_destroy (ns_server);
+	if (ns_proxy)
+		net_store_destroy (ns_proxy);
+#endif
+
+	/* no need to free ip/real_hostname, this process is exiting */
+#ifdef WIN32
+	/* under win32 we use a thread -> shared memory, must free! */
+	if (proxy_ip)
+		free (proxy_ip);
+	if (ip)
+		free (ip);
+	if (real_hostname)
+		free (real_hostname);
+#endif
+
+	return 0;
+}
+
+static void
+server_connect (server *serv, char *hostname, int port, int no_login)
+{
+	int pid, read_des[2];
+	session *sess = serv->server_session;
+
+#ifdef USE_OPENSSL
+	if (!ctx && serv->use_ssl)
+	{
+		if (!(ctx = _SSL_context_init (ssl_cb_info, FALSE)))
+		{
+			fprintf (stderr, "_SSL_context_init failed\n");
+			exit (1);
+		}
+	}
+#endif
+
+	if (!hostname[0])
+		return;
+
+	if (port < 0)
+	{
+		/* use default port for this server type */
+		port = 6667;
+#ifdef USE_OPENSSL
+		if (serv->use_ssl)
+			port = 9999;
+#endif
+	}
+	port &= 0xffff;	/* wrap around */
+
+	if (serv->connected || serv->connecting || serv->recondelay_tag)
+		server_disconnect (sess, TRUE, -1);
+
+	fe_progressbar_start (sess);
+
+	EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, hostname, NULL, NULL, NULL, 0);
+
+	safe_strcpy (serv->servername, hostname, sizeof (serv->servername));
+	/* overlap illegal in strncpy */
+	if (hostname != serv->hostname)
+		safe_strcpy (serv->hostname, hostname, sizeof (serv->hostname));
+
+#ifdef USE_OPENSSL
+	if (serv->use_ssl)
+	{
+		char cert_file[256];
+
+		/* first try network specific cert/key */
+		snprintf (cert_file, sizeof (cert_file), "%s/%s.pem",
+					 get_xdir_fs (), server_get_network (serv, TRUE));
+		if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+			SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		else
+		{
+			/* if that doesn't exist, try ~/.xchat2/client.pem */
+			snprintf (cert_file, sizeof (cert_file), "%s/%s.pem",
+						 get_xdir_fs (), "client");
+			if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+				SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		}
+	}
+#endif
+
+	server_set_defaults (serv);
+	serv->connecting = TRUE;
+	serv->port = port;
+	serv->no_login = no_login;
+
+	fe_server_event (serv, FE_SE_CONNECTING, 0);
+	fe_set_away (serv);
+	server_flush_queue (serv);
+
+#ifdef WIN32
+	if (_pipe (read_des, 4096, _O_BINARY) < 0)
+#else
+	if (pipe (read_des) < 0)
+#endif
+		return;
+#ifdef __EMX__ /* os/2 */
+	setmode (read_des[0], O_BINARY);
+	setmode (read_des[1], O_BINARY);
+#endif
+	serv->childread = read_des[0];
+	serv->childwrite = read_des[1];
+
+	/* create both sockets now, drop one later */
+	net_sockets (&serv->sok4, &serv->sok6);
+#ifdef USE_MSPROXY
+	/* In case of MS Proxy we have a separate UDP control connection */
+	if (!serv->dont_use_proxy && (serv->proxy_type == 5))
+		udp_sockets (&serv->proxy_sok4, &serv->proxy_sok6);
+	else
+#endif
+	{
+		serv->proxy_sok4 = -1;
+		serv->proxy_sok6 = -1;
+	}
+
+#ifdef WIN32
+	CloseHandle (CreateThread (NULL, 0,
+										(LPTHREAD_START_ROUTINE)server_child,
+										serv, 0, (DWORD *)&pid));
+#else
+#ifdef LOOKUPD
+	rand();	/* CL: net_resolve calls rand() when LOOKUPD is set, so prepare a different seed for each child. This method giver a bigger variation in seed values than calling srand(time(0)) in the child itself. */
+#endif
+	switch (pid = fork ())
+	{
+	case -1:
+		return;
+
+	case 0:
+		/* this is the child */
+		setuid (getuid ());
+		server_child (serv);
+		_exit (0);
+	}
+#endif
+	serv->childpid = pid;
+	serv->iotag = fe_input_add (serv->childread, FIA_READ, server_read_child,
+										 serv);
+}
+
+void
+server_fill_her_up (server *serv)
+{
+	serv->connect = server_connect;
+	serv->disconnect = server_disconnect;
+	serv->cleanup = server_cleanup;
+	serv->flush_queue = server_flush_queue;
+	serv->auto_reconnect = auto_reconnect;
+
+	proto_fill_her_up (serv);
+}
+
+void
+server_set_encoding (server *serv, char *new_encoding)
+{
+	char *space;
+
+	if (serv->encoding)
+	{
+		free (serv->encoding);
+		/* can be left as NULL to indicate system encoding */
+		serv->encoding = NULL;
+		serv->using_cp1255 = FALSE;
+		serv->using_irc = FALSE;
+	}
+
+	if (new_encoding)
+	{
+		serv->encoding = strdup (new_encoding);
+		/* the serverlist GUI might have added a space 
+			and short description - remove it. */
+		space = strchr (serv->encoding, ' ');
+		if (space)
+			space[0] = 0;
+
+		/* server_inline() uses these flags */
+		if (!strcasecmp (serv->encoding, "CP1255") ||
+			 !strcasecmp (serv->encoding, "WINDOWS-1255"))
+			serv->using_cp1255 = TRUE;
+		else if (!strcasecmp (serv->encoding, "IRC"))
+			serv->using_irc = TRUE;
+	}
+}
+
+server *
+server_new (void)
+{
+	static int id = 0;
+	server *serv;
+
+	serv = malloc (sizeof (struct server));
+	memset (serv, 0, sizeof (struct server));
+
+	/* use server.c and proto-irc.c functions */
+	server_fill_her_up (serv);
+
+	serv->id = id++;
+	serv->sok = -1;
+	strcpy (serv->nick, prefs.nick1);
+	server_set_defaults (serv);
+
+	serv_list = g_slist_prepend (serv_list, serv);
+
+	fe_new_server (serv);
+
+	return serv;
+}
+
+int
+is_server (server *serv)
+{
+	return g_slist_find (serv_list, serv) ? 1 : 0;
+}
+
+void
+server_set_defaults (server *serv)
+{
+	if (serv->chantypes)
+		free (serv->chantypes);
+	if (serv->chanmodes)
+		free (serv->chanmodes);
+	if (serv->nick_prefixes)
+		free (serv->nick_prefixes);
+	if (serv->nick_modes)
+		free (serv->nick_modes);
+
+	serv->chantypes = strdup ("#&!+");
+	serv->chanmodes = strdup ("beI,k,l");
+	serv->nick_prefixes = strdup ("@%+");
+	serv->nick_modes = strdup ("ohv");
+
+	serv->nickcount = 1;
+	serv->nickservtype = 1;
+	serv->end_of_motd = FALSE;
+	serv->is_away = FALSE;
+	serv->supports_watch = FALSE;
+	serv->bad_prefix = FALSE;
+	serv->use_who = TRUE;
+	serv->have_namesx = FALSE;
+	serv->have_uhnames = FALSE;
+	serv->have_whox = FALSE;
+	serv->have_capab = FALSE;
+	serv->have_idmsg = FALSE;
+	serv->have_except = FALSE;
+}
+
+char *
+server_get_network (server *serv, gboolean fallback)
+{
+	if (serv->network)
+		return ((ircnet *)serv->network)->name;
+
+	if (fallback)
+		return serv->servername;
+
+	return NULL;
+}
+
+void
+server_set_name (server *serv, char *name)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	if (name[0] == 0)
+		name = serv->hostname;
+
+	/* strncpy parameters must NOT overlap */
+	if (name != serv->servername)
+	{
+		safe_strcpy (serv->servername, name, sizeof (serv->servername));
+	}
+
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+			fe_set_title (sess);
+		list = list->next;
+	}
+
+	if (serv->server_session->type == SESS_SERVER)
+	{
+		if (serv->network)
+		{
+			safe_strcpy (serv->server_session->channel, ((ircnet *)serv->network)->name, CHANLEN);
+		} else
+		{
+			safe_strcpy (serv->server_session->channel, name, CHANLEN);
+		}
+		fe_set_channel (serv->server_session);
+	}
+}
+
+struct away_msg *
+server_away_find_message (server *serv, char *nick)
+{
+	struct away_msg *away;
+	GSList *list = away_list;
+	while (list)
+	{
+		away = (struct away_msg *) list->data;
+		if (away->server == serv && !serv->p_cmp (nick, away->nick))
+			return away;
+		list = list->next;
+	}
+	return NULL;
+}
+
+static void
+server_away_free_messages (server *serv)
+{
+	GSList *list, *next;
+	struct away_msg *away;
+
+	list = away_list;
+	while (list)
+	{
+		away = list->data;
+		next = list->next;
+		if (away->server == serv)
+		{
+			away_list = g_slist_remove (away_list, away);
+			if (away->message)
+				free (away->message);
+			free (away);
+			next = away_list;
+		}
+		list = next;
+	}
+}
+
+void
+server_away_save_message (server *serv, char *nick, char *msg)
+{
+	struct away_msg *away = server_away_find_message (serv, nick);
+
+	if (away)						  /* Change message for known user */
+	{
+		if (away->message)
+			free (away->message);
+		away->message = strdup (msg);
+	} else
+		/* Create brand new entry */
+	{
+		away = malloc (sizeof (struct away_msg));
+		if (away)
+		{
+			away->server = serv;
+			safe_strcpy (away->nick, nick, sizeof (away->nick));
+			away->message = strdup (msg);
+			away_list = g_slist_prepend (away_list, away);
+		}
+	}
+}
+
+void
+server_free (server *serv)
+{
+	serv->cleanup (serv);
+
+	serv_list = g_slist_remove (serv_list, serv);
+
+	dcc_notify_kill (serv);
+	serv->flush_queue (serv);
+	server_away_free_messages (serv);
+
+	free (serv->nick_modes);
+	free (serv->nick_prefixes);
+	free (serv->chanmodes);
+	free (serv->chantypes);
+	if (serv->bad_nick_prefixes)
+		free (serv->bad_nick_prefixes);
+	if (serv->last_away_reason)
+		free (serv->last_away_reason);
+	if (serv->encoding)
+		free (serv->encoding);
+	if (serv->autojoin)
+		free (serv->autojoin);
+
+	fe_server_callback (serv);
+
+	free (serv);
+
+	notify_cleanup ();
+}
diff --git a/src/common/server.h b/src/common/server.h
new file mode 100644
index 00000000..874d27b2
--- /dev/null
+++ b/src/common/server.h
@@ -0,0 +1,26 @@
+#ifndef XCHAT_SERVER_H
+#define XCHAT_SERVER_H
+
+extern GSList *serv_list;
+
+/* eventually need to keep the tcp_* functions isolated to server.c */
+int tcp_send_len (server *serv, char *buf, int len);
+int tcp_send (server *serv, char *buf);
+void tcp_sendf (server *serv, char *fmt, ...);
+int tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len);
+
+server *server_new (void);
+int is_server (server *serv);
+void server_fill_her_up (server *serv);
+void server_set_encoding (server *serv, char *new_encoding);
+void server_set_defaults (server *serv);
+char *server_get_network (server *serv, gboolean fallback);
+void server_set_name (server *serv, char *name);
+void server_free (server *serv);
+
+void server_away_save_message (server *serv, char *nick, char *msg);
+struct away_msg *server_away_find_message (server *serv, char *nick);
+
+void base64_encode (char *to, char *from, unsigned int len);
+
+#endif
diff --git a/src/common/servlist.c b/src/common/servlist.c
new file mode 100644
index 00000000..20a156a8
--- /dev/null
+++ b/src/common/servlist.c
@@ -0,0 +1,1311 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "xchat.h"
+#include <glib/ghash.h>
+
+#include "cfgfiles.h"
+#include "fe.h"
+#include "server.h"
+#include "text.h"
+#include "util.h" /* token_foreach */
+#include "xchatc.h"
+
+#include "servlist.h"
+
+
+struct defaultserver
+{
+	char *network;
+	char *host;
+	char *channel;
+	char *charset;
+};
+
+static const struct defaultserver def[] =
+{
+	{"2600net",	0},
+	{0,			"irc.2600.net"},
+
+	{"7-indonesia",	0},
+	{0,			"irc.7-indonesia.org"},
+
+	{"AccessIRC",	0},
+	{0,			"irc.accessirc.net"},
+	{0,			"eu.accessirc.net"},
+
+	{"AfterNET",	0},
+	{0,			"irc.afternet.org"},
+	{0,			"us.afternet.org"},
+	{0,			"eu.afternet.org"},
+
+	{"Aitvaras",	0},
+#ifdef USE_IPV6
+#ifdef USE_OPENSSL
+	{0,			"irc6.ktu.lt/+7668"},
+#endif
+	{0,			"irc6.ktu.lt/7666"},
+#endif
+#ifdef USE_OPENSSL
+	{0,			"irc.data.lt/+6668"},
+	{0,			"irc-ssl.omnitel.net/+6668"},
+	{0,			"irc-ssl.le.lt/+9999"},
+#endif
+	{0,			"irc.data.lt"},
+	{0,			"irc.omnitel.net"},
+	{0,			"irc.ktu.lt"},
+	{0,			"irc.le.lt"},
+	{0,			"irc.takas.lt"},
+	{0,			"irc.5ci.net"},
+	{0,			"irc.kis.lt"},
+
+	{"AmigaNet",	0},
+	{0,			"irc.amiganet.org"},
+	{0,			"us.amiganet.org"},
+	{0,			"uk.amiganet.org"},
+/*	{0,			"no.amiganet.org"},
+	{0,			"au.amiganet.org"},*/
+
+	{"ARCNet",	0},
+	{0,			"se1.arcnet.vapor.com"},
+	{0,			"us1.arcnet.vapor.com"},
+	{0,			"us2.arcnet.vapor.com"},
+	{0,			"us3.arcnet.vapor.com"},
+	{0,			"ca1.arcnet.vapor.com"},
+	{0,			"de1.arcnet.vapor.com"},
+	{0,			"de3.arcnet.vapor.com"},
+	{0,			"ch1.arcnet.vapor.com"},
+	{0,			"be1.arcnet.vapor.com"},
+	{0,			"nl3.arcnet.vapor.com"},
+	{0,			"uk1.arcnet.vapor.com"},
+	{0,			"uk2.arcnet.vapor.com"},
+/*	{0,			"uk3.arcnet.vapor.com"},*/
+	{0,			"fr1.arcnet.vapor.com"},
+
+	{"AstroLink",	0},
+	{0,			"irc.astrolink.org"},
+
+	{"AustNet",	0},
+	{0,			"au.austnet.org"},
+	{0,			"us.austnet.org"},
+	{0,			"ca.austnet.org"},
+
+/*	{"AxeNet",	0},
+	{0,			"irc.axenet.org"},
+	{0,			"angel.axenet.org"},
+	{0,			"energy.axenet.org"},
+	{0,			"python.axenet.org"},*/
+
+	{"AzzurraNet",	0},
+	{0,			"irc.azzurra.org"},
+	{0,			"crypto.azzurra.org"},
+
+	{"Beirut", 0},
+	{0,			"irc.beirut.com"},
+
+	{"ChattingAway", 0},
+	{0,			"irc.chattingaway.com"},
+
+	{"ChatJunkies",	0, "#xchat"},
+	{0,			"irc.chatjunkies.org"},
+	{0,			"nl.chatjunkies.org"},
+
+	{"ChatNet",	0},
+	{0,			"US.ChatNet.Org"},
+	{0,			"EU.ChatNet.Org"},
+
+	{"ChatSociety", 0},
+	{0,			"us.chatsociety.net"},
+	{0,			"eu.chatsociety.net"},
+
+	{"ChatSpike", 0},
+	{0,			"irc.chatspike.net"},
+
+	{"CoolChat",	0},
+	{0,			"irc.coolchat.net"},
+/*	{0,			"unix.coolchat.net"},
+	{0,			"toronto.coolchat.net"},*/
+
+	{"Criten", 0},
+	{0,			"irc.criten.net"},
+	{0,			"irc.eu.criten.net"},
+
+	{"DALnet",	0},
+	{0,			"irc.dal.net"},
+	{0,			"irc.eu.dal.net"},
+
+	{"Dark-Tou-Net",	0},
+	{0,			"irc.d-t-net.de"},
+	{0,			"bw.d-t-net.de"},
+	{0,			"nc.d-t-net.de"},
+	{0,			"wakka.d-t-net.de"},
+
+	{"DarkMyst", 0},
+	{0,			"irc.darkmyst.org"},
+
+	{"DeepIRC", 0},
+	{0,			"irc.deepirc.net"},
+
+	{"DeltaAnime", 0},
+	{0,			"irc.deltaanime.net"},
+
+	{"EFnet",	0},
+	{0,			"irc.blackened.com"},
+	{0,			"irc.Prison.NET"},
+	{0,			"irc.Qeast.net"},
+	{0,			"irc.efnet.pl"},
+	{0,			"efnet.demon.co.uk"},
+/*	{0,			"irc.lagged.org"},*/
+	{0,			"irc.lightning.net"},
+	{0,			"irc.mindspring.com"},
+	{0,			"irc.easynews.com"},
+	{0,			"irc.servercentral.net"},
+
+	{"EnterTheGame",	0},
+	{0,			"IRC.EnterTheGame.Com"},
+
+	{"EUIrc",	0},
+	{0,			"irc.euirc.net"},
+	{0,			"irc.ham.de.euirc.net"},
+	{0,			"irc.ber.de.euirc.net"},
+	{0,			"irc.ffm.de.euirc.net"},
+	{0,			"irc.bre.de.euirc.net"},
+	{0,			"irc.hes.de.euirc.net"},
+	{0,			"irc.vie.at.euirc.net"},
+	{0,			"irc.inn.at.euirc.net"},
+	{0,			"irc.bas.ch.euirc.net"},
+
+	{"EuropNet", 0},
+	{0,			"irc.europnet.org"},
+
+	{"EU-IRC",	0},
+	{0,			"irc.eu-irc.net"},
+
+	{"FDFNet",	0},
+	{0,			"irc.fdfnet.net"},
+	{0,			"irc.eu.fdfnet.net"},
+
+	{"FEFNet",	0},
+	{0,			"irc.fef.net"},
+	{0,			"irc.ggn.net"},
+	{0,			"irc.vendetta.com"},
+
+	{"FreeNode",	0},
+	{0,				"irc.freenode.net"},
+
+/*	{"Freeworld",	0},
+	{0,			"kabel.freeworld.nu"},
+	{0,			"irc.freeworld.nu"},*/
+
+	{"GalaxyNet",	0},
+	{0,			"irc.galaxynet.org"},
+/*	{0,			"sprynet.us.galaxynet.org"},
+	{0,			"atlanta.ga.us.galaxynet.org"},*/
+
+	{"GamesNET",	0},
+	{0,				"irc.gamesnet.net"},
+/*	{0,				"irc.us.gamesnet.net"},
+	{0,				"east.us.gamesnet.net"},
+	{0,				"west.us.gamesnet.net"},*/
+	{0,				"irc.ca.gamesnet.net"},
+	{0,				"irc.eu.gamesnet.net"},
+
+	{"GeekShed",	0},
+	{0,			"irc.geekshed.net"},
+
+	{"German-Elite",	0},
+	{0,			"dominion.german-elite.net"},
+	{0,			"komatu.german-elite.net"},
+/*	{0,			"liberty.german-elite.net"},*/
+
+	{"GimpNet",		0},
+	{0,			"irc.gimp.org"},
+/*	{0,			"irc.au.gimp.org"},*/
+	{0,			"irc.us.gimp.org"},
+
+	{"HabberNet",	0},
+	{0,			"irc.habber.net"},
+
+	{"Hashmark",	0},
+	{0,			"irc.hashmark.net"},
+
+	{"IdleMonkeys", 0},
+	{0,			"irc.idlemonkeys.net"},
+
+/*	{"Infinity-IRC",	0},
+	{0,			"Atlanta.GA.US.Infinity-IRC.Org"},
+	{0,			"Babylon.NY.US.Infinity-IRC.Org"},
+	{0,			"Sunshine.Ca.US.Infinity-IRC.Org"},
+	{0,			"IRC.Infinity-IRC.Org"},*/
+
+	{"iZ-smart.net",	0},
+	{0,			"irc.iZ-smart.net/6666"},
+	{0,			"irc.iZ-smart.net/6667"},
+	{0,			"irc.iZ-smart.net/6668"},
+
+	{"IrcLink",	0},
+	{0,			"irc.irclink.net"},
+	{0,			"Alesund.no.eu.irclink.net"},
+	{0,			"Oslo.no.eu.irclink.net"},
+	{0,			"frogn.no.eu.irclink.net"},
+	{0,			"tonsberg.no.eu.irclink.net"},
+
+	{"IRCNet",		0},
+	{0,				"irc.ircnet.com"},
+	{0,				"irc.stealth.net/6668"},
+	{0,				"ircnet.demon.co.uk"},
+/*	{0,				"ircnet.hinet.hr"},*/
+	{0,				"irc.datacomm.ch"},
+/*	{0,				"ircnet.kaptech.fr"},
+	{0,				"ircnet.easynet.co.uk"},*/
+	{0,				"random.ircd.de"},
+	{0,				"ircnet.netvision.net.il"},
+/*	{0,				"irc.seed.net.tw"},*/
+	{0,				"irc.cs.hut.fi"},
+
+	{"Irctoo.net",	0},
+	{0,			"irc.irctoo.net"},
+
+	{"Krstarica", 0},
+	{0,			"irc.krstarica.com"},
+
+	{"Librenet",	0},
+	{0,			"irc.librenet.net"},
+	{0,			"ielf.fr.librenet.net"},
+
+	{"LinkNet",	0},
+	{0,			"irc.link-net.org"},
+	{0,			"irc.no.link-net.org"},
+/*	{0,			"irc.gamesden.net.au"},*/
+	{0,			"irc.bahnhof.se"},
+/*	{0,			"irc.kinexuseurope.co.uk"},
+	{0,			"irc.gamiix.com"},*/
+
+	{"MagicStar",	0},
+	{0,			"irc.magicstar.net"},
+
+	{"Majistic",	0},
+	{0,			"irc.majistic.net"},
+
+	{"MindForge",	0},
+	{0,			"irc.mindforge.org"},
+
+	{"MintIRC",	0},
+	{0,			"irc.mintirc.net"},
+
+	{"MIXXnet",		0},
+	{0,			"irc.mixxnet.net"},
+
+	{"NeverNET",	0},
+	{0,			"irc.nevernet.net"},
+	{0,			"imagine.nevernet.net"},
+	{0,			"dimension.nevernet.net"},
+	{0,			"universe.nevernet.net"},
+	{0,			"wayland.nevernet.net"},
+	{0,			"forte.nevernet.net"},
+
+	{"NixHelpNet",	0},
+	{0,			"irc.nixhelp.org"},
+	{0,			"us.nixhelp.org"},
+	{0,			"uk.nixhelp.org"},
+	{0,			"uk2.nixhelp.org"},
+	{0,			"uk3.nixhelp.org"},
+	{0,			"nl.nixhelp.org"},
+	{0,			"ca.ld.nixhelp.org"},
+	{0,			"us.co.nixhelp.org"},
+	{0,			"us.ca.nixhelp.org"},
+	{0,			"us.pa.nixhelp.org"},
+
+	{"NullusNet",	0},
+	{0,			"irc.nullus.net"},
+
+	{"Oceanius", 0},
+	{0,			"irc.oceanius.com"},
+
+	{"OFTC",	0},
+	{0,			"irc.oftc.net"},
+
+	{"OtherNet",	0},
+	{0,			"irc.othernet.org"},
+
+	{"OzNet",	0},
+	{0,			"irc.oz.org"},
+
+	{"PIRC.PL",	0},
+	{0,			"irc.pirc.pl"},
+
+	{"PTlink",	0},
+	{0,			"irc.PTlink.net"},
+	{0,			"aaia.PTlink.net"},
+
+	{"PTNet, ISP's",	0},
+	{0,			"irc.PTNet.org"},
+	{0,			"rccn.PTnet.org"},
+	{0,			"EUnet.PTnet.org"},
+	{0,			"madinfo.PTnet.org"},
+	{0,			"netc2.PTnet.org"},
+	{0,			"netc1.PTnet.org"},
+	{0,			"telepac1.ptnet.org"},
+	{0,			"esoterica.PTnet.org"},
+	{0,			"ip-hub.ptnet.org"},
+	{0,			"telepac1.ptnet.org"},
+	{0,			"nortenet.PTnet.org"},
+
+	{"PTNet, UNI",	0},
+	{0,			"irc.PTNet.org"},
+	{0,			"rccn.PTnet.org"},
+	{0,			"uevora.PTnet.org"},
+	{0,			"umoderna.PTnet.org"},
+	{0,			"ist.PTnet.org"},
+	{0,			"aaum.PTnet.org"},
+	{0,			"uc.PTnet.org"},
+	{0,			"ualg.ptnet.org"},
+	{0,			"madinfo.PTnet.org"},
+/*	{0,			"isep.PTnet.org"},*/
+	{0,			"ua.PTnet.org"},
+	{0,			"ipg.PTnet.org"},
+	{0,			"isec.PTnet.org"},
+	{0,			"utad.PTnet.org"},
+	{0,			"iscte.PTnet.org"},
+	{0,			"ubi.PTnet.org"},
+
+	{"QuakeNet",	0},
+	{0,			"irc.quakenet.org"},
+	{0,			"irc.se.quakenet.org"},
+	{0,			"irc.dk.quakenet.org"},
+	{0,			"irc.no.quakenet.org"},
+	{0,			"irc.fi.quakenet.org"},
+	{0,			"irc.be.quakenet.org"},
+	{0,			"irc.uk.quakenet.org"},
+	{0,			"irc.de.quakenet.org"},
+	{0,			"irc.it.quakenet.org"},
+
+	{"RebelChat",	0},
+	{0,			"irc.rebelchat.org"},
+
+/*	{"Recycled-IRC",  0},
+	{0,			"irc.recycled-irc.org"},
+	{0,			"vermin.recycled-irc.org"},
+	{0,			"waste.recycled-irc.org"},
+	{0,			"lumber.recycled-irc.org"},
+	{0,			"trash.recycled-irc.org"},
+	{0,			"unwashed.recycled-irc.org"},
+	{0,			"garbage.recycled-irc.org"},
+	{0,			"dustbin.recycled-irc.org"},*/
+
+	{"RizeNET", 0},
+	{0,			"irc.rizenet.org"},
+	{0,			"omega.rizenet.org"},
+	{0,			"evelance.rizenet.org"},
+	{0,			"lisa.rizenet.org"},
+	{0,			"scott.rizenet.org"},
+
+	{"Rizon", 0},
+	{0,			"irc.rizon.net"},
+
+	{"RusNet", 0, 0, "KOI8-R (Cyrillic)"},
+	{0,			"irc.tomsk.net"},
+	{0,			"irc.rinet.ru"},
+	{0,			"irc.run.net"},
+	{0,			"irc.ru"},
+	{0,			"irc.lucky.net"},
+
+	{"SceneNet",	0},
+	{0,			"irc.scene.org"},
+	{0,			"irc.eu.scene.org"},
+	{0,			"irc.us.scene.org"},
+
+	{"SeilEn.de",	0},
+	{0,			"irc.seilen.de"},
+
+	{"SlashNET",	0},
+	{0,			"irc.slashnet.org"},
+	{0,			"area51.slashnet.org"},
+	{0,			"moo.slashnet.org"},
+	{0,			"radon.slashnet.org"},
+
+	{"Sohbet.Net", 0},
+	{0,			"irc.sohbet.net"},
+
+	{"SolidIRC", 0},
+	{0,			"irc.solidirc.com"},
+
+	{"SorceryNet",	0},
+	{0,			"irc.sorcery.net/9000"},
+	{0,			"irc.us.sorcery.net/9000"},
+	{0,			"irc.eu.sorcery.net/9000"},
+
+	{"Spidernet",	0},
+	{0,			"us.spidernet.org"},
+	{0,			"eu.spidernet.org"},
+	{0,			"irc.spidernet.org"},
+
+	{"StarChat", 0},
+	{0,			"irc.starchat.net"},
+	{0,			"gainesville.starchat.net"},
+	{0,			"freebsd.starchat.net"},
+	{0,			"sunset.starchat.net"},
+	{0,			"revenge.starchat.net"},
+	{0,			"tahoma.starchat.net"},
+	{0,			"neo.starchat.net"},
+
+	{"TNI3",			0},
+	{0,			"irc.tni3.com"},
+
+	{"TURLINet",			0},
+	{0,			"irc.turli.net"},
+	{0,			"irc.servx.ru"},
+	{0,			"irc.gavnos.ru"},
+
+	{"UnderNet",	0},
+	{0,			"us.undernet.org"},
+	{0,			"eu.undernet.org"},
+
+	{"UniBG",		0},
+	{0,			"irc.lirex.com"},
+	{0,			"irc.naturella.com"},
+	{0,			"irc.spnet.net"},
+	{0,			"irc.techno-link.com"},
+	{0,			"irc.telecoms.bg"},
+	{0,			"irc.tu-varna.edu"},
+
+	{"Whiffle",	0},
+	{0,			"irc.whiffle.org"},
+
+	{"Worldnet",		0},
+	{0,			"irc.worldnet.net"},
+	{0,			"irc.fr.worldnet.net"},
+
+	{"Xentonix.net",	0},
+	{0,			"irc.xentonix.net"},
+
+	{"XWorld",	0},
+	{0,			"Buffalo.NY.US.XWorld.org"},
+	{0,			"Minneapolis.MN.US.Xworld.Org"},
+	{0,			"Rochester.NY.US.XWorld.org"},
+	{0,			"Bayern.DE.EU.XWorld.Org"},
+	{0,			"Chicago.IL.US.XWorld.Org"},
+
+	{0,0}
+};
+
+GSList *network_list = 0;
+
+
+void
+servlist_connect (session *sess, ircnet *net, gboolean join)
+{
+	ircserver *ircserv;
+	GSList *list;
+	char *port;
+	server *serv;
+
+	if (!sess)
+		sess = new_ircwindow (NULL, NULL, SESS_SERVER, TRUE);
+
+	serv = sess->server;
+
+	/* connect to the currently selected Server-row */
+	list = g_slist_nth (net->servlist, net->selected);
+	if (!list)
+		list = net->servlist;
+	if (!list)
+		return;
+	ircserv = list->data;
+
+	/* incase a protocol switch is added to the servlist gui */
+	server_fill_her_up (sess->server);
+
+	if (join)
+	{
+		sess->willjoinchannel[0] = 0;
+
+		if (net->autojoin)
+		{
+			if (serv->autojoin)
+				free (serv->autojoin);
+			serv->autojoin = strdup (net->autojoin);
+		}
+	}
+
+	serv->password[0] = 0;
+	if (net->pass)
+		safe_strcpy (serv->password, net->pass, sizeof (serv->password));
+
+	if (net->flags & FLAG_USE_GLOBAL)
+	{
+		strcpy (serv->nick, prefs.nick1);
+	} else
+	{
+		if (net->nick)
+			strcpy (serv->nick, net->nick);
+	}
+
+	serv->dont_use_proxy = (net->flags & FLAG_USE_PROXY) ? FALSE : TRUE;
+
+#ifdef USE_OPENSSL
+	serv->use_ssl = (net->flags & FLAG_USE_SSL) ? TRUE : FALSE;
+	serv->accept_invalid_cert =
+		(net->flags & FLAG_ALLOW_INVALID) ? TRUE : FALSE;
+#endif
+
+	serv->network = net;
+
+	port = strrchr (ircserv->hostname, '/');
+	if (port)
+	{
+		*port = 0;
+
+		/* support "+port" to indicate SSL (like mIRC does) */
+		if (port[1] == '+')
+		{
+#ifdef USE_OPENSSL
+			serv->use_ssl = TRUE;
+#endif
+			serv->connect (serv, ircserv->hostname, atoi (port + 2), FALSE);
+		} else
+		{
+			serv->connect (serv, ircserv->hostname, atoi (port + 1), FALSE);
+		}
+
+		*port = '/';
+	} else
+		serv->connect (serv, ircserv->hostname, -1, FALSE);
+
+	server_set_encoding (serv, net->encoding);
+}
+
+int
+servlist_connect_by_netname (session *sess, char *network, gboolean join)
+{
+	ircnet *net;
+	GSList *list = network_list;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (strcasecmp (net->name, network) == 0)
+		{
+			servlist_connect (sess, net, join);
+			return 1;
+		}
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+int
+servlist_have_auto (void)
+{
+	GSList *list = network_list;
+	ircnet *net;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (net->flags & FLAG_AUTO_CONNECT)
+			return 1;
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+int
+servlist_auto_connect (session *sess)
+{
+	GSList *list = network_list;
+	ircnet *net;
+	int ret = 0;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (net->flags & FLAG_AUTO_CONNECT)
+		{
+			servlist_connect (sess, net, TRUE);
+			ret = 1;
+		}
+
+		list = list->next;
+	}
+
+	return ret;
+}
+
+static gint
+servlist_cycle_cb (server *serv)
+{
+	if (serv->network)
+	{
+		PrintTextf (serv->server_session,
+			_("Cycling to next server in %s...\n"), ((ircnet *)serv->network)->name);
+		servlist_connect (serv->server_session, serv->network, TRUE);
+	}
+
+	return 0;
+}
+
+int
+servlist_cycle (server *serv)
+{
+	ircnet *net;
+	int max, del;
+
+	net = serv->network;
+	if (net)
+	{
+		max = g_slist_length (net->servlist);
+		if (max > 0)
+		{
+			/* try the next server, if that option is on */
+			if (net->flags & FLAG_CYCLE)
+			{
+				net->selected++;
+				if (net->selected >= max)
+					net->selected = 0;
+			}
+
+			del = prefs.recon_delay * 1000;
+			if (del < 1000)
+				del = 500;				  /* so it doesn't block the gui */
+
+			if (del)
+				serv->recondelay_tag = fe_timeout_add (del, servlist_cycle_cb, serv);
+			else
+				servlist_connect (serv->server_session, net, TRUE);
+
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+ircserver *
+servlist_server_find (ircnet *net, char *name, int *pos)
+{
+	GSList *list = net->servlist;
+	ircserver *serv;
+	int i = 0;
+
+	while (list)
+	{
+		serv = list->data;
+		if (strcmp (serv->hostname, name) == 0)
+		{
+			if (pos)
+				*pos = i;
+			return serv;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+/* find a network (e.g. (ircnet *) to "FreeNode") from a hostname
+   (e.g. "irc.eu.freenode.net") */
+
+ircnet *
+servlist_net_find_from_server (char *server_name)
+{
+	GSList *list = network_list;
+	GSList *slist;
+	ircnet *net;
+	ircserver *serv;
+
+	while (list)
+	{
+		net = list->data;
+
+		slist = net->servlist;
+		while (slist)
+		{
+			serv = slist->data;
+			if (strcasecmp (serv->hostname, server_name) == 0)
+				return net;
+			slist = slist->next;
+		}
+
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+ircnet *
+servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *))
+{
+	GSList *list = network_list;
+	ircnet *net;
+	int i = 0;
+
+	while (list)
+	{
+		net = list->data;
+		if (cmpfunc (net->name, name) == 0)
+		{
+			if (pos)
+				*pos = i;
+			return net;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+ircserver *
+servlist_server_add (ircnet *net, char *name)
+{
+	ircserver *serv;
+
+	serv = malloc (sizeof (ircserver));
+	memset (serv, 0, sizeof (ircserver));
+	serv->hostname = strdup (name);
+
+	net->servlist = g_slist_append (net->servlist, serv);
+
+	return serv;
+}
+
+void
+servlist_server_remove (ircnet *net, ircserver *serv)
+{
+	free (serv->hostname);
+	free (serv);
+	net->servlist = g_slist_remove (net->servlist, serv);
+}
+
+static void
+servlist_server_remove_all (ircnet *net)
+{
+	ircserver *serv;
+
+	while (net->servlist)
+	{
+		serv = net->servlist->data;
+		servlist_server_remove (net, serv);
+	}
+}
+
+static void
+free_and_clear (char *str)
+{
+	if (str)
+	{
+		char *orig = str;
+		while (*str)
+			*str++ = 0;
+		free (orig);
+	}
+}
+
+/* executed on exit: Clear any password strings */
+
+void
+servlist_cleanup (void)
+{
+	GSList *list;
+	ircnet *net;
+
+	for (list = network_list; list; list = list->next)
+	{
+		net = list->data;
+		free_and_clear (net->pass);
+		free_and_clear (net->nickserv);
+	}
+}
+
+void
+servlist_net_remove (ircnet *net)
+{
+	GSList *list;
+	server *serv;
+
+	servlist_server_remove_all (net);
+	network_list = g_slist_remove (network_list, net);
+
+	if (net->nick)
+		free (net->nick);
+	if (net->nick2)
+		free (net->nick2);
+	if (net->user)
+		free (net->user);
+	if (net->real)
+		free (net->real);
+	free_and_clear (net->pass);
+	if (net->autojoin)
+		free (net->autojoin);
+	if (net->command)
+		free (net->command);
+	free_and_clear (net->nickserv);
+	if (net->comment)
+		free (net->comment);
+	if (net->encoding)
+		free (net->encoding);
+	free (net->name);
+	free (net);
+
+	/* for safety */
+	list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->network == net)
+			serv->network = NULL;
+		list = list->next;
+	}
+}
+
+ircnet *
+servlist_net_add (char *name, char *comment, int prepend)
+{
+	ircnet *net;
+
+	net = malloc (sizeof (ircnet));
+	memset (net, 0, sizeof (ircnet));
+	net->name = strdup (name);
+/*	net->comment = strdup (comment);*/
+	net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY;
+
+	if (prepend)
+		network_list = g_slist_prepend (network_list, net);
+	else
+		network_list = g_slist_append (network_list, net);
+
+	return net;
+}
+
+static void
+servlist_load_defaults (void)
+{
+	int i = 0, j = 0;
+	ircnet *net = NULL;
+
+	while (1)
+	{
+		if (def[i].network)
+		{
+			net = servlist_net_add (def[i].network, def[i].host, FALSE);
+			net->encoding = strdup ("IRC (Latin/Unicode Hybrid)");
+			if (def[i].channel)
+				net->autojoin = strdup (def[i].channel);
+			if (def[i].charset)
+			{
+				free (net->encoding);
+				net->encoding = strdup (def[i].charset);
+			}
+			if (g_str_hash (def[i].network) == 0x8e1b96f7)
+				prefs.slist_select = j;
+			j++;
+		} else
+		{
+			servlist_server_add (net, def[i].host);
+			if (!def[i+1].host && !def[i+1].network)
+				break;
+		}
+		i++;
+	}
+}
+
+static int
+servlist_load (void)
+{
+	FILE *fp;
+	char buf[2048];
+	int len;
+	char *tmp;
+	ircnet *net = NULL;
+
+	fp = xchat_fopen_file ("servlist_.conf", "r", 0);
+	if (!fp)
+		return FALSE;
+
+	while (fgets (buf, sizeof (buf) - 2, fp))
+	{
+		len = strlen (buf);
+		buf[len] = 0;
+		buf[len-1] = 0;	/* remove the trailing \n */
+		if (net)
+		{
+			switch (buf[0])
+			{
+			case 'I':
+				net->nick = strdup (buf + 2);
+				break;
+			case 'i':
+				net->nick2 = strdup (buf + 2);
+				break;
+			case 'U':
+				net->user = strdup (buf + 2);
+				break;
+			case 'R':
+				net->real = strdup (buf + 2);
+				break;
+			case 'P':
+				net->pass = strdup (buf + 2);
+				break;
+			case 'J':
+				net->autojoin = strdup (buf + 2);
+				break;
+			case 'C':
+				if (net->command)
+				{
+					/* concat extra commands with a \n separator */
+					tmp = net->command;
+					net->command = malloc (strlen (tmp) + strlen (buf + 2) + 2);
+					strcpy (net->command, tmp);
+					strcat (net->command, "\n");
+					strcat (net->command, buf + 2);
+					free (tmp);
+				} else
+					net->command = strdup (buf + 2);
+				break;
+			case 'F':
+				net->flags = atoi (buf + 2);
+				break;
+			case 'D':
+				net->selected = atoi (buf + 2);
+				break;
+			case 'E':
+				net->encoding = strdup (buf + 2);
+				break;
+			case 'S':	/* new server/hostname for this network */
+				servlist_server_add (net, buf + 2);
+				break;
+			case 'B':
+				net->nickserv = strdup (buf + 2);
+				break;
+			}
+		}
+		if (buf[0] == 'N')
+			net = servlist_net_add (buf + 2, /* comment */ NULL, FALSE);
+	}
+	fclose (fp);
+
+	return TRUE;
+}
+
+void
+servlist_init (void)
+{
+	if (!network_list)
+		if (!servlist_load ())
+			servlist_load_defaults ();
+}
+
+/* check if a charset is known by Iconv */
+int
+servlist_check_encoding (char *charset)
+{
+	GIConv gic;
+	char *c;
+
+	c = strchr (charset, ' ');
+	if (c)
+		c[0] = 0;
+
+	if (!strcasecmp (charset, "IRC")) /* special case */
+	{
+		if (c)
+			c[0] = ' ';
+		return TRUE;
+	}
+
+	gic = g_iconv_open (charset, "UTF-8");
+
+	if (c)
+		c[0] = ' ';
+
+	if (gic != (GIConv)-1)
+	{
+		g_iconv_close (gic);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+servlist_write_ccmd (char *str, void *fp)
+{
+	return fprintf (fp, "C=%s\n", (str[0] == '/') ? str + 1 : str);
+}
+
+
+int
+servlist_save (void)
+{
+	FILE *fp;
+	char buf[256];
+	ircnet *net;
+	ircserver *serv;
+	GSList *list;
+	GSList *hlist;
+#ifndef WIN32
+	int first = FALSE;
+
+	snprintf (buf, sizeof (buf), "%s/servlist_.conf", get_xdir_fs ());
+	if (access (buf, F_OK) != 0)
+		first = TRUE;
+#endif
+
+	fp = xchat_fopen_file ("servlist_.conf", "w", 0);
+	if (!fp)
+		return FALSE;
+
+#ifndef WIN32
+	if (first)
+		chmod (buf, 0600);
+#endif
+	fprintf (fp, "v="PACKAGE_VERSION"\n\n");
+
+	list = network_list;
+	while (list)
+	{
+		net = list->data;
+
+		fprintf (fp, "N=%s\n", net->name);
+		if (net->nick)
+			fprintf (fp, "I=%s\n", net->nick);
+		if (net->nick2)
+			fprintf (fp, "i=%s\n", net->nick2);
+		if (net->user)
+			fprintf (fp, "U=%s\n", net->user);
+		if (net->real)
+			fprintf (fp, "R=%s\n", net->real);
+		if (net->pass)
+			fprintf (fp, "P=%s\n", net->pass);
+		if (net->autojoin)
+			fprintf (fp, "J=%s\n", net->autojoin);
+		if (net->nickserv)
+			fprintf (fp, "B=%s\n", net->nickserv);
+		if (net->encoding && strcasecmp (net->encoding, "System") &&
+			 strcasecmp (net->encoding, "System default"))
+		{
+			fprintf (fp, "E=%s\n", net->encoding);
+			if (!servlist_check_encoding (net->encoding))
+			{
+				snprintf (buf, sizeof (buf), _("Warning: \"%s\" character set is unknown. No conversion will be applied for network %s."),
+							 net->encoding, net->name);
+				fe_message (buf, FE_MSG_WARN);
+			}
+		}
+
+		if (net->command)
+			token_foreach (net->command, '\n', servlist_write_ccmd, fp);
+
+		fprintf (fp, "F=%d\nD=%d\n", net->flags, net->selected);
+
+		hlist = net->servlist;
+		while (hlist)
+		{
+			serv = hlist->data;
+			fprintf (fp, "S=%s\n", serv->hostname);
+			hlist = hlist->next;
+		}
+
+		if (fprintf (fp, "\n") < 1)
+		{
+			fclose (fp);
+			return FALSE;
+		}
+
+		list = list->next;
+	}
+
+	fclose (fp);
+	return TRUE;
+}
+
+static void
+joinlist_free1 (GSList *list)
+{
+	GSList *head = list;
+
+	for (; list; list = list->next)
+		g_free (list->data);
+	g_slist_free (head);
+}
+
+void
+joinlist_free (GSList *channels, GSList *keys)
+{
+	joinlist_free1 (channels);
+	joinlist_free1 (keys);
+}
+
+gboolean
+joinlist_is_in_list (server *serv, char *channel)
+{
+	GSList *channels, *keys;
+	GSList *list;
+
+	if (!serv->network || !((ircnet *)serv->network)->autojoin)
+		return FALSE;
+
+	joinlist_split (((ircnet *)serv->network)->autojoin, &channels, &keys);
+
+	for (list = channels; list; list = list->next)
+	{
+		if (serv->p_cmp (list->data, channel) == 0)
+			return TRUE;
+	}
+
+	joinlist_free (channels, keys);
+
+	return FALSE;
+}
+
+gchar *
+joinlist_merge (GSList *channels, GSList *keys)
+{
+	GString *out = g_string_new (NULL);
+	GSList *list;
+	int i, j;
+
+	for (; channels; channels = channels->next)
+	{
+		g_string_append (out, channels->data);
+
+		if (channels->next)
+			g_string_append_c (out, ',');
+	}
+
+	/* count number of REAL keys */
+	for (i = 0, list = keys; list; list = list->next)
+		if (list->data)
+			i++;
+
+	if (i > 0)
+	{
+		g_string_append_c (out, ' ');
+
+		for (j = 0; keys; keys = keys->next)
+		{
+			if (keys->data)
+			{
+				g_string_append (out, keys->data);
+				j++;
+				if (j == i)
+					break;
+			}
+
+			if (keys->next)
+				g_string_append_c (out, ',');
+		}
+	}
+
+	return g_string_free (out, FALSE);
+}
+
+void
+joinlist_split (char *autojoin, GSList **channels, GSList **keys)
+{
+	char *parta, *partb;
+	char *chan, *key;
+	int len;
+
+	*channels = NULL;
+	*keys = NULL;
+
+	/* after the first space, the keys begin */
+	parta = autojoin;
+	partb = strchr (autojoin, ' ');
+	if (partb)
+		partb++;
+
+	while (1)
+	{
+		chan = parta;
+		key = partb;
+
+		if (1)
+		{
+			while (parta[0] != 0 && parta[0] != ',' && parta[0] != ' ')
+			{
+				parta++;
+			}
+		}
+
+		if (partb)
+		{
+			while (partb[0] != 0 && partb[0] != ',' && partb[0] != ' ')
+			{
+				partb++;
+			}
+		}
+
+		len = parta - chan;
+		if (len < 1)
+			break;
+		*channels = g_slist_append (*channels, g_strndup (chan, len));
+
+		len = partb - key;
+		*keys = g_slist_append (*keys, len ? g_strndup (key, len) : NULL);
+
+		if (parta[0] == ' ' || parta[0] == 0)
+			break;
+		parta++;
+
+		if (partb)
+		{
+			if (partb[0] == 0 || partb[0] == ' ')
+				partb = NULL;	/* no more keys, but maybe more channels? */
+			else
+				partb++;
+		}
+	}
+
+#if 0
+	GSList *lista, *listb;
+	int i;
+
+	printf("-----\n");
+	i = 0;
+	lista = *channels;
+	listb = *keys;
+	while (lista)
+	{
+		printf("%d. |%s| |%s|\n", i, lista->data, listb->data);
+		i++;
+		lista = lista->next;
+		listb = listb->next;
+	}
+	printf("-----\n\n");
+#endif
+}
+
+
diff --git a/src/common/servlist.h b/src/common/servlist.h
new file mode 100644
index 00000000..9b7734f4
--- /dev/null
+++ b/src/common/servlist.h
@@ -0,0 +1,62 @@
+#ifndef XCHAT_SERVLIST_H
+#define XCHAT_SERVLIST_H
+
+typedef struct ircserver
+{
+	char *hostname;
+} ircserver;
+
+typedef struct ircnet
+{
+	char *name;
+	char *nick;
+	char *nick2;
+	char *user;
+	char *real;
+	char *pass;
+	char *autojoin;
+	char *command;
+	char *nickserv;
+	char *comment;
+	char *encoding;
+	GSList *servlist;
+	int selected;
+	guint32 flags;
+} ircnet;
+
+extern GSList *network_list;
+
+#define FLAG_CYCLE				1
+#define FLAG_USE_GLOBAL			2
+#define FLAG_USE_SSL				4
+#define FLAG_AUTO_CONNECT		8
+#define FLAG_USE_PROXY			16
+#define FLAG_ALLOW_INVALID		32
+#define FLAG_FAVORITE			64
+#define FLAG_COUNT				7
+
+void servlist_init (void);
+int servlist_save (void);
+int servlist_cycle (server *serv);
+void servlist_connect (session *sess, ircnet *net, gboolean join);
+int servlist_connect_by_netname (session *sess, char *network, gboolean join);
+int servlist_auto_connect (session *sess);
+int servlist_have_auto (void);
+int servlist_check_encoding (char *charset);
+void servlist_cleanup (void);
+
+ircnet *servlist_net_add (char *name, char *comment, int prepend);
+void servlist_net_remove (ircnet *net);
+ircnet *servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *));
+ircnet *servlist_net_find_from_server (char *server_name);
+
+void servlist_server_remove (ircnet *net, ircserver *serv);
+ircserver *servlist_server_add (ircnet *net, char *name);
+ircserver *servlist_server_find (ircnet *net, char *name, int *pos);
+
+void joinlist_split (char *autojoin, GSList **channels, GSList **keys);
+gboolean joinlist_is_in_list (server *serv, char *channel);
+void joinlist_free (GSList *channels, GSList *keys);
+gchar *joinlist_merge (GSList *channels, GSList *keys);
+
+#endif
diff --git a/src/common/ssl.c b/src/common/ssl.c
new file mode 100644
index 00000000..a18ad47c
--- /dev/null
+++ b/src/common/ssl.c
@@ -0,0 +1,323 @@
+/*
+ * ssl.c v0.0.3
+ * Copyright (C) 2000  --  DaP <profeta@freemail.c3.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <openssl/ssl.h>		  /* SSL_() */
+#include <openssl/err.h>		  /* ERR_() */
+#include <time.h>					  /* asctime() */
+#include <string.h>				  /* strncpy() */
+#include "ssl.h"					  /* struct cert_info */
+#include "inet.h"
+#include "../../config.h"		  /* HAVE_SNPRINTF */
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+/* globals */
+static struct chiper_info chiper_info;		/* static buffer for _SSL_get_cipher_info() */
+static char err_buf[256];			/* generic error buffer */
+
+
+/* +++++ Internal functions +++++ */
+
+static void
+__SSL_fill_err_buf (char *funcname)
+{
+	int err;
+	char buf[256];
+
+
+	err = ERR_get_error ();
+	ERR_error_string (err, buf);
+	snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err);
+}
+
+
+static void
+__SSL_critical_error (char *funcname)
+{
+	__SSL_fill_err_buf (funcname);
+	fprintf (stderr, "%s\n", err_buf);
+
+	exit (1);
+}
+
+/* +++++ SSL functions +++++ */
+
+SSL_CTX *
+_SSL_context_init (void (*info_cb_func), int server)
+{
+	SSL_CTX *ctx;
+#ifdef WIN32
+	int i, r;
+#endif
+
+	SSLeay_add_ssl_algorithms ();
+	SSL_load_error_strings ();
+	ctx = SSL_CTX_new (server ? SSLv3_server_method() : SSLv3_client_method ());
+
+	SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
+	SSL_CTX_set_timeout (ctx, 300);
+
+	/* used in SSL_connect(), SSL_accept() */
+	SSL_CTX_set_info_callback (ctx, info_cb_func);
+
+#ifdef WIN32
+	/* under win32, OpenSSL needs to be seeded with some randomness */
+	for (i = 0; i < 128; i++)
+	{
+		r = rand ();
+		RAND_seed ((unsigned char *)&r, sizeof (r));
+	}
+#endif
+
+	return(ctx);
+}
+
+static void
+ASN1_TIME_snprintf (char *buf, int buf_len, ASN1_TIME * tm)
+{
+	char *expires = NULL;
+	BIO *inMem = BIO_new (BIO_s_mem ());
+
+	ASN1_TIME_print (inMem, tm);
+	BIO_get_mem_data (inMem, &expires);
+	buf[0] = 0;
+	if (expires != NULL)
+	{
+		memset (buf, 0, buf_len);
+		strncpy (buf, expires, 24);
+	}
+	BIO_free (inMem);
+}
+
+
+static void
+broke_oneline (char *oneline, char *parray[])
+{
+	char *pt, *ppt;
+	int i;
+
+
+	i = 0;
+	ppt = pt = oneline + 1;
+	while ((pt = strchr (pt, '/')))
+	{
+		*pt = 0;
+		parray[i++] = ppt;
+		ppt = ++pt;
+	}
+	parray[i++] = ppt;
+	parray[i] = NULL;
+}
+
+
+/*
+    FIXME: Master-Key, Extensions, CA bits
+	    (openssl x509 -text -in servcert.pem)
+*/
+int
+_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
+{
+	X509 *peer_cert;
+	EVP_PKEY *peer_pkey;
+	/* EVP_PKEY *ca_pkey; */
+	/* EVP_PKEY *tmp_pkey; */
+	char notBefore[64];
+	char notAfter[64];
+	int alg;
+	int sign_alg;
+
+
+	if (!(peer_cert = SSL_get_peer_certificate (ssl)))
+		return (1);				  /* FATAL? */
+
+	X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject,
+							 sizeof (cert_info->subject));
+	X509_NAME_oneline (X509_get_issuer_name (peer_cert), cert_info->issuer,
+							 sizeof (cert_info->issuer));
+	broke_oneline (cert_info->subject, cert_info->subject_word);
+	broke_oneline (cert_info->issuer, cert_info->issuer_word);
+
+	alg = OBJ_obj2nid (peer_cert->cert_info->key->algor->algorithm);
+	sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm);
+	ASN1_TIME_snprintf (notBefore, sizeof (notBefore),
+							  X509_get_notBefore (peer_cert));
+	ASN1_TIME_snprintf (notAfter, sizeof (notAfter),
+							  X509_get_notAfter (peer_cert));
+
+	peer_pkey = X509_get_pubkey (peer_cert);
+
+	strncpy (cert_info->algorithm,
+				(alg == NID_undef) ? "Unknown" : OBJ_nid2ln (alg),
+				sizeof (cert_info->algorithm));
+	cert_info->algorithm_bits = EVP_PKEY_bits (peer_pkey);
+	strncpy (cert_info->sign_algorithm,
+				(sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln (sign_alg),
+				sizeof (cert_info->sign_algorithm));
+	/* EVP_PKEY_bits(ca_pkey)); */
+	cert_info->sign_algorithm_bits = 0;
+	strncpy (cert_info->notbefore, notBefore, sizeof (cert_info->notbefore));
+	strncpy (cert_info->notafter, notAfter, sizeof (cert_info->notafter));
+
+	EVP_PKEY_free (peer_pkey);
+
+	/* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */
+/*
+	if (ssl->session->sess_cert->peer_rsa_tmp) {
+		tmp_pkey = EVP_PKEY_new();
+		EVP_PKEY_assign_RSA(tmp_pkey, ssl->session->sess_cert->peer_rsa_tmp);
+		cert_info->rsa_tmp_bits = EVP_PKEY_bits (tmp_pkey);
+		EVP_PKEY_free(tmp_pkey);
+	} else
+		fprintf(stderr, "REMOTE SIDE DOESN'T PROVIDES ->peer_rsa_tmp\n");
+*/
+	cert_info->rsa_tmp_bits = 0;
+
+	X509_free (peer_cert);
+
+	return (0);
+}
+
+
+struct chiper_info *
+_SSL_get_cipher_info (SSL * ssl)
+{
+	SSL_CIPHER *c;
+
+
+	c = SSL_get_current_cipher (ssl);
+	strncpy (chiper_info.version, SSL_CIPHER_get_version (c),
+				sizeof (chiper_info.version));
+	strncpy (chiper_info.chiper, SSL_CIPHER_get_name (c),
+				sizeof (chiper_info.chiper));
+	SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits);
+
+	return (&chiper_info);
+}
+
+
+int
+_SSL_send (SSL * ssl, char *buf, int len)
+{
+	int num;
+
+
+	num = SSL_write (ssl, buf, len);
+
+	switch (SSL_get_error (ssl, num))
+	{
+	case SSL_ERROR_SSL:			  /* setup errno! */
+		/* ??? */
+		__SSL_fill_err_buf ("SSL_write");
+		fprintf (stderr, "%s\n", err_buf);
+		break;
+	case SSL_ERROR_SYSCALL:
+		/* ??? */
+		perror ("SSL_write/write");
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		/* fprintf(stderr, "SSL closed on write\n"); */
+		break;
+	}
+
+	return (num);
+}
+
+
+int
+_SSL_recv (SSL * ssl, char *buf, int len)
+{
+	int num;
+
+
+	num = SSL_read (ssl, buf, len);
+
+	switch (SSL_get_error (ssl, num))
+	{
+	case SSL_ERROR_SSL:
+		/* ??? */
+		__SSL_fill_err_buf ("SSL_read");
+		fprintf (stderr, "%s\n", err_buf);
+		break;
+	case SSL_ERROR_SYSCALL:
+		/* ??? */
+		if (!would_block ())
+			perror ("SSL_read/read");
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		/* fprintf(stdeerr, "SSL closed on read\n"); */
+		break;
+	}
+
+	return (num);
+}
+
+
+SSL *
+_SSL_socket (SSL_CTX *ctx, int sd)
+{
+	SSL *ssl;
+
+
+	if (!(ssl = SSL_new (ctx)))
+		/* FATAL */
+		__SSL_critical_error ("SSL_new");
+
+	SSL_set_fd (ssl, sd);
+	if (ctx->method == SSLv3_client_method())
+		SSL_set_connect_state (ssl);
+	else
+	        SSL_set_accept_state(ssl);
+
+	return (ssl);
+}
+
+
+char *
+_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert)
+{
+	if (!SSL_CTX_set_default_verify_paths (ctx))
+	{
+		__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
+		return (err_buf);
+	}
+/*
+	if (cacert)
+	{
+		if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL))
+		{
+			__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
+			return (err_buf);
+		}
+	}
+*/
+	SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback);
+
+	return (NULL);
+}
+
+
+void
+_SSL_close (SSL * ssl)
+{
+	SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
+	SSL_free (ssl);
+	ERR_remove_state (0);		  /* free state buffer */
+}
diff --git a/src/common/ssl.h b/src/common/ssl.h
new file mode 100644
index 00000000..26eb0f88
--- /dev/null
+++ b/src/common/ssl.h
@@ -0,0 +1,65 @@
+/*
+    ...
+*/
+
+struct cert_info {
+    char subject[256];
+    char *subject_word[12];
+    char issuer[256];
+    char *issuer_word[12];
+    char algorithm[32];
+    int algorithm_bits;
+    char sign_algorithm[32];
+    int sign_algorithm_bits;
+    char notbefore[32];
+    char notafter[32];
+
+    int rsa_tmp_bits;
+};
+
+struct chiper_info {
+    char version[16];
+    char chiper[24];
+    int chiper_bits;
+};
+
+SSL_CTX *_SSL_context_init (void (*info_cb_func), int server);
+#define _SSL_context_free(a)	SSL_CTX_free(a);
+
+SSL *_SSL_socket (SSL_CTX *ctx, int sd);
+char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert);
+/*
+    int SSL_connect(SSL *);
+    int SSL_accept(SSL *);
+    int SSL_get_fd(SSL *);
+*/
+void _SSL_close (SSL * ssl);
+
+int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl);
+struct chiper_info *_SSL_get_cipher_info (SSL * ssl);
+
+/*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/
+/*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/
+
+int _SSL_send (SSL * ssl, char *buf, int len);
+int _SSL_recv (SSL * ssl, char *buf, int len);
+
+/* misc */
+/*void broke_oneline (char *oneline, char *parray[]);*/
+
+/*char *_SSL_do_cipher_base64(char *buf, int buf_len, char *key, int operation);*/		/* must be freed */
+
+/*void *_SSL_get_sess_obj(SSL *ssl, int type);*/		/* NOT must be freed */
+#define	_SSL_get_sess_pkey(a)	_SSL_get_sess_obj(a, 0)
+#define	_SSL_get_sess_prkey(a)	_SSL_get_sess_obj(a, 1)
+#define	_SSL_get_sess_x509(a)	_SSL_get_sess_obj(a, 2)
+/*char *_SSL_get_obj_base64(void *s, int type);*/		/* must be freed */
+#define	_SSL_get_pkey_base64(a)		_SSL_get_obj_base64(a, 0)
+#define	_SSL_get_prkey_base64(a)	_SSL_get_obj_base64(a, 1)
+#define	_SSL_get_x509_base64(a)		_SSL_get_obj_base64(a, 2)
+/*char *_SSL_get_ctx_obj_base64(SSL_CTX *ctx, int type);*/	/* must be freed */
+#define	_SSL_get_ctx_pkey_base64(a)	_SSL_get_ctx_obj_base64(a, 0)
+#define	_SSL_get_ctx_prkey_base64(a)	_SSL_get_ctx_obj_base64(a, 1)
+#define	_SSL_get_ctx_x509_base64(a)	_SSL_get_ctx_obj_base64(a, 2)
+
+/*int _SSL_verify_x509(X509 *x509);*/
diff --git a/src/common/text.c b/src/common/text.c
new file mode 100644
index 00000000..a2198517
--- /dev/null
+++ b/src/common/text.c
@@ -0,0 +1,2309 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "xchat.h"
+#include <glib/ghash.h>
+#include "cfgfiles.h"
+#include "chanopt.h"
+#include "plugin.h"
+#include "fe.h"
+#include "server.h"
+#include "util.h"
+#include "outbound.h"
+#include "xchatc.h"
+#include "text.h"
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+struct pevt_stage1
+{
+	int len;
+	char *data;
+	struct pevt_stage1 *next;
+};
+
+
+static void mkdir_p (char *dir);
+static char *log_create_filename (char *channame);
+
+
+static char *
+scrollback_get_filename (session *sess, char *buf, int max)
+{
+	char *net, *chan;
+
+	net = server_get_network (sess->server, FALSE);
+	if (!net)
+		return NULL;
+
+	snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, "");
+	mkdir_p (buf);
+
+	chan = log_create_filename (sess->channel);
+	snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, chan);
+	free (chan);
+
+	return buf;
+}
+
+#if 0
+
+static void
+scrollback_unlock (session *sess)
+{
+	char buf[1024];
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
+		return;
+
+	strcat (buf, ".lock");
+	unlink (buf);
+}
+
+static gboolean
+scrollback_lock (session *sess)
+{
+	char buf[1024];
+	int fh;
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
+		return FALSE;
+
+	strcat (buf, ".lock");
+
+	if (access (buf, F_OK) == 0)
+		return FALSE;	/* can't get lock */
+
+	fh = open (buf, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
+	if (fh == -1)
+		return FALSE;
+
+	return TRUE;
+}
+
+#endif
+
+void
+scrollback_close (session *sess)
+{
+	if (sess->scrollfd != -1)
+	{
+		close (sess->scrollfd);
+		sess->scrollfd = -1;
+	}
+}
+
+static char *
+file_to_buffer (char *file, int *len)
+{
+	int fh;
+	char *buf;
+	struct stat st;
+
+	fh = open (file, O_RDONLY | OFLAGS);
+	if (fh == -1)
+		return NULL;
+
+	fstat (fh, &st);
+
+	buf = malloc (st.st_size);
+	if (!buf)
+	{
+		close (fh);
+		return NULL;
+	}
+
+	if (read (fh, buf, st.st_size) != st.st_size)
+	{
+		free (buf);
+		close (fh);
+		return NULL;
+	}
+
+	*len = st.st_size;
+	close (fh);
+	return buf;
+}
+
+/* shrink the file to roughly prefs.max_lines */
+
+static void
+scrollback_shrink (session *sess)
+{
+	char file[1024];
+	char *buf;
+	int fh;
+	int lines;
+	int line;
+	int len;
+	char *p;
+
+	scrollback_close (sess);
+	sess->scrollwritten = 0;
+	lines = 0;
+
+	if (scrollback_get_filename (sess, file, sizeof (file)) == NULL)
+		return;
+
+	buf = file_to_buffer (file, &len);
+	if (!buf)
+		return;
+
+	/* count all lines */
+	p = buf;
+	while (p != buf + len)
+	{
+		if (*p == '\n')
+			lines++;
+		p++;
+	}
+
+	fh = open (file, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
+	if (fh == -1)
+	{
+		free (buf);
+		return;
+	}
+
+	line = 0;
+	p = buf;
+	while (p != buf + len)
+	{
+		if (*p == '\n')
+		{
+			line++;
+			if (line >= lines - prefs.max_lines &&
+				 p + 1 != buf + len)
+			{
+				p++;
+				write (fh, p, len - (p - buf));
+				break;
+			}
+		}
+		p++;
+	}
+
+	close (fh);
+	free (buf);
+}
+
+static void
+scrollback_save (session *sess, char *text)
+{
+	char buf[512 * 4];
+	time_t stamp;
+	int len;
+
+	if (sess->type == SESS_SERVER)
+		return;
+
+	if (sess->text_scrollback == SET_DEFAULT)
+	{
+		if (!prefs.text_replay)
+			return;
+	}
+	else
+	{
+		if (sess->text_scrollback != SET_ON)
+			return;
+	}
+
+	if (sess->scrollfd == -1)
+	{
+		if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL)
+			return;
+
+		sess->scrollfd = open (buf, O_CREAT | O_APPEND | O_WRONLY, 0644);
+		if (sess->scrollfd == -1)
+			return;
+	}
+
+	stamp = time (0);
+	if (sizeof (stamp) == 4)	/* gcc will optimize one of these out */
+		write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %d ", (int)stamp));
+	else
+		write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %"G_GINT64_FORMAT" ", (gint64)stamp));
+
+	len = strlen (text);
+	write (sess->scrollfd, text, len);
+	if (len && text[len - 1] != '\n')
+		write (sess->scrollfd, "\n", 1);
+
+	sess->scrollwritten++;
+
+	if ((sess->scrollwritten * 2 > prefs.max_lines && prefs.max_lines > 0) ||
+       sess->scrollwritten > 32000)
+		scrollback_shrink (sess);
+}
+
+void
+scrollback_load (session *sess)
+{
+	int fh;
+	char buf[512 * 4];
+	char *text;
+	time_t stamp;
+	int lines;
+	char *map, *end_map;
+	struct stat statbuf;
+	const char *begin, *eol;
+
+	if (sess->text_scrollback == SET_DEFAULT)
+	{
+		if (!prefs.text_replay)
+			return;
+	}
+	else
+	{
+		if (sess->text_scrollback != SET_ON)
+			return;
+	}
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL)
+		return;
+
+	fh = open (buf, O_RDONLY | OFLAGS);
+	if (fh == -1)
+		return;
+
+	if (fstat (fh, &statbuf) < 0)
+		return;
+
+	map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fh, 0);
+	if (map == MAP_FAILED)
+		return;
+
+	end_map = map + statbuf.st_size;
+	
+	lines = 0;
+	begin = map;
+	while (begin < end_map)
+	{
+		int n_bytes;
+
+		eol = memchr (begin, '\n', end_map - begin);
+
+		if (!eol)
+			eol = end_map;
+
+		n_bytes = MIN (eol - begin, sizeof (buf) - 1);
+		
+		strncpy (buf, begin, n_bytes);
+
+		buf[n_bytes] = 0;
+		
+		if (buf[0] == 'T')
+		{
+			if (sizeof (time_t) == 4)
+				stamp = strtoul (buf + 2, NULL, 10);
+			else
+				stamp = strtoull (buf + 2, NULL, 10); /* just incase time_t is 64 bits */
+			text = strchr (buf + 3, ' ');
+			if (text)
+			{
+				text = strip_color (text + 1, -1, STRIP_COLOR);
+				fe_print_text (sess, text, stamp);
+				g_free (text);
+			}
+			lines++;
+		}
+
+		begin = eol + 1;
+	}
+
+	sess->scrollwritten = lines;
+
+	if (lines)
+	{
+		text = ctime (&stamp);
+		text[24] = 0;	/* get rid of the \n */
+		snprintf (buf, sizeof (buf), "\n*\t%s %s\n\n", _("Loaded log from"), text);
+		fe_print_text (sess, buf, 0);
+		/*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
+	}
+
+	munmap (map, statbuf.st_size);
+	close (fh);
+}
+
+void
+log_close (session *sess)
+{
+	char obuf[512];
+	time_t currenttime;
+
+	if (sess->logfd != -1)
+	{
+		currenttime = time (NULL);
+		write (sess->logfd, obuf,
+			 snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"),
+						  ctime (&currenttime)));
+		close (sess->logfd);
+		sess->logfd = -1;
+	}
+}
+
+static void
+mkdir_p (char *dir)	/* like "mkdir -p" from a shell, FS encoding */
+{
+	char *start = dir;
+
+	/* the whole thing already exists? */
+	if (access (dir, F_OK) == 0)
+		return;
+
+	while (*dir)
+	{
+#ifdef WIN32
+		if (dir != start && (*dir == '/' || *dir == '\\'))
+#else
+		if (dir != start && *dir == '/')
+#endif
+		{
+			*dir = 0;
+#ifdef WIN32
+			mkdir (start);
+#else
+			mkdir (start, S_IRUSR | S_IWUSR | S_IXUSR);
+#endif
+			*dir = '/';
+		}
+		dir++;
+	}
+}
+
+static char *
+log_create_filename (char *channame)
+{
+	char *tmp, *ret;
+	int mbl;
+
+	ret = tmp = strdup (channame);
+	while (*tmp)
+	{
+		mbl = g_utf8_skip[((unsigned char *)tmp)[0]];
+		if (mbl == 1)
+		{
+#ifndef WIN32
+			*tmp = rfc_tolower (*tmp);
+			if (*tmp == '/')
+#else
+			/* win32 can't handle filenames with \|/><:"*? characters */
+			if (*tmp == '\\' || *tmp == '|' || *tmp == '/' ||
+				 *tmp == '>'  || *tmp == '<' || *tmp == ':' ||
+				 *tmp == '\"' || *tmp == '*' || *tmp == '?')
+#endif
+				*tmp = '_';
+		}
+		tmp += mbl;
+	}
+
+	return ret;
+}
+
+/* like strcpy, but % turns into %% */
+
+static char *
+log_escape_strcpy (char *dest, char *src, char *end)
+{
+	while (*src)
+	{
+		*dest = *src;
+		if (dest + 1 == end)
+			break;
+		dest++;
+		src++;
+
+		if (*src == '%')
+		{
+			if (dest + 1 == end)
+				break;
+			dest[0] = '%';
+			dest++;
+		}
+	}
+
+	dest[0] = 0;
+	return dest - 1;
+}
+
+/* substitutes %c %n %s into buffer */
+
+static void
+log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
+{
+	char *end = buf + bufsize;
+
+	while (1)
+	{
+		switch (fmt[0])
+		{
+		case 0:
+			buf[0] = 0;
+			return;
+
+		case '%':
+			fmt++;
+			switch (fmt[0])
+			{
+			case 'c':
+				buf = log_escape_strcpy (buf, c, end);
+				break;
+			case 'n':
+				buf = log_escape_strcpy (buf, n, end);
+				break;
+			case 's':
+				buf = log_escape_strcpy (buf, s, end);
+				break;
+			default:
+				buf[0] = '%';
+				buf++;
+				buf[0] = fmt[0];
+				break;
+			}
+			break;
+
+		default:
+			buf[0] = fmt[0];
+		}
+		fmt++;
+		buf++;
+		/* doesn't fit? */
+		if (buf == end)
+		{
+			buf[-1] = 0;
+			return;
+		}
+	}
+}
+
+static char *
+log_create_pathname (char *servname, char *channame, char *netname)
+{
+	char fname[384];
+	char fnametime[384];
+	char *fs;
+	struct tm *tm;
+	time_t now;
+
+	if (!netname)
+		netname = "NETWORK";
+
+	/* first, everything is in UTF-8 */
+	if (!rfc_casecmp (channame, servname))
+		channame = strdup ("server");
+	else
+		channame = log_create_filename (channame);
+	log_insert_vars (fname, sizeof (fname), prefs.logmask, channame, netname, servname);
+	free (channame);
+
+	/* insert time/date */
+	now = time (NULL);
+	tm = localtime (&now);
+	strftime (fnametime, sizeof (fnametime), fname, tm);
+
+	/* create final path/filename */
+#ifdef WIN32
+	if (fnametime[0] == '/' || (fnametime[0] >= 'A' && fnametime[1] == ':'))
+#else
+	if (fnametime[0] == '/')	/* is it fullpath already? */
+#endif
+		snprintf (fname, sizeof (fname), "%s", fnametime);
+	else
+		snprintf (fname, sizeof (fname), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime);
+
+	/* now we need it in FileSystem encoding */
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+
+	/* create all the subdirectories */
+	if (fs)
+		mkdir_p (fs);
+
+	return fs;
+}
+
+static int
+log_open_file (char *servname, char *channame, char *netname)
+{
+	char buf[512];
+	int fd;
+	char *file;
+	time_t currenttime;
+
+	file = log_create_pathname (servname, channame, netname);
+	if (!file)
+		return -1;
+
+#ifdef WIN32
+	fd = open (file, O_CREAT | O_APPEND | O_WRONLY, S_IREAD|S_IWRITE);
+#else
+	fd = open (file, O_CREAT | O_APPEND | O_WRONLY, 0644);
+#endif
+	g_free (file);
+
+	if (fd == -1)
+		return -1;
+	currenttime = time (NULL);
+	write (fd, buf,
+			 snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"),
+						  ctime (&currenttime)));
+
+	return fd;
+}
+
+static void
+log_open (session *sess)
+{
+	static gboolean log_error = FALSE;
+
+	log_close (sess);
+	sess->logfd = log_open_file (sess->server->servername, sess->channel,
+										  server_get_network (sess->server, FALSE));
+
+	if (!log_error && sess->logfd == -1)
+	{
+		char message[512];
+		snprintf (message, sizeof (message),
+					_("* Can't open log file(s) for writing. Check the\n" \
+					  "  permissions on %s/xchatlogs"), get_xdir_utf8 ());
+		fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR);
+
+		log_error = TRUE;
+	}
+}
+
+void
+log_open_or_close (session *sess)
+{
+	if (sess->text_logging == SET_DEFAULT)
+	{
+		if (prefs.logging)
+			log_open (sess);
+		else
+			log_close (sess);
+	}
+	else
+	{
+		if (sess->text_logging)
+			log_open (sess);
+		else
+			log_close (sess);
+	}
+}
+
+int
+get_stamp_str (char *fmt, time_t tim, char **ret)
+{
+	char *loc = NULL;
+	char dest[128];
+	gsize len;
+
+	/* strftime wants the format string in LOCALE! */
+	if (!prefs.utf8_locale)
+	{
+		const gchar *charset;
+
+		g_get_charset (&charset);
+		loc = g_convert_with_fallback (fmt, -1, charset, "UTF-8", "?", 0, 0, 0);
+		if (loc)
+			fmt = loc;
+	}
+
+	len = strftime (dest, sizeof (dest), fmt, localtime (&tim));
+	if (len)
+	{
+		if (prefs.utf8_locale)
+			*ret = g_strdup (dest);
+		else
+			*ret = g_locale_to_utf8 (dest, len, 0, &len, 0);
+	}
+
+	if (loc)
+		g_free (loc);
+
+	return len;
+}
+
+static void
+log_write (session *sess, char *text)
+{
+	char *temp;
+	char *stamp;
+	char *file;
+	int len;
+
+	if (sess->text_logging == SET_DEFAULT)
+	{
+		if (!prefs.logging)
+			return;
+	}
+	else
+	{
+		if (sess->text_logging != SET_ON)
+			return;
+	}
+
+	if (sess->logfd == -1)
+		log_open (sess);
+
+	/* change to a different log file? */
+	file = log_create_pathname (sess->server->servername, sess->channel,
+										 server_get_network (sess->server, FALSE));
+	if (file)
+	{
+		if (access (file, F_OK) != 0)
+		{
+			close (sess->logfd);
+			sess->logfd = log_open_file (sess->server->servername, sess->channel,
+												  server_get_network (sess->server, FALSE));
+		}
+		g_free (file);
+	}
+
+	if (prefs.timestamp_logs)
+	{
+		len = get_stamp_str (prefs.timestamp_log_format, time (0), &stamp);
+		if (len)
+		{
+			write (sess->logfd, stamp, len);
+			g_free (stamp);
+		}
+	}
+	temp = strip_color (text, -1, STRIP_ALL);
+	len = strlen (temp);
+	write (sess->logfd, temp, len);
+	/* lots of scripts/plugins print without a \n at the end */
+	if (temp[len - 1] != '\n')
+		write (sess->logfd, "\n", 1);	/* emulate what xtext would display */
+	g_free (temp);
+}
+
+/* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8                           */
+/* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */
+/*           2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */
+/*              thing useful in this range and it helps us receive from mIRC */
+/*           3. The five undefined chars in CP1252 80-9f are replaced with   */
+/*              ISO-8859-15 control codes.                                   */
+/*           4. Handles 0xa4 as a Euro symbol ala ISO-8859-15.               */
+/*           5. Uses ISO-8859-1 (which matches CP1252) for everything else.  */
+/*           6. This routine measured 3x faster than g_convert :)            */
+
+static unsigned char *
+iso_8859_1_to_utf8 (unsigned char *text, int len, gsize *bytes_written)
+{
+	unsigned int idx;
+	unsigned char *res, *output;
+	static const unsigned short lowtable[] = /* 74 byte table for 80-a4 */
+	{
+	/* compressed utf-8 table: if the first byte's 0x20 bit is set, it
+	   indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */
+		0x82ac, /* 80 Euro. CP1252 from here on... */
+		0xe281, /* 81 NA */
+		0x809a, /* 82 */
+		0xe692, /* 83 */
+		0x809e, /* 84 */
+		0x80a6, /* 85 */
+		0x80a0, /* 86 */
+		0x80a1, /* 87 */
+		0xeb86, /* 88 */
+		0x80b0, /* 89 */
+		0xe5a0, /* 8a */
+		0x80b9, /* 8b */
+		0xe592, /* 8c */
+		0xe28d, /* 8d NA */
+		0xe5bd, /* 8e */
+		0xe28f, /* 8f NA */
+		0xe290, /* 90 NA */
+		0x8098, /* 91 */
+		0x8099, /* 92 */
+		0x809c, /* 93 */
+		0x809d, /* 94 */
+		0x80a2, /* 95 */
+		0x8093, /* 96 */
+		0x8094, /* 97 */
+		0xeb9c, /* 98 */
+		0x84a2, /* 99 */
+		0xe5a1, /* 9a */
+		0x80ba, /* 9b */
+		0xe593, /* 9c */
+		0xe29d, /* 9d NA */
+		0xe5be, /* 9e */
+		0xe5b8, /* 9f */
+		0xe2a0, /* a0 */
+		0xe2a1, /* a1 */
+		0xe2a2, /* a2 */
+		0xe2a3, /* a3 */
+		0x82ac  /* a4 ISO-8859-15 Euro. */
+	};
+
+	if (len == -1)
+		len = strlen (text);
+
+	/* worst case scenario: every byte turns into 3 bytes */
+	res = output = g_malloc ((len * 3) + 1);
+	if (!output)
+		return NULL;
+
+	while (len)
+	{
+		if (G_LIKELY (*text < 0x80))
+		{
+			*output = *text;	/* ascii maps directly */
+		}
+		else if (*text <= 0xa4)	/* 80-a4 use a lookup table */
+		{
+			idx = *text - 0x80;
+			if (lowtable[idx] & 0x2000)
+			{
+				*output++ = (lowtable[idx] >> 8) & 0xdf; /* 2 byte utf-8 */
+				*output = lowtable[idx] & 0xff;
+			}
+			else
+			{
+				*output++ = 0xe2;	/* 3 byte utf-8 */
+				*output++ = (lowtable[idx] >> 8) & 0xff;
+				*output = lowtable[idx] & 0xff;
+			}
+		}
+		else if (*text < 0xc0)
+		{
+			*output++ = 0xc2;
+			*output = *text;
+		}
+		else
+		{
+			*output++ = 0xc3;
+			*output = *text - 0x40;
+		}
+		output++;
+		text++;
+		len--;
+	}
+	*output = 0;	/* terminate */
+	*bytes_written = output - res;
+
+	return res;
+}
+
+char *
+text_validate (char **text, int *len)
+{
+	char *utf;
+	gsize utf_len;
+
+	/* valid utf8? */
+	if (g_utf8_validate (*text, *len, 0))
+		return NULL;
+
+#ifdef WIN32
+	if (GetACP () == 1252) /* our routine is better than iconv's 1252 */
+#else
+	if (prefs.utf8_locale)
+#endif
+		/* fallback to iso-8859-1 */
+		utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
+	else
+	{
+		/* fallback to locale */
+		utf = g_locale_to_utf8 (*text, *len, 0, &utf_len, NULL);
+		if (!utf)
+			utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
+	}
+
+	if (!utf) 
+	{
+		*text = g_strdup ("%INVALID%");
+		*len = 9;
+	} else
+	{
+		*text = utf;
+		*len = utf_len;
+	}
+
+	return utf;
+}
+
+void
+PrintText (session *sess, char *text)
+{
+	char *conv;
+
+	if (!sess)
+	{
+		if (!sess_list)
+			return;
+		sess = (session *) sess_list->data;
+	}
+
+	/* make sure it's valid utf8 */
+	if (text[0] == 0)
+	{
+		text = "\n";
+		conv = NULL;
+	} else
+	{
+		int len = -1;
+		conv = text_validate ((char **)&text, &len);
+	}
+
+	log_write (sess, text);
+	scrollback_save (sess, text);
+	fe_print_text (sess, text, 0);
+
+	if (conv)
+		g_free (conv);
+}
+
+void
+PrintTextf (session *sess, char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	PrintText (sess, buf);
+	g_free (buf);
+}
+
+/* Print Events stuff here --AGL */
+
+/* Consider the following a NOTES file:
+
+   The main upshot of this is:
+   * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
+   * The default text engine can be config'ed
+
+   By default it should appear *exactly* the same (I'm working hard not to change the default style) but if you go into Settings->Edit Event Texts you can change the text's. The format is thus:
+
+   The normal %Cx (color) and %B (bold) etc work
+
+   $x is replaced with the data in var x (e.g. $1 is often the nick)
+
+   $axxx is replace with a single byte of value xxx (in base 10)
+
+   AGL (990507)
+ */
+
+/* These lists are thus:
+   pntevts_text[] are the strings the user sees (WITH %x etc)
+   pntevts[] are the data strings with \000 etc
+ */
+
+/* To add a new event:
+
+   Think up a name (like "Join")
+   Make up a pevt_name_help struct
+	Add an entry to textevents.in
+	Type: make textevents
+ */
+
+/* Internals:
+
+   On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
+   defaults are loaded. Any missing events are filled from defaults.
+   Each event is parsed by pevt_build_string and a binary output is produced
+   which looks like:
+
+   (byte) value: 0 = {
+   (int) numbers of bytes
+   (char []) that number of byte to be memcpy'ed into the buffer
+   }
+   1 =
+   (byte) number of varable to insert
+   2 = end of buffer
+
+   Each XP_TE_* signal is hard coded to call text_emit which calls
+   display_event which decodes the data
+
+   This means that this system *should be faster* than snprintf because
+   it always 'knows' that format of the string (basically is preparses much
+   of the work)
+
+   --AGL
+ */
+
+char *pntevts_text[NUM_XP];
+char *pntevts[NUM_XP];
+
+#define pevt_generic_none_help NULL
+
+static char * const pevt_genmsg_help[] = {
+	N_("Left message"),
+	N_("Right message"),
+};
+
+static char * const pevt_join_help[] = {
+	N_("The nick of the joining person"),
+	N_("The channel being joined"),
+	N_("The host of the person"),
+};
+
+static char * const pevt_chanaction_help[] = {
+	N_("Nickname"),
+	N_("The action"),
+	N_("Mode char"),
+	N_("Identified text"),
+};
+
+static char * const pevt_chanmsg_help[] = {
+	N_("Nickname"),
+	N_("The text"),
+	N_("Mode char"),
+	N_("Identified text"),
+};
+
+static char * const pevt_privmsg_help[] = {
+	N_("Nickname"),
+	N_("The message"),
+	N_("Identified text")
+};
+
+static char * const pevt_changenick_help[] = {
+	N_("Old nickname"),
+	N_("New nickname"),
+};
+
+static char * const pevt_newtopic_help[] = {
+	N_("Nick of person who changed the topic"),
+	N_("Topic"),
+	N_("Channel"),
+};
+
+static char * const pevt_topic_help[] = {
+	N_("Channel"),
+	N_("Topic"),
+};
+
+static char * const pevt_kick_help[] = {
+	N_("The nickname of the kicker"),
+	N_("The person being kicked"),
+	N_("The channel"),
+	N_("The reason"),
+};
+
+static char * const pevt_part_help[] = {
+	N_("The nick of the person leaving"),
+	N_("The host of the person"),
+	N_("The channel"),
+};
+
+static char * const pevt_chandate_help[] = {
+	N_("The channel"),
+	N_("The time"),
+};
+
+static char * const pevt_topicdate_help[] = {
+	N_("The channel"),
+	N_("The creator"),
+	N_("The time"),
+};
+
+static char * const pevt_quit_help[] = {
+	N_("Nick"),
+	N_("Reason"),
+	N_("Host"),
+};
+
+static char * const pevt_pingrep_help[] = {
+	N_("Who it's from"),
+	N_("The time in x.x format (see below)"),
+};
+
+static char * const pevt_notice_help[] = {
+	N_("Who it's from"),
+	N_("The message"),
+};
+
+static char * const pevt_channotice_help[] = {
+	N_("Who it's from"),
+	N_("The Channel it's going to"),
+	N_("The message"),
+};
+
+static char * const pevt_uchangenick_help[] = {
+	N_("Old nickname"),
+	N_("New nickname"),
+};
+
+static char * const pevt_ukick_help[] = {
+	N_("The person being kicked"),
+	N_("The channel"),
+	N_("The nickname of the kicker"),
+	N_("The reason"),
+};
+
+static char * const pevt_partreason_help[] = {
+	N_("The nick of the person leaving"),
+	N_("The host of the person"),
+	N_("The channel"),
+	N_("The reason"),
+};
+
+static char * const pevt_ctcpsnd_help[] = {
+	N_("The sound"),
+	N_("The nick of the person"),
+	N_("The channel"),
+};
+
+static char * const pevt_ctcpgen_help[] = {
+	N_("The CTCP event"),
+	N_("The nick of the person"),
+};
+
+static char * const pevt_ctcpgenc_help[] = {
+	N_("The CTCP event"),
+	N_("The nick of the person"),
+	N_("The Channel it's going to"),
+};
+
+static char * const pevt_chansetkey_help[] = {
+	N_("The nick of the person who set the key"),
+	N_("The key"),
+};
+
+static char * const pevt_chansetlimit_help[] = {
+	N_("The nick of the person who set the limit"),
+	N_("The limit"),
+};
+
+static char * const pevt_chanop_help[] = {
+	N_("The nick of the person who did the op'ing"),
+	N_("The nick of the person who has been op'ed"),
+};
+
+static char * const pevt_chanhop_help[] = {
+	N_("The nick of the person who has been halfop'ed"),
+	N_("The nick of the person who did the halfop'ing"),
+};
+
+static char * const pevt_chanvoice_help[] = {
+	N_("The nick of the person who did the voice'ing"),
+	N_("The nick of the person who has been voice'ed"),
+};
+
+static char * const pevt_chanban_help[] = {
+	N_("The nick of the person who did the banning"),
+	N_("The ban mask"),
+};
+
+static char * const pevt_chanrmkey_help[] = {
+	N_("The nick who removed the key"),
+};
+
+static char * const pevt_chanrmlimit_help[] = {
+	N_("The nick who removed the limit"),
+};
+
+static char * const pevt_chandeop_help[] = {
+	N_("The nick of the person of did the deop'ing"),
+	N_("The nick of the person who has been deop'ed"),
+};
+static char * const pevt_chandehop_help[] = {
+	N_("The nick of the person of did the dehalfop'ing"),
+	N_("The nick of the person who has been dehalfop'ed"),
+};
+
+static char * const pevt_chandevoice_help[] = {
+	N_("The nick of the person of did the devoice'ing"),
+	N_("The nick of the person who has been devoice'ed"),
+};
+
+static char * const pevt_chanunban_help[] = {
+	N_("The nick of the person of did the unban'ing"),
+	N_("The ban mask"),
+};
+
+static char * const pevt_chanexempt_help[] = {
+	N_("The nick of the person who did the exempt"),
+	N_("The exempt mask"),
+};
+
+static char * const pevt_chanrmexempt_help[] = {
+	N_("The nick of the person removed the exempt"),
+	N_("The exempt mask"),
+};
+
+static char * const pevt_chaninvite_help[] = {
+	N_("The nick of the person who did the invite"),
+	N_("The invite mask"),
+};
+
+static char * const pevt_chanrminvite_help[] = {
+	N_("The nick of the person removed the invite"),
+	N_("The invite mask"),
+};
+
+static char * const pevt_chanmodegen_help[] = {
+	N_("The nick of the person setting the mode"),
+	N_("The mode's sign (+/-)"),
+	N_("The mode letter"),
+	N_("The channel it's being set on"),
+};
+
+static char * const pevt_whois1_help[] = {
+	N_("Nickname"),
+	N_("Username"),
+	N_("Host"),
+	N_("Full name"),
+};
+
+static char * const pevt_whois2_help[] = {
+	N_("Nickname"),
+	N_("Channel Membership/\"is an IRC operator\""),
+};
+
+static char * const pevt_whois3_help[] = {
+	N_("Nickname"),
+	N_("Server Information"),
+};
+
+static char * const pevt_whois4_help[] = {
+	N_("Nickname"),
+	N_("Idle time"),
+};
+
+static char * const pevt_whois4t_help[] = {
+	N_("Nickname"),
+	N_("Idle time"),
+	N_("Signon time"),
+};
+
+static char * const pevt_whois5_help[] = {
+	N_("Nickname"),
+	N_("Away reason"),
+};
+
+static char * const pevt_whois6_help[] = {
+	N_("Nickname"),
+};
+
+static char * const pevt_whoisid_help[] = {
+	N_("Nickname"),
+	N_("Message"),
+	"Numeric"
+};
+
+static char * const pevt_whoisauth_help[] = {
+	N_("Nickname"),
+	N_("Message"),
+	N_("Account"),
+};
+
+static char * const pevt_whoisrealhost_help[] = {
+	N_("Nickname"),
+	N_("Real user@host"),
+	N_("Real IP"),
+	N_("Message"),
+};
+
+static char * const pevt_generic_channel_help[] = {
+	N_("Channel Name"),
+};
+
+static char * const pevt_servertext_help[] = {
+	N_("Text"),
+	N_("Server Name"),
+	N_("Raw Numeric or Identifier")
+};
+
+static char * const pevt_sslmessage_help[] = {
+	N_("Text"),
+	N_("Server Name")
+};
+
+static char * const pevt_invited_help[] = {
+	N_("Channel Name"),
+	N_("Nick of person who invited you"),
+	N_("Server Name"),
+};
+
+static char * const pevt_usersonchan_help[] = {
+	N_("Channel Name"),
+	N_("Users"),
+};
+
+static char * const pevt_nickclash_help[] = {
+	N_("Nickname in use"),
+	N_("Nick being tried"),
+};
+
+static char * const pevt_connfail_help[] = {
+	N_("Error"),
+};
+
+static char * const pevt_connect_help[] = {
+	N_("Host"),
+	N_("IP"),
+	N_("Port"),
+};
+
+static char * const pevt_sconnect_help[] = {
+	"PID"
+};
+
+static char * const pevt_generic_nick_help[] = {
+	N_("Nickname"),
+	N_("Server Name"),
+	N_("Network")
+};
+
+static char * const pevt_chanmodes_help[] = {
+	N_("Channel Name"),
+	N_("Modes string"),
+};
+
+static char * const pevt_rawmodes_help[] = {
+	N_("Nickname"),
+	N_("Modes string"),
+};
+
+static char * const pevt_kill_help[] = {
+	N_("Nickname"),
+	N_("Reason"),
+};
+
+static char * const pevt_dccchaterr_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+	N_("Port"),
+	N_("Error"),
+};
+
+static char * const pevt_dccstall_help[] = {
+	N_("DCC Type"),
+	N_("Filename"),
+	N_("Nickname"),
+};
+
+static char * const pevt_generic_file_help[] = {
+	N_("Filename"),
+	N_("Error"),
+};
+
+static char * const pevt_dccrecverr_help[] = {
+	N_("Filename"),
+	N_("Destination filename"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccrecvcomp_help[] = {
+	N_("Filename"),
+	N_("Destination filename"),
+	N_("Nickname"),
+	N_("CPS"),
+};
+
+static char * const pevt_dccconfail_help[] = {
+	N_("DCC Type"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccchatcon_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+};
+
+static char * const pevt_dcccon_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+	N_("Filename"),
+};
+
+static char * const pevt_dccsendfail_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccsendcomp_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("CPS"),
+};
+
+static char * const pevt_dccoffer_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("Pathname"),
+};
+
+static char * const pevt_dccfileabort_help[] = {
+	N_("Nickname"),
+	N_("Filename")
+};
+
+static char * const pevt_dccchatabort_help[] = {
+	N_("Nickname"),
+};
+
+static char * const pevt_dccresumeoffer_help[] = {
+	N_("Nickname"),
+	N_("Filename"),
+	N_("Position"),
+};
+
+static char * const pevt_dccsendoffer_help[] = {
+	N_("Nickname"),
+	N_("Filename"),
+	N_("Size"),
+	N_("IP address"),
+};
+
+static char * const pevt_dccgenericoffer_help[] = {
+	N_("DCC String"),
+	N_("Nickname"),
+};
+
+static char * const pevt_notifynumber_help[] = {
+	N_("Number of notify items"),
+};
+
+static char * const pevt_serverlookup_help[] = {
+	N_("Server Name"),
+};
+
+static char * const pevt_servererror_help[] = {
+	N_("Text"),
+};
+
+static char * const pevt_foundip_help[] = {
+	N_("IP"),
+};
+
+static char * const pevt_dccrename_help[] = {
+	N_("Old Filename"),
+	N_("New Filename"),
+};
+
+static char * const pevt_ctcpsend_help[] = {
+	N_("Receiver"),
+	N_("Message"),
+};
+
+static char * const pevt_ignoreaddremove_help[] = {
+	N_("Hostmask"),
+};
+
+static char * const pevt_resolvinguser_help[] = {
+	N_("Nickname"),
+	N_("Hostname"),
+};
+
+static char * const pevt_malformed_help[] = {
+	N_("Nickname"),
+	N_("The Packet"),
+};
+
+static char * const pevt_pingtimeout_help[] = {
+	N_("Seconds"),
+};
+
+static char * const pevt_uinvite_help[] = {
+	N_("Nick of person who have been invited"),
+	N_("Channel Name"),
+	N_("Server Name"),
+};
+
+static char * const pevt_banlist_help[] = {
+	N_("Channel"),
+	N_("Banmask"),
+	N_("Who set the ban"),
+	N_("Ban time"),
+};
+
+static char * const pevt_discon_help[] = {
+	N_("Error"),
+};
+
+#include "textevents.h"
+
+static void
+pevent_load_defaults ()
+{
+	int i;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts_text[i])
+			free (pntevts_text[i]);
+
+		/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+		if (te[i].num_args & 128)
+			pntevts_text[i] = strdup (te[i].def);
+		else
+			pntevts_text[i] = strdup (_(te[i].def));
+	}
+}
+
+void
+pevent_make_pntevts ()
+{
+	int i, m;
+	char out[1024];
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts[i] != NULL)
+			free (pntevts[i]);
+		if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
+		{
+			snprintf (out, sizeof (out),
+						 _("Error parsing event %s.\nLoading default."), te[i].name);
+			fe_message (out, FE_MSG_WARN);
+			free (pntevts_text[i]);
+			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+			if (te[i].num_args & 128)
+				pntevts_text[i] = strdup (te[i].def);
+			else
+				pntevts_text[i] = strdup (_(te[i].def));
+			if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
+			{
+				fprintf (stderr,
+							"XChat CRITICAL *** default event text failed to build!\n");
+				abort ();
+			}
+		}
+	}
+}
+
+/* Loading happens at 2 levels:
+   1) File is read into blocks
+   2) Pe block is parsed and loaded
+
+   --AGL */
+
+/* Better hope you pass good args.. --AGL */
+
+static void
+pevent_trigger_load (int *i_penum, char **i_text, char **i_snd)
+{
+	int penum = *i_penum, len;
+	char *text = *i_text, *snd = *i_snd;
+
+	if (penum != -1 && text != NULL)
+	{
+		len = strlen (text) + 1;
+		if (pntevts_text[penum])
+			free (pntevts_text[penum]);
+		pntevts_text[penum] = malloc (len);
+		memcpy (pntevts_text[penum], text, len);
+	}
+
+	if (text)
+		free (text);
+	if (snd)
+		free (snd);
+	*i_text = NULL;
+	*i_snd = NULL;
+	*i_penum = 0;
+}
+
+static int
+pevent_find (char *name, int *i_i)
+{
+	int i = *i_i, j;
+
+	j = i + 1;
+	while (1)
+	{
+		if (j == NUM_XP)
+			j = 0;
+		if (strcmp (te[j].name, name) == 0)
+		{
+			*i_i = j;
+			return j;
+		}
+		if (j == i)
+			return -1;
+		j++;
+	}
+}
+
+int
+pevent_load (char *filename)
+{
+	/* AGL, I've changed this file and pevent_save, could you please take a look at
+	 *      the changes and possibly modify them to suit you
+	 *      //David H
+	 */
+	char *buf, *ibuf;
+	int fd, i = 0, pnt = 0;
+	struct stat st;
+	char *text = NULL, *snd = NULL;
+	int penum = 0;
+	char *ofs;
+
+	if (filename == NULL)
+		fd = xchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
+	else
+		fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
+
+	if (fd == -1)
+		return 1;
+	if (fstat (fd, &st) != 0)
+		return 1;
+	ibuf = malloc (st.st_size);
+	read (fd, ibuf, st.st_size);
+	close (fd);
+
+	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
+	{
+		if (buf[0] == '#')
+			continue;
+		if (strlen (buf) == 0)
+			continue;
+
+		ofs = strchr (buf, '=');
+		if (!ofs)
+			continue;
+		*ofs = 0;
+		ofs++;
+		/*if (*ofs == 0)
+			continue;*/
+
+		if (strcmp (buf, "event_name") == 0)
+		{
+			if (penum >= 0)
+				pevent_trigger_load (&penum, &text, &snd);
+			penum = pevent_find (ofs, &i);
+			continue;
+		} else if (strcmp (buf, "event_text") == 0)
+		{
+			if (text)
+				free (text);
+
+#if 0
+			/* This allows updating of old strings. We don't use new defaults
+				if the user has customized the strings (.e.g a text theme).
+				Hash of the old default is enough to identify and replace it.
+				This only works in English. */
+
+			switch (g_str_hash (ofs))
+			{
+			case 0x526743a4:
+		/* %C08,02 Hostmask                  PRIV NOTI CHAN CTCP INVI UNIG %O */
+				text = strdup (te[XP_TE_IGNOREHEADER].def);
+				break;
+
+			case 0xe91bc9c2:
+		/* %C08,02                                                         %O */
+				text = strdup (te[XP_TE_IGNOREFOOTER].def);
+				break;
+
+			case 0x1fbfdf22:
+		/* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */
+				text = strdup (te[XP_TE_DCCFILEERR].def);
+				break;
+
+			default:
+				text = strdup (ofs);
+			}
+#else
+			text = strdup (ofs);
+#endif
+
+			continue;
+		}/* else if (strcmp (buf, "event_sound") == 0)
+		{
+			if (snd)
+				free (snd);
+			snd = strdup (ofs);
+			continue;
+		}*/
+
+		continue;
+	}
+
+	pevent_trigger_load (&penum, &text, &snd);
+	free (ibuf);
+	return 0;
+}
+
+static void
+pevent_check_all_loaded ()
+{
+	int i;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts_text[i] == NULL)
+		{
+			/*printf ("%s\n", te[i].name);
+			snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of XChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]);
+			   gtkutil_simpledialog(out); */
+			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+			if (te[i].num_args & 128)
+				pntevts_text[i] = strdup (te[i].def);
+			else
+				pntevts_text[i] = strdup (_(te[i].def));
+		}
+	}
+}
+
+void
+load_text_events ()
+{
+	memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
+	memset (&pntevts, 0, sizeof (char *) * (NUM_XP));
+
+	if (pevent_load (NULL))
+		pevent_load_defaults ();
+	pevent_check_all_loaded ();
+	pevent_make_pntevts ();
+}
+
+/*
+	CL: format_event now handles filtering of arguments:
+	1) if prefs.stripcolor is set, filter all style control codes from arguments
+	2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
+*/
+#define ARG_FLAG(argn) (1 << (argn))
+
+void
+format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args)
+{
+	int len, oi, ii, numargs;
+	char *i, *ar, d, a, done_all = FALSE;
+
+	i = pntevts[index];
+	numargs = te[index].num_args & 0x7f;
+
+	oi = ii = len = d = a = 0;
+	o[0] = 0;
+
+	if (i == NULL)
+		return;
+
+	while (done_all == FALSE)
+	{
+		d = i[ii++];
+		switch (d)
+		{
+		case 0:
+			memcpy (&len, &(i[ii]), sizeof (int));
+			ii += sizeof (int);
+			if (oi + len > sizeofo)
+			{
+				printf ("Overflow in display_event (%s)\n", i);
+				o[0] = 0;
+				return;
+			}
+			memcpy (&(o[oi]), &(i[ii]), len);
+			oi += len;
+			ii += len;
+			break;
+		case 1:
+			a = i[ii++];
+			if (a > numargs)
+			{
+				fprintf (stderr,
+							"XChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
+							a, numargs, i);
+				break;
+			}
+			ar = args[(int) a + 1];
+			if (ar == NULL)
+			{
+				printf ("arg[%d] is NULL in print event\n", a + 1);
+			} else
+			{
+				if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL);
+				else len = strip_hidden_attribute (ar, &o[oi]);
+				oi += len;
+			}
+			break;
+		case 2:
+			o[oi++] = '\n';
+			o[oi++] = 0;
+			done_all = TRUE;
+			continue;
+		case 3:
+/*			if (sess->type == SESS_DIALOG)
+			{
+				if (prefs.dialog_indent_nicks)
+					o[oi++] = '\t';
+				else
+					o[oi++] = ' ';
+			} else
+			{*/
+				if (prefs.indent_nicks)
+					o[oi++] = '\t';
+				else
+					o[oi++] = ' ';
+			/*}*/
+			break;
+		}
+	}
+	o[oi] = 0;
+	if (*o == '\n')
+		o[0] = 0;
+}
+
+static void
+display_event (session *sess, int event, char **args, unsigned int stripcolor_args)
+{
+	char o[4096];
+	format_event (sess, event, args, o, sizeof (o), stripcolor_args);
+	if (o[0])
+		PrintText (sess, o);
+}
+
+int
+pevt_build_string (const char *input, char **output, int *max_arg)
+{
+	struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next;
+	int clen;
+	char o[4096], d, *obuf, *i;
+	int oi, ii, max = -1, len, x;
+
+	len = strlen (input);
+	i = malloc (len + 1);
+	memcpy (i, input, len + 1);
+	check_special_chars (i, TRUE);
+
+	len = strlen (i);
+
+	clen = oi = ii = 0;
+
+	for (;;)
+	{
+		if (ii == len)
+			break;
+		d = i[ii++];
+		if (d != '$')
+		{
+			o[oi++] = d;
+			continue;
+		}
+		if (i[ii] == '$')
+		{
+			o[oi++] = '$';
+			continue;
+		}
+		if (oi > 0)
+		{
+			s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+			if (base == NULL)
+				base = s;
+			if (last != NULL)
+				last->next = s;
+			last = s;
+			s->next = NULL;
+			s->data = malloc (oi + sizeof (int) + 1);
+			s->len = oi + sizeof (int) + 1;
+			clen += oi + sizeof (int) + 1;
+			s->data[0] = 0;
+			memcpy (&(s->data[1]), &oi, sizeof (int));
+			memcpy (&(s->data[1 + sizeof (int)]), o, oi);
+			oi = 0;
+		}
+		if (ii == len)
+		{
+			fe_message ("String ends with a $", FE_MSG_WARN);
+			return 1;
+		}
+		d = i[ii++];
+		if (d == 'a')
+		{								  /* Hex value */
+			x = 0;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x = d * 100;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x += d * 10;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x += d;
+			if (x > 255)
+				goto a_range_error;
+			o[oi++] = x;
+			continue;
+
+		 a_len_error:
+			fe_message ("String ends in $a", FE_MSG_WARN);
+			return 1;
+		 a_range_error:
+			fe_message ("$a value is greater than 255", FE_MSG_WARN);
+			return 1;
+		}
+		if (d == 't')
+		{
+			/* Tab - if tabnicks is set then write '\t' else ' ' */
+			s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+			if (base == NULL)
+				base = s;
+			if (last != NULL)
+				last->next = s;
+			last = s;
+			s->next = NULL;
+			s->data = malloc (1);
+			s->len = 1;
+			clen += 1;
+			s->data[0] = 3;
+
+			continue;
+		}
+		if (d < '1' || d > '9')
+		{
+			snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d);
+			fe_message (o, FE_MSG_WARN);
+			return 1;
+		}
+		d -= '0';
+		if (max < d)
+			max = d;
+		s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+		if (base == NULL)
+			base = s;
+		if (last != NULL)
+			last->next = s;
+		last = s;
+		s->next = NULL;
+		s->data = malloc (2);
+		s->len = 2;
+		clen += 2;
+		s->data[0] = 1;
+		s->data[1] = d - 1;
+	}
+	if (oi > 0)
+	{
+		s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+		if (base == NULL)
+			base = s;
+		if (last != NULL)
+			last->next = s;
+		last = s;
+		s->next = NULL;
+		s->data = malloc (oi + sizeof (int) + 1);
+		s->len = oi + sizeof (int) + 1;
+		clen += oi + sizeof (int) + 1;
+		s->data[0] = 0;
+		memcpy (&(s->data[1]), &oi, sizeof (int));
+		memcpy (&(s->data[1 + sizeof (int)]), o, oi);
+		oi = 0;
+	}
+	s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+	if (base == NULL)
+		base = s;
+	if (last != NULL)
+		last->next = s;
+	last = s;
+	s->next = NULL;
+	s->data = malloc (1);
+	s->len = 1;
+	clen += 1;
+	s->data[0] = 2;
+
+	oi = 0;
+	s = base;
+	obuf = malloc (clen);
+	while (s)
+	{
+		next = s->next;
+		memcpy (&obuf[oi], s->data, s->len);
+		oi += s->len;
+		free (s->data);
+		free (s);
+		s = next;
+	}
+
+	free (i);
+
+	if (max_arg)
+		*max_arg = max;
+	if (output)
+		*output = obuf;
+
+	return 0;
+}
+
+
+/* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
+/* also light/dark gray (14/15) */
+/* 5,7,8 are all shades of yellow which happen to look dman near the same */
+
+static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
+
+static int
+color_of (char *name)
+{
+	int i = 0, sum = 0;
+
+	while (name[i])
+		sum += name[i++];
+	sum %= sizeof (rcolors) / sizeof (char);
+	return rcolors[sum];
+}
+
+
+/* called by EMIT_SIGNAL macro */
+
+void
+text_emit (int index, session *sess, char *a, char *b, char *c, char *d)
+{
+	char *word[PDIWORDS];
+	int i;
+	unsigned int stripcolor_args = (prefs.stripcolor ? 0xFFFFFFFF : 0);
+	char tbuf[NICKLEN + 4];
+
+	if (prefs.colorednicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG))
+	{
+		snprintf (tbuf, sizeof (tbuf), "\003%d%s", color_of (a), a);
+		a = tbuf;
+		stripcolor_args &= ~ARG_FLAG(1);	/* don't strip color from this argument */
+	}
+
+	word[0] = te[index].name;
+	word[1] = (a ? a : "\000");
+	word[2] = (b ? b : "\000");
+	word[3] = (c ? c : "\000");
+	word[4] = (d ? d : "\000");
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	if (plugin_emit_print (sess, word))
+		return;
+
+	/* If a plugin's callback executes "/close", 'sess' may be invalid */
+	if (!is_session (sess))
+		return;
+
+	switch (index)
+	{
+	case XP_TE_JOIN:
+	case XP_TE_PART:
+	case XP_TE_PARTREASON:
+	case XP_TE_QUIT:
+		/* implement ConfMode / Hide Join and Part Messages */
+		if (chanopt_is_set (prefs.confmode, sess->text_hidejoinpart))
+			return;
+		break;
+
+	/* ===Private message=== */
+	case XP_TE_PRIVMSG:
+	case XP_TE_DPRIVMSG:
+	case XP_TE_PRIVACTION:
+	case XP_TE_DPRIVACTION:
+		if (chanopt_is_set_a (prefs.input_beep_priv, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_priv, sess->alert_taskbar))
+			fe_flash_window (sess);
+		/* why is this one different? because of plugin-tray.c's hooks! ugly */
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+
+	/* ===Highlighted message=== */
+	case XP_TE_HCHANACTION:
+	case XP_TE_HCHANMSG:
+		if (chanopt_is_set_a (prefs.input_beep_hilight, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_hilight, sess->alert_taskbar))
+			fe_flash_window (sess);
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+
+	/* ===Channel message=== */
+	case XP_TE_CHANACTION:
+	case XP_TE_CHANMSG:
+		if (chanopt_is_set_a (prefs.input_beep_chans, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_chans, sess->alert_taskbar))
+			fe_flash_window (sess);
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+	}
+
+	sound_play_event (index);
+	display_event (sess, index, word, stripcolor_args);
+}
+
+char *
+text_find_format_string (char *name)
+{
+	int i = 0;
+
+	i = pevent_find (name, &i);
+	if (i >= 0)
+		return pntevts_text[i];
+
+	return NULL;
+}
+
+int
+text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d)
+{
+	int i = 0;
+
+	i = pevent_find (name, &i);
+	if (i >= 0)
+	{
+		text_emit (i, sess, a, b, c, d);
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+pevent_save (char *fn)
+{
+	int fd, i;
+	char buf[1024];
+
+	if (!fn)
+		fd = xchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE);
+	else
+		fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180,
+									 XOF_FULLPATH | XOF_DOMODE);
+	if (fd == -1)
+	{
+		/*
+		   fe_message ("Error opening config file\n", FALSE); 
+		   If we get here when X-Chat is closing the fe-message causes a nice & hard crash
+		   so we have to use perror which doesn't rely on GTK
+		 */
+
+		perror ("Error opening config file\n");
+		return;
+	}
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		write (fd, buf, snprintf (buf, sizeof (buf),
+										  "event_name=%s\n", te[i].name));
+		write (fd, buf, snprintf (buf, sizeof (buf),
+										  "event_text=%s\n\n", pntevts_text[i]));
+	}
+
+	close (fd);
+}
+
+/* =========================== */
+/* ========== SOUND ========== */
+/* =========================== */
+
+char *sound_files[NUM_XP];
+
+void
+sound_beep (session *sess)
+{
+	if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0])
+		/* user defined beep _file_ */
+		sound_play_event (XP_TE_BEEP);
+	else
+		/* system beep */
+		fe_beep ();
+}
+
+static char *
+sound_find_command (void)
+{
+	/* some sensible unix players. You're bound to have one of them */
+	static const char * const progs[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL};
+	char *cmd;
+	int i = 0;
+
+	if (prefs.soundcmd[0])
+		return g_strdup (prefs.soundcmd);
+
+	while (progs[i])
+	{
+		cmd = g_find_program_in_path (progs[i]);
+		if (cmd)
+			return cmd;
+		i++;
+	}
+
+	return NULL;
+}
+
+void
+sound_play (const char *file, gboolean quiet)
+{
+	char buf[512];
+	char wavfile[512];
+	char *file_fs;
+	char *cmd;
+
+	/* the pevents GUI editor triggers this after removing a soundfile */
+	if (!file[0])
+		return;
+
+#ifdef WIN32
+	/* check for fullpath, windows style */
+	if (strlen (file) > 3 &&
+		 file[1] == ':' && (file[2] == '\\' || file[2] == '/') )
+	{
+		strncpy (wavfile, file, sizeof (wavfile));
+	} else
+#endif
+	if (file[0] != '/')
+	{
+		snprintf (wavfile, sizeof (wavfile), "%s/%s", prefs.sounddir, file);
+	} else
+	{
+		strncpy (wavfile, file, sizeof (wavfile));
+	}
+	wavfile[sizeof (wavfile) - 1] = 0;	/* ensure termination */
+
+	file_fs = xchat_filename_from_utf8 (wavfile, -1, 0, 0, 0);
+	if (!file_fs)
+		return;
+
+	if (access (file_fs, R_OK) == 0)
+	{
+		cmd = sound_find_command ();
+
+#ifdef WIN32
+		if (cmd == NULL || strcmp (cmd, "esdplay") == 0)
+		{
+			PlaySound (file_fs, NULL, SND_NODEFAULT|SND_FILENAME|SND_ASYNC);
+		} else
+#endif
+		{
+			if (cmd)
+			{
+				if (strchr (file_fs, ' '))
+					snprintf (buf, sizeof (buf), "%s \"%s\"", cmd, file_fs);
+				else
+					snprintf (buf, sizeof (buf), "%s %s", cmd, file_fs);
+				buf[sizeof (buf) - 1] = '\0';
+				xchat_exec (buf);
+			}
+		}
+
+		if (cmd)
+			g_free (cmd);
+
+	} else
+	{
+		if (!quiet)
+		{
+			snprintf (buf, sizeof (buf), _("Cannot read sound file:\n%s"), wavfile);
+			fe_message (buf, FE_MSG_ERROR);
+		}
+	}
+
+	g_free (file_fs);
+}
+
+void
+sound_play_event (int i)
+{
+	if (sound_files[i])
+		sound_play (sound_files[i], FALSE);
+}
+
+static void
+sound_load_event (char *evt, char *file)
+{
+	int i = 0;
+
+	if (file[0] && pevent_find (evt, &i) != -1)
+	{
+		if (sound_files[i])
+			free (sound_files[i]);
+		sound_files[i] = strdup (file);
+	}
+}
+
+void
+sound_load ()
+{
+	int fd;
+	char buf[512];
+	char evt[128];
+
+	memset (&sound_files, 0, sizeof (char *) * (NUM_XP));
+
+	fd = xchat_open_file ("sound.conf", O_RDONLY, 0, 0);
+	if (fd == -1)
+		return;
+
+	evt[0] = 0;
+	while (waitline (fd, buf, sizeof buf, FALSE) != -1)
+	{
+		if (strncmp (buf, "event=", 6) == 0)
+		{
+			safe_strcpy (evt, buf + 6, sizeof (evt));
+		}
+		else if (strncmp (buf, "sound=", 6) == 0)
+		{
+			if (evt[0] != 0)
+			{
+				sound_load_event (evt, buf + 6);
+				evt[0] = 0;
+			}
+		}
+	}
+
+	close (fd);
+}
+
+void
+sound_save ()
+{
+	int fd, i;
+	char buf[512];
+
+	fd = xchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180,
+								 XOF_DOMODE);
+	if (fd == -1)
+		return;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (sound_files[i] && sound_files[i][0])
+		{
+			write (fd, buf, snprintf (buf, sizeof (buf),
+											  "event=%s\n", te[i].name));
+			write (fd, buf, snprintf (buf, sizeof (buf),
+											  "sound=%s\n\n", sound_files[i]));
+		}
+	}
+
+	close (fd);
+}
diff --git a/src/common/text.h b/src/common/text.h
new file mode 100644
index 00000000..150821ae
--- /dev/null
+++ b/src/common/text.h
@@ -0,0 +1,42 @@
+#include "textenums.h"
+
+#ifndef XCHAT_TEXT_H
+#define XCHAT_TEXT_H
+
+#define EMIT_SIGNAL(i, sess, a, b, c, d, e) text_emit(i, sess, a, b, c, d)
+
+struct text_event
+{
+	char *name;
+	char * const *help;
+	int num_args;
+	char *def;
+};
+
+void scrollback_close (session *sess);
+void scrollback_load (session *sess);
+
+int text_word_check (char *word, int len);
+void PrintText (session *sess, char *text);
+void PrintTextf (session *sess, char *format, ...);
+void log_close (session *sess);
+void log_open_or_close (session *sess);
+void load_text_events (void);
+void pevent_save (char *fn);
+int pevt_build_string (const char *input, char **output, int *max_arg);
+int pevent_load (char *filename);
+void pevent_make_pntevts (void);
+void text_emit (int index, session *sess, char *a, char *b, char *c, char *d);
+int text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d);
+char *text_validate (char **text, int *len);
+int get_stamp_str (char *fmt, time_t tim, char **ret);
+void format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args);
+char *text_find_format_string (char *name);
+ 
+void sound_play (const char *file, gboolean quiet);
+void sound_play_event (int i);
+void sound_beep (session *);
+void sound_load ();
+void sound_save ();
+
+#endif
diff --git a/src/common/textenums.h b/src/common/textenums.h
new file mode 100644
index 00000000..682a35ef
--- /dev/null
+++ b/src/common/textenums.h
@@ -0,0 +1,76 @@
+/* this file is auto generated, edit textevents.in instead! */
+
+enum
+{
+	XP_TE_ADDNOTIFY,		XP_TE_BANLIST,
+	XP_TE_BANNED,		XP_TE_BEEP,
+	XP_TE_CHANGENICK,		XP_TE_CHANACTION,
+	XP_TE_HCHANACTION,		XP_TE_CHANBAN,
+	XP_TE_CHANDATE,		XP_TE_CHANDEHOP,
+	XP_TE_CHANDEOP,		XP_TE_CHANDEVOICE,
+	XP_TE_CHANEXEMPT,		XP_TE_CHANHOP,
+	XP_TE_CHANINVITE,		XP_TE_CHANLISTHEAD,
+	XP_TE_CHANMSG,		XP_TE_CHANMODEGEN,
+	XP_TE_CHANMODES,		XP_TE_HCHANMSG,
+	XP_TE_CHANNOTICE,		XP_TE_CHANOP,
+	XP_TE_CHANRMEXEMPT,		XP_TE_CHANRMINVITE,
+	XP_TE_CHANRMKEY,		XP_TE_CHANRMLIMIT,
+	XP_TE_CHANSETKEY,		XP_TE_CHANSETLIMIT,
+	XP_TE_CHANUNBAN,		XP_TE_CHANVOICE,
+	XP_TE_CONNECTED,		XP_TE_CONNECT,
+	XP_TE_CONNFAIL,		XP_TE_CTCPGEN,
+	XP_TE_CTCPGENC,		XP_TE_CTCPSEND,
+	XP_TE_CTCPSND,		XP_TE_CTCPSNDC,
+	XP_TE_DCCCHATABORT,		XP_TE_DCCCONCHAT,
+	XP_TE_DCCCHATF,		XP_TE_DCCCHATOFFER,
+	XP_TE_DCCCHATOFFERING,		XP_TE_DCCCHATREOFFER,
+	XP_TE_DCCCONFAIL,		XP_TE_DCCGENERICOFFER,
+	XP_TE_DCCHEAD,		XP_TE_MALFORMED,
+	XP_TE_DCCOFFER,		XP_TE_DCCIVAL,
+	XP_TE_DCCRECVABORT,		XP_TE_DCCRECVCOMP,
+	XP_TE_DCCCONRECV,		XP_TE_DCCRECVERR,
+	XP_TE_DCCFILEERR,		XP_TE_DCCRENAME,
+	XP_TE_DCCRESUMEREQUEST,		XP_TE_DCCSENDABORT,
+	XP_TE_DCCSENDCOMP,		XP_TE_DCCCONSEND,
+	XP_TE_DCCSENDFAIL,		XP_TE_DCCSENDOFFER,
+	XP_TE_DCCSTALL,		XP_TE_DCCTOUT,
+	XP_TE_DELNOTIFY,		XP_TE_DISCON,
+	XP_TE_FOUNDIP,		XP_TE_GENMSG,
+	XP_TE_IGNOREADD,		XP_TE_IGNORECHANGE,
+	XP_TE_IGNOREFOOTER,		XP_TE_IGNOREHEADER,
+	XP_TE_IGNOREREMOVE,		XP_TE_IGNOREEMPTY,
+	XP_TE_INVITE,		XP_TE_INVITED,
+	XP_TE_JOIN,		XP_TE_KEYWORD,
+	XP_TE_KICK,		XP_TE_KILL,
+	XP_TE_MSGSEND,		XP_TE_MOTD,
+	XP_TE_MOTDSKIP,		XP_TE_NICKCLASH,
+	XP_TE_NICKFAIL,		XP_TE_NODCC,
+	XP_TE_NOCHILD,		XP_TE_NOTICE,
+	XP_TE_NOTICESEND,		XP_TE_NOTIFYEMPTY,
+	XP_TE_NOTIFYHEAD,		XP_TE_NOTIFYNUMBER,
+	XP_TE_NOTIFYOFFLINE,		XP_TE_NOTIFYONLINE,
+	XP_TE_OPENDIALOG,		XP_TE_PART,
+	XP_TE_PARTREASON,		XP_TE_PINGREP,
+	XP_TE_PINGTIMEOUT,		XP_TE_PRIVACTION,
+	XP_TE_DPRIVACTION,		XP_TE_PRIVMSG,
+	XP_TE_DPRIVMSG,		XP_TE_ALREADYPROCESS,
+	XP_TE_QUIT,		XP_TE_RAWMODES,
+	XP_TE_WALLOPS,		XP_TE_RESOLVINGUSER,
+	XP_TE_SERVERCONNECTED,		XP_TE_SERVERERROR,
+	XP_TE_SERVERLOOKUP,		XP_TE_SERVNOTICE,
+	XP_TE_SERVTEXT,		XP_TE_SSLMESSAGE,
+	XP_TE_STOPCONNECT,		XP_TE_TOPIC,
+	XP_TE_NEWTOPIC,		XP_TE_TOPICDATE,
+	XP_TE_UKNHOST,		XP_TE_USERLIMIT,
+	XP_TE_USERSONCHAN,		XP_TE_WHOIS_AUTH,
+	XP_TE_WHOIS5,		XP_TE_WHOIS2,
+	XP_TE_WHOIS6,		XP_TE_WHOIS_ID,
+	XP_TE_WHOIS4,		XP_TE_WHOIS4T,
+	XP_TE_WHOIS1,		XP_TE_WHOIS_REALHOST,
+	XP_TE_WHOIS3,		XP_TE_WHOIS_SPECIAL,
+	XP_TE_UJOIN,		XP_TE_UKICK,
+	XP_TE_UPART,		XP_TE_UPARTREASON,
+	XP_TE_UACTION,		XP_TE_UINVITE,
+	XP_TE_UCHANMSG,		XP_TE_UCHANGENICK,
+	NUM_XP
+};
diff --git a/src/common/textevents.h b/src/common/textevents.h
new file mode 100644
index 00000000..08a54da5
--- /dev/null
+++ b/src/common/textevents.h
@@ -0,0 +1,424 @@
+/* this file is auto generated, edit textevents.in instead! */
+
+const struct text_event te[] = {
+
+{"Add Notify", pevt_generic_nick_help, 1, 
+N_("%C22*%O$t$1 added to notify list.")},
+
+{"Ban List", pevt_banlist_help, 4, 
+N_("%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3")},
+
+{"Banned", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(You are banned).")},
+
+{"Beep", pevt_generic_none_help, 128, 
+""},
+
+{"Change Nick", pevt_changenick_help, 2, 
+N_("%C22*%O$t$1 is now known as $2")},
+
+{"Channel Action", pevt_chanaction_help, 132, 
+"%C18*$t$1%O $2"},
+
+{"Channel Action Hilight", pevt_chanaction_help, 132, 
+"%C21*%O$t%C21%B$1%O%C21 $2"},
+
+{"Channel Ban", pevt_chanban_help, 2, 
+N_("%C22*%O$t$1 sets ban on $2")},
+
+{"Channel Creation", pevt_chandate_help, 2, 
+N_("%C22*%O$tChannel $1 created on $2")},
+
+{"Channel DeHalfOp", pevt_chandehop_help, 2, 
+N_("%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2")},
+
+{"Channel DeOp", pevt_chandeop_help, 2, 
+N_("%C22*%O$t%C26$1%O removes channel operator status from%C26 $2")},
+
+{"Channel DeVoice", pevt_chandevoice_help, 2, 
+N_("%C22*%O$t%C26$1%O removes voice from%C26 $2")},
+
+{"Channel Exempt", pevt_chanexempt_help, 2, 
+N_("%C22*%O$t$1 sets exempt on $2")},
+
+{"Channel Half-Operator", pevt_chanhop_help, 2, 
+N_("%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2")},
+
+{"Channel INVITE", pevt_chaninvite_help, 2, 
+N_("%C22*%O$t$1 sets invite on $2")},
+
+{"Channel List", pevt_generic_none_help, 0, 
+N_("%UChannel          Users   Topic")},
+
+{"Channel Message", pevt_chanmsg_help, 132, 
+"%C18%H<%H$4$1%H>%H%O$t$2"},
+
+{"Channel Mode Generic", pevt_chanmodegen_help, 4, 
+N_("%C22*%O$t$1 sets mode $2$3 $4")},
+
+{"Channel Modes", pevt_chanmodes_help, 2, 
+N_("%C22*%O$t%C22Channel $1 modes: $2")},
+
+{"Channel Msg Hilight", pevt_chanmsg_help, 132, 
+"$4%C21%B%H<%H$1%H>%H%O%C21$t$2"},
+
+{"Channel Notice", pevt_channotice_help, 131, 
+"%C28-%C29$1/$2%C28-%O$t$3"},
+
+{"Channel Operator", pevt_chanop_help, 2, 
+N_("%C22*%O$t%C26$1%O gives channel operator status to%C26 $2")},
+
+{"Channel Remove Exempt", pevt_chanrmexempt_help, 2, 
+N_("%C22*%O$t$1 removes exempt on $2")},
+
+{"Channel Remove Invite", pevt_chanrminvite_help, 2, 
+N_("%C22*%O$t$1 removes invite on $2")},
+
+{"Channel Remove Keyword", pevt_chanrmkey_help, 1, 
+N_("%C22*%O$t$1 removes channel keyword")},
+
+{"Channel Remove Limit", pevt_chanrmlimit_help, 1, 
+N_("%C22*%O$t$1 removes user limit")},
+
+{"Channel Set Key", pevt_chansetkey_help, 2, 
+N_("%C22*%O$t$1 sets channel keyword to $2")},
+
+{"Channel Set Limit", pevt_chansetlimit_help, 2, 
+N_("%C22*%O$t$1 sets channel limit to $2")},
+
+{"Channel UnBan", pevt_chanunban_help, 2, 
+N_("%C22*%O$t$1 removes ban on $2")},
+
+{"Channel Voice", pevt_chanvoice_help, 2, 
+N_("%C22*%O$t%C26$1%O gives voice to%C26 $2")},
+
+{"Connected", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22Connected. Now logging in...")},
+
+{"Connecting", pevt_connect_help, 3, 
+N_("%C22*%O$t%C22Connecting to $1 ($2) port $3%O...")},
+
+{"Connection Failed", pevt_connfail_help, 1, 
+N_("%C21*%O$t%C21Connection failed. Error: $1")},
+
+{"CTCP Generic", pevt_ctcpgen_help, 2, 
+N_("%C22*%O$tReceived a CTCP $1 from $2")},
+
+{"CTCP Generic to Channel", pevt_ctcpgenc_help, 3, 
+N_("%C22*%O$tReceived a CTCP $1 from $2 (to $3)")},
+
+{"CTCP Send", pevt_ctcpsend_help, 2, 
+N_("%C19>%O$1%C19<%O$tCTCP $2")},
+
+{"CTCP Sound", pevt_ctcpsnd_help, 2, 
+N_("%C22*%O$tReceived a CTCP Sound $1 from $2")},
+
+{"CTCP Sound to Channel", pevt_ctcpsnd_help, 3, 
+N_("%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)")},
+
+{"DCC CHAT Abort", pevt_dccchatabort_help, 1, 
+N_("%C22*%O$tDCC CHAT to %C26$1%O aborted.")},
+
+{"DCC CHAT Connect", pevt_dccchatcon_help, 2, 
+N_("%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]")},
+
+{"DCC CHAT Failed", pevt_dccchaterr_help, 4, 
+N_("%C22*%O$tDCC CHAT to %C26$1%O lost ($4).")},
+
+{"DCC CHAT Offer", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tReceived a DCC CHAT offer from $1")},
+
+{"DCC CHAT Offering", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tOffering DCC CHAT to $1")},
+
+{"DCC CHAT Reoffer", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tAlready offering CHAT to $1")},
+
+{"DCC Conection Failed", pevt_dccconfail_help, 3, 
+N_("%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).")},
+
+{"DCC Generic Offer", pevt_dccgenericoffer_help, 2, 
+N_("%C22*%O$tReceived '$1%O' from $2")},
+
+{"DCC Header", pevt_generic_none_help, 0, 
+N_("%C24,18 Type  To/From    Status  Size    Pos     File         ")},
+
+{"DCC Malformed", pevt_malformed_help, 2, 
+N_("%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2")},
+
+{"DCC Offer", pevt_dccoffer_help, 3, 
+N_("%C22*%O$tOffering%C26 $1%O to%C26 $2")},
+
+{"DCC Offer Not Valid", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo such DCC offer.")},
+
+{"DCC RECV Abort", pevt_dccfileabort_help, 2, 
+N_("%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.")},
+
+{"DCC RECV Complete", pevt_dccrecvcomp_help, 4, 
+N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.")},
+
+{"DCC RECV Connect", pevt_dcccon_help, 3, 
+N_("%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]")},
+
+{"DCC RECV Failed", pevt_dccrecverr_help, 4, 
+N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).")},
+
+{"DCC RECV File Open Error", pevt_generic_file_help, 2, 
+N_("%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).")},
+
+{"DCC Rename", pevt_dccrename_help, 2, 
+N_("%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.")},
+
+{"DCC RESUME Request", pevt_dccresumeoffer_help, 3, 
+N_("%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.")},
+
+{"DCC SEND Abort", pevt_dccfileabort_help, 2, 
+N_("%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.")},
+
+{"DCC SEND Complete", pevt_dccsendcomp_help, 3, 
+N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.")},
+
+{"DCC SEND Connect", pevt_dcccon_help, 3, 
+N_("%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]")},
+
+{"DCC SEND Failed", pevt_dccsendfail_help, 3, 
+N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3")},
+
+{"DCC SEND Offer", pevt_dccsendoffer_help, 4, 
+N_("%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)")},
+
+{"DCC Stall", pevt_dccstall_help, 3, 
+N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.")},
+
+{"DCC Timeout", pevt_dccstall_help, 3, 
+N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.")},
+
+{"Delete Notify", pevt_generic_nick_help, 1, 
+N_("%C22*%O$t$1 deleted from notify list.")},
+
+{"Disconnected", pevt_discon_help, 1, 
+N_("%C22*%O$tDisconnected ($1).")},
+
+{"Found IP", pevt_foundip_help, 1, 
+N_("%C22*%O$tFound your IP: [$1]")},
+
+{"Generic Message", pevt_genmsg_help, 130, 
+"$1$t$2"},
+
+{"Ignore Add", pevt_ignoreaddremove_help, 1, 
+N_("%O%C26$1%O added to ignore list.")},
+
+{"Ignore Changed", pevt_ignoreaddremove_help, 1, 
+N_("Ignore on %C26$1%O changed.")},
+
+{"Ignore Footer", pevt_generic_none_help, 0, 
+N_("%C24,18                                                              ")},
+
+{"Ignore Header", pevt_generic_none_help, 0, 
+N_("%C24,18 Hostmask                  PRIV NOTI CHAN CTCP DCC  INVI UNIG ")},
+
+{"Ignore Remove", pevt_ignoreaddremove_help, 1, 
+N_("%O%C26$1%O removed from ignore list.")},
+
+{"Ignorelist Empty", pevt_generic_none_help, 0, 
+N_("  Ignore list is empty.")},
+
+{"Invite", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).")},
+
+{"Invited", pevt_invited_help, 3, 
+N_("%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)")},
+
+{"Join", pevt_join_help, 3, 
+N_("%C19*%O$t%C19%B$1 %B($3) has joined $2")},
+
+{"Keyword", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).")},
+
+{"Kick", pevt_kick_help, 4, 
+N_("%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)")},
+
+{"Killed", pevt_kill_help, 2, 
+N_("%C22*%O$tYou have been killed by $1 ($2%O%C22)")},
+
+{"Message Send", pevt_ctcpsend_help, 130, 
+"%C19>%O$1%C19<%O$t$2"},
+
+{"Motd", pevt_servertext_help, 129, 
+"%C16*%O$t$1%O"},
+
+{"MOTD Skipped", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22MOTD Skipped.")},
+
+{"Nick Clash", pevt_nickclash_help, 2, 
+N_("%C22*%O$t$1 already in use. Retrying with $2...")},
+
+{"Nick Failed", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNickname already in use. Use /NICK to try another.")},
+
+{"No DCC", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo such DCC.")},
+
+{"No Running Process", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo process is currently running")},
+
+{"Notice", pevt_notice_help, 130, 
+"%C28-%C29$1%C28-%O$t$2"},
+
+{"Notice Send", pevt_ctcpsend_help, 130, 
+"%C19>%O$1%C19<%O$t$2"},
+
+{"Notify Empty", pevt_generic_none_help, 0, 
+N_("$tNotify list is empty.")},
+
+{"Notify Header", pevt_generic_none_help, 0, 
+N_("%C24,18 %B  Notify List                           ")},
+
+{"Notify Number", pevt_notifynumber_help, 1, 
+N_("%C22*%O$t$1 users in notify list.")},
+
+{"Notify Offline", pevt_generic_nick_help, 3, 
+N_("%C22*%O$tNotify: $1 is offline ($3).")},
+
+{"Notify Online", pevt_generic_nick_help, 3, 
+N_("%C22*%O$tNotify: $1 is online ($3).")},
+
+{"Open Dialog", pevt_generic_none_help, 128, 
+""},
+
+{"Part", pevt_part_help, 3, 
+N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3")},
+
+{"Part with Reason", pevt_partreason_help, 4, 
+N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)")},
+
+{"Ping Reply", pevt_pingrep_help, 2, 
+N_("%C22*%O$tPing reply from $1: $2 second(s)")},
+
+{"Ping Timeout", pevt_pingtimeout_help, 1, 
+N_("%C22*%O$tNo ping reply for $1 seconds, disconnecting.")},
+
+{"Private Action", pevt_privmsg_help, 131, 
+"%C18**$t$3$1%O $2 %C18**"},
+
+{"Private Action to Dialog", pevt_privmsg_help, 131, 
+"%C18*$t$3$1%O $2"},
+
+{"Private Message", pevt_privmsg_help, 131, 
+"%C28*%C29$3$1%C28*$t%O$2"},
+
+{"Private Message to Dialog", pevt_privmsg_help, 131, 
+"%C18%H<%H$3$1%H>%H%O$t$2"},
+
+{"Process Already Running", pevt_generic_none_help, 0, 
+N_("%C22*%O$tA process is already running")},
+
+{"Quit", pevt_quit_help, 3, 
+N_("%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)")},
+
+{"Raw Modes", pevt_rawmodes_help, 2, 
+N_("%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]")},
+
+{"Receive Wallops", pevt_privmsg_help, 2, 
+N_("%C28-%C29$1/Wallops%C28-%O$t$2")},
+
+{"Resolving User", pevt_resolvinguser_help, 2, 
+N_("%C22*%O$tLooking up IP number for%C26 $1%O...")},
+
+{"Server Connected", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22Connected.")},
+
+{"Server Error", pevt_servererror_help, 129, 
+"%C22*%O$t$1"},
+
+{"Server Lookup", pevt_serverlookup_help, 1, 
+N_("%C22*%O$t%C22Looking up $1")},
+
+{"Server Notice", pevt_servertext_help, 130, 
+"%C22*%O$t$1"},
+
+{"Server Text", pevt_servertext_help, 131, 
+"%C22*%O$t$1"},
+
+{"SSL Message", pevt_sslmessage_help, 130, 
+"%C22*%O$t$1"},
+
+{"Stop Connection", pevt_sconnect_help, 1, 
+N_("%C22*%O$tStopped previous connection attempt (pid=$1)")},
+
+{"Topic", pevt_topic_help, 2, 
+N_("%C29*%O$t%C29Topic for $1%C %C29is: $2")},
+
+{"Topic Change", pevt_newtopic_help, 3, 
+N_("%C22*%O$t$1 has changed the topic to: $2")},
+
+{"Topic Creation", pevt_topicdate_help, 3, 
+N_("%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3")},
+
+{"Unknown Host", pevt_generic_none_help, 0, 
+N_("%C22*%O$tUnknown host. Maybe you misspelled it?")},
+
+{"User Limit", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).")},
+
+{"Users On Channel", pevt_usersonchan_help, 2, 
+N_("%C22*%O$t%C26Users on $1:%C $2")},
+
+{"WhoIs Authenticated", pevt_whoisauth_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3")},
+
+{"WhoIs Away Line", pevt_whois5_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)")},
+
+{"WhoIs Channel/Oper Line", pevt_whois2_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs End", pevt_whois6_help, 1, 
+N_("%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.")},
+
+{"WhoIs Identified", pevt_whoisid_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs Idle Line", pevt_whois4_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2")},
+
+{"WhoIs Idle Line with Signon", pevt_whois4t_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3")},
+
+{"WhoIs Name Line", pevt_whois1_help, 4, 
+N_("%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4")},
+
+{"WhoIs Real Host", pevt_whoisrealhost_help, 4, 
+N_("%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3")},
+
+{"WhoIs Server Line", pevt_whois3_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs Special", pevt_whoisid_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"You Join", pevt_join_help, 3, 
+N_("%C19*%O$t%C19Now talking on $2")},
+
+{"You Kicked", pevt_ukick_help, 4, 
+N_("%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)")},
+
+{"You Part", pevt_part_help, 3, 
+N_("%C23*$tYou have left channel $3")},
+
+{"You Part with Reason", pevt_partreason_help, 4, 
+N_("%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)")},
+
+{"Your Action", pevt_chanaction_help, 131, 
+"%C18*$t$1%O $2"},
+
+{"Your Invitation", pevt_uinvite_help, 3, 
+N_("%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)")},
+
+{"Your Message", pevt_chanmsg_help, 132, 
+"%C31%H<%H$4$1%H>%H%O%C30$t$2"},
+
+{"Your Nick Changing", pevt_uchangenick_help, 2, 
+N_("%C22*%O$tYou are now known as $2")},
+};
diff --git a/src/common/textevents.in b/src/common/textevents.in
new file mode 100644
index 00000000..5b4ce18c
--- /dev/null
+++ b/src/common/textevents.in
@@ -0,0 +1,840 @@
+Add Notify
+XP_TE_ADDNOTIFY
+pevt_generic_nick_help
+%C22*%O$t$1 added to notify list.
+1
+
+Ban List
+XP_TE_BANLIST
+pevt_banlist_help
+%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3
+4
+
+Banned
+XP_TE_BANNED
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(You are banned).
+1
+
+Beep
+XP_TE_BEEP
+pevt_generic_none_help
+
+n0
+
+Change Nick
+XP_TE_CHANGENICK
+pevt_changenick_help
+%C22*%O$t$1 is now known as $2
+2
+
+Channel Action
+XP_TE_CHANACTION
+pevt_chanaction_help
+%C18*$t$1%O $2
+n4
+
+Channel Action Hilight
+XP_TE_HCHANACTION
+pevt_chanaction_help
+%C21*%O$t%C21%B$1%O%C21 $2
+n4
+
+Channel Ban
+XP_TE_CHANBAN
+pevt_chanban_help
+%C22*%O$t$1 sets ban on $2
+2
+
+Channel Creation
+XP_TE_CHANDATE
+pevt_chandate_help
+%C22*%O$tChannel $1 created on $2
+2
+
+Channel DeHalfOp
+XP_TE_CHANDEHOP
+pevt_chandehop_help
+%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2
+2
+
+Channel DeOp
+XP_TE_CHANDEOP
+pevt_chandeop_help
+%C22*%O$t%C26$1%O removes channel operator status from%C26 $2
+2
+
+Channel DeVoice
+XP_TE_CHANDEVOICE
+pevt_chandevoice_help
+%C22*%O$t%C26$1%O removes voice from%C26 $2
+2
+
+Channel Exempt
+XP_TE_CHANEXEMPT
+pevt_chanexempt_help
+%C22*%O$t$1 sets exempt on $2
+2
+
+Channel Half-Operator
+XP_TE_CHANHOP
+pevt_chanhop_help
+%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2
+2
+
+Channel INVITE
+XP_TE_CHANINVITE
+pevt_chaninvite_help
+%C22*%O$t$1 sets invite on $2
+2
+
+Channel List
+XP_TE_CHANLISTHEAD
+pevt_generic_none_help
+%UChannel          Users   Topic
+0
+
+Channel Message
+XP_TE_CHANMSG
+pevt_chanmsg_help
+%C18%H<%H$4$1%H>%H%O$t$2
+n4
+
+Channel Mode Generic
+XP_TE_CHANMODEGEN
+pevt_chanmodegen_help
+%C22*%O$t$1 sets mode $2$3 $4
+4
+
+Channel Modes
+XP_TE_CHANMODES
+pevt_chanmodes_help
+%C22*%O$t%C22Channel $1 modes: $2
+2
+
+Channel Msg Hilight
+XP_TE_HCHANMSG
+pevt_chanmsg_help
+$4%C21%B%H<%H$1%H>%H%O%C21$t$2
+n4
+
+Channel Notice
+XP_TE_CHANNOTICE
+pevt_channotice_help
+%C28-%C29$1/$2%C28-%O$t$3
+n3
+
+Channel Operator
+XP_TE_CHANOP
+pevt_chanop_help
+%C22*%O$t%C26$1%O gives channel operator status to%C26 $2
+2
+
+Channel Remove Exempt
+XP_TE_CHANRMEXEMPT
+pevt_chanrmexempt_help
+%C22*%O$t$1 removes exempt on $2
+2
+
+Channel Remove Invite
+XP_TE_CHANRMINVITE
+pevt_chanrminvite_help
+%C22*%O$t$1 removes invite on $2
+2
+
+Channel Remove Keyword
+XP_TE_CHANRMKEY
+pevt_chanrmkey_help
+%C22*%O$t$1 removes channel keyword
+1
+
+Channel Remove Limit
+XP_TE_CHANRMLIMIT
+pevt_chanrmlimit_help
+%C22*%O$t$1 removes user limit
+1
+
+Channel Set Key
+XP_TE_CHANSETKEY
+pevt_chansetkey_help
+%C22*%O$t$1 sets channel keyword to $2
+2
+
+Channel Set Limit
+XP_TE_CHANSETLIMIT
+pevt_chansetlimit_help
+%C22*%O$t$1 sets channel limit to $2
+2
+
+Channel UnBan
+XP_TE_CHANUNBAN
+pevt_chanunban_help
+%C22*%O$t$1 removes ban on $2
+2
+
+Channel Voice
+XP_TE_CHANVOICE
+pevt_chanvoice_help
+%C22*%O$t%C26$1%O gives voice to%C26 $2
+2
+
+Connected
+XP_TE_CONNECTED
+pevt_generic_none_help
+%C22*%O$t%C22Connected. Now logging in...
+0
+
+Connecting
+XP_TE_CONNECT
+pevt_connect_help
+%C22*%O$t%C22Connecting to $1 ($2) port $3%O...
+3
+
+Connection Failed
+XP_TE_CONNFAIL
+pevt_connfail_help
+%C21*%O$t%C21Connection failed. Error: $1
+1
+
+CTCP Generic
+XP_TE_CTCPGEN
+pevt_ctcpgen_help
+%C22*%O$tReceived a CTCP $1 from $2
+2
+
+CTCP Generic to Channel
+XP_TE_CTCPGENC
+pevt_ctcpgenc_help
+%C22*%O$tReceived a CTCP $1 from $2 (to $3)
+3
+
+CTCP Send
+XP_TE_CTCPSEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$tCTCP $2
+2
+
+CTCP Sound
+XP_TE_CTCPSND
+pevt_ctcpsnd_help
+%C22*%O$tReceived a CTCP Sound $1 from $2
+2
+
+CTCP Sound to Channel
+XP_TE_CTCPSNDC
+pevt_ctcpsnd_help
+%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)
+3
+
+DCC CHAT Abort
+XP_TE_DCCCHATABORT
+pevt_dccchatabort_help
+%C22*%O$tDCC CHAT to %C26$1%O aborted.
+1
+
+DCC CHAT Connect
+XP_TE_DCCCONCHAT
+pevt_dccchatcon_help
+%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]
+2
+
+DCC CHAT Failed
+XP_TE_DCCCHATF
+pevt_dccchaterr_help
+%C22*%O$tDCC CHAT to %C26$1%O lost ($4).
+4
+
+DCC CHAT Offer
+XP_TE_DCCCHATOFFER
+pevt_generic_nick_help
+%C22*%O$tReceived a DCC CHAT offer from $1
+1
+
+DCC CHAT Offering
+XP_TE_DCCCHATOFFERING
+pevt_generic_nick_help
+%C22*%O$tOffering DCC CHAT to $1
+1
+
+DCC CHAT Reoffer
+XP_TE_DCCCHATREOFFER
+pevt_generic_nick_help
+%C22*%O$tAlready offering CHAT to $1
+1
+
+DCC Conection Failed
+XP_TE_DCCCONFAIL
+pevt_dccconfail_help
+%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).
+3
+
+DCC Generic Offer
+XP_TE_DCCGENERICOFFER
+pevt_dccgenericoffer_help
+%C22*%O$tReceived '$1%O' from $2
+2
+
+DCC Header
+XP_TE_DCCHEAD
+pevt_generic_none_help
+%C24,18 Type  To/From    Status  Size    Pos     File         
+0
+
+DCC Malformed
+XP_TE_MALFORMED
+pevt_malformed_help
+%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2
+2
+
+DCC Offer
+XP_TE_DCCOFFER
+pevt_dccoffer_help
+%C22*%O$tOffering%C26 $1%O to%C26 $2
+3
+
+DCC Offer Not Valid
+XP_TE_DCCIVAL
+pevt_generic_none_help
+%C22*%O$tNo such DCC offer.
+0
+
+DCC RECV Abort
+XP_TE_DCCRECVABORT
+pevt_dccfileabort_help
+%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.
+2
+
+DCC RECV Complete
+XP_TE_DCCRECVCOMP
+pevt_dccrecvcomp_help
+%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.
+4
+
+DCC RECV Connect
+XP_TE_DCCCONRECV
+pevt_dcccon_help
+%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]
+3
+
+DCC RECV Failed
+XP_TE_DCCRECVERR
+pevt_dccrecverr_help
+%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).
+4
+
+DCC RECV File Open Error
+XP_TE_DCCFILEERR
+pevt_generic_file_help
+%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).
+2
+
+DCC Rename
+XP_TE_DCCRENAME
+pevt_dccrename_help
+%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.
+2
+
+DCC RESUME Request
+XP_TE_DCCRESUMEREQUEST
+pevt_dccresumeoffer_help
+%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.
+3
+
+DCC SEND Abort
+XP_TE_DCCSENDABORT
+pevt_dccfileabort_help
+%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.
+2
+
+DCC SEND Complete
+XP_TE_DCCSENDCOMP
+pevt_dccsendcomp_help
+%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.
+3
+
+DCC SEND Connect
+XP_TE_DCCCONSEND
+pevt_dcccon_help
+%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]
+3
+
+DCC SEND Failed
+XP_TE_DCCSENDFAIL
+pevt_dccsendfail_help
+%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3
+3
+
+DCC SEND Offer
+XP_TE_DCCSENDOFFER
+pevt_dccsendoffer_help
+%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)
+4
+
+DCC Stall
+XP_TE_DCCSTALL
+pevt_dccstall_help
+%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.
+3
+
+DCC Timeout
+XP_TE_DCCTOUT
+pevt_dccstall_help
+%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.
+3
+
+Delete Notify
+XP_TE_DELNOTIFY
+pevt_generic_nick_help
+%C22*%O$t$1 deleted from notify list.
+1
+
+Disconnected
+XP_TE_DISCON
+pevt_discon_help
+%C22*%O$tDisconnected ($1).
+1
+
+Found IP
+XP_TE_FOUNDIP
+pevt_foundip_help
+%C22*%O$tFound your IP: [$1]
+1
+
+Generic Message
+XP_TE_GENMSG
+pevt_genmsg_help
+$1$t$2
+n2
+
+Ignore Add
+XP_TE_IGNOREADD
+pevt_ignoreaddremove_help
+%O%C26$1%O added to ignore list.
+1
+
+Ignore Changed
+XP_TE_IGNORECHANGE
+pevt_ignoreaddremove_help
+Ignore on %C26$1%O changed.
+1
+
+Ignore Footer
+XP_TE_IGNOREFOOTER
+pevt_generic_none_help
+%C24,18                                                              
+0
+
+Ignore Header
+XP_TE_IGNOREHEADER
+pevt_generic_none_help
+%C24,18 Hostmask                  PRIV NOTI CHAN CTCP DCC  INVI UNIG 
+0
+
+Ignore Remove
+XP_TE_IGNOREREMOVE
+pevt_ignoreaddremove_help
+%O%C26$1%O removed from ignore list.
+1
+
+Ignorelist Empty
+XP_TE_IGNOREEMPTY
+pevt_generic_none_help
+  Ignore list is empty.
+0
+
+Invite
+XP_TE_INVITE
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).
+1
+
+Invited
+XP_TE_INVITED
+pevt_invited_help
+%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)
+3
+
+Join
+XP_TE_JOIN
+pevt_join_help
+%C19*%O$t%C19%B$1 %B($3) has joined $2
+3
+
+Keyword
+XP_TE_KEYWORD
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).
+1
+
+Kick
+XP_TE_KICK
+pevt_kick_help
+%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)
+4
+
+Killed
+XP_TE_KILL
+pevt_kill_help
+%C22*%O$tYou have been killed by $1 ($2%O%C22)
+2
+
+Message Send
+XP_TE_MSGSEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$t$2
+n2
+
+Motd
+XP_TE_MOTD
+pevt_servertext_help
+%C16*%O$t$1%O
+n1
+
+MOTD Skipped
+XP_TE_MOTDSKIP
+pevt_generic_none_help
+%C22*%O$t%C22MOTD Skipped.
+0
+
+Nick Clash
+XP_TE_NICKCLASH
+pevt_nickclash_help
+%C22*%O$t$1 already in use. Retrying with $2...
+2
+
+Nick Failed
+XP_TE_NICKFAIL
+pevt_generic_none_help
+%C22*%O$tNickname already in use. Use /NICK to try another.
+0
+
+No DCC
+XP_TE_NODCC
+pevt_generic_none_help
+%C22*%O$tNo such DCC.
+0
+
+No Running Process
+XP_TE_NOCHILD
+pevt_generic_none_help
+%C22*%O$tNo process is currently running
+0
+
+Notice
+XP_TE_NOTICE
+pevt_notice_help
+%C28-%C29$1%C28-%O$t$2
+n2
+
+Notice Send
+XP_TE_NOTICESEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$t$2
+n2
+
+Notify Empty
+XP_TE_NOTIFYEMPTY
+pevt_generic_none_help
+$tNotify list is empty.
+0
+
+Notify Header
+XP_TE_NOTIFYHEAD
+pevt_generic_none_help
+%C24,18 %B  Notify List                           
+0
+
+Notify Number
+XP_TE_NOTIFYNUMBER
+pevt_notifynumber_help
+%C22*%O$t$1 users in notify list.
+1
+
+Notify Offline
+XP_TE_NOTIFYOFFLINE
+pevt_generic_nick_help
+%C22*%O$tNotify: $1 is offline ($3).
+3
+
+Notify Online
+XP_TE_NOTIFYONLINE
+pevt_generic_nick_help
+%C22*%O$tNotify: $1 is online ($3).
+3
+
+Open Dialog
+XP_TE_OPENDIALOG
+pevt_generic_none_help
+
+n0
+
+Part
+XP_TE_PART
+pevt_part_help
+%C23*%O$t%C23$1 (%O%C23$2) has left $3
+3
+
+Part with Reason
+XP_TE_PARTREASON
+pevt_partreason_help
+%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)
+4
+
+Ping Reply
+XP_TE_PINGREP
+pevt_pingrep_help
+%C22*%O$tPing reply from $1: $2 second(s)
+2
+
+Ping Timeout
+XP_TE_PINGTIMEOUT
+pevt_pingtimeout_help
+%C22*%O$tNo ping reply for $1 seconds, disconnecting.
+1
+
+Private Action
+XP_TE_PRIVACTION
+pevt_privmsg_help
+%C18**$t$3$1%O $2 %C18**
+n3
+
+Private Action to Dialog
+XP_TE_DPRIVACTION
+pevt_privmsg_help
+%C18*$t$3$1%O $2
+n3
+
+Private Message
+XP_TE_PRIVMSG
+pevt_privmsg_help
+%C28*%C29$3$1%C28*$t%O$2
+n3
+
+Private Message to Dialog
+XP_TE_DPRIVMSG
+pevt_privmsg_help
+%C18%H<%H$3$1%H>%H%O$t$2
+n3
+
+Process Already Running
+XP_TE_ALREADYPROCESS
+pevt_generic_none_help
+%C22*%O$tA process is already running
+0
+
+Quit
+XP_TE_QUIT
+pevt_quit_help
+%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)
+3
+
+Raw Modes
+XP_TE_RAWMODES
+pevt_rawmodes_help
+%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]
+2
+
+Receive Wallops
+XP_TE_WALLOPS
+pevt_privmsg_help
+%C28-%C29$1/Wallops%C28-%O$t$2
+2
+
+Resolving User
+XP_TE_RESOLVINGUSER
+pevt_resolvinguser_help
+%C22*%O$tLooking up IP number for%C26 $1%O...
+2
+
+Server Connected
+XP_TE_SERVERCONNECTED
+pevt_generic_none_help
+%C22*%O$t%C22Connected.
+0
+
+Server Error
+XP_TE_SERVERERROR
+pevt_servererror_help
+%C22*%O$t$1
+n1
+
+Server Lookup
+XP_TE_SERVERLOOKUP
+pevt_serverlookup_help
+%C22*%O$t%C22Looking up $1
+1
+
+Server Notice
+XP_TE_SERVNOTICE
+pevt_servertext_help
+%C22*%O$t$1
+n2
+
+Server Text
+XP_TE_SERVTEXT
+pevt_servertext_help
+%C22*%O$t$1
+n3
+
+SSL Message
+XP_TE_SSLMESSAGE
+pevt_sslmessage_help
+%C22*%O$t$1
+n2
+
+Stop Connection
+XP_TE_STOPCONNECT
+pevt_sconnect_help
+%C22*%O$tStopped previous connection attempt (pid=$1)
+1
+
+Topic
+XP_TE_TOPIC
+pevt_topic_help
+%C29*%O$t%C29Topic for $1%C %C29is: $2
+2
+
+Topic Change
+XP_TE_NEWTOPIC
+pevt_newtopic_help
+%C22*%O$t$1 has changed the topic to: $2
+3
+
+Topic Creation
+XP_TE_TOPICDATE
+pevt_topicdate_help
+%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3
+3
+
+Unknown Host
+XP_TE_UKNHOST
+pevt_generic_none_help
+%C22*%O$tUnknown host. Maybe you misspelled it?
+0
+
+User Limit
+XP_TE_USERLIMIT
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).
+1
+
+Users On Channel
+XP_TE_USERSONCHAN
+pevt_usersonchan_help
+%C22*%O$t%C26Users on $1:%C $2
+2
+
+WhoIs Authenticated
+XP_TE_WHOIS_AUTH
+pevt_whoisauth_help
+%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3
+3
+
+WhoIs Away Line
+XP_TE_WHOIS5
+pevt_whois5_help
+%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)
+2
+
+WhoIs Channel/Oper Line
+XP_TE_WHOIS2
+pevt_whois2_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs End
+XP_TE_WHOIS6
+pevt_whois6_help
+%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.
+1
+
+WhoIs Identified
+XP_TE_WHOIS_ID
+pevt_whoisid_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs Idle Line
+XP_TE_WHOIS4
+pevt_whois4_help
+%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2
+2
+
+WhoIs Idle Line with Signon
+XP_TE_WHOIS4T
+pevt_whois4t_help
+%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3
+3
+
+WhoIs Name Line
+XP_TE_WHOIS1
+pevt_whois1_help
+%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4
+4
+
+WhoIs Real Host
+XP_TE_WHOIS_REALHOST
+pevt_whoisrealhost_help
+%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3
+4
+
+WhoIs Server Line
+XP_TE_WHOIS3
+pevt_whois3_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs Special
+XP_TE_WHOIS_SPECIAL
+pevt_whoisid_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+3
+
+You Join
+XP_TE_UJOIN
+pevt_join_help
+%C19*%O$t%C19Now talking on $2
+3
+
+You Kicked
+XP_TE_UKICK
+pevt_ukick_help
+%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)
+4
+
+You Part
+XP_TE_UPART
+pevt_part_help
+%C23*$tYou have left channel $3
+3
+
+You Part with Reason
+XP_TE_UPARTREASON
+pevt_partreason_help
+%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)
+4
+
+Your Action
+XP_TE_UACTION
+pevt_chanaction_help
+%C18*$t$1%O $2
+n3
+
+Your Invitation
+XP_TE_UINVITE
+pevt_uinvite_help
+%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)
+3
+
+Your Message
+XP_TE_UCHANMSG
+pevt_chanmsg_help
+%C31%H<%H$4$1%H>%H%O%C30$t$2
+n4
+
+Your Nick Changing
+XP_TE_UCHANGENICK
+pevt_uchangenick_help
+%C22*%O$tYou are now known as $2
+2
+
diff --git a/src/common/tree.c b/src/common/tree.c
new file mode 100644
index 00000000..1627bd98
--- /dev/null
+++ b/src/common/tree.c
@@ -0,0 +1,215 @@
+/*
+This is used for quick userlist insertion and lookup. It's not really
+a tree, but it could be :)
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "tree.h"
+
+#define ARRAY_GROW 32
+
+struct _tree
+{
+	int elements;
+	int array_size;
+	void **array;
+	tree_cmp_func *cmp;
+	void *data;
+};
+
+tree *
+tree_new (tree_cmp_func *cmp, void *data)
+{
+	tree *t = calloc (1, sizeof (tree));
+	t->cmp = cmp;
+	t->data = data;
+	return t;
+}
+
+void
+tree_destroy (tree *t)
+{
+	if (t)
+	{
+		if (t->array)
+			free (t->array);
+		free (t);
+	}
+}
+
+static int
+tree_find_insertion_pos (tree *t, void *key, int *done)
+{
+	int c, u, l, idx;
+
+	if (t->elements < 1)
+	{
+		*done = 1;
+		t->array[0] = key;
+		t->elements++;
+		return 0;
+	}
+
+	if (t->elements < 2)
+	{
+		*done = 1;
+		c = t->cmp (key, t->array[0], t->data);
+		if (c == 0)
+			return -1;
+		t->elements++;
+		if (c > 0)
+		{
+			t->array[1] = key;
+			return 1;
+		}
+		t->array[1] = t->array[0];
+		t->array[0] = key;
+		return 0;
+	}
+
+	*done = 0;
+
+	c = t->cmp (key, t->array[0], t->data);
+	if (c < 0)
+		return 0;	/* prepend */
+
+	c = t->cmp (key, t->array[t->elements - 1], t->data);
+	if (c > 0)
+		return t->elements;	/* append */
+
+	l = 0;
+	u = t->elements - 1;
+	while (1)
+	{
+		idx = (l + u) / 2;
+		c = t->cmp (key, t->array[idx], t->data);
+
+		if (0 > c)
+			u = idx;
+		else if (0 < c && 0 > t->cmp (key, t->array[idx+1], t->data))
+			return idx + 1;
+		else if (c == 0)
+			return -1;
+		else
+			l = idx + 1;
+	}
+}
+
+static void
+tree_insert_at_pos (tree *t, void *key, int pos)
+{
+	int post_bytes;
+
+	/* append is easy */
+	if (pos != t->elements)
+	{
+		post_bytes = (t->elements - pos) * sizeof (void *);
+		memmove (&t->array[pos + 1], &t->array[pos], post_bytes);
+	}
+
+	t->array[pos] = key;
+	t->elements++;
+}
+
+static void *
+mybsearch (const void *key, void **array, size_t nmemb,
+			  int (*compar) (const void *, const void *, void *data), void *data, int *pos)
+{
+	int l, u, idx;
+	int comparison;
+
+	l = 0;
+	u = nmemb;
+	while (l < u)
+	{
+		idx = (l + u) / 2;
+		comparison = (*compar) (key, array[idx], data);
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else
+		{
+			*pos = idx;
+			return array[idx];
+		}
+	}
+
+	return NULL;
+}
+
+void *
+tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos)
+{
+	if (!t || !t->array)
+		return NULL;
+
+	return mybsearch (key, &t->array[0], t->elements, cmp, data, pos);
+}
+
+static void
+tree_remove_at_pos (tree *t, int pos)
+{
+	int post_bytes;
+
+	t->elements--;
+	if (pos != t->elements)
+	{
+		post_bytes = (t->elements - pos) * sizeof (void *);
+		memmove (&t->array[pos], &t->array[pos + 1], post_bytes);
+	}
+}
+
+int
+tree_remove (tree *t, void *key, int *pos)
+{
+	void *data;
+
+	data = tree_find (t, key, t->cmp, t->data, pos);
+	if (!data)
+		return 0;
+
+	tree_remove_at_pos (t, *pos);
+	return 1;
+}
+
+void
+tree_foreach (tree *t, tree_traverse_func *func, void *data)
+{
+	int j;
+
+	if (!t || !t->array)
+		return;
+
+	for (j = 0; j < t->elements; j++)
+	{
+		if (!func (t->array[j], data))
+			break;
+	}
+}
+
+int
+tree_insert (tree *t, void *key)
+{
+	int pos, done;
+
+	if (!t)
+		return -1;
+
+	if (t->array_size < t->elements + 1)
+	{
+		int new_size = t->array_size + ARRAY_GROW;
+
+		t->array = realloc (t->array, sizeof (void *) * new_size);
+		t->array_size = new_size;
+	}
+
+	pos = tree_find_insertion_pos (t, key, &done);
+	if (!done && pos != -1)
+		tree_insert_at_pos (t, key, pos);
+
+	return pos;
+}
diff --git a/src/common/tree.h b/src/common/tree.h
new file mode 100644
index 00000000..b1b66aa9
--- /dev/null
+++ b/src/common/tree.h
@@ -0,0 +1,16 @@
+#ifndef XCHAT_TREE_H
+#define XCHAT_TREE_H
+
+typedef struct _tree tree;
+
+typedef int (tree_cmp_func) (const void *keya, const void *keyb, void *data);
+typedef int (tree_traverse_func) (const void *key, void *data);
+
+tree *tree_new (tree_cmp_func *cmp, void *data);
+void tree_destroy (tree *t);
+void *tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos);
+int tree_remove (tree *t, void *key, int *pos);
+void tree_foreach (tree *t, tree_traverse_func *func, void *data);
+int tree_insert (tree *t, void *key);
+
+#endif
diff --git a/src/common/url.c b/src/common/url.c
new file mode 100644
index 00000000..92aeab0a
--- /dev/null
+++ b/src/common/url.c
@@ -0,0 +1,280 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <ctype.h>
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "tree.h"
+#include "url.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+void *url_tree = NULL;
+
+
+static int
+url_free (char *url, void *data)
+{
+	free (url);
+	return TRUE;
+}
+
+void
+url_clear (void)
+{
+	tree_foreach (url_tree, (tree_traverse_func *)url_free, NULL);
+	tree_destroy (url_tree);
+	url_tree = NULL;
+}
+
+static int
+url_save_cb (char *url, FILE *fd)
+{
+	fprintf (fd, "%s\n", url);
+	return TRUE;
+}
+
+void
+url_save (const char *fname, const char *mode, gboolean fullpath)
+{
+	FILE *fd;
+
+	if (fullpath)
+		fd = xchat_fopen_file (fname, mode, XOF_FULLPATH);
+	else
+		fd = xchat_fopen_file (fname, mode, 0);
+	if (fd == NULL)
+		return;
+
+	tree_foreach (url_tree, (tree_traverse_func *)url_save_cb, fd);
+	fclose (fd);
+}
+
+void
+url_autosave (void)
+{
+	url_save ("url.save", "a", FALSE);
+}
+
+static int
+url_find (char *urltext)
+{
+	int pos;
+
+	if (tree_find (url_tree, urltext, (tree_cmp_func *)strcasecmp, NULL, &pos))
+		return 1;
+	return 0;
+}
+
+static void
+url_add (char *urltext, int len)
+{
+	char *data = malloc (len + 1);
+	if (!data)
+		return;
+	memcpy (data, urltext, len);
+	data[len] = 0;
+
+	if (data[len - 1] == '.')	/* chop trailing dot */
+	{
+		len--;
+		data[len] = 0;
+	}
+	if (data[len - 1] == ')')	/* chop trailing ) */
+		data[len - 1] = 0;
+
+	if (url_find (data))
+	{
+		free (data);
+		return;
+	}
+
+	if (!url_tree)
+		url_tree = tree_new ((tree_cmp_func *)strcasecmp, NULL);
+
+	tree_insert (url_tree, data);
+	fe_url_add (data);
+}
+
+/* check if a word is clickable. This is called on mouse motion events, so
+   keep it FAST! This new version was found to be almost 3x faster than
+   2.4.4 release. */
+
+int
+url_check_word (char *word, int len)
+{
+#define D(x) (x), ((sizeof (x)) - 1)
+	static const struct {
+		const char *s;
+		int len;
+	}
+	prefix[] = {
+		{ D("irc.") },
+		{ D("ftp.") },
+		{ D("www.") },
+		{ D("irc://") },
+		{ D("ftp://") },
+		{ D("http://") },
+		{ D("https://") },
+		{ D("file://") },
+		{ D("rtsp://") },
+		{ D("ut2004://") },
+	},
+	suffix[] = {
+		{ D(".org") },
+		{ D(".net") },
+		{ D(".com") },
+		{ D(".edu") },
+		{ D(".html") },
+		{ D(".info") },
+		{ D(".name") },
+	};
+#undef D
+	const char *at, *dot;
+	int i, dots;
+
+	if (len > 1 && word[1] == '#' && strchr("@+^%*#", word[0]))
+		return WORD_CHANNEL;
+
+	if ((word[0] == '#' || word[0] == '&') && word[1] != '#' && word[1] != 0)
+		return WORD_CHANNEL;
+
+	for (i = 0; i < G_N_ELEMENTS(prefix); i++)
+	{
+		int l;
+
+		l = prefix[i].len;
+		if (len > l)
+		{
+			int j;
+
+			/* This is pretty much strncasecmp(). */
+			for (j = 0; j < l; j++)
+			{
+				unsigned char c = word[j];
+				if (tolower(c) != prefix[i].s[j])
+					break;
+			}
+			if (j == l)
+				return WORD_URL;
+		}
+	}
+
+	at = strchr (word, '@');	  /* check for email addy */
+	dot = strrchr (word, '.');
+	if (at && dot)
+	{
+		if (at < dot)
+		{
+			if (strchr (word, '*'))
+				return WORD_HOST;
+			else
+				return WORD_EMAIL;
+		}
+	}
+ 
+	/* check if it's an IP number */
+	dots = 0;
+	for (i = 0; i < len; i++)
+	{
+		if (word[i] == '.' && i > 0)
+			dots++;	/* allow 127.0.0.1:80 */
+		else if (!isdigit ((unsigned char) word[i]) && word[i] != ':')
+		{
+			dots = 0;
+			break;
+		}
+	}
+	if (dots == 3)
+		return WORD_HOST;
+
+	if (len > 5)
+	{
+		for (i = 0; i < G_N_ELEMENTS(suffix); i++)
+		{
+			int l;
+
+			l = suffix[i].len;
+			if (len > l)
+			{
+				const unsigned char *p = &word[len - l];
+				int j;
+
+				/* This is pretty much strncasecmp(). */
+				for (j = 0; j < l; j++)
+				{
+					if (tolower(p[j]) != suffix[i].s[j])
+						break;
+				}
+				if (j == l)
+					return WORD_HOST;
+			}
+		}
+
+		if (word[len - 3] == '.' &&
+			 isalpha ((unsigned char) word[len - 2]) &&
+				isalpha ((unsigned char) word[len - 1]))
+			return WORD_HOST;
+	}
+
+	return 0;
+}
+
+void
+url_check_line (char *buf, int len)
+{
+	char *po = buf;
+	char *start;
+	int wlen;
+
+	if (buf[0] == ':' && buf[1] != 0)
+		po++;
+
+	start = po;
+
+	/* check each "word" (space separated) */
+	while (1)
+	{
+		switch (po[0])
+		{
+		case 0:
+		case ' ':
+			wlen = po - start;
+			if (wlen > 2)
+			{
+				if (url_check_word (start, wlen) == WORD_URL)
+				{
+					url_add (start, wlen);
+				}
+			}
+			if (po[0] == 0)
+				return;
+			po++;
+			start = po;
+			break;
+
+		default:
+			po++;
+		}
+	}
+}
diff --git a/src/common/url.h b/src/common/url.h
new file mode 100644
index 00000000..487a2007
--- /dev/null
+++ b/src/common/url.h
@@ -0,0 +1,19 @@
+#ifndef XCHAT_URL_H
+#define XCHAT_URL_H
+
+extern void *url_tree;
+
+#define WORD_URL     1
+#define WORD_NICK    2
+#define WORD_CHANNEL 3
+#define WORD_HOST    4
+#define WORD_EMAIL   5
+#define WORD_DIALOG  -1
+
+void url_clear (void);
+void url_save (const char *fname, const char *mode, gboolean fullpath);
+void url_autosave (void);
+int url_check_word (char *word, int len);
+void url_check_line (char *buf, int len);
+
+#endif
diff --git a/src/common/userlist.c b/src/common/userlist.c
new file mode 100644
index 00000000..18ac4bed
--- /dev/null
+++ b/src/common/userlist.c
@@ -0,0 +1,454 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 "xchat.h"
+#include "modes.h"
+#include "fe.h"
+#include "notify.h"
+#include "tree.h"
+#include "xchatc.h"
+#include "util.h"
+
+
+static int
+nick_cmp_az_ops (server *serv, struct User *user1, struct User *user2)
+{
+	unsigned int access1 = user1->access;
+	unsigned int access2 = user2->access;
+	int pos;
+
+	if (access1 != access2)
+	{
+		for (pos = 0; pos < USERACCESS_SIZE; pos++)
+		{
+			if ((access1&(1<<pos)) && (access2&(1<<pos)))
+				break;
+			if ((access1&(1<<pos)) && !(access2&(1<<pos)))
+				return -1;
+			if (!(access1&(1<<pos)) && (access2&(1<<pos)))
+				return 1;
+		}
+	}
+
+	return serv->p_cmp (user1->nick, user2->nick);
+}
+
+static int
+nick_cmp_alpha (struct User *user1, struct User *user2, server *serv)
+{
+	return serv->p_cmp (user1->nick, user2->nick);
+}
+
+static int
+nick_cmp (struct User *user1, struct User *user2, server *serv)
+{
+	switch (prefs.userlist_sort)
+	{
+	case 0:
+		return nick_cmp_az_ops (serv, user1, user2);
+	case 1:
+		return serv->p_cmp (user1->nick, user2->nick);
+	case 2:
+		return -1 * nick_cmp_az_ops (serv, user1, user2);
+	case 3:
+		return -1 * serv->p_cmp (user1->nick, user2->nick);
+	default:
+		return -1;
+	}
+}
+
+/*
+ insert name in appropriate place in linked list. Returns row number or:
+  -1: duplicate
+*/
+
+static int
+userlist_insertname (session *sess, struct User *newuser)
+{
+	if (!sess->usertree)
+	{
+		sess->usertree = tree_new ((tree_cmp_func *)nick_cmp, sess->server);
+		sess->usertree_alpha = tree_new ((tree_cmp_func *)nick_cmp_alpha, sess->server);
+	}
+
+	tree_insert (sess->usertree_alpha, newuser);
+	return tree_insert (sess->usertree, newuser);
+}
+
+void
+userlist_set_away (struct session *sess, char *nick, unsigned int away)
+{
+	struct User *user;
+
+	user = userlist_find (sess, nick);
+	if (user)
+	{
+		if (user->away != away)
+		{
+			user->away = away;
+			/* rehash GUI */
+			fe_userlist_rehash (sess, user);
+			if (away)
+				fe_userlist_update (sess, user);
+		}
+	}
+}
+
+int
+userlist_add_hostname (struct session *sess, char *nick, char *hostname,
+							  char *realname, char *servername, unsigned int away)
+{
+	struct User *user;
+
+	user = userlist_find (sess, nick);
+	if (user)
+	{
+		if (!user->hostname && hostname)
+			user->hostname = strdup (hostname);
+		if (!user->realname && realname)
+			user->realname = strdup (realname);
+		if (!user->servername && servername)
+			user->servername = strdup (servername);
+
+		if (away != 0xff)
+		{
+			if (prefs.showhostname_in_userlist || user->away != away)
+			{
+				user->away = away;
+				fe_userlist_rehash (sess, user);
+			}
+			user->away = away;
+		}
+
+		fe_userlist_update (sess, user);
+
+		return 1;
+	}
+	return 0;
+}
+
+static int
+free_user (struct User *user, gpointer data)
+{
+	if (user->realname)
+		free (user->realname);
+	if (user->hostname)
+		free (user->hostname);
+	if (user->servername)
+		free (user->servername);
+	free (user);
+
+	return TRUE;
+}
+
+void
+userlist_free (session *sess)
+{
+	tree_foreach (sess->usertree, (tree_traverse_func *)free_user, NULL);
+	tree_destroy (sess->usertree);
+	tree_destroy (sess->usertree_alpha);
+
+	sess->usertree = NULL;
+	sess->usertree_alpha = NULL;
+	sess->me = NULL;
+
+	sess->ops = 0;
+	sess->hops = 0;
+	sess->voices = 0;
+	sess->total = 0;
+}
+
+void
+userlist_clear (session *sess)
+{
+	fe_userlist_clear (sess);
+	userlist_free (sess);
+	fe_userlist_numbers (sess);
+}
+
+static int
+find_cmp (const char *name, struct User *user, server *serv)
+{
+	return serv->p_cmp ((char *)name, user->nick);
+}
+
+struct User *
+userlist_find (struct session *sess, char *name)
+{
+	int pos;
+
+	if (sess->usertree_alpha)
+		return tree_find (sess->usertree_alpha, name,
+								(tree_cmp_func *)find_cmp, sess->server, &pos);
+
+	return NULL;
+}
+
+struct User *
+userlist_find_global (struct server *serv, char *name)
+{
+	struct User *user;
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+		{
+			user = userlist_find (sess, name);
+			if (user)
+				return user;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static void
+update_counts (session *sess, struct User *user, char prefix,
+					int level, int offset)
+{
+	switch (prefix)
+	{
+	case '@':
+		user->op = level;
+		sess->ops += offset;
+		break;
+	case '%':
+		user->hop = level;
+		sess->hops += offset;
+		break;
+	case '+':
+		user->voice = level;
+		sess->voices += offset;
+		break;
+	}
+}
+
+void
+userlist_update_mode (session *sess, char *name, char mode, char sign)
+{
+	int access;
+	int offset = 0;
+	int level;
+	int pos;
+	char prefix;
+	struct User *user;
+
+	user = userlist_find (sess, name);
+	if (!user)
+		return;
+
+	/* remove from binary trees, before we loose track of it */
+	tree_remove (sess->usertree, user, &pos);
+	tree_remove (sess->usertree_alpha, user, &pos);
+
+	/* which bit number is affected? */
+	access = mode_access (sess->server, mode, &prefix);
+
+	if (sign == '+')
+	{
+		level = TRUE;
+		if (!(user->access & (1 << access)))
+		{
+			offset = 1;
+			user->access |= (1 << access);
+		}
+	} else
+	{
+		level = FALSE;
+		if (user->access & (1 << access))
+		{
+			offset = -1;
+			user->access &= ~(1 << access);
+		}
+	}
+
+	/* now what is this users highest prefix? e.g. @ for ops */
+	user->prefix[0] = get_nick_prefix (sess->server, user->access);
+
+	/* update the various counts using the CHANGED prefix only */
+	update_counts (sess, user, prefix, level, offset);
+
+	/* insert it back into its new place */
+	tree_insert (sess->usertree_alpha, user);
+	pos = tree_insert (sess->usertree, user);
+
+	/* let GTK move it too */
+	fe_userlist_move (sess, user, pos);
+	fe_userlist_numbers (sess);
+}
+
+int
+userlist_change (struct session *sess, char *oldname, char *newname)
+{
+	struct User *user = userlist_find (sess, oldname);
+	int pos;
+
+	if (user)
+	{
+		tree_remove (sess->usertree, user, &pos);
+		tree_remove (sess->usertree_alpha, user, &pos);
+
+		safe_strcpy (user->nick, newname, NICKLEN);
+
+		tree_insert (sess->usertree_alpha, user);
+
+		fe_userlist_move (sess, user, tree_insert (sess->usertree, user));
+		fe_userlist_numbers (sess);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+userlist_remove (struct session *sess, char *name)
+{
+	struct User *user;
+	int pos;
+
+	user = userlist_find (sess, name);
+	if (!user)
+		return FALSE;
+
+	if (user->voice)
+		sess->voices--;
+	if (user->op)
+		sess->ops--;
+	if (user->hop)
+		sess->hops--;
+	sess->total--;
+	fe_userlist_numbers (sess);
+	fe_userlist_remove (sess, user);
+
+	if (user == sess->me)
+		sess->me = NULL;
+
+	tree_remove (sess->usertree, user, &pos);
+	tree_remove (sess->usertree_alpha, user, &pos);
+	free_user (user, NULL);
+
+	return TRUE;
+}
+
+void
+userlist_add (struct session *sess, char *name, char *hostname)
+{
+	struct User *user;
+	int row, prefix_chars;
+	unsigned int acc;
+
+	acc = nick_access (sess->server, name, &prefix_chars);
+
+	notify_set_online (sess->server, name + prefix_chars);
+
+	user = malloc (sizeof (struct User));
+	memset (user, 0, sizeof (struct User));
+
+	user->access = acc;
+
+	/* assume first char is the highest level nick prefix */
+	if (prefix_chars)
+		user->prefix[0] = name[0];
+
+	/* add it to our linked list */
+	if (hostname)
+		user->hostname = strdup (hostname);
+	safe_strcpy (user->nick, name + prefix_chars, NICKLEN);
+	/* is it me? */
+	if (!sess->server->p_cmp (user->nick, sess->server->nick))
+		user->me = TRUE;
+	row = userlist_insertname (sess, user);
+
+	/* duplicate? some broken servers trigger this */
+	if (row == -1)
+	{
+		if (user->hostname)
+			free (user->hostname);
+		free (user);
+		return;
+	}
+
+	sess->total++;
+
+	/* most ircds don't support multiple modechars infront of the nickname
+      for /NAMES - though they should. */
+	while (prefix_chars)
+	{
+		update_counts (sess, user, name[0], TRUE, 1);
+		name++;
+		prefix_chars--;
+	}
+
+	if (user->me)
+		sess->me = user;
+
+	fe_userlist_insert (sess, user, row, FALSE);
+	fe_userlist_numbers (sess);
+}
+
+static int
+rehash_cb (struct User *user, session *sess)
+{
+	fe_userlist_rehash (sess, user);
+	return TRUE;
+}
+
+void
+userlist_rehash (session *sess)
+{
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)rehash_cb, sess);
+}
+
+static int
+flat_cb (struct User *user, GSList **list)
+{
+	*list = g_slist_prepend (*list, user);
+	return TRUE;
+}
+
+GSList *
+userlist_flat_list (session *sess)
+{
+	GSList *list = NULL;
+
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)flat_cb, &list);
+	return g_slist_reverse (list);
+}
+
+static int
+double_cb (struct User *user, GList **list)
+{
+	*list = g_list_prepend(*list, user);
+	return TRUE;
+}
+
+GList *
+userlist_double_list(session *sess)
+{
+	GList *list = NULL;
+
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)double_cb, &list);
+	return list;
+}
diff --git a/src/common/userlist.h b/src/common/userlist.h
new file mode 100644
index 00000000..28831acd
--- /dev/null
+++ b/src/common/userlist.h
@@ -0,0 +1,41 @@
+#include <time.h>
+
+#ifndef XCHAT_USERLIST_H
+#define XCHAT_USERLIST_H
+
+struct User
+{
+	char nick[NICKLEN];
+	char *hostname;
+	char *realname;
+	char *servername;
+	time_t lasttalk;
+	unsigned int access;	/* axs bit field */
+	char prefix[2]; /* @ + % */
+	unsigned int op:1;
+	unsigned int hop:1;
+	unsigned int voice:1;
+	unsigned int me:1;
+	unsigned int away:1;
+	unsigned int selected:1;
+};
+
+#define USERACCESS_SIZE 12
+
+int userlist_add_hostname (session *sess, char *nick,
+									char *hostname, char *realname,
+									char *servername, unsigned int away);
+void userlist_set_away (session *sess, char *nick, unsigned int away);
+struct User *userlist_find (session *sess, char *name);
+struct User *userlist_find_global (server *serv, char *name);
+void userlist_clear (session *sess);
+void userlist_free (session *sess);
+void userlist_add (session *sess, char *name, char *hostname);
+int userlist_remove (session *sess, char *name);
+int userlist_change (session *sess, char *oldname, char *newname);
+void userlist_update_mode (session *sess, char *name, char mode, char sign);
+GSList *userlist_flat_list (session *sess);
+GList *userlist_double_list (session *sess);
+void userlist_rehash (session *sess);
+
+#endif
diff --git a/src/common/util.c b/src/common/util.c
new file mode 100644
index 00000000..49517ec4
--- /dev/null
+++ b/src/common/util.c
@@ -0,0 +1,1729 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#define __APPLE_API_STRICT_CONFORMANCE
+
+#define _FILE_OFFSET_BITS 64
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef WIN32
+#include <sys/timeb.h>
+#include <process.h>
+#else
+#include <sys/types.h>
+#include <pwd.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#endif
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include "xchat.h"
+#include "xchatc.h"
+#include <glib/gmarkup.h>
+#include <ctype.h>
+#include "util.h"
+#include "../../config.h"
+
+#define WANTSOCKET
+#include "inet.h"
+
+#if defined (USING_FREEBSD) || defined (__APPLE__)
+#include <sys/sysctl.h>
+#endif
+#ifdef SOCKS
+#include <socks.h>
+#endif
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+#ifdef USE_DEBUG
+
+#undef free
+#undef malloc
+#undef realloc
+#undef strdup
+
+int current_mem_usage;
+
+struct mem_block
+{
+	char *file;
+	void *buf;
+	int size;
+	int line;
+	int total;
+	struct mem_block *next;
+};
+
+struct mem_block *mroot = NULL;
+
+void *
+xchat_malloc (int size, char *file, int line)
+{
+	void *ret;
+	struct mem_block *new;
+
+	current_mem_usage += size;
+	ret = malloc (size);
+	if (!ret)
+	{
+		printf ("Out of memory! (%d)\n", current_mem_usage);
+		exit (255);
+	}
+
+	new = malloc (sizeof (struct mem_block));
+	new->buf = ret;
+	new->size = size;
+	new->next = mroot;
+	new->line = line;
+	new->file = strdup (file);
+	mroot = new;
+
+	printf ("%s:%d Malloc'ed %d bytes, now \033[35m%d\033[m\n", file, line,
+				size, current_mem_usage);
+
+	return ret;
+}
+
+void *
+xchat_realloc (char *old, int len, char *file, int line)
+{
+	char *ret;
+
+	ret = xchat_malloc (len, file, line);
+	if (ret)
+	{
+		strcpy (ret, old);
+		xchat_dfree (old, file, line);
+	}
+	return ret;
+}
+
+void *
+xchat_strdup (char *str, char *file, int line)
+{
+	void *ret;
+	struct mem_block *new;
+	int size;
+
+	size = strlen (str) + 1;
+	current_mem_usage += size;
+	ret = malloc (size);
+	if (!ret)
+	{
+		printf ("Out of memory! (%d)\n", current_mem_usage);
+		exit (255);
+	}
+	strcpy (ret, str);
+
+	new = malloc (sizeof (struct mem_block));
+	new->buf = ret;
+	new->size = size;
+	new->next = mroot;
+	new->line = line;
+	new->file = strdup (file);
+	mroot = new;
+
+	printf ("%s:%d strdup (\"%-.40s\") size: %d, total: \033[35m%d\033[m\n",
+				file, line, str, size, current_mem_usage);
+
+	return ret;
+}
+
+void
+xchat_mem_list (void)
+{
+	struct mem_block *cur, *p;
+	GSList *totals = 0;
+	GSList *list;
+
+	cur = mroot;
+	while (cur)
+	{
+		list = totals;
+		while (list)
+		{
+			p = list->data;
+			if (p->line == cur->line &&
+					strcmp (p->file, cur->file) == 0)
+			{
+				p->total += p->size;
+				break;
+			}
+			list = list->next;
+		}
+		if (!list)
+		{
+			cur->total = cur->size;
+			totals = g_slist_prepend (totals, cur);
+		}
+		cur = cur->next;
+	}
+
+	fprintf (stderr, "file              line   size    num  total\n");  
+	list = totals;
+	while (list)
+	{
+		cur = list->data;
+		fprintf (stderr, "%-15.15s %6d %6d %6d %6d\n", cur->file, cur->line,
+					cur->size, cur->total/cur->size, cur->total);
+		list = list->next;
+	}
+}
+
+void
+xchat_dfree (void *buf, char *file, int line)
+{
+	struct mem_block *cur, *last;
+
+	if (buf == NULL)
+	{
+		printf ("%s:%d \033[33mTried to free NULL\033[m\n", file, line);
+		return;
+	}
+
+	last = NULL;
+	cur = mroot;
+	while (cur)
+	{
+		if (buf == cur->buf)
+			break;
+		last = cur;
+		cur = cur->next;
+	}
+	if (cur == NULL)
+	{
+		printf ("%s:%d \033[31mTried to free unknown block %lx!\033[m\n",
+				  file, line, (unsigned long) buf);
+		/*      abort(); */
+		free (buf);
+		return;
+	}
+	current_mem_usage -= cur->size;
+	printf ("%s:%d Free'ed %d bytes, usage now \033[35m%d\033[m\n",
+				file, line, cur->size, current_mem_usage);
+	if (last)
+		last->next = cur->next;
+	else
+		mroot = cur->next;
+	free (cur->file);
+	free (cur);
+}
+
+#define malloc(n) xchat_malloc(n, __FILE__, __LINE__)
+#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__)
+#define free(n) xchat_dfree(n, __FILE__, __LINE__)
+#define strdup(n) xchat_strdup(n, __FILE__, __LINE__)
+
+#endif /* MEMORY_DEBUG */
+
+char *
+file_part (char *file)
+{
+	char *filepart = file;
+	if (!file)
+		return "";
+	while (1)
+	{
+		switch (*file)
+		{
+		case 0:
+			return (filepart);
+		case '/':
+#ifdef WIN32
+		case '\\':
+#endif
+			filepart = file + 1;
+			break;
+		}
+		file++;
+	}
+}
+
+void
+path_part (char *file, char *path, int pathlen)
+{
+	unsigned char t;
+	char *filepart = file_part (file);
+	t = *filepart;
+	*filepart = 0;
+	safe_strcpy (path, file, pathlen);
+	*filepart = t;
+}
+
+char *				/* like strstr(), but nocase */
+nocasestrstr (const char *s, const char *wanted)
+{
+	register const int len = strlen (wanted);
+
+	if (len == 0)
+		return (char *)s;
+	while (rfc_tolower(*s) != rfc_tolower(*wanted) || strncasecmp (s, wanted, len))
+		if (*s++ == '\0')
+			return (char *)NULL;
+	return (char *)s;
+}
+
+char *
+errorstring (int err)
+{
+	switch (err)
+	{
+	case -1:
+		return "";
+	case 0:
+		return _("Remote host closed socket");
+#ifndef WIN32
+	}
+#else
+	case WSAECONNREFUSED:
+		return _("Connection refused");
+	case WSAENETUNREACH:
+	case WSAEHOSTUNREACH:
+		return _("No route to host");
+	case WSAETIMEDOUT:
+		return _("Connection timed out");
+	case WSAEADDRNOTAVAIL:
+		return _("Cannot assign that address");
+	case WSAECONNRESET:
+		return _("Connection reset by peer");
+	}
+
+	/* can't use strerror() on Winsock errors! */
+	if (err >= WSABASEERR)
+	{
+		static char tbuf[384];
+		OSVERSIONINFO osvi;
+
+		osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+		GetVersionEx (&osvi);
+
+		/* FormatMessage works on WSA*** errors starting from Win2000 */
+		if (osvi.dwMajorVersion >= 5)
+		{
+			if (FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM |
+									  FORMAT_MESSAGE_IGNORE_INSERTS |
+									  FORMAT_MESSAGE_MAX_WIDTH_MASK,
+									  NULL, err,
+									  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
+									  tbuf, sizeof (tbuf), NULL))
+			{
+				int len;
+				char *utf;
+
+				tbuf[sizeof (tbuf) - 1] = 0;
+				len = strlen (tbuf);
+				if (len >= 2)
+					tbuf[len - 2] = 0;	/* remove the cr-lf */
+
+				/* now convert to utf8 */
+				utf = g_locale_to_utf8 (tbuf, -1, 0, 0, 0);
+				if (utf)
+				{
+					safe_strcpy (tbuf, utf, sizeof (tbuf));
+					g_free (utf);
+					return tbuf;
+				}
+			}
+		}	/* ! if (osvi.dwMajorVersion >= 5) */
+
+		/* fallback to error number */
+		sprintf (tbuf, "%s %d", _("Error"), err);
+		return tbuf;
+	} /* ! if (err >= WSABASEERR) */
+#endif	/* ! WIN32 */
+
+	return strerror (err);
+}
+
+int
+waitline (int sok, char *buf, int bufsize, int use_recv)
+{
+	int i = 0;
+
+	while (1)
+	{
+		if (use_recv)
+		{
+			if (recv (sok, &buf[i], 1, 0) < 1)
+				return -1;
+		} else
+		{
+			if (read (sok, &buf[i], 1) < 1)
+				return -1;
+		}
+		if (buf[i] == '\n' || bufsize == i + 1)
+		{
+			buf[i] = 0;
+			return i;
+		}
+		i++;
+	}
+}
+
+/* checks for "~" in a file and expands */
+
+char *
+expand_homedir (char *file)
+{
+#ifndef WIN32
+	char *ret, *user;
+	struct passwd *pw;
+
+	if (*file == '~')
+	{
+		if (file[1] != '\0' && file[1] != '/')
+		{
+			user = strdup(file);
+			if (strchr(user,'/') != NULL)
+				*(strchr(user,'/')) = '\0';
+			if ((pw = getpwnam(user + 1)) == NULL)
+			{
+				free(user);
+				return strdup(file);
+			}
+			free(user);
+			user = strchr(file, '/') != NULL ? strchr(file,'/') : file;
+			ret = malloc(strlen(user) + strlen(pw->pw_dir) + 1);
+			strcpy(ret, pw->pw_dir);
+			strcat(ret, user);
+		}
+		else
+		{
+			ret = malloc (strlen (file) + strlen (g_get_home_dir ()) + 1);
+			sprintf (ret, "%s%s", g_get_home_dir (), file + 1);
+		}
+		return ret;
+	}
+#endif
+	return strdup (file);
+}
+
+gchar *
+strip_color (const char *text, int len, int flags)
+{
+	char *new_str;
+
+	if (len == -1)
+		len = strlen (text);
+
+	new_str = g_malloc (len + 2);
+	strip_color2 (text, len, new_str, flags);
+
+	if (flags & STRIP_ESCMARKUP)
+	{
+		char *esc = g_markup_escape_text (new_str, -1);
+		g_free (new_str);
+		return esc;
+	}
+
+	return new_str;
+}
+
+/* CL: strip_color2 strips src and writes the output at dst; pass the same pointer
+	in both arguments to strip in place. */
+int
+strip_color2 (const char *src, int len, char *dst, int flags)
+{
+	int rcol = 0, bgcol = 0;
+	char *start = dst;
+
+	if (len == -1) len = strlen (src);
+	while (len-- > 0)
+	{
+		if (rcol > 0 && (isdigit ((unsigned char)*src) ||
+			(*src == ',' && isdigit ((unsigned char)src[1]) && !bgcol)))
+		{
+			if (src[1] != ',') rcol--;
+			if (*src == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*src)
+			{
+			case '\003':			  /*ATTR_COLOR: */
+				if (!(flags & STRIP_COLOR)) goto pass_char;
+				rcol = 2;
+				break;
+			case HIDDEN_CHAR:	/* CL: invisible text (for event formats only) */	/* this takes care of the topic */
+				if (!(flags & STRIP_HIDDEN)) goto pass_char;
+				break;
+			case '\007':			  /*ATTR_BEEP: */
+			case '\017':			  /*ATTR_RESET: */
+			case '\026':			  /*ATTR_REVERSE: */
+			case '\002':			  /*ATTR_BOLD: */
+			case '\037':			  /*ATTR_UNDERLINE: */
+			case '\035':			  /*ATTR_ITALICS: */
+				if (!(flags & STRIP_ATTRIB)) goto pass_char;
+				break;
+			default:
+			pass_char:
+				*dst++ = *src;
+			}
+		}
+		src++;
+	}
+	*dst = 0;
+
+	return (int) (dst - start);
+}
+
+int
+strip_hidden_attribute (char *src, char *dst)
+{
+	int len = 0;
+	while (*src != '\000')
+	{
+		if (*src != HIDDEN_CHAR)
+		{
+			*dst++ = *src;
+			len++;
+		}
+		src++;
+	}
+	return len;
+}
+
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+
+static void
+get_cpu_info (double *mhz, int *cpus)
+{
+
+#ifdef USING_LINUX
+
+	char buf[256];
+	int fh;
+
+	*mhz = 0;
+	*cpus = 0;
+
+	fh = open ("/proc/cpuinfo", O_RDONLY);	/* linux 2.2+ only */
+	if (fh == -1)
+	{
+		*cpus = 1;
+		return;
+	}
+
+	while (1)
+	{
+		if (waitline (fh, buf, sizeof buf, FALSE) < 0)
+			break;
+		if (!strncmp (buf, "cycle frequency [Hz]\t:", 22))	/* alpha */
+		{
+			*mhz = atoi (buf + 23) / 1000000;
+		} else if (!strncmp (buf, "cpu MHz\t\t:", 10))	/* i386 */
+		{
+			*mhz = atof (buf + 11) + 0.5;
+		} else if (!strncmp (buf, "clock\t\t:", 8))	/* PPC */
+		{
+			*mhz = atoi (buf + 9);
+		} else if (!strncmp (buf, "processor\t", 10))
+		{
+			(*cpus)++;
+		}
+	}
+	close (fh);
+	if (!*cpus)
+		*cpus = 1;
+
+#endif
+#ifdef USING_FREEBSD
+
+	int mib[2], ncpu;
+	u_long freq;
+	size_t len;
+
+	freq = 0;
+	*mhz = 0;
+	*cpus = 0;
+
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+
+	len = sizeof(ncpu);
+	sysctl(mib, 2, &ncpu, &len, NULL, 0);
+
+	len = sizeof(freq);
+	sysctlbyname("machdep.tsc_freq", &freq, &len, NULL, 0);
+
+	*cpus = ncpu;
+	*mhz = (freq / 1000000);
+
+#endif
+#ifdef __APPLE__
+
+	int mib[2], ncpu;
+	unsigned long long freq;
+	size_t len;
+
+	freq = 0;
+	*mhz = 0;
+	*cpus = 0;
+
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+
+	len = sizeof(ncpu);
+	sysctl(mib, 2, &ncpu, &len, NULL, 0);
+
+	len = sizeof(freq);
+        sysctlbyname("hw.cpufrequency", &freq, &len, NULL, 0);
+
+	*cpus = ncpu;
+	*mhz = (freq / 1000000);
+
+#endif
+
+}
+#endif
+
+#ifdef WIN32
+
+static int
+get_mhz (void)
+{
+	HKEY hKey;
+	int result, data, dataSize;
+
+	if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Hardware\\Description\\System\\"
+		"CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
+	{
+		dataSize = sizeof (data);
+		result = RegQueryValueEx (hKey, "~MHz", 0, 0, (LPBYTE)&data, &dataSize);
+		RegCloseKey (hKey);
+		if (result == ERROR_SUCCESS)
+			return data;
+	}
+	return 0;	/* fails on Win9x */
+}
+
+char *
+get_cpu_str (void)
+{
+	static char verbuf[64];
+	OSVERSIONINFO osvi;
+	SYSTEM_INFO si;
+	double mhz;
+
+	osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+	GetVersionEx (&osvi);
+	GetSystemInfo (&si);
+
+	mhz = get_mhz ();
+	if (mhz)
+	{
+		double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz;
+		const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz";
+		sprintf (verbuf, "Windows %ld.%ld [i%d86/%.2f%s]",
+					osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel, 
+					cpuspeed, cpuspeedstr);
+	} else
+		sprintf (verbuf, "Windows %ld.%ld [i%d86]",
+			osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel);
+
+	return verbuf;
+}
+
+#else
+
+char *
+get_cpu_str (void)
+{
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+	double mhz;
+#endif
+	int cpus = 1;
+	struct utsname un;
+	static char *buf = NULL;
+
+	if (buf)
+		return buf;
+
+	buf = malloc (128);
+
+	uname (&un);
+
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+	get_cpu_info (&mhz, &cpus);
+	if (mhz)
+	{
+		double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz;
+		const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz";
+		snprintf (buf, 128,
+					(cpus == 1) ? "%s %s [%s/%.2f%s]" : "%s %s [%s/%.2f%s/SMP]",
+					un.sysname, un.release, un.machine,
+					cpuspeed, cpuspeedstr);
+	} else
+#endif
+		snprintf (buf, 128,
+					(cpus == 1) ? "%s %s [%s]" : "%s %s [%s/SMP]",
+					un.sysname, un.release, un.machine);
+
+	return buf;
+}
+
+#endif
+
+int
+buf_get_line (char *ibuf, char **buf, int *position, int len)
+{
+	int pos = *position, spos = pos;
+
+	if (pos == len)
+		return 0;
+
+	while (ibuf[pos++] != '\n')
+	{
+		if (pos == len)
+			return 0;
+	}
+	pos--;
+	ibuf[pos] = 0;
+	*buf = &ibuf[spos];
+	pos++;
+	*position = pos;
+	return 1;
+}
+
+int match(const char *mask, const char *string)
+{
+  register const char *m = mask, *s = string;
+  register char ch;
+  const char *bm, *bs;		/* Will be reg anyway on a decent CPU/compiler */
+
+  /* Process the "head" of the mask, if any */
+  while ((ch = *m++) && (ch != '*'))
+    switch (ch)
+    {
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	if (rfc_tolower(*s) != rfc_tolower(ch))
+	  return 0;
+      case '?':
+	if (!*s++)
+	  return 0;
+    };
+  if (!ch)
+    return !(*s);
+
+  /* We got a star: quickly find if/where we match the next char */
+got_star:
+  bm = m;			/* Next try rollback here */
+  while ((ch = *m++))
+    switch (ch)
+    {
+      case '?':
+	if (!*s++)
+	  return 0;
+      case '*':
+	bm = m;
+	continue;		/* while */
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	goto break_while;	/* C is structured ? */
+    };
+break_while:
+  if (!ch)
+    return 1;			/* mask ends with '*', we got it */
+  ch = rfc_tolower(ch);
+  while (rfc_tolower(*s++) != ch)
+    if (!*s)
+      return 0;
+  bs = s;			/* Next try start from here */
+
+  /* Check the rest of the "chunk" */
+  while ((ch = *m++))
+  {
+    switch (ch)
+    {
+      case '*':
+	goto got_star;
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	if (rfc_tolower(*s) != rfc_tolower(ch))
+	{
+	  if (!*s)
+	    return 0;
+	  m = bm;
+	  s = bs;
+	  goto got_star;
+	};
+      case '?':
+	if (!*s++)
+	  return 0;
+    };
+  };
+  if (*s)
+  {
+    m = bm;
+    s = bs;
+    goto got_star;
+  };
+  return 1;
+}
+
+void
+for_files (char *dirname, char *mask, void callback (char *file))
+{
+	DIR *dir;
+	struct dirent *ent;
+	char *buf;
+
+	dir = opendir (dirname);
+	if (dir)
+	{
+		while ((ent = readdir (dir)))
+		{
+			if (strcmp (ent->d_name, ".") && strcmp (ent->d_name, ".."))
+			{
+				if (match (mask, ent->d_name))
+				{
+					buf = malloc (strlen (dirname) + strlen (ent->d_name) + 2);
+					sprintf (buf, "%s/%s", dirname, ent->d_name);
+					callback (buf);
+					free (buf);
+				}
+			}
+		}
+		closedir (dir);
+	}
+}
+
+/*void
+tolowerStr (char *str)
+{
+	while (*str)
+	{
+		*str = rfc_tolower (*str);
+		str++;
+	}
+}*/
+
+typedef struct
+{
+	char *code, *country;
+} domain_t;
+
+static int
+country_compare (const void *a, const void *b)
+{
+	return strcasecmp (a, ((domain_t *)b)->code);
+}
+
+static const domain_t domain[] =
+{
+		{"AC", N_("Ascension Island") },
+		{"AD", N_("Andorra") },
+		{"AE", N_("United Arab Emirates") },
+		{"AF", N_("Afghanistan") },
+		{"AG", N_("Antigua and Barbuda") },
+		{"AI", N_("Anguilla") },
+		{"AL", N_("Albania") },
+		{"AM", N_("Armenia") },
+		{"AN", N_("Netherlands Antilles") },
+		{"AO", N_("Angola") },
+		{"AQ", N_("Antarctica") },
+		{"AR", N_("Argentina") },
+		{"ARPA", N_("Reverse DNS") },
+		{"AS", N_("American Samoa") },
+		{"AT", N_("Austria") },
+		{"ATO", N_("Nato Fiel") },
+		{"AU", N_("Australia") },
+		{"AW", N_("Aruba") },
+		{"AX", N_("Aland Islands") },
+		{"AZ", N_("Azerbaijan") },
+		{"BA", N_("Bosnia and Herzegovina") },
+		{"BB", N_("Barbados") },
+		{"BD", N_("Bangladesh") },
+		{"BE", N_("Belgium") },
+		{"BF", N_("Burkina Faso") },
+		{"BG", N_("Bulgaria") },
+		{"BH", N_("Bahrain") },
+		{"BI", N_("Burundi") },
+		{"BIZ", N_("Businesses"), },
+		{"BJ", N_("Benin") },
+		{"BM", N_("Bermuda") },
+		{"BN", N_("Brunei Darussalam") },
+		{"BO", N_("Bolivia") },
+		{"BR", N_("Brazil") },
+		{"BS", N_("Bahamas") },
+		{"BT", N_("Bhutan") },
+		{"BV", N_("Bouvet Island") },
+		{"BW", N_("Botswana") },
+		{"BY", N_("Belarus") },
+		{"BZ", N_("Belize") },
+		{"CA", N_("Canada") },
+		{"CC", N_("Cocos Islands") },
+		{"CD", N_("Democratic Republic of Congo") },
+		{"CF", N_("Central African Republic") },
+		{"CG", N_("Congo") },
+		{"CH", N_("Switzerland") },
+		{"CI", N_("Cote d'Ivoire") },
+		{"CK", N_("Cook Islands") },
+		{"CL", N_("Chile") },
+		{"CM", N_("Cameroon") },
+		{"CN", N_("China") },
+		{"CO", N_("Colombia") },
+		{"COM", N_("Internic Commercial") },
+		{"CR", N_("Costa Rica") },
+		{"CS", N_("Serbia and Montenegro") },
+		{"CU", N_("Cuba") },
+		{"CV", N_("Cape Verde") },
+		{"CX", N_("Christmas Island") },
+		{"CY", N_("Cyprus") },
+		{"CZ", N_("Czech Republic") },
+		{"DE", N_("Germany") },
+		{"DJ", N_("Djibouti") },
+		{"DK", N_("Denmark") },
+		{"DM", N_("Dominica") },
+		{"DO", N_("Dominican Republic") },
+		{"DZ", N_("Algeria") },
+		{"EC", N_("Ecuador") },
+		{"EDU", N_("Educational Institution") },
+		{"EE", N_("Estonia") },
+		{"EG", N_("Egypt") },
+		{"EH", N_("Western Sahara") },
+		{"ER", N_("Eritrea") },
+		{"ES", N_("Spain") },
+		{"ET", N_("Ethiopia") },
+		{"EU", N_("European Union") },
+		{"FI", N_("Finland") },
+		{"FJ", N_("Fiji") },
+		{"FK", N_("Falkland Islands") },
+		{"FM", N_("Micronesia") },
+		{"FO", N_("Faroe Islands") },
+		{"FR", N_("France") },
+		{"GA", N_("Gabon") },
+		{"GB", N_("Great Britain") },
+		{"GD", N_("Grenada") },
+		{"GE", N_("Georgia") },
+		{"GF", N_("French Guiana") },
+		{"GG", N_("British Channel Isles") },
+		{"GH", N_("Ghana") },
+		{"GI", N_("Gibraltar") },
+		{"GL", N_("Greenland") },
+		{"GM", N_("Gambia") },
+		{"GN", N_("Guinea") },
+		{"GOV", N_("Government") },
+		{"GP", N_("Guadeloupe") },
+		{"GQ", N_("Equatorial Guinea") },
+		{"GR", N_("Greece") },
+		{"GS", N_("S. Georgia and S. Sandwich Isles") },
+		{"GT", N_("Guatemala") },
+		{"GU", N_("Guam") },
+		{"GW", N_("Guinea-Bissau") },
+		{"GY", N_("Guyana") },
+		{"HK", N_("Hong Kong") },
+		{"HM", N_("Heard and McDonald Islands") },
+		{"HN", N_("Honduras") },
+		{"HR", N_("Croatia") },
+		{"HT", N_("Haiti") },
+		{"HU", N_("Hungary") },
+		{"ID", N_("Indonesia") },
+		{"IE", N_("Ireland") },
+		{"IL", N_("Israel") },
+		{"IM", N_("Isle of Man") },
+		{"IN", N_("India") },
+		{"INFO", N_("Informational") },
+		{"INT", N_("International") },
+		{"IO", N_("British Indian Ocean Territory") },
+		{"IQ", N_("Iraq") },
+		{"IR", N_("Iran") },
+		{"IS", N_("Iceland") },
+		{"IT", N_("Italy") },
+		{"JE", N_("Jersey") },
+		{"JM", N_("Jamaica") },
+		{"JO", N_("Jordan") },
+		{"JP", N_("Japan") },
+		{"KE", N_("Kenya") },
+		{"KG", N_("Kyrgyzstan") },
+		{"KH", N_("Cambodia") },
+		{"KI", N_("Kiribati") },
+		{"KM", N_("Comoros") },
+		{"KN", N_("St. Kitts and Nevis") },
+		{"KP", N_("North Korea") },
+		{"KR", N_("South Korea") },
+		{"KW", N_("Kuwait") },
+		{"KY", N_("Cayman Islands") },
+		{"KZ", N_("Kazakhstan") },
+		{"LA", N_("Laos") },
+		{"LB", N_("Lebanon") },
+		{"LC", N_("Saint Lucia") },
+		{"LI", N_("Liechtenstein") },
+		{"LK", N_("Sri Lanka") },
+		{"LR", N_("Liberia") },
+		{"LS", N_("Lesotho") },
+		{"LT", N_("Lithuania") },
+		{"LU", N_("Luxembourg") },
+		{"LV", N_("Latvia") },
+		{"LY", N_("Libya") },
+		{"MA", N_("Morocco") },
+		{"MC", N_("Monaco") },
+		{"MD", N_("Moldova") },
+		{"MED", N_("United States Medical") },
+		{"MG", N_("Madagascar") },
+		{"MH", N_("Marshall Islands") },
+		{"MIL", N_("Military") },
+		{"MK", N_("Macedonia") },
+		{"ML", N_("Mali") },
+		{"MM", N_("Myanmar") },
+		{"MN", N_("Mongolia") },
+		{"MO", N_("Macau") },
+		{"MP", N_("Northern Mariana Islands") },
+		{"MQ", N_("Martinique") },
+		{"MR", N_("Mauritania") },
+		{"MS", N_("Montserrat") },
+		{"MT", N_("Malta") },
+		{"MU", N_("Mauritius") },
+		{"MV", N_("Maldives") },
+		{"MW", N_("Malawi") },
+		{"MX", N_("Mexico") },
+		{"MY", N_("Malaysia") },
+		{"MZ", N_("Mozambique") },
+		{"NA", N_("Namibia") },
+		{"NC", N_("New Caledonia") },
+		{"NE", N_("Niger") },
+		{"NET", N_("Internic Network") },
+		{"NF", N_("Norfolk Island") },
+		{"NG", N_("Nigeria") },
+		{"NI", N_("Nicaragua") },
+		{"NL", N_("Netherlands") },
+		{"NO", N_("Norway") },
+		{"NP", N_("Nepal") },
+		{"NR", N_("Nauru") },
+		{"NU", N_("Niue") },
+		{"NZ", N_("New Zealand") },
+		{"OM", N_("Oman") },
+		{"ORG", N_("Internic Non-Profit Organization") },
+		{"PA", N_("Panama") },
+		{"PE", N_("Peru") },
+		{"PF", N_("French Polynesia") },
+		{"PG", N_("Papua New Guinea") },
+		{"PH", N_("Philippines") },
+		{"PK", N_("Pakistan") },
+		{"PL", N_("Poland") },
+		{"PM", N_("St. Pierre and Miquelon") },
+		{"PN", N_("Pitcairn") },
+		{"PR", N_("Puerto Rico") },
+		{"PS", N_("Palestinian Territory") },
+		{"PT", N_("Portugal") },
+		{"PW", N_("Palau") },
+		{"PY", N_("Paraguay") },
+		{"QA", N_("Qatar") },
+		{"RE", N_("Reunion") },
+		{"RO", N_("Romania") },
+		{"RPA", N_("Old School ARPAnet") },
+		{"RU", N_("Russian Federation") },
+		{"RW", N_("Rwanda") },
+		{"SA", N_("Saudi Arabia") },
+		{"SB", N_("Solomon Islands") },
+		{"SC", N_("Seychelles") },
+		{"SD", N_("Sudan") },
+		{"SE", N_("Sweden") },
+		{"SG", N_("Singapore") },
+		{"SH", N_("St. Helena") },
+		{"SI", N_("Slovenia") },
+		{"SJ", N_("Svalbard and Jan Mayen Islands") },
+		{"SK", N_("Slovak Republic") },
+		{"SL", N_("Sierra Leone") },
+		{"SM", N_("San Marino") },
+		{"SN", N_("Senegal") },
+		{"SO", N_("Somalia") },
+		{"SR", N_("Suriname") },
+		{"ST", N_("Sao Tome and Principe") },
+		{"SU", N_("Former USSR") },
+		{"SV", N_("El Salvador") },
+		{"SY", N_("Syria") },
+		{"SZ", N_("Swaziland") },
+		{"TC", N_("Turks and Caicos Islands") },
+		{"TD", N_("Chad") },
+		{"TF", N_("French Southern Territories") },
+		{"TG", N_("Togo") },
+		{"TH", N_("Thailand") },
+		{"TJ", N_("Tajikistan") },
+		{"TK", N_("Tokelau") },
+		{"TL", N_("East Timor") },
+		{"TM", N_("Turkmenistan") },
+		{"TN", N_("Tunisia") },
+		{"TO", N_("Tonga") },
+		{"TP", N_("East Timor") },
+		{"TR", N_("Turkey") },
+		{"TT", N_("Trinidad and Tobago") },
+		{"TV", N_("Tuvalu") },
+		{"TW", N_("Taiwan") },
+		{"TZ", N_("Tanzania") },
+		{"UA", N_("Ukraine") },
+		{"UG", N_("Uganda") },
+		{"UK", N_("United Kingdom") },
+		{"US", N_("United States of America") },
+		{"UY", N_("Uruguay") },
+		{"UZ", N_("Uzbekistan") },
+		{"VA", N_("Vatican City State") },
+		{"VC", N_("St. Vincent and the Grenadines") },
+		{"VE", N_("Venezuela") },
+		{"VG", N_("British Virgin Islands") },
+		{"VI", N_("US Virgin Islands") },
+		{"VN", N_("Vietnam") },
+		{"VU", N_("Vanuatu") },
+		{"WF", N_("Wallis and Futuna Islands") },
+		{"WS", N_("Samoa") },
+		{"YE", N_("Yemen") },
+		{"YT", N_("Mayotte") },
+		{"YU", N_("Yugoslavia") },
+		{"ZA", N_("South Africa") },
+		{"ZM", N_("Zambia") },
+		{"ZW", N_("Zimbabwe") },
+};
+
+char *
+country (char *hostname)
+{
+	char *p;
+	domain_t *dom;
+
+	if (!hostname || !*hostname || isdigit ((unsigned char) hostname[strlen (hostname) - 1]))
+		return _("Unknown");
+	if ((p = strrchr (hostname, '.')))
+		p++;
+	else
+		p = hostname;
+
+	dom = bsearch (p, domain, sizeof (domain) / sizeof (domain_t),
+						sizeof (domain_t), country_compare);
+
+	if (!dom)
+		return _("Unknown");
+
+	return _(dom->country);
+}
+
+void
+country_search (char *pattern, void *ud, void (*print)(void *, char *, ...))
+{
+	const domain_t *dom;
+	int i;
+
+	for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++)
+	{
+		dom = &domain[i];
+		if (match (pattern, dom->country) || match (pattern, _(dom->country)))
+		{
+			print (ud, "%s = %s\n", dom->code, _(dom->country));
+		}
+	}
+}
+
+/* I think gnome1.0.x isn't necessarily linked against popt, ah well! */
+/* !!! For now use this inlined function, or it would break fe-text building */
+/* .... will find a better solution later. */
+/*#ifndef USE_GNOME*/
+
+/* this is taken from gnome-libs 1.2.4 */
+#define POPT_ARGV_ARRAY_GROW_DELTA 5
+
+int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr) {
+    char * buf, * bufStart, * dst;
+    const char * src;
+    char quote = '\0';
+    int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
+    char ** argv = malloc(sizeof(*argv) * argvAlloced);
+    const char ** argv2;
+    int argc = 0;
+    int i, buflen;
+
+    buflen = strlen(s) + 1;
+/*    bufStart = buf = alloca(buflen);*/
+	 bufStart = buf = malloc (buflen);
+    memset(buf, '\0', buflen);
+
+    src = s;
+    argv[argc] = buf;
+
+    while (*src) {
+	if (quote == *src) {
+	    quote = '\0';
+	} else if (quote) {
+	    if (*src == '\\') {
+		src++;
+		if (!*src) {
+		    free(argv);
+			 free(bufStart);
+		    return 1;
+		}
+		if (*src != quote) *buf++ = '\\';
+	    }
+	    *buf++ = *src;
+	/*} else if (isspace((unsigned char) *src)) {*/
+	} else if (*src == ' ') {
+	    if (*argv[argc]) {
+		buf++, argc++;
+		if (argc == argvAlloced) {
+		    argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA;
+		    argv = realloc(argv, sizeof(*argv) * argvAlloced);
+		}
+		argv[argc] = buf;
+	    }
+	} else switch (*src) {
+	  case '"':
+	  case '\'':
+	    quote = *src;
+	    break;
+	  case '\\':
+	    src++;
+	    if (!*src) {
+		free(argv);
+		free(bufStart);
+		return 1;
+	    }
+	    /* fallthrough */
+	  default:
+	    *buf++ = *src;
+	}
+
+	src++;
+    }
+
+    if (strlen(argv[argc])) {
+	argc++, buf++;
+    }
+
+    dst = malloc((argc + 1) * sizeof(*argv) + (buf - bufStart));
+    argv2 = (void *) dst;
+    dst += (argc + 1) * sizeof(*argv);
+    memcpy((void *)argv2, argv, argc * sizeof(*argv));
+    argv2[argc] = NULL;
+    memcpy(dst, bufStart, buf - bufStart);
+
+    for (i = 0; i < argc; i++) {
+	argv2[i] = dst + (argv[i] - bufStart);
+    }
+
+    free(argv);
+
+    *argvPtr = (char **)argv2;	/* XXX don't change the API */
+    *argcPtr = argc;
+
+	 free (bufStart);
+
+    return 0;
+}
+
+int
+util_exec (const char *cmd)
+{
+	int pid;
+	char **argv;
+	int argc;
+	int fd;
+
+	if (my_poptParseArgvString (cmd, &argc, &argv) != 0)
+		return -1;
+
+#ifndef WIN32
+	pid = fork ();
+	if (pid == -1)
+		return -1;
+	if (pid == 0)
+	{
+		/* Now close all open file descriptors except stdin, stdout and stderr */
+		for (fd = 3; fd < 1024; fd++) close(fd);
+		execvp (argv[0], argv);
+		_exit (0);
+	} else
+	{
+		free (argv);
+		return pid;
+	}
+#else
+	spawnvp (_P_DETACH, argv[0], argv);
+	free (argv);
+	return 0;
+#endif
+}
+
+int
+util_execv (char * const argv[])
+{
+	int pid, fd;
+
+#ifndef WIN32
+	pid = fork ();
+	if (pid == -1)
+		return -1;
+	if (pid == 0)
+	{
+		/* Now close all open file descriptors except stdin, stdout and stderr */
+		for (fd = 3; fd < 1024; fd++) close(fd);
+		execv (argv[0], argv);
+		_exit (0);
+	} else
+	{
+		return pid;
+	}
+#else
+	spawnv (_P_DETACH, argv[0], argv);
+	return 0;
+#endif
+}
+
+unsigned long
+make_ping_time (void)
+{
+#ifndef WIN32
+	struct timeval timev;
+	gettimeofday (&timev, 0);
+#else
+	GTimeVal timev;
+	g_get_current_time (&timev);
+#endif
+	return (timev.tv_sec - 50000) * 1000000 + timev.tv_usec;
+}
+
+
+/************************************************************************
+ *    This technique was borrowed in part from the source code to 
+ *    ircd-hybrid-5.3 to implement case-insensitive string matches which
+ *    are fully compliant with Section 2.2 of RFC 1459, the copyright
+ *    of that code being (C) 1990 Jarkko Oikarinen and under the GPL.
+ *    
+ *    A special thanks goes to Mr. Okarinen for being the one person who
+ *    seems to have ever noticed this section in the original RFC and
+ *    written code for it.  Shame on all the rest of you (myself included).
+ *    
+ *        --+ Dagmar d'Surreal
+ */
+
+int
+rfc_casecmp (const char *s1, const char *s2)
+{
+	register unsigned char *str1 = (unsigned char *) s1;
+	register unsigned char *str2 = (unsigned char *) s2;
+	register int res;
+
+	while ((res = rfc_tolower (*str1) - rfc_tolower (*str2)) == 0)
+	{
+		if (*str1 == '\0')
+			return 0;
+		str1++;
+		str2++;
+	}
+	return (res);
+}
+
+int
+rfc_ncasecmp (char *str1, char *str2, int n)
+{
+	register unsigned char *s1 = (unsigned char *) str1;
+	register unsigned char *s2 = (unsigned char *) str2;
+	register int res;
+
+	while ((res = rfc_tolower (*s1) - rfc_tolower (*s2)) == 0)
+	{
+		s1++;
+		s2++;
+		n--;
+		if (n == 0 || (*s1 == '\0' && *s2 == '\0'))
+			return 0;
+	}
+	return (res);
+}
+
+const unsigned char rfc_tolowertab[] =
+	{ 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
+	0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
+	0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+	0x1e, 0x1f,
+	' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
+	'*', '+', ',', '-', '.', '/',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	':', ';', '<', '=', '>', '?',
+	'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+	'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+	't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
+	'_',
+	'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+	'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+	't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
+	0x7f,
+	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+	0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+	0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+	0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+	0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+	0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+	0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+	0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+/*static unsigned char touppertab[] =
+	{ 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
+	0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
+	0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+	0x1e, 0x1f,
+	' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
+	'*', '+', ',', '-', '.', '/',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	':', ';', '<', '=', '>', '?',
+	'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+	'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+	'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
+	0x5f,
+	'`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+	'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+	'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
+	0x7f,
+	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+	0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+	0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+	0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+	0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+	0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+	0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+	0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};*/
+
+/*static int
+rename_utf8 (char *oldname, char *newname)
+{
+	int sav, res;
+	char *fso, *fsn;
+
+	fso = xchat_filename_from_utf8 (oldname, -1, 0, 0, 0);
+	if (!fso)
+		return FALSE;
+	fsn = xchat_filename_from_utf8 (newname, -1, 0, 0, 0);
+	if (!fsn)
+	{
+		g_free (fso);
+		return FALSE;
+	}
+
+	res = rename (fso, fsn);
+	sav = errno;
+	g_free (fso);
+	g_free (fsn);
+	errno = sav;
+	return res;
+}
+
+static int
+unlink_utf8 (char *fname)
+{
+	int res;
+	char *fs;
+
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+	if (!fs)
+		return FALSE;
+
+	res = unlink (fs);
+	g_free (fs);
+	return res;
+}*/
+
+static gboolean
+file_exists_utf8 (char *fname)
+{
+	int res;
+	char *fs;
+
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+	if (!fs)
+		return FALSE;
+
+	res = access (fs, F_OK);
+	g_free (fs);
+	if (res == 0)
+		return TRUE;
+	return FALSE;
+}
+
+static gboolean
+copy_file (char *dl_src, char *dl_dest, int permissions)	/* FS encoding */
+{
+	int tmp_src, tmp_dest;
+	gboolean ok = FALSE;
+	char dl_tmp[4096];
+	int return_tmp, return_tmp2;
+
+	if ((tmp_src = open (dl_src, O_RDONLY | OFLAGS)) == -1)
+	{
+		fprintf (stderr, "Unable to open() file '%s' (%s) !", dl_src,
+				  strerror (errno));
+		return FALSE;
+	}
+
+	if ((tmp_dest =
+		 open (dl_dest, O_WRONLY | O_CREAT | O_TRUNC | OFLAGS, permissions)) < 0)
+	{
+		close (tmp_src);
+		fprintf (stderr, "Unable to create file '%s' (%s) !", dl_src,
+				  strerror (errno));
+		return FALSE;
+	}
+
+	for (;;)
+	{
+		return_tmp = read (tmp_src, dl_tmp, sizeof (dl_tmp));
+
+		if (!return_tmp)
+		{
+			ok = TRUE;
+			break;
+		}
+
+		if (return_tmp < 0)
+		{
+			fprintf (stderr, "download_move_to_completed_dir(): "
+				"error reading while moving file to save directory (%s)",
+				 strerror (errno));
+			break;
+		}
+
+		return_tmp2 = write (tmp_dest, dl_tmp, return_tmp);
+
+		if (return_tmp2 < 0)
+		{
+			fprintf (stderr, "download_move_to_completed_dir(): "
+				"error writing while moving file to save directory (%s)",
+				 strerror (errno));
+			break;
+		}
+
+		if (return_tmp < sizeof (dl_tmp))
+		{
+			ok = TRUE;
+			break;
+		}
+	}
+
+	close (tmp_dest);
+	close (tmp_src);
+	return ok;
+}
+
+/* Takes care of moving a file from a temporary download location to a completed location. Now in UTF-8. */
+void
+move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions)
+{
+	char src[4096];
+	char dst[4096];
+	int res, i;
+	char *src_fs;	/* FileSystem encoding */
+	char *dst_fs;
+
+	/* if dcc_dir and dcc_completed_dir are the same then we are done */
+	if (0 == strcmp (src_dir, dst_dir) ||
+		 0 == dst_dir[0])
+		return;			/* Already in "completed dir" */
+
+	snprintf (src, sizeof (src), "%s/%s", src_dir, fname);
+	snprintf (dst, sizeof (dst), "%s/%s", dst_dir, fname);
+
+	/* already exists in completed dir? Append a number */
+	if (file_exists_utf8 (dst))
+	{
+		for (i = 0; ; i++)
+		{
+			snprintf (dst, sizeof (dst), "%s/%s.%d", dst_dir, fname, i);
+			if (!file_exists_utf8 (dst))
+				break;
+		}
+	}
+
+	/* convert UTF-8 to filesystem encoding */
+	src_fs = xchat_filename_from_utf8 (src, -1, 0, 0, 0);
+	if (!src_fs)
+		return;
+	dst_fs = xchat_filename_from_utf8 (dst, -1, 0, 0, 0);
+	if (!dst_fs)
+	{
+		g_free (src_fs);
+		return;
+	}
+
+	/* first try a simple rename move */
+	res = rename (src_fs, dst_fs);
+
+	if (res == -1 && (errno == EXDEV || errno == EPERM))
+	{
+		/* link failed because either the two paths aren't on the */
+		/* same filesystem or the filesystem doesn't support hard */
+		/* links, so we have to do a copy. */
+		if (copy_file (src_fs, dst_fs, dccpermissions))
+			unlink (src_fs);
+	}
+
+	g_free (dst_fs);
+	g_free (src_fs);
+}
+
+int
+mkdir_utf8 (char *dir)
+{
+	int ret;
+
+	dir = xchat_filename_from_utf8 (dir, -1, 0, 0, 0);
+	if (!dir)
+		return -1;
+
+#ifdef WIN32
+	ret = mkdir (dir);
+#else
+	ret = mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+#endif
+	g_free (dir);
+
+	return ret;
+}
+
+/* separates a string according to a 'sep' char, then calls the callback
+   function for each token found */
+
+int
+token_foreach (char *str, char sep,
+					int (*callback) (char *str, void *ud), void *ud)
+{
+	char t, *start = str;
+
+	while (1)
+	{
+		if (*str == sep || *str == 0)
+		{
+			t = *str;
+			*str = 0;
+			if (callback (start, ud) < 1)
+			{
+				*str = t;
+				return FALSE;
+			}
+			*str = t;
+
+			if (*str == 0)
+				break;
+			str++;
+			start = str;
+
+		} else
+		{
+			/* chars $00-$7f can never be embedded in utf-8 */
+			str++;
+		}
+	}
+
+	return TRUE;
+}
+
+/* 31 bit string hash functions */
+
+guint32
+str_hash (const char *key)
+{
+	const char *p = key;
+	guint32 h = *p;
+
+	if (h)
+		for (p += 1; *p != '\0'; p++)
+			h = (h << 5) - h + *p;
+
+	return h;
+}
+
+guint32
+str_ihash (const unsigned char *key)
+{
+	const char *p = key;
+	guint32 h = rfc_tolowertab [(guint)*p];
+
+	if (h)
+		for (p += 1; *p != '\0'; p++)
+			h = (h << 5) - h + rfc_tolowertab [(guint)*p];
+
+	return h;
+}
+
+/* features: 1. "src" must be valid, NULL terminated UTF-8 */
+/*           2. "dest" will be left with valid UTF-8 - no partial chars! */
+
+void
+safe_strcpy (char *dest, const char *src, int bytes_left)
+{
+	int mbl;
+
+	while (1)
+	{
+		mbl = g_utf8_skip[*((unsigned char *)src)];
+
+		if (bytes_left < (mbl + 1)) /* can't fit with NULL? */
+		{
+			*dest = 0;
+			break;
+		}
+
+		if (mbl == 1)	/* one byte char */
+		{
+			*dest = *src;
+			if (*src == 0)
+				break;	/* it all fit */
+			dest++;
+			src++;
+			bytes_left--;
+		}
+		else				/* multibyte char */
+		{
+			memcpy (dest, src, mbl);
+			dest += mbl;
+			src += mbl;
+			bytes_left -= mbl;
+		}
+	}
+}
diff --git a/src/common/util.h b/src/common/util.h
new file mode 100644
index 00000000..fce45def
--- /dev/null
+++ b/src/common/util.h
@@ -0,0 +1,54 @@
+/************************************************************************
+ *    This technique was borrowed in part from the source code to 
+ *    ircd-hybrid-5.3 to implement case-insensitive string matches which
+ *    are fully compliant with Section 2.2 of RFC 1459, the copyright
+ *    of that code being (C) 1990 Jarkko Oikarinen and under the GPL.
+ *    
+ *    A special thanks goes to Mr. Okarinen for being the one person who
+ *    seems to have ever noticed this section in the original RFC and
+ *    written code for it.  Shame on all the rest of you (myself included).
+ *    
+ *        --+ Dagmar d'Surreal
+ */
+
+#ifndef XCHAT_UTIL_H
+#define XCHAT_UTIL_H
+
+#define rfc_tolower(c) (rfc_tolowertab[(unsigned char)(c)])
+
+extern const unsigned char rfc_tolowertab[];
+
+int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr);
+char *expand_homedir (char *file);
+void path_part (char *file, char *path, int pathlen);
+int match (const char *mask, const char *string);
+char *file_part (char *file);
+void for_files (char *dirname, char *mask, void callback (char *file));
+int rfc_casecmp (const char *, const char *);
+int rfc_ncasecmp (char *, char *, int);
+int buf_get_line (char *, char **, int *, int len);
+char *nocasestrstr (const char *text, const char *tofind);
+char *country (char *);
+void country_search (char *pattern, void *ud, void (*print)(void *, char *, ...));
+char *get_cpu_str (void);
+int util_exec (const char *cmd);
+int util_execv (char * const argv[]);
+#define STRIP_COLOR 1
+#define STRIP_ATTRIB 2
+#define STRIP_HIDDEN 4
+#define STRIP_ESCMARKUP 8
+#define STRIP_ALL 7
+gchar *strip_color (const char *text, int len, int flags);
+int strip_color2 (const char *src, int len, char *dst, int flags);
+int strip_hidden_attribute (char *src, char *dst);
+char *errorstring (int err);
+int waitline (int sok, char *buf, int bufsize, int);
+unsigned long make_ping_time (void);
+void move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions);
+int mkdir_utf8 (char *dir);
+int token_foreach (char *str, char sep, int (*callback) (char *str, void *ud), void *ud);
+guint32 str_hash (const char *key);
+guint32 str_ihash (const unsigned char *key);
+void safe_strcpy (char *dest, const char *src, int bytes_left);
+
+#endif
diff --git a/src/common/xchat-plugin.h b/src/common/xchat-plugin.h
new file mode 100644
index 00000000..30b19295
--- /dev/null
+++ b/src/common/xchat-plugin.h
@@ -0,0 +1,334 @@
+/* You can distribute this header with your plugins for easy compilation */
+#ifndef XCHAT_PLUGIN_H
+#define XCHAT_PLUGIN_H
+
+#include <time.h>
+
+#define XCHAT_IFACE_MAJOR	1
+#define XCHAT_IFACE_MINOR	9
+#define XCHAT_IFACE_MICRO	11
+#define XCHAT_IFACE_VERSION	((XCHAT_IFACE_MAJOR * 10000) + \
+				 (XCHAT_IFACE_MINOR * 100) + \
+				 (XCHAT_IFACE_MICRO))
+
+#define XCHAT_PRI_HIGHEST	127
+#define XCHAT_PRI_HIGH		64
+#define XCHAT_PRI_NORM		0
+#define XCHAT_PRI_LOW		(-64)
+#define XCHAT_PRI_LOWEST	(-128)
+
+#define XCHAT_FD_READ		1
+#define XCHAT_FD_WRITE		2
+#define XCHAT_FD_EXCEPTION	4
+#define XCHAT_FD_NOTSOCKET	8
+
+#define XCHAT_EAT_NONE		0	/* pass it on through! */
+#define XCHAT_EAT_XCHAT		1	/* don't let xchat see this event */
+#define XCHAT_EAT_PLUGIN	2	/* don't let other plugins see this event */
+#define XCHAT_EAT_ALL		(XCHAT_EAT_XCHAT|XCHAT_EAT_PLUGIN)	/* don't let anything see this event */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _xchat_plugin xchat_plugin;
+typedef struct _xchat_list xchat_list;
+typedef struct _xchat_hook xchat_hook;
+#ifndef PLUGIN_C
+typedef struct _xchat_context xchat_context;
+#endif
+
+#ifndef PLUGIN_C
+struct _xchat_plugin
+{
+	/* these are only used on win32 */
+	xchat_hook *(*xchat_hook_command) (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+	xchat_hook *(*xchat_hook_server) (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+	xchat_hook *(*xchat_hook_print) (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph,
+		   int fd,
+		   int flags,
+		   int (*callback) (int fd, int flags, void *user_data),
+		   void *userdata);
+	void *(*xchat_unhook) (xchat_plugin *ph,
+	      xchat_hook *hook);
+	void (*xchat_print) (xchat_plugin *ph,
+	     const char *text);
+	void (*xchat_printf) (xchat_plugin *ph,
+	      const char *format, ...);
+	void (*xchat_command) (xchat_plugin *ph,
+	       const char *command);
+	void (*xchat_commandf) (xchat_plugin *ph,
+		const char *format, ...);
+	int (*xchat_nickcmp) (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+	int (*xchat_set_context) (xchat_plugin *ph,
+		   xchat_context *ctx);
+	xchat_context *(*xchat_find_context) (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+	xchat_context *(*xchat_get_context) (xchat_plugin *ph);
+	const char *(*xchat_get_info) (xchat_plugin *ph,
+		const char *id);
+	int (*xchat_get_prefs) (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+	xchat_list * (*xchat_list_get) (xchat_plugin *ph,
+		const char *name);
+	void (*xchat_list_free) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * const * (*xchat_list_fields) (xchat_plugin *ph,
+		   const char *name);
+	int (*xchat_list_next) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * (*xchat_list_str) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	int (*xchat_list_int) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	void * (*xchat_plugingui_add) (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+	void (*xchat_plugingui_remove) (xchat_plugin *ph,
+			void *handle);
+	int (*xchat_emit_print) (xchat_plugin *ph,
+			const char *event_name, ...);
+	int (*xchat_read_fd) (xchat_plugin *ph,
+			void *src,
+			char *buf,
+			int *len);
+	time_t (*xchat_list_time) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	char *(*xchat_gettext) (xchat_plugin *ph,
+		const char *msgid);
+	void (*xchat_send_modes) (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+	char *(*xchat_strip) (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+	void (*xchat_free) (xchat_plugin *ph,
+	    void *ptr);
+};
+#endif
+
+
+xchat_hook *
+xchat_hook_command (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+
+xchat_hook *
+xchat_hook_server (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+
+xchat_hook *
+xchat_hook_print (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+
+xchat_hook *
+xchat_hook_timer (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+
+xchat_hook *
+xchat_hook_fd (xchat_plugin *ph,
+		int fd,
+		int flags,
+		int (*callback) (int fd, int flags, void *user_data),
+		void *userdata);
+
+void *
+xchat_unhook (xchat_plugin *ph,
+	      xchat_hook *hook);
+
+void
+xchat_print (xchat_plugin *ph,
+	     const char *text);
+
+void
+xchat_printf (xchat_plugin *ph,
+	      const char *format, ...);
+
+void
+xchat_command (xchat_plugin *ph,
+	       const char *command);
+
+void
+xchat_commandf (xchat_plugin *ph,
+		const char *format, ...);
+
+int
+xchat_nickcmp (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+
+int
+xchat_set_context (xchat_plugin *ph,
+		   xchat_context *ctx);
+
+xchat_context *
+xchat_find_context (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+
+xchat_context *
+xchat_get_context (xchat_plugin *ph);
+
+const char *
+xchat_get_info (xchat_plugin *ph,
+		const char *id);
+
+int
+xchat_get_prefs (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+
+xchat_list *
+xchat_list_get (xchat_plugin *ph,
+		const char *name);
+
+void
+xchat_list_free (xchat_plugin *ph,
+		 xchat_list *xlist);
+
+const char * const *
+xchat_list_fields (xchat_plugin *ph,
+		   const char *name);
+
+int
+xchat_list_next (xchat_plugin *ph,
+		 xchat_list *xlist);
+
+const char *
+xchat_list_str (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+
+int
+xchat_list_int (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+
+time_t
+xchat_list_time (xchat_plugin *ph,
+		 xchat_list *xlist,
+		 const char *name);
+
+void *
+xchat_plugingui_add (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+
+void
+xchat_plugingui_remove (xchat_plugin *ph,
+			void *handle);
+
+int 
+xchat_emit_print (xchat_plugin *ph,
+		  const char *event_name, ...);
+
+char *
+xchat_gettext (xchat_plugin *ph,
+	       const char *msgid);
+
+void
+xchat_send_modes (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+
+char *
+xchat_strip (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+
+void
+xchat_free (xchat_plugin *ph,
+	    void *ptr);
+
+#if !defined(PLUGIN_C) && defined(WIN32)
+#ifndef XCHAT_PLUGIN_HANDLE
+#define XCHAT_PLUGIN_HANDLE (ph)
+#endif
+#define xchat_hook_command ((XCHAT_PLUGIN_HANDLE)->xchat_hook_command)
+#define xchat_hook_server ((XCHAT_PLUGIN_HANDLE)->xchat_hook_server)
+#define xchat_hook_print ((XCHAT_PLUGIN_HANDLE)->xchat_hook_print)
+#define xchat_hook_timer ((XCHAT_PLUGIN_HANDLE)->xchat_hook_timer)
+#define xchat_hook_fd ((XCHAT_PLUGIN_HANDLE)->xchat_hook_fd)
+#define xchat_unhook ((XCHAT_PLUGIN_HANDLE)->xchat_unhook)
+#define xchat_print ((XCHAT_PLUGIN_HANDLE)->xchat_print)
+#define xchat_printf ((XCHAT_PLUGIN_HANDLE)->xchat_printf)
+#define xchat_command ((XCHAT_PLUGIN_HANDLE)->xchat_command)
+#define xchat_commandf ((XCHAT_PLUGIN_HANDLE)->xchat_commandf)
+#define xchat_nickcmp ((XCHAT_PLUGIN_HANDLE)->xchat_nickcmp)
+#define xchat_set_context ((XCHAT_PLUGIN_HANDLE)->xchat_set_context)
+#define xchat_find_context ((XCHAT_PLUGIN_HANDLE)->xchat_find_context)
+#define xchat_get_context ((XCHAT_PLUGIN_HANDLE)->xchat_get_context)
+#define xchat_get_info ((XCHAT_PLUGIN_HANDLE)->xchat_get_info)
+#define xchat_get_prefs ((XCHAT_PLUGIN_HANDLE)->xchat_get_prefs)
+#define xchat_list_get ((XCHAT_PLUGIN_HANDLE)->xchat_list_get)
+#define xchat_list_free ((XCHAT_PLUGIN_HANDLE)->xchat_list_free)
+#define xchat_list_fields ((XCHAT_PLUGIN_HANDLE)->xchat_list_fields)
+#define xchat_list_str ((XCHAT_PLUGIN_HANDLE)->xchat_list_str)
+#define xchat_list_int ((XCHAT_PLUGIN_HANDLE)->xchat_list_int)
+#define xchat_list_time ((XCHAT_PLUGIN_HANDLE)->xchat_list_time)
+#define xchat_list_next ((XCHAT_PLUGIN_HANDLE)->xchat_list_next)
+#define xchat_plugingui_add ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_add)
+#define xchat_plugingui_remove ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_remove)
+#define xchat_emit_print ((XCHAT_PLUGIN_HANDLE)->xchat_emit_print)
+#define xchat_gettext ((XCHAT_PLUGIN_HANDLE)->xchat_gettext)
+#define xchat_send_modes ((XCHAT_PLUGIN_HANDLE)->xchat_send_modes)
+#define xchat_strip ((XCHAT_PLUGIN_HANDLE)->xchat_strip)
+#define xchat_free ((XCHAT_PLUGIN_HANDLE)->xchat_free)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/common/xchat.c b/src/common/xchat.c
new file mode 100644
index 00000000..afac9a0e
--- /dev/null
+++ b/src/common/xchat.c
@@ -0,0 +1,951 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define WANTSOCKET
+#include "inet.h"
+
+#ifndef WIN32
+#include <sys/wait.h>
+#include <signal.h>
+#endif
+
+#include "xchat.h"
+#include "fe.h"
+#include "util.h"
+#include "cfgfiles.h"
+#include "chanopt.h"
+#include "ignore.h"
+#include "xchat-plugin.h"
+#include "plugin.h"
+#include "plugin-timer.h"
+#include "notify.h"
+#include "server.h"
+#include "servlist.h"
+#include "outbound.h"
+#include "text.h"
+#include "url.h"
+#include "xchatc.h"
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#include "ssl.h"
+#endif
+
+#ifdef USE_MSPROXY
+#include "msproxy.h"
+#endif
+
+#ifdef USE_LIBPROXY
+#include <proxy.h>
+#endif
+
+GSList *popup_list = 0;
+GSList *button_list = 0;
+GSList *dlgbutton_list = 0;
+GSList *command_list = 0;
+GSList *ctcp_list = 0;
+GSList *replace_list = 0;
+GSList *sess_list = 0;
+GSList *dcc_list = 0;
+GSList *ignore_list = 0;
+GSList *usermenu_list = 0;
+GSList *urlhandler_list = 0;
+GSList *tabmenu_list = 0;
+
+static int in_xchat_exit = FALSE;
+int xchat_is_quitting = FALSE;
+/* command-line args */
+int arg_dont_autoconnect = FALSE;
+int arg_skip_plugins = FALSE;
+char *arg_url = NULL;
+char *arg_command = NULL;
+gint arg_existing = FALSE;
+
+#ifdef USE_DBUS
+#include "dbus/dbus-client.h"
+#include "dbus/dbus-plugin.h"
+#endif /* USE_DBUS */
+
+struct session *current_tab;
+struct session *current_sess = 0;
+struct xchatprefs prefs;
+
+#ifdef USE_OPENSSL
+SSL_CTX *ctx = NULL;
+#endif
+
+#ifdef USE_LIBPROXY
+pxProxyFactory *libproxy_factory;
+#endif
+
+int
+is_session (session * sess)
+{
+	return g_slist_find (sess_list, sess) ? 1 : 0;
+}
+
+session *
+find_dialog (server *serv, char *nick)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv && sess->type == SESS_DIALOG)
+		{
+			if (!serv->p_cmp (nick, sess->channel))
+				return (sess);
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+session *
+find_channel (server *serv, char *chan)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if ((!serv || serv == sess->server) && sess->type != SESS_DIALOG)
+		{
+			if (!serv->p_cmp (chan, sess->channel))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static void
+lagcheck_update (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	
+	if (!prefs.lagometer)
+		return;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->lag_sent)
+			fe_set_lag (serv, -1);
+
+		list = list->next;
+	}
+}
+
+void
+lag_check (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	unsigned long tim;
+	char tbuf[128];
+	time_t now = time (0);
+	int lag;
+
+	tim = make_ping_time ();
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd)
+		{
+			lag = now - serv->ping_recv;
+			if (prefs.pingtimeout && lag > prefs.pingtimeout && lag > 0)
+			{
+				sprintf (tbuf, "%d", lag);
+				EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
+								 NULL, NULL, 0);
+				if (prefs.autoreconnect)
+					serv->auto_reconnect (serv, FALSE, -1);
+			} else
+			{
+				snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim);
+				serv->p_ping (serv, "", tbuf);
+				serv->lag_sent = tim;
+				fe_set_lag (serv, -1);
+			}
+		}
+		list = list->next;
+	}
+}
+
+static int
+away_check (void)
+{
+	session *sess;
+	GSList *list;
+	int full, sent, loop = 0;
+
+	if (!prefs.away_track || prefs.away_size_max < 1)
+		return 1;
+
+doover:
+	/* request an update of AWAY status of 1 channel every 30 seconds */
+	full = TRUE;
+	sent = 0;	/* number of WHOs (users) requested */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+
+		if (sess->server->connected &&
+			 sess->type == SESS_CHANNEL &&
+			 sess->channel[0] &&
+			 sess->total <= prefs.away_size_max)
+		{
+			if (!sess->done_away_check)
+			{
+				full = FALSE;
+
+				/* if we're under 31 WHOs, send another channels worth */
+				if (sent < 31 && !sess->doing_who)
+				{
+					sess->done_away_check = TRUE;
+					sess->doing_who = TRUE;
+					/* this'll send a WHO #channel */
+					sess->server->p_away_status (sess->server, sess->channel);
+					sent += sess->total;
+				}
+			}
+		}
+
+		list = list->next;
+	}
+
+	/* done them all, reset done_away_check to FALSE and start over */
+	if (full)
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			sess->done_away_check = FALSE;
+			list = list->next;
+		}
+		loop++;
+		if (loop < 2)
+			goto doover;
+	}
+
+	return 1;
+}
+
+static int
+xchat_misc_checks (void)		/* this gets called every 1/2 second */
+{
+	static int count = 0;
+#ifdef USE_MSPROXY
+	static int count2 = 0;
+#endif
+
+	count++;
+
+	lagcheck_update ();			/* every 500ms */
+
+	if (count % 2)
+		dcc_check_timeouts ();	/* every 1 second */
+
+	if (count >= 60)				/* every 30 seconds */
+	{
+		if (prefs.lagometer)
+			lag_check ();
+		count = 0;
+	}
+
+#ifdef USE_MSPROXY	
+	count2++;
+	if (count2 >= 720)			/* 720 every 6 minutes */
+	{
+		msproxy_keepalive ();
+		count2 = 0;
+	}
+#endif
+
+	return 1;
+}
+
+/* executed when the first irc window opens */
+
+static void
+irc_init (session *sess)
+{
+	static int done_init = FALSE;
+	char buf[512];
+
+	if (done_init)
+		return;
+
+	done_init = TRUE;
+
+	plugin_add (sess, NULL, NULL, timer_plugin_init, NULL, NULL, FALSE);
+
+#ifdef USE_PLUGIN
+	if (!arg_skip_plugins)
+		plugin_auto_load (sess);	/* autoload ~/.xchat *.so */
+#endif
+
+#ifdef USE_DBUS
+	plugin_add (sess, NULL, NULL, dbus_plugin_init, NULL, NULL, FALSE);
+#endif
+
+	if (prefs.notify_timeout)
+		notify_tag = fe_timeout_add (prefs.notify_timeout * 1000,
+											  notify_checklist, 0);
+
+	fe_timeout_add (prefs.away_timeout * 1000, away_check, 0);
+	fe_timeout_add (500, xchat_misc_checks, 0);
+
+	if (arg_url != NULL)
+	{
+		snprintf (buf, sizeof (buf), "server %s", arg_url);
+		handle_command (sess, buf, FALSE);
+		g_free (arg_url);	/* from GOption */
+	}
+
+	if (arg_command != NULL)
+	{
+		g_free (arg_command);
+	}
+
+	/* load -e ~/.xchat2/startup.txt */
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), "startup.txt");
+	load_perform_file (sess, buf);
+}
+
+static session *
+session_new (server *serv, char *from, int type, int focus)
+{
+	session *sess;
+
+	sess = malloc (sizeof (struct session));
+	memset (sess, 0, sizeof (struct session));
+
+	sess->server = serv;
+	sess->logfd = -1;
+	sess->scrollfd = -1;
+	sess->type = type;
+
+	sess->alert_beep = SET_DEFAULT;
+	sess->alert_taskbar = SET_DEFAULT;
+	sess->alert_tray = SET_DEFAULT;
+
+	sess->text_hidejoinpart = SET_DEFAULT;
+	sess->text_logging = SET_DEFAULT;
+	sess->text_scrollback = SET_DEFAULT;
+
+	if (from != NULL)
+		safe_strcpy (sess->channel, from, CHANLEN);
+
+	sess_list = g_slist_prepend (sess_list, sess);
+
+	fe_new_window (sess, focus);
+
+	return sess;
+}
+
+session *
+new_ircwindow (server *serv, char *name, int type, int focus)
+{
+	session *sess;
+
+	switch (type)
+	{
+	case SESS_SERVER:
+		serv = server_new ();
+		if (prefs.use_server_tab)
+			sess = session_new (serv, name, SESS_SERVER, focus);
+		else
+			sess = session_new (serv, name, SESS_CHANNEL, focus);
+		serv->server_session = sess;
+		serv->front_session = sess;
+		break;
+	case SESS_DIALOG:
+		sess = session_new (serv, name, type, focus);
+		log_open_or_close (sess);
+		break;
+	default:
+/*	case SESS_CHANNEL:
+	case SESS_NOTICES:
+	case SESS_SNOTICES:*/
+		sess = session_new (serv, name, type, focus);
+		break;
+	}
+
+	irc_init (sess);
+	scrollback_load (sess);
+	chanopt_load (sess);
+	plugin_emit_dummy_print (sess, "Open Context");
+
+	return sess;
+}
+
+static void
+exec_notify_kill (session * sess)
+{
+#ifndef WIN32
+	struct nbexec *re;
+	if (sess->running_exec != NULL)
+	{
+		re = sess->running_exec;
+		sess->running_exec = NULL;
+		kill (re->childpid, SIGKILL);
+		waitpid (re->childpid, NULL, WNOHANG);
+		fe_input_remove (re->iotag);
+		close (re->myfd);
+		if (re->linebuf)
+			free(re->linebuf);
+		free (re);
+	}
+#endif
+}
+
+static void
+send_quit_or_part (session * killsess)
+{
+	int willquit = TRUE;
+	GSList *list;
+	session *sess;
+	server *killserv = killsess->server;
+
+	/* check if this is the last session using this server */
+	list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == killserv && sess != killsess)
+		{
+			willquit = FALSE;
+			list = 0;
+		} else
+			list = list->next;
+	}
+
+	if (xchat_is_quitting)
+		willquit = TRUE;
+
+	if (killserv->connected)
+	{
+		if (willquit)
+		{
+			if (!killserv->sent_quit)
+			{
+				killserv->flush_queue (killserv);
+				server_sendquit (killsess);
+				killserv->sent_quit = TRUE;
+			}
+		} else
+		{
+			if (killsess->type == SESS_CHANNEL && killsess->channel[0] &&
+				 !killserv->sent_quit)
+			{
+				server_sendpart (killserv, killsess->channel, 0);
+			}
+		}
+	}
+}
+
+void
+session_free (session *killsess)
+{
+	server *killserv = killsess->server;
+	session *sess;
+	GSList *list;
+
+	plugin_emit_dummy_print (killsess, "Close Context");
+
+	if (current_tab == killsess)
+		current_tab = NULL;
+
+	if (killserv->server_session == killsess)
+		killserv->server_session = NULL;
+
+	if (killserv->front_session == killsess)
+	{
+		/* front_session is closed, find a valid replacement */
+		killserv->front_session = NULL;
+		list = sess_list;
+		while (list)
+		{
+			sess = (session *) list->data;
+			if (sess != killsess && sess->server == killserv)
+			{
+				killserv->front_session = sess;
+				if (!killserv->server_session)
+					killserv->server_session = sess;
+				break;
+			}
+			list = list->next;
+		}
+	}
+
+	if (!killserv->server_session)
+		killserv->server_session = killserv->front_session;
+
+	sess_list = g_slist_remove (sess_list, killsess);
+
+	if (killsess->type == SESS_CHANNEL)
+		userlist_free (killsess);
+
+	exec_notify_kill (killsess);
+
+	log_close (killsess);
+	scrollback_close (killsess);
+	chanopt_save (killsess);
+
+	send_quit_or_part (killsess);
+
+	history_free (&killsess->history);
+	if (killsess->topic)
+		free (killsess->topic);
+	if (killsess->current_modes)
+		free (killsess->current_modes);
+
+	fe_session_callback (killsess);
+
+	if (current_sess == killsess)
+	{
+		current_sess = NULL;
+		if (sess_list)
+			current_sess = sess_list->data;
+	}
+
+	free (killsess);
+
+	if (!sess_list && !in_xchat_exit)
+		xchat_exit ();						/* sess_list is empty, quit! */
+
+	list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == killserv)
+			return;					  /* this server is still being used! */
+		list = list->next;
+	}
+
+	server_free (killserv);
+}
+
+static void
+free_sessions (void)
+{
+	GSList *list = sess_list;
+
+	while (list)
+	{
+		fe_close_window (list->data);
+		list = sess_list;
+	}
+}
+
+
+static char defaultconf_ctcp[] =
+	"NAME TIME\n"				"CMD nctcp %s TIME %t\n\n"\
+	"NAME PING\n"				"CMD nctcp %s PING %d\n\n";
+
+static char defaultconf_replace[] =
+	"NAME teh\n"				"CMD the\n\n";
+/*	"NAME r\n"					"CMD are\n\n"\
+	"NAME u\n"					"CMD you\n\n"*/
+
+static char defaultconf_commands[] =
+	"NAME ACTION\n"		"CMD me &2\n\n"\
+	"NAME AME\n"			"CMD allchan me &2\n\n"\
+	"NAME ANICK\n"			"CMD allserv nick &2\n\n"\
+	"NAME AMSG\n"			"CMD allchan say &2\n\n"\
+	"NAME BANLIST\n"		"CMD quote MODE %c +b\n\n"\
+	"NAME CHAT\n"			"CMD dcc chat %2\n\n"\
+	"NAME DIALOG\n"		"CMD query %2\n\n"\
+	"NAME DMSG\n"			"CMD msg =%2 &3\n\n"\
+	"NAME EXIT\n"			"CMD quit\n\n"\
+	"NAME GREP\n"			"CMD lastlog -r &2\n\n"\
+	"NAME J\n"				"CMD join &2\n\n"\
+	"NAME KILL\n"			"CMD quote KILL %2 :&3\n\n"\
+	"NAME LEAVE\n"			"CMD part &2\n\n"\
+	"NAME M\n"				"CMD msg &2\n\n"\
+	"NAME ONOTICE\n"		"CMD notice @%c &2\n\n"\
+	"NAME RAW\n"			"CMD quote &2\n\n"\
+	"NAME SERVHELP\n"		"CMD quote HELP\n\n"\
+	"NAME SPING\n"			"CMD ping\n\n"\
+	"NAME SQUERY\n"		"CMD quote SQUERY %2 :&3\n\n"\
+	"NAME SSLSERVER\n"	"CMD server -ssl &2\n\n"\
+	"NAME SV\n"				"CMD echo xchat %v %m\n\n"\
+	"NAME UMODE\n"			"CMD mode %n &2\n\n"\
+	"NAME UPTIME\n"		"CMD quote STATS u\n\n"\
+	"NAME VER\n"			"CMD ctcp %2 VERSION\n\n"\
+	"NAME VERSION\n"		"CMD ctcp %2 VERSION\n\n"\
+	"NAME WALLOPS\n"		"CMD quote WALLOPS :&2\n\n"\
+	"NAME WII\n"			"CMD quote WHOIS %2 %2\n\n";
+
+static char defaultconf_urlhandlers[] =
+		"NAME Open Link in Opera\n"		"CMD !opera -remote 'openURL(%s)'\n\n";
+
+#ifdef USE_SIGACTION
+/* Close and open log files on SIGUSR1. Usefull for log rotating */
+
+static void 
+sigusr1_handler (int signal, siginfo_t *si, void *un)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		log_open_or_close (sess);
+		list = list->next;
+	}
+}
+
+/* Execute /SIGUSR2 when SIGUSR2 received */
+
+static void
+sigusr2_handler (int signal, siginfo_t *si, void *un)
+{
+	session *sess = current_sess;
+
+	if (sess)
+		handle_command (sess, "SIGUSR2", FALSE);
+}
+#endif
+
+static gint
+xchat_auto_connect (gpointer userdata)
+{
+	servlist_auto_connect (NULL);
+	return 0;
+}
+
+static void
+xchat_init (void)
+{
+	char buf[3068];
+	const char *cs = NULL;
+
+#ifdef WIN32
+	WSADATA wsadata;
+
+#ifdef USE_IPV6
+	if (WSAStartup(0x0202, &wsadata) != 0)
+	{
+		MessageBox (NULL, "Cannot find winsock 2.2+", "Error", MB_OK);
+		exit (0);
+	}
+#else
+	WSAStartup(0x0101, &wsadata);
+#endif	/* !USE_IPV6 */
+#endif	/* !WIN32 */
+
+#ifdef USE_SIGACTION
+	struct sigaction act;
+
+	/* ignore SIGPIPE's */
+	act.sa_handler = SIG_IGN;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGPIPE, &act, NULL);
+
+	/* Deal with SIGUSR1's & SIGUSR2's */
+	act.sa_sigaction = sigusr1_handler;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGUSR1, &act, NULL);
+
+	act.sa_sigaction = sigusr2_handler;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGUSR2, &act, NULL);
+#else
+#ifndef WIN32
+	/* good enough for these old systems */
+	signal (SIGPIPE, SIG_IGN);
+#endif
+#endif
+
+	if (g_get_charset (&cs))
+		prefs.utf8_locale = TRUE;
+
+	load_text_events ();
+	sound_load ();
+	notify_load ();
+	ignore_load ();
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s~%s~\n"				"CMD query %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD send %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD whois %%s %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD notify -n ASK %%s\n\n"\
+
+		"NAME SUB\n"					"CMD %s\n\n"\
+			"NAME %s\n"					"CMD op %%a\n\n"\
+			"NAME %s\n"					"CMD deop %%a\n\n"\
+			"NAME SEP\n"				"CMD \n\n"\
+			"NAME %s\n"					"CMD voice %%a\n\n"\
+			"NAME %s\n"					"CMD devoice %%a\n"\
+			"NAME SEP\n"				"CMD \n\n"\
+			"NAME SUB\n"				"CMD %s\n\n"\
+				"NAME %s\n"				"CMD kick %%s\n\n"\
+				"NAME %s\n"				"CMD ban %%s\n\n"\
+				"NAME SEP\n"			"CMD \n\n"\
+				"NAME %s *!*@*.host\n""CMD ban %%s 0\n\n"\
+				"NAME %s *!*@domain\n""CMD ban %%s 1\n\n"\
+				"NAME %s *!*user@*.host\n""CMD ban %%s 2\n\n"\
+				"NAME %s *!*user@domain\n""CMD ban %%s 3\n\n"\
+				"NAME SEP\n"			"CMD \n\n"\
+				"NAME %s *!*@*.host\n""CMD kickban %%s 0\n\n"\
+				"NAME %s *!*@domain\n""CMD kickban %%s 1\n\n"\
+				"NAME %s *!*user@*.host\n""CMD kickban %%s 2\n\n"\
+				"NAME %s *!*user@domain\n""CMD kickban %%s 3\n\n"\
+			"NAME ENDSUB\n"			"CMD \n\n"\
+		"NAME ENDSUB\n"				"CMD \n\n",
+
+		_("_Open Dialog Window"), "xchat-dialog",
+		_("_Send a File"), "gtk-floppy",
+		_("_User Info (WhoIs)"), "gtk-info",
+		_("_Add to Friends List"), "gtk-add",
+		_("O_perator Actions"),
+
+		_("Give Ops"),
+		_("Take Ops"),
+		_("Give Voice"),
+		_("Take Voice"),
+
+		_("Kick/Ban"),
+		_("Kick"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("KickBan"),
+		_("KickBan"),
+		_("KickBan"),
+		_("KickBan"));
+
+	list_loadconf ("popup.conf", &popup_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"				"CMD part\n\n"
+		"NAME %s\n"				"CMD getstr # join \"%s\"\n\n"
+		"NAME %s\n"				"CMD quote LINKS\n\n"
+		"NAME %s\n"				"CMD ping\n\n"
+		"NAME TOGGLE %s\n"	"CMD irc_hide_version\n\n",
+				_("Leave Channel"),
+				_("Join Channel..."),
+				_("Enter Channel to Join:"),
+				_("Server Links"),
+				_("Ping Server"),
+				_("Hide Version"));
+	list_loadconf ("usermenu.conf", &usermenu_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"		"CMD op %%a\n\n"
+		"NAME %s\n"		"CMD deop %%a\n\n"
+		"NAME %s\n"		"CMD ban %%s\n\n"
+		"NAME %s\n"		"CMD getstr \"%s\" \"kick %%s\" \"%s\"\n\n"
+		"NAME %s\n"		"CMD send %%s\n\n"
+		"NAME %s\n"		"CMD query %%s\n\n",
+				_("Op"),
+				_("DeOp"),
+				_("Ban"),
+				_("Kick"),
+				_("bye"),
+				_("Enter reason to kick %s:"),
+				_("Sendfile"),
+				_("Dialog"));
+	list_loadconf ("buttons.conf", &button_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"				"CMD whois %%s %%s\n\n"
+		"NAME %s\n"				"CMD send %%s\n\n"
+		"NAME %s\n"				"CMD dcc chat %%s\n\n"
+		"NAME %s\n"				"CMD clear\n\n"
+		"NAME %s\n"				"CMD ping %%s\n\n",
+				_("WhoIs"),
+				_("Send"),
+				_("Chat"),
+				_("Clear"),
+				_("Ping"));
+	list_loadconf ("dlgbuttons.conf", &dlgbutton_list, buf);
+
+	list_loadconf ("tabmenu.conf", &tabmenu_list, NULL);
+	list_loadconf ("ctcpreply.conf", &ctcp_list, defaultconf_ctcp);
+	list_loadconf ("commands.conf", &command_list, defaultconf_commands);
+	list_loadconf ("replace.conf", &replace_list, defaultconf_replace);
+	list_loadconf ("urlhandlers.conf", &urlhandler_list,
+						defaultconf_urlhandlers);
+
+	servlist_init ();							/* load server list */
+
+	/* if we got a URL, don't open the server list GUI */
+	if (!prefs.slist_skip && !arg_url)
+		fe_serverlist_open (NULL);
+
+	/* turned OFF via -a arg */
+	if (!arg_dont_autoconnect)
+	{
+		/* do any auto connects */
+		if (!servlist_have_auto ())	/* if no new windows open .. */
+		{
+			/* and no serverlist gui ... */
+			if (prefs.slist_skip || arg_url)
+				/* we'll have to open one. */
+				new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+		} else
+		{
+			fe_idle_add (xchat_auto_connect, NULL);
+		}
+	} else
+	{
+		if (prefs.slist_skip || arg_url)
+			new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	}
+}
+
+void
+xchat_exit (void)
+{
+	xchat_is_quitting = TRUE;
+	in_xchat_exit = TRUE;
+	plugin_kill_all ();
+	fe_cleanup ();
+	if (prefs.autosave)
+	{
+		save_config ();
+		if (prefs.save_pevents)
+			pevent_save (NULL);
+	}
+	if (prefs.autosave_url)
+		url_autosave ();
+	sound_save ();
+	notify_save ();
+	ignore_save ();
+	free_sessions ();
+	chanopt_save_all ();
+	servlist_cleanup ();
+	fe_exit ();
+}
+
+#ifndef WIN32
+
+static int
+child_handler (gpointer userdata)
+{
+	int pid = GPOINTER_TO_INT (userdata);
+
+	if (waitpid (pid, 0, WNOHANG) == pid)
+		return 0;					  /* remove timeout handler */
+	return 1;						  /* keep the timeout handler */
+}
+
+#endif
+
+void
+xchat_exec (const char *cmd)
+{
+#ifdef WIN32
+	util_exec (cmd);
+#else
+	int pid = util_exec (cmd);
+	if (pid != -1)
+	/* zombie avoiding system. Don't ask! it has to be like this to work
+      with zvt (which overrides the default handler) */
+		fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid));
+#endif
+}
+
+void
+xchat_execv (char * const argv[])
+{
+#ifdef WIN32
+	util_execv (argv);
+#else
+	int pid = util_execv (argv);
+	if (pid != -1)
+	/* zombie avoiding system. Don't ask! it has to be like this to work
+      with zvt (which overrides the default handler) */
+		fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid));
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+	int ret;
+	
+	srand (time (0));	/* CL: do this only once! */
+
+#ifdef SOCKS
+	SOCKSinit (argv[0]);
+#endif
+
+	ret = fe_args (argc, argv);
+	if (ret != -1)
+		return ret;
+	
+#ifdef USE_DBUS
+	xchat_remote ();
+#endif
+
+	load_config ();
+
+#ifdef USE_LIBPROXY
+	libproxy_factory = px_proxy_factory_new();
+#endif
+
+	fe_init ();
+
+	xchat_init ();
+
+	fe_main ();
+
+#ifdef USE_LIBPROXY
+	px_proxy_factory_free(libproxy_factory);
+#endif
+
+#ifdef USE_OPENSSL
+	if (ctx)
+		_SSL_context_free (ctx);
+#endif
+
+#ifdef USE_DEBUG
+	xchat_mem_list ();
+#endif
+
+#ifdef WIN32
+	WSACleanup ();
+#endif
+
+	return 0;
+}
diff --git a/src/common/xchat.h b/src/common/xchat.h
new file mode 100644
index 00000000..db7a6c4b
--- /dev/null
+++ b/src/common/xchat.h
@@ -0,0 +1,575 @@
+#include "../../config.h"
+
+#include <glib/gslist.h>
+#include <glib/glist.h>
+#include <glib/gutils.h>
+#include <glib/giochannel.h>
+#include <glib/gstrfuncs.h>
+#include <time.h>			/* need time_t */
+
+#ifndef XCHAT_H
+#define XCHAT_H
+
+#include "history.h"
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+#ifndef HAVE_VSNPRINTF
+#define vsnprintf g_vsnprintf
+#endif
+
+#ifdef USE_DEBUG
+#define malloc(n) xchat_malloc(n, __FILE__, __LINE__)
+#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__)
+#define free(n) xchat_dfree(n, __FILE__, __LINE__)
+#define strdup(n) xchat_strdup(n, __FILE__, __LINE__)
+void *xchat_malloc (int size, char *file, int line);
+void *xchat_strdup (char *str, char *file, int line);
+void xchat_dfree (void *buf, char *file, int line);
+void *xchat_realloc (char *old, int len, char *file, int line);
+#endif
+
+#ifdef SOCKS
+#ifdef __sgi
+#include <sys/time.h>
+#define INCLUDE_PROTOTYPES 1
+#endif
+#include <socks.h>
+#endif
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#endif
+
+#ifdef __EMX__						  /* for o/s 2 */
+#define OFLAGS O_BINARY
+#define strcasecmp stricmp
+#define strncasecmp strnicmp
+#define PATH_MAX MAXPATHLEN
+#define FILEPATH_LEN_MAX MAXPATHLEN
+#endif
+
+/* force a 32bit CMP.L */
+#define CMPL(a, c0, c1, c2, c3) (a == (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)))
+#define WORDL(c0, c1, c2, c3) (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24))
+#define WORDW(c0, c1) (guint16)(c0 | (c1 << 8))
+
+#ifdef WIN32						/* for win32 */
+#define OFLAGS O_BINARY
+#define sleep(t) _sleep(t*1000)
+#include <direct.h>
+#define	F_OK	0
+#define	X_OK	1
+#define	W_OK	2
+#define	R_OK	4
+#ifndef S_ISDIR
+#define	S_ISDIR(m)	((m) & _S_IFDIR)
+#endif
+#define NETWORK_PRIVATE
+#else									/* for unix */
+#define OFLAGS 0
+#endif
+
+#define FONTNAMELEN	127
+#define PATHLEN		255
+#define DOMAINLEN	100
+#define NICKLEN		64				/* including the NULL, so 63 really */
+#define CHANLEN		300
+#define PDIWORDS		32
+#define USERNAMELEN 10
+#define HIDDEN_CHAR	8			/* invisible character for xtext */
+
+#if defined(ENABLE_NLS) && !defined(_)
+#  include <libintl.h>
+#  define _(x) gettext(x)
+#  ifdef gettext_noop
+#    define N_(String) gettext_noop (String)
+#  else
+#    define N_(String) (String)
+#  endif
+#endif
+#if !defined(_)
+#  define N_(String) (String)
+#  define _(x) (x)
+#endif
+
+struct nbexec
+{
+	int myfd;
+	int childpid;
+	int tochannel;					  /* making this int keeps the struct 4-byte aligned */
+	int iotag;
+	char *linebuf;
+	int buffill;
+	struct session *sess;
+};
+
+struct xchatprefs
+{
+	char nick1[NICKLEN];
+	char nick2[NICKLEN];
+	char nick3[NICKLEN];
+	char realname[127];
+	char username[127];
+	char nick_suffix[4];			/* Only ever holds a one-character string. */
+	char awayreason[256];
+	char quitreason[256];
+	char partreason[256];
+	char font_normal[FONTNAMELEN + 1];
+	char doubleclickuser[256];
+	char sounddir[PATHLEN + 1];
+	char soundcmd[PATHLEN + 1];
+	char background[PATHLEN + 1];
+	char dccdir[PATHLEN + 1];
+	char dcc_completed_dir[PATHLEN + 1];
+	char irc_extra_hilight[300];
+	char irc_no_hilight[300];
+	char irc_nick_hilight[300];
+	char dnsprogram[72];
+	char hostname[127];
+	char cmdchar[4];
+	char logmask[256];
+	char stamp_format[64];
+	char timestamp_log_format[64];
+	char irc_id_ytext[64];
+	char irc_id_ntext[64];
+
+	char proxy_host[64];
+	int proxy_port;
+	int proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */
+	int proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */
+	unsigned int proxy_auth;
+	char proxy_user[32];
+	char proxy_pass[32];
+
+	int first_dcc_send_port;
+	int last_dcc_send_port;
+
+	int tint_red;
+	int tint_green;
+	int tint_blue;
+
+	int away_timeout;
+	int away_size_max;
+
+	int gui_pane_left_size;
+	int gui_pane_right_size;
+
+	int gui_ulist_pos;
+	int tab_pos;
+
+	int _tabs_position;
+	int tab_layout;
+	int max_auto_indent;
+	int dcc_blocksize;
+	int max_lines;
+	int notify_timeout;
+	int dcctimeout;
+	int dccstalltimeout;
+	int dcc_global_max_get_cps;
+	int dcc_global_max_send_cps;
+	int dcc_max_get_cps;
+	int dcc_max_send_cps;
+	int mainwindow_left;
+	int mainwindow_top;
+	int mainwindow_width;
+	int mainwindow_height;
+	int completion_sort;
+	int gui_win_state;
+	int gui_url_mod;
+	int gui_usermenu;
+	int gui_join_dialog;
+	int gui_quit_dialog;
+	int dialog_left;
+	int dialog_top;
+	int dialog_width;
+	int dialog_height;
+	int dccpermissions;
+	int recon_delay;
+	int bantype;
+	int userlist_sort;
+	guint32 local_ip;
+	guint32 dcc_ip;
+	char dcc_ip_str[DOMAINLEN + 1];
+
+	unsigned int tab_small;
+	unsigned int tab_sort;
+	unsigned int mainwindow_save;
+	unsigned int perc_color;
+	unsigned int perc_ascii;
+	unsigned int autosave;
+	unsigned int autodialog;
+	unsigned int autosave_url;
+	unsigned int autoreconnect;
+	unsigned int autoreconnectonfail;
+	unsigned int invisible;
+	unsigned int servernotice;
+	unsigned int wallops;
+	unsigned int skipmotd;
+	unsigned int autorejoin;
+	unsigned int colorednicks;
+	unsigned int chanmodebuttons;
+	unsigned int userlistbuttons;
+	unsigned int showhostname_in_userlist;
+	unsigned int nickcompletion;
+	unsigned int completion_amount;
+	unsigned int tabchannels;
+	unsigned int paned_userlist;
+	unsigned int autodccchat;
+	unsigned int autodccsend;
+	unsigned int autoresume;
+	unsigned int autoopendccsendwindow;
+	unsigned int autoopendccrecvwindow;
+	unsigned int autoopendccchatwindow;
+	unsigned int transparent;
+	unsigned int stripcolor;
+	unsigned int timestamp;
+	unsigned int fastdccsend;
+	unsigned int dcc_send_fillspaces;
+	unsigned int dcc_remove;
+	unsigned int slist_fav;
+	unsigned int slist_skip;
+	unsigned int slist_select;
+	unsigned int filterbeep;
+
+	unsigned int input_balloon_chans;
+	unsigned int input_balloon_hilight;
+	unsigned int input_balloon_priv;
+	unsigned int input_balloon_time;
+
+	unsigned int input_beep_chans;
+	unsigned int input_beep_hilight;
+	unsigned int input_beep_priv;
+
+	unsigned int input_flash_chans;
+	unsigned int input_flash_hilight;
+	unsigned int input_flash_priv;
+
+	unsigned int input_tray_chans;
+	unsigned int input_tray_hilight;
+	unsigned int input_tray_priv;
+
+	unsigned int truncchans;
+	unsigned int privmsgtab;
+	unsigned int irc_join_delay;
+	unsigned int logging;
+	unsigned int timestamp_logs;
+	unsigned int newtabstofront;
+	unsigned int dccwithnick;
+	unsigned int hidever;
+	unsigned int ip_from_server;
+	unsigned int raw_modes;
+	unsigned int show_away_once;
+	unsigned int show_away_message;
+	unsigned int auto_unmark_away;
+	unsigned int away_track;
+	unsigned int userhost;
+	unsigned int irc_whois_front;
+	unsigned int use_server_tab;
+	unsigned int notices_tabs;
+	unsigned int style_namelistgad;
+	unsigned int style_inputbox;
+	unsigned int windows_as_tabs;
+	unsigned int indent_nicks;
+	unsigned int text_replay;
+	unsigned int show_marker;
+	unsigned int show_separator;
+	unsigned int thin_separator;
+	unsigned int auto_indent;
+	unsigned int wordwrap;
+	unsigned int gui_input_spell;
+	unsigned int gui_tray;
+	unsigned int gui_tray_flags;
+	unsigned int gui_tweaks;
+	unsigned int _gui_ulist_left;
+	unsigned int throttle;
+	unsigned int topicbar;
+	unsigned int hideuserlist;
+	unsigned int hidemenu;
+	unsigned int perlwarnings;
+	unsigned int lagometer;
+	unsigned int throttlemeter;
+	unsigned int pingtimeout;
+	unsigned int whois_on_notifyonline;
+	unsigned int wait_on_exit;
+	unsigned int confmode;
+	unsigned int utf8_locale;
+	unsigned int identd;
+
+	unsigned int ctcp_number_limit;	/*flood */
+	unsigned int ctcp_time_limit;	/*seconds of floods */
+
+	unsigned int msg_number_limit;	/*same deal */
+	unsigned int msg_time_limit;
+
+	/* Tells us if we need to save, only when they've been edited.
+		This is so that we continue using internal defaults (which can
+		change in the next release) until the user edits them. */
+	unsigned int save_pevents:1;
+};
+
+/* Session types */
+#define SESS_SERVER	1
+#define SESS_CHANNEL	2
+#define SESS_DIALOG	3
+#define SESS_NOTICES	4
+#define SESS_SNOTICES	5
+
+/* Per-Channel Settings */
+#define SET_OFF 0
+#define SET_ON 1
+#define SET_DEFAULT 2 /* use global setting */
+
+typedef struct session
+{
+	/* Per-Channel Alerts */
+	/* use a byte, because we need a pointer to each element */
+	guint8 alert_beep;
+	guint8 alert_taskbar;
+	guint8 alert_tray;
+
+	/* Per-Channel Settings */
+	guint8 text_hidejoinpart;
+	guint8 text_logging;
+	guint8 text_scrollback;
+
+	struct server *server;
+	void *usertree_alpha;			/* pure alphabetical tree */
+	void *usertree;					/* ordered with Ops first */
+	struct User *me;					/* points to myself in the usertree */
+	char channel[CHANLEN];
+	char waitchannel[CHANLEN];		  /* waiting to join channel (/join sent) */
+	char willjoinchannel[CHANLEN];	  /* will issue /join for this channel */
+	char channelkey[64];			  /* XXX correct max length? */
+	int limit;						  /* channel user limit */
+	int logfd;
+	int scrollfd;							/* scrollback filedes */
+	int scrollwritten;					/* number of lines written */
+
+	char lastnick[NICKLEN];			  /* last nick you /msg'ed */
+
+	struct history history;
+
+	int ops;								/* num. of ops in channel */
+	int hops;						  /* num. of half-oped users */
+	int voices;							/* num. of voiced people */
+	int total;							/* num. of users in channel */
+
+	char *quitreason;
+	char *topic;
+	char *current_modes;					/* free() me */
+
+	int mode_timeout_tag;
+
+	struct session *lastlog_sess;
+	struct nbexec *running_exec;
+
+	struct session_gui *gui;		/* initialized by fe_new_window */
+	struct restore_gui *res;
+
+	int type;					/* SESS_* */
+
+	int new_data:1;			/* new data avail? (purple tab) */
+	int nick_said:1;		/* your nick mentioned? (blue tab) */
+	int msg_said:1;			/* new msg available? (red tab) */
+
+	int ignore_date:1;
+	int ignore_mode:1;
+	int ignore_names:1;
+	int end_of_names:1;
+	int doing_who:1;		/* /who sent on this channel */
+	int done_away_check:1;	/* done checking for away status changes */
+	unsigned int lastlog_regexp:1;	/* this is a lastlog and using regexp */
+} session;
+
+struct msproxy_state_t
+{
+	gint32				clientid;
+	gint32				serverid;
+	unsigned char		seq_recv;		/* seq number of last packet recv.	*/
+	unsigned char		seq_sent;		/* seq number of last packet sent.	*/
+};
+
+typedef struct server
+{
+	/*  server control operations (in server*.c) */
+	void (*connect)(struct server *, char *hostname, int port, int no_login);
+	void (*disconnect)(struct session *, int sendquit, int err);
+	int  (*cleanup)(struct server *);
+	void (*flush_queue)(struct server *);
+	void (*auto_reconnect)(struct server *, int send_quit, int err);
+	/* irc protocol functions (in proto*.c) */
+	void (*p_inline)(struct server *, char *buf, int len);
+	void (*p_invite)(struct server *, char *channel, char *nick);
+	void (*p_cycle)(struct server *, char *channel, char *key);
+	void (*p_ctcp)(struct server *, char *to, char *msg);
+	void (*p_nctcp)(struct server *, char *to, char *msg);
+	void (*p_quit)(struct server *, char *reason);
+	void (*p_kick)(struct server *, char *channel, char *nick, char *reason);
+	void (*p_part)(struct server *, char *channel, char *reason);
+	void (*p_ns_identify)(struct server *, char *pass);
+	void (*p_ns_ghost)(struct server *, char *usname, char *pass);
+	void (*p_join)(struct server *, char *channel, char *key);
+	void (*p_join_list)(struct server *, GSList *channels, GSList *keys);
+	void (*p_login)(struct server *, char *user, char *realname);
+	void (*p_join_info)(struct server *, char *channel);
+	void (*p_mode)(struct server *, char *target, char *mode);
+	void (*p_user_list)(struct server *, char *channel);
+	void (*p_away_status)(struct server *, char *channel);
+	void (*p_whois)(struct server *, char *nicks);
+	void (*p_get_ip)(struct server *, char *nick);
+	void (*p_get_ip_uh)(struct server *, char *nick);
+	void (*p_set_back)(struct server *);
+	void (*p_set_away)(struct server *, char *reason);
+	void (*p_message)(struct server *, char *channel, char *text);
+	void (*p_action)(struct server *, char *channel, char *act);
+	void (*p_notice)(struct server *, char *channel, char *text);
+	void (*p_topic)(struct server *, char *channel, char *topic);
+	void (*p_list_channels)(struct server *, char *arg, int min_users);
+	void (*p_change_nick)(struct server *, char *new_nick);
+	void (*p_names)(struct server *, char *channel);
+	void (*p_ping)(struct server *, char *to, char *timestring);
+/*	void (*p_set_away)(struct server *);*/
+	int (*p_raw)(struct server *, char *raw);
+	int (*p_cmp)(const char *s1, const char *s2);
+
+	int port;
+	int sok;					/* is equal to sok4 or sok6 (the one we are using) */
+	int sok4;					/* tcp4 socket */
+	int sok6;					/* tcp6 socket */
+	int proxy_type;
+	int proxy_sok;				/* Additional information for MS Proxy beast */
+	int proxy_sok4;
+	int proxy_sok6;
+	struct msproxy_state_t msp_state;
+	int id;					/* unique ID number (for plugin API) */
+#ifdef USE_OPENSSL
+	SSL *ssl;
+	int ssl_do_connect_tag;
+#else
+	void *ssl;
+#endif
+	int childread;
+	int childwrite;
+	int childpid;
+	int iotag;
+	int recondelay_tag;				/* reconnect delay timeout */
+	int joindelay_tag;				/* waiting before we send JOIN */
+	char hostname[128];				/* real ip number */
+	char servername[128];			/* what the server says is its name */
+	char password[86];
+	char nick[NICKLEN];
+	char linebuf[2048];				/* RFC says 512 chars including \r\n */
+	char *last_away_reason;
+	int pos;								/* current position in linebuf */
+	int nickcount;
+	int nickservtype;					/* 0=/MSG nickserv 1=/NICKSERV 2=/NS */
+
+	char *chantypes;					/* for 005 numeric - free me */
+	char *chanmodes;					/* for 005 numeric - free me */
+	char *nick_prefixes;				/* e.g. "*@%+" */
+	char *nick_modes;					/* e.g. "aohv" */
+	char *bad_nick_prefixes;		/* for ircd that doesn't give the modes */
+	int modes_per_line;				/* 6 on undernet, 4 on efnet etc... */
+
+	void *network;						/* points to entry in servlist.c or NULL! */
+
+	GSList *outbound_queue;
+	time_t next_send;						/* cptr->since in ircu */
+	time_t prev_now;					/* previous now-time */
+	int sendq_len;						/* queue size */
+	int lag;								/* milliseconds */
+
+	struct session *front_session;	/* front-most window/tab */
+	struct session *server_session;	/* server window/tab */
+
+	struct server_gui *gui;		  /* initialized by fe_new_server */
+
+	unsigned int ctcp_counter;	  /*flood */
+	time_t ctcp_last_time;
+
+	unsigned int msg_counter;	  /*counts the msg tab opened in a certain time */
+	time_t msg_last_time;
+
+	/*time_t connect_time;*/				/* when did it connect? */
+	time_t lag_sent;
+	time_t ping_recv;					/* when we last got a ping reply */
+	time_t away_time;					/* when we were marked away */
+
+	char *encoding;					/* NULL for system */
+	char *autojoin;			/* list of channels & keys to join */
+
+	unsigned int motd_skipped:1;
+	unsigned int connected:1;
+	unsigned int connecting:1;
+	unsigned int no_login:1;
+	unsigned int skip_next_userhost:1;/* used for "get my ip from server" */
+	unsigned int skip_next_whois:1;	/* hide whois output */
+	unsigned int inside_whois:1;
+	unsigned int doing_dns:1;			/* /dns has been done */
+	unsigned int end_of_motd:1;		/* end of motd reached (logged in) */
+	unsigned int sent_quit:1;			/* sent a QUIT already? */
+	unsigned int use_listargs:1;		/* undernet and dalnet need /list >0,<10000 */
+	unsigned int is_away:1;
+	unsigned int reconnect_away:1;	/* whether to reconnect in is_away state */
+	unsigned int dont_use_proxy:1;	/* to proxy or not to proxy */
+	unsigned int supports_watch:1;	/* supports the WATCH command */
+	unsigned int bad_prefix:1;			/* gave us a bad PREFIX= 005 number */
+	unsigned int have_namesx:1;		/* 005 tokens NAMESX and UHNAMES */
+	unsigned int have_uhnames:1;
+	unsigned int have_whox:1;		/* have undernet's WHOX features */
+	unsigned int have_capab:1;		/* supports CAPAB (005 tells us) */
+	unsigned int have_idmsg:1;		/* freenode's IDENTIFY-MSG */
+	unsigned int have_except:1;	/* ban exemptions +e */
+	unsigned int using_cp1255:1;	/* encoding is CP1255/WINDOWS-1255? */
+	unsigned int using_irc:1;		/* encoding is "IRC" (CP1252/UTF-8 hybrid)? */
+	unsigned int use_who:1;			/* whether to use WHO command to get dcc_ip */
+#ifdef USE_OPENSSL
+	unsigned int use_ssl:1;				  /* is server SSL capable? */
+	unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */
+#endif
+} server;
+
+typedef int (*cmd_callback) (struct session * sess, char *tbuf, char *word[],
+									  char *word_eol[]);
+
+struct commands
+{
+	char *name;
+	cmd_callback callback;
+	char needserver;
+	char needchannel;
+	gint16 handle_quotes;
+	char *help;
+};
+
+struct away_msg
+{
+	struct server *server;
+	char nick[NICKLEN];
+	char *message;
+};
+
+/* not just for popups, but used for usercommands, ctcp replies,
+   userlist buttons etc */
+
+struct popup
+{
+	char *cmd;
+	char *name;
+};
+
+/* CL: get a random int in the range [0..n-1]. DON'T use rand() % n, it gives terrible results. */
+#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n)))
+
+#if defined(WIN32) && GLIB_CHECK_VERSION(2,4,0)
+#define xchat_filename_from_utf8 g_locale_from_utf8
+#define xchat_filename_to_utf8 g_locale_to_utf8
+#else
+#define xchat_filename_from_utf8 g_filename_from_utf8
+#define xchat_filename_to_utf8 g_filename_to_utf8
+#endif
+
+#endif
diff --git a/src/common/xchatc.h b/src/common/xchatc.h
new file mode 100644
index 00000000..c22e17dd
--- /dev/null
+++ b/src/common/xchatc.h
@@ -0,0 +1,39 @@
+#ifndef XCHAT_C_H
+#define XCHAT_C_H
+
+extern struct xchatprefs prefs;
+
+extern int xchat_is_quitting;
+extern gint arg_skip_plugins;	/* command-line args */
+extern gint arg_dont_autoconnect;
+extern char *arg_url;
+extern char *arg_command;
+extern gint arg_existing;
+
+extern session *current_sess;
+extern session *current_tab;
+
+extern GSList *popup_list;
+extern GSList *button_list;
+extern GSList *dlgbutton_list;
+extern GSList *command_list;
+extern GSList *ctcp_list;
+extern GSList *replace_list;
+extern GSList *sess_list;
+extern GSList *dcc_list;
+extern GSList *ignore_list;
+extern GSList *usermenu_list;
+extern GSList *urlhandler_list;
+extern GSList *tabmenu_list;
+
+session * find_channel (server *serv, char *chan);
+session * find_dialog (server *serv, char *nick);
+session * new_ircwindow (server *serv, char *name, int type, int focus);
+int is_session (session * sess);
+void session_free (session *killsess);
+void lag_check (void);
+void xchat_exit (void);
+void xchat_exec (const char *cmd);
+void xchat_execv (char * const argv[]);
+
+#endif
diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am
new file mode 100644
index 00000000..7db38096
--- /dev/null
+++ b/src/fe-gtk/Makefile.am
@@ -0,0 +1,32 @@
+localedir = $(datadir)/locale
+
+bin_PROGRAMS = xchat
+
+INCLUDES = $(GUI_CFLAGS) -DG_DISABLE_CAST_CHECKS -DLOCALEDIR=\"$(localedir)\"
+
+xchat_LDADD = ../common/libxchatcommon.a $(GUI_LIBS)
+
+EXTRA_DIST = \
+	about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \
+	chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \
+	maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \
+	plugin-tray.h plugingui.c plugingui.h rawlog.h search.h sexy-spell-entry.h \
+   textgui.h urlgrab.h userlistgui.h xtext.h
+
+if USE_MMX
+mmx_cmod_S = mmx_cmod.S
+endif
+
+if DO_PLUGIN
+plugingui_c = plugingui.c
+endif
+
+if USE_LIBSEXY
+sexy_spell_entry_c = sexy-spell-entry.c
+endif
+
+xchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \
+	dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \
+	maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \
+	rawlog.c search.c servlistgui.c setup.c $(sexy_spell_entry_c) textgui.c \
+	urlgrab.c userlistgui.c xtext.c
diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c
new file mode 100644
index 00000000..60700aec
--- /dev/null
+++ b/src/fe-gtk/about.c
@@ -0,0 +1,161 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 "fe-gtk.h"
+
+#include <gtk/gtkmain.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkwindow.h>
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#endif
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "palette.h"
+#include "pixmaps.h"
+#include "gtkutil.h"
+#include "about.h"
+
+
+#if 0 /*def USE_GNOME*/
+
+void
+menu_about (GtkWidget * wid, gpointer sess)
+{
+	char buf[512];
+	const gchar *author[] = { "Peter Zelezny <zed@xchat.org>", 0 };
+
+	(snprintf) (buf, sizeof (buf),
+				 "An IRC Client for UNIX.\n\n"
+				 "This binary was compiled on "__DATE__"\n"
+				 "Using GTK %d.%d.%d X %d\n"
+				 "Running on %s",
+				 gtk_major_version, gtk_minor_version, gtk_micro_version,
+#ifdef USE_XLIB
+				VendorRelease (GDK_DISPLAY ()), get_cpu_str());
+#else
+				666, get_cpu_str());
+#endif
+
+	gtk_widget_show (gnome_about_new ("X-Chat", PACKAGE_VERSION,
+							"(C) 1998-2005 Peter Zelezny", author, buf, 0));
+}
+
+#else
+
+static GtkWidget *about = 0;
+
+static int
+about_close (void)
+{
+	about = 0;
+	return 0;
+}
+
+void
+menu_about (GtkWidget * wid, gpointer sess)
+{
+	GtkWidget *vbox, *label, *hbox;
+	char buf[512];
+	const char *locale = NULL;
+	extern GtkWindow *parent_window;      /* maingui.c */
+
+	if (about)
+	{
+		gtk_window_present (GTK_WINDOW (about));
+		return;
+	}
+
+	about = gtk_dialog_new ();
+	gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable (GTK_WINDOW (about), FALSE);
+	gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME));
+	if (parent_window)
+		gtk_window_set_transient_for (GTK_WINDOW (about), parent_window);
+	g_signal_connect (G_OBJECT (about), "destroy",
+							G_CALLBACK (about_close), 0);
+
+	vbox = GTK_DIALOG (about)->vbox;
+
+	wid = gtk_image_new_from_pixbuf (pix_xchat);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+	gtk_container_add (GTK_CONTAINER (vbox), label);
+	g_get_charset (&locale);
+	(snprintf) (buf, sizeof (buf),
+				"<span size=\"x-large\"><b>"DISPLAY_NAME" "PACKAGE_VERSION"</b></span>\n\n"
+				"%s\n\n"
+#ifdef WIN32
+				/* leave this message to avoid time wasting bug reports! */
+				"This version is unofficial and comes with no support.\n\n"
+#endif
+				"%s\n"
+				"<b>Charset</b>: %s "
+#ifdef WIN32 
+				"<b>GTK+</b>: %i.%i.%i\n"
+#else
+				"<b>Renderer</b>: %s\n"
+#endif
+				"<b>Compiled</b>: "__DATE__"\n\n"
+				"<small>\302\251 1998-2010 Peter \305\275elezn\303\275 &lt;zed@xchat.org></small>",
+					_("A multiplatform IRC Client"),
+					get_cpu_str(),
+					locale,
+#ifdef WIN32
+					gtk_major_version,
+					gtk_minor_version,
+					gtk_micro_version
+#else
+#ifdef USE_XFT
+					"Xft"
+#else
+					"Pango"
+#endif
+#endif
+					);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT);
+	gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0);
+	gtk_widget_grab_default (wid);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (gtkutil_destroy), about);
+
+	gtk_widget_show_all (about);
+}
+#endif
diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h
new file mode 100644
index 00000000..2bad159c
--- /dev/null
+++ b/src/fe-gtk/about.h
@@ -0,0 +1 @@
+void menu_about (GtkWidget * wid, gpointer sess);
diff --git a/src/fe-gtk/ascii.c b/src/fe-gtk/ascii.c
new file mode 100644
index 00000000..f1adbdfc
--- /dev/null
+++ b/src/fe-gtk/ascii.c
@@ -0,0 +1,177 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 "fe-gtk.h"
+
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "ascii.h"
+#include "maingui.h"
+
+static const unsigned char table[]=
+{
+/* Line 1 */ '\n', 
+0xc2,0xa1,0xc2,0xbf,0xc2,0xa2,0xc2,0xa3,0xe2,0x82,0xac,0xc2,0xa5,0xc2,0xa6,0xc2,
+0xa7,0xc2,0xa8,0xc2,0xa9,0xc2,0xae,0xc2,0xaa,0xc2,0xab,0xc2,0xbb,0xc2,0xac,0xc2,
+0xad,0xc2,0xaf,0xe2,0x99,0xaa,
+/* Line 2 */ '\n', 
+0xc2,0xba,0xc2,0xb9,0xc2,0xb2,0xc2,0xb3,0xc2,0xb4,0xc2,0xb5,0xc3,0x9e,0xc3,0xbe,
+0xc2,0xb6,0xc2,0xb7,0xc2,0xb8,0xc2,0xb0,0xc2,0xbc,0xc2,0xbd,0xc2,0xbe,0xc3,0x97,
+0xc2,0xb1,0xc3,0xb7,
+/* Line 3 */ '\n', 
+0xc3,0x80,0xc3,0x81,0xc3,0x82,0xc3,0x83,0xc3,0x84,0xc3,0x85,0xc3,0x86,0xc4,0x82,
+0xc4,0x84,0x20,0xc3,0x87,0xc4,0x86,0xc4,0x8c,0xc5,0x92,0x20,0xc4,0x8e,0xc4,0x90,
+0x20,
+/* Line 4 */ '\n', 
+0xc3,0xa0,0xc3,0xa1,0xc3,0xa2,0xc3,0xa3,0xc3,0xa4,0xc3,0xa5,0xc3,0xa6,0xc4,0x83,
+0xc4,0x85,0x20,0xc3,0xa7,0xc4,0x87,0xc4,0x8d,0xc5,0x93,0x20,0xc4,0x8f,0xc4,0x91,
+0x20,
+/* Line 5 */ '\n', 
+0xc3,0x88,0xc3,0x89,0xc3,0x8a,0xc3,0x8b,0xc4,0x98,0xc4,0x9a,0x20,0xc4,0x9e,0x20,
+0xc3,0x8c,0xc3,0x8d,0xc3,0x8e,0xc3,0x8f,0xc4,0xb0,0x20,0xc4,0xb9,0xc4,0xbd,0xc5,
+0x81,
+/* Line 6 */ '\n', 
+0xc3,0xa8,0xc3,0xa9,0xc3,0xaa,0xc3,0xab,0xc4,0x99,0xc4,0x9b,0x20,0xc4,0x9f,0x20,
+0xc3,0xac,0xc3,0xad,0xc3,0xae,0xc3,0xaf,0xc4,0xb1,0x20,0xc4,0xba,0xc4,0xbe,0xc5,
+0x82,
+/* Line 7 */ '\n', 
+0xc3,0x91,0xc5,0x83,0xc5,0x87,0x20,0xc3,0x92,0xc3,0x93,0xc3,0x94,0xc3,0x95,0xc3,
+0x96,0xc3,0x98,0xc5,0x90,0x20,0xc5,0x94,0xc5,0x98,0x20,0xc5,0x9a,0xc5,0x9e,0xc5,
+0xa0,
+/* Line 8 */ '\n', 
+0xc3,0xb1,0xc5,0x84,0xc5,0x88,0x20,0xc3,0xb2,0xc3,0xb3,0xc3,0xb4,0xc3,0xb5,0xc3,
+0xb6,0xc3,0xb8,0xc5,0x91,0x20,0xc5,0x95,0xc5,0x99,0x20,0xc5,0x9b,0xc5,0x9f,0xc5,
+0xa1,
+/* Line 9 */ '\n', 
+0x20,0xc5,0xa2,0xc5,0xa4,0x20,0xc3,0x99,0xc3,0x9a,0xc3,0x9b,0xc5,0xb2,0xc3,0x9c,
+0xc5,0xae,0xc5,0xb0,0x20,0xc3,0x9d,0xc3,0x9f,0x20,0xc5,0xb9,0xc5,0xbb,0xc5,0xbd,
+/* Line 10 */ '\n', 
+0x20,0xc5,0xa3,0xc5,0xa5,0x20,0xc3,0xb9,0xc3,0xba,0xc3,0xbb,0xc5,0xb3,0xc3,0xbc,
+0xc5,0xaf,0xc5,0xb1,0x20,0xc3,0xbd,0xc3,0xbf,0x20,0xc5,0xba,0xc5,0xbc,0xc5,0xbe,
+/* Line 11 */ '\n', 
+0xd0,0x90,0xd0,0x91,0xd0,0x92,0xd0,0x93,0xd0,0x94,0xd0,0x95,0xd0,0x81,0xd0,0x96,
+0xd0,0x97,0xd0,0x98,0xd0,0x99,0xd0,0x9a,0xd0,0x9b,0xd0,0x9c,0xd0,0x9d,0xd0,0x9e,
+0xd0,0x9f,0xd0,0xa0,
+/* Line 12 */ '\n', 
+0xd0,0xb0,0xd0,0xb1,0xd0,0xb2,0xd0,0xb3,0xd0,0xb4,0xd0,0xb5,0xd1,0x91,0xd0,0xb6,
+0xd0,0xb7,0xd0,0xb8,0xd0,0xb9,0xd0,0xba,0xd0,0xbb,0xd0,0xbc,0xd0,0xbd,0xd0,0xbe,
+0xd0,0xbf,0xd1,0x80,
+/* Line 13 */ '\n', 
+0xd0,0xa1,0xd0,0xa2,0xd0,0xa3,0xd0,0xa4,0xd0,0xa5,0xd0,0xa6,0xd0,0xa7,0xd0,0xa8,
+0xd0,0xa9,0xd0,0xaa,0xd0,0xab,0xd0,0xac,0xd0,0xad,0xd0,0xae,0xd0,0xaf,
+/* Line 14 */ '\n', 
+0xd1,0x81,0xd1,0x82,0xd1,0x83,0xd1,0x84,0xd1,0x85,0xd1,0x86,0xd1,0x87,0xd1,0x88,
+0xd1,0x89,0xd1,0x8a,0xd1,0x8b,0xd1,0x8c,0xd1,0x8d,0xd1,0x8e,0xd1,0x8f,0
+};
+
+
+static gboolean
+ascii_enter (GtkWidget * wid, GdkEventCrossing *event, GtkWidget *label)
+{
+	char buf[64];
+	const char *text;
+	gunichar u;
+
+	text = gtk_button_get_label (GTK_BUTTON (wid));
+	u = g_utf8_get_char (text);
+	sprintf (buf, "%s U+%04X", text, u);
+	gtk_label_set_text (GTK_LABEL (label), buf);
+
+	return FALSE;
+}
+
+static void
+ascii_click (GtkWidget * wid, gpointer userdata)
+{
+	int tmp_pos;
+	const char *text;
+
+	if (current_sess)
+	{
+		text = gtk_button_get_label (GTK_BUTTON (wid));
+		wid = current_sess->gui->input_box;
+		tmp_pos = SPELL_ENTRY_GET_POS (wid);
+		SPELL_ENTRY_INSERT (wid, text, -1, &tmp_pos);
+		SPELL_ENTRY_SET_POS (wid, tmp_pos);
+	}
+}
+
+void
+ascii_open (void)
+{
+	int i, len;
+	const unsigned char *table_pos;
+	char name[8];
+	GtkWidget *frame, *label, *but, *hbox = NULL, *vbox, *win;
+
+	win = mg_create_generic_tab ("charmap", _("Character Chart"), TRUE, TRUE,
+										  NULL, NULL, 0, 0, &vbox, NULL);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 5);
+
+	label = gtk_label_new (NULL);
+
+	table_pos = table;
+	i = 0;
+	while (table_pos[0] != 0)
+	{
+		if (table_pos[0] == '\n' || i == 0)
+		{
+			table_pos++;
+			hbox = gtk_hbox_new (0, 0);
+			gtk_container_add (GTK_CONTAINER (vbox), hbox);
+			gtk_widget_show (hbox);
+			i++;
+			continue;
+		}
+
+		i++;
+		len = g_utf8_skip[table_pos[0]];
+		memcpy (name, table_pos, len);
+		name[len] = 0;
+
+		but = gtk_button_new_with_label (name);
+		gtk_widget_set_size_request (but, 28, -1);
+		g_signal_connect (G_OBJECT (but), "clicked",
+								G_CALLBACK (ascii_click), NULL);
+		g_signal_connect (G_OBJECT (but), "enter_notify_event",
+								G_CALLBACK (ascii_enter), label);
+		gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0);
+		gtk_widget_show (but);
+
+		table_pos += len;
+	}
+
+	frame = gtk_frame_new ("");
+	gtk_container_add (GTK_CONTAINER (hbox), frame);
+	gtk_container_add (GTK_CONTAINER (frame), label);
+	gtk_widget_show (label);
+	gtk_widget_show (frame);
+
+	gtk_widget_show (win);
+}
diff --git a/src/fe-gtk/ascii.h b/src/fe-gtk/ascii.h
new file mode 100644
index 00000000..afd3bd4f
--- /dev/null
+++ b/src/fe-gtk/ascii.h
@@ -0,0 +1 @@
+void ascii_open (void);
diff --git a/src/fe-gtk/banlist.c b/src/fe-gtk/banlist.c
new file mode 100644
index 00000000..afaa7eb4
--- /dev/null
+++ b/src/fe-gtk/banlist.c
@@ -0,0 +1,418 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/modes.h"
+#include "../common/outbound.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "banlist.h"
+
+/* model for the banlist tree */
+enum
+{
+	MASK_COLUMN,
+	FROM_COLUMN,
+	DATE_COLUMN,
+	N_COLUMNS
+};
+
+static GtkTreeView *
+get_view (struct session *sess)
+{
+	return GTK_TREE_VIEW (sess->res->banlist_treeview);
+}
+
+static GtkListStore *
+get_store (struct session *sess)
+{
+	return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess)));
+}
+
+static gboolean
+supports_exempt (server *serv)
+{
+	char *cm = serv->chanmodes;
+
+	if (serv->have_except)
+		return TRUE;
+
+	if (!cm)
+		return FALSE;
+
+	while (*cm)
+	{
+		if (*cm == ',')
+			break;
+		if (*cm == 'e')
+			return TRUE;
+		cm++;
+	}
+
+	return FALSE;
+}
+
+void
+fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exempt)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	char buf[512];
+
+	store = get_store (sess);
+	gtk_list_store_append (store, &iter);
+
+	if (is_exempt)
+	{
+		snprintf (buf, sizeof (buf), "(EX) %s", mask);
+		gtk_list_store_set (store, &iter, 0, buf, 1, who, 2, when, -1);
+	} else
+	{
+		gtk_list_store_set (store, &iter, 0, mask, 1, who, 2, when, -1);
+	}
+}
+
+void
+fe_ban_list_end (struct session *sess, int is_exemption)
+{
+	gtk_widget_set_sensitive (sess->res->banlist_butRefresh, TRUE);
+}
+
+/**
+ *  * Performs the actual refresh operations.
+ *  */
+static void
+banlist_do_refresh (struct session *sess)
+{
+	char tbuf[256];
+	if (sess->server->connected)
+	{
+		GtkListStore *store;
+
+		gtk_widget_set_sensitive (sess->res->banlist_butRefresh, FALSE);
+
+		snprintf (tbuf, sizeof tbuf, "XChat: Ban List (%s, %s)",
+						sess->channel, sess->server->servername);
+		mg_set_title (sess->res->banlist_window, tbuf);
+
+		store = get_store (sess);
+		gtk_list_store_clear (store);
+
+		handle_command (sess, "ban", FALSE);
+#ifdef WIN32
+		if (0)
+#else
+		if (supports_exempt (sess->server))
+#endif
+		{
+			snprintf (tbuf, sizeof (tbuf), "quote mode %s +e", sess->channel);
+			handle_command (sess, tbuf, FALSE);
+		}
+
+	} else
+	{
+		fe_message (_("Not connected."), FE_MSG_ERROR);
+	}
+}
+
+static void
+banlist_refresh (GtkWidget * wid, struct session *sess)
+{
+	/* JG NOTE: Didn't see actual use of wid here, so just forwarding
+	   *          * this to chanlist_do_refresh because I use it without any widget
+	   *          * param in chanlist_build_gui_list when the user presses enter
+	   *          * or apply for the first time if the list has not yet been
+	   *          * received.
+	   *          */
+	banlist_do_refresh (sess);
+}
+
+static int
+banlist_unban_inner (gpointer none, struct session *sess, int do_exempts)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+	char tbuf[2048];
+	char **masks, *tmp, *space;
+	int num_sel, i;
+
+	/* grab the list of selected items */
+	model = GTK_TREE_MODEL (get_store (sess));
+	sel = gtk_tree_view_get_selection (get_view (sess));
+	num_sel = 0;
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			if (gtk_tree_selection_iter_is_selected (sel, &iter))
+				num_sel++;
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	if (num_sel < 1)
+		return 0;
+
+	/* create an array of all the masks */
+	masks = calloc (1, num_sel * sizeof (char *));
+
+	i = 0;
+	gtk_tree_model_get_iter_first (model, &iter);
+	do
+	{
+		if (gtk_tree_selection_iter_is_selected (sel, &iter))
+		{
+			gtk_tree_model_get (model, &iter, MASK_COLUMN, &masks[i], -1);
+			space = strchr (masks[i], ' ');
+
+			if (do_exempts)
+			{
+				if (space)
+				{
+					/* remove the "(EX) " */
+					tmp = masks[i];
+					masks[i] = g_strdup (space + 1);
+					g_free (tmp);
+					i++;
+				}
+			} else
+			{
+				if (!space)
+					i++;
+			}
+		}
+	}
+	while (gtk_tree_model_iter_next (model, &iter));
+
+	/* and send to server */
+	if (do_exempts)
+		send_channel_modes (sess, tbuf, masks, 0, i, '-', 'e', 0);
+	else
+		send_channel_modes (sess, tbuf, masks, 0, i, '-', 'b', 0);
+
+	/* now free everything, and refresh banlist */	
+	for (i=0; i < num_sel; i++)
+		g_free (masks[i]);
+	free (masks);
+
+	return num_sel;
+}
+
+static void
+banlist_unban (GtkWidget * wid, struct session *sess)
+{
+	int num = 0;
+
+	num += banlist_unban_inner (wid, sess, FALSE);
+	num += banlist_unban_inner (wid, sess, TRUE);
+
+	if (num < 1)
+	{
+		fe_message (_("You must select some bans."), FE_MSG_ERROR);
+		return;
+	}
+
+	banlist_do_refresh (sess);
+}
+
+static void
+banlist_clear_cb (GtkDialog *dialog, gint response, gpointer sess)
+{
+	GtkTreeSelection *sel;
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+
+	if (response == GTK_RESPONSE_OK)
+	{
+		sel = gtk_tree_view_get_selection (get_view (sess));
+		gtk_tree_selection_select_all (sel);
+		banlist_unban (NULL, sess);
+	}
+}
+
+static void
+banlist_clear (GtkWidget * wid, struct session *sess)
+{
+	GtkWidget *dialog;
+
+	dialog = gtk_message_dialog_new (NULL, 0,
+								GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
+					_("Are you sure you want to remove all bans in %s?"), sess->channel);
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (banlist_clear_cb), sess);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+}
+
+static void
+banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+	GSList **lp = data;
+	GSList *list = NULL;
+	GtkTreeIter *copy;
+
+	if (!lp) return;
+	list = *lp;
+	copy = g_malloc (sizeof (GtkTreeIter));
+	g_return_if_fail (copy != NULL);
+	*copy = *iter;
+
+	list = g_slist_append (list, copy);
+	*(GSList **)data = list;
+}
+
+static void
+banlist_crop (GtkWidget * wid, struct session *sess)
+{
+	GtkTreeSelection *select;
+	GSList *list = NULL, *node;
+	int num_sel;
+
+	/* remember which bans are selected */
+	select = gtk_tree_view_get_selection (get_view (sess));
+	/* gtk_tree_selected_get_selected_rows() isn't present in gtk 2.0.x */
+	gtk_tree_selection_selected_foreach (select, banlist_add_selected_cb,
+	                                     &list);
+
+	num_sel = g_slist_length (list);
+	/* select all, then unselect those that we remembered */
+	if (num_sel)
+	{
+		gtk_tree_selection_select_all (select);
+
+		for (node = list; node; node = node->next)
+			gtk_tree_selection_unselect_iter (select, node->data);
+		
+		g_slist_foreach (list, (GFunc)g_free, NULL);
+		g_slist_free (list);
+
+		banlist_unban (NULL, sess);
+	} else
+		fe_message (_("You must select some bans."), FE_MSG_ERROR);
+}
+
+static GtkWidget *
+banlist_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeSelection *select;
+	GtkTreeViewColumn *col;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+	                            G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             MASK_COLUMN, _("Mask"),
+	                             FROM_COLUMN, _("From"),
+	                             DATE_COLUMN, _("Date"), -1);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), MASK_COLUMN);
+	gtk_tree_view_column_set_alignment (col, 0.5);
+	gtk_tree_view_column_set_min_width (col, 300);
+	gtk_tree_view_column_set_sort_column_id (col, MASK_COLUMN);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), FROM_COLUMN);
+	gtk_tree_view_column_set_alignment (col, 0.5);
+	gtk_tree_view_column_set_sort_column_id (col, FROM_COLUMN);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN);
+	gtk_tree_view_column_set_alignment (col, 0.5);
+
+	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+banlist_closegui (GtkWidget *wid, session *sess)
+{
+	if (is_session (sess))
+		sess->res->banlist_window = 0;
+}
+
+void
+banlist_opengui (struct session *sess)
+{
+	GtkWidget *vbox1;
+	GtkWidget *bbox;
+	char tbuf[256];
+
+	if (sess->res->banlist_window)
+	{
+		mg_bring_tofront (sess->res->banlist_window);
+		return;
+	}
+
+	if (sess->type != SESS_CHANNEL)
+	{
+		fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: Ban List (%s)"),
+					sess->server->servername);
+
+	sess->res->banlist_window = mg_create_generic_tab ("BanList", tbuf, FALSE,
+					TRUE, banlist_closegui, sess, 550, 200, &vbox1, sess->server);
+
+	/* create banlist view */
+	sess->res->banlist_treeview = banlist_treeview_new (vbox1);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
+	gtk_box_pack_end (GTK_BOX (vbox1), bbox, 0, 0, 0);
+	gtk_widget_show (bbox);
+
+	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, sess,
+	                _("Remove"));
+	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, sess,
+	                _("Crop"));
+	gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, sess,
+	                _("Clear"));
+
+	sess->res->banlist_butRefresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, sess, _("Refresh"));
+
+	banlist_do_refresh (sess);
+
+	gtk_widget_show (sess->res->banlist_window);
+}
diff --git a/src/fe-gtk/banlist.h b/src/fe-gtk/banlist.h
new file mode 100644
index 00000000..7ceccd00
--- /dev/null
+++ b/src/fe-gtk/banlist.h
@@ -0,0 +1 @@
+void banlist_opengui (session *sess);
diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c
new file mode 100644
index 00000000..2f65b518
--- /dev/null
+++ b/src/fe-gtk/chanlist.c
@@ -0,0 +1,943 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkcombobox.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkvseparator.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/util.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "gtkutil.h"
+#include "maingui.h"
+
+
+#include "custom-list.h"
+
+
+enum
+{
+	COL_CHANNEL,
+	COL_USERS,
+	COL_TOPIC,
+	N_COLUMNS
+};
+
+#ifndef CUSTOM_LIST
+typedef struct	/* this is now in custom-list.h */
+{
+	char *topic;
+	char *collation_key;
+	guint32	pos;
+	guint32 users;
+	/* channel string lives beyond "users" */
+#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow))
+}
+chanlistrow;
+#endif
+
+#define GET_MODEL(xserv) (gtk_tree_view_get_model(GTK_TREE_VIEW(xserv->gui->chanlist_list)))
+
+
+static gboolean
+chanlist_match (server *serv, const char *str)
+{
+	switch (serv->gui->chanlist_search_type)
+	{
+	case 1:
+		return match (GTK_ENTRY (serv->gui->chanlist_wild)->text, str);
+#ifndef WIN32
+	case 2:
+		if (!serv->gui->have_regex)
+			return 0;
+		/* regex returns 0 if it's a match: */
+		return !regexec (&serv->gui->chanlist_match_regex, str, 1, NULL, REG_NOTBOL);
+#endif
+	default:	/* case 0: */
+		return nocasestrstr (str, GTK_ENTRY (serv->gui->chanlist_wild)->text) ? 1 : 0;
+	}
+}
+
+/**
+ * Updates the caption to reflect the number of users and channels
+ */
+static void
+chanlist_update_caption (server *serv)
+{
+	gchar tbuf[256];
+
+	snprintf (tbuf, sizeof tbuf,
+				 _("Displaying %d/%d users on %d/%d channels."),
+				 serv->gui->chanlist_users_shown_count,
+				 serv->gui->chanlist_users_found_count,
+				 serv->gui->chanlist_channels_shown_count,
+				 serv->gui->chanlist_channels_found_count);
+
+	gtk_label_set_text (GTK_LABEL (serv->gui->chanlist_label), tbuf);
+	serv->gui->chanlist_caption_is_stale = FALSE;
+}
+
+static void
+chanlist_update_buttons (server *serv)
+{
+	if (serv->gui->chanlist_channels_shown_count)
+	{
+		gtk_widget_set_sensitive (serv->gui->chanlist_join, TRUE);
+		gtk_widget_set_sensitive (serv->gui->chanlist_savelist, TRUE);
+	}
+	else
+	{
+		gtk_widget_set_sensitive (serv->gui->chanlist_join, FALSE);
+		gtk_widget_set_sensitive (serv->gui->chanlist_savelist, FALSE);
+	}
+}
+
+static void
+chanlist_reset_counters (server *serv)
+{
+	serv->gui->chanlist_users_found_count = 0;
+	serv->gui->chanlist_users_shown_count = 0;
+	serv->gui->chanlist_channels_found_count = 0;
+	serv->gui->chanlist_channels_shown_count = 0;
+
+	chanlist_update_caption (serv);
+	chanlist_update_buttons (serv);
+}
+
+/* free up our entire linked list and all the nodes */
+
+static void
+chanlist_data_free (server *serv)
+{
+	GSList *rows;
+	chanlistrow *data;
+
+	if (serv->gui->chanlist_data_stored_rows)
+	{
+		for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL;
+			  rows = rows->next)
+		{
+			data = rows->data;
+			g_free (data->topic);
+			g_free (data->collation_key);
+			free (data);
+		}
+
+		g_slist_free (serv->gui->chanlist_data_stored_rows);
+		serv->gui->chanlist_data_stored_rows = NULL;
+	}
+
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+}
+
+/* add any rows we received from the server in the last 0.25s to the GUI */
+
+static void
+chanlist_flush_pending (server *serv)
+{
+	GSList *list = serv->gui->chanlist_pending_rows;
+	GtkTreeModel *model;
+	chanlistrow *row;
+
+	if (!list)
+	{
+		if (serv->gui->chanlist_caption_is_stale)
+			chanlist_update_caption (serv);
+		return;
+	}
+	model = GET_MODEL (serv);
+
+	while (list)
+	{
+		row = list->data;
+		custom_list_append (CUSTOM_LIST (model), row);
+		list = list->next;
+	}
+
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+	chanlist_update_caption (serv);
+}
+
+static gboolean
+chanlist_timeout (server *serv)
+{
+	chanlist_flush_pending (serv);
+	return TRUE;
+}
+
+/**
+ * Places a data row into the gui GtkTreeView, if and only if the row matches
+ * the user and regex/search requirements.
+ */
+static void
+chanlist_place_row_in_gui (server *serv, chanlistrow *next_row, gboolean force)
+{
+	GtkTreeModel *model;
+
+	/* First, update the 'found' counter values */
+	serv->gui->chanlist_users_found_count += next_row->users;
+	serv->gui->chanlist_channels_found_count++;
+
+	if (serv->gui->chanlist_channels_shown_count == 1)
+		/* join & save buttons become live */
+		chanlist_update_buttons (serv);
+
+	if (next_row->users < serv->gui->chanlist_minusers)
+	{
+		serv->gui->chanlist_caption_is_stale = TRUE;
+		return;
+	}
+
+	if (next_row->users > serv->gui->chanlist_maxusers
+		 && serv->gui->chanlist_maxusers > 0)
+	{
+		serv->gui->chanlist_caption_is_stale = TRUE;
+		return;
+	}
+
+	if (GTK_ENTRY (serv->gui->chanlist_wild)->text[0])
+	{
+		/* Check what the user wants to match. If both buttons or _neither_
+		 * button is checked, look for match in both by default. 
+		 */
+		if (serv->gui->chanlist_match_wants_channel ==
+			 serv->gui->chanlist_match_wants_topic)
+		{
+			if (!chanlist_match (serv, GET_CHAN (next_row))
+				 && !chanlist_match (serv, next_row->topic))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+
+		else if (serv->gui->chanlist_match_wants_channel)
+		{
+			if (!chanlist_match (serv, GET_CHAN (next_row)))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+
+		else if (serv->gui->chanlist_match_wants_topic)
+		{
+			if (!chanlist_match (serv, next_row->topic))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+	}
+
+	if (force || serv->gui->chanlist_channels_shown_count < 20)
+	{
+		model = GET_MODEL (serv);
+		/* makes it appear fast :) */
+		custom_list_append (CUSTOM_LIST (model), next_row);
+		chanlist_update_caption (serv);
+	}
+	else
+		/* add it to GUI at the next update interval */
+		serv->gui->chanlist_pending_rows = g_slist_prepend (serv->gui->chanlist_pending_rows, next_row);
+
+	/* Update the 'shown' counter values */
+	serv->gui->chanlist_users_shown_count += next_row->users;
+	serv->gui->chanlist_channels_shown_count++;
+}
+
+/* Performs the LIST download from the IRC server. */
+
+static void
+chanlist_do_refresh (server *serv)
+{
+	if (serv->gui->chanlist_flash_tag)
+	{
+		g_source_remove (serv->gui->chanlist_flash_tag);
+		serv->gui->chanlist_flash_tag = 0;
+	}
+
+	if (!serv->connected)
+	{
+		fe_message (_("Not connected."), FE_MSG_ERROR);
+		return;
+	}
+
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+	gtk_widget_set_sensitive (serv->gui->chanlist_refresh, FALSE);
+
+	chanlist_data_free (serv);
+	chanlist_reset_counters (serv);
+
+	/* can we request a list with minusers arg? */
+	if (serv->use_listargs)
+	{
+		/* yes - it will download faster */
+		serv->p_list_channels (serv, "", serv->gui->chanlist_minusers);
+		/* don't allow the spin button below this value from now on */
+		serv->gui->chanlist_minusers_downloaded = serv->gui->chanlist_minusers;
+	}
+	else
+	{
+		/* download all, filter minusers locally only */
+		serv->p_list_channels (serv, "", 1);
+		serv->gui->chanlist_minusers_downloaded = 1;
+	}
+
+/*	gtk_spin_button_set_range ((GtkSpinButton *)serv->gui->chanlist_min_spin,
+										serv->gui->chanlist_minusers_downloaded, 999999);*/
+}
+
+static void
+chanlist_refresh (GtkWidget * wid, server *serv)
+{
+	chanlist_do_refresh (serv);
+}
+
+/**
+ * Fills the gui GtkTreeView with stored items from the GSList.
+ */
+static void
+chanlist_build_gui_list (server *serv)
+{
+	GSList *rows;
+
+	/* first check if the list is present */
+	if (serv->gui->chanlist_data_stored_rows == NULL)
+	{
+		/* start a download */
+		chanlist_do_refresh (serv);
+		return;
+	}
+
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+
+	/* discard pending rows FIXME: free the structs? */
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+
+	/* Reset the counters */
+	chanlist_reset_counters (serv);
+
+	/* Refill the list */
+	for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL;
+		  rows = rows->next)
+	{
+		chanlist_place_row_in_gui (serv, rows->data, TRUE);
+	}
+
+	custom_list_resort ((CustomList *)GET_MODEL (serv));
+}
+
+/**
+ * Accepts incoming channel data from inbound.c, allocates new space for a
+ * chanlistrow, adds it to our linked list and calls chanlist_place_row_in_gui.
+ */
+void
+fe_add_chan_list (server *serv, char *chan, char *users, char *topic)
+{
+	chanlistrow *next_row;
+	int len = strlen (chan) + 1;
+
+	/* we allocate the struct and channel string in one go */
+	next_row = malloc (sizeof (chanlistrow) + len);
+	memcpy (((char *)next_row) + sizeof (chanlistrow), chan, len);
+	next_row->topic = strip_color (topic, -1, STRIP_ALL);
+	next_row->collation_key = g_utf8_collate_key (chan, len-1);
+	if (!(next_row->collation_key))
+		next_row->collation_key = g_strdup (chan);
+	next_row->users = atoi (users);
+
+	/* add this row to the data */
+	serv->gui->chanlist_data_stored_rows =
+		g_slist_prepend (serv->gui->chanlist_data_stored_rows, next_row);
+
+	/* _possibly_ add the row to the gui */
+	chanlist_place_row_in_gui (serv, next_row, FALSE);
+}
+
+void
+fe_chan_list_end (server *serv)
+{
+	/* download complete */
+	chanlist_flush_pending (serv);
+	gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE);
+	custom_list_resort ((CustomList *)GET_MODEL (serv));
+}
+
+static void
+chanlist_search_pressed (GtkButton * button, server *serv)
+{
+	chanlist_build_gui_list (serv);
+}
+
+static void
+chanlist_find_cb (GtkWidget * wid, server *serv)
+{
+#ifndef WIN32
+	/* recompile the regular expression. */
+	if (serv->gui->have_regex)
+	{
+		serv->gui->have_regex = 0;
+		regfree (&serv->gui->chanlist_match_regex);
+	}
+
+	if (regcomp (&serv->gui->chanlist_match_regex, GTK_ENTRY (wid)->text,
+					 REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0)
+		serv->gui->have_regex = 1;
+#endif
+}
+
+static void
+chanlist_match_channel_button_toggled (GtkWidget * wid, server *serv)
+{
+	serv->gui->chanlist_match_wants_channel = GTK_TOGGLE_BUTTON (wid)->active;
+}
+
+static void
+chanlist_match_topic_button_toggled (GtkWidget * wid, server *serv)
+{
+	serv->gui->chanlist_match_wants_topic = GTK_TOGGLE_BUTTON (wid)->active;
+}
+
+static char *
+chanlist_get_selected (server *serv, gboolean get_topic)
+{
+	char *chan;
+	GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (serv->gui->chanlist_list));
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!gtk_tree_selection_get_selected (sel, &model, &iter))
+		return NULL;
+
+	gtk_tree_model_get (model, &iter, get_topic ? COL_TOPIC : COL_CHANNEL, &chan, -1);
+	return chan;
+}
+
+static void
+chanlist_join (GtkWidget * wid, server *serv)
+{
+	char tbuf[CHANLEN + 6];
+	char *chan = chanlist_get_selected (serv, FALSE);
+	if (chan)
+	{
+		if (serv->connected && (strcmp (chan, "*") != 0))
+		{
+			snprintf (tbuf, sizeof (tbuf), "join %s", chan);
+			handle_command (serv->server_session, tbuf, FALSE);
+		} else
+			gdk_beep ();
+		g_free (chan);
+	}
+}
+
+static void
+chanlist_filereq_done (server *serv, char *file)
+{
+	time_t t = time (0);
+	int fh, users;
+	char *chan, *topic;
+	char buf[1024];
+	GtkTreeModel *model = GET_MODEL (serv);
+	GtkTreeIter iter;
+
+	if (!file)
+		return;
+
+	fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, 0600,
+								 XOF_DOMODE | XOF_FULLPATH);
+	if (fh == -1)
+		return;
+
+	snprintf (buf, sizeof buf, "XChat Channel List: %s - %s\n",
+				 serv->servername, ctime (&t));
+	write (fh, buf, strlen (buf));
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter,
+									  COL_CHANNEL, &chan,
+									  COL_USERS, &users,
+									  COL_TOPIC, &topic, -1);
+			snprintf (buf, sizeof buf, "%-16s %-5d%s\n", chan, users, topic);
+			g_free (chan);
+			g_free (topic);
+			write (fh, buf, strlen (buf));
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	close (fh);
+}
+
+static void
+chanlist_save (GtkWidget * wid, server *serv)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model = GET_MODEL (serv);
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+		gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done,
+								serv, NULL, FRF_WRITE);
+}
+
+static gboolean
+chanlist_flash (server *serv)
+{
+	if (serv->gui->chanlist_refresh->state != GTK_STATE_ACTIVE)
+		gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_ACTIVE);
+	else
+		gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_PRELIGHT);
+
+	return TRUE;
+}
+
+static void
+chanlist_minusers (GtkSpinButton *wid, server *serv)
+{
+	serv->gui->chanlist_minusers = gtk_spin_button_get_value_as_int (wid);
+
+	if (serv->gui->chanlist_minusers < serv->gui->chanlist_minusers_downloaded)
+	{
+		if (serv->gui->chanlist_flash_tag == 0)
+			serv->gui->chanlist_flash_tag = g_timeout_add (500, (GSourceFunc)chanlist_flash, serv);
+	}
+	else
+	{
+		if (serv->gui->chanlist_flash_tag)
+		{
+			g_source_remove (serv->gui->chanlist_flash_tag);
+			serv->gui->chanlist_flash_tag = 0;
+		}
+	}
+}
+
+static void
+chanlist_maxusers (GtkSpinButton *wid, server *serv)
+{
+	serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid);
+}
+
+static void
+chanlist_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+						  GtkTreeViewColumn *column, gpointer data)
+{
+	chanlist_join (0, (server *) data);	/* double clicked a row */
+}
+
+static void
+chanlist_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+static void
+chanlist_copychannel (GtkWidget *item, server *serv)
+{
+	char *chan = chanlist_get_selected (serv, FALSE);
+	if (chan)
+	{
+		gtkutil_copy_to_clipboard (item, NULL, chan);
+		g_free (chan);
+	}
+}
+
+static void
+chanlist_copytopic (GtkWidget *item, server *serv)
+{
+	char *topic = chanlist_get_selected (serv, TRUE);
+	if (topic)
+	{
+		gtkutil_copy_to_clipboard (item, NULL, topic);
+		g_free (topic);
+	}
+}
+
+static gboolean
+chanlist_button_cb (GtkTreeView *tree, GdkEventButton *event, server *serv)
+{
+	GtkWidget *menu;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	char *chan;
+
+	if (event->button != 3)
+		return FALSE;
+
+	if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0))
+		return FALSE;
+
+	/* select what they right-clicked on */
+	sel = gtk_tree_view_get_selection (tree);
+	gtk_tree_selection_unselect_all (sel);
+	gtk_tree_selection_select_path (sel, path);
+	gtk_tree_path_free (path);
+
+	menu = gtk_menu_new ();
+	if (event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (chanlist_menu_destroy), NULL);
+	mg_create_icon_item (_("_Join Channel"), GTK_STOCK_JUMP_TO, menu,
+								chanlist_join, serv);
+	mg_create_icon_item (_("_Copy Channel Name"), GTK_STOCK_COPY, menu,
+								chanlist_copychannel, serv);
+	mg_create_icon_item (_("Copy _Topic Text"), GTK_STOCK_COPY, menu,
+								chanlist_copytopic, serv);
+
+	chan = chanlist_get_selected (serv, FALSE);
+	menu_addfavoritemenu (serv, menu, chan);
+	g_free (chan);
+
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time);
+
+	return TRUE;
+}
+
+static void
+chanlist_destroy_widget (GtkWidget *wid, server *serv)
+{
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+	chanlist_data_free (serv);
+
+	if (serv->gui->chanlist_flash_tag)
+	{
+		g_source_remove (serv->gui->chanlist_flash_tag);
+		serv->gui->chanlist_flash_tag = 0;
+	}
+
+	if (serv->gui->chanlist_tag)
+	{
+		g_source_remove (serv->gui->chanlist_tag);
+		serv->gui->chanlist_tag = 0;
+	}
+
+#ifndef WIN32
+	if (serv->gui->have_regex)
+	{
+		regfree (&serv->gui->chanlist_match_regex);
+		serv->gui->have_regex = 0;
+	}
+#endif
+}
+
+static void
+chanlist_closegui (GtkWidget *wid, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->chanlist_window = NULL;
+}
+
+static void
+chanlist_add_column (GtkWidget *tree, int textcol, int size, char *title, gboolean right_justified)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *col;
+
+	renderer = gtk_cell_renderer_text_new ();
+	if (right_justified)
+		g_object_set (G_OBJECT (renderer), "xalign", (gfloat) 1.0, NULL);
+	g_object_set (G_OBJECT (renderer), "ypad", (gint) 0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title,
+																renderer, "text", textcol, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), textcol);
+	gtk_tree_view_column_set_sort_column_id (col, textcol);
+	gtk_tree_view_column_set_resizable (col, TRUE);
+	if (textcol != COL_TOPIC)
+	{
+		gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_fixed_width (col, size);
+	}
+}
+
+static void
+chanlist_combo_cb (GtkWidget *combo, server *serv)
+{
+	serv->gui->chanlist_search_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+}
+
+void
+chanlist_opengui (server *serv, int do_refresh)
+{
+	GtkWidget *vbox, *hbox, *table, *wid, *view;
+	char tbuf[256];
+	GtkListStore *store;
+
+	if (serv->gui->chanlist_window)
+	{
+		mg_bring_tofront (serv->gui->chanlist_window);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: Channel List (%s)"),
+				 server_get_network (serv, TRUE));
+
+	serv->gui->chanlist_pending_rows = NULL;
+	serv->gui->chanlist_tag = 0;
+	serv->gui->chanlist_flash_tag = 0;
+	serv->gui->chanlist_data_stored_rows = NULL;
+
+	if (!serv->gui->chanlist_minusers)
+		serv->gui->chanlist_minusers = 5;
+
+	if (!serv->gui->chanlist_maxusers)
+		serv->gui->chanlist_maxusers = 9999;
+
+	serv->gui->chanlist_window =
+		mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui,
+								serv, 640, 480, &vbox, serv);
+
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_box_set_spacing (GTK_BOX (vbox), 12);
+
+	/* make a label to store the user/channel info */
+	wid = gtk_label_new (NULL);
+	gtk_box_pack_start (GTK_BOX (vbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_label = wid;
+
+	/* ============================================================= */
+
+	store = (GtkListStore *) custom_list_new();
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view->parent),
+													 GTK_SHADOW_IN);
+	serv->gui->chanlist_list = view;
+
+	g_signal_connect (G_OBJECT (view), "row_activated",
+							G_CALLBACK (chanlist_dclick_cb), serv);
+	g_signal_connect (G_OBJECT (view), "button-press-event",
+							G_CALLBACK (chanlist_button_cb), serv);
+
+	chanlist_add_column (view, COL_CHANNEL, 96, _("Channel"), FALSE);
+	chanlist_add_column (view, COL_USERS,   50, _("Users"),   TRUE);
+	chanlist_add_column (view, COL_TOPIC,   50, _("Topic"),   FALSE);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	/* this is a speed up, but no horizontal scrollbar :( */
+	/*gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);*/
+	gtk_widget_show (view);
+
+	/* ============================================================= */
+
+	table = gtk_table_new (4, 4, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
+	gtk_box_pack_start (GTK_BOX (vbox), table, 0, 1, 0);
+	gtk_widget_show (table);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_FIND, 0, chanlist_search_pressed, serv,
+								 _("_Search"));
+	serv->gui->chanlist_search = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 3, 4,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_REFRESH, 0, chanlist_refresh, serv,
+								 _("_Download List"));
+	serv->gui->chanlist_refresh = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_SAVE_AS, 0, chanlist_save, serv,
+								 _("Save _List..."));
+	serv->gui->chanlist_savelist = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_JUMP_TO, 0, chanlist_join, serv,
+						 _("_Join Channel"));
+	serv->gui->chanlist_join = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 0, 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Show only:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 3, 4,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_box_set_spacing (GTK_BOX (hbox), 9);
+	gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 3, 4,
+							GTK_FILL, GTK_FILL, 0, 0);
+	gtk_widget_show (hbox);
+
+	wid = gtk_label_new (_("channels with"));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_spin_button_new_with_range (1, 999999, 1);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										serv->gui->chanlist_minusers);
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (chanlist_minusers), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_min_spin = wid;
+
+	wid = gtk_label_new (_("to"));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_spin_button_new_with_range (1, 999999, 1);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										serv->gui->chanlist_maxusers);
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (chanlist_maxusers), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_label_new (_("users."));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Look in:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_box_set_spacing (GTK_BOX (hbox), 12);
+	gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 3,
+							GTK_FILL, GTK_FILL, 0, 0);
+	gtk_widget_show (hbox);
+
+	wid = gtk_check_button_new_with_label (_("Channel name"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC
+							  (chanlist_match_channel_button_toggled), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_check_button_new_with_label (_("Topic"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC (chanlist_match_topic_button_toggled),
+							  serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	serv->gui->chanlist_match_wants_channel = 1;
+	serv->gui->chanlist_match_wants_topic = 1;
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Search type:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_combo_box_new_text ();
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Simple Search"));
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Pattern Match (Wildcards)"));
+#ifndef WIN32
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Regular Expression"));
+#endif
+	gtk_combo_box_set_active (GTK_COMBO_BOX (wid), serv->gui->chanlist_search_type);
+	gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "changed",
+							G_CALLBACK (chanlist_combo_cb), serv);
+	gtk_widget_show (wid);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Find:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 0, 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_entry_new_with_max_length (255);
+	gtk_signal_connect (GTK_OBJECT (wid), "changed",
+							  GTK_SIGNAL_FUNC (chanlist_find_cb), serv);
+	gtk_signal_connect (GTK_OBJECT (wid), "activate",
+							  GTK_SIGNAL_FUNC (chanlist_search_pressed),
+							  (gpointer) serv);
+	gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 0, 1,
+							GTK_EXPAND | GTK_FILL, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_wild = wid;
+
+	chanlist_find_cb (wid, serv);
+
+	/* ============================================================= */
+
+	wid = gtk_vseparator_new ();
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, 0, 5,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	g_signal_connect (G_OBJECT (serv->gui->chanlist_window), "destroy",
+							G_CALLBACK (chanlist_destroy_widget), serv);
+
+	/* reset the counters. */
+	chanlist_reset_counters (serv);
+
+	serv->gui->chanlist_tag = g_timeout_add (250, (GSourceFunc)chanlist_timeout, serv);
+
+	if (do_refresh)
+		chanlist_do_refresh (serv);
+
+	chanlist_update_buttons (serv);
+	gtk_widget_show (serv->gui->chanlist_window);
+	gtk_widget_grab_focus (serv->gui->chanlist_refresh);
+}
diff --git a/src/fe-gtk/chanlist.h b/src/fe-gtk/chanlist.h
new file mode 100644
index 00000000..19a8b25e
--- /dev/null
+++ b/src/fe-gtk/chanlist.h
@@ -0,0 +1 @@
+void chanlist_opengui (server *serv, int do_refresh);
diff --git a/src/fe-gtk/chanview-tabs.c b/src/fe-gtk/chanview-tabs.c
new file mode 100644
index 00000000..8e3da8e6
--- /dev/null
+++ b/src/fe-gtk/chanview-tabs.c
@@ -0,0 +1,779 @@
+/* file included in chanview.c */
+
+typedef struct
+{
+	GtkWidget *outer;	/* outer box */
+	GtkWidget *inner;	/* inner box */
+	GtkWidget *b1;		/* button1 */
+	GtkWidget *b2;		/* button2 */
+} tabview;
+
+static void chanview_populate (chanview *cv);
+
+/* ignore "toggled" signal? */
+static int ignore_toggle = FALSE;
+static int tab_left_is_moving = 0;
+static int tab_right_is_moving = 0;
+
+/* userdata for gobjects used here:
+ *
+ * tab (togglebuttons inside boxes):
+ *   "u" userdata passed to tab-focus callback function (sess)
+ *   "c" the tab's (chan *)
+ *
+ * box (family box)
+ *   "f" family
+ *
+ */
+
+/*
+ * GtkViewports request at least as much space as their children do.
+ * If we don't intervene here, the GtkViewport will be granted its
+ * request, even at the expense of resizing the top-level window.
+ */
+static void
+cv_tabs_sizerequest (GtkWidget *viewport, GtkRequisition *requisition, chanview *cv)
+{
+	if (!cv->vertical)
+		requisition->width = 1;
+	else
+		requisition->height = 1;
+}
+
+static void
+cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv)
+{
+	GtkAdjustment *adj;
+	GtkWidget *inner;
+	gint viewport_size;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	if (adj->upper <= viewport_size)
+	{
+		gtk_widget_hide (((tabview *)cv)->b1);
+		gtk_widget_hide (((tabview *)cv)->b2);
+	} else
+	{
+		gtk_widget_show (((tabview *)cv)->b1);
+		gtk_widget_show (((tabview *)cv)->b2);
+	}
+}
+
+static gint
+tab_search_offset (GtkWidget *inner, gint start_offset,
+				   gboolean forward, gboolean vertical)
+{
+	GList *boxes;
+	GList *tabs;
+	GtkWidget *box;
+	GtkWidget *button;
+	gint found;
+
+	boxes = GTK_BOX (inner)->children;
+	if (!forward && boxes)
+		boxes = g_list_last (boxes);
+
+	while (boxes)
+	{
+		box = ((GtkBoxChild *)boxes->data)->widget;
+		boxes = (forward ? boxes->next : boxes->prev);
+
+		tabs = GTK_BOX (box)->children;
+		if (!forward && tabs)
+			tabs = g_list_last (tabs);
+
+		while (tabs)
+		{
+			button = ((GtkBoxChild *)tabs->data)->widget;
+			tabs = (forward ? tabs->next : tabs->prev);
+
+			if (!GTK_IS_TOGGLE_BUTTON (button))
+				continue;
+
+			found = (vertical ? button->allocation.y : button->allocation.x);
+			if ((forward && found > start_offset) ||
+				(!forward && found < start_offset))
+				return found;
+		}
+	}
+
+	return 0;
+}
+
+static void
+tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv)
+{
+	GtkAdjustment *adj;
+	gint viewport_size;
+	gfloat new_value;
+	GtkWidget *inner;
+	gfloat i;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	new_value = tab_search_offset (inner, adj->value, 0, cv->vertical);
+
+	if (new_value + viewport_size > adj->upper)
+		new_value = adj->upper - viewport_size;
+
+	if (!tab_left_is_moving)
+	{
+		tab_left_is_moving = 1;
+
+		for (i = adj->value; ((i > new_value) && (tab_left_is_moving)); i -= 0.1)
+		{
+			gtk_adjustment_set_value (adj, i);
+			while (g_main_pending ())
+				g_main_iteration (TRUE);
+		}
+
+		gtk_adjustment_set_value (adj, new_value);
+
+		tab_left_is_moving = 0;		/* hSP: set to false in case we didnt get stopped (the normal case) */
+	}
+	else
+	{
+		tab_left_is_moving = 0;		/* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
+	}
+}
+
+static void
+tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
+{
+	GtkAdjustment *adj;
+	gint viewport_size;
+	gfloat new_value;
+	GtkWidget *inner;
+	gfloat i;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	new_value = tab_search_offset (inner, adj->value, 1, cv->vertical);
+
+	if (new_value == 0 || new_value + viewport_size > adj->upper)
+		new_value = adj->upper - viewport_size;
+
+	if (!tab_right_is_moving)
+	{
+		tab_right_is_moving = 1;
+
+		for (i = adj->value; ((i < new_value) && (tab_right_is_moving)); i += 0.1)
+		{
+			gtk_adjustment_set_value (adj, i);
+			while (g_main_pending ())
+				g_main_iteration (TRUE);
+		}
+
+		gtk_adjustment_set_value (adj, new_value);
+
+		tab_right_is_moving = 0;		/* hSP: set to false in case we didnt get stopped (the normal case) */
+	}
+	else
+	{
+		tab_right_is_moving = 0;		/* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
+	}
+}
+
+static gboolean
+tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv)
+{
+	/* mouse wheel scrolling */
+	if (event->direction == GDK_SCROLL_UP)
+		tab_scroll_left_up_clicked (widget, cv);
+	else if (event->direction == GDK_SCROLL_DOWN)
+		tab_scroll_right_down_clicked (widget, cv);
+
+	return FALSE;
+}
+
+static void
+cv_tabs_xclick_cb (GtkWidget *button, chanview *cv)
+{
+	cv->cb_xbutton (cv, cv->focused, cv->focused->tag, cv->focused->userdata);
+}
+
+/* make a Scroll (arrow) button */
+
+static GtkWidget *
+make_sbutton (GtkArrowType type, void *click_cb, void *userdata)
+{
+	GtkWidget *button, *arrow;
+
+	button = gtk_button_new ();
+	arrow = gtk_arrow_new (type, GTK_SHADOW_NONE);
+	gtk_container_add (GTK_CONTAINER (button), arrow);
+	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+	g_signal_connect (G_OBJECT (button), "clicked",
+							G_CALLBACK (click_cb), userdata);
+	g_signal_connect (G_OBJECT (button), "scroll_event",
+							G_CALLBACK (tab_scroll_cb), userdata);
+	gtk_widget_show (arrow);
+
+	return button;
+}
+
+static void
+cv_tabs_init (chanview *cv)
+{
+	GtkWidget *box, *hbox = NULL;
+	GtkWidget *viewport;
+	GtkWidget *outer;
+	GtkWidget *button;
+
+	if (cv->vertical)
+		outer = gtk_vbox_new (0, 0);
+	else
+		outer = gtk_hbox_new (0, 0);
+	((tabview *)cv)->outer = outer;
+	g_signal_connect (G_OBJECT (outer), "size_allocate",
+							G_CALLBACK (cv_tabs_sizealloc), cv);
+/*	gtk_container_set_border_width (GTK_CONTAINER (outer), 2);*/
+	gtk_widget_show (outer);
+
+	viewport = gtk_viewport_new (0, 0);
+	gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+	g_signal_connect (G_OBJECT (viewport), "size_request",
+							G_CALLBACK (cv_tabs_sizerequest), cv);
+	g_signal_connect (G_OBJECT (viewport), "scroll_event",
+							G_CALLBACK (tab_scroll_cb), cv);
+	gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0);
+	gtk_widget_show (viewport);
+
+	if (cv->vertical)
+		box = gtk_vbox_new (FALSE, 0);
+	else
+		box = gtk_hbox_new (FALSE, 0);
+	((tabview *)cv)->inner = box;
+	gtk_container_add (GTK_CONTAINER (viewport), box);
+	gtk_widget_show (box);
+
+	/* if vertical, the buttons can be side by side */
+	if (cv->vertical)
+	{
+		hbox = gtk_hbox_new (FALSE, 0);
+		gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0);
+		gtk_widget_show (hbox);
+	}
+
+	/* make the Scroll buttons */
+	((tabview *)cv)->b2 = make_sbutton (cv->vertical ?
+													GTK_ARROW_UP : GTK_ARROW_LEFT,
+													tab_scroll_left_up_clicked,
+													cv);
+
+	((tabview *)cv)->b1 = make_sbutton (cv->vertical ?
+													GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
+													tab_scroll_right_down_clicked,
+													cv);
+
+	if (hbox)
+	{
+		gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2);
+		gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1);
+	} else
+	{
+		gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0);
+		gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0);
+	}
+
+	button = gtkutil_button (outer, GTK_STOCK_CLOSE, NULL, cv_tabs_xclick_cb,
+									 cv, 0);
+	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+	GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
+
+	gtk_container_add (GTK_CONTAINER (cv->box), outer);
+}
+
+static void
+cv_tabs_postinit (chanview *cv)
+{
+}
+
+static void
+tab_add_sorted (chanview *cv, GtkWidget *box, GtkWidget *tab, chan *ch)
+{
+	GList *list;
+	GtkBoxChild *child;
+	int i = 0;
+	void *b;
+
+	if (!cv->sorted)
+	{
+		gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+		gtk_widget_show (tab);
+		return;
+	}
+
+	/* sorting TODO:
+    *   - move tab if renamed (dialogs) */
+
+	/* userdata, passed to mg_tabs_compare() */
+	b = ch->userdata;
+
+	list = GTK_BOX (box)->children;
+	while (list)
+	{
+		child = list->data;
+		if (!GTK_IS_SEPARATOR (child->widget))
+		{
+			void *a = g_object_get_data (G_OBJECT (child->widget), "u");
+
+			if (ch->tag == 0 && cv->cb_compare (a, b) > 0)
+			{
+				gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+				gtk_box_reorder_child (GTK_BOX (box), tab, i);
+				gtk_widget_show (tab);
+				return;
+			}
+		}
+		i++;
+		list = list->next;
+	}
+
+	/* append */
+	gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (box), tab, i);
+	gtk_widget_show (tab);
+}
+
+/* remove empty boxes and separators */
+
+static void
+cv_tabs_prune (chanview *cv)
+{
+	GList *boxes, *children;
+	GtkWidget *box, *inner;
+	GtkBoxChild *child;
+	int empty;
+
+	inner = ((tabview *)cv)->inner;
+	boxes = GTK_BOX (inner)->children;
+	while (boxes)
+	{
+		child = boxes->data;
+		box = child->widget;
+		boxes = boxes->next;
+
+		/* check if the box is empty (except a vseperator) */
+		empty = TRUE;
+		children = GTK_BOX (box)->children;
+		while (children)
+		{
+			if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
+			{
+				empty = FALSE;
+				break;
+			}
+			children = children->next;
+		}
+
+		if (empty)
+			gtk_widget_destroy (box);
+	}
+}
+
+static void
+tab_add_real (chanview *cv, GtkWidget *tab, chan *ch)
+{
+	GList *boxes, *children;
+	GtkWidget *sep, *box, *inner;
+	GtkBoxChild *child;
+	int empty;
+
+	inner = ((tabview *)cv)->inner;
+	/* see if a family for this tab already exists */
+	boxes = GTK_BOX (inner)->children;
+	while (boxes)
+	{
+		child = boxes->data;
+		box = child->widget;
+
+		if (g_object_get_data (G_OBJECT (box), "f") == ch->family)
+		{
+			tab_add_sorted (cv, box, tab, ch);
+			gtk_widget_queue_resize (inner->parent);
+			return;
+		}
+
+		boxes = boxes->next;
+
+		/* check if the box is empty (except a vseperator) */
+		empty = TRUE;
+		children = GTK_BOX (box)->children;
+		while (children)
+		{
+			if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
+			{
+				empty = FALSE;
+				break;
+			}
+			children = children->next;
+		}
+
+		if (empty)
+			gtk_widget_destroy (box);
+	}
+
+	/* create a new family box */
+	if (cv->vertical)
+	{
+		/* vertical */
+		box = gtk_vbox_new (FALSE, 0);
+		sep = gtk_hseparator_new ();
+	} else
+	{
+		/* horiz */
+		box = gtk_hbox_new (FALSE, 0);
+		sep = gtk_vseparator_new ();
+	}
+
+	gtk_box_pack_end (GTK_BOX (box), sep, 0, 0, 4);
+	gtk_widget_show (sep);
+	gtk_box_pack_start (GTK_BOX (inner), box, 0, 0, 0);
+	g_object_set_data (G_OBJECT (box), "f", ch->family);
+	gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+	gtk_widget_show (tab);
+	gtk_widget_show (box);
+	gtk_widget_queue_resize (inner->parent);
+}
+
+static gboolean
+tab_ignore_cb (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+	return TRUE;
+}
+
+/* called when a tab is clicked (button down) */
+
+static void
+tab_pressed_cb (GtkToggleButton *tab, chan *ch)
+{
+	chan *old_tab;
+	int is_switching = TRUE;
+	chanview *cv = ch->cv;
+
+	ignore_toggle = TRUE;
+	/* de-activate the old tab */
+	old_tab = cv->focused;
+	if (old_tab && old_tab->impl)
+	{
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_tab->impl), FALSE);
+		if (old_tab == ch)
+			is_switching = FALSE;
+	}
+	gtk_toggle_button_set_active (tab, TRUE);
+	ignore_toggle = FALSE;
+	cv->focused = ch;
+
+	if (/*tab->active*/is_switching)
+		/* call the focus callback */
+		cv->cb_focus (cv, ch, ch->tag, ch->userdata);
+}
+
+/* called for keyboard tab toggles only */
+static void
+tab_toggled_cb (GtkToggleButton *tab, chan *ch)
+{
+	if (ignore_toggle)
+		return;
+
+	/* activated a tab via keyboard */
+	tab_pressed_cb (tab, ch);
+}
+
+static gboolean
+tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch)
+{
+	return ch->cv->cb_contextmenu (ch->cv, ch, ch->tag, ch->userdata, event);
+}
+
+static void *
+cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
+{
+	GtkWidget *but;
+
+	but = gtk_toggle_button_new_with_label (name);
+	gtk_widget_set_name (but, "xchat-tab");
+	g_object_set_data (G_OBJECT (but), "c", ch);
+	/* used to trap right-clicks */
+	g_signal_connect (G_OBJECT (but), "button_press_event",
+						 	G_CALLBACK (tab_click_cb), ch);
+	/* avoid prelights */
+	g_signal_connect (G_OBJECT (but), "enter_notify_event",
+						 	G_CALLBACK (tab_ignore_cb), NULL);
+	g_signal_connect (G_OBJECT (but), "leave_notify_event",
+						 	G_CALLBACK (tab_ignore_cb), NULL);
+	g_signal_connect (G_OBJECT (but), "pressed",
+							G_CALLBACK (tab_pressed_cb), ch);
+	/* for keyboard */
+	g_signal_connect (G_OBJECT (but), "toggled",
+						 	G_CALLBACK (tab_toggled_cb), ch);
+	g_object_set_data (G_OBJECT (but), "u", ch->userdata);
+
+	tab_add_real (cv, but, ch);
+
+	return but;
+}
+
+/* traverse all the family boxes of tabs 
+ *
+ * A "group" is basically:
+ * GtkV/HBox
+ * `-GtkViewPort
+ *   `-GtkV/HBox (inner box)
+ *     `- GtkBox (family box)
+ *        `- GtkToggleButton
+ *        `- GtkToggleButton
+ *        `- ...
+ *     `- GtkBox
+ *        `- GtkToggleButton
+ *        `- GtkToggleButton
+ *        `- ...
+ *     `- ...
+ *
+ * */
+
+static int
+tab_group_for_each_tab (chanview *cv,
+								int (*callback) (GtkWidget *tab, int num, int usernum),
+								int usernum)
+{
+	GList *tabs;
+	GList *boxes;
+	GtkBoxChild *child;
+	GtkBox *innerbox;
+	int i;
+
+	innerbox = (GtkBox *) ((tabview *)cv)->inner;
+	boxes = innerbox->children;
+	i = 0;
+	while (boxes)
+	{
+		child = boxes->data;
+		tabs = GTK_BOX (child->widget)->children;
+
+		while (tabs)
+		{
+			child = tabs->data;
+
+			if (!GTK_IS_SEPARATOR (child->widget))
+			{
+				if (callback (child->widget, i, usernum) != -1)
+					return i;
+				i++;
+			}
+			tabs = tabs->next;
+		}
+
+		boxes = boxes->next;
+	}
+
+	return i;
+}
+
+static int
+tab_check_focus_cb (GtkWidget *tab, int num, int unused)
+{
+	if (GTK_TOGGLE_BUTTON (tab)->active)
+		return num;
+
+	return -1;
+}
+
+/* returns the currently focused tab number */
+
+static int
+tab_group_get_cur_page (chanview *cv)
+{
+	return tab_group_for_each_tab (cv, tab_check_focus_cb, 0);
+}
+
+static void
+cv_tabs_focus (chan *ch)
+{
+	if (ch->impl)
+	/* focus the new one (tab_pressed_cb defocuses the old one) */
+		tab_pressed_cb (GTK_TOGGLE_BUTTON (ch->impl), ch);
+}
+
+static int
+tab_focus_num_cb (GtkWidget *tab, int num, int want)
+{
+	if (num == want)
+	{
+		cv_tabs_focus (g_object_get_data (G_OBJECT (tab), "c"));
+		return 1;
+	}
+
+	return -1;
+}
+
+static void
+cv_tabs_change_orientation (chanview *cv)
+{
+	/* cleanup the old one */
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	/* now rebuild a new tabbar or tree */
+	cv->func_init (cv);
+	chanview_populate (cv);
+}
+
+/* switch to the tab number specified */
+
+static void
+cv_tabs_move_focus (chanview *cv, gboolean relative, int num)
+{
+	int i, max;
+
+	if (relative)
+	{
+		max = cv->size;
+		i = tab_group_get_cur_page (cv) + num;
+		/* make it wrap around at both ends */
+		if (i < 0)
+			i = max - 1;
+		if (i >= max)
+			i = 0;
+		tab_group_for_each_tab (cv, tab_focus_num_cb, i);
+		return;
+	}
+
+	tab_group_for_each_tab (cv, tab_focus_num_cb, num);
+}
+
+static void
+cv_tabs_remove (chan *ch)
+{
+	gtk_widget_destroy (ch->impl);
+	ch->impl = NULL;
+
+	cv_tabs_prune (ch->cv);
+}
+
+static void
+cv_tabs_move (chan *ch, int delta)
+{
+	int i, pos = 0;
+	GList *list;
+	GtkWidget *parent = ((GtkWidget *)ch->impl)->parent;
+
+	i = 0;
+	for (list = GTK_BOX (parent)->children; list; list = list->next)
+	{
+		GtkBoxChild *child_entry;
+
+		child_entry = list->data;
+		if (child_entry->widget == ch->impl)
+			pos = i;
+		i++;
+	}
+
+	pos = (pos - delta) % i;
+	gtk_box_reorder_child (GTK_BOX (parent), ch->impl, pos);
+}
+
+static void
+cv_tabs_move_family (chan *ch, int delta)
+{
+	int i, pos = 0;
+	GList *list;
+	GtkWidget *box = NULL;
+
+	/* find position of tab's family */
+	i = 0;
+	for (list = GTK_BOX (((tabview *)ch->cv)->inner)->children; list; list = list->next)
+	{
+		GtkBoxChild *child_entry;
+		void *fam;
+
+		child_entry = list->data;
+		fam = g_object_get_data (G_OBJECT (child_entry->widget), "f");
+		if (fam == ch->family)
+		{
+			box = child_entry->widget;
+			pos = i;
+		}
+		i++;
+	}
+
+	pos = (pos - delta) % i;
+	gtk_box_reorder_child (GTK_BOX (box->parent), box, pos);
+}
+
+static void
+cv_tabs_cleanup (chanview *cv)
+{
+	if (cv->box)
+		gtk_widget_destroy (((tabview *)cv)->outer);
+}
+
+static void
+cv_tabs_set_color (chan *ch, PangoAttrList *list)
+{
+	gtk_label_set_attributes (GTK_LABEL (GTK_BIN (ch->impl)->child), list);
+}
+
+static void
+cv_tabs_rename (chan *ch, char *name)
+{
+	PangoAttrList *attr;
+	GtkWidget *tab = ch->impl;
+
+	attr = gtk_label_get_attributes (GTK_LABEL (GTK_BIN (tab)->child));
+	if (attr)
+		pango_attr_list_ref (attr);
+
+	gtk_button_set_label (GTK_BUTTON (tab), name);
+	gtk_widget_queue_resize (tab->parent->parent->parent);
+
+	if (attr)
+	{
+		gtk_label_set_attributes (GTK_LABEL (GTK_BIN (tab)->child), attr);
+		pango_attr_list_unref (attr);
+	}
+}
+
+static gboolean
+cv_tabs_is_collapsed (chan *ch)
+{
+	return FALSE;
+}
+
+static chan *
+cv_tabs_get_parent (chan *ch)
+{
+	return NULL;
+}
diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c
new file mode 100644
index 00000000..5373f21b
--- /dev/null
+++ b/src/fe-gtk/chanview-tree.c
@@ -0,0 +1,364 @@
+/* file included in chanview.c */
+
+typedef struct
+{
+	GtkTreeView *tree;
+	GtkWidget *scrollw;	/* scrolledWindow */
+} treeview;
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "fe-gtk.h"
+#include "maingui.h"
+
+#include <gdk/gdk.h>
+#include <gtk/gtktreeview.h>
+
+static void 	/* row-activated, when a row is double clicked */
+cv_tree_activated_cb (GtkTreeView *view, GtkTreePath *path,
+							 GtkTreeViewColumn *column, gpointer data)
+{
+	if (gtk_tree_view_row_expanded (view, path))
+		gtk_tree_view_collapse_row (view, path);
+	else
+		gtk_tree_view_expand_row (view, path, FALSE);
+}
+
+static void		/* row selected callback */
+cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	chan *ch;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1);
+
+		cv->focused = ch;
+		cv->cb_focus (cv, ch, ch->tag, ch->userdata);
+	}
+}
+
+static gboolean
+cv_tree_click_cb (GtkTreeView *tree, GdkEventButton *event, chanview *cv)
+{
+	chan *ch;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	int ret = FALSE;
+
+	if (event->button != 3 && event->state == 0)
+		return FALSE;
+
+	sel = gtk_tree_view_get_selection (tree);
+	if (gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0))
+	{
+		if (event->button == 2)
+		{
+			gtk_tree_selection_unselect_all (sel);
+			gtk_tree_selection_select_path (sel, path);
+		}
+		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (cv->store), &iter, path))
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			ret = cv->cb_contextmenu (cv, ch, ch->tag, ch->userdata, event);
+		}
+		gtk_tree_path_free (path);
+	}
+	return ret;
+}
+
+static void
+cv_tree_init (chanview *cv)
+{
+	GtkWidget *view, *win;
+	GtkCellRenderer *renderer;
+	static const GtkTargetEntry dnd_src_target[] =
+	{
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }
+	};
+	static const GtkTargetEntry dnd_dest_target[] =
+	{
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	win = gtk_scrolled_window_new (0, 0);
+	/*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win),
+													 GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (cv->box), win);
+	gtk_widget_show (win);
+
+	view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
+	gtk_widget_set_name (view, "xchat-tree");
+	if (cv->style)
+		gtk_widget_set_style (view, cv->style);
+	/*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/
+	GTK_WIDGET_UNSET_FLAGS (view, GTK_CAN_FOCUS);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
+#if GTK_CHECK_VERSION(2,10,0)
+	if (!(prefs.gui_tweaks & 8))
+		gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (view), TRUE);
+#endif
+	gtk_container_add (GTK_CONTAINER (win), view);
+
+	/* icon column */
+	if (cv->use_icons)
+	{
+		renderer = gtk_cell_renderer_pixbuf_new ();
+		if (prefs.gui_tweaks & 32)
+			g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+		gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+																	-1, NULL, renderer,
+																	"pixbuf", COL_PIXBUF, NULL);
+	}
+
+	/* main column */
+	renderer = gtk_cell_renderer_text_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+																-1, NULL, renderer,
+									"text", COL_NAME, "attributes", COL_ATTR, NULL);
+
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
+							"changed", G_CALLBACK (cv_tree_sel_cb), cv);
+	g_signal_connect (G_OBJECT (view), "button-press-event",
+							G_CALLBACK (cv_tree_click_cb), cv);
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (cv_tree_activated_cb), NULL);
+
+	gtk_drag_dest_set (view, GTK_DEST_DEFAULT_ALL, dnd_dest_target, 1,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	gtk_drag_source_set (view, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_COPY);
+
+#ifndef WIN32
+	g_signal_connect (G_OBJECT (view), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+#endif
+
+	((treeview *)cv)->tree = GTK_TREE_VIEW (view);
+	((treeview *)cv)->scrollw = win;
+	gtk_widget_show (view);
+}
+
+static void
+cv_tree_postinit (chanview *cv)
+{
+	gtk_tree_view_expand_all (((treeview *)cv)->tree);
+}
+
+static void *
+cv_tree_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
+{
+	GtkTreePath *path;
+
+	if (parent)
+	{
+		/* expand the parent node */
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (cv->store), parent);
+		if (path)
+		{
+			gtk_tree_view_expand_row (((treeview *)cv)->tree, path, FALSE);
+			gtk_tree_path_free (path);
+		}
+	}
+
+	return NULL;
+}
+
+static void
+cv_tree_change_orientation (chanview *cv)
+{
+}
+
+static void
+cv_tree_focus (chan *ch)
+{
+	GtkTreeView *tree = ((treeview *)ch->cv)->tree;
+	GtkTreeModel *model = gtk_tree_view_get_model (tree);
+	GtkTreePath *path;
+	GtkTreeIter parent;
+	GdkRectangle cell_rect;
+	GdkRectangle vis_rect;
+	gint dest_y;
+
+	/* expand the parent node */
+	if (gtk_tree_model_iter_parent (model, &parent, &ch->iter))
+	{
+		path = gtk_tree_model_get_path (model, &parent);
+		if (path)
+		{
+			/*if (!gtk_tree_view_row_expanded (tree, path))
+			{
+				gtk_tree_path_free (path);
+				return;
+			}*/
+			gtk_tree_view_expand_row (tree, path, FALSE);
+			gtk_tree_path_free (path);
+		}
+	}
+
+	path = gtk_tree_model_get_path (model, &ch->iter);
+	if (path)
+	{
+		/* This full section does what
+		 * gtk_tree_view_scroll_to_cell (tree, path, NULL, TRUE, 0.5, 0.5);
+		 * does, except it only scrolls the window if the provided cell is
+		 * not visible. Basic algorithm taken from gtktreeview.c */
+
+		/* obtain information to see if the cell is visible */
+		gtk_tree_view_get_background_area (tree, path, NULL, &cell_rect);
+		gtk_tree_view_get_visible_rect (tree, &vis_rect);
+
+		/* The cordinates aren't offset correctly */
+		gtk_tree_view_widget_to_tree_coords( tree, cell_rect.x, cell_rect.y, NULL, &cell_rect.y );
+
+		/* only need to scroll if out of bounds */
+		if (cell_rect.y < vis_rect.y ||
+				cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height)
+		{
+			dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * 0.5);
+			if (dest_y < 0)
+				dest_y = 0;
+			gtk_tree_view_scroll_to_point (tree, -1, dest_y);
+		}
+		/* theft done, now make it focused like */
+		gtk_tree_view_set_cursor (tree, path, NULL, FALSE);
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+cv_tree_move_focus (chanview *cv, gboolean relative, int num)
+{
+	chan *ch;
+
+	if (relative)
+	{
+		num += cv_find_number_of_chan (cv, cv->focused);
+		num %= cv->size;
+		/* make it wrap around at both ends */
+		if (num < 0)
+			num = cv->size - 1;
+	}
+
+	ch = cv_find_chan_by_number (cv, num);
+	if (ch)
+		cv_tree_focus (ch);
+}
+
+static void
+cv_tree_remove (chan *ch)
+{
+}
+
+static void
+move_row (chan *ch, int delta, GtkTreeIter *parent)
+{
+	GtkTreeStore *store = ch->cv->store;
+	GtkTreeIter *src = &ch->iter;
+	GtkTreeIter dest = ch->iter;
+	GtkTreePath *dest_path;
+
+	if (delta < 0) /* down */
+	{
+		if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &dest))
+			gtk_tree_store_swap (store, src, &dest);
+		else	/* move to top */
+			gtk_tree_store_move_after (store, src, NULL);
+
+	} else
+	{
+		dest_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &dest);
+		if (gtk_tree_path_prev (dest_path))
+		{
+			gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &dest, dest_path);
+			gtk_tree_store_swap (store, src, &dest);
+		} else
+		{	/* move to bottom */
+			gtk_tree_store_move_before (store, src, NULL);
+		}
+
+		gtk_tree_path_free (dest_path);
+	}
+}
+
+static void
+cv_tree_move (chan *ch, int delta)
+{
+	GtkTreeIter parent;
+
+	/* do nothing if this is a server row */
+	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter))
+		move_row (ch, delta, &parent);
+}
+
+static void
+cv_tree_move_family (chan *ch, int delta)
+{
+	move_row (ch, delta, NULL);
+}
+
+static void
+cv_tree_cleanup (chanview *cv)
+{
+	if (cv->box)
+		/* kill the scrolled window */
+		gtk_widget_destroy (((treeview *)cv)->scrollw);
+}
+
+static void
+cv_tree_set_color (chan *ch, PangoAttrList *list)
+{
+	/* nothing to do, it's already set in the store */
+}
+
+static void
+cv_tree_rename (chan *ch, char *name)
+{
+	/* nothing to do, it's already renamed in the store */
+}
+
+static chan *
+cv_tree_get_parent (chan *ch)
+{
+	chan *parent_ch = NULL;
+	GtkTreeIter parent;
+
+	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter))
+	{
+		gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &parent, COL_CHAN, &parent_ch, -1);
+	}
+
+	return parent_ch;
+}
+
+static gboolean
+cv_tree_is_collapsed (chan *ch)
+{
+	chan *parent = cv_tree_get_parent (ch);
+	GtkTreePath *path = NULL;
+	gboolean ret;
+
+	if (parent == NULL)
+		return FALSE;
+
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (parent->cv->store),
+											  &parent->iter);
+	ret = !gtk_tree_view_row_expanded (((treeview *)parent->cv)->tree, path);
+	gtk_tree_path_free (path);
+	
+	return ret;
+}
diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c
new file mode 100644
index 00000000..e90c4df8
--- /dev/null
+++ b/src/fe-gtk/chanview.c
@@ -0,0 +1,643 @@
+/* abstract channel view: tabs or tree or anything you like */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "chanview.h"
+#include "gtkutil.h"
+
+
+/* treeStore columns */
+
+#define COL_NAME 0		/* (char *) */
+#define COL_CHAN 1		/* (chan *) */
+#define COL_ATTR 2		/* (PangoAttrList *) */
+#define COL_PIXBUF 3		/* (GdkPixbuf *) */
+
+struct _chanview
+{
+	/* impl scratch area */
+	char implscratch[sizeof (void *) * 8];
+
+	GtkTreeStore *store;
+	int size;			/* number of channels in view */
+
+	GtkWidget *box;	/* the box we destroy when changing implementations */
+	GtkStyle *style;	/* style used for tree */
+	chan *focused;		/* currently focused channel */
+	int trunc_len;
+
+	/* callbacks */
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata);
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata);
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *);
+	int (*cb_compare) (void *a, void *b);
+
+	/* impl */
+	void (*func_init) (chanview *);
+	void (*func_postinit) (chanview *);
+	void *(*func_add) (chanview *, chan *, char *, GtkTreeIter *);
+	void (*func_move_focus) (chanview *, gboolean, int);
+	void (*func_change_orientation) (chanview *);
+	void (*func_remove) (chan *);
+	void (*func_move) (chan *, int delta);
+	void (*func_move_family) (chan *, int delta);
+	void (*func_focus) (chan *);
+	void (*func_set_color) (chan *, PangoAttrList *);
+	void (*func_rename) (chan *, char *);
+	gboolean (*func_is_collapsed) (chan *);
+	chan *(*func_get_parent) (chan *);
+	void (*func_cleanup) (chanview *);
+
+	unsigned int sorted:1;
+	unsigned int vertical:1;
+	unsigned int use_icons:1;
+};
+
+struct _chan
+{
+	chanview *cv;	/* our owner */
+	GtkTreeIter iter;
+	void *userdata;	/* session * */
+	void *family;		/* server * or null */
+	void *impl;	/* togglebutton or null */
+	GdkPixbuf *icon;
+	short allow_closure;	/* allow it to be closed when it still has children? */
+	short tag;
+};
+
+static chan *cv_find_chan_by_number (chanview *cv, int num);
+static int cv_find_number_of_chan (chanview *cv, chan *find_ch);
+
+
+/* ======= TABS ======= */
+
+#include "chanview-tabs.c"
+
+
+/* ======= TREE ======= */
+
+#include "chanview-tree.c"
+
+
+/* ==== ABSTRACT CHANVIEW ==== */
+
+static char *
+truncate_tab_name (char *name, int max)
+{
+	char *buf;
+
+	if (max > 2 && g_utf8_strlen (name, -1) > max)
+	{
+		/* truncate long channel names */
+		buf = malloc (strlen (name) + 4);
+		strcpy (buf, name);
+		g_utf8_offset_to_pointer (buf, max)[0] = 0;
+		strcat (buf, "..");
+		return buf;
+	}
+
+	return name;
+}
+
+/* iterate through a model, into 1 depth of children */
+
+static void
+model_foreach_1 (GtkTreeModel *model, void (*func)(void *, GtkTreeIter *),
+					  void *userdata)
+{
+	GtkTreeIter iter, inner;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			func (userdata, &iter);
+			if (gtk_tree_model_iter_children (model, &inner, &iter))
+			{
+				do
+					func (userdata, &inner);
+				while (gtk_tree_model_iter_next (model, &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+static void
+chanview_pop_cb (chanview *cv, GtkTreeIter *iter)
+{
+	chan *ch;
+	char *name;
+	PangoAttrList *attr;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter,
+							  COL_NAME, &name, COL_CHAN, &ch, COL_ATTR, &attr, -1);
+	ch->impl = cv->func_add (cv, ch, name, NULL);
+	if (attr)
+	{
+		cv->func_set_color (ch, attr);
+		pango_attr_list_unref (attr);
+	}
+	g_free (name);
+}
+
+static void
+chanview_populate (chanview *cv)
+{
+	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_pop_cb, cv);
+}
+
+void
+chanview_set_impl (chanview *cv, int type)
+{
+	/* cleanup the old one */
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	switch (type)
+	{
+	case 0:
+		cv->func_init = cv_tabs_init;
+		cv->func_postinit = cv_tabs_postinit;
+		cv->func_add = cv_tabs_add;
+		cv->func_move_focus = cv_tabs_move_focus;
+		cv->func_change_orientation = cv_tabs_change_orientation;
+		cv->func_remove = cv_tabs_remove;
+		cv->func_move = cv_tabs_move;
+		cv->func_move_family = cv_tabs_move_family;
+		cv->func_focus = cv_tabs_focus;
+		cv->func_set_color = cv_tabs_set_color;
+		cv->func_rename = cv_tabs_rename;
+		cv->func_is_collapsed = cv_tabs_is_collapsed;
+		cv->func_get_parent = cv_tabs_get_parent;
+		cv->func_cleanup = cv_tabs_cleanup;
+		break;
+
+	default:
+		cv->func_init = cv_tree_init;
+		cv->func_postinit = cv_tree_postinit;
+		cv->func_add = cv_tree_add;
+		cv->func_move_focus = cv_tree_move_focus;
+		cv->func_change_orientation = cv_tree_change_orientation;
+		cv->func_remove = cv_tree_remove;
+		cv->func_move = cv_tree_move;
+		cv->func_move_family = cv_tree_move_family;
+		cv->func_focus = cv_tree_focus;
+		cv->func_set_color = cv_tree_set_color;
+		cv->func_rename = cv_tree_rename;
+		cv->func_is_collapsed = cv_tree_is_collapsed;
+		cv->func_get_parent = cv_tree_get_parent;
+		cv->func_cleanup = cv_tree_cleanup;
+		break;
+	}
+
+	/* now rebuild a new tabbar or tree */
+	cv->func_init (cv);
+
+	chanview_populate (cv);
+
+	cv->func_postinit (cv);
+
+	/* force re-focus */
+	if (cv->focused)
+		cv->func_focus (cv->focused);
+}
+
+static void
+chanview_free_ch (chanview *cv, GtkTreeIter *iter)
+{
+	chan *ch;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, COL_CHAN, &ch, -1);
+	free (ch);
+}
+
+static void
+chanview_destroy_store (chanview *cv)	/* free every (chan *) in the store */
+{
+	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_free_ch, cv);
+	g_object_unref (cv->store);
+}
+
+static void
+chanview_destroy (chanview *cv)
+{
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	if (cv->box)
+		gtk_widget_destroy (cv->box);
+
+	chanview_destroy_store (cv);
+	free (cv);
+}
+
+static void
+chanview_box_destroy_cb (GtkWidget *box, chanview *cv)
+{
+	cv->box = NULL;
+	chanview_destroy (cv);
+}
+
+chanview *
+chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
+				  GtkStyle *style)
+{
+	chanview *cv;
+
+	cv = calloc (1, sizeof (chanview));
+	cv->store = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_POINTER,
+											  PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF);
+	cv->style = style;
+	cv->box = gtk_hbox_new (0, 0);
+	cv->trunc_len = trunc_len;
+	cv->sorted = sort;
+	cv->use_icons = use_icons;
+	gtk_widget_show (cv->box);
+	chanview_set_impl (cv, type);
+
+	g_signal_connect (G_OBJECT (cv->box), "destroy",
+							G_CALLBACK (chanview_box_destroy_cb), cv);
+
+	return cv;
+}
+
+/* too lazy for signals */
+
+void
+chanview_set_callbacks (chanview *cv,
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata),
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata),
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
+	int (*cb_compare) (void *a, void *b))
+{
+	cv->cb_focus = cb_focus;
+	cv->cb_xbutton = cb_xbutton;
+	cv->cb_contextmenu = cb_contextmenu;
+	cv->cb_compare = cb_compare;
+}
+
+/* find a place to insert this new entry, based on the compare function */
+
+static void
+chanview_insert_sorted (chanview *cv, GtkTreeIter *add_iter, GtkTreeIter *parent, void *ud)
+{
+	GtkTreeIter iter;
+	chan *ch;
+
+	if (cv->sorted && gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &iter, parent))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			if (ch->tag == 0 && cv->cb_compare (ch->userdata, ud) > 0)
+			{
+				gtk_tree_store_insert_before (cv->store, add_iter, parent, &iter);
+				return;
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	gtk_tree_store_append (cv->store, add_iter, parent);
+}
+
+/* find a parent node with the same "family" pointer (i.e. the Server tab) */
+
+static int
+chanview_find_parent (chanview *cv, void *family, GtkTreeIter *search_iter, chan *avoid)
+{
+	chan *search_ch;
+
+	/* find this new row's parent, if any */
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), search_iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), search_iter, 
+									  COL_CHAN, &search_ch, -1);
+			if (family == search_ch->family && search_ch != avoid /*&&
+				 gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/)
+				return TRUE;
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), search_iter));
+	}
+
+	return FALSE;
+}
+
+static chan *
+chanview_add_real (chanview *cv, char *name, void *family, void *userdata,
+						 gboolean allow_closure, int tag, GdkPixbuf *icon,
+						 chan *ch, chan *avoid)
+{
+	GtkTreeIter parent_iter;
+	GtkTreeIter iter;
+	gboolean has_parent = FALSE;
+
+	if (chanview_find_parent (cv, family, &parent_iter, avoid))
+	{
+		chanview_insert_sorted (cv, &iter, &parent_iter, userdata);
+		has_parent = TRUE;
+	} else
+	{
+		gtk_tree_store_append (cv->store, &iter, NULL);
+	}
+
+	if (!ch)
+	{
+		ch = calloc (1, sizeof (chan));
+		ch->userdata = userdata;
+		ch->family = family;
+		ch->cv = cv;
+		ch->allow_closure = allow_closure;
+		ch->tag = tag;
+		ch->icon = icon;
+	}
+	memcpy (&(ch->iter), &iter, sizeof (iter));
+
+	gtk_tree_store_set (cv->store, &iter, COL_NAME, name, COL_CHAN, ch,
+							  COL_PIXBUF, icon, -1);
+
+	cv->size++;
+	if (!has_parent)
+		ch->impl = cv->func_add (cv, ch, name, NULL);
+	else
+		ch->impl = cv->func_add (cv, ch, name, &parent_iter);
+
+	return ch;
+}
+
+chan *
+chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon)
+{
+	char *new_name;
+	chan *ret;
+
+	new_name = truncate_tab_name (name, cv->trunc_len);
+
+	ret = chanview_add_real (cv, new_name, family, userdata, allow_closure, tag, icon, NULL, NULL);
+
+	if (new_name != name)
+		free (new_name);
+
+	return ret;
+}
+
+int
+chanview_get_size (chanview *cv)
+{
+	return cv->size;
+}
+
+GtkWidget *
+chanview_get_box (chanview *cv)
+{
+	return cv->box;
+}
+
+void
+chanview_move_focus (chanview *cv, gboolean relative, int num)
+{
+	cv->func_move_focus (cv, relative, num);
+}
+
+GtkOrientation
+chanview_get_orientation (chanview *cv)
+{
+	return (cv->vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
+}
+
+void
+chanview_set_orientation (chanview *cv, gboolean vertical)
+{
+	if (vertical != cv->vertical)
+	{
+		cv->vertical = vertical;
+		cv->func_change_orientation (cv);
+	}
+}
+
+int
+chan_get_tag (chan *ch)
+{
+	return ch->tag;
+}
+
+void *
+chan_get_userdata (chan *ch)
+{
+	return ch->userdata;
+}
+
+void
+chan_focus (chan *ch)
+{
+	if (ch->cv->focused == ch)
+		return;
+
+	ch->cv->func_focus (ch);
+}
+
+void
+chan_move (chan *ch, int delta)
+{
+	ch->cv->func_move (ch, delta);
+}
+
+void
+chan_move_family (chan *ch, int delta)
+{
+	ch->cv->func_move_family (ch, delta);
+}
+
+void
+chan_set_color (chan *ch, PangoAttrList *list)
+{
+	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_ATTR, list, -1);	
+	ch->cv->func_set_color (ch, list);
+}
+
+void
+chan_rename (chan *ch, char *name, int trunc_len)
+{
+	char *new_name;
+
+	new_name = truncate_tab_name (name, trunc_len);
+
+	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_NAME, new_name, -1);
+	ch->cv->func_rename (ch, new_name);
+	ch->cv->trunc_len = trunc_len;
+
+	if (new_name != name)
+		free (new_name);
+}
+
+/* this thing is overly complicated */
+
+static int
+cv_find_number_of_chan (chanview *cv, chan *find_ch)
+{
+	GtkTreeIter iter, inner;
+	chan *ch;
+	int i = 0;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			if (ch == find_ch)
+				return i;
+			i++;
+
+			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
+			{
+				do
+				{
+					gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
+					if (ch == find_ch)
+						return i;
+					i++;
+				}
+				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	return 0;	/* WARNING */
+}
+
+/* this thing is overly complicated too */
+
+static chan *
+cv_find_chan_by_number (chanview *cv, int num)
+{
+	GtkTreeIter iter, inner;
+	chan *ch;
+	int i = 0;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
+	{
+		do
+		{
+			if (i == num)
+			{
+				gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+				return ch;
+			}
+			i++;
+
+			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
+			{
+				do
+				{
+					if (i == num)
+					{
+						gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
+						return ch;
+					}
+					i++;
+				}
+				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	return NULL;
+}
+
+static void
+chan_emancipate_children (chan *ch)
+{
+	char *name;
+	chan *childch;
+	GtkTreeIter childiter;
+	PangoAttrList *attr;
+
+	while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch->cv->store), &childiter, &ch->iter))
+	{
+		/* remove and re-add all the children, but avoid using "ch" as parent */
+		gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &childiter,
+								  COL_NAME, &name, COL_CHAN, &childch, COL_ATTR, &attr, -1);
+		ch->cv->func_remove (childch);
+		gtk_tree_store_remove (ch->cv->store, &childiter);
+		ch->cv->size--;
+		chanview_add_real (childch->cv, name, childch->family, childch->userdata, childch->allow_closure, childch->tag, childch->icon, childch, ch);
+		if (attr)
+		{
+			childch->cv->func_set_color (childch, attr);
+			pango_attr_list_unref (attr);
+		}
+		g_free (name);
+	}
+}
+
+gboolean
+chan_remove (chan *ch, gboolean force)
+{
+	chan *new_ch;
+	int i, num;
+	extern int xchat_is_quitting;
+
+	if (xchat_is_quitting)	/* avoid lots of looping on exit */
+		return TRUE;
+
+	/* is this ch allowed to be closed while still having children? */
+	if (!force &&
+		 gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch->cv->store), &ch->iter) &&
+		 !ch->allow_closure)
+		return FALSE;
+
+	chan_emancipate_children (ch);
+	ch->cv->func_remove (ch);
+
+	/* is it the focused one? */
+	if (ch->cv->focused == ch)
+	{
+		ch->cv->focused = NULL;
+
+		/* try to move the focus to some other valid channel */
+		num = cv_find_number_of_chan (ch->cv, ch);
+		/* move to the one left of the closing tab */
+		new_ch = cv_find_chan_by_number (ch->cv, num - 1);
+		if (new_ch && new_ch != ch)
+		{
+			chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
+		} else
+		{
+			/* if it fails, try focus from tab 0 and up */
+			for (i = 0; i < ch->cv->size; i++)
+			{
+				new_ch = cv_find_chan_by_number (ch->cv, i);
+				if (new_ch && new_ch != ch)
+				{
+					chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
+					break;
+				}
+			}
+		}
+	}
+
+	ch->cv->size--;
+	gtk_tree_store_remove (ch->cv->store, &ch->iter);
+	free (ch);
+	return TRUE;
+}
+
+gboolean
+chan_is_collapsed (chan *ch)
+{
+	return ch->cv->func_is_collapsed (ch);
+}
+
+chan *
+chan_get_parent (chan *ch)
+{
+	return ch->cv->func_get_parent (ch);
+}
diff --git a/src/fe-gtk/chanview.h b/src/fe-gtk/chanview.h
new file mode 100644
index 00000000..75b5ef1f
--- /dev/null
+++ b/src/fe-gtk/chanview.h
@@ -0,0 +1,31 @@
+typedef struct _chanview chanview;
+typedef struct _chan chan;
+
+chanview *chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, GtkStyle *style);
+void chanview_set_callbacks (chanview *cv,
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata),
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata),
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
+	int (*cb_compare) (void *a, void *b));
+void chanview_set_impl (chanview *cv, int type);
+chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
+int chanview_get_size (chanview *cv);
+GtkWidget *chanview_get_box (chanview *cv);
+void chanview_move_focus (chanview *cv, gboolean relative, int num);
+GtkOrientation chanview_get_orientation (chanview *cv);
+void chanview_set_orientation (chanview *cv, gboolean vertical);
+
+int chan_get_tag (chan *ch);
+void *chan_get_userdata (chan *ch);
+void chan_focus (chan *ch);
+void chan_move (chan *ch, int delta);
+void chan_move_family (chan *ch, int delta);
+void chan_set_color (chan *ch, PangoAttrList *list);
+void chan_rename (chan *ch, char *new_name, int trunc_len);
+gboolean chan_remove (chan *ch, gboolean force);
+gboolean chan_is_collapsed (chan *ch);
+chan * chan_get_parent (chan *ch);
+
+#define FOCUS_NEW_ALL 1
+#define FOCUS_NEW_ONLY_ASKED 2
+#define FOCUS_NEW_NONE 0
diff --git a/src/fe-gtk/custom-list.c b/src/fe-gtk/custom-list.c
new file mode 100644
index 00000000..ac20e0ff
--- /dev/null
+++ b/src/fe-gtk/custom-list.c
@@ -0,0 +1,753 @@
+#include <string.h>
+#include <stdlib.h>
+#include "custom-list.h"
+
+/* indent -i3 -ci3 -ut -ts3 -bli0 -c0 custom-list.c */
+
+/* boring declarations of local functions */
+
+static void custom_list_init (CustomList * pkg_tree);
+
+static void custom_list_class_init (CustomListClass * klass);
+
+static void custom_list_tree_model_init (GtkTreeModelIface * iface);
+
+static void custom_list_finalize (GObject * object);
+
+static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel * tree_model);
+
+static gint custom_list_get_n_columns (GtkTreeModel * tree_model);
+
+static GType custom_list_get_column_type (GtkTreeModel * tree_model,
+														gint index);
+
+static gboolean custom_list_get_iter (GtkTreeModel * tree_model,
+												  GtkTreeIter * iter, GtkTreePath * path);
+
+static GtkTreePath *custom_list_get_path (GtkTreeModel * tree_model,
+														GtkTreeIter * iter);
+
+static void custom_list_get_value (GtkTreeModel * tree_model,
+											  GtkTreeIter * iter,
+											  gint column, GValue * value);
+
+static gboolean custom_list_iter_next (GtkTreeModel * tree_model,
+													GtkTreeIter * iter);
+
+static gboolean custom_list_iter_children (GtkTreeModel * tree_model,
+														 GtkTreeIter * iter,
+														 GtkTreeIter * parent);
+
+static gboolean custom_list_iter_has_child (GtkTreeModel * tree_model,
+														  GtkTreeIter * iter);
+
+static gint custom_list_iter_n_children (GtkTreeModel * tree_model,
+													  GtkTreeIter * iter);
+
+static gboolean custom_list_iter_nth_child (GtkTreeModel * tree_model,
+														  GtkTreeIter * iter,
+														  GtkTreeIter * parent, gint n);
+
+static gboolean custom_list_iter_parent (GtkTreeModel * tree_model,
+													  GtkTreeIter * iter,
+													  GtkTreeIter * child);
+
+  /* -- GtkTreeSortable interface functions -- */
+
+static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable *
+																			sortable,
+																			gint * sort_col_id,
+																			GtkSortType * order);
+
+static void custom_list_sortable_set_sort_column_id (GtkTreeSortable *
+																	  sortable,
+																	  gint sort_col_id,
+																	  GtkSortType order);
+
+static void custom_list_sortable_set_sort_func (GtkTreeSortable * sortable,
+																gint sort_col_id,
+																GtkTreeIterCompareFunc
+																sort_func, gpointer user_data,
+																GtkDestroyNotify
+																destroy_func);
+
+static void custom_list_sortable_set_default_sort_func (GtkTreeSortable *
+																		  sortable,
+																		  GtkTreeIterCompareFunc
+																		  sort_func,
+																		  gpointer user_data,
+																		  GtkDestroyNotify
+																		  destroy_func);
+
+static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable *
+																				sortable);
+
+
+
+static GObjectClass *parent_class = NULL;	/* GObject stuff - nothing to worry about */
+
+
+static void
+custom_list_sortable_init (GtkTreeSortableIface * iface)
+{
+	iface->get_sort_column_id = custom_list_sortable_get_sort_column_id;
+	iface->set_sort_column_id = custom_list_sortable_set_sort_column_id;
+	iface->set_sort_func = custom_list_sortable_set_sort_func;	/* NOT SUPPORTED */
+	iface->set_default_sort_func = custom_list_sortable_set_default_sort_func;	/* NOT SUPPORTED */
+	iface->has_default_sort_func = custom_list_sortable_has_default_sort_func;	/* NOT SUPPORTED */
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_get_type: here we register our new type and its interfaces
+ *                        with the type system. If you want to implement
+ *                        additional interfaces like GtkTreeSortable, you
+ *                        will need to do it here.
+ *
+ *****************************************************************************/
+
+static GType
+custom_list_get_type (void)
+{
+	static GType custom_list_type = 0;
+
+	if (custom_list_type)
+		return custom_list_type;
+
+	/* Some boilerplate type registration stuff */
+	if (1)
+	{
+		static const GTypeInfo custom_list_info = {
+			sizeof (CustomListClass),
+			NULL,	/* base_init */
+			NULL,	/* base_finalize */
+			(GClassInitFunc) custom_list_class_init,
+			NULL,	/* class finalize */
+			NULL,	/* class_data */
+			sizeof (CustomList),
+			0,	/* n_preallocs */
+			(GInstanceInitFunc) custom_list_init
+		};
+
+		custom_list_type =
+			g_type_register_static (G_TYPE_OBJECT, "CustomList",
+											&custom_list_info, (GTypeFlags) 0);
+	}
+
+	/* Here we register our GtkTreeModel interface with the type system */
+	if (1)
+	{
+		static const GInterfaceInfo tree_model_info = {
+			(GInterfaceInitFunc) custom_list_tree_model_init,
+			NULL,
+			NULL
+		};
+
+		g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL,
+											  &tree_model_info);
+	}
+
+	/* Add GtkTreeSortable interface */
+	if (1)
+	{
+		static const GInterfaceInfo tree_sortable_info = {
+			(GInterfaceInitFunc) custom_list_sortable_init,
+			NULL,
+			NULL
+		};
+
+		g_type_add_interface_static (custom_list_type,
+											  GTK_TYPE_TREE_SORTABLE,
+											  &tree_sortable_info);
+	}
+
+	return custom_list_type;
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_class_init: more boilerplate GObject/GType stuff.
+ *                          Init callback for the type system,
+ *                          called once when our new class is created.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_class_init (CustomListClass * klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = (GObjectClass *) g_type_class_peek_parent (klass);
+	object_class = (GObjectClass *) klass;
+
+	object_class->finalize = custom_list_finalize;
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_tree_model_init: init callback for the interface registration
+ *                               in custom_list_get_type. Here we override
+ *                               the GtkTreeModel interface functions that
+ *                               we implement.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_tree_model_init (GtkTreeModelIface * iface)
+{
+	iface->get_flags = custom_list_get_flags;
+	iface->get_n_columns = custom_list_get_n_columns;
+	iface->get_column_type = custom_list_get_column_type;
+	iface->get_iter = custom_list_get_iter;
+	iface->get_path = custom_list_get_path;
+	iface->get_value = custom_list_get_value;
+	iface->iter_next = custom_list_iter_next;
+	iface->iter_children = custom_list_iter_children;
+	iface->iter_has_child = custom_list_iter_has_child;
+	iface->iter_n_children = custom_list_iter_n_children;
+	iface->iter_nth_child = custom_list_iter_nth_child;
+	iface->iter_parent = custom_list_iter_parent;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_init: this is called everytime a new custom list object
+ *                    instance is created (we do that in custom_list_new).
+ *                    Initialise the list structure's fields here.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_init (CustomList * custom_list)
+{
+	custom_list->n_columns = CUSTOM_LIST_N_COLUMNS;
+
+	custom_list->column_types[0] = G_TYPE_STRING;	/* CUSTOM_LIST_COL_NAME      */
+	custom_list->column_types[1] = G_TYPE_UINT;	/* CUSTOM_LIST_COL_USERS     */
+	custom_list->column_types[2] = G_TYPE_STRING;	/* CUSTOM_LIST_COL_TOPIC     */
+
+	custom_list->num_rows = 0;
+	custom_list->num_alloc = 0;
+	custom_list->rows = NULL;
+
+	custom_list->sort_id = SORT_ID_CHANNEL;
+	custom_list->sort_order = GTK_SORT_ASCENDING;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_finalize: this is called just before a custom list is
+ *                        destroyed. Free dynamically allocated memory here.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_finalize (GObject * object)
+{
+	custom_list_clear (CUSTOM_LIST (object));
+
+	/* must chain up - finalize parent */
+	(*parent_class->finalize) (object);
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_flags: tells the rest of the world whether our tree model
+ *                         has any special characteristics. In our case,
+ *                         we have a list model (instead of a tree), and each
+ *                         tree iter is valid as long as the row in question
+ *                         exists, as it only contains a pointer to our struct.
+ *
+ *****************************************************************************/
+
+static GtkTreeModelFlags
+custom_list_get_flags (GtkTreeModel * tree_model)
+{
+	return (GTK_TREE_MODEL_LIST_ONLY /*| GTK_TREE_MODEL_ITERS_PERSIST */ );
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_n_columns: tells the rest of the world how many data
+ *                             columns we export via the tree model interface
+ *
+ *****************************************************************************/
+
+static gint
+custom_list_get_n_columns (GtkTreeModel * tree_model)
+{
+	return 3;/*CUSTOM_LIST (tree_model)->n_columns;*/
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_column_type: tells the rest of the world which type of
+ *                               data an exported model column contains
+ *
+ *****************************************************************************/
+
+static GType
+custom_list_get_column_type (GtkTreeModel * tree_model, gint index)
+{
+	return CUSTOM_LIST (tree_model)->column_types[index];
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_iter: converts a tree path (physical position) into a
+ *                        tree iter structure (the content of the iter
+ *                        fields will only be used internally by our model).
+ *                        We simply store a pointer to our chanlistrow
+ *                        structure that represents that row in the tree iter.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_get_iter (GtkTreeModel * tree_model,
+							 GtkTreeIter * iter, GtkTreePath * path)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+	chanlistrow *record;
+	gint n;
+
+	n = gtk_tree_path_get_indices (path)[0];
+	if (n >= custom_list->num_rows || n < 0)
+		return FALSE;
+
+	record = custom_list->rows[n];
+
+	/* We simply store a pointer to our custom record in the iter */
+	iter->user_data = record;
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_path: converts a tree iter into a tree path (ie. the
+ *                        physical position of that row in the list).
+ *
+ *****************************************************************************/
+
+static GtkTreePath *
+custom_list_get_path (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	GtkTreePath *path;
+	chanlistrow *record;
+
+	record = (chanlistrow *) iter->user_data;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, record->pos);
+
+	return path;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_value: Returns a row's exported data columns
+ *                         (_get_value is what gtk_tree_model_get uses)
+ *
+ *****************************************************************************/
+
+static void
+custom_list_get_value (GtkTreeModel * tree_model,
+							  GtkTreeIter * iter, gint column, GValue * value)
+{
+	chanlistrow *record;
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	if (custom_list->num_rows == 0)
+		return;
+
+	g_value_init (value, custom_list->column_types[column]);
+
+	record = (chanlistrow *) iter->user_data;
+
+	switch (column)
+	{
+	case CUSTOM_LIST_COL_NAME:
+		g_value_set_static_string (value, GET_CHAN (record));
+		break;
+
+	case CUSTOM_LIST_COL_USERS:
+		g_value_set_uint (value, record->users);
+		break;
+
+	case CUSTOM_LIST_COL_TOPIC:
+		g_value_set_static_string (value, record->topic);
+		break;
+	}
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_next: Takes an iter structure and sets it to point
+ *                         to the next row.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_next (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	chanlistrow *record, *nextrecord;
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	record = (chanlistrow *) iter->user_data;
+
+	/* Is this the last record in the list? */
+	if ((record->pos + 1) >= custom_list->num_rows)
+		return FALSE;
+
+	nextrecord = custom_list->rows[(record->pos + 1)];
+
+	g_assert (nextrecord != NULL);
+	g_assert (nextrecord->pos == (record->pos + 1));
+
+	iter->user_data = nextrecord;
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_children: Returns TRUE or FALSE depending on whether
+ *                             the row specified by 'parent' has any children.
+ *                             If it has children, then 'iter' is set to
+ *                             point to the first child. Special case: if
+ *                             'parent' is NULL, then the first top-level
+ *                             row should be returned if it exists.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_children (GtkTreeModel * tree_model,
+									GtkTreeIter * iter, GtkTreeIter * parent)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* this is a list, nodes have no children */
+	if (parent)
+		return FALSE;
+
+	/* parent == NULL is a special case; we need to return the first top-level row */
+	/* No rows => no first row */
+	if (custom_list->num_rows == 0)
+		return FALSE;
+
+	/* Set iter to first item in list */
+	iter->user_data = custom_list->rows[0];
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_has_child: Returns TRUE or FALSE depending on whether
+ *                              the row specified by 'iter' has any children.
+ *                              We only have a list and thus no children.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_has_child (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_n_children: Returns the number of children the row
+ *                               specified by 'iter' has. This is usually 0,
+ *                               as we only have a list and thus do not have
+ *                               any children to any rows. A special case is
+ *                               when 'iter' is NULL, in which case we need
+ *                               to return the number of top-level nodes,
+ *                               ie. the number of rows in our list.
+ *
+ *****************************************************************************/
+
+static gint
+custom_list_iter_n_children (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* special case: if iter == NULL, return number of top-level rows */
+	if (!iter)
+		return custom_list->num_rows;
+
+	return 0;	/* otherwise, this is easy again for a list */
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_nth_child: If the row specified by 'parent' has any
+ *                              children, set 'iter' to the n-th child and
+ *                              return TRUE if it exists, otherwise FALSE.
+ *                              A special case is when 'parent' is NULL, in
+ *                              which case we need to set 'iter' to the n-th
+ *                              row if it exists.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_nth_child (GtkTreeModel * tree_model,
+									 GtkTreeIter * iter, GtkTreeIter * parent, gint n)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* a list has only top-level rows */
+	if (parent)
+		return FALSE;
+
+	/* special case: if parent == NULL, set iter to n-th top-level row */
+	if (n >= custom_list->num_rows)
+		return FALSE;
+
+	iter->user_data = custom_list->rows[n];
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As
+ *                           we have a list and thus no children and no
+ *                           parents of children, we can just return FALSE.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_parent (GtkTreeModel * tree_model,
+								 GtkTreeIter * iter, GtkTreeIter * child)
+{
+	return FALSE;
+}
+
+static gboolean
+custom_list_sortable_get_sort_column_id (GtkTreeSortable * sortable,
+													  gint * sort_col_id,
+													  GtkSortType * order)
+{
+	CustomList *custom_list = CUSTOM_LIST (sortable);
+
+	if (sort_col_id)
+		*sort_col_id = custom_list->sort_id;
+
+	if (order)
+		*order = custom_list->sort_order;
+
+	return TRUE;
+}
+
+
+static void
+custom_list_sortable_set_sort_column_id (GtkTreeSortable * sortable,
+													  gint sort_col_id, GtkSortType order)
+{
+	CustomList *custom_list = CUSTOM_LIST (sortable);
+
+	if (custom_list->sort_id == sort_col_id
+		 && custom_list->sort_order == order)
+		return;
+
+	custom_list->sort_id = sort_col_id;
+	custom_list->sort_order = order;
+
+	custom_list_resort (custom_list);
+
+	/* emit "sort-column-changed" signal to tell any tree views
+	 *  that the sort column has changed (so the little arrow
+	 *  in the column header of the sort column is drawn
+	 *  in the right column)                                     */
+
+	gtk_tree_sortable_sort_column_changed (sortable);
+}
+
+static void
+custom_list_sortable_set_sort_func (GtkTreeSortable * sortable,
+												gint sort_col_id,
+												GtkTreeIterCompareFunc sort_func,
+												gpointer user_data,
+												GtkDestroyNotify destroy_func)
+{
+}
+
+static void
+custom_list_sortable_set_default_sort_func (GtkTreeSortable * sortable,
+														  GtkTreeIterCompareFunc sort_func,
+														  gpointer user_data,
+														  GtkDestroyNotify destroy_func)
+{
+}
+
+static gboolean
+custom_list_sortable_has_default_sort_func (GtkTreeSortable * sortable)
+{
+	return FALSE;
+}
+
+/* fast as possible compare func for sorting.
+   TODO: If fast enough, use a unicode collation key and strcmp. */
+
+#define TOSML(c) (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c))
+
+static inline int
+fast_ascii_stricmp (const char *s1, const char *s2)
+{
+	int c1, c2;
+
+	while (*s1 && *s2)
+	{
+		c1 = (int) (unsigned char) TOSML (*s1);
+		c2 = (int) (unsigned char) TOSML (*s2);
+		if (c1 != c2)
+			return (c1 - c2);
+		s1++;
+		s2++;
+	}
+
+	return (((int) (unsigned char) *s1) - ((int) (unsigned char) *s2));
+}
+
+static gint
+custom_list_qsort_compare_func (chanlistrow ** a, chanlistrow ** b,
+										  CustomList * custom_list)
+{
+	if (custom_list->sort_order == GTK_SORT_DESCENDING)
+	{
+		chanlistrow **tmp = a;
+		a = b;
+		b = tmp;
+	}
+
+	if (custom_list->sort_id == SORT_ID_USERS)
+	{
+		return (*a)->users - (*b)->users;
+	}
+
+	if (custom_list->sort_id == SORT_ID_TOPIC)
+	{
+		return fast_ascii_stricmp ((*a)->topic, (*b)->topic);
+	}
+
+	return strcmp ((*a)->collation_key, (*b)->collation_key);
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_new:  This is what you use in your own code to create a
+ *                    new custom list tree model for you to use.
+ *
+ *****************************************************************************/
+
+CustomList *
+custom_list_new (void)
+{
+	return (CustomList *) g_object_new (CUSTOM_TYPE_LIST, NULL);
+}
+
+void
+custom_list_append (CustomList * custom_list, chanlistrow * newrecord)
+{
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	gulong newsize;
+	guint pos;
+
+	if (custom_list->num_rows >= custom_list->num_alloc)
+	{
+		custom_list->num_alloc += 64;
+		newsize = custom_list->num_alloc * sizeof (chanlistrow *);
+		custom_list->rows = g_realloc (custom_list->rows, newsize);
+	}
+
+	/* TODO: Binary search insert? */
+
+	pos = custom_list->num_rows;
+	custom_list->rows[pos] = newrecord;
+	custom_list->num_rows++;
+	newrecord->pos = pos;
+
+	/* inform the tree view and other interested objects
+	 *  (e.g. tree row references) that we have inserted
+	 *  a new row, and where it was inserted */
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, newrecord->pos);
+/*  custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);*/
+	iter.user_data = newrecord;
+	gtk_tree_model_row_inserted (GTK_TREE_MODEL (custom_list), path, &iter);
+	gtk_tree_path_free (path);
+}
+
+void
+custom_list_resort (CustomList * custom_list)
+{
+	GtkTreePath *path;
+	gint *neworder, i;
+
+	if (custom_list->num_rows < 2)
+		return;
+
+	/* resort */
+	g_qsort_with_data (custom_list->rows,
+							 custom_list->num_rows,
+							 sizeof (chanlistrow *),
+							 (GCompareDataFunc) custom_list_qsort_compare_func,
+							 custom_list);
+
+	/* let other objects know about the new order */
+	neworder = malloc (sizeof (gint) * custom_list->num_rows);
+
+	for (i = custom_list->num_rows - 1; i >= 0; i--)
+	{
+		/* Note that the API reference might be wrong about
+		 * this, see bug number 124790 on bugs.gnome.org.
+		 * Both will work, but one will give you 'jumpy'
+		 * selections after row reordering. */
+		/* neworder[(custom_list->rows[i])->pos] = i; */
+		neworder[i] = (custom_list->rows[i])->pos;
+		(custom_list->rows[i])->pos = i;
+	}
+
+	path = gtk_tree_path_new ();
+	gtk_tree_model_rows_reordered (GTK_TREE_MODEL (custom_list), path, NULL,
+											 neworder);
+	gtk_tree_path_free (path);
+	free (neworder);
+}
+
+void
+custom_list_clear (CustomList * custom_list)
+{
+	int i, max = custom_list->num_rows - 1;
+	GtkTreePath *path;
+
+	for (i = max; i >= 0; i--)
+	{
+		path = gtk_tree_path_new ();
+		gtk_tree_path_append_index (path, custom_list->rows[i]->pos);
+		gtk_tree_model_row_deleted (GTK_TREE_MODEL (custom_list), path);
+		gtk_tree_path_free (path);
+	}
+
+	custom_list->num_rows = 0;
+	custom_list->num_alloc = 0;
+
+	g_free (custom_list->rows);
+	custom_list->rows = NULL;
+}
diff --git a/src/fe-gtk/custom-list.h b/src/fe-gtk/custom-list.h
new file mode 100644
index 00000000..d9e4f09e
--- /dev/null
+++ b/src/fe-gtk/custom-list.h
@@ -0,0 +1,85 @@
+#ifndef _custom_list_h_included_
+#define _custom_list_h_included_
+
+#include <gtk/gtk.h>
+
+/* Some boilerplate GObject defines. 'klass' is used
+ *   instead of 'class', because 'class' is a C++ keyword */
+
+#define CUSTOM_TYPE_LIST            (custom_list_get_type ())
+#define CUSTOM_LIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList))
+#define CUSTOM_LIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  CUSTOM_TYPE_LIST, CustomListClass))
+#define CUSTOM_IS_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST))
+#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  CUSTOM_TYPE_LIST))
+#define CUSTOM_LIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  CUSTOM_TYPE_LIST, CustomListClass))
+
+/* The data columns that we export via the tree model interface */
+
+enum
+{
+	CUSTOM_LIST_COL_NAME,
+	CUSTOM_LIST_COL_USERS,
+	CUSTOM_LIST_COL_TOPIC,
+	CUSTOM_LIST_N_COLUMNS
+};
+
+enum
+{
+	SORT_ID_CHANNEL,
+	SORT_ID_USERS,
+	SORT_ID_TOPIC
+};
+
+typedef struct
+{
+	char *topic;
+	char *collation_key;
+	guint32 pos;						  /* pos within the array */
+	guint32 users;
+	/* channel string lives beyond "users" */
+#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow))
+}
+chanlistrow;
+
+typedef struct _CustomList CustomList;
+typedef struct _CustomListClass CustomListClass;
+
+
+
+/* CustomList: this structure contains everything we need for our
+ *             model implementation. You can add extra fields to
+ *             this structure, e.g. hashtables to quickly lookup
+ *             rows or whatever else you might need, but it is
+ *             crucial that 'parent' is the first member of the
+ *             structure.                                          */
+struct _CustomList
+{
+	GObject parent;
+
+	guint num_rows;				  /* number of rows that we have used */
+	guint num_alloc;					/* number of rows allocated */
+	chanlistrow **rows;			  /* a dynamically allocated array of pointers to the
+										   *  CustomRecord structure for each row */
+
+	gint n_columns;
+	GType column_types[CUSTOM_LIST_N_COLUMNS];
+
+	gint sort_id;
+	GtkSortType sort_order;
+};
+
+
+/* CustomListClass: more boilerplate GObject stuff */
+
+struct _CustomListClass
+{
+	GObjectClass parent_class;
+};
+
+
+CustomList *custom_list_new (void);
+void custom_list_append (CustomList *, chanlistrow *);
+void custom_list_resort (CustomList *);
+void custom_list_clear (CustomList *);
+
+#endif /* _custom_list_h_included_ */
diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c
new file mode 100644
index 00000000..61f6d502
--- /dev/null
+++ b/src/fe-gtk/dccgui.c
@@ -0,0 +1,1098 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "../common/inet.h"
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkexpander.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrendererpixbuf.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkversion.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/network.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+
+
+enum	/* DCC SEND/RECV */
+{
+	COL_TYPE,
+	COL_STATUS,
+	COL_FILE,
+	COL_SIZE,
+	COL_POS,
+	COL_PERC,
+	COL_SPEED,
+	COL_ETA,
+	COL_NICK,
+	COL_DCC, /* struct DCC * */
+	COL_COLOR,	/* GdkColor */
+	N_COLUMNS
+};
+
+enum	/* DCC CHAT */
+{
+	CCOL_STATUS,
+	CCOL_NICK,
+	CCOL_RECV,
+	CCOL_SENT,
+	CCOL_START,
+	CCOL_DCC,	/* struct DCC * */
+	CCOL_COLOR,	/* GdkColor * */
+	CN_COLUMNS
+};
+
+struct dccwindow
+{
+	GtkWidget *window;
+
+	GtkWidget *list;
+	GtkListStore *store;
+	GtkTreeSelection *sel;
+
+	GtkWidget *abort_button;
+	GtkWidget *accept_button;
+	GtkWidget *resume_button;
+	GtkWidget *open_button;
+
+	GtkWidget *file_label;
+	GtkWidget *address_label;
+};
+
+struct my_dcc_send
+{
+	struct session *sess;
+	char *nick;
+	int maxcps;
+	int passive;
+};
+
+static struct dccwindow dccfwin = {NULL, };	/* file */
+static struct dccwindow dcccwin = {NULL, };	/* chat */
+static GdkPixbuf *pix_up = NULL;	/* down arrow */
+static GdkPixbuf *pix_dn = NULL;	/* up arrow */
+static int win_width = 600;
+static int win_height = 256;
+static short view_mode;	/* 1=download 2=upload 3=both */
+#define VIEW_DOWNLOAD 1
+#define VIEW_UPLOAD 2
+#define VIEW_BOTH 3
+
+#define KILOBYTE 1024
+#define MEGABYTE (KILOBYTE * 1024)
+#define GIGABYTE (MEGABYTE * 1024)
+
+
+static void
+proper_unit (DCC_SIZE size, char *buf, int buf_len)
+{
+	if (size <= KILOBYTE)
+	{
+		snprintf (buf, buf_len, "%"DCC_SFMT"B", size);
+	}
+	else if (size > KILOBYTE && size <= MEGABYTE)
+	{
+		snprintf (buf, buf_len, "%"DCC_SFMT"kB", size / KILOBYTE);
+	}
+	else
+	{
+		snprintf (buf, buf_len, "%.2fMB", (float)size / MEGABYTE);
+	}
+}
+
+static void
+dcc_send_filereq_file (struct my_dcc_send *mdc, char *file)
+{
+	if (file)
+		dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive);
+	else
+	{
+		free (mdc->nick);
+		free (mdc);
+	}
+}
+
+void
+fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive)
+{
+	char tbuf[128];
+	struct my_dcc_send *mdc;
+	
+	mdc = malloc (sizeof (*mdc));
+	mdc->sess = sess;
+	mdc->nick = strdup (nick);
+	mdc->maxcps = maxcps;
+	mdc->passive = passive;
+
+	snprintf (tbuf, sizeof tbuf, _("Send file to %s"), nick);
+	gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, NULL, FRF_MULTIPLE);
+}
+
+static void
+dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char pos[16], siz[16];
+	char *date;
+
+	date = ctime (&dcc->starttime);
+	date[strlen (date) - 1] = 0;	/* remove the \n */
+
+	proper_unit (dcc->pos, pos, sizeof (pos));
+	proper_unit (dcc->size, siz, sizeof (siz));
+
+	gtk_list_store_set (store, iter,
+							  CCOL_STATUS, _(dccstat[dcc->dccstat].name),
+							  CCOL_NICK, dcc->nick,
+							  CCOL_RECV, pos,
+							  CCOL_SENT, siz,
+							  CCOL_START, date,
+							  CCOL_DCC, dcc,
+							  CCOL_COLOR,
+							  dccstat[dcc->dccstat].color == 1 ?
+								NULL :
+								colors + dccstat[dcc->dccstat].color,
+							  -1);
+}
+
+static void
+dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char pos[16], size[16], kbs[14], perc[14], eta[14];
+	int to_go;
+	float per;
+
+	if (!pix_up)
+		pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up",
+													GTK_ICON_SIZE_MENU, NULL);
+
+	/* percentage ack'ed */
+	per = (float) ((dcc->ack * 100.00) / dcc->size);
+	proper_unit (dcc->size, size, sizeof (size));
+	proper_unit (dcc->pos, pos, sizeof (pos));
+	snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
+/*	proper_unit (dcc->ack, ack, sizeof (ack));*/
+	snprintf (perc, sizeof (perc), "%.0f%%", per);
+	if (dcc->cps != 0)
+	{
+		to_go = (dcc->size - dcc->ack) / dcc->cps;
+		snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
+					 to_go / 3600, (to_go / 60) % 60, to_go % 60);
+	} else
+		strcpy (eta, "--:--:--");
+
+	if (update_only)
+		gtk_list_store_set (store, iter,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+	else
+		gtk_list_store_set (store, iter,
+								  COL_TYPE, pix_up,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_FILE, file_part (dcc->file),
+								  COL_SIZE, size,
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_NICK, dcc->nick,
+								  COL_DCC, dcc,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+}
+
+static void
+dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char size[16], pos[16], kbs[16], perc[14], eta[16];
+	float per;
+	int to_go;
+
+	if (!pix_dn)
+		pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down",
+													GTK_ICON_SIZE_MENU, NULL);
+
+	proper_unit (dcc->size, size, sizeof (size));
+	if (dcc->dccstat == STAT_QUEUED)
+		proper_unit (dcc->resumable, pos, sizeof (pos));
+	else
+		proper_unit (dcc->pos, pos, sizeof (pos));
+	snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
+	/* percentage recv'ed */
+	per = (float) ((dcc->pos * 100.00) / dcc->size);
+	snprintf (perc, sizeof (perc), "%.0f%%", per);
+	if (dcc->cps != 0)
+	{
+		to_go = (dcc->size - dcc->pos) / dcc->cps;
+		snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
+					 to_go / 3600, (to_go / 60) % 60, to_go % 60);
+	} else
+		strcpy (eta, "--:--:--");
+
+	if (update_only)
+		gtk_list_store_set (store, iter,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+	else
+		gtk_list_store_set (store, iter,
+								  COL_TYPE, pix_dn,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_FILE, file_part (dcc->file),
+								  COL_SIZE, size,
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_NICK, dcc->nick,
+								  COL_DCC, dcc,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+}
+
+static gboolean
+dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col)
+{
+	struct DCC *dcc;
+
+	if (gtk_tree_model_get_iter_first (model, iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, iter, col, &dcc, -1);
+			if (dcc == find_dcc)
+				return TRUE;
+		}
+		while (gtk_tree_model_iter_next (model, iter));
+	}
+
+	return FALSE;
+}
+
+static void
+dcc_update_recv (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dccfwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+		return;
+
+	dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE);
+}
+
+static void
+dcc_update_chat (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dcccwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
+		return;
+
+	dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE);
+}
+
+static void
+dcc_update_send (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dccfwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+		return;
+
+	dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE);
+}
+
+static void
+close_dcc_file_window (GtkWindow *win, gpointer data)
+{
+	dccfwin.window = NULL;
+}
+
+static void
+dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
+{
+	GtkTreeIter iter;
+
+	if (prepend)
+		gtk_list_store_prepend (store, &iter);
+	else
+		gtk_list_store_append (store, &iter);
+
+	if (dcc->type == TYPE_RECV)
+		dcc_prepare_row_recv (dcc, store, &iter, FALSE);
+	else
+		dcc_prepare_row_send (dcc, store, &iter, FALSE);
+}
+
+static void
+dcc_fill_window (int flags)
+{
+	struct DCC *dcc;
+	GSList *list;
+	GtkTreeIter iter;
+	int i = 0;
+
+	gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store));
+
+	if (flags & VIEW_UPLOAD)
+	{
+		list = dcc_list;
+		while (list)
+		{
+			dcc = list->data;
+			if (dcc->type == TYPE_SEND)
+			{
+				dcc_append (dcc, dccfwin.store, FALSE);
+				i++;
+			}
+			list = list->next;
+		}
+	}
+
+	if (flags & VIEW_DOWNLOAD)
+	{
+		list = dcc_list;
+		while (list)
+		{
+			dcc = list->data;
+			if (dcc->type == TYPE_RECV)
+			{
+				dcc_append (dcc, dccfwin.store, FALSE);
+				i++;
+			}
+			list = list->next;
+		}
+	}
+
+	/* if only one entry, select it (so Accept button can work) */
+	if (i == 1)
+	{
+		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter);
+		gtk_tree_selection_select_iter (dccfwin.sel, &iter);
+	}
+}
+
+/* return list of selected DCCs */
+
+static GSList *
+treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column)
+{
+	GtkTreeIter iter;
+	GSList *list = NULL;
+	void *ptr;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			if (gtk_tree_selection_iter_is_selected (sel, &iter))
+			{
+				gtk_tree_model_get (model, &iter, column, &ptr, -1);
+				list = g_slist_prepend (list, ptr);
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	return g_slist_reverse (list);
+}
+
+static GSList *
+dcc_get_selected (void)
+{
+	return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store),
+											dccfwin.sel, COL_DCC);
+}
+
+static void
+resume_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	char buf[512];
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+		return;
+	dcc = list->data;
+	g_slist_free (list);
+
+	if (dcc->type == TYPE_RECV && !dcc_resume (dcc))
+	{
+		switch (dcc->resume_error)
+		{
+		case 0:	/* unknown error */
+			fe_message (_("That file is not resumable."), FE_MSG_ERROR);
+			break;
+		case 1:
+			snprintf (buf, sizeof (buf),
+						_(	"Cannot access file: %s\n"
+							"%s.\n"
+							"Resuming not possible."), dcc->destfile,	
+							errorstring (dcc->resume_errno));
+			fe_message (buf, FE_MSG_ERROR);
+			break;
+		case 2:
+			fe_message (_("File in download directory is larger "
+							"than file offered. Resuming not possible."), FE_MSG_ERROR);
+			break;
+		case 3:
+			fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR);
+		}
+	}
+}
+
+static void
+abort_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+accept_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		if (dcc->type != TYPE_SEND)
+			dcc_get (dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+browse_folder (char *dir)
+{
+#ifdef WIN32
+	/* no need for file:// in ShellExecute() */
+	fe_open_url (dir);
+#else
+	char buf[512];
+
+	snprintf (buf, sizeof (buf), "file://%s", dir);
+	fe_open_url (buf);
+#endif
+}
+
+static void
+browse_dcc_folder (void)
+{
+	if (prefs.dcc_completed_dir[0])
+		browse_folder (prefs.dcc_completed_dir);
+	else
+		browse_folder (prefs.dccdir);
+}
+
+static void
+dcc_details_populate (struct DCC *dcc)
+{
+	char buf[128];
+
+	if (!dcc)
+	{
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL);
+		gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL);
+		return;
+	}
+
+	/* full path */
+	if (dcc->type == TYPE_RECV)
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile);
+	else
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file);
+
+	/* address and port */
+	snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port);
+	gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf);
+}
+
+static void
+dcc_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+	{
+		gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+		gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+		gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
+		dcc_details_populate (NULL);
+		return;
+	}
+
+	gtk_widget_set_sensitive (dccfwin.abort_button, TRUE);
+
+	if (list->next)	/* multi selection */
+	{
+		gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
+		gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
+		dcc_details_populate (list->data);
+	}
+	else
+	{
+		/* turn OFF/ON appropriate buttons */
+		dcc = list->data;
+		if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
+		{
+			gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
+			gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
+		}
+		else
+		{
+			gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+			gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+		}
+
+		dcc_details_populate (dcc);
+	}
+
+	g_slist_free (list);
+}
+
+static void
+dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+					GtkTreeViewColumn *column, gpointer data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+		return;
+	dcc = list->data;
+	g_slist_free (list);
+
+	if (dcc->type == TYPE_RECV)
+	{
+		accept_clicked (0, 0);
+		return;
+	}
+
+	switch (dcc->dccstat)
+	{
+	case STAT_FAILED:
+	case STAT_ABORTED:
+	case STAT_DONE:
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+}
+
+static void
+dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified)
+{
+	GtkCellRenderer *renderer;
+
+	renderer = gtk_cell_renderer_text_new ();
+	if (right_justified)
+		g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer,
+																"text", textcol, "foreground-gdk", colorcol,
+																NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+}
+
+static GtkWidget *
+dcc_detail_label (char *text, GtkWidget *box, int num)
+{
+	GtkWidget *label;
+	char buf[64];
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<b>%s</b>", text);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
+
+	return label;
+}
+
+static void
+dcc_exp_cb (GtkWidget *exp, GtkWidget *box)
+{
+#if GTK_CHECK_VERSION(2,20,0)
+	if (gtk_widget_get_visible (box))
+#else
+	if (GTK_WIDGET_VISIBLE (box))
+#endif
+		gtk_widget_hide (box);
+	else
+		gtk_widget_show (box);
+}
+
+static void
+dcc_toggle (GtkWidget *item, gpointer data)
+{
+	if (GTK_TOGGLE_BUTTON (item)->active)
+	{
+		view_mode = GPOINTER_TO_INT (data);
+		dcc_fill_window (GPOINTER_TO_INT (data));
+	}
+}
+
+static gboolean
+dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data)
+{
+	/* remember the window size */
+	gtk_window_get_size (win, &win_width, &win_height);
+	return FALSE;
+}
+
+int
+fe_dcc_open_recv_win (int passive)
+{
+	GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox;
+	GtkListStore *store;
+	GSList *group;
+
+	if (dccfwin.window)
+	{
+		if (!passive)
+			mg_bring_tofront (dccfwin.window);
+		return TRUE;
+	}
+	dccfwin.window = mg_create_generic_tab ("Transfers", _("XChat: Uploads and Downloads"),
+														 FALSE, TRUE, close_dcc_file_window, NULL,
+														 win_width, win_height, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3);
+	gtk_box_set_spacing (GTK_BOX (vbox), 3);
+
+	store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR);
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	/* Up/Down Icon column */
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL,
+																gtk_cell_renderer_pixbuf_new (),
+																"pixbuf", COL_TYPE, NULL);
+	dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE);
+	dcc_add_column (view, COL_FILE,   COL_COLOR, _("File"), FALSE);
+	dcc_add_column (view, COL_SIZE,   COL_COLOR, _("Size"), TRUE);
+	dcc_add_column (view, COL_POS,    COL_COLOR, _("Position"), TRUE);
+	dcc_add_column (view, COL_PERC,   COL_COLOR, "%", TRUE);
+	dcc_add_column (view, COL_SPEED,  COL_COLOR, "KB/s", TRUE);
+	dcc_add_column (view, COL_ETA,    COL_COLOR, _("ETA"), FALSE);
+	dcc_add_column (view, COL_NICK,   COL_COLOR, _("Nick"), FALSE);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE);
+
+	dccfwin.list = view;
+	dccfwin.store = store;
+	dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	view_mode = VIEW_BOTH;
+	gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE);
+
+	if (!prefs.windows_as_tabs)
+		g_signal_connect (G_OBJECT (dccfwin.window), "configure_event",
+								G_CALLBACK (dcc_configure_cb), 0);
+	g_signal_connect (G_OBJECT (dccfwin.sel), "changed",
+							G_CALLBACK (dcc_row_cb), NULL);
+	/* double click */
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (dcc_dclick_cb), NULL);
+
+	table = gtk_table_new (1, 3, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 16);
+	gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0);
+
+	radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH));
+	gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
+
+	radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD));
+	gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
+
+	radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD));
+	gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+
+	exp = gtk_expander_new (_("Details"));
+	gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	detailbox = gtk_table_new (3, 3, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2);
+	gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6);
+	g_signal_connect (G_OBJECT (exp), "activate",
+							G_CALLBACK (dcc_exp_cb), detailbox);
+	gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0);
+	dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
+
+	dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort"));
+	dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept"));
+	dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume"));
+	dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder..."));
+	gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+	gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+	gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
+
+	dcc_fill_window (3);
+	gtk_widget_show_all (dccfwin.window);
+	gtk_widget_hide (detailbox);
+
+	return FALSE;
+}
+
+int
+fe_dcc_open_send_win (int passive)
+{
+	/* combined send/recv GUI */
+	return fe_dcc_open_recv_win (passive);
+}
+
+
+/* DCC CHAT GUIs BELOW */
+
+static GSList *
+dcc_chat_get_selected (void)
+{
+	return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store),
+											dcccwin.sel, CCOL_DCC);
+}
+
+static void
+accept_chat_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_chat_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_get (dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+abort_chat_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_chat_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+dcc_chat_close_cb (void)
+{
+	dcccwin.window = NULL;
+}
+
+static void
+dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
+{
+	GtkTreeIter iter;
+
+	if (prepend)
+		gtk_list_store_prepend (store, &iter);
+	else
+		gtk_list_store_append (store, &iter);
+
+	dcc_prepare_row_chat (dcc, store, &iter, FALSE);
+}
+
+static void
+dcc_chat_fill_win (void)
+{
+	struct DCC *dcc;
+	GSList *list;
+	GtkTreeIter iter;
+	int i = 0;
+
+	gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store));
+
+	list = dcc_list;
+	while (list)
+	{
+		dcc = list->data;
+		if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV)
+		{
+			dcc_chat_append (dcc, dcccwin.store, FALSE);
+			i++;
+		}
+		list = list->next;
+	}
+
+	/* if only one entry, select it (so Accept button can work) */
+	if (i == 1)
+	{
+		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter);
+		gtk_tree_selection_select_iter (dcccwin.sel, &iter);
+	}
+}
+
+static void
+dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_chat_get_selected ();
+	if (!list)
+	{
+		gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+		gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
+		return;
+	}
+
+	gtk_widget_set_sensitive (dcccwin.abort_button, TRUE);
+
+	if (list->next)	/* multi selection */
+		gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
+	else
+	{
+		/* turn OFF/ON appropriate buttons */
+		dcc = list->data;
+		if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV)
+			gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
+		else
+			gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+	}
+
+	g_slist_free (list);
+}
+
+static void
+dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+						  GtkTreeViewColumn *column, gpointer data)
+{
+	accept_chat_clicked (0, 0);
+}
+
+int
+fe_dcc_open_chat_win (int passive)
+{
+	GtkWidget *view, *vbox, *bbox;
+	GtkListStore *store;
+
+	if (dcccwin.window)
+	{
+		if (!passive)
+			mg_bring_tofront (dcccwin.window);
+		return TRUE;
+	}
+
+	dcccwin.window =
+			  mg_create_generic_tab ("DCCChat", _("XChat: DCC Chat List"),
+						FALSE, TRUE, dcc_chat_close_cb, NULL, 550, 180, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3);
+	gtk_box_set_spacing (GTK_BOX (vbox), 3);
+
+	store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_POINTER, GDK_TYPE_COLOR);
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+
+	dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE);
+	dcc_add_column (view, CCOL_NICK,   CCOL_COLOR, _("Nick"), FALSE);
+	dcc_add_column (view, CCOL_RECV,   CCOL_COLOR, _("Recv"), TRUE);
+	dcc_add_column (view, CCOL_SENT,   CCOL_COLOR, _("Sent"), TRUE);
+	dcc_add_column (view, CCOL_START,  CCOL_COLOR, _("Start Time"), FALSE);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+
+	dcccwin.list = view;
+	dcccwin.store = store;
+	dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE);
+
+	g_signal_connect (G_OBJECT (dcccwin.sel), "changed",
+							G_CALLBACK (dcc_chat_row_cb), NULL);
+	/* double click */
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (dcc_chat_dclick_cb), NULL);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
+
+	dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort"));
+	dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept"));
+	gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+	gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
+
+	dcc_chat_fill_win ();
+	gtk_widget_show_all (dcccwin.window);
+
+	return FALSE;
+}
+
+void
+fe_dcc_add (struct DCC *dcc)
+{
+	switch (dcc->type)
+	{
+	case TYPE_RECV:
+		if (dccfwin.window && (view_mode & VIEW_DOWNLOAD))
+			dcc_append (dcc, dccfwin.store, TRUE);
+		break;
+
+	case TYPE_SEND:
+		if (dccfwin.window && (view_mode & VIEW_UPLOAD))
+			dcc_append (dcc, dccfwin.store, TRUE);
+		break;
+
+	default: /* chat */
+		if (dcccwin.window)
+			dcc_chat_append (dcc, dcccwin.store, TRUE);
+	}
+}
+
+void
+fe_dcc_update (struct DCC *dcc)
+{
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+		dcc_update_send (dcc);
+		break;
+
+	case TYPE_RECV:
+		dcc_update_recv (dcc);
+		break;
+
+	default:
+		dcc_update_chat (dcc);
+	}
+}
+
+void
+fe_dcc_remove (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+	case TYPE_RECV:
+		if (dccfwin.window)
+		{
+			if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+				gtk_list_store_remove (dccfwin.store, &iter);
+		}
+		break;
+
+	default:	/* chat */
+		if (dcccwin.window)
+		{
+			if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
+				gtk_list_store_remove (dcccwin.store, &iter);
+		}
+		break;
+	}
+}
diff --git a/src/fe-gtk/editlist.c b/src/fe-gtk/editlist.c
new file mode 100644
index 00000000..5af67e32
--- /dev/null
+++ b/src/fe-gtk/editlist.c
@@ -0,0 +1,409 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkstock.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvseparator.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/xchatc.h"
+#include "../common/fe.h"
+#include "menu.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "editlist.h"
+
+
+static GtkWidget *editlist_gui_entry_name;
+static GtkWidget *editlist_gui_entry_cmd;
+static GtkWidget *editlist_gui_window;
+static GtkWidget *editlist_gui_list;
+static GSList *editlist_list;
+static char *editlist_file;
+static char *editlist_help;
+
+
+
+static void
+editlist_gui_load (GtkWidget * listgad)
+{
+	gchar *nnew[2];
+	GSList *list = editlist_list;
+	struct popup *pop;
+
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		nnew[0] = pop->name;
+		nnew[1] = pop->cmd;
+		gtk_clist_append (GTK_CLIST (listgad), nnew);
+		list = list->next;
+	}
+}
+
+static void
+editlist_gui_row_unselected (GtkWidget * clist, gint row, gint column,
+									  GdkEventButton * even, gpointer none)
+{
+	gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), "");
+	gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), "");
+}
+
+static void
+editlist_gui_row_selected (GtkWidget * clist, gint row, gint column,
+									GdkEventButton * even, gpointer none)
+{
+	char *name, *cmd;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		gtk_clist_get_text (GTK_CLIST (clist), row, 0, &name);
+		gtk_clist_get_text (GTK_CLIST (clist), row, 1, &cmd);
+
+		name = strdup (name);
+		cmd = strdup (cmd);
+
+		gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), name);
+		gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), cmd);
+
+		free (name);
+		free (cmd);
+	} else
+	{
+		editlist_gui_row_unselected (0, 0, 0, 0, 0);
+	}
+}
+
+static void
+editlist_gui_handle_cmd (GtkWidget * igad)
+{
+	int row;
+	const char *reply;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		reply = gtk_entry_get_text (GTK_ENTRY (igad));
+		gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 1, reply);
+	}
+}
+
+static void
+editlist_gui_handle_name (GtkWidget * igad)
+{
+	int row;
+	const char *ctcp;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		ctcp = gtk_entry_get_text (GTK_ENTRY (igad));
+		gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 0, ctcp);
+	}
+}
+
+static void
+editlist_gui_addnew (GtkWidget * igad)
+{
+	int i;
+	gchar *nnew[2];
+
+	nnew[0] = _("*NEW*");
+	nnew[1] = _("EDIT ME");
+
+	i = gtk_clist_append (GTK_CLIST (editlist_gui_list), nnew);
+	gtk_clist_select_row (GTK_CLIST (editlist_gui_list), i, 0);
+	gtk_clist_moveto (GTK_CLIST (editlist_gui_list), i, 0, 0.5, 0);
+}
+
+static void
+editlist_gui_delete (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		gtk_clist_unselect_all (GTK_CLIST (editlist_gui_list));
+		gtk_clist_remove (GTK_CLIST (editlist_gui_list), row);
+	}
+}
+
+static void
+editlist_gui_save (GtkWidget * igad)
+{
+	int fh, i = 0;
+	char buf[512];
+	char *a, *b;
+
+	fh = xchat_open_file (editlist_file, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (1)
+		{
+			if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 0, &a))
+				break;
+			gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 1, &b);
+			snprintf (buf, sizeof (buf), "NAME %s\nCMD %s\n\n", a, b);
+			write (fh, buf, strlen (buf));
+			i++;
+		}
+		close (fh);
+		gtk_widget_destroy (editlist_gui_window);
+		if (editlist_list == replace_list)
+		{
+			list_free (&replace_list);
+			list_loadconf (editlist_file, &replace_list, 0);
+		} else if (editlist_list == popup_list)
+		{
+			list_free (&popup_list);
+			list_loadconf (editlist_file, &popup_list, 0);
+		} else if (editlist_list == button_list)
+		{
+			GSList *list = sess_list;
+			struct session *sess;
+			list_free (&button_list);
+			list_loadconf (editlist_file, &button_list, 0);
+			while (list)
+			{
+				sess = (struct session *) list->data;
+				fe_buttons_update (sess);
+				list = list->next;
+			}
+		} else if (editlist_list == dlgbutton_list)
+		{
+			GSList *list = sess_list;
+			struct session *sess;
+			list_free (&dlgbutton_list);
+			list_loadconf (editlist_file, &dlgbutton_list, 0);
+			while (list)
+			{
+				sess = (struct session *) list->data;
+				fe_dlgbuttons_update (sess);
+				list = list->next;
+			}
+		} else if (editlist_list == ctcp_list)
+		{
+			list_free (&ctcp_list);
+			list_loadconf (editlist_file, &ctcp_list, 0);
+		} else if (editlist_list == command_list)
+		{
+			list_free (&command_list);
+			list_loadconf (editlist_file, &command_list, 0);
+		} else if (editlist_list == usermenu_list)
+		{
+			list_free (&usermenu_list);
+			list_loadconf (editlist_file, &usermenu_list, 0);
+			usermenu_update ();
+		} else
+		{
+			list_free (&urlhandler_list);
+			list_loadconf (editlist_file, &urlhandler_list, 0);
+		}
+	}
+}
+
+static void
+editlist_gui_help (GtkWidget * igad)
+{
+/*	if (editlist_help)*/
+		fe_message (editlist_help, FE_MSG_INFO);
+}
+
+static void
+editlist_gui_sort (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+		gtk_clist_unselect_row (GTK_CLIST (editlist_gui_list), row, 0);
+	gtk_clist_sort (GTK_CLIST (editlist_gui_list));
+}
+
+static void
+editlist_gui_movedown (GtkWidget * igad)
+{
+	int row;
+	char *temp;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), row + 1, 0, &temp))
+			return;
+		gtk_clist_freeze (GTK_CLIST (editlist_gui_list));
+		gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row, row + 1);
+		gtk_clist_thaw (GTK_CLIST (editlist_gui_list));
+		row++;
+		if (!gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) !=
+			 GTK_VISIBILITY_FULL)
+			gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.9, 0);
+	}
+}
+
+static void
+editlist_gui_moveup (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1 && row > 0)
+	{
+		gtk_clist_freeze (GTK_CLIST (editlist_gui_list));
+		gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row - 1, row);
+		gtk_clist_thaw (GTK_CLIST (editlist_gui_list));
+		row--;
+		if (gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) !=
+			 GTK_VISIBILITY_FULL)
+			gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.1, 0);
+	}
+}
+
+static void
+editlist_gui_close (void)
+{
+	editlist_gui_window = 0;
+}
+
+void
+editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass,
+						 char *file, char *help)
+{
+	gchar *titles[2];
+	GtkWidget *vbox, *hbox, *button;
+
+	if (title1)
+	{
+		titles[0] = title1;
+		titles[1] = title2;
+	} else
+	{
+		titles[0] = _("Name");
+		titles[1] = _("Command");
+	}
+
+	if (editlist_gui_window)
+	{
+		mg_bring_tofront (editlist_gui_window);
+		return;
+	}
+
+	editlist_list = list;
+	editlist_file = file;
+	editlist_help = help;
+
+	editlist_gui_window =
+			  mg_create_generic_tab (wmclass, title, TRUE, FALSE,
+											 editlist_gui_close, NULL, 450, 250, &vbox, 0);
+
+	editlist_gui_list = gtkutil_clist_new (2, titles, vbox, GTK_POLICY_ALWAYS,
+														editlist_gui_row_selected, 0,
+														editlist_gui_row_unselected, 0,
+														GTK_SELECTION_BROWSE);
+	gtk_clist_set_column_width (GTK_CLIST (editlist_gui_list), 0, 90);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	button = gtkutil_button (hbox, GTK_STOCK_GO_UP, 0, editlist_gui_moveup,
+									 0, _("Move Up"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_GO_DOWN, 0, editlist_gui_movedown,
+									 0, _("Move Dn"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtk_vseparator_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+	gtk_widget_show (button);
+
+	button = gtkutil_button (hbox, GTK_STOCK_CANCEL, 0, gtkutil_destroy,
+									 editlist_gui_window, _("Cancel"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_SAVE, 0, editlist_gui_save,
+									 0, _("Save"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	button = gtkutil_button (hbox, GTK_STOCK_ADD, 0, editlist_gui_addnew,
+									 0, _("Add New"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_REMOVE, 0, editlist_gui_delete,
+									 0, _("Delete"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtk_vseparator_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+	gtk_widget_show (button);
+
+	button = gtkutil_button (hbox, GTK_STOCK_SORT_ASCENDING, 0, editlist_gui_sort,
+									 0, _("Sort"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_HELP, 0, editlist_gui_help,
+									 0, _("Help"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	if (!help)
+		gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	editlist_gui_entry_name = gtk_entry_new_with_max_length (82);
+	gtk_widget_set_usize (editlist_gui_entry_name, 96, 0);
+	gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_name), "changed",
+							  GTK_SIGNAL_FUNC (editlist_gui_handle_name), 0);
+	gtk_box_pack_start (GTK_BOX (hbox), editlist_gui_entry_name, 0, 0, 0);
+	gtk_widget_show (editlist_gui_entry_name);
+
+	editlist_gui_entry_cmd = gtk_entry_new_with_max_length (255);
+	gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_cmd), "changed",
+							  GTK_SIGNAL_FUNC (editlist_gui_handle_cmd), 0);
+	gtk_container_add (GTK_CONTAINER (hbox), editlist_gui_entry_cmd);
+	gtk_widget_show (editlist_gui_entry_cmd);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	editlist_gui_load (editlist_gui_list);
+
+	gtk_widget_show (editlist_gui_window);
+}
diff --git a/src/fe-gtk/editlist.h b/src/fe-gtk/editlist.h
new file mode 100644
index 00000000..f17cc2e0
--- /dev/null
+++ b/src/fe-gtk/editlist.h
@@ -0,0 +1 @@
+void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help);
diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c
new file mode 100644
index 00000000..5efcaeec
--- /dev/null
+++ b/src/fe-gtk/fe-gtk.c
@@ -0,0 +1,1063 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkmain.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkversion.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/text.h"
+#include "../common/cfgfiles.h"
+#include "../common/xchatc.h"
+#include "../common/plugin.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "pixmaps.h"
+#include "joind.h"
+#include "xtext.h"
+#include "palette.h"
+#include "menu.h"
+#include "notifygui.h"
+#include "textgui.h"
+#include "fkeys.h"
+#include "plugin-tray.h"
+#include "urlgrab.h"
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#include <gtk/gtkinvisible.h>
+#endif
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+GdkPixmap *channelwin_pix;
+
+
+#ifdef USE_XLIB
+
+static void
+redraw_trans_xtexts (void)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int done_main = FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (GTK_XTEXT (sess->gui->xtext)->transparent)
+		{
+			if (!sess->gui->is_tab || !done_main)
+				gtk_xtext_refresh (GTK_XTEXT (sess->gui->xtext), 1);
+			if (sess->gui->is_tab)
+				done_main = TRUE;
+		}
+		list = list->next;
+	}
+}
+
+static GdkFilterReturn
+root_event_cb (GdkXEvent *xev, GdkEventProperty *event, gpointer data)
+{
+	static Atom at = None;
+	XEvent *xevent = (XEvent *)xev;
+
+	if (xevent->type == PropertyNotify)
+	{
+		if (at == None)
+			at = XInternAtom (xevent->xproperty.display, "_XROOTPMAP_ID", True);
+
+		if (at == xevent->xproperty.atom)
+			redraw_trans_xtexts ();
+	}
+
+	return GDK_FILTER_CONTINUE;
+}
+
+#endif
+
+/* === command-line parameter parsing : requires glib 2.6 === */
+
+static char *arg_cfgdir = NULL;
+static gint arg_show_autoload = 0;
+static gint arg_show_config = 0;
+static gint arg_show_version = 0;
+static gint arg_minimize = 0;
+
+static const GOptionEntry gopt_entries[] = 
+{
+ {"no-auto",	'a', 0, G_OPTION_ARG_NONE,	&arg_dont_autoconnect, N_("Don't auto connect to servers"), NULL},
+ {"cfgdir",	'd', 0, G_OPTION_ARG_STRING,	&arg_cfgdir, N_("Use a different config directory"), "PATH"},
+ {"no-plugins",	'n', 0, G_OPTION_ARG_NONE,	&arg_skip_plugins, N_("Don't auto load any plugins"), NULL},
+ {"plugindir",	'p', 0, G_OPTION_ARG_NONE,	&arg_show_autoload, N_("Show plugin auto-load directory"), NULL},
+ {"configdir",	'u', 0, G_OPTION_ARG_NONE,	&arg_show_config, N_("Show user config directory"), NULL},
+ {"url",	 0,  0, G_OPTION_ARG_STRING,	&arg_url, N_("Open an irc://server:port/channel URL"), "URL"},
+#ifndef WIN32	/* uses DBUS */
+ {"command",	'c', 0, G_OPTION_ARG_STRING,	&arg_command, N_("Execute command:"), "COMMAND"},
+ {"existing",	'e', 0, G_OPTION_ARG_NONE,	&arg_existing, N_("Open URL or execute command in an existing XChat"), NULL},
+#endif
+ {"minimize",	 0,  0, G_OPTION_ARG_INT,	&arg_minimize, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")},
+ {"version",	'v', 0, G_OPTION_ARG_NONE,	&arg_show_version, N_("Show version information"), NULL},
+ {NULL}
+};
+
+int
+fe_args (int argc, char *argv[])
+{
+	GError *error = NULL;
+	GOptionContext *context;
+
+#ifdef ENABLE_NLS
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+#endif
+
+	context = g_option_context_new (NULL);
+	g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE);
+	g_option_context_add_group (context, gtk_get_option_group (FALSE));
+	g_option_context_parse (context, &argc, &argv, &error);
+
+	if (error)
+	{
+		if (error->message)
+			printf ("%s\n", error->message);
+		return 1;
+	}
+
+	g_option_context_free (context);
+
+	if (arg_show_version)
+	{
+		printf (PACKAGE_TARNAME" "PACKAGE_VERSION"\n");
+		return 0;
+	}
+
+	if (arg_show_autoload)
+	{
+#ifdef WIN32
+		/* see the chdir() below */
+		char *sl, *exe = strdup (argv[0]);
+		sl = strrchr (exe, '\\');
+		if (sl)
+		{
+			*sl = 0;
+			printf ("%s\\plugins\n", exe);
+		}
+#else
+		printf ("%s\n", XCHATLIBDIR"/plugins");
+#endif
+		return 0;
+	}
+
+	if (arg_show_config)
+	{
+		printf ("%s\n", get_xdir_fs ());
+		return 0;
+	}
+
+#ifdef WIN32
+	/* this is mainly for irc:// URL handling. When windows calls us from */
+	/* I.E, it doesn't give an option of "Start in" directory, like short */
+	/* cuts can. So we have to set the current dir manually, to the path  */
+	/* of the exe. */
+	{
+		char *tmp = strdup (argv[0]);
+		char *sl;
+
+		sl = strrchr (tmp, '\\');
+		if (sl)
+		{
+			*sl = 0;
+			chdir (tmp);
+		}
+		free (tmp);
+	}
+#endif
+
+	if (arg_cfgdir)	/* we want filesystem encoding */
+	{
+		xdir_fs = strdup (arg_cfgdir);
+		if (xdir_fs[strlen (xdir_fs) - 1] == '/')
+			xdir_fs[strlen (xdir_fs) - 1] = 0;
+		g_free (arg_cfgdir);
+	}
+
+	gtk_init (&argc, &argv);
+
+#ifdef USE_XLIB
+	gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK);
+	gdk_window_add_filter (gdk_get_default_root_window (),
+								  (GdkFilterFunc)root_event_cb, NULL);
+#endif
+
+	return -1;
+}
+
+const char cursor_color_rc[] =
+	"style \"xc-ib-st\""
+	"{"
+#ifdef USE_GTKSPELL
+		"GtkTextView::cursor-color=\"#%02x%02x%02x\""
+#else
+		"GtkEntry::cursor-color=\"#%02x%02x%02x\""
+#endif
+	"}"
+	"widget \"*.xchat-inputbox\" style : application \"xc-ib-st\"";
+
+GtkStyle *
+create_input_style (GtkStyle *style)
+{
+	char buf[256];
+	static int done_rc = FALSE;
+
+	pango_font_description_free (style->font_desc);
+	style->font_desc = pango_font_description_from_string (prefs.font_normal);
+
+	/* fall back */
+	if (pango_font_description_get_size (style->font_desc) == 0)
+	{
+		snprintf (buf, sizeof (buf), _("Failed to open font:\n\n%s"), prefs.font_normal);
+		fe_message (buf, FE_MSG_ERROR);
+		pango_font_description_free (style->font_desc);
+		style->font_desc = pango_font_description_from_string ("sans 11");
+	}
+
+	if (prefs.style_inputbox && !done_rc)
+	{
+		done_rc = TRUE;
+		sprintf (buf, cursor_color_rc, (colors[COL_FG].red >> 8),
+			(colors[COL_FG].green >> 8), (colors[COL_FG].blue >> 8));
+		gtk_rc_parse_string (buf);
+	}
+
+	style->bg[GTK_STATE_NORMAL] = colors[COL_FG];
+	style->base[GTK_STATE_NORMAL] = colors[COL_BG];
+	style->text[GTK_STATE_NORMAL] = colors[COL_FG];
+
+	return style;
+}
+
+void
+fe_init (void)
+{
+	palette_load ();
+	key_init ();
+	pixmaps_init ();
+
+	channelwin_pix = pixmap_load_from_file (prefs.background);
+	input_style = create_input_style (gtk_style_new ());
+}
+
+void
+fe_main (void)
+{
+	gtk_main ();
+
+	/* sleep for 3 seconds so any QUIT messages are not lost. The  */
+	/* GUI is closed at this point, so the user doesn't even know! */
+	if (prefs.wait_on_exit)
+		sleep (3);
+}
+
+void
+fe_cleanup (void)
+{
+	/* it's saved when pressing OK in setup.c */
+	/*palette_save ();*/
+}
+
+void
+fe_exit (void)
+{
+	gtk_main_quit ();
+}
+
+int
+fe_timeout_add (int interval, void *callback, void *userdata)
+{
+	return g_timeout_add (interval, (GSourceFunc) callback, userdata);
+}
+
+void
+fe_timeout_remove (int tag)
+{
+	g_source_remove (tag);
+}
+
+#ifdef WIN32
+
+static void
+log_handler (const gchar   *log_domain,
+		       GLogLevelFlags log_level,
+		       const gchar   *message,
+		       gpointer	      unused_data)
+{
+	session *sess;
+
+	if (getenv ("XCHAT_WARNING_IGNORE"))
+		return;
+
+	sess = find_dialog (serv_list->data, "(warnings)");
+	if (!sess)
+		sess = new_ircwindow (serv_list->data, "(warnings)", SESS_DIALOG, 0);
+
+	PrintTextf (sess, "%s\t%s\n", log_domain, message);
+	if (getenv ("XCHAT_WARNING_ABORT"))
+		abort ();
+}
+
+#endif
+
+/* install tray stuff */
+
+static int
+fe_idle (gpointer data)
+{
+	session *sess = sess_list->data;
+
+	plugin_add (sess, NULL, NULL, tray_plugin_init, tray_plugin_deinit, NULL, FALSE);
+
+	if (arg_minimize == 1)
+		gtk_window_iconify (GTK_WINDOW (sess->gui->window));
+	else if (arg_minimize == 2)
+		tray_toggle_visibility (FALSE);
+
+	return 0;
+}
+
+void
+fe_new_window (session *sess, int focus)
+{
+	int tab = FALSE;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		if (prefs.privmsgtab)
+			tab = TRUE;
+	} else
+	{
+		if (prefs.tabchannels)
+			tab = TRUE;
+	}
+
+	mg_changui_new (sess, NULL, tab, focus);
+
+#ifdef WIN32
+	g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("Gdk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("Gtk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+#endif
+
+	if (!sess_list->next)
+		g_idle_add (fe_idle, NULL);
+}
+
+void
+fe_new_server (struct server *serv)
+{
+	serv->gui = malloc (sizeof (struct server_gui));
+	memset (serv->gui, 0, sizeof (struct server_gui));
+}
+
+void
+fe_message (char *msg, int flags)
+{
+	GtkWidget *dialog;
+	int type = GTK_MESSAGE_WARNING;
+
+	if (flags & FE_MSG_ERROR)
+		type = GTK_MESSAGE_ERROR;
+	if (flags & FE_MSG_INFO)
+		type = GTK_MESSAGE_INFO;
+
+	dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type,
+												GTK_BUTTONS_OK, "%s", msg);
+	if (flags & FE_MSG_MARKUP)
+		gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg);
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (gtk_widget_destroy), 0);
+	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+
+	if (flags & FE_MSG_WAIT)
+		gtk_dialog_run (GTK_DIALOG (dialog));
+}
+
+void
+fe_idle_add (void *func, void *data)
+{
+	g_idle_add (func, data);
+}
+
+void
+fe_input_remove (int tag)
+{
+	g_source_remove (tag);
+}
+
+int
+fe_input_add (int sok, int flags, void *func, void *data)
+{
+	int tag, type = 0;
+	GIOChannel *channel;
+
+#ifdef WIN32
+	if (flags & FIA_FD)
+		channel = g_io_channel_win32_new_fd (sok);
+	else
+		channel = g_io_channel_win32_new_socket (sok);
+#else
+	channel = g_io_channel_unix_new (sok);
+#endif
+
+	if (flags & FIA_READ)
+		type |= G_IO_IN | G_IO_HUP | G_IO_ERR;
+	if (flags & FIA_WRITE)
+		type |= G_IO_OUT | G_IO_ERR;
+	if (flags & FIA_EX)
+		type |= G_IO_PRI;
+
+	tag = g_io_add_watch (channel, type, (GIOFunc) func, data);
+	g_io_channel_unref (channel);
+
+	return tag;
+}
+
+void
+fe_set_topic (session *sess, char *topic, char *stripped_topic)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), stripped_topic);
+		mg_set_topic_tip (sess);
+	} else
+	{
+		if (sess->res->topic_text)
+			free (sess->res->topic_text);
+		sess->res->topic_text = strdup (stripped_topic);
+	}
+}
+
+void
+fe_set_hilight (struct session *sess)
+{
+	if (sess->gui->is_tab)
+		fe_set_tab_color (sess, 3);	/* set tab to blue */
+
+	if (prefs.input_flash_hilight)
+		fe_flash_window (sess); /* taskbar flash */
+}
+
+static void
+fe_update_mode_entry (session *sess, GtkWidget *entry, char **text, char *new_text)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		if (sess->gui->flag_wid[0])	/* channel mode buttons enabled? */
+			gtk_entry_set_text (GTK_ENTRY (entry), new_text);
+	} else
+	{
+		if (sess->gui->is_tab)
+		{
+			if (*text)
+				free (*text);
+			*text = strdup (new_text);
+		}
+	}
+}
+
+void
+fe_update_channel_key (struct session *sess)
+{
+	fe_update_mode_entry (sess, sess->gui->key_entry,
+								 &sess->res->key_text, sess->channelkey);
+	fe_set_title (sess);
+}
+
+void
+fe_update_channel_limit (struct session *sess)
+{
+	char tmp[16];
+
+	sprintf (tmp, "%d", sess->limit);
+	fe_update_mode_entry (sess, sess->gui->limit_entry,
+								 &sess->res->limit_text, tmp);
+	fe_set_title (sess);
+}
+
+int
+fe_is_chanwindow (struct server *serv)
+{
+	if (!serv->gui->chanlist_window)
+		return 0;
+	return 1;
+}
+
+int
+fe_is_banwindow (struct session *sess)
+{
+   if (!sess->res->banlist_window)
+     return 0;
+   return 1;
+}
+
+void
+fe_notify_update (char *name)
+{
+	if (!name)
+		notify_gui_update ();
+}
+
+void
+fe_text_clear (struct session *sess, int lines)
+{
+	gtk_xtext_clear (sess->res->buffer, lines);
+}
+
+void
+fe_close_window (struct session *sess)
+{
+	if (sess->gui->is_tab)
+		mg_tab_close (sess);
+	else
+		gtk_widget_destroy (sess->gui->window);
+}
+
+void
+fe_progressbar_start (session *sess)
+{
+	if (!sess->gui->is_tab || current_tab == sess)
+	/* if it's the focused tab, create it for real! */
+		mg_progressbar_create (sess->gui);
+	else
+	/* otherwise just remember to create on when it gets focused */
+		sess->res->c_graph = TRUE;
+}
+
+void
+fe_progressbar_end (server *serv)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)				  /* check all windows that use this server and  *
+									   * remove the connecting graph, if it has one. */
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->gui->bar)
+				mg_progressbar_destroy (sess->gui);
+			sess->res->c_graph = FALSE;
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_print_text (struct session *sess, char *text, time_t stamp)
+{
+	PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.indent_nicks, stamp);
+
+	if (!sess->new_data && sess != current_tab &&
+		 sess->gui->is_tab && !sess->nick_said && stamp == 0)
+	{
+		sess->new_data = TRUE;
+		if (sess->msg_said)
+			fe_set_tab_color (sess, 2);
+		else
+			fe_set_tab_color (sess, 1);
+	}
+}
+
+void
+fe_beep (void)
+{
+	gdk_beep ();
+}
+
+#ifndef WIN32
+static int
+lastlog_regex_cmp (char *a, regex_t *reg)
+{
+	return !regexec (reg, a, 1, NULL, REG_NOTBOL);
+}
+#endif
+
+void
+fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp)
+{
+#ifndef WIN32
+	regex_t reg;
+#endif
+
+	if (gtk_xtext_is_empty (sess->res->buffer))
+	{
+		PrintText (lastlog_sess, _("Search buffer is empty.\n"));
+		return;
+	}
+
+	if (!regexp)
+	{
+		gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer,
+								 (void *) nocasestrstr, sstr);
+		return;
+	}
+
+#ifndef WIN32
+	if (regcomp (&reg, sstr, REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0)
+	{
+		gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer,
+								 (void *) lastlog_regex_cmp, &reg);
+		regfree (&reg);
+	}
+#endif
+}
+
+void
+fe_set_lag (server *serv, int lag)
+{
+	GSList *list = sess_list;
+	session *sess;
+	gdouble per;
+	char lagtext[64];
+	char lagtip[128];
+	unsigned long nowtim;
+
+	if (lag == -1)
+	{
+		if (!serv->lag_sent)
+			return;
+		nowtim = make_ping_time ();
+		lag = (nowtim - serv->lag_sent) / 100000;
+	}
+
+	per = (double)((double)lag / (double)10);
+	if (per > 1.0)
+		per = 1.0;
+
+	snprintf (lagtext, sizeof (lagtext) - 1, "%s%d.%ds",
+				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);
+	snprintf (lagtip, sizeof (lagtip) - 1, "Lag: %s%d.%d seconds",
+				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->res->lag_tip)
+				free (sess->res->lag_tip);
+			sess->res->lag_tip = strdup (lagtip);
+
+			if (!sess->gui->is_tab || current_tab == sess)
+			{
+				if (sess->gui->lagometer)
+				{
+					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->lagometer, per);
+					add_tip (sess->gui->lagometer->parent, lagtip);
+				}
+				if (sess->gui->laginfo)
+					gtk_label_set_text ((GtkLabel *) sess->gui->laginfo, lagtext);
+			} else
+			{
+				sess->res->lag_value = per;
+				if (sess->res->lag_text)
+					free (sess->res->lag_text);
+				sess->res->lag_text = strdup (lagtext);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_throttle (server *serv)
+{
+	GSList *list = sess_list;
+	struct session *sess;
+	float per;
+	char tbuf[96];
+	char tip[160];
+
+	per = (float) serv->sendq_len / 1024.0;
+	if (per > 1.0)
+		per = 1.0;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			snprintf (tbuf, sizeof (tbuf) - 1, _("%d bytes"), serv->sendq_len);
+			snprintf (tip, sizeof (tip) - 1, _("Network send queue: %d bytes"), serv->sendq_len);
+
+			if (sess->res->queue_tip)
+				free (sess->res->queue_tip);
+			sess->res->queue_tip = strdup (tip);
+
+			if (!sess->gui->is_tab || current_tab == sess)
+			{
+				if (sess->gui->throttlemeter)
+				{
+					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->throttlemeter, per);
+					add_tip (sess->gui->throttlemeter->parent, tip);
+				}
+				if (sess->gui->throttleinfo)
+					gtk_label_set_text ((GtkLabel *) sess->gui->throttleinfo, tbuf);
+			} else
+			{
+				sess->res->queue_value = per;
+				if (sess->res->queue_text)
+					free (sess->res->queue_text);
+				sess->res->queue_text = strdup (tbuf);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_ctrl_gui (session *sess, fe_gui_action action, int arg)
+{
+	switch (action)
+	{
+	case FE_GUI_HIDE:
+		gtk_widget_hide (sess->gui->window); break;
+	case FE_GUI_SHOW:
+		gtk_widget_show (sess->gui->window);
+		gtk_window_present (GTK_WINDOW (sess->gui->window));
+		break;
+	case FE_GUI_FOCUS:
+		mg_bring_tofront_sess (sess); break;
+	case FE_GUI_FLASH:
+		fe_flash_window (sess); break;
+	case FE_GUI_COLOR:
+		fe_set_tab_color (sess, arg); break;
+	case FE_GUI_ICONIFY:
+		gtk_window_iconify (GTK_WINDOW (sess->gui->window)); break;
+	case FE_GUI_MENU:
+		menu_bar_toggle ();	/* toggle menubar on/off */
+		break;
+	case FE_GUI_ATTACH:
+		mg_detach (sess, arg);	/* arg: 0=toggle 1=detach 2=attach */
+		break;
+	case FE_GUI_APPLY:
+		setup_apply_real (TRUE, TRUE);
+	}
+}
+
+static void
+dcc_saveas_cb (struct DCC *dcc, char *file)
+{
+	if (is_dcc (dcc))
+	{
+		if (dcc->dccstat == STAT_QUEUED)
+		{
+			if (file)
+				dcc_get_with_destfile (dcc, file);
+			else if (dcc->resume_sent == 0)
+				dcc_abort (dcc->serv->front_session, dcc);
+		}
+	}
+}
+
+void
+fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud)
+{
+	/* warning, assuming fe_confirm is used by DCC only! */
+	struct DCC *dcc = ud;
+
+	if (dcc->file)
+		gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file,
+								FRF_WRITE|FRF_FILTERISINITIAL|FRF_NOASKOVERWRITE);
+}
+
+int
+fe_gui_info (session *sess, int info_type)
+{
+	switch (info_type)
+	{
+	case 0:	/* window status */
+#if GTK_CHECK_VERSION(2,20,0)
+		if (!gtk_widget_get_visible (GTK_WIDGET (sess->gui->window)))
+#else
+		if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess->gui->window)))
+#endif
+			return 2;	/* hidden (iconified or systray) */
+#if GTK_CHECK_VERSION(2,4,0)
+		if (gtk_window_is_active (GTK_WINDOW (sess->gui->window)))
+#else
+#if GTK_CHECK_VERSION(2,2,0)
+		if (GTK_WINDOW (sess->gui->window)->is_active)
+#endif
+#endif
+			return 1;	/* active/focused */
+
+		return 0;		/* normal (no keyboard focus or behind a window) */
+	}
+
+	return -1;
+}
+
+void *
+fe_gui_info_ptr (session *sess, int info_type)
+{
+	switch (info_type)
+	{
+	case 0:	/* native window pointer (for plugins) */
+#ifdef WIN32
+		return GDK_WINDOW_HWND (sess->gui->window->window);
+#else
+		return sess->gui->window;
+#endif
+		break;
+
+	case 1:	/* GtkWindow * (for plugins) */
+		return sess->gui->window;
+	}
+	return NULL;
+}
+
+char *
+fe_get_inputbox_contents (session *sess)
+{
+	/* not the current tab */
+	if (sess->res->input_text)
+		return sess->res->input_text;
+
+	/* current focused tab */
+	return SPELL_ENTRY_GET_TEXT (sess->gui->input_box);
+}
+
+int
+fe_get_inputbox_cursor (session *sess)
+{
+	/* not the current tab (we don't remember the cursor pos) */
+	if (sess->res->input_text)
+		return 0;
+
+	/* current focused tab */
+	return SPELL_ENTRY_GET_POS (sess->gui->input_box);
+}
+
+void
+fe_set_inputbox_cursor (session *sess, int delta, int pos)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		if (delta)
+			pos += SPELL_ENTRY_GET_POS (sess->gui->input_box);
+		SPELL_ENTRY_SET_POS (sess->gui->input_box, pos);
+	} else
+	{
+		/* we don't support changing non-front tabs yet */
+	}
+}
+
+void
+fe_set_inputbox_contents (session *sess, char *text)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		SPELL_ENTRY_SET_TEXT (sess->gui->input_box, text);
+	} else
+	{
+		if (sess->res->input_text)
+			free (sess->res->input_text);
+		sess->res->input_text = strdup (text);
+	}
+}
+
+#ifndef WIN32
+
+static gboolean
+try_browser (const char *browser, const char *arg, const char *url)
+{
+	const char *argv[4];
+	char *path;
+
+	path = g_find_program_in_path (browser);
+	if (!path)
+		return 0;
+
+	argv[0] = path;
+	argv[1] = url;
+	argv[2] = NULL;
+	if (arg)
+	{
+		argv[1] = arg;
+		argv[2] = url;
+		argv[3] = NULL;
+	}
+	xchat_execv (argv);
+	g_free (path);
+	return 1;
+}
+
+#endif
+
+static void
+fe_open_url_inner (const char *url)
+{
+#ifdef WIN32
+	ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL);
+#else
+	/* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */
+	if (try_browser ("xdg-open", NULL, url))
+		return;
+
+	/* try to detect GNOME */
+	if (g_getenv ("GNOME_DESKTOP_SESSION_ID"))
+	{
+		if (try_browser ("gnome-open", NULL, url)) /* Gnome 2.4+ has this */
+			return;
+	}
+
+	/* try to detect KDE */
+	if (g_getenv ("KDE_FULL_SESSION"))
+	{
+		if (try_browser ("kfmclient", "exec", url))
+			return;
+	}
+
+	/* everything failed, what now? just try firefox */
+	if (try_browser ("firefox", NULL, url))
+		return;
+
+	/* fresh out of ideas... */
+	try_browser ("mozilla", NULL, url);
+#endif
+}
+
+static void
+fe_open_url_locale (const char *url)
+{
+#ifndef WIN32
+	if (url[0] != '/' && strchr (url, ':') == NULL)
+	{
+		url = g_strdup_printf ("http://%s", url);
+		fe_open_url_inner (url);
+		g_free ((char *)url);
+		return;
+	}
+#endif
+	fe_open_url_inner (url);
+}
+
+void
+fe_open_url (const char *url)
+{
+	char *loc;
+
+	if (prefs.utf8_locale)
+	{
+		fe_open_url_locale (url);
+		return;
+	}
+
+	/* the OS expects it in "locale" encoding. This makes it work on
+	   unix systems that use ISO-8859-x and Win32. */
+	loc = g_locale_from_utf8 (url, -1, 0, 0, 0);
+	if (loc)
+	{
+		fe_open_url_locale (loc);
+		g_free (loc);
+	}
+}
+
+void
+fe_server_event (server *serv, int type, int arg)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv && (current_tab == sess || !sess->gui->is_tab))
+		{
+			session_gui *gui = sess->gui;
+
+			switch (type)
+			{
+			case FE_SE_CONNECTING:	/* connecting in progress */
+			case FE_SE_RECONDELAY:	/* reconnect delay begun */
+				/* enable Disconnect item */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
+				break;
+
+			case FE_SE_CONNECT:
+				/* enable Disconnect and Away menu items */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 1);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
+				break;
+
+			case FE_SE_LOGGEDIN:	/* end of MOTD */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 1);
+				/* if number of auto-join channels is zero, open joind */
+				if (arg == 0)
+					joind_open (serv);
+				break;
+
+			case FE_SE_DISCONNECT:
+				/* disable Disconnect and Away menu items */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 0);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 0);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 0);
+				/* close the join-dialog, if one exists */
+				joind_close (serv);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_get_file (const char *title, char *initial,
+				 void (*callback) (void *userdata, char *file), void *userdata,
+				 int flags)
+				
+{
+	/* OK: Call callback once per file, then once more with file=NULL. */
+	/* CANCEL: Call callback once with file=NULL. */
+	gtkutil_file_req (title, callback, userdata, initial, flags | FRF_FILTERISINITIAL);
+}
diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h
new file mode 100644
index 00000000..12516259
--- /dev/null
+++ b/src/fe-gtk/fe-gtk.h
@@ -0,0 +1,197 @@
+#include "../../config.h"
+
+#ifdef WIN32
+/* If you're compiling this for Windows, your release is un-official
+ * and not condoned. Please don't use the XChat name. Make up your
+ * own name! */
+#define DISPLAY_NAME "XChat-Unofficial"
+#else
+#define DISPLAY_NAME "XChat"
+#endif
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <regex.h>
+#endif
+
+#if defined(ENABLE_NLS) && !defined(_)
+#  include <libintl.h>
+#  define _(x) gettext(x)
+#  ifdef gettext_noop
+#    define N_(String) gettext_noop (String)
+#  else
+#    define N_(String) (String)
+#  endif
+#endif
+#if !defined(ENABLE_NLS) && defined(_)
+#  undef _
+#  define N_(String) (String)
+#  define _(x) (x)
+#endif
+
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtksignal.h>
+
+#undef gtk_signal_connect
+#define gtk_signal_connect g_signal_connect
+
+#define flag_t flag_wid[0]
+#define flag_n flag_wid[1]
+#define flag_s flag_wid[2]
+#define flag_i flag_wid[3]
+#define flag_p flag_wid[4]
+#define flag_m flag_wid[5]
+#define flag_l flag_wid[6]
+#define flag_k flag_wid[7]
+#define flag_b flag_wid[8]
+#define NUM_FLAG_WIDS 9
+
+struct server_gui
+{
+	GtkWidget *rawlog_window;
+	GtkWidget *rawlog_textlist;
+
+	/* join dialog */
+	GtkWidget *joind_win;
+	GtkWidget *joind_entry;
+	GtkWidget *joind_radio1;
+	GtkWidget *joind_radio2;
+	GtkWidget *joind_check;
+
+	/* chanlist variables */
+	GtkWidget *chanlist_wild;		/* GtkEntry */
+	GtkWidget *chanlist_window;
+	GtkWidget *chanlist_list;
+	GtkWidget *chanlist_label;
+	GtkWidget *chanlist_min_spin;	/* minusers GtkSpinButton */
+	GtkWidget *chanlist_refresh;	/* buttons */
+	GtkWidget *chanlist_join;
+	GtkWidget *chanlist_savelist;
+	GtkWidget *chanlist_search;
+
+	GSList *chanlist_data_stored_rows;	/* stored list so it can be resorted  */
+	GSList *chanlist_pending_rows;
+	gint chanlist_tag;
+	gint chanlist_flash_tag;
+
+	gboolean chanlist_match_wants_channel;	/* match in channel name */
+	gboolean chanlist_match_wants_topic;	/* match in topic */
+
+#ifndef WIN32
+	regex_t chanlist_match_regex;	/* compiled regular expression here */
+	unsigned int have_regex;
+#endif
+
+	guint chanlist_users_found_count;	/* users total for all channels */
+	guint chanlist_users_shown_count;	/* users total for displayed channels */
+	guint chanlist_channels_found_count;	/* channel total for /LIST operation */
+	guint chanlist_channels_shown_count;	/* total number of displayed 
+														   channels */
+
+	int chanlist_maxusers;
+	int chanlist_minusers;
+	int chanlist_minusers_downloaded;	/* used by LIST IRC command */
+	int chanlist_search_type;		/* 0=simple 1=pattern/wildcard 2=regexp */
+	gboolean chanlist_caption_is_stale;
+};
+
+/* this struct is persistant even when delinking/relinking */
+
+typedef struct restore_gui
+{
+	/* banlist stuff */
+	GtkWidget *banlist_window;
+	GtkWidget *banlist_treeview;
+	GtkWidget *banlist_butRefresh;
+
+	void *tab;			/* (chan *) */
+
+	/* information stored when this tab isn't front-most */
+	void *user_model;	/* for filling the GtkTreeView */
+	void *buffer;		/* xtext_Buffer */
+	char *input_text;	/* input text buffer (while not-front tab) */
+	char *topic_text;	/* topic GtkEntry buffer */
+	char *key_text;
+	char *limit_text;
+	gfloat old_ul_value;	/* old userlist value (for adj) */
+	gfloat lag_value;	/* lag-o-meter */
+	char *lag_text;	/* lag-o-meter text */
+	char *lag_tip;		/* lag-o-meter tooltip */
+	gfloat queue_value; /* outbound queue meter */
+	char *queue_text;		/* outbound queue text */
+	char *queue_tip;		/* outbound queue tooltip */
+	short flag_wid_state[NUM_FLAG_WIDS];
+	unsigned int c_graph:1;	/* connecting graph, is there one? */
+} restore_gui;
+
+typedef struct session_gui
+{
+	GtkWidget
+		*xtext,
+		*vscrollbar,
+		*window,	/* toplevel */
+		*topic_entry,
+		*note_book,
+		*main_table,
+		*user_tree,	/* GtkTreeView */
+		*user_box,	/* userlist box */
+		*button_box_parent,
+		*button_box,	/* userlist buttons' box */
+		*dialogbutton_box,
+		*topicbutton_box,
+		*meter_box,	/* all the meters inside this */
+		*lagometer,
+		*laginfo,
+		*throttlemeter,
+		*throttleinfo,
+		*topic_bar,
+		*hpane_left,
+		*hpane_right,
+		*vpane_left,
+		*vpane_right,
+		*menu,
+		*bar,				/* connecting progress bar */
+		*nick_box,		/* contains label to the left of input_box */
+		*nick_label,
+		*op_xpm,			/* icon to the left of nickname */
+		*namelistinfo,	/* label above userlist */
+		*input_box,
+		*flag_wid[NUM_FLAG_WIDS],		/* channelmode buttons */
+		*limit_entry,		  /* +l */
+		*key_entry;		  /* +k */
+
+#define MENU_ID_NUM 12
+	GtkWidget *menu_item[MENU_ID_NUM+1]; /* some items we may change state of */
+
+	void *chanview;	/* chanview.h */
+
+	int bartag;		/*connecting progressbar timeout */
+
+	int pane_left_size;	/*last position of the pane*/
+	int pane_right_size;
+
+	guint16 is_tab;	/* is tab or toplevel? */
+	guint16 ul_hidden;	/* userlist hidden? */
+
+} session_gui;
+
+extern GdkPixmap *channelwin_pix;
+extern GdkPixmap *dialogwin_pix;
+
+
+#ifdef USE_GTKSPELL
+char *SPELL_ENTRY_GET_TEXT (GtkWidget *entry);
+#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_text_buffer_set_text (gtk_text_view_get_buffer(GTK_TEXT_VIEW(e)),txt,-1);
+#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_text_view_set_editable(GTK_TEXT_VIEW(e), v)
+int SPELL_ENTRY_GET_POS (GtkWidget *entry);
+void SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos);
+void SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos);
+#else
+#define SPELL_ENTRY_GET_TEXT(e) (GTK_ENTRY(e)->text)
+#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt)
+#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_editable_set_editable(GTK_EDITABLE(e),v)
+#define SPELL_ENTRY_GET_POS(e) gtk_editable_get_position(GTK_EDITABLE(e))
+#define SPELL_ENTRY_SET_POS(e,p) gtk_editable_set_position(GTK_EDITABLE(e),p);
+#define SPELL_ENTRY_INSERT(e,t,l,p) gtk_editable_insert_text(GTK_EDITABLE(e),t,l,p)
+#endif
diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c
new file mode 100644
index 00000000..014b5cc3
--- /dev/null
+++ b/src/fe-gtk/fkeys.c
@@ -0,0 +1,1814 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtklabel.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkoptionmenu.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/userlist.h"
+#include "../common/outbound.h"
+#include "../common/util.h"
+#include "../common/text.h"
+#include "../common/plugin.h"
+#include <gdk/gdkkeysyms.h>
+#include "gtkutil.h"
+#include "menu.h"
+#include "xtext.h"
+#include "palette.h"
+#include "maingui.h"
+#include "textgui.h"
+#include "fkeys.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+static void replace_handle (GtkWidget * wid);
+void key_action_tab_clean (void);
+
+/***************** Key Binding Code ******************/
+
+/* NOTES:
+
+   To add a new action:
+   1) inc KEY_MAX_ACTIONS
+   2) write the function at the bottom of this file (with all the others)
+   FIXME: Write about calling and returning
+   3) Add it to key_actions
+
+   --AGL
+
+ */
+
+/* Remember that the *number* of actions is this *plus* 1 --AGL */
+#define KEY_MAX_ACTIONS 14
+/* These are cp'ed from history.c --AGL */
+#define STATE_SHIFT     GDK_SHIFT_MASK
+#define	STATE_ALT	GDK_MOD1_MASK
+#define STATE_CTRL	GDK_CONTROL_MASK
+
+struct key_binding
+{
+	int keyval;						  /* GDK keynumber */
+	char *keyname;					  /* String with the name of the function */
+	int action;						  /* Index into key_actions */
+	int mod;							  /* Flags of STATE_* above */
+	char *data1, *data2;			  /* Pointers to strings, these must be freed */
+	struct key_binding *next;
+};
+
+struct key_action
+{
+	int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 struct session * sess);
+	char *name;
+	char *help;
+};
+
+struct gcomp_data
+{
+	char data[CHANLEN];
+	int elen;
+};
+
+static int key_load_kbs (char *);
+static void key_load_defaults ();
+static void key_save_kbs (char *);
+static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt,
+											  char *d1, char *d2, struct session *sess);
+int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+							  struct session *sess);
+static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt,
+											  char *d1, char *d2, struct session *sess);
+static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt,
+											 char *d1, char *d2, struct session *sess);
+static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt,
+											 char *d1, char *d2, struct session *sess);
+static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt,
+												char *d1, char *d2, struct session *sess);
+static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1,
+										  char *d2, struct session *sess);
+static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1,
+                                                                                        char *d2, struct session *sess);
+static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1,
+										 char *d2, struct session *sess);
+static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt,
+												 char *d1, char *d2,
+												 struct session *sess);
+static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt,
+												 char *d1, char *d2,
+												 struct session *sess);
+static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+
+static GtkWidget *key_dialog;
+static struct key_binding *keys_root = NULL;
+
+static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
+
+	{key_action_handle_command, "Run Command",
+	 N_("The \002Run Command\002 action runs the data in Data 1 as if it has been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate seperate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")},
+	{key_action_page_switch, "Change Page",
+	 N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position")},
+	{key_action_insert, "Insert in Buffer",
+	 N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")},
+	{key_action_scroll_page, "Scroll Page",
+	 N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")},
+	{key_action_set_buffer, "Set Buffer",
+	 N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
+	{key_action_history_up, "Last Command",
+	 N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")},
+	{key_action_history_down, "Next Command",
+	 N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")},
+	{key_action_tab_comp, "Complete nick/command",
+	 N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")},
+	{key_action_comp_chng, "Change Selected Nick",
+	 N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")},
+	{key_action_replace, "Check For Replace",
+	 N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")},
+	{key_action_move_tab_left, "Move front tab left",
+	 N_("This command moves the front tab left by one")},
+	{key_action_move_tab_right, "Move front tab right",
+	 N_("This command moves the front tab right by one")},
+	{key_action_move_tab_family_left, "Move tab family left",
+	 N_("This command moves the current tab family to the left")},
+	{key_action_move_tab_family_right, "Move tab family right",
+	 N_("This command moves the current tab family to the right")},
+	{key_action_put_history, "Push input line into history",
+	 N_("Push input line into history but doesn't send to server")},
+};
+
+void
+key_init ()
+{
+	keys_root = NULL;
+	if (key_load_kbs (NULL) == 1)
+	{
+		key_load_defaults ();
+		if (key_load_kbs (NULL) == 1)
+			fe_message (_("There was an error loading key"
+							" bindings configuration"), FE_MSG_ERROR);
+	}
+}
+
+static char *
+key_get_key_name (int keyval)
+{
+	return gdk_keyval_name (gdk_keyval_to_lower (keyval));
+}
+
+/* Ok, here are the NOTES
+
+   key_handle_key_press now handles all the key presses and history_keypress is
+   now defunct. It goes thru the linked list keys_root and finds a matching
+   key. It runs the action func and switches on these values:
+   0) Return
+   1) Find next
+   2) stop signal and return
+
+   * history_keypress is now dead (and gone)
+   * key_handle_key_press now takes its role
+   * All the possible actions are in a struct called key_actions (in fkeys.c)
+   * it is made of {function, name, desc}
+   * key bindings can pass 2 *text* strings to the handler. If more options are nee
+   ded a format can be put on one of these options
+   * key actions are passed {
+   the entry widget
+   the Gdk event
+   data 1
+   data 2
+   session struct
+   }
+   * key bindings are stored in a linked list of key_binding structs
+   * which looks like {
+   int keyval;  GDK keynumber
+   char *keyname;  String with the name of the function 
+   int action;  Index into key_actions 
+   int mod; Flags of STATE_* above 
+   char *data1, *data2;  Pointers to strings, these must be freed 
+   struct key_binding *next;
+   }
+   * remember that is (data1 || data2) != NULL then they need to be free()'ed
+
+   --AGL
+
+ */
+
+gboolean
+key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
+{
+	struct key_binding *kb, *last = NULL;
+	int keyval = evt->keyval;
+	int mod, n;
+	GSList *list;
+
+	/* where did this event come from? */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->input_box == wid)
+		{
+			if (sess->gui->is_tab)
+				sess = current_tab;
+			break;
+		}
+		list = list->next;
+	}
+	if (!list)
+		return FALSE;
+	current_sess = sess;
+
+	if (plugin_emit_keypress (sess, evt->state, evt->keyval, evt->length, evt->string))
+		return 1;
+
+	/* maybe the plugin closed this tab? */
+	if (!is_session (sess))
+		return 1;
+
+	mod = evt->state & (STATE_CTRL | STATE_ALT | STATE_SHIFT);
+
+	kb = keys_root;
+	while (kb)
+	{
+		if (kb->keyval == keyval && kb->mod == mod)
+		{
+			if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
+				return 0;
+
+			/* Bump this binding to the top of the list */
+			if (last != NULL)
+			{
+				last->next = kb->next;
+				kb->next = keys_root;
+				keys_root = kb;
+			}
+			/* Run the function */
+			n = key_actions[kb->action].handler (wid, evt, kb->data1,
+															 kb->data2, sess);
+			switch (n)
+			{
+			case 0:
+				return 1;
+			case 2:
+				g_signal_stop_emission_by_name (G_OBJECT (wid),
+														"key_press_event");
+				return 1;
+			}
+		}
+		last = kb;
+		kb = kb->next;
+	}
+
+	switch (keyval)
+	{
+	case GDK_space:
+		key_action_tab_clean ();
+		break;
+
+#if defined(USE_GTKSPELL) && !defined(WIN32)
+	/* gtktextview has no 'activate' event, so we trap ENTER here */
+	case GDK_Return:
+	case GDK_KP_Enter:
+		if (!(evt->state & GDK_CONTROL_MASK))
+		{
+			g_signal_stop_emission_by_name (G_OBJECT (wid), "key_press_event");
+			mg_inputbox_cb (wid, sess->gui);
+		}
+#endif
+	}
+
+	return 0;
+}
+
+/* Walks keys_root and free()'s everything */
+/*static void
+key_free_all ()
+{
+	struct key_binding *cur, *next;
+
+	cur = keys_root;
+	while (cur)
+	{
+		next = cur->next;
+		if (cur->data1)
+			free (cur->data1);
+		if (cur->data2)
+			free (cur->data2);
+		free (cur);
+		cur = next;
+	}
+	keys_root = NULL;
+}*/
+
+/* Turns mod flags into a C-A-S string */
+static char *
+key_make_mod_str (int mod, char *buf)
+{
+	int i = 0;
+
+	if (mod & STATE_CTRL)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'C';
+	}
+	if (mod & STATE_ALT)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'A';
+	}
+	if (mod & STATE_SHIFT)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'S';
+	}
+	buf[i] = 0;
+	return buf;
+}
+
+/* ***** GUI code here ******************* */
+
+/* NOTE: The key_dialog defin is above --AGL */
+static GtkWidget *key_dialog_act_menu, *key_dialog_kb_clist;
+static GtkWidget *key_dialog_tog_c, *key_dialog_tog_s, *key_dialog_tog_a;
+static GtkWidget *key_dialog_ent_key, *key_dialog_ent_d1, *key_dialog_ent_d2;
+static GtkWidget *key_dialog_text;
+
+static void
+key_load_defaults ()
+{
+		/* This is the default config */
+#define defcfg \
+		"C\nPrior\nChange Page\nD1:-1\nD2:Relative\n\n"\
+		"C\nNext\nChange Page\nD1:1\nD2:Relative\n\n"\
+		"A\n9\nChange Page\nD1:9\nD2!\n\n"\
+		"A\n8\nChange Page\nD1:8\nD2!\n\n"\
+		"A\n7\nChange Page\nD1:7\nD2!\n\n"\
+		"A\n6\nChange Page\nD1:6\nD2!\n\n"\
+		"A\n5\nChange Page\nD1:5\nD2!\n\n"\
+		"A\n4\nChange Page\nD1:4\nD2!\n\n"\
+		"A\n3\nChange Page\nD1:3\nD2!\n\n"\
+		"A\n2\nChange Page\nD1:2\nD2!\n\n"\
+		"A\n1\nChange Page\nD1:1\nD2!\n\n"\
+		"C\no\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"C\nb\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"C\nk\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\
+		"S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\
+		"None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\
+		"None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\
+		"S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\
+		"S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\
+		"None\nDown\nNext Command\nD1!\nD2!\n\n"\
+		"None\nUp\nLast Command\nD1!\nD2!\n\n"\
+		"None\nTab\nComplete nick/command\nD1!\nD2!\n\n"\
+		"None\nspace\nCheck For Replace\nD1!\nD2!\n\n"\
+		"None\nReturn\nCheck For Replace\nD1!\nD2!\n\n"\
+		"None\nKP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\
+		"C\nTab\nComplete nick/command\nD1:Up\nD2!\n\n"\
+		"A\nLeft\nMove front tab left\nD1!\nD2!\n\n"\
+		"A\nRight\nMove front tab right\nD1!\nD2!\n\n"\
+		"CS\nPrior\nMove tab family left\nD1!\nD2!\n\n"\
+		"CS\nNext\nMove tab family right\nD1!\nD2!\n\n"\
+		"None\nF9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
+	int fd;
+
+	fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, XOF_DOMODE);
+	if (fd < 0)
+		/* ???!!! */
+		return;
+
+	write (fd, defcfg, strlen (defcfg));
+	close (fd);
+}
+
+static void
+key_dialog_close ()
+{
+	key_dialog = NULL;
+	key_save_kbs (NULL);
+}
+
+static void
+key_dialog_add_new (GtkWidget * button, GtkCList * list)
+{
+	gchar *strs[] = { "", NULL, NULL, NULL, NULL };
+	struct key_binding *kb;
+
+	strs[1] = _("<none>");
+	strs[2] = _("<none>");
+	strs[3] = _("<none>");
+	strs[4] = _("<none>");
+
+	kb = malloc (sizeof (struct key_binding));
+
+	kb->keyval = 0;
+	kb->keyname = NULL;
+	kb->action = -1;
+	kb->mod = 0;
+	kb->data1 = kb->data2 = NULL;
+	kb->next = keys_root;
+
+	keys_root = kb;
+
+	gtk_clist_set_row_data (GTK_CLIST (list),
+									gtk_clist_append (GTK_CLIST (list), strs), kb);
+
+}
+
+static void
+key_dialog_delete (GtkWidget * button, GtkCList * list)
+{
+	struct key_binding *kb, *cur, *last;
+	int row = gtkutil_clist_selection ((GtkWidget *) list);
+
+	if (row != -1)
+	{
+		kb = gtk_clist_get_row_data (list, row);
+		cur = keys_root;
+		last = NULL;
+		while (cur)
+		{
+			if (cur == kb)
+			{
+				if (last)
+					last->next = kb->next;
+				else
+					keys_root = kb->next;
+
+				if (kb->data1)
+					free (kb->data1);
+				if (kb->data2)
+					free (kb->data2);
+				free (kb);
+				gtk_clist_remove (list, row);
+				return;
+			}
+			last = cur;
+			cur = cur->next;
+		}
+		printf ("*** key_dialog_delete: couldn't find kb in list!\n");
+		/*if (getenv ("XCHAT_DEBUG"))
+			abort ();*/
+	}
+}
+
+static void
+key_print_text (GtkXText *xtext, char *text)
+{
+	unsigned int old = prefs.timestamp;
+	prefs.timestamp = 0;	/* temporarily disable stamps */
+	gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
+	PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
+	prefs.timestamp = old;
+}
+
+static void
+key_dialog_sel_act (GtkWidget * un, int num)
+{
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+
+	if (row != -1)
+	{
+		kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+		kb->action = num;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 2,
+								  _(key_actions[num].name));
+		if (key_actions[num].help)
+		{
+			key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[num].help));
+		}
+	}
+}
+
+static void
+key_dialog_sel_row (GtkWidget * clist, gint row, gint column,
+						  GdkEventButton * evt, gpointer data)
+{
+	struct key_binding *kb = gtk_clist_get_row_data (GTK_CLIST (clist), row);
+
+	if (kb == NULL)
+	{
+		printf ("*** key_dialog_sel_row: kb == NULL\n");
+		abort ();
+	}
+	if (kb->action > -1 && kb->action <= KEY_MAX_ACTIONS)
+	{
+		gtk_option_menu_set_history (GTK_OPTION_MENU (key_dialog_act_menu),
+											  kb->action);
+		if (key_actions[kb->action].help)
+		{
+			key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[kb->action].help));
+		}
+	}
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_c),
+										  (kb->mod & STATE_CTRL) == STATE_CTRL);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_s),
+										  (kb->mod & STATE_SHIFT) == STATE_SHIFT);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_a),
+										  (kb->mod & STATE_ALT) == STATE_ALT);
+
+	if (kb->data1)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), kb->data1);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), "");
+
+	if (kb->data2)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), kb->data2);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), "");
+
+	if (kb->keyname)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), kb->keyname);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), "");
+}
+
+static void
+key_dialog_tog_key (GtkWidget * tog, int kstate)
+{
+	int state = GTK_TOGGLE_BUTTON (tog)->active;
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+	char buf[32];
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	if (state)
+		kb->mod |= kstate;
+	else
+		kb->mod &= ~kstate;
+
+	gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 0,
+							  key_make_mod_str (kb->mod, buf));
+}
+
+static GtkWidget *
+key_dialog_make_toggle (char *label, void *callback, void *option,
+								GtkWidget * box)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (label);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC (callback), option);
+	gtk_box_pack_end (GTK_BOX (box), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	return wid;
+}
+
+static GtkWidget *
+key_dialog_make_entry (char *label, char *act, void *callback, void *option,
+							  GtkWidget * box)
+{
+	GtkWidget *wid, *hbox;;
+
+	hbox = gtk_hbox_new (0, 2);
+	if (label)
+	{
+		wid = gtk_label_new (label);
+		gtk_widget_show (wid);
+		gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	}
+	wid = gtk_entry_new ();
+	if (act)
+	{
+		gtk_signal_connect (GTK_OBJECT (wid), act, GTK_SIGNAL_FUNC (callback),
+								  option);
+	}
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	gtk_widget_show (hbox);
+
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	return wid;
+}
+
+static void
+key_dialog_set_key (GtkWidget * entry, GdkEventKey * evt, void *none)
+{
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+
+	gtk_entry_set_text (GTK_ENTRY (entry), "");
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	kb->keyval = evt->keyval;
+	kb->keyname = key_get_key_name (kb->keyval);
+	gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 1, kb->keyname);
+	gtk_entry_set_text (GTK_ENTRY (entry), kb->keyname);
+	g_signal_stop_emission_by_name (G_OBJECT (entry), "key_press_event");
+}
+
+static void
+key_dialog_set_data (GtkWidget * entry, int d)
+{
+	const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+	char *buf;
+	int len = strlen (text);
+
+	len++;
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	if (d == 0)
+	{									  /* using data1 */
+		if (kb->data1)
+			free (kb->data1);
+		buf = (char *) malloc (len);
+		memcpy (buf, text, len);
+		kb->data1 = buf;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 3, text);
+	} else
+	{
+		if (kb->data2)
+			free (kb->data2);
+		buf = (char *) malloc (len);
+		memcpy (buf, text, len);
+		kb->data2 = buf;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 4, text);
+	}
+}
+
+void
+key_dialog_show ()
+{
+	GtkWidget *vbox, *hbox, *list, *vbox2, *wid, *wid2, *wid3, *hbox2;
+	struct key_binding *kb;
+	gchar *titles[] = { NULL, NULL, NULL, "1", "2" };
+	char temp[32];
+	int i;
+
+	titles[0] = _("Mod");
+	titles[1] = _("Key");
+	titles[2] = _("Action");
+
+	if (key_dialog)
+	{
+		mg_bring_tofront (key_dialog);
+		return;
+	}
+
+	key_dialog =
+			  mg_create_generic_tab ("editkeys", _("XChat: Keyboard Shortcuts"),
+							TRUE, FALSE, key_dialog_close, NULL, 560, 330, &vbox, 0);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, 1, 1, 0);
+
+	list = gtkutil_clist_new (5, titles, hbox, 0, key_dialog_sel_row, 0, NULL,
+									  0, GTK_SELECTION_SINGLE);
+	gtk_widget_set_usize (list, 400, 0);
+	key_dialog_kb_clist = list;
+
+	gtk_widget_show (hbox);
+
+	kb = keys_root;
+
+	gtk_clist_set_column_width (GTK_CLIST (list), 1, 50);
+	gtk_clist_set_column_width (GTK_CLIST (list), 2, 120);
+	gtk_clist_set_column_width (GTK_CLIST (list), 3, 50);
+	gtk_clist_set_column_width (GTK_CLIST (list), 4, 50);
+
+	while (kb)
+	{
+		titles[0] = key_make_mod_str (kb->mod, temp);
+		titles[1] = kb->keyname;
+		if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
+			titles[2] = _("<none>");
+		else
+			titles[2] = key_actions[kb->action].name;
+		if (kb->data1)
+			titles[3] = kb->data1;
+		else
+			titles[3] = _("<none>");
+
+		if (kb->data2)
+			titles[4] = kb->data2;
+		else
+			titles[4] = _("<none>");
+
+		gtk_clist_set_row_data (GTK_CLIST (list),
+										gtk_clist_append (GTK_CLIST (list), titles),
+										kb);
+
+		kb = kb->next;
+	}
+
+	vbox2 = gtk_vbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (hbox), vbox2, 1, 1, 0);
+	wid = gtk_button_new_with_label (_("Add New"));
+	gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (key_dialog_add_new), list);
+	gtk_widget_show (wid);
+	wid = gtk_button_new_with_label (_("Delete"));
+	gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (key_dialog_delete), list);
+	gtk_widget_show (wid);
+	gtk_widget_show (vbox2);
+
+	wid = gtk_option_menu_new ();
+	wid2 = gtk_menu_new ();
+
+	for (i = 0; i <= KEY_MAX_ACTIONS; i++)
+	{
+		wid3 = gtk_menu_item_new_with_label (_(key_actions[i].name));
+		gtk_widget_show (wid3);
+		gtk_menu_shell_append (GTK_MENU_SHELL (wid2), wid3);
+		gtk_signal_connect (GTK_OBJECT (wid3), "activate",
+								  GTK_SIGNAL_FUNC (key_dialog_sel_act),
+								  GINT_TO_POINTER (i));
+	}
+
+	gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), wid2);
+	gtk_option_menu_set_history (GTK_OPTION_MENU (wid), 0);
+	gtk_box_pack_end (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	key_dialog_act_menu = wid;
+
+	key_dialog_tog_s = key_dialog_make_toggle (_("Shift"), key_dialog_tog_key,
+															 (void *) STATE_SHIFT, vbox2);
+	key_dialog_tog_a = key_dialog_make_toggle (_("Alt"), key_dialog_tog_key,
+															 (void *) STATE_ALT, vbox2);
+	key_dialog_tog_c = key_dialog_make_toggle (_("Ctrl"), key_dialog_tog_key,
+															 (void *) STATE_CTRL, vbox2);
+
+	key_dialog_ent_key = key_dialog_make_entry (_("Key"), "key_press_event",
+															  key_dialog_set_key, NULL,
+															  vbox2);
+
+	key_dialog_ent_d1 = key_dialog_make_entry (_("Data 1"), "activate",
+															 key_dialog_set_data, NULL,
+															 vbox2);
+	key_dialog_ent_d2 = key_dialog_make_entry (_("Data 2"), "activate",
+															 key_dialog_set_data,
+															 (void *) 1, vbox2);
+
+	hbox2 = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox2, 0, 0, 1);
+
+	wid = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (wid), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (wid),
+									  channelwin_pix,
+									  prefs.transparent);
+	gtk_widget_set_usize (wid, 0, 75);
+	gtk_box_pack_start (GTK_BOX (hbox2), wid, 1, 1, 1);
+	gtk_xtext_set_font (GTK_XTEXT (wid), prefs.font_normal);
+	gtk_widget_show (wid);
+
+	wid2 = gtk_vscrollbar_new (GTK_XTEXT (wid)->adj);
+	gtk_box_pack_start (GTK_BOX (hbox2), wid2, 0, 0, 0);
+	gtk_widget_show (wid2);
+
+	gtk_widget_show (hbox2);
+	key_dialog_text = wid;
+
+	gtk_widget_show_all (key_dialog);
+}
+
+static void
+key_save_kbs (char *fn)
+{
+	int fd, i;
+	char buf[512];
+	struct key_binding *kb;
+
+	if (!fn)
+		fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE);
+	else
+		fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE | XOF_FULLPATH);
+	if (fd < 0)
+	{
+		fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR);
+		return;
+	}
+	write (fd, buf,
+			 snprintf (buf, 510, "# XChat key bindings config file\n\n"));
+
+	kb = keys_root;
+	i = 0;
+
+	while (kb)
+	{
+		if (kb->keyval == -1 || kb->keyname == NULL || kb->action < 0)
+		{
+			kb = kb->next;
+			continue;
+		}
+		i = 0;
+		if (kb->mod & STATE_CTRL)
+		{
+			i++;
+			write (fd, "C", 1);
+		}
+		if (kb->mod & STATE_ALT)
+		{
+			i++;
+			write (fd, "A", 1);
+		}
+		if (kb->mod & STATE_SHIFT)
+		{
+			i++;
+			write (fd, "S", 1);
+		}
+		if (i == 0)
+			write (fd, "None\n", 5);
+		else
+			write (fd, "\n", 1);
+
+		write (fd, buf, snprintf (buf, 510, "%s\n%s\n", kb->keyname,
+										  key_actions[kb->action].name));
+		if (kb->data1 && kb->data1[0])
+			write (fd, buf, snprintf (buf, 510, "D1:%s\n", kb->data1));
+		else
+			write (fd, "D1!\n", 4);
+
+		if (kb->data2 && kb->data2[0])
+			write (fd, buf, snprintf (buf, 510, "D2:%s\n", kb->data2));
+		else
+			write (fd, "D2!\n", 4);
+
+		write (fd, "\n", 1);
+
+		kb = kb->next;
+	}
+
+	close (fd);
+}
+
+/* I just know this is going to be a nasty parse, if you think it's bugged
+   it almost certainly is so contact the XChat dev team --AGL */
+
+static inline int
+key_load_kbs_helper_mod (char *in, int *out)
+{
+	int n, len, mod = 0;
+
+	/* First strip off the fluff */
+	while (in[0] == ' ' || in[0] == '\t')
+		in++;
+	len = strlen (in);
+	while (in[len] == ' ' || in[len] == '\t')
+	{
+		in[len] = 0;
+		len--;
+	}
+
+	if (strcmp (in, "None") == 0)
+	{
+		*out = 0;
+		return 0;
+	}
+	for (n = 0; n < len; n++)
+	{
+		switch (in[n])
+		{
+		case 'C':
+			mod |= STATE_CTRL;
+			break;
+		case 'A':
+			mod |= STATE_ALT;
+			break;
+		case 'S':
+			mod |= STATE_SHIFT;
+			break;
+		default:
+			return 1;
+		}
+	}
+
+	*out = mod;
+	return 0;
+}
+
+/* These are just local defines to keep me sane --AGL */
+
+#define KBSTATE_MOD 0
+#define KBSTATE_KEY 1
+#define KBSTATE_ACT 2
+#define KBSTATE_DT1 3
+#define KBSTATE_DT2 4
+
+/* *** Warning, Warning! - massive function ahead! --AGL */
+
+static int
+key_load_kbs (char *filename)
+{
+	char *buf, *ibuf;
+	struct stat st;
+	struct key_binding *kb = NULL, *last = NULL;
+	int fd, len, pnt = 0, state = 0, n;
+
+	if (filename == NULL)
+		fd = xchat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
+	else
+		fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
+	if (fd < 0)
+		return 1;
+	if (fstat (fd, &st) != 0)
+		return 1;
+	ibuf = malloc (st.st_size);
+	read (fd, ibuf, st.st_size);
+	close (fd);
+
+	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
+	{
+		if (buf[0] == '#')
+			continue;
+		if (strlen (buf) == 0)
+			continue;
+
+		switch (state)
+		{
+		case KBSTATE_MOD:
+			kb = (struct key_binding *) malloc (sizeof (struct key_binding));
+			if (key_load_kbs_helper_mod (buf, &kb->mod))
+				goto corrupt_file;
+			state = KBSTATE_KEY;
+			continue;
+		case KBSTATE_KEY:
+			/* First strip off the fluff */
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+			len = strlen (buf);
+			while (buf[len] == ' ' || buf[len] == '\t')
+			{
+				buf[len] = 0;
+				len--;
+			}
+
+			n = gdk_keyval_from_name (buf);
+			if (n == 0)
+			{
+				/* Unknown keyname, abort */
+				if (last)
+					last->next = NULL;
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Unknown keyname %s in key bindings config file\nLoad aborted, please fix %s/keybindings.conf\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 2;
+			}
+			kb->keyname = gdk_keyval_name (n);
+			kb->keyval = n;
+
+			state = KBSTATE_ACT;
+			continue;
+		case KBSTATE_ACT:
+			/* First strip off the fluff */
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+			len = strlen (buf);
+			while (buf[len] == ' ' || buf[len] == '\t')
+			{
+				buf[len] = 0;
+				len--;
+			}
+
+			for (n = 0; n < KEY_MAX_ACTIONS + 1; n++)
+			{
+				if (strcmp (key_actions[n].name, buf) == 0)
+				{
+					kb->action = n;
+					break;
+				}
+			}
+
+			if (n == KEY_MAX_ACTIONS + 1)
+			{
+				if (last)
+					last->next = NULL;
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Unknown action %s in key bindings config file\nLoad aborted, Please fix %s/keybindings\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 3;
+			}
+			state = KBSTATE_DT1;
+			continue;
+		case KBSTATE_DT1:
+		case KBSTATE_DT2:
+			if (state == KBSTATE_DT1)
+				kb->data1 = kb->data2 = NULL;
+
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+
+			if (buf[0] != 'D')
+			{
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Expecting Data line (beginning Dx{:|!}) but got:\n%s\n\nLoad aborted, Please fix %s/keybindings\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 4;
+			}
+			switch (buf[1])
+			{
+			case '1':
+				if (state != KBSTATE_DT1)
+					goto corrupt_file;
+				break;
+			case '2':
+				if (state != KBSTATE_DT2)
+					goto corrupt_file;
+				break;
+			default:
+				goto corrupt_file;
+			}
+
+			if (buf[2] == ':')
+			{
+				len = strlen (buf);
+				/* Add one for the NULL, subtract 3 for the "Dx:" */
+				len++;
+				len -= 3;
+				if (state == KBSTATE_DT1)
+				{
+					kb->data1 = malloc (len);
+					memcpy (kb->data1, &buf[3], len);
+				} else
+				{
+					kb->data2 = malloc (len);
+					memcpy (kb->data2, &buf[3], len);
+				}
+			} else if (buf[2] == '!')
+			{
+				if (state == KBSTATE_DT1)
+					kb->data1 = NULL;
+				else
+					kb->data2 = NULL;
+			}
+			if (state == KBSTATE_DT1)
+			{
+				state = KBSTATE_DT2;
+				continue;
+			} else
+			{
+				if (last)
+					last->next = kb;
+				else
+					keys_root = kb;
+				last = kb;
+
+				state = KBSTATE_MOD;
+			}
+
+			continue;
+		}
+	}
+	if (last)
+		last->next = NULL;
+	free (ibuf);
+	return 0;
+
+ corrupt_file:
+	/*if (getenv ("XCHAT_DEBUG"))
+		abort ();*/
+	snprintf (ibuf, 1024,
+						_("Key bindings config file is corrupt, load aborted\n"
+								 "Please fix %s/keybindings.conf\n"),
+						 get_xdir_utf8 ());
+	fe_message (ibuf, FE_MSG_ERROR);
+	free (ibuf);
+	return 5;
+}
+
+/* ***** Key actions start here *********** */
+
+/* See the NOTES above --AGL */
+
+/* "Run command" */
+static int
+key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
+									char *d2, struct session *sess)
+{
+	int ii, oi, len;
+	char out[2048], d = 0;
+
+	if (!d1)
+		return 0;
+
+	len = strlen (d1);
+
+	/* Replace each "\n" substring with '\n' */
+	for (ii = oi = 0; ii < len; ii++)
+	{
+		d = d1[ii];
+		if (d == '\\')
+		{
+			ii++;
+			d = d1[ii];
+			if (d == 'n')
+				out[oi++] = '\n';
+			else if (d == '\\')
+				out[oi++] = '\\';
+			else
+			{
+				out[oi++] = '\\';
+				out[oi++] = d;
+			}
+			continue;
+		}
+		out[oi++] = d;
+	}
+	out[oi] = 0;
+
+	handle_multiline (sess, out, 0, 0);
+	return 0;
+}
+
+static int
+key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
+								char *d2, struct session *sess)
+{
+	int len, i, num;
+
+	if (!d1)
+		return 1;
+
+	len = strlen (d1);
+	if (!len)
+		return 1;
+
+	for (i = 0; i < len; i++)
+	{
+		if (d1[i] < '0' || d1[i] > '9')
+		{
+			if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
+				continue;
+			else
+				return 1;
+		}
+	}
+
+	num = atoi (d1);
+	if (!d2)
+		num--;
+	if (!d2 || d2[0] == 0)
+		mg_switch_page (FALSE, num);
+	else
+		mg_switch_page (TRUE, num);
+	return 0;
+}
+
+int
+key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 struct session *sess)
+{
+	int tmp_pos;
+
+	if (!d1)
+		return 1;
+
+	tmp_pos = SPELL_ENTRY_GET_POS (wid);
+	SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos);
+	SPELL_ENTRY_SET_POS (wid, tmp_pos);
+	return 2;
+}
+
+/* handles PageUp/Down keys */
+static int
+key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
+								char *d2, struct session *sess)
+{
+	int value, end;
+	GtkAdjustment *adj;
+	enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
+	int type = PAGE_DOWN;
+
+	if (d1)
+	{
+		if (!strcasecmp (d1, "up"))
+			type = PAGE_UP;
+		else if (!strcasecmp (d1, "+1"))
+			type = LINE_DOWN;
+		else if (!strcasecmp (d1, "-1"))
+			type = LINE_UP;
+	}
+
+	if (!sess)
+		return 0;
+
+	adj = GTK_RANGE (sess->gui->vscrollbar)->adjustment;
+	end = adj->upper - adj->lower - adj->page_size;
+
+	switch (type)
+	{
+	case LINE_UP:
+		value = adj->value - 1.0;
+		break;
+
+	case LINE_DOWN:
+		value = adj->value + 1.0;
+		break;
+
+	case PAGE_UP:
+		value = adj->value - (adj->page_size - 1);
+		break;
+
+	default:	/* PAGE_DOWN */
+		value = adj->value + (adj->page_size - 1);
+		break;
+	}
+
+	if (value < 0)
+		value = 0;
+	if (value > end)
+		value = end;
+
+	gtk_adjustment_set_value (adj, value);
+
+	return 0;
+}
+
+static int
+key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+							  struct session *sess)
+{
+	if (!d1)
+		return 1;
+	if (d1[0] == 0)
+		return 1;
+
+	SPELL_ENTRY_SET_TEXT (wid, d1);
+	SPELL_ENTRY_SET_POS (wid, -1);
+
+	return 2;
+}
+
+static int
+key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+							  struct session *sess)
+{
+	char *new_line;
+
+	new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
+	if (new_line)
+	{
+		SPELL_ENTRY_SET_TEXT (wid, new_line);
+		SPELL_ENTRY_SET_POS (wid, -1);
+	}
+
+	return 2;
+}
+
+static int
+key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								 char *d2, struct session *sess)
+{
+	char *new_line;
+
+	new_line = history_down (&sess->history);
+	if (new_line)
+	{
+		SPELL_ENTRY_SET_TEXT (wid, new_line);
+		SPELL_ENTRY_SET_POS (wid, -1);
+	}
+
+	return 2;
+}
+
+/* old data that we reuse */
+static struct gcomp_data old_gcomp;
+
+/* work on the data, ie return only channels */
+static int
+double_chan_cb (session *lsess, GList **list)
+{
+	if (lsess->type == SESS_CHANNEL)
+		*list = g_list_prepend(*list, lsess->channel);
+	return TRUE;
+}
+
+/* convert a slist -> list. */
+static GList *
+chanlist_double_list (GSList *inlist)
+{
+	GList *list = NULL;
+	g_slist_foreach(inlist, (GFunc)double_chan_cb, &list);
+	return list;
+}
+
+/* handle commands */
+static int
+double_cmd_cb (struct popup *pop, GList **list)
+{
+	*list = g_list_prepend(*list, pop->name);
+	return TRUE;
+}
+
+/* convert a slist -> list. */
+static GList *
+cmdlist_double_list (GSList *inlist)
+{
+	GList *list = NULL;
+	g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list);
+	return list;
+}
+
+static char *
+gcomp_nick_func (char *data)
+{
+	if (data)
+		return ((struct User *)data)->nick;
+	return "";
+}
+
+void
+key_action_tab_clean(void)
+{
+	if (old_gcomp.elen)
+	{
+		old_gcomp.data[0] = 0;
+		old_gcomp.elen = 0;
+	}
+}
+
+/* Used in the followig completers */
+#define COMP_BUF 2048
+
+/* For use in sorting the user list for completion */
+static int
+talked_recent_cmp (struct User *a, struct User *b)
+{
+	if (a->lasttalk < b->lasttalk)
+		return -1;
+
+	if (a->lasttalk > b->lasttalk)
+		return 1;
+
+	return 0;
+}
+
+static int
+key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2,
+							struct session *sess)
+{
+	int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, found = 0,
+	    prefix_len, skip_len = 0, is_nick, is_cmd = 0;
+	char buf[COMP_BUF], ent[CHANLEN], *postfix = NULL, *result, *ch;
+	GList *list = NULL, *tmp_list = NULL;
+	const char *text;
+	GCompletion *gcomp = NULL;
+
+	/* force the IM Context to reset */
+	SPELL_ENTRY_SET_EDITABLE (t, FALSE);
+	SPELL_ENTRY_SET_EDITABLE (t, TRUE);
+
+	text = SPELL_ENTRY_GET_TEXT (t);
+	if (text[0] == 0)
+		return 1;
+
+	len = g_utf8_strlen (text, -1); /* must be null terminated */
+
+	cursor_pos = SPELL_ENTRY_GET_POS (t);
+
+	buf[0] = 0; /* make sure we don't get garbage in the buffer */
+
+	/* handle "nick: " or "nick " or "#channel "*/
+	ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos));
+	if (ch && ch[0] == ' ')
+	{
+		skip_len++;
+		ch = g_utf8_find_prev_char(text, ch);
+		if (!ch)
+			return 2;
+
+		cursor_pos = g_utf8_pointer_to_offset(text, ch);
+		if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' || 
+					g_utf8_get_char_validated(ch, -1) == ',' ||
+					g_utf8_get_char_validated(ch, -1) == prefs.nick_suffix[0]))
+		{
+			skip_len++;
+		}
+		else
+			cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1));
+	}
+
+	comp = skip_len;
+	
+	/* store the text following the cursor for reinsertion later */
+	if ((cursor_pos + skip_len) < len)
+		postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len);
+
+	for (ent_start = cursor_pos; ; --ent_start)
+	{
+		if (ent_start == 0)
+			break;
+		ch = g_utf8_offset_to_pointer(text, ent_start - 1);
+		if (ch && ch[0] == ' ')
+			break;
+	}
+
+	if (ent_start == 0 && text[0] == prefs.cmdchar[0])
+	{
+		ent_start++;
+		is_cmd = 1;
+	}
+	
+	prefix_len = ent_start;
+	elen = cursor_pos - ent_start;
+
+	g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen);
+
+	is_nick = (ent[0] == '#' || ent[0] == '&' || is_cmd) ? 0 : 1;
+	
+	if (sess->type == SESS_DIALOG && is_nick)
+	{
+		/* tab in a dialog completes the other person's name */
+		if (rfc_ncasecmp (sess->channel, ent, elen) == 0)
+		{
+			result =  sess->channel;
+			is_nick = 0;
+		}
+		else
+			return 2;
+	}
+	else
+	{
+		if (is_nick)
+		{
+			gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func);
+			tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */
+			if (prefs.completion_sort == 1)	/* sort in last-talk order? */
+				tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp);
+		}
+		else
+		{
+			gcomp = g_completion_new (NULL);
+			if (is_cmd)
+			{
+				tmp_list = cmdlist_double_list (command_list);
+				for(i = 0; xc_cmds[i].name != NULL ; i++)
+				{
+					tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name);
+				}
+				tmp_list = plugin_command_list(tmp_list);
+			}
+			else
+				tmp_list = chanlist_double_list (sess_list);
+		}
+		tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */
+		g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp);
+		if (tmp_list)
+		{
+			g_completion_add_items (gcomp, tmp_list);
+			g_list_free (tmp_list);
+		}
+
+		if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0))
+		{
+			key_action_tab_clean ();
+			comp = 0;
+		}
+	
+#if GLIB_CHECK_VERSION(2,4,0)
+		list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result);
+#else
+		list = g_completion_complete (gcomp, comp ? old_gcomp.data : ent, &result);
+#endif
+		
+		if (result == NULL) /* No matches found */
+		{
+			g_completion_free(gcomp);
+			return 2;
+		}
+
+		if (comp) /* existing completion */
+		{
+			while(list) /* find the current entry */
+			{
+				if(rfc_ncasecmp(list->data, ent, elen) == 0)
+				{
+					found = 1;
+					break;
+				}
+				list = list->next;
+			}
+
+			if (found)
+			{
+				if (!(d1 && d1[0])) /* not holding down shift */
+				{
+					if (g_list_next(list) == NULL)
+						list = g_list_first(list);
+					else
+						list = g_list_next(list);
+				}
+				else
+				{
+					if (g_list_previous(list) == NULL)
+						list = g_list_last(list);
+					else
+						list = g_list_previous(list);
+				}
+				g_free(result);
+				result = (char*)list->data;
+			}
+			else
+			{
+				g_free(result);
+				g_completion_free(gcomp);
+				return 2;
+			}
+		}
+		else
+		{
+			strcpy(old_gcomp.data, ent);
+			old_gcomp.elen = elen;
+
+			/* Get the first nick and put out the data for future nickcompletes */
+			if (prefs.completion_amount && g_list_length (list) <= prefs.completion_amount)
+			{
+				g_free(result);
+				result = (char*)list->data;
+			}
+			else
+			{
+				/* bash style completion */
+				if (g_list_next(list) != NULL)
+				{
+					if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */
+					{
+						if (prefix_len)
+							g_utf8_strncpy (buf, text, prefix_len);
+						strncat (buf, result, COMP_BUF - prefix_len);
+						cursor_pos = strlen (buf);
+						g_free(result);
+#if !GLIB_CHECK_VERSION(2,4,0)
+						g_utf8_validate (buf, -1, (const gchar **)&result);
+						(*result) = 0;
+#endif
+						if (postfix)
+						{
+							strcat (buf, " ");
+							strncat (buf, postfix, COMP_BUF - cursor_pos -1);
+						}
+						SPELL_ENTRY_SET_TEXT (t, buf);
+						SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
+						buf[0] = 0;
+					}
+					else
+						g_free(result);
+					while (list)
+					{
+						len = strlen (buf);	/* current buffer */
+						elen = strlen (list->data);	/* next item to add */
+						if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */
+						{
+							PrintText (sess, buf);
+							buf[0] = 0;
+							len = 0;
+						}
+						strcpy (buf + len, (char *) list->data);
+						strcpy (buf + len + elen, " ");
+						list = list->next;
+					}
+					PrintText (sess, buf);
+					g_completion_free(gcomp);
+					return 2;
+				}
+				/* Only one matching entry */
+				g_free(result);
+				result = list->data;
+			}
+		}
+	}
+	
+	if(result)
+	{
+		if (prefix_len)
+			g_utf8_strncpy(buf, text, prefix_len);
+		strncat (buf, result, COMP_BUF - (prefix_len + 3)); /* make sure nicksuffix and space fits */
+		if(!prefix_len && is_nick)
+			strcat (buf, &prefs.nick_suffix[0]);
+		strcat (buf, " ");
+		cursor_pos = strlen (buf);
+		if (postfix)
+			strncat (buf, postfix, COMP_BUF - cursor_pos - 2);
+		SPELL_ENTRY_SET_TEXT (t, buf);
+		SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
+	}
+	if (gcomp)
+		g_completion_free(gcomp);
+	return 2;
+}
+#undef COMP_BUF
+
+static int
+key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+		struct session *sess)
+{
+	key_action_tab_comp(wid, ent, d1, d2, sess);
+	return 2;
+}
+
+
+static int
+key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+						  struct session *sess)
+{
+	replace_handle (wid);
+	return 1;
+}
+
+
+static int
+key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								  char *d2, struct session *sess)
+{
+	mg_move_tab (sess, +1);
+	return 2;						  /* don't allow default action */
+}
+
+static int
+key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	mg_move_tab (sess, -1);
+	return 2;						  /* -''- */
+}
+
+static int
+key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								  char *d2, struct session *sess)
+{
+	mg_move_tab_family (sess, +1);
+	return 2;						  /* don't allow default action */
+}
+
+static int
+key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	mg_move_tab_family (sess, -1);
+	return 2;						  /* -''- */
+}
+
+static int
+key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
+	SPELL_ENTRY_SET_TEXT (wid, "");
+	return 2;						  /* -''- */
+}
+
+
+/* -------- */
+
+
+#define STATE_SHIFT	GDK_SHIFT_MASK
+#define STATE_ALT		GDK_MOD1_MASK
+#define STATE_CTRL	GDK_CONTROL_MASK
+
+static void
+replace_handle (GtkWidget *t)
+{
+	const char *text, *postfix_pnt;
+	struct popup *pop;
+	GSList *list = replace_list;
+	char word[256];
+	char postfix[256];
+	char outbuf[4096];
+	int c, len, xlen;
+
+	text = SPELL_ENTRY_GET_TEXT (t);
+
+	len = strlen (text);
+	if (len < 1)
+		return;
+
+	for (c = len - 1; c > 0; c--)
+	{
+		if (text[c] == ' ')
+			break;
+	}
+	if (text[c] == ' ')
+		c++;
+	xlen = c;
+	if (len - c >= (sizeof (word) - 12))
+		return;
+	if (len - c < 1)
+		return;
+	memcpy (word, &text[c], len - c);
+	word[len - c] = 0;
+	len = strlen (word);
+	if (word[0] == '\'' && word[len] == '\'')
+		return;
+	postfix_pnt = NULL;
+	for (c = 0; c < len; c++)
+	{
+		if (word[c] == '\'')
+		{
+			postfix_pnt = &word[c + 1];
+			word[c] = 0;
+			break;
+		}
+	}
+
+	if (postfix_pnt != NULL)
+	{
+		if (strlen (postfix_pnt) > sizeof (postfix) - 12)
+			return;
+		strcpy (postfix, postfix_pnt);
+	}
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (strcmp (pop->name, word) == 0)
+		{
+			memcpy (outbuf, text, xlen);
+			outbuf[xlen] = 0;
+			if (postfix_pnt == NULL)
+				snprintf (word, sizeof (word), "%s", pop->cmd);
+			else
+				snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix);
+			strcat (outbuf, word);
+			SPELL_ENTRY_SET_TEXT (t, outbuf);
+			SPELL_ENTRY_SET_POS (t, -1);
+			return;
+		}
+		list = list->next;
+	}
+}
+
diff --git a/src/fe-gtk/fkeys.h b/src/fe-gtk/fkeys.h
new file mode 100644
index 00000000..20cd4c73
--- /dev/null
+++ b/src/fe-gtk/fkeys.h
@@ -0,0 +1,5 @@
+void key_init (void);
+void key_dialog_show (void);
+int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
+int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 session *sess);
diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c
new file mode 100644
index 00000000..63ab491b
--- /dev/null
+++ b/src/fe-gtk/gtkutil.c
@@ -0,0 +1,675 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#define _FILE_OFFSET_BITS 64 /* allow selection of large files */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtktooltips.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkclipboard.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcellrenderertoggle.h>
+#include <gtk/gtkversion.h>
+#include <gtk/gtkfilechooserdialog.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "gtkutil.h"
+#include "pixmaps.h"
+
+/* gtkutil.c, just some gtk wrappers */
+
+extern void path_part (char *file, char *path, int pathlen);
+
+
+struct file_req
+{
+	GtkWidget *dialog;
+	void *userdata;
+	filereqcallback callback;
+	int flags;		/* FRF_* flags */
+};
+
+static char last_dir[256] = "";
+
+
+static void
+gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq)
+{
+	freq->callback (freq->userdata, NULL);
+	free (freq);
+}
+
+static void
+gtkutil_check_file (char *file, struct file_req *freq)
+{
+	struct stat st;
+	int axs = FALSE;
+
+	path_part (file, last_dir, sizeof (last_dir));
+
+	/* check if the file is readable or writable */
+	if (freq->flags & FRF_WRITE)
+	{
+		if (access (last_dir, W_OK) == 0)
+			axs = TRUE;
+	} else
+	{
+		if (stat (file, &st) != -1)
+		{
+			if (!S_ISDIR (st.st_mode) || (freq->flags & FRF_CHOOSEFOLDER))
+				axs = TRUE;
+		}
+	}
+
+	if (axs)
+	{
+		char *utf8_file;
+		/* convert to UTF8. It might be converted back to locale by
+			server.c's g_convert */
+		utf8_file = xchat_filename_to_utf8 (file, -1, NULL, NULL, NULL);
+		if (utf8_file)
+		{
+			freq->callback (freq->userdata, utf8_file);
+			g_free (utf8_file);
+		} else
+		{
+			fe_message ("Filename encoding is corrupt.", FE_MSG_ERROR);
+		}
+	} else
+	{
+		if (freq->flags & FRF_WRITE)
+			fe_message (_("Cannot write to that file."), FE_MSG_ERROR);
+		else
+			fe_message (_("Cannot read that file."), FE_MSG_ERROR);
+	}
+}
+
+static void
+gtkutil_file_req_done (GtkWidget * wid, struct file_req *freq)
+{
+	GSList *files, *cur;
+	GtkFileChooser *fs = GTK_FILE_CHOOSER (freq->dialog);
+
+	if (freq->flags & FRF_MULTIPLE)
+	{
+		files = cur = gtk_file_chooser_get_filenames (fs);
+		while (cur)
+		{
+			gtkutil_check_file (cur->data, freq);
+			g_free (cur->data);
+			cur = cur->next;
+		}
+		if (files)
+			g_slist_free (files);
+	} else
+	{
+		if (freq->flags & FRF_CHOOSEFOLDER)
+			gtkutil_check_file (gtk_file_chooser_get_current_folder (fs), freq);
+		else
+			gtkutil_check_file (gtk_file_chooser_get_filename (fs), freq);
+	}
+
+	/* this should call the "destroy" cb, where we free(freq) */
+	gtk_widget_destroy (freq->dialog);
+}
+
+static void
+gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq)
+{
+	switch (res)
+	{
+	case GTK_RESPONSE_ACCEPT:
+		gtkutil_file_req_done (dialog, freq);
+		break;
+
+	case GTK_RESPONSE_CANCEL:
+		/* this should call the "destroy" cb, where we free(freq) */
+		gtk_widget_destroy (freq->dialog);
+	}
+}
+
+void
+gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter,
+						int flags)
+{
+	struct file_req *freq;
+	GtkWidget *dialog;
+	extern char *get_xdir_fs (void);
+
+	if (flags & FRF_WRITE)
+	{
+		dialog = gtk_file_chooser_dialog_new (title, NULL,
+												GTK_FILE_CHOOSER_ACTION_SAVE,
+												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+												GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+												NULL);
+		if (filter && filter[0])	/* filter becomes initial name when saving */
+		{
+			char temp[1024];
+			path_part (filter, temp, sizeof (temp));
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp);
+			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter));
+		}
+#if GTK_CHECK_VERSION(2,8,0)
+		if (!(flags & FRF_NOASKOVERWRITE))
+			gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
+#endif
+	}
+	else
+		dialog = gtk_file_chooser_dialog_new (title, NULL,
+												GTK_FILE_CHOOSER_ACTION_OPEN,
+												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+												GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+												NULL);
+	if (flags & FRF_MULTIPLE)
+		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
+	if (last_dir[0])
+		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir);
+	if (flags & FRF_ADDFOLDER)
+		gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
+														  get_xdir_fs (), NULL);
+	if (flags & FRF_CHOOSEFOLDER)
+	{
+		gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
+	}
+	else
+	{
+		if (filter && (flags & FRF_FILTERISINITIAL))
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
+	}
+
+	freq = malloc (sizeof (struct file_req));
+	freq->dialog = dialog;
+	freq->flags = flags;
+	freq->callback = callback;
+	freq->userdata = userdata;
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (gtkutil_file_req_response), freq);
+	g_signal_connect (G_OBJECT (dialog), "destroy",
+						   G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq);
+	gtk_widget_show (dialog);
+}
+
+void
+gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad)
+{
+	gtk_widget_destroy (dgad);
+}
+
+static void
+gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry)
+{
+	void (*callback) (int cancel, char *text, void *user_data);
+	char *text;
+	void *user_data;
+
+	text = (char *) gtk_entry_get_text (GTK_ENTRY (entry));
+	callback = g_object_get_data (G_OBJECT (dialog), "cb");
+	user_data = g_object_get_data (G_OBJECT (dialog), "ud");
+
+	switch (arg1)
+	{
+	case GTK_RESPONSE_REJECT:
+		callback (TRUE, text, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	case GTK_RESPONSE_ACCEPT:
+		callback (FALSE, text, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	}
+}
+
+static void
+gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog)
+{
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+}
+
+void
+fe_get_str (char *msg, char *def, void *callback, void *userdata)
+{
+	GtkWidget *dialog;
+	GtkWidget *entry;
+	GtkWidget *hbox;
+	GtkWidget *label;
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	g_object_set_data (G_OBJECT (dialog), "cb", callback);
+	g_object_set_data (G_OBJECT (dialog), "ud", userdata);
+
+	entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (gtkutil_str_enter), dialog);
+	gtk_entry_set_text (GTK_ENTRY (entry), def);
+	gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
+
+	label = gtk_label_new (msg);
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (gtkutil_get_str_response), entry);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+}
+
+static void
+gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin)
+{
+	void (*callback) (int cancel, int value, void *user_data);
+	int num;
+	void *user_data;
+
+	num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin));
+	callback = g_object_get_data (G_OBJECT (dialog), "cb");
+	user_data = g_object_get_data (G_OBJECT (dialog), "ud");
+
+	switch (arg1)
+	{
+	case GTK_RESPONSE_REJECT:
+		callback (TRUE, num, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	case GTK_RESPONSE_ACCEPT:
+		callback (FALSE, num, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	}
+}
+
+void
+fe_get_int (char *msg, int def, void *callback, void *userdata)
+{
+	GtkWidget *dialog;
+	GtkWidget *spin;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkAdjustment *adj;
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	g_object_set_data (G_OBJECT (dialog), "cb", callback);
+	g_object_set_data (G_OBJECT (dialog), "ud", userdata);
+
+	spin = gtk_spin_button_new (NULL, 1, 0);
+	adj = gtk_spin_button_get_adjustment ((GtkSpinButton*)spin);
+	adj->lower = 0;
+	adj->upper = 1024;
+	adj->step_increment = 1;
+	gtk_adjustment_changed (adj);
+	gtk_spin_button_set_value ((GtkSpinButton*)spin, def);
+	gtk_box_pack_end (GTK_BOX (hbox), spin, 0, 0, 0);
+
+	label = gtk_label_new (msg);
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (gtkutil_get_number_response), spin);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+}
+
+GtkWidget *
+gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback,
+					 void *userdata, char *labeltext)
+{
+	GtkWidget *wid, *img, *bbox;
+
+	wid = gtk_button_new ();
+
+	if (labeltext)
+	{
+		gtk_button_set_label (GTK_BUTTON (wid), labeltext);
+		gtk_button_set_image (GTK_BUTTON (wid), gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU));
+		gtk_button_set_use_underline (GTK_BUTTON (wid), TRUE);
+		if (box)
+			gtk_container_add (GTK_CONTAINER (box), wid);
+	}
+	else
+	{
+		bbox = gtk_hbox_new (0, 0);
+		gtk_container_add (GTK_CONTAINER (wid), bbox);
+		gtk_widget_show (bbox);
+
+		img = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU);
+		if (stock == GTK_STOCK_GOTO_LAST)
+			gtk_widget_set_usize (img, 10, 6);
+		gtk_container_add (GTK_CONTAINER (bbox), img);
+		gtk_widget_show (img);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (wid);
+	if (tip)
+		add_tip (wid, tip);
+
+	return wid;
+}
+
+void
+gtkutil_label_new (char *text, GtkWidget * box)
+{
+	GtkWidget *label = gtk_label_new (text);
+	gtk_container_add (GTK_CONTAINER (box), label);
+	gtk_widget_show (label);
+}
+
+GtkWidget *
+gtkutil_entry_new (int max, GtkWidget * box, void *callback,
+						 gpointer userdata)
+{
+	GtkWidget *entry = gtk_entry_new_with_max_length (max);
+	gtk_container_add (GTK_CONTAINER (box), entry);
+	if (callback)
+		g_signal_connect (G_OBJECT (entry), "changed",
+								G_CALLBACK (callback), userdata);
+	gtk_widget_show (entry);
+	return entry;
+}
+
+GtkWidget *
+gtkutil_clist_new (int columns, char *titles[],
+						 GtkWidget * box, int policy,
+						 void *select_callback, gpointer select_userdata,
+						 void *unselect_callback,
+						 gpointer unselect_userdata, int selection_mode)
+{
+	GtkWidget *clist, *win;
+
+	win = gtk_scrolled_window_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (box), win);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_AUTOMATIC, policy);
+	gtk_widget_show (win);
+
+	if (titles)
+		clist = gtk_clist_new_with_titles (columns, titles);
+	else
+		clist = gtk_clist_new (columns);
+
+	gtk_clist_set_selection_mode (GTK_CLIST (clist), selection_mode);
+	gtk_clist_column_titles_passive (GTK_CLIST (clist));
+	gtk_container_add (GTK_CONTAINER (win), clist);
+	if (select_callback)
+	{
+		g_signal_connect (G_OBJECT (clist), "select_row",
+								G_CALLBACK (select_callback), select_userdata);
+	}
+	if (unselect_callback)
+	{
+		g_signal_connect (G_OBJECT (clist), "unselect_row",
+								G_CALLBACK (unselect_callback), unselect_userdata);
+	}
+	gtk_widget_show (clist);
+
+	return clist;
+}
+
+int
+gtkutil_clist_selection (GtkWidget * clist)
+{
+	if (GTK_CLIST (clist)->selection)
+		return GPOINTER_TO_INT(GTK_CLIST (clist)->selection->data);
+	return -1;
+}
+
+static int
+int_compare (const int * elem1, const int * elem2)
+{
+	return (*elem1) - (*elem2);
+}
+
+int
+gtkutil_clist_multiple_selection (GtkWidget * clist, int ** rows, const int max_rows)
+{
+	int i = 0;
+	GList *tmp_clist;
+	*rows = malloc (sizeof (int) * max_rows );
+	memset( *rows, -1, max_rows * sizeof(int) );
+
+	for( tmp_clist = GTK_CLIST(clist)->selection;
+			tmp_clist && i < max_rows; tmp_clist = tmp_clist->next, i++)
+	{
+		(*rows)[i] = GPOINTER_TO_INT( tmp_clist->data );
+	}
+	qsort(*rows, i, sizeof(int), (void *)int_compare);
+	return i;
+
+}
+
+void
+add_tip (GtkWidget * wid, char *text)
+{
+	static GtkTooltips *tip = NULL;
+	if (!tip)
+		tip = gtk_tooltips_new ();
+	gtk_tooltips_set_tip (tip, wid, text, 0);
+}
+
+void
+show_and_unfocus (GtkWidget * wid)
+{
+	GTK_WIDGET_UNSET_FLAGS (wid, GTK_CAN_FOCUS);
+	gtk_widget_show (wid);
+}
+
+void
+gtkutil_set_icon (GtkWidget *win)
+{
+	gtk_window_set_icon (GTK_WINDOW (win), pix_xchat);
+}
+
+extern GtkWidget *parent_window;	/* maingui.c */
+
+GtkWidget *
+gtkutil_window_new (char *title, char *role, int width, int height, int flags)
+{
+	GtkWidget *win;
+
+	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtkutil_set_icon (win);
+#ifdef WIN32
+	gtk_window_set_wmclass (GTK_WINDOW (win), "XChat", "xchat");
+#endif
+	gtk_window_set_title (GTK_WINDOW (win), title);
+	gtk_window_set_default_size (GTK_WINDOW (win), width, height);
+	gtk_window_set_role (GTK_WINDOW (win), role);
+	if (flags & 1)
+		gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
+	if ((flags & 2) && parent_window)
+	{
+		gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG);
+		gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window));
+	}
+
+	return win;
+}
+
+/* pass NULL as selection to paste to both clipboard & X11 text */
+void
+gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection,
+                           const gchar *str)
+{
+	GtkWidget *win;
+	GtkClipboard *clip, *clip2;
+
+	win = gtk_widget_get_toplevel (GTK_WIDGET (widget));
+	if (GTK_WIDGET_TOPLEVEL (win))
+	{
+		int len = strlen (str);
+
+		if (selection)
+		{
+			clip = gtk_widget_get_clipboard (win, selection);
+			gtk_clipboard_set_text (clip, str, len);
+		} else
+		{
+			/* copy to both primary X selection and clipboard */
+			clip = gtk_widget_get_clipboard (win, GDK_SELECTION_PRIMARY);
+			clip2 = gtk_widget_get_clipboard (win, GDK_SELECTION_CLIPBOARD);
+			gtk_clipboard_set_text (clip, str, len);
+			gtk_clipboard_set_text (clip2, str, len);
+		}
+	}
+}
+
+/* Treeview util functions */
+
+GtkWidget *
+gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model,
+                      GtkTreeCellDataFunc mapper, ...)
+{
+	GtkWidget *win, *view;
+	GtkCellRenderer *renderer = NULL;
+	GtkTreeViewColumn *col;
+	va_list args;
+	int col_id = 0;
+	GType type;
+	char *title, *attr;
+
+	win = gtk_scrolled_window_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (box), win);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_widget_show (win);
+
+	view = gtk_tree_view_new_with_model (model);
+	/* the view now has a ref on the model, we can unref it */
+	g_object_unref (G_OBJECT (model));
+	gtk_container_add (GTK_CONTAINER (win), view);
+
+	va_start (args, mapper);
+	for (col_id = va_arg (args, int); col_id != -1; col_id = va_arg (args, int))
+	{
+		type = gtk_tree_model_get_column_type (model, col_id);
+		switch (type)
+		{
+			case G_TYPE_BOOLEAN:
+				renderer = gtk_cell_renderer_toggle_new ();
+				attr = "active";
+				break;
+			case G_TYPE_STRING:	/* fall through */
+			default:
+				renderer = gtk_cell_renderer_text_new ();
+				attr = "text";
+				break;
+		}
+
+		title = va_arg (args, char *);
+		if (mapper)	/* user-specified function to set renderer attributes */
+		{
+			col = gtk_tree_view_column_new_with_attributes (title, renderer, NULL);
+			gtk_tree_view_column_set_cell_data_func (col, renderer, mapper,
+			                                         GINT_TO_POINTER (col_id), NULL);
+		} else
+		{
+			/* just set the typical attribute for this type of renderer */
+			col = gtk_tree_view_column_new_with_attributes (title, renderer,
+			                                                attr, col_id, NULL);
+		}
+		gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
+	}
+
+	va_end (args);
+
+	return view;
+}
+
+gboolean
+gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret)
+{
+	GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
+	gboolean success;
+
+	success = gtk_tree_model_get_iter (model, iter_ret, path);
+	gtk_tree_path_free (path);
+	return success;
+}
+
+/*gboolean
+gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret)
+{
+	GtkTreeModel *store;
+	GtkTreeSelection *select;
+	
+	select = gtk_tree_view_get_selection (view);
+	return gtk_tree_selection_get_selected (select, &store, iter_ret);
+}*/
+
+gboolean
+gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...)
+{
+	GtkTreeModel *store;
+	GtkTreeSelection *select;
+	gboolean has_selected;
+	va_list args;
+	
+	select = gtk_tree_view_get_selection (view);
+	has_selected = gtk_tree_selection_get_selected (select, &store, iter_ret);
+
+	if (has_selected) {
+		va_start (args, iter_ret);
+		gtk_tree_model_get_valist (store, iter_ret, args);
+		va_end (args);
+	}
+
+	return has_selected;
+}
+
diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h
new file mode 100644
index 00000000..9bf9e058
--- /dev/null
+++ b/src/fe-gtk/gtkutil.h
@@ -0,0 +1,39 @@
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreemodel.h>
+
+typedef void (*filereqcallback) (void *, char *file);
+
+#define FRF_WRITE 1
+#define FRF_MULTIPLE 2
+#define FRF_ADDFOLDER 4
+#define FRF_CHOOSEFOLDER 8
+#define FRF_FILTERISINITIAL 16
+#define FRF_NOASKOVERWRITE 32
+
+void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, int flags);
+void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad);
+GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback,
+				 void *userdata, char *labeltext);
+void gtkutil_label_new (char *text, GtkWidget * box);
+GtkWidget *gtkutil_entry_new (int max, GtkWidget * box, void *callback,
+										gpointer userdata);
+GtkWidget *gtkutil_clist_new (int columns, char *titles[], GtkWidget * box,
+										int policy, void *select_callback,
+										gpointer select_userdata,
+										void *unselect_callback,
+										gpointer unselect_userdata, int selection_mode);
+int gtkutil_clist_selection (GtkWidget * clist);
+int gtkutil_clist_multiple_selection (GtkWidget * clist,
+													int ** rows, const int max_rows);
+void add_tip (GtkWidget * wid, char *text);
+void show_and_unfocus (GtkWidget * wid);
+void gtkutil_set_icon (GtkWidget *win);
+GtkWidget *gtkutil_window_new (char *title, char *role, int width, int height, int flags);
+void gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection,
+                                const gchar *str);
+GtkWidget *gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model,
+                                 GtkTreeCellDataFunc mapper, ...);
+gboolean gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret);
+gboolean gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret);
+gboolean gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...);
+
diff --git a/src/fe-gtk/ignoregui.c b/src/fe-gtk/ignoregui.c
new file mode 100644
index 00000000..dc5fce9c
--- /dev/null
+++ b/src/fe-gtk/ignoregui.c
@@ -0,0 +1,449 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkversion.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcellrenderertoggle.h>
+
+#include "../common/xchat.h"
+#include "../common/ignore.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "gtkutil.h"
+#include "maingui.h"
+
+enum
+{
+	MASK_COLUMN,
+	CHAN_COLUMN,
+	PRIV_COLUMN,
+	NOTICE_COLUMN,
+	CTCP_COLUMN,
+	DCC_COLUMN,
+	INVITE_COLUMN,
+	UNIGNORE_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *ignorewin = 0;
+
+static GtkWidget *num_ctcp;
+static GtkWidget *num_priv;
+static GtkWidget *num_chan;
+static GtkWidget *num_noti;
+static GtkWidget *num_invi;
+
+static GtkTreeModel *
+get_store (void)
+{
+	return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (ignorewin), "view"));
+}
+
+static int
+ignore_get_flags (GtkTreeModel *model, GtkTreeIter *iter)
+{
+	gboolean chan, priv, noti, ctcp, dcc, invi, unig;
+	int flags = 0;
+
+	gtk_tree_model_get (model, iter, 1, &chan, 2, &priv, 3, &noti,
+	                    4, &ctcp, 5, &dcc, 6, &invi, 7, &unig, -1);
+	if (chan)
+		flags |= IG_CHAN;
+	if (priv)
+		flags |= IG_PRIV;
+	if (noti)
+		flags |= IG_NOTI;
+	if (ctcp)
+		flags |= IG_CTCP;
+	if (dcc)
+		flags |= IG_DCC;
+	if (invi)
+		flags |= IG_INVI;
+	if (unig)
+		flags |= IG_UNIG;
+	return flags;
+}
+
+static void
+mask_edited (GtkCellRendererText *render, gchar *path, gchar *new, gpointer dat)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	char *old;
+	int flags;
+
+	gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter);
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &old, -1);
+	
+	if (!strcmp (old, new))	/* no change */
+		;
+	else if (ignore_exists (new))	/* duplicate, ignore */
+		fe_message (_("That mask already exists."), FE_MSG_ERROR);
+	else
+	{
+		/* delete old mask, and add new one with original flags */
+		ignore_del (old, NULL);
+		flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter);
+		ignore_add (new, flags);
+
+		/* update tree */
+		gtk_list_store_set (store, &iter, MASK_COLUMN, new, -1);
+	}
+	g_free (old);
+	
+}
+
+static void
+option_toggled (GtkCellRendererToggle *render, gchar *path, gpointer data)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	int col_id = GPOINTER_TO_INT (data);
+	gboolean active;
+	char *mask;
+	int flags;
+
+	gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter);
+
+	/* update model */
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, col_id, &active, -1);
+	gtk_list_store_set (store, &iter, col_id, !active, -1);
+
+	/* update ignore list */
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &mask, -1);
+	flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter);
+	if (ignore_add (mask, flags) != 2)
+		g_warning ("ignore treeview is out of sync!\n");
+	
+	g_free (mask);
+}
+
+static GtkWidget *
+ignore_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	GtkCellRenderer *render;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
+	                             NULL,
+	                             MASK_COLUMN, _("Mask"),
+	                             CHAN_COLUMN, _("Channel"),
+	                             PRIV_COLUMN, _("Private"),
+	                             NOTICE_COLUMN, _("Notice"),
+	                             CTCP_COLUMN, _("CTCP"),
+	                             DCC_COLUMN, _("DCC"),
+	                             INVITE_COLUMN, _("Invite"),
+	                             UNIGNORE_COLUMN, _("Unignore"),
+	                             -1);
+
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);
+
+	/* attach to signals and customise columns */
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+	{
+		GList *list = gtk_tree_view_column_get_cell_renderers (col);
+		GList *tmp;
+
+		for (tmp = list; tmp; tmp = tmp->next)
+		{
+			render = tmp->data;
+			if (col_id > 0)	/* it's a toggle button column */
+			{
+				g_signal_connect (render, "toggled", G_CALLBACK (option_toggled),
+				                  GINT_TO_POINTER (col_id));
+			} else	/* mask column */
+			{
+				g_object_set (G_OBJECT (render), "editable", TRUE, NULL);
+				g_signal_connect (render, "edited", G_CALLBACK (mask_edited), NULL);
+				/* make this column sortable */
+				gtk_tree_view_column_set_sort_column_id (col, col_id);
+				gtk_tree_view_column_set_min_width (col, 272);
+			}
+			/* centre titles */
+			gtk_tree_view_column_set_alignment (col, 0.5);
+		}
+
+		g_list_free (list);
+	}
+	
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess)
+{
+	GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
+	GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *mask = NULL;
+
+	if (gtkutil_treeview_get_selected (view, &iter, 0, &mask, -1))
+	{
+		/* delete this row, select next one */
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		gtk_list_store_remove (store, &iter);
+#else
+		if (gtk_list_store_remove (store, &iter))
+		{
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+			gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
+			gtk_tree_view_set_cursor (view, path, NULL, FALSE);
+			gtk_tree_path_free (path);
+		}
+#endif
+
+		ignore_del (mask, NULL);
+		g_free (mask);
+	}
+}
+
+static void
+ignore_store_new (int cancel, char *mask, gpointer data)
+{
+	GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	int flags = IG_CHAN | IG_PRIV | IG_NOTI | IG_CTCP | IG_DCC | IG_INVI;
+
+	if (cancel)
+		return;
+	/* check if it already exists */
+	if (ignore_exists (mask))
+	{
+		fe_message (_("That mask already exists."), FE_MSG_ERROR);
+		return;
+	}
+
+	ignore_add (mask, flags);
+
+	gtk_list_store_append (store, &iter);
+	/* ignore everything by default */
+	gtk_list_store_set (store, &iter, 0, mask, 1, TRUE, 2, TRUE, 3, TRUE,
+	                    4, TRUE, 5, TRUE, 6, TRUE, 7, FALSE, -1);
+	/* make sure the new row is visible and selected */
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+	gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
+	gtk_tree_view_set_cursor (view, path, NULL, FALSE);
+	gtk_tree_path_free (path);
+}
+
+static void
+ignore_clear_entry_clicked (GtkWidget * wid, gpointer unused)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	char *mask;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+	{
+		/* remove from ignore_list */
+		do
+		{
+			mask = NULL;
+			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MASK_COLUMN, &mask, -1);
+			ignore_del (mask, NULL);
+			g_free (mask);
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+
+		/* remove from GUI */
+		gtk_list_store_clear (store);
+	}
+}
+
+static void
+ignore_new_entry_clicked (GtkWidget * wid, struct session *sess)
+{
+	fe_get_str (_("Enter mask to ignore:"), "nick!userid@host.com",
+	            ignore_store_new, NULL);
+
+}
+
+static void
+close_ignore_gui_callback ()
+{
+	ignore_save ();
+	ignorewin = 0;
+}
+
+static GtkWidget *
+ignore_stats_entry (GtkWidget * box, char *label, int value)
+{
+	GtkWidget *wid;
+	char buf[16];
+
+	sprintf (buf, "%d", value);
+	gtkutil_label_new (label, box);
+	wid = gtkutil_entry_new (16, box, 0, 0);
+	gtk_widget_set_size_request (wid, 30, -1);
+	gtk_editable_set_editable (GTK_EDITABLE (wid), FALSE);
+	gtk_widget_set_sensitive (GTK_WIDGET (wid), FALSE);
+	gtk_entry_set_text (GTK_ENTRY (wid), buf);
+
+	return wid;
+}
+
+void
+ignore_gui_open ()
+{
+	GtkWidget *vbox, *box, *stat_box, *frame;
+	GtkWidget *view;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GSList *temp = ignore_list;
+	char *mask;
+	gboolean private, chan, notice, ctcp, dcc, invite, unignore;
+
+	if (ignorewin)
+	{
+		mg_bring_tofront (ignorewin);
+		return;
+	}
+
+	ignorewin =
+			  mg_create_generic_tab ("IgnoreList", _("XChat: Ignore list"),
+											FALSE, TRUE, close_ignore_gui_callback,
+											NULL, 600, 256, &vbox, 0);
+
+	view = ignore_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (ignorewin), "view", view);
+	
+	frame = gtk_frame_new (_("Ignore Stats:"));
+	gtk_widget_show (frame);
+
+	stat_box = gtk_hbox_new (0, 2);
+	gtk_container_set_border_width (GTK_CONTAINER (stat_box), 6);
+	gtk_container_add (GTK_CONTAINER (frame), stat_box);
+	gtk_widget_show (stat_box);
+
+	num_chan = ignore_stats_entry (stat_box, _("Channel:"), ignored_chan);
+	num_priv = ignore_stats_entry (stat_box, _("Private:"), ignored_priv);
+	num_noti = ignore_stats_entry (stat_box, _("Notice:"), ignored_noti);
+	num_ctcp = ignore_stats_entry (stat_box, _("CTCP:"), ignored_ctcp);
+	num_invi = ignore_stats_entry (stat_box, _("Invite:"), ignored_invi);
+
+	gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, 5);
+
+	box = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 5);
+	gtk_widget_show (box);
+
+	gtkutil_button (box, GTK_STOCK_NEW, 0, ignore_new_entry_clicked, 0,
+						 _("Add..."));
+	gtkutil_button (box, GTK_STOCK_DELETE, 0, ignore_delete_entry_clicked,
+						 0, _("Delete"));
+	gtkutil_button (box, GTK_STOCK_CLEAR, 0, ignore_clear_entry_clicked,
+						 0, _("Clear"));
+
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
+
+	while (temp)
+	{
+		struct ignore *ignore = temp->data;
+
+		mask = ignore->mask;
+		chan = (ignore->type & IG_CHAN);
+		private = (ignore->type & IG_PRIV);
+		notice = (ignore->type & IG_NOTI);
+		ctcp = (ignore->type & IG_CTCP);
+		dcc = (ignore->type & IG_DCC);
+		invite = (ignore->type & IG_INVI);
+		unignore = (ignore->type & IG_UNIG);
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter,
+		                    MASK_COLUMN, mask,
+		                    CHAN_COLUMN, chan,
+		                    PRIV_COLUMN, private,
+		                    NOTICE_COLUMN, notice,
+		                    CTCP_COLUMN, ctcp,
+		                    DCC_COLUMN, dcc,
+		                    INVITE_COLUMN, invite,
+		                    UNIGNORE_COLUMN, unignore,
+		                    -1);
+		
+		temp = temp->next;
+	}
+	gtk_widget_show (ignorewin);
+}
+
+void
+fe_ignore_update (int level)
+{
+	/* some ignores have changed via /ignore, we should update
+	   the gui now */
+	/* level 1 = the list only. */
+	/* level 2 = the numbers only. */
+	/* for now, ignore level 1, since the ignore GUI isn't realtime,
+	   only saved when you click OK */
+	char buf[16];
+
+	if (level == 2 && ignorewin)
+	{
+		sprintf (buf, "%d", ignored_ctcp);
+		gtk_entry_set_text (GTK_ENTRY (num_ctcp), buf);
+
+		sprintf (buf, "%d", ignored_noti);
+		gtk_entry_set_text (GTK_ENTRY (num_noti), buf);
+
+		sprintf (buf, "%d", ignored_chan);
+		gtk_entry_set_text (GTK_ENTRY (num_chan), buf);
+
+		sprintf (buf, "%d", ignored_invi);
+		gtk_entry_set_text (GTK_ENTRY (num_invi), buf);
+
+		sprintf (buf, "%d", ignored_priv);
+		gtk_entry_set_text (GTK_ENTRY (num_priv), buf);
+	}
+}
diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c
new file mode 100644
index 00000000..ee5c56d1
--- /dev/null
+++ b/src/fe-gtk/joind.c
@@ -0,0 +1,257 @@
+/* Copyright (c) 2005 Peter Zelezny
+   All Rights Reserved.
+
+   joind.c - The Join Dialog.
+
+   Popups up when you connect without any autojoin channels and helps you
+   to find or join a channel.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <gtk/gtkbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkwindow.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/server.h"
+#include "../common/fe.h"
+#include "fe-gtk.h"
+#include "chanlist.h"
+
+
+static void
+joind_radio2_cb (GtkWidget *radio, server *serv)
+{
+	if (GTK_TOGGLE_BUTTON (radio)->active)
+	{
+		gtk_widget_grab_focus (serv->gui->joind_entry);
+		gtk_editable_set_position (GTK_EDITABLE (serv->gui->joind_entry), 999);
+	}
+}
+
+static void
+joind_entryenter_cb (GtkWidget *entry, GtkWidget *ok)
+{
+	gtk_widget_grab_focus (ok);
+}
+
+static void
+joind_entryfocus_cb (GtkWidget *entry, GdkEventFocus *event, server *serv)
+{
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2), TRUE);
+}
+
+static void
+joind_destroy_cb (GtkWidget *win, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->joind_win = NULL;
+}
+
+static void
+joind_ok_cb (GtkWidget *ok, server *serv)
+{
+	if (!is_server (serv))
+	{
+		gtk_widget_destroy (gtk_widget_get_toplevel (ok));
+		return;
+	}
+
+	/* do nothing */
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio1)->active)
+		goto xit;
+
+	/* join specific channel */
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2)->active)
+	{
+		char *text = GTK_ENTRY (serv->gui->joind_entry)->text;
+		if (strlen (text) < 2)
+		{
+			fe_message (_("Channel name too short, try again."), FE_MSG_ERROR);
+			return;
+		}
+		serv->p_join (serv, text, "");
+		goto xit;
+	}
+
+	/* channel list */
+	chanlist_opengui (serv, TRUE);
+
+xit:
+	prefs.gui_join_dialog = 0;
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_check)->active)
+		prefs.gui_join_dialog = 1;
+
+	gtk_widget_destroy (serv->gui->joind_win);
+	serv->gui->joind_win = NULL;
+}
+
+static void
+joind_show_dialog (server *serv)
+{
+	GtkWidget *dialog1;
+	GtkWidget *dialog_vbox1;
+	GtkWidget *vbox1;
+	GtkWidget *hbox1;
+	GtkWidget *image1;
+	GtkWidget *vbox2;
+	GtkWidget *label;
+	GtkWidget *radiobutton1;
+	GtkWidget *radiobutton2;
+	GtkWidget *radiobutton3;
+	GSList *radiobutton1_group;
+	GtkWidget *hbox2;
+	GtkWidget *entry1;
+	GtkWidget *checkbutton1;
+	GtkWidget *dialog_action_area1;
+	GtkWidget *okbutton1;
+	char buf[256];
+	char buf2[256];
+
+	serv->gui->joind_win = dialog1 = gtk_dialog_new ();
+	gtk_window_set_title (GTK_WINDOW (dialog1), _("XChat: Connection Complete"));
+	gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_position (GTK_WINDOW (dialog1), GTK_WIN_POS_MOUSE);
+
+	dialog_vbox1 = GTK_DIALOG (dialog1)->vbox;
+	gtk_widget_show (dialog_vbox1);
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox1);
+	gtk_box_pack_start (GTK_BOX (dialog_vbox1), vbox1, TRUE, TRUE, 0);
+
+	hbox1 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0);
+
+	image1 = gtk_image_new_from_stock ("gtk-yes", GTK_ICON_SIZE_DIALOG);
+	gtk_widget_show (image1);
+	gtk_box_pack_start (GTK_BOX (hbox1), image1, FALSE, TRUE, 24);
+	gtk_misc_set_alignment (GTK_MISC (image1), 0.5, 0.06);
+
+	vbox2 = gtk_vbox_new (FALSE, 10);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox2), 6);
+	gtk_widget_show (vbox2);
+	gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0);
+
+	snprintf (buf2, sizeof (buf2), _("Connection to %s complete."),
+				 server_get_network (serv, TRUE));
+	snprintf (buf, sizeof (buf), "\n<b>%s</b>", buf2);
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	label = gtk_label_new (_("In the Server-List window, no channel (chat room) has been entered to be automatically joined for this network."));
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	GTK_LABEL (label)->wrap = TRUE;
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	label = gtk_label_new (_("What would you like to do next?"));
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	serv->gui->joind_radio1 = radiobutton1 = gtk_radio_button_new_with_mnemonic (NULL, _("_Nothing, I'll join a channel later."));
+	gtk_widget_show (radiobutton1);
+	gtk_box_pack_start (GTK_BOX (vbox2), radiobutton1, FALSE, FALSE, 0);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton1));
+
+	hbox2 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox2);
+	gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0);
+
+	serv->gui->joind_radio2 = radiobutton2 = gtk_radio_button_new_with_mnemonic (NULL, _("_Join this channel:"));
+	gtk_widget_show (radiobutton2);
+	gtk_box_pack_start (GTK_BOX (hbox2), radiobutton2, FALSE, FALSE, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton2), radiobutton1_group);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton2));
+
+	serv->gui->joind_entry = entry1 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry1), "#");
+	gtk_widget_show (entry1);
+	gtk_box_pack_start (GTK_BOX (hbox2), entry1, TRUE, TRUE, 8);
+
+	snprintf (buf, sizeof (buf), "<small>     %s</small>",
+				 _("If you know the name of the channel you want to join, enter it here."));
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	radiobutton3 = gtk_radio_button_new_with_mnemonic (NULL, _("O_pen the Channel-List window."));
+	gtk_widget_show (radiobutton3);
+	gtk_box_pack_start (GTK_BOX (vbox2), radiobutton3, FALSE, FALSE, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton3), radiobutton1_group);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton3));
+
+	snprintf (buf, sizeof (buf), "<small>     %s</small>",
+				 _("Retrieving the Channel-List may take a minute or two."));
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	serv->gui->joind_check = checkbutton1 = gtk_check_button_new_with_mnemonic (_("_Always show this dialog after connecting."));
+	if (prefs.gui_join_dialog)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton1), TRUE);
+	gtk_widget_show (checkbutton1);
+	gtk_box_pack_start (GTK_BOX (vbox1), checkbutton1, FALSE, FALSE, 0);
+
+	dialog_action_area1 = GTK_DIALOG (dialog1)->action_area;
+	gtk_widget_show (dialog_action_area1);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END);
+
+	okbutton1 = gtk_button_new_from_stock ("gtk-ok");
+	gtk_widget_show (okbutton1);
+	gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog1)->action_area), okbutton1, FALSE, TRUE, 0);
+	GTK_WIDGET_SET_FLAGS (okbutton1, GTK_CAN_DEFAULT);
+
+	g_signal_connect (G_OBJECT (dialog1), "destroy",
+							G_CALLBACK (joind_destroy_cb), serv);
+	g_signal_connect (G_OBJECT (entry1), "focus_in_event",
+							G_CALLBACK (joind_entryfocus_cb), serv);
+	g_signal_connect (G_OBJECT (entry1), "activate",
+							G_CALLBACK (joind_entryenter_cb), okbutton1);
+	g_signal_connect (G_OBJECT (radiobutton2), "toggled",
+							G_CALLBACK (joind_radio2_cb), serv);
+	g_signal_connect (G_OBJECT (okbutton1), "clicked",
+							G_CALLBACK (joind_ok_cb), serv);
+
+	gtk_widget_grab_focus (okbutton1);
+	gtk_widget_show_all (dialog1);
+}
+
+void
+joind_open (server *serv)
+{
+	if (prefs.gui_join_dialog)
+		joind_show_dialog (serv);
+}
+
+void
+joind_close (server *serv)
+{
+	if (serv->gui->joind_win)
+	{
+		gtk_widget_destroy (serv->gui->joind_win);
+		serv->gui->joind_win = NULL;
+	}
+}
diff --git a/src/fe-gtk/joind.h b/src/fe-gtk/joind.h
new file mode 100644
index 00000000..aa0fd0ad
--- /dev/null
+++ b/src/fe-gtk/joind.h
@@ -0,0 +1,2 @@
+void joind_open (server *serv);
+void joind_close (server *serv);
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c
new file mode 100644
index 00000000..ef269f95
--- /dev/null
+++ b/src/fe-gtk/maingui.c
@@ -0,0 +1,3811 @@
+/* X-Chat
+ * Copyright (C) 1998-2005 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <gtk/gtkarrow.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkeventbox.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhpaned.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkbbox.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/xchatc.h"
+#include "../common/outbound.h"
+#include "../common/inbound.h"
+#include "../common/plugin.h"
+#include "../common/modes.h"
+#include "../common/url.h"
+#include "fe-gtk.h"
+#include "banlist.h"
+#include "gtkutil.h"
+#include "joind.h"
+#include "palette.h"
+#include "maingui.h"
+#include "menu.h"
+#include "fkeys.h"
+#include "userlistgui.h"
+#include "chanview.h"
+#include "pixmaps.h"
+#include "plugin-tray.h"
+#include "xtext.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#include <gtkspell/gtkspell.h>
+#endif
+
+#ifdef USE_LIBSEXY
+#include "sexy-spell-entry.h"
+#endif
+
+#define GUI_SPACING (3)
+#define GUI_BORDER (0)
+#define SCROLLBAR_SPACING (2)
+
+enum
+{
+	POS_INVALID = 0,
+	POS_TOPLEFT = 1,
+	POS_BOTTOMLEFT = 2,
+	POS_TOPRIGHT = 3,
+	POS_BOTTOMRIGHT = 4,
+	POS_TOP = 5,	/* for tabs only */
+	POS_BOTTOM = 6,
+	POS_HIDDEN = 7
+};
+
+/* two different types of tabs */
+#define TAG_IRC 0		/* server, channel, dialog */
+#define TAG_UTIL 1	/* dcc, notify, chanlist */
+
+static void mg_create_entry (session *sess, GtkWidget *box);
+static void mg_link_irctab (session *sess, int focus);
+
+static session_gui static_mg_gui;
+static session_gui *mg_gui = NULL;	/* the shared irc tab */
+static int ignore_chanmode = FALSE;
+static const char chan_flags[] = { 't', 'n', 's', 'i', 'p', 'm', 'l', 'k' };
+
+static chan *active_tab = NULL;	/* active tab */
+GtkWidget *parent_window = NULL;			/* the master window */
+
+GtkStyle *input_style;
+
+static PangoAttrList *away_list;
+static PangoAttrList *newdata_list;
+static PangoAttrList *nickseen_list;
+static PangoAttrList *newmsg_list;
+static PangoAttrList *plain_list = NULL;
+
+
+#ifdef USE_GTKSPELL
+
+/* use these when it's a GtkTextView instead of GtkEntry */
+
+char *
+SPELL_ENTRY_GET_TEXT (GtkWidget *entry)
+{
+	static char *last = NULL;	/* warning: don't overlap 2 GET_TEXT calls! */
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+	GtkTextIter start_iter, end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buf, &start_iter, 0);
+	gtk_text_buffer_get_end_iter (buf, &end_iter);
+	g_free (last);
+	last = gtk_text_buffer_get_text (buf, &start_iter, &end_iter, FALSE);
+	return last;
+}
+
+void
+SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	gtk_text_buffer_get_iter_at_offset (buf, &iter, pos);
+	gtk_text_buffer_place_cursor (buf, &iter);
+}
+
+int
+SPELL_ENTRY_GET_POS (GtkWidget *entry)
+{
+	GtkTextIter cursor;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	gtk_text_buffer_get_iter_at_mark (buf, &cursor, gtk_text_buffer_get_insert (buf));
+	return gtk_text_iter_get_offset (&cursor);
+}
+
+void
+SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	/* len is bytes. pos is chars. */
+	gtk_text_buffer_get_iter_at_offset (buf, &iter, *pos);
+	gtk_text_buffer_insert (buf, &iter, text, len);
+	*pos += g_utf8_strlen (text, len);
+}
+
+#endif
+
+static PangoAttrList *
+mg_attr_list_create (GdkColor *col, int size)
+{
+	PangoAttribute *attr;
+	PangoAttrList *list;
+
+	list = pango_attr_list_new ();
+
+	if (col)
+	{
+		attr = pango_attr_foreground_new (col->red, col->green, col->blue);
+		attr->start_index = 0;
+		attr->end_index = 0xffff;
+		pango_attr_list_insert (list, attr);
+	}
+
+	if (size > 0)
+	{
+		attr = pango_attr_scale_new (size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL);
+		attr->start_index = 0;
+		attr->end_index = 0xffff;
+		pango_attr_list_insert (list, attr);
+	}
+
+	return list;
+}
+
+static void
+mg_create_tab_colors (void)
+{
+	if (plain_list)
+	{
+		pango_attr_list_unref (plain_list);
+		pango_attr_list_unref (newmsg_list);
+		pango_attr_list_unref (newdata_list);
+		pango_attr_list_unref (nickseen_list);
+		pango_attr_list_unref (away_list);
+	}
+
+	plain_list = mg_attr_list_create (NULL, prefs.tab_small);
+	newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.tab_small);
+	nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.tab_small);
+	newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.tab_small);
+	away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE);
+}
+
+#ifdef WIN32
+#define WINVER 0x0501	/* needed for vc6? */
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+
+/* Flash the taskbar button on Windows when there's a highlight event. */
+
+static void
+flash_window (GtkWidget *win)
+{
+	FLASHWINFO fi;
+	static HMODULE user = NULL;
+	static BOOL (*flash) (PFLASHWINFO) = NULL;
+
+	if (!user)
+	{
+		user = GetModuleHandleA ("USER32");
+		if (!user)
+			return;	/* this should never fail */
+	}
+
+	if (!flash)
+	{
+		flash = (void *)GetProcAddress (user, "FlashWindowEx");
+		if (!flash)
+			return;	/* this fails on NT4.0 and Win95 */
+	}
+
+	fi.cbSize = sizeof (fi);
+	fi.hwnd = GDK_WINDOW_HWND (win->window);
+	fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG;
+	fi.uCount = 0;
+	fi.dwTimeout = 500;
+	flash (&fi);
+	/*FlashWindowEx (&fi);*/
+}
+#else
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+
+static void
+set_window_urgency (GtkWidget *win, gboolean set)
+{
+	XWMHints *hints;
+
+	hints = XGetWMHints(GDK_WINDOW_XDISPLAY(win->window), GDK_WINDOW_XWINDOW(win->window));
+	if (set)
+		hints->flags |= XUrgencyHint;
+	else
+		hints->flags &= ~XUrgencyHint;
+	XSetWMHints(GDK_WINDOW_XDISPLAY(win->window),
+	            GDK_WINDOW_XWINDOW(win->window), hints);
+	XFree(hints);
+}
+
+static void
+flash_window (GtkWidget *win)
+{
+	set_window_urgency (win, TRUE);
+}
+
+static void
+unflash_window (GtkWidget *win)
+{
+	set_window_urgency (win, FALSE);
+}
+#endif
+#endif
+
+/* flash the taskbar button */
+
+void
+fe_flash_window (session *sess)
+{
+#if defined(WIN32) || defined(USE_XLIB)
+	if (fe_gui_info (sess, 0) != 1)	/* only do it if not focused */
+		flash_window (sess->gui->window);
+#endif
+}
+
+/* set a tab plain, red, light-red, or blue */
+
+void
+fe_set_tab_color (struct session *sess, int col)
+{
+	struct session *server_sess = sess->server->server_session;
+	if (sess->gui->is_tab && (col == 0 || sess != current_tab))
+	{
+		switch (col)
+		{
+		case 0:	/* no particular color (theme default) */
+			sess->new_data = FALSE;
+			sess->msg_said = FALSE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, plain_list);
+			break;
+		case 1:	/* new data has been displayed (dark red) */
+			sess->new_data = TRUE;
+			sess->msg_said = FALSE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, newdata_list);
+
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = TRUE;
+				server_sess->msg_said = FALSE;
+				server_sess->nick_said = FALSE;
+				chan_set_color (chan_get_parent (sess->res->tab), newdata_list);
+			}
+				
+			break;
+		case 2:	/* new message arrived in channel (light red) */
+			sess->new_data = FALSE;
+			sess->msg_said = TRUE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, newmsg_list);
+			
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = FALSE;
+				server_sess->msg_said = TRUE;
+				server_sess->nick_said = FALSE;
+				chan_set_color (chan_get_parent (sess->res->tab), newmsg_list);
+			}
+			
+			break;
+		case 3:	/* your nick has been seen (blue) */
+			sess->new_data = FALSE;
+			sess->msg_said = FALSE;
+			sess->nick_said = TRUE;
+			chan_set_color (sess->res->tab, nickseen_list);
+
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = FALSE;
+				server_sess->msg_said = FALSE;
+				server_sess->nick_said = TRUE;
+				chan_set_color (chan_get_parent (sess->res->tab), nickseen_list);
+			}
+				
+			break;
+		}
+	}
+}
+
+static void
+mg_set_myself_away (session_gui *gui, gboolean away)
+{
+	gtk_label_set_attributes (GTK_LABEL (GTK_BIN (gui->nick_label)->child),
+									  away ? away_list : NULL);
+}
+
+/* change the little icon to the left of your nickname */
+
+void
+mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away)
+{
+	if (gui->op_xpm)
+	{
+		if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */
+		{
+			mg_set_myself_away (gui, away);
+			return;
+		}
+
+		gtk_widget_destroy (gui->op_xpm);
+		gui->op_xpm = NULL;
+	}
+
+	if (pix)
+	{
+		gui->op_xpm = gtk_image_new_from_pixbuf (pix);
+		gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->op_xpm, 0, 0, 0);
+		gtk_widget_show (gui->op_xpm);
+	}
+
+	mg_set_myself_away (gui, away);
+}
+
+static gboolean
+mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
+{
+	GSList *list;
+	session *sess;
+
+	if (gui->is_tab)
+		return FALSE;
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui == gui)
+		{
+			current_sess = sess;
+			if (!sess->server->server_session)
+				sess->server->server_session = sess;
+			break;
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+void
+mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
+{
+	char *cmd;
+	static int ignore = FALSE;
+	GSList *list;
+	session *sess = NULL;
+
+	if (ignore)
+		return;
+
+	cmd = SPELL_ENTRY_GET_TEXT (igad);
+	if (cmd[0] == 0)
+		return;
+
+	cmd = strdup (cmd);
+
+	/* avoid recursive loop */
+	ignore = TRUE;
+	SPELL_ENTRY_SET_TEXT (igad, "");
+	ignore = FALSE;
+
+	/* where did this event come from? */
+	if (gui->is_tab)
+	{
+		sess = current_tab;
+	} else
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->gui == gui)
+				break;
+			list = list->next;
+		}
+		if (!list)
+			sess = NULL;
+	}
+
+	if (sess)
+		handle_multiline (sess, cmd, TRUE, FALSE);
+
+	free (cmd);
+}
+
+static gboolean
+has_key (char *modes)
+{
+	if (!modes)
+		return FALSE;
+	/* this is a crude check, but "-k" can't exist, so it works. */
+	while (*modes)
+	{
+		if (*modes == 'k')
+			return TRUE;
+		if (*modes == ' ')
+			return FALSE;
+		modes++;
+	}
+	return FALSE;
+}
+
+void
+fe_set_title (session *sess)
+{
+	char tbuf[512];
+	int type;
+
+	if (sess->gui->is_tab && sess != current_tab)
+		return;
+
+	type = sess->type;
+
+	if (sess->server->connected == FALSE && sess->type != SESS_DIALOG)
+		goto def;
+
+	switch (type)
+	{
+	case SESS_DIALOG:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s %s @ %s",
+					 _("Dialog with"), sess->channel, server_get_network (sess->server, TRUE));
+		break;
+	case SESS_SERVER:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s",
+					 sess->server->nick, server_get_network (sess->server, TRUE));
+		break;
+	case SESS_CHANNEL:
+		/* don't display keys in the titlebar */
+		if ((!(prefs.gui_tweaks & 16)) && has_key (sess->current_modes))
+			snprintf (tbuf, sizeof (tbuf),
+						 DISPLAY_NAME": %s @ %s / %s",
+						 sess->server->nick, server_get_network (sess->server, TRUE),
+						 sess->channel);
+		else
+			snprintf (tbuf, sizeof (tbuf),
+						 DISPLAY_NAME": %s @ %s / %s (%s)",
+						 sess->server->nick, server_get_network (sess->server, TRUE),
+						 sess->channel, sess->current_modes ? sess->current_modes : "");
+		if (prefs.gui_tweaks & 1)
+			snprintf (tbuf + strlen (tbuf), 9, " (%d)", sess->total);
+		break;
+	case SESS_NOTICES:
+	case SESS_SNOTICES:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s (notices)",
+					 sess->server->nick, server_get_network (sess->server, TRUE));
+		break;
+	default:
+	def:
+		gtk_window_set_title (GTK_WINDOW (sess->gui->window), DISPLAY_NAME);
+		return;
+	}
+
+	gtk_window_set_title (GTK_WINDOW (sess->gui->window), tbuf);
+}
+
+static gboolean
+mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata)
+{
+	prefs.gui_win_state = 0;
+	if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
+		prefs.gui_win_state = 1;
+
+	if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) &&
+		 (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) &&
+		 (prefs.gui_tray_flags & 4))
+	{
+		tray_toggle_visibility (TRUE);
+		gtk_window_deiconify (wid);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess)
+{
+	if (sess == NULL)			/* for the main_window */
+	{
+		if (mg_gui)
+		{
+			if (prefs.mainwindow_save)
+			{
+				sess = current_sess;
+				gtk_window_get_position (GTK_WINDOW (wid), &prefs.mainwindow_left,
+												 &prefs.mainwindow_top);
+				gtk_window_get_size (GTK_WINDOW (wid), &prefs.mainwindow_width,
+											&prefs.mainwindow_height);
+			}
+		}
+	}
+
+	if (sess)
+	{
+		if (sess->type == SESS_DIALOG && prefs.mainwindow_save)
+		{
+			gtk_window_get_position (GTK_WINDOW (wid), &prefs.dialog_left,
+											 &prefs.dialog_top);
+			gtk_window_get_size (GTK_WINDOW (wid), &prefs.dialog_width,
+										&prefs.dialog_height);
+		}
+
+		if (((GtkXText *) sess->gui->xtext)->transparent)
+			gtk_widget_queue_draw (sess->gui->xtext);
+	}
+
+	return FALSE;
+}
+
+/* move to a non-irc tab */
+
+static void
+mg_show_generic_tab (GtkWidget *box)
+{
+	int num;
+	GtkWidget *f = NULL;
+
+#if defined(GTK_WIDGET_HAS_FOCUS)
+	if (current_sess && GTK_WIDGET_HAS_FOCUS (current_sess->gui->input_box))
+#else
+	if (current_sess && gtk_widget_has_focus (current_sess->gui->input_box))
+#endif
+		f = current_sess->gui->input_box;
+
+	num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box);
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (mg_gui->note_book), num);
+	gtk_tree_view_set_model (GTK_TREE_VIEW (mg_gui->user_tree), NULL);
+	gtk_window_set_title (GTK_WINDOW (mg_gui->window),
+								 g_object_get_data (G_OBJECT (box), "title"));
+	gtk_widget_set_sensitive (mg_gui->menu, FALSE);
+
+	if (f)
+		gtk_widget_grab_focus (f);
+}
+
+/* a channel has been focused */
+
+static void
+mg_focus (session *sess)
+{
+	if (sess->gui->is_tab)
+		current_tab = sess;
+	current_sess = sess;
+
+	/* dirty trick to avoid auto-selection */
+	SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, FALSE);
+	gtk_widget_grab_focus (sess->gui->input_box);
+	SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, TRUE);
+
+	sess->server->front_session = sess;
+
+	if (sess->server->server_session != NULL)
+	{
+		if (sess->server->server_session->type != SESS_SERVER)
+			sess->server->server_session = sess;
+	} else
+	{
+		sess->server->server_session = sess;
+	}
+
+	if (sess->new_data || sess->nick_said || sess->msg_said)
+	{
+		sess->nick_said = FALSE;
+		sess->msg_said = FALSE;
+		sess->new_data = FALSE;
+		/* when called via mg_changui_new, is_tab might be true, but
+			sess->res->tab is still NULL. */
+		if (sess->res->tab)
+			fe_set_tab_color (sess, 0);
+	}
+}
+
+static int
+mg_progressbar_update (GtkWidget *bar)
+{
+	static int type = 0;
+	static float pos = 0;
+
+	pos += 0.05;
+	if (pos >= 0.99)
+	{
+		if (type == 0)
+		{
+			type = 1;
+			gtk_progress_bar_set_orientation ((GtkProgressBar *) bar,
+														 GTK_PROGRESS_RIGHT_TO_LEFT);
+		} else
+		{
+			type = 0;
+			gtk_progress_bar_set_orientation ((GtkProgressBar *) bar,
+														 GTK_PROGRESS_LEFT_TO_RIGHT);
+		}
+		pos = 0.05;
+	}
+	gtk_progress_bar_set_fraction ((GtkProgressBar *) bar, pos);
+	return 1;
+}
+
+void
+mg_progressbar_create (session_gui *gui)
+{
+	gui->bar = gtk_progress_bar_new ();
+	gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->bar, 0, 0, 0);
+	gtk_widget_show (gui->bar);
+	gui->bartag = fe_timeout_add (50, mg_progressbar_update, gui->bar);
+}
+
+void
+mg_progressbar_destroy (session_gui *gui)
+{
+	fe_timeout_remove (gui->bartag);
+	gtk_widget_destroy (gui->bar);
+	gui->bar = 0;
+	gui->bartag = 0;
+}
+
+/* switching tabs away from this one, so remember some info about it! */
+
+static void
+mg_unpopulate (session *sess)
+{
+	restore_gui *res;
+	session_gui *gui;
+	int i;
+
+	gui = sess->gui;
+	res = sess->res;
+
+	res->input_text = strdup (SPELL_ENTRY_GET_TEXT (gui->input_box));
+	res->topic_text = strdup (GTK_ENTRY (gui->topic_entry)->text);
+	res->limit_text = strdup (GTK_ENTRY (gui->limit_entry)->text);
+	res->key_text = strdup (GTK_ENTRY (gui->key_entry)->text);
+	if (gui->laginfo)
+		res->lag_text = strdup (gtk_label_get_text (GTK_LABEL (gui->laginfo)));
+	if (gui->throttleinfo)
+		res->queue_text = strdup (gtk_label_get_text (GTK_LABEL (gui->throttleinfo)));
+
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+		res->flag_wid_state[i] = GTK_TOGGLE_BUTTON (gui->flag_wid[i])->active;
+
+	res->old_ul_value = userlist_get_value (gui->user_tree);
+	if (gui->lagometer)
+		res->lag_value = gtk_progress_bar_get_fraction (
+													GTK_PROGRESS_BAR (gui->lagometer));
+	if (gui->throttlemeter)
+		res->queue_value = gtk_progress_bar_get_fraction (
+													GTK_PROGRESS_BAR (gui->throttlemeter));
+
+	if (gui->bar)
+	{
+		res->c_graph = TRUE;	/* still have a graph, just not visible now */
+		mg_progressbar_destroy (gui);
+	}
+}
+
+static void
+mg_restore_label (GtkWidget *label, char **text)
+{
+	if (!label)
+		return;
+
+	if (*text)
+	{
+		gtk_label_set_text (GTK_LABEL (label), *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		gtk_label_set_text (GTK_LABEL (label), "");
+	}
+}
+
+static void
+mg_restore_entry (GtkWidget *entry, char **text)
+{
+	if (*text)
+	{
+		gtk_entry_set_text (GTK_ENTRY (entry), *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	}
+	gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+}
+
+static void
+mg_restore_speller (GtkWidget *entry, char **text)
+{
+	if (*text)
+	{
+		SPELL_ENTRY_SET_TEXT (entry, *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		SPELL_ENTRY_SET_TEXT (entry, "");
+	}
+	SPELL_ENTRY_SET_POS (entry, -1);
+}
+
+void
+mg_set_topic_tip (session *sess)
+{
+	char *text;
+
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		if (sess->topic)
+		{
+			text = g_strdup_printf (_("Topic for %s is: %s"), sess->channel,
+						 sess->topic);
+			add_tip (sess->gui->topic_entry, text);
+			g_free (text);
+		} else
+			add_tip (sess->gui->topic_entry, _("No topic is set"));
+		break;
+	default:
+		if (GTK_ENTRY (sess->gui->topic_entry)->text &&
+			 GTK_ENTRY (sess->gui->topic_entry)->text[0])
+			add_tip (sess->gui->topic_entry, GTK_ENTRY (sess->gui->topic_entry)->text);
+		else
+			add_tip (sess->gui->topic_entry, NULL);
+	}
+}
+
+static void
+mg_hide_empty_pane (GtkPaned *pane)
+{
+#if defined(GTK_WIDGET_VISIBLE)
+	if ((pane->child1 == NULL || !GTK_WIDGET_VISIBLE (pane->child1)) &&
+		 (pane->child2 == NULL || !GTK_WIDGET_VISIBLE (pane->child2)))
+#else
+	if ((pane->child1 == NULL || !gtk_widget_get_visible (pane->child1)) &&
+		 (pane->child2 == NULL || !gtk_widget_get_visible (pane->child2)))
+#endif
+	{
+		gtk_widget_hide (GTK_WIDGET (pane));
+		return;
+	}
+
+	gtk_widget_show (GTK_WIDGET (pane));
+}
+
+static void
+mg_hide_empty_boxes (session_gui *gui)
+{
+	/* hide empty vpanes - so the handle is not shown */
+	mg_hide_empty_pane ((GtkPaned*)gui->vpane_right);
+	mg_hide_empty_pane ((GtkPaned*)gui->vpane_left);
+}
+
+static void
+mg_userlist_showhide (session *sess, int show)
+{
+	session_gui *gui = sess->gui;
+	int handle_size;
+
+	if (show)
+	{
+		gtk_widget_show (gui->user_box);
+		gui->ul_hidden = 0;
+
+		gtk_widget_style_get (GTK_WIDGET (gui->hpane_right), "handle-size", &handle_size, NULL);
+		gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (prefs.gui_pane_right_size + handle_size));
+	}
+	else
+	{
+		gtk_widget_hide (gui->user_box);
+		gui->ul_hidden = 1;
+	}
+
+	mg_hide_empty_boxes (gui);
+}
+
+static gboolean
+mg_is_userlist_and_tree_combined (void)
+{
+	if (prefs.tab_pos == POS_TOPLEFT && prefs.gui_ulist_pos == POS_BOTTOMLEFT)
+		return TRUE;
+	if (prefs.tab_pos == POS_BOTTOMLEFT && prefs.gui_ulist_pos == POS_TOPLEFT)
+		return TRUE;
+
+	if (prefs.tab_pos == POS_TOPRIGHT && prefs.gui_ulist_pos == POS_BOTTOMRIGHT)
+		return TRUE;
+	if (prefs.tab_pos == POS_BOTTOMRIGHT && prefs.gui_ulist_pos == POS_TOPRIGHT)
+		return TRUE;
+
+	return FALSE;
+}
+
+/* decide if the userlist should be shown or hidden for this tab */
+
+void
+mg_decide_userlist (session *sess, gboolean switch_to_current)
+{
+	/* when called from menu.c we need this */
+	if (sess->gui == mg_gui && switch_to_current)
+		sess = current_tab;
+
+	if (prefs.hideuserlist)
+	{
+		mg_userlist_showhide (sess, FALSE);
+		return;
+	}
+
+	switch (sess->type)
+	{
+	case SESS_SERVER:
+	case SESS_DIALOG:
+	case SESS_NOTICES:
+	case SESS_SNOTICES:
+		if (mg_is_userlist_and_tree_combined ())
+			mg_userlist_showhide (sess, TRUE);	/* show */
+		else
+			mg_userlist_showhide (sess, FALSE);	/* hide */
+		break;
+	default:		
+		mg_userlist_showhide (sess, TRUE);	/* show */
+	}
+}
+
+static void
+mg_userlist_toggle_cb (GtkWidget *button, gpointer userdata)
+{
+	prefs.hideuserlist = !prefs.hideuserlist;
+	mg_decide_userlist (current_sess, FALSE);
+	gtk_widget_grab_focus (current_sess->gui->input_box);
+}
+
+static int ul_tag = 0;
+
+static gboolean
+mg_populate_userlist (session *sess)
+{
+	session_gui *gui;
+
+	if (!sess)
+		sess = current_tab;
+
+	if (is_session (sess))
+	{
+		gui = sess->gui;
+		if (sess->type == SESS_DIALOG)
+			mg_set_access_icon (sess->gui, NULL, sess->server->is_away);
+		else
+			mg_set_access_icon (sess->gui, get_user_icon (sess->server, sess->me), sess->server->is_away);
+		userlist_show (sess);
+		userlist_set_value (sess->gui->user_tree, sess->res->old_ul_value);
+	}
+
+	ul_tag = 0;
+	return 0;
+}
+
+/* fill the irc tab with a new channel */
+
+static void
+mg_populate (session *sess)
+{
+	session_gui *gui = sess->gui;
+	restore_gui *res = sess->res;
+	int i, render = TRUE;
+	guint16 vis = gui->ul_hidden;
+
+	switch (sess->type)
+	{
+	case SESS_DIALOG:
+		/* show the dialog buttons */
+		gtk_widget_show (gui->dialogbutton_box);
+		/* hide the chan-mode buttons */
+		gtk_widget_hide (gui->topicbutton_box);
+		/* hide the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* shouldn't edit the topic */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE);
+		break;
+	case SESS_SERVER:
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (gui->topicbutton_box);
+		/* hide the dialog buttons */
+		gtk_widget_hide (gui->dialogbutton_box);
+		/* hide the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* shouldn't edit the topic */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE);
+		break;
+	default:
+		/* hide the dialog buttons */
+		gtk_widget_hide (gui->dialogbutton_box);
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (gui->topicbutton_box);
+		/* show the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* let the topic be editted */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), TRUE);
+	}
+
+	/* move to THE irc tab */
+	if (gui->is_tab)
+		gtk_notebook_set_current_page (GTK_NOTEBOOK (gui->note_book), 0);
+
+	/* xtext size change? Then don't render, wait for the expose caused
+      by showing/hidding the userlist */
+	if (vis != gui->ul_hidden && gui->user_box->allocation.width > 1)
+		render = FALSE;
+
+	gtk_xtext_buffer_show (GTK_XTEXT (gui->xtext), res->buffer, render);
+
+	if (gui->is_tab)
+		gtk_widget_set_sensitive (gui->menu, TRUE);
+
+	/* restore all the GtkEntry's */
+	mg_restore_entry (gui->topic_entry, &res->topic_text);
+	mg_restore_speller (gui->input_box, &res->input_text);
+	mg_restore_entry (gui->key_entry, &res->key_text);
+	mg_restore_entry (gui->limit_entry, &res->limit_text);
+	mg_restore_label (gui->laginfo, &res->lag_text);
+	mg_restore_label (gui->throttleinfo, &res->queue_text);
+
+	mg_focus (sess);
+	fe_set_title (sess);
+
+	/* this one flickers, so only change if necessary */
+	if (strcmp (sess->server->nick, gtk_button_get_label (GTK_BUTTON (gui->nick_label))) != 0)
+		gtk_button_set_label (GTK_BUTTON (gui->nick_label), sess->server->nick);
+
+	/* this is slow, so make it a timeout event */
+	if (!gui->is_tab)
+	{
+		mg_populate_userlist (sess);
+	} else
+	{
+		if (ul_tag == 0)
+			ul_tag = g_idle_add ((GSourceFunc)mg_populate_userlist, NULL);
+	}
+
+	fe_userlist_numbers (sess);
+
+	/* restore all the channel mode buttons */
+	ignore_chanmode = TRUE;
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->flag_wid[i]),
+												res->flag_wid_state[i]);
+	ignore_chanmode = FALSE;
+
+	if (gui->lagometer)
+	{
+		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->lagometer),
+												 res->lag_value);
+		if (res->lag_tip)
+			add_tip (sess->gui->lagometer->parent, res->lag_tip);
+	}
+	if (gui->throttlemeter)
+	{
+		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->throttlemeter),
+												 res->queue_value);
+		if (res->queue_tip)
+			add_tip (sess->gui->throttlemeter->parent, res->queue_tip);
+	}
+
+	/* did this tab have a connecting graph? restore it.. */
+	if (res->c_graph)
+	{
+		res->c_graph = FALSE;
+		mg_progressbar_create (gui);
+	}
+
+	/* menu items */
+	GTK_CHECK_MENU_ITEM (gui->menu_item[MENU_ID_AWAY])->active = sess->server->is_away;
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], sess->server->connected);
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], sess->server->end_of_motd);
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT],
+									  sess->server->connected || sess->server->recondelay_tag);
+
+	mg_set_topic_tip (sess);
+
+	plugin_emit_dummy_print (sess, "Focus Tab");
+}
+
+void
+mg_bring_tofront_sess (session *sess)	/* IRC tab or window */
+{
+	if (sess->gui->is_tab)
+		chan_focus (sess->res->tab);
+	else
+		gtk_window_present (GTK_WINDOW (sess->gui->window));
+}
+
+void
+mg_bring_tofront (GtkWidget *vbox)	/* non-IRC tab or window */
+{
+	chan *ch;
+
+	ch = g_object_get_data (G_OBJECT (vbox), "ch");
+	if (ch)
+		chan_focus (ch);
+	else
+		gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (vbox)));
+}
+
+void
+mg_switch_page (int relative, int num)
+{
+	if (mg_gui)
+		chanview_move_focus (mg_gui->chanview, relative, num);
+}
+
+/* a toplevel IRC window was destroyed */
+
+static void
+mg_topdestroy_cb (GtkWidget *win, session *sess)
+{
+/*	printf("enter mg_topdestroy. sess %p was destroyed\n", sess);*/
+
+	/* kill the text buffer */
+	gtk_xtext_buffer_free (sess->res->buffer);
+	/* kill the user list */
+	g_object_unref (G_OBJECT (sess->res->user_model));
+
+	session_free (sess);	/* tell xchat.c about it */
+}
+
+/* cleanup an IRC tab */
+
+static void
+mg_ircdestroy (session *sess)
+{
+	GSList *list;
+
+	/* kill the text buffer */
+	gtk_xtext_buffer_free (sess->res->buffer);
+	/* kill the user list */
+	g_object_unref (G_OBJECT (sess->res->user_model));
+
+	session_free (sess);	/* tell xchat.c about it */
+
+	if (mg_gui == NULL)
+	{
+/*		puts("-> mg_gui is already NULL");*/
+		return;
+	}
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->is_tab)
+		{
+/*			puts("-> some tabs still remain");*/
+			return;
+		}
+		list = list->next;
+	}
+
+/*	puts("-> no tabs left, killing main tabwindow");*/
+	gtk_widget_destroy (mg_gui->window);
+	active_tab = NULL;
+	mg_gui = NULL;
+	parent_window = NULL;
+}
+
+static void
+mg_tab_close_cb (GtkWidget *dialog, gint arg1, session *sess)
+{
+	GSList *list, *next;
+
+	gtk_widget_destroy (dialog);
+	if (arg1 == GTK_RESPONSE_OK && is_session (sess))
+	{
+		/* force it NOT to send individual PARTs */
+		sess->server->sent_quit = TRUE;
+
+		for (list = sess_list; list;)
+		{
+			next = list->next;
+			if (((session *)list->data)->server == sess->server &&
+				 ((session *)list->data) != sess)
+				fe_close_window ((session *)list->data);
+			list = next;
+		}
+
+		/* just send one QUIT - better for BNCs */
+		sess->server->sent_quit = FALSE;
+		fe_close_window (sess);
+	}
+}
+
+void
+mg_tab_close (session *sess)
+{
+	GtkWidget *dialog;
+	GSList *list;
+	int i;
+
+	if (chan_remove (sess->res->tab, FALSE))
+		mg_ircdestroy (sess);
+	else
+	{
+		for (i = 0, list = sess_list; list; list = list->next)
+			if (((session *)list->data)->server == sess->server)
+				i++;
+		dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0,
+						GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
+						_("This server still has %d channels or dialogs associated with it. "
+						  "Close them all?"), i);
+		g_signal_connect (G_OBJECT (dialog), "response",
+								G_CALLBACK (mg_tab_close_cb), sess);
+		if (prefs.tab_layout)
+		{
+			gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+		}
+		else
+		{
+			gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);		
+		}
+		gtk_widget_show (dialog);
+	}
+}
+
+static void
+mg_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+void
+mg_create_icon_item (char *label, char *stock, GtkWidget *menu,
+							void *callback, void *userdata)
+{
+	GtkWidget *item;
+
+	item = create_icon_menu (label, stock, TRUE);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback),
+							userdata);
+	gtk_widget_show (item);
+}
+
+static int
+mg_count_networks (void)
+{
+	int cons = 0;
+	GSList *list;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		if (((server *)list->data)->connected)
+			cons++;
+	}
+	return cons;
+}
+
+static int
+mg_count_dccs (void)
+{
+	GSList *list;
+	struct DCC *dcc;
+	int dccs = 0;
+
+	list = dcc_list;
+	while (list)
+	{
+		dcc = list->data;
+		if ((dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) &&
+			 dcc->dccstat == STAT_ACTIVE)
+			dccs++;
+		list = list->next;
+	}
+
+	return dccs;
+}
+
+void
+mg_open_quit_dialog (gboolean minimize_button)
+{
+	static GtkWidget *dialog = NULL;
+	GtkWidget *dialog_vbox1;
+	GtkWidget *table1;
+	GtkWidget *image;
+	GtkWidget *checkbutton1;
+	GtkWidget *label;
+	GtkWidget *dialog_action_area1;
+	GtkWidget *button;
+	char *text, *connecttext;
+	int cons;
+	int dccs;
+
+	if (dialog)
+	{
+		gtk_window_present (GTK_WINDOW (dialog));
+		return;
+	}
+
+	dccs = mg_count_dccs ();
+	cons = mg_count_networks ();
+	if (dccs + cons == 0 || !prefs.gui_quit_dialog)
+	{
+		xchat_exit ();
+		return;
+	}
+
+	dialog = gtk_dialog_new ();
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Quit XChat?"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
+	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+	gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+
+	dialog_vbox1 = GTK_DIALOG (dialog)->vbox;
+	gtk_widget_show (dialog_vbox1);
+
+	table1 = gtk_table_new (2, 2, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (dialog_vbox1), table1, TRUE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table1), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 12);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 12);
+
+	image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+	gtk_widget_show (image);
+	gtk_table_attach (GTK_TABLE (table1), image, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	checkbutton1 = gtk_check_button_new_with_mnemonic (_("Don't ask next time."));
+	gtk_widget_show (checkbutton1);
+	gtk_table_attach (GTK_TABLE (table1), checkbutton1, 0, 2, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 4);
+
+	connecttext = g_strdup_printf (_("You are connected to %i IRC networks."), cons);
+	text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s\n%s",
+								_("Are you sure you want to quit?"),
+								cons ? connecttext : "",
+								dccs ? _("Some file transfers are still active.") : "");
+	g_free (connecttext);
+	label = gtk_label_new (text);
+	g_free (text);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table1), label, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK), 0, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	dialog_action_area1 = GTK_DIALOG (dialog)->action_area;
+	gtk_widget_show (dialog_action_area1);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1),
+										GTK_BUTTONBOX_END);
+
+	if (minimize_button)
+	{
+		button = gtk_button_new_with_mnemonic (_("_Minimize to Tray"));
+		gtk_widget_show (button);
+		gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 1);
+	}
+
+	button = gtk_button_new_from_stock ("gtk-cancel");
+	gtk_widget_show (button);
+	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+											GTK_RESPONSE_CANCEL);
+	gtk_widget_grab_focus (button);
+
+	button = gtk_button_new_from_stock ("gtk-quit");
+	gtk_widget_show (button);
+	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 0);
+
+	gtk_widget_show (dialog);
+
+	switch (gtk_dialog_run (GTK_DIALOG (dialog)))
+	{
+	case 0:
+		if (GTK_TOGGLE_BUTTON (checkbutton1)->active)
+			prefs.gui_quit_dialog = 0;
+		xchat_exit ();
+		break;
+	case 1: /* minimize to tray */
+		if (GTK_TOGGLE_BUTTON (checkbutton1)->active)
+		{
+			prefs.gui_tray_flags |= 1;
+			/*prefs.gui_quit_dialog = 0;*/
+		}
+		/* force tray icon ON, if not already */
+		if (!prefs.gui_tray)
+		{
+			prefs.gui_tray = 1;
+			tray_apply_setup ();
+		}
+		tray_toggle_visibility (TRUE);
+		break;
+	}
+
+	gtk_widget_destroy (dialog);
+	dialog = NULL;
+}
+
+void
+mg_close_sess (session *sess)
+{
+	if (sess_list->next == NULL)
+	{
+		mg_open_quit_dialog (FALSE);
+		return;
+	}
+
+	fe_close_window (sess);
+}
+
+static int
+mg_chan_remove (chan *ch)
+{
+	/* remove the tab from chanview */
+	chan_remove (ch, TRUE);
+	/* any tabs left? */
+	if (chanview_get_size (mg_gui->chanview) < 1)
+	{
+		/* if not, destroy the main tab window */
+		gtk_widget_destroy (mg_gui->window);
+		current_tab = NULL;
+		active_tab = NULL;
+		mg_gui = NULL;
+		parent_window = NULL;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* destroy non-irc tab/window */
+
+static void
+mg_close_gen (chan *ch, GtkWidget *box)
+{
+	char *title = g_object_get_data (G_OBJECT (box), "title");
+
+	if (title)
+		free (title);
+	if (!ch)
+		ch = g_object_get_data (G_OBJECT (box), "ch");
+	if (ch)
+	{
+		/* remove from notebook */
+		gtk_widget_destroy (box);
+		/* remove the tab from chanview */
+		mg_chan_remove (ch);
+	} else
+	{
+		gtk_widget_destroy (gtk_widget_get_toplevel (box));
+	}
+}
+
+/* the "X" close button has been pressed (tab-view) */
+
+static void
+mg_xbutton_cb (chanview *cv, chan *ch, int tag, gpointer userdata)
+{
+	if (tag == TAG_IRC)	/* irc tab */
+		mg_close_sess (userdata);
+	else						/* non-irc utility tab */
+		mg_close_gen (ch, userdata);
+}
+
+static void
+mg_link_gentab (chan *ch, GtkWidget *box)
+{
+	int num;
+	GtkWidget *win;
+
+	g_object_ref (box);
+
+	num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box);
+	gtk_notebook_remove_page (GTK_NOTEBOOK (mg_gui->note_book), num);
+	mg_chan_remove (ch);
+
+	win = gtkutil_window_new (g_object_get_data (G_OBJECT (box), "title"), "",
+									  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "w")),
+									  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "h")),
+									  3);
+	/* so it doesn't try to chan_remove (there's no tab anymore) */
+	g_object_steal_data (G_OBJECT (box), "ch");
+	gtk_container_set_border_width (GTK_CONTAINER (box), 0);
+	gtk_container_add (GTK_CONTAINER (win), box);
+	gtk_widget_show (win);
+
+	g_object_unref (box);
+}
+
+static void
+mg_detach_tab_cb (GtkWidget *item, chan *ch)
+{
+	if (chan_get_tag (ch) == TAG_IRC)	/* IRC tab */
+	{
+		/* userdata is session * */
+		mg_link_irctab (chan_get_userdata (ch), 1);
+		return;
+	}
+
+	/* userdata is GtkWidget * */
+	mg_link_gentab (ch, chan_get_userdata (ch));	/* non-IRC tab */
+}
+
+static void
+mg_destroy_tab_cb (GtkWidget *item, chan *ch)
+{
+	/* treat it just like the X button press */
+	mg_xbutton_cb (mg_gui->chanview, ch, chan_get_tag (ch), chan_get_userdata (ch));
+}
+
+static void
+mg_color_insert (GtkWidget *item, gpointer userdata)
+{
+	char buf[32];
+	char *text;
+	int num = GPOINTER_TO_INT (userdata);
+
+	if (num > 99)
+	{
+		switch (num)
+		{
+		case 100:
+			text = "\002"; break;
+		case 101:
+			text = "\037"; break;
+		case 102:
+			text = "\035"; break;
+		default:
+			text = "\017"; break;
+		}
+		key_action_insert (current_sess->gui->input_box, 0, text, 0, 0);
+	} else
+	{
+		sprintf (buf, "\003%02d", num);
+		key_action_insert (current_sess->gui->input_box, 0, buf, 0, 0);
+	}
+}
+
+static void
+mg_markup_item (GtkWidget *menu, char *text, int arg)
+{
+	GtkWidget *item;
+
+	item = gtk_menu_item_new_with_label ("");
+	gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), text);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (mg_color_insert), GINT_TO_POINTER (arg));
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+}
+
+GtkWidget *
+mg_submenu (GtkWidget *menu, char *text)
+{
+	GtkWidget *submenu, *item;
+
+	item = gtk_menu_item_new_with_mnemonic (text);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+
+	submenu = gtk_menu_new ();
+	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+	gtk_widget_show (submenu);
+
+	return submenu;
+}
+
+static void
+mg_create_color_menu (GtkWidget *menu, session *sess)
+{
+	GtkWidget *submenu;
+	GtkWidget *subsubmenu;
+	char buf[256];
+	int i;
+
+	submenu = mg_submenu (menu, _("Insert Attribute or Color Code"));
+
+	mg_markup_item (submenu, _("<b>Bold</b>"), 100);
+	mg_markup_item (submenu, _("<u>Underline</u>"), 101);
+	/*mg_markup_item (submenu, _("<i>Italic</i>"), 102);*/
+	mg_markup_item (submenu, _("Normal"), 103);
+
+	subsubmenu = mg_submenu (submenu, _("Colors 0-7"));
+
+	for (i = 0; i < 8; i++)
+	{
+		sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">"
+					"   </span></tt>",
+				i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8);
+		mg_markup_item (subsubmenu, buf, i);
+	}
+
+	subsubmenu = mg_submenu (submenu, _("Colors 8-15"));
+
+	for (i = 8; i < 16; i++)
+	{
+		sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">"
+					"   </span></tt>",
+				i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8);
+		mg_markup_item (subsubmenu, buf, i);
+	}
+}
+
+static void
+mg_set_guint8 (GtkCheckMenuItem *item, guint8 *setting)
+{
+	session *sess = current_sess;
+	guint8 logging = sess->text_logging;
+
+	*setting = SET_OFF;
+	if (item->active)
+		*setting = SET_ON;
+
+	/* has the logging setting changed? */
+	if (logging != sess->text_logging)
+		log_open_or_close (sess);
+}
+
+static void
+mg_perchan_menu_item (char *label, GtkWidget *menu, guint8 *setting, guint global)
+{
+	guint8 initial_value = *setting;
+
+	/* if it's using global value, use that as initial state */
+	if (initial_value == SET_DEFAULT)
+		initial_value = global;
+
+	menu_toggle_item (label, menu, mg_set_guint8, setting, initial_value);
+}
+
+static void
+mg_create_perchannelmenu (session *sess, GtkWidget *menu)
+{
+	GtkWidget *submenu;
+
+	submenu = menu_quick_sub (_("_Settings"), menu, NULL, XCMENU_MNEMONIC, -1);
+
+	mg_perchan_menu_item (_("_Log to Disk"), submenu, &sess->text_logging, prefs.logging);
+	mg_perchan_menu_item (_("_Reload Scrollback"), submenu, &sess->text_scrollback, prefs.text_replay);
+	if (sess->type == SESS_CHANNEL)
+		mg_perchan_menu_item (_("_Hide Join/Part Messages"), submenu, &sess->text_hidejoinpart, prefs.confmode);
+}
+
+static void
+mg_create_alertmenu (session *sess, GtkWidget *menu)
+{
+	GtkWidget *submenu;
+
+	submenu = menu_quick_sub (_("_Extra Alerts"), menu, NULL, XCMENU_MNEMONIC, -1);
+
+	mg_perchan_menu_item (_("Beep on _Message"), submenu, &sess->alert_beep, prefs.input_beep_chans);
+	mg_perchan_menu_item (_("Blink Tray _Icon"), submenu, &sess->alert_tray, prefs.input_tray_chans);
+	mg_perchan_menu_item (_("Blink Task _Bar"), submenu, &sess->alert_taskbar, prefs.input_flash_chans);
+}
+
+static void
+mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch)
+{
+	GtkWidget *menu, *item;
+	char buf[256];
+
+	menu = gtk_menu_new ();
+
+	if (sess)
+	{
+		char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1);
+		snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name);
+		g_free (name);
+
+		item = gtk_menu_item_new_with_label ("");
+		gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), buf);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+		gtk_widget_show (item);
+
+		/* separator */
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+		/* per-channel alerts */
+		mg_create_alertmenu (sess, menu);
+
+		/* per-channel settings */
+		mg_create_perchannelmenu (sess, menu);
+
+		/* separator */
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+		if (sess->type == SESS_CHANNEL)
+			menu_addfavoritemenu (sess->server, menu, sess->channel);
+	}
+
+	mg_create_icon_item (_("_Detach"), GTK_STOCK_REDO, menu,
+								mg_detach_tab_cb, ch);
+	mg_create_icon_item (_("_Close"), GTK_STOCK_CLOSE, menu,
+								mg_destroy_tab_cb, ch);
+	if (sess && tabmenu_list)
+		menu_create (menu, tabmenu_list, sess->channel, FALSE);
+	menu_add_plugin_items (menu, "\x4$TAB", sess->channel);
+
+	if (event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (mg_menu_destroy), NULL);
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time);
+}
+
+static gboolean
+mg_tab_contextmenu_cb (chanview *cv, chan *ch, int tag, gpointer ud, GdkEventButton *event)
+{
+	/* shift-click to close a tab */
+	if ((event->state & GDK_SHIFT_MASK) && event->type == GDK_BUTTON_PRESS)
+	{
+		mg_xbutton_cb (cv, ch, tag, ud);
+		return FALSE;
+	}
+
+	if (event->button != 3)
+		return FALSE;
+
+	if (tag == TAG_IRC)
+		mg_create_tabmenu (ud, event, ch);
+	else
+		mg_create_tabmenu (NULL, event, ch);
+
+	return TRUE;
+}
+
+void
+mg_dnd_drop_file (session *sess, char *target, char *uri)
+{
+	char *p, *data, *next, *fname;
+
+	p = data = strdup (uri);
+	while (*p)
+	{
+		next = strchr (p, '\r');
+		if (strncasecmp ("file:", p, 5) == 0)
+		{
+			if (next)
+				*next = 0;
+			fname = g_filename_from_uri (p, NULL, NULL);
+			if (fname)
+			{
+				/* dcc_send() expects utf-8 */
+				p = xchat_filename_to_utf8 (fname, -1, 0, 0, 0);
+				if (p)
+				{
+					dcc_send (sess, target, p, prefs.dcc_max_send_cps, 0);
+					g_free (p);
+				}
+				g_free (fname);
+			}
+		}
+		if (!next)
+			break;
+		p = next + 1;
+		if (*p == '\n')
+			p++;
+	}
+	free (data);
+
+}
+
+static void
+mg_dialog_dnd_drop (GtkWidget * widget, GdkDragContext * context, gint x,
+						  gint y, GtkSelectionData * selection_data, guint info,
+						  guint32 time, gpointer ud)
+{
+	if (current_sess->type == SESS_DIALOG)
+		/* sess->channel is really the nickname of dialogs */
+		mg_dnd_drop_file (current_sess, current_sess->channel, selection_data->data);
+}
+
+/* add a tabbed channel */
+
+static void
+mg_add_chan (session *sess)
+{
+	GdkPixbuf *icon;
+	char *name = _("<none>");
+
+	if (sess->channel[0])
+		name = sess->channel;
+
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		icon = pix_channel;
+		break;
+	case SESS_SERVER:
+		icon = pix_server;
+		break;
+	default:
+		icon = pix_dialog;
+	}
+
+	sess->res->tab = chanview_add (sess->gui->chanview, name, sess->server, sess,
+											 sess->type == SESS_SERVER ? FALSE : TRUE,
+											 TAG_IRC, icon);
+	if (plain_list == NULL)
+		mg_create_tab_colors ();
+
+	chan_set_color (sess->res->tab, plain_list);
+
+	if (sess->res->buffer == NULL)
+	{
+		sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext));
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		sess->res->user_model = userlist_create_model ();
+	}
+}
+
+static void
+mg_userlist_button (GtkWidget * box, char *label, char *cmd,
+						  int a, int b, int c, int d)
+{
+	GtkWidget *wid = gtk_button_new_with_label (label);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (userlist_button_cb), cmd);
+	gtk_table_attach_defaults (GTK_TABLE (box), wid, a, b, c, d);
+	show_and_unfocus (wid);
+}
+
+static GtkWidget *
+mg_create_userlistbuttons (GtkWidget *box)
+{
+	struct popup *pop;
+	GSList *list = button_list;
+	int a = 0, b = 0;
+	GtkWidget *tab;
+
+	tab = gtk_table_new (5, 2, FALSE);
+	gtk_box_pack_end (GTK_BOX (box), tab, FALSE, FALSE, 0);
+
+	while (list)
+	{
+		pop = list->data;
+		if (pop->cmd[0])
+		{
+			mg_userlist_button (tab, pop->name, pop->cmd, a, a + 1, b, b + 1);
+			a++;
+			if (a == 2)
+			{
+				a = 0;
+				b++;
+			}
+		}
+		list = list->next;
+	}
+
+	return tab;
+}
+
+static void
+mg_topic_cb (GtkWidget *entry, gpointer userdata)
+{
+	session *sess = current_sess;
+	char *text;
+
+	if (sess->channel[0] && sess->server->connected && sess->type == SESS_CHANNEL)
+	{
+		text = GTK_ENTRY (entry)->text;
+		if (text[0] == 0)
+			text = NULL;
+		sess->server->p_topic (sess->server, sess->channel, text);
+	} else
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	/* restore focus to the input widget, where the next input will most
+likely be */
+	gtk_widget_grab_focus (sess->gui->input_box);
+}
+
+static void
+mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata)
+{
+	GSList *list, *next;
+	session *sess;
+
+/*	puts("enter mg_tabwindow_kill_cb");*/
+	xchat_is_quitting = TRUE;
+
+	/* see if there's any non-tab windows left */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		next = list->next;
+		if (!sess->gui->is_tab)
+		{
+			xchat_is_quitting = FALSE;
+/*			puts("-> will not exit, some toplevel windows left");*/
+		} else
+		{
+			mg_ircdestroy (sess);
+		}
+		list = next;
+	}
+
+	current_tab = NULL;
+	active_tab = NULL;
+	mg_gui = NULL;
+	parent_window = NULL;
+}
+
+static GtkWidget *
+mg_changui_destroy (session *sess)
+{
+	GtkWidget *ret = NULL;
+
+	if (sess->gui->is_tab)
+	{
+		/* avoid calling the "destroy" callback */
+		g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window),
+														  mg_tabwindow_kill_cb, 0);
+		/* remove the tab from the chanview */
+		if (!mg_chan_remove (sess->res->tab))
+			/* if the window still exists, restore the signal handler */
+			g_signal_connect (G_OBJECT (sess->gui->window), "destroy",
+									G_CALLBACK (mg_tabwindow_kill_cb), 0);
+	} else
+	{
+		/* avoid calling the "destroy" callback */
+		g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window),
+														  mg_topdestroy_cb, sess);
+		/*gtk_widget_destroy (sess->gui->window);*/
+		/* don't destroy until the new one is created. Not sure why, but */
+		/* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */
+		/*           assertion `GDK_IS_COLORMAP (cmap)' failed */
+		ret = sess->gui->window;
+		free (sess->gui);
+		sess->gui = NULL;
+	}
+	return ret;
+}
+
+static void
+mg_link_irctab (session *sess, int focus)
+{
+	GtkWidget *win;
+
+	if (sess->gui->is_tab)
+	{
+		win = mg_changui_destroy (sess);
+		mg_changui_new (sess, sess->res, 0, focus);
+		mg_populate (sess);
+		xchat_is_quitting = FALSE;
+		if (win)
+			gtk_widget_destroy (win);
+		return;
+	}
+
+	mg_unpopulate (sess);
+	win = mg_changui_destroy (sess);
+	mg_changui_new (sess, sess->res, 1, focus);
+	/* the buffer is now attached to a different widget */
+	((xtext_buffer *)sess->res->buffer)->xtext = (GtkXText *)sess->gui->xtext;
+	if (win)
+		gtk_widget_destroy (win);
+}
+
+void
+mg_detach (session *sess, int mode)
+{
+	switch (mode)
+	{
+	/* detach only */
+	case 1:
+		if (sess->gui->is_tab)
+			mg_link_irctab (sess, 1);
+		break;
+	/* attach only */
+	case 2:
+		if (!sess->gui->is_tab)
+			mg_link_irctab (sess, 1);
+		break;
+	/* toggle */
+	default:
+		mg_link_irctab (sess, 1);
+	}
+}
+
+static int
+check_is_number (char *t)
+{
+	while (*t)
+	{
+		if (*t < '0' || *t > '9')
+			return FALSE;
+		t++;
+	}
+	return TRUE;
+}
+
+static void
+mg_change_flag (GtkWidget * wid, session *sess, char flag)
+{
+	server *serv = sess->server;
+	char mode[3];
+
+	mode[1] = flag;
+	mode[2] = '\0';
+	if (serv->connected && sess->channel[0])
+	{
+		if (GTK_TOGGLE_BUTTON (wid)->active)
+			mode[0] = '+';
+		else
+			mode[0] = '-';
+		serv->p_mode (serv, sess->channel, mode);
+		serv->p_join_info (serv, sess->channel);
+		sess->ignore_mode = TRUE;
+		sess->ignore_date = TRUE;
+	}
+}
+
+static void
+flagl_hit (GtkWidget * wid, struct session *sess)
+{
+	char modes[512];
+	const char *limit_str;
+	server *serv = sess->server;
+
+	if (GTK_TOGGLE_BUTTON (wid)->active)
+	{
+		if (serv->connected && sess->channel[0])
+		{
+			limit_str = gtk_entry_get_text (GTK_ENTRY (sess->gui->limit_entry));
+			if (check_is_number ((char *)limit_str) == FALSE)
+			{
+				fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR);
+				gtk_entry_set_text (GTK_ENTRY (sess->gui->limit_entry), "");
+				gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), FALSE);
+				return;
+			}
+			snprintf (modes, sizeof (modes), "+l %d", atoi (limit_str));
+			serv->p_mode (serv, sess->channel, modes);
+			serv->p_join_info (serv, sess->channel);
+		}
+	} else
+		mg_change_flag (wid, sess, 'l');
+}
+
+static void
+flagk_hit (GtkWidget * wid, struct session *sess)
+{
+	char modes[512];
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		snprintf (modes, sizeof (modes), "-k %s", 
+			  gtk_entry_get_text (GTK_ENTRY (sess->gui->key_entry)));
+
+		if (GTK_TOGGLE_BUTTON (wid)->active)
+			modes[0] = '+';
+
+		serv->p_mode (serv, sess->channel, modes);
+	}
+}
+
+static void
+mg_flagbutton_cb (GtkWidget *but, char *flag)
+{
+	session *sess;
+	char mode;
+
+	if (ignore_chanmode)
+		return;
+
+	sess = current_sess;
+	mode = tolower ((unsigned char) flag[0]);
+
+	switch (mode)
+	{
+	case 'l':
+		flagl_hit (but, sess);
+		break;
+	case 'k':
+		flagk_hit (but, sess);
+		break;
+	case 'b':
+		ignore_chanmode = TRUE;
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_b), FALSE);
+		ignore_chanmode = FALSE;
+		banlist_opengui (sess);
+		break;
+	default:
+		mg_change_flag (but, sess, mode);
+	}
+}
+
+static GtkWidget *
+mg_create_flagbutton (char *tip, GtkWidget *box, char *face)
+{
+	GtkWidget *wid;
+
+	wid = gtk_toggle_button_new_with_label (face);
+	gtk_widget_set_size_request (wid, 18, 0);
+	add_tip (wid, tip);
+	gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (mg_flagbutton_cb), face);
+	show_and_unfocus (wid);
+
+	return wid;
+}
+
+static void
+mg_key_entry_cb (GtkWidget * igad, gpointer userdata)
+{
+	char modes[512];
+	session *sess = current_sess;
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		snprintf (modes, sizeof (modes), "+k %s",
+				gtk_entry_get_text (GTK_ENTRY (igad)));
+		serv->p_mode (serv, sess->channel, modes);
+		serv->p_join_info (serv, sess->channel);
+	}
+}
+
+static void
+mg_limit_entry_cb (GtkWidget * igad, gpointer userdata)
+{
+	char modes[512];
+	session *sess = current_sess;
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		if (check_is_number ((char *)gtk_entry_get_text (GTK_ENTRY (igad))) == FALSE)
+		{
+			gtk_entry_set_text (GTK_ENTRY (igad), "");
+			fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR);
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_l), FALSE);
+			return;
+		}
+		snprintf (modes, sizeof(modes), "+l %d", 
+				atoi (gtk_entry_get_text (GTK_ENTRY (igad))));
+		serv->p_mode (serv, sess->channel, modes);
+		serv->p_join_info (serv, sess->channel);
+	}
+}
+
+static void
+mg_apply_entry_style (GtkWidget *entry)
+{
+	gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]);
+	gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]);
+	gtk_widget_modify_font (entry, input_style->font_desc);
+}
+
+static void
+mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
+{
+	gui->flag_t = mg_create_flagbutton (_("Topic Protection"), box, "T");
+	gui->flag_n = mg_create_flagbutton (_("No outside messages"), box, "N");
+	gui->flag_s = mg_create_flagbutton (_("Secret"), box, "S");
+	gui->flag_i = mg_create_flagbutton (_("Invite Only"), box, "I");
+	gui->flag_p = mg_create_flagbutton (_("Private"), box, "P");
+	gui->flag_m = mg_create_flagbutton (_("Moderated"), box, "M");
+	gui->flag_b = mg_create_flagbutton (_("Ban List"), box, "B");
+
+	gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K");
+	gui->key_entry = gtk_entry_new ();
+	gtk_widget_set_name (gui->key_entry, "xchat-inputbox");
+	gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16);
+	gtk_widget_set_size_request (gui->key_entry, 30, -1);
+	gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
+	g_signal_connect (G_OBJECT (gui->key_entry), "activate",
+							G_CALLBACK (mg_key_entry_cb), NULL);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (gui->key_entry);
+
+	gui->flag_l = mg_create_flagbutton (_("User Limit"), box, "L");
+	gui->limit_entry = gtk_entry_new ();
+	gtk_widget_set_name (gui->limit_entry, "xchat-inputbox");
+	gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
+	gtk_widget_set_size_request (gui->limit_entry, 30, -1);
+	gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
+	g_signal_connect (G_OBJECT (gui->limit_entry), "activate",
+							G_CALLBACK (mg_limit_entry_cb), NULL);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (gui->limit_entry);
+}
+
+/*static void
+mg_create_link_buttons (GtkWidget *box, gpointer userdata)
+{
+	gtkutil_button (box, GTK_STOCK_CLOSE, _("Close this tab/window"),
+						 mg_x_click_cb, userdata, 0);
+
+	if (!userdata)
+	gtkutil_button (box, GTK_STOCK_REDO, _("Attach/Detach this tab"),
+						 mg_link_cb, userdata, 0);
+}*/
+
+static void
+mg_dialog_button_cb (GtkWidget *wid, char *cmd)
+{
+	/* the longest cmd is 12, and the longest nickname is 64 */
+	char buf[128];
+	char *host = "";
+	char *topic;
+
+	if (!current_sess)
+		return;
+
+	topic = (char *)(GTK_ENTRY (current_sess->gui->topic_entry)->text);
+	topic = strrchr (topic, '@');
+	if (topic)
+		host = topic + 1;
+
+	auto_insert (buf, sizeof (buf), cmd, 0, 0, "", "", "",
+					 server_get_network (current_sess->server, TRUE), host, "",
+					 current_sess->channel);
+
+	handle_command (current_sess, buf, TRUE);
+
+	/* dirty trick to avoid auto-selection */
+	SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE);
+	gtk_widget_grab_focus (current_sess->gui->input_box);
+	SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE);
+}
+
+static void
+mg_dialog_button (GtkWidget *box, char *name, char *cmd)
+{
+	GtkWidget *wid;
+
+	wid = gtk_button_new_with_label (name);
+	gtk_box_pack_start (GTK_BOX (box), wid, FALSE, FALSE, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (mg_dialog_button_cb), cmd);
+	gtk_widget_set_size_request (wid, -1, 0);
+}
+
+static void
+mg_create_dialogbuttons (GtkWidget *box)
+{
+	struct popup *pop;
+	GSList *list = dlgbutton_list;
+
+	while (list)
+	{
+		pop = list->data;
+		if (pop->cmd[0])
+			mg_dialog_button (box, pop->name, pop->cmd);
+		list = list->next;
+	}
+}
+
+static void
+mg_create_topicbar (session *sess, GtkWidget *box)
+{
+	GtkWidget *hbox, *topic, *bbox;
+	session_gui *gui = sess->gui;
+
+	gui->topic_bar = hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	if (!gui->is_tab)
+		sess->res->tab = NULL;
+
+	gui->topic_entry = topic = gtk_entry_new ();
+	gtk_widget_set_name (topic, "xchat-inputbox");
+	gtk_container_add (GTK_CONTAINER (hbox), topic);
+	g_signal_connect (G_OBJECT (topic), "activate",
+							G_CALLBACK (mg_topic_cb), 0);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (topic);
+
+	gui->topicbutton_box = bbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0);
+	mg_create_chanmodebuttons (gui, bbox);
+
+	gui->dialogbutton_box = bbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0);
+	mg_create_dialogbuttons (bbox);
+
+	if (!prefs.paned_userlist)
+		gtkutil_button (hbox, GTK_STOCK_GOTO_LAST, _("Show/Hide userlist"),
+							 mg_userlist_toggle_cb, 0, 0);
+}
+
+/* check if a word is clickable */
+
+static int
+mg_word_check (GtkWidget * xtext, char *word, int len)
+{
+	session *sess = current_sess;
+	int ret;
+
+	ret = url_check_word (word, len);	/* common/url.c */
+	if (ret == 0)
+	{
+		if (( (word[0]=='@' || word[0]=='+' || word[0]=='%') && userlist_find (sess, word+1)) || userlist_find (sess, word))
+			return WORD_NICK;
+
+		if (sess->type == SESS_DIALOG)
+			return WORD_DIALOG;
+	}
+
+	return ret;
+}
+
+/* mouse click inside text area */
+
+static void
+mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
+{
+	session *sess = current_sess;
+
+	if (even->button == 1)			/* left button */
+	{
+		if (word == NULL)
+		{
+			mg_focus (sess);
+			return;
+		}
+
+		if ((even->state & 13) == prefs.gui_url_mod)
+		{
+			switch (mg_word_check (xtext, word, strlen (word)))
+			{
+			case WORD_URL:
+			case WORD_HOST:
+				fe_open_url (word);
+			}
+		}
+		return;
+	}
+
+	if (even->button == 2)
+	{
+		if (sess->type == SESS_DIALOG)
+			menu_middlemenu (sess, even);
+		else if (even->type == GDK_2BUTTON_PRESS)
+			userlist_select (sess, word);
+		return;
+	}
+
+	switch (mg_word_check (xtext, word, strlen (word)))
+	{
+	case 0:
+		menu_middlemenu (sess, even);
+		break;
+	case WORD_URL:
+	case WORD_HOST:
+		menu_urlmenu (even, word);
+		break;
+	case WORD_NICK:
+		menu_nickmenu (sess, even, (word[0]=='@' || word[0]=='+' || word[0]=='%') ?
+			word+1 : word, FALSE);
+		break;
+	case WORD_CHANNEL:
+		if (*word == '@' || *word == '+' || *word=='^' || *word=='%' || *word=='*')
+			word++;
+		menu_chanmenu (sess, even, word);
+		break;
+	case WORD_EMAIL:
+		{
+			char *newword = malloc (strlen (word) + 10);
+			if (*word == '~')
+				word++;
+			sprintf (newword, "mailto:%s", word);
+			menu_urlmenu (even, newword);
+			free (newword);
+		}
+		break;
+	case WORD_DIALOG:
+		menu_nickmenu (sess, even, sess->channel, FALSE);
+		break;
+	}
+}
+
+void
+mg_update_xtext (GtkWidget *wid)
+{
+	GtkXText *xtext = GTK_XTEXT (wid);
+
+	gtk_xtext_set_palette (xtext, colors);
+	gtk_xtext_set_max_lines (xtext, prefs.max_lines);
+	gtk_xtext_set_tint (xtext, prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (xtext, channelwin_pix, prefs.transparent);
+	gtk_xtext_set_wordwrap (xtext, prefs.wordwrap);
+	gtk_xtext_set_show_marker (xtext, prefs.show_marker);
+	gtk_xtext_set_show_separator (xtext, prefs.indent_nicks ? prefs.show_separator : 0);
+	gtk_xtext_set_indent (xtext, prefs.indent_nicks);
+	if (!gtk_xtext_set_font (xtext, prefs.font_normal))
+	{
+		fe_message ("Failed to open any font. I'm out of here!", FE_MSG_WAIT | FE_MSG_ERROR);
+		exit (1);
+	}
+
+	gtk_xtext_refresh (xtext, FALSE);
+}
+
+/* handle errors reported by xtext */
+
+static void
+mg_xtext_error (int type)
+{
+	switch (type)
+	{
+	case 0:
+		fe_message (_("Unable to set transparent background!\n\n"
+						"You may be using a non-compliant window\n"
+						"manager that is not currently supported.\n"), FE_MSG_WARN);
+		prefs.transparent = 0;
+		/* no others exist yet */
+	}
+}
+
+static void
+mg_create_textarea (session *sess, GtkWidget *box)
+{
+	GtkWidget *inbox, *vbox, *frame;
+	GtkXText *xtext;
+	session_gui *gui = sess->gui;
+	static const GtkTargetEntry dnd_targets[] =
+	{
+		{"text/uri-list", 0, 1}
+	};
+	static const GtkTargetEntry dnd_dest_targets[] =
+	{
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 },
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (box), vbox);
+
+	inbox = gtk_hbox_new (FALSE, SCROLLBAR_SPACING);
+	gtk_container_add (GTK_CONTAINER (vbox), inbox);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (inbox), frame);
+
+	gui->xtext = gtk_xtext_new (colors, TRUE);
+	xtext = GTK_XTEXT (gui->xtext);
+	gtk_xtext_set_max_indent (xtext, prefs.max_auto_indent);
+	gtk_xtext_set_thin_separator (xtext, prefs.thin_separator);
+	gtk_xtext_set_error_function (xtext, mg_xtext_error);
+	gtk_xtext_set_urlcheck_function (xtext, mg_word_check);
+	gtk_xtext_set_max_lines (xtext, prefs.max_lines);
+	gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (xtext));
+	mg_update_xtext (GTK_WIDGET (xtext));
+
+	g_signal_connect (G_OBJECT (xtext), "word_click",
+							G_CALLBACK (mg_word_clicked), NULL);
+
+	gui->vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (xtext)->adj);
+	gtk_box_pack_start (GTK_BOX (inbox), gui->vscrollbar, FALSE, TRUE, 0);
+#ifndef WIN32	/* needs more work */
+	gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+
+	gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received",
+							G_CALLBACK (mg_dialog_dnd_drop), NULL);
+#endif
+}
+
+static GtkWidget *
+mg_create_infoframe (GtkWidget *box)
+{
+	GtkWidget *frame, *label, *hbox;
+
+	frame = gtk_frame_new (0);
+	gtk_frame_set_shadow_type ((GtkFrame*)frame, GTK_SHADOW_OUT);
+	gtk_container_add (GTK_CONTAINER (box), frame);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (frame), hbox);
+
+	label = gtk_label_new (NULL);
+	gtk_container_add (GTK_CONTAINER (hbox), label);
+
+	return label;
+}
+
+static void
+mg_create_meters (session_gui *gui, GtkWidget *parent_box)
+{
+	GtkWidget *infbox, *wid, *box;
+
+	gui->meter_box = infbox = box = gtk_vbox_new (0, 1);
+	gtk_box_pack_start (GTK_BOX (parent_box), box, 0, 0, 0);
+
+	if ((prefs.lagometer & 2) || (prefs.throttlemeter & 2))
+	{
+		infbox = gtk_hbox_new (0, 0);
+		gtk_box_pack_start (GTK_BOX (box), infbox, 0, 0, 0);
+	}
+
+	if (prefs.lagometer & 1)
+	{
+		gui->lagometer = wid = gtk_progress_bar_new ();
+#ifdef WIN32
+		gtk_widget_set_size_request (wid, 1, 10);
+#else
+		gtk_widget_set_size_request (wid, 1, 8);
+#endif
+
+		wid = gtk_event_box_new ();
+		gtk_container_add (GTK_CONTAINER (wid), gui->lagometer);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+	if (prefs.lagometer & 2)
+	{
+		gui->laginfo = wid = mg_create_infoframe (infbox);
+		gtk_label_set_text ((GtkLabel *) wid, "Lag");
+	}
+
+	if (prefs.throttlemeter & 1)
+	{
+		gui->throttlemeter = wid = gtk_progress_bar_new ();
+#ifdef WIN32
+		gtk_widget_set_size_request (wid, 1, 10);
+#else
+		gtk_widget_set_size_request (wid, 1, 8);
+#endif
+
+		wid = gtk_event_box_new ();
+		gtk_container_add (GTK_CONTAINER (wid), gui->throttlemeter);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+	if (prefs.throttlemeter & 2)
+	{
+		gui->throttleinfo = wid = mg_create_infoframe (infbox);
+		gtk_label_set_text ((GtkLabel *) wid, "Throttle");
+	}
+}
+
+void
+mg_update_meters (session_gui *gui)
+{
+	gtk_widget_destroy (gui->meter_box);
+	gui->lagometer = NULL;
+	gui->laginfo = NULL;
+	gui->throttlemeter = NULL;
+	gui->throttleinfo = NULL;
+
+	mg_create_meters (gui, gui->button_box_parent);
+	gtk_widget_show_all (gui->meter_box);
+}
+
+static void
+mg_create_userlist (session_gui *gui, GtkWidget *box)
+{
+	GtkWidget *frame, *ulist, *vbox;
+
+	vbox = gtk_vbox_new (0, 1);
+	gtk_container_add (GTK_CONTAINER (box), vbox);
+
+	frame = gtk_frame_new (NULL);
+	if (!(prefs.gui_tweaks & 1))
+		gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING);
+
+	gui->namelistinfo = gtk_label_new (NULL);
+	gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo);
+
+	gui->user_tree = ulist = userlist_create (vbox);
+
+	if (prefs.style_namelistgad)
+	{
+		gtk_widget_set_style (ulist, input_style);
+		gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]);
+	}
+
+	mg_create_meters (gui, vbox);
+
+	gui->button_box_parent = vbox;
+	gui->button_box = mg_create_userlistbuttons (vbox);
+}
+
+static void
+mg_leftpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui)
+{
+	prefs.gui_pane_left_size = gtk_paned_get_position (pane);
+}
+
+static void
+mg_rightpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui)
+{
+	int handle_size;
+
+/*	if (pane->child1 == NULL || (!GTK_WIDGET_VISIBLE (pane->child1)))
+		return;
+	if (pane->child2 == NULL || (!GTK_WIDGET_VISIBLE (pane->child2)))
+		return;*/
+
+	gtk_widget_style_get (GTK_WIDGET (pane), "handle-size", &handle_size, NULL);
+	/* record the position from the RIGHT side */
+	prefs.gui_pane_right_size = GTK_WIDGET (pane)->allocation.width - gtk_paned_get_position (pane) - handle_size;
+}
+
+static gboolean
+mg_add_pane_signals (session_gui *gui)
+{
+	g_signal_connect (G_OBJECT (gui->hpane_right), "notify::position",
+							G_CALLBACK (mg_rightpane_cb), gui);
+	g_signal_connect (G_OBJECT (gui->hpane_left), "notify::position",
+							G_CALLBACK (mg_leftpane_cb), gui);
+	return FALSE;
+}
+
+static void
+mg_create_center (session *sess, session_gui *gui, GtkWidget *box)
+{
+	GtkWidget *vbox, *hbox, *book;
+
+	/* sep between top and bottom of left side */
+	gui->vpane_left = gtk_vpaned_new ();
+
+	/* sep between top and bottom of right side */
+	gui->vpane_right = gtk_vpaned_new ();
+
+	/* sep between left and xtext */
+	gui->hpane_left = gtk_hpaned_new ();
+	gtk_paned_set_position (GTK_PANED (gui->hpane_left), prefs.gui_pane_left_size);
+
+	/* sep between xtext and right side */
+	gui->hpane_right = gtk_hpaned_new ();
+
+	if (prefs.gui_tweaks & 4)
+	{
+		gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE);
+		gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE);
+	}
+	else
+	{
+		gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE);
+		gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE);
+	}
+	gtk_paned_pack2 (GTK_PANED (gui->hpane_right), gui->vpane_right, FALSE, TRUE);
+
+	gtk_container_add (GTK_CONTAINER (box), gui->hpane_left);
+
+	gui->note_book = book = gtk_notebook_new ();
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE);
+	gtk_paned_pack1 (GTK_PANED (gui->hpane_right), book, TRUE, TRUE);
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_paned_pack1 (GTK_PANED (gui->vpane_right), hbox, FALSE, TRUE);
+	mg_create_userlist (gui, hbox);
+
+	gui->user_box = hbox;
+
+	vbox = gtk_vbox_new (FALSE, 3);
+	gtk_notebook_append_page (GTK_NOTEBOOK (book), vbox, NULL);
+	mg_create_topicbar (sess, vbox);
+	mg_create_textarea (sess, vbox);
+	mg_create_entry (sess, vbox);
+
+	g_idle_add ((GSourceFunc)mg_add_pane_signals, gui);
+}
+
+static void
+mg_change_nick (int cancel, char *text, gpointer userdata)
+{
+	char buf[256];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "nick %s", text);
+		handle_command (current_sess, buf, FALSE);
+	}
+}
+
+static void
+mg_nickclick_cb (GtkWidget *button, gpointer userdata)
+{
+	fe_get_str (_("Enter new nickname:"), current_sess->server->nick,
+					mg_change_nick, NULL);
+}
+
+/* make sure chanview and userlist positions are sane */
+
+static void
+mg_sanitize_positions (int *cv, int *ul)
+{
+	if (prefs.tab_layout == 2)
+	{
+		/* treeview can't be on TOP or BOTTOM */
+		if (*cv == POS_TOP || *cv == POS_BOTTOM)
+			*cv = POS_TOPLEFT;
+	}
+
+	/* userlist can't be on TOP or BOTTOM */
+	if (*ul == POS_TOP || *ul == POS_BOTTOM)
+		*ul = POS_TOPRIGHT;
+
+	/* can't have both in the same place */
+	if (*cv == *ul)
+	{
+		*cv = POS_TOPRIGHT;
+		if (*ul == POS_TOPRIGHT)
+			*cv = POS_BOTTOMRIGHT;
+	}
+}
+
+static void
+mg_place_userlist_and_chanview_real (session_gui *gui, GtkWidget *userlist, GtkWidget *chanview)
+{
+	int unref_userlist = FALSE;
+	int unref_chanview = FALSE;
+
+	/* first, remove userlist/treeview from their containers */
+	if (userlist && userlist->parent)
+	{
+		g_object_ref (userlist);
+		gtk_container_remove (GTK_CONTAINER (userlist->parent), userlist);
+		unref_userlist = TRUE;
+	}
+
+	if (chanview && chanview->parent)
+	{
+		g_object_ref (chanview);
+		gtk_container_remove (GTK_CONTAINER (chanview->parent), chanview);
+		unref_chanview = TRUE;
+	}
+
+	if (chanview)
+	{
+		/* incase the previous pos was POS_HIDDEN */
+		gtk_widget_show (chanview);
+
+		gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, 0);
+		gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 2);
+
+		/* then place them back in their new positions */
+		switch (prefs.tab_pos)
+		{
+		case POS_TOPLEFT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE);
+			break;
+		case POS_BOTTOMLEFT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE);
+			break;
+		case POS_TOPRIGHT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE);
+			break;
+		case POS_BOTTOMRIGHT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE);
+			break;
+		case POS_TOP:
+			gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, GUI_SPACING-1);
+			gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+									1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+			break;
+		case POS_HIDDEN:
+			gtk_widget_hide (chanview);
+			/* always attach it to something to avoid ref_count=0 */
+			if (prefs.gui_ulist_pos == POS_TOP)
+				gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+										1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+
+			else
+				gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+										1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+			break;
+		default:/* POS_BOTTOM */
+			gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 3);
+			gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+									1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+		}
+	}
+
+	if (userlist)
+	{
+		switch (prefs.gui_ulist_pos)
+		{
+		case POS_TOPLEFT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE);
+			break;
+		case POS_BOTTOMLEFT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE);
+			break;
+		case POS_BOTTOMRIGHT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE);
+			break;
+		/*case POS_HIDDEN:
+			break;*/	/* Hide using the VIEW menu instead */
+		default:/* POS_TOPRIGHT */
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE);
+		}
+	}
+
+	if (unref_chanview)
+		g_object_unref (chanview);
+	if (unref_userlist)
+		g_object_unref (userlist);
+
+	mg_hide_empty_boxes (gui);
+}
+
+static void
+mg_place_userlist_and_chanview (session_gui *gui)
+{
+	GtkOrientation orientation;
+	GtkWidget *chanviewbox = NULL;
+	int pos;
+
+	mg_sanitize_positions (&prefs.tab_pos, &prefs.gui_ulist_pos);
+
+	if (gui->chanview)
+	{
+		pos = prefs.tab_pos;
+
+		orientation = chanview_get_orientation (gui->chanview);
+		if ((pos == POS_BOTTOM || pos == POS_TOP) && orientation == GTK_ORIENTATION_VERTICAL)
+			chanview_set_orientation (gui->chanview, FALSE);
+		else if ((pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT) && orientation == GTK_ORIENTATION_HORIZONTAL)
+			chanview_set_orientation (gui->chanview, TRUE);
+		chanviewbox = chanview_get_box (gui->chanview);
+	}
+
+	mg_place_userlist_and_chanview_real (gui, gui->user_box, chanviewbox);
+}
+
+void
+mg_change_layout (int type)
+{
+	if (mg_gui)
+	{
+		/* put tabs at the bottom */
+		if (type == 0 && prefs.tab_pos != POS_BOTTOM && prefs.tab_pos != POS_TOP)
+			prefs.tab_pos = POS_BOTTOM;
+
+		mg_place_userlist_and_chanview (mg_gui);
+		chanview_set_impl (mg_gui->chanview, type);
+	}
+}
+
+static void
+mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu)
+{
+	mg_create_color_menu (menu, NULL);
+}
+
+static void
+mg_create_entry (session *sess, GtkWidget *box)
+{
+	GtkWidget *sw, *hbox, *but, *entry;
+	session_gui *gui = sess->gui;
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	gui->nick_box = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), gui->nick_box, 0, 0, 0);
+
+	gui->nick_label = but = gtk_button_new_with_label (sess->server->nick);
+	gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
+	GTK_WIDGET_UNSET_FLAGS (but, GTK_CAN_FOCUS);
+	gtk_box_pack_end (GTK_BOX (gui->nick_box), but, 0, 0, 0);
+	g_signal_connect (G_OBJECT (but), "clicked",
+							G_CALLBACK (mg_nickclick_cb), NULL);
+
+#ifdef USE_GTKSPELL
+	gui->input_box = entry = gtk_text_view_new ();
+	gtk_widget_set_size_request (entry, 0, 1);
+	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (entry), GTK_WRAP_NONE);
+	gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (entry), FALSE);
+	if (prefs.gui_input_spell)
+		gtkspell_new_attach (GTK_TEXT_VIEW (entry), NULL, NULL);
+
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+													 GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+												GTK_POLICY_NEVER,
+												GTK_POLICY_NEVER);
+	gtk_container_add (GTK_CONTAINER (sw), entry);
+	gtk_container_add (GTK_CONTAINER (hbox), sw);
+#else
+#ifdef USE_LIBSEXY
+	gui->input_box = entry = sexy_spell_entry_new ();
+	sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.gui_input_spell);
+#else
+	gui->input_box = entry = gtk_entry_new ();
+#endif
+	gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 2048);
+	g_signal_connect (G_OBJECT (entry), "activate",
+							G_CALLBACK (mg_inputbox_cb), gui);
+	gtk_container_add (GTK_CONTAINER (hbox), entry);
+#endif
+
+	gtk_widget_set_name (entry, "xchat-inputbox");
+	g_signal_connect (G_OBJECT (entry), "key_press_event",
+							G_CALLBACK (key_handle_key_press), NULL);
+	g_signal_connect (G_OBJECT (entry), "focus_in_event",
+							G_CALLBACK (mg_inputbox_focus), gui);
+	g_signal_connect (G_OBJECT (entry), "populate_popup",
+							G_CALLBACK (mg_inputbox_rightclick), NULL);
+	gtk_widget_grab_focus (entry);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (entry);
+}
+
+static void
+mg_switch_tab_cb (chanview *cv, chan *ch, int tag, gpointer ud)
+{
+	chan *old;
+	session *sess = ud;
+
+	old = active_tab;
+	active_tab = ch;
+
+	if (tag == TAG_IRC)
+	{
+		if (active_tab != old)
+		{
+			if (old && current_tab)
+				mg_unpopulate (current_tab);
+			mg_populate (sess);
+		}
+	} else if (old != active_tab)
+	{
+		/* userdata for non-irc tabs is actually the GtkBox */
+		mg_show_generic_tab (ud);
+		if (!mg_is_userlist_and_tree_combined ())
+			mg_userlist_showhide (current_sess, FALSE);	/* hide */
+	}
+}
+
+/* compare two tabs (for tab sorting function) */
+
+static int
+mg_tabs_compare (session *a, session *b)
+{
+	/* server tabs always go first */
+	if (a->type == SESS_SERVER)
+		return -1;
+
+	/* then channels */
+	if (a->type == SESS_CHANNEL && b->type != SESS_CHANNEL)
+		return -1;
+	if (a->type != SESS_CHANNEL && b->type == SESS_CHANNEL)
+		return 1;
+
+	return strcasecmp (a->channel, b->channel);
+}
+
+static void
+mg_create_tabs (session_gui *gui)
+{
+	gboolean use_icons = FALSE;
+
+	/* if any one of these PNGs exist, the chanview will create
+	 * the extra column for icons. */
+	if (pix_channel || pix_dialog || pix_server || pix_util)
+		use_icons = TRUE;
+
+	gui->chanview = chanview_new (prefs.tab_layout, prefs.truncchans,
+											prefs.tab_sort, use_icons,
+											prefs.style_namelistgad ? input_style : NULL);
+	chanview_set_callbacks (gui->chanview, mg_switch_tab_cb, mg_xbutton_cb,
+									mg_tab_contextmenu_cb, (void *)mg_tabs_compare);
+	mg_place_userlist_and_chanview (gui);
+}
+
+static gboolean
+mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata)
+{
+	current_sess = current_tab;
+	if (current_sess)
+	{
+		gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext));
+		plugin_emit_dummy_print (current_sess, "Focus Window");
+	}
+#ifndef WIN32
+#ifdef USE_XLIB
+	unflash_window (GTK_WIDGET (win));
+#endif
+#endif
+	return FALSE;
+}
+
+static gboolean
+mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess)
+{
+	current_sess = sess;
+	if (!sess->server->server_session)
+		sess->server->server_session = sess;
+	gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext));
+#ifndef WIN32
+#ifdef USE_XLIB
+	unflash_window (GTK_WIDGET (win));
+#endif
+#endif
+	plugin_emit_dummy_print (sess, "Focus Window");
+	return FALSE;
+}
+
+static void
+mg_create_menu (session_gui *gui, GtkWidget *table, int away_state)
+{
+	GtkAccelGroup *accel_group;
+
+	accel_group = gtk_accel_group_new ();
+	gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (table)),
+										 accel_group);
+	g_object_unref (accel_group);
+
+	gui->menu = menu_create_main (accel_group, TRUE, away_state, !gui->is_tab,
+											gui->menu_item);
+	gtk_table_attach (GTK_TABLE (table), gui->menu, 0, 3, 0, 1,
+						   GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static void
+mg_create_irctab (session *sess, GtkWidget *table)
+{
+	GtkWidget *vbox;
+	session_gui *gui = sess->gui;
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 2, 3,
+						   GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+	mg_create_center (sess, gui, vbox);
+}
+
+static void
+mg_create_topwindow (session *sess)
+{
+	GtkWidget *win;
+	GtkWidget *table;
+
+	if (sess->type == SESS_DIALOG)
+		win = gtkutil_window_new ("XChat", NULL,
+										  prefs.dialog_width, prefs.dialog_height, 0);
+	else
+		win = gtkutil_window_new ("XChat", NULL,
+										  prefs.mainwindow_width,
+										  prefs.mainwindow_height, 0);
+	sess->gui->window = win;
+	gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER);
+
+	g_signal_connect (G_OBJECT (win), "focus_in_event",
+							G_CALLBACK (mg_topwin_focus_cb), sess);
+	g_signal_connect (G_OBJECT (win), "destroy",
+							G_CALLBACK (mg_topdestroy_cb), sess);
+	g_signal_connect (G_OBJECT (win), "configure_event",
+							G_CALLBACK (mg_configure_cb), sess);
+
+	palette_alloc (win);
+
+	table = gtk_table_new (4, 3, FALSE);
+	/* spacing under the menubar */
+	gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING);
+	/* left and right borders */
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1);
+	gtk_container_add (GTK_CONTAINER (win), table);
+
+	mg_create_irctab (sess, table);
+	mg_create_menu (sess->gui, table, sess->server->is_away);
+
+	if (sess->res->buffer == NULL)
+	{
+		sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext));
+		gtk_xtext_buffer_show (GTK_XTEXT (sess->gui->xtext), sess->res->buffer, TRUE);
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		sess->res->user_model = userlist_create_model ();
+	}
+
+	userlist_show (sess);
+
+	gtk_widget_show_all (table);
+
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+
+	if (!prefs.topicbar)
+		gtk_widget_hide (sess->gui->topic_bar);
+
+	if (!prefs.userlistbuttons)
+		gtk_widget_hide (sess->gui->button_box);
+
+	if (prefs.gui_tweaks & 2)
+		gtk_widget_hide (sess->gui->nick_box);
+
+	mg_decide_userlist (sess, FALSE);
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* hide the chan-mode buttons */
+		gtk_widget_hide (sess->gui->topicbutton_box);
+	} else
+	{
+		gtk_widget_hide (sess->gui->dialogbutton_box);
+
+		if (!prefs.chanmodebuttons)
+			gtk_widget_hide (sess->gui->topicbutton_box);
+	}
+
+	mg_place_userlist_and_chanview (sess->gui);
+
+	gtk_widget_show (win);
+}
+
+static gboolean
+mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+	GSList *list;
+	session *sess;
+
+	if ((prefs.gui_tray_flags & 1) && tray_toggle_visibility (FALSE))
+		return TRUE;
+
+	/* check for remaining toplevel windows */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (!sess->gui->is_tab)
+			return FALSE;
+		list = list->next;
+	}
+
+	mg_open_quit_dialog (TRUE);
+	return TRUE;
+}
+
+static void
+mg_create_tabwindow (session *sess)
+{
+	GtkWidget *win;
+	GtkWidget *table;
+
+	win = gtkutil_window_new ("XChat", NULL, prefs.mainwindow_width,
+									  prefs.mainwindow_height, 0);
+	sess->gui->window = win;
+	gtk_window_move (GTK_WINDOW (win), prefs.mainwindow_left,
+						  prefs.mainwindow_top);
+	if (prefs.gui_win_state)
+		gtk_window_maximize (GTK_WINDOW (win));
+	gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER);
+
+	g_signal_connect (G_OBJECT (win), "delete_event",
+						   G_CALLBACK (mg_tabwindow_de_cb), 0);
+	g_signal_connect (G_OBJECT (win), "destroy",
+						   G_CALLBACK (mg_tabwindow_kill_cb), 0);
+	g_signal_connect (G_OBJECT (win), "focus_in_event",
+							G_CALLBACK (mg_tabwin_focus_cb), NULL);
+	g_signal_connect (G_OBJECT (win), "configure_event",
+							G_CALLBACK (mg_configure_cb), NULL);
+	g_signal_connect (G_OBJECT (win), "window_state_event",
+							G_CALLBACK (mg_windowstate_cb), NULL);
+
+	palette_alloc (win);
+
+	sess->gui->main_table = table = gtk_table_new (4, 3, FALSE);
+	/* spacing under the menubar */
+	gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING);
+	/* left and right borders */
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1);
+	gtk_container_add (GTK_CONTAINER (win), table);
+
+	mg_create_irctab (sess, table);
+	mg_create_tabs (sess->gui);
+	mg_create_menu (sess->gui, table, sess->server->is_away);
+
+	mg_focus (sess);
+
+	gtk_widget_show_all (table);
+
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+
+	mg_decide_userlist (sess, FALSE);
+
+	if (!prefs.topicbar)
+		gtk_widget_hide (sess->gui->topic_bar);
+
+	if (!prefs.chanmodebuttons)
+		gtk_widget_hide (sess->gui->topicbutton_box);
+
+	if (!prefs.userlistbuttons)
+		gtk_widget_hide (sess->gui->button_box);
+
+	if (prefs.gui_tweaks & 2)
+		gtk_widget_hide (sess->gui->nick_box);
+
+	mg_place_userlist_and_chanview (sess->gui);
+
+	gtk_widget_show (win);
+}
+
+void
+mg_apply_setup (void)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int done_main = FALSE;
+
+	mg_create_tab_colors ();
+
+	while (list)
+	{
+		sess = list->data;
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
+		if (!sess->gui->is_tab || !done_main)
+			mg_place_userlist_and_chanview (sess->gui);
+		if (sess->gui->is_tab)
+			done_main = TRUE;
+		list = list->next;
+	}
+}
+
+static chan *
+mg_add_generic_tab (char *name, char *title, void *family, GtkWidget *box)
+{
+	chan *ch;
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (mg_gui->note_book), box, NULL);
+	gtk_widget_show (box);
+
+	ch = chanview_add (mg_gui->chanview, name, NULL, box, TRUE, TAG_UTIL, pix_util);
+	chan_set_color (ch, plain_list);
+	/* FIXME: memory leak */
+	g_object_set_data (G_OBJECT (box), "title", strdup (title));
+	g_object_set_data (G_OBJECT (box), "ch", ch);
+
+	if (prefs.newtabstofront)
+		chan_focus (ch);
+
+	return ch;
+}
+
+void
+fe_buttons_update (session *sess)
+{
+	session_gui *gui = sess->gui;
+
+	gtk_widget_destroy (gui->button_box);
+	gui->button_box = mg_create_userlistbuttons (gui->button_box_parent);
+
+	if (prefs.userlistbuttons)
+		gtk_widget_show (sess->gui->button_box);
+	else
+		gtk_widget_hide (sess->gui->button_box);
+}
+
+void
+fe_clear_channel (session *sess)
+{
+	char tbuf[CHANLEN+6];
+	session_gui *gui = sess->gui;
+
+	if (sess->gui->is_tab)
+	{
+		if (sess->waitchannel[0])
+		{
+			if (prefs.truncchans > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.truncchans)
+			{
+				/* truncate long channel names */
+				tbuf[0] = '(';
+				strcpy (tbuf + 1, sess->waitchannel);
+				g_utf8_offset_to_pointer(tbuf, prefs.truncchans)[0] = 0;
+				strcat (tbuf, "..)");
+			} else
+			{
+				sprintf (tbuf, "(%s)", sess->waitchannel);
+			}
+		}
+		else
+			strcpy (tbuf, _("<none>"));
+		chan_rename (sess->res->tab, tbuf, prefs.truncchans);
+	}
+
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		gtk_entry_set_text (GTK_ENTRY (gui->topic_entry), "");
+
+		if (gui->op_xpm)
+		{
+			gtk_widget_destroy (gui->op_xpm);
+			gui->op_xpm = 0;
+		}
+	} else
+	{
+		if (sess->res->topic_text)
+		{
+			free (sess->res->topic_text);
+			sess->res->topic_text = NULL;
+		}
+	}
+}
+
+void
+fe_set_nonchannel (session *sess, int state)
+{
+}
+
+void
+fe_dlgbuttons_update (session *sess)
+{
+	GtkWidget *box;
+	session_gui *gui = sess->gui;
+
+	gtk_widget_destroy (gui->dialogbutton_box);
+
+	gui->dialogbutton_box = box = gtk_hbox_new (0, 0);
+	gtk_box_pack_start (GTK_BOX (gui->topic_bar), box, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (gui->topic_bar), box, 3);
+	mg_create_dialogbuttons (box);
+
+	gtk_widget_show_all (box);
+
+	if (current_tab && current_tab->type != SESS_DIALOG)
+		gtk_widget_hide (current_tab->gui->dialogbutton_box);
+}
+
+void
+fe_update_mode_buttons (session *sess, char mode, char sign)
+{
+	int state, i;
+
+	if (sign == '+')
+		state = TRUE;
+	else
+		state = FALSE;
+
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+	{
+		if (chan_flags[i] == mode)
+		{
+			if (!sess->gui->is_tab || sess == current_tab)
+			{
+				ignore_chanmode = TRUE;
+				if (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i])->active != state)
+					gtk_toggle_button_set_active (
+							GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i]), state);
+				ignore_chanmode = FALSE;
+			} else
+			{
+				sess->res->flag_wid_state[i] = state;
+			}
+			return;
+		}
+	}
+}
+
+void
+fe_set_nick (server *serv, char *newnick)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (current_tab == sess || !sess->gui->is_tab)
+				gtk_button_set_label (GTK_BUTTON (sess->gui->nick_label), newnick);
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_away (server *serv)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (!sess->gui->is_tab || sess == current_tab)
+			{
+				GTK_CHECK_MENU_ITEM (sess->gui->menu_item[MENU_ID_AWAY])->active = serv->is_away;
+				/* gray out my nickname */
+				mg_set_myself_away (sess->gui, serv->is_away);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_channel (session *sess)
+{
+	if (sess->res->tab != NULL)
+		chan_rename (sess->res->tab, sess->channel, prefs.truncchans);
+}
+
+void
+mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
+{
+	int first_run = FALSE;
+	session_gui *gui;
+	struct User *user = NULL;
+
+	if (!res)
+	{
+		res = malloc (sizeof (restore_gui));
+		memset (res, 0, sizeof (restore_gui));
+	}
+
+	sess->res = res;
+
+	if (!sess->server->front_session)
+		sess->server->front_session = sess;
+
+	if (!is_channel (sess->server, sess->channel))
+		user = userlist_find_global (sess->server, sess->channel);
+
+	if (!tab)
+	{
+		gui = malloc (sizeof (session_gui));
+		memset (gui, 0, sizeof (session_gui));
+		gui->is_tab = FALSE;
+		sess->gui = gui;
+		mg_create_topwindow (sess);
+		fe_set_title (sess);
+		if (user && user->hostname)
+			set_topic (sess, user->hostname, user->hostname);
+		return;
+	}
+
+	if (mg_gui == NULL)
+	{
+		first_run = TRUE;
+		gui = &static_mg_gui;
+		memset (gui, 0, sizeof (session_gui));
+		gui->is_tab = TRUE;
+		sess->gui = gui;
+		mg_create_tabwindow (sess);
+		mg_gui = gui;
+		parent_window = gui->window;
+	} else
+	{
+		sess->gui = gui = mg_gui;
+		gui->is_tab = TRUE;
+	}
+
+	if (user && user->hostname)
+		set_topic (sess, user->hostname, user->hostname);
+
+	mg_add_chan (sess);
+
+	if (first_run || (prefs.newtabstofront == FOCUS_NEW_ONLY_ASKED && focus)
+			|| prefs.newtabstofront == FOCUS_NEW_ALL )
+		chan_focus (res->tab);
+}
+
+GtkWidget *
+mg_create_generic_tab (char *name, char *title, int force_toplevel,
+							  int link_buttons,
+							  void *close_callback, void *userdata,
+							  int width, int height, GtkWidget **vbox_ret,
+							  void *family)
+{
+	GtkWidget *vbox, *win;
+
+	if (prefs.tab_pos == POS_HIDDEN && prefs.windows_as_tabs)
+		prefs.windows_as_tabs = 0;
+
+	if (force_toplevel || !prefs.windows_as_tabs)
+	{
+		win = gtkutil_window_new (title, name, width, height, 3);
+		vbox = gtk_vbox_new (0, 0);
+		*vbox_ret = vbox;
+		gtk_container_add (GTK_CONTAINER (win), vbox);
+		gtk_widget_show (vbox);
+		if (close_callback)
+			g_signal_connect (G_OBJECT (win), "destroy",
+									G_CALLBACK (close_callback), userdata);
+		return win;
+	}
+
+	vbox = gtk_vbox_new (0, 2);
+	g_object_set_data (G_OBJECT (vbox), "w", GINT_TO_POINTER (width));
+	g_object_set_data (G_OBJECT (vbox), "h", GINT_TO_POINTER (height));
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+	*vbox_ret = vbox;
+
+	if (close_callback)
+		g_signal_connect (G_OBJECT (vbox), "destroy",
+								G_CALLBACK (close_callback), userdata);
+
+	mg_add_generic_tab (name, title, family, vbox);
+
+/*	if (link_buttons)
+	{
+		hbox = gtk_hbox_new (FALSE, 0);
+		gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0);
+		mg_create_link_buttons (hbox, ch);
+		gtk_widget_show (hbox);
+	}*/
+
+	return vbox;
+}
+
+void
+mg_move_tab (session *sess, int delta)
+{
+	if (sess->gui->is_tab)
+		chan_move (sess->res->tab, delta);
+}
+
+void
+mg_move_tab_family (session *sess, int delta)
+{
+	if (sess->gui->is_tab)
+		chan_move_family (sess->res->tab, delta);
+}
+
+void
+mg_set_title (GtkWidget *vbox, char *title) /* for non-irc tab/window only */
+{
+	char *old;
+
+	old = g_object_get_data (G_OBJECT (vbox), "title");
+	if (old)
+	{
+		g_object_set_data (G_OBJECT (vbox), "title", strdup (title));
+		free (old);
+	} else
+	{
+		gtk_window_set_title (GTK_WINDOW (vbox), title);
+	}
+}
+
+void
+fe_server_callback (server *serv)
+{
+	joind_close (serv);
+
+	if (serv->gui->chanlist_window)
+		mg_close_gen (NULL, serv->gui->chanlist_window);
+
+	if (serv->gui->rawlog_window)
+		mg_close_gen (NULL, serv->gui->rawlog_window);
+
+	free (serv->gui);
+}
+
+/* called when a session is being killed */
+
+void
+fe_session_callback (session *sess)
+{
+	if (sess->res->banlist_window)
+		mg_close_gen (NULL, sess->res->banlist_window);
+
+	if (sess->res->input_text)
+		free (sess->res->input_text);
+
+	if (sess->res->topic_text)
+		free (sess->res->topic_text);
+
+	if (sess->res->limit_text)
+		free (sess->res->limit_text);
+
+	if (sess->res->key_text)
+		free (sess->res->key_text);
+
+	if (sess->res->queue_text)
+		free (sess->res->queue_text);
+	if (sess->res->queue_tip)
+		free (sess->res->queue_tip);
+
+	if (sess->res->lag_text)
+		free (sess->res->lag_text);
+	if (sess->res->lag_tip)
+		free (sess->res->lag_tip);
+
+	if (sess->gui->bartag)
+		fe_timeout_remove (sess->gui->bartag);
+
+	if (sess->gui != &static_mg_gui)
+		free (sess->gui);
+	free (sess->res);
+}
+
+/* ===== DRAG AND DROP STUFF ===== */
+
+static gboolean
+is_child_of (GtkWidget *widget, GtkWidget *parent)
+{
+	while (widget)
+	{
+		if (widget->parent == parent)
+			return TRUE;
+		widget = widget->parent;
+	}
+	return FALSE;
+}
+
+static void
+mg_handle_drop (GtkWidget *widget, int y, int *pos, int *other_pos)
+{
+	int height;
+	session_gui *gui = current_sess->gui;
+
+	gdk_drawable_get_size (widget->window, NULL, &height);
+
+	if (y < height / 2)
+	{
+		if (is_child_of (widget, gui->vpane_left))
+			*pos = 1;	/* top left */
+		else
+			*pos = 3;	/* top right */
+	}
+	else
+	{
+		if (is_child_of (widget, gui->vpane_left))
+			*pos = 2;	/* bottom left */
+		else
+			*pos = 4;	/* bottom right */
+	}
+
+	/* both in the same pos? must move one */
+	if (*pos == *other_pos)
+	{
+		switch (*other_pos)
+		{
+		case 1:
+			*other_pos = 2;
+			break;
+		case 2:
+			*other_pos = 1;
+			break;
+		case 3:
+			*other_pos = 4;
+			break;
+		case 4:
+			*other_pos = 3;
+			break;
+		}
+	}
+
+	mg_place_userlist_and_chanview (gui);
+}
+
+static gboolean
+mg_is_gui_target (GdkDragContext *context)
+{
+	char *target_name;
+
+	if (!context || !context->targets || !context->targets->data)
+		return FALSE;
+
+	target_name = gdk_atom_name (context->targets->data);
+	if (target_name)
+	{
+		/* if it's not XCHAT_CHANVIEW or XCHAT_USERLIST */
+		/* we should ignore it. */
+		if (target_name[0] != 'X')
+		{
+			g_free (target_name);
+			return FALSE;
+		}
+		g_free (target_name);
+	}
+
+	return TRUE;
+}
+
+/* this begin callback just creates an nice of the source */
+
+gboolean
+mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata)
+{
+#ifndef WIN32	/* leaks GDI pool memory - don't use on win32 */
+	int width, height;
+	GdkColormap *cmap;
+	GdkPixbuf *pix, *pix2;
+
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	cmap = gtk_widget_get_colormap (widget);
+	gdk_drawable_get_size (widget->window, &width, &height);
+
+	pix = gdk_pixbuf_get_from_drawable (NULL, widget->window, cmap, 0, 0, 0, 0, width, height);
+	pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER);
+	g_object_unref (pix);
+
+	gtk_drag_set_icon_pixbuf (context, pix2, 0, 0);
+	g_object_set_data (G_OBJECT (widget), "ico", pix2);
+#endif
+
+	return TRUE;
+}
+
+void
+mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata)
+{
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return;
+
+#ifndef WIN32
+	g_object_unref (g_object_get_data (G_OBJECT (widget), "ico"));
+#endif
+}
+
+/* drop complete */
+
+gboolean
+mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data)
+{
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	switch (context->action)
+	{
+	case GDK_ACTION_MOVE:
+		/* from userlist */
+		mg_handle_drop (widget, y, &prefs.gui_ulist_pos, &prefs.tab_pos);
+		break;
+	case GDK_ACTION_COPY:
+		/* from tree - we use GDK_ACTION_COPY for the tree */
+		mg_handle_drop (widget, y, &prefs.tab_pos, &prefs.gui_ulist_pos);
+		break;
+	default:
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* draw highlight rectangle in the destination */
+
+gboolean
+mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar)
+{
+	GdkGC *gc;
+	GdkColor col;
+	GdkGCValues val;
+	int half, width, height;
+	int ox, oy;
+	GtkPaned *paned;
+	GdkDrawable *draw;
+
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	if (scbar)	/* scrollbar */
+	{
+		ox = widget->allocation.x;
+		oy = widget->allocation.y;
+		width = widget->allocation.width;
+		height = widget->allocation.height;
+		draw = widget->window;
+	}
+	else
+	{
+		ox = oy = 0;
+		gdk_drawable_get_size (widget->window, &width, &height);
+		draw = widget->window;
+	}
+
+	val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+	val.graphics_exposures = 0;
+	val.function = GDK_XOR;
+
+	gc = gdk_gc_new_with_values (widget->window, &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION);
+	col.red = rand() % 0xffff;
+	col.green = rand() % 0xffff;
+	col.blue = rand() % 0xffff;
+	gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE);
+	gdk_gc_set_foreground (gc, &col);
+
+	half = height / 2;
+
+#if 0
+	/* are both tree/userlist on the same side? */
+	paned = (GtkPaned *)widget->parent->parent;
+	if (paned->child1 != NULL && paned->child2 != NULL)
+	{
+		gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4);
+		gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2);
+		g_object_unref (gc);
+		return TRUE;
+	}
+#endif
+
+	if (y < half)
+	{
+		gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4);
+		gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2);
+		gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half);
+	}
+	else
+	{
+		gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2);
+		gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4);
+		gtk_widget_queue_draw_area (widget, ox, oy, width, half);
+	}
+
+	g_object_unref (gc);
+
+	return TRUE;
+}
diff --git a/src/fe-gtk/maingui.h b/src/fe-gtk/maingui.h
new file mode 100644
index 00000000..bc9aaefd
--- /dev/null
+++ b/src/fe-gtk/maingui.h
@@ -0,0 +1,33 @@
+extern GtkStyle *input_style;
+extern GtkWidget *parent_window;
+
+void mg_changui_new (session *sess, restore_gui *res, int tab, int focus);
+void mg_update_xtext (GtkWidget *wid);
+void mg_open_quit_dialog (gboolean minimize_button);
+void mg_switch_page (int relative, int num);
+void mg_move_tab (session *, int delta);
+void mg_move_tab_family (session *, int delta);
+void mg_bring_tofront (GtkWidget *vbox);
+void mg_bring_tofront_sess (session *sess);
+void mg_decide_userlist (session *sess, gboolean switch_to_current);
+void mg_set_topic_tip (session *sess);
+GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, int link_buttons, void *close_callback, void *userdata, int width, int height, GtkWidget **vbox_ret, void *family);
+void mg_set_title (GtkWidget *button, char *title);
+void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
+void mg_apply_setup (void);
+void mg_close_sess (session *);
+void mg_tab_close (session *sess);
+void mg_detach (session *sess, int mode);
+void mg_progressbar_create (session_gui *gui);
+void mg_progressbar_destroy (session_gui *gui);
+void mg_dnd_drop_file (session *sess, char *target, char *uri);
+void mg_change_layout (int type);
+void mg_update_meters (session_gui *);
+void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
+void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
+GtkWidget *mg_submenu (GtkWidget *menu, char *text);
+/* DND */
+gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata);
+void mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata);
+gboolean mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data);
+gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); 
diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c
new file mode 100644
index 00000000..d04be222
--- /dev/null
+++ b/src/fe-gtk/menu.c
@@ -0,0 +1,2270 @@
+/* X-Chat
+ * Copyright (C) 1998-2007 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkradiomenuitem.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenubar.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkversion.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/ignore.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/servlist.h"
+#include "../common/notify.h"
+#include "../common/util.h"
+#include "xtext.h"
+#include "about.h"
+#include "ascii.h"
+#include "banlist.h"
+#include "chanlist.h"
+#include "editlist.h"
+#include "fkeys.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "notifygui.h"
+#include "pixmaps.h"
+#include "rawlog.h"
+#include "palette.h"
+#include "plugingui.h"
+#include "search.h"
+#include "textgui.h"
+#include "urlgrab.h"
+#include "userlistgui.h"
+#include "menu.h"
+
+static GSList *submenu_list;
+
+enum
+{
+	M_MENUITEM,
+	M_NEWMENU,
+	M_END,
+	M_SEP,
+	M_MENUTOG,
+	M_MENURADIO,
+	M_MENUSTOCK,
+	M_MENUPIX,
+	M_MENUSUB
+};
+
+struct mymenu
+{
+	char *text;
+	void *callback;
+	char *image;
+	unsigned char type;	/* M_XXX */
+	unsigned char id;		/* MENU_ID_XXX (menu.h) */
+	unsigned char state;	/* ticked or not? */
+	unsigned char sensitive;	/* shaded out? */
+	guint key;				/* GDK_x */
+};
+
+#define XCMENU_DOLIST 1
+#define XCMENU_SHADED 1
+#define XCMENU_MARKUP 2
+#define XCMENU_MNEMONIC 4
+
+/* execute a userlistbutton/popupmenu command */
+
+static void
+nick_command (session * sess, char *cmd)
+{
+	if (*cmd == '!')
+		xchat_exec (cmd + 1);
+	else
+		handle_command (sess, cmd, TRUE);
+}
+
+/* fill in the %a %s %n etc and execute the command */
+
+void
+nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
+{
+	char *buf;
+	char *host = _("Host unknown");
+	struct User *user;
+	int len;
+
+/*	if (sess->type == SESS_DIALOG)
+	{
+		buf = (char *)(GTK_ENTRY (sess->gui->topic_entry)->text);
+		buf = strrchr (buf, '@');
+		if (buf)
+			host = buf + 1;
+	} else*/
+	{
+		user = userlist_find (sess, nick);
+		if (user && user->hostname)
+			host = strchr (user->hostname, '@') + 1;
+	}
+
+	/* this can't overflow, since popup->cmd is only 256 */
+	len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512;
+	buf = malloc (len);
+
+	auto_insert (buf, len, cmd, 0, 0, allnick, sess->channel, "",
+					 server_get_network (sess->server, TRUE), host,
+					 sess->server->nick, nick);
+
+	nick_command (sess, buf);
+
+	free (buf);
+}
+
+/* userlist button has been clicked */
+
+void
+userlist_button_cb (GtkWidget * button, char *cmd)
+{
+	int i, num_sel, using_allnicks = FALSE;
+	char **nicks, *allnicks;
+	char *nick = NULL;
+	session *sess;
+
+	sess = current_sess;
+
+	if (strstr (cmd, "%a"))
+		using_allnicks = TRUE;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* fake a selection */
+		nicks = malloc (sizeof (char *) * 2);
+		nicks[0] = g_strdup (sess->channel);
+		nicks[1] = NULL;
+		num_sel = 1;
+	} else
+	{
+		/* find number of selected rows */
+		nicks = userlist_selection_list (sess->gui->user_tree, &num_sel);
+		if (num_sel < 1)
+		{
+			nick_command_parse (sess, cmd, "", "");
+			return;
+		}
+	}
+
+	/* create "allnicks" string */
+	allnicks = malloc (((NICKLEN + 1) * num_sel) + 1);
+	*allnicks = 0;
+
+	i = 0;
+	while (nicks[i])
+	{
+		if (i > 0)
+			strcat (allnicks, " ");
+		strcat (allnicks, nicks[i]);
+
+		if (!nick)
+			nick = nicks[0];
+
+		/* if not using "%a", execute the command once for each nickname */
+		if (!using_allnicks)
+			nick_command_parse (sess, cmd, nicks[i], "");
+
+		i++;
+	}
+
+	if (using_allnicks)
+	{
+		if (!nick)
+			nick = "";
+		nick_command_parse (sess, cmd, nick, allnicks);
+	}
+
+	while (num_sel)
+	{
+		num_sel--;
+		g_free (nicks[num_sel]);
+	}
+
+	free (nicks);
+	free (allnicks);
+}
+
+/* a popup-menu-item has been selected */
+
+static void
+popup_menu_cb (GtkWidget * item, char *cmd)
+{
+	char *nick;
+
+	/* the userdata is set in menu_quick_item() */
+	nick = g_object_get_data (G_OBJECT (item), "u");
+
+	if (!nick)	/* userlist popup menu */
+	{
+		/* treat it just like a userlist button */
+		userlist_button_cb (NULL, cmd);
+		return;
+	}
+
+	if (!current_sess)	/* for url grabber window */
+		nick_command_parse (sess_list->data, cmd, nick, nick);
+	else
+		nick_command_parse (current_sess, cmd, nick, nick);
+}
+
+GtkWidget *
+menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata,
+						int state)
+{
+	GtkWidget *item;
+
+	item = gtk_check_menu_item_new_with_mnemonic (label);
+	gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+GtkWidget *
+menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags,
+					  gpointer userdata, char *icon)
+{
+	GtkWidget *img, *item;
+	char *path;
+
+	if (!label)
+		item = gtk_menu_item_new ();
+	else
+	{
+		if (icon)
+		{
+			/*if (flags & XCMENU_MARKUP)
+				item = gtk_image_menu_item_new_with_markup (label);
+			else*/
+				item = gtk_image_menu_item_new_with_mnemonic (label);
+			img = NULL;
+			if (access (icon, R_OK) == 0)	/* try fullpath */
+				img = gtk_image_new_from_file (icon);
+			else
+			{
+				/* try relative to ~/.xchat2 */
+				path = g_strdup_printf ("%s/%s", get_xdir_fs (), icon);
+				if (access (path, R_OK) == 0)
+					img = gtk_image_new_from_file (path);
+				else
+					img = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU);
+				g_free (path);
+			}
+
+			if (img)
+				gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img);
+		}
+		else
+		{
+			if (flags & XCMENU_MARKUP)
+			{
+				item = gtk_menu_item_new_with_label ("");
+				if (flags & XCMENU_MNEMONIC)
+					gtk_label_set_markup_with_mnemonic (GTK_LABEL (GTK_BIN (item)->child), label);
+				else
+					gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), label);
+			} else
+			{
+				if (flags & XCMENU_MNEMONIC)
+					item = gtk_menu_item_new_with_mnemonic (label);
+				else
+					item = gtk_menu_item_new_with_label (label);
+			}
+		}
+	}
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_object_set_data (G_OBJECT (item), "u", userdata);
+	if (cmd)
+		g_signal_connect (G_OBJECT (item), "activate",
+								G_CALLBACK (popup_menu_cb), cmd);
+	if (flags & XCMENU_SHADED)
+		gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
+	gtk_widget_show_all (item);
+
+	return item;
+}
+
+static void
+menu_quick_item_with_callback (void *callback, char *label, GtkWidget * menu,
+										 void *arg)
+{
+	GtkWidget *item;
+
+	item = gtk_menu_item_new_with_label (label);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), arg);
+	gtk_widget_show (item);
+}
+
+GtkWidget *
+menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos)
+{
+	GtkWidget *sub_menu;
+	GtkWidget *sub_item;
+
+	if (!name)
+		return menu;
+
+	/* Code to add a submenu */
+	sub_menu = gtk_menu_new ();
+	if (flags & XCMENU_MARKUP)
+	{
+		sub_item = gtk_menu_item_new_with_label ("");
+		gtk_label_set_markup (GTK_LABEL (GTK_BIN (sub_item)->child), name);
+	}
+	else
+	{
+		if (flags & XCMENU_MNEMONIC)
+			sub_item = gtk_menu_item_new_with_mnemonic (name);
+		else
+			sub_item = gtk_menu_item_new_with_label (name);
+	}
+	gtk_menu_shell_insert (GTK_MENU_SHELL (menu), sub_item, pos);
+	gtk_widget_show (sub_item);
+	gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub_item), sub_menu);
+
+	if (sub_item_ret)
+		*sub_item_ret = sub_item;
+
+	if (flags & XCMENU_DOLIST)
+		/* We create a new element in the list */
+		submenu_list = g_slist_prepend (submenu_list, sub_menu);
+	return sub_menu;
+}
+
+static GtkWidget *
+menu_quick_endsub ()
+{
+	/* Just delete the first element in the linked list pointed to by first */
+	if (submenu_list)
+		submenu_list = g_slist_remove (submenu_list, submenu_list->data);
+
+	if (submenu_list)
+		return (submenu_list->data);
+	else
+		return NULL;
+}
+
+static void
+toggle_cb (GtkWidget *item, char *pref_name)
+{
+	char buf[256];
+
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+		snprintf (buf, sizeof (buf), "set %s 1", pref_name);
+	else
+		snprintf (buf, sizeof (buf), "set %s 0", pref_name);
+
+	handle_command (current_sess, buf, FALSE);
+}
+
+static int
+is_in_path (char *cmd)
+{
+	char *prog = strdup (cmd + 1);	/* 1st char is "!" */
+	char *space, *path, *orig;
+
+	orig = prog; /* save for free()ing */
+	/* special-case these default entries. */
+	/*                  123456789012345678 */
+	if (strncmp (prog, "gnome-terminal -x ", 18) == 0)
+	/* don't check for gnome-terminal, but the thing it's executing! */
+		prog += 18;
+
+	space = strchr (prog, ' ');	/* this isn't 100% but good enuf */
+	if (space)
+		*space = 0;
+
+	path = g_find_program_in_path (prog);
+	if (path)
+	{
+		g_free (path);
+		g_free (orig);
+		return 1;
+	}
+
+	g_free (orig);
+	return 0;
+}
+
+/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */
+
+static void
+menu_extract_icon (char *name, char **label, char **icon)
+{
+	char *p = name;
+	char *start = NULL;
+	char *end = NULL;
+
+	while (*p)
+	{
+		if (*p == '~')
+		{
+			/* escape \~ */
+			if (p == name || p[-1] != '\\')
+			{
+				if (!start)
+					start = p + 1;
+				else if (!end)
+					end = p + 1;
+			}
+		}
+		p++;
+	}
+
+	if (!end)
+		end = p;
+
+	if (start && start != end)
+	{
+		*label = g_strndup (name, (start - name) - 1);
+		*icon = g_strndup (start, (end - start) - 1);
+	}
+	else
+	{
+		*label = g_strdup (name);
+		*icon = NULL;
+	}
+}
+
+/* append items to "menu" using the (struct popup*) list provided */
+
+void
+menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
+{
+	struct popup *pop;
+	GtkWidget *tempmenu = menu, *subitem = NULL;
+	int childcount = 0;
+
+	submenu_list = g_slist_prepend (0, menu);
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+
+		if (!strncasecmp (pop->name, "SUB", 3))
+		{
+			childcount = 0;
+			tempmenu = menu_quick_sub (pop->cmd, tempmenu, &subitem, XCMENU_DOLIST|XCMENU_MNEMONIC, -1);
+
+		} else if (!strncasecmp (pop->name, "TOGGLE", 6))
+		{
+			childcount++;
+			menu_toggle_item (pop->name + 7, tempmenu, toggle_cb, pop->cmd,
+									cfg_get_bool (pop->cmd));
+
+		} else if (!strncasecmp (pop->name, "ENDSUB", 6))
+		{
+			/* empty sub menu due to no programs in PATH? */
+			if (check_path && childcount < 1)
+				gtk_widget_destroy (subitem);
+			subitem = NULL;
+
+			if (tempmenu != menu)
+				tempmenu = menu_quick_endsub ();
+			/* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */
+
+		} else if (!strncasecmp (pop->name, "SEP", 3))
+		{
+			menu_quick_item (0, 0, tempmenu, XCMENU_SHADED, 0, 0);
+
+		} else
+		{
+			char *icon, *label;
+
+			/* default command in xchat.c */
+			if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s"))
+			{
+				/* don't create this item if already in notify list */
+				if (!target || notify_is_in_list (current_sess->server, target))
+				{
+					list = list->next;
+					continue;
+				}
+			}
+
+			menu_extract_icon (pop->name, &label, &icon);
+
+			if (!check_path || pop->cmd[0] != '!')
+			{
+				menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
+			/* check if the program is in path, if not, leave it out! */
+			} else if (is_in_path (pop->cmd))
+			{
+				childcount++;
+				menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
+			}
+
+			g_free (label);
+			g_free (icon);
+		}
+
+		list = list->next;
+	}
+
+	/* Let's clean up the linked list from mem */
+	while (submenu_list)
+		submenu_list = g_slist_remove (submenu_list, submenu_list->data);
+}
+
+static char *str_copy = NULL;		/* for all pop-up menus */
+static GtkWidget *nick_submenu = NULL;	/* user info submenu */
+
+static void
+menu_destroy (GtkWidget *menu, gpointer objtounref)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+	if (objtounref)
+		g_object_unref (G_OBJECT (objtounref));
+	nick_submenu = NULL;
+}
+
+static void
+menu_popup (GtkWidget *menu, GdkEventButton *event, gpointer objtounref)
+{
+#if (GTK_MAJOR_VERSION != 2) || (GTK_MINOR_VERSION != 0)
+	if (event && event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+#endif
+
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (menu_destroy), objtounref);
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+						 0, event ? event->time : 0);
+}
+
+static void
+menu_nickinfo_cb (GtkWidget *menu, session *sess)
+{
+	char buf[512];
+
+	if (!is_session (sess))
+		return;
+
+	/* issue a /WHOIS */
+	snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy);
+	handle_command (sess, buf, FALSE);
+	/* and hide the output */
+	sess->server->skip_next_whois = 1;
+}
+
+static void
+copy_to_clipboard_cb (GtkWidget *item, char *url)
+{
+	gtkutil_copy_to_clipboard (item, NULL, url);
+}
+
+/* returns boolean: Some data is missing */
+
+static gboolean
+menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
+{
+	char buf[512];
+	char unknown[96];
+	char *real, *fmt;
+	struct away_msg *away;
+	gboolean missing = FALSE;
+	GtkWidget *item;
+
+	/* let the translators tweak this if need be */
+	fmt = _("<tt><b>%-11s</b></tt> %s");
+	snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown"));
+
+	if (user->realname)
+	{
+		real = strip_color (user->realname, -1, STRIP_ALL|STRIP_ESCMARKUP);
+		snprintf (buf, sizeof (buf), fmt, _("Real Name:"), real);
+		g_free (real);
+	} else
+	{
+		snprintf (buf, sizeof (buf), fmt, _("Real Name:"), unknown);
+	}
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->realname ? user->realname : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("User:"),
+				 user->hostname ? user->hostname : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->hostname ? user->hostname : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("Country:"),
+				 user->hostname ? country(user->hostname) : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->hostname ? country(user->hostname) : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("Server:"),
+				 user->servername ? user->servername : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->servername ? user->servername : unknown);
+
+	if (user->lasttalk)
+	{
+		char min[96];
+
+		snprintf (min, sizeof (min), _("%u minutes ago"),
+					(unsigned int) ((time (0) - user->lasttalk) / 60));
+		snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), min);
+	} else
+	{
+		snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), unknown);
+	}
+	menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+
+	if (user->away)
+	{
+		away = server_away_find_message (current_sess->server, user->nick);
+		if (away)
+		{
+			char *msg = strip_color (away->message ? away->message : unknown, -1, STRIP_ALL|STRIP_ESCMARKUP);
+			snprintf (buf, sizeof (buf), fmt, _("Away Msg:"), msg);
+			g_free (msg);
+			item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+			g_signal_connect (G_OBJECT (item), "activate",
+									G_CALLBACK (copy_to_clipboard_cb), 
+									away->message ? away->message : unknown);
+		}
+		else
+			missing = TRUE;
+	}
+
+	return missing;
+}
+
+void
+fe_userlist_update (session *sess, struct User *user)
+{
+	GList *items, *next;
+
+	if (!nick_submenu || !str_copy)
+		return;
+
+	/* not the same nick as the menu? */
+	if (sess->server->p_cmp (user->nick, str_copy))
+		return;
+
+	/* get rid of the "show" signal */
+	g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess);
+
+	/* destroy all the old items */
+	items = ((GtkMenuShell *) nick_submenu)->children;
+	while (items)
+	{
+		next = items->next;
+		gtk_widget_destroy (items->data);
+		items = next;
+	}
+
+	/* and re-create them with new info */
+	menu_create_nickinfo_menu (user, nick_submenu);
+}
+
+void
+menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
+{
+	char buf[512];
+	struct User *user;
+	GtkWidget *submenu, *menu = gtk_menu_new ();
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (nick);
+
+	submenu_list = 0;	/* first time through, might not be 0 */
+
+	/* more than 1 nick selected? */
+	if (num_sel > 1)
+	{
+		snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel);
+		menu_quick_item (0, buf, menu, 0, 0, 0);
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+	} else
+	{
+		user = userlist_find (sess, nick);	/* lasttalk is channel specific */
+		if (!user)
+			user = userlist_find_global (current_sess->server, nick);
+		if (user)
+		{
+			nick_submenu = submenu = menu_quick_sub (nick, menu, NULL, XCMENU_DOLIST, -1);
+
+			if (menu_create_nickinfo_menu (user, submenu) ||
+				 !user->hostname || !user->realname || !user->servername)
+			{
+				g_signal_connect (G_OBJECT (submenu), "show", G_CALLBACK (menu_nickinfo_cb), sess);
+			}
+
+			menu_quick_endsub ();
+			menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+		}
+	}
+
+	if (num_sel > 1)
+		menu_create (menu, popup_list, NULL, FALSE);
+	else
+		menu_create (menu, popup_list, str_copy, FALSE);
+
+	if (num_sel == 0)	/* xtext click */
+		menu_add_plugin_items (menu, "\x5$NICK", str_copy);
+	else	/* userlist treeview click */
+		menu_add_plugin_items (menu, "\x5$NICK", NULL);
+
+	menu_popup (menu, event, NULL);
+}
+
+/* stuff for the View menu */
+
+static void
+menu_showhide_cb (session *sess)
+{
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+	else
+		gtk_widget_show (sess->gui->menu);
+}
+
+static void
+menu_topic_showhide_cb (session *sess)
+{
+	if (prefs.topicbar)
+		gtk_widget_show (sess->gui->topic_bar);
+	else
+		gtk_widget_hide (sess->gui->topic_bar);
+}
+
+static void
+menu_userlist_showhide_cb (session *sess)
+{
+	mg_decide_userlist (sess, TRUE);
+}
+
+static void
+menu_ulbuttons_showhide_cb (session *sess)
+{
+	if (prefs.userlistbuttons)
+		gtk_widget_show (sess->gui->button_box);
+	else
+		gtk_widget_hide (sess->gui->button_box);
+}
+
+static void
+menu_cmbuttons_showhide_cb (session *sess)
+{
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (sess->gui->topicbutton_box);
+		else
+			gtk_widget_hide (sess->gui->topicbutton_box);
+		break;
+	default:
+		gtk_widget_hide (sess->gui->topicbutton_box);
+	}
+}
+
+static void
+menu_setting_foreach (void (*callback) (session *), int id, guint state)
+{
+	session *sess;
+	GSList *list;
+	int maindone = FALSE;	/* do it only once for EVERY tab */
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+
+		if (!sess->gui->is_tab || !maindone)
+		{
+			if (sess->gui->is_tab)
+				maindone = TRUE;
+			if (id != -1)
+				GTK_CHECK_MENU_ITEM (sess->gui->menu_item[id])->active = state;
+			if (callback)
+				callback (sess);
+		}
+
+		list = list->next;
+	}
+}
+
+void
+menu_bar_toggle (void)
+{
+	prefs.hidemenu = !prefs.hidemenu;
+	menu_setting_foreach (menu_showhide_cb, MENU_ID_MENUBAR, !prefs.hidemenu);
+}
+
+static void
+menu_bar_toggle_cb (void)
+{
+	menu_bar_toggle ();
+	if (prefs.hidemenu)
+		fe_message (_("The Menubar is now hidden. You can show it again"
+						  " by pressing F9 or right-clicking in a blank part of"
+						  " the main text area."), FE_MSG_INFO);
+}
+
+static void
+menu_topicbar_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.topicbar = !prefs.topicbar;
+	menu_setting_foreach (menu_topic_showhide_cb, MENU_ID_TOPICBAR,
+								 prefs.topicbar);
+}
+
+static void
+menu_userlist_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.hideuserlist = !prefs.hideuserlist;
+	menu_setting_foreach (menu_userlist_showhide_cb, MENU_ID_USERLIST,
+								 !prefs.hideuserlist);
+}
+
+static void
+menu_ulbuttons_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.userlistbuttons = !prefs.userlistbuttons;
+	menu_setting_foreach (menu_ulbuttons_showhide_cb, MENU_ID_ULBUTTONS,
+								 prefs.userlistbuttons);
+}
+
+static void
+menu_cmbuttons_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.chanmodebuttons = !prefs.chanmodebuttons;
+	menu_setting_foreach (menu_cmbuttons_showhide_cb, MENU_ID_MODEBUTTONS,
+								 prefs.chanmodebuttons);
+}
+
+void
+menu_middlemenu (session *sess, GdkEventButton *event)
+{
+	GtkWidget *menu;
+	GtkAccelGroup *accel_group;
+
+	accel_group = gtk_accel_group_new ();
+	menu = menu_create_main (accel_group, FALSE, sess->server->is_away, !sess->gui->is_tab, NULL);
+	menu_popup (menu, event, accel_group);
+}
+
+static void
+open_url_cb (GtkWidget *item, char *url)
+{
+	char buf[512];
+
+	/* pass this to /URL so it can handle irc:// */
+	snprintf (buf, sizeof (buf), "URL %s", url);
+	handle_command (current_sess, buf, FALSE);
+}
+
+void
+menu_urlmenu (GdkEventButton *event, char *url)
+{
+	GtkWidget *menu;
+	char *tmp, *chop;
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (url);
+
+	menu = gtk_menu_new ();
+	/* more than 51 chars? Chop it */
+	if (g_utf8_strlen (str_copy, -1) >= 52)
+	{
+		tmp = strdup (str_copy);
+		chop = g_utf8_offset_to_pointer (tmp, 48);
+		chop[0] = chop[1] = chop[2] = '.';
+		chop[3] = 0;
+		menu_quick_item (0, tmp, menu, XCMENU_SHADED, 0, 0);
+		free (tmp);
+	} else
+	{
+		menu_quick_item (0, str_copy, menu, XCMENU_SHADED, 0, 0);
+	}
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+	/* Two hardcoded entries */
+	if (strncmp (str_copy, "irc://", 6) == 0 ||
+	    strncmp (str_copy, "ircs://",7) == 0)
+		menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
+	else
+		menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
+	menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
+	/* custom ones from urlhandlers.conf */
+	menu_create (menu, urlhandler_list, str_copy, TRUE);
+	menu_add_plugin_items (menu, "\x4$URL", str_copy);
+	menu_popup (menu, event, NULL);
+}
+
+static void
+menu_chan_cycle (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "CYCLE %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+static void
+menu_chan_part (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "part %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+static void
+menu_chan_join (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "join %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+void
+menu_chanmenu (struct session *sess, GdkEventButton * event, char *chan)
+{
+	GtkWidget *menu;
+	int is_joined = FALSE;
+
+	if (find_channel (sess->server, chan))
+		is_joined = TRUE;
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (chan);
+
+	menu = gtk_menu_new ();
+
+	menu_quick_item (0, chan, menu, XCMENU_SHADED, str_copy, 0);
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, str_copy, 0);
+
+	if (!is_joined)
+		menu_quick_item_with_callback (menu_chan_join, _("Join Channel"), menu,
+												 str_copy);
+	else
+	{
+		menu_quick_item_with_callback (menu_chan_part, _("Part Channel"), menu,
+												 str_copy);
+		menu_quick_item_with_callback (menu_chan_cycle, _("Cycle Channel"), menu,
+												 str_copy);
+	}
+
+	menu_addfavoritemenu (sess->server, menu, str_copy);
+
+	menu_add_plugin_items (menu, "\x5$CHAN", str_copy);
+	menu_popup (menu, event, NULL);
+}
+
+static void
+menu_delfav_cb (GtkWidget *item, server *serv)
+{
+	servlist_autojoinedit (serv->network, str_copy, FALSE);
+}
+
+static void
+menu_addfav_cb (GtkWidget *item, server *serv)
+{
+	servlist_autojoinedit (serv->network, str_copy, TRUE);
+}
+
+void
+menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel)
+{
+	if (!serv->network)
+		return;
+
+	if (channel != str_copy)
+	{
+		if (str_copy)
+			free (str_copy);
+		str_copy = strdup (channel);
+	}
+
+	if (joinlist_is_in_list (serv, channel))
+		mg_create_icon_item (_("_Remove from Favorites"), GTK_STOCK_REMOVE, menu, menu_delfav_cb, serv);
+	else
+		mg_create_icon_item (_("_Add to Favorites"), GTK_STOCK_ADD, menu, menu_addfav_cb, serv);
+}
+
+static void
+menu_open_server_list (GtkWidget *wid, gpointer none)
+{
+	fe_serverlist_open (current_sess);
+}
+
+static void
+menu_settings (GtkWidget * wid, gpointer none)
+{
+	extern void setup_open (void);
+	setup_open ();
+}
+
+static void
+menu_usermenu (void)
+{
+	editlist_gui_open (NULL, NULL, usermenu_list, _("XChat: User menu"),
+							 "usermenu", "usermenu.conf", 0);
+}
+
+static void
+usermenu_create (GtkWidget *menu)
+{
+	menu_create (menu, usermenu_list, "", FALSE);
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);	/* sep */
+	menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu..."), menu, 0);
+}
+
+static void
+usermenu_destroy (GtkWidget * menu)
+{
+	GList *items = ((GtkMenuShell *) menu)->children;
+	GList *next;
+
+	while (items)
+	{
+		next = items->next;
+		gtk_widget_destroy (items->data);
+		items = next;
+	}
+}
+
+void
+usermenu_update (void)
+{
+	int done_main = FALSE;
+	GSList *list = sess_list;
+	session *sess;
+	GtkWidget *menu;
+
+	while (list)
+	{
+		sess = list->data;
+		menu = sess->gui->menu_item[MENU_ID_USERMENU];
+		if (sess->gui->is_tab)
+		{
+			if (!done_main && menu)
+			{
+				usermenu_destroy (menu);
+				usermenu_create (menu);
+				done_main = TRUE;
+			}
+		} else if (menu)
+		{
+			usermenu_destroy (menu);
+			usermenu_create (menu);
+		}
+		list = list->next;
+	}
+}
+
+static void
+menu_newserver_window (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 0;
+	new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_newchannel_window (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 0;
+	new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_newserver_tab (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+	int oldf = prefs.newtabstofront;
+
+	prefs.tabchannels = 1;
+	/* force focus if setting is "only requested tabs" */
+	if (prefs.newtabstofront == 2)
+		prefs.newtabstofront = 1;
+	new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	prefs.tabchannels = old;
+	prefs.newtabstofront = oldf;
+}
+
+static void
+menu_newchannel_tab (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 1;
+	new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_rawlog (GtkWidget * wid, gpointer none)
+{
+	open_rawlog (current_sess->server);
+}
+
+static void
+menu_detach (GtkWidget * wid, gpointer none)
+{
+	mg_detach (current_sess, 0);
+}
+
+static void
+menu_close (GtkWidget * wid, gpointer none)
+{
+	mg_close_sess (current_sess);
+}
+
+static void
+menu_quit (GtkWidget * wid, gpointer none)
+{
+	mg_open_quit_dialog (FALSE);
+}
+
+static void
+menu_search ()
+{
+	search_open (current_sess);
+}
+
+static void
+menu_resetmarker (GtkWidget * wid, gpointer none)
+{
+	gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext));
+}
+
+static void
+menu_flushbuffer (GtkWidget * wid, gpointer none)
+{
+	fe_text_clear (current_sess, 0);
+}
+
+static void
+savebuffer_req_done (session *sess, char *file)
+{
+	int fh;
+
+	if (!file)
+		return;
+
+	fh = open (file, O_TRUNC | O_WRONLY | O_CREAT, 0600);
+	if (fh != -1)
+	{
+		gtk_xtext_save (GTK_XTEXT (sess->gui->xtext), fh);
+		close (fh);
+	}
+}
+
+static void
+menu_savebuffer (GtkWidget * wid, gpointer none)
+{
+	gtkutil_file_req (_("Select an output filename"), savebuffer_req_done,
+							current_sess, NULL, FRF_WRITE);
+}
+
+static void
+menu_disconnect (GtkWidget * wid, gpointer none)
+{
+	handle_command (current_sess, "DISCON", FALSE);
+}
+
+static void
+menu_reconnect (GtkWidget * wid, gpointer none)
+{
+	if (current_sess->server->hostname[0])
+		handle_command (current_sess, "RECONNECT", FALSE);
+	else
+		fe_serverlist_open (current_sess);
+}
+
+static void
+menu_join_cb (GtkWidget *dialog, gint response, GtkEntry *entry)
+{
+	switch (response)
+	{
+	case GTK_RESPONSE_ACCEPT:
+		menu_chan_join (NULL, entry->text);
+		break;
+
+	case GTK_RESPONSE_HELP:
+		chanlist_opengui (current_sess->server, TRUE);
+		break;
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+menu_join_entry_cb (GtkWidget *entry, GtkDialog *dialog)
+{
+	gtk_dialog_response (dialog, GTK_RESPONSE_ACCEPT);
+}
+
+static void
+menu_join (GtkWidget * wid, gpointer none)
+{
+	GtkWidget *hbox, *dialog, *entry, *label;
+
+	dialog = gtk_dialog_new_with_buttons (_("Join Channel"),
+									GTK_WINDOW (parent_window), 0,
+									_("Retrieve channel list..."), GTK_RESPONSE_HELP,
+									GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+									GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+									NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	entry = gtk_entry_new ();
+	GTK_ENTRY (entry)->editable = 0;	/* avoid auto-selection */
+	gtk_entry_set_text (GTK_ENTRY (entry), "#");
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (menu_join_entry_cb), dialog);
+	gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
+
+	label = gtk_label_new (_("Enter Channel to Join:"));
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (menu_join_cb), entry);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+
+	gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE);
+	gtk_editable_set_position (GTK_EDITABLE (entry), 1);
+}
+
+static void
+menu_away (GtkCheckMenuItem *item, gpointer none)
+{
+	handle_command (current_sess, item->active ? "away" : "back", FALSE);
+}
+
+static void
+menu_chanlist (GtkWidget * wid, gpointer none)
+{
+	chanlist_opengui (current_sess->server, FALSE);
+}
+
+static void
+menu_banlist (GtkWidget * wid, gpointer none)
+{
+	banlist_opengui (current_sess);
+}
+
+#ifdef USE_PLUGIN
+
+static void
+menu_loadplugin (void)
+{
+	plugingui_load ();
+}
+
+static void
+menu_pluginlist (void)
+{
+	plugingui_open ();
+}
+
+#else
+
+#define menu_pluginlist 0
+#define menu_loadplugin 0
+
+#endif
+
+#define usercommands_help  _("User Commands - Special codes:\n\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+									"%t  =  time/date\n"\
+                           "%v  =  xchat version\n"\
+                           "%2  =  word 2\n"\
+                           "%3  =  word 3\n"\
+                           "&2  =  word 2 to the end of line\n"\
+                           "&3  =  word 3 to the end of line\n\n"\
+                           "eg:\n"\
+                           "/cmd john hello\n\n"\
+                           "%2 would be \042john\042\n"\
+                           "&2 would be \042john hello\042.")
+
+#define ulbutton_help       _("Userlist Buttons - Special codes:\n\n"\
+                           "%a  =  all selected nicks\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+                           "%h  =  selected nick's hostname\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+                           "%s  =  selected nick\n"\
+									"%t  =  time/date\n")
+
+#define dlgbutton_help      _("Dialog Buttons - Special codes:\n\n"\
+                           "%a  =  all selected nicks\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+                           "%h  =  selected nick's hostname\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+                           "%s  =  selected nick\n"\
+									"%t  =  time/date\n")
+
+#define ctcp_help          _("CTCP Replies - Special codes:\n\n"\
+                           "%d  =  data (the whole ctcp)\n"\
+									"%e  =  current network name\n"\
+									"%m  =  machine info\n"\
+                           "%s  =  nick who sent the ctcp\n"\
+                           "%t  =  time/date\n"\
+                           "%2  =  word 2\n"\
+                           "%3  =  word 3\n"\
+                           "&2  =  word 2 to the end of line\n"\
+                           "&3  =  word 3 to the end of line\n\n")
+
+#define url_help           _("URL Handlers - Special codes:\n\n"\
+                           "%s  =  the URL string\n\n"\
+                           "Putting a ! infront of the command\n"\
+                           "indicates it should be sent to a\n"\
+                           "shell instead of XChat")
+
+static void
+menu_usercommands (void)
+{
+	editlist_gui_open (NULL, NULL, command_list, _("XChat: User Defined Commands"),
+							 "commands", "commands.conf", usercommands_help);
+}
+
+static void
+menu_ulpopup (void)
+{
+	editlist_gui_open (NULL, NULL, popup_list, _("XChat: Userlist Popup menu"), "popup",
+							 "popup.conf", ulbutton_help);
+}
+
+static void
+menu_rpopup (void)
+{
+	editlist_gui_open (_("Text"), _("Replace with"), replace_list, _("XChat: Replace"), "replace",
+							 "replace.conf", 0);
+}
+
+static void
+menu_urlhandlers (void)
+{
+	editlist_gui_open (NULL, NULL, urlhandler_list, _("XChat: URL Handlers"), "urlhandlers",
+							 "urlhandlers.conf", url_help);
+}
+
+static void
+menu_evtpopup (void)
+{
+	pevent_dialog_show ();
+}
+
+static void
+menu_keypopup (void)
+{
+	key_dialog_show ();
+}
+
+static void
+menu_ulbuttons (void)
+{
+	editlist_gui_open (NULL, NULL, button_list, _("XChat: Userlist buttons"), "buttons",
+							 "buttons.conf", ulbutton_help);
+}
+
+static void
+menu_dlgbuttons (void)
+{
+	editlist_gui_open (NULL, NULL, dlgbutton_list, _("XChat: Dialog buttons"), "dlgbuttons",
+							 "dlgbuttons.conf", dlgbutton_help);
+}
+
+static void
+menu_ctcpguiopen (void)
+{
+	editlist_gui_open (NULL, NULL, ctcp_list, _("XChat: CTCP Replies"), "ctcpreply",
+							 "ctcpreply.conf", ctcp_help);
+}
+
+static void
+menu_docs (GtkWidget *wid, gpointer none)
+{
+	fe_open_url ("http://xchat.org/docs/");
+}
+
+/*static void
+menu_webpage (GtkWidget *wid, gpointer none)
+{
+	fe_open_url ("http://xchat.org");
+}*/
+
+static void
+menu_dcc_win (GtkWidget *wid, gpointer none)
+{
+	fe_dcc_open_recv_win (FALSE);
+	fe_dcc_open_send_win (FALSE);
+}
+
+static void
+menu_dcc_chat_win (GtkWidget *wid, gpointer none)
+{
+	fe_dcc_open_chat_win (FALSE);
+}
+
+void
+menu_change_layout (void)
+{
+	if (prefs.tab_layout == 0)
+	{
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 1);
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 0);
+		mg_change_layout (0);
+	} else
+	{
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 0);
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 1);
+		mg_change_layout (2);
+	}
+}
+
+static void
+menu_layout_cb (GtkWidget *item, gpointer none)
+{
+	prefs.tab_layout = 2;
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+		prefs.tab_layout = 0;
+
+	menu_change_layout ();
+}
+
+static void
+menu_apply_metres_cb (session *sess)
+{
+	mg_update_meters (sess->gui);
+}
+
+static void
+menu_metres_off (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 0;
+		prefs.throttlemeter = 0;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_text (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 2;
+		prefs.throttlemeter = 2;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_graph (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 1;
+		prefs.throttlemeter = 1;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_both (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 3;
+		prefs.throttlemeter = 3;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static struct mymenu mymenu[] = {
+	{N_("_XChat"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+
+	{N_("_New"), 0, GTK_STOCK_NEW, M_MENUSUB, 0, 0, 1},
+		{N_("Server Tab..."), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_t},
+		{N_("Channel Tab..."), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Server Window..."), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Channel Window..."), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+
+#ifdef USE_PLUGIN
+	{N_("_Load Plugin or Script..."), menu_loadplugin, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 1},
+#else
+	{N_("_Load Plugin or Script..."), 0, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 0},
+#endif
+	{0, 0, 0, M_SEP, 0, 0, 0},	/* 11 */
+#define DETACH_OFFSET (12)
+	{0, menu_detach, GTK_STOCK_REDO, M_MENUSTOCK, 0, 0, 1, GDK_I},	/* 12 */
+#define CLOSE_OFFSET (13)
+	{0, menu_close, GTK_STOCK_CLOSE, M_MENUSTOCK, 0, 0, 1, GDK_w},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("_Quit"), menu_quit, GTK_STOCK_QUIT, M_MENUSTOCK, 0, 0, 1, GDK_q},	/* 15 */
+
+	{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
+#define MENUBAR_OFFSET (17)
+	{N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_F9},
+	{N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1},
+	{N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_F7},
+	{N_("U_serlist Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
+	{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1},	/* 23 */
+#define TABS_OFFSET (24)
+		{N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1},
+		{N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},
+	{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1},	/* 27 */
+#define METRE_OFFSET (28)
+		{N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},	/* 32 */
+
+	{N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("_Disconnect"), menu_disconnect, GTK_STOCK_DISCONNECT, M_MENUSTOCK, MENU_ID_DISCONNECT, 0, 1},
+	{N_("_Reconnect"), menu_reconnect, GTK_STOCK_CONNECT, M_MENUSTOCK, MENU_ID_RECONNECT, 0, 1},
+	{N_("Join a Channel..."), menu_join, GTK_STOCK_JUMP_TO, M_MENUSTOCK, MENU_ID_JOIN, 0, 1},
+	{N_("List of Channels..."), menu_chanlist, GTK_STOCK_INDEX, M_MENUITEM, 0, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+#define AWAY_OFFSET (39)
+	{N_("Marked Away"), menu_away, 0, M_MENUTOG, MENU_ID_AWAY, 0, 1, GDK_a},
+
+	{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1},	/* 40 */
+
+	{N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("_Preferences"), menu_settings, GTK_STOCK_PREFERENCES, M_MENUSTOCK, 0, 0, 1},
+
+	{N_("Advanced"), 0, GTK_STOCK_JUSTIFY_LEFT, M_MENUSUB, 0, 0, 1},
+		{N_("Auto Replace..."), menu_rpopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("CTCP Replies..."), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Dialog Buttons..."), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Keyboard Shortcuts..."), menu_keypopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Text Events..."), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("URL Handlers..."), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1},
+		{N_("User Commands..."), menu_usercommands, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Userlist Buttons..."), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Userlist Popup..."), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},		/* 53 */
+
+	{N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("Ban List..."), menu_banlist, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Character Chart..."), ascii_open, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Direct Chat..."), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1},
+	{N_("File Transfers..."), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Friends List..."), notify_opengui, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Ignore List..."), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Plugins and Scripts..."), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Raw Log..."), menu_rawlog, 0, M_MENUITEM, 0, 0, 1},	/* 62 */
+	{N_("URL Grabber..."), url_opengui, 0, M_MENUITEM, 0, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_m},
+	{N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l},
+#define SEARCH_OFFSET 67
+	{N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f},
+	{N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1},
+
+	{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},	/* 69 */
+	{N_("_Contents"), menu_docs, GTK_STOCK_HELP, M_MENUSTOCK, 0, 0, 1, GDK_F1},
+#if 0
+	{N_("Check for updates"), menu_update, 0, M_MENUITEM, 0, 1},
+#endif
+	{N_("_About"), menu_about, GTK_STOCK_ABOUT, M_MENUSTOCK, 0, 0, 1},
+
+	{0, 0, 0, M_END, 0, 0, 0},
+};
+
+GtkWidget *
+create_icon_menu (char *labeltext, void *stock_name, int is_stock)
+{
+	GtkWidget *item, *img;
+
+	if (is_stock)
+		img = gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_MENU);
+	else
+		img = gtk_image_new_from_pixbuf (*((GdkPixbuf **)stock_name));
+	item = gtk_image_menu_item_new_with_mnemonic (labeltext);
+	gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img);
+	gtk_widget_show (img);
+
+	return item;
+}
+
+#if GTK_CHECK_VERSION(2,4,0)
+
+/* Override the default GTK2.4 handler, which would make menu
+   bindings not work when the menu-bar is hidden. */
+static gboolean
+menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data)
+{
+	/* GTK2.2 behaviour */
+#if GTK_CHECK_VERSION(2,20,0)
+	return gtk_widget_is_sensitive (widget);
+#else
+	return GTK_WIDGET_IS_SENSITIVE (widget);
+#endif
+}
+
+#endif
+
+
+/* === STUFF FOR /MENU === */
+
+static GtkMenuItem *
+menu_find_item (GtkWidget *menu, char *name)
+{
+	GList *items = ((GtkMenuShell *) menu)->children;
+	GtkMenuItem *item;
+	GtkWidget *child;
+	const char *labeltext;
+
+	while (items)
+	{
+		item = items->data;
+		child = GTK_BIN (item)->child;
+		if (child)	/* separators arn't labels, skip them */
+		{
+			labeltext = g_object_get_data (G_OBJECT (item), "name");
+			if (!labeltext)
+				labeltext = gtk_label_get_text (GTK_LABEL (child));
+			if (!menu_streq (labeltext, name, 1))
+				return item;
+		} else if (name == NULL)
+		{
+			return item;
+		}
+		items = items->next;
+	}
+
+	return NULL;
+}
+
+static GtkWidget *
+menu_find_path (GtkWidget *menu, char *path)
+{
+	GtkMenuItem *item;
+	char *s;
+	char name[128];
+	int len;
+
+	/* grab the next part of the path */
+	s = strchr (path, '/');
+	len = s - path;
+	if (!s)
+		len = strlen (path);
+	len = MIN (len, sizeof (name) - 1);
+	memcpy (name, path, len);
+	name[len] = 0;
+
+	item = menu_find_item (menu, name);
+	if (!item)
+		return NULL;
+
+	menu = gtk_menu_item_get_submenu (item);
+	if (!menu)
+		return NULL;
+
+	path += len;
+	if (*path == 0)
+		return menu;
+
+	return menu_find_path (menu, path + 1);
+}
+
+static GtkWidget *
+menu_find (GtkWidget *menu, char *path, char *label)
+{
+	GtkWidget *item = NULL;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+		item = (GtkWidget *)menu_find_item (menu, label);
+	return item;
+}
+
+static void
+menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, char *))
+{
+	GSList *list = sess_list;
+	int tabdone = FALSE;
+	session *sess;
+
+	if (!me->is_main)
+		return;	/* not main menu */
+
+	while (list)
+	{
+		sess = list->data;
+		/* do it only once for tab sessions, since they share a GUI */
+		if (!sess->gui->is_tab || !tabdone)
+		{
+			callback (sess->gui->menu, me, NULL);
+			if (sess->gui->is_tab)
+				tabdone = TRUE;
+		}
+		list = list->next;
+	}
+}
+
+static void
+menu_update_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item;
+
+	item = menu_find (menu, me->path, me->label);
+	if (item)
+	{
+		gtk_widget_set_sensitive (item, me->enable);
+		/* must do it without triggering the callback */
+		if (GTK_IS_CHECK_MENU_ITEM (item))
+			GTK_CHECK_MENU_ITEM (item)->active = me->state;
+	}
+}
+
+/* radio state changed via mouse click */
+static void
+menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
+{
+	me->state = 0;
+	if (item->active)
+		me->state = 1;
+
+	/* update the state, incase this was changed via right-click. */
+	/* This will update all other windows and menu bars */
+	menu_foreach_gui (me, menu_update_cb);
+
+	if (me->state && me->cmd)
+		handle_command (current_sess, me->cmd, FALSE);
+}
+
+/* toggle state changed via mouse click */
+static void
+menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
+{
+	me->state = 0;
+	if (item->active)
+		me->state = 1;
+
+	/* update the state, incase this was changed via right-click. */
+	/* This will update all other windows and menu bars */
+	menu_foreach_gui (me, menu_update_cb);
+
+	if (me->state)
+		handle_command (current_sess, me->cmd, FALSE);
+	else
+		handle_command (current_sess, me->ucmd, FALSE);
+}
+
+static GtkWidget *
+menu_radio_item (char *label, GtkWidget *menu, void *callback, void *userdata,
+						int state, char *groupname)
+{
+	GtkWidget *item;
+	GtkMenuItem *parent;
+	GSList *grouplist = NULL;
+
+	parent = menu_find_item (menu, groupname);
+	if (parent)
+		grouplist = gtk_radio_menu_item_get_group ((GtkRadioMenuItem *)parent);
+
+	item = gtk_radio_menu_item_new_with_label (grouplist, label);
+	gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+static void
+menu_reorder (GtkMenu *menu, GtkWidget *item, int pos)
+{
+	if (pos == 0xffff)	/* outbound.c uses this default */
+		return;
+
+	if (pos < 0)	/* position offset from end/bottom */
+		gtk_menu_reorder_child (menu, item, (g_list_length (GTK_MENU_SHELL (menu)->children) + pos) - 1);
+	else
+		gtk_menu_reorder_child (menu, item, pos);
+}
+
+static GtkWidget *
+menu_add_radio (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_radio_item (me->label, menu, menu_radio_cb, me, me->state, me->group);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_toggle (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_toggle_item (me->label, menu, menu_toggle_cb, me, me->state);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_item (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_quick_item (me->cmd, me->label, menu, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, target, me->icon);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_sub (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+	int pos;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		pos = me->pos;
+		if (pos < 0)	/* position offset from end/bottom */
+			pos = g_list_length (GTK_MENU_SHELL (menu)->children) + pos;
+		menu_quick_sub (me->label, menu, &item, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, pos);
+	}
+	return item;
+}
+
+static void
+menu_del_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item = menu_find (menu, me->path + me->root_offset, me->label);
+	if (item)
+		gtk_widget_destroy (item);
+}
+
+static void
+menu_add_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item;
+	GtkAccelGroup *accel_group;
+
+	if (me->group)	/* have a group name? Must be a radio item */
+		item = menu_add_radio (menu, me);
+	else if (me->ucmd)	/* have unselect-cmd? Must be a toggle item */
+		item = menu_add_toggle (menu, me);
+	else if (me->cmd || !me->label)	/* label=NULL for separators */
+		item = menu_add_item (menu, me, target);
+	else
+		item = menu_add_sub (menu, me);
+
+	if (item)
+	{
+		gtk_widget_set_sensitive (item, me->enable);
+		if (me->key)
+		{
+			accel_group = g_object_get_data (G_OBJECT (menu), "accel");
+			if (accel_group)	/* popup menus don't have them */
+				gtk_widget_add_accelerator (item, "activate", accel_group, me->key,
+													 me->modifier, GTK_ACCEL_VISIBLE);
+		}
+	}
+}
+
+char *
+fe_menu_add (menu_entry *me)
+{
+	char *text;
+
+	menu_foreach_gui (me, menu_add_cb);
+
+	if (!me->markup)
+		return NULL;
+
+	if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL))
+		return NULL;
+
+	/* return the label with markup stripped */
+	return text;
+}
+
+void
+fe_menu_del (menu_entry *me)
+{
+	menu_foreach_gui (me, menu_del_cb);
+}
+
+void
+fe_menu_update (menu_entry *me)
+{
+	menu_foreach_gui (me, menu_update_cb);
+}
+
+/* used to add custom menus to the right-click menu */
+
+static void
+menu_add_plugin_mainmenu_items (GtkWidget *menu)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;	/* outbound.c */
+	while (list)
+	{
+		me = list->data;
+		if (me->is_main)
+			menu_add_cb (menu, me, NULL);
+		list = list->next;
+	}
+}
+
+void
+menu_add_plugin_items (GtkWidget *menu, char *root, char *target)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;	/* outbound.c */
+	while (list)
+	{
+		me = list->data;
+		if (!me->is_main && !strncmp (me->path, root + 1, root[0]))
+			menu_add_cb (menu, me, target);
+		list = list->next;
+	}
+}
+
+/* === END STUFF FOR /MENU === */
+
+GtkWidget *
+menu_create_main (void *accel_group, int bar, int away, int toplevel,
+						GtkWidget **menu_widgets)
+{
+	int i = 0;
+	GtkWidget *item;
+	GtkWidget *menu = 0;
+	GtkWidget *menu_item = 0;
+	GtkWidget *menu_bar;
+	GtkWidget *usermenu = 0;
+	GtkWidget *submenu = 0;
+	int close_mask = GDK_CONTROL_MASK;
+	int away_mask = GDK_MOD1_MASK;
+	char *key_theme = NULL;
+	GtkSettings *settings;
+	GSList *group = NULL;
+
+	if (bar)
+		menu_bar = gtk_menu_bar_new ();
+	else
+		menu_bar = gtk_menu_new ();
+
+	/* /MENU needs to know this later */
+	g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group);
+
+#if GTK_CHECK_VERSION(2,4,0)
+	g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel",
+							G_CALLBACK (menu_canacaccel), 0);
+#endif
+
+	/* set the initial state of toggles */
+	mymenu[MENUBAR_OFFSET].state = !prefs.hidemenu;
+	mymenu[MENUBAR_OFFSET+1].state = prefs.topicbar;
+	mymenu[MENUBAR_OFFSET+2].state = !prefs.hideuserlist;
+	mymenu[MENUBAR_OFFSET+3].state = prefs.userlistbuttons;
+	mymenu[MENUBAR_OFFSET+4].state = prefs.chanmodebuttons;
+
+	mymenu[AWAY_OFFSET].state = away;
+
+	switch (prefs.tab_layout)
+	{
+	case 0:
+		mymenu[TABS_OFFSET].state = 1;
+		mymenu[TABS_OFFSET+1].state = 0;
+		break;
+	default:
+		mymenu[TABS_OFFSET].state = 0;
+		mymenu[TABS_OFFSET+1].state = 1;
+	}
+
+	mymenu[METRE_OFFSET].state = 0;
+	mymenu[METRE_OFFSET+1].state = 0;
+	mymenu[METRE_OFFSET+2].state = 0;
+	mymenu[METRE_OFFSET+3].state = 0;
+	switch (prefs.lagometer)
+	{
+	case 0:
+		mymenu[METRE_OFFSET].state = 1;
+		break;
+	case 1:
+		mymenu[METRE_OFFSET+1].state = 1;
+		break;
+	case 2:
+		mymenu[METRE_OFFSET+2].state = 1;
+		break;
+	default:
+		mymenu[METRE_OFFSET+3].state = 1;
+	}
+
+	/* change Close binding to ctrl-shift-w when using emacs keys */
+	settings = gtk_widget_get_settings (menu_bar);
+	if (settings)
+	{
+		g_object_get (settings, "gtk-key-theme-name", &key_theme, NULL);
+		if (key_theme)
+		{
+			if (!strcasecmp (key_theme, "Emacs"))
+			{
+				close_mask = GDK_SHIFT_MASK | GDK_CONTROL_MASK;
+				mymenu[SEARCH_OFFSET].key = 0;
+			}
+			g_free (key_theme);
+		}
+	}
+
+	/* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */
+	{
+		char *help = _("_Help");
+		char *under = strchr (help, '_');
+		if (under && (under[1] == 'a' || under[1] == 'A'))
+			away_mask = GDK_MOD1_MASK | GDK_CONTROL_MASK;
+	}
+
+	if (!toplevel)
+	{
+		mymenu[DETACH_OFFSET].text = N_("_Detach");
+		mymenu[CLOSE_OFFSET].text = N_("_Close");
+	}
+	else
+	{
+		mymenu[DETACH_OFFSET].text = N_("_Attach");
+		mymenu[CLOSE_OFFSET].text = N_("_Close");
+	}
+
+	while (1)
+	{
+		item = NULL;
+		if (mymenu[i].id == MENU_ID_USERMENU && !prefs.gui_usermenu)
+		{
+			i++;
+			continue;
+		}
+
+		switch (mymenu[i].type)
+		{
+		case M_NEWMENU:
+			if (menu)
+				gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
+			item = menu = gtk_menu_new ();
+			if (mymenu[i].id == MENU_ID_USERMENU)
+				usermenu = menu;
+			menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
+			/* record the English name for /menu */
+			g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item);
+			gtk_widget_show (menu_item);
+			break;
+
+		case M_MENUPIX:
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, FALSE);
+			goto normalitem;
+
+		case M_MENUSTOCK:
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
+			goto normalitem;
+
+		case M_MENUITEM:
+			item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
+normalitem:
+			if (mymenu[i].key != 0)
+				gtk_widget_add_accelerator (item, "activate", accel_group,
+										mymenu[i].key,
+										mymenu[i].key == GDK_F1 ? 0 :
+										mymenu[i].key == GDK_w ? close_mask :
+										GDK_CONTROL_MASK,
+										GTK_ACCEL_VISIBLE);
+			if (mymenu[i].callback)
+				g_signal_connect (G_OBJECT (item), "activate",
+										G_CALLBACK (mymenu[i].callback), 0);
+			if (submenu)
+				gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+			else
+				gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		case M_MENUTOG:
+			item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
+togitem:
+			/* must avoid callback for Radio buttons */
+			GTK_CHECK_MENU_ITEM (item)->active = mymenu[i].state;
+			/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+													 mymenu[i].state);*/
+			if (mymenu[i].key != 0)
+				gtk_widget_add_accelerator (item, "activate", accel_group,
+									mymenu[i].key, mymenu[i].id == MENU_ID_AWAY ?
+									away_mask : GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
+			if (mymenu[i].callback)
+				g_signal_connect (G_OBJECT (item), "toggled",
+										G_CALLBACK (mymenu[i].callback), 0);
+			if (submenu)
+				gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+			else
+				gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			gtk_widget_set_sensitive (item, mymenu[i].sensitive);
+			break;
+
+		case M_MENURADIO:
+			item = gtk_radio_menu_item_new_with_mnemonic (group, _(mymenu[i].text));
+			group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+			goto togitem;
+
+		case M_SEP:
+			item = gtk_menu_item_new ();
+			gtk_widget_set_sensitive (item, FALSE);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		case M_MENUSUB:
+			group = NULL;
+			submenu = gtk_menu_new ();
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
+			/* record the English name for /menu */
+			g_object_set_data (G_OBJECT (item), "name", mymenu[i].text);
+			gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		/*case M_END:*/ default:
+			if (!submenu)
+			{
+				if (menu)
+				{
+					gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
+					menu_add_plugin_mainmenu_items (menu_bar);
+				}
+				if (usermenu)
+					usermenu_create (usermenu);
+				return (menu_bar);
+			}
+			submenu = NULL;
+		}
+
+		/* record this GtkWidget * so it's state might be changed later */
+		if (mymenu[i].id != 0 && menu_widgets)
+			/* this ends up in sess->gui->menu_item[MENU_ID_XXX] */
+			menu_widgets[mymenu[i].id] = item;
+
+		i++;
+	}
+}
diff --git a/src/fe-gtk/menu.h b/src/fe-gtk/menu.h
new file mode 100644
index 00000000..7fef79cd
--- /dev/null
+++ b/src/fe-gtk/menu.h
@@ -0,0 +1,41 @@
+GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
+void menu_urlmenu (GdkEventButton * event, char *url);
+void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
+void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel);
+void menu_nickmenu (session *sess, GdkEventButton * event, char *nick, int num_sel);
+void menu_middlemenu (session *sess, GdkEventButton *event);
+void userlist_button_cb (GtkWidget * button, char *cmd);
+void nick_command_parse (session *sess, char *cmd, char *nick, char *allnick);
+void usermenu_update (void);
+GtkWidget *menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, int state);
+GtkWidget *menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, gpointer userdata, char *icon);
+GtkWidget *menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos);
+GtkWidget *create_icon_menu (char *labeltext, void *stock_name, int is_stock);
+void menu_create (GtkWidget *menu, GSList *list, char *target, int check_path);
+void menu_bar_toggle (void);
+void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
+void menu_change_layout (void);
+
+/* for menu_quick functions */
+#define XCMENU_DOLIST 1
+#define XCMENU_SHADED 1
+#define XCMENU_MARKUP 2
+#define XCMENU_MNEMONIC 4
+
+/* menu items we keep a GtkWidget* for (to change their state) */
+#define MENU_ID_AWAY 1
+#define MENU_ID_MENUBAR 2
+#define MENU_ID_TOPICBAR 3
+#define MENU_ID_USERLIST 4
+#define MENU_ID_ULBUTTONS 5
+#define MENU_ID_MODEBUTTONS 6
+#define MENU_ID_LAYOUT_TABS 7
+#define MENU_ID_LAYOUT_TREE 8
+#define MENU_ID_DISCONNECT 9
+#define MENU_ID_RECONNECT 10
+#define MENU_ID_JOIN 11
+#define MENU_ID_USERMENU 12
+
+#if (MENU_ID_NUM < MENU_ID_USERMENU)
+#error MENU_ID_NUM is set wrong
+#endif
diff --git a/src/fe-gtk/mmx_cmod.S b/src/fe-gtk/mmx_cmod.S
new file mode 100644
index 00000000..12e866de
--- /dev/null
+++ b/src/fe-gtk/mmx_cmod.S
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 1997-2001, Michael Jennings
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies of the Software, its documentation and marketing & publicity
+ * materials, and acknowledgment shall be given in the documentation, materials
+ * and software packages that this Software was used.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* MMX routines for tinting XImages written by Willem Monsuwe <willem@stack.nl> */
+
+/* Function calling conventions:
+ *   shade_ximage_xx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+ */
+
+#define data	8(%ebp)
+#define bpl	12(%ebp)
+#define w	16(%ebp)
+#define h	20(%ebp)
+#define rm	24(%ebp)
+#define gm	28(%ebp)
+#define bm	32(%ebp)
+
+#ifdef UNDERSCORE_SYMBOLS /* need this to link with msvc */
+#define SHADE_XIMAGE_15 _shade_ximage_15_mmx
+#define SHADE_XIMAGE_16 _shade_ximage_16_mmx
+#define SHADE_XIMAGE_32 _shade_ximage_32_mmx
+#define HAVE_MMX _have_mmx
+#else
+#define SHADE_XIMAGE_15 shade_ximage_15_mmx
+#define SHADE_XIMAGE_16 shade_ximage_16_mmx
+#define SHADE_XIMAGE_32 shade_ximage_32_mmx
+#define HAVE_MMX have_mmx
+#endif
+
+.globl SHADE_XIMAGE_15
+.globl SHADE_XIMAGE_16
+.globl SHADE_XIMAGE_32
+.globl HAVE_MMX
+
+.bss
+.text
+.align 8
+
+#define ENTER                   \
+        pushl %ebp              ;\
+        movl %esp, %ebp         ;\
+        pushl %ebx              ;\
+        pushl %ecx              ;\
+        pushl %edx              ;\
+        pushl %edi              ;\
+        pushl %esi              ;\
+        movl data, %esi         ;\
+        movl w, %ebx            ;\
+        movl h, %edx
+
+#define LEAVE                   \
+4:                              ;\
+        emms                    ;\
+        popl %esi               ;\
+        popl %edi               ;\
+        popl %edx               ;\
+        popl %ecx               ;\
+        popl %ebx               ;\
+        movl %ebp, %esp         ;\
+        popl %ebp               ;\
+        ret
+
+
+SHADE_XIMAGE_15:
+        ENTER
+
+        leal -6(%esi, %ebx, 2), %esi
+        negl %ebx
+        jz 5f
+
+        /* Setup multipliers */
+        movd rm, %mm5
+        movd gm, %mm6
+        movd bm, %mm7
+        punpcklwd %mm5, %mm5    /* 00 00 00 00 rm rm rm rm */
+        punpcklwd %mm6, %mm6    /* 00 00 00 00 gm gm gm gm */
+        punpcklwd %mm7, %mm7    /* 00 00 00 00 bm bm bm bm */
+        punpckldq %mm5, %mm5    /* rm rm rm rm rm rm rm rm */
+        punpckldq %mm6, %mm6    /* gm gm gm gm gm gm gm gm */
+        punpckldq %mm7, %mm7    /* bm bm bm bm bm bm bm bm */
+
+        cmpl $256, rm
+        jg shade_ximage_15_mmx_saturate
+        cmpl $256, gm
+        jg shade_ximage_15_mmx_saturate
+        cmpl $256, bm
+        jg shade_ximage_15_mmx_saturate
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+        
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+shade_ximage_15_mmx_saturate:
+
+        pcmpeqw %mm3, %mm3
+        psllw $5, %mm3          /* ff e0 ff e0 ff e0 ff e0 */
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm3, %mm1      /* ff eg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm3, %mm0        /* 00 0r */
+        psubw %mm3, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm3, %mm1      /* ff eg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm3, %mm0        /* 00 0r */
+        psubw %mm3, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+SHADE_XIMAGE_16:
+        ENTER
+
+        leal -6(%esi, %ebx, 2), %esi
+        negl %ebx
+        jz 5f
+
+        /* Setup multipliers */
+        movd rm, %mm5
+        movd gm, %mm6
+        movd bm, %mm7
+        punpcklwd %mm5, %mm5    /* 00 00 00 00 rm rm rm rm */
+        punpcklwd %mm6, %mm6    /* 00 00 00 00 gm gm gm gm */
+        punpcklwd %mm7, %mm7    /* 00 00 00 00 bm bm bm bm */
+        punpckldq %mm5, %mm5    /* rm rm rm rm rm rm rm rm */
+        punpckldq %mm6, %mm6    /* gm gm gm gm gm gm gm gm */
+        punpckldq %mm7, %mm7    /* bm bm bm bm bm bm bm bm */
+
+        cmpl $256, rm
+        jg shade_ximage_16_mmx_saturate
+        cmpl $256, gm
+        jg shade_ximage_16_mmx_saturate
+        cmpl $256, bm
+        jg shade_ximage_16_mmx_saturate
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+        
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+	jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+shade_ximage_16_mmx_saturate:
+
+        pcmpeqw %mm3, %mm3
+        movq %mm3, %mm4
+        psllw $5, %mm3          /* ff e0 ff e0 ff e0 ff e0 */
+        psllw $6, %mm4          /* ff c0 ff c0 ff c0 ff c0 */
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm4, %mm1      /* ff cg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm4, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm4, %mm1      /* ff cg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm4, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+SHADE_XIMAGE_32:
+        ENTER
+
+        leal (%esi, %ebx, 4), %esi
+        negl %ebx
+        jz 3f
+
+        movd rm, %mm4
+        movd gm, %mm5
+        movd bm, %mm6
+        psllq $32, %mm4
+        psllq $16, %mm5
+        por %mm6, %mm4
+        por %mm5, %mm4
+
+        pcmpeqw %mm6, %mm6
+        psllw $15, %mm6                 /* 80 00 80 00 80 00 80 00 */
+        movq %mm6, %mm5
+        pmulhw %mm4, %mm5               /* Get correction factor */
+1:
+        movl %ebx, %ecx
+2:
+        movd (%esi, %ecx, 4), %mm1      /* 00 rr gg bb */
+        pxor %mm0, %mm0
+        punpcklbw %mm1, %mm0            /* 00 00 rr 00 gg 00 bb 00 */
+        pxor %mm6, %mm0                 /* Flip sign */
+
+        pmulhw %mm4, %mm0               /* 00 00 xx rr xx gg xx bb */
+        psubw %mm5, %mm0                /* Correct range */
+        packuswb %mm0, %mm0             /* 00 rr gg bb 00 rr gg bb */
+
+        movd %mm0, (%esi, %ecx, 4)
+
+        incl %ecx
+        jnz 2b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+3:
+        LEAVE
+
+
+HAVE_MMX:
+	push	%ebx
+/* Check if bit 21 in flags word is writeable */
+	pushfl	
+	popl	%eax
+	movl	%eax,%ebx
+	xorl	$0x00200000, %eax
+	pushl	%eax
+	popfl
+	pushfl
+	popl	%eax
+
+	cmpl	%eax, %ebx
+	je	8f
+
+/* OK, we have CPUID */
+
+	movl	$1, %eax
+	cpuid
+	
+	test	$0x00800000, %edx
+	jz	8f
+
+	movl	$1, %eax	/* success, have mmx */
+	popl	%ebx
+	ret
+
+8:
+	xorl	%eax,%eax	/* failed, no mmx */
+	popl	%ebx
+	ret
+
+#if defined(__GNUC__) && !defined(_WIN32)
+.section .note.GNU-stack, "", @progbits
+.previous
+#endif
diff --git a/src/fe-gtk/mmx_cmod.h b/src/fe-gtk/mmx_cmod.h
new file mode 100644
index 00000000..52d07102
--- /dev/null
+++ b/src/fe-gtk/mmx_cmod.h
@@ -0,0 +1,4 @@
+void shade_ximage_15_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+void shade_ximage_16_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+void shade_ximage_32_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+int have_mmx (void);
diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c
new file mode 100644
index 00000000..5acb683a
--- /dev/null
+++ b/src/fe-gtk/notifygui.c
@@ -0,0 +1,442 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#include "../common/notify.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/util.h"
+#include "../common/userlist.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "palette.h"
+#include "notifygui.h"
+
+
+/* model for the notify treeview */
+enum
+{
+	USER_COLUMN,
+	STATUS_COLUMN,
+	SERVER_COLUMN,
+	SEEN_COLUMN,
+	COLOUR_COLUMN,
+	NPS_COLUMN, 	/* struct notify_per_server * */
+	N_COLUMNS
+};
+
+
+static GtkWidget *notify_window = 0;
+static GtkWidget *notify_button_opendialog;
+static GtkWidget *notify_button_remove;
+
+
+static void
+notify_closegui (void)
+{
+	notify_window = 0;
+}
+
+/* Need this to be able to set the foreground colour property of a row
+ * from a GdkColor * in the model  -Vince
+ */
+static void
+notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell,
+                                 GtkTreeModel *model, GtkTreeIter *iter,
+                                 gpointer data)
+{
+	gchar *text;
+	GdkColor *colour;
+	int model_column = GPOINTER_TO_INT (data);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (model), iter, 
+	                    COLOUR_COLUMN, &colour,
+	                    model_column, &text, -1);
+	g_object_set (G_OBJECT (cell), "text", text, NULL);
+	g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL);
+	g_free (text);
+}
+
+static void
+notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view)
+{
+	GtkTreeIter iter;
+	struct notify_per_server *servnot;
+
+	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
+	{
+		gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0);
+		gtk_widget_set_sensitive (notify_button_remove, TRUE);
+		return;
+	}
+
+	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
+	gtk_widget_set_sensitive (notify_button_remove, FALSE);
+}
+
+static GtkWidget *
+notify_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_POINTER,	/* can't specify colour! */
+										 G_TYPE_POINTER
+	                           );
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
+	                             notify_treecell_property_mapper,
+	                             USER_COLUMN, _("Name"),
+	                             STATUS_COLUMN, _("Status"),
+	                             SERVER_COLUMN, _("Network"),
+	                             SEEN_COLUMN, _("Last Seen"), -1);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);
+
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+			gtk_tree_view_column_set_alignment (col, 0.5);
+
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
+							"changed", G_CALLBACK (notify_row_cb), view);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+void
+notify_gui_update (void)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+	GSList *slist;
+	gchar *name, *status, *server, *seen;
+	int online, servcount;
+	time_t lastseen;
+	char agobuf[128];
+
+	GtkListStore *store;
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	gboolean valid;	/* true if we don't need to append a new tree row */
+
+	if (!notify_window)
+		return;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		name = notify->name;
+		status = _("Offline");
+		server = "";
+
+		online = FALSE;
+		lastseen = 0;
+		/* First see if they're online on any servers */
+		slist = notify->server_list;
+		while (slist)
+		{
+			servnot = (struct notify_per_server *) slist->data;
+			if (servnot->ison)
+				online = TRUE;
+			if (servnot->lastseen > lastseen)
+				lastseen = servnot->lastseen;
+			slist = slist->next;
+		}
+
+		if (!online)				  /* Offline on all servers */
+		{
+			if (!lastseen)
+				seen = _("Never");
+			else
+			{
+				snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
+				seen = agobuf;
+			}
+			if (!valid)	/* create new tree row if required */
+				gtk_list_store_append (store, &iter);
+			gtk_list_store_set (store, &iter, 0, name, 1, status,
+			                    2, server, 3, seen, 4, &colors[4], 5, NULL, -1);
+			if (valid)
+				valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
+
+		} else
+		{
+			/* Online - add one line per server */
+			servcount = 0;
+			slist = notify->server_list;
+			status = _("Online");
+			while (slist)
+			{
+				servnot = (struct notify_per_server *) slist->data;
+				if (servnot->ison)
+				{
+					if (servcount > 0)
+						name = "";
+					server = server_get_network (servnot->server, TRUE);
+
+					snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
+					seen = agobuf;
+
+					if (!valid)	/* create new tree row if required */
+						gtk_list_store_append (store, &iter);
+					gtk_list_store_set (store, &iter, 0, name, 1, status,
+					                    2, server, 3, seen, 4, &colors[3], 5, servnot, -1);
+					if (valid)
+						valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
+
+					servcount++;
+				}
+				slist = slist->next;
+			}
+		}
+		
+		list = list->next;
+	}
+
+	while (valid)
+	{
+		GtkTreeIter old = iter;
+		/* get next iter now because removing invalidates old one */
+		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
+                                      &iter);
+		gtk_list_store_remove (store, &old);
+	}
+}
+
+static void
+notify_opendialog_clicked (GtkWidget * igad)
+{
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	struct notify_per_server *servnot;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
+	{
+		if (servnot)
+			open_query (servnot->server, servnot->notify->name, TRUE);
+	}
+}
+
+static void
+notify_remove_clicked (GtkWidget * igad)
+{
+	GtkTreeView *view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *path = NULL;
+	gboolean found = FALSE;
+	char *name;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1))
+	{
+		model = gtk_tree_view_get_model (view);
+		found = (*name != 0);
+		while (!found)	/* the real nick is some previous node */
+		{
+			g_free (name); /* it's useless to us */
+			if (!path)
+				path = gtk_tree_model_get_path (model, &iter);
+			if (!gtk_tree_path_prev (path))	/* arrgh! no previous node! */
+			{
+				g_warning ("notify list state is invalid\n");
+				break;
+			}
+			if (!gtk_tree_model_get_iter (model, &iter, path))
+				break;
+			gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1);
+			found = (*name != 0);
+		}
+		if (path)
+			gtk_tree_path_free (path);
+		if (!found)
+			return;
+
+		/* ok, now we can remove it */
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+		notify_deluser (name);
+		g_free (name);
+	}
+}
+
+static void
+notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry)
+{
+	char *networks;
+	char *text;
+
+	text = GTK_ENTRY (entry)->text;
+	if (text[0] && response == GTK_RESPONSE_ACCEPT)
+	{
+		networks = GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net"))->text;
+		if (strcasecmp (networks, "ALL") == 0 || networks[0] == 0)
+			notify_adduser (text, NULL);
+		else
+			notify_adduser (text, networks);
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog)
+{
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+}
+
+void
+fe_notify_ask (char *nick, char *networks)
+{
+	GtkWidget *dialog;
+	GtkWidget *entry;
+	GtkWidget *label;
+	GtkWidget *wid;
+	GtkWidget *table;
+	char *msg = _("Enter nickname to add:");
+	char buf[256];
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	if (parent_window)
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+
+	table = gtk_table_new (2, 3, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (table), 12);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 8);
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), table);
+
+	label = gtk_label_new (msg);
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
+
+	entry = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry), nick);
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (notifygui_add_enter), dialog);
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (notifygui_add_cb), entry);
+
+	label = gtk_label_new (_("Notify on these networks:"));
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3);
+
+	wid = gtk_entry_new ();
+	g_object_set_data (G_OBJECT (entry), "net", wid);
+	g_signal_connect (G_OBJECT (wid), "activate",
+						 	G_CALLBACK (notifygui_add_enter), dialog);
+	gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL");
+	gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3);
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted."));
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4);
+
+	gtk_widget_show_all (dialog);
+}
+
+static void
+notify_add_clicked (GtkWidget * igad)
+{
+	fe_notify_ask ("", NULL);
+}
+
+void
+notify_opengui (void)
+{
+	GtkWidget *vbox, *bbox;
+	GtkWidget *view;
+
+	if (notify_window)
+	{
+		mg_bring_tofront (notify_window);
+		return;
+	}
+
+	notify_window =
+		mg_create_generic_tab ("Notify", _("XChat: Friends List"), FALSE, TRUE,
+		                       notify_closegui, NULL, 400, 250, &vbox, 0);
+
+	view = notify_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (notify_window), "view", view);
+  
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0);
+	gtk_widget_show (bbox);
+
+	gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0,
+	                _("Add..."));
+
+	notify_button_remove =
+	gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0,
+	                _("Remove"));
+
+	notify_button_opendialog =
+	gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0,
+	                _("Open Dialog"));
+
+	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
+	gtk_widget_set_sensitive (notify_button_remove, FALSE);
+
+	notify_gui_update ();
+
+	gtk_widget_show (notify_window);
+}
diff --git a/src/fe-gtk/notifygui.h b/src/fe-gtk/notifygui.h
new file mode 100644
index 00000000..360834dc
--- /dev/null
+++ b/src/fe-gtk/notifygui.h
@@ -0,0 +1,2 @@
+void notify_gui_update (void);
+void notify_opengui (void);
diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c
new file mode 100644
index 00000000..ebae92ff
--- /dev/null
+++ b/src/fe-gtk/palette.c
@@ -0,0 +1,226 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "fe-gtk.h"
+#include "palette.h"
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "../common/cfgfiles.h"
+
+
+GdkColor colors[] = {
+	/* colors for xtext */
+	{0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */
+	{0, 0x0000, 0x0000, 0x0000}, /* 17 black */
+	{0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */
+	{0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */
+	{0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */
+	{0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */
+	{0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */
+	{0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */
+	{0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */
+	{0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */
+	{0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */
+	{0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */
+	{0, 0x451e, 0x451e, 0xe666}, /* 28 blue */
+	{0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */
+	{0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */
+	{0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */
+
+	{0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */
+	{0, 0x0000, 0x0000, 0x0000}, /* 17 black */
+	{0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */
+	{0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */
+	{0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */
+	{0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */
+	{0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */
+	{0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */
+	{0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */
+	{0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */
+	{0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */
+	{0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */
+	{0, 0x451e, 0x451e, 0xe666}, /* 28 blue */
+	{0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */
+	{0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */
+	{0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */
+
+	{0, 0xffff, 0xffff, 0xffff}, /* 32 marktext Fore (white) */
+	{0, 0x3535, 0x6e6e, 0xc1c1}, /* 33 marktext Back (blue) */
+	{0, 0x0000, 0x0000, 0x0000}, /* 34 foreground (black) */
+	{0, 0xf0f0, 0xf0f0, 0xf0f0}, /* 35 background (white) */
+	{0, 0xcccc, 0x1010, 0x1010}, /* 36 marker line (red) */
+
+	/* colors for GUI */
+	{0, 0x9999, 0x0000, 0x0000}, /* 37 tab New Data (dark red) */
+	{0, 0x0000, 0x0000, 0xffff}, /* 38 tab Nick Mentioned (blue) */
+	{0, 0xffff, 0x0000, 0x0000}, /* 39 tab New Message (red) */
+	{0, 0x9595, 0x9595, 0x9595}, /* 40 away user (grey) */
+};
+#define MAX_COL 40
+
+
+void
+palette_alloc (GtkWidget * widget)
+{
+	int i;
+	static int done_alloc = FALSE;
+	GdkColormap *cmap;
+
+	if (!done_alloc)		  /* don't do it again */
+	{
+		done_alloc = TRUE;
+		cmap = gtk_widget_get_colormap (widget);
+		for (i = MAX_COL; i >= 0; i--)
+			gdk_colormap_alloc_color (cmap, &colors[i], FALSE, TRUE);
+	}
+}
+
+/* maps XChat 2.0.x colors to current */
+static const int remap[] =
+{
+	0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
+	33,	/* 16:marktextback */
+	32,	/* 17:marktextfore */
+	34,	/* 18: fg */
+	35,	/* 19: bg */
+	37,	/* 20: newdata */
+	38,	/* 21: blue */
+	39,	/* 22: newmsg */
+	40		/* 23: away */
+};
+
+void
+palette_load (void)
+{
+	int i, j, l, fh, res;
+	char prefname[256];
+	struct stat st;
+	char *cfg;
+	int red, green, blue;
+	int upgrade = FALSE;
+
+	fh = xchat_open_file ("colors.conf", O_RDONLY, 0, 0);
+	if (fh == -1)
+	{
+		fh = xchat_open_file ("palette.conf", O_RDONLY, 0, 0);
+		upgrade = TRUE;
+	}
+
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		cfg = malloc (st.st_size + 1);
+		if (cfg)
+		{
+			cfg[0] = '\0';
+			l = read (fh, cfg, st.st_size);
+			if (l >= 0)
+				cfg[l] = '\0';
+
+			if (!upgrade)
+			{
+				/* mIRC colors 0-31 are here */
+				for (i = 0; i < 32; i++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d", i);
+					cfg_get_color (cfg, prefname, &red, &green, &blue);
+					colors[i].red = red;
+					colors[i].green = green;
+					colors[i].blue = blue;
+				}
+
+				/* our special colors are mapped at 256+ */
+				for (i = 256, j = 32; j < MAX_COL+1; i++, j++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d", i);
+					cfg_get_color (cfg, prefname, &red, &green, &blue);
+					colors[j].red = red;
+					colors[j].green = green;
+					colors[j].blue = blue;
+				}
+
+			} else
+			{
+				/* loading 2.0.x palette.conf */
+				for (i = 0; i < MAX_COL+1; i++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d_red", i);
+					red = cfg_get_int (cfg, prefname);
+
+					snprintf (prefname, sizeof prefname, "color_%d_grn", i);
+					green = cfg_get_int (cfg, prefname);
+
+					snprintf (prefname, sizeof prefname, "color_%d_blu", i);
+					blue = cfg_get_int_with_result (cfg, prefname, &res);
+
+					if (res)
+					{
+						colors[remap[i]].red = red;
+						colors[remap[i]].green = green;
+						colors[remap[i]].blue = blue;
+					}
+				}
+
+				/* copy 0-15 to 16-31 */
+				for (i = 0; i < 16; i++)
+				{
+					colors[i+16].red = colors[i].red;
+					colors[i+16].green = colors[i].green;
+					colors[i+16].blue = colors[i].blue;
+				}
+			}
+			free (cfg);
+		}
+		close (fh);
+	}
+}
+
+void
+palette_save (void)
+{
+	int i, j, fh;
+	char prefname[256];
+
+	fh = xchat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		/* mIRC colors 0-31 are here */
+		for (i = 0; i < 32; i++)
+		{
+			snprintf (prefname, sizeof prefname, "color_%d", i);
+			cfg_put_color (fh, colors[i].red, colors[i].green, colors[i].blue, prefname);
+		}
+
+		/* our special colors are mapped at 256+ */
+		for (i = 256, j = 32; j < MAX_COL+1; i++, j++)
+		{
+			snprintf (prefname, sizeof prefname, "color_%d", i);
+			cfg_put_color (fh, colors[j].red, colors[j].green, colors[j].blue, prefname);
+		}
+
+		close (fh);
+	}
+}
diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h
new file mode 100644
index 00000000..c97693bb
--- /dev/null
+++ b/src/fe-gtk/palette.h
@@ -0,0 +1,15 @@
+extern GdkColor colors[];
+
+#define COL_MARK_FG 32
+#define COL_MARK_BG 33
+#define COL_FG 34
+#define COL_BG 35
+#define COL_MARKER 36
+#define COL_NEW_DATA 37
+#define COL_HILIGHT 38
+#define COL_NEW_MSG 39
+#define COL_AWAY 40
+
+void palette_alloc (GtkWidget * widget);
+void palette_load (void);
+void palette_save (void);
diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c
new file mode 100644
index 00000000..3d85c3b0
--- /dev/null
+++ b/src/fe-gtk/pixmaps.c
@@ -0,0 +1,123 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+#include "../common/xchat.h"
+#include "../common/fe.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#include <gtk/gtkstock.h>
+
+#include "../pixmaps/inline_pngs.h"
+
+GdkPixbuf *pix_xchat;
+GdkPixbuf *pix_book;
+
+GdkPixbuf *pix_purple;
+GdkPixbuf *pix_red;
+GdkPixbuf *pix_op;
+GdkPixbuf *pix_hop;
+GdkPixbuf *pix_voice;
+
+GdkPixbuf *pix_tray_msg;
+GdkPixbuf *pix_tray_hilight;
+GdkPixbuf *pix_tray_file;
+
+GdkPixbuf *pix_channel;
+GdkPixbuf *pix_dialog;
+GdkPixbuf *pix_server;
+GdkPixbuf *pix_util;
+
+
+static GdkPixmap *
+pixmap_load_from_file_real (char *file)
+{
+	GdkPixbuf *img;
+	GdkPixmap *pixmap;
+
+	img = gdk_pixbuf_new_from_file (file, 0);
+	if (!img)
+		return NULL;
+	gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128);
+	gdk_pixbuf_unref (img);
+
+	return pixmap;
+}
+
+GdkPixmap *
+pixmap_load_from_file (char *filename)
+{
+	char buf[256];
+	GdkPixmap *pix;
+
+	if (filename[0] == '\0')
+		return NULL;
+
+	pix = pixmap_load_from_file_real (filename);
+	if (pix == NULL)
+	{
+		strcpy (buf, "Cannot open:\n\n");
+		strncpy (buf + 14, filename, sizeof (buf) - 14);
+		buf[sizeof (buf) - 1] = 0;
+		fe_message (buf, FE_MSG_ERROR);
+	}
+
+	return pix;
+}
+
+#define LOADPIX(vv,pp,ff) \
+	vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); \
+	if (!vv) \
+		vv = gdk_pixbuf_new_from_inline (-1, pp, FALSE, 0);
+
+#define LOADPIX_DISKONLY(vv,ff) \
+	vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0);
+
+#define EXT ".png"
+
+void
+pixmaps_init (void)
+{
+	pix_book = gdk_pixbuf_new_from_inline (-1, bookpng, FALSE, 0);
+
+	/* used in About window, tray icon and WindowManager icon. */
+	LOADPIX (pix_xchat, xchatpng, "xchat"EXT);
+
+	/* userlist icons, with inlined defaults */
+	LOADPIX (pix_hop, hoppng, "hop"EXT);
+	LOADPIX (pix_purple, purplepng, "purple"EXT);
+	LOADPIX (pix_red, redpng, "red"EXT);
+	LOADPIX (pix_op, oppng, "op"EXT);
+	LOADPIX (pix_voice, voicepng, "voice"EXT);
+
+	/* tray icons, with inlined defaults */
+	LOADPIX (pix_tray_msg, traymsgpng, "message"EXT);
+	LOADPIX (pix_tray_hilight, trayhilightpng, "highlight"EXT);
+	LOADPIX (pix_tray_file, trayfilepng, "fileoffer"EXT);
+
+	/* treeview icons, no defaults, load from disk only */
+	LOADPIX_DISKONLY (pix_channel,	"channel"EXT);
+	LOADPIX_DISKONLY (pix_dialog,		"dialog"EXT);
+	LOADPIX_DISKONLY (pix_server,		"server"EXT);
+	LOADPIX_DISKONLY (pix_util,		"util"EXT);
+}
diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h
new file mode 100644
index 00000000..91b9696e
--- /dev/null
+++ b/src/fe-gtk/pixmaps.h
@@ -0,0 +1,19 @@
+extern GdkPixbuf *pix_book;
+extern GdkPixbuf *pix_hop;
+extern GdkPixbuf *pix_purple;
+extern GdkPixbuf *pix_red;
+extern GdkPixbuf *pix_op;
+extern GdkPixbuf *pix_voice;
+extern GdkPixbuf *pix_xchat;
+
+extern GdkPixbuf *pix_tray_msg;
+extern GdkPixbuf *pix_tray_hilight;
+extern GdkPixbuf *pix_tray_file;
+
+extern GdkPixbuf *pix_channel;
+extern GdkPixbuf *pix_dialog;
+extern GdkPixbuf *pix_server;
+extern GdkPixbuf *pix_util;
+
+extern GdkPixmap *pixmap_load_from_file (char *file);
+extern void pixmaps_init (void);
diff --git a/src/fe-gtk/plugin-tray.c b/src/fe-gtk/plugin-tray.c
new file mode 100644
index 00000000..8603abf4
--- /dev/null
+++ b/src/fe-gtk/plugin-tray.c
@@ -0,0 +1,852 @@
+/* Copyright (C) 2006-2007 Peter Zelezny. */
+
+#include <string.h>
+#include <unistd.h>
+#include "../common/xchat-plugin.h"
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/inbound.h"
+#include "../common/server.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "fe-gtk.h"
+#include "pixmaps.h"
+#include "maingui.h"
+#include "menu.h"
+#include <gtk/gtk.h>
+
+#define LIBNOTIFY
+
+typedef enum	/* current icon status */
+{
+	TS_NONE,
+	TS_MESSAGE,
+	TS_HIGHLIGHT,
+	TS_FILEOFFER,
+	TS_CUSTOM /* plugin */
+} TrayStatus;
+
+typedef enum
+{
+	WS_FOCUSED,
+	WS_NORMAL,
+	WS_HIDDEN
+} WinStatus;
+
+typedef GdkPixbuf* TrayIcon;
+#define tray_icon_from_file(f) gdk_pixbuf_new_from_file(f,NULL)
+#define tray_icon_free(i) g_object_unref(i)
+
+#define ICON_NORMAL pix_xchat
+#define ICON_MSG pix_tray_msg
+#define ICON_HILIGHT pix_tray_hilight
+#define ICON_FILE pix_tray_file
+#define TIMEOUT 500
+
+static GtkStatusIcon *sticon;
+static gint flash_tag;
+static TrayStatus tray_status;
+static xchat_plugin *ph;
+
+static TrayIcon custom_icon1;
+static TrayIcon custom_icon2;
+
+static int tray_priv_count = 0;
+static int tray_pub_count = 0;
+static int tray_hilight_count = 0;
+static int tray_file_count = 0;
+
+
+void tray_apply_setup (void);
+
+
+static WinStatus
+tray_get_window_status (void)
+{
+	const char *st;
+
+	st = xchat_get_info (ph, "win_status");
+
+	if (!st)
+		return WS_HIDDEN;
+
+	if (!strcmp (st, "active"))
+		return WS_FOCUSED;
+
+	if (!strcmp (st, "hidden"))
+		return WS_HIDDEN;
+
+	return WS_NORMAL;
+}
+
+static int
+tray_count_channels (void)
+{
+	int cons = 0;
+	GSList *list;
+	session *sess;
+
+	for (list = sess_list; list; list = list->next)
+	{
+		sess = list->data;
+		if (sess->server->connected && sess->channel[0] &&
+			 sess->type == SESS_CHANNEL)
+			cons++;
+	}
+	return cons;
+}
+
+static int
+tray_count_networks (void)
+{
+	int cons = 0;
+	GSList *list;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		if (((server *)list->data)->connected)
+			cons++;
+	}
+	return cons;
+}
+
+void
+fe_tray_set_tooltip (const char *text)
+{
+	if (sticon)
+		gtk_status_icon_set_tooltip (sticon, text);
+}
+
+#ifdef LIBNOTIFY
+
+/* dynamic access to libnotify.so */
+
+static void *nn_mod = NULL;
+/* prototypes */
+static gboolean (*nn_init) (char *);
+static void (*nn_uninit) (void);
+/* recent versions of libnotify don't take the fourth GtkWidget argument, but passing an
+ * extra NULL argument will be fine */
+static void *(*nn_new) (const gchar *summary, const gchar *message, const gchar *icon, gpointer dummy);
+static gboolean (*nn_show) (void *noti, GError **error);
+static void (*nn_set_timeout) (void *noti, gint timeout);
+
+static void
+libnotify_cleanup (void)
+{
+	if (nn_mod)
+	{
+		nn_uninit ();
+		g_module_close (nn_mod);
+		nn_mod = NULL;
+	}
+}
+
+static gboolean
+libnotify_notify_new (const char *title, const char *text, GtkStatusIcon *icon)
+{
+	void *noti;
+
+	if (!nn_mod)
+	{
+		nn_mod = g_module_open ("libnotify", G_MODULE_BIND_LAZY);
+		if (!nn_mod)
+		{
+			nn_mod = g_module_open ("libnotify.so.1", G_MODULE_BIND_LAZY);
+			if (!nn_mod)
+				return FALSE;
+		}
+
+		if (!g_module_symbol (nn_mod, "notify_init", (gpointer)&nn_init))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_uninit", (gpointer)&nn_uninit))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_new", (gpointer)&nn_new))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_show", (gpointer)&nn_show))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_set_timeout", (gpointer)&nn_set_timeout))
+			goto bad;
+		if (!nn_init (PACKAGE_NAME))
+			goto bad;
+	}
+
+	text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP);
+	title = strip_color (title, -1, STRIP_ALL);
+	noti = nn_new (title, text, XCHATSHAREDIR"/pixmaps/xchat.png", NULL);
+	g_free ((char *)title);
+	g_free ((char *)text);
+
+	nn_set_timeout (noti, prefs.input_balloon_time*1000);
+	nn_show (noti, NULL);
+	g_object_unref (G_OBJECT (noti));
+
+	return TRUE;
+
+bad:
+	g_module_close (nn_mod);
+	nn_mod = NULL;
+	return FALSE;
+}
+
+#endif
+
+void
+fe_tray_set_balloon (const char *title, const char *text)
+{
+#ifndef WIN32
+	const char *argv[8];
+	const char *path;
+	char time[16];
+	WinStatus ws;
+
+	/* no balloons if the window is focused */
+	ws = tray_get_window_status ();
+	if (ws == WS_FOCUSED)
+		return;
+
+	/* bit 1 of flags means "no balloons unless hidden/iconified" */
+	if (ws != WS_HIDDEN && (prefs.gui_tray_flags & 2))
+		return;
+
+	/* FIXME: this should close the current balloon */
+	if (!text)
+		return;
+
+#ifdef LIBNOTIFY
+	/* try it via libnotify.so */
+	if (libnotify_notify_new (title, text, sticon))
+		return;	/* success */
+#endif
+
+	/* try it the crude way */
+	path = g_find_program_in_path ("notify-send");
+	if (path)
+	{
+		sprintf(time, "%d000",prefs.input_balloon_time);
+		argv[0] = path;
+		argv[1] = "-i";
+		argv[2] = "gtk-dialog-info";
+		if (access (XCHATSHAREDIR"/pixmaps/xchat.png", R_OK) == 0)
+			argv[2] = XCHATSHAREDIR"/pixmaps/xchat.png";
+		argv[3] = "-t";
+		argv[4] = time;
+		argv[5] = title;
+		text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP);
+		argv[6] = text;
+		argv[7] = NULL;
+		xchat_execv (argv);
+		g_free ((char *)path);
+		g_free ((char *)text);
+	}
+	else
+	{
+		/* show this error only once */
+		static unsigned char said_it = FALSE;
+		if (!said_it)
+		{
+			said_it = TRUE;
+			fe_message (_("Cannot find 'notify-send' to open balloon alerts.\nPlease install libnotify."), FE_MSG_ERROR);
+		}
+	}
+#endif
+}
+
+static void
+tray_set_balloonf (const char *text, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	fe_tray_set_balloon (buf, text);
+	g_free (buf);
+}
+
+static void
+tray_set_tipf (const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	fe_tray_set_tooltip (buf);
+	g_free (buf);
+}
+
+static void
+tray_stop_flash (void)
+{
+	int nets, chans;
+
+	if (flash_tag)
+	{
+		g_source_remove (flash_tag);
+		flash_tag = 0;
+	}
+
+	if (sticon)
+	{
+		gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+		nets = tray_count_networks ();
+		chans = tray_count_channels ();
+		if (nets)
+			tray_set_tipf (_("XChat: Connected to %u networks and %u channels"),
+								nets, chans);
+		else
+			tray_set_tipf ("XChat: %s", _("Not connected."));
+	}
+
+	if (custom_icon1)
+	{
+		tray_icon_free (custom_icon1);
+		custom_icon1 = NULL;
+	}
+
+	if (custom_icon2)
+	{
+		tray_icon_free (custom_icon2);
+		custom_icon2 = NULL;
+	}
+
+	tray_status = TS_NONE;
+}
+
+static void
+tray_reset_counts (void)
+{
+	tray_priv_count = 0;
+	tray_pub_count = 0;
+	tray_hilight_count = 0;
+	tray_file_count = 0;
+}
+
+static int
+tray_timeout_cb (TrayIcon icon)
+{
+	if (custom_icon1)
+	{
+		if (gtk_status_icon_get_pixbuf (sticon) == custom_icon1)
+		{
+			if (custom_icon2)
+				gtk_status_icon_set_from_pixbuf (sticon, custom_icon2);
+			else
+				gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+		}
+		else
+		{
+			gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+		}
+	}
+	else
+	{
+		if (gtk_status_icon_get_pixbuf (sticon) == ICON_NORMAL)
+			gtk_status_icon_set_from_pixbuf (sticon, icon);
+		else
+			gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+	}
+	return 1;
+}
+
+static void
+tray_set_flash (TrayIcon icon)
+{
+	if (!sticon)
+		return;
+
+	/* already flashing the same icon */
+	if (flash_tag && gtk_status_icon_get_pixbuf (sticon) == icon)
+		return;
+
+	/* no flashing if window is focused */
+	if (tray_get_window_status () == WS_FOCUSED)
+		return;
+
+	tray_stop_flash ();
+
+	gtk_status_icon_set_from_pixbuf (sticon, icon);
+	flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, icon);
+}
+
+void
+fe_tray_set_flash (const char *filename1, const char *filename2, int tout)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	if (tout == -1)
+		tout = TIMEOUT;
+
+	custom_icon1 = tray_icon_from_file (filename1);
+	if (filename2)
+		custom_icon2 = tray_icon_from_file (filename2);
+
+	gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+	flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL);
+	tray_status = TS_CUSTOM;
+}
+
+void
+fe_tray_set_icon (feicon icon)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	switch (icon)
+	{
+	case FE_ICON_NORMAL:
+		break;
+	case FE_ICON_MESSAGE:
+		tray_set_flash (ICON_MSG);
+		break;
+	case FE_ICON_HIGHLIGHT:
+	case FE_ICON_PRIVMSG:
+		tray_set_flash (ICON_HILIGHT);
+		break;
+	case FE_ICON_FILEOFFER:
+		tray_set_flash (ICON_FILE);
+	}
+}
+
+void
+fe_tray_set_file (const char *filename)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	if (filename)
+	{
+		custom_icon1 = tray_icon_from_file (filename);
+		gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+		tray_status = TS_CUSTOM;
+	}
+}
+
+gboolean
+tray_toggle_visibility (gboolean force_hide)
+{
+	static int x, y;
+	static GdkScreen *screen;
+	GtkWindow *win;
+
+	if (!sticon)
+		return FALSE;
+
+	/* ph may have an invalid context now */
+	xchat_set_context (ph, xchat_find_context (ph, NULL, NULL));
+
+	win = (GtkWindow *)xchat_get_info (ph, "win_ptr");
+
+	tray_stop_flash ();
+	tray_reset_counts ();
+
+	if (!win)
+		return FALSE;
+
+#if GTK_CHECK_VERSION(2,20,0)
+	if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win)))
+#else
+	if (force_hide || GTK_WIDGET_VISIBLE (win))
+#endif
+	{
+		gtk_window_get_position (win, &x, &y);
+		screen = gtk_window_get_screen (win);
+		gtk_widget_hide (GTK_WIDGET (win));
+	}
+	else
+	{
+		gtk_window_set_screen (win, screen);
+		gtk_window_move (win, x, y);
+		gtk_widget_show (GTK_WIDGET (win));
+		gtk_window_present (win);
+	}
+
+	return TRUE;
+}
+
+static void
+tray_menu_restore_cb (GtkWidget *item, gpointer userdata)
+{
+	tray_toggle_visibility (FALSE);
+}
+
+static void
+tray_menu_quit_cb (GtkWidget *item, gpointer userdata)
+{
+	mg_open_quit_dialog (FALSE);
+}
+
+/* returns 0-mixed 1-away 2-back */
+
+static int
+tray_find_away_status (void)
+{
+	GSList *list;
+	server *serv;
+	int away = 0;
+	int back = 0;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		serv = list->data;
+
+		if (serv->is_away || serv->reconnect_away)
+			away++;
+		else
+			back++;
+	}
+
+	if (away && back)
+		return 0;
+
+	if (away)
+		return 1;
+
+	return 2;
+}
+
+static void
+tray_foreach_server (GtkWidget *item, char *cmd)
+{
+	GSList *list;
+	server *serv;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		serv = list->data;
+		if (serv->connected)
+			handle_command (serv->server_session, cmd, FALSE);
+	}
+}
+
+static GtkWidget *
+tray_make_item (GtkWidget *menu, char *label, void *callback, void *userdata)
+{
+	GtkWidget *item;
+
+	if (label)
+		item = gtk_menu_item_new_with_mnemonic (label);
+	else
+		item = gtk_menu_item_new ();
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+static void
+tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting)
+{
+	*setting = item->active;
+}
+
+static void
+blink_item (unsigned int *setting, GtkWidget *menu, char *label)
+{
+	menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting);
+}
+
+static void
+tray_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+static void
+tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
+{
+	GtkWidget *menu;
+	GtkWidget *submenu;
+	GtkWidget *item;
+	int away_status;
+
+	/* ph may have an invalid context now */
+	xchat_set_context (ph, xchat_find_context (ph, NULL, NULL));
+
+	menu = gtk_menu_new ();
+	/*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/
+
+	if (tray_get_window_status () == WS_HIDDEN)
+		tray_make_item (menu, _("_Restore"), tray_menu_restore_cb, NULL);
+	else
+		tray_make_item (menu, _("_Hide"), tray_menu_restore_cb, NULL);
+	tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
+
+	submenu = mg_submenu (menu, _("_Blink on"));
+	blink_item (&prefs.input_tray_chans, submenu, _("Channel Message"));
+	blink_item (&prefs.input_tray_priv, submenu, _("Private Message"));
+	blink_item (&prefs.input_tray_hilight, submenu, _("Highlighted Message"));
+	/*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/
+
+	submenu = mg_submenu (menu, _("_Change status"));
+	away_status = tray_find_away_status ();
+	item = tray_make_item (submenu, _("_Away"), tray_foreach_server, "away");
+	if (away_status == 1)
+		gtk_widget_set_sensitive (item, FALSE);
+	item = tray_make_item (submenu, _("_Back"), tray_foreach_server, "back");
+	if (away_status == 2)
+		gtk_widget_set_sensitive (item, FALSE);
+
+	tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
+	mg_create_icon_item (_("_Quit"), GTK_STOCK_QUIT, menu, tray_menu_quit_cb, NULL);
+
+	menu_add_plugin_items (menu, "\x5$TRAY", NULL);
+
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (tray_menu_destroy), NULL);
+
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, gtk_status_icon_position_menu,
+						 userdata, button, time);
+}
+
+static void
+tray_init (void)
+{
+	flash_tag = 0;
+	tray_status = TS_NONE;
+	custom_icon1 = NULL;
+	custom_icon2 = NULL;
+
+	sticon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL);
+	if (!sticon)
+		return;
+	g_signal_connect (G_OBJECT (sticon), "popup-menu",
+							G_CALLBACK (tray_menu_cb), sticon);
+	g_signal_connect (G_OBJECT (sticon), "activate",
+							G_CALLBACK (tray_menu_restore_cb), NULL);
+}
+
+static int
+tray_hilight_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_hilight)
+	{
+		tray_set_flash (ICON_HILIGHT);
+
+		/* FIXME: hides any previous private messages */
+		tray_hilight_count++;
+		if (tray_hilight_count == 1)
+			tray_set_tipf (_("XChat: Highlighted message from: %s (%s)"),
+								word[1], xchat_get_info (ph, "channel"));
+		else
+			tray_set_tipf (_("XChat: %u highlighted messages, latest from: %s (%s)"),
+								tray_hilight_count, word[1], xchat_get_info (ph, "channel"));
+	}
+
+	if (prefs.input_balloon_hilight)
+		tray_set_balloonf (word[2], _("XChat: Highlighted message from: %s (%s)"),
+								 word[1], xchat_get_info (ph, "channel"));
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_message_cb (char *word[], void *userdata)
+{
+	if (/*tray_status == TS_MESSAGE ||*/ tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;
+
+	if (prefs.input_tray_chans)
+	{
+		tray_set_flash (ICON_MSG);
+
+		tray_pub_count++;
+		if (tray_pub_count == 1)
+			tray_set_tipf (_("XChat: New public message from: %s (%s)"),
+								word[1], xchat_get_info (ph, "channel"));
+		else
+			tray_set_tipf (_("XChat: %u new public messages."), tray_pub_count);
+	}
+
+	if (prefs.input_balloon_chans)
+		tray_set_balloonf (word[2], _("XChat: New public message from: %s (%s)"),
+								 word[1], xchat_get_info (ph, "channel"));
+
+	return XCHAT_EAT_NONE;
+}
+
+static void
+tray_priv (char *from, char *text)
+{
+	const char *network;
+
+	if (alert_match_word (from, prefs.irc_no_hilight))
+		return;
+
+	tray_set_flash (ICON_HILIGHT);
+
+	network = xchat_get_info (ph, "network");
+	if (!network)
+		network = xchat_get_info (ph, "server");
+
+	tray_priv_count++;
+	if (tray_priv_count == 1)
+		tray_set_tipf (_("XChat: Private message from: %s (%s)"),
+							from, network);
+	else
+		tray_set_tipf (_("XChat: %u private messages, latest from: %s (%s)"),
+							tray_priv_count, from, network);
+
+	if (prefs.input_balloon_priv)
+		tray_set_balloonf (text, _("XChat: Private message from: %s (%s)"),
+								 from, network);
+}
+
+static int
+tray_priv_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_priv)
+		tray_priv (word[1], word[2]);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_invited_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_priv)
+		tray_priv (word[2], "Invited");
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_dcc_cb (char *word[], void *userdata)
+{
+	const char *network;
+
+/*	if (tray_status == TS_FILEOFFER)
+		return XCHAT_EAT_NONE;*/
+
+	network = xchat_get_info (ph, "network");
+	if (!network)
+		network = xchat_get_info (ph, "server");
+
+	if (prefs.input_tray_priv)
+	{
+		tray_set_flash (ICON_FILE);
+
+		tray_file_count++;
+		if (tray_file_count == 1)
+			tray_set_tipf (_("XChat: File offer from: %s (%s)"),
+								word[1], network);
+		else
+			tray_set_tipf (_("XChat: %u file offers, latest from: %s (%s)"),
+								tray_file_count, word[1], network);
+	}
+
+	if (prefs.input_balloon_priv)
+		tray_set_balloonf ("", _("XChat: File offer from: %s (%s)"),
+								word[1], network);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_focus_cb (char *word[], void *userdata)
+{
+	tray_stop_flash ();
+	tray_reset_counts ();
+	return XCHAT_EAT_NONE;
+}
+
+static void
+tray_cleanup (void)
+{
+	tray_stop_flash ();
+
+	if (sticon)
+	{
+		g_object_unref ((GObject *)sticon);
+		sticon = NULL;
+	}
+}
+
+void
+tray_apply_setup (void)
+{
+	if (sticon)
+	{
+		if (!prefs.gui_tray)
+			tray_cleanup ();
+	}
+	else
+	{
+		if (prefs.gui_tray)
+			tray_init ();
+	}
+}
+
+int
+tray_plugin_init (xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg)
+{
+	/* we need to save this for use with any xchat_* functions */
+	ph = plugin_handle;
+
+	*plugin_name = "";
+	*plugin_desc = "";
+	*plugin_version = "";
+
+	xchat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL);
+	xchat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL);
+
+	xchat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL);
+	xchat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL);
+	xchat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL);
+
+	xchat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL);
+
+	xchat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL);
+
+	xchat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL);
+
+	if (prefs.gui_tray)
+		tray_init ();
+
+	return 1;       /* return 1 for success */
+}
+
+int
+tray_plugin_deinit (xchat_plugin *plugin_handle)
+{
+#ifdef WIN32
+	tray_cleanup ();
+#elif defined(LIBNOTIFY)
+	libnotify_cleanup ();
+#endif
+	return 1;
+}
diff --git a/src/fe-gtk/plugin-tray.h b/src/fe-gtk/plugin-tray.h
new file mode 100644
index 00000000..d54be5a4
--- /dev/null
+++ b/src/fe-gtk/plugin-tray.h
@@ -0,0 +1,4 @@
+int tray_plugin_init (void *, char **, char **, char **, char *);
+int tray_plugin_deinit (void *);
+gboolean tray_toggle_visibility (gboolean force_hide);
+void tray_apply_setup (void);
diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c
new file mode 100644
index 00000000..de59e649
--- /dev/null
+++ b/src/fe-gtk/plugingui.c
@@ -0,0 +1,239 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#define PLUGIN_C
+typedef struct session xchat_context;
+#include "../common/xchat-plugin.h"
+#include "../common/plugin.h"
+#include "../common/util.h"
+#include "../common/outbound.h"
+#include "../common/fe.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+
+/* model for the plugin treeview */
+enum
+{
+	NAME_COLUMN,
+	VERSION_COLUMN,
+	FILE_COLUMN,
+	DESC_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *plugin_window = NULL;
+
+
+static GtkWidget *
+plugingui_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+	                            G_TYPE_STRING, G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             NAME_COLUMN, _("Name"),
+	                             VERSION_COLUMN, _("Version"),
+	                             FILE_COLUMN, _("File"),
+	                             DESC_COLUMN, _("Description"), -1);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+			gtk_tree_view_column_set_alignment (col, 0.5);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+plugingui_close_button (GtkWidget * wid, gpointer a)
+{
+	gtk_widget_destroy (plugin_window);
+}
+
+static void
+plugingui_close (GtkWidget * wid, gpointer a)
+{
+	plugin_window = NULL;
+}
+
+extern GSList *plugin_list;
+
+void
+fe_pluginlist_update (void)
+{
+	xchat_plugin *pl;
+	GSList *list;
+	GtkTreeView *view;
+	GtkListStore *store;
+	GtkTreeIter iter;
+
+	if (!plugin_window)
+		return;
+
+	view = g_object_get_data (G_OBJECT (plugin_window), "view");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	gtk_list_store_clear (store);
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		if (pl->version[0] != 0)
+		{
+			gtk_list_store_append (store, &iter);
+			gtk_list_store_set (store, &iter, NAME_COLUMN, pl->name,
+			                    VERSION_COLUMN, pl->version,
+			                    FILE_COLUMN, file_part (pl->filename),
+			                    DESC_COLUMN, pl->desc, -1);
+		}
+		list = list->next;
+	}
+}
+
+static void
+plugingui_load_cb (session *sess, char *file)
+{
+	if (file)
+	{
+		char *buf = malloc (strlen (file) + 9);
+
+		if (strchr (file, ' '))
+			sprintf (buf, "LOAD \"%s\"", file);
+		else
+			sprintf (buf, "LOAD %s", file);
+		handle_command (sess, buf, FALSE);
+		free (buf);
+	}
+}
+
+void
+plugingui_load (void)
+{
+	gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb,
+							current_sess, NULL, FRF_ADDFOLDER);
+}
+
+static void
+plugingui_loadbutton_cb (GtkWidget * wid, gpointer unused)
+{
+	plugingui_load ();
+}
+
+static void
+plugingui_unload (GtkWidget * wid, gpointer unused)
+{
+	int len;
+	char *modname, *file, *buf;
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	
+	view = g_object_get_data (G_OBJECT (plugin_window), "view");
+	if (!gtkutil_treeview_get_selected (view, &iter, NAME_COLUMN, &modname,
+	                                    FILE_COLUMN, &file, -1))
+		return;
+
+	len = strlen (file);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (file + len - 4, ".dll") == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (file + len - 3, ".sl") == 0)
+#else
+	if (len > 3 && strcasecmp (file + len - 3, ".so") == 0)
+#endif
+#endif
+	{
+		if (plugin_kill (modname, FALSE) == 2)
+			fe_message (_("That plugin is refusing to unload.\n"), FE_MSG_ERROR);
+	} else
+	{
+		/* let python.so or perl.so handle it */
+		buf = malloc (strlen (file) + 10);
+		if (strchr (file, ' '))
+			sprintf (buf, "UNLOAD \"%s\"", file);
+		else
+			sprintf (buf, "UNLOAD %s", file);
+		handle_command (current_sess, buf, FALSE);
+		free (buf);
+	}
+
+	g_free (modname);
+	g_free (file);
+}
+
+void
+plugingui_open (void)
+{
+	GtkWidget *view;
+	GtkWidget *vbox, *action_area;
+
+	if (plugin_window)
+	{
+		gtk_window_present (GTK_WINDOW (plugin_window));
+		return;
+	}
+
+	plugin_window = gtk_dialog_new ();
+	g_signal_connect (G_OBJECT (plugin_window), "destroy",
+							G_CALLBACK (plugingui_close), 0);
+	gtk_window_set_default_size (GTK_WINDOW (plugin_window), 500, 250);
+	vbox = GTK_DIALOG (plugin_window)->vbox;
+	action_area = GTK_DIALOG (plugin_window)->action_area;
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
+	gtk_window_set_position (GTK_WINDOW (plugin_window), GTK_WIN_POS_CENTER);
+	gtk_window_set_title (GTK_WINDOW (plugin_window), _("XChat: Plugins and Scripts"));
+
+	view = plugingui_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (plugin_window), "view", view);
+
+	gtkutil_button (action_area, GTK_STOCK_REVERT_TO_SAVED, NULL,
+	                plugingui_loadbutton_cb, NULL, _("_Load..."));
+
+	gtkutil_button (action_area, GTK_STOCK_DELETE, NULL,
+	                plugingui_unload, NULL, _("_UnLoad"));
+
+	gtkutil_button (action_area,
+						 GTK_STOCK_CLOSE, NULL, plugingui_close_button,
+						 NULL, _("_Close"));
+ 
+	fe_pluginlist_update ();
+
+	gtk_widget_show (plugin_window);
+}
diff --git a/src/fe-gtk/plugingui.h b/src/fe-gtk/plugingui.h
new file mode 100644
index 00000000..945d9a01
--- /dev/null
+++ b/src/fe-gtk/plugingui.h
@@ -0,0 +1,2 @@
+void plugingui_open (void);
+void plugingui_load (void);
diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c
new file mode 100644
index 00000000..56ca0510
--- /dev/null
+++ b/src/fe-gtk/rawlog.c
@@ -0,0 +1,152 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtkstock.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/server.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+#include "rawlog.h"
+#include "xtext.h"
+
+
+static void
+close_rawlog (GtkWidget *wid, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->rawlog_window = 0;
+}
+
+static void
+rawlog_save (server *serv, char *file)
+{
+	int fh = -1;
+
+	if (file)
+	{
+		if (serv->gui->rawlog_window)
+			fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT,
+										 0600, XOF_DOMODE | XOF_FULLPATH);
+		if (fh != -1)
+		{
+			gtk_xtext_save (GTK_XTEXT (serv->gui->rawlog_textlist), fh);
+			close (fh);
+		}
+	}
+}
+
+static int
+rawlog_clearbutton (GtkWidget * wid, server *serv)
+{
+	gtk_xtext_clear (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, 0);
+	return FALSE;
+}
+
+static int
+rawlog_savebutton (GtkWidget * wid, server *serv)
+{
+	gtkutil_file_req (_("Save As..."), rawlog_save, serv, NULL, FRF_WRITE);
+	return FALSE;
+}
+
+void
+open_rawlog (struct server *serv)
+{
+	GtkWidget *hbox, *vscrollbar, *vbox;
+	char tbuf[256];
+
+	if (serv->gui->rawlog_window)
+	{
+		mg_bring_tofront (serv->gui->rawlog_window);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: Rawlog (%s)"), serv->servername);
+	serv->gui->rawlog_window =
+		mg_create_generic_tab ("RawLog", tbuf, FALSE, TRUE, close_rawlog, serv,
+							 640, 320, &vbox, serv);
+
+	hbox = gtk_hbox_new (FALSE, 2);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+	gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);
+	gtk_widget_show (hbox);
+
+	serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (serv->gui->rawlog_textlist),
+									  channelwin_pix, prefs.transparent);
+
+	gtk_container_add (GTK_CONTAINER (hbox), serv->gui->rawlog_textlist);
+	gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.font_normal);
+	GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1;
+	gtk_widget_show (serv->gui->rawlog_textlist);
+
+	vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (serv->gui->rawlog_textlist)->adj);
+	gtk_box_pack_start (GTK_BOX (hbox), vscrollbar, FALSE, FALSE, 0);
+	show_and_unfocus (vscrollbar);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLEAR, NULL, rawlog_clearbutton,
+						 serv, _("Clear rawlog"));
+
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, rawlog_savebutton,
+						 serv, _("Save As..."));
+
+	gtk_widget_show (serv->gui->rawlog_window);
+}
+
+void
+fe_add_rawlog (server *serv, char *text, int len, int outbound)
+{
+	char *new_text;
+
+	if (!serv->gui->rawlog_window)
+		return;
+
+	new_text = malloc (len + 7);
+
+	len = sprintf (new_text, "\0033>>\017 %s", text);
+	if (outbound)
+	{
+		new_text[1] = '4';
+		new_text[2] = '<';
+		new_text[3] = '<';
+	}
+	gtk_xtext_append (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, new_text, len);
+	free (new_text);
+}
diff --git a/src/fe-gtk/rawlog.h b/src/fe-gtk/rawlog.h
new file mode 100644
index 00000000..db41e2a7
--- /dev/null
+++ b/src/fe-gtk/rawlog.h
@@ -0,0 +1 @@
+void open_rawlog (server *serv);
diff --git a/src/fe-gtk/search.c b/src/fe-gtk/search.c
new file mode 100644
index 00000000..d62e79c7
--- /dev/null
+++ b/src/fe-gtk/search.c
@@ -0,0 +1,159 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkvseparator.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtktogglebutton.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "xtext.h"
+#include "maingui.h"
+
+
+static textentry *last;	/* our last search pos */
+static int case_match = 0;
+static int search_backward = 0;
+
+
+static void
+search_search (session * sess, const gchar *text)
+{
+	if (!is_session (sess))
+	{
+		fe_message (_("The window you opened this Search "
+						"for doesn't exist anymore."), FE_MSG_ERROR);
+		return;
+	}
+
+	last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text,
+									 last, case_match, search_backward);
+	if (!last)
+		fe_message (_("Search hit end, not found."), FE_MSG_ERROR);
+}
+
+static void
+search_find_cb (GtkWidget * button, session * sess)
+{
+	GtkEntry *entry;
+	const gchar *text;
+
+	entry = g_object_get_data (G_OBJECT (button), "e");
+	text = gtk_entry_get_text (entry);
+	search_search (sess, text);
+}
+
+static void
+search_close_cb (GtkWidget * button, GtkWidget * win)
+{
+	gtk_widget_destroy (win);
+}
+
+static void
+search_entry_cb (GtkWidget * entry, session * sess)
+{
+	search_search (sess, gtk_entry_get_text (GTK_ENTRY (entry)));
+}
+
+static gboolean 
+search_key_cb (GtkWidget * window, GdkEventKey * key, gpointer userdata)
+{
+	if (key->keyval == GDK_Escape)
+		gtk_widget_destroy (window);
+	return FALSE;
+}
+
+static void
+search_caseign_cb (GtkToggleButton * but, session * sess)
+{
+	case_match = (but->active)? 1: 0;
+}
+
+static void
+search_dirbwd_cb (GtkToggleButton * but, session * sess)
+{
+	search_backward = (but->active)? 1: 0;
+}
+
+void
+search_open (session * sess)
+{
+	GtkWidget *win, *hbox, *vbox, *entry, *wid;
+
+	last = NULL;
+	win = mg_create_generic_tab ("search", _("XChat: Search"), TRUE, FALSE,
+								 NULL, NULL, 0, 0, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 12);
+	gtk_box_set_spacing (GTK_BOX (vbox), 4);
+
+	hbox = gtk_hbox_new (0, 10);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+	gtk_widget_show (hbox);
+
+	gtkutil_label_new (_("Find:"), hbox);
+
+	entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (entry), "activate",
+							G_CALLBACK (search_entry_cb), sess);
+	gtk_container_add (GTK_CONTAINER (hbox), entry);
+	gtk_widget_show (entry);
+	gtk_widget_grab_focus (entry);
+
+	wid = gtk_check_button_new_with_mnemonic (_("_Match case"));
+	GTK_TOGGLE_BUTTON (wid)->active = case_match;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_caseign_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	gtk_widget_show (wid);
+
+	wid = gtk_check_button_new_with_mnemonic (_("Search _backwards"));
+	GTK_TOGGLE_BUTTON (wid)->active = search_backward;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_dirbwd_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win,
+						_("_Close"));
+	wid = gtkutil_button (hbox, GTK_STOCK_FIND, 0, search_find_cb, sess,
+								_("_Find"));
+	g_object_set_data (G_OBJECT (wid), "e", entry);
+
+	g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (search_key_cb), win);
+
+	gtk_widget_show (win);
+}
diff --git a/src/fe-gtk/search.h b/src/fe-gtk/search.h
new file mode 100644
index 00000000..8fa1b628
--- /dev/null
+++ b/src/fe-gtk/search.h
@@ -0,0 +1 @@
+void search_open (session * sess);
diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c
new file mode 100644
index 00000000..2ac0e6c9
--- /dev/null
+++ b/src/fe-gtk/servlistgui.c
@@ -0,0 +1,1918 @@
+/* X-Chat
+ * Copyright (C) 2004-2008 Peter Zelezny.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <gtk/gtkversion.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcomboboxentry.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtktree.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkvbbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkwindow.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/servlist.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+
+#include "fe-gtk.h"
+#include "gtkutil.h"
+#include "menu.h"
+#include "pixmaps.h"
+
+
+/* servlistgui.c globals */
+static GtkWidget *serverlist_win = NULL;
+static GtkWidget *networks_tree;	/* network TreeView */
+static int ignore_changed = FALSE;
+#ifdef WIN32
+static int win_width = 324;
+static int win_height = 426;
+#else
+static int win_width = 364;
+static int win_height = 478;
+#endif
+
+/* global user info */
+static GtkWidget *entry_nick1;
+static GtkWidget *entry_nick2;
+static GtkWidget *entry_nick3;
+static GtkWidget *entry_guser;
+static GtkWidget *entry_greal;
+
+/* edit area */
+static GtkWidget *edit_win;
+static GtkWidget *edit_entry_nick;
+static GtkWidget *edit_entry_nick2;
+static GtkWidget *edit_entry_user;
+static GtkWidget *edit_entry_real;
+static GtkWidget *edit_entry_join;
+static GtkWidget *edit_entry_pass;
+static GtkWidget *edit_entry_cmd;
+static GtkWidget *edit_entry_nickserv;
+static GtkWidget *edit_label_nick;
+static GtkWidget *edit_label_nick2;
+static GtkWidget *edit_label_real;
+static GtkWidget *edit_label_user;
+static GtkWidget *edit_tree;
+
+static ircnet *selected_net = NULL;
+static ircserver *selected_serv = NULL;
+static ircnet *fav_add_net = NULL; /* used in Add/Remove fav context menus */
+static session *servlist_sess;
+
+static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data);
+static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net);
+
+
+static const char *pages[]=
+{
+	"UTF-8 (Unicode)",
+	"IRC (Latin/Unicode Hybrid)",
+	"ISO-8859-15 (Western Europe)",
+	"ISO-8859-2 (Central Europe)",
+	"ISO-8859-7 (Greek)",
+	"ISO-8859-8 (Hebrew)",
+	"ISO-8859-9 (Turkish)",
+	"ISO-2022-JP (Japanese)",
+	"SJIS (Japanese)",
+	"CP949 (Korean)",
+	"KOI8-R (Cyrillic)",
+	"CP1251 (Cyrillic)",
+	"CP1256 (Arabic)",
+	"CP1257 (Baltic)",
+	"GB18030 (Chinese)",
+	"TIS-620 (Thai)",
+	NULL
+};
+
+static void
+servlist_select_and_show (GtkTreeView *treeview, GtkTreeIter *iter,
+								  GtkListStore *store)
+{
+	GtkTreePath *path;
+	GtkTreeSelection *sel;
+
+	sel = gtk_tree_view_get_selection (treeview);
+
+	/* select this network */
+	gtk_tree_selection_select_iter (sel, iter);
+	/* and make sure it's visible */
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+	if (path)
+	{
+		gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+		gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+servlist_servers_populate (ircnet *net, GtkWidget *treeview)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	int i;
+	ircserver *serv;
+	GSList *list = net->servlist;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	gtk_list_store_clear (store);
+
+	i = 0;
+	while (list)
+	{
+		serv = list->data;
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter, 0, serv->hostname, 1, 1, -1);
+
+		if (net->selected == i)
+			/* select this server */
+			servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+
+		i++;
+		list = list->next;
+	}
+}
+
+static void
+servlist_networks_populate_ (GtkWidget *treeview, GSList *netlist, gboolean favorites)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	int i;
+	ircnet *net;
+
+	if (!netlist)
+	{
+		net = servlist_net_add (_("New Network"), "", FALSE);
+		servlist_server_add (net, "newserver/6667");
+		netlist = network_list;
+	}
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	gtk_list_store_clear (store);
+
+	i = 0;
+	while (netlist)
+	{
+		net = netlist->data;
+		if (!favorites || (net->flags & FLAG_FAVORITE))
+		{
+			if (favorites)
+				gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, 400, -1);
+			else
+				gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, (net->flags & FLAG_FAVORITE) ? 800 : 400, -1);
+			if (i == prefs.slist_select)
+			{
+				/* select this network */
+				servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+				selected_net = net;
+			}
+		}
+		i++;
+		netlist = netlist->next;
+	}
+}
+
+static void
+servlist_networks_populate (GtkWidget *treeview, GSList *netlist)
+{
+	servlist_networks_populate_ (treeview, netlist, prefs.slist_fav);
+}
+
+static void
+servlist_server_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	ircserver *serv;
+	char *servname;
+	int pos;
+
+	if (!selected_net)
+		return;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &servname, -1);
+		serv = servlist_server_find (selected_net, servname, &pos);
+		g_free (servname);
+		if (serv)
+			selected_net->selected = pos;
+		selected_serv = serv;
+	}
+}
+
+static void
+servlist_start_editing (GtkTreeView *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+
+	sel = gtk_tree_view_get_selection (tree);
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+		if (path)
+		{
+			gtk_tree_view_set_cursor (tree, path,
+									gtk_tree_view_get_column (tree, 0), TRUE);
+			gtk_tree_path_free (path);
+		}
+	}
+}
+
+static void
+servlist_addserver_cb (GtkWidget *item, GtkWidget *treeview)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	if (!selected_net)
+		return;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	servlist_server_add (selected_net, "newserver/6667");
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, 0, "newserver/6667", 1, 1, -1);
+
+	/* select this server */
+	servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+	/*servlist_start_editing (GTK_TREE_VIEW (treeview));*/
+
+	servlist_server_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL);
+}
+
+static void
+servlist_addnet_cb (GtkWidget *item, GtkTreeView *treeview)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+	ircnet *net;
+
+	net = servlist_net_add (_("New Network"), "", TRUE);
+	net->encoding = strdup ("IRC (Latin/Unicode Hybrid)");
+	servlist_server_add (net, "newserver/6667");
+
+	store = (GtkListStore *)gtk_tree_view_get_model (treeview);
+	gtk_list_store_prepend (store, &iter);
+	gtk_list_store_set (store, &iter, 0, net->name, 1, 1, -1);
+
+	/* select this network */
+	servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, store);
+	servlist_start_editing (GTK_TREE_VIEW (networks_tree));
+
+	servlist_network_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL);
+}
+
+static void
+servlist_deletenetwork (ircnet *net)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* remove from GUI */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	/* remove from list */
+	servlist_net_remove (net);
+
+	/* force something to be selected */
+	gtk_tree_model_get_iter_first (model, &iter);
+	servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter,
+									  GTK_LIST_STORE (model));
+	servlist_network_row_cb (sel, NULL);
+}
+
+static void
+servlist_deletenetdialog_cb (GtkDialog *dialog, gint arg1, ircnet *net)
+{
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	if (arg1 == GTK_RESPONSE_OK)
+		servlist_deletenetwork (net);
+}
+
+static void
+servlist_move_server (ircserver *serv, int delta)
+{
+	int pos;
+
+	pos = g_slist_index (selected_net->servlist, serv);
+	if (pos >= 0)
+	{
+		pos += delta;
+		if (pos >= 0)
+		{
+			selected_net->servlist = g_slist_remove (selected_net->servlist, serv);
+			selected_net->servlist = g_slist_insert (selected_net->servlist, serv, pos);
+			servlist_servers_populate (selected_net, edit_tree);
+		}
+	}
+}
+
+static void
+servlist_move_network (ircnet *net, int delta)
+{
+	int pos;
+
+	pos = g_slist_index (network_list, net);
+	if (pos >= 0)
+	{
+		pos += delta;
+		if (pos >= 0)
+		{
+			/*prefs.slist_select += delta;*/
+			network_list = g_slist_remove (network_list, net);
+			network_list = g_slist_insert (network_list, net, pos);
+			servlist_networks_populate (networks_tree, network_list);
+		}
+	}
+}
+
+static gboolean
+servlist_net_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer tree)
+{
+	if (!selected_net)
+		return FALSE;
+
+	if (evt->state & GDK_SHIFT_MASK)
+	{
+		if (evt->keyval == GDK_Up)
+		{
+			servlist_move_network (selected_net, -1);
+		}
+		else if (evt->keyval == GDK_Down)
+		{
+			servlist_move_network (selected_net, +1);
+		}
+	}
+
+	return FALSE;
+}
+
+static gboolean
+servlist_serv_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
+{
+	if (!selected_net || !selected_serv)
+		return FALSE;
+
+	if (evt->state & GDK_SHIFT_MASK)
+	{
+		if (evt->keyval == GDK_Up)
+		{
+			servlist_move_server (selected_serv, -1);
+		}
+		else if (evt->keyval == GDK_Down)
+		{
+			servlist_move_server (selected_serv, +1);
+		}
+	}
+
+	return FALSE;
+}
+
+static gint
+servlist_compare (ircnet *net1, ircnet *net2)
+{
+	gchar *net1_casefolded, *net2_casefolded;
+	int result=0;
+
+	net1_casefolded=g_utf8_casefold(net1->name,-1),
+	net2_casefolded=g_utf8_casefold(net2->name,-1),
+
+	result=g_utf8_collate(net1_casefolded,net2_casefolded);
+
+	g_free(net1_casefolded);
+	g_free(net2_casefolded);
+
+	return result;
+
+}
+
+static void
+servlist_sort (GtkWidget *button, gpointer none)
+{
+	network_list=g_slist_sort(network_list,(GCompareFunc)servlist_compare);
+	servlist_networks_populate (networks_tree, network_list);
+}
+
+static gboolean
+servlist_has_selection (GtkTreeView *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* make sure something is selected */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+	return gtk_tree_selection_get_selected (sel, &model, &iter);
+}
+
+static void
+servlist_favor (GtkWidget *button, gpointer none)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!selected_net)
+		return;
+
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		if (selected_net->flags & FLAG_FAVORITE)
+		{
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 400, -1);
+			selected_net->flags &= ~FLAG_FAVORITE;
+		}
+		else
+		{
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 800, -1);
+			selected_net->flags |= FLAG_FAVORITE;
+		}
+	}
+}
+
+static void
+servlist_update_from_entry (char **str, GtkWidget *entry)
+{
+	if (*str)
+		free (*str);
+
+	if (GTK_ENTRY (entry)->text[0] == 0)
+		*str = NULL;
+	else
+		*str = strdup (GTK_ENTRY (entry)->text);
+}
+
+static void
+servlist_edit_update (ircnet *net)
+{
+	servlist_update_from_entry (&net->nick, edit_entry_nick);
+	servlist_update_from_entry (&net->nick2, edit_entry_nick2);
+	servlist_update_from_entry (&net->user, edit_entry_user);
+	servlist_update_from_entry (&net->real, edit_entry_real);
+
+	servlist_update_from_entry (&net->autojoin, edit_entry_join);
+	servlist_update_from_entry (&net->command, edit_entry_cmd);
+	servlist_update_from_entry (&net->nickserv, edit_entry_nickserv);
+	servlist_update_from_entry (&net->pass, edit_entry_pass);
+}
+
+static void
+servlist_edit_close_cb (GtkWidget *button, gpointer userdata)
+{
+	if (selected_net)
+		servlist_edit_update (selected_net);
+
+	gtk_widget_destroy (edit_win);
+	edit_win = NULL;
+}
+
+static gint
+servlist_editwin_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer none)
+{
+	servlist_edit_close_cb (NULL, NULL);
+	return FALSE;
+}
+
+static gboolean
+servlist_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer none)
+{
+	/* remember the window size */
+	gtk_window_get_size (win, &win_width, &win_height);
+	return FALSE;
+}
+
+static void
+servlist_edit_cb (GtkWidget *but, gpointer none)
+{
+	if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree)))
+		return;
+
+	edit_win = servlist_open_edit (serverlist_win, selected_net);
+	gtkutil_set_icon (edit_win);
+	servlist_servers_populate (selected_net, edit_tree);
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree))),
+							"changed", G_CALLBACK (servlist_server_row_cb), NULL);
+	g_signal_connect (G_OBJECT (edit_win), "delete_event",
+						 	G_CALLBACK (servlist_editwin_delete_cb), 0);
+	g_signal_connect (G_OBJECT (edit_tree), "key_press_event",
+							G_CALLBACK (servlist_serv_keypress_cb), 0);
+	gtk_widget_show (edit_win);
+}
+
+static void
+servlist_deletenet_cb (GtkWidget *item, ircnet *net)
+{
+	GtkWidget *dialog;
+
+	if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree)))
+		return;
+
+	net = selected_net;
+	if (!net)
+		return;
+	dialog = gtk_message_dialog_new (GTK_WINDOW (serverlist_win),
+												GTK_DIALOG_DESTROY_WITH_PARENT |
+												GTK_DIALOG_MODAL,
+												GTK_MESSAGE_QUESTION,
+												GTK_BUTTONS_OK_CANCEL,
+							_("Really remove network \"%s\" and all its servers?"),
+												net->name);
+	g_signal_connect (dialog, "response",
+							G_CALLBACK (servlist_deletenetdialog_cb), net);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+}
+
+static void
+servlist_deleteserver (ircserver *serv, GtkTreeModel *model)
+{
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+
+	/* don't remove the last server */
+	if (selected_net && g_slist_length (selected_net->servlist) < 2)
+		return;
+
+	/* remove from GUI */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	/* remove from list */
+	if (selected_net)
+		servlist_server_remove (selected_net, serv);
+}
+
+static void
+servlist_editserverbutton_cb (GtkWidget *item, gpointer none)
+{
+	servlist_start_editing (GTK_TREE_VIEW (edit_tree));
+}
+
+static void
+servlist_deleteserver_cb (GtkWidget *item, gpointer none)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *servname;
+	ircserver *serv;
+	int pos;
+
+	/* find the selected item in the GUI */
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (edit_tree));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree));
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &servname, -1);
+		serv = servlist_server_find (selected_net, servname, &pos);
+		g_free (servname);
+		if (serv)
+			servlist_deleteserver (serv, model);
+	}
+}
+
+static ircnet *
+servlist_find_selected_net (GtkTreeSelection *sel)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *netname;
+	int pos;
+	ircnet *net = NULL;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &netname, -1);
+		net = servlist_net_find (netname, &pos, strcmp);
+		g_free (netname);
+		if (net)
+			prefs.slist_select = pos;
+	}
+
+	return net;
+}
+
+static void
+servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	ircnet *net;
+
+	selected_net = NULL;
+
+	net = servlist_find_selected_net (sel);
+	if (net)
+		selected_net = net;
+}
+
+static int
+servlist_savegui (void)
+{
+	char *sp;
+
+	/* check for blank username, ircd will not allow this */
+	if (GTK_ENTRY (entry_guser)->text[0] == 0)
+		return 1;
+
+	if (GTK_ENTRY (entry_greal)->text[0] == 0)
+		return 1;
+
+	strcpy (prefs.nick1, GTK_ENTRY (entry_nick1)->text);
+	strcpy (prefs.nick2, GTK_ENTRY (entry_nick2)->text);
+	strcpy (prefs.nick3, GTK_ENTRY (entry_nick3)->text);
+	strcpy (prefs.username, GTK_ENTRY (entry_guser)->text);
+	sp = strchr (prefs.username, ' ');
+	if (sp)
+		sp[0] = 0;	/* spaces will break the login */
+	strcpy (prefs.realname, GTK_ENTRY (entry_greal)->text);
+	servlist_save ();
+
+	return 0;
+}
+
+static gboolean
+servlist_get_iter_from_name (GtkTreeModel *model, gchar *name, GtkTreeIter *iter)
+{
+	GtkTreePath *path = gtk_tree_path_new_from_string (name);
+
+	if (!gtk_tree_model_get_iter (model, iter, path))
+	{
+		gtk_tree_path_free (path);
+		return FALSE;
+	}
+
+	gtk_tree_path_free (path);
+	return TRUE;
+}
+
+static void
+servlist_editchannel_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model)
+{
+	GtkTreeIter iter;
+	static int loop_guard = FALSE;
+
+	if (loop_guard)
+		return;
+
+	if (!servlist_get_iter_from_name (model, name, &iter))
+		return;
+
+	loop_guard = TRUE;
+	/* delete empty item */
+	if (newval[0] == 0)
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+	else
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, newval, -1);
+	loop_guard = FALSE;
+}
+
+static void
+servlist_editkey_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model)
+{
+	GtkTreeIter iter;
+
+	if (!servlist_get_iter_from_name (model, name, &iter))
+		return;
+
+	gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, newval, -1);
+}
+
+static void
+servlist_addchannel (GtkWidget *tree, char *channel)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, 0, channel, 1, "", 2, TRUE, -1);
+
+	/* select this server */
+	servlist_select_and_show (GTK_TREE_VIEW (tree), &iter, store);
+	servlist_start_editing (GTK_TREE_VIEW (tree));
+}
+
+static void
+servlist_addchannel_cb (GtkWidget *item, GtkWidget *tree)
+{
+	servlist_addchannel (tree, _("#channel"));
+}
+
+static void
+servlist_deletechannel_cb (GtkWidget *item, GtkWidget *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* find the selected item in the GUI */
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+}
+
+static void
+servlist_editchannelbutton_cb (GtkWidget *item, GtkWidget *tree)
+{
+	servlist_start_editing (GTK_TREE_VIEW (tree));
+}
+
+/* save everything from the GUI to the GtkEntry */
+
+static void
+servlist_autojoineditok_cb (GtkWidget *button, GtkWidget *tree)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *channel, *key;
+	char *autojoin;
+	GSList *channels = NULL, *keys = NULL;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, 0, &channel, 1, &key, -1);
+			channels = g_slist_append (channels, channel);
+			if (key && key[0] == 0)
+			{
+				/* NULL out empty keys */
+				g_free (key);
+				keys = g_slist_append (keys, NULL);				
+			}
+			else
+				keys = g_slist_append (keys, key);
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	gtk_widget_destroy (gtk_widget_get_toplevel (button));
+
+	autojoin = joinlist_merge (channels, keys);
+	if (autojoin)
+	{
+		if (edit_win && selected_net)
+			gtk_entry_set_text (GTK_ENTRY (edit_entry_join), autojoin);
+		else
+		{
+			if (fav_add_net->autojoin)
+				free (fav_add_net->autojoin);
+			fav_add_net->autojoin = strdup (autojoin);
+		}
+		g_free (autojoin);
+	}
+
+	/* this does g_free too */
+	joinlist_free (channels, keys);
+
+	if (fav_add_net)
+		servlist_save ();
+}
+
+void
+servlist_autojoinedit (ircnet *net, char *channel, gboolean add)
+{
+	GtkWidget *win;
+	GtkWidget *scrolledwindow;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	GtkWidget *tree;
+	GtkWidget *table;
+	GtkWidget *label;
+	GtkWidget *label2;
+	GtkWidget *bbox;
+	GtkWidget *wid;
+
+	GtkWidget *vbuttonbox1;
+	GtkWidget *buttonadd;
+	GtkWidget *buttonremove;
+	GtkWidget *buttonedit;
+
+	char buf[128];
+	char lab[128];
+	GSList *channels, *keys;
+	GSList *clist, *klist;
+	GtkTreeIter iter;
+
+	if (edit_win && selected_net)
+		/* update net->autojoin */
+		servlist_edit_update (selected_net);
+
+	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 4);
+	gtk_window_set_title (GTK_WINDOW (win), _("XChat: Favorite Channels (Auto-Join List)"));
+	gtk_window_set_default_size (GTK_WINDOW (win), 354, 256);
+	gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
+	if (edit_win)
+		gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (edit_win));
+	gtk_window_set_modal (GTK_WINDOW (win), TRUE);
+	gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_role (GTK_WINDOW (win), "editserv");
+
+	table = gtk_table_new (1, 1, FALSE);
+	gtk_container_add (GTK_CONTAINER (win), table);
+	gtk_widget_show (table);
+
+	snprintf (buf, sizeof (buf), _("These channels will be joined whenever you connect to %s."), net->name);
+	label = gtk_label_new (buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 3, 3);
+	gtk_widget_show (label);
+
+	label2 = gtk_label_new ("");
+	gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER);
+	gtk_table_attach (GTK_TABLE (table), label2, 0, 2, 1, 2, GTK_FILL, 0, 3, 3);
+	gtk_widget_show (label2);
+
+	scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
+	gtk_table_attach (GTK_TABLE (table), scrolledwindow, 0, 1, 2, 3, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow),
+													 GTK_SHADOW_IN);
+	gtk_widget_show (scrolledwindow);
+
+	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	model = GTK_TREE_MODEL (store);
+
+	tree = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow), tree);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), TRUE);
+	gtk_widget_show (tree);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editchannel_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (tree), -1,
+						 		_("Channel"), renderer,
+						 		"text", 0,
+								"editable", 2,
+								NULL);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editkey_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (tree), -1,
+						 		_("Key (Password)"), renderer,
+						 		"text", 1,
+								"editable", 2,
+								NULL);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 1), TRUE);
+
+	gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *)model, 0, GTK_SORT_ASCENDING);
+
+	/* ===BUTTONS=== */
+	vbuttonbox1 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox1);
+	gtk_table_attach (GTK_TABLE (table), vbuttonbox1, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 3, 0);
+
+	buttonadd = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (buttonadd), "clicked",
+							G_CALLBACK (servlist_addchannel_cb), tree);
+	gtk_widget_show (buttonadd);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd);
+	GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT);
+
+	buttonremove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (buttonremove), "clicked",
+							G_CALLBACK (servlist_deletechannel_cb), tree);
+	gtk_widget_show (buttonremove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove);
+	GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT);
+
+	buttonedit = gtk_button_new_with_mnemonic (_("_Edit"));
+	g_signal_connect (G_OBJECT (buttonedit), "clicked",
+							G_CALLBACK (servlist_editchannelbutton_cb), tree);
+	gtk_widget_show (buttonedit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit);
+	GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
+	gtk_box_set_spacing (GTK_BOX (bbox), 4);
+	gtk_table_attach (GTK_TABLE (table), bbox, 0, 1, 3, 4, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 4);
+	gtk_widget_show (bbox);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+	g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), win);
+	gtk_container_add (GTK_CONTAINER (bbox), wid);
+	gtk_widget_show (wid);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (servlist_autojoineditok_cb), tree);
+	gtk_container_add (GTK_CONTAINER (bbox), wid);
+	gtk_widget_show (wid);
+	gtk_widget_grab_focus (wid);
+	/* =========== */
+
+	if (net->autojoin)
+	{
+		joinlist_split (net->autojoin, &channels, &keys);
+
+		clist = channels;
+		klist = keys;
+
+		while (clist)
+		{
+			if (channel && !add && !rfc_casecmp (channel, clist->data))
+			{
+				snprintf (buf, sizeof (buf), _("%s has been removed."), channel);
+				snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf);
+				gtk_label_set_markup (GTK_LABEL (label2), lab);
+			}
+			else
+			{
+				gtk_list_store_append (store, &iter);
+				gtk_list_store_set (store, &iter, 0, clist->data, 1, klist->data, 2, TRUE, -1);
+			}
+
+			klist = klist->next;
+			clist = clist->next;
+		}
+
+		joinlist_free (channels, keys);
+	}
+
+	if (channel && add)
+	{
+		servlist_addchannel (tree, channel);
+		snprintf (buf, sizeof (buf), _("%s has been added."), channel);
+		snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf);
+		gtk_label_set_markup (GTK_LABEL (label2), lab);
+	}
+
+	fav_add_net = net;
+
+	gtk_widget_show (win);
+}
+
+static void
+servlist_autojoinedit_cb (GtkWidget *button, ircnet *net)
+{
+	servlist_autojoinedit (net, NULL, FALSE);
+}
+
+static void
+servlist_connect_cb (GtkWidget *button, gpointer userdata)
+{
+	if (!selected_net)
+		return;
+
+	if (servlist_savegui () != 0)
+	{
+		fe_message (_("User name and Real name cannot be left blank."), FE_MSG_ERROR);
+		return;
+	}
+
+ 	if (!is_session (servlist_sess))
+		servlist_sess = NULL;	/* open a new one */
+
+	{
+		GSList *list;
+		session *sess;
+		session *chosen = servlist_sess;
+
+		servlist_sess = NULL;	/* open a new one */
+
+		for (list = sess_list; list; list = list->next)
+		{
+			sess = list->data;
+			if (sess->server->network == selected_net)
+			{
+				servlist_sess = sess;
+				if (sess->server->connected)
+					servlist_sess = NULL;	/* open a new one */
+				break;
+			}
+		}
+
+		/* use the chosen one, if it's empty */
+		if (!servlist_sess &&
+			  chosen &&
+			 !chosen->server->connected &&
+			  chosen->server->server_session->channel[0] == 0)
+		{
+			servlist_sess = chosen;
+		}
+	}
+
+	servlist_connect (servlist_sess, selected_net, TRUE);
+
+	gtk_widget_destroy (serverlist_win);
+	serverlist_win = NULL;
+	selected_net = NULL;
+}
+
+static void
+servlist_celledit_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2,
+							 gpointer user_data)
+{
+	GtkTreeModel *model = (GtkTreeModel *)user_data;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *netname;
+	ircnet *net;
+
+	if (!arg1 || !arg2)
+		return;
+
+	path = gtk_tree_path_new_from_string (arg1);
+	if (!path)
+		return;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+	{
+		gtk_tree_path_free (path);
+		return;
+	}
+	gtk_tree_model_get (model, &iter, 0, &netname, -1);
+
+	net = servlist_net_find (netname, NULL, strcmp);
+	g_free (netname);
+	if (net)
+	{
+		/* delete empty item */
+		if (arg2[0] == 0)
+		{
+			servlist_deletenetwork (net);
+			gtk_tree_path_free (path);
+			return;
+		}
+
+		netname = net->name;
+		net->name = strdup (arg2);
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, net->name, -1);
+		free (netname);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+servlist_check_cb (GtkWidget *but, gpointer num_p)
+{
+	int num = GPOINTER_TO_INT (num_p);
+
+	if (!selected_net)
+		return;
+
+	if ((1 << num) == FLAG_CYCLE || (1 << num) == FLAG_USE_PROXY)
+	{
+		/* these ones are reversed, so it's compat with 2.0.x */
+		if (GTK_TOGGLE_BUTTON (but)->active)
+			selected_net->flags &= ~(1 << num);
+		else
+			selected_net->flags |= (1 << num);
+	} else
+	{
+		if (GTK_TOGGLE_BUTTON (but)->active)
+			selected_net->flags |= (1 << num);
+		else
+			selected_net->flags &= ~(1 << num);
+	}
+
+	if ((1 << num) == FLAG_USE_GLOBAL)
+	{
+		if (GTK_TOGGLE_BUTTON (but)->active)
+		{
+			gtk_widget_hide (edit_label_nick);
+			gtk_widget_hide (edit_entry_nick);
+
+			gtk_widget_hide (edit_label_nick2);
+			gtk_widget_hide (edit_entry_nick2);
+
+			gtk_widget_hide (edit_label_user);
+			gtk_widget_hide (edit_entry_user);
+
+			gtk_widget_hide (edit_label_real);
+			gtk_widget_hide (edit_entry_real);
+		} else
+		{
+			gtk_widget_show (edit_label_nick);
+			gtk_widget_show (edit_entry_nick);
+
+			gtk_widget_show (edit_label_nick2);
+			gtk_widget_show (edit_entry_nick2);
+
+			gtk_widget_show (edit_label_user);
+			gtk_widget_show (edit_entry_user);
+
+			gtk_widget_show (edit_label_real);
+			gtk_widget_show (edit_entry_real);
+		}
+	}
+}
+
+static GtkWidget *
+servlist_create_check (int num, int state, GtkWidget *table, int row, int col, char *labeltext)
+{
+	GtkWidget *but;
+
+	but = gtk_check_button_new_with_label (labeltext);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (but), state);
+	g_signal_connect (G_OBJECT (but), "toggled",
+							G_CALLBACK (servlist_check_cb), GINT_TO_POINTER (num));
+	gtk_table_attach (GTK_TABLE (table), but, col, col+2, row, row+1,
+						   GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	gtk_widget_show (but);
+
+	return but;
+}
+
+static GtkWidget *
+servlist_create_entry (GtkWidget *table, char *labeltext, int row,
+							  char *def, GtkWidget **label_ret, char *tip)
+{
+	GtkWidget *label, *entry;
+
+	label = gtk_label_new_with_mnemonic (labeltext);
+	if (label_ret)
+		*label_ret = label;
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row+1,
+							GTK_FILL, 0, 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	entry = gtk_entry_new ();
+	add_tip (entry, tip);
+	gtk_widget_show (entry);
+	gtk_entry_set_text (GTK_ENTRY (entry), def ? def : "");
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+	if (row == 15)	/* for "Channels to Join:" */
+	{
+		GtkWidget *button, *box;
+
+		box = gtk_hbox_new (0, 0);
+		button = gtk_button_new_with_label ("...");
+		g_signal_connect (G_OBJECT (button), "clicked",
+								G_CALLBACK (servlist_autojoinedit_cb), selected_net);
+
+		gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0);
+		gtk_box_pack_end (GTK_BOX (box), button, 0, 0, 0);
+		gtk_widget_show_all (box);
+
+		gtk_table_attach (GTK_TABLE (table), box, 2, 3, row, row+1,
+								GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	}
+	else
+	{
+		gtk_table_attach (GTK_TABLE (table), entry, 2, 3, row, row+1,
+								GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	}
+
+	return entry;
+}
+
+static gint
+servlist_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer userdata)
+{
+	servlist_savegui ();
+	serverlist_win = NULL;
+	selected_net = NULL;
+
+	if (sess_list == NULL)
+		xchat_exit ();
+
+	return FALSE;
+}
+
+static void
+servlist_close_cb (GtkWidget *button, gpointer userdata)
+{
+	servlist_savegui ();
+	gtk_widget_destroy (serverlist_win);
+	serverlist_win = NULL;
+	selected_net = NULL;
+
+	if (sess_list == NULL)
+		xchat_exit ();
+}
+
+/* convert "host:port" format to "host/port" */
+
+static char *
+servlist_sanitize_hostname (char *host)
+{
+	char *ret, *c, *e;
+
+	ret = strdup (host);
+
+	c = strchr  (ret, ':');
+	e = strrchr (ret, ':');
+
+	/* if only one colon exists it's probably not IPv6 */
+	if (c && c == e)
+		*c = '/';
+
+	return ret;
+}
+
+static void
+servlist_editserver_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2,
+								gpointer user_data)
+{
+	GtkTreeModel *model = (GtkTreeModel *)user_data;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *servname;
+	ircserver *serv;
+
+	if (!selected_net)
+		return;
+
+	path = gtk_tree_path_new_from_string (arg1);
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+	{
+		gtk_tree_path_free (path);
+		return;
+	}
+
+	gtk_tree_model_get (model, &iter, 0, &servname, -1);
+	serv = servlist_server_find (selected_net, servname, NULL);
+	g_free (servname);
+
+	if (serv)
+	{
+		/* delete empty item */
+		if (arg2[0] == 0)
+		{
+			servlist_deleteserver (serv, model);
+			gtk_tree_path_free (path);
+			return;
+		}
+
+		servname = serv->hostname;
+		serv->hostname = servlist_sanitize_hostname (arg2);
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, serv->hostname, -1);
+		free (servname);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+servlist_combo_cb (GtkEntry *entry, gpointer userdata)
+{
+	if (!selected_net)
+		return;
+
+	if (!ignore_changed)
+	{
+		if (selected_net->encoding)
+			free (selected_net->encoding);
+		selected_net->encoding = strdup (entry->text);
+	}
+}
+
+static GtkWidget *
+servlist_create_charsetcombo (void)
+{
+	GtkWidget *cb;
+	int i;
+
+	cb = gtk_combo_box_entry_new_text ();
+	gtk_combo_box_append_text (GTK_COMBO_BOX (cb), "System default");
+	i = 0;
+	while (pages[i])
+	{
+		gtk_combo_box_append_text (GTK_COMBO_BOX (cb), (char *)pages[i]);
+		i++;
+	}
+	g_signal_connect (G_OBJECT (GTK_BIN (cb)->child), "changed",
+							G_CALLBACK (servlist_combo_cb), NULL);
+
+	return cb;
+}
+
+static void
+no_servlist (GtkWidget * igad, gpointer serv)
+{
+	if (GTK_TOGGLE_BUTTON (igad)->active)
+		prefs.slist_skip = TRUE;
+	else
+		prefs.slist_skip = FALSE;
+}
+
+static void
+fav_servlist (GtkWidget * igad, gpointer serv)
+{
+	if (GTK_TOGGLE_BUTTON (igad)->active)
+		prefs.slist_fav = TRUE;
+	else
+		prefs.slist_fav = FALSE;
+
+	servlist_networks_populate (networks_tree, network_list);
+}
+
+static GtkWidget *
+bold_label (char *text)
+{
+	char buf[128];
+	GtkWidget *label;
+
+	snprintf (buf, sizeof (buf), "<b>%s</b>", text);
+	label = gtk_label_new (buf);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+	gtk_widget_show (label);
+
+	return label;
+}
+
+static GtkWidget *
+servlist_open_edit (GtkWidget *parent, ircnet *net)
+{
+	GtkWidget *editwindow;
+	GtkWidget *vbox5;
+	GtkWidget *table3;
+	GtkWidget *label17;
+	GtkWidget *label16;
+	GtkWidget *label21;
+	GtkWidget *label34;
+	GtkWidget *comboboxentry_charset;
+	GtkWidget *hbox1;
+	GtkWidget *scrolledwindow2;
+	GtkWidget *treeview_servers;
+	GtkWidget *vbuttonbox1;
+	GtkWidget *buttonadd;
+	GtkWidget *buttonremove;
+	GtkWidget *buttonedit;
+	GtkWidget *hseparator2;
+	GtkWidget *hbuttonbox4;
+	GtkWidget *button10;
+	GtkWidget *check;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	char buf[128];
+	char buf2[128 + 8];
+
+	editwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (editwindow), 4);
+	snprintf (buf, sizeof (buf), _("XChat: Edit %s"), net->name);
+	gtk_window_set_title (GTK_WINDOW (editwindow), buf);
+	gtk_window_set_default_size (GTK_WINDOW (editwindow), 354, 0);
+	gtk_window_set_position (GTK_WINDOW (editwindow), GTK_WIN_POS_MOUSE);
+	gtk_window_set_transient_for (GTK_WINDOW (editwindow), GTK_WINDOW (parent));
+	gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE);
+	gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_role (GTK_WINDOW (editwindow), "editserv");
+
+	vbox5 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox5);
+	gtk_container_add (GTK_CONTAINER (editwindow), vbox5);
+
+	table3 = gtk_table_new (17, 3, FALSE);
+	gtk_widget_show (table3);
+	gtk_box_pack_start (GTK_BOX (vbox5), table3, TRUE, TRUE, 0);
+	gtk_table_set_row_spacings (GTK_TABLE (table3), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table3), 8);
+
+	snprintf (buf, sizeof (buf), _("Servers for %s"), net->name);
+	snprintf (buf2, sizeof (buf2), "<b>%s</b>", buf);
+	label16 = gtk_label_new (buf2);
+	gtk_widget_show (label16);
+	gtk_table_attach (GTK_TABLE (table3), label16, 0, 3, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+	gtk_label_set_use_markup (GTK_LABEL (label16), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label16), 0, 0.5);
+
+	check = servlist_create_check (0, !(net->flags & FLAG_CYCLE), table3,
+								  2, 1, _("Connect to selected server only"));
+	add_tip (check, _("Don't cycle through all the servers when the connection fails."));
+
+	label17 = bold_label (_("Your Details"));
+	gtk_table_attach (GTK_TABLE (table3), label17, 0, 3, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+
+	servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3,
+								  4, 1, _("Use global user information"));
+
+	edit_entry_nick =
+		servlist_create_entry (table3, _("_Nick name:"), 5, net->nick,
+									  &edit_label_nick, 0);
+
+	edit_entry_nick2 =
+		servlist_create_entry (table3, _("Second choice:"), 6, net->nick2,
+									  &edit_label_nick2, 0);
+
+	edit_entry_user =
+		servlist_create_entry (table3, _("_User name:"), 7, net->user,
+									  &edit_label_user, 0);
+
+	edit_entry_real =
+		servlist_create_entry (table3, _("Rea_l name:"), 8, net->real,
+									  &edit_label_real, 0);
+
+	label21 = bold_label (_("Connecting"));
+	gtk_table_attach (GTK_TABLE (table3), label21, 0, 3, 9, 10,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+
+	servlist_create_check (3, net->flags & FLAG_AUTO_CONNECT, table3,
+								  11, 1, _("Auto connect to this network at startup"));
+	servlist_create_check (4, !(net->flags & FLAG_USE_PROXY), table3,
+								  12, 1, _("Bypass proxy server"));
+	check = servlist_create_check (2, net->flags & FLAG_USE_SSL, table3,
+								  13, 1, _("Use SSL for all the servers on this network"));
+#ifndef USE_OPENSSL
+	gtk_widget_set_sensitive (check, FALSE);
+#endif
+	check = servlist_create_check (5, net->flags & FLAG_ALLOW_INVALID, table3,
+								  14, 1, _("Accept invalid SSL certificate"));
+#ifndef USE_OPENSSL
+	gtk_widget_set_sensitive (check, FALSE);
+#endif
+
+	edit_entry_join =
+		servlist_create_entry (table3, _("_Favorite channels:"), 15,
+									  net->autojoin, 0,
+				  _("Channels to join, separated by commas, but not spaces!"));
+
+	edit_entry_cmd =
+		servlist_create_entry (table3, _("Connect command:"), 16,
+									  net->command, 0,
+					_("Extra command to execute after connecting. If you need more than one, set this to LOAD -e <filename>, where <filename> is a text-file full of commands to execute."));
+
+	edit_entry_nickserv =
+		servlist_create_entry (table3, _("Nickserv password:"), 17,
+									  net->nickserv, 0,
+					_("If your nickname requires a password, enter it here. Not all IRC networks support this."));
+	gtk_entry_set_visibility (GTK_ENTRY (edit_entry_nickserv), FALSE);
+
+	edit_entry_pass =
+		servlist_create_entry (table3, _("Server password:"), 18,
+									  net->pass, 0,
+					_("Password for the server, if in doubt, leave blank."));
+	gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE);
+
+	label34 = gtk_label_new (_("Character set:"));
+	gtk_widget_show (label34);
+	gtk_table_attach (GTK_TABLE (table3), label34, 1, 2, 19, 20,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label34), 0, 0.5);
+
+	comboboxentry_charset = servlist_create_charsetcombo ();
+	ignore_changed = TRUE;
+	gtk_entry_set_text (GTK_ENTRY (GTK_BIN (comboboxentry_charset)->child), net->encoding ? net->encoding : "System default");
+	ignore_changed = FALSE;
+	gtk_widget_show (comboboxentry_charset);
+	gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 2, 3, 19, 20,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	hbox1 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox1);
+	gtk_table_attach (GTK_TABLE (table3), hbox1, 1, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+
+	scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow2);
+	gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow2, TRUE, TRUE, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2),
+													 GTK_SHADOW_IN);
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	model = GTK_TREE_MODEL (store);
+
+	edit_tree = treeview_servers = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_widget_show (treeview_servers);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow2), treeview_servers);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_servers),
+												  FALSE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editserver_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (treeview_servers), -1,
+						 		0, renderer,
+						 		"text", 0,
+								"editable", 1,
+								NULL);
+
+	vbuttonbox1 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox1);
+	gtk_box_pack_start (GTK_BOX (hbox1), vbuttonbox1, FALSE, FALSE, 3);
+
+	buttonadd = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (buttonadd), "clicked",
+							G_CALLBACK (servlist_addserver_cb), edit_tree);
+	gtk_widget_show (buttonadd);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd);
+	GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT);
+
+	buttonremove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (buttonremove), "clicked",
+							G_CALLBACK (servlist_deleteserver_cb), NULL);
+	gtk_widget_show (buttonremove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove);
+	GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT);
+
+	buttonedit = gtk_button_new_with_mnemonic (_("_Edit"));
+	g_signal_connect (G_OBJECT (buttonedit), "clicked",
+							G_CALLBACK (servlist_editserverbutton_cb), NULL);
+	gtk_widget_show (buttonedit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit);
+	GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT);
+
+	hseparator2 = gtk_hseparator_new ();
+	gtk_widget_show (hseparator2);
+	gtk_box_pack_start (GTK_BOX (vbox5), hseparator2, FALSE, FALSE, 8);
+
+	hbuttonbox4 = gtk_hbutton_box_new ();
+	gtk_widget_show (hbuttonbox4);
+	gtk_box_pack_start (GTK_BOX (vbox5), hbuttonbox4, FALSE, FALSE, 0);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox4),
+										GTK_BUTTONBOX_END);
+
+	button10 = gtk_button_new_from_stock ("gtk-close");
+	g_signal_connect (G_OBJECT (button10), "clicked",
+							G_CALLBACK (servlist_edit_close_cb), 0);
+	gtk_widget_show (button10);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox4), button10);
+	GTK_WIDGET_SET_FLAGS (button10, GTK_CAN_DEFAULT);
+
+	if (net->flags & FLAG_USE_GLOBAL)
+	{
+		gtk_widget_hide (edit_label_nick);
+		gtk_widget_hide (edit_entry_nick);
+
+		gtk_widget_hide (edit_label_nick2);
+		gtk_widget_hide (edit_entry_nick2);
+
+		gtk_widget_hide (edit_label_user);
+		gtk_widget_hide (edit_entry_user);
+
+		gtk_widget_hide (edit_label_real);
+		gtk_widget_hide (edit_entry_real);
+	}
+
+	gtk_widget_grab_focus (button10);
+	gtk_widget_grab_default (button10);
+
+	return editwindow;
+}
+
+static GtkWidget *
+servlist_open_networks (void)
+{
+	GtkWidget *servlist;
+	GtkWidget *vbox1;
+	GtkWidget *label2;
+	GtkWidget *table1;
+	GtkWidget *label3;
+	GtkWidget *label4;
+	GtkWidget *label5;
+	GtkWidget *label6;
+	GtkWidget *label7;
+	GtkWidget *entry1;
+	GtkWidget *entry2;
+	GtkWidget *entry3;
+	GtkWidget *entry4;
+	GtkWidget *entry5;
+	GtkWidget *vbox2;
+	GtkWidget *label1;
+	GtkWidget *table4;
+	GtkWidget *scrolledwindow3;
+	GtkWidget *treeview_networks;
+	GtkWidget *checkbutton_skip;
+	GtkWidget *checkbutton_fav;
+	GtkWidget *hbox;
+	GtkWidget *vbuttonbox2;
+	GtkWidget *button_add;
+	GtkWidget *button_remove;
+	GtkWidget *button_edit;
+	GtkWidget *button_sort;
+	GtkWidget *hseparator1;
+	GtkWidget *hbuttonbox1;
+	GtkWidget *button_connect;
+	GtkWidget *button_close;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+
+	servlist = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (servlist), 4);
+	gtk_window_set_title (GTK_WINDOW (servlist), _("XChat: Network List"));
+	gtk_window_set_default_size (GTK_WINDOW (servlist), win_width, win_height);
+	gtk_window_set_position (GTK_WINDOW (servlist), GTK_WIN_POS_MOUSE);
+	gtk_window_set_role (GTK_WINDOW (servlist), "servlist");
+	gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG);
+	if (current_sess)
+		gtk_window_set_transient_for (GTK_WINDOW (servlist), GTK_WINDOW (current_sess->gui->window));
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox1);
+	gtk_container_add (GTK_CONTAINER (servlist), vbox1);
+
+	label2 = bold_label (_("User Information"));
+	gtk_box_pack_start (GTK_BOX (vbox1), label2, FALSE, FALSE, 0);
+
+	table1 = gtk_table_new (5, 2, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (vbox1), table1, FALSE, FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table1), 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 4);
+
+	label3 = gtk_label_new_with_mnemonic (_("_Nick name:"));
+	gtk_widget_show (label3);
+	gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5);
+
+	label4 = gtk_label_new (_("Second choice:"));
+	gtk_widget_show (label4);
+	gtk_table_attach (GTK_TABLE (table1), label4, 0, 1, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
+
+	label5 = gtk_label_new (_("Third choice:"));
+	gtk_widget_show (label5);
+	gtk_table_attach (GTK_TABLE (table1), label5, 0, 1, 2, 3,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5);
+
+	label6 = gtk_label_new_with_mnemonic (_("_User name:"));
+	gtk_widget_show (label6);
+	gtk_table_attach (GTK_TABLE (table1), label6, 0, 1, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5);
+
+	label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
+	gtk_widget_show (label7);
+	gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label7), 0, 0.5);
+
+	entry_nick1 = entry1 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry1), prefs.nick1);
+	gtk_widget_show (entry1);
+	gtk_table_attach (GTK_TABLE (table1), entry1, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_nick2 = entry2 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry2), prefs.nick2);
+	gtk_widget_show (entry2);
+	gtk_table_attach (GTK_TABLE (table1), entry2, 1, 2, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_nick3 = entry3 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry3), prefs.nick3);
+	gtk_widget_show (entry3);
+	gtk_table_attach (GTK_TABLE (table1), entry3, 1, 2, 2, 3,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_guser = entry4 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry4), prefs.username);
+	gtk_widget_show (entry4);
+	gtk_table_attach (GTK_TABLE (table1), entry4, 1, 2, 3, 4,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_greal = entry5 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry5), prefs.realname);
+	gtk_widget_show (entry5);
+	gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	vbox2 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox2);
+	gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0);
+
+	label1 = bold_label (_("Networks"));
+	gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0);
+
+	table4 = gtk_table_new (2, 2, FALSE);
+	gtk_widget_show (table4);
+	gtk_box_pack_start (GTK_BOX (vbox2), table4, TRUE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table4), 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table4), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table4), 3);
+
+	scrolledwindow3 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow3);
+	gtk_table_attach (GTK_TABLE (table4), scrolledwindow3, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow3),
+											  GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow3),
+													 GTK_SHADOW_IN);
+
+	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT);
+	model = GTK_TREE_MODEL (store);
+
+	networks_tree = treeview_networks = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_widget_show (treeview_networks);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow3), treeview_networks);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_networks),
+												  FALSE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_celledit_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (treeview_networks), -1,
+						 		0, renderer,
+						 		"text", 0,
+								"editable", 1,
+								"weight", 2,
+								NULL);
+
+	hbox = gtk_hbox_new (0, FALSE);
+	gtk_table_attach (GTK_TABLE (table4), hbox, 0, 2, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_widget_show (hbox);
+
+	checkbutton_skip =
+		gtk_check_button_new_with_mnemonic (_("Skip network list on startup"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_skip),
+											prefs.slist_skip);
+	gtk_container_add (GTK_CONTAINER (hbox), checkbutton_skip);
+	g_signal_connect (G_OBJECT (checkbutton_skip), "toggled",
+							G_CALLBACK (no_servlist), 0);
+	gtk_widget_show (checkbutton_skip);
+
+	checkbutton_fav =
+		gtk_check_button_new_with_mnemonic (_("Show favorites only"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_fav),
+											prefs.slist_fav);
+	gtk_container_add (GTK_CONTAINER (hbox), checkbutton_fav);
+	g_signal_connect (G_OBJECT (checkbutton_fav), "toggled",
+							G_CALLBACK (fav_servlist), 0);
+	gtk_widget_show (checkbutton_fav);
+
+	vbuttonbox2 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox2), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox2), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox2);
+	gtk_table_attach (GTK_TABLE (table4), vbuttonbox2, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	button_add = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (button_add), "clicked",
+							G_CALLBACK (servlist_addnet_cb), networks_tree);
+	gtk_widget_show (button_add);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_add);
+	GTK_WIDGET_SET_FLAGS (button_add, GTK_CAN_DEFAULT);
+
+	button_remove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (button_remove), "clicked",
+							G_CALLBACK (servlist_deletenet_cb), 0);
+	gtk_widget_show (button_remove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_remove);
+	GTK_WIDGET_SET_FLAGS (button_remove, GTK_CAN_DEFAULT);
+
+	button_edit = gtk_button_new_with_mnemonic (_("_Edit..."));
+	g_signal_connect (G_OBJECT (button_edit), "clicked",
+							G_CALLBACK (servlist_edit_cb), 0);
+	gtk_widget_show (button_edit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_edit);
+	GTK_WIDGET_SET_FLAGS (button_edit, GTK_CAN_DEFAULT);
+
+	button_sort = gtk_button_new_with_mnemonic (_("_Sort"));
+	add_tip (button_sort, _("Sorts the network list in alphabetical order. "
+				"Use SHIFT-UP and SHIFT-DOWN keys to move a row."));
+	g_signal_connect (G_OBJECT (button_sort), "clicked",
+							G_CALLBACK (servlist_sort), 0);
+	gtk_widget_show (button_sort);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort);
+	GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT);
+
+	button_sort = gtk_button_new_with_mnemonic (_("_Favor"));
+	add_tip (button_sort, _("Mark or unmark this network as a favorite."));
+	g_signal_connect (G_OBJECT (button_sort), "clicked",
+							G_CALLBACK (servlist_favor), 0);
+	gtk_widget_show (button_sort);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort);
+	GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT);
+
+	hseparator1 = gtk_hseparator_new ();
+	gtk_widget_show (hseparator1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hseparator1, FALSE, TRUE, 4);
+
+	hbuttonbox1 = gtk_hbutton_box_new ();
+	gtk_widget_show (hbuttonbox1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (hbuttonbox1), 8);
+
+	button_close = gtk_button_new_from_stock ("gtk-close");
+	gtk_widget_show (button_close);
+	g_signal_connect (G_OBJECT (button_close), "clicked",
+							G_CALLBACK (servlist_close_cb), 0);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_close);
+	GTK_WIDGET_SET_FLAGS (button_close, GTK_CAN_DEFAULT);
+
+	button_connect = gtkutil_button (hbuttonbox1, GTK_STOCK_CONNECT, NULL,
+												servlist_connect_cb, NULL, _("C_onnect"));
+	GTK_WIDGET_SET_FLAGS (button_connect, GTK_CAN_DEFAULT);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5);
+
+	gtk_widget_grab_focus (networks_tree);
+	gtk_widget_grab_default (button_close);
+	return servlist;
+}
+
+void
+fe_serverlist_open (session *sess)
+{
+	if (serverlist_win)
+	{
+		gtk_window_present (GTK_WINDOW (serverlist_win));
+		return;
+	}
+
+	servlist_sess = sess;
+
+	serverlist_win = servlist_open_networks ();
+	gtkutil_set_icon (serverlist_win);
+
+	servlist_networks_populate (networks_tree, network_list);
+
+	g_signal_connect (G_OBJECT (serverlist_win), "delete_event",
+						 	G_CALLBACK (servlist_delete_cb), 0);
+	g_signal_connect (G_OBJECT (serverlist_win), "configure_event",
+							G_CALLBACK (servlist_configure_cb), 0);
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree))),
+							"changed", G_CALLBACK (servlist_network_row_cb), NULL);
+	g_signal_connect (G_OBJECT (networks_tree), "key_press_event",
+							G_CALLBACK (servlist_net_keypress_cb), networks_tree);
+
+	gtk_widget_show (serverlist_win);
+}
diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c
new file mode 100644
index 00000000..517e0944
--- /dev/null
+++ b/src/fe-gtk/setup.c
@@ -0,0 +1,2137 @@
+/* X-Chat
+ * Copyright (C) 2004-2007 Peter Zelezny.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/text.h"
+#include "../common/userlist.h"
+#include "../common/util.h"
+#include "../common/xchatc.h"
+#include "fe-gtk.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "palette.h"
+#include "pixmaps.h"
+#include "menu.h"
+
+#include <gtk/gtkcolorseldialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmisc.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkalignment.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkfontsel.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkcombobox.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreestore.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkhscale.h>
+#ifdef WIN32
+#include "../common/fe.h"
+#endif
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#include <gtkspell/gtkspell.h>
+#endif
+#ifdef USE_LIBSEXY
+#include "sexy-spell-entry.h"
+#endif
+
+GtkStyle *create_input_style (GtkStyle *);
+
+#define LABEL_INDENT 12
+
+static int last_selected_page = 0;
+static int last_selected_row = 0; /* sound row */
+static gboolean color_change;
+static struct xchatprefs setup_prefs;
+static GtkWidget *cancel_button;
+static GtkWidget *font_dialog = NULL;
+
+enum
+{
+	ST_END,
+	ST_TOGGLE,
+	ST_TOGGLR,
+	ST_3OGGLE,
+	ST_ENTRY,
+	ST_EFONT,
+	ST_EFILE,
+	ST_EFOLDER,
+	ST_MENU,
+	ST_RADIO,
+	ST_NUMBER,
+	ST_HSCALE,
+	ST_HEADER,
+	ST_LABEL,
+	ST_ALERTHEAD
+};
+
+typedef struct
+{
+	int type;
+	char *label;
+	int offset;
+	char *tooltip;
+	char const *const *list;
+	int extra;
+} setting;
+
+
+static const setting textbox_settings[] =
+{
+	{ST_HEADER,	N_("Text Box Appearance"),0,0,0},
+	{ST_EFONT,  N_("Font:"), P_OFFSETNL(font_normal), 0, 0, sizeof prefs.font_normal},
+	{ST_EFILE,  N_("Background image:"), P_OFFSETNL(background), 0, 0, sizeof prefs.background},
+	{ST_NUMBER,	N_("Scrollback lines:"), P_OFFINTNL(max_lines),0,0,100000},
+	{ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(colorednicks),
+					N_("Give each person on IRC a different color"),0,0},
+	{ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(indent_nicks),
+					N_("Make nick names right-justified"),0,0},
+	{ST_TOGGLE, N_("Transparent background"), P_OFFINTNL(transparent),0,0,0},
+	{ST_TOGGLR, N_("Show marker line"), P_OFFINTNL(show_marker),
+					N_("Insert a red line after the last read text."),0,0},
+	{ST_HEADER, N_("Transparency Settings"), 0,0,0},
+	{ST_HSCALE, N_("Red:"), P_OFFINTNL(tint_red),0,0,0},
+	{ST_HSCALE, N_("Green:"), P_OFFINTNL(tint_green),0,0,0},
+	{ST_HSCALE, N_("Blue:"), P_OFFINTNL(tint_blue),0,0,0},
+
+	{ST_HEADER,	N_("Time Stamps"),0,0,0},
+	{ST_TOGGLE, N_("Enable time stamps"), P_OFFINTNL(timestamp),0,0,2},
+	{ST_ENTRY,  N_("Time stamp format:"), P_OFFSETNL(stamp_format),
+					N_("See strftime manpage for details."),0,sizeof prefs.stamp_format},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const tabcompmenu[] = 
+{
+	N_("A-Z"),
+	N_("Last-spoke order"),
+	NULL
+};
+
+static const setting inputbox_settings[] =
+{
+	{ST_HEADER, N_("Input box"),0,0,0},
+	{ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_inputbox),0,0,0},
+#if defined(USE_GTKSPELL) || defined(USE_LIBSEXY)
+	{ST_TOGGLE, N_("Spell checking"), P_OFFINTNL(gui_input_spell),0,0,0},
+#endif
+
+	{ST_HEADER, N_("Nick Completion"),0,0,0},
+	{ST_TOGGLE, N_("Automatic nick completion (without TAB key)"), P_OFFINTNL(nickcompletion),
+					0,0,0},
+	{ST_ENTRY,	N_("Nick completion suffix:"), P_OFFSETNL(nick_suffix),0,0,sizeof prefs.nick_suffix},
+	{ST_MENU,	N_("Nick completion sorted:"), P_OFFINTNL(completion_sort), 0, tabcompmenu, 0},
+
+#if 0	/* obsolete */
+	{ST_HEADER, N_("Input Box Codes"),0,0,0},
+	{ST_TOGGLE, N_("Interpret %nnn as an ASCII value"), P_OFFINTNL(perc_ascii),0,0,0},
+	{ST_TOGGLE, N_("Interpret %C, %B as Color, Bold etc"), P_OFFINTNL(perc_color),0,0,0},
+#endif
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+/*static const char *const lagmenutext[] = 
+{
+	N_("Off"),
+	N_("Graph"),
+	N_("Info text"),
+	N_("Both"),
+	NULL
+};*/
+
+static const char *const ulmenutext[] = 
+{
+	N_("A-Z, Ops first"),
+	N_("A-Z"),
+	N_("Z-A, Ops last"),
+	N_("Z-A"),
+	N_("Unsorted"),
+	NULL
+};
+
+static const char *const cspos[] =
+{
+	N_("Left (Upper)"),
+	N_("Left (Lower)"),
+	N_("Right (Upper)"),
+	N_("Right (Lower)"),
+	N_("Top"),
+	N_("Bottom"),
+	N_("Hidden"),
+	NULL
+};
+
+static const char *const ulpos[] =
+{
+	N_("Left (Upper)"),
+	N_("Left (Lower)"),
+	N_("Right (Upper)"),
+	N_("Right (Lower)"),
+	NULL
+};
+
+static const setting userlist_settings[] =
+{
+	{ST_HEADER,	N_("User List"),0,0,0},
+	{ST_TOGGLE, N_("Show hostnames in user list"), P_OFFINTNL(showhostname_in_userlist), 0, 0, 0},
+	{ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_namelistgad),0,0,0},
+/*	{ST_TOGGLE, N_("Resizable user list"), P_OFFINTNL(paned_userlist),0,0,0},*/
+	{ST_MENU,	N_("User list sorted by:"), P_OFFINTNL(userlist_sort), 0, ulmenutext, 0},
+	{ST_MENU,	N_("Show user list at:"), P_OFFINTNL(gui_ulist_pos), 0, ulpos, 1},
+
+	{ST_HEADER,	N_("Away tracking"),0,0,0},
+	{ST_TOGGLE,	N_("Track the Away status of users and mark them in a different color"), P_OFFINTNL(away_track),0,0,2},
+	{ST_NUMBER, N_("On channels smaller than:"), P_OFFINTNL(away_size_max),0,0,10000},
+
+	{ST_HEADER,	N_("Action Upon Double Click"),0,0,0},
+	{ST_ENTRY,	N_("Execute command:"), P_OFFSETNL(doubleclickuser), 0, 0, sizeof prefs.doubleclickuser},
+
+/*	{ST_HEADER,	N_("Extra Gadgets"),0,0,0},
+	{ST_MENU,	N_("Lag meter:"), P_OFFINTNL(lagometer), 0, lagmenutext, 0},
+	{ST_MENU,	N_("Throttle meter:"), P_OFFINTNL(throttlemeter), 0, lagmenutext, 0},*/
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const tabwin[] =
+{
+	N_("Windows"),
+	N_("Tabs"),
+	NULL
+};
+
+#if 0
+static const char *const focusnewtabsmenu[] =
+{
+	N_("Never"),
+	N_("Always"),
+	N_("Only requested tabs"),
+	NULL
+};
+#endif
+
+static const char *const swtype[] =
+{
+	N_("Tabs"),	/* 0 tabs */
+	"",			/* 1 reserved */
+	N_("Tree"),	/* 2 tree */
+	NULL
+};
+
+static const setting tabs_settings[] =
+{
+	/*{ST_HEADER,	N_("Channel Switcher"),0,0,0},*/
+	{ST_RADIO,  N_("Switcher type:"),P_OFFINTNL(tab_layout), 0, swtype, 0},
+	{ST_TOGGLE, N_("Open an extra tab for server messages"), P_OFFINTNL(use_server_tab), 0, 0, 0},
+	{ST_TOGGLE, N_("Open an extra tab for server notices"), P_OFFINTNL(notices_tabs), 0, 0, 0},
+	{ST_TOGGLE, N_("Open a new tab when you receive a private message"), P_OFFINTNL(autodialog), 0, 0, 0},
+	{ST_TOGGLE, N_("Sort tabs in alphabetical order"), P_OFFINTNL(tab_sort), 0, 0, 0},
+	{ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(tab_small), 0, 0, 0},
+#if 0
+	{ST_MENU,	N_("Focus new tabs:"), P_OFFINTNL(newtabstofront), 0, focusnewtabsmenu, 0},
+#endif
+	{ST_MENU,	N_("Show channel switcher at:"), P_OFFINTNL(tab_pos), 0, cspos, 1},
+	{ST_NUMBER,	N_("Shorten tab labels to:"), P_OFFINTNL(truncchans), 0, (const char **)N_("letters."), 99},
+
+	{ST_HEADER,	N_("Tabs or Windows"),0,0,0},
+	{ST_MENU,	N_("Open channels in:"), P_OFFINTNL(tabchannels), 0, tabwin, 0},
+	{ST_MENU,	N_("Open dialogs in:"), P_OFFINTNL(privmsgtab), 0, tabwin, 0},
+	{ST_MENU,	N_("Open utilities in:"), P_OFFINTNL(windows_as_tabs), N_("Open DCC, Ignore, Notify etc, in tabs or windows?"), tabwin, 0},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const dccaccept[] =
+{
+	N_("No"),
+	N_("Yes"),
+	N_("Browse for save folder every time"),
+	NULL
+};
+
+static const setting filexfer_settings[] =
+{
+	{ST_HEADER, N_("Files and Directories"), 0, 0, 0},
+	{ST_MENU,	N_("Auto accept file offers:"), P_OFFINTNL(autodccsend), 0, dccaccept, 0},
+	{ST_EFOLDER,N_("Download files to:"), P_OFFSETNL(dccdir), 0, 0, sizeof prefs.dccdir},
+	{ST_EFOLDER,N_("Move completed files to:"), P_OFFSETNL(dcc_completed_dir), 0, 0, sizeof prefs.dcc_completed_dir},
+	{ST_TOGGLE, N_("Save nick name in filenames"), P_OFFINTNL(dccwithnick), 0, 0, 0},
+
+	{ST_HEADER, N_("Network Settings"), 0, 0, 0},
+	{ST_TOGGLE, N_("Get my address from the IRC server"), P_OFFINTNL(ip_from_server),
+					N_("Asks the IRC server for your real address. Use this if you have a 192.168.*.* address!"), 0, 0},
+	{ST_ENTRY,	N_("DCC IP address:"), P_OFFSETNL(dcc_ip_str),
+					N_("Claim you are at this address when offering files."), 0, sizeof prefs.dcc_ip_str},
+	{ST_NUMBER,	N_("First DCC send port:"), P_OFFINTNL(first_dcc_send_port), 0, 0, 65535},
+	{ST_NUMBER,	N_("Last DCC send port:"), P_OFFINTNL(last_dcc_send_port), 0, 
+		(const char **)N_("!Leave ports at zero for full range."), 65535},
+
+	{ST_HEADER, N_("Maximum File Transfer Speeds (bytes per second)"), 0, 0, 0},
+	{ST_NUMBER,	N_("One upload:"), P_OFFINTNL(dcc_max_send_cps), 
+					N_("Maximum speed for one transfer"), 0, 1000000},
+	{ST_NUMBER,	N_("One download:"), P_OFFINTNL(dcc_max_get_cps),
+					N_("Maximum speed for one transfer"), 0, 1000000},
+	{ST_NUMBER,	N_("All uploads combined:"), P_OFFINTNL(dcc_global_max_send_cps),
+					N_("Maximum speed for all files"), 0, 1000000},
+	{ST_NUMBER,	N_("All downloads combined:"), P_OFFINTNL(dcc_global_max_get_cps),
+					N_("Maximum speed for all files"), 0, 1000000},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const int balloonlist[3] =
+{
+	P_OFFINTNL(input_balloon_chans), P_OFFINTNL(input_balloon_priv), P_OFFINTNL(input_balloon_hilight)
+};
+
+static const int trayblinklist[3] =
+{
+	P_OFFINTNL(input_tray_chans), P_OFFINTNL(input_tray_priv), P_OFFINTNL(input_tray_hilight)
+};
+
+static const int taskbarlist[3] =
+{
+	P_OFFINTNL(input_flash_chans), P_OFFINTNL(input_flash_priv), P_OFFINTNL(input_flash_hilight)
+};
+
+static const int beeplist[3] =
+{
+	P_OFFINTNL(input_beep_chans), P_OFFINTNL(input_beep_priv), P_OFFINTNL(input_beep_hilight)
+};
+
+static const setting alert_settings[] =
+{
+	{ST_HEADER,	N_("Alerts"),0,0,0},
+
+	{ST_ALERTHEAD},
+#ifndef WIN32
+	{ST_3OGGLE, N_("Show tray balloons on:"), 0, 0, (void *)balloonlist, 0},
+#endif
+	{ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0},
+	{ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
+	{ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0},
+
+	{ST_TOGGLE,	N_("Enable system tray icon"), P_OFFINTNL(gui_tray), 0, 0, 0},
+
+	{ST_HEADER,	N_("Highlighted Messages"),0,0,0},
+	{ST_LABEL,	N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},
+
+	{ST_ENTRY,	N_("Extra words to highlight:"), P_OFFSETNL(irc_extra_hilight), 0, 0, sizeof prefs.irc_extra_hilight},
+	{ST_ENTRY,	N_("Nick names not to highlight:"), P_OFFSETNL(irc_no_hilight), 0, 0, sizeof prefs.irc_no_hilight},
+	{ST_ENTRY,	N_("Nick names to always highlight:"), P_OFFSETNL(irc_nick_hilight), 0, 0, sizeof prefs.irc_nick_hilight},
+	{ST_LABEL,	N_("Separate multiple words with commas.\nWildcards are accepted.")},
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const setting general_settings[] =
+{
+	{ST_HEADER,	N_("Default Messages"),0,0,0},
+	{ST_ENTRY,	N_("Quit:"), P_OFFSETNL(quitreason), 0, 0, sizeof prefs.quitreason},
+	{ST_ENTRY,	N_("Leave channel:"), P_OFFSETNL(partreason), 0, 0, sizeof prefs.partreason},
+	{ST_ENTRY,	N_("Away:"), P_OFFSETNL(awayreason), 0, 0, sizeof prefs.awayreason},
+
+	{ST_HEADER,	N_("Away"),0,0,0},
+	{ST_TOGGLE,	N_("Announce away messages"), P_OFFINTNL(show_away_message),
+					N_("Announce your away messages to all channels"), 0, 0},
+	{ST_TOGGLE,	N_("Show away once"), P_OFFINTNL(show_away_once), N_("Show identical away messages only once"), 0, 0},
+	{ST_TOGGLE,	N_("Automatically unmark away"), P_OFFINTNL(auto_unmark_away), N_("Unmark yourself as away before sending messages"), 0, 0},
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+#if 0
+static const setting advanced_settings[] =
+{
+	{ST_HEADER,	N_("Advanced Settings"),0,0,0},
+	{ST_NUMBER,	N_("Auto reconnect delay:"), P_OFFINTNL(recon_delay), 0, 0, 9999},
+	{ST_TOGGLE,	N_("Display MODEs in raw form"), P_OFFINTNL(raw_modes), 0, 0, 0},
+	{ST_TOGGLE,	N_("Whois on notify"), P_OFFINTNL(whois_on_notifyonline), N_("Sends a /WHOIS when a user comes online in your notify list"), 0, 0},
+	{ST_TOGGLE,	N_("Hide join and part messages"), P_OFFINTNL(confmode), N_("Hide channel join/part messages by default"), 0, 0},
+	{ST_HEADER,	N_("Auto Open DCC Windows"),0,0,0},
+	{ST_TOGGLE, N_("Send window"), P_OFFINTNL(autoopendccsendwindow), 0, 0, 0},
+	{ST_TOGGLE, N_("Receive window"), P_OFFINTNL(autoopendccrecvwindow), 0, 0, 0},
+	{ST_TOGGLE, N_("Chat window"), P_OFFINTNL(autoopendccchatwindow), 0, 0, 0},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+#endif
+
+static const setting logging_settings[] =
+{
+	{ST_HEADER,	N_("Logging"),0,0,0},
+	{ST_TOGGLE,	N_("Display scrollback from previous session"), P_OFFINTNL(text_replay), 0, 0, 0},
+	{ST_TOGGLE,	N_("Enable logging of conversations to disk"), P_OFFINTNL(logging), 0, 0, 2},
+	{ST_ENTRY,	N_("Log filename:"), P_OFFSETNL(logmask), 0, 0, sizeof prefs.logmask},
+	{ST_LABEL,	N_("%s=Server %c=Channel %n=Network.")},
+
+	{ST_HEADER,	N_("Time Stamps"),0,0,0},
+	{ST_TOGGLE,	N_("Insert timestamps in logs"), P_OFFINTNL(timestamp_logs), 0, 0, 2},
+	{ST_ENTRY,	N_("Log timestamp format:"), P_OFFSETNL(timestamp_log_format), 0, 0, sizeof prefs.timestamp_log_format},
+	{ST_LABEL,	N_("See strftime manpage for details.")},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const proxytypes[] =
+{
+	N_("(Disabled)"),
+	N_("Wingate"),
+	N_("Socks4"),
+	N_("Socks5"),
+	N_("HTTP"),
+#ifdef USE_MSPROXY
+	N_("MS Proxy (ISA)"),
+#endif
+#ifdef USE_LIBPROXY
+	N_("Auto"),
+#endif
+	NULL
+};
+
+static const char *const proxyuse[] =
+{
+	N_("All Connections"),
+	N_("IRC Server Only"),
+	N_("DCC Get Only"),
+	NULL
+};
+
+static const setting network_settings[] =
+{
+	{ST_HEADER,	N_("Your Address"), 0, 0, 0, 0},
+	{ST_ENTRY,	N_("Bind to:"), P_OFFSETNL(hostname), 0, 0, sizeof prefs.hostname},
+	{ST_LABEL,	N_("Only useful for computers with multiple addresses.")},
+
+	{ST_HEADER,	N_("Proxy Server"), 0, 0, 0, 0},
+	{ST_ENTRY,	N_("Hostname:"), P_OFFSETNL(proxy_host), 0, 0, sizeof prefs.proxy_host},
+	{ST_NUMBER,	N_("Port:"), P_OFFINTNL(proxy_port), 0, 0, 65535},
+	{ST_MENU,	N_("Type:"), P_OFFINTNL(proxy_type), 0, proxytypes, 0},
+	{ST_MENU,	N_("Use proxy for:"), P_OFFINTNL(proxy_use), 0, proxyuse, 0},
+
+	{ST_HEADER,	N_("Proxy Authentication"), 0, 0, 0, 0},
+#ifdef USE_MSPROXY
+	{ST_TOGGLE,	N_("Use Authentication (MS Proxy, HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0},
+#else
+	{ST_TOGGLE,	N_("Use Authentication (HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0},
+#endif
+	{ST_ENTRY,	N_("Username:"), P_OFFSETNL(proxy_user), 0, 0, sizeof prefs.proxy_user},
+	{ST_ENTRY,	N_("Password:"), P_OFFSETNL(proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.proxy_pass},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+#define setup_get_str(pr,set) (((char *)pr)+set->offset)
+#define setup_get_int(pr,set) *(((int *)pr)+set->offset)
+#define setup_get_int3(pr,off) *(((int *)pr)+off) 
+
+#define setup_set_int(pr,set,num) *((int *)pr+set->offset)=num
+#define setup_set_str(pr,set,str) strcpy(((char *)pr)+set->offset,str)
+
+
+static void
+setup_3oggle_cb (GtkToggleButton *but, unsigned int *setting)
+{
+	*setting = but->active;
+}
+
+static void
+setup_headlabel (GtkWidget *tab, int row, int col, char *text)
+{
+	GtkWidget *label;
+	char buf[128];
+	char *sp;
+
+	snprintf (buf, sizeof (buf), "<b><span size=\"smaller\">%s</span></b>", text);
+	sp = strchr (buf + 17, ' ');
+	if (sp)
+		*sp = '\n';
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, col, col + 1, row, row + 1, 0, 0, 4, 0);
+}
+
+static void
+setup_create_alert_header (GtkWidget *tab, int row, const setting *set)
+{
+	setup_headlabel (tab, row, 3, _("Channel Message"));
+	setup_headlabel (tab, row, 4, _("Private Message"));
+	setup_headlabel (tab, row, 5, _("Highlighted Message"));
+}
+
+/* makes 3 toggles side-by-side */
+
+static void
+setup_create_3oggle (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *label, *wid;
+	int *offsets = (int *)set->list;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[0]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[0]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 3, 4, row, row + 1, 0, 0, 0, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[1]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[1]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, 0, 0, 0, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[2]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[2]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 5, 6, row, row + 1, 0, 0, 0, 0);
+}
+
+static void
+setup_toggle_cb (GtkToggleButton *but, const setting *set)
+{
+	GtkWidget *label, *disable_wid;
+
+	setup_set_int (&setup_prefs, set, but->active ? 1 : 0);
+
+	/* does this toggle also enable/disable another widget? */
+	disable_wid = g_object_get_data (G_OBJECT (but), "nxt");
+	if (disable_wid)
+	{
+		gtk_widget_set_sensitive (disable_wid, but->active);
+		label = g_object_get_data (G_OBJECT (disable_wid), "lbl");
+		gtk_widget_set_sensitive (label, but->active);
+	}
+}
+
+static void
+setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1,
+							GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static GtkWidget *
+setup_create_toggleL (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_table_attach (GTK_TABLE (tab), wid, 2, row==6 ? 6 : 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	return wid;
+}
+
+#if 0
+static void
+setup_create_toggle (GtkWidget *box, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+}
+#endif
+
+static GtkWidget *
+setup_create_italic_label (char *text)
+{
+	GtkWidget *label;
+	char buf[256];
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", text);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+
+	return label;
+}
+
+static void
+setup_spin_cb (GtkSpinButton *spin, const setting *set)
+{
+	setup_set_int (&setup_prefs, set, gtk_spin_button_get_value_as_int (spin));
+}
+
+static GtkWidget *
+setup_create_spin (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *label, *wid, *rbox, *align;
+	char *text;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
+	gtk_table_attach (GTK_TABLE (table), align, 3, 4, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	rbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (align), rbox);
+
+	wid = gtk_spin_button_new_with_range (0, set->extra, 1);
+	g_object_set_data (G_OBJECT (wid), "lbl", label);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (setup_spin_cb), (gpointer)set);
+	gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
+
+	if (set->list)
+	{
+		text = _((char *)set->list);
+		if (text[0] == '!')
+			label = setup_create_italic_label (text + 1);
+		else
+			label = gtk_label_new (text);
+		gtk_box_pack_start (GTK_BOX (rbox), label, 0, 0, 6);
+	}
+
+	return wid;
+}
+
+static gint
+setup_apply_tint (int *tag)
+{
+	prefs.tint_red = setup_prefs.tint_red;
+	prefs.tint_green = setup_prefs.tint_green;
+	prefs.tint_blue = setup_prefs.tint_blue;
+	mg_update_xtext (current_sess->gui->xtext);
+	*tag = 0;
+	return 0;
+}
+
+static void
+setup_hscale_cb (GtkHScale *wid, const setting *set)
+{
+	static int tag = 0;
+
+	setup_set_int (&setup_prefs, set, gtk_range_get_value(GTK_RANGE(wid)));
+	if(tag == 0)
+		tag = g_idle_add ((GSourceFunc)setup_apply_tint, &tag);
+}
+
+static void
+setup_create_hscale (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_hscale_new_with_range (0., 255., 1.);
+	gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT);
+	gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT(wid), "value_changed",
+							G_CALLBACK (setup_hscale_cb), (gpointer)set);
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+}
+
+
+static GtkWidget *proxy_user; 	/* username GtkEntry */
+static GtkWidget *proxy_pass; 	/* password GtkEntry */
+
+static void
+setup_menu_cb (GtkWidget *cbox, const setting *set)
+{
+	int n = gtk_combo_box_get_active (GTK_COMBO_BOX (cbox));
+
+	/* set the prefs.<field> */
+	setup_set_int (&setup_prefs, set, n + set->extra);
+
+	if (set->list == proxytypes)
+	{
+		/* only HTTP and Socks5 can use a username/pass */
+		gtk_widget_set_sensitive (proxy_user, (n == 3 || n == 4 || n == 5));
+		gtk_widget_set_sensitive (proxy_pass, (n == 3 || n == 4 || n == 5));
+	}
+}
+
+static void
+setup_radio_cb (GtkWidget *item, const setting *set)
+{
+	if (GTK_TOGGLE_BUTTON (item)->active)
+	{
+		int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));
+		/* set the prefs.<field> */
+		setup_set_int (&setup_prefs, set, n);
+	}
+}
+
+static int
+setup_create_radio (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid, *hbox;
+	int i;
+	const char **text = (const char **)set->list;
+	GSList *group;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_table_attach (GTK_TABLE (table), hbox, 3, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	i = 0;
+	group = NULL;
+	while (text[i])
+	{
+		if (text[i][0] != 0)
+		{
+			wid = gtk_radio_button_new_with_mnemonic (group, _(text[i]));
+			/*if (set->tooltip)
+				add_tip (wid, _(set->tooltip));*/
+			group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wid));
+			gtk_container_add (GTK_CONTAINER (hbox), wid);
+			if (i == setup_get_int (&setup_prefs, set))
+				gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+			g_object_set_data (G_OBJECT (wid), "n", GINT_TO_POINTER (i));
+			g_signal_connect (G_OBJECT (wid), "toggled",
+									G_CALLBACK (setup_radio_cb), (gpointer)set);
+		}
+		i++;
+		row++;
+	}
+
+	return i;
+}
+
+/*
+static const char *id_strings[] =
+{
+	"",
+	"*",
+	"%C4*%C18%B%B",
+	"%U"
+};
+
+static void
+setup_id_menu_cb (GtkWidget *item, char *dest)
+{
+	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));
+
+	strcpy (dest, id_strings[n]);
+}
+
+static void
+setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest)
+{
+	GtkWidget *wid, *menu, *item;
+	int i, def = 0;
+	static const char *text[] =
+	{
+		("(disabled)"),
+		("A star (*)"),
+		("A red star (*)"),
+		("Underlined")
+	};
+
+	wid = gtk_label_new (label);
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_option_menu_new ();
+	menu = gtk_menu_new ();
+
+	for (i = 0; i < 4; i++)
+	{
+		if (strcmp (id_strings[i], dest) == 0)
+		{
+			def = i;
+			break;
+		}
+	}
+
+	i = 0;
+	while (text[i])
+	{
+		item = gtk_menu_item_new_with_label (_(text[i]));
+		g_object_set_data (G_OBJECT (item), "n", GINT_TO_POINTER (i));
+
+		gtk_widget_show (item);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+		g_signal_connect (G_OBJECT (item), "activate",
+								G_CALLBACK (setup_id_menu_cb), dest);
+		i++;
+	}
+
+	gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), menu);
+	gtk_option_menu_set_history (GTK_OPTION_MENU (wid), def);
+
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+*/
+
+static void
+setup_create_menu (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid, *cbox, *box;
+	const char **text = (const char **)set->list;
+	int i;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	cbox = gtk_combo_box_new_text ();
+
+	for (i = 0; text[i]; i++)
+		gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _(text[i]));
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (cbox),
+									  setup_get_int (&setup_prefs, set) - set->extra);
+	g_signal_connect (G_OBJECT (cbox), "changed",
+							G_CALLBACK (setup_menu_cb), (gpointer)set);
+
+	box = gtk_hbox_new (0, 0);
+	gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0);
+	gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static void
+setup_filereq_cb (GtkWidget *entry, char *file)
+{
+	if (file)
+	{
+		if (file[0])
+			gtk_entry_set_text (GTK_ENTRY (entry), file);
+	}
+}
+
+static void
+setup_browsefile_cb (GtkWidget *button, GtkWidget *entry)
+{
+	gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, 0);
+}
+
+static void
+setup_fontsel_destroy (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	font_dialog = NULL;
+}
+
+static void
+setup_fontsel_cb (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	GtkWidget *entry;
+	char *font_name;
+
+	entry = g_object_get_data (G_OBJECT (button), "e");
+	font_name = gtk_font_selection_dialog_get_font_name (dialog);
+
+	gtk_entry_set_text (GTK_ENTRY (entry), font_name);
+
+	g_free (font_name);
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	font_dialog = NULL;
+}
+
+static void
+setup_fontsel_cancel (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	font_dialog = NULL;
+}
+
+static void
+setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry)
+{
+	gtkutil_file_req (_("Select Download Folder"), setup_filereq_cb, entry, entry->text, FRF_CHOOSEFOLDER);
+}
+
+static void
+setup_browsefont_cb (GtkWidget *button, GtkWidget *entry)
+{
+	GtkFontSelection *sel;
+	GtkFontSelectionDialog *dialog;
+
+	dialog = (GtkFontSelectionDialog *) gtk_font_selection_dialog_new (_("Select font"));
+	font_dialog = (GtkWidget *)dialog;	/* global var */
+
+	sel = (GtkFontSelection *) dialog->fontsel;
+
+	if (GTK_ENTRY (entry)->text[0])
+		gtk_font_selection_set_font_name (sel, GTK_ENTRY (entry)->text);
+
+	g_object_set_data (G_OBJECT (dialog->ok_button), "e", entry);
+
+	g_signal_connect (G_OBJECT (dialog), "destroy",
+							G_CALLBACK (setup_fontsel_destroy), dialog);
+	g_signal_connect (G_OBJECT (dialog->ok_button), "clicked",
+							G_CALLBACK (setup_fontsel_cb), dialog);
+	g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked",
+							G_CALLBACK (setup_fontsel_cancel), dialog);
+
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
+setup_entry_cb (GtkEntry *entry, setting *set)
+{
+	int size;
+	int pos;
+	int len = strlen (entry->text);
+	unsigned char *p = entry->text;
+
+	/* need to truncate? */
+	if (len >= set->extra)
+	{
+		len = pos = 0;
+		while (1)
+		{
+			size = g_utf8_skip [*p];
+			len += size;
+			p += size;
+			/* truncate to "set->extra" BYTES */
+			if (len >= set->extra)
+			{
+				gtk_editable_delete_text (GTK_EDITABLE (entry), pos, -1);
+				break;
+			}
+			pos++;
+		}
+	}
+	else
+	{
+		setup_set_str (&setup_prefs, set, entry->text);
+	}
+}
+
+static void
+setup_create_label (GtkWidget *table, int row, const setting *set)
+{
+	gtk_table_attach (GTK_TABLE (table), setup_create_italic_label (_(set->label)),
+							set->extra ? 1 : 3, 5, row, row + 1, GTK_FILL,
+							GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static GtkWidget *
+setup_create_entry (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *label;
+	GtkWidget *wid, *bwid;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_entry_new ();
+	g_object_set_data (G_OBJECT (wid), "lbl", label);
+	if (set->list)
+		gtk_entry_set_visibility (GTK_ENTRY (wid), FALSE);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_entry_set_max_length (GTK_ENTRY (wid), set->extra - 1);
+	gtk_entry_set_text (GTK_ENTRY (wid), setup_get_str (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "changed",
+							G_CALLBACK (setup_entry_cb), (gpointer)set);
+
+	if (set->offset == P_OFFSETNL(proxy_user))
+		proxy_user = wid;
+	if (set->offset == P_OFFSETNL(proxy_pass))
+		proxy_pass = wid; 
+
+	/* only http and Socks5 can auth */
+	if ( (set->offset == P_OFFSETNL(proxy_pass) ||
+			set->offset == P_OFFSETNL(proxy_user)) &&
+	     (setup_prefs.proxy_type != 4 && setup_prefs.proxy_type != 3 && setup_prefs.proxy_type != 5) )
+		gtk_widget_set_sensitive (wid, FALSE);
+
+	if (set->type == ST_ENTRY)
+		gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1,
+								GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+	else
+	{
+		gtk_table_attach (GTK_TABLE (table), wid, 3, 5, row, row + 1,
+								GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+		bwid = gtk_button_new_with_label (_("Browse..."));
+		gtk_table_attach (GTK_TABLE (table), bwid, 5, 6, row, row + 1,
+								GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0);
+		if (set->type == ST_EFILE)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefile_cb), wid);
+		if (set->type == ST_EFONT)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefont_cb), wid);
+		if (set->type == ST_EFOLDER)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefolder_cb), wid);
+	}
+
+	return wid;
+}
+
+static void
+setup_create_header (GtkWidget *table, int row, char *labeltext)
+{
+	GtkWidget *label;
+	char buf[128];
+
+	if (row == 0)
+		snprintf (buf, sizeof (buf), "<b>%s</b>", _(labeltext));
+	else
+		snprintf (buf, sizeof (buf), "\n<b>%s</b>", _(labeltext));
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 5);
+}
+
+static GtkWidget *
+setup_create_frame (GtkWidget **left, GtkWidget *box)
+{
+	GtkWidget *tab, *hbox, *inbox = box;
+
+	tab = gtk_table_new (3, 2, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (tab), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (tab), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (tab), 3);
+	gtk_container_add (GTK_CONTAINER (inbox), tab);
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (inbox), hbox);
+
+	*left = gtk_vbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), *left, 0, 0, 0);
+
+	return tab;
+}
+
+static void
+open_data_cb (GtkWidget *button, gpointer data)
+{
+	fe_open_url (get_xdir_utf8 ());
+}
+
+static GtkWidget *
+setup_create_page (const setting *set)
+{
+	int i, row, do_disable;
+	GtkWidget *tab, *box, *left;
+	GtkWidget *wid = NULL, *prev;
+
+	box = gtk_vbox_new (FALSE, 1);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
+
+	tab = setup_create_frame (&left, box);
+
+	i = row = do_disable = 0;
+	while (set[i].type != ST_END)
+	{
+		prev = wid;
+
+		switch (set[i].type)
+		{
+		case ST_HEADER:
+			setup_create_header (tab, row, set[i].label);
+			break;
+		case ST_EFONT:
+		case ST_ENTRY:
+		case ST_EFILE:
+		case ST_EFOLDER:
+			wid = setup_create_entry (tab, row, &set[i]);
+			break;
+		case ST_TOGGLR:
+			row--;
+			setup_create_toggleR (tab, row, &set[i]);
+			break;
+		case ST_TOGGLE:
+			wid = setup_create_toggleL (tab, row, &set[i]);
+			do_disable = set[i].extra;
+			break;
+		case ST_3OGGLE:
+			setup_create_3oggle (tab, row, &set[i]);
+			break;
+		case ST_MENU:
+			setup_create_menu (tab, row, &set[i]);
+			break;
+		case ST_RADIO:
+			row += setup_create_radio (tab, row, &set[i]);
+			break;
+		case ST_NUMBER:
+			wid = setup_create_spin (tab, row, &set[i]);
+			break;
+		case ST_HSCALE:
+			setup_create_hscale (tab, row, &set[i]);
+			break;
+		case ST_LABEL:
+			setup_create_label (tab, row, &set[i]);
+			break;
+		case ST_ALERTHEAD:
+			setup_create_alert_header (tab, row, &set[i]);
+		}
+
+		/* will this toggle disable the "next" widget? */
+		do_disable--;
+		if (do_disable == 0)
+		{
+			/* setup_toggle_cb uses this data */
+			g_object_set_data (G_OBJECT (prev), "nxt", wid);
+			/* force initial sensitive state */
+			gtk_widget_set_sensitive (wid, GTK_TOGGLE_BUTTON (prev)->active);
+			gtk_widget_set_sensitive (g_object_get_data (G_OBJECT (wid), "lbl"),
+											  GTK_TOGGLE_BUTTON (prev)->active);
+		}
+
+		i++;
+		row++;
+	}
+
+#if 0
+	if (set == general_settings)
+	{
+		setup_create_id_menu (tab, _("Mark identified users with:"),	
+									 row, setup_prefs.irc_id_ytext);
+		setup_create_id_menu (tab, _("Mark not-identified users with:"),	
+									 row + 1, setup_prefs.irc_id_ntext);
+	}
+#endif
+
+	if (set == logging_settings)
+	{
+		GtkWidget *but = gtk_button_new_with_label (_("Open Data Folder"));
+		gtk_box_pack_start (GTK_BOX (left), but, 0, 0, 0);
+		g_signal_connect (G_OBJECT (but), "clicked",
+								G_CALLBACK (open_data_cb), 0);
+	}
+
+	return box;
+}
+
+static void
+setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog)
+{
+	GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog);
+	GdkColor *col;
+	GdkColor old_color;
+	GtkStyle *style;
+
+	col = g_object_get_data (G_OBJECT (button), "c");
+	old_color = *col;
+
+	button = g_object_get_data (G_OBJECT (button), "b");
+
+	if (!GTK_IS_WIDGET (button))
+	{
+		gtk_widget_destroy (dialog);
+		return;
+	}
+
+	color_change = TRUE;
+
+	gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), col);
+
+	gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE);
+
+	style = gtk_style_new ();
+	style->bg[0] = *col;
+	gtk_widget_set_style (button, style);
+	g_object_unref (style);
+
+	/* is this line correct?? */
+	gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1);
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+setup_color_cb (GtkWidget *button, gpointer userdata)
+{
+	GtkWidget *dialog;
+	GtkColorSelectionDialog *cdialog;
+	GdkColor *color;
+
+	color = &colors[GPOINTER_TO_INT (userdata)];
+
+	dialog = gtk_color_selection_dialog_new (_("Select color"));
+	cdialog = GTK_COLOR_SELECTION_DIALOG (dialog);
+
+	gtk_widget_hide (cdialog->help_button);
+	g_signal_connect (G_OBJECT (cdialog->ok_button), "clicked",
+							G_CALLBACK (setup_color_ok_cb), dialog);
+	g_signal_connect (G_OBJECT (cdialog->cancel_button), "clicked",
+							G_CALLBACK (gtkutil_destroy), dialog);
+	g_object_set_data (G_OBJECT (cdialog->ok_button), "c", color);
+	g_object_set_data (G_OBJECT (cdialog->ok_button), "b", button);
+	gtk_widget_set_sensitive (cdialog->help_button, FALSE);
+	gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), color);
+	gtk_widget_show (dialog);
+}
+
+static void
+setup_create_color_button (GtkWidget *table, int num, int row, int col)
+{
+	GtkWidget *but;
+	GtkStyle *style;
+	char buf[64];
+
+	if (num > 31)
+		strcpy (buf, "<span size=\"x-small\"> </span>");
+	else
+						/* 12345678901 23456789 01  23456789 */
+		sprintf (buf, "<span size=\"x-small\">%d</span>", num);
+	but = gtk_button_new_with_label (" ");
+	gtk_label_set_markup (GTK_LABEL (GTK_BIN (but)->child), buf);
+	/* win32 build uses this to turn off themeing */
+	g_object_set_data (G_OBJECT (but), "xchat-color", (gpointer)1);
+	gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	g_signal_connect (G_OBJECT (but), "clicked",
+							G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num));
+	style = gtk_style_new ();
+	style->bg[GTK_STATE_NORMAL] = colors[num];
+	gtk_widget_set_style (but, style);
+	g_object_unref (style);
+}
+
+static void
+setup_create_other_colorR (char *text, int num, int row, GtkWidget *tab)
+{
+	GtkWidget *label;
+
+	label = gtk_label_new (text);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 5, 9, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+	setup_create_color_button (tab, num, row, 9);
+}
+
+static void
+setup_create_other_color (char *text, int num, int row, GtkWidget *tab)
+{
+	GtkWidget *label;
+
+	label = gtk_label_new (text);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+	setup_create_color_button (tab, num, row, 3);
+}
+
+static GtkWidget *
+setup_create_color_page (void)
+{
+	GtkWidget *tab, *box, *label;
+	int i;
+
+	box = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
+
+	tab = gtk_table_new (9, 2, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (tab), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (tab), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (tab), 3);
+	gtk_container_add (GTK_CONTAINER (box), tab);
+
+	setup_create_header (tab, 0, N_("Text Colors"));
+
+	label = gtk_label_new (_("mIRC colors:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	for (i = 0; i < 16; i++)
+		setup_create_color_button (tab, i, 1, i+3);
+
+	label = gtk_label_new (_("Local colors:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	for (i = 16; i < 32; i++)
+		setup_create_color_button (tab, i, 2, (i+3) - 16);
+
+	setup_create_other_color (_("Foreground:"), COL_FG, 3, tab);
+	setup_create_other_colorR (_("Background:"), COL_BG, 3, tab);
+
+	setup_create_header (tab, 5, N_("Marking Text"));
+
+	setup_create_other_color (_("Foreground:"), COL_MARK_FG, 6, tab);
+	setup_create_other_colorR (_("Background:"), COL_MARK_BG, 6, tab);
+
+	setup_create_header (tab, 8, N_("Interface Colors"));
+
+	setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab);
+	setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab);
+	setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab);
+	setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab);
+	setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab);
+
+	return box;
+}
+
+/* === GLOBALS for sound GUI === */
+
+static GtkWidget *sndprog_entry;
+static GtkWidget *sndfile_entry;
+static GtkWidget *snddir_entry;
+static int ignore_changed = FALSE;
+
+extern struct text_event te[]; /* text.c */
+extern char *sound_files[];
+
+static void
+setup_snd_apply (void)
+{
+	strcpy (setup_prefs.sounddir, GTK_ENTRY (snddir_entry)->text);
+	strcpy (setup_prefs.soundcmd, GTK_ENTRY (sndprog_entry)->text);
+}
+
+static void
+setup_snd_populate (GtkTreeView * treeview)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	int i;
+
+	sel = gtk_tree_view_get_selection (treeview);
+	store = (GtkListStore *)gtk_tree_view_get_model (treeview);
+
+	for (i = NUM_XP-1; i >= 0; i--)
+	{
+		gtk_list_store_prepend (store, &iter);
+		if (sound_files[i])
+			gtk_list_store_set (store, &iter, 0, te[i].name, 1, sound_files[i], 2, i, -1);
+		else
+			gtk_list_store_set (store, &iter, 0, te[i].name, 1, "", 2, i, -1);
+		if (i == last_selected_row)
+		{
+			gtk_tree_selection_select_iter (sel, &iter);
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+			if (path)
+			{
+				gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
+				gtk_tree_path_free (path);
+			}
+		}
+	}
+}
+
+static int
+setup_snd_get_selected (GtkTreeSelection *sel, GtkTreeIter *iter)
+{
+	int n;
+	GtkTreeModel *model;
+
+	if (!gtk_tree_selection_get_selected (sel, &model, iter))
+		return -1;
+
+	gtk_tree_model_get (model, iter, 2, &n, -1);
+	return n;
+}
+
+static void
+setup_snd_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	int n;
+	GtkTreeIter iter;
+
+	n = setup_snd_get_selected (sel, &iter);
+	if (n == -1)
+		return;
+	last_selected_row = n;
+
+	ignore_changed = TRUE;
+	if (sound_files[n])
+		gtk_entry_set_text (GTK_ENTRY (sndfile_entry), sound_files[n]);
+	else
+		gtk_entry_set_text (GTK_ENTRY (sndfile_entry), "");
+	ignore_changed = FALSE;
+}
+
+static void
+setup_snd_add_columns (GtkTreeView * treeview)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeModel *model;
+
+	/* event column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, _("Event"), renderer,
+																"text", 0, NULL);
+
+	/* file column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, _("Sound file"), renderer,
+																"text", 1, NULL);
+
+	model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT));
+	gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model);
+	g_object_unref (model);
+}
+
+static void
+setup_autotoggle_cb (GtkToggleButton *but, GtkToggleButton *ext)
+{
+	if (but->active)
+	{
+		setup_prefs.soundcmd[0] = 0;
+		gtk_entry_set_text (GTK_ENTRY (sndprog_entry), "");
+		gtk_widget_set_sensitive (sndprog_entry, FALSE);
+	} else
+	{
+		gtk_widget_set_sensitive (sndprog_entry, TRUE);
+	}
+}
+
+static void
+setup_snd_filereq_cb (GtkWidget *entry, char *file)
+{
+	if (file)
+	{
+		if (file[0])
+			gtk_entry_set_text (GTK_ENTRY (entry), file);
+	}
+}
+
+static void
+setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry)
+{
+	gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, NULL, 0);
+}
+
+static void
+setup_snd_play_cb (GtkWidget *button, GtkEntry *entry)
+{
+	sound_play (entry->text, FALSE);
+}
+
+static void
+setup_snd_changed_cb (GtkEntry *ent, GtkTreeView *tree)
+{
+	int n;
+	GtkTreeIter iter;
+	GtkListStore *store;
+	GtkTreeSelection *sel;
+
+	if (ignore_changed)
+		return;
+
+	sel = gtk_tree_view_get_selection (tree);
+	n = setup_snd_get_selected (sel, &iter);
+	if (n == -1)
+		return;
+
+	/* get the new sound file */
+	if (sound_files[n])
+		free (sound_files[n]);
+	sound_files[n] = strdup (GTK_ENTRY (ent)->text);
+
+	/* update the TreeView list */
+	store = (GtkListStore *)gtk_tree_view_get_model (tree);
+	gtk_list_store_set (store, &iter, 1, sound_files[n], -1);
+
+	gtk_widget_set_sensitive (cancel_button, FALSE);
+}
+
+static GtkWidget *
+setup_create_sound_page (void)
+{
+	GtkWidget *vbox1;
+	GtkWidget *vbox2;
+	GtkWidget *table2;
+	GtkWidget *label2;
+	GtkWidget *label3;
+	GtkWidget *radio_external;
+	GSList *radio_group = NULL;
+	GtkWidget *radio_auto;
+	GtkWidget *label4;
+	GtkWidget *entry3;
+	GtkWidget *scrolledwindow1;
+	GtkWidget *sound_tree;
+	GtkWidget *table1;
+	GtkWidget *sound_label;
+	GtkWidget *sound_browse;
+	GtkWidget *sound_play;
+	GtkTreeSelection *sel;
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6);
+	gtk_widget_show (vbox1);
+
+	vbox2 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox2);
+	gtk_container_add (GTK_CONTAINER (vbox1), vbox2);
+
+	table2 = gtk_table_new (4, 3, FALSE);
+	gtk_widget_show (table2);
+	gtk_box_pack_start (GTK_BOX (vbox2), table2, FALSE, TRUE, 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table2), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table2), 4);
+
+	label2 = gtk_label_new (_("Sound playing method:"));
+	gtk_widget_show (label2);
+	gtk_table_attach (GTK_TABLE (table2), label2, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5);
+
+	label3 =
+		gtk_label_new_with_mnemonic (_("External sound playing _program:"));
+	gtk_widget_show (label3);
+	gtk_table_attach (GTK_TABLE (table2), label3, 0, 1, 2, 3,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5);
+
+	sndprog_entry = gtk_entry_new ();
+	if (setup_prefs.soundcmd[0] == 0)
+		gtk_widget_set_sensitive (sndprog_entry, FALSE);
+	else
+		gtk_entry_set_text (GTK_ENTRY (sndprog_entry), setup_prefs.soundcmd);
+	gtk_widget_show (sndprog_entry);
+	gtk_table_attach (GTK_TABLE (table2), sndprog_entry, 1, 3, 2, 3,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	radio_external =
+		gtk_radio_button_new_with_mnemonic (NULL, _("_External program"));
+	gtk_widget_show (radio_external);
+	gtk_table_attach (GTK_TABLE (table2), radio_external, 1, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_external),
+										 radio_group);
+	radio_group =
+		gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_external));
+
+	radio_auto = gtk_radio_button_new_with_mnemonic (NULL, _("_Automatic"));
+	g_signal_connect (G_OBJECT (radio_auto), "toggled",
+							G_CALLBACK (setup_autotoggle_cb), radio_external);
+	gtk_widget_show (radio_auto);
+	gtk_table_attach (GTK_TABLE (table2), radio_auto, 1, 3, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_auto),
+										 radio_group);
+	radio_group =
+		gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_auto));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_auto), setup_prefs.soundcmd[0] == 0);
+
+	label4 = gtk_label_new_with_mnemonic (_("Sound files _directory:"));
+	gtk_widget_show (label4);
+	gtk_table_attach (GTK_TABLE (table2), label4, 0, 1, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
+
+	snddir_entry = entry3 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry3), setup_prefs.sounddir);
+	gtk_widget_show (entry3);
+	gtk_table_attach (GTK_TABLE (table2), entry3, 1, 3, 3, 4,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow1);
+	gtk_container_add (GTK_CONTAINER (vbox2), scrolledwindow1);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1),
+											  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1),
+													 GTK_SHADOW_IN);
+
+	sound_tree = gtk_tree_view_new ();
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (sound_tree));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	setup_snd_add_columns (GTK_TREE_VIEW (sound_tree));
+	setup_snd_populate (GTK_TREE_VIEW (sound_tree));
+	g_signal_connect (G_OBJECT (sel), "changed",
+							G_CALLBACK (setup_snd_row_cb), NULL);
+	gtk_widget_show (sound_tree);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow1), sound_tree);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (sound_tree), TRUE);
+
+	table1 = gtk_table_new (2, 3, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (vbox2), table1, FALSE, TRUE, 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 4);
+
+	sound_label = gtk_label_new_with_mnemonic (_("Sound file:"));
+	gtk_widget_show (sound_label);
+	gtk_table_attach (GTK_TABLE (table1), sound_label, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (sound_label), 0, 0.5);
+
+	sndfile_entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (sndfile_entry), "changed",
+							G_CALLBACK (setup_snd_changed_cb), sound_tree);
+	gtk_widget_show (sndfile_entry);
+	gtk_table_attach (GTK_TABLE (table1), sndfile_entry, 0, 1, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	sound_browse = gtk_button_new_with_mnemonic (_("_Browse..."));
+	g_signal_connect (G_OBJECT (sound_browse), "clicked",
+							G_CALLBACK (setup_snd_browse_cb), sndfile_entry);
+	gtk_widget_show (sound_browse);
+	gtk_table_attach (GTK_TABLE (table1), sound_browse, 1, 2, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+#ifdef GTK_STOCK_MEDIA_PLAY
+	sound_play = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
+#else
+	sound_play = gtk_button_new_with_mnemonic (_("_Play"));
+#endif
+	g_signal_connect (G_OBJECT (sound_play), "clicked",
+							G_CALLBACK (setup_snd_play_cb), sndfile_entry);
+	gtk_widget_show (sound_play);
+	gtk_table_attach (GTK_TABLE (table1), sound_play, 2, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label3), sndprog_entry);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label4), entry3);
+	setup_snd_row_cb (sel, NULL);
+
+	return vbox1;
+}
+
+static void
+setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab)
+{
+	GtkWidget *oframe, *frame, *label, *vvbox;
+	char buf[128];
+
+	/* frame for whole page */
+	oframe = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (oframe), GTK_SHADOW_IN);
+
+	vvbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (oframe), vvbox);
+
+	/* border for the label */
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+	gtk_box_pack_start (GTK_BOX (vvbox), frame, FALSE, TRUE, 0);
+
+	/* label */
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<b><big>%s</big></b>", _(title));
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_misc_set_padding (GTK_MISC (label), 2, 1);
+	gtk_container_add (GTK_CONTAINER (frame), label);
+
+	gtk_container_add (GTK_CONTAINER (vvbox), tab);
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (book), oframe, NULL);
+}
+
+static const char *const cata[] =
+{
+	N_("Interface"),
+		N_("Text box"),
+		N_("Input box"),
+		N_("User list"),
+		N_("Channel switcher"),
+		N_("Colors"),
+		NULL,
+	N_("Chatting"),
+		N_("Alerts"),
+		N_("General"),
+		N_("Logging"),
+		N_("Sound"),
+/*		N_("Advanced"),*/
+		NULL,
+	N_("Network"),
+		N_("Network setup"),
+		N_("File transfers"),
+		NULL,
+	NULL
+};
+
+static GtkWidget *
+setup_create_pages (GtkWidget *box)
+{
+	GtkWidget *book;
+
+	book = gtk_notebook_new ();
+
+	setup_add_page (cata[1], book, setup_create_page (textbox_settings));
+	setup_add_page (cata[2], book, setup_create_page (inputbox_settings));
+	setup_add_page (cata[3], book, setup_create_page (userlist_settings));
+	setup_add_page (cata[4], book, setup_create_page (tabs_settings));
+	setup_add_page (cata[5], book, setup_create_color_page ());
+	setup_add_page (cata[8], book, setup_create_page (alert_settings));
+	setup_add_page (cata[9], book, setup_create_page (general_settings));
+	setup_add_page (cata[10], book, setup_create_page (logging_settings));
+	setup_add_page (cata[11], book, setup_create_sound_page ());
+	setup_add_page (cata[14], book, setup_create_page (network_settings));
+	setup_add_page (cata[15], book, setup_create_page (filexfer_settings));
+
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE);
+	gtk_container_add (GTK_CONTAINER (box), book);
+
+	return book;
+}
+
+static void
+setup_tree_cb (GtkTreeView *treeview, GtkWidget *book)
+{
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	int page;
+
+	if (gtk_tree_selection_get_selected (selection, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 1, &page, -1);
+		if (page != -1)
+		{
+			gtk_notebook_set_current_page (GTK_NOTEBOOK (book), page);
+			last_selected_page = page;
+		}
+	}
+}
+
+static gboolean
+setup_tree_select_filter (GtkTreeSelection *selection, GtkTreeModel *model,
+								  GtkTreePath *path, gboolean path_selected,
+								  gpointer data)
+{
+	if (gtk_tree_path_get_depth (path) > 1)
+		return TRUE;
+	return FALSE;
+}
+
+static void
+setup_create_tree (GtkWidget *box, GtkWidget *book)
+{
+	GtkWidget *tree;
+	GtkWidget *frame;
+	GtkTreeStore *model;
+	GtkTreeIter iter;
+	GtkTreeIter child_iter;
+	GtkTreeIter *sel_iter = NULL;
+	GtkCellRenderer *renderer;
+	GtkTreeSelection *sel;
+	int i, page;
+
+	model = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT);
+
+	i = 0;
+	page = 0;
+	do
+	{
+		gtk_tree_store_append (model, &iter, NULL);
+		gtk_tree_store_set (model, &iter, 0, _(cata[i]), 1, -1, -1);
+		i++;
+
+		do
+		{
+			gtk_tree_store_append (model, &child_iter, &iter);
+			gtk_tree_store_set (model, &child_iter, 0, _(cata[i]), 1, page, -1);
+			if (page == last_selected_page)
+				sel_iter = gtk_tree_iter_copy (&child_iter);
+			page++;
+			i++;
+		} while (cata[i]);
+
+		i++;
+
+	} while (cata[i]);
+
+	tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
+	g_object_unref (G_OBJECT (model));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
+	gtk_tree_selection_set_select_function (sel, setup_tree_select_filter,
+														 NULL, NULL);
+	g_signal_connect (G_OBJECT (tree), "cursor_changed",
+							G_CALLBACK (setup_tree_cb), book);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree),
+							    -1, _("Categories"), renderer, "text", 0, NULL);
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (tree));
+
+	frame = gtk_frame_new (NULL);
+	gtk_container_add (GTK_CONTAINER (frame), tree);
+	gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (box), frame, 0);
+
+	if (sel_iter)
+	{
+		gtk_tree_selection_select_iter (sel, sel_iter);
+		gtk_tree_iter_free (sel_iter);
+	}
+}
+
+static void
+setup_apply_entry_style (GtkWidget *entry)
+{
+	gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]);
+	gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]);
+	gtk_widget_modify_font (entry, input_style->font_desc);
+}
+
+static void
+setup_apply_to_sess (session_gui *gui)
+{
+#ifdef USE_GTKSPELL
+	GtkSpell *spell;
+#endif
+
+	mg_update_xtext (gui->xtext);
+
+	if (prefs.style_namelistgad)
+		gtk_widget_set_style (gui->user_tree, input_style);
+
+	if (prefs.style_inputbox)
+	{
+		extern char cursor_color_rc[];
+		char buf[256];
+		sprintf (buf, cursor_color_rc,
+				(colors[COL_FG].red >> 8),
+				(colors[COL_FG].green >> 8),
+				(colors[COL_FG].blue >> 8));
+		gtk_rc_parse_string (buf);
+
+		setup_apply_entry_style (gui->input_box);
+		setup_apply_entry_style (gui->limit_entry);
+		setup_apply_entry_style (gui->key_entry);
+		setup_apply_entry_style (gui->topic_entry);
+	}
+
+	if (prefs.userlistbuttons)
+		gtk_widget_show (gui->button_box);
+	else
+		gtk_widget_hide (gui->button_box);
+
+#ifdef USE_GTKSPELL
+	spell = gtkspell_get_from_text_view (GTK_TEXT_VIEW (gui->input_box));
+	if (prefs.gui_input_spell)
+	{
+		if (!spell)
+			gtkspell_new_attach (GTK_TEXT_VIEW (gui->input_box), NULL, NULL);
+	}
+	else
+	{
+		if (spell)
+			gtkspell_detach (spell);
+	}
+#endif
+
+#ifdef USE_LIBSEXY
+	sexy_spell_entry_set_checked ((SexySpellEntry *)gui->input_box, prefs.gui_input_spell);
+#endif
+}
+
+static void
+unslash (char *dir)
+{
+	if (dir[0])
+	{
+		int len = strlen (dir) - 1;
+#ifdef WIN32
+		if (dir[len] == '/' || dir[len] == '\\')
+#else
+		if (dir[len] == '/')
+#endif
+			dir[len] = 0;
+	}
+}
+
+void
+setup_apply_real (int new_pix, int do_ulist, int do_layout)
+{
+	GSList *list;
+	session *sess;
+	int done_main = FALSE;
+
+	/* remove trailing slashes */
+	unslash (prefs.dccdir);
+	unslash (prefs.dcc_completed_dir);
+
+	mkdir_utf8 (prefs.dccdir);
+	mkdir_utf8 (prefs.dcc_completed_dir);
+
+	if (new_pix)
+	{
+		if (channelwin_pix)
+			g_object_unref (channelwin_pix);
+		channelwin_pix = pixmap_load_from_file (prefs.background);
+	}
+
+	input_style = create_input_style (input_style);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->is_tab)
+		{
+			/* only apply to main tabwindow once */
+			if (!done_main)
+			{
+				done_main = TRUE;
+				setup_apply_to_sess (sess->gui);
+			}
+		} else
+		{
+			setup_apply_to_sess (sess->gui);
+		}
+
+		log_open_or_close (sess);
+
+		if (do_ulist)
+			userlist_rehash (sess);
+
+		list = list->next;
+	}
+
+	mg_apply_setup ();
+	tray_apply_setup ();
+
+	if (do_layout)
+		menu_change_layout ();
+}
+
+static void
+setup_apply (struct xchatprefs *pr)
+{
+	int new_pix = FALSE;
+	int noapply = FALSE;
+	int do_ulist = FALSE;
+	int do_layout = FALSE;
+
+	if (strcmp (pr->background, prefs.background) != 0)
+		new_pix = TRUE;
+
+#define DIFF(a) (pr->a != prefs.a)
+
+	if (DIFF (paned_userlist))
+		noapply = TRUE;
+	if (DIFF (lagometer))
+		noapply = TRUE;
+	if (DIFF (throttlemeter))
+		noapply = TRUE;
+	if (DIFF (showhostname_in_userlist))
+		noapply = TRUE;
+	if (DIFF (tab_small))
+		noapply = TRUE;
+	if (DIFF (tab_sort))
+		noapply = TRUE;
+	if (DIFF (use_server_tab))
+		noapply = TRUE;
+	if (DIFF (style_namelistgad))
+		noapply = TRUE;
+	if (DIFF (truncchans))
+		noapply = TRUE;
+	if (DIFF (tab_layout))
+		do_layout = TRUE;
+
+	if (color_change || (DIFF (away_size_max)) || (DIFF (away_track)))
+		do_ulist = TRUE;
+
+	if ((pr->tab_pos == 5 || pr->tab_pos == 6) &&
+		 pr->tab_layout == 2 && pr->tab_pos != prefs.tab_pos)
+		fe_message (_("You cannot place the tree on the top or bottom!\n"
+						"Please change to the <b>Tabs</b> layout in the <b>View</b>"
+						" menu first."),
+						FE_MSG_WARN | FE_MSG_MARKUP);
+
+	memcpy (&prefs, pr, sizeof (prefs));
+
+	setup_apply_real (new_pix, do_ulist, do_layout);
+
+	if (noapply)
+		fe_message (_("Some settings were changed that require a"
+						" restart to take full effect."), FE_MSG_WARN);
+
+#ifndef WIN32
+	if (prefs.autodccsend == 1)
+	{
+		if (!strcmp ((char *)g_get_home_dir (), prefs.dccdir))
+		{
+			fe_message (_("*WARNING*\n"
+							 "Auto accepting DCC to your home directory\n"
+							 "can be dangerous and is exploitable. Eg:\n"
+							 "Someone could send you a .bash_profile"), FE_MSG_WARN);
+		}
+	}
+#endif
+}
+
+#if 0
+static void
+setup_apply_cb (GtkWidget *but, GtkWidget *win)
+{
+	/* setup_prefs -> xchat */
+	setup_apply (&setup_prefs);
+}
+#endif
+
+static void
+setup_ok_cb (GtkWidget *but, GtkWidget *win)
+{
+	setup_snd_apply ();
+	gtk_widget_destroy (win);
+	setup_apply (&setup_prefs);
+	save_config ();
+	palette_save ();
+}
+
+static GtkWidget *
+setup_window_open (void)
+{
+	GtkWidget *wid, *win, *vbox, *hbox, *hbbox;
+
+	win = gtkutil_window_new (_("XChat: Preferences"), "prefs", 0, 0, 3);
+
+	vbox = gtk_vbox_new (FALSE, 5);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_container_add (GTK_CONTAINER (win), vbox);
+
+	hbox = gtk_hbox_new (FALSE, 4);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
+	setup_create_tree (hbox, setup_create_pages (hbox));
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+	/* prepare the button box */
+	hbbox = gtk_hbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (hbbox), 4);
+	gtk_box_pack_end (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0);
+
+	/* standard buttons */
+	/* GNOME doesn't like apply */
+#if 0
+	wid = gtk_button_new_from_stock (GTK_STOCK_APPLY);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (setup_apply_cb), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+#endif
+
+	cancel_button = wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (gtkutil_destroy), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (setup_ok_cb), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+
+	wid = gtk_hseparator_new ();
+	gtk_box_pack_end (GTK_BOX (vbox), wid, FALSE, FALSE, 0);
+
+	gtk_widget_show_all (win);
+
+	return win;
+}
+
+static void
+setup_close_cb (GtkWidget *win, GtkWidget **swin)
+{
+	*swin = NULL;
+
+	if (font_dialog)
+	{
+		gtk_widget_destroy (font_dialog);
+		font_dialog = NULL;
+	}
+}
+
+void
+setup_open (void)
+{
+	static GtkWidget *setup_window = NULL;
+
+	if (setup_window)
+	{
+		gtk_window_present (GTK_WINDOW (setup_window));
+		return;
+	}
+
+	memcpy (&setup_prefs, &prefs, sizeof (prefs));
+
+	color_change = FALSE;
+	setup_window = setup_window_open ();
+
+	g_signal_connect (G_OBJECT (setup_window), "destroy",
+							G_CALLBACK (setup_close_cb), &setup_window);
+}
diff --git a/src/fe-gtk/sexy-spell-entry.c b/src/fe-gtk/sexy-spell-entry.c
new file mode 100644
index 00000000..d67ffe2d
--- /dev/null
+++ b/src/fe-gtk/sexy-spell-entry.c
@@ -0,0 +1,1329 @@
+/*
+ * @file libsexy/sexy-icon-entry.c Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+#include "sexy-spell-entry.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+/*#include "gtkspell-iso-codes.h"
+#include "sexy-marshal.h"*/
+
+/*
+ * Bunch of poop to make enchant into a runtime dependency rather than a
+ * compile-time dependency.  This makes it so I don't have to hear the
+ * complaints from people with binary distributions who don't get spell
+ * checking because they didn't check their configure output.
+ */
+struct EnchantDict;
+struct EnchantBroker;
+
+typedef void (*EnchantDictDescribeFn) (const char * const lang_tag,
+                                       const char * const provider_name,
+                                       const char * const provider_desc,
+                                       const char * const provider_file,
+                                       void * user_data);
+
+static struct EnchantBroker * (*enchant_broker_init) (void);
+static void (*enchant_broker_free) (struct EnchantBroker * broker);
+static void (*enchant_broker_free_dict) (struct EnchantBroker * broker, struct EnchantDict * dict);
+static void (*enchant_broker_list_dicts) (struct EnchantBroker * broker, EnchantDictDescribeFn fn, void * user_data);
+static struct EnchantDict * (*enchant_broker_request_dict) (struct EnchantBroker * broker, const char *const tag);
+
+static void (*enchant_dict_add_to_personal) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static void (*enchant_dict_add_to_session) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static int (*enchant_dict_check) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static void (*enchant_dict_describe) (struct EnchantDict * dict, EnchantDictDescribeFn fn, void * user_data);
+static void (*enchant_dict_free_suggestions) (struct EnchantDict * dict, char **suggestions);
+static void (*enchant_dict_store_replacement) (struct EnchantDict * dict, const char *const mis, ssize_t mis_len, const char *const cor, ssize_t cor_len);
+static char ** (*enchant_dict_suggest) (struct EnchantDict * dict, const char *const word, ssize_t len, size_t * out_n_suggs);
+static gboolean have_enchant = FALSE;
+
+struct _SexySpellEntryPriv
+{
+	struct EnchantBroker *broker;
+	PangoAttrList        *attr_list;
+	gint                  mark_character;
+	GHashTable           *dict_hash;
+	GSList               *dict_list;
+	gchar               **words;
+	gint                 *word_starts;
+	gint                 *word_ends;
+	gboolean              checked;
+};
+
+static void sexy_spell_entry_class_init(SexySpellEntryClass *klass);
+static void sexy_spell_entry_editable_init (GtkEditableClass *iface);
+static void sexy_spell_entry_init(SexySpellEntry *entry);
+static void sexy_spell_entry_finalize(GObject *obj);
+static void sexy_spell_entry_destroy(GtkObject *obj);
+static gint sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event);
+static gint sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event);
+
+/* GtkEditable handlers */
+static void sexy_spell_entry_changed(GtkEditable *editable, gpointer data);
+
+/* Other handlers */
+static gboolean sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry);
+
+/* Internal utility functions */
+static gint       gtk_entry_find_position                     (GtkEntry             *entry,
+                                                               gint                  x);
+static gboolean   word_misspelled                             (SexySpellEntry       *entry,
+                                                               int                   start,
+                                                               int                   end);
+static gboolean   default_word_check                          (SexySpellEntry       *entry,
+                                                               const gchar          *word);
+static gboolean   sexy_spell_entry_activate_language_internal (SexySpellEntry       *entry,
+                                                               const gchar          *lang,
+                                                               GError              **error);
+static gchar     *get_lang_from_dict                          (struct EnchantDict   *dict);
+static void       sexy_spell_entry_recheck_all                (SexySpellEntry       *entry);
+static void       entry_strsplit_utf8                         (GtkEntry             *entry,
+                                                               gchar              ***set,
+                                                               gint                **starts,
+                                                               gint                **ends);
+
+static GtkEntryClass *parent_class = NULL;
+
+G_DEFINE_TYPE_EXTENDED(SexySpellEntry, sexy_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, sexy_spell_entry_editable_init));
+
+enum
+{
+	WORD_CHECK,
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = {0};
+
+static gboolean
+spell_accumulator(GSignalInvocationHint *hint, GValue *return_accu, const GValue *handler_return, gpointer data)
+{
+	gboolean ret = g_value_get_boolean(handler_return);
+	/* Handlers return TRUE if the word is misspelled.  In this
+	 * case, it means that we want to stop if the word is checked
+	 * as correct */
+	g_value_set_boolean (return_accu, ret);
+	return ret;
+}
+
+static void
+initialize_enchant ()
+{
+	GModule *enchant;
+	gpointer funcptr;
+
+	enchant = g_module_open("libenchant", 0);
+	if (enchant == NULL)
+	{
+		enchant = g_module_open("libenchant.so.1", 0);
+		if (enchant == NULL)
+			return;
+	}
+
+	have_enchant = TRUE;
+
+#define MODULE_SYMBOL(name, func) \
+	g_module_symbol(enchant, (name), &funcptr); \
+	(func) = funcptr;
+
+	MODULE_SYMBOL("enchant_broker_init", enchant_broker_init)
+	MODULE_SYMBOL("enchant_broker_free", enchant_broker_free)
+	MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict)
+	MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts)
+	MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict)
+
+	MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal)
+	MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session)
+	MODULE_SYMBOL("enchant_dict_check", enchant_dict_check)
+	MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe)
+	MODULE_SYMBOL("enchant_dict_free_suggestions",
+				  enchant_dict_free_suggestions)
+	MODULE_SYMBOL("enchant_dict_store_replacement",
+				  enchant_dict_store_replacement)
+	MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest)
+
+#undef MODULE_SYMBOL
+}
+
+static void
+sexy_spell_entry_class_init(SexySpellEntryClass *klass)
+{
+	GObjectClass *gobject_class;
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkEntryClass *entry_class;
+
+	initialize_enchant();
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class = G_OBJECT_CLASS(klass);
+	object_class  = GTK_OBJECT_CLASS(klass);
+	widget_class  = GTK_WIDGET_CLASS(klass);
+	entry_class   = GTK_ENTRY_CLASS(klass);
+
+	if (have_enchant)
+		klass->word_check = default_word_check;
+
+	gobject_class->finalize = sexy_spell_entry_finalize;
+
+	object_class->destroy = sexy_spell_entry_destroy;
+
+	widget_class->expose_event = sexy_spell_entry_expose;
+	widget_class->button_press_event = sexy_spell_entry_button_press;
+
+	/**
+	 * SexySpellEntry::word-check:
+	 * @entry: The entry on which the signal is emitted.
+	 * @word: The word to check.
+	 *
+	 * The ::word-check signal is emitted whenever the entry has to check
+	 * a word.  This allows the application to mark words as correct even
+	 * if none of the active dictionaries contain it, such as nicknames in
+	 * a chat client.
+	 *
+	 * Returns: %FALSE to indicate that the word should be marked as
+	 * correct.
+	 */
+/*	signals[WORD_CHECK] = g_signal_new("word_check",
+					   G_TYPE_FROM_CLASS(object_class),
+					   G_SIGNAL_RUN_LAST,
+					   G_STRUCT_OFFSET(SexySpellEntryClass, word_check),
+					   (GSignalAccumulator) spell_accumulator, NULL,
+					   sexy_marshal_BOOLEAN__STRING,
+					   G_TYPE_BOOLEAN,
+					   1, G_TYPE_STRING);*/
+}
+
+static void
+sexy_spell_entry_editable_init (GtkEditableClass *iface)
+{
+}
+
+static gint
+gtk_entry_find_position (GtkEntry *entry, gint x)
+{
+	PangoLayout *layout;
+	PangoLayoutLine *line;
+	const gchar *text;
+	gint cursor_index;
+	gint index;
+	gint pos;
+	gboolean trailing;
+
+	x = x + entry->scroll_offset;
+
+	layout = gtk_entry_get_layout(entry);
+	text = pango_layout_get_text(layout);
+	cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text;
+
+	line = pango_layout_get_lines(layout)->data;
+	pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
+
+	if (index >= cursor_index && entry->preedit_length) {
+		if (index >= cursor_index + entry->preedit_length) {
+			index -= entry->preedit_length;
+		} else {
+			index = cursor_index;
+			trailing = FALSE;
+		}
+	}
+
+	pos = g_utf8_pointer_to_offset (text, text + index);
+	pos += trailing;
+
+	return pos;
+}
+
+static void
+insert_underline(SexySpellEntry *entry, guint start, guint end)
+{
+	PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0);
+	PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
+
+	ucolor->start_index = start;
+	unline->start_index = start;
+
+	ucolor->end_index = end;
+	unline->end_index = end;
+
+	pango_attr_list_insert (entry->priv->attr_list, ucolor);
+	pango_attr_list_insert (entry->priv->attr_list, unline);
+}
+
+static void
+get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position)
+{
+	const gchar *text;
+	gint i, bytes_pos;
+
+	*start = -1;
+	*end = -1;
+
+	if (entry->priv->words == NULL)
+		return;
+
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
+
+	for (i = 0; entry->priv->words[i]; i++) {
+		if (bytes_pos >= entry->priv->word_starts[i] &&
+		    bytes_pos <= entry->priv->word_ends[i]) {
+			*start = entry->priv->word_starts[i];
+			*end   = entry->priv->word_ends[i];
+			return;
+		}
+	}
+}
+
+static void
+add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *word;
+	gint start, end;
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+
+	dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
+	if (dict)
+		enchant_dict_add_to_personal(dict, word, -1);
+
+	g_free(word);
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static void
+ignore_all(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *word;
+	gint start, end;
+	GSList *li;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+
+	for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+		struct EnchantDict *dict = (struct EnchantDict *) li->data;
+		enchant_dict_add_to_session(dict, word, -1);
+	}
+
+	g_free(word);
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static void
+replace_word(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *oldword;
+	const char *newword;
+	gint start, end;
+	gint cursor;
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	oldword = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+	newword = gtk_label_get_text(GTK_LABEL(GTK_BIN(menuitem)->child));
+
+	cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
+	/* is the cursor at the end? If so, restore it there */
+	if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry)), -1) == cursor)
+		cursor = -1;
+	else if(cursor > start && cursor <= end)
+		cursor = start;
+
+	gtk_editable_delete_text(GTK_EDITABLE(entry), start, end);
+	gtk_editable_set_position(GTK_EDITABLE(entry), start);
+	gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
+							 &start);
+	gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
+
+	dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
+
+        if (dict)
+		enchant_dict_store_replacement(dict,
+					       oldword, -1,
+					       newword, -1);
+
+	g_free(oldword);
+}
+
+static void
+build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word)
+{
+	GtkWidget *mi;
+	gchar **suggestions;
+	size_t n_suggestions, i;
+
+	if (!have_enchant)
+		return;
+
+	suggestions = enchant_dict_suggest(dict, word, -1, &n_suggestions);
+
+	if (suggestions == NULL || n_suggestions == 0) {
+		/* no suggestions.  put something in the menu anyway... */
+		GtkWidget *label = gtk_label_new("");
+		gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>"));
+
+		mi = gtk_separator_menu_item_new();
+		gtk_container_add(GTK_CONTAINER(mi), label);
+		gtk_widget_show_all(mi);
+		gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+	} else {
+		/* build a set of menus with suggestions */
+		for (i = 0; i < n_suggestions; i++) {
+			if ((i != 0) && (i % 10 == 0)) {
+				mi = gtk_separator_menu_item_new();
+				gtk_widget_show(mi);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+
+				mi = gtk_menu_item_new_with_label(_("More..."));
+				gtk_widget_show(mi);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+
+				menu = gtk_menu_new();
+				gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+			}
+
+			mi = gtk_menu_item_new_with_label(suggestions[i]);
+			g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+			g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(replace_word), entry);
+			gtk_widget_show(mi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+		}
+	}
+
+	enchant_dict_free_suggestions(dict, suggestions);
+}
+
+static GtkWidget *
+build_spelling_menu(SexySpellEntry *entry, const gchar *word)
+{
+	struct EnchantDict *dict;
+	GtkWidget *topmenu, *mi;
+	gchar *label;
+
+	if (!have_enchant)
+		return NULL;
+
+	topmenu = gtk_menu_new();
+
+	if (entry->priv->dict_list == NULL)
+		return topmenu;
+
+#if 1
+	dict = (struct EnchantDict *) entry->priv->dict_list->data;
+	build_suggestion_menu(entry, topmenu, dict, word);
+#else
+	/* Suggestions */
+	if (g_slist_length(entry->priv->dict_list) == 1) {
+		dict = (struct EnchantDict *) entry->priv->dict_list->data;
+		build_suggestion_menu(entry, topmenu, dict, word);
+	} else {
+		GSList *li;
+		GtkWidget *menu;
+		gchar *lang, *lang_name;
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+			dict = (struct EnchantDict *) li->data;
+			lang = get_lang_from_dict(dict);
+			lang_name = gtkspell_iso_codes_lookup_name_for_code(lang);
+			if (lang_name) {
+				mi = gtk_menu_item_new_with_label(lang_name);
+				g_free(lang_name);
+			} else {
+				mi = gtk_menu_item_new_with_label(lang);
+			}
+			g_free(lang);
+
+			gtk_widget_show(mi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+			menu = gtk_menu_new();
+			gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+			build_suggestion_menu(entry, menu, dict, word);
+		}
+	}
+#endif
+
+	/* Separator */
+	mi = gtk_separator_menu_item_new ();
+	gtk_widget_show(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	/* + Add to Dictionary */
+	label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word);
+	mi = gtk_image_menu_item_new_with_label(label);
+	g_free(label);
+
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
+
+#if 1
+	dict = (struct EnchantDict *) entry->priv->dict_list->data;
+	g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+	g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry);
+#else
+	if (g_slist_length(entry->priv->dict_list) == 1) {
+		dict = (struct EnchantDict *) entry->priv->dict_list->data;
+		g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+		g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry);
+	} else {
+		GSList *li;
+		GtkWidget *menu, *submi;
+		gchar *lang, *lang_name;
+
+		menu = gtk_menu_new();
+		gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+			dict = (struct EnchantDict *)li->data;
+			lang = get_lang_from_dict(dict);
+			lang_name = gtkspell_iso_codes_lookup_name_for_code(lang);
+			if (lang_name) {
+				submi = gtk_menu_item_new_with_label(lang_name);
+				g_free(lang_name);
+			} else {
+				submi = gtk_menu_item_new_with_label(lang);
+			}
+			g_free(lang);
+			g_object_set_data(G_OBJECT(submi), "enchant-dict", dict);
+
+			g_signal_connect(G_OBJECT(submi), "activate", G_CALLBACK(add_to_dictionary), entry);
+
+			gtk_widget_show(submi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), submi);
+		}
+	}
+#endif
+
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	/* - Ignore All */
+	mi = gtk_image_menu_item_new_with_label(_("Ignore All"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
+	g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(ignore_all), entry);
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	return topmenu;
+}
+
+static void
+sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data)
+{
+	GtkWidget *icon, *mi;
+	gint start, end;
+	gchar *word;
+
+	if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
+		return;
+
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	if (start == end)
+		return;
+	if (!word_misspelled(entry, start, end))
+		return;
+
+	/* separator */
+	mi = gtk_separator_menu_item_new();
+	gtk_widget_show(mi);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+
+	/* Above the separator, show the suggestions menu */
+	icon = gtk_image_new_from_stock(GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
+	mi = gtk_image_menu_item_new_with_label(_("Spelling Suggestions"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), icon);
+
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+	g_assert(word != NULL);
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_spelling_menu(entry, word));
+	g_free(word);
+
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+}
+
+static void
+sexy_spell_entry_init(SexySpellEntry *entry)
+{
+	entry->priv = g_new0(SexySpellEntryPriv, 1);
+
+	entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	if (have_enchant)
+		sexy_spell_entry_activate_default_languages(entry);
+
+	entry->priv->attr_list = pango_attr_list_new();
+
+	entry->priv->checked = TRUE;
+
+	g_signal_connect(G_OBJECT(entry), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu), entry);
+	g_signal_connect(G_OBJECT(entry), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup), NULL);
+	g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sexy_spell_entry_changed), NULL);
+}
+
+static void
+sexy_spell_entry_finalize(GObject *obj)
+{
+	SexySpellEntry *entry;
+
+	g_return_if_fail(obj != NULL);
+	g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj));
+
+	entry = SEXY_SPELL_ENTRY(obj);
+
+	if (entry->priv->attr_list)
+		pango_attr_list_unref(entry->priv->attr_list);
+	if (entry->priv->dict_hash)
+		g_hash_table_destroy(entry->priv->dict_hash);
+	if (entry->priv->words)
+		g_strfreev(entry->priv->words);
+	if (entry->priv->word_starts)
+		g_free(entry->priv->word_starts);
+	if (entry->priv->word_ends)
+		g_free(entry->priv->word_ends);
+
+	if (have_enchant) {
+		if (entry->priv->broker) {
+			GSList *li;
+			for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+				struct EnchantDict *dict = (struct EnchantDict*) li->data;
+				enchant_broker_free_dict (entry->priv->broker, dict);
+			}
+			g_slist_free (entry->priv->dict_list);
+
+			enchant_broker_free(entry->priv->broker);
+		}
+	}
+
+	g_free(entry->priv);
+
+	if (G_OBJECT_CLASS(parent_class)->finalize)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static void
+sexy_spell_entry_destroy(GtkObject *obj)
+{
+	SexySpellEntry *entry;
+
+	entry = SEXY_SPELL_ENTRY(obj);
+
+	if (GTK_OBJECT_CLASS(parent_class)->destroy)
+		GTK_OBJECT_CLASS(parent_class)->destroy(obj);
+}
+
+/**
+ * sexy_spell_entry_new
+ *
+ * Creates a new SexySpellEntry widget.
+ *
+ * Returns: a new #SexySpellEntry.
+ */
+GtkWidget *
+sexy_spell_entry_new(void)
+{
+	return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL));
+}
+
+GQuark
+sexy_spell_error_quark(void)
+{
+	static GQuark q = 0;
+	if (q == 0)
+		q = g_quark_from_static_string("sexy-spell-error-quark");
+	return q;
+}
+
+static gboolean
+default_word_check(SexySpellEntry *entry, const gchar *word)
+{
+	gboolean result = TRUE;
+	GSList *li;
+
+	if (!have_enchant)
+		return result;
+
+	if (g_unichar_isalpha(*word) == FALSE) {
+		/* We only want to check words */
+		return FALSE;
+	}
+	for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+		struct EnchantDict *dict = (struct EnchantDict *) li->data;
+		if (enchant_dict_check(dict, word, strlen(word)) == 0) {
+			result = FALSE;
+			break;
+		}
+	}
+	return result;
+}
+
+static gboolean
+word_misspelled(SexySpellEntry *entry, int start, int end)
+{
+	const gchar *text;
+	gchar *word;
+	gboolean ret;
+
+	if (start == end)
+		return FALSE;
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	word = g_new0(gchar, end - start + 2);
+
+	g_strlcpy(word, text + start, end - start + 1);
+
+#if 0
+	g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret);
+#else
+	ret = default_word_check (entry, word);
+#endif
+
+	g_free(word);
+	return ret;
+}
+
+static void
+check_word(SexySpellEntry *entry, int start, int end)
+{
+	PangoAttrIterator *it;
+
+	/* Check to see if we've got any attributes at this position.
+	 * If so, free them, since we'll readd it if the word is misspelled */
+	it = pango_attr_list_get_iterator(entry->priv->attr_list);
+	if (it == NULL)
+		return;
+	do {
+		gint s, e;
+		pango_attr_iterator_range(it, &s, &e);
+		if (s == start) {
+			GSList *attrs = pango_attr_iterator_get_attrs(it);
+			g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
+			g_slist_free(attrs);
+		}
+	} while (pango_attr_iterator_next(it));
+	pango_attr_iterator_destroy(it);
+
+	if (word_misspelled(entry, start, end))
+		insert_underline(entry, start, end);
+}
+
+static void
+sexy_spell_entry_recheck_all(SexySpellEntry *entry)
+{
+	GdkRectangle rect;
+	GtkWidget *widget = GTK_WIDGET(entry);
+	PangoLayout *layout;
+	int length, i;
+
+	if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
+		return;
+
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	/* Remove all existing pango attributes.  These will get readded as we check */
+	pango_attr_list_unref(entry->priv->attr_list);
+	entry->priv->attr_list = pango_attr_list_new();
+
+	/* Loop through words */
+	for (i = 0; entry->priv->words[i]; i++) {
+		length = strlen(entry->priv->words[i]);
+		if (length == 0)
+			continue;
+		check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
+	}
+
+	layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+	pango_layout_set_attributes(layout, entry->priv->attr_list);
+
+	if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry))) {
+		rect.x = 0; rect.y = 0;
+		rect.width  = widget->allocation.width;
+		rect.height = widget->allocation.height;
+		gdk_window_invalidate_rect(widget->window, &rect, TRUE);
+	}
+}
+
+static gint
+sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
+	GtkEntry *gtk_entry = GTK_ENTRY(widget);
+	PangoLayout *layout;
+
+	if (entry->priv->checked) {
+		layout = gtk_entry_get_layout(gtk_entry);
+		pango_layout_set_attributes(layout, entry->priv->attr_list);
+	}
+
+	return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
+}
+
+static gint
+sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
+	GtkEntry *gtk_entry = GTK_ENTRY(widget);
+	gint pos;
+
+	pos = gtk_entry_find_position(gtk_entry, event->x);
+	entry->priv->mark_character = pos;
+
+	return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry)
+{
+	/* Menu popped up from a keybinding (menu key or <shift>+F10). Use
+	 * the cursor position as the mark position */
+	entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
+	return FALSE;
+}
+
+static void
+entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
+{
+	PangoLayout   *layout;
+	PangoLogAttr  *log_attrs;
+	const gchar   *text;
+	gint           n_attrs, n_strings, i, j;
+
+	layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
+
+	/* Find how many words we have */
+	n_strings = 0;
+	for (i = 0; i < n_attrs; i++)
+		if (log_attrs[i].is_word_start)
+			n_strings++;
+
+	*set    = g_new0(gchar *, n_strings + 1);
+	*starts = g_new0(gint, n_strings);
+	*ends   = g_new0(gint, n_strings);
+
+	/* Copy out strings */
+	for (i = 0, j = 0; i < n_attrs; i++) {
+		if (log_attrs[i].is_word_start) {
+			gint cend, bytes;
+			gchar *start;
+
+			/* Find the end of this string */
+			cend = i;
+			while (!(log_attrs[cend].is_word_end))
+				cend++;
+
+			/* Copy sub-string */
+			start = g_utf8_offset_to_pointer(text, i);
+			bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
+			(*set)[j]    = g_new0(gchar, bytes + 1);
+			(*starts)[j] = (gint) (start - text);
+			(*ends)[j]   = (gint) (start - text + bytes);
+			g_utf8_strncpy((*set)[j], start, cend - i);
+
+			/* Move on to the next word */
+			j++;
+		}
+	}
+
+	g_free (log_attrs);
+}
+
+static void
+sexy_spell_entry_changed(GtkEditable *editable, gpointer data)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable);
+	if (entry->priv->checked == FALSE)
+		return;
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static gboolean
+enchant_has_lang(const gchar *lang, GSList *langs) {
+	GSList *i;
+	for (i = langs; i; i = g_slist_next(i)) {
+		if (strcmp(lang, i->data) == 0) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * sexy_spell_entry_activate_default_languages:
+ * @entry: A #SexySpellEntry.
+ *
+ * Activate spell checking for languages specified in the $LANG
+ * or $LANGUAGE environment variables.  These languages are
+ * activated by default, so this function need only be called
+ * if they were previously deactivated.
+ */
+void
+sexy_spell_entry_activate_default_languages(SexySpellEntry *entry)
+{
+#if GLIB_CHECK_VERSION (2, 6, 0)
+	const gchar* const *langs;
+	int i;
+	gchar *lastprefix = NULL;
+	GSList *enchant_langs;
+
+	if (!have_enchant)
+		return;
+
+	if (!entry->priv->broker)
+		entry->priv->broker = enchant_broker_init();
+
+
+	langs = g_get_language_names ();
+
+	if (langs == NULL)
+		return;
+
+	enchant_langs = sexy_spell_entry_get_languages(entry);
+
+	for (i = 0; langs[i]; i++) {
+		if ((g_strncasecmp(langs[i], "C", 1) != 0) &&
+		    (strlen(langs[i]) >= 2) &&
+		    enchant_has_lang(langs[i], enchant_langs)) {
+			if ((lastprefix == NULL) || (g_str_has_prefix(langs[i], lastprefix) == FALSE))
+				sexy_spell_entry_activate_language_internal(entry, langs[i], NULL);
+			if (lastprefix != NULL)
+				g_free(lastprefix);
+			lastprefix = g_strndup(langs[i], 2);
+		}
+	}
+	if (lastprefix != NULL)
+		g_free(lastprefix);
+
+	g_slist_foreach(enchant_langs, (GFunc) g_free, NULL);
+	g_slist_free(enchant_langs);
+
+	/* If we don't have any languages activated, use "en" */
+	if (entry->priv->dict_list == NULL)
+		sexy_spell_entry_activate_language_internal(entry, "en", NULL);
+#else
+	gchar *lang;
+
+	if (!have_enchant)
+		return;
+
+	lang = (gchar *) g_getenv("LANG");
+
+	if (lang != NULL) {
+		if (g_strncasecmp(lang, "C", 1) == 0)
+			lang = NULL;
+		else if (lang[0] == '\0')
+			lang = NULL;
+	}
+
+	if (lang == NULL)
+		lang = "en";
+
+	sexy_spell_entry_activate_language_internal(entry, lang, NULL);
+#endif
+}
+
+static void
+get_lang_from_dict_cb(const char * const lang_tag,
+		      const char * const provider_name,
+		      const char * const provider_desc,
+		      const char * const provider_file,
+		      void * user_data) {
+	gchar **lang = (gchar **)user_data;
+	*lang = g_strdup(lang_tag);
+}
+
+static gchar *
+get_lang_from_dict(struct EnchantDict *dict)
+{
+	gchar *lang;
+
+	if (!have_enchant)
+		return NULL;
+
+	enchant_dict_describe(dict, get_lang_from_dict_cb, &lang);
+	return lang;
+}
+
+static gboolean
+sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error)
+{
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return FALSE;
+
+	if (!entry->priv->broker)
+		entry->priv->broker = enchant_broker_init();
+
+	if (g_hash_table_lookup(entry->priv->dict_hash, lang))
+		return TRUE;
+
+	dict = enchant_broker_request_dict(entry->priv->broker, lang);
+
+	if (!dict) {
+		g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang);
+		return FALSE;
+	}
+
+	entry->priv->dict_list = g_slist_append(entry->priv->dict_list, (gpointer) dict);
+	g_hash_table_insert(entry->priv->dict_hash, get_lang_from_dict(dict), (gpointer) dict);
+
+	return TRUE;
+}
+
+static void
+dict_describe_cb(const char * const lang_tag,
+		 const char * const provider_name,
+		 const char * const provider_desc,
+		 const char * const provider_file,
+		 void * user_data)
+{
+	GSList **langs = (GSList **)user_data;
+
+	*langs = g_slist_append(*langs, (gpointer)g_strdup(lang_tag));
+}
+
+/**
+ * sexy_spell_entry_get_languages:
+ * @entry: A #SexySpellEntry.
+ *
+ * Retrieve a list of language codes for which dictionaries are available.
+ *
+ * Returns: a new #GList object, or %NULL on error.
+ */
+GSList *
+sexy_spell_entry_get_languages(const SexySpellEntry *entry)
+{
+	GSList *langs = NULL;
+
+	g_return_val_if_fail(entry != NULL, NULL);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
+
+	if (enchant_broker_list_dicts == NULL)
+		return NULL;
+
+	if (!entry->priv->broker)
+		return NULL;
+
+	enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs);
+
+	return langs;
+}
+
+/**
+ * sexy_spell_entry_get_language_name:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language code to lookup a friendly name for.
+ *
+ * Get a friendly name for a given locale.
+ *
+ * Returns: The name of the locale. Should be freed with g_free()
+ */
+gchar *
+sexy_spell_entry_get_language_name(const SexySpellEntry *entry,
+								   const gchar *lang)
+{
+	/*if (have_enchant)
+		return gtkspell_iso_codes_lookup_name_for_code(lang);*/
+	return NULL;
+}
+
+/**
+ * sexy_spell_entry_language_is_active:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language to use, in a form enchant understands.
+ *
+ * Determine if a given language is currently active.
+ *
+ * Returns: TRUE if the language is active.
+ */
+gboolean
+sexy_spell_entry_language_is_active(const SexySpellEntry *entry,
+									const gchar *lang)
+{
+	return (g_hash_table_lookup(entry->priv->dict_hash, lang) != NULL);
+}
+
+/**
+ * sexy_spell_entry_activate_language:
+ * @entry: A #SexySpellEntry
+ * @lang: The language to use in a form Enchant understands. Typically either
+ *        a two letter language code or a locale code in the form xx_XX.
+ * @error: Return location for error.
+ *
+ * Activate spell checking for the language specifed.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error)
+{
+	gboolean ret;
+
+	g_return_val_if_fail(entry != NULL, FALSE);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
+	g_return_val_if_fail(lang != NULL && lang != '\0', FALSE);
+
+	if (!have_enchant)
+		return FALSE;
+
+	if (error)
+		g_return_val_if_fail(*error == NULL, FALSE);
+
+	ret = sexy_spell_entry_activate_language_internal(entry, lang, error);
+
+	if (ret) {
+		if (entry->priv->words) {
+			g_strfreev(entry->priv->words);
+			g_free(entry->priv->word_starts);
+			g_free(entry->priv->word_ends);
+		}
+		entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+		sexy_spell_entry_recheck_all(entry);
+	}
+
+	return ret;
+}
+
+/**
+ * sexy_spell_entry_deactivate_language:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language in a form Enchant understands. Typically either
+ *        a two letter language code or a locale code in the form xx_XX.
+ *
+ * Deactivate spell checking for the language specifed.
+ */
+void
+sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang)
+{
+	g_return_if_fail(entry != NULL);
+	g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry));
+
+	if (!have_enchant)
+		return;
+
+	if (!entry->priv->dict_list)
+		return;
+
+	if (lang) {
+		struct EnchantDict *dict;
+
+		dict = g_hash_table_lookup(entry->priv->dict_hash, lang);
+		if (!dict)
+			return;
+		enchant_broker_free_dict(entry->priv->broker, dict);
+		entry->priv->dict_list = g_slist_remove(entry->priv->dict_list, dict);
+		g_hash_table_remove (entry->priv->dict_hash, lang);
+	} else {
+		/* deactivate all */
+		GSList *li;
+		struct EnchantDict *dict;
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+			dict = (struct EnchantDict *)li->data;
+			enchant_broker_free_dict(entry->priv->broker, dict);
+		}
+
+		g_slist_free (entry->priv->dict_list);
+		g_hash_table_destroy (entry->priv->dict_hash);
+		entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+		entry->priv->dict_list = NULL;
+	}
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+/**
+ * sexy_spell_entry_set_active_languages:
+ * @entry: A #SexySpellEntry
+ * @langs: A list of language codes to activate, in a form Enchant understands.
+ *         Typically either a two letter language code or a locale code in the
+ *         form xx_XX.
+ * @error: Return location for error.
+ *
+ * Activate spell checking for only the languages specified.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error)
+{
+	GSList *li;
+
+	g_return_val_if_fail(entry != NULL, FALSE);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
+	g_return_val_if_fail(langs != NULL, FALSE);
+
+	if (!have_enchant)
+		return FALSE;
+
+	/* deactivate all languages first */
+	sexy_spell_entry_deactivate_language(entry, NULL);
+
+	for (li = langs; li; li = g_slist_next(li)) {
+		if (sexy_spell_entry_activate_language_internal(entry,
+		    (const gchar *) li->data, error) == FALSE)
+			return FALSE;
+	}
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+	return TRUE;
+}
+
+/**
+ * sexy_spell_entry_get_active_languages:
+ * @entry: A #SexySpellEntry
+ *
+ * Retrieve a list of the currently active languages.
+ *
+ * Returns: A GSList of char* values with language codes (en, fr, etc).  Both
+ *          the data and the list must be freed by the user.
+ */
+GSList *
+sexy_spell_entry_get_active_languages(SexySpellEntry *entry)
+{
+	GSList *ret = NULL, *li;
+	struct EnchantDict *dict;
+	gchar *lang;
+
+	g_return_val_if_fail(entry != NULL, NULL);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
+
+	if (!have_enchant)
+		return NULL;
+
+	for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+		dict = (struct EnchantDict *) li->data;
+		lang = get_lang_from_dict(dict);
+		ret = g_slist_append(ret, lang);
+	}
+	return ret;
+}
+
+/**
+ * sexy_spell_entry_is_checked:
+ * @entry: A #SexySpellEntry.
+ *
+ * Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled.
+ *
+ * Returns: TRUE if the entry has spell-checking enabled.
+ */
+gboolean
+sexy_spell_entry_is_checked(SexySpellEntry *entry)
+{
+	return entry->priv->checked;
+}
+
+/**
+ * sexy_spell_entry_set_checked:
+ * @entry: A #SexySpellEntry.
+ * @checked: Whether to enable spell-checking
+ *
+ * Sets whether the entry has spell-checking enabled.
+ */
+void
+sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked)
+{
+	GtkWidget *widget;
+
+	if (entry->priv->checked == checked)
+		return;
+
+	entry->priv->checked = checked;
+	widget = GTK_WIDGET(entry);
+
+	if (checked == FALSE && GTK_WIDGET_REALIZED(widget)) {
+		PangoLayout *layout;
+		GdkRectangle rect;
+
+		pango_attr_list_unref(entry->priv->attr_list);
+		entry->priv->attr_list = pango_attr_list_new();
+
+		layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+		pango_layout_set_attributes(layout, entry->priv->attr_list);
+
+		rect.x = 0; rect.y = 0;
+		rect.width  = widget->allocation.width;
+		rect.height = widget->allocation.height;
+		gdk_window_invalidate_rect(widget->window, &rect, TRUE);
+	} else {
+		if (entry->priv->words) {
+			g_strfreev(entry->priv->words);
+			g_free(entry->priv->word_starts);
+			g_free(entry->priv->word_ends);
+		}
+		entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+		sexy_spell_entry_recheck_all(entry);
+	}
+}
diff --git a/src/fe-gtk/sexy-spell-entry.h b/src/fe-gtk/sexy-spell-entry.h
new file mode 100644
index 00000000..61d1b795
--- /dev/null
+++ b/src/fe-gtk/sexy-spell-entry.h
@@ -0,0 +1,87 @@
+/*
+ * @file libsexy/sexy-spell-entry.h Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+#ifndef _SEXY_SPELL_ENTRY_H_
+#define _SEXY_SPELL_ENTRY_H_
+
+typedef struct _SexySpellEntry      SexySpellEntry;
+typedef struct _SexySpellEntryClass SexySpellEntryClass;
+typedef struct _SexySpellEntryPriv  SexySpellEntryPriv;
+
+#include <gtk/gtkentry.h>
+
+#define SEXY_TYPE_SPELL_ENTRY            (sexy_spell_entry_get_type())
+#define SEXY_SPELL_ENTRY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntry))
+#define SEXY_SPELL_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass))
+#define SEXY_IS_SPELL_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_SPELL_ENTRY))
+#define SEXY_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_SPELL_ENTRY))
+#define SEXY_SPELL_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass))
+
+#define SEXY_SPELL_ERROR                 (sexy_spell_error_quark())
+
+typedef enum {
+	SEXY_SPELL_ERROR_BACKEND,
+} SexySpellError;
+
+struct _SexySpellEntry
+{
+	GtkEntry parent_object;
+
+	SexySpellEntryPriv *priv;
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+struct _SexySpellEntryClass
+{
+	GtkEntryClass parent_class;
+
+	/* Signals */
+	gboolean (*word_check)(SexySpellEntry *entry, const gchar *word);
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType      sexy_spell_entry_get_type(void);
+GtkWidget *sexy_spell_entry_new(void);
+GQuark     sexy_spell_error_quark(void);
+
+GSList    *sexy_spell_entry_get_languages(const SexySpellEntry *entry);
+gchar     *sexy_spell_entry_get_language_name(const SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_language_is_active(const SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error);
+void       sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error);
+GSList    *sexy_spell_entry_get_active_languages(SexySpellEntry *entry);
+gboolean   sexy_spell_entry_is_checked(SexySpellEntry *entry);
+void       sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked);
+void       sexy_spell_entry_activate_default_languages(SexySpellEntry *entry);
+
+G_END_DECLS
+
+#endif
diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c
new file mode 100644
index 00000000..604da44b
--- /dev/null
+++ b/src/fe-gtk/textgui.c
@@ -0,0 +1,456 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/fe.h"
+#include "../common/text.h"
+#include "gtkutil.h"
+#include "xtext.h"
+#include "maingui.h"
+#include "palette.h"
+#include "textgui.h"
+
+extern struct text_event te[];
+extern char *pntevts_text[];
+extern char *pntevts[];
+
+static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid,
+	*pevent_dialog_entry,
+	*pevent_dialog_list, *pevent_dialog_hlist;
+
+enum
+{
+	COL_EVENT_NAME,
+	COL_EVENT_TEXT,
+	COL_ROW,
+	N_COLUMNS
+};
+
+
+/* this is only used in xtext.c for indented timestamping */
+int
+xtext_get_stamp_str (time_t tim, char **ret)
+{
+	return get_stamp_str (prefs.stamp_format, tim, ret);
+}
+
+static void
+PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet)
+{
+	unsigned char *tab, *new_text;
+	int leftlen;
+
+	if (len == 0)
+		len = 1;
+
+	if (!indent)
+	{
+		if (prefs.timestamp)
+		{
+			int stamp_size;
+			char *stamp;
+
+			if (timet == 0)
+				timet = time (0);
+
+			stamp_size = get_stamp_str (prefs.stamp_format, timet, &stamp);
+			new_text = malloc (len + stamp_size + 1);
+			memcpy (new_text, stamp, stamp_size);
+			g_free (stamp);
+			memcpy (new_text + stamp_size, text, len);
+			gtk_xtext_append (xtbuf, new_text, len + stamp_size);
+			free (new_text);
+		} else
+			gtk_xtext_append (xtbuf, text, len);
+		return;
+	}
+
+	tab = strchr (text, '\t');
+	if (tab && tab < (text + len))
+	{
+		leftlen = tab - text;
+		gtk_xtext_append_indent (xtbuf,
+										 text, leftlen, tab + 1, len - (leftlen + 1), timet);
+	} else
+		gtk_xtext_append_indent (xtbuf, 0, 0, text, len, timet);
+}
+
+void
+PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp)
+{
+	char *last_text = text;
+	int len = 0;
+	int beep_done = FALSE;
+
+	/* split the text into separate lines */
+	while (1)
+	{
+		switch (*text)
+		{
+		case 0:
+			PrintTextLine (xtbuf, last_text, len, indent, stamp);
+			return;
+		case '\n':
+			PrintTextLine (xtbuf, last_text, len, indent, stamp);
+			text++;
+			if (*text == 0)
+				return;
+			last_text = text;
+			len = 0;
+			break;
+		case ATTR_BEEP:
+			*text = ' ';
+			if (!beep_done) /* beeps may be slow, so only do 1 per line */
+			{
+				beep_done = TRUE;
+				if (!prefs.filterbeep)
+					gdk_beep ();
+			}
+		default:
+			text++;
+			len++;
+		}
+	}
+}
+
+static void
+pevent_dialog_close (GtkWidget *wid, gpointer arg)
+{
+	pevent_dialog = NULL;
+	pevent_save (NULL);
+}
+
+static void
+pevent_dialog_update (GtkWidget * wid, GtkWidget * twid)
+{
+	int len, m;
+	const char *text;
+	char *out;
+	int sig;
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
+													&iter, COL_ROW, &sig, -1))
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (wid));
+	len = strlen (text);
+
+	if (pevt_build_string (text, &out, &m) != 0)
+	{
+		fe_message (_("There was an error parsing the string"), FE_MSG_ERROR);
+		return;
+	}
+	if (m > (te[sig].num_args & 0x7f))
+	{
+		free (out);
+		out = malloc (4096);
+		snprintf (out, 4096,
+					 _("This signal is only passed %d args, $%d is invalid"),
+					 te[sig].num_args & 0x7f, m);
+		fe_message (out, FE_MSG_WARN);
+		free (out);
+		return;
+	}
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list));
+	gtk_list_store_set (store, &iter, COL_EVENT_TEXT, text, -1);
+
+	if (pntevts_text[sig])
+		free (pntevts_text[sig]);
+	if (pntevts[sig])
+		free (pntevts[sig]);
+
+	pntevts_text[sig] = malloc (len + 1);
+	memcpy (pntevts_text[sig], text, len + 1);
+	pntevts[sig] = out;
+
+	out = malloc (len + 2);
+	memcpy (out, text, len + 1);
+	out[len] = '\n';
+	out[len + 1] = 0;
+	check_special_chars (out, TRUE);
+
+	PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0);
+	free (out);
+
+	/* save this when we exit */
+	prefs.save_pevents = 1;
+}
+
+static void
+pevent_dialog_hfill (GtkWidget * list, int e)
+{
+	int i = 0;
+	char *text;
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist));
+	gtk_list_store_clear (store);
+	while (i < (te[e].num_args & 0x7f))
+	{
+		text = _(te[e].help[i]);
+		i++;
+		if (text[0] == '\001')
+			text++;
+		gtk_list_store_insert_with_values (store, &iter, -1,
+													  0, i,
+													  1, text, -1);
+	}
+}
+
+static void
+pevent_dialog_unselect (void)
+{
+	gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), "");
+	gtk_list_store_clear ((GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist)));
+}
+
+static void
+pevent_dialog_select (GtkTreeSelection *sel, gpointer store)
+{
+	char *text;
+	int sig;
+	GtkTreeIter iter;
+
+	if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
+													&iter, COL_ROW, &sig, -1))
+	{
+		pevent_dialog_unselect ();
+	}
+	else
+	{
+		gtk_tree_model_get (store, &iter, COL_EVENT_TEXT, &text, -1);
+		gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), text);
+		g_free (text);
+		pevent_dialog_hfill (pevent_dialog_hlist, sig);
+	}
+}
+
+static void
+pevent_dialog_fill (GtkWidget * list)
+{
+	int i;
+	GtkListStore *store;
+	GtkTreeIter iter;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+	gtk_list_store_clear (store);
+
+	i = NUM_XP;
+	do
+	{
+		i--;
+		gtk_list_store_insert_with_values (store, &iter, 0,
+													  COL_EVENT_NAME, te[i].name,
+													  COL_EVENT_TEXT, pntevts_text[i],
+													  COL_ROW, i, -1);
+	}
+	while (i != 0);
+}
+
+static void
+pevent_save_req_cb (void *arg1, char *file)
+{
+	if (file)
+		pevent_save (file);
+}
+
+static void
+pevent_save_cb (GtkWidget * wid, void *data)
+{
+	if (data)
+	{
+		gtkutil_file_req (_("Print Texts File"), pevent_save_req_cb, NULL,
+								NULL, FRF_WRITE);
+		return;
+	}
+	pevent_save (NULL);
+}
+
+static void
+pevent_load_req_cb (void *arg1, char *file)
+{
+	if (file)
+	{
+		pevent_load (file);
+		pevent_make_pntevts ();
+		pevent_dialog_fill (pevent_dialog_list);
+		pevent_dialog_unselect ();
+		prefs.save_pevents = 1;
+	}
+}
+
+static void
+pevent_load_cb (GtkWidget * wid, void *data)
+{
+	gtkutil_file_req (_("Print Texts File"), pevent_load_req_cb, NULL, NULL, 0);
+}
+
+static void
+pevent_ok_cb (GtkWidget * wid, void *data)
+{
+	gtk_widget_destroy (pevent_dialog);
+}
+
+static void
+pevent_test_cb (GtkWidget * wid, GtkWidget * twid)
+{
+	int len, n;
+	char *out, *text;
+
+	for (n = 0; n < NUM_XP; n++)
+	{
+		text = _(pntevts_text[n]);
+		len = strlen (text);
+
+		out = malloc (len + 2);
+		memcpy (out, text, len + 1);
+		out[len] = '\n';
+		out[len + 1] = 0;
+		check_special_chars (out, TRUE);
+
+		PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0);
+		free (out);
+	}
+}
+
+void
+pevent_dialog_show ()
+{
+	GtkWidget *vbox, *hbox, *tbox, *wid, *bh, *th;
+	GtkListStore *store, *hstore;
+	GtkTreeSelection *sel;
+
+	if (pevent_dialog)
+	{
+		mg_bring_tofront (pevent_dialog);
+		return;
+	}
+
+	pevent_dialog =
+			  mg_create_generic_tab ("edit events", _("Edit Events"),
+											 TRUE, FALSE, pevent_dialog_close, NULL,
+											 600, 455, &vbox, 0);
+
+	wid = gtk_vpaned_new ();
+	th = gtk_vbox_new (0, 2);
+	bh = gtk_vbox_new (0, 2);
+	gtk_widget_show (th);
+	gtk_widget_show (bh);
+	gtk_paned_pack1 (GTK_PANED (wid), th, 1, 1);
+	gtk_paned_pack2 (GTK_PANED (wid), bh, 0, 1);
+	gtk_box_pack_start (GTK_BOX (vbox), wid, 1, 1, 0);
+	gtk_widget_show (wid);
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
+	                            G_TYPE_STRING, G_TYPE_INT);
+	pevent_dialog_list = gtkutil_treeview_new (th, GTK_TREE_MODEL (store), NULL,
+															 COL_EVENT_NAME, _("Event"),
+															 COL_EVENT_TEXT, _("Text"), -1);
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pevent_dialog_list));
+	g_signal_connect (G_OBJECT (sel), "changed",
+							G_CALLBACK (pevent_dialog_select), store);
+
+	pevent_dialog_twid = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (pevent_dialog_twid), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (pevent_dialog_twid),
+									  channelwin_pix, prefs.transparent);
+
+	pevent_dialog_entry = gtk_entry_new_with_max_length (255);
+	g_signal_connect (G_OBJECT (pevent_dialog_entry), "activate",
+							G_CALLBACK (pevent_dialog_update), pevent_dialog_twid);
+	gtk_box_pack_start (GTK_BOX (bh), pevent_dialog_entry, 0, 0, 0);
+	gtk_widget_show (pevent_dialog_entry);
+
+	tbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (bh), tbox);
+	gtk_widget_show (tbox);
+
+	gtk_widget_set_usize (pevent_dialog_twid, 150, 20);
+	gtk_container_add (GTK_CONTAINER (tbox), pevent_dialog_twid);
+	gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.font_normal);
+
+	wid = gtk_vscrollbar_new (GTK_XTEXT (pevent_dialog_twid)->adj);
+	gtk_box_pack_start (GTK_BOX (tbox), wid, FALSE, FALSE, 0);
+	show_and_unfocus (wid);
+
+	gtk_widget_show (pevent_dialog_twid);
+
+	hstore = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+	pevent_dialog_hlist = gtkutil_treeview_new (bh, GTK_TREE_MODEL (hstore),
+															  NULL,
+															  0, _("$ Number"),
+															  1, _("Description"), -1);
+	gtk_widget_show (pevent_dialog_hlist);
+
+	pevent_dialog_fill (pevent_dialog_list);
+	gtk_widget_show (pevent_dialog_list);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 2);
+	/*wid = gtk_button_new_with_label (_("Save"));
+	gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (pevent_save_cb), NULL);
+	gtk_widget_show (wid);*/
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, pevent_save_cb,
+						 (void *) 1, _("Save As..."));
+	gtkutil_button (hbox, GTK_STOCK_OPEN, NULL, pevent_load_cb,
+						 (void *) 0, _("Load From..."));
+	wid = gtk_button_new_with_label (_("Test All"));
+	gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (pevent_test_cb), pevent_dialog_twid);
+	gtk_widget_show (wid);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (pevent_ok_cb), NULL);
+	gtk_widget_show (wid);
+
+	gtk_widget_show (hbox);
+
+	gtk_widget_show (pevent_dialog);
+}
diff --git a/src/fe-gtk/textgui.h b/src/fe-gtk/textgui.h
new file mode 100644
index 00000000..23db5848
--- /dev/null
+++ b/src/fe-gtk/textgui.h
@@ -0,0 +1,2 @@
+void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp);
+void pevent_dialog_show (void);
diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c
new file mode 100644
index 00000000..6e5f1e0d
--- /dev/null
+++ b/src/fe-gtk/urlgrab.c
@@ -0,0 +1,208 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/url.h"
+#include "../common/tree.h"
+#include "gtkutil.h"
+#include "menu.h"
+#include "maingui.h"
+#include "urlgrab.h"
+
+/* model for the URL treeview */
+enum
+{
+	URL_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *urlgrabberwindow = 0;
+
+
+static gboolean
+url_treeview_url_clicked_cb (GtkWidget *view, GdkEventButton *event,
+                             gpointer data)
+{
+	GtkTreeIter iter;
+	gchar *url;
+
+	if (!event ||
+	    !gtkutil_treeview_get_selected (GTK_TREE_VIEW (view), &iter,
+	                                    URL_COLUMN, &url, -1))
+	{
+		return FALSE;
+	}
+	
+	switch (event->button)
+	{
+		case 1:
+			if (event->type == GDK_2BUTTON_PRESS)
+				fe_open_url (url);
+			break;
+		case 3:
+			menu_urlmenu (event, url);
+			break;
+		default:
+			break;
+	}
+	g_free (url);
+
+	return FALSE;
+}
+
+static GtkWidget *
+url_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             URL_COLUMN, _("URL"), -1);
+	g_signal_connect (G_OBJECT (view), "button_press_event",
+	                  G_CALLBACK (url_treeview_url_clicked_cb), NULL);
+	/* don't want column headers */
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+url_closegui (GtkWidget *wid, gpointer userdata)
+{
+	urlgrabberwindow = 0;
+}
+
+static void
+url_button_clear (void)
+{
+	GtkListStore *store;
+	
+	url_clear ();
+	store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow),
+	                                           "model"));
+	gtk_list_store_clear (store);
+}
+
+static void
+url_button_copy (GtkWidget *widget, gpointer data)
+{
+	GtkTreeView *view = GTK_TREE_VIEW (data);
+	GtkTreeIter iter;
+	gchar *url = NULL;
+
+	if (gtkutil_treeview_get_selected (view, &iter, URL_COLUMN, &url, -1))
+	{
+		gtkutil_copy_to_clipboard (GTK_WIDGET (view), NULL, url);
+		g_free (url);
+	}
+}
+
+static void
+url_save_callback (void *arg1, char *file)
+{
+	if (file)
+		url_save (file, "w", TRUE);
+}
+
+static void
+url_button_save (void)
+{
+	gtkutil_file_req (_("Select an output filename"),
+							url_save_callback, NULL, NULL, FRF_WRITE);
+}
+
+void
+fe_url_add (const char *urltext)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	
+	if (urlgrabberwindow)
+	{
+		store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow),
+		                                           "model"));
+		gtk_list_store_prepend (store, &iter);
+		gtk_list_store_set (store, &iter,
+		                    URL_COLUMN, urltext,
+		                    -1);
+	}
+}
+
+static int
+populate_cb (char *urltext, gpointer userdata)
+{
+	fe_url_add (urltext);
+	return TRUE;
+}
+
+void
+url_opengui ()
+{
+	GtkWidget *vbox, *hbox, *view;
+
+	if (urlgrabberwindow)
+	{
+		mg_bring_tofront (urlgrabberwindow);
+		return;
+	}
+
+	urlgrabberwindow =
+		mg_create_generic_tab ("UrlGrabber", _("XChat: URL Grabber"), FALSE,
+							 TRUE, url_closegui, NULL, 400, 256, &vbox, 0);
+	view = url_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (urlgrabberwindow), "model",
+	                   gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
+	gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLEAR,
+						 _("Clear list"), url_button_clear, 0, _("Clear"));
+	gtkutil_button (hbox, GTK_STOCK_COPY,
+						 _("Copy selected URL"), url_button_copy, view, _("Copy"));
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS,
+						 _("Save list to a file"), url_button_save, 0, _("Save As..."));
+
+	gtk_widget_show (urlgrabberwindow);
+
+	tree_foreach (url_tree, (tree_traverse_func *)populate_cb, NULL);
+}
diff --git a/src/fe-gtk/urlgrab.h b/src/fe-gtk/urlgrab.h
new file mode 100644
index 00000000..cc534241
--- /dev/null
+++ b/src/fe-gtk/urlgrab.h
@@ -0,0 +1,2 @@
+void url_autosave (void);
+void url_opengui (void);
diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c
new file mode 100644
index 00000000..f040a6a1
--- /dev/null
+++ b/src/fe-gtk/userlistgui.c
@@ -0,0 +1,718 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkdnd.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkcellrendererpixbuf.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkliststore.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "../common/userlist.h"
+#include "../common/modes.h"
+#include "../common/notify.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+#include "menu.h"
+#include "pixmaps.h"
+#include "userlistgui.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+
+enum
+{
+	COL_PIX=0,		// GdkPixbuf *
+	COL_NICK=1,		// char *
+	COL_HOST=2,		// char *
+	COL_USER=3,		// struct User *
+	COL_GDKCOLOR=4	// GdkColor *
+};
+
+
+GdkPixbuf *
+get_user_icon (server *serv, struct User *user)
+{
+	char *pre;
+	int level;
+
+	if (!user)
+		return NULL;
+
+	/* these ones are hardcoded */
+	switch (user->prefix[0])
+	{
+	case 0: return NULL;
+	case '@': return pix_op;
+	case '%': return pix_hop;
+	case '+': return pix_voice;
+	}
+
+	/* find out how many levels above Op this user is */
+	pre = strchr (serv->nick_prefixes, '@');
+	if (pre && pre != serv->nick_prefixes)
+	{
+		pre--;
+		level = 0;
+		while (1)
+		{
+			if (pre[0] == user->prefix[0])
+			{
+				switch (level)
+				{
+				case 0: return pix_red;	/* 1 level above op */
+				case 1: return pix_purple;	 /* 2 levels above op */
+				}
+				break;	/* 3+, no icons */
+			}
+			level++;
+			if (pre == serv->nick_prefixes)
+				break;
+			pre--;
+		}
+	}
+
+	return NULL;
+}
+
+void
+fe_userlist_numbers (session *sess)
+{
+	char tbuf[256];
+
+	if (sess == current_tab || !sess->gui->is_tab)
+	{
+		if (sess->total)
+		{
+			snprintf (tbuf, sizeof (tbuf), _("%d ops, %d total"), sess->ops, sess->total);
+			tbuf[sizeof (tbuf) - 1] = 0;
+			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), tbuf);
+		} else
+		{
+			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), NULL);
+		}
+
+		if (sess->type == SESS_CHANNEL && prefs.gui_tweaks & 1)
+			fe_set_title (sess);
+	}
+}
+
+static void
+scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model)
+{
+	GtkTreePath *path = gtk_tree_model_get_path (model, iter);
+	if (path)
+	{
+		gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+		gtk_tree_path_free (path);
+	}
+}
+
+/* select a row in the userlist by nick-name */
+
+void
+userlist_select (session *sess, char *name)
+{
+	GtkTreeIter iter;
+	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	struct User *row_user;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+			if (sess->server->p_cmp (row_user->nick, name) == 0)
+			{
+				if (gtk_tree_selection_iter_is_selected (selection, &iter))
+					gtk_tree_selection_unselect_iter (selection, &iter);
+				else
+					gtk_tree_selection_select_iter (selection, &iter);
+
+				/* and make sure it's visible */
+				scroll_to_iter (&iter, treeview, model);
+				return;
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+char **
+userlist_selection_list (GtkWidget *widget, int *num_ret)
+{
+	GtkTreeIter iter;
+	GtkTreeView *treeview = (GtkTreeView *) widget;
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	struct User *user;
+	int i, num_sel;
+	char **nicks;
+
+	*num_ret = 0;
+	/* first, count the number of selections */
+	num_sel = 0;
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			if (gtk_tree_selection_iter_is_selected (selection, &iter))
+				num_sel++;
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	if (num_sel < 1)
+		return NULL;
+
+	nicks = malloc (sizeof (char *) * (num_sel + 1));
+
+	i = 0;
+	gtk_tree_model_get_iter_first (model, &iter);
+	do
+	{
+		if (gtk_tree_selection_iter_is_selected (selection, &iter))
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
+			nicks[i] = g_strdup (user->nick);
+			i++;
+			nicks[i] = NULL;
+		}
+	}
+	while (gtk_tree_model_iter_next (model, &iter));
+
+	*num_ret = i;
+	return nicks;
+}
+
+void
+fe_userlist_set_selected (struct session *sess)
+{
+	GtkListStore *store = sess->res->user_model;
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sess->gui->user_tree));
+	GtkTreeIter iter;
+	struct User *user;
+
+	/* if it's not front-most tab it doesn't own the GtkTreeView! */
+	if (store != (GtkListStore*) gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)))
+		return;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_USER, &user, -1);
+
+			if (gtk_tree_selection_iter_is_selected (selection, &iter))
+				user->selected = 1;
+			else
+				user->selected = 0;
+				
+		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+	}
+}
+
+static GtkTreeIter *
+find_row (GtkTreeView *treeview, GtkTreeModel *model, struct User *user,
+			 int *selected)
+{
+	static GtkTreeIter iter;
+	struct User *row_user;
+
+	*selected = FALSE;
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+			if (row_user == user)
+			{
+				if (gtk_tree_view_get_model (treeview) == model)
+				{
+					if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (treeview), &iter))
+						*selected = TRUE;
+				}
+				return &iter;
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	return NULL;
+}
+
+void
+userlist_set_value (GtkWidget *treeview, gfloat val)
+{
+	gtk_adjustment_set_value (
+			gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)), val);
+}
+
+gfloat
+userlist_get_value (GtkWidget *treeview)
+{
+	return gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview))->value;
+}
+
+int
+fe_userlist_remove (session *sess, struct User *user)
+{
+	GtkTreeIter *iter;
+/*	GtkAdjustment *adj;
+	gfloat val, end;*/
+	int sel;
+
+	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
+						  sess->res->user_model, user, &sel);
+	if (!iter)
+		return 0;
+
+/*	adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (sess->gui->user_tree));
+	val = adj->value;*/
+
+	gtk_list_store_remove (sess->res->user_model, iter);
+
+	/* is it the front-most tab? */
+/*	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
+		 == sess->res->user_model)
+	{
+		end = adj->upper - adj->lower - adj->page_size;
+		if (val > end)
+			val = end;
+		gtk_adjustment_set_value (adj, val);
+	}*/
+
+	return sel;
+}
+
+void
+fe_userlist_rehash (session *sess, struct User *user)
+{
+	GtkTreeIter *iter;
+	int sel;
+	int do_away = TRUE;
+
+	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
+						  sess->res->user_model, user, &sel);
+	if (!iter)
+		return;
+
+	if (prefs.away_size_max < 1 || !prefs.away_track)
+		do_away = FALSE;
+
+	gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
+							  COL_HOST, user->hostname,
+							  COL_GDKCOLOR, (do_away)
+									?	(user->away ? &colors[COL_AWAY] : NULL)
+									:	(NULL),
+							  -1);
+}
+
+void
+fe_userlist_insert (session *sess, struct User *newuser, int row, int sel)
+{
+	GtkTreeModel *model = sess->res->user_model;
+	GdkPixbuf *pix = get_user_icon (sess->server, newuser);
+	GtkTreeIter iter;
+	int do_away = TRUE;
+	char *nick;
+
+	if (prefs.away_size_max < 1 || !prefs.away_track)
+		do_away = FALSE;
+
+	nick = newuser->nick;
+	if (prefs.gui_tweaks & 64)
+	{
+		nick = malloc (strlen (newuser->nick) + 2);
+		nick[0] = newuser->prefix[0];
+		if (!nick[0] || nick[0] == ' ')
+			strcpy (nick, newuser->nick);
+		else
+			strcpy (nick + 1, newuser->nick);
+		pix = NULL;
+	}
+
+	gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, row,
+									COL_PIX, pix,
+									COL_NICK, nick,
+									COL_HOST, newuser->hostname,
+									COL_USER, newuser,
+									COL_GDKCOLOR, (do_away)
+										?	(newuser->away ? &colors[COL_AWAY] : NULL)
+										:	(NULL),
+								  -1);
+
+	if (prefs.gui_tweaks & 64)
+		free (nick);
+
+	/* is it me? */
+	if (newuser->me && sess->gui->nick_box)
+	{
+		if (!sess->gui->is_tab || sess == current_tab)
+			mg_set_access_icon (sess->gui, pix, sess->server->is_away);
+	}
+
+#if 0
+	if (prefs.hilitenotify && notify_isnotify (sess, newuser->nick))
+	{
+		gtk_clist_set_foreground ((GtkCList *) sess->gui->user_clist, row,
+										  &colors[prefs.nu_color]);
+	}
+#endif
+
+	/* is it the front-most tab? */
+	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
+		 == model)
+	{
+		if (sel)
+			gtk_tree_selection_select_iter (gtk_tree_view_get_selection
+										(GTK_TREE_VIEW (sess->gui->user_tree)), &iter);
+	}
+}
+
+void
+fe_userlist_move (session *sess, struct User *user, int new_row)
+{
+	fe_userlist_insert (sess, user, new_row, fe_userlist_remove (sess, user));
+}
+
+void
+fe_userlist_clear (session *sess)
+{
+	gtk_list_store_clear (sess->res->user_model);
+}
+
+static void
+userlist_dnd_drop (GtkTreeView *widget, GdkDragContext *context,
+						 gint x, gint y, GtkSelectionData *selection_data,
+						 guint info, guint ttime, gpointer userdata)
+{
+	struct User *user;
+	GtkTreePath *path;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
+		return;
+
+	model = gtk_tree_view_get_model (widget);
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		return;
+	gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
+
+	mg_dnd_drop_file (current_sess, user->nick, selection_data->data);
+}
+
+static gboolean
+userlist_dnd_motion (GtkTreeView *widget, GdkDragContext *context, gint x,
+							gint y, guint ttime, gpointer tree)
+{
+	GtkTreePath *path;
+	GtkTreeSelection *sel;
+
+	if (!tree)
+		return FALSE;
+
+	if (gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
+	{
+		sel = gtk_tree_view_get_selection (widget);
+		gtk_tree_selection_unselect_all (sel);
+		gtk_tree_selection_select_path (sel, path);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+userlist_dnd_leave (GtkTreeView *widget, GdkDragContext *context, guint ttime)
+{
+	gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (widget));
+	return TRUE;
+}
+
+void *
+userlist_create_model (void)
+{
+	return gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
+										G_TYPE_POINTER, GDK_TYPE_COLOR);
+}
+
+static void
+userlist_add_columns (GtkTreeView * treeview)
+{
+	GtkCellRenderer *renderer;
+
+	/* icon column */
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, NULL, renderer,
+																"pixbuf", 0, NULL);
+
+	/* nick column */
+	renderer = gtk_cell_renderer_text_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, NULL, renderer,
+													"text", 1, "foreground-gdk", 4, NULL);
+
+	if (prefs.showhostname_in_userlist)
+	{
+		/* hostname column */
+		renderer = gtk_cell_renderer_text_new ();
+		if (prefs.gui_tweaks & 32)
+			g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+		gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+		gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																	-1, NULL, renderer,
+																	"text", 2, NULL);
+	}
+}
+
+static gint
+userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
+{
+	char **nicks;
+	int i;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+
+	if (!event)
+		return FALSE;
+
+	if (!(event->state & GDK_CONTROL_MASK) &&
+		event->type == GDK_2BUTTON_PRESS && prefs.doubleclickuser[0])
+	{
+		nicks = userlist_selection_list (widget, &i);
+		if (nicks)
+		{
+			nick_command_parse (current_sess, prefs.doubleclickuser, nicks[0],
+									  nicks[0]);
+			while (i)
+			{
+				i--;
+				g_free (nicks[i]);
+			}
+			free (nicks);
+		}
+		return TRUE;
+	}
+
+	if (event->button == 3)
+	{
+		/* do we have a multi-selection? */
+		nicks = userlist_selection_list (widget, &i);
+		if (nicks && i > 1)
+		{
+			menu_nickmenu (current_sess, event, nicks[0], i);
+			while (i)
+			{
+				i--;
+				g_free (nicks[i]);
+			}
+			free (nicks);
+			return TRUE;
+		}
+		if (nicks)
+		{
+			g_free (nicks[0]);
+			free (nicks);
+		}
+
+		sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+			 event->x, event->y, &path, 0, 0, 0))
+		{
+			gtk_tree_selection_unselect_all (sel);
+			gtk_tree_selection_select_path (sel, path);
+			gtk_tree_path_free (path);
+			nicks = userlist_selection_list (widget, &i);
+			if (nicks)
+			{
+				menu_nickmenu (current_sess, event, nicks[0], i);
+				while (i)
+				{
+					i--;
+					g_free (nicks[i]);
+				}
+				free (nicks);
+			}
+		} else
+		{
+			gtk_tree_selection_unselect_all (sel);
+		}
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+userlist_key_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
+{
+	if (evt->keyval >= GDK_asterisk && evt->keyval <= GDK_z)
+	{
+		/* dirty trick to avoid auto-selection */
+		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE);
+		gtk_widget_grab_focus (current_sess->gui->input_box);
+		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE);
+		gtk_widget_event (current_sess->gui->input_box, (GdkEvent *)evt);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+GtkWidget *
+userlist_create (GtkWidget *box)
+{
+	GtkWidget *sw, *treeview;
+	static const GtkTargetEntry dnd_dest_targets[] =
+	{
+		{"text/uri-list", 0, 1},
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }
+	};
+	static const GtkTargetEntry dnd_src_target[] =
+	{
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+													 GTK_SHADOW_ETCHED_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+											  prefs.showhostname_in_userlist ?
+												GTK_POLICY_AUTOMATIC :
+												GTK_POLICY_NEVER,
+											  GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0);
+	gtk_widget_show (sw);
+
+	treeview = gtk_tree_view_new ();
+	gtk_widget_set_name (treeview, "xchat-userlist");
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+	gtk_tree_selection_set_mode (gtk_tree_view_get_selection
+										  (GTK_TREE_VIEW (treeview)),
+										  GTK_SELECTION_MULTIPLE);
+
+	/* set up drops */
+	gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, dnd_dest_targets, 2,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE);
+
+	/* file DND (for DCC) */
+	g_signal_connect (G_OBJECT (treeview), "drag_motion",
+							G_CALLBACK (userlist_dnd_motion), treeview);
+	g_signal_connect (G_OBJECT (treeview), "drag_leave",
+							G_CALLBACK (userlist_dnd_leave), 0);
+	g_signal_connect (G_OBJECT (treeview), "drag_data_received",
+							G_CALLBACK (userlist_dnd_drop), treeview);
+
+	g_signal_connect (G_OBJECT (treeview), "button_press_event",
+							G_CALLBACK (userlist_click_cb), 0);
+	g_signal_connect (G_OBJECT (treeview), "key_press_event",
+							G_CALLBACK (userlist_key_cb), 0);
+
+	/* tree/chanview DND */
+#ifndef WIN32	/* leaks GDI pool memory, don't enable */
+	g_signal_connect (G_OBJECT (treeview), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+#endif
+
+	userlist_add_columns (GTK_TREE_VIEW (treeview));
+
+	gtk_container_add (GTK_CONTAINER (sw), treeview);
+	gtk_widget_show (treeview);
+
+	return treeview;
+}
+
+void
+userlist_show (session *sess)
+{
+	gtk_tree_view_set_model (GTK_TREE_VIEW (sess->gui->user_tree),
+									 sess->res->user_model);
+}
+
+void
+fe_uselect (session *sess, char *word[], int do_clear, int scroll_to)
+{
+	int thisname;
+	char *name;
+	GtkTreeIter iter;
+	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	struct User *row_user;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		if (do_clear)
+			gtk_tree_selection_unselect_all (selection);
+
+		do
+		{
+			if (*word[0])
+			{
+				gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+				thisname = 0;
+				while ( *(name = word[thisname++]) )
+				{
+					if (sess->server->p_cmp (row_user->nick, name) == 0)
+					{
+						gtk_tree_selection_select_iter (selection, &iter);
+						if (scroll_to)
+							scroll_to_iter (&iter, treeview, model);
+						break;
+					}
+				}
+			}
+
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
diff --git a/src/fe-gtk/userlistgui.h b/src/fe-gtk/userlistgui.h
new file mode 100644
index 00000000..b49e2b9b
--- /dev/null
+++ b/src/fe-gtk/userlistgui.h
@@ -0,0 +1,8 @@
+void userlist_set_value (GtkWidget *treeview, gfloat val);
+gfloat userlist_get_value (GtkWidget *treeview);
+GtkWidget *userlist_create (GtkWidget *box);
+void *userlist_create_model (void);
+void userlist_show (session *sess);
+void userlist_select (session *sess, char *name);
+char **userlist_selection_list (GtkWidget *widget, int *num_ret);
+GdkPixbuf *get_user_icon (server *serv, struct User *user);
diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c
new file mode 100644
index 00000000..fa9803c7
--- /dev/null
+++ b/src/fe-gtk/xtext.c
@@ -0,0 +1,5478 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * =========================================================================
+ *
+ * xtext, the text widget used by X-Chat.
+ * By Peter Zelezny <zed@xchat.org>.
+ *
+ */
+
+#define XCHAT							/* using xchat */
+#define TINT_VALUE 195				/* 195/255 of the brightness. */
+#define MOTION_MONITOR				/* URL hilights. */
+#define SMOOTH_SCROLL				/* line-by-line or pixel scroll? */
+#define SCROLL_HACK					/* use XCopyArea scroll, or full redraw? */
+#undef COLOR_HILIGHT				/* Color instead of underline? */
+/* Italic is buggy because it assumes drawing an italic string will have
+   identical extents to the normal font. This is only true some of the
+   time, so we can't use this hack yet. */
+#undef ITALIC							/* support Italic? */
+#define GDK_MULTIHEAD_SAFE
+#define USE_DB							/* double buffer */
+
+#define MARGIN 2						/* dont touch. */
+#define REFRESH_TIMEOUT 20
+#define WORDWRAP_LIMIT 24
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkselection.h>
+#include <gtk/gtkclipboard.h>
+#include <gtk/gtkversion.h>
+#include <gtk/gtkwindow.h>
+
+#ifdef XCHAT
+#include "../../config.h"			/* can define USE_XLIB here */
+#else
+#define USE_XLIB
+#endif
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#endif
+
+#ifdef USE_MMX
+#include "mmx_cmod.h"
+#endif
+
+#include "xtext.h"
+
+#define charlen(str) g_utf8_skip[*(guchar *)(str)]
+
+#ifdef WIN32
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+#endif
+
+/* is delimiter */
+#define is_del(c) \
+	(c == ' ' || c == '\n' || c == ')' || c == '(' || \
+	 c == '>' || c == '<' || c == ATTR_RESET || c == ATTR_BOLD || c == 0)
+
+#ifdef SCROLL_HACK
+/* force scrolling off */
+#define dontscroll(buf) (buf)->last_pixel_pos = 0x7fffffff
+#else
+#define dontscroll(buf)
+#endif
+
+static GtkWidgetClass *parent_class = NULL;
+
+struct textentry
+{
+	struct textentry *next;
+	struct textentry *prev;
+	unsigned char *str;
+	time_t stamp;
+	gint16 str_width;
+	gint16 str_len;
+	gint16 mark_start;
+	gint16 mark_end;
+	gint16 indent;
+	gint16 left_len;
+	gint16 lines_taken;
+#define RECORD_WRAPS 4
+	guint16 wrap_offset[RECORD_WRAPS];
+	guchar mb;		/* boolean: is multibyte? */
+	guchar tag;
+	guchar pad1;
+	guchar pad2;	/* 32-bit align : 44 bytes total */
+};
+
+enum
+{
+	WORD_CLICK,
+	LAST_SIGNAL
+};
+
+/* values for selection info */
+enum
+{
+	TARGET_UTF8_STRING,
+	TARGET_STRING,
+	TARGET_TEXT,
+	TARGET_COMPOUND_TEXT
+};
+
+static guint xtext_signals[LAST_SIGNAL];
+
+#ifdef XCHAT
+char *nocasestrstr (const char *text, const char *tofind);	/* util.c */
+int xtext_get_stamp_str (time_t, char **);
+#endif
+static void gtk_xtext_render_page (GtkXText * xtext);
+static void gtk_xtext_calc_lines (xtext_buffer *buf, int);
+#if defined(USE_XLIB) || defined(WIN32)
+static void gtk_xtext_load_trans (GtkXText * xtext);
+static void gtk_xtext_free_trans (GtkXText * xtext);
+#endif
+static char *gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret);
+static textentry *gtk_xtext_nth (GtkXText *xtext, int line, int *subline);
+static void gtk_xtext_adjustment_changed (GtkAdjustment * adj,
+														GtkXText * xtext);
+static int gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *);
+static void gtk_xtext_recalc_widths (xtext_buffer *buf, int);
+static void gtk_xtext_fix_indent (xtext_buffer *buf);
+static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line);
+static char *gtk_xtext_conv_color (unsigned char *text, int len, int *newlen);
+static unsigned char *
+gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
+							  int *newlen, int *mb_ret, int strip_hidden);
+static gboolean gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add);
+static int gtk_xtext_render_page_timeout (GtkXText * xtext);
+
+/* some utility functions first */
+
+#ifndef XCHAT	/* xchat has this in util.c */
+
+static char *
+nocasestrstr (const char *s, const char *tofind)
+{
+   register const size_t len = strlen (tofind);
+
+   if (len == 0)
+     return (char *)s;
+   while (toupper(*s) != toupper(*tofind) || strncasecmp (s, tofind, len))
+     if (*s++ == '\0')
+       return (char *)NULL;
+   return (char *)s;   
+}
+
+#endif
+
+/* gives width of a 8bit string - with no mIRC codes in it */
+
+static int
+gtk_xtext_text_width_8bit (GtkXText *xtext, unsigned char *str, int len)
+{
+	int width = 0;
+
+	while (len)
+	{
+		width += xtext->fontwidth[*str];
+		str++;
+		len--;
+	}
+
+	return width;
+}
+
+#ifdef WIN32
+
+static void
+win32_draw_bg (GtkXText *xtext, int x, int y, int width, int height)
+{
+	HDC hdc;
+	HWND hwnd;
+	HRGN rgn;
+
+	if (xtext->shaded)
+	{
+		/* xtext->pixmap is really a GdkImage, created in win32_tint() */
+		gdk_draw_image (xtext->draw_buf, xtext->bgc, (GdkImage*)xtext->pixmap,
+							 x, y, x, y, width, height);
+	} else
+	{
+		hwnd = GDK_WINDOW_HWND (xtext->draw_buf);
+		hdc = GetDC (hwnd);
+
+		rgn = CreateRectRgn (x, y, x + width, y + height);
+		SelectClipRgn (hdc, rgn);
+
+		PaintDesktop (hdc);
+
+		ReleaseDC (hwnd, hdc);
+		DeleteObject (rgn);
+	}
+}
+
+static void
+xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height)
+{
+	if (xtext->transparent)
+		win32_draw_bg (xtext, x, y, width, height);
+	else
+		gdk_draw_rectangle (xtext->draw_buf, xtext->bgc, 1, x, y, width, height);
+}
+
+#else
+
+#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, \
+																	  1,x,y,w,h);
+
+#endif
+
+/* ========================================= */
+/* ========== XFT 1 and 2 BACKEND ========== */
+/* ========================================= */
+
+#ifdef USE_XFT
+
+static void
+backend_font_close (GtkXText *xtext)
+{
+	XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font);
+#ifdef ITALIC
+	XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->ifont);
+#endif
+}
+
+static void
+backend_init (GtkXText *xtext)
+{
+	if (xtext->xftdraw == NULL)
+	{
+		xtext->xftdraw = XftDrawCreate (
+			GDK_WINDOW_XDISPLAY (xtext->draw_buf),
+			GDK_WINDOW_XWINDOW (xtext->draw_buf),
+			GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (xtext->draw_buf)),
+			GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (xtext->draw_buf)));
+		XftDrawSetSubwindowMode (xtext->xftdraw, IncludeInferiors);
+	}
+}
+
+static void
+backend_deinit (GtkXText *xtext)
+{
+	if (xtext->xftdraw)
+	{
+		XftDrawDestroy (xtext->xftdraw);
+		xtext->xftdraw = NULL;
+	}
+}
+
+static XftFont *
+backend_font_open_real (Display *xdisplay, char *name, gboolean italics)
+{
+	XftFont *font = NULL;
+	PangoFontDescription *fontd;
+	int weight, slant, screen = DefaultScreen (xdisplay);
+
+	fontd = pango_font_description_from_string (name);
+
+	if (pango_font_description_get_size (fontd) != 0)
+	{
+		weight = pango_font_description_get_weight (fontd);
+		/* from pangoft2-fontmap.c */
+		if (weight < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_LIGHT) / 2)
+			weight = XFT_WEIGHT_LIGHT;
+		else if (weight < (PANGO_WEIGHT_NORMAL + 600) / 2)
+			weight = XFT_WEIGHT_MEDIUM;
+		else if (weight < (600 + PANGO_WEIGHT_BOLD) / 2)
+			weight = XFT_WEIGHT_DEMIBOLD;
+		else if (weight < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD) / 2)
+			weight = XFT_WEIGHT_BOLD;
+		else
+			weight = XFT_WEIGHT_BLACK;
+
+		slant = pango_font_description_get_style (fontd);
+		if (slant == PANGO_STYLE_ITALIC)
+			slant = XFT_SLANT_ITALIC;
+		else if (slant == PANGO_STYLE_OBLIQUE)
+			slant = XFT_SLANT_OBLIQUE;
+		else
+			slant = XFT_SLANT_ROMAN;
+
+		font = XftFontOpen (xdisplay, screen,
+						XFT_FAMILY, XftTypeString, pango_font_description_get_family (fontd),
+						XFT_CORE, XftTypeBool, False,
+						XFT_SIZE, XftTypeDouble, (double)pango_font_description_get_size (fontd)/PANGO_SCALE,
+						XFT_WEIGHT, XftTypeInteger, weight,
+						XFT_SLANT, XftTypeInteger, italics ? XFT_SLANT_ITALIC : slant,
+						NULL);
+	}
+	pango_font_description_free (fontd);
+
+	if (font == NULL)
+	{
+		font = XftFontOpenName (xdisplay, screen, name);
+		if (font == NULL)
+			font = XftFontOpenName (xdisplay, screen, "sans-11");
+	}
+
+	return font;
+}
+
+static void
+backend_font_open (GtkXText *xtext, char *name)
+{
+	Display *dis = GDK_WINDOW_XDISPLAY (xtext->draw_buf);
+
+	xtext->font = backend_font_open_real (dis, name, FALSE);
+#ifdef ITALIC
+	xtext->ifont = backend_font_open_real (dis, name, TRUE);
+#endif
+}
+
+inline static int
+backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret)
+{
+	XGlyphInfo ext;
+
+	if (*str < 128)
+	{
+		*mbl_ret = 1;
+		return xtext->fontwidth[*str];
+	}
+
+	*mbl_ret = charlen (str);
+	XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, *mbl_ret, &ext);
+
+	return ext.xOff;
+}
+
+static int
+backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb)
+{
+	XGlyphInfo ext;
+
+	if (!is_mb)
+		return gtk_xtext_text_width_8bit (xtext, str, len);
+
+	XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, len, &ext);
+	return ext.xOff;
+}
+
+static void
+backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y,
+						 char *str, int len, int str_width, int is_mb)
+{
+	/*Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);*/
+	void (*draw_func) (XftDraw *, XftColor *, XftFont *, int, int, XftChar8 *, int) = (void *)XftDrawString8;
+	XftFont *font;
+
+	/* if all ascii, use String8 to avoid the conversion penalty */
+	if (is_mb)
+		draw_func = (void *)XftDrawStringUtf8;
+
+	if (dofill)
+	{
+/*		register GC xgc = GDK_GC_XGC (gc);
+		XSetForeground (xdisplay, xgc, xtext->xft_bg->pixel);
+		XFillRectangle (xdisplay, GDK_WINDOW_XWINDOW (xtext->draw_buf), xgc, x,
+							 y - xtext->font->ascent, str_width, xtext->fontsize);*/
+		XftDrawRect (xtext->xftdraw, xtext->xft_bg, x,
+						 y - xtext->font->ascent, str_width, xtext->fontsize);
+	}
+
+	font = xtext->font;
+#ifdef ITALIC
+	if (xtext->italics)
+		font = xtext->ifont;
+#endif
+
+	draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len);
+
+	if (xtext->overdraw)
+		draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len);
+
+	if (xtext->bold)
+		draw_func (xtext->xftdraw, xtext->xft_fg, font, x + 1, y, str, len);
+}
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+	gdk_gc_set_clip_rectangle (xtext->bgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+	gdk_gc_set_clip_rectangle (xtext->bgc, NULL);
+}*/
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	Region reg;
+	XRectangle rect;
+
+	rect.x = area->x;
+	rect.y = area->y;
+	rect.width = area->width;
+	rect.height = area->height;
+
+	reg = XCreateRegion ();
+	XUnionRectWithRegion (&rect, reg, reg);
+	XftDrawSetClip (xtext->xftdraw, reg);
+	XDestroyRegion (reg);
+
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	XftDrawSetClip (xtext->xftdraw, NULL);
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+}
+*/
+#else	/* !USE_XFT */
+
+/* ======================================= */
+/* ============ PANGO BACKEND ============ */
+/* ======================================= */
+
+static void
+backend_font_close (GtkXText *xtext)
+{
+	pango_font_description_free (xtext->font->font);
+#ifdef ITALIC
+	pango_font_description_free (xtext->font->ifont);
+#endif
+}
+
+static void
+backend_init (GtkXText *xtext)
+{
+	if (xtext->layout == NULL)
+	{
+		xtext->layout = gtk_widget_create_pango_layout (GTK_WIDGET (xtext), 0); 
+		if (xtext->font)
+			pango_layout_set_font_description (xtext->layout, xtext->font->font);
+	}
+}
+
+static void
+backend_deinit (GtkXText *xtext)
+{
+	if (xtext->layout)
+	{
+		g_object_unref (xtext->layout);
+		xtext->layout = NULL;
+	}
+}
+
+static PangoFontDescription *
+backend_font_open_real (char *name)
+{
+	PangoFontDescription *font;
+
+	font = pango_font_description_from_string (name);
+	if (font && pango_font_description_get_size (font) == 0)
+	{
+		pango_font_description_free (font);
+		font = pango_font_description_from_string ("sans 11");
+	}
+	if (!font)
+		font = pango_font_description_from_string ("sans 11");
+
+	return font;
+}
+
+static void
+backend_font_open (GtkXText *xtext, char *name)
+{
+	PangoLanguage *lang;
+	PangoContext *context;
+	PangoFontMetrics *metrics;
+
+	xtext->font = &xtext->pango_font;
+	xtext->font->font = backend_font_open_real (name);
+	if (!xtext->font->font)
+	{
+		xtext->font = NULL;
+		return;
+	}
+#ifdef ITALIC
+	xtext->font->ifont = backend_font_open_real (name);
+	pango_font_description_set_style (xtext->font->ifont, PANGO_STYLE_ITALIC);
+#endif
+
+	backend_init (xtext);
+	pango_layout_set_font_description (xtext->layout, xtext->font->font);
+
+	/* vte does it this way */
+	context = gtk_widget_get_pango_context (GTK_WIDGET (xtext));
+	lang = pango_context_get_language (context);
+	metrics = pango_context_get_metrics (context, xtext->font->font, lang);
+	xtext->font->ascent = pango_font_metrics_get_ascent (metrics) / PANGO_SCALE;
+	xtext->font->descent = pango_font_metrics_get_descent (metrics) / PANGO_SCALE;
+	pango_font_metrics_unref (metrics);
+}
+
+static int
+backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb)
+{
+	int width;
+
+	if (!is_mb)
+		return gtk_xtext_text_width_8bit (xtext, str, len);
+
+	if (*str == 0)
+		return 0;
+
+	pango_layout_set_text (xtext->layout, str, len);
+	pango_layout_get_pixel_size (xtext->layout, &width, NULL);
+
+	return width;
+}
+
+inline static int
+backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret)
+{
+	int width;
+
+	if (*str < 128)
+	{
+		*mbl_ret = 1;
+		return xtext->fontwidth[*str];
+	}
+
+	*mbl_ret = charlen (str);
+	pango_layout_set_text (xtext->layout, str, *mbl_ret);
+	pango_layout_get_pixel_size (xtext->layout, &width, NULL);
+
+	return width;
+}
+
+/* simplified version of gdk_draw_layout_line_with_colors() */
+
+static void 
+xtext_draw_layout_line (GdkDrawable      *drawable,
+								GdkGC            *gc,
+								gint              x, 
+								gint              y,
+								PangoLayoutLine  *line)
+{
+	GSList *tmp_list = line->runs;
+	PangoRectangle logical_rect;
+	gint x_off = 0;
+
+	while (tmp_list)
+	{
+		PangoLayoutRun *run = tmp_list->data;
+
+		pango_glyph_string_extents (run->glyphs, run->item->analysis.font,
+											 NULL, &logical_rect);
+
+		gdk_draw_glyphs (drawable, gc, run->item->analysis.font,
+							  x + x_off / PANGO_SCALE, y, run->glyphs);
+
+		x_off += logical_rect.width;
+		tmp_list = tmp_list->next;
+	}
+}
+
+static void
+backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y,
+						 char *str, int len, int str_width, int is_mb)
+{
+	GdkGCValues val;
+	GdkColor col;
+	PangoLayoutLine *line;
+
+#ifdef ITALIC
+	if (xtext->italics)
+		pango_layout_set_font_description (xtext->layout, xtext->font->ifont);
+#endif
+
+	pango_layout_set_text (xtext->layout, str, len);
+
+	if (dofill)
+	{
+#ifdef WIN32
+		if (xtext->transparent && !xtext->backcolor)
+			win32_draw_bg (xtext, x, y - xtext->font->ascent, str_width,
+								xtext->fontsize);
+		else
+#endif
+		{
+			gdk_gc_get_values (gc, &val);
+			col.pixel = val.background.pixel;
+			gdk_gc_set_foreground (gc, &col);
+			gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y -
+									  xtext->font->ascent, str_width, xtext->fontsize);
+			col.pixel = val.foreground.pixel;
+			gdk_gc_set_foreground (gc, &col);
+		}
+	}
+
+	line = pango_layout_get_lines (xtext->layout)->data;
+
+	xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line);
+
+	if (xtext->overdraw)
+		xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line);
+
+	if (xtext->bold)
+		xtext_draw_layout_line (xtext->draw_buf, gc, x + 1, y, line);
+
+#ifdef ITALIC
+	if (xtext->italics)
+		pango_layout_set_font_description (xtext->layout, xtext->font->font);
+#endif
+}
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+	gdk_gc_set_clip_rectangle (xtext->bgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+	gdk_gc_set_clip_rectangle (xtext->bgc, NULL);
+}*/
+
+#endif /* !USE_PANGO */
+
+static void
+xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index)
+{
+	GdkColor col;
+
+	col.pixel = xtext->palette[index];
+	gdk_gc_set_foreground (gc, &col);
+
+#ifdef USE_XFT
+	if (gc == xtext->fgc)
+		xtext->xft_fg = &xtext->color[index];
+	else
+		xtext->xft_bg = &xtext->color[index];
+#endif
+}
+
+#ifdef USE_XFT
+
+#define xtext_set_bg(xt,gc,index) xt->xft_bg = &xt->color[index]
+
+#else
+
+static void
+xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index)
+{
+	GdkColor col;
+
+	col.pixel = xtext->palette[index];
+	gdk_gc_set_background (gc, &col);
+}
+
+#endif
+
+static void
+gtk_xtext_init (GtkXText * xtext)
+{
+	xtext->pixmap = NULL;
+	xtext->io_tag = 0;
+	xtext->add_io_tag = 0;
+	xtext->scroll_tag = 0;
+	xtext->max_lines = 0;
+	xtext->col_back = XTEXT_BG;
+	xtext->col_fore = XTEXT_FG;
+	xtext->nc = 0;
+	xtext->pixel_offset = 0;
+	xtext->bold = FALSE;
+	xtext->underline = FALSE;
+	xtext->italics = FALSE;
+	xtext->hidden = FALSE;
+	xtext->font = NULL;
+#ifdef USE_XFT
+	xtext->xftdraw = NULL;
+#else
+	xtext->layout = NULL;
+#endif
+	xtext->jump_out_offset = 0;
+	xtext->jump_in_offset = 0;
+	xtext->ts_x = 0;
+	xtext->ts_y = 0;
+	xtext->clip_x = 0;
+	xtext->clip_x2 = 1000000;
+	xtext->clip_y = 0;
+	xtext->clip_y2 = 1000000;
+	xtext->error_function = NULL;
+	xtext->urlcheck_function = NULL;
+	xtext->color_paste = FALSE;
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+	xtext->render_hilights_only = FALSE;
+	xtext->un_hilight = FALSE;
+	xtext->recycle = FALSE;
+	xtext->dont_render = FALSE;
+	xtext->dont_render2 = FALSE;
+	xtext->overdraw = FALSE;
+	xtext->tint_red = xtext->tint_green = xtext->tint_blue = TINT_VALUE;
+
+	xtext->adj = (GtkAdjustment *) gtk_adjustment_new (0, 0, 1, 1, 1, 1);
+	g_object_ref (G_OBJECT (xtext->adj));
+	g_object_ref_sink (G_OBJECT (xtext->adj));
+	g_object_unref (G_OBJECT (xtext->adj));
+
+	xtext->vc_signal_tag = g_signal_connect (G_OBJECT (xtext->adj),
+				"value_changed", G_CALLBACK (gtk_xtext_adjustment_changed), xtext);
+	{
+		static const GtkTargetEntry targets[] = {
+			{ "UTF8_STRING", 0, TARGET_UTF8_STRING },
+			{ "STRING", 0, TARGET_STRING },
+			{ "TEXT",   0, TARGET_TEXT }, 
+			{ "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT }
+		};
+		static const gint n_targets = sizeof (targets) / sizeof (targets[0]);
+
+		gtk_selection_add_targets (GTK_WIDGET (xtext), GDK_SELECTION_PRIMARY,
+											targets, n_targets);
+	}
+
+	if (getenv ("XCHAT_OVERDRAW"))
+		xtext->overdraw = TRUE;
+}
+
+static void
+gtk_xtext_adjustment_set (xtext_buffer *buf, int fire_signal)
+{
+	GtkAdjustment *adj = buf->xtext->adj;
+
+	if (buf->xtext->buffer == buf)
+	{
+		adj->lower = 0;
+		adj->upper = buf->num_lines;
+
+		if (adj->upper == 0)
+			adj->upper = 1;
+
+		adj->page_size =
+			(GTK_WIDGET (buf->xtext)->allocation.height -
+			 buf->xtext->font->descent) / buf->xtext->fontsize;
+		adj->page_increment = adj->page_size;
+
+		if (adj->value > adj->upper - adj->page_size)
+			adj->value = adj->upper - adj->page_size;
+
+		if (adj->value < 0)
+			adj->value = 0;
+
+		if (fire_signal)
+			gtk_adjustment_changed (adj);
+	}
+}
+
+static gint
+gtk_xtext_adjustment_timeout (GtkXText * xtext)
+{
+	gtk_xtext_render_page (xtext);
+	xtext->io_tag = 0;
+	return 0;
+}
+
+static void
+gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext)
+{
+#ifdef SMOOTH_SCROLL
+	if (xtext->buffer->old_value != xtext->adj->value)
+#else
+	if ((int) xtext->buffer->old_value != (int) xtext->adj->value)
+#endif
+	{
+		if (xtext->adj->value >= xtext->adj->upper - xtext->adj->page_size)
+			xtext->buffer->scrollbar_down = TRUE;
+		else
+			xtext->buffer->scrollbar_down = FALSE;
+
+		if (xtext->adj->value + 1 == xtext->buffer->old_value ||
+			 xtext->adj->value - 1 == xtext->buffer->old_value)	/* clicked an arrow? */
+		{
+			if (xtext->io_tag)
+			{
+				g_source_remove (xtext->io_tag);
+				xtext->io_tag = 0;
+			}
+			gtk_xtext_render_page (xtext);
+		} else
+		{
+			if (!xtext->io_tag)
+				xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT,
+															(GSourceFunc)
+															gtk_xtext_adjustment_timeout,
+															xtext);
+		}
+	}
+	xtext->buffer->old_value = adj->value;
+}
+
+GtkWidget *
+gtk_xtext_new (GdkColor palette[], int separator)
+{
+	GtkXText *xtext;
+
+	xtext = g_object_new (gtk_xtext_get_type (), NULL);
+	xtext->separator = separator;
+	xtext->wordwrap = TRUE;
+	xtext->buffer = gtk_xtext_buffer_new (xtext);
+	xtext->orig_buffer = xtext->buffer;
+
+	gtk_widget_set_double_buffered (GTK_WIDGET (xtext), FALSE);
+	gtk_xtext_set_palette (xtext, palette);
+
+	return GTK_WIDGET (xtext);
+}
+
+static void
+gtk_xtext_destroy (GtkObject * object)
+{
+	GtkXText *xtext = GTK_XTEXT (object);
+
+	if (xtext->add_io_tag)
+	{
+		g_source_remove (xtext->add_io_tag);
+		xtext->add_io_tag = 0;
+	}
+
+	if (xtext->scroll_tag)
+	{
+		g_source_remove (xtext->scroll_tag);
+		xtext->scroll_tag = 0;
+	}
+
+	if (xtext->io_tag)
+	{
+		g_source_remove (xtext->io_tag);
+		xtext->io_tag = 0;
+	}
+
+	if (xtext->pixmap)
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent)
+			gtk_xtext_free_trans (xtext);
+		else
+#endif
+			g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+	}
+
+	if (xtext->font)
+	{
+		backend_font_close (xtext);
+		xtext->font = NULL;
+	}
+
+	if (xtext->adj)
+	{
+		g_signal_handlers_disconnect_matched (G_OBJECT (xtext->adj),
+					G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, xtext);
+	/*	gtk_signal_disconnect_by_data (GTK_OBJECT (xtext->adj), xtext);*/
+		g_object_unref (G_OBJECT (xtext->adj));
+		xtext->adj = NULL;
+	}
+
+	if (xtext->bgc)
+	{
+		g_object_unref (xtext->bgc);
+		xtext->bgc = NULL;
+	}
+
+	if (xtext->fgc)
+	{
+		g_object_unref (xtext->fgc);
+		xtext->fgc = NULL;
+	}
+
+	if (xtext->light_gc)
+	{
+		g_object_unref (xtext->light_gc);
+		xtext->light_gc = NULL;
+	}
+
+	if (xtext->dark_gc)
+	{
+		g_object_unref (xtext->dark_gc);
+		xtext->dark_gc = NULL;
+	}
+
+	if (xtext->thin_gc)
+	{
+		g_object_unref (xtext->thin_gc);
+		xtext->thin_gc = NULL;
+	}
+
+	if (xtext->marker_gc)
+	{
+		g_object_unref (xtext->marker_gc);
+		xtext->marker_gc = NULL;
+	}
+
+	if (xtext->hand_cursor)
+	{
+		gdk_cursor_unref (xtext->hand_cursor);
+		xtext->hand_cursor = NULL;
+	}
+
+	if (xtext->resize_cursor)
+	{
+		gdk_cursor_unref (xtext->resize_cursor);
+		xtext->resize_cursor = NULL;
+	}
+
+	if (xtext->orig_buffer)
+	{
+		gtk_xtext_buffer_free (xtext->orig_buffer);
+		xtext->orig_buffer = NULL;
+	}
+
+	if (GTK_OBJECT_CLASS (parent_class)->destroy)
+		(*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+gtk_xtext_unrealize (GtkWidget * widget)
+{
+	backend_deinit (GTK_XTEXT (widget));
+
+	/* if there are still events in the queue, this'll avoid segfault */
+	gdk_window_set_user_data (widget->window, NULL);
+
+	if (parent_class->unrealize)
+		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_xtext_realize (GtkWidget * widget)
+{
+	GtkXText *xtext;
+	GdkWindowAttr attributes;
+	GdkGCValues val;
+	GdkColor col;
+	GdkColormap *cmap;
+
+	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+	xtext = GTK_XTEXT (widget);
+
+	attributes.x = widget->allocation.x;
+	attributes.y = widget->allocation.y;
+	attributes.width = widget->allocation.width;
+	attributes.height = widget->allocation.height;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.event_mask = gtk_widget_get_events (widget) |
+		GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+#ifdef MOTION_MONITOR
+		| GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK;
+#else
+		| GDK_POINTER_MOTION_MASK;
+#endif
+
+	cmap = gtk_widget_get_colormap (widget);
+	attributes.colormap = cmap;
+	attributes.visual = gtk_widget_get_visual (widget);
+
+	widget->window = gdk_window_new (widget->parent->window, &attributes,
+												GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL |
+												GDK_WA_COLORMAP);
+
+	gdk_window_set_user_data (widget->window, widget);
+
+	xtext->depth = gdk_drawable_get_visual (widget->window)->depth;
+
+	val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+	val.graphics_exposures = 0;
+
+	xtext->bgc = gdk_gc_new_with_values (widget->window, &val,
+													 GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->fgc = gdk_gc_new_with_values (widget->window, &val,
+													 GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->light_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+
+	/* for the separator bar (light) */
+	col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->light_gc, &col);
+
+	/* for the separator bar (dark) */
+	col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->dark_gc, &col);
+
+	/* for the separator bar (thinline) */
+	col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->thin_gc, &col);
+
+	/* for the marker bar (marker) */
+	col.pixel = xtext->palette[XTEXT_MARKER];
+	gdk_gc_set_foreground (xtext->marker_gc, &col);
+
+	xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+	xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+	xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+
+	/* draw directly to window */
+	xtext->draw_buf = widget->window;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (xtext->transparent)
+	{
+		gtk_xtext_load_trans (xtext);
+	} else
+#endif
+	if (xtext->pixmap)
+	{
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+		xtext->ts_x = xtext->ts_y = 0;
+		gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+	}
+
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+	xtext->hand_cursor = gdk_cursor_new (GDK_HAND1);
+	xtext->resize_cursor = gdk_cursor_new (GDK_LEFT_SIDE);
+#else
+	xtext->hand_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_HAND1);
+	xtext->resize_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_LEFT_SIDE);
+#endif
+
+	gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
+	widget->style = gtk_style_attach (widget->style, widget->window);
+
+	backend_init (xtext);
+}
+
+static void
+gtk_xtext_size_request (GtkWidget * widget, GtkRequisition * requisition)
+{
+	requisition->width = 200;
+	requisition->height = 90;
+}
+
+static void
+gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	int height_only = FALSE;
+	int do_trans = TRUE;
+
+	if (allocation->width == xtext->buffer->window_width)
+		height_only = TRUE;
+
+	if (allocation->x == widget->allocation.x &&
+		 allocation->y == widget->allocation.y && xtext->avoid_trans)
+		do_trans = FALSE;
+
+	xtext->avoid_trans = FALSE;
+
+	widget->allocation = *allocation;
+	if (GTK_WIDGET_REALIZED (widget))
+	{
+		xtext->buffer->window_width = allocation->width;
+		xtext->buffer->window_height = allocation->height;
+
+		gdk_window_move_resize (widget->window, allocation->x, allocation->y,
+										allocation->width, allocation->height);
+		dontscroll (xtext->buffer);	/* force scrolling off */
+		if (!height_only)
+			gtk_xtext_calc_lines (xtext->buffer, FALSE);
+		else
+		{
+			xtext->buffer->pagetop_ent = NULL;
+			gtk_xtext_adjustment_set (xtext->buffer, FALSE);
+		}
+#if defined(USE_XLIB) || defined(WIN32)
+		if (do_trans && xtext->transparent && xtext->shaded)
+		{
+			gtk_xtext_free_trans (xtext);
+			gtk_xtext_load_trans (xtext);
+		}
+#endif
+		if (xtext->buffer->scrollbar_down)
+			gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+											  xtext->adj->page_size);
+	}
+}
+
+static void
+gtk_xtext_selection_clear_full (xtext_buffer *buf)
+{
+	textentry *ent = buf->text_first;
+	while (ent)
+	{
+		ent->mark_start = -1;
+		ent->mark_end = -1;
+		ent = ent->next;
+	}
+}
+
+static int
+gtk_xtext_selection_clear (xtext_buffer *buf)
+{
+	textentry *ent;
+	int ret = 0;
+
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+			ret = 1;
+		ent->mark_start = -1;
+		ent->mark_end = -1;
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+
+	return ret;
+}
+
+static int
+find_x (GtkXText *xtext, textentry *ent, unsigned char *text, int x, int indent)
+{
+	int xx = indent;
+	int i = 0;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	unsigned char *orig = text;
+	int mbl;
+	int char_width;
+
+	while (*text)
+	{
+		mbl = 1;
+		if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol)))
+		{
+			if (text[1] != ',') rcol--;
+			if (*text == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+			text++;
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*text)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				text++;
+				break;
+			case ATTR_HIDDEN:
+				if (xtext->ignore_hidden)
+					goto def;
+				hidden = !hidden;
+				text++;
+				break;
+			default:
+			def:
+				char_width = backend_get_char_width (xtext, text, &mbl);
+				if (!hidden) xx += char_width;
+				text += mbl;
+				if (xx >= x)
+					return i + (orig - ent->str);
+			}
+		}
+
+		i += mbl;
+		if (text - orig >= ent->str_len)
+			return ent->str_len;
+	}
+
+	return ent->str_len;
+}
+
+static int
+gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline,
+						int line, int *out_of_bounds)
+{
+	int indent;
+	unsigned char *str;
+
+	if (subline < 1)
+		indent = ent->indent;
+	else
+		indent = xtext->buffer->indent;
+
+	if (line > xtext->adj->page_size || line < 0)
+		return 0;
+
+	if (xtext->buffer->grid_dirty || line > 255)
+	{
+		str = ent->str + gtk_xtext_find_subline (xtext, ent, subline);
+		if (str >= ent->str + ent->str_len)
+			return 0;
+	} else
+	{
+		if (xtext->buffer->grid_offset[line] > ent->str_len)
+			return 0;
+		str = ent->str + xtext->buffer->grid_offset[line];
+	}
+
+	if (x < indent)
+	{
+		*out_of_bounds = 1;
+		return (str - ent->str);
+	}
+
+	*out_of_bounds = 0;
+
+	return find_x (xtext, ent, str, x, indent);
+}
+
+static textentry *
+gtk_xtext_find_char (GtkXText * xtext, int x, int y, int *off,
+							int *out_of_bounds)
+{
+	textentry *ent;
+	int line;
+	int subline;
+
+	line = (y + xtext->pixel_offset) / xtext->fontsize;
+	ent = gtk_xtext_nth (xtext, line + (int)xtext->adj->value, &subline);
+	if (!ent)
+		return 0;
+
+	if (off)
+		*off = gtk_xtext_find_x (xtext, x, ent, subline, line, out_of_bounds);
+
+	return ent;
+}
+
+static void
+gtk_xtext_draw_sep (GtkXText * xtext, int y)
+{
+	int x, height;
+	GdkGC *light, *dark;
+
+	if (y == -1)
+	{
+		y = 0;
+		height = GTK_WIDGET (xtext)->allocation.height;
+	} else
+	{
+		height = xtext->fontsize;
+	}
+
+	/* draw the separator line */
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		light = xtext->light_gc;
+		dark = xtext->dark_gc;
+
+		x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (x < 1)
+			return;
+
+		if (xtext->thinline)
+		{
+			if (xtext->moving_separator)
+				gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height);
+			else
+				gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height);
+		} else
+		{
+			if (xtext->moving_separator)
+			{
+				gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height);
+				gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height);
+			} else
+			{
+				gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height);
+				gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height);
+			}
+		}
+	}
+}
+
+static void
+gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y)
+{
+	int x, width, render_y;
+
+	if (!xtext->marker) return;
+
+	if (xtext->buffer->marker_pos == ent)
+	{
+		render_y = y + xtext->font->descent;
+	}
+	else if (xtext->buffer->marker_pos == ent->next && ent->next != NULL)
+	{
+		render_y = y + xtext->font->descent + xtext->fontsize * ent->lines_taken;
+	}
+	else return;
+
+	x = 0;
+	width = GTK_WIDGET (xtext)->allocation.width;
+
+	gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y);
+
+#if GTK_CHECK_VERSION(2,4,0)
+	if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))))
+#else
+	if (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))->has_focus)
+#endif
+	{
+		xtext->buffer->marker_seen = TRUE;
+	}
+}
+
+#ifdef USE_SHM
+static int
+have_shm_pixmaps(Display *dpy)
+{
+	int major, minor;
+	static int checked = 0;
+	static int have = FALSE;
+
+	if (!checked)
+	{
+		XShmQueryVersion (dpy, &major, &minor, &have);
+		checked = 1;
+	}
+
+	return have;
+}
+#endif
+
+static void
+gtk_xtext_paint (GtkWidget *widget, GdkRectangle *area)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	textentry *ent_start, *ent_end;
+	int x, y;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (xtext->transparent)
+	{
+		gdk_window_get_origin (widget->window, &x, &y);
+		/* update transparency only if it moved */
+		if (xtext->last_win_x != x || xtext->last_win_y != y)
+		{
+			xtext->last_win_x = x;
+			xtext->last_win_y = y;
+#ifndef WIN32
+#ifdef USE_SHM
+			if (xtext->shaded && !have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf)))
+#else
+			if (xtext->shaded)
+#endif
+			{
+				xtext->recycle = TRUE;
+				gtk_xtext_load_trans (xtext);
+				xtext->recycle = FALSE;
+			} else
+#endif
+			{
+				gtk_xtext_free_trans (xtext);
+				gtk_xtext_load_trans (xtext);
+			}
+		}
+	}
+#endif
+
+	if (area->x == 0 && area->y == 0 &&
+		 area->height == widget->allocation.height &&
+		 area->width == widget->allocation.width)
+	{
+		dontscroll (xtext->buffer);	/* force scrolling off */
+		gtk_xtext_render_page (xtext);
+		return;
+	}
+
+	ent_start = gtk_xtext_find_char (xtext, area->x, area->y, NULL, NULL);
+	if (!ent_start)
+	{
+		xtext_draw_bg (xtext, area->x, area->y, area->width, area->height);
+		goto xit;
+	}
+	ent_end = gtk_xtext_find_char (xtext, area->x + area->width,
+											 area->y + area->height, NULL, NULL);
+	if (!ent_end)
+		ent_end = xtext->buffer->text_last;
+
+	/* can't set a clip here, because fgc/bgc are used to draw the DB too */
+/*	backend_set_clip (xtext, area);*/
+	xtext->clip_x = area->x;
+	xtext->clip_x2 = area->x + area->width;
+	xtext->clip_y = area->y;
+	xtext->clip_y2 = area->y + area->height;
+
+	/* y is the last pixel y location it rendered text at */
+	y = gtk_xtext_render_ents (xtext, ent_start, ent_end);
+
+	if (y && y < widget->allocation.height && !ent_end->next)
+	{
+		GdkRectangle rect;
+
+		rect.x = 0;
+		rect.y = y;
+		rect.width = widget->allocation.width;
+		rect.height = widget->allocation.height - y;
+
+		/* fill any space below the last line that also intersects with
+			the exposure rectangle */
+		if (gdk_rectangle_intersect (area, &rect, &rect))
+		{
+			xtext_draw_bg (xtext, rect.x, rect.y, rect.width, rect.height);
+		}
+	}
+
+	/*backend_clear_clip (xtext);*/
+	xtext->clip_x = 0;
+	xtext->clip_x2 = 1000000;
+	xtext->clip_y = 0;
+	xtext->clip_y2 = 1000000;
+
+xit:
+	x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+	if (area->x <= x)
+		gtk_xtext_draw_sep (xtext, -1);
+}
+
+static gboolean
+gtk_xtext_expose (GtkWidget * widget, GdkEventExpose * event)
+{
+	gtk_xtext_paint (widget, &event->area);
+	return FALSE;
+}
+
+/* render a selection that has extended or contracted upward */
+
+static void
+gtk_xtext_selection_up (GtkXText *xtext, textentry *start, textentry *end,
+								int start_offset)
+{
+	/* render all the complete lines */
+	if (start->next == end)
+		gtk_xtext_render_ents (xtext, end, NULL);
+	else
+		gtk_xtext_render_ents (xtext, start->next, end);
+
+	/* now the incomplete upper line */
+	if (start == xtext->buffer->last_ent_start)
+		xtext->jump_in_offset = xtext->buffer->last_offset_start;
+	else
+		xtext->jump_in_offset = start_offset;
+	gtk_xtext_render_ents (xtext, start, NULL);
+	xtext->jump_in_offset = 0;
+}
+
+/* render a selection that has extended or contracted downward */
+
+static void
+gtk_xtext_selection_down (GtkXText *xtext, textentry *start, textentry *end,
+								  int end_offset)
+{
+	/* render all the complete lines */
+	if (end->prev == start)
+		gtk_xtext_render_ents (xtext, start, NULL);
+	else
+		gtk_xtext_render_ents (xtext, start, end->prev);
+
+	/* now the incomplete bottom line */
+	if (end == xtext->buffer->last_ent_end)
+		xtext->jump_out_offset = xtext->buffer->last_offset_end;
+	else
+		xtext->jump_out_offset = end_offset;
+	gtk_xtext_render_ents (xtext, end, NULL);
+	xtext->jump_out_offset = 0;
+}
+
+static void
+gtk_xtext_selection_render (GtkXText *xtext,
+									 textentry *start_ent, int start_offset,
+									 textentry *end_ent, int end_offset)
+{
+	textentry *ent;
+	int start, end;
+
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+
+	/* force an optimized render if there was no previous selection */
+	if (xtext->buffer->last_ent_start == NULL && start_ent == end_ent)
+	{
+		xtext->buffer->last_offset_start = start_offset;
+		xtext->buffer->last_offset_end = end_offset;
+		goto lamejump;
+	}
+
+	/* mark changed within 1 ent only? */
+	if (xtext->buffer->last_ent_start == start_ent &&
+		 xtext->buffer->last_ent_end == end_ent)
+	{
+		/* when only 1 end of the selection is changed, we can really
+			save on rendering */
+		if (xtext->buffer->last_offset_start == start_offset ||
+			 xtext->buffer->last_offset_end == end_offset)
+		{
+lamejump:
+			ent = end_ent;
+			/* figure out where to start and end the rendering */
+			if (end_offset > xtext->buffer->last_offset_end)
+			{
+				end = end_offset;
+				start = xtext->buffer->last_offset_end;
+			} else if (end_offset < xtext->buffer->last_offset_end)
+			{
+				end = xtext->buffer->last_offset_end;
+				start = end_offset;
+			} else if (start_offset < xtext->buffer->last_offset_start)
+			{
+				end = xtext->buffer->last_offset_start;
+				start = start_offset;
+				ent = start_ent;
+			} else if (start_offset > xtext->buffer->last_offset_start)
+			{
+				end = start_offset;
+				start = xtext->buffer->last_offset_start;
+				ent = start_ent;
+			} else
+			{	/* WORD selects end up here */
+				end = end_offset;
+				start = start_offset;
+			}
+		} else
+		{
+			/* LINE selects end up here */
+			/* so which ent actually changed? */
+			ent = start_ent;
+			if (xtext->buffer->last_offset_start == start_offset)
+				ent = end_ent;
+
+			end = MAX (xtext->buffer->last_offset_end, end_offset);
+			start = MIN (xtext->buffer->last_offset_start, start_offset);
+		}
+
+		xtext->jump_out_offset = end;
+		xtext->jump_in_offset = start;
+		gtk_xtext_render_ents (xtext, ent, NULL);
+		xtext->jump_out_offset = 0;
+		xtext->jump_in_offset = 0;
+	}
+	/* marking downward? */
+	else if (xtext->buffer->last_ent_start == start_ent &&
+				xtext->buffer->last_offset_start == start_offset)
+	{
+		/* find the range that covers both old and new selection */
+		ent = start_ent;
+		while (ent)
+		{
+			if (ent == xtext->buffer->last_ent_end)
+			{
+				gtk_xtext_selection_down (xtext, ent, end_ent, end_offset);
+				/*gtk_xtext_render_ents (xtext, ent, end_ent);*/
+				break;
+			}
+			if (ent == end_ent)
+			{
+				gtk_xtext_selection_down (xtext, ent, xtext->buffer->last_ent_end, end_offset);
+				/*gtk_xtext_render_ents (xtext, ent, xtext->buffer->last_ent_end);*/
+				break;
+			}
+			ent = ent->next;
+		}
+	}
+	/* marking upward? */
+	else if (xtext->buffer->last_ent_end == end_ent &&
+				xtext->buffer->last_offset_end == end_offset)
+	{
+		ent = end_ent;
+		while (ent)
+		{
+			if (ent == start_ent)
+			{
+				gtk_xtext_selection_up (xtext, xtext->buffer->last_ent_start, ent, start_offset);
+				/*gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, ent);*/
+				break;
+			}
+			if (ent == xtext->buffer->last_ent_start)
+			{
+				gtk_xtext_selection_up (xtext, start_ent, ent, start_offset);
+				/*gtk_xtext_render_ents (xtext, start_ent, ent);*/
+				break;
+			}
+			ent = ent->prev;
+		}
+	}
+	else	/* cross-over mark (stretched or shrunk at both ends) */
+	{
+		/* unrender the old mark */
+		gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, xtext->buffer->last_ent_end);
+		/* now render the new mark, but skip overlaps */
+		if (start_ent == xtext->buffer->last_ent_start)
+		{
+			/* if the new mark is a sub-set of the old, do nothing */
+			if (start_ent != end_ent)
+				gtk_xtext_render_ents (xtext, start_ent->next, end_ent);
+		} else if (end_ent == xtext->buffer->last_ent_end)
+		{
+			/* if the new mark is a sub-set of the old, do nothing */
+			if (start_ent != end_ent)
+				gtk_xtext_render_ents (xtext, start_ent, end_ent->prev);
+		} else
+			gtk_xtext_render_ents (xtext, start_ent, end_ent);
+	}
+
+	xtext->buffer->last_ent_start = start_ent;
+	xtext->buffer->last_ent_end = end_ent;
+	xtext->buffer->last_offset_start = start_offset;
+	xtext->buffer->last_offset_end = end_offset;
+
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+}
+
+static void
+gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean render)
+{
+	textentry *ent;
+	textentry *ent_end;
+	textentry *ent_start;
+	int offset_start;
+	int offset_end;
+	int low_x;
+	int low_y;
+	int high_x;
+	int high_y;
+	int tmp;
+
+	if (xtext->select_start_y > xtext->select_end_y)
+	{
+		low_x = xtext->select_end_x;
+		low_y = xtext->select_end_y;
+		high_x = xtext->select_start_x;
+		high_y = xtext->select_start_y;
+	} else
+	{
+		low_x = xtext->select_start_x;
+		low_y = xtext->select_start_y;
+		high_x = xtext->select_end_x;
+		high_y = xtext->select_end_y;
+	}
+
+	ent_start = gtk_xtext_find_char (xtext, low_x, low_y, &offset_start, &tmp);
+	if (!ent_start)
+	{
+		if (xtext->adj->value != xtext->buffer->old_value)
+			gtk_xtext_render_page (xtext);
+		return;
+	}
+
+	ent_end = gtk_xtext_find_char (xtext, high_x, high_y, &offset_end, &tmp);
+	if (!ent_end)
+	{
+		ent_end = xtext->buffer->text_last;
+		if (!ent_end)
+		{
+			if (xtext->adj->value != xtext->buffer->old_value)
+				gtk_xtext_render_page (xtext);
+			return;
+		}
+		offset_end = ent_end->str_len;
+	}
+
+	/* marking less than a complete line? */
+	/* make sure "start" is smaller than "end" (swap them if need be) */
+	if (ent_start == ent_end && offset_start > offset_end)
+	{
+		tmp = offset_start;
+		offset_start = offset_end;
+		offset_end = tmp;
+	}
+
+	/* has the selection changed? Dont render unless necessary */
+	if (xtext->buffer->last_ent_start == ent_start &&
+		 xtext->buffer->last_ent_end == ent_end &&
+		 xtext->buffer->last_offset_start == offset_start &&
+		 xtext->buffer->last_offset_end == offset_end)
+		return;
+
+	/* set all the old mark_ fields to -1 */
+	gtk_xtext_selection_clear (xtext->buffer);
+
+	ent_start->mark_start = offset_start;
+	ent_start->mark_end = offset_end;
+
+	if (ent_start != ent_end)
+	{
+		ent_start->mark_end = ent_start->str_len;
+		if (offset_end != 0)
+		{
+			ent_end->mark_start = 0;
+			ent_end->mark_end = offset_end;
+		}
+
+		/* set all the mark_ fields of the ents within the selection */
+		ent = ent_start->next;
+		while (ent && ent != ent_end)
+		{
+			ent->mark_start = 0;
+			ent->mark_end = ent->str_len;
+			ent = ent->next;
+		}
+	}
+
+	if (render)
+		gtk_xtext_selection_render (xtext, ent_start, offset_start, ent_end, offset_end);
+}
+
+static gint
+gtk_xtext_scrolldown_timeout (GtkXText * xtext)
+{
+	int p_y, win_height;
+
+	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
+
+	if (p_y > win_height &&
+		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
+	{
+		xtext->adj->value++;
+		gtk_adjustment_changed (xtext->adj);
+		gtk_xtext_render_page (xtext);
+		return 1;
+	}
+
+	xtext->scroll_tag = 0;
+	return 0;
+}
+
+static gint
+gtk_xtext_scrollup_timeout (GtkXText * xtext)
+{
+	int p_y;
+
+	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
+
+	if (p_y < 0 && xtext->adj->value > 0.0)
+	{
+		xtext->adj->value--;
+		gtk_adjustment_changed (xtext->adj);
+		gtk_xtext_render_page (xtext);
+		return 1;
+	}
+
+	xtext->scroll_tag = 0;
+	return 0;
+}
+
+static void
+gtk_xtext_selection_update (GtkXText * xtext, GdkEventMotion * event, int p_y, gboolean render)
+{
+	int win_height;
+	int moved;
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
+
+	/* selecting past top of window, scroll up! */
+	if (p_y < 0 && xtext->adj->value >= 0)
+	{
+		if (!xtext->scroll_tag)
+			xtext->scroll_tag = g_timeout_add (100,
+															(GSourceFunc)
+														 	gtk_xtext_scrollup_timeout,
+															xtext);
+		return;
+	}
+
+	/* selecting past bottom of window, scroll down! */
+	if (p_y > win_height &&
+		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
+	{
+		if (!xtext->scroll_tag)
+			xtext->scroll_tag = g_timeout_add (100,
+															(GSourceFunc)
+															gtk_xtext_scrolldown_timeout,
+															xtext);
+		return;
+	}
+
+	moved = (int)xtext->adj->value - xtext->select_start_adj;
+	xtext->select_start_y -= (moved * xtext->fontsize);
+	xtext->select_start_adj = xtext->adj->value;
+	gtk_xtext_selection_draw (xtext, event, render);
+}
+
+static char *
+gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
+						  int *ret_off, int *ret_len)
+{
+	textentry *ent;
+	int offset;
+	unsigned char *str;
+	unsigned char *word;
+	int len;
+	int out_of_bounds = 0;
+
+	ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds);
+	if (!ent)
+		return 0;
+
+	if (out_of_bounds)
+		return 0;
+
+	if (offset == ent->str_len)
+		return 0;
+
+	if (offset < 1)
+		return 0;
+
+	/*offset--;*/	/* FIXME: not all chars are 1 byte */
+
+	str = ent->str + offset;
+
+	while (!is_del (*str) && str != ent->str)
+		str--;
+	word = str + 1;
+
+	len = 0;
+	str = word;
+	while (!is_del (*str) && len != ent->str_len)
+	{
+		str++;
+		len++;
+	}
+
+	if (len > 0 && word[len-1]=='.')
+	{
+		len--;
+		str--;
+	}
+
+	if (ret_ent)
+		*ret_ent = ent;
+	if (ret_off)
+		*ret_off = word - ent->str;
+	if (ret_len)
+		*ret_len = str - word;
+
+	return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, FALSE);
+}
+
+#ifdef MOTION_MONITOR
+
+static void
+gtk_xtext_unrender_hilight (GtkXText *xtext)
+{
+	xtext->render_hilights_only = TRUE;
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+	xtext->un_hilight = TRUE;
+
+	gtk_xtext_render_ents (xtext, xtext->hilight_ent, NULL);
+
+	xtext->render_hilights_only = FALSE;
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+	xtext->un_hilight = FALSE;
+}
+
+static gboolean
+gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+
+	if (xtext->cursor_hand)
+	{
+		gtk_xtext_unrender_hilight (xtext);
+		xtext->hilight_start = -1;
+		xtext->hilight_end = -1;
+		xtext->cursor_hand = FALSE;
+		gdk_window_set_cursor (widget->window, 0);
+		xtext->hilight_ent = NULL;
+	}
+
+	if (xtext->cursor_resize)
+	{
+		gtk_xtext_unrender_hilight (xtext);
+		xtext->hilight_start = -1;
+		xtext->hilight_end = -1;
+		xtext->cursor_resize = FALSE;
+		gdk_window_set_cursor (widget->window, 0);
+		xtext->hilight_ent = NULL;
+	}
+
+	return FALSE;
+}
+
+#endif
+
+/* check if we should mark time stamps, and if a redraw is needed */
+
+static gboolean
+gtk_xtext_check_mark_stamp (GtkXText *xtext, GdkModifierType mask)
+{
+	gboolean redraw = FALSE;
+
+	if ((mask & GDK_SHIFT_MASK))
+	{
+		if (!xtext->mark_stamp)
+		{
+			redraw = TRUE;	/* must redraw all */
+			xtext->mark_stamp = TRUE;
+		}
+	} else
+	{
+		if (xtext->mark_stamp)
+		{
+			redraw = TRUE;	/* must redraw all */
+			xtext->mark_stamp = FALSE;
+		}
+	}
+	return redraw;
+}
+
+static gboolean
+gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	GdkModifierType mask;
+	int redraw, tmp, x, y, offset, len, line_x;
+	unsigned char *word;
+	textentry *word_ent;
+
+	gdk_window_get_pointer (widget->window, &x, &y, &mask);
+
+	if (xtext->moving_separator)
+	{
+		if (x < (3 * widget->allocation.width) / 5 && x > 15)
+		{
+			tmp = xtext->buffer->indent;
+			xtext->buffer->indent = x;
+			gtk_xtext_fix_indent (xtext->buffer);
+			if (tmp != xtext->buffer->indent)
+			{
+				gtk_xtext_recalc_widths (xtext->buffer, FALSE);
+				if (xtext->buffer->scrollbar_down)
+					gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+													  xtext->adj->page_size);
+				if (!xtext->io_tag)
+					xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT,
+																(GSourceFunc)
+																gtk_xtext_adjustment_timeout,
+																xtext);
+			}
+		}
+		return FALSE;
+	}
+
+	if (xtext->button_down)
+	{
+		redraw = gtk_xtext_check_mark_stamp (xtext, mask);
+		gtk_grab_add (widget);
+		/*gdk_pointer_grab (widget->window, TRUE,
+									GDK_BUTTON_RELEASE_MASK |
+									GDK_BUTTON_MOTION_MASK, NULL, NULL, 0);*/
+		xtext->select_end_x = x;
+		xtext->select_end_y = y;
+		gtk_xtext_selection_update (xtext, event, y, !redraw);
+		xtext->hilighting = TRUE;
+
+		/* user has pressed or released SHIFT, must redraw entire selection */
+		if (redraw)
+		{
+			xtext->force_stamp = TRUE;
+			gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start,
+										  xtext->buffer->last_ent_end);
+			xtext->force_stamp = FALSE;
+		}
+		return FALSE;
+	}
+#ifdef MOTION_MONITOR
+
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (line_x == x || line_x == x + 1 || line_x == x - 1)
+		{
+			if (!xtext->cursor_resize)
+			{
+				gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
+										  		xtext->resize_cursor);
+				xtext->cursor_resize = TRUE;
+			}
+			return FALSE;
+		}
+	}
+
+	if (xtext->urlcheck_function == NULL)
+		return FALSE;
+
+	word = gtk_xtext_get_word (xtext, x, y, &word_ent, &offset, &len);
+	if (word)
+	{
+		if (xtext->urlcheck_function (GTK_WIDGET (xtext), word, len) > 0)
+		{
+			if (!xtext->cursor_hand ||
+				 xtext->hilight_ent != word_ent ||
+				 xtext->hilight_start != offset ||
+				 xtext->hilight_end != offset + len)
+			{
+				if (!xtext->cursor_hand)
+				{
+					gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
+											  		xtext->hand_cursor);
+					xtext->cursor_hand = TRUE;
+				}
+
+				/* un-render the old hilight */
+				if (xtext->hilight_ent)
+					gtk_xtext_unrender_hilight (xtext);
+
+				xtext->hilight_ent = word_ent;
+				xtext->hilight_start = offset;
+				xtext->hilight_end = offset + len;
+
+				xtext->skip_border_fills = TRUE;
+				xtext->render_hilights_only = TRUE;
+				xtext->skip_stamp = TRUE;
+
+				gtk_xtext_render_ents (xtext, word_ent, NULL);
+
+				xtext->skip_border_fills = FALSE;
+				xtext->render_hilights_only = FALSE;
+				xtext->skip_stamp = FALSE;
+			}
+			return FALSE;
+		}
+	}
+
+	gtk_xtext_leave_notify (widget, NULL);
+
+#endif
+
+	return FALSE;
+}
+
+static void
+gtk_xtext_set_clip_owner (GtkWidget * xtext, GdkEventButton * event)
+{
+	char *str;
+	int len;
+
+	if (GTK_XTEXT (xtext)->selection_buffer &&
+		GTK_XTEXT (xtext)->selection_buffer != GTK_XTEXT (xtext)->buffer)
+		gtk_xtext_selection_clear (GTK_XTEXT (xtext)->selection_buffer);
+
+	GTK_XTEXT (xtext)->selection_buffer = GTK_XTEXT (xtext)->buffer;
+
+	str = gtk_xtext_selection_get_text (GTK_XTEXT (xtext), &len);
+	if (str)
+	{
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+										str, len);
+#else
+		gtk_clipboard_set_text (gtk_widget_get_clipboard (xtext, GDK_SELECTION_CLIPBOARD),
+										str, len);
+#endif
+		free (str);
+	}
+
+	gtk_selection_owner_set (xtext, GDK_SELECTION_PRIMARY, event->time);
+}
+
+static void
+gtk_xtext_unselect (GtkXText *xtext)
+{
+	xtext_buffer *buf = xtext->buffer;
+
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+
+	xtext->jump_in_offset = buf->last_ent_start->mark_start;
+	/* just a single ent was marked? */
+	if (buf->last_ent_start == buf->last_ent_end)
+	{
+		xtext->jump_out_offset = buf->last_ent_start->mark_end;
+		buf->last_ent_end = NULL;
+	}
+
+	gtk_xtext_selection_clear (xtext->buffer);
+
+	/* FIXME: use jump_out on multi-line selects too! */
+	gtk_xtext_render_ents (xtext, buf->last_ent_start, buf->last_ent_end);
+
+	xtext->jump_in_offset = 0;
+	xtext->jump_out_offset = 0;
+
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+
+	xtext->buffer->last_ent_start = NULL;
+	xtext->buffer->last_ent_end = NULL;
+}
+
+static gboolean
+gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	unsigned char *word;
+	int old;
+
+	if (xtext->moving_separator)
+	{
+		xtext->moving_separator = FALSE;
+		old = xtext->buffer->indent;
+		if (event->x < (4 * widget->allocation.width) / 5 && event->x > 15)
+			xtext->buffer->indent = event->x;
+		gtk_xtext_fix_indent (xtext->buffer);
+		if (xtext->buffer->indent != old)
+		{
+			gtk_xtext_recalc_widths (xtext->buffer, FALSE);
+			gtk_xtext_adjustment_set (xtext->buffer, TRUE);
+			gtk_xtext_render_page (xtext);
+		} else
+			gtk_xtext_draw_sep (xtext, -1);
+		return FALSE;
+	}
+
+	if (xtext->word_or_line_select)
+	{
+		xtext->word_or_line_select = FALSE;
+		xtext->button_down = FALSE;
+		return FALSE;
+	}
+
+	if (event->button == 1)
+	{
+		xtext->button_down = FALSE;
+
+		gtk_grab_remove (widget);
+		/*gdk_pointer_ungrab (0);*/
+
+		/* got a new selection? */
+		if (xtext->buffer->last_ent_start)
+		{
+			xtext->color_paste = FALSE;
+			if (event->state & GDK_CONTROL_MASK)
+				xtext->color_paste = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		if (xtext->select_start_x == event->x &&
+			 xtext->select_start_y == event->y &&
+			 xtext->buffer->last_ent_start)
+		{
+			gtk_xtext_unselect (xtext);
+			xtext->mark_stamp = FALSE;
+			return FALSE;
+		}
+
+		if (!xtext->hilighting)
+		{
+			word = gtk_xtext_get_word (xtext, event->x, event->y, 0, 0, 0);
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event);
+		} else
+		{
+			xtext->hilighting = FALSE;
+		}
+	}
+
+
+	return FALSE;
+}
+
+static gboolean
+gtk_xtext_button_press (GtkWidget * widget, GdkEventButton * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	GdkModifierType mask;
+	textentry *ent;
+	unsigned char *word;
+	int line_x, x, y, offset, len;
+
+	gdk_window_get_pointer (widget->window, &x, &y, &mask);
+
+	if (event->button == 3 || event->button == 2) /* right/middle click */
+	{
+		word = gtk_xtext_get_word (xtext, x, y, 0, 0, 0);
+		if (word)
+		{
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
+								word, event);
+		} else
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
+								"", event);
+		return FALSE;
+	}
+
+	if (event->button != 1)		  /* we only want left button */
+		return FALSE;
+
+	if (event->type == GDK_2BUTTON_PRESS)	/* WORD select */
+	{
+		gtk_xtext_check_mark_stamp (xtext, mask);
+		if (gtk_xtext_get_word (xtext, x, y, &ent, &offset, &len))
+		{
+			if (len == 0)
+				return FALSE;
+			gtk_xtext_selection_clear (xtext->buffer);
+			ent->mark_start = offset;
+			ent->mark_end = offset + len;
+			gtk_xtext_selection_render (xtext, ent, offset, ent, offset + len);
+			xtext->word_or_line_select = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		return FALSE;
+	}
+
+	if (event->type == GDK_3BUTTON_PRESS)	/* LINE select */
+	{
+		gtk_xtext_check_mark_stamp (xtext, mask);
+		if (gtk_xtext_get_word (xtext, x, y, &ent, 0, 0))
+		{
+			gtk_xtext_selection_clear (xtext->buffer);
+			ent->mark_start = 0;
+			ent->mark_end = ent->str_len;
+			gtk_xtext_selection_render (xtext, ent, 0, ent, ent->str_len);
+			xtext->word_or_line_select = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		return FALSE;
+	}
+
+	/* check if it was a separator-bar click */
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (line_x == x || line_x == x + 1 || line_x == x - 1)
+		{
+			xtext->moving_separator = TRUE;
+			/* draw the separator line */
+			gtk_xtext_draw_sep (xtext, -1);
+			return FALSE;
+		}
+	}
+
+	xtext->button_down = TRUE;
+	xtext->select_start_x = x;
+	xtext->select_start_y = y;
+	xtext->select_start_adj = xtext->adj->value;
+
+	return FALSE;
+}
+
+/* another program has claimed the selection */
+
+static gboolean
+gtk_xtext_selection_kill (GtkXText *xtext, GdkEventSelection *event)
+{
+#ifndef WIN32
+	if (xtext->buffer->last_ent_start)
+		gtk_xtext_unselect (xtext);
+#endif
+	return TRUE;
+}
+
+static char *
+gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret)
+{
+	textentry *ent;
+	char *txt;
+	char *pos;
+	char *stripped;
+	int len;
+	int first = TRUE;
+	xtext_buffer *buf;
+
+	buf = xtext->selection_buffer;
+	if (!buf)
+		return NULL;
+
+	/* first find out how much we need to malloc ... */
+	len = 0;
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+		{
+			/* include timestamp? */
+			if (ent->mark_start == 0 && xtext->mark_stamp)
+			{
+				char *time_str;
+				int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str);
+				g_free (time_str);
+				len += stamp_size;
+			}
+
+			if (ent->mark_end - ent->mark_start > 0)
+				len += (ent->mark_end - ent->mark_start) + 1;
+			else
+				len++;
+		}
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+
+	if (len < 1)
+		return NULL;
+
+	/* now allocate mem and copy buffer */
+	pos = txt = malloc (len);
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+		{
+			if (!first)
+			{
+				*pos = '\n';
+				pos++;
+			}
+			first = FALSE;
+			if (ent->mark_end - ent->mark_start > 0)
+			{
+				/* include timestamp? */
+				if (ent->mark_start == 0 && xtext->mark_stamp)
+				{
+					char *time_str;
+					int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str);
+					memcpy (pos, time_str, stamp_size);
+					g_free (time_str);
+					pos += stamp_size;
+				}
+
+				memcpy (pos, ent->str + ent->mark_start,
+						  ent->mark_end - ent->mark_start);
+				pos += ent->mark_end - ent->mark_start;
+			}
+		}
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+	*pos = 0;
+
+	if (xtext->color_paste)
+	{
+		/*stripped = gtk_xtext_conv_color (txt, strlen (txt), &len);*/
+		stripped = txt;
+		len = strlen (txt);
+	} else
+	{
+		stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, 0, FALSE);
+		free (txt);
+	}
+
+	*len_ret = len;
+	return stripped;
+}
+
+/* another program is asking for our selection */
+
+static void
+gtk_xtext_selection_get (GtkWidget * widget,
+								 GtkSelectionData * selection_data_ptr,
+								 guint info, guint time)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	char *stripped;
+	guchar *new_text;
+	int len;
+	gsize glen;
+
+	stripped = gtk_xtext_selection_get_text (xtext, &len);
+	if (!stripped)
+		return;
+
+	switch (info)
+	{
+	case TARGET_UTF8_STRING:
+		/* it's already in utf8 */
+		gtk_selection_data_set_text (selection_data_ptr, stripped, len);
+		break;
+	case TARGET_TEXT:
+	case TARGET_COMPOUND_TEXT:
+		{
+			GdkAtom encoding;
+			gint format;
+			gint new_length;
+
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+			gdk_string_to_compound_text (
+#else
+			gdk_string_to_compound_text_for_display (
+												gdk_drawable_get_display (widget->window),
+#endif
+												stripped, &encoding, &format, &new_text,
+												&new_length);
+			gtk_selection_data_set (selection_data_ptr, encoding, format,
+											new_text, new_length);
+			gdk_free_compound_text (new_text);
+		}
+		break;
+	default:
+		new_text = g_locale_from_utf8 (stripped, len, NULL, &glen, NULL);
+		gtk_selection_data_set (selection_data_ptr, GDK_SELECTION_TYPE_STRING,
+										8, new_text, glen);
+		g_free (new_text);
+	}
+
+	free (stripped);
+}
+
+static gboolean
+gtk_xtext_scroll (GtkWidget *widget, GdkEventScroll *event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	gfloat new_value;
+
+	if (event->direction == GDK_SCROLL_UP)		/* mouse wheel pageUp */
+	{
+		new_value = xtext->adj->value - (xtext->adj->page_increment / 10);
+		if (new_value < xtext->adj->lower)
+			new_value = xtext->adj->lower;
+		gtk_adjustment_set_value (xtext->adj, new_value);
+	}
+	else if (event->direction == GDK_SCROLL_DOWN)	/* mouse wheel pageDn */
+	{
+		new_value = xtext->adj->value + (xtext->adj->page_increment / 10);
+		if (new_value > (xtext->adj->upper - xtext->adj->page_size))
+			new_value = xtext->adj->upper - xtext->adj->page_size;
+		gtk_adjustment_set_value (xtext->adj, new_value);
+	}
+
+	return FALSE;
+}
+
+static void
+gtk_xtext_class_init (GtkXTextClass * class)
+{
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkXTextClass *xtext_class;
+
+	object_class = (GtkObjectClass *) class;
+	widget_class = (GtkWidgetClass *) class;
+	xtext_class = (GtkXTextClass *) class;
+
+	parent_class = gtk_type_class (gtk_widget_get_type ());
+
+	xtext_signals[WORD_CLICK] =
+		g_signal_new ("word_click",
+							G_TYPE_FROM_CLASS (object_class),
+							G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+							G_STRUCT_OFFSET (GtkXTextClass, word_click),
+							NULL, NULL,
+							gtk_marshal_VOID__POINTER_POINTER,
+							G_TYPE_NONE,
+							2, G_TYPE_POINTER, G_TYPE_POINTER);
+	object_class->destroy = gtk_xtext_destroy;
+
+	widget_class->realize = gtk_xtext_realize;
+	widget_class->unrealize = gtk_xtext_unrealize;
+	widget_class->size_request = gtk_xtext_size_request;
+	widget_class->size_allocate = gtk_xtext_size_allocate;
+	widget_class->button_press_event = gtk_xtext_button_press;
+	widget_class->button_release_event = gtk_xtext_button_release;
+	widget_class->motion_notify_event = gtk_xtext_motion_notify;
+	widget_class->selection_clear_event = (void *)gtk_xtext_selection_kill;
+	widget_class->selection_get = gtk_xtext_selection_get;
+	widget_class->expose_event = gtk_xtext_expose;
+	widget_class->scroll_event = gtk_xtext_scroll;
+#ifdef MOTION_MONITOR
+	widget_class->leave_notify_event = gtk_xtext_leave_notify;
+#endif
+
+	xtext_class->word_click = NULL;
+}
+
+GType
+gtk_xtext_get_type (void)
+{
+	static GType xtext_type = 0;
+
+	if (!xtext_type)
+	{
+		static const GTypeInfo xtext_info =
+		{
+			sizeof (GtkXTextClass),
+			NULL,		/* base_init */
+			NULL,		/* base_finalize */
+			(GClassInitFunc) gtk_xtext_class_init,
+			NULL,		/* class_finalize */
+			NULL,		/* class_data */
+			sizeof (GtkXText),
+			0,		/* n_preallocs */
+			(GInstanceInitFunc) gtk_xtext_init,
+		};
+
+		xtext_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkXText",
+														 &xtext_info, 0);
+	}
+
+	return xtext_type;
+}
+
+/* strip MIRC colors and other attribs. */
+
+/* CL: needs to strip hidden when called by gtk_xtext_text_width, but not when copying text */
+
+static unsigned char *
+gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
+							  int *newlen, int *mb_ret, int strip_hidden)
+{
+	int i = 0;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	unsigned char *new_str;
+	int mb = FALSE;
+
+	if (outbuf == NULL)
+		new_str = malloc (len + 2);
+	else
+		new_str = outbuf;
+
+	while (len > 0)
+	{
+		if (*text >= 128)
+			mb = TRUE;
+
+		if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol)))
+		{
+			if (text[1] != ',') rcol--;
+			if (*text == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*text)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+				break;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				break;
+			case ATTR_HIDDEN:
+				hidden = !hidden;
+				break;
+			default:
+				if (!(hidden && strip_hidden))
+					new_str[i++] = *text;
+			}
+		}
+		text++;
+		len--;
+	}
+
+	new_str[i] = 0;
+
+	if (newlen != NULL)
+		*newlen = i;
+
+	if (mb_ret != NULL)
+		*mb_ret = mb;
+
+	return new_str;
+}
+
+/* GeEkMaN: converts mIRC control codes to literal control codes */
+
+static char *
+gtk_xtext_conv_color (unsigned char *text, int len, int *newlen)
+{
+	int i, j = 2;
+	char cchar = 0;
+	char *new_str;
+	int mbl;
+
+	for (i = 0; i < len;)
+	{
+		switch (text[i])
+		{
+		case ATTR_COLOR:
+		case ATTR_RESET:
+		case ATTR_REVERSE:
+		case ATTR_BOLD:
+		case ATTR_UNDERLINE:
+		case ATTR_ITALICS:
+		case ATTR_HIDDEN:
+			j += 3;
+			i++;
+			break;
+		default:
+			mbl = charlen (text + i);
+			j += mbl;
+			i += mbl;
+		}
+	}
+
+	new_str = malloc (j);
+	j = 0;
+
+	for (i = 0; i < len;)
+	{
+		switch (text[i])
+		{
+		case ATTR_COLOR:
+			cchar = 'C';
+			break;
+		case ATTR_RESET:
+			cchar = 'O';
+			break;
+		case ATTR_REVERSE:
+			cchar = 'R';
+			break;
+		case ATTR_BOLD:
+			cchar = 'B';
+			break;
+		case ATTR_UNDERLINE:
+			cchar = 'U';
+			break;
+		case ATTR_ITALICS:
+			cchar = 'I';
+			break;
+		case ATTR_HIDDEN:
+			cchar = 'H';
+			break;
+		case ATTR_BEEP:
+			break;
+		default:
+			mbl = charlen (text + i);
+			if (mbl == 1)
+			{
+				new_str[j] = text[i];
+				j++;
+				i++;
+			} else
+			{
+				/* invalid utf8 safe guard */
+				if (i + mbl > len)
+					mbl = len - i;
+				memcpy (new_str + j, text + i, mbl);
+				j += mbl;
+				i += mbl;
+			}
+		}
+		if (cchar != 0)
+		{
+			new_str[j++] = '%';
+			new_str[j++] = cchar;
+			cchar = 0;
+			i++;
+		}
+	}
+
+	new_str[j] = 0;
+	*newlen = j;
+
+	return new_str;
+}
+
+/* gives width of a string, excluding the mIRC codes */
+
+static int
+gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len,
+							 int *mb_ret)
+{
+	unsigned char *new_buf;
+	int new_len, mb;
+
+	new_buf = gtk_xtext_strip_color (text, len, xtext->scratch_buffer,
+												&new_len, &mb, !xtext->ignore_hidden);
+
+	if (mb_ret)
+		*mb_ret = mb;
+
+	return backend_get_text_width (xtext, new_buf, new_len, mb);
+}
+
+/* actually draw text to screen (one run with the same color/attribs) */
+
+static int
+gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str,
+								int len, GdkGC *gc, int is_mb)
+{
+	int str_width, dofill;
+	GdkDrawable *pix = NULL;
+	int dest_x, dest_y;
+
+	if (xtext->dont_render || len < 1 || xtext->hidden)
+		return 0;
+
+	str_width = backend_get_text_width (xtext, str, len, is_mb);
+
+	if (xtext->dont_render2)
+		return str_width;
+
+	/* roll-your-own clipping (avoiding XftDrawString is always good!) */
+	if (x > xtext->clip_x2 || x + str_width < xtext->clip_x)
+		return str_width;
+	if (y - xtext->font->ascent > xtext->clip_y2 || (y - xtext->font->ascent) + xtext->fontsize < xtext->clip_y)
+		return str_width;
+
+	if (xtext->render_hilights_only)
+	{
+		if (!xtext->in_hilight)	/* is it a hilight prefix? */
+			return str_width;
+#ifndef COLOR_HILIGHT
+		if (!xtext->un_hilight)	/* doing a hilight? no need to draw the text */
+			goto dounder;
+#endif
+	}
+
+#ifdef USE_DB
+#ifdef WIN32
+	if (!xtext->transparent)
+#endif
+	{
+		pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth);
+		if (pix)
+		{
+#ifdef USE_XFT
+			XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (pix));
+#endif
+			dest_x = x;
+			dest_y = y - xtext->font->ascent;
+
+			gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y);
+
+			x = 0;
+			y = xtext->font->ascent;
+			xtext->draw_buf = pix;
+		}
+	}
+#endif
+
+	dofill = TRUE;
+
+	/* backcolor is always handled by XDrawImageString */
+	if (!xtext->backcolor && xtext->pixmap)
+	{
+	/* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */
+		xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width,
+							xtext->fontsize);
+		dofill = FALSE;	/* already drawn the background */
+	}
+
+	backend_draw_text (xtext, dofill, gc, x, y, str, len, str_width, is_mb);
+
+#ifdef USE_DB
+	if (pix)
+	{
+		GdkRectangle clip;
+		GdkRectangle dest;
+
+		gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y);
+		xtext->draw_buf = GTK_WIDGET (xtext)->window;
+#ifdef USE_XFT
+		XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (xtext->draw_buf));
+#endif
+#if 0
+		gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, 0, 0, dest_x,
+								 dest_y, str_width, xtext->fontsize);
+#else
+		clip.x = xtext->clip_x;
+		clip.y = xtext->clip_y;
+		clip.width = xtext->clip_x2 - xtext->clip_x;
+		clip.height = xtext->clip_y2 - xtext->clip_y;
+
+		dest.x = dest_x;
+		dest.y = dest_y;
+		dest.width = str_width;
+		dest.height = xtext->fontsize;
+
+		if (gdk_rectangle_intersect (&clip, &dest, &dest))
+			/* dump the DB to window, but only within the clip_x/x2/y/y2 */
+			gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix,
+									 dest.x - dest_x, dest.y - dest_y,
+									 dest.x, dest.y, dest.width, dest.height);
+#endif
+		g_object_unref (pix);
+	}
+#endif
+
+	if (xtext->underline)
+	{
+#ifdef USE_XFT
+		GdkColor col;
+#endif
+
+#ifndef COLOR_HILIGHT
+dounder:
+#endif
+
+#ifdef USE_XFT
+		col.pixel = xtext->xft_fg->pixel;
+		gdk_gc_set_foreground (gc, &col);
+#endif
+		if (pix)
+			y = dest_y + xtext->font->ascent + 1;
+		else
+		{
+			y++;
+			dest_x = x;
+		}
+		/* draw directly to window, it's out of the range of our DB */
+		gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y);
+	}
+
+	return str_width;
+}
+
+static void
+gtk_xtext_reset (GtkXText * xtext, int mark, int attribs)
+{
+	if (attribs)
+	{
+		xtext->underline = FALSE;
+		xtext->bold = FALSE;
+		xtext->italics = FALSE;
+		xtext->hidden = FALSE;
+	}
+	if (!mark)
+	{
+		xtext->backcolor = FALSE;
+		if (xtext->col_fore != XTEXT_FG)
+			xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+		if (xtext->col_back != XTEXT_BG)
+			xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+	}
+	xtext->col_fore = XTEXT_FG;
+	xtext->col_back = XTEXT_BG;
+	xtext->parsing_color = FALSE;
+	xtext->parsing_backcolor = FALSE;
+	xtext->nc = 0;
+}
+
+/* render a single line, which WONT wrap, and parse mIRC colors */
+
+static int
+gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
+							 unsigned char *str, int len, int win_width, int indent,
+							 int line, int left_only, int *x_size_ret)
+{
+	GdkGC *gc;
+	int i = 0, x = indent, j = 0;
+	unsigned char *pstr = str;
+	int col_num, tmp;
+	int offset;
+	int mark = FALSE;
+	int ret = 1;
+
+	xtext->in_hilight = FALSE;
+
+	offset = str - ent->str;
+
+	if (line < 255 && line >= 0)
+		xtext->buffer->grid_offset[line] = offset;
+
+	gc = xtext->fgc;				  /* our foreground GC */
+
+	if (ent->mark_start != -1 &&
+		 ent->mark_start <= i + offset && ent->mark_end > i + offset)
+	{
+		xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+		xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+		xtext->backcolor = TRUE;
+		mark = TRUE;
+	}
+#ifdef MOTION_MONITOR
+	if (xtext->hilight_ent == ent &&
+		 xtext->hilight_start <= i + offset && xtext->hilight_end > i + offset)
+	{
+		if (!xtext->un_hilight)
+		{
+#ifdef COLOR_HILIGHT
+			xtext_set_bg (xtext, gc, 2);
+#else
+			xtext->underline = TRUE;
+#endif
+		}
+		xtext->in_hilight = TRUE;
+	}
+#endif
+
+	if (!xtext->skip_border_fills && !xtext->dont_render)
+	{
+		/* draw background to the left of the text */
+		if (str == ent->str && indent > MARGIN && xtext->buffer->time_stamp)
+		{
+			/* don't overwrite the timestamp */
+			if (indent > xtext->stamp_width)
+			{
+				xtext_draw_bg (xtext, xtext->stamp_width, y - xtext->font->ascent,
+									indent - xtext->stamp_width, xtext->fontsize);
+			}
+		} else
+		{
+			/* fill the indent area with background gc */
+			if (indent >= xtext->clip_x)
+			{
+				xtext_draw_bg (xtext, 0, y - xtext->font->ascent,
+									MIN (indent, xtext->clip_x2), xtext->fontsize);
+			}
+		}
+	}
+
+	if (xtext->jump_in_offset > 0 && offset < xtext->jump_in_offset)
+		xtext->dont_render2 = TRUE;
+
+	while (i < len)
+	{
+
+#ifdef MOTION_MONITOR
+		if (xtext->hilight_ent == ent && xtext->hilight_start == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			if (!xtext->un_hilight)
+			{
+#ifdef COLOR_HILIGHT
+				xtext_set_bg (xtext, gc, 2);
+#else
+				xtext->underline = TRUE;
+#endif
+			}
+
+			xtext->in_hilight = TRUE;
+		}
+#endif
+
+		if ((xtext->parsing_color && isdigit (str[i]) && xtext->nc < 2) ||
+			 (xtext->parsing_color && str[i] == ',' && isdigit (str[i+1]) && xtext->nc < 3 && !xtext->parsing_backcolor))
+		{
+			pstr++;
+			if (str[i] == ',')
+			{
+				xtext->parsing_backcolor = TRUE;
+				if (xtext->nc)
+				{
+					xtext->num[xtext->nc] = 0;
+					xtext->nc = 0;
+					col_num = atoi (xtext->num);
+					if (col_num == 99)	/* mIRC lameness */
+						col_num = XTEXT_FG;
+					else
+						col_num = col_num % XTEXT_MIRC_COLS;
+					xtext->col_fore = col_num;
+					if (!mark)
+						xtext_set_fg (xtext, gc, col_num);
+				}
+			} else
+			{
+				xtext->num[xtext->nc] = str[i];
+				if (xtext->nc < 7)
+					xtext->nc++;
+			}
+		} else
+		{
+			if (xtext->parsing_color)
+			{
+				xtext->parsing_color = FALSE;
+				if (xtext->nc)
+				{
+					xtext->num[xtext->nc] = 0;
+					xtext->nc = 0;
+					col_num = atoi (xtext->num);
+					if (xtext->parsing_backcolor)
+					{
+						if (col_num == 99)	/* mIRC lameness */
+							col_num = XTEXT_BG;
+						else
+							col_num = col_num % XTEXT_MIRC_COLS;
+						if (col_num == XTEXT_BG)
+							xtext->backcolor = FALSE;
+						else
+							xtext->backcolor = TRUE;
+						if (!mark)
+							xtext_set_bg (xtext, gc, col_num);
+						xtext->col_back = col_num;
+					} else
+					{
+						if (col_num == 99)	/* mIRC lameness */
+							col_num = XTEXT_FG;
+						else
+							col_num = col_num % XTEXT_MIRC_COLS;
+						if (!mark)
+							xtext_set_fg (xtext, gc, col_num);
+						xtext->col_fore = col_num;
+					}
+					xtext->parsing_backcolor = FALSE;
+				} else
+				{
+					/* got a \003<non-digit>... i.e. reset colors */
+					x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+					pstr += j;
+					j = 0;
+					gtk_xtext_reset (xtext, mark, FALSE);
+				}
+			}
+
+			switch (str[i])
+			{
+			case '\n':
+			/*case ATTR_BEEP:*/
+				break;
+			case ATTR_REVERSE:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j + 1;
+				j = 0;
+				tmp = xtext->col_fore;
+				xtext->col_fore = xtext->col_back;
+				xtext->col_back = tmp;
+				if (!mark)
+				{
+					xtext_set_fg (xtext, gc, xtext->col_fore);
+					xtext_set_bg (xtext, gc, xtext->col_back);
+				}
+				if (xtext->col_back != XTEXT_BG)
+					xtext->backcolor = TRUE;
+				else
+					xtext->backcolor = FALSE;
+				break;
+			case ATTR_BOLD:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->bold = !xtext->bold;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_UNDERLINE:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->underline = !xtext->underline;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_ITALICS:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->italics = !xtext->italics;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_HIDDEN:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->hidden = (!xtext->hidden) & (!xtext->ignore_hidden);
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_RESET:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j + 1;
+				j = 0;
+				gtk_xtext_reset (xtext, mark, !xtext->in_hilight);
+				break;
+			case ATTR_COLOR:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->parsing_color = TRUE;
+				pstr += j + 1;
+				j = 0;
+				break;
+			default:
+				tmp = charlen (str + i);
+				/* invalid utf8 safe guard */
+				if (tmp + i > len)
+					tmp = len - i;
+				j += tmp;	/* move to the next utf8 char */
+			}
+		}
+		i += charlen (str + i);	/* move to the next utf8 char */
+		/* invalid utf8 safe guard */
+		if (i > len)
+			i = len;
+
+		/* Separate the left part, the space and the right part
+		   into separate runs, and reset bidi state inbetween.
+		   Perform this only on the first line of the message.
+                */
+		if (offset == 0)
+		{
+			/* we've reached the end of the left part? */
+			if ((pstr-str)+j == ent->left_len)
+			{
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j;
+				j = 0;
+			}
+			else if ((pstr-str)+j == ent->left_len+1)
+			{
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j;
+				j = 0;
+			}
+		}
+
+		/* have we been told to stop rendering at this point? */
+		if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset))
+		{
+			gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			ret = 0;	/* skip the rest of the lines, we're done. */
+			j = 0;
+			break;
+		}
+
+		if (xtext->jump_in_offset > 0 && xtext->jump_in_offset == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext->dont_render2 = FALSE;
+		}
+
+#ifdef MOTION_MONITOR
+		if (xtext->hilight_ent == ent && xtext->hilight_end == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+#ifdef COLOR_HILIGHT
+			if (mark)
+			{
+				xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+				xtext->backcolor = TRUE;
+			} else
+			{
+				xtext_set_bg (xtext, gc, xtext->col_back);
+				if (xtext->col_back != XTEXT_BG)
+					xtext->backcolor = TRUE;
+				else
+					xtext->backcolor = FALSE;
+			}
+#else
+			xtext->underline = FALSE;
+#endif
+			xtext->in_hilight = FALSE;
+			if (xtext->render_hilights_only)
+			{
+				/* stop drawing this ent */
+				ret = 0;
+				break;
+			}
+		}
+#endif
+
+		if (!mark && ent->mark_start == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+			xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+			xtext->backcolor = TRUE;
+			mark = TRUE;
+		}
+
+		if (mark && ent->mark_end == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext_set_bg (xtext, gc, xtext->col_back);
+			xtext_set_fg (xtext, gc, xtext->col_fore);
+			if (xtext->col_back != XTEXT_BG)
+				xtext->backcolor = TRUE;
+			else
+				xtext->backcolor = FALSE;
+			mark = FALSE;
+		}
+
+	}
+
+	if (j)
+		x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+
+	if (mark)
+	{
+		xtext_set_bg (xtext, gc, xtext->col_back);
+		xtext_set_fg (xtext, gc, xtext->col_fore);
+		if (xtext->col_back != XTEXT_BG)
+			xtext->backcolor = TRUE;
+		else
+			xtext->backcolor = FALSE;
+	}
+
+	/* draw background to the right of the text */
+	if (!left_only && !xtext->dont_render)
+	{
+		/* draw separator now so it doesn't appear to flicker */
+		gtk_xtext_draw_sep (xtext, y - xtext->font->ascent);
+		if (!xtext->skip_border_fills && xtext->clip_x2 >= x)
+		{
+			int xx = MAX (x, xtext->clip_x);
+
+			xtext_draw_bg (xtext,
+								xx,	/* x */
+								y - xtext->font->ascent, /* y */
+				MIN (xtext->clip_x2 - xx, (win_width + MARGIN) - xx), /* width */
+								xtext->fontsize);		/* height */
+		}
+	}
+
+	xtext->dont_render2 = FALSE;
+
+	/* return how much we drew in the x direction */
+	if (x_size_ret)
+		*x_size_ret = x - indent;
+
+	return ret;
+}
+
+#ifdef USE_XLIB
+
+/* get the desktop/root window */
+
+static Window desktop_window = None;
+
+static Window
+get_desktop_window (Display *xdisplay, Window the_window)
+{
+	Atom prop, type;
+	int format;
+	unsigned long length, after;
+	unsigned char *data;
+	unsigned int nchildren;
+	Window w, root, *children, parent;
+
+	prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True);
+	if (prop == None)
+	{
+		prop = XInternAtom (xdisplay, "_XROOTCOLOR_PIXEL", True);
+		if (prop == None)
+			return None;
+	}
+
+	for (w = the_window; w; w = parent)
+	{
+		if ((XQueryTree (xdisplay, w, &root, &parent, &children,
+				&nchildren)) == False)
+			return None;
+
+		if (nchildren)
+			XFree (children);
+
+		XGetWindowProperty (xdisplay, w, prop, 0L, 1L, False,
+								  AnyPropertyType, &type, &format, &length, &after,
+								  &data);
+		if (data)
+			XFree (data);
+
+		if (type != None)
+			return (desktop_window = w);
+	}
+
+	return (desktop_window = None);
+}
+
+/* find the root window (backdrop) Pixmap */
+
+static Pixmap
+get_pixmap_prop (Display *xdisplay, Window the_window)
+{
+	Atom type;
+	int format;
+	unsigned long length, after;
+	unsigned char *data;
+	Pixmap pix = None;
+	static Atom prop = None;
+
+	if (desktop_window == None)
+		desktop_window = get_desktop_window (xdisplay, the_window);
+	if (desktop_window == None)
+		desktop_window = DefaultRootWindow (xdisplay);
+
+	if (prop == None)
+		prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True);
+	if (prop == None)
+		return None;
+
+	XGetWindowProperty (xdisplay, desktop_window, prop, 0L, 1L, False,
+							  AnyPropertyType, &type, &format, &length, &after,
+							  &data);
+	if (data)
+	{
+		if (type == XA_PIXMAP)
+			pix = *((Pixmap *) data);
+
+		XFree (data);
+	}
+
+	return pix;
+}
+
+/* slow generic routine, for the depths/bpp we don't know about */
+
+static void
+shade_ximage_generic (GdkVisual *visual, XImage *ximg, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	int x, y;
+	int bgr = (256 - rm) * (bg & visual->red_mask);
+	int bgg = (256 - gm) * (bg & visual->green_mask);
+	int bgb = (256 - bm) * (bg & visual->blue_mask);
+
+	for (x = 0; x < w; x++)
+	{
+		for (y = 0; y < h; y++)
+		{
+			unsigned long pixel = XGetPixel (ximg, x, y);
+			int r, g, b;
+
+			r = rm * (pixel & visual->red_mask) + bgr;
+			g = gm * (pixel & visual->green_mask) + bgg;
+			b = bm * (pixel & visual->blue_mask) + bgb;
+
+			XPutPixel (ximg, x, y,
+							((r >> 8) & visual->red_mask) |
+							((g >> 8) & visual->green_mask) |
+							((b >> 8) & visual->blue_mask));
+		}
+	}
+}
+
+#endif
+
+/* Fast shading routine. Based on code by Willem Monsuwe <willem@stack.nl> */
+
+#define SHADE_IMAGE(bytes, type, rmask, gmask, bmask) \
+	unsigned char *ptr; \
+	int x, y; \
+	int bgr = (256 - rm) * (bg & rmask); \
+	int bgg = (256 - gm) * (bg & gmask); \
+	int bgb = (256 - bm) * (bg & bmask); \
+	ptr = (unsigned char *) data + (w * bytes); \
+	for (y = h; --y >= 0;) \
+	{ \
+		for (x = -w; x < 0; x++) \
+		{ \
+			int r, g, b; \
+			b = ((type *) ptr)[x]; \
+			r = rm * (b & rmask) + bgr; \
+			g = gm * (b & gmask) + bgg; \
+			b = bm * (b & bmask) + bgb; \
+			((type *) ptr)[x] = ((r >> 8) & rmask) \
+										| ((g >> 8) & gmask) \
+										| ((b >> 8) & bmask); \
+		} \
+		ptr += bpl; \
+    }
+
+/* RGB 15 */
+static void
+shade_ximage_15 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (2, guint16, 0x7c00, 0x3e0, 0x1f);
+}
+
+/* RGB 16 */
+static void
+shade_ximage_16 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (2, guint16, 0xf800, 0x7e0, 0x1f);
+}
+
+/* RGB 24 */
+static void
+shade_ximage_24 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	/* 24 has to be a special case, there's no guint24, or 24bit MOV :) */
+	unsigned char *ptr;
+	int x, y;
+	int bgr = (256 - rm) * ((bg & 0xff0000) >> 16);
+	int bgg = (256 - gm) * ((bg & 0xff00) >> 8);
+	int bgb = (256 - bm) * (bg & 0xff);
+
+	ptr = (unsigned char *) data + (w * 3);
+	for (y = h; --y >= 0;)
+	{
+		for (x = -(w * 3); x < 0; x += 3)
+		{
+			int r, g, b;
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+			r = (ptr[x + 0] * rm + bgr) >> 8;
+			g = (ptr[x + 1] * gm + bgg) >> 8;
+			b = (ptr[x + 2] * bm + bgb) >> 8;
+			ptr[x + 0] = r;
+			ptr[x + 1] = g;
+			ptr[x + 2] = b;
+#else
+			r = (ptr[x + 2] * rm + bgr) >> 8;
+			g = (ptr[x + 1] * gm + bgg) >> 8;
+			b = (ptr[x + 0] * bm + bgb) >> 8;
+			ptr[x + 2] = r;
+			ptr[x + 1] = g;
+			ptr[x + 0] = b;
+#endif
+		}
+		ptr += bpl;
+	}
+}
+
+/* RGB 32 */
+static void
+shade_ximage_32 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (4, guint32, 0xff0000, 0xff00, 0xff);
+}
+
+static void
+shade_image (GdkVisual *visual, void *data, int bpl, int bpp, int w, int h,
+				 int rm, int gm, int bm, int bg, int depth)
+{
+	int bg_r, bg_g, bg_b;
+
+	bg_r = bg & visual->red_mask;
+	bg_g = bg & visual->green_mask;
+	bg_b = bg & visual->blue_mask;
+
+#ifdef USE_MMX
+	/* the MMX routines are about 50% faster at 16-bit. */
+	/* only use MMX routines with a pure black background */
+	if (bg_r == 0 && bg_g == 0 && bg_b == 0 && have_mmx ())	/* do a runtime check too! */
+	{
+		switch (depth)
+		{
+		case 15:
+			shade_ximage_15_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		case 16:
+			shade_ximage_16_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		case 24:
+			if (bpp != 32)
+				goto generic;
+		case 32:
+			shade_ximage_32_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		default:
+			goto generic;
+		}
+	} else
+	{
+generic:
+#endif
+		switch (depth)
+		{
+		case 15:
+			shade_ximage_15 (data, bpl, w, h, rm, gm, bm, bg);
+			break;
+		case 16:
+			shade_ximage_16 (data, bpl, w, h, rm, gm, bm, bg);
+			break;
+		case 24:
+			if (bpp != 32)
+			{
+				shade_ximage_24 (data, bpl, w, h, rm, gm, bm, bg);
+				break;
+			}
+		case 32:
+			shade_ximage_32 (data, bpl, w, h, rm, gm, bm, bg);
+		}
+#ifdef USE_MMX
+	}
+#endif
+}
+
+#ifdef USE_XLIB
+
+#ifdef USE_SHM
+
+static XImage *
+get_shm_image (Display *xdisplay, XShmSegmentInfo *shminfo, int x, int y,
+					int w, int h, int depth, Pixmap pix)
+{
+	XImage *ximg;
+
+	shminfo->shmid = -1;
+	shminfo->shmaddr = (char*) -1;
+	ximg = XShmCreateImage (xdisplay, 0, depth, ZPixmap, 0, shminfo, w, h);
+	if (!ximg)
+		return NULL;
+
+	shminfo->shmid = shmget (IPC_PRIVATE, ximg->bytes_per_line * ximg->height,
+									 IPC_CREAT|0600);
+	if (shminfo->shmid == -1)
+	{
+		XDestroyImage (ximg);
+		return NULL;
+	}
+
+	shminfo->readOnly = False;
+	ximg->data = shminfo->shmaddr = (char *)shmat (shminfo->shmid, 0, 0);
+	if (shminfo->shmaddr == ((char *)-1))
+	{
+		shmctl (shminfo->shmid, IPC_RMID, 0);
+		XDestroyImage (ximg);
+		return NULL;
+	}
+
+	XShmAttach (xdisplay, shminfo);
+	XSync (xdisplay, False);
+	shmctl (shminfo->shmid, IPC_RMID, 0);
+	XShmGetImage (xdisplay, pix, ximg, x, y, AllPlanes);
+
+	return ximg;
+}
+
+static XImage *
+get_image (GtkXText *xtext, Display *xdisplay, XShmSegmentInfo *shminfo,
+			  int x, int y, int w, int h, int depth, Pixmap pix)
+{
+	XImage *ximg;
+
+	xtext->shm = 1;
+	ximg = get_shm_image (xdisplay, shminfo, x, y, w, h, depth, pix);
+	if (!ximg)
+	{
+		xtext->shm = 0;
+		ximg = XGetImage (xdisplay, pix, x, y, w, h, -1, ZPixmap);
+	}
+
+	return ximg;
+}
+
+#endif
+
+static GdkPixmap *
+shade_pixmap (GtkXText * xtext, Pixmap p, int x, int y, int w, int h)
+{
+	unsigned int dummy, width, height, depth;
+	GdkPixmap *shaded_pix;
+	Window root;
+	Pixmap tmp;
+	XImage *ximg;
+	XGCValues gcv;
+	GC tgc;
+	Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);
+
+#ifdef USE_SHM
+	int shm_pixmaps;
+	shm_pixmaps = have_shm_pixmaps(xdisplay);
+#endif
+
+	XGetGeometry (xdisplay, p, &root, &dummy, &dummy, &width, &height,
+					  &dummy, &depth);
+
+	if (width < x + w || height < y + h || x < 0 || y < 0)
+	{
+		gcv.subwindow_mode = IncludeInferiors;
+		gcv.graphics_exposures = False;
+		tgc = XCreateGC (xdisplay, p, GCGraphicsExposures|GCSubwindowMode,
+							  &gcv);
+		tmp = XCreatePixmap (xdisplay, p, w, h, depth);
+		XSetTile (xdisplay, tgc, p);
+		XSetFillStyle (xdisplay, tgc, FillTiled);
+		XSetTSOrigin (xdisplay, tgc, -x, -y);
+		XFillRectangle (xdisplay, tmp, tgc, 0, 0, w, h);
+		XFreeGC (xdisplay, tgc);
+
+#ifdef USE_SHM
+		if (shm_pixmaps)
+			ximg = get_image (xtext, xdisplay, &xtext->shminfo, 0, 0, w, h, depth, tmp);
+		else
+#endif
+			ximg = XGetImage (xdisplay, tmp, 0, 0, w, h, -1, ZPixmap);
+		XFreePixmap (xdisplay, tmp);
+	} else
+	{
+#ifdef USE_SHM
+		if (shm_pixmaps)
+			ximg = get_image (xtext, xdisplay, &xtext->shminfo, x, y, w, h, depth, p);
+		else
+#endif
+			ximg = XGetImage (xdisplay, p, x, y, w, h, -1, ZPixmap);
+	}
+
+	if (!ximg)
+		return NULL;
+
+	if (depth <= 14)
+	{
+		shade_ximage_generic (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window),
+									 ximg, ximg->bytes_per_line, w, h, xtext->tint_red,
+									 xtext->tint_green, xtext->tint_blue,
+									 xtext->palette[XTEXT_BG]);
+	} else
+	{
+		shade_image (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window),
+						 ximg->data, ximg->bytes_per_line, ximg->bits_per_pixel,
+						 w, h, xtext->tint_red, xtext->tint_green, xtext->tint_blue,
+						 xtext->palette[XTEXT_BG], depth);
+	}
+
+	if (xtext->recycle)
+		shaded_pix = xtext->pixmap;
+	else
+	{
+#ifdef USE_SHM
+		if (xtext->shm && shm_pixmaps)
+		{
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+			shaded_pix = gdk_pixmap_foreign_new (
+				XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth));
+#else
+			shaded_pix = gdk_pixmap_foreign_new_for_display (
+				gdk_drawable_get_display (xtext->draw_buf),
+				XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth));
+#endif
+		} else
+#endif
+		{
+			shaded_pix = gdk_pixmap_new (GTK_WIDGET (xtext)->window, w, h, depth);
+		}
+	}
+
+#ifdef USE_SHM
+	if (!xtext->shm || !shm_pixmaps)
+#endif
+		XPutImage (xdisplay, GDK_WINDOW_XWINDOW (shaded_pix),
+					  GDK_GC_XGC (xtext->fgc), ximg, 0, 0, 0, 0, w, h);
+	XDestroyImage (ximg);
+
+	return shaded_pix;
+}
+
+#endif /* !USE_XLIB */
+
+/* free transparency xtext->pixmap */
+#if defined(USE_XLIB) || defined(WIN32)
+
+static void
+gtk_xtext_free_trans (GtkXText * xtext)
+{
+	if (xtext->pixmap)
+	{
+#ifdef USE_SHM
+		if (xtext->shm && have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf)))
+		{
+			XFreePixmap (GDK_WINDOW_XDISPLAY (xtext->pixmap),
+							 GDK_WINDOW_XWINDOW (xtext->pixmap));
+			XShmDetach (GDK_WINDOW_XDISPLAY (xtext->draw_buf), &xtext->shminfo);
+			shmdt (xtext->shminfo.shmaddr);
+		}
+#endif
+		g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+		xtext->shm = 0;
+	}
+}
+
+#endif
+
+#ifdef WIN32
+
+static GdkPixmap *
+win32_tint (GtkXText *xtext, GdkImage *img, int width, int height)
+{
+	guchar *pixelp;
+	int x, y;
+	GdkPixmap *pix;
+	GdkVisual *visual = gdk_drawable_get_visual (GTK_WIDGET (xtext)->window);
+	guint32 pixel;
+	int r, g, b;
+
+	if (img->depth <= 14)
+	{
+		/* slow generic routine */
+		for (y = 0; y < height; y++)
+		{
+			for (x = 0; x < width; x++)
+			{
+				if (img->depth == 1)
+				{
+					pixel = (((guchar *) img->mem)[y * img->bpl + (x >> 3)] & (1 << (7 - (x & 0x7)))) != 0;
+					goto here;
+				}
+
+				if (img->depth == 4)
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1);
+					if (x&1)
+					{
+						pixel = (*pixelp) & 0x0F;
+						goto here;
+					}
+
+					pixel = (*pixelp) >> 4;
+					goto here;
+				}
+
+				pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp;
+
+				switch (img->bpp)
+				{
+				case 1:
+					pixel = *pixelp; break;
+
+				/* Windows is always LSB, no need to check img->byte_order. */
+				case 2:
+					pixel = pixelp[0] | (pixelp[1] << 8); break;
+
+				case 3:
+					pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break;
+
+				case 4:
+					pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break;
+				}
+
+here:
+				r = (pixel & visual->red_mask) >> visual->red_shift;
+				g = (pixel & visual->green_mask) >> visual->green_shift;
+				b = (pixel & visual->blue_mask) >> visual->blue_shift;
+
+				/* actual tinting is only these 3 lines */
+				pixel = ((r * xtext->tint_red) >> 8) << visual->red_shift |
+							((g * xtext->tint_green) >> 8) << visual->green_shift |
+							((b * xtext->tint_blue) >> 8) << visual->blue_shift;
+
+				if (img->depth == 1)
+					if (pixel & 1)
+						((guchar *) img->mem)[y * img->bpl + (x >> 3)] |= (1 << (7 - (x & 0x7)));
+					else
+						((guchar *) img->mem)[y * img->bpl + (x >> 3)] &= ~(1 << (7 - (x & 0x7)));
+				else if (img->depth == 4)
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1);
+
+					if (x&1)
+					{
+						*pixelp &= 0xF0;
+						*pixelp |= (pixel & 0x0F);
+					} else
+					{
+						*pixelp &= 0x0F;
+						*pixelp |= (pixel << 4);
+					}
+				} else
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp;
+
+					/* Windows is always LSB, no need to check img->byte_order. */
+					switch (img->bpp)
+					{
+					case 4:
+						pixelp[3] = 0;
+					case 3:
+						pixelp[2] = ((pixel >> 16) & 0xFF);
+					case 2:
+						pixelp[1] = ((pixel >> 8) & 0xFF);
+					case 1:
+						pixelp[0] = (pixel & 0xFF);
+					}
+				}
+			}
+		}
+	} else
+	{
+		shade_image (visual, img->mem, img->bpl, img->bpp, width, height,
+						 xtext->tint_red, xtext->tint_green, xtext->tint_blue,
+						 xtext->palette[XTEXT_BG], visual->depth);
+	}
+
+	/* no need to dump it to a Pixmap, it's one and the same on win32 */
+	pix = (GdkPixmap *)img;
+
+	return pix;
+}
+
+#endif /* !WIN32 */
+
+/* grab pixmap from root window and set xtext->pixmap */
+#if defined(USE_XLIB) || defined(WIN32)
+
+static void
+gtk_xtext_load_trans (GtkXText * xtext)
+{
+#ifdef WIN32
+	GdkImage *img;
+	int width, height;
+	HDC hdc;
+	HWND hwnd;
+
+	/* if not shaded, we paint directly with PaintDesktop() */
+	if (!xtext->shaded)
+		return;
+
+	hwnd = GDK_WINDOW_HWND (GTK_WIDGET (xtext)->window);
+	hdc = GetDC (hwnd);
+	PaintDesktop (hdc);
+	ReleaseDC (hwnd, hdc);
+
+	gdk_window_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+	img = gdk_image_get (GTK_WIDGET (xtext)->window, 0, 0, width+128, height);
+	xtext->pixmap = win32_tint (xtext, img, img->width, img->height);
+
+#else
+
+	Pixmap rootpix;
+	GtkWidget *widget = GTK_WIDGET (xtext);
+	int x, y;
+
+	rootpix = get_pixmap_prop (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XWINDOW (widget->window));
+	if (rootpix == None)
+	{
+		if (xtext->error_function)
+			xtext->error_function (0);
+		xtext->transparent = FALSE;
+		return;
+	}
+
+	gdk_window_get_origin (widget->window, &x, &y);
+
+	if (xtext->shaded)
+	{
+		int width, height;
+		gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+		xtext->pixmap = shade_pixmap (xtext, rootpix, x, y, width+105, height);
+		if (xtext->pixmap == NULL)
+		{
+			xtext->shaded = 0;
+			goto noshade;
+		}
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+		xtext->ts_x = xtext->ts_y = 0;
+	} else
+	{
+noshade:
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		xtext->pixmap = gdk_pixmap_foreign_new (rootpix);
+#else
+		xtext->pixmap = gdk_pixmap_foreign_new_for_display (gdk_drawable_get_display (GTK_WIDGET (xtext)->window), rootpix);
+#endif
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, -x, -y);
+		xtext->ts_x = -x;
+		xtext->ts_y = -y;
+	}
+	gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+#endif /* !WIN32 */
+}
+
+#endif /* ! XLIB || WIN32 */
+
+/* walk through str until this line doesn't fit anymore */
+
+static int
+find_next_wrap (GtkXText * xtext, textentry * ent, unsigned char *str,
+					 int win_width, int indent)
+{
+	unsigned char *last_space = str;
+	unsigned char *orig_str = str;
+	int str_width = indent;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	int mbl;
+	int char_width;
+	int ret;
+	int limit_offset = 0;
+
+	/* single liners */
+	if (win_width >= ent->str_width + ent->indent)
+		return ent->str_len;
+
+	/* it does happen! */
+	if (win_width < 1)
+	{
+		ret = ent->str_len - (str - ent->str);
+		goto done;
+	}
+
+	while (1)
+	{
+		if (rcol > 0 && (isdigit (*str) || (*str == ',' && isdigit (str[1]) && !bgcol)))
+		{
+			if (str[1] != ',') rcol--;
+			if (*str == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+			limit_offset++;
+			str++;
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*str)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				limit_offset++;
+				str++;
+				break;
+			case ATTR_HIDDEN:
+				if (xtext->ignore_hidden)
+					goto def;
+				hidden = !hidden;
+				limit_offset++;
+				str++;
+				break;
+			default:
+			def:
+				char_width = backend_get_char_width (xtext, str, &mbl);
+				if (!hidden) str_width += char_width;
+				if (str_width > win_width)
+				{
+					if (xtext->wordwrap)
+					{
+						if (str - last_space > WORDWRAP_LIMIT + limit_offset)
+							ret = str - orig_str; /* fall back to character wrap */
+						else
+						{
+							if (*last_space == ' ')
+								last_space++;
+							ret = last_space - orig_str;
+							if (ret == 0) /* fall back to character wrap */
+								ret = str - orig_str;
+						}
+						goto done;
+					}
+					ret = str - orig_str;
+					goto done;
+				}
+
+				/* keep a record of the last space, for wordwrapping */
+				if (is_del (*str))
+				{
+					last_space = str;
+					limit_offset = 0;
+				}
+
+				/* progress to the next char */
+				str += mbl;
+
+			}
+		}
+
+		if (str >= ent->str + ent->str_len)
+		{
+			ret = str - orig_str;
+			goto done;
+		}
+	}
+
+done:
+
+	/* must make progress */
+	if (ret < 1)
+		ret = 1;
+
+	return ret;
+}
+
+/* find the offset, in bytes, that wrap number 'line' starts at */
+
+static int
+gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line)
+{
+	int win_width;
+	unsigned char *str;
+	int indent, str_pos, line_pos, len;
+
+	if (ent->lines_taken < 2 || line < 1)
+		return 0;
+
+	/* we record the first 4 lines' wraps, so take a shortcut */
+	if (line <= RECORD_WRAPS)
+		return ent->wrap_offset[line - 1];
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &win_width, 0);
+	win_width -= MARGIN;
+
+/*	indent = ent->indent;
+	str = ent->str;
+	line_pos = str_pos = 0;*/
+
+	/* start from the last recorded wrap, and move forward */
+	indent = xtext->buffer->indent;
+	str_pos = ent->wrap_offset[RECORD_WRAPS-1];
+	str = str_pos + ent->str;
+	line_pos = RECORD_WRAPS;
+
+	do
+	{
+		len = find_next_wrap (xtext, ent, str, win_width, indent);
+		indent = xtext->buffer->indent;
+		str += len;
+		str_pos += len;
+		line_pos++;
+		if (line_pos >= line)
+			return str_pos;
+	}
+	while (str < ent->str + ent->str_len);
+
+	return 0;
+}
+
+/* horrible hack for drawing time stamps */
+
+static void
+gtk_xtext_render_stamp (GtkXText * xtext, textentry * ent,
+								char *text, int len, int line, int win_width)
+{
+	textentry tmp_ent;
+	int jo, ji, hs;
+	int xsize, y;
+
+	/* trashing ent here, so make a backup first */
+	memcpy (&tmp_ent, ent, sizeof (tmp_ent));
+	ent->mb = TRUE;	/* make non-english days of the week work */
+	jo = xtext->jump_out_offset;	/* back these up */
+	ji = xtext->jump_in_offset;
+	hs = xtext->hilight_start;
+	xtext->jump_out_offset = 0;
+	xtext->jump_in_offset = 0;
+	xtext->hilight_start = 0xffff;	/* temp disable */
+
+	if (xtext->mark_stamp)
+	{
+		/* if this line is marked, mark this stamp too */
+		if (ent->mark_start == 0)	
+		{
+			ent->mark_start = 0;
+			ent->mark_end = len;
+		}
+		else
+		{
+			ent->mark_start = -1;
+			ent->mark_end = -1;
+		}
+		ent->str = text;
+	}
+
+	y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset;
+	gtk_xtext_render_str (xtext, y, ent, text, len,
+								 win_width, 2, line, TRUE, &xsize);
+
+	/* restore everything back to how it was */
+	memcpy (ent, &tmp_ent, sizeof (tmp_ent));
+	xtext->jump_out_offset = jo;
+	xtext->jump_in_offset = ji;
+	xtext->hilight_start = hs;
+
+	/* with a non-fixed-width font, sometimes we don't draw enough
+		background i.e. when this stamp is shorter than xtext->stamp_width */
+	xsize += MARGIN;
+	if (xsize < xtext->stamp_width)
+	{
+		y -= xtext->font->ascent;
+		xtext_draw_bg (xtext,
+							xsize,	/* x */
+							y,			/* y */
+							xtext->stamp_width - xsize,	/* width */
+							xtext->fontsize					/* height */);
+	}
+}
+
+/* render a single line, which may wrap to more lines */
+
+static int
+gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
+							  int lines_max, int subline, int win_width)
+{
+	unsigned char *str;
+	int indent, taken, entline, len, y, start_subline;
+
+	entline = taken = 0;
+	str = ent->str;
+	indent = ent->indent;
+	start_subline = subline;
+
+#ifdef XCHAT
+	/* draw the timestamp */
+	if (xtext->auto_indent && xtext->buffer->time_stamp &&
+		 (!xtext->skip_stamp || xtext->mark_stamp || xtext->force_stamp))
+	{
+		char *time_str;
+		int len;
+
+		len = xtext_get_stamp_str (ent->stamp, &time_str);
+		gtk_xtext_render_stamp (xtext, ent, time_str, len, line, win_width);
+		g_free (time_str);
+	}
+#endif
+
+	/* draw each line one by one */
+	do
+	{
+		/* if it's one of the first 4 wraps, we don't need to calculate it, it's
+			recorded in ->wrap_offset. This saves us a loop. */
+		if (entline < RECORD_WRAPS)
+		{
+			if (ent->lines_taken < 2)
+				len = ent->str_len;
+			else
+			{
+				if (entline > 0)
+					len = ent->wrap_offset[entline] - ent->wrap_offset[entline-1];
+				else
+					len = ent->wrap_offset[0];
+			}
+		} else
+			len = find_next_wrap (xtext, ent, str, win_width, indent);
+
+		entline++;
+
+		y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset;
+		if (!subline)
+		{
+			if (!gtk_xtext_render_str (xtext, y, ent, str, len, win_width,
+												indent, line, FALSE, NULL))
+			{
+				/* small optimization */
+				gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline + 1));
+				return ent->lines_taken - subline;
+			}
+		} else
+		{
+			xtext->dont_render = TRUE;
+			gtk_xtext_render_str (xtext, y, ent, str, len, win_width,
+										 indent, line, FALSE, NULL);
+			xtext->dont_render = FALSE;
+			subline--;
+			line--;
+			taken--;
+		}
+
+		indent = xtext->buffer->indent;
+		line++;
+		taken++;
+		str += len;
+
+		if (line >= lines_max)
+			break;
+
+	}
+	while (str < ent->str + ent->str_len);
+
+	gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline));
+
+	return taken;
+}
+
+void
+gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[])
+{
+	int i;
+	GdkColor col;
+
+	for (i = (XTEXT_COLS-1); i >= 0; i--)
+	{
+#ifdef USE_XFT
+		xtext->color[i].color.red = palette[i].red;
+		xtext->color[i].color.green = palette[i].green;
+		xtext->color[i].color.blue = palette[i].blue;
+		xtext->color[i].color.alpha = 0xffff;
+		xtext->color[i].pixel = palette[i].pixel;
+#endif
+		xtext->palette[i] = palette[i].pixel;
+	}
+
+	if (GTK_WIDGET_REALIZED (xtext))
+	{
+		xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+		xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+		xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+
+		col.pixel = xtext->palette[XTEXT_MARKER];
+		gdk_gc_set_foreground (xtext->marker_gc, &col);
+	}
+	xtext->col_fore = XTEXT_FG;
+	xtext->col_back = XTEXT_BG;
+}
+
+static void
+gtk_xtext_fix_indent (xtext_buffer *buf)
+{
+	int j;
+
+	/* make indent a multiple of the space width */
+	if (buf->indent && buf->xtext->space_width)
+	{
+		j = 0;
+		while (j < buf->indent)
+		{
+			j += buf->xtext->space_width;
+		}
+		buf->indent = j;
+	}
+
+	dontscroll (buf);	/* force scrolling off */
+}
+
+static void
+gtk_xtext_recalc_widths (xtext_buffer *buf, int do_str_width)
+{
+	textentry *ent;
+
+	/* since we have a new font, we have to recalc the text widths */
+	ent = buf->text_first;
+	while (ent)
+	{
+		if (do_str_width)
+		{
+			ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str,
+														ent->str_len, NULL);
+		}
+		if (ent->left_len != -1)
+		{
+			ent->indent =
+				(buf->indent -
+				 gtk_xtext_text_width (buf->xtext, ent->str,
+										ent->left_len, NULL)) - buf->xtext->space_width;
+			if (ent->indent < MARGIN)
+				ent->indent = MARGIN;
+		}
+		ent = ent->next;
+	}
+
+	gtk_xtext_calc_lines (buf, FALSE);
+}
+
+int
+gtk_xtext_set_font (GtkXText *xtext, char *name)
+{
+	int i;
+	unsigned char c;
+
+	if (xtext->font)
+		backend_font_close (xtext);
+
+	/* realize now, so that font_open has a XDisplay */
+	gtk_widget_realize (GTK_WIDGET (xtext));
+
+	backend_font_open (xtext, name);
+	if (xtext->font == NULL)
+		return FALSE;
+
+	/* measure the width of every char;  only the ASCII ones for XFT */
+	for (i = 0; i < sizeof(xtext->fontwidth)/sizeof(xtext->fontwidth[0]); i++)
+	{
+		c = i;
+		xtext->fontwidth[i] = backend_get_text_width (xtext, &c, 1, TRUE);
+	}
+	xtext->space_width = xtext->fontwidth[' '];
+	xtext->fontsize = xtext->font->ascent + xtext->font->descent;
+
+#ifdef XCHAT
+	{
+		char *time_str;
+		int stamp_size = xtext_get_stamp_str (time(0), &time_str);
+		xtext->stamp_width =
+			gtk_xtext_text_width (xtext, time_str, stamp_size, NULL) + MARGIN;
+		g_free (time_str);
+	}
+#endif
+
+	gtk_xtext_fix_indent (xtext->buffer);
+
+	if (GTK_WIDGET_REALIZED (xtext))
+		gtk_xtext_recalc_widths (xtext->buffer, TRUE);
+
+	return TRUE;
+}
+
+void
+gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans)
+{
+	GdkGCValues val;
+	gboolean shaded = FALSE;
+
+	if (trans && (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255))
+		shaded = TRUE;
+
+#if !defined(USE_XLIB) && !defined(WIN32)
+	shaded = FALSE;
+	trans = FALSE;
+#endif
+
+	if (xtext->pixmap)
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent)
+			gtk_xtext_free_trans (xtext);
+		else
+#endif
+			g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+	}
+
+	xtext->transparent = trans;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (trans)
+	{
+		xtext->shaded = shaded;
+		if (GTK_WIDGET_REALIZED (xtext))
+			gtk_xtext_load_trans (xtext);
+		return;
+	}
+#endif
+
+	dontscroll (xtext->buffer);
+	xtext->pixmap = pixmap;
+
+	if (pixmap != 0)
+	{
+		g_object_ref (pixmap);
+		if (GTK_WIDGET_REALIZED (xtext))
+		{
+			gdk_gc_set_tile (xtext->bgc, pixmap);
+			gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+			xtext->ts_x = xtext->ts_y = 0;
+			gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+		}
+	} else if (GTK_WIDGET_REALIZED (xtext))
+	{
+		g_object_unref (xtext->bgc);
+		val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+		val.graphics_exposures = 0;
+		xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window,
+								&val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+		xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+	}
+}
+
+void
+gtk_xtext_save (GtkXText * xtext, int fh)
+{
+	textentry *ent;
+	int newlen;
+	char *buf;
+
+	ent = xtext->buffer->text_first;
+	while (ent)
+	{
+		buf = gtk_xtext_strip_color (ent->str, ent->str_len, NULL,
+											  &newlen, NULL, FALSE);
+		write (fh, buf, newlen);
+		write (fh, "\n", 1);
+		free (buf);
+		ent = ent->next;
+	}
+}
+
+/* count how many lines 'ent' will take (with wraps) */
+
+static int
+gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
+{
+	unsigned char *str;
+	int indent, taken, len;
+	int win_width;
+
+	win_width = buf->window_width - MARGIN;
+
+	if (ent->str_width + ent->indent < win_width)
+		return 1;
+
+	indent = ent->indent;
+	str = ent->str;
+	taken = 0;
+
+	do
+	{
+		len = find_next_wrap (buf->xtext, ent, str, win_width, indent);
+		if (taken < RECORD_WRAPS)
+			ent->wrap_offset[taken] = (str + len) - ent->str;
+		indent = buf->indent;
+		taken++;
+		str += len;
+	}
+	while (str < ent->str + ent->str_len);
+
+	return taken;
+}
+
+/* Calculate number of actual lines (with wraps), to set adj->lower. *
+ * This should only be called when the window resizes.               */
+
+static void
+gtk_xtext_calc_lines (xtext_buffer *buf, int fire_signal)
+{
+	textentry *ent;
+	int width;
+	int height;
+	int lines;
+
+	gdk_drawable_get_size (GTK_WIDGET (buf->xtext)->window, &width, &height);
+	width -= MARGIN;
+
+	if (width < 30 || height < buf->xtext->fontsize || width < buf->indent + 30)
+		return;
+
+	lines = 0;
+	ent = buf->text_first;
+	while (ent)
+	{
+		ent->lines_taken = gtk_xtext_lines_taken (buf, ent);
+		lines += ent->lines_taken;
+		ent = ent->next;
+	}
+
+	buf->pagetop_ent = NULL;
+	buf->num_lines = lines;
+	gtk_xtext_adjustment_set (buf, fire_signal);
+}
+
+/* find the n-th line in the linked list, this includes wrap calculations */
+
+static textentry *
+gtk_xtext_nth (GtkXText *xtext, int line, int *subline)
+{
+	int lines = 0;
+	textentry *ent;
+
+	ent = xtext->buffer->text_first;
+
+	/* -- optimization -- try to make a short-cut using the pagetop ent */
+	if (xtext->buffer->pagetop_ent)
+	{
+		if (line == xtext->buffer->pagetop_line)
+		{
+			*subline = xtext->buffer->pagetop_subline;
+			return xtext->buffer->pagetop_ent;
+		}
+		if (line > xtext->buffer->pagetop_line)
+		{
+			/* lets start from the pagetop instead of the absolute beginning */
+			ent = xtext->buffer->pagetop_ent;
+			lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline;
+		}
+		else if (line > xtext->buffer->pagetop_line - line)
+		{
+			/* move backwards from pagetop */
+			ent = xtext->buffer->pagetop_ent;
+			lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline;
+			while (1)
+			{
+				if (lines <= line)
+				{
+					*subline = line - lines;
+					return ent;
+				}
+				ent = ent->prev;
+				if (!ent)
+					break;
+				lines -= ent->lines_taken;
+			}
+			return 0;
+		}
+	}
+	/* -- end of optimization -- */
+
+	while (ent)
+	{
+		lines += ent->lines_taken;
+		if (lines > line)
+		{
+			*subline = ent->lines_taken - (lines - line);
+			return ent;
+		}
+		ent = ent->next;
+	}
+	return 0;
+}
+
+/* render enta (or an inclusive range enta->entb) */
+
+static int
+gtk_xtext_render_ents (GtkXText * xtext, textentry * enta, textentry * entb)
+{
+	textentry *ent, *orig_ent, *tmp_ent;
+	int line;
+	int lines_max;
+	int width;
+	int height;
+	int subline;
+	int drawing = FALSE;
+
+	if (xtext->buffer->indent < MARGIN)
+		xtext->buffer->indent = MARGIN;	  /* 2 pixels is our left margin */
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+	width -= MARGIN;
+
+	if (width < 32 || height < xtext->fontsize || width < xtext->buffer->indent + 30)
+		return 0;
+
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1;
+	line = 0;
+	orig_ent = xtext->buffer->pagetop_ent;
+	subline = xtext->buffer->pagetop_subline;
+
+	/* used before a complete page is in buffer */
+	if (orig_ent == NULL)
+		orig_ent = xtext->buffer->text_first;
+
+	/* check if enta is before the start of this page */
+	if (entb)
+	{
+		tmp_ent = orig_ent;
+		while (tmp_ent)
+		{
+			if (tmp_ent == enta)
+				break;
+			if (tmp_ent == entb)
+			{
+				drawing = TRUE;
+				break;
+			}
+			tmp_ent = tmp_ent->next;
+		}
+	}
+
+	ent = orig_ent;
+	while (ent)
+	{
+		if (entb && ent == enta)
+			drawing = TRUE;
+
+		if (drawing || ent == entb || ent == enta)
+		{
+			gtk_xtext_reset (xtext, FALSE, TRUE);
+			line += gtk_xtext_render_line (xtext, ent, line, lines_max,
+													 subline, width);
+			subline = 0;
+			xtext->jump_in_offset = 0;	/* jump_in_offset only for the 1st */
+		} else
+		{
+			if (ent == orig_ent)
+			{
+				line -= subline;
+				subline = 0;
+			}
+			line += ent->lines_taken;
+		}
+
+		if (ent == entb)
+			break;
+
+		if (line >= lines_max)
+			break;
+
+		ent = ent->next;
+	}
+
+	/* space below last line */
+	return (xtext->fontsize * line) - xtext->pixel_offset;
+}
+
+/* render a whole page/window, starting from 'startline' */
+
+static void
+gtk_xtext_render_page (GtkXText * xtext)
+{
+	textentry *ent;
+	int line;
+	int lines_max;
+	int width;
+	int height;
+	int subline;
+	int startline = xtext->adj->value;
+
+	if(!GTK_WIDGET_REALIZED(xtext))
+	  return;
+
+	if (xtext->buffer->indent < MARGIN)
+		xtext->buffer->indent = MARGIN;	  /* 2 pixels is our left margin */
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+
+	if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32)
+		return;
+
+#ifdef SMOOTH_SCROLL
+	xtext->pixel_offset = (xtext->adj->value - startline) * xtext->fontsize;
+#else
+	xtext->pixel_offset = 0;
+#endif
+
+	subline = line = 0;
+	ent = xtext->buffer->text_first;
+
+	if (startline > 0)
+		ent = gtk_xtext_nth (xtext, startline, &subline);
+
+	xtext->buffer->pagetop_ent = ent;
+	xtext->buffer->pagetop_subline = subline;
+	xtext->buffer->pagetop_line = startline;
+
+#ifdef SCROLL_HACK
+{
+	int pos, overlap;
+	GdkRectangle area;
+
+	if (xtext->buffer->num_lines <= xtext->adj->page_size)
+		dontscroll (xtext->buffer);
+
+#ifdef SMOOTH_SCROLL
+	pos = xtext->adj->value * xtext->fontsize;
+#else
+	pos = startline * xtext->fontsize;
+#endif
+	overlap = xtext->buffer->last_pixel_pos - pos;
+	xtext->buffer->last_pixel_pos = pos;
+
+#ifdef USE_DB
+#ifdef WIN32
+	if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height)
+#else
+	if (!xtext->pixmap && abs (overlap) < height)
+#endif
+#else
+	/* dont scroll PageUp/Down without a DB, it looks ugly */
+#ifdef WIN32
+	if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize))
+#else
+	if (!xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize))
+#endif
+#endif
+	{
+		/* so the obscured regions are exposed */
+		gdk_gc_set_exposures (xtext->fgc, TRUE);
+		if (overlap < 1)	/* DOWN */
+		{
+			int remainder;
+
+			gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf,
+									 0, -overlap, 0, 0, width, height + overlap);
+			remainder = ((height - xtext->font->descent) % xtext->fontsize) +
+							xtext->font->descent;
+			area.y = (height + overlap) - remainder;
+			area.height = remainder - overlap;
+		} else
+		{
+			gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf,
+									 0, 0, 0, overlap, width, height - overlap);
+			area.y = 0;
+			area.height = overlap;
+		}
+		gdk_gc_set_exposures (xtext->fgc, FALSE);
+
+		if (area.height > 0)
+		{
+			area.x = 0;
+			area.width = width;
+			gtk_xtext_paint (GTK_WIDGET (xtext), &area);
+		}
+		xtext->buffer->grid_dirty = TRUE;
+
+		return;
+	}
+}
+#endif
+
+	xtext->buffer->grid_dirty = FALSE;
+	width -= MARGIN;
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1;
+
+	while (ent)
+	{
+		gtk_xtext_reset (xtext, FALSE, TRUE);
+		line += gtk_xtext_render_line (xtext, ent, line, lines_max,
+												 subline, width);
+		subline = 0;
+
+		if (line >= lines_max)
+			break;
+
+		ent = ent->next;
+	}
+
+	line = (xtext->fontsize * line) - xtext->pixel_offset;
+	/* fill any space below the last line with our background GC */
+	xtext_draw_bg (xtext, 0, line, width + MARGIN, height - line);
+
+	/* draw the separator line */
+	gtk_xtext_draw_sep (xtext, -1);
+}
+
+void
+gtk_xtext_refresh (GtkXText * xtext, int do_trans)
+{
+	if (GTK_WIDGET_REALIZED (GTK_WIDGET (xtext)))
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent && do_trans)
+		{
+			gtk_xtext_free_trans (xtext);
+			gtk_xtext_load_trans (xtext);
+		}
+#endif
+		gtk_xtext_render_page (xtext);
+	}
+}
+
+static int
+gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent)
+{
+	int visible;
+
+	/* Set visible to TRUE if this is the current buffer */
+	/* and this ent shows up on the screen now */
+	visible = buffer->xtext->buffer == buffer &&
+				 gtk_xtext_check_ent_visibility (buffer->xtext, ent, 0);
+
+	if (ent == buffer->pagetop_ent)
+		buffer->pagetop_ent = NULL;
+
+	if (ent == buffer->last_ent_start)
+	{
+		buffer->last_ent_start = ent->next;
+		buffer->last_offset_start = 0;
+	}
+
+	if (ent == buffer->last_ent_end)
+	{
+		buffer->last_ent_start = NULL;
+		buffer->last_ent_end = NULL;
+	}
+
+	if (buffer->marker_pos == ent) buffer->marker_pos = NULL;
+
+	free (ent);
+	return visible;
+}
+
+/* remove the topline from the list */
+
+static void
+gtk_xtext_remove_top (xtext_buffer *buffer)
+{
+	textentry *ent;
+
+	ent = buffer->text_first;
+	if (!ent)
+		return;
+	buffer->num_lines -= ent->lines_taken;
+	buffer->pagetop_line -= ent->lines_taken;
+	buffer->last_pixel_pos -= (ent->lines_taken * buffer->xtext->fontsize);
+	buffer->text_first = ent->next;
+	if (buffer->text_first)
+		buffer->text_first->prev = NULL;
+	else
+		buffer->text_last = NULL;
+
+	buffer->old_value -= ent->lines_taken;
+	if (buffer->xtext->buffer == buffer)	/* is it the current buffer? */
+	{
+		buffer->xtext->adj->value -= ent->lines_taken;
+		buffer->xtext->select_start_adj -= ent->lines_taken;
+	}
+
+	if (gtk_xtext_kill_ent (buffer, ent))
+	{
+		if (!buffer->xtext->add_io_tag)
+		{
+			/* remove scrolling events */
+			if (buffer->xtext->io_tag)
+			{
+				g_source_remove (buffer->xtext->io_tag);
+				buffer->xtext->io_tag = 0;
+			}
+			buffer->xtext->force_render = TRUE;
+			buffer->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2,
+														(GSourceFunc)
+														gtk_xtext_render_page_timeout,
+														buffer->xtext);
+		}
+	}
+}
+
+static void
+gtk_xtext_remove_bottom (xtext_buffer *buffer)
+{
+	textentry *ent;
+
+	ent = buffer->text_last;
+	if (!ent)
+		return;
+	buffer->num_lines -= ent->lines_taken;
+	buffer->text_last = ent->prev;
+	if (buffer->text_last)
+		buffer->text_last->next = NULL;
+	else
+		buffer->text_first = NULL;
+
+	gtk_xtext_kill_ent (buffer, ent);
+}
+
+/* If lines=0 => clear all */
+
+void
+gtk_xtext_clear (xtext_buffer *buf, int lines)
+{
+	textentry *next;
+
+	if (lines != 0)
+	{
+		if (lines < 0)
+		{
+			/* delete lines from bottom */
+			lines *= -1;
+			while (lines)
+			{
+				gtk_xtext_remove_bottom (buf);
+				lines--;
+			}
+		}
+		else
+		{
+			/* delete lines from top */
+			while (lines)
+			{
+				gtk_xtext_remove_top (buf);
+				lines--;
+			}
+		}
+	}
+	else
+	{
+		/* delete all */
+		if (buf->xtext->auto_indent)
+			buf->indent = MARGIN;
+		buf->scrollbar_down = TRUE;
+		buf->last_ent_start = NULL;
+		buf->last_ent_end = NULL;
+		buf->marker_pos = NULL;
+		dontscroll (buf);
+
+		while (buf->text_first)
+		{
+			next = buf->text_first->next;
+			free (buf->text_first);
+			buf->text_first = next;
+		}
+		buf->text_last = NULL;
+	}
+
+	if (buf->xtext->buffer == buf)
+	{
+		gtk_xtext_calc_lines (buf, TRUE);
+		gtk_xtext_refresh (buf->xtext, 0);
+	} else
+	{
+		gtk_xtext_calc_lines (buf, FALSE);
+	}
+}
+
+static gboolean
+gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add)
+{
+	textentry *ent;
+	int lines_max;
+	int line = 0;
+	int width;
+	int height;
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + add;
+	ent = xtext->buffer->pagetop_ent;
+
+	while (ent && line < lines_max)
+	{
+		if (find_ent == ent)
+			return TRUE;
+		line += ent->lines_taken;
+		ent = ent->next;
+	}
+
+	return FALSE;
+}
+
+void
+gtk_xtext_check_marker_visibility (GtkXText * xtext)
+{
+	if (gtk_xtext_check_ent_visibility (xtext, xtext->buffer->marker_pos, 1))
+		xtext->buffer->marker_seen = TRUE;
+}
+
+textentry *
+gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward)
+{
+	textentry *ent, *fent;
+	int line;
+	gchar *str, *nee, *hay;	/* needle in haystack */
+
+	gtk_xtext_selection_clear_full (xtext->buffer);
+	xtext->buffer->last_ent_start = NULL;
+	xtext->buffer->last_ent_end = NULL;
+
+	/* set up text comparand for Case Match or Ignore */
+	if (case_match)
+		nee = g_strdup (text);
+	else
+		nee = g_utf8_casefold (text, strlen (text));
+
+	/* Validate that start gives a currently valid ent pointer */
+	ent = xtext->buffer->text_first;
+	while (ent)
+	{
+		if (ent == start)
+			break;
+		ent = ent->next;
+	}
+	if (!ent)
+		start = NULL;
+
+	/* Choose first ent to look at */
+	if (start)
+		ent = backward? start->prev: start->next;
+	else
+		ent = backward? xtext->buffer->text_last: xtext->buffer->text_first;
+
+	/* Search from there to one end or the other until found */
+	while (ent)
+	{
+		/* If Case Ignore, fold before & free after calling strstr */
+		if (case_match)
+			hay = g_strdup (ent->str);
+		else
+			hay = g_utf8_casefold (ent->str, strlen (ent->str));
+		/* Try to find the needle in this haystack */
+		str = g_strstr_len (hay, strlen (hay), nee);
+		g_free (hay);
+		if (str)
+			break;
+		ent = backward? ent->prev: ent->next;
+	}
+	fent = ent;
+
+	/* Save distance to start, end of found string */
+	if (ent)
+	{
+		ent->mark_start = str - hay;
+		ent->mark_end = ent->mark_start + strlen (nee);
+
+		/* is the match visible? Might need to scroll */
+		if (!gtk_xtext_check_ent_visibility (xtext, ent, 0))
+		{
+			ent = xtext->buffer->text_first;
+			line = 0;
+			while (ent)
+			{
+				line += ent->lines_taken;
+				ent = ent->next;
+				if (ent == fent)
+					break;
+			}
+			while (line > xtext->adj->upper - xtext->adj->page_size)
+				line--;
+			if (backward)
+				line -= xtext->adj->page_size - ent->lines_taken;
+			xtext->adj->value = line;
+			xtext->buffer->scrollbar_down = FALSE;
+			gtk_adjustment_changed (xtext->adj);
+		}
+	}
+
+	g_free (nee);
+	gtk_widget_queue_draw (GTK_WIDGET (xtext));
+
+	return fent;
+}
+
+static int
+gtk_xtext_render_page_timeout (GtkXText * xtext)
+{
+	GtkAdjustment *adj = xtext->adj;
+
+	xtext->add_io_tag = 0;
+
+	/* less than a complete page? */
+	if (xtext->buffer->num_lines <= adj->page_size)
+	{
+		xtext->buffer->old_value = 0;
+		adj->value = 0;
+		gtk_xtext_render_page (xtext);
+	} else if (xtext->buffer->scrollbar_down)
+	{
+		g_signal_handler_block (xtext->adj, xtext->vc_signal_tag);
+		gtk_xtext_adjustment_set (xtext->buffer, FALSE);
+		gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
+		g_signal_handler_unblock (xtext->adj, xtext->vc_signal_tag);
+		xtext->buffer->old_value = adj->value;
+		gtk_xtext_render_page (xtext);
+	} else
+	{
+		gtk_xtext_adjustment_set (xtext->buffer, TRUE);
+		if (xtext->force_render)
+		{
+			xtext->force_render = FALSE;
+			gtk_xtext_render_page (xtext);
+		}
+	}
+
+	return 0;
+}
+
+/* append a textentry to our linked list */
+
+static void
+gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
+{
+	unsigned int mb;
+	int i;
+
+	/* we don't like tabs */
+	i = 0;
+	while (i < ent->str_len)
+	{
+		if (ent->str[i] == '\t')
+			ent->str[i] = ' ';
+		i++;
+	}
+
+	ent->stamp = stamp;
+	if (stamp == 0)
+		ent->stamp = time (0);
+	ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, ent->str_len, &mb);
+	ent->mb = FALSE;
+	if (mb)
+		ent->mb = TRUE;
+	ent->mark_start = -1;
+	ent->mark_end = -1;
+	ent->next = NULL;
+
+	if (ent->indent < MARGIN)
+		ent->indent = MARGIN;	  /* 2 pixels is the left margin */
+
+	/* append to our linked list */
+	if (buf->text_last)
+		buf->text_last->next = ent;
+	else
+		buf->text_first = ent;
+	ent->prev = buf->text_last;
+	buf->text_last = ent;
+
+	ent->lines_taken = gtk_xtext_lines_taken (buf, ent);
+	buf->num_lines += ent->lines_taken;
+
+	if (buf->reset_marker_pos || 
+		((buf->marker_pos == NULL || buf->marker_seen) && (buf->xtext->buffer != buf || 
+#if GTK_CHECK_VERSION(2,4,0)
+		!gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext)))))))
+#else
+		!(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus)))
+#endif
+	{
+		buf->marker_pos = ent;
+		dontscroll (buf); /* force scrolling off */
+		buf->marker_seen = FALSE;
+		buf->reset_marker_pos = FALSE;
+	}
+
+	if (buf->xtext->max_lines > 2 && buf->xtext->max_lines < buf->num_lines)
+	{
+		gtk_xtext_remove_top (buf);
+	}
+
+	if (buf->xtext->buffer == buf)
+	{
+#ifdef SCROLL_HACK
+		/* this could be improved */
+		if ((buf->num_lines - 1) <= buf->xtext->adj->page_size)
+			dontscroll (buf);
+#endif
+
+		if (!buf->xtext->add_io_tag)
+		{
+			/* remove scrolling events */
+			if (buf->xtext->io_tag)
+			{
+				g_source_remove (buf->xtext->io_tag);
+				buf->xtext->io_tag = 0;
+			}
+			buf->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2,
+															(GSourceFunc)
+															gtk_xtext_render_page_timeout,
+															buf->xtext);
+		}
+	} else if (buf->scrollbar_down)
+	{
+		buf->old_value = buf->num_lines - buf->xtext->adj->page_size;
+		if (buf->old_value < 0)
+			buf->old_value = 0;
+	}
+}
+
+/* the main two public functions */
+
+void
+gtk_xtext_append_indent (xtext_buffer *buf,
+								 unsigned char *left_text, int left_len,
+								 unsigned char *right_text, int right_len,
+								 time_t stamp)
+{
+	textentry *ent;
+	unsigned char *str;
+	int space;
+	int tempindent;
+	int left_width;
+
+	if (left_len == -1)
+		left_len = strlen (left_text);
+
+	if (right_len == -1)
+		right_len = strlen (right_text);
+
+	if (right_len >= sizeof (buf->xtext->scratch_buffer))
+		right_len = sizeof (buf->xtext->scratch_buffer) - 1;
+
+	if (right_text[right_len-1] == '\n')
+		right_len--;
+
+	ent = malloc (left_len + right_len + 2 + sizeof (textentry));
+	str = (unsigned char *) ent + sizeof (textentry);
+
+	memcpy (str, left_text, left_len);
+	str[left_len] = ' ';
+	memcpy (str + left_len + 1, right_text, right_len);
+	str[left_len + 1 + right_len] = 0;
+
+	left_width = gtk_xtext_text_width (buf->xtext, left_text, left_len, NULL);
+
+	ent->left_len = left_len;
+	ent->str = str;
+	ent->str_len = left_len + 1 + right_len;
+	ent->indent = (buf->indent - left_width) - buf->xtext->space_width;
+
+	if (buf->time_stamp)
+		space = buf->xtext->stamp_width;
+	else
+		space = 0;
+
+	/* do we need to auto adjust the separator position? */
+	if (buf->xtext->auto_indent && ent->indent < MARGIN + space)
+	{
+		tempindent = MARGIN + space + buf->xtext->space_width + left_width;
+
+		if (tempindent > buf->indent)
+			buf->indent = tempindent;
+
+		if (buf->indent > buf->xtext->max_auto_indent)
+			buf->indent = buf->xtext->max_auto_indent;
+
+		gtk_xtext_fix_indent (buf);
+		gtk_xtext_recalc_widths (buf, FALSE);
+
+		ent->indent = (buf->indent - left_width) - buf->xtext->space_width;
+		buf->xtext->force_render = TRUE;
+	}
+
+	gtk_xtext_append_entry (buf, ent, stamp);
+}
+
+void
+gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len)
+{
+	textentry *ent;
+
+	if (len == -1)
+		len = strlen (text);
+
+	if (text[len-1] == '\n')
+		len--;
+
+	if (len >= sizeof (buf->xtext->scratch_buffer))
+		len = sizeof (buf->xtext->scratch_buffer) - 1;
+
+	ent = malloc (len + 1 + sizeof (textentry));
+	ent->str = (unsigned char *) ent + sizeof (textentry);
+	ent->str_len = len;
+	if (len)
+		memcpy (ent->str, text, len);
+	ent->str[len] = 0;
+	ent->indent = 0;
+	ent->left_len = -1;
+
+	gtk_xtext_append_entry (buf, ent, 0);
+}
+
+gboolean
+gtk_xtext_is_empty (xtext_buffer *buf)
+{
+	return buf->text_first == NULL;
+}
+
+int
+gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area,
+						 int (*cmp_func) (char *, void *), void *userdata)
+{
+	textentry *ent = search_area->text_first;
+	int matches = 0;
+
+	while (ent)
+	{
+		if (cmp_func (ent->str, userdata))
+		{
+			matches++;
+			/* copy the text over */
+			if (search_area->xtext->auto_indent)
+				gtk_xtext_append_indent (out, ent->str, ent->left_len,
+												 ent->str + ent->left_len + 1,
+												 ent->str_len - ent->left_len - 1, 0);
+			else
+				gtk_xtext_append (out, ent->str, ent->str_len);
+			/* copy the timestamp over */
+			out->text_last->stamp = ent->stamp;
+		}
+		ent = ent->next;
+	}
+
+	return matches;
+}
+
+void
+gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data)
+{
+	textentry *ent = buf->text_first;
+
+	while (ent)
+	{
+		(*func) (buf->xtext, ent->str, data);
+		ent = ent->next;
+	}
+}
+
+void
+gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int))
+{
+	xtext->error_function = error_function;
+}
+
+void
+gtk_xtext_set_indent (GtkXText *xtext, gboolean indent)
+{
+	xtext->auto_indent = indent;
+}
+
+void
+gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent)
+{
+	xtext->max_auto_indent = max_auto_indent;
+}
+
+void
+gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines)
+{
+	xtext->max_lines = max_lines;
+}
+
+void
+gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker)
+{
+	xtext->marker = show_marker;
+}
+
+void
+gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator)
+{
+	xtext->separator = show_separator;
+}
+
+void
+gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator)
+{
+	xtext->thinline = thin_separator;
+}
+
+void
+gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean time_stamp)
+{
+	buf->time_stamp = time_stamp;
+}
+
+void
+gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue)
+{
+	xtext->tint_red = tint_red;
+	xtext->tint_green = tint_green;
+	xtext->tint_blue = tint_blue;
+
+	/*if (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255)
+		shaded = TRUE;*/
+}
+
+void
+gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int))
+{
+	xtext->urlcheck_function = urlcheck_function;
+}
+
+void
+gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean wordwrap)
+{
+	xtext->wordwrap = wordwrap;
+}
+
+void
+gtk_xtext_reset_marker_pos (GtkXText *xtext)
+{
+	xtext->buffer->marker_pos = NULL;
+	dontscroll (xtext->buffer); /* force scrolling off */
+	gtk_xtext_render_page (xtext);
+	xtext->buffer->reset_marker_pos = TRUE;
+}
+
+void
+gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render)
+{
+	int w, h;
+
+	buf->xtext = xtext;
+
+	if (xtext->buffer == buf)
+		return;
+
+/*printf("text_buffer_show: xtext=%p buffer=%p\n", xtext, buf);*/
+
+	if (xtext->add_io_tag)
+	{
+		g_source_remove (xtext->add_io_tag);
+		xtext->add_io_tag = 0;
+	}
+
+	if (xtext->io_tag)
+	{
+		g_source_remove (xtext->io_tag);
+		xtext->io_tag = 0;
+	}
+
+	if (!GTK_WIDGET_REALIZED (GTK_WIDGET (xtext)))
+		gtk_widget_realize (GTK_WIDGET (xtext));
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &w, &h);
+
+	/* after a font change */
+	if (buf->needs_recalc)
+	{
+		buf->needs_recalc = FALSE;
+		gtk_xtext_recalc_widths (buf, TRUE);
+	}
+
+	/* now change to the new buffer */
+	xtext->buffer = buf;
+	dontscroll (buf);	/* force scrolling off */
+	xtext->adj->value = buf->old_value;
+	xtext->adj->upper = buf->num_lines;
+	if (xtext->adj->upper == 0)
+		xtext->adj->upper = 1;
+	/* sanity check */
+	else if (xtext->adj->value > xtext->adj->upper - xtext->adj->page_size)
+	{
+		/*buf->pagetop_ent = NULL;*/
+		xtext->adj->value = xtext->adj->upper - xtext->adj->page_size;
+		if (xtext->adj->value < 0)
+			xtext->adj->value = 0;
+	}
+
+	if (render)
+	{
+		/* did the window change size since this buffer was last shown? */
+		if (buf->window_width != w)
+		{
+			buf->window_width = w;
+			gtk_xtext_calc_lines (buf, FALSE);
+			if (buf->scrollbar_down)
+				gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+												  xtext->adj->page_size);
+		} else if (buf->window_height != h)
+		{
+			buf->window_height = h;
+			buf->pagetop_ent = NULL;
+			gtk_xtext_adjustment_set (buf, FALSE);
+		}
+
+		gtk_xtext_render_page (xtext);
+		gtk_adjustment_changed (xtext->adj);
+	} else
+	{
+		/* avoid redoing the transparency */
+		xtext->avoid_trans = TRUE;
+	}
+}
+
+xtext_buffer *
+gtk_xtext_buffer_new (GtkXText *xtext)
+{
+	xtext_buffer *buf;
+
+	buf = malloc (sizeof (xtext_buffer));
+	memset (buf, 0, sizeof (xtext_buffer));
+	buf->old_value = -1;
+	buf->xtext = xtext;
+	buf->scrollbar_down = TRUE;
+	buf->indent = xtext->space_width * 2;
+	dontscroll (buf);
+
+	return buf;
+}
+
+void
+gtk_xtext_buffer_free (xtext_buffer *buf)
+{
+	textentry *ent, *next;
+
+	if (buf->xtext->buffer == buf)
+		buf->xtext->buffer = buf->xtext->orig_buffer;
+
+	if (buf->xtext->selection_buffer == buf)
+		buf->xtext->selection_buffer = NULL;
+
+	ent = buf->text_first;
+	while (ent)
+	{
+		next = ent->next;
+		free (ent);
+		ent = next;
+	}
+
+	free (buf);
+}
diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h
new file mode 100644
index 00000000..a37ddc32
--- /dev/null
+++ b/src/fe-gtk/xtext.h
@@ -0,0 +1,275 @@
+#ifndef __XTEXT_H__
+#define __XTEXT_H__
+
+#include <gtk/gtkadjustment.h>
+#ifdef USE_XFT
+#include <X11/Xft/Xft.h>
+#endif
+
+#ifdef USE_SHM
+#include <X11/Xlib.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+
+#define GTK_TYPE_XTEXT              (gtk_xtext_get_type ())
+#define GTK_XTEXT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText))
+#define GTK_XTEXT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_XTEXT, GtkXTextClass))
+#define GTK_IS_XTEXT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_XTEXT))
+#define GTK_IS_XTEXT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT))
+#define GTK_XTEXT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass))
+
+#define ATTR_BOLD			'\002'
+#define ATTR_COLOR		'\003'
+#define ATTR_BLINK		'\006'
+#define ATTR_BEEP			'\007'
+#define ATTR_HIDDEN		'\010'
+#define ATTR_ITALICS2	'\011'
+#define ATTR_RESET		'\017'
+#define ATTR_REVERSE		'\026'
+#define ATTR_ITALICS		'\035'
+#define ATTR_UNDERLINE	'\037'
+
+/* these match palette.h */
+#define XTEXT_MIRC_COLS 32
+#define XTEXT_COLS 37		/* 32 plus 5 for extra stuff below */
+#define XTEXT_MARK_FG 32	/* for marking text */
+#define XTEXT_MARK_BG 33
+#define XTEXT_FG 34
+#define XTEXT_BG 35
+#define XTEXT_MARKER 36		/* for marker line */
+
+typedef struct _GtkXText GtkXText;
+typedef struct _GtkXTextClass GtkXTextClass;
+typedef struct textentry textentry;
+
+typedef struct {
+	GtkXText *xtext;					/* attached to this widget */
+
+	gfloat old_value;					/* last known adj->value */
+	textentry *text_first;
+	textentry *text_last;
+	guint16 grid_offset[256];
+
+	textentry *last_ent_start;	  /* this basically describes the last rendered */
+	textentry *last_ent_end;	  /* selection. */
+	int last_offset_start;
+	int last_offset_end;
+
+	int last_pixel_pos;
+
+	int pagetop_line;
+	int pagetop_subline;
+	textentry *pagetop_ent;			/* what's at xtext->adj->value */
+
+	int num_lines;
+	int indent;						  /* position of separator (pixels) from left */
+
+	textentry *marker_pos;
+
+	int window_width;				/* window size when last rendered. */
+	int window_height;
+
+	unsigned int time_stamp:1;
+	unsigned int scrollbar_down:1;
+	unsigned int needs_recalc:1;
+	unsigned int grid_dirty:1;
+	unsigned int marker_seen:1;
+	unsigned int reset_marker_pos:1;
+} xtext_buffer;
+
+struct _GtkXText
+{
+	GtkWidget widget;
+
+	xtext_buffer *buffer;
+	xtext_buffer *orig_buffer;
+	xtext_buffer *selection_buffer;
+
+#ifdef USE_SHM
+	XShmSegmentInfo shminfo;
+#endif
+
+	GtkAdjustment *adj;
+	GdkPixmap *pixmap;				/* 0 = use palette[19] */
+	GdkDrawable *draw_buf;			/* points to ->window */
+	GdkCursor *hand_cursor;
+	GdkCursor *resize_cursor;
+
+	int pixel_offset;					/* amount of pixels the top line is chopped by */
+
+	int last_win_x;
+	int last_win_y;
+	int last_win_h;
+	int last_win_w;
+
+	int tint_red;
+	int tint_green;
+	int tint_blue;
+
+	GdkGC *bgc;						  /* backing pixmap */
+	GdkGC *fgc;						  /* text foreground color */
+	GdkGC *light_gc;				  /* sep bar */
+	GdkGC *dark_gc;
+	GdkGC *thin_gc;
+	GdkGC *marker_gc;
+	gulong palette[XTEXT_COLS];
+
+	gint io_tag;					  /* for delayed refresh events */
+	gint add_io_tag;				  /* "" when adding new text */
+	gint scroll_tag;				  /* marking-scroll timeout */
+	gulong vc_signal_tag;        /* signal handler for "value_changed" adj */
+
+	int select_start_adj;		  /* the adj->value when the selection started */
+	int select_start_x;
+	int select_start_y;
+	int select_end_x;
+	int select_end_y;
+
+	int max_lines;
+
+	int col_fore;
+	int col_back;
+
+	int depth;						  /* gdk window depth */
+
+	char num[8];					  /* for parsing mirc color */
+	int nc;							  /* offset into xtext->num */
+
+	textentry *hilight_ent;
+	int hilight_start;
+	int hilight_end;
+
+	guint16 fontwidth[128];	  /* each char's width, only the ASCII ones */
+
+#ifdef USE_XFT
+	XftColor color[XTEXT_COLS];
+	XftColor *xft_fg;
+	XftColor *xft_bg;				/* both point into color[20] */
+	XftDraw *xftdraw;
+	XftFont *font;
+	XftFont *ifont;				/* italics */
+#else
+	struct pangofont
+	{
+		PangoFontDescription *font;
+		PangoFontDescription *ifont;	/* italics */
+		int ascent;
+		int descent;
+	} *font, pango_font;
+	PangoLayout *layout;
+#endif
+
+	int fontsize;
+	int space_width;				  /* width (pixels) of the space " " character */
+	int stamp_width;				  /* width of "[88:88:88]" */
+	int max_auto_indent;
+
+	unsigned char scratch_buffer[4096];
+
+	void (*error_function) (int type);
+	int (*urlcheck_function) (GtkWidget * xtext, char *word, int len);
+
+	int jump_out_offset;	/* point at which to stop rendering */
+	int jump_in_offset;	/* "" start rendering */
+
+	int ts_x;			/* ts origin for ->bgc GC */
+	int ts_y;
+
+	int clip_x;			/* clipping (x directions) */
+	int clip_x2;		/* from x to x2 */
+
+	int clip_y;			/* clipping (y directions) */
+	int clip_y2;		/* from y to y2 */
+
+	/* current text states */
+	unsigned int bold:1;
+	unsigned int underline:1;
+	unsigned int italics:1;
+	unsigned int hidden:1;
+
+	/* text parsing states */
+	unsigned int parsing_backcolor:1;
+	unsigned int parsing_color:1;
+	unsigned int backcolor:1;
+
+	/* various state information */
+	unsigned int moving_separator:1;
+	unsigned int word_or_line_select:1;
+	unsigned int button_down:1;
+	unsigned int hilighting:1;
+	unsigned int dont_render:1;
+	unsigned int dont_render2:1;
+	unsigned int cursor_hand:1;
+	unsigned int cursor_resize:1;
+	unsigned int skip_border_fills:1;
+	unsigned int skip_stamp:1;
+	unsigned int mark_stamp:1;	/* Cut&Paste with stamps? */
+	unsigned int force_stamp:1;	/* force redrawing it */
+	unsigned int render_hilights_only:1;
+	unsigned int in_hilight:1;
+	unsigned int un_hilight:1;
+	unsigned int recycle:1;
+	unsigned int avoid_trans:1;
+	unsigned int force_render:1;
+	unsigned int shm:1;
+	unsigned int color_paste:1; /* CTRL was pressed when selection finished */
+
+	/* settings/prefs */
+	unsigned int auto_indent:1;
+	unsigned int thinline:1;
+	unsigned int transparent:1;
+	unsigned int shaded:1;
+	unsigned int marker:1;
+	unsigned int separator:1;
+	unsigned int wordwrap:1;
+	unsigned int overdraw:1;
+	unsigned int ignore_hidden:1;	/* rawlog uses this */
+};
+
+struct _GtkXTextClass
+{
+	GtkWidgetClass parent_class;
+	void (*word_click) (GtkXText * xtext, char *word, GdkEventButton * event);
+};
+
+GtkWidget *gtk_xtext_new (GdkColor palette[], int separator);
+void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len);
+void gtk_xtext_append_indent (xtext_buffer *buf,
+										unsigned char *left_text, int left_len,
+										unsigned char *right_text, int right_len,
+										time_t stamp);
+int gtk_xtext_set_font (GtkXText *xtext, char *name);
+void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans);
+void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]);
+void gtk_xtext_clear (xtext_buffer *buf, int lines);
+void gtk_xtext_save (GtkXText * xtext, int fh);
+void gtk_xtext_refresh (GtkXText * xtext, int do_trans);
+int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata);
+textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward);
+void gtk_xtext_reset_marker_pos (GtkXText *xtext);
+void gtk_xtext_check_marker_visibility(GtkXText *xtext);
+
+gboolean gtk_xtext_is_empty (xtext_buffer *buf);
+typedef void (*GtkXTextForeach) (GtkXText *xtext, unsigned char *text, void *data);
+void gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data);
+
+void gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int));
+void gtk_xtext_set_indent (GtkXText *xtext, gboolean indent);
+void gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent);
+void gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines);
+void gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker);
+void gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator);
+void gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator);
+void gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean timestamp);
+void gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue);
+void gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int));
+void gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean word_wrap);
+
+xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext);
+void gtk_xtext_buffer_free (xtext_buffer *buf);
+void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render);
+GType gtk_xtext_get_type (void);
+
+#endif
diff --git a/src/fe-text/Makefile.am b/src/fe-text/Makefile.am
new file mode 100644
index 00000000..ef48203e
--- /dev/null
+++ b/src/fe-text/Makefile.am
@@ -0,0 +1,9 @@
+bin_PROGRAMS = xchat-text
+
+EXTRA_DIST = README 
+
+INCLUDES = $(COMMON_CFLAGS)
+
+xchat_text_LDADD = ../common/libxchatcommon.a $(COMMON_LIBS)
+xchat_text_SOURCES = fe-text.c fe-text.h
+
diff --git a/src/fe-text/README b/src/fe-text/README
new file mode 100644
index 00000000..bb760aae
--- /dev/null
+++ b/src/fe-text/README
@@ -0,0 +1,5 @@
+fe-text README
+~~~~~~~~~~~~~~
+
+This is an experimental text frontend for X-Chat.
+If anyone wants to add ncurses support, they are welcome to.
diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c
new file mode 100644
index 00000000..2bc2e649
--- /dev/null
+++ b/src/fe-text/fe-text.c
@@ -0,0 +1,862 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <ctype.h>
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/outbound.h"
+#include "../common/util.h"
+#include "../common/fe.h"
+#include "fe-text.h"
+
+
+static GSList *tmr_list;		  /* timer list */
+static int tmr_list_count;
+static GSList *se_list;			  /* socket event list */
+static int se_list_count;
+static int done = FALSE;		  /* finished ? */
+
+
+static void
+send_command (char *cmd)
+{
+	handle_multiline (sess_list->data, cmd, TRUE, FALSE);
+}
+
+static void
+read_stdin (void)
+{
+	int len, i = 0;
+	static int pos = 0;
+	static char inbuf[1024];
+	char tmpbuf[512];
+
+	len = read (STDIN_FILENO, tmpbuf, sizeof tmpbuf - 1);
+
+	while (i < len)
+	{
+		switch (tmpbuf[i])
+		{
+		case '\r':
+			break;
+
+		case '\n':
+			inbuf[pos] = 0;
+			pos = 0;
+			send_command (inbuf);
+			break;
+
+		default:
+			inbuf[pos] = tmpbuf[i];
+			if (pos < (sizeof inbuf - 2))
+				pos++;
+		}
+		i++;
+	}
+}
+
+static int done_intro = 0;
+
+void
+fe_new_window (struct session *sess, int focus)
+{
+	char buf[512];
+
+	sess->gui = malloc (4);
+
+	if (!sess->server->front_session)
+		sess->server->front_session = sess;
+	if (!sess->server->server_session)
+		sess->server->server_session = sess;
+	if (!current_tab)
+		current_tab = sess;
+
+	if (done_intro)
+		return;
+	done_intro = 1;
+
+	snprintf (buf, sizeof (buf),
+				"\n"
+				" \017xchat \00310"PACKAGE_VERSION"\n"
+				" \017Running on \00310%s \017glib \00310%d.%d.%d\n"
+				" \017This binary compiled \00310"__DATE__"\017\n",
+				get_cpu_str(),
+				glib_major_version, glib_minor_version, glib_micro_version);
+	fe_print_text (sess, buf, 0);
+
+	fe_print_text (sess, "\n\nCompiled in Features\0032:\017 "
+#ifdef USE_PLUGIN
+	"Plugin "
+#endif
+#ifdef ENABLE_NLS
+	"NLS "
+#endif
+#ifdef USE_OPENSSL
+	"OpenSSL "
+#endif
+#ifdef USE_IPV6
+	"IPv6"
+#endif
+	"\n\n", 0);
+	fflush (stdout);
+	fflush (stdin);
+}
+
+static int
+get_stamp_str (time_t tim, char *dest, int size)
+{
+	return strftime (dest, size, prefs.stamp_format, localtime (&tim));
+}
+
+static int
+timecat (char *buf)
+{
+	char stampbuf[64];
+
+	get_stamp_str (time (0), stampbuf, sizeof (stampbuf));
+	strcat (buf, stampbuf);
+	return strlen (stampbuf);
+}
+
+/*                       0  1  2  3  4  5  6  7   8   9   10 11  12  13  14 15 */
+static const short colconv[] = { 0, 7, 4, 2, 1, 3, 5, 11, 13, 12, 6, 16, 14, 15, 10, 7 };
+
+void
+fe_print_text (struct session *sess, char *text, time_t stamp)
+{
+	int dotime = FALSE;
+	char num[8];
+	int reverse = 0, under = 0, bold = 0,
+		comma, k, i = 0, j = 0, len = strlen (text);
+	unsigned char *newtext = malloc (len + 1024);
+
+	if (prefs.timestamp)
+	{
+		newtext[0] = 0;
+		j += timecat (newtext);
+	}
+	while (i < len)
+	{
+		if (dotime && text[i] != 0)
+		{
+			dotime = FALSE;
+			newtext[j] = 0;
+			j += timecat (newtext);
+		}
+		switch (text[i])
+		{
+		case 3:
+			i++;
+			if (!isdigit (text[i]))
+			{
+				newtext[j] = 27;
+				j++;
+				newtext[j] = '[';
+				j++;
+				newtext[j] = 'm';
+				j++;
+				i--;
+				goto jump2;
+			}
+			k = 0;
+			comma = FALSE;
+			while (i < len)
+			{
+				if (text[i] >= '0' && text[i] <= '9' && k < 2)
+				{
+					num[k] = text[i];
+					k++;
+				} else
+				{
+					int col, mirc;
+					num[k] = 0;
+					newtext[j] = 27;
+					j++;
+					newtext[j] = '[';
+					j++;
+					if (k == 0)
+					{
+						newtext[j] = 'm';
+						j++;
+					} else
+					{
+						if (comma)
+							col = 40;
+						else
+							col = 30;
+						mirc = atoi (num);
+						mirc = colconv[mirc];
+						if (mirc > 9)
+						{
+							mirc += 50;
+							sprintf ((char *) &newtext[j], "%dm", mirc + col);
+						} else
+						{
+							sprintf ((char *) &newtext[j], "%dm", mirc + col);
+						}
+						j = strlen (newtext);
+					}
+					switch (text[i])
+					{
+					case ',':
+						comma = TRUE;
+						break;
+					default:
+						goto jump;
+					}
+					k = 0;
+				}
+				i++;
+			}
+			break;
+		case '\026':				  /* REVERSE */
+			if (reverse)
+			{
+				reverse = FALSE;
+				strcpy (&newtext[j], "\033[27m");
+			} else
+			{
+				reverse = TRUE;
+				strcpy (&newtext[j], "\033[7m");
+			}
+			j = strlen (newtext);
+			break;
+		case '\037':				  /* underline */
+			if (under)
+			{
+				under = FALSE;
+				strcpy (&newtext[j], "\033[24m");
+			} else
+			{
+				under = TRUE;
+				strcpy (&newtext[j], "\033[4m");
+			}
+			j = strlen (newtext);
+			break;
+		case '\002':				  /* bold */
+			if (bold)
+			{
+				bold = FALSE;
+				strcpy (&newtext[j], "\033[22m");
+			} else
+			{
+				bold = TRUE;
+				strcpy (&newtext[j], "\033[1m");
+			}
+			j = strlen (newtext);
+			break;
+		case '\007':
+			if (!prefs.filterbeep)
+			{
+				newtext[j] = text[i];
+				j++;
+			}
+			break;
+		case '\017':				  /* reset all */
+			strcpy (&newtext[j], "\033[m");
+			j += 3;
+			reverse = FALSE;
+			bold = FALSE;
+			under = FALSE;
+			break;
+		case '\t':
+			newtext[j] = ' ';
+			j++;
+			break;
+		case '\n':
+			newtext[j] = '\r';
+			j++;
+			if (prefs.timestamp)
+				dotime = TRUE;
+		default:
+			newtext[j] = text[i];
+			j++;
+		}
+	 jump2:
+		i++;
+	 jump:
+		i += 0;
+	}
+	newtext[j] = 0;
+	write (STDOUT_FILENO, newtext, j);
+	free (newtext);
+}
+
+void
+fe_timeout_remove (int tag)
+{
+	timerevent *te;
+	GSList *list;
+
+	list = tmr_list;
+	while (list)
+	{
+		te = (timerevent *) list->data;
+		if (te->tag == tag)
+		{
+			tmr_list = g_slist_remove (tmr_list, te);
+			free (te);
+			return;
+		}
+		list = list->next;
+	}
+}
+
+int
+fe_timeout_add (int interval, void *callback, void *userdata)
+{
+	struct timeval now;
+	timerevent *te = malloc (sizeof (timerevent));
+
+	tmr_list_count++;				  /* this overflows at 2.2Billion, who cares!! */
+
+	te->tag = tmr_list_count;
+	te->interval = interval;
+	te->callback = callback;
+	te->userdata = userdata;
+
+	gettimeofday (&now, NULL);
+	te->next_call = now.tv_sec * 1000 + (now.tv_usec / 1000) + te->interval;
+
+	tmr_list = g_slist_prepend (tmr_list, te);
+
+	return te->tag;
+}
+
+void
+fe_input_remove (int tag)
+{
+	socketevent *se;
+	GSList *list;
+
+	list = se_list;
+	while (list)
+	{
+		se = (socketevent *) list->data;
+		if (se->tag == tag)
+		{
+			se_list = g_slist_remove (se_list, se);
+			free (se);
+			return;
+		}
+		list = list->next;
+	}
+}
+
+int
+fe_input_add (int sok, int flags, void *func, void *data)
+{
+	socketevent *se = malloc (sizeof (socketevent));
+
+	se_list_count++;				  /* this overflows at 2.2Billion, who cares!! */
+
+	se->tag = se_list_count;
+	se->sok = sok;
+	se->rread = flags & FIA_READ;
+	se->wwrite = flags & FIA_WRITE;
+	se->eexcept = flags & FIA_EX;
+	se->callback = func;
+	se->userdata = data;
+	se_list = g_slist_prepend (se_list, se);
+
+	return se->tag;
+}
+
+int
+fe_args (int argc, char *argv[])
+{
+	if (argc > 1)
+	{
+		if (!strcasecmp (argv[1], "--version") || !strcasecmp (argv[1], "-v"))
+		{
+			puts (PACKAGE_VERSION);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+void
+fe_init (void)
+{
+	se_list = 0;
+	se_list_count = 0;
+	tmr_list = 0;
+	tmr_list_count = 0;
+	prefs.autosave = 0;
+	prefs.use_server_tab = 0;
+	prefs.autodialog = 0;
+	prefs.lagometer = 0;
+	prefs.slist_skip = 1;
+}
+
+void
+fe_main (void)
+{
+	struct timeval timeout, now;
+	socketevent *se;
+	timerevent *te;
+	fd_set rd, wd, ex;
+	GSList *list;
+	guint64 shortest, delay;
+
+	if (!sess_list)
+		new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+
+#ifdef ENABLE_NLS
+	bindtextdomain (GETTEXT_PACKAGE, PREFIX"/share/locale");
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+#endif
+
+	while (!done)
+	{
+		FD_ZERO (&rd);
+		FD_ZERO (&wd);
+		FD_ZERO (&ex);
+
+		list = se_list;
+		while (list)
+		{
+			se = (socketevent *) list->data;
+			if (se->rread)
+				FD_SET (se->sok, &rd);
+			if (se->wwrite)
+				FD_SET (se->sok, &wd);
+			if (se->eexcept)
+				FD_SET (se->sok, &ex);
+			list = list->next;
+		}
+
+		FD_SET (STDIN_FILENO, &rd);	/* for reading keyboard */
+
+		/* find the shortest timeout event */
+		shortest = 0;
+		list = tmr_list;
+		while (list)
+		{
+			te = (timerevent *) list->data;
+			if (te->next_call < shortest || shortest == 0)
+				shortest = te->next_call;
+			list = list->next;
+		}
+		gettimeofday (&now, NULL);
+		delay = shortest - ((now.tv_sec * 1000) + (now.tv_usec / 1000));
+		timeout.tv_sec = delay / 1000;
+		timeout.tv_usec = (delay % 1000) * 1000;
+
+		select (FD_SETSIZE, &rd, &wd, &ex, &timeout);
+
+		if (FD_ISSET (STDIN_FILENO, &rd))
+			read_stdin ();
+
+		/* set all checked flags to false */
+		list = se_list;
+		while (list)
+		{
+			se = (socketevent *) list->data;
+			se->checked = 0;
+			list = list->next;
+		}
+
+		/* check all the socket callbacks */
+		list = se_list;
+		while (list)
+		{
+			se = (socketevent *) list->data;
+			se->checked = 1;
+			if (se->rread && FD_ISSET (se->sok, &rd))
+			{
+				se->callback (NULL, 1, se->userdata);
+			} else if (se->wwrite && FD_ISSET (se->sok, &wd))
+			{
+				se->callback (NULL, 2, se->userdata);
+			} else if (se->eexcept && FD_ISSET (se->sok, &ex))
+			{
+				se->callback (NULL, 4, se->userdata);
+			}
+			list = se_list;
+			if (list)
+			{
+				se = (socketevent *) list->data;
+				while (se->checked)
+				{
+					list = list->next;
+					if (!list)
+						break;
+					se = (socketevent *) list->data;
+				}
+			}
+		}
+
+		/* now check our list of timeout events, some might need to be called! */
+		gettimeofday (&now, NULL);
+		list = tmr_list;
+		while (list)
+		{
+			te = (timerevent *) list->data;
+			list = list->next;
+			if (now.tv_sec * 1000 + (now.tv_usec / 1000) >= te->next_call)
+			{
+				/* if the callback returns 0, it must be removed */
+				if (te->callback (te->userdata) == 0)
+				{
+					fe_timeout_remove (te->tag);
+				} else
+				{
+					te->next_call = now.tv_sec * 1000 + (now.tv_usec / 1000) + te->interval;
+				}
+			}
+		}
+
+	}
+}
+
+void
+fe_exit (void)
+{
+	done = TRUE;
+}
+
+void
+fe_new_server (struct server *serv)
+{
+	serv->gui = malloc (4);
+}
+
+void
+fe_message (char *msg, int flags)
+{
+	puts (msg);
+}
+
+void
+fe_close_window (struct session *sess)
+{
+	session_free (sess);
+	done = TRUE;
+}
+
+void
+fe_beep (void)
+{
+	putchar (7);
+}
+
+void
+fe_add_rawlog (struct server *serv, char *text, int len, int outbound)
+{
+}
+void
+fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
+{
+}
+void
+fe_cleanup (void)
+{
+}
+void
+fe_set_hilight (struct session *sess)
+{
+}
+void
+fe_set_tab_color (struct session *sess, int col)
+{
+}
+void
+fe_update_mode_buttons (struct session *sess, char mode, char sign)
+{
+}
+void
+fe_update_channel_key (struct session *sess)
+{
+}
+void
+fe_update_channel_limit (struct session *sess)
+{
+}
+int
+fe_is_chanwindow (struct server *serv)
+{
+	return 0;
+}
+
+void
+fe_add_chan_list (struct server *serv, char *chan, char *users, char *topic)
+{
+}
+void
+fe_chan_list_end (struct server *serv)
+{
+}
+int
+fe_is_banwindow (struct session *sess)
+{
+	return 0;
+}
+void
+fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption)
+{
+}
+void
+fe_ban_list_end (struct session *sess, int is_exemption)
+{
+}
+void
+fe_notify_update (char *name)
+{
+}
+void
+fe_notify_ask (char *name, char *networks)
+{
+}
+void
+fe_text_clear (struct session *sess, int lines)
+{
+}
+void
+fe_progressbar_start (struct session *sess)
+{
+}
+void
+fe_progressbar_end (struct server *serv)
+{
+}
+void
+fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel)
+{
+}
+int
+fe_userlist_remove (struct session *sess, struct User *user)
+{
+	return 0;
+}
+void
+fe_userlist_rehash (struct session *sess, struct User *user)
+{
+}
+void
+fe_userlist_move (struct session *sess, struct User *user, int new_row)
+{
+}
+void
+fe_userlist_numbers (struct session *sess)
+{
+}
+void
+fe_userlist_clear (struct session *sess)
+{
+}
+void
+fe_userlist_set_selected (struct session *sess)
+{
+}
+void
+fe_dcc_add (struct DCC *dcc)
+{
+}
+void
+fe_dcc_update (struct DCC *dcc)
+{
+}
+void
+fe_dcc_remove (struct DCC *dcc)
+{
+}
+void
+fe_clear_channel (struct session *sess)
+{
+}
+void
+fe_session_callback (struct session *sess)
+{
+}
+void
+fe_server_callback (struct server *serv)
+{
+}
+void
+fe_url_add (const char *text)
+{
+}
+void
+fe_pluginlist_update (void)
+{
+}
+void
+fe_buttons_update (struct session *sess)
+{
+}
+void
+fe_dlgbuttons_update (struct session *sess)
+{
+}
+void
+fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive)
+{
+}
+void
+fe_set_channel (struct session *sess)
+{
+}
+void
+fe_set_title (struct session *sess)
+{
+}
+void
+fe_set_nonchannel (struct session *sess, int state)
+{
+}
+void
+fe_set_nick (struct server *serv, char *newnick)
+{
+}
+void
+fe_change_nick (struct server *serv, char *nick, char *newnick)
+{
+}
+void
+fe_ignore_update (int level)
+{
+}
+int
+fe_dcc_open_recv_win (int passive)
+{
+	return FALSE;
+}
+int
+fe_dcc_open_send_win (int passive)
+{
+	return FALSE;
+}
+int
+fe_dcc_open_chat_win (int passive)
+{
+	return FALSE;
+}
+void
+fe_userlist_hide (session * sess)
+{
+}
+void
+fe_lastlog (session * sess, session * lastlog_sess, char *sstr, gboolean regexp)
+{
+}
+void
+fe_set_lag (server * serv, int lag)
+{
+}
+void
+fe_set_throttle (server * serv)
+{
+}
+void
+fe_set_away (server *serv)
+{
+}
+void
+fe_serverlist_open (session *sess)
+{
+}
+void
+fe_get_str (char *prompt, char *def, void *callback, void *ud)
+{
+}
+void
+fe_get_int (char *prompt, int def, void *callback, void *ud)
+{
+}
+void
+fe_idle_add (void *func, void *data)
+{
+}
+void
+fe_ctrl_gui (session *sess, fe_gui_action action, int arg)
+{
+}
+int
+fe_gui_info (session *sess, int info_type)
+{
+	return -1;
+}
+void *
+fe_gui_info_ptr (session *sess, int info_type)
+{
+	return NULL;
+}
+void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud)
+{
+}
+char *fe_get_inputbox_contents (struct session *sess)
+{
+	return NULL;
+}
+void fe_set_inputbox_contents (struct session *sess, char *text)
+{
+}
+int fe_get_inputbox_cursor (struct session *sess)
+{
+	return 0;
+}
+void fe_set_inputbox_cursor (struct session *sess, int delta, int pos)
+{
+}
+void fe_open_url (const char *url)
+{
+}
+void fe_menu_del (menu_entry *me)
+{
+}
+char *fe_menu_add (menu_entry *me)
+{
+	return NULL;
+}
+void fe_menu_update (menu_entry *me)
+{
+}
+void fe_uselect (struct session *sess, char *word[], int do_clear, int scroll_to)
+{
+}
+void
+fe_server_event (server *serv, int type, int arg)
+{
+}
+void
+fe_flash_window (struct session *sess)
+{
+}
+void fe_get_file (const char *title, char *initial,
+				 void (*callback) (void *userdata, char *file), void *userdata,
+				 int flags)
+{
+}
+void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout){}
+void fe_tray_set_file (const char *filename){}
+void fe_tray_set_icon (feicon icon){}
+void fe_tray_set_tooltip (const char *text){}
+void fe_tray_set_balloon (const char *title, const char *text){}
+void fe_userlist_update (session *sess, struct User *user){}
diff --git a/src/fe-text/fe-text.h b/src/fe-text/fe-text.h
new file mode 100644
index 00000000..b8afa284
--- /dev/null
+++ b/src/fe-text/fe-text.h
@@ -0,0 +1,29 @@
+
+typedef int (*socket_callback) (void *source, int condition, void *user_data);
+typedef int (*timer_callback) (void *user_data);
+
+struct socketeventRec
+{
+	socket_callback callback;
+	void *userdata;
+	int sok;
+	int tag;
+	int rread:1;
+	int wwrite:1;
+	int eexcept:1;
+	int checked:1;
+};
+
+typedef struct socketeventRec socketevent;
+
+
+struct timerRec
+{
+	timer_callback callback;
+	void *userdata;
+	int interval;
+	int tag;
+	guint64 next_call;	/* miliseconds */
+};
+
+typedef struct timerRec timerevent;
diff --git a/src/pixmaps/Makefile.am b/src/pixmaps/Makefile.am
new file mode 100644
index 00000000..5476f9d4
--- /dev/null
+++ b/src/pixmaps/Makefile.am
@@ -0,0 +1,20 @@
+## Process this file with automake to produce Makefile.in
+
+LIST =	traymsgpng $(srcdir)/message.png \
+			trayhilightpng $(srcdir)/highlight.png \
+			trayfilepng $(srcdir)/fileoffer.png \
+			bookpng $(srcdir)/book.png \
+			hoppng $(srcdir)/hop.png \
+			oppng $(srcdir)/op.png \
+			purplepng $(srcdir)/purple.png \
+			redpng $(srcdir)/red.png \
+			voicepng $(srcdir)/voice.png \
+			xchatpng $(srcdir)/../../xchat.png
+
+PNGS = message.png highlight.png fileoffer.png book.png hop.png op.png purple.png red.png voice.png
+noinst_HEADERS = inline_pngs.h
+CLEANFILES = $(noinst_HEADERS)
+EXTRA_DIST = $(PNGS)
+
+inline_pngs.h: $(PNGS)
+	@gdkpixbufcsourcepath@ --raw --build-list $(LIST) > $(srcdir)/inline_pngs.h
diff --git a/src/pixmaps/book.png b/src/pixmaps/book.png
new file mode 100644
index 00000000..1f1e4301
--- /dev/null
+++ b/src/pixmaps/book.png
Binary files differdiff --git a/src/pixmaps/fileoffer.png b/src/pixmaps/fileoffer.png
new file mode 100644
index 00000000..fb16cb22
--- /dev/null
+++ b/src/pixmaps/fileoffer.png
Binary files differdiff --git a/src/pixmaps/highlight.png b/src/pixmaps/highlight.png
new file mode 100644
index 00000000..818f60f1
--- /dev/null
+++ b/src/pixmaps/highlight.png
Binary files differdiff --git a/src/pixmaps/hop.png b/src/pixmaps/hop.png
new file mode 100644
index 00000000..30a84601
--- /dev/null
+++ b/src/pixmaps/hop.png
Binary files differdiff --git a/src/pixmaps/message.png b/src/pixmaps/message.png
new file mode 100644
index 00000000..de09c2ae
--- /dev/null
+++ b/src/pixmaps/message.png
Binary files differdiff --git a/src/pixmaps/op.png b/src/pixmaps/op.png
new file mode 100644
index 00000000..8b2f4f9f
--- /dev/null
+++ b/src/pixmaps/op.png
Binary files differdiff --git a/src/pixmaps/purple.png b/src/pixmaps/purple.png
new file mode 100644
index 00000000..5910d3f0
--- /dev/null
+++ b/src/pixmaps/purple.png
Binary files differdiff --git a/src/pixmaps/red.png b/src/pixmaps/red.png
new file mode 100644
index 00000000..cb940a33
--- /dev/null
+++ b/src/pixmaps/red.png
Binary files differdiff --git a/src/pixmaps/voice.png b/src/pixmaps/voice.png
new file mode 100644
index 00000000..8bbf7a57
--- /dev/null
+++ b/src/pixmaps/voice.png
Binary files differdiff --git a/src/version-script b/src/version-script
new file mode 100644
index 00000000..048c1f55
--- /dev/null
+++ b/src/version-script
@@ -0,0 +1,34 @@
+EXPORTED {
+	global:
+		xchat_hook_command;
+		xchat_hook_server;
+		xchat_hook_print;
+		xchat_hook_timer;
+		xchat_hook_fd;
+		xchat_unhook;
+		xchat_print;
+		xchat_printf;
+		xchat_command;
+		xchat_commandf;
+		xchat_nickcmp;
+		xchat_set_context;
+		xchat_find_context;
+		xchat_get_context;
+		xchat_get_info;
+		xchat_get_prefs;
+		xchat_list_get;
+		xchat_list_free;
+		xchat_list_fields;
+		xchat_list_next;
+		xchat_list_str;
+		xchat_list_int;
+		xchat_plugingui_add;
+		xchat_plugingui_remove;
+		xchat_emit_print;
+		xchat_list_time;
+		xchat_gettext;
+		xchat_send_modes;
+		xchat_strip;
+		xchat_free;
+	local: *;
+};