summary refs log tree commit diff stats
path: root/src/fe-gtk/fe-gtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-gtk/fe-gtk.c')
-rw-r--r--src/fe-gtk/fe-gtk.c1063
1 files changed, 1063 insertions, 0 deletions
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);
+}