diff Plugins/Output/crossfade/configure.c @ 261:5fd398406cf7 trunk

[svn] Still need to pry internal OSS from clenched fingers. In need of anti-warning love too, but it compiles.
author chainsaw
date Tue, 06 Dec 2005 16:09:32 -0800
parents
children 5410de731c3c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Output/crossfade/configure.c	Tue Dec 06 16:09:32 2005 -0800
@@ -0,0 +1,2180 @@
+
+/* 
+ *  XMMS Crossfade Plugin
+ *  Copyright (C) 2000-2004  Peter Eisenlohr <peter@eisenlohr.org>
+ *
+ *  based on the original OSS Output Plugin
+ *  Copyright (C) 1998-2000  Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; 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
+
+#undef PRESET_SUPPORT
+
+#include "crossfade.h"
+#include "configure.h"
+#include "interface.h"
+#include "monitor.h"
+#include "support.h"
+#ifdef HAVE_OSS
+#  include "oss.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_OSS
+#  ifdef HAVE_SYS_SOUNDCARD_H
+#    include <sys/soundcard.h>
+#  elif defined(HAVE_MACHINE_SOUNDCARD_H)
+#    include <machine/soundcard.h>
+#  endif
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+#  include <samplerate.h>
+#endif
+
+
+#define HIDE(name)					\
+{ if((set_wgt = lookup_widget(config_win, name)))	\
+    gtk_widget_hide(set_wgt); }
+
+#define SHOW(name)					\
+{ if((set_wgt = lookup_widget(config_win, name)))	\
+    gtk_widget_show(set_wgt); }
+
+
+#define SETW_SENSITIVE(wgt, sensitive)		\
+  gtk_widget_set_sensitive(wgt, sensitive)
+
+#define SETW_TOGGLE(wgt, active)				\
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wgt), active)
+
+#define SETW_SPIN(wgt, value)					\
+  gtk_spin_button_set_value(GTK_SPIN_BUTTON(wgt), value)
+
+
+#define SET_SENSITIVE(name, sensitive)			\
+{ if((set_wgt = lookup_widget(config_win, name)))	\
+    gtk_widget_set_sensitive(set_wgt, sensitive); }
+
+#define SET_TOGGLE(name, active)					\
+{ if((set_wgt = lookup_widget(config_win, name)))			\
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_wgt), active); }
+
+#define SET_SPIN(name, value)						\
+{ if((set_wgt = lookup_widget(config_win, name)))			\
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(set_wgt), value); }
+
+#define SET_PAGE(name, index)					\
+{ if((set_wgt = lookup_widget(config_win, name)))		\
+    gtk_notebook_set_page(GTK_NOTEBOOK(set_wgt), index); }
+
+#define SET_HISTORY(name, index)					\
+{ if((set_wgt = lookup_widget(config_win, name)))			\
+    gtk_option_menu_set_history(GTK_OPTION_MENU(set_wgt), index); }
+
+
+#define GET_SENSITIVE(name)			\
+((get_wgt = lookup_widget(config_win, name))	\
+  && GTK_WIDGET_SENSITIVE(get_wgt))		\
+
+#define GET_TOGGLE(name)					\
+((get_wgt = lookup_widget(config_win, name))			\
+  && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(get_wgt)))
+
+#define GET_SPIN(name)							\
+((get_wgt = lookup_widget(config_win, name))				\
+  ? gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(get_wgt)) : 0)
+
+
+static GtkWidget *config_win = NULL;
+static GtkWidget *about_win = NULL;
+static GtkWidget *set_wgt;
+static GtkWidget *get_wgt;
+
+/* init with DEFAULT_CFG to make sure all string pointers are set to NULL */
+static config_t _cfg = CONFIG_DEFAULT;
+static config_t *cfg = &_cfg; 
+
+/* some helpers to keep track of the GUI's state */
+static gboolean checking = FALSE;
+static gint            op_index;
+static plugin_config_t op_config;
+static gint            ep_index;
+
+
+static void update_plugin_config(gchar **config_string, gchar *name,
+				 plugin_config_t *pc, gboolean save);
+
+/*****************************************************************************/
+
+void g_free_f(gpointer data, gpointer user_data)
+{
+  g_free(data);
+}
+
+/*****************************************************************************/
+
+#ifdef PRESET_SUPPORT
+static void
+scan_presets(gchar *filename)
+{
+  struct stat stats;
+  FILE       *fh;
+  gchar      *data, **lines, *tmp, *name;
+  int         i;
+
+  if(lstat(filename, &stats)) {
+    DEBUG(("[crossfade] scan_presets: \"%s\":\n", filename));
+    PERROR("[crossfade] scan_presets: lstat");
+    return;
+  }
+  if(stats.st_size <= 0) return;
+
+  if(!(data = g_malloc(stats.st_size + 1))) {
+    DEBUG(("[crossfade] scan_presets: g_malloc(%ld) failed!\n", stats.st_size));
+    return;
+  }
+
+  if(!(fh = fopen(filename, "r"))) {
+    PERROR("[crossfade] scan_presets: fopen");
+    g_free(data);
+    return;
+  }
+
+  if(fread(data, stats.st_size, 1, fh) != 1) {
+    DEBUG(("[crossfade] scan_presets: fread() failed!\n")); 
+    g_free(data);
+    fclose(fh);
+    return;
+  }
+  fclose(fh);
+  data[stats.st_size] = 0;
+
+  lines = g_strsplit(data, "\n", 0);
+  g_free(data);
+
+  if(!lines) {
+    DEBUG(("[crossfade] scan_presets: g_strsplit() failed!\n"));
+    return;
+  }
+
+  g_list_foreach(config->presets, g_free_f, NULL);
+  g_list_free(config->presets);
+  config->presets = NULL;
+
+  for(i=0; lines[i]; i++) {
+    if(lines[i][0] == '[') {
+      if((tmp = strchr(lines[i], ']'))) {
+	*tmp = 0;
+	if((name = g_strdup(lines[i]+1)))
+	  config->presets = g_list_append(config->presets, name);
+      }
+    }
+  }
+
+  g_strfreev(lines);
+}
+#endif
+
+static void
+read_fade_config(ConfigFile *cfgfile, gchar *section, gchar *key, fade_config_t *fc)
+{
+  gchar *s = NULL;
+  gint n;
+  
+  if(!cfgfile || !section || !key || !fc) return;
+
+  xmms_cfg_read_string(cfgfile, section, key, &s);
+  if(!s) return;
+
+  n = sscanf(s,
+	     "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+	     &fc->type,
+	     &fc->pause_len_ms,
+	     &fc->simple_len_ms,
+	     &fc->out_enable,
+	     &fc->out_len_ms,
+	     &fc->out_volume,
+	     &fc->ofs_type,
+	     &fc->ofs_type_wanted,
+	     &fc->ofs_custom_ms,
+	     &fc->in_locked,
+	     &fc->in_enable,
+	     &fc->in_len_ms,
+	     &fc->in_volume,
+	     &fc->flush_pause_enable,
+	     &fc->flush_pause_len_ms,
+	     &fc->flush_in_enable,
+	     &fc->flush_in_len_ms,
+	     &fc->flush_in_volume);
+
+  g_free(s);
+}
+
+static void
+write_fade_config(ConfigFile *cfgfile, gchar *section, gchar *key, fade_config_t *fc)
+{
+  gchar *s;
+  
+  if(!cfgfile || !section || !key || !fc) return;
+
+  s = g_strdup_printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+		      fc->type,
+		      fc->pause_len_ms,
+		      fc->simple_len_ms,
+		      fc->out_enable,
+		      fc->out_len_ms,
+		      fc->out_volume,
+		      fc->ofs_type,
+		      fc->ofs_type_wanted,
+		      fc->ofs_custom_ms,
+		      fc->in_locked,
+		      fc->in_enable,
+		      fc->in_len_ms,
+		      fc->in_volume,
+		      fc->flush_pause_enable,
+		      fc->flush_pause_len_ms,
+		      fc->flush_in_enable,
+		      fc->flush_in_len_ms,
+		      fc->flush_in_volume);
+
+  if(!s) return;
+
+  xmms_cfg_write_string(cfgfile, section, key, s);
+  g_free(s);
+}
+
+void
+xfade_load_config()
+{
+#ifdef PRESET_SUPPORT
+  gchar      *filename;
+#endif
+  gchar      *section = "Crossfade";
+  ConfigFile *cfgfile;
+
+  if((cfgfile = xmms_cfg_open_default_file())) {
+    /* config items used in v0.1 */
+    xmms_cfg_read_int    (cfgfile, section, "output_method",        &config->output_method);
+    xmms_cfg_read_int    (cfgfile, section, "audio_device",         &config->oss_audio_device);
+    xmms_cfg_read_boolean(cfgfile, section, "use_alt_audio_device", &config->oss_use_alt_audio_device);
+    xmms_cfg_read_string (cfgfile, section, "alt_audio_device",     &config->oss_alt_audio_device);
+    xmms_cfg_read_int    (cfgfile, section, "mixer_device",         &config->oss_mixer_device);
+    xmms_cfg_read_string (cfgfile, section, "output_plugin",        &config->op_name);
+    xmms_cfg_read_string (cfgfile, section, "op_config_string",     &config->op_config_string);
+    xmms_cfg_read_int    (cfgfile, section, "buffer_size",          &config->mix_size_ms);
+    xmms_cfg_read_int    (cfgfile, section, "sync_size",            &config->sync_size_ms);
+    xmms_cfg_read_int    (cfgfile, section, "preload_size",         &config->preload_size_ms);
+    xmms_cfg_read_int    (cfgfile, section, "songchange_timeout",   &config->songchange_timeout);
+    xmms_cfg_read_boolean(cfgfile, section, "enable_mixer",         &config->enable_mixer);
+    xmms_cfg_read_boolean(cfgfile, section, "mixer_reverse",        &config->mixer_reverse);
+    xmms_cfg_read_boolean(cfgfile, section, "enable_debug",         &config->enable_debug);
+    xmms_cfg_read_boolean(cfgfile, section, "enable_monitor",       &config->enable_monitor);
+
+    /* config items introduced by v0.2 */
+    xmms_cfg_read_int    (cfgfile, section, "oss_buffer_size",      &config->oss_buffer_size_ms);
+    xmms_cfg_read_int    (cfgfile, section, "oss_preload_size",     &config->oss_preload_size_ms);
+    xmms_cfg_read_boolean(cfgfile, section, "oss_mixer_use_master", &config->oss_mixer_use_master);
+    xmms_cfg_read_boolean(cfgfile, section, "gap_lead_enable",      &config->gap_lead_enable);
+    xmms_cfg_read_int    (cfgfile, section, "gap_lead_len_ms",      &config->gap_lead_len_ms);
+    xmms_cfg_read_int    (cfgfile, section, "gap_lead_level",       &config->gap_lead_level);
+    xmms_cfg_read_boolean(cfgfile, section, "gap_trail_enable",     &config->gap_trail_enable);
+    xmms_cfg_read_int    (cfgfile, section, "gap_trail_len_ms",     &config->gap_trail_len_ms);
+    xmms_cfg_read_int    (cfgfile, section, "gap_trail_level",      &config->gap_trail_level);
+    xmms_cfg_read_int    (cfgfile, section, "gap_trail_locked",     &config->gap_trail_locked);
+    
+    /* config items introduced by v0.2.1 */
+    xmms_cfg_read_boolean(cfgfile, section, "buffer_size_auto",     &config->mix_size_auto);
+    
+    /* config items introduced by v0.2.3 */
+    xmms_cfg_read_boolean(cfgfile, section, "album_detection",      &config->album_detection);
+      
+    /* config items introduced by v0.2.4 */
+    xmms_cfg_read_boolean(cfgfile, section, "http_workaround",      &config->enable_http_workaround);
+    xmms_cfg_read_boolean(cfgfile, section, "enable_op_max_used",   &config->enable_op_max_used);
+    xmms_cfg_read_int    (cfgfile, section, "op_max_used_ms",       &config->op_max_used_ms);
+    
+    /* config items introduced by v0.2.6 */
+    xmms_cfg_read_string (cfgfile, section, "effect_plugin",        &config->ep_name);
+    xmms_cfg_read_boolean(cfgfile, section, "effect_enable",        &config->ep_enable);
+    xmms_cfg_read_int    (cfgfile, section, "output_rate",          &config->output_rate);
+    
+    /* config items introduced by v0.2.7 */
+    xmms_cfg_read_boolean(cfgfile, section, "oss_maxbuf_enable",    &config->oss_maxbuf_enable);
+
+    /* config items introduced by v0.3.0 */
+    xmms_cfg_read_boolean(cfgfile, section, "use_alt_mixer_device", &config->oss_use_alt_mixer_device);
+    xmms_cfg_read_int    (cfgfile, section, "oss_fragments",        &config->oss_fragments);
+    xmms_cfg_read_int    (cfgfile, section, "oss_fragment_size",    &config->oss_fragment_size);
+    xmms_cfg_read_boolean(cfgfile, section, "volnorm_enable",       &config->volnorm_enable);
+    xmms_cfg_read_boolean(cfgfile, section, "volnorm_use_qa",       &config->volnorm_use_qa);
+    xmms_cfg_read_int    (cfgfile, section, "volnorm_target",       &config->volnorm_target);
+    xmms_cfg_read_boolean(cfgfile, section, "output_keep_opened",   &config->output_keep_opened);
+    xmms_cfg_read_boolean(cfgfile, section, "mixer_software",       &config->mixer_software);
+    xmms_cfg_read_int    (cfgfile, section, "mixer_vol_left",       &config->mixer_vol_left);
+    xmms_cfg_read_int    (cfgfile, section, "mixer_vol_right",      &config->mixer_vol_right);
+    
+    /* config items introduced by v0.3.2 */
+    xmms_cfg_read_boolean(cfgfile, section, "no_xfade_if_same_file",&config->no_xfade_if_same_file);
+
+    /* config items introduced by v0.3.3 */
+    xmms_cfg_read_string (cfgfile, section, "alt_mixer_device",     &config->oss_alt_mixer_device);
+    xmms_cfg_read_boolean(cfgfile, section, "gap_crossing",         &config->gap_crossing);
+
+    /* config items introduced by v0.3.6 */
+    xmms_cfg_read_int    (cfgfile, section, "output_quality",       &config->output_quality);
+
+    /* fade configs */
+    read_fade_config(cfgfile, section, "fc_xfade",   &config->fc[FADE_CONFIG_XFADE]);
+    read_fade_config(cfgfile, section, "fc_manual",  &config->fc[FADE_CONFIG_MANUAL]);
+    read_fade_config(cfgfile, section, "fc_album",   &config->fc[FADE_CONFIG_ALBUM]);
+    read_fade_config(cfgfile, section, "fc_start",   &config->fc[FADE_CONFIG_START]);
+    read_fade_config(cfgfile, section, "fc_stop",    &config->fc[FADE_CONFIG_STOP]);
+    read_fade_config(cfgfile, section, "fc_eop",     &config->fc[FADE_CONFIG_EOP]);
+    read_fade_config(cfgfile, section, "fc_seek",    &config->fc[FADE_CONFIG_SEEK]);
+    read_fade_config(cfgfile, section, "fc_pause",   &config->fc[FADE_CONFIG_PAUSE]);
+    
+    xmms_cfg_free(cfgfile);
+    DEBUG(("[crossfade] load_config: configuration loaded\n"));
+  }
+  else
+    DEBUG(("[crossfade] load_config: error loading config, using defaults\n"));
+
+#ifdef PRESET_SUPPORT
+  filename = g_strconcat(g_get_home_dir(), "/.xmms/xmms-crossfade-presets", NULL);
+  scan_presets(filename);
+  g_free(filename);
+#endif
+}
+
+void
+xfade_save_config()
+{
+  gchar      *section = "Crossfade";
+  ConfigFile *cfgfile;
+
+  if((cfgfile = xmms_cfg_open_default_file())) {
+    /* obsolete config items */
+    xmms_cfg_remove_key(cfgfile, section, "underrun_pct");
+    xmms_cfg_remove_key(cfgfile, section, "enable_crossfade");
+    xmms_cfg_remove_key(cfgfile, section, "enable_gapkiller");
+    xmms_cfg_remove_key(cfgfile, section, "mixer_use_master");
+    xmms_cfg_remove_key(cfgfile, section, "late_effect");
+    xmms_cfg_remove_key(cfgfile, section, "gap_lead_length");
+      
+    /* config items used in v0.1 */
+    xmms_cfg_write_int    (cfgfile, section, "output_method",        config->output_method);
+    xmms_cfg_write_int    (cfgfile, section, "audio_device",         config->oss_audio_device);
+    xmms_cfg_write_boolean(cfgfile, section, "use_alt_audio_device", config->oss_use_alt_audio_device);
+    xmms_cfg_write_string (cfgfile, section, "alt_audio_device",     config->oss_alt_audio_device ? config->oss_alt_audio_device : DEFAULT_OSS_ALT_AUDIO_DEVICE);
+    xmms_cfg_write_int    (cfgfile, section, "mixer_device",         config->oss_mixer_device);
+    xmms_cfg_write_string (cfgfile, section, "output_plugin",        config->op_name ? config->op_name : DEFAULT_OP_NAME);
+    xmms_cfg_write_string (cfgfile, section, "op_config_string",     config->op_config_string ? config->op_config_string : DEFAULT_OP_CONFIG_STRING);
+    xmms_cfg_write_int    (cfgfile, section, "buffer_size",          config->mix_size_ms);
+    xmms_cfg_write_int    (cfgfile, section, "sync_size",            config->sync_size_ms);
+    xmms_cfg_write_int    (cfgfile, section, "preload_size",         config->preload_size_ms);
+    xmms_cfg_write_int    (cfgfile, section, "songchange_timeout",   config->songchange_timeout);
+    xmms_cfg_write_boolean(cfgfile, section, "enable_mixer",         config->enable_mixer);
+    xmms_cfg_write_boolean(cfgfile, section, "mixer_reverse",        config->mixer_reverse);
+    xmms_cfg_write_boolean(cfgfile, section, "enable_debug",         config->enable_debug);
+    xmms_cfg_write_boolean(cfgfile, section, "enable_monitor",       config->enable_monitor);
+    
+    /* config items introduced by v0.2 */
+    xmms_cfg_write_int    (cfgfile, section, "oss_buffer_size",      config->oss_buffer_size_ms);
+    xmms_cfg_write_int    (cfgfile, section, "oss_preload_size",     config->oss_preload_size_ms);
+    xmms_cfg_write_boolean(cfgfile, section, "oss_mixer_use_master", config->oss_mixer_use_master);
+    xmms_cfg_write_boolean(cfgfile, section, "gap_lead_enable",      config->gap_lead_enable);
+    xmms_cfg_write_int    (cfgfile, section, "gap_lead_len_ms",      config->gap_lead_len_ms);
+    xmms_cfg_write_int    (cfgfile, section, "gap_lead_level",       config->gap_lead_level);
+    xmms_cfg_write_boolean(cfgfile, section, "gap_trail_enable",     config->gap_trail_enable);
+    xmms_cfg_write_int    (cfgfile, section, "gap_trail_len_ms",     config->gap_trail_len_ms);
+    xmms_cfg_write_int    (cfgfile, section, "gap_trail_level",      config->gap_trail_level);
+    xmms_cfg_write_int    (cfgfile, section, "gap_trail_locked",     config->gap_trail_locked);
+    
+    /* config items introduced by v0.2.1 */
+    xmms_cfg_write_boolean(cfgfile, section, "buffer_size_auto",     config->mix_size_auto);
+    
+    /* config items introduced by v0.2.3 */
+    xmms_cfg_write_boolean(cfgfile, section, "album_detection",      config->album_detection);
+    
+    /* config items introduced by v0.2.4 */
+    xmms_cfg_write_boolean(cfgfile, section, "http_workaround",      config->enable_http_workaround);
+    xmms_cfg_write_boolean(cfgfile, section, "enable_op_max_used",   config->enable_op_max_used);
+    xmms_cfg_write_int    (cfgfile, section, "op_max_used_ms",       config->op_max_used_ms);
+    
+    /* config items introduced by v0.2.6 */
+    xmms_cfg_write_string (cfgfile, section, "effect_plugin",        config->ep_name ? config->ep_name : DEFAULT_EP_NAME);
+    xmms_cfg_write_boolean(cfgfile, section, "effect_enable",        config->ep_enable);
+    xmms_cfg_write_int    (cfgfile, section, "output_rate",          config->output_rate);
+   
+    /* config items introduced by v0.2.7 */
+    xmms_cfg_write_boolean(cfgfile, section, "oss_maxbuf_enable",    config->oss_maxbuf_enable);
+    
+    /* config items introduced by v0.3.0 */
+    xmms_cfg_write_boolean(cfgfile, section, "use_alt_mixer_device", config->oss_use_alt_mixer_device);
+    xmms_cfg_write_int    (cfgfile, section, "oss_fragments",        config->oss_fragments);
+    xmms_cfg_write_int    (cfgfile, section, "oss_fragment_size",    config->oss_fragment_size);
+#ifdef VOLUME_NORMALIZER
+    xmms_cfg_write_boolean(cfgfile, section, "volnorm_enable",       config->volnorm_enable);
+    xmms_cfg_write_boolean(cfgfile, section, "volnorm_use_qa",       config->volnorm_use_qa);
+    xmms_cfg_write_int    (cfgfile, section, "volnorm_target",       config->volnorm_target);
+#endif
+    xmms_cfg_write_boolean(cfgfile, section, "output_keep_opened",   config->output_keep_opened);
+    xmms_cfg_write_boolean(cfgfile, section, "mixer_software",       config->mixer_software);
+    xmms_cfg_write_int    (cfgfile, section, "mixer_vol_left",       config->mixer_vol_left);
+    xmms_cfg_write_int    (cfgfile, section, "mixer_vol_right",      config->mixer_vol_right);
+
+    /* config items introduced by v0.3.2 */
+    xmms_cfg_write_boolean(cfgfile, section, "no_xfade_if_same_file",config->no_xfade_if_same_file);
+
+    /* config items introduced by v0.3.2 */
+    xmms_cfg_write_string (cfgfile, section, "alt_mixer_device",     config->oss_alt_mixer_device ? config->oss_alt_mixer_device : DEFAULT_OSS_ALT_MIXER_DEVICE);
+    xmms_cfg_write_boolean(cfgfile, section, "gap_crossing",         config->gap_crossing);
+
+    /* config items introduced by v0.3.6 */
+    xmms_cfg_write_int    (cfgfile, section, "output_quality",       config->output_quality);
+
+    /* fade configs */
+    write_fade_config(cfgfile, section, "fc_xfade",   &config->fc[FADE_CONFIG_XFADE]);
+    write_fade_config(cfgfile, section, "fc_manual",  &config->fc[FADE_CONFIG_MANUAL]);
+    write_fade_config(cfgfile, section, "fc_album",   &config->fc[FADE_CONFIG_ALBUM]);
+    write_fade_config(cfgfile, section, "fc_start",   &config->fc[FADE_CONFIG_START]);
+    write_fade_config(cfgfile, section, "fc_stop",    &config->fc[FADE_CONFIG_STOP]);
+    write_fade_config(cfgfile, section, "fc_eop",     &config->fc[FADE_CONFIG_EOP]);
+    write_fade_config(cfgfile, section, "fc_seek",    &config->fc[FADE_CONFIG_SEEK]);
+    write_fade_config(cfgfile, section, "fc_pause",   &config->fc[FADE_CONFIG_PAUSE]);
+    
+    xmms_cfg_write_default_file(cfgfile);
+    xmms_cfg_free              (cfgfile);
+    DEBUG(("[crossfade] save_config: configuration saved\n"));
+  }
+  else
+    DEBUG(("[crossfade] save_config: error saving configuration!\n"));
+}
+
+#define SAFE_FREE(x) if(x) { g_free(x); x = NULL; }
+void
+xfade_free_config()
+{
+  SAFE_FREE(cfg->oss_alt_audio_device);
+  SAFE_FREE(cfg->oss_alt_mixer_device);
+  SAFE_FREE(cfg->op_config_string);
+  SAFE_FREE(cfg->op_name);
+
+  g_list_foreach(config->presets, g_free_f, NULL);
+  g_list_free(config->presets);
+  config->presets = NULL;
+}
+
+void
+xfade_load_plugin_config(gchar *config_string,
+			 gchar *plugin_name,
+			 plugin_config_t *plugin_config)
+{
+  update_plugin_config(&config_string, plugin_name, plugin_config, FALSE);
+}
+
+void
+xfade_save_plugin_config(gchar **config_string,
+			 gchar *plugin_name,
+			 plugin_config_t *plugin_config)
+{
+  update_plugin_config(config_string, plugin_name, plugin_config, TRUE);
+}
+
+/*** helpers *****************************************************************/
+
+gint
+xfade_cfg_fadeout_len(fade_config_t *fc)
+{
+  if(!fc) return 0;
+  switch(fc->type) {
+  case FADE_TYPE_SIMPLE_XF:
+    return fc->simple_len_ms;
+  case FADE_TYPE_ADVANCED_XF:
+    return fc->out_enable ? fc->out_len_ms : 0;
+  case FADE_TYPE_FADEOUT:
+  case FADE_TYPE_PAUSE_ADV:
+    return fc->out_len_ms;
+  }
+  return 0;
+}
+
+gint
+xfade_cfg_fadeout_volume(fade_config_t *fc)
+{
+  gint volume;
+  if(!fc) return 0;
+  switch(fc->type) {
+  case FADE_TYPE_ADVANCED_XF:
+  case FADE_TYPE_FADEOUT:
+    volume = fc->out_volume;
+    if(volume <   0) volume =   0;
+    if(volume > 100) volume = 100;
+    return volume;
+  }
+  return 0;
+}
+
+gint
+xfade_cfg_offset(fade_config_t *fc)
+{
+  if(!fc) return 0;
+  switch(fc->type) {
+  case FADE_TYPE_FLUSH:
+    return fc->flush_pause_enable ? fc->flush_pause_len_ms : 0;
+  case FADE_TYPE_PAUSE:
+    return fc->pause_len_ms;
+  case FADE_TYPE_SIMPLE_XF:
+    return -fc->simple_len_ms;
+  case FADE_TYPE_ADVANCED_XF:
+    switch(fc->ofs_type) {
+    case FC_OFFSET_LOCK_OUT:
+      return -fc->out_len_ms;
+    case FC_OFFSET_LOCK_IN:
+      return -fc->in_len_ms;
+    case FC_OFFSET_CUSTOM:
+      return fc->ofs_custom_ms;
+    }
+    return 0;
+  case FADE_TYPE_FADEOUT:
+  case FADE_TYPE_PAUSE_ADV:
+    return fc->ofs_custom_ms;
+  }
+  return 0;
+}
+
+gint
+xfade_cfg_fadein_len(fade_config_t *fc)
+{
+  if(!fc) return 0;
+  switch(fc->type) {
+  case FADE_TYPE_FLUSH:
+    return fc->flush_in_enable ? fc->flush_in_len_ms : 0;
+  case FADE_TYPE_SIMPLE_XF:
+    return fc->simple_len_ms;
+  case FADE_TYPE_ADVANCED_XF:
+    return
+      fc->in_locked
+      ? (fc->out_enable ? fc->out_len_ms : 0)
+      : (fc->in_enable  ? fc->in_len_ms  : 0);
+  case FADE_TYPE_FADEIN:
+  case FADE_TYPE_PAUSE_ADV:
+    return fc->in_len_ms;
+  }
+  return 0;
+}
+
+gint
+xfade_cfg_fadein_volume(fade_config_t *fc)
+{
+  gint volume;
+  if(!fc) return 0;
+  switch(fc->type) {
+  case FADE_TYPE_FLUSH:
+    volume = fc->flush_in_volume;
+    break;
+  case FADE_TYPE_ADVANCED_XF:
+    volume = fc->in_locked ? fc->out_volume : fc->in_volume;
+    break;
+  case FADE_TYPE_FADEIN:
+    volume = fc->in_volume;
+    break;
+  default:
+    volume = 0;
+  }
+  if(volume <   0) volume =   0;
+  if(volume > 100) volume = 100;
+  return volume;
+}
+
+gboolean
+xfade_cfg_gap_trail_enable(config_t *cfg)
+{
+  return cfg->gap_trail_locked
+    ? cfg->gap_lead_enable
+    : cfg->gap_trail_enable;
+}
+
+gint
+xfade_cfg_gap_trail_len(config_t *cfg)
+{
+  if(!xfade_cfg_gap_trail_enable(cfg)) return 0;
+  return cfg->gap_trail_locked
+    ? cfg->gap_lead_len_ms
+    : cfg->gap_trail_len_ms;
+}
+
+gint
+xfade_cfg_gap_trail_level(config_t *cfg)
+{
+  return cfg->gap_trail_locked
+    ? cfg->gap_lead_level
+    : cfg->gap_trail_level;
+}
+
+gint
+xfade_mix_size_ms(config_t *cfg)
+{
+  if(cfg->mix_size_auto) {
+    gint i, min_size = 0;
+
+    for(i=0; i<MAX_FADE_CONFIGS; i++) {
+      gint size   = xfade_cfg_fadeout_len(&cfg->fc[i]);
+      gint offset = xfade_cfg_offset(&cfg->fc[i]);
+
+      if(cfg->fc[i].type == FADE_TYPE_PAUSE_ADV)
+	size += xfade_cfg_fadein_len(&cfg->fc[i]);
+
+      if(size < -offset)
+	size = -offset;
+
+      if(size > min_size)
+	min_size = size;
+    }
+    return min_size += xfade_cfg_gap_trail_len(cfg)
+      +                cfg->songchange_timeout;
+  }
+  else
+    return cfg->mix_size_ms;
+}
+
+/*** internal helpers ********************************************************/
+
+static void
+add_menu_item(GtkWidget *menu, gchar *title, GtkSignalFunc func, gint index, gint **imap)
+{
+  GtkWidget *item;
+  if(!menu || !title || !func) return;
+  item = gtk_menu_item_new_with_label(title);
+  gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(func), (gpointer)index);
+  gtk_widget_show(item);
+  gtk_menu_append(GTK_MENU(menu), item);
+
+  if(imap) *((*imap)++) = index;
+}
+
+/*** output method ***********************************************************/
+
+/*-- callbacks --------------------------------------------------------------*/
+
+void on_output_oss_radio_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  SET_PAGE("output_notebook", 0);
+  cfg->output_method = OUTPUT_METHOD_BUILTIN_OSS;
+}
+
+void on_output_plugin_radio_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  SET_PAGE("output_notebook", 1);
+  cfg->output_method = OUTPUT_METHOD_PLUGIN;
+}
+
+void on_output_none_radio_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  SET_PAGE("output_notebook", 2);
+  cfg->output_method = OUTPUT_METHOD_BUILTIN_NULL;
+}
+
+static void resampling_rate_cb(GtkWidget *widget, gint index)
+{
+  cfg->output_rate = index;
+}
+
+#ifdef HAVE_LIBSAMPLERATE
+static void resampling_quality_cb(GtkWidget *widget, gint index)
+{
+  cfg->output_quality = index;
+}
+#endif
+
+/*** oss output **************************************************************/
+
+static void
+scan_devices(gchar *type, GtkWidget *option_menu, GtkSignalFunc signal_f)
+{
+#ifdef HAVE_OSS
+  gchar buffer[256];
+  FILE *file;
+
+  GtkWidget *item;
+  gboolean   found = FALSE;
+  gint       type_len = strlen(type);
+#endif
+
+  GtkWidget *menu;
+  gint  index = 0;
+  gint  mixer = 0;
+
+  menu = gtk_menu_new();
+
+#ifdef HAVE_OSS
+  /* look for devices in /dev/sndstat or /proc/asound/sndstat (OSS style) */
+  if((file = fopen("/dev/sndstat", "r")) ||
+     (file = fopen("/proc/asound/sndstat", "r")) ||
+     (file = fopen("/proc/asound/oss/sndstat", "r"))) {
+    while(fgets(buffer, sizeof(buffer), file)) {
+      gint i = strlen(buffer)-1;
+      while((i >= 0) && isspace(buffer[i])) buffer[i--] = 0;
+      if(found) {
+	if(!buffer[0] || !isdigit(buffer[0])) break;
+	if(index == 0) {
+	  gchar *label, *p = strchr(buffer, ':');
+	  if(p) do p++; while(*p == ' ');
+	  else p = buffer;
+	  label = g_strdup_printf("Default (%s)", p);
+	  item = gtk_menu_item_new_with_label(label);
+	  g_free(label);
+	}
+	else item = gtk_menu_item_new_with_label(buffer);
+	
+	gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(signal_f),
+			   (gpointer)index);
+	gtk_widget_show(item);
+	gtk_menu_append(GTK_MENU(menu), item);
+	index++;
+      }
+      else if(!strcmp(buffer, type))
+	found = TRUE;
+      else if(!strncmp(buffer, type, type_len))
+	DEBUG(("[crossfade] scan_devices: %s\n", buffer));
+    }
+    fclose(file);
+    
+    if(!found)
+      DEBUG(("[crossfade] scan_devices: section \"%s\" not found!\n", type));
+  }
+  else {
+    DEBUG(("[crossfade] scan_devices: no sndstat found!\n"));
+#ifdef SOUND_MIXER_INFO
+    /* from xmms-3dse7 by Frank Cornelis */
+    DEBUG(("[crossfade] scan_devices: using alternate method...\n"));
+    for(;;) {
+      gchar      dev_name[32];
+      int        fd;
+      mixer_info info;
+      gchar     *label;
+      
+      if(mixer != 0)
+	sprintf(dev_name, "/dev/mixer%d", mixer);
+      else
+	strcpy(dev_name, "/dev/mixer");
+      
+      if((fd = open(dev_name, O_RDONLY)) != -1) {
+	if(ioctl(fd, SOUND_MIXER_INFO, &info) != -1) {
+	  label = g_strdup_printf(index ? "%s" : "Default (%s)", info.name);
+	  add_menu_item(menu, label, signal_f, index, NULL);
+	  g_free(label);
+	  index++;
+	}
+	close(fd);
+      }
+      else break;
+      mixer++;
+    }
+#endif                                                                       
+  }
+#endif  /* HAVE_OSS */
+  
+  /* create default entry if no device(s) could be found */
+  if(index == 0) add_menu_item(menu, "Default", signal_f, 0, NULL);
+
+  /* attach menu */
+  gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu);
+}
+
+/*-- oss output callbacks ---------------------------------------------------*/
+
+void check_oss_dependencies()
+{
+  if(checking) return;
+  checking = TRUE;
+
+  SET_SENSITIVE("oss_adevice_optionmenu", !cfg->oss_use_alt_audio_device);
+  SET_SENSITIVE("oss_adevice_alt_entry",   cfg->oss_use_alt_audio_device);
+
+  SET_SENSITIVE("oss_mdevice_optionmenu", !cfg->oss_use_alt_mixer_device);
+  SET_SENSITIVE("oss_mdevice_alt_entry",   cfg->oss_use_alt_mixer_device);
+
+  SET_SENSITIVE("osshwb_fragments_label", !cfg->oss_maxbuf_enable);
+  SET_SENSITIVE("osshwb_fragments_spin",  !cfg->oss_maxbuf_enable);
+  SET_SENSITIVE("osshwb_fragsize_label",  !cfg->oss_maxbuf_enable);
+  SET_SENSITIVE("osshwb_fragsize_spin",   !cfg->oss_maxbuf_enable);
+
+  checking = FALSE;
+}
+
+void config_adevice_cb(GtkWidget *widget, gint device)
+{
+  cfg->oss_audio_device = device;
+}
+
+void config_mdevice_cb(GtkWidget *widget, gint device)
+{
+  cfg->oss_mixer_device = device;
+}
+
+void on_config_adevice_alt_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->oss_use_alt_audio_device = gtk_toggle_button_get_active(togglebutton);
+  check_oss_dependencies();
+}
+
+void on_config_mdevice_alt_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->oss_use_alt_mixer_device = gtk_toggle_button_get_active(togglebutton);
+  check_oss_dependencies();
+}
+
+void on_osshwb_maxbuf_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->oss_maxbuf_enable = gtk_toggle_button_get_active(togglebutton);
+  check_oss_dependencies();
+}
+
+/*** plugin output ***********************************************************/
+
+static gchar *
+strip(gchar *s)
+{
+  gchar *p;
+  if(!s) return NULL;
+  for(; *s == ' '; s++);
+  if(!*s) return s;
+  for(p = s+strlen(s)-1; *p == ' '; p--);
+  *++p = 0;
+  return s;
+}
+
+static void
+update_plugin_config(gchar **config_string, gchar *name,
+		     plugin_config_t *pc, gboolean save)
+{
+  plugin_config_t default_pc = DEFAULT_OP_CONFIG;
+
+  gchar *buffer = NULL;
+  gchar  out[1024];
+
+  gboolean plugin_found = FALSE;
+  gchar   *plugin, *next_plugin;
+  gchar   *args;
+
+  if(pc && !save) *pc = default_pc;
+  if(!config_string || !*config_string || !name || !pc) {
+    DEBUG(("[crossfade] update_plugin_config: missing arg!\n"));
+    return;
+  }
+
+  buffer = g_strdup(*config_string);
+  out[0] = 0;
+
+  for(plugin = buffer; plugin; plugin = next_plugin) {
+    if((next_plugin = strchr(plugin, ';'))) *next_plugin++ = 0;
+    if((args = strchr(plugin, '='))) *args++ = 0;
+    plugin = strip(plugin);
+    if(!*plugin || !args || !*args) continue;
+
+    if(save) {
+      if(0 == strcmp(plugin, name)) continue;
+      if(*out) strcat(out, "; ");
+      strcat(out, plugin);
+      strcat(out, "=");
+      strcat(out, args);
+      continue;
+    }
+    else if(strcmp(plugin, name)) continue;
+
+    args = strip(args);
+    sscanf(args, "%d,%d,%d,%d",
+	   &pc->throttle_enable,
+	   &pc->max_write_enable,
+	   &pc->max_write_len,
+	   &pc->force_reopen);
+    pc->max_write_len &= -4;
+    plugin_found = TRUE;
+  }
+
+  if(save) {
+    /* only save if settings differ from defaults */
+    if((  pc->throttle_enable  != default_pc.throttle_enable)
+       ||(pc->max_write_enable != default_pc.max_write_enable)
+       ||(pc->max_write_len    != default_pc.max_write_len)
+       ||(pc->force_reopen     != default_pc.force_reopen)) {
+      if(*out) strcat(out, "; ");
+      sprintf(out + strlen(out), "%s=%d,%d,%d,%d", name,
+	      pc->throttle_enable ? 1 : 0,
+	      pc->max_write_enable ? 1 : 0,
+	      pc->max_write_len,
+	      pc->force_reopen);
+    }
+    if(*config_string) g_free(*config_string);
+    *config_string = g_strdup(out);
+  }
+
+  g_free(buffer);
+}
+
+static void
+config_plugin_cb(GtkWidget *widget, gint index);
+
+static gint
+scan_plugins(GtkWidget *option_menu, gchar *selected)
+{
+  GtkWidget *menu = gtk_menu_new();
+  GList     *list = g_list_first(get_output_list());  /* XMMS */
+  gint      index = 0;
+  gint  sel_index = -1;
+  gint  def_index = -1;
+
+  /* sanity check */
+  if(selected == NULL) selected = "";
+
+  /* parse module list */
+  while(list) {
+    OutputPlugin *op = (OutputPlugin *)list->data;
+    GtkWidget  *item = gtk_menu_item_new_with_label(op->description);
+    
+    if(op == get_crossfade_oplugin_info())  /* disable selecting ourselves */
+      gtk_widget_set_sensitive(item, FALSE);
+    else {
+      if(def_index == -1) def_index = index;
+      if(selected && !strcmp(g_basename(op->filename), selected))
+	sel_index = index;
+    }
+
+    /* create menu item */
+    gtk_signal_connect(GTK_OBJECT(item), "activate",
+		       GTK_SIGNAL_FUNC(config_plugin_cb), (gpointer)index++);
+    gtk_widget_show(item);
+    gtk_menu_append(GTK_MENU(menu), item);
+
+    /* advance to next module */
+    list = g_list_next(list);
+  }
+    
+  /* attach menu */
+  gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu);
+  
+  if(sel_index == -1) {
+    DEBUG(("[crossfade] scan_plugins: plugin not found (\"%s\")\n", selected));
+    return def_index;  /* use default (first entry) */
+  }
+  return sel_index;
+}
+
+/*-- plugin output callbacks ------------------------------------------------*/
+
+static void
+config_plugin_cb(GtkWidget *widget, gint index)
+{
+  OutputPlugin *op = g_list_nth_data(get_output_list(), index);  /* XMMS */
+
+  /* get plugin options from gui */
+  op_config.throttle_enable  = GET_TOGGLE("op_throttle_check");
+  op_config.max_write_enable = GET_TOGGLE("op_maxblock_check");
+  op_config.max_write_len    = GET_SPIN  ("op_maxblock_spin");
+  op_config.force_reopen     = GET_TOGGLE("op_forcereopen_check");
+
+  /* config -> string */
+  xfade_save_plugin_config(&cfg->op_config_string, cfg->op_name, &op_config);
+
+  /* select new plugin */
+  op_index = index;
+
+  /* get new plugin's name */
+  if(cfg->op_name) g_free(cfg->op_name);
+  cfg->op_name = (op && op->filename) 
+    ? g_strdup(g_basename(op->filename)) : NULL;
+
+  /* string -> config */
+  xfade_load_plugin_config(cfg->op_config_string, cfg->op_name, &op_config);
+
+  /* update gui */
+  SET_SENSITIVE("op_configure_button",  op && (op->configure != NULL)); 
+  SET_SENSITIVE("op_about_button",      op && (op->about != NULL));
+  SET_TOGGLE   ("op_throttle_check",    op_config.throttle_enable);
+  SET_TOGGLE   ("op_maxblock_check",    op_config.max_write_enable);
+  SET_SPIN     ("op_maxblock_spin",     op_config.max_write_len); 
+  SET_SENSITIVE("op_maxblock_spin",     op_config.max_write_enable);
+  SET_TOGGLE   ("op_forcereopen_check", op_config.force_reopen);
+}
+
+void on_output_plugin_configure_button_clicked (GtkButton *button, gpointer user_data)
+{
+  OutputPlugin *op = g_list_nth_data(get_output_list(), op_index);  /* XMMS */
+  if((op == NULL) || (op->configure == NULL)) return;
+  op->configure();
+}
+
+void on_output_plugin_about_button_clicked(GtkButton *button, gpointer user_data)
+{
+  OutputPlugin *op = g_list_nth_data(get_output_list(), op_index);  /* XMMS */
+  if((op == NULL) || (op->about == NULL)) return;
+  op->about();
+}
+
+void on_op_throttle_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  op_config.throttle_enable = gtk_toggle_button_get_active(togglebutton);
+}
+
+void on_op_maxblock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  op_config.max_write_enable = gtk_toggle_button_get_active(togglebutton);
+  SET_SENSITIVE("op_maxblock_spin", op_config.max_write_enable);
+}
+
+void on_op_maxblock_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  op_config.max_write_len = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+}
+
+void on_op_forcereopen_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  op_config.max_write_enable = gtk_toggle_button_get_active(togglebutton);
+}
+
+/*** effects *****************************************************************/
+
+static void
+config_effect_plugin_cb(GtkWidget *widget, gint index);
+
+static gint
+scan_effect_plugins(GtkWidget *option_menu, gchar *selected)
+{
+  GtkWidget *menu = gtk_menu_new();
+  GList     *list = g_list_first(get_effect_list());  /* XMMS */
+  gint      index = 0;
+  gint  sel_index = -1;
+  gint  def_index = -1;
+
+  /* sanity check */
+  if(selected == NULL) selected = "";
+
+  /* parse module list */
+  while(list) {
+    EffectPlugin *ep = (EffectPlugin *)list->data;
+    GtkWidget  *item = gtk_menu_item_new_with_label(ep->description);
+    
+    if(def_index == -1) def_index = index;
+    if(selected && !strcmp(g_basename(ep->filename), selected))
+      sel_index = index;
+
+    /* create menu item */
+    gtk_signal_connect(GTK_OBJECT(item), "activate",
+		       GTK_SIGNAL_FUNC(config_effect_plugin_cb), (gpointer)index++);
+    gtk_widget_show(item);
+    gtk_menu_append(GTK_MENU(menu), item);
+
+    /* advance to next module */
+    list = g_list_next(list);
+  }
+    
+  /* attach menu */
+  gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu);
+  
+  if(sel_index == -1) {
+    DEBUG(("[crossfade] scan_effect_plugins: plugin not found (\"%s\")\n", selected));
+    return def_index;  /* use default (first entry) */
+  }
+  return sel_index;
+}
+
+/*-- plugin output callbacks ------------------------------------------------*/
+
+static void
+config_effect_plugin_cb(GtkWidget *widget, gint index)
+{
+  EffectPlugin *ep = g_list_nth_data(get_effect_list(), index);  /* XMMS */
+
+  /* select new plugin */
+  ep_index = index;
+
+  /* get new plugin's name */
+  if(cfg->ep_name) g_free(cfg->ep_name);
+  cfg->ep_name = (ep && ep->filename) 
+    ? g_strdup(g_basename(ep->filename)) : NULL;
+
+  /* update gui */
+  SET_SENSITIVE("ep_configure_button", ep && (ep->configure != NULL)); 
+  SET_SENSITIVE("ep_about_button",     ep && (ep->about != NULL));
+
+  /* 0.3.5: apply effect config immediatelly */
+  if(config->ep_name) g_free(config->ep_name);
+  config->ep_name = g_strdup(cfg->ep_name);
+  xfade_realize_ep_config();
+}
+
+void on_ep_configure_button_clicked(GtkButton *button, gpointer user_data)
+{
+  EffectPlugin *ep = g_list_nth_data(get_effect_list(), ep_index);  /* XMMS */
+  if((ep == NULL) || (ep->configure == NULL)) return;
+  ep->configure();
+}
+
+void on_ep_about_button_clicked(GtkButton *button, gpointer user_data)
+{
+  EffectPlugin *ep = g_list_nth_data(get_effect_list(), ep_index);  /* XMMS */
+  if((ep == NULL) || (ep->about == NULL)) return;
+  ep->about();
+}
+
+void on_ep_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  /* 0.3.5: apply effect config immediatelly */
+  config->ep_enable = cfg->ep_enable = GET_TOGGLE("ep_enable_check");
+  xfade_realize_ep_config();
+}
+
+/*-- volume normalizer ------------------------------------------------------*/
+
+void check_effects_dependencies()
+{
+  if(checking) return;
+  checking = TRUE;
+
+  SET_SENSITIVE("volnorm_target_spin",      cfg->volnorm_enable);
+  SET_SENSITIVE("volnorm_target_label",     cfg->volnorm_enable);
+  SET_SENSITIVE("volnorm_quantaudio_check", cfg->volnorm_enable);
+  SET_SENSITIVE("volnorm_target_spin",      cfg->volnorm_enable);
+
+  checking = FALSE;
+}
+
+void on_volnorm_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->volnorm_enable = gtk_toggle_button_get_active(togglebutton);
+  check_effects_dependencies();
+}
+
+/*** crossfader **************************************************************/
+
+static void xf_config_cb(GtkWidget *widget, gint index);
+static void xf_type_cb  (GtkWidget *widget, gint index);
+
+/* crude hack to keep track of menu items */
+static gint xf_config_index_map[MAX_FADE_CONFIGS];
+static gint xf_type_index_map  [MAX_FADE_TYPES];
+
+static void
+create_crossfader_config_menu()
+{
+  GtkWidget *optionmenu, *menu;
+  gint i, *imap;
+
+  if((optionmenu = lookup_widget(config_win, "xf_config_optionmenu"))) {
+    for(i=0; i<MAX_FADE_CONFIGS; i++) xf_config_index_map[i] = -1;
+    imap = xf_config_index_map;
+    menu = gtk_menu_new();
+    add_menu_item(menu, "Start of playback",    xf_config_cb, FADE_CONFIG_START, &imap);
+    add_menu_item(menu, "Automatic songchange", xf_config_cb, FADE_CONFIG_XFADE, &imap);
+#if 0
+    /* this should be FADE_TYPE_NONE all the time, anyway,
+       so no need to make it configureable by the user */
+    add_menu_item(menu, "Automatic (gapless)",  xf_config_cb, FADE_CONFIG_ALBUM, &imap);
+#endif
+    add_menu_item(menu, "Manual songchange",    xf_config_cb, FADE_CONFIG_MANUAL, &imap);
+    add_menu_item(menu, "Manual stop",          xf_config_cb, FADE_CONFIG_STOP, &imap);
+    add_menu_item(menu, "End of playlist",      xf_config_cb, FADE_CONFIG_EOP, &imap);
+    add_menu_item(menu, "Seeking",              xf_config_cb, FADE_CONFIG_SEEK, &imap);
+    add_menu_item(menu, "Pause",                xf_config_cb, FADE_CONFIG_PAUSE, &imap);
+    gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+  }
+
+}
+
+static void
+create_crossfader_type_menu()
+{
+  GtkWidget *optionmenu, *menu;
+  gint i, *imap;
+  guint32 mask;
+
+  if((optionmenu = lookup_widget(config_win, "xf_type_optionmenu"))) {
+    for(i=0; i<MAX_FADE_TYPES; i++) xf_type_index_map[i] = -1;
+    imap = xf_type_index_map;
+    menu = gtk_menu_new();
+    mask = cfg->fc[cfg->xf_index].type_mask;
+    if(mask & (1 << FADE_TYPE_REOPEN))      add_menu_item(menu, "Reopen output device", xf_type_cb, FADE_TYPE_REOPEN, &imap);
+    if(mask & (1 << FADE_TYPE_FLUSH))       add_menu_item(menu, "Flush output device",  xf_type_cb, FADE_TYPE_FLUSH, &imap);
+    if(mask & (1 << FADE_TYPE_NONE))        add_menu_item(menu, "None (gapless/off)",   xf_type_cb, FADE_TYPE_NONE, &imap);
+    if(mask & (1 << FADE_TYPE_PAUSE))       add_menu_item(menu, "Pause",                xf_type_cb, FADE_TYPE_PAUSE, &imap);
+    if(mask & (1 << FADE_TYPE_SIMPLE_XF))   add_menu_item(menu, "Simple crossfade",     xf_type_cb, FADE_TYPE_SIMPLE_XF, &imap);
+    if(mask & (1 << FADE_TYPE_ADVANCED_XF)) add_menu_item(menu, "Advanced crossfade",   xf_type_cb, FADE_TYPE_ADVANCED_XF, &imap);
+    if(mask & (1 << FADE_TYPE_FADEIN))      add_menu_item(menu, "Fadein",               xf_type_cb, FADE_TYPE_FADEIN, &imap);
+    if(mask & (1 << FADE_TYPE_FADEOUT))     add_menu_item(menu, "Fadeout",              xf_type_cb, FADE_TYPE_FADEOUT, &imap);
+    if(mask & (1 << FADE_TYPE_PAUSE_NONE))  add_menu_item(menu, "None",                 xf_type_cb, FADE_TYPE_PAUSE_NONE, &imap);
+    if(mask & (1 << FADE_TYPE_PAUSE_ADV))   add_menu_item(menu, "Fadeout/Fadein",       xf_type_cb, FADE_TYPE_PAUSE_ADV, &imap);
+    gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+  }
+}
+
+#define NONE             0x00000000L
+#define XF_CONFIG        0x00000001L
+#define XF_TYPE          0x00000002L
+#define XF_MIX_SIZE      0x00000004L
+#define XF_FADEOUT       0x00000008L
+#define XF_OFFSET        0x00000010L
+#define XF_FADEIN        0x00000020L
+#define XF_PAGE          0x00000040L
+#define XF_FLUSH         0x00000080L 
+#define ANY              0xffffffffL
+
+static void
+check_crossfader_dependencies(guint32 mask)
+{
+  fade_config_t *fc = &cfg->fc[cfg->xf_index];
+  gint i;
+
+  /* HACK: avoid endless recursion */
+  if(checking) return;
+  checking = TRUE;
+
+  if(mask & XF_FLUSH) {
+    SET_TOGGLE   ("xftfp_enable_check",  fc->flush_pause_enable);
+    SET_SENSITIVE("xftfp_length_label",  fc->flush_pause_enable);
+    SET_SENSITIVE("xftfp_length_spin",   fc->flush_pause_enable);
+    SET_TOGGLE   ("xftffi_enable_check", fc->flush_in_enable);
+    SET_SENSITIVE("xftffi_length_label", fc->flush_in_enable);
+    SET_SENSITIVE("xftffi_length_spin",  fc->flush_in_enable);
+    SET_SENSITIVE("xftffi_volume_label", fc->flush_in_enable);
+    SET_SENSITIVE("xftffi_volume_spin",  fc->flush_in_enable);
+  }
+
+  if(mask & XF_MIX_SIZE) {
+    SET_TOGGLE   ("xf_autobuf_check", cfg->mix_size_auto);
+    SET_SENSITIVE("xf_buffer_spin",  !cfg->mix_size_auto);
+    SET_SPIN     ("xf_buffer_spin",  xfade_mix_size_ms(cfg));
+  }
+
+  if(mask & XF_CONFIG) {
+    for(i=0; i<MAX_FADE_CONFIGS && (xf_config_index_map[i] != cfg->xf_index); i++);
+    if(i == MAX_FADE_CONFIGS) i=0;
+    SET_HISTORY("xf_config_optionmenu", i);
+  }
+
+  if(mask & XF_TYPE) {
+    create_crossfader_type_menu();
+    for(i=0; i<MAX_FADE_TYPES && (xf_type_index_map[i] != fc->type); i++);
+    if(i == MAX_FADE_TYPES) {
+      fc->type = FADE_TYPE_NONE;
+      for(i=0; i<MAX_FADE_TYPES && (xf_type_index_map[i] != fc->type); i++);
+      if(i == MAX_FADE_CONFIGS) i=0;
+    }
+    SET_HISTORY("xf_type_optionmenu", i);
+  }
+  
+  if(mask & XF_PAGE) {
+    SET_PAGE("xf_type_notebook",   fc->type);
+    SET_SPIN("pause_length_spin",  fc->pause_len_ms);
+    SET_SPIN("simple_length_spin", fc->simple_len_ms);
+    if(fc->config == FADE_CONFIG_SEEK) {
+      HIDE("xftf_pause_frame");
+      HIDE("xftf_fadein_frame");
+    }
+    else {
+      SHOW("xftf_pause_frame");
+      SHOW("xftf_fadein_frame");
+    }
+  }
+  
+  if(mask & XF_FADEOUT) {
+    SET_TOGGLE   ("fadeout_enable_check", fc->out_enable);
+    SET_SENSITIVE("fadeout_length_label", fc->out_enable);
+    SET_SENSITIVE("fadeout_length_spin",  fc->out_enable);
+    SET_SPIN     ("fadeout_length_spin",  fc->out_len_ms);
+    SET_SENSITIVE("fadeout_volume_label", fc->out_enable);
+    SET_SENSITIVE("fadeout_volume_spin",  fc->out_enable);
+    SET_SPIN     ("fadeout_volume_spin",  fc->out_volume);
+    SET_SPIN     ("xftfo_length_spin",    fc->out_len_ms);
+    SET_SPIN     ("xftfo_volume_spin",    fc->out_volume);
+    SET_SPIN     ("xftfoi_fadeout_spin",  fc->out_len_ms);
+  }
+  
+  if(mask & XF_FADEIN) {
+    SET_TOGGLE   ("fadein_lock_check",    fc->in_locked);
+    SET_SENSITIVE("fadein_enable_check", !fc->in_locked);
+    SET_TOGGLE   ("fadein_enable_check",  fc->in_locked ? fc->out_enable : fc->in_enable);
+    SET_SENSITIVE("fadein_length_label", !fc->in_locked && fc->in_enable);
+    SET_SENSITIVE("fadein_length_spin",  !fc->in_locked && fc->in_enable);
+    SET_SPIN     ("fadein_length_spin",   fc->in_locked ? fc->out_len_ms : fc->in_len_ms);
+    SET_SENSITIVE("fadein_volume_label", !fc->in_locked && fc->in_enable);
+    SET_SENSITIVE("fadein_volume_spin",  !fc->in_locked && fc->in_enable);
+    SET_SPIN     ("fadein_volume_spin",   fc->in_locked ? fc->out_volume : fc->in_volume);
+    SET_SPIN     ("xftfi_length_spin",    fc->in_len_ms);
+    SET_SPIN     ("xftfi_volume_spin",    fc->in_volume);
+    SET_SPIN     ("xftfoi_fadein_spin",   fc->in_len_ms);
+  }
+  
+  if(mask & XF_OFFSET) {
+    if(fc->out_enable)
+      SET_SENSITIVE("xfofs_lockout_radiobutton", TRUE);
+    if(!fc->in_locked && fc->in_enable)
+      SET_SENSITIVE("xfofs_lockin_radiobutton",  TRUE);
+
+    switch(fc->ofs_type) {
+    case FC_OFFSET_LOCK_OUT:
+      if(!fc->out_enable) {
+	SET_TOGGLE("xfofs_none_radiobutton", TRUE);
+	fc->ofs_type = FC_OFFSET_NONE;
+      }
+      break;
+    case FC_OFFSET_LOCK_IN:
+      if(!(!fc->in_locked && fc->in_enable)) {
+	if((fc->in_locked && fc->out_enable)) {
+	  SET_TOGGLE("xfofs_lockout_radiobutton", TRUE);
+	  fc->ofs_type = FC_OFFSET_LOCK_OUT;
+	} else {
+	  SET_TOGGLE("xfofs_none_radiobutton", TRUE);
+	  fc->ofs_type = FC_OFFSET_NONE;
+	}
+      }
+      break;
+    }
+    
+    switch(fc->ofs_type_wanted) {
+    case FC_OFFSET_NONE:
+      SET_TOGGLE("xfofs_none_radiobutton", TRUE);
+      fc->ofs_type = FC_OFFSET_NONE;
+      break;
+    case FC_OFFSET_LOCK_OUT:
+      if(fc->out_enable) {
+	SET_TOGGLE("xfofs_lockout_radiobutton", TRUE);
+	fc->ofs_type = FC_OFFSET_LOCK_OUT;
+      }
+      break;
+    case FC_OFFSET_LOCK_IN:
+      if(!fc->in_locked && fc->in_enable) {
+	SET_TOGGLE("xfofs_lockin_radiobutton", TRUE);
+	fc->ofs_type = FC_OFFSET_LOCK_IN;
+      }
+      else if(fc->out_enable) {
+	SET_TOGGLE("xfofs_lockout_radiobutton", TRUE);
+	fc->ofs_type = FC_OFFSET_LOCK_OUT;
+      }
+      break;
+    case FC_OFFSET_CUSTOM:
+      SET_TOGGLE("xfofs_custom_radiobutton", TRUE);
+      fc->ofs_type = FC_OFFSET_CUSTOM;
+      break;
+    }
+    
+    if(!fc->out_enable)
+      SET_SENSITIVE("xfofs_lockout_radiobutton", FALSE);
+    if(!(!fc->in_locked && fc->in_enable))
+      SET_SENSITIVE("xfofs_lockin_radiobutton",  FALSE);
+  
+    SET_SENSITIVE("xfofs_custom_spin",   fc->ofs_type == FC_OFFSET_CUSTOM);
+    SET_SPIN     ("xfofs_custom_spin",   xfade_cfg_offset(fc));
+    SET_SPIN     ("xftfo_silence_spin",  xfade_cfg_offset(fc));
+    SET_SPIN     ("xftfoi_silence_spin", xfade_cfg_offset(fc));
+  }
+
+  checking = FALSE;
+}
+
+/*-- crossfader callbacks ---------------------------------------------------*/
+
+void on_xf_buffer_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->mix_size_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(NONE);
+}
+
+void on_xf_autobuf_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  cfg->mix_size_auto = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_MIX_SIZE);
+}
+
+/* - config/type  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void xf_config_cb(GtkWidget *widget, gint index)
+{
+  if(checking) return;
+  cfg->xf_index = index;
+  check_crossfader_dependencies(ANY & ~XF_CONFIG);
+}
+
+void xf_type_cb(GtkWidget *widget, gint index)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].type = index;
+  check_crossfader_dependencies(ANY & ~XF_CONFIG);
+}
+
+/* - flush  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_xftfp_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].flush_pause_enable
+    = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_FLUSH|XF_MIX_SIZE);
+}
+
+void on_xftfp_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].flush_pause_len_ms
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_FLUSH);
+}
+
+void on_xftffi_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].flush_in_enable
+    = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_FLUSH|XF_OFFSET|XF_FADEOUT|XF_FADEIN);
+}
+
+void on_xftffi_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].flush_in_len_ms
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_FLUSH);
+}
+
+void on_xftffi_volume_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].flush_in_volume
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_FLUSH);
+}
+
+/* - pause  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_pause_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].pause_len_ms
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_MIX_SIZE);
+}
+
+/* - simple - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_simple_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].simple_len_ms
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_MIX_SIZE);
+}
+
+/* - crossfade-fadeout  - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_fadeout_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].out_enable
+    = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET|XF_FADEOUT|XF_FADEIN);
+}
+
+void on_fadeout_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].out_len_ms
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET|XF_FADEIN);
+}
+
+void on_fadeout_volume_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  cfg->fc[cfg->xf_index].out_volume
+    = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_OFFSET|XF_FADEIN);
+}
+
+/* - crossfade-offset - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_xfofs_none_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking || !gtk_toggle_button_get_active(togglebutton)) return;
+  cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_NONE;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_NONE;
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET);
+}
+
+void on_xfofs_none_radiobutton_clicked(GtkButton *button, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_NONE;
+}
+
+void on_xfofs_lockout_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking || !gtk_toggle_button_get_active(togglebutton)) return;
+  cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_LOCK_OUT;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_OUT;
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET);
+}
+
+void on_xfofs_lockout_radiobutton_clicked(GtkButton *button, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_OUT;
+}
+
+void on_xfofs_lockin_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking || !gtk_toggle_button_get_active(togglebutton)) return;
+  cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_LOCK_IN;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_IN;
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET);
+}
+
+void on_xfofs_lockin_radiobutton_clicked(GtkButton *button, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_IN;
+}
+
+void on_xfofs_custom_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking || !gtk_toggle_button_get_active(togglebutton)) return;
+  cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_CUSTOM;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_CUSTOM;
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET);
+}
+
+void on_xfofs_custom_radiobutton_clicked(GtkButton *button, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_CUSTOM;
+}
+
+void on_xfofs_custom_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].ofs_custom_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_MIX_SIZE);
+}
+
+/* - crossfade-fadein - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+
+void on_fadein_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].in_locked = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_OFFSET|XF_FADEIN);
+}
+
+void on_fadein_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].in_enable = gtk_toggle_button_get_active(togglebutton);
+  check_crossfader_dependencies(XF_OFFSET|XF_FADEIN);
+}
+
+void on_fadein_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].in_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET);
+}
+
+void on_fadein_volume_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->fc[cfg->xf_index].in_volume = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_crossfader_dependencies(NONE);
+}
+
+/*-- fadein -----------------------------------------------------------------*/
+
+/* signal set to on_fadein_length_spin_changed */
+/* signal set to on_fadein_volume_spin_changed */
+
+/*-- fadeout ----------------------------------------------------------------*/
+
+/* signal set to on_fadeout_length_spin_changed */
+/* signal set to on_fadeout_volume_spin_changed */
+
+/*-- fadeout/fadein ---------------------------------------------------------*/
+
+/* signal set to on_fadeout_length_spin_changed */
+/* signal set to on_xfofs_custom_spin_changed */
+/* signal set to on_fadeout_volume_spin_changed */
+
+/*** gap killer **************************************************************/
+
+void check_gapkiller_dependencies()
+{
+  if(checking) return;
+  checking = TRUE;
+
+  SET_SENSITIVE("lgap_length_spin",  cfg->gap_lead_enable);
+  SET_SENSITIVE("lgap_level_spin",   cfg->gap_lead_enable);
+  SET_SENSITIVE("tgap_enable_check", !cfg->gap_trail_locked);
+  SET_SENSITIVE("tgap_length_spin",  !cfg->gap_trail_locked && cfg->gap_trail_enable);
+  SET_SENSITIVE("tgap_level_spin",   !cfg->gap_trail_locked && cfg->gap_trail_enable);
+
+  if(cfg->gap_trail_locked) {
+    SET_TOGGLE("tgap_enable_check", cfg->gap_lead_enable);
+    SET_SPIN  ("tgap_length_spin",  cfg->gap_lead_len_ms);
+    SET_SPIN  ("tgap_level_spin",   cfg->gap_lead_level);
+  }
+  else {
+    SET_TOGGLE("tgap_enable_check", cfg->gap_trail_enable);
+    SET_SPIN  ("tgap_length_spin",  cfg->gap_trail_len_ms);
+    SET_SPIN  ("tgap_level_spin",   cfg->gap_trail_level);
+  }
+
+  if(cfg->mix_size_auto)
+    SET_SPIN("xf_buffer_spin", xfade_mix_size_ms(cfg));
+
+  checking = FALSE;
+}
+
+/*-- gapkiller callbacks ----------------------------------------------------*/
+
+void on_lgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_lead_enable = gtk_toggle_button_get_active(togglebutton);
+  check_gapkiller_dependencies();
+}
+
+void on_lgap_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_lead_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_gapkiller_dependencies();
+}
+
+void on_lgap_level_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_lead_level = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_gapkiller_dependencies();
+}
+
+void on_tgap_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_trail_locked = gtk_toggle_button_get_active(togglebutton);
+  check_gapkiller_dependencies();
+}
+
+void on_tgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_trail_enable = gtk_toggle_button_get_active(togglebutton);
+  check_gapkiller_dependencies();
+}
+
+void on_tgap_length_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_trail_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+}
+
+void on_tgap_level_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->gap_trail_level = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+}
+
+/*** misc ********************************************************************/
+
+void check_misc_dependencies()
+{
+  if(checking) return;
+  checking = TRUE;
+
+  if(cfg->mix_size_auto)
+    SET_SPIN("xf_buffer_spin", xfade_mix_size_ms(cfg));
+
+  SET_SENSITIVE("moth_opmaxused_spin", cfg->enable_op_max_used);
+
+  checking = FALSE;
+}
+
+/*-- misc callbacks ---------------------------------------------------------*/
+
+void on_config_mixopt_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  SET_SENSITIVE("mixopt_reverse_check",  gtk_toggle_button_get_active(togglebutton));
+  SET_SENSITIVE("mixopt_software_check", gtk_toggle_button_get_active(togglebutton));
+}
+
+void on_moth_songchange_spin_changed(GtkEditable *editable, gpointer user_data)
+{
+  if(checking) return;
+  cfg->songchange_timeout = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable));
+  check_misc_dependencies();
+}
+
+void on_moth_opmaxused_check_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  if(checking) return;
+  cfg->enable_op_max_used = gtk_toggle_button_get_active(togglebutton);
+  check_misc_dependencies();
+}
+
+/*** presets *****************************************************************/
+
+void on_presets_list_click_column(GtkCList *clist, gint column, gpointer user_data)
+{
+  DEBUG(("*** column=%d\n", column));
+}
+
+/*** main config *************************************************************/
+
+void on_config_apply_clicked(GtkButton *button, gpointer user_data)
+{
+  GtkWidget *widget;
+
+  /* get current notebook page */
+  if((widget = lookup_widget(config_win, "config_notebook")))
+    cfg->page = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget));
+
+  /* output method */
+
+  /* sample rate */
+
+  /* output method: builtin OSS */
+  if((widget = lookup_widget(config_win, "output_oss_notebook")))
+    cfg->oss_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget));
+
+  if((widget = lookup_widget(config_win, "oss_adevice_alt_entry"))) {
+    if(cfg->oss_alt_audio_device) g_free(cfg->oss_alt_audio_device);
+    cfg->oss_alt_audio_device = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
+    g_strstrip(cfg->oss_alt_audio_device);
+  }
+
+  if((widget = lookup_widget(config_win, "oss_mdevice_alt_entry"))) {
+    if(cfg->oss_alt_mixer_device) g_free(cfg->oss_alt_mixer_device);
+    cfg->oss_alt_mixer_device = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
+    g_strstrip(cfg->oss_alt_mixer_device);
+  }
+
+  cfg->oss_buffer_size_ms  = GET_SPIN  ("ossbuf_buffer_spin");
+  cfg->oss_preload_size_ms = GET_SPIN  ("ossbuf_preload_spin");
+
+  cfg->oss_fragments       = GET_SPIN  ("osshwb_fragments_spin");
+  cfg->oss_fragment_size   = GET_SPIN  ("osshwb_fragsize_spin");
+  cfg->oss_maxbuf_enable   = GET_TOGGLE("osshwb_maxbuf_check");
+
+  cfg->oss_mixer_use_master = GET_TOGGLE("ossmixer_pcm_check");
+
+  /* output method: plugin */
+  op_config.throttle_enable  = GET_TOGGLE("op_throttle_check");
+  op_config.max_write_enable = GET_TOGGLE("op_maxblock_check");
+  op_config.max_write_len    = GET_SPIN  ("op_maxblock_spin");
+  op_config.force_reopen     = GET_TOGGLE("op_forcereopen_check");
+
+  xfade_save_plugin_config(&cfg->op_config_string, cfg->op_name, &op_config);
+
+  /* output method: none: */
+
+  /* effects: pre-mixing effect plugin */
+
+  /* effects: volume normalizer */
+  cfg->volnorm_target = GET_SPIN  ("volnorm_target_spin");
+  cfg->volnorm_use_qa = GET_TOGGLE("volnorm_quantaudio_check");
+
+  /* crossfader */
+  cfg->mix_size_auto = GET_TOGGLE("xf_autobuf_check");
+
+  /* gap killer */
+  cfg->gap_lead_enable  = GET_TOGGLE("lgap_enable_check");
+  cfg->gap_lead_len_ms  = GET_SPIN  ("lgap_length_spin");
+  cfg->gap_lead_level   = GET_SPIN  ("lgap_level_spin");
+
+  cfg->gap_trail_locked = GET_TOGGLE("tgap_lock_check");
+
+  cfg->gap_crossing     = GET_TOGGLE("gadv_crossing_check");
+
+  /* misc */
+  cfg->enable_debug           = GET_TOGGLE("debug_stderr_check");
+  cfg->enable_monitor         = GET_TOGGLE("debug_monitor_check");
+  cfg->enable_mixer           = GET_TOGGLE("mixopt_enable_check");
+  cfg->mixer_reverse          = GET_TOGGLE("mixopt_reverse_check"); 
+  cfg->mixer_software         = GET_TOGGLE("mixopt_software_check"); 
+  cfg->preload_size_ms        = GET_SPIN  ("moth_preload_spin");
+  cfg->album_detection        = GET_TOGGLE("noxf_album_check");
+  cfg->no_xfade_if_same_file  = GET_TOGGLE("noxf_samefile_check");
+  cfg->enable_http_workaround = GET_TOGGLE("moth_httpworkaround_check");
+  cfg->op_max_used_ms         = GET_SPIN  ("moth_opmaxused_spin");
+  cfg->output_keep_opened     = GET_TOGGLE("moth_outputkeepopened_check");
+
+  /* presets */
+
+  /* lock buffer */
+  g_static_mutex_lock(&buffer_mutex);
+
+  /* free existing strings */
+  if(config->oss_alt_audio_device) g_free(config->oss_alt_audio_device);
+  if(config->oss_alt_mixer_device) g_free(config->oss_alt_mixer_device);
+  if(config->op_config_string)     g_free(config->op_config_string);
+  if(config->op_name)              g_free(config->op_name);
+  if(config->ep_name)              g_free(config->ep_name);
+
+  /* copy current settings (dupping the strings) */
+  *config = *cfg;
+  config->oss_alt_audio_device = g_strdup(cfg->oss_alt_audio_device);
+  config->oss_alt_mixer_device = g_strdup(cfg->oss_alt_mixer_device);
+  config->op_config_string     = g_strdup(cfg->op_config_string);
+  config->op_name              = g_strdup(cfg->op_name);
+  config->ep_name              = g_strdup(cfg->ep_name);
+
+  /* tell the engine that the config has changed */
+  xfade_realize_config();
+  
+  /* unlock buffer */
+  g_static_mutex_unlock(&buffer_mutex);
+
+  /* save configuration */
+  xfade_save_config();
+  
+  /* show/hide monitor win depending on config->enable_monitor */
+  xfade_check_monitor_win(); 
+}
+
+void on_config_ok_clicked(GtkButton *button, gpointer user_data)
+{
+  /* apply and save config */
+  on_config_apply_clicked(button, user_data);
+
+  /* close and destroy window */
+  gtk_widget_destroy(config_win);
+}
+
+void xfade_configure()
+{
+  GtkWidget *widget;
+
+  if(!config_win) {
+    /* create */
+    if(!(config_win = create_config_win())) {
+      DEBUG(("[crossfade] plugin_configure: error creating window!\n"));
+      return;
+    }
+
+    /* update config_win when window is destroyed */
+    gtk_signal_connect(GTK_OBJECT(config_win), "destroy",
+		       GTK_SIGNAL_FUNC(gtk_widget_destroyed), &config_win);
+    
+    /* free any strings that might be left in our local copy of the config */
+    if(cfg->oss_alt_audio_device) g_free(cfg->oss_alt_audio_device);
+    if(cfg->oss_alt_mixer_device) g_free(cfg->oss_alt_mixer_device);
+    if(cfg->op_config_string)     g_free(cfg->op_config_string);
+    if(cfg->op_name)              g_free(cfg->op_name);
+    if(cfg->ep_name)              g_free(cfg->ep_name);
+
+    /* copy current settings (dupping the strings) */
+    *cfg = *config;
+    cfg->oss_alt_audio_device = g_strdup(config->oss_alt_audio_device);
+    cfg->oss_alt_mixer_device = g_strdup(config->oss_alt_mixer_device);
+    cfg->op_config_string     = g_strdup(config->op_config_string);
+    cfg->op_name              = g_strdup(config->op_name);
+    cfg->ep_name              = g_strdup(config->ep_name);
+
+    /* go to remembered notebook page */
+    if((widget = lookup_widget(config_win, "config_notebook")))
+      gtk_notebook_set_page(GTK_NOTEBOOK(widget), config->page);
+
+    /* output: method */
+#ifdef HAVE_OSS
+    SET_SENSITIVE("output_oss_radio", TRUE);
+#else
+    SET_SENSITIVE("output_oss_radio", FALSE);
+#endif
+
+    switch(cfg->output_method) {
+    case OUTPUT_METHOD_BUILTIN_OSS:
+      widget = lookup_widget(config_win, "output_oss_radio");
+      break;
+    case OUTPUT_METHOD_PLUGIN:
+      widget = lookup_widget(config_win, "output_plugin_radio");
+      break;
+    case OUTPUT_METHOD_BUILTIN_NULL:
+      widget = lookup_widget(config_win, "output_none_radio");
+      break;
+    default:
+      widget = NULL;
+    }
+    if(widget) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
+
+    if((widget = lookup_widget(config_win, "output_notebook")))
+      gtk_notebook_set_page(GTK_NOTEBOOK(widget), cfg->output_method);
+
+    /* output: resampling rate */
+    if((widget = lookup_widget(config_win, "resampling_rate_optionmenu"))) {
+      GtkWidget *menu = gtk_menu_new();
+      GtkWidget *item;
+
+      item = gtk_menu_item_new_with_label("44100 Hz");
+      gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(resampling_rate_cb), (gpointer)44100);
+      gtk_widget_show(item);
+      gtk_menu_append(GTK_MENU(menu), item);
+      
+      item = gtk_menu_item_new_with_label("48000 Hz");
+      gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(resampling_rate_cb), (gpointer)48000);
+      gtk_widget_show(item);
+      gtk_menu_append(GTK_MENU(menu), item);
+
+      gtk_option_menu_set_menu(GTK_OPTION_MENU(widget), menu);
+
+      switch(cfg->output_rate) {
+      default:
+	DEBUG(("[crossfade] plugin_configure: WARNING: invalid output sample rate (%d)!\n", cfg->output_rate));
+	DEBUG(("[crossfade] plugin_configure:          ... using default of 44100\n"));
+	cfg->output_rate = 44100;
+      case 44100: gtk_option_menu_set_history(GTK_OPTION_MENU(widget), 0); break;
+      case 48000: gtk_option_menu_set_history(GTK_OPTION_MENU(widget), 1); break;
+      }
+    }
+
+    /* output: resampling quality (libsamplerate setting) */
+#ifdef HAVE_LIBSAMPLERATE
+    if((widget = lookup_widget(config_win, "resampling_quality_optionmenu"))) {
+      GtkWidget *menu = gtk_menu_new();
+      GtkWidget *item;
+
+      GtkTooltips *tooltips = (GtkTooltips *)gtk_object_get_data(GTK_OBJECT(config_win), "tooltips");
+
+      int converter_type;
+      const char *name, *description;
+      for(converter_type = 0; (name = src_get_name(converter_type));
+	  converter_type++) {
+	description = src_get_description(converter_type);
+
+	item = gtk_menu_item_new_with_label(name);
+	gtk_tooltips_set_tip(tooltips, item, description, NULL);
+
+	gtk_signal_connect(GTK_OBJECT(item), "activate",
+			   GTK_SIGNAL_FUNC(resampling_quality_cb), (gpointer)converter_type);
+	gtk_widget_show(item);
+	gtk_menu_append(GTK_MENU(menu), item);
+      }
+      
+      gtk_option_menu_set_menu   (GTK_OPTION_MENU(widget), menu);
+      gtk_option_menu_set_history(GTK_OPTION_MENU(widget), cfg->output_quality);
+    }
+#else
+    HIDE("resampling_quality_hbox");
+    HIDE("resampling_quality_optionmenu");
+#endif
+
+    /* output method: builtin OSS */
+    if((widget = lookup_widget(config_win, "output_oss_notebook")))
+      gtk_notebook_set_page(GTK_NOTEBOOK(widget), cfg->oss_page);
+
+    if((widget = lookup_widget(config_win, "oss_adevice_optionmenu"))) {
+      scan_devices("Audio devices:", widget, config_adevice_cb);
+      gtk_option_menu_set_history(GTK_OPTION_MENU(widget),
+				  cfg->oss_audio_device);
+      gtk_widget_set_sensitive(widget, !cfg->oss_use_alt_audio_device);
+    }
+    SET_TOGGLE("oss_adevice_alt_check", cfg->oss_use_alt_audio_device);
+    if((widget = lookup_widget(config_win, "oss_adevice_alt_entry"))) {
+      gtk_entry_set_text(GTK_ENTRY(widget), cfg->oss_alt_audio_device
+			 ? cfg->oss_alt_audio_device
+			 : DEFAULT_OSS_ALT_AUDIO_DEVICE);
+      gtk_widget_set_sensitive(widget, cfg->oss_use_alt_audio_device);
+    }
+    
+    if((widget = lookup_widget(config_win, "oss_mdevice_optionmenu"))) {
+      scan_devices("Mixers:", widget, config_mdevice_cb);
+      gtk_option_menu_set_history(GTK_OPTION_MENU(widget),
+				  cfg->oss_mixer_device);
+      gtk_widget_set_sensitive(widget, !cfg->oss_use_alt_mixer_device);
+    }
+    SET_TOGGLE("oss_mdevice_alt_check", cfg->oss_use_alt_mixer_device);
+    if((widget = lookup_widget(config_win, "oss_mdevice_alt_entry"))) {
+      gtk_entry_set_text(GTK_ENTRY(widget), cfg->oss_alt_mixer_device
+        		 ? cfg->oss_alt_mixer_device
+        		 : DEFAULT_OSS_ALT_MIXER_DEVICE); 
+      gtk_widget_set_sensitive(widget, cfg->oss_use_alt_mixer_device);
+    }
+
+    SET_SPIN  ("ossbuf_buffer_spin",  cfg->oss_buffer_size_ms);
+    SET_SPIN  ("ossbuf_preload_spin", cfg->oss_preload_size_ms);
+
+    SET_SPIN  ("osshwb_fragments_spin", cfg->oss_fragments);
+    SET_SPIN  ("osshwb_fragsize_spin",  cfg->oss_fragment_size);
+    SET_TOGGLE("osshwb_maxbuf_check",   cfg->oss_maxbuf_enable);
+
+    SET_TOGGLE("ossmixer_pcm_check", cfg->oss_mixer_use_master);
+
+    check_oss_dependencies();
+
+    /* output method: plugin */
+    xfade_load_plugin_config(cfg->op_config_string, cfg->op_name, &op_config);
+    SET_TOGGLE   ("op_throttle_check",    op_config.throttle_enable);
+    SET_TOGGLE   ("op_maxblock_check",    op_config.max_write_enable);
+    SET_SPIN     ("op_maxblock_spin",     op_config.max_write_len); 
+    SET_SENSITIVE("op_maxblock_spin",     op_config.max_write_enable);
+    SET_TOGGLE   ("op_forcereopen_check", op_config.force_reopen);
+
+    if((widget = lookup_widget(config_win, "op_plugin_optionmenu"))) {
+      OutputPlugin *op = NULL;
+      if((op_index = scan_plugins(widget, cfg->op_name)) >= 0) {
+	gtk_option_menu_set_history(GTK_OPTION_MENU(widget), op_index);
+	op = g_list_nth_data(get_output_list(), op_index);  /* XMMS */
+      }
+      SET_SENSITIVE("op_configure_button", op && (op->configure != NULL));
+      SET_SENSITIVE("op_about_button",     op && (op->about     != NULL));
+    }
+
+    /* output method: none */
+
+    /* effects: pre-mixing effect plugin */
+    if((widget = lookup_widget(config_win, "ep_plugin_optionmenu"))) {
+      EffectPlugin *ep = NULL;
+      if((ep_index = scan_effect_plugins(widget, cfg->ep_name)) >= 0) {
+	gtk_option_menu_set_history(GTK_OPTION_MENU(widget), ep_index);
+	ep = g_list_nth_data(get_effect_list(), ep_index);  /* XMMS */
+      }
+      SET_SENSITIVE("ep_configure_button", ep && (ep->configure != NULL));
+      SET_SENSITIVE("ep_about_button",     ep && (ep->about     != NULL));
+      SET_TOGGLE   ("ep_enable_check",     cfg->ep_enable);
+    }
+
+    /* effects: volume normalizer */
+    SET_TOGGLE("volnorm_enable_check",     cfg->volnorm_enable);
+    SET_TOGGLE("volnorm_quantaudio_check", cfg->volnorm_use_qa);
+    SET_SPIN  ("volnorm_target_spin",      cfg->volnorm_target);
+
+    check_effects_dependencies();
+    
+    /* crossfader */
+    create_crossfader_config_menu();
+
+    if((cfg->xf_index < 0) || (cfg->xf_index >= MAX_FADE_CONFIGS)) {
+      DEBUG(("[crossfade] plugin_configure: crossfade index out of range (%d)!\n", cfg->xf_index));
+      cfg->xf_index = CLAMP(cfg->xf_index, 0, MAX_FADE_CONFIGS);
+    }
+
+    check_crossfader_dependencies(ANY);
+    
+    /* gap killer */
+    SET_TOGGLE   ("lgap_enable_check",   cfg->gap_lead_enable);
+    SET_SPIN     ("lgap_length_spin",    cfg->gap_lead_len_ms);
+    SET_SPIN     ("lgap_level_spin",     cfg->gap_lead_level);
+    SET_TOGGLE   ("tgap_lock_check",     cfg->gap_trail_locked);
+    SET_TOGGLE   ("tgap_enable_check",   cfg->gap_trail_enable);
+    SET_SPIN     ("tgap_length_spin",    cfg->gap_trail_len_ms);
+    SET_SPIN     ("tgap_level_spin",     cfg->gap_trail_level);
+    SET_TOGGLE   ("gadv_crossing_check", cfg->gap_crossing);
+
+    check_gapkiller_dependencies();
+
+    /* misc */
+    SET_TOGGLE("debug_stderr_check",          cfg->enable_debug);
+    SET_TOGGLE("debug_monitor_check",         cfg->enable_monitor);
+    SET_TOGGLE("mixopt_enable_check",         cfg->enable_mixer);
+    SET_TOGGLE("mixopt_reverse_check",        cfg->mixer_reverse);
+    SET_TOGGLE("mixopt_software_check",       cfg->mixer_software);
+    SET_SPIN  ("moth_songchange_spin",        cfg->songchange_timeout);
+    SET_SPIN  ("moth_preload_spin",           cfg->preload_size_ms);
+    SET_TOGGLE("noxf_album_check",            cfg->album_detection);
+    SET_TOGGLE("noxf_samefile_check",         cfg->album_detection);
+    SET_TOGGLE("moth_httpworkaround_check",   cfg->enable_http_workaround);
+    SET_TOGGLE("moth_opmaxused_check",        cfg->enable_op_max_used);
+    SET_SPIN  ("moth_opmaxused_spin",         cfg->op_max_used_ms);
+    SET_TOGGLE("moth_outputkeepopened_check", cfg->output_keep_opened);
+
+    check_misc_dependencies();
+
+    /* presets */
+    if((set_wgt = lookup_widget(config_win, "presets_list_list"))) {
+      GList *item;
+
+      for(item = config->presets; item; item = g_list_next(item)) {
+	gchar *name = (gchar *)item->data;
+	gchar *text[] = {name, "Default", "No"};
+	gtk_clist_append(GTK_CLIST(set_wgt), text);
+      }
+    }
+
+    /* show window near mouse pointer */
+    gtk_window_set_position(GTK_WINDOW(config_win), GTK_WIN_POS_MOUSE);
+    gtk_widget_show(config_win);
+  }
+  else
+    /* bring window to front */
+    gdk_window_raise(config_win->window);
+}
+
+void xfade_about()
+{
+  if(!about_win) {
+    gchar *about_text = 
+      "XMMS Crossfade Plugin "VERSION"\n"
+      "Copyright (C) 2000-2004  Peter Eisenlohr <peter@eisenlohr.org>\n"
+      "\n"
+      "based on the original OSS Output Plugin  Copyright (C) 1998-2000\n"
+      "Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies\n"
+      "\n"
+      "This program is free software; you can redistribute it and/or modify\n"
+      "it under the terms of the GNU General Public License as published by\n"
+      "the Free Software Foundation; either version 2 of the License, or\n"
+      "(at your option) any later version.\n"
+      "\n"
+      "This program is distributed in the hope that it will be useful,\n"
+      "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+      "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+      "GNU General Public License for more details.\n"
+      "\n"
+      "You should have received a copy of the GNU General Public License\n"
+      "along with this program; if not, write to the Free Software\n"
+      "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,\n"
+      "USA.";
+
+    about_win = create_about_win();
+
+    /* update about_win when window is destroyed */
+    gtk_signal_connect(GTK_OBJECT(about_win), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &about_win);
+    
+    /* set about box text (this is done here and not in interface.c because
+       of the VERSION #define -- there is no way to do this with GLADE */
+    if((set_wgt = lookup_widget(about_win, "about_label")))
+      gtk_label_set_text(GTK_LABEL(set_wgt), about_text);
+    
+    /* show near mouse pointer */
+    gtk_window_set_position(GTK_WINDOW(about_win), GTK_WIN_POS_MOUSE);
+    gtk_widget_show(about_win);
+  }
+  else
+    gdk_window_raise(about_win->window);
+}