diff src/iris/iris.c @ 116:a578bf9b2851 trunk

[svn] - iris vis plugin port
author nenolod
date Tue, 24 Oct 2006 21:25:31 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iris/iris.c	Tue Oct 24 21:25:31 2006 -0700
@@ -0,0 +1,1025 @@
+/*  Iris - visualization plugin for XMMS
+ *  Copyright (C) 2000-2002 Cédric DELFOSSE (cdelfosse@free.fr)
+ *
+ *  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 "config.h"
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <gtk/gtk.h>
+#include <math.h>
+
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <X11/extensions/xf86vmode.h>
+#include <pthread.h>
+#ifdef HAVE_SCHED_SETSCHEDULER
+#include <sched.h>
+#endif
+#include <stdlib.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <audacious/plugin.h>
+#include <audacious/util.h>
+#include <audacious/beepctrl.h>
+#include <audacious/configdb.h>
+#include "iris.h"
+
+#define BEAT_MAX 100
+
+GLfloat y_angle = 45.0, y_speed = 0.5;
+GLfloat x_angle = 20.0, x_speed = 0.0;
+GLfloat z_angle = 0.0, z_speed = 0.0;
+static GLfloat scale;
+static GLfloat x_angle_wanted;
+
+GLWindow GLWin;
+
+/* attributes for a single buffered visual in RGBA format with at least
+ * 4 bits per color and a 16 bit depth buffer */
+static int attrListSgl[] = { GLX_RGBA, GLX_RED_SIZE, 4,
+  GLX_GREEN_SIZE, 4,
+  GLX_BLUE_SIZE, 4,
+  GLX_DEPTH_SIZE, 16,
+  None
+};
+
+/* attributes for a double buffered visual in RGBA format with at least
+ * 4 bits per color and a 16 bit depth buffer */
+static int attrListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER,
+  GLX_RED_SIZE, 4,
+  GLX_GREEN_SIZE, 4,
+  GLX_BLUE_SIZE, 4,
+  GLX_DEPTH_SIZE, 16,
+  None
+};
+
+static gboolean going = FALSE;
+static gboolean initialized = FALSE;
+static gboolean grabbed_pointer = FALSE, firsttime = TRUE;
+static Atom wmDelete;
+static pthread_t draw_thread;
+
+datas_t datas;
+
+static int num_bands = NUM_BANDS;
+static int beat = 0;
+
+unsigned int transition_frames = 0;
+unsigned int max_transition_frames = 0;
+
+static void iris_init (void);
+static void iris_cleanup (void);
+static void iris_about (void);
+static void iris_playback_start (void);
+static void iris_playback_stop (void);
+static void iris_render_freq (gint16 data[][256]);
+
+static float dps = 0;
+
+gint beat_before = -1;
+
+extern iris_config newconfig;
+
+VisPlugin iris_vp = {
+  NULL,
+  NULL,
+  0,
+  NULL,				/* Description */
+  0,
+  1,
+  iris_init,			/* init */
+  iris_cleanup,			/* cleanup */
+  iris_about,			/* about */
+  iris_configure,		/* configure */
+  NULL,				/* disable_plugin */
+  iris_playback_start,		/* playback_start */
+  iris_playback_stop,		/* playback_stop */
+  NULL,				/* render_pcm */
+  iris_render_freq		/* render_freq */
+};
+
+
+void
+about_close_clicked (GtkWidget * w, GtkWidget ** window)
+{
+  gtk_widget_destroy (*window);
+  *window = NULL;
+}
+
+
+void
+about_closed (GtkWidget * w, GdkEvent * e, GtkWidget ** window)
+{
+  about_close_clicked (w, window);
+}
+
+
+static void
+iris_about (void)
+{
+  static GtkWidget *window_about = NULL;
+  GtkWidget *vbox, *button, *close, *label;
+
+  if (window_about)
+    return;
+
+  window_about = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title (GTK_WINDOW (window_about), "About IRIS plugin");
+  gtk_window_set_policy (GTK_WINDOW (window_about), FALSE, FALSE, FALSE);
+  gtk_window_set_position (GTK_WINDOW (window_about), GTK_WIN_POS_MOUSE);
+  vbox = gtk_vbox_new (FALSE, 4);
+  gtk_container_add (GTK_CONTAINER (window_about), vbox);
+  gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
+  gtk_widget_show (vbox);
+
+  label = gtk_label_new ("\n\
+Iris XMMS Plugin.\n\
+Copyright (C) 2001-2003, Cédric Delfosse.\n\
+\n\
+Email: <cdelfosse@free.fr> \n\
+Homepage: <http://cdelfosse.free.fr/xmms-iris/>\n\
+Development: <http://savannah.gnu.org/projects/iris/>\n\
+\n\
+Authors:\n\
+Cédric Delfosse\n\
+Marinus Schraal\n\
+Ron Lockwood-Childs\n\
+Ported to Audacious by Arthur Taylor\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");
+  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 8);
+  gtk_widget_show (label);
+
+  button = gtk_hbutton_box_new ();
+  gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 8);
+  gtk_widget_show (button);
+
+  close = gtk_button_new_with_label ("Close");
+  GTK_WIDGET_SET_FLAGS (close, GTK_CAN_DEFAULT);
+  gtk_window_set_default (GTK_WINDOW (window_about), close);
+  gtk_hbutton_box_set_layout_default (GTK_BUTTONBOX_END);
+  gtk_box_pack_end (GTK_BOX (button), close, FALSE, FALSE, 8);
+  gtk_widget_show (close);
+
+  gtk_signal_connect (GTK_OBJECT (close), "clicked",
+		      GTK_SIGNAL_FUNC (about_close_clicked), &window_about);
+  gtk_signal_connect (GTK_OBJECT (window_about), "delete-event",
+		      GTK_SIGNAL_FUNC (about_closed), &window_about);
+
+  gtk_widget_show (window_about);
+}
+
+
+/* Creates an empty cursor icon for when hovering over our window content */
+void
+hide_cursor ()
+{
+  Cursor no_ptr;
+  Pixmap bm_no;
+  Colormap cmap;
+  XColor black, dummy;
+
+  static unsigned char bm_no_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+  cmap = DefaultColormap (GLWin.dpy, DefaultScreen (GLWin.dpy));
+  XAllocNamedColor (GLWin.dpy, cmap, "black", &black, &dummy);
+  bm_no = XCreateBitmapFromData (GLWin.dpy, GLWin.window, bm_no_data, 8, 8);
+  no_ptr =
+    XCreatePixmapCursor (GLWin.dpy, bm_no, bm_no, &black, &black, 0, 0);
+  XDefineCursor (GLWin.dpy, GLWin.window, no_ptr);
+}
+
+
+/* this function creates our window and sets it up properly */
+static Window
+create_window (char *title)
+{
+  XVisualInfo *vi;
+  int i;
+
+  /* get a connection */
+  GLWin.dpy = XOpenDisplay (0);
+  if (GLWin.dpy == NULL)
+    g_log (NULL, G_LOG_LEVEL_CRITICAL,
+	   __FILE__ ": XOpenDisplay returns NULL");
+  GLWin.screen = DefaultScreen (GLWin.dpy);
+
+  /* get an appropriate visual */
+  vi = glXChooseVisual (GLWin.dpy, GLWin.screen, attrListDbl);
+  if (vi == NULL)
+    {
+      vi = glXChooseVisual (GLWin.dpy, GLWin.screen, attrListSgl);
+      GLWin.DoubleBuffered = 0;
+    }
+  else
+    GLWin.DoubleBuffered = 1;
+
+  if (vi == NULL)
+    g_log (NULL, G_LOG_LEVEL_CRITICAL,
+	   __FILE__ ": glXChooseVisual returns NULL");
+
+  glXQueryVersion (GLWin.dpy, &GLWin.glxMajorVersion, &GLWin.glxMinorVersion);
+  /* create a GLX context */
+  GLWin.ctx = glXCreateContext (GLWin.dpy, vi, 0, GL_TRUE);
+  if (GLWin.ctx == NULL)
+    g_log (NULL, G_LOG_LEVEL_CRITICAL,
+	   __FILE__ ": glXCreateContext can\'t create a rendering context");
+
+  /* create a color map */
+  GLWin.attr.colormap =
+    XCreateColormap (GLWin.dpy, RootWindow (GLWin.dpy, vi->screen),
+		     vi->visual, AllocNone);
+  GLWin.attr.border_pixel = 0;
+
+  if ((config.fullscreen && firsttime) || GLWin.fs)
+    {
+      int bestMode = 0;
+
+      GLWin.fs = True;
+      GLWin.fs_width = config.fs_width;
+      GLWin.fs_height = config.fs_height;
+      /* look for mode with requested resolution */
+      for (i = 0; i < GLWin.modeNum; i++)
+	{
+	  if ((GLWin.modes[i]->hdisplay == GLWin.fs_width)
+	      && (GLWin.modes[i]->vdisplay == GLWin.fs_height))
+	    {
+	      bestMode = i;
+	    }
+	}
+      XF86VidModeSwitchToMode (GLWin.dpy, GLWin.screen,
+			       GLWin.modes[bestMode]);
+      XF86VidModeSetViewPort (GLWin.dpy, GLWin.screen, 0, 0);
+      GLWin.fs_width = GLWin.modes[bestMode]->hdisplay;
+      GLWin.fs_height = GLWin.modes[bestMode]->vdisplay;
+
+      /* create a fullscreen window */
+      GLWin.attr.override_redirect = True;
+      GLWin.attr.event_mask =
+	ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
+      GLWin.window =
+	XCreateWindow (GLWin.dpy,
+		       RootWindow (GLWin.dpy, vi->screen), 0,
+		       0, GLWin.fs_width, GLWin.fs_height, 0, vi->depth,
+		       InputOutput, vi->visual,
+		       CWBorderPixel | CWColormap |
+		       CWEventMask | CWOverrideRedirect, &GLWin.attr);
+      XWarpPointer (GLWin.dpy, None, GLWin.window, 0, 0, 0, 0, 0, 0);
+      XMapRaised (GLWin.dpy, GLWin.window);
+      XGrabKeyboard (GLWin.dpy, GLWin.window, True, GrabModeAsync,
+		     GrabModeAsync, CurrentTime);
+      XGrabPointer (GLWin.dpy, GLWin.window, True, ButtonPressMask,
+		    GrabModeAsync, GrabModeAsync, GLWin.window,
+		    FALSE, CurrentTime);
+    }
+  else
+    {
+      XClassHint xclasshint={"xmms","visplugin"};
+      GLWin.window_width = config.window_width;
+      GLWin.window_height = config.window_height;
+      /* needed if those aren't set yet - ideally need a way to get the default values for these */
+      if (config.window_height == 0 || config.window_width == 0)
+	{
+	  config.window_width = 640;
+	  config.window_height = 480;
+	}
+
+      /* create a window in window mode */
+      GLWin.attr.event_mask =
+	ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
+      GLWin.window =
+	XCreateWindow (GLWin.dpy,
+		       RootWindow (GLWin.dpy, vi->screen), 0,
+		       0, GLWin.window_width, GLWin.window_height, 0,
+		       vi->depth, InputOutput, vi->visual,
+		       CWBorderPixel | CWColormap | CWEventMask, &GLWin.attr);
+      XmbSetWMProperties(GLWin.dpy, GLWin.window, "iris","iris", NULL, 0, NULL, NULL, &xclasshint);
+
+      /* only set window title and handle wm_delete_events if in windowed mode */
+      wmDelete = XInternAtom (GLWin.dpy, "WM_DELETE_WINDOW", True);
+      XSetWMProtocols (GLWin.dpy, GLWin.window, &wmDelete, 1);
+      XSetStandardProperties (GLWin.dpy, GLWin.window, title,
+			      title, None, NULL, 0, NULL);
+      XMapRaised (GLWin.dpy, GLWin.window);
+    }
+  /* connect the glx-context to the window */
+  if (!glXMakeCurrent (GLWin.dpy, GLWin.window, GLWin.ctx))
+    g_log (NULL, G_LOG_LEVEL_CRITICAL,
+	   __FILE__ ": glXMakeCurrent returns an error");
+  if (glXIsDirect (GLWin.dpy, GLWin.ctx))
+    GLWin.DRI = 1;
+
+  hide_cursor ();
+
+  XFree (vi);
+  firsttime = FALSE;
+  return GLWin.window;
+}
+
+
+VisPlugin *
+get_vplugin_info (void)
+{
+  iris_vp.description = g_strdup_printf ("iris 3D analyzer %s", VERSION);
+  return &iris_vp;
+}
+
+
+static int
+detect_beat (gint32 loudness)
+{
+  static gint32 aged;		/* smoothed out oudness */
+  static gint32 lowest;		/* quietest point in current beat */
+  static int elapsed;		/* frames since last beat */
+  static int isquiet;		/* was previous frame quiet */
+  int detected_beat;
+
+  /* Incorporate the current loudness into history */
+  aged = (aged * 7 + loudness) >> 3;
+  elapsed++;
+
+  /* If silent, then clobber the beat */
+  if (aged < 2000 || elapsed > BEAT_MAX)
+    {
+      elapsed = 0;
+      lowest = aged;
+    }
+  else if (aged < lowest)
+    lowest = aged;
+
+  /* Beats are detected by looking for a sudden loudness after a lull.
+   * They are also limited to occur no more than once every 15 frames,
+   * so the beat flashes don't get too annoying.
+   */
+  detected_beat = (loudness * 4 > aged * 3 && aged * 2 > lowest * 3
+		   && elapsed > 15);
+  if (detected_beat)
+    lowest = aged, elapsed = 0;
+
+  /* Silence is computed from the aged loudness.  The quietref value is
+   * set to TRUE only at the start of silence, not throughout the silent
+   * period.  Also, there is some hysteresis so that silence followed
+   * by a slight noise and more silence won't count as two silent
+   * periods -- that sort of thing happens during many fade edits, so
+   * we have to account for it.
+   */
+  if (aged < (isquiet ? 1500 : 500))
+    /* Quiet now -- is this the start of quiet? */
+    isquiet = TRUE;
+  else
+    isquiet = FALSE;
+
+  /* return the result */
+  return detected_beat;
+}
+
+
+/* limit_rotation_speed keep rotation speed to at least dps_config
+   by second */
+static void
+limit_rotation_speed (gboolean init)
+{
+  static struct timeval tv_past;
+  struct timezone tz;
+  struct timeval tv_now;
+  float dps_config = 15;
+
+  if (!init)
+    {
+      long t;
+
+      gettimeofday (&tv_now, &tz);
+      t =
+	(tv_now.tv_sec - tv_past.tv_sec) * 10000000 + (tv_now.tv_usec -
+						       tv_past.tv_usec);
+      dps = y_speed * ((float) 1000000 / (float) t);
+      if (dps >= (float) dps_config)
+	y_speed -= 0.02;
+      else
+	y_speed += 0.02;
+
+      memcpy (&tv_past, &tv_now, sizeof (struct timeval));
+    }
+  else
+    gettimeofday (&tv_past, &tz);
+}
+
+
+/* limit_fps tries to keep the number of
+   frame per seconds to config.fps */
+static void
+limit_fps (gboolean init)
+{
+  static float fps = 0;
+  static struct timeval tv_past;
+  struct timezone tz;
+  struct timeval tv_now;
+  static int usec = 0;
+
+  if (init)
+    gettimeofday (&tv_past, &tz);
+  else
+    {
+      long t;
+
+      gettimeofday (&tv_now, &tz);
+      t =
+	(tv_now.tv_sec - tv_past.tv_sec) * 10000000 + (tv_now.tv_usec -
+						       tv_past.tv_usec);
+      fps = (float) 1000000 / (float) t;
+
+      if (fps >= (float) config.fps)
+	usec += 100;
+      else
+	{
+	  if (usec > 0)
+	    usec -= 100;
+	}
+      xmms_usleep (usec);
+      memcpy (&tv_past, &tv_now, sizeof (struct timeval));
+    }
+}
+
+static void
+init_general_draw_mode (int num)
+{
+  int transparency;
+  int wireframe;
+
+  if (theme[num].config->global->transparency != -1)
+    transparency = theme[num].config->global->transparency;
+  else
+    transparency = (int) (2.0 * rand () / (RAND_MAX + 1.0));
+
+  if (theme[num].config->global->wireframe != -1)
+    wireframe = theme[num].config->global->wireframe;
+  else
+    wireframe = (int) (2.0 * rand () / (RAND_MAX + 1.0));
+
+  if (transparency)
+    {
+      if (!glIsEnabled (GL_BLEND))
+	{
+	  glBlendFunc (GL_SRC_ALPHA, GL_ONE);
+	  glEnable (GL_BLEND);
+	}
+      glDisable (GL_DEPTH_TEST);
+    }
+  else
+    {
+      if (glIsEnabled (GL_BLEND))
+	{
+	  glDisable (GL_BLEND);
+	}
+      glEnable (GL_DEPTH_TEST);
+    }
+
+  if (wireframe)
+    glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
+  else
+    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
+
+  if (glIsEnabled(GL_TEXTURE_2D))
+    glDisable(GL_TEXTURE_2D);
+}
+
+
+static int
+compute_theme ()
+{
+  gfloat f_rand, f;
+  gint i;
+
+  for (i = 0, f_rand = 0.0; i < THEME_NUMBER; i++)
+    f_rand += theme[i].config->global->priority;
+  f_rand = f_rand * rand () / (RAND_MAX + 1.0);
+
+  i = 0;
+  f = 0;
+  while (i < THEME_NUMBER)
+    {
+      gfloat f_theme;
+      f_theme = theme[i].config->global->priority;
+      if (f_theme)
+	{
+	  f += f_theme;
+	  if (f_rand < f)
+	    break;
+	}
+      i++;
+    }
+
+  if (!f)
+    return (int) ((gfloat) THEME_NUMBER * rand () / (RAND_MAX + 1.0));
+  else
+    return i;
+}
+
+
+static int
+choose_theme (gboolean init)
+{
+  static long sec_btw_theme = 10 * 10000000;
+  static struct timeval tv_past;
+  struct timezone tz;
+  struct timeval tv_now;
+  static int th, th_tmp;
+
+  if (!init)
+    {
+      long t;
+
+      gettimeofday (&tv_now, &tz);
+      t =
+	(tv_now.tv_sec - tv_past.tv_sec) * 10000000 + (tv_now.tv_usec -
+						       tv_past.tv_usec);
+      if ((t > sec_btw_theme) || (beat && config.change_theme_on_beat))
+	{
+	  /* we come here if:
+	     - time for the theme is expired
+	     - a beat is detected and the change_theme_on_beat option is on
+	   */
+	  if (config.transition)
+	    {
+	      if (transition_frames == 0)
+		{
+		  th_tmp = compute_theme ();
+		  if (th != th_tmp)
+		    {
+		      transition_frames =
+			(unsigned int) config.fps * config.trans_duration /
+			10;
+		      max_transition_frames = transition_frames;
+		      memcpy (&tv_past, &tv_now, sizeof (struct timeval));
+		      init_theme_transition (transition_frames,
+					     max_transition_frames);
+		    }
+		}
+	    }
+	  else
+	    {
+	      th = compute_theme ();
+	      init_general_draw_mode (th);
+	      if (theme[th].init_draw_mode != NULL)
+		theme[th].init_draw_mode ();
+	      memcpy (&tv_past, &tv_now, sizeof (struct timeval));
+	      x_angle_wanted = theme[th].get_x_angle ();
+	      x_speed = copysign (0.08, x_angle_wanted - x_angle);
+	    }
+	}
+      /* change theme after half of the set frames have passed */
+      else if ((((int) max_transition_frames / 2) == transition_frames)
+	       && config.transition && (transition_frames != 0))
+	{
+	  th = th_tmp;
+	  init_general_draw_mode (th);
+	  if (theme[th].init_draw_mode != NULL)
+	    theme[th].init_draw_mode ();
+	  x_angle_wanted = theme[th].get_x_angle ();
+	  x_speed = copysign (0.08, x_angle_wanted - x_angle);
+	}
+    }
+  else
+    {
+      /* the first time choose_theme is called,
+         tv_past is initialized */
+      gettimeofday (&tv_past, &tz);
+      th = compute_theme ();
+      init_general_draw_mode (th);
+      if (theme[th].init_draw_mode != NULL)
+	theme[th].init_draw_mode ();
+    }
+
+  return (th);
+}
+
+
+static void
+draw_iris (void)
+{
+  int th;
+
+  limit_fps (FALSE);
+  th = choose_theme (FALSE);
+
+  if ((config.color_beat) && (beat_before > 0))
+    glClearColor (config.color_flash_red, config.color_flash_green,
+		  config.color_flash_blue, 1);
+  else
+    glClearColor (config.bgc_red, config.bgc_green, config.bgc_blue, 1);
+  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushMatrix ();
+  glTranslatef (0.0, -0.5, -7.0);
+  glRotatef (x_angle, 1.0, 0.0, 0.0);
+  glRotatef (y_angle, 0.0, 1.0, 0.0);
+  glRotatef (z_angle, 0.0, 0.0, 1.0);
+
+  if (transition_frames > 0 && config.transition)
+    {
+      theme_transition ();
+      transition_frames--;
+    }
+
+  theme[th].draw_one_frame (beat);
+
+  glEnd ();
+  glPopMatrix ();
+
+  glXSwapBuffers (GLWin.dpy, GLWin.window);
+}
+
+
+static gint
+disable_func (gpointer data)
+{
+  iris_vp.disable_plugin (&iris_vp);
+  return FALSE;
+}
+
+
+GLvoid
+init_gl (GLvoid)
+{
+  glMatrixMode (GL_PROJECTION);
+  glLoadIdentity ();
+  glFrustum (-1, 1, -1.5, 1, 1.5, 12);
+  glMatrixMode (GL_MODELVIEW);
+  glLoadIdentity ();
+}
+
+
+GLvoid
+kill_gl_window (GLvoid)
+{
+  if (GLWin.ctx)
+    {
+      glXMakeCurrent (GLWin.dpy, None, NULL);
+      glXDestroyContext (GLWin.dpy, GLWin.ctx);
+      GLWin.ctx = NULL;
+    }
+  /* switch back to original desktop resolution if we were in fs */
+  if (GLWin.fs)
+    {
+      XF86VidModeSwitchToMode (GLWin.dpy, GLWin.screen, &GLWin.deskMode);
+      XF86VidModeSetViewPort (GLWin.dpy, GLWin.screen, 0, 0);
+    }
+}
+
+
+void *
+draw_thread_func (void *arg)
+{
+  Bool configured = FALSE;
+
+  g_log (NULL, G_LOG_LEVEL_DEBUG,  __FILE__ ": draw_thread_func: Starting.");
+
+  if ((GLWin.window = create_window ("Iris")) == 0)
+    {
+      g_log (NULL, G_LOG_LEVEL_CRITICAL,
+	     __FILE__ ": unable to create window");
+      pthread_exit (NULL);
+    }
+
+  init_gl ();
+  choose_theme (TRUE);
+
+
+#ifdef HAVE_SCHED_SETSCHEDULER
+  if (xmms_check_realtime_priority ())
+    {
+      struct sched_param sparam;
+      sparam.sched_priority = sched_get_priority_max (SCHED_OTHER);
+      pthread_setschedparam (pthread_self (), SCHED_OTHER, &sparam);
+    }
+#endif
+
+  while (going)			/* plugin enabled */
+    {
+      while (XPending (GLWin.dpy))
+	{
+	  XEvent event;
+	  KeySym keysym;
+	  char buf[16];
+
+	  XNextEvent (GLWin.dpy, &event);
+	  switch (event.type)
+	    {
+	    case Expose:
+	      if (event.xexpose.count != 0)
+		break;
+	      configured = TRUE;
+	      break;
+	    case ConfigureNotify:
+	      glViewport (0, 0, event.xconfigure.width,
+			  event.xconfigure.height);
+	      configured = TRUE;
+	      break;
+	    case KeyPress:
+
+	      XLookupString (&event.xkey, buf, 16, &keysym, NULL);
+	      switch (keysym)
+		{
+		case XK_Escape:
+
+		  /* Ugly hack to get the disable_plugin call in the main thread. */
+		  GDK_THREADS_ENTER ();
+		  gtk_idle_add (disable_func, NULL);
+		  GDK_THREADS_LEAVE ();
+		  break;
+		case XK_z:
+		  xmms_remote_playlist_prev (iris_vp.xmms_session);
+		  break;
+		case XK_x:
+		  xmms_remote_play (iris_vp.xmms_session);
+		  break;
+		case XK_c:
+		  xmms_remote_pause (iris_vp.xmms_session);
+		  break;
+		case XK_v:
+		  xmms_remote_stop (iris_vp.xmms_session);
+		  break;
+		case XK_b:
+		  xmms_remote_playlist_next (iris_vp.xmms_session);
+		  break;
+		case XK_Left:
+		  y_speed -= 0.1;
+		  if (y_speed < -3.0)
+		    y_speed = -3.0;
+		  break;
+		case XK_Right:
+		  y_speed += 0.1;
+		  if (y_speed > 3.0)
+		    y_speed = 3.0;
+		  break;
+		case XK_Return:
+		  x_speed = 0.0;
+		  y_speed = 0.3;
+		  z_speed = 0.0;
+		  x_angle = 70.0;
+		  y_angle = 45.0;
+		  z_angle = 0.0;
+		  break;
+		case XK_Tab:
+		  iris_configure ();
+		  break;
+		case XK_f:
+		  //iris_save_window_attributes ();
+		  kill_gl_window ();
+		  XCloseDisplay (GLWin.dpy);
+		  GLWin.fs = !GLWin.fs;
+		  create_window ("Iris");
+		  init_gl ();
+		  choose_theme (TRUE);
+		  break;
+		}
+
+	      break;
+	    case ClientMessage:
+	      if ((Atom) event.xclient.data.l[0] == wmDelete)
+		{
+		  GDK_THREADS_ENTER ();
+		  gtk_idle_add (disable_func, NULL);
+		  GDK_THREADS_LEAVE ();
+		}
+	      break;
+	    }
+	}
+      if (configured)
+	{
+	  limit_rotation_speed (FALSE);
+
+	  if ((x_angle > x_angle_wanted) && (x_speed > 0))
+	    x_angle = x_angle_wanted;
+	  else if ((x_angle < x_angle_wanted) && (x_speed < 0))
+	    x_angle = x_angle_wanted;
+
+	  x_angle += x_speed;
+	  if (x_angle > 85.0)
+	    x_angle = 85.0;
+	  else if (x_angle < 0.0)
+	    x_angle = 0.0;
+
+	  y_angle += y_speed;
+	  if (y_angle >= 360.0)
+	    y_angle -= 360.0;
+
+	  z_angle += z_speed;
+	  if (z_angle >= 360.0)
+	    z_angle -= 360.0;
+
+	  draw_iris ();
+	}
+    }
+
+  g_log (NULL, G_LOG_LEVEL_DEBUG,  __FILE__ ": draw_thread_func: Exiting.");
+  pthread_exit (NULL);
+}
+
+
+static void
+start_display (void)
+{
+  scale = 1.0 / log (256.0);
+
+  x_speed = 0.0;
+  y_speed = 0.3;
+  z_speed = 0.0;
+  x_angle = 45.0;
+  y_angle = 45.0;
+  z_angle = 0.0;
+
+  going = TRUE;
+  limit_fps (TRUE);
+  limit_rotation_speed (TRUE);
+  if (pthread_create (&draw_thread, NULL, draw_thread_func, NULL))
+    g_log (NULL, G_LOG_LEVEL_CRITICAL,  __FILE__ ": pthread_create: Can't create drawing thread.");
+   
+}
+
+
+static void
+stop_display (void)
+{
+  if (going)
+    {
+      going = FALSE;
+      pthread_join (draw_thread, NULL);
+    }
+
+  kill_gl_window ();
+
+  if (GLWin.window)
+    {
+      if (grabbed_pointer)
+	{
+	  XUngrabPointer (GLWin.dpy, CurrentTime);
+	  grabbed_pointer = FALSE;
+	}
+
+      XDestroyWindow (GLWin.dpy, GLWin.window);
+      GLWin.window = 0;
+    }
+  XCloseDisplay (GLWin.dpy);
+}
+
+
+static void
+iris_init (void)
+{
+  int i;
+  
+  initialized = TRUE;
+  iris_first_init ();
+  datas.loudness = 0;
+  /* if the config window is open, we don't want to trash the config the user
+   * has done */
+  if (!config_window)
+    iris_config_read ();
+  for (i = 0; i < THEME_NUMBER; i++)
+    if (theme[i].init != NULL)
+      theme[i].init ();
+  srand (666);
+  start_display ();
+}
+
+
+static void
+iris_cleanup (void)
+{
+  int i;
+
+  if(initialized)
+  {
+    stop_display ();
+    for (i = 0; i < THEME_NUMBER; i++)
+      if (theme[i].cleanup != NULL)
+	theme[i].cleanup ();
+  }
+}
+
+
+static void
+iris_playback_start (void)
+{
+}
+
+
+static void
+iris_playback_stop (void)
+{
+}
+
+
+static void
+iris_render_freq (gint16 data[2][256])
+{
+  GLfloat val;
+  static int angle = 0;
+  int i;
+
+  for (i = 0; i < num_bands; i++)
+    {
+      int y, c;
+      gint xscale[] =
+	{ 0, 1, 2, 3, 5, 7, 10, 14, 20, 28, 40, 54, 74, 101, 137, 156,
+	255
+      };
+      gint xscale8[] = { 0, 2, 5, 10, 20, 40, 74, 137, 255 };
+
+      if (num_bands == 16)
+	for (c = xscale[i], y = 0; c < xscale[i + 1]; c++)
+	  {
+	    if (data[0][c] > y)
+	      y = data[0][c];
+	  }
+      else
+	for (c = xscale8[i], y = 0; c < xscale8[i + 1]; c++)
+	  {
+	    if (data[0][c] > y)
+	      y = data[0][c];
+	  }
+
+      datas.loudness +=
+	(y / (xscale[i + 1] - xscale[i] + 1)) * (abs (i - NUM_BANDS / 2) +
+						 NUM_BANDS / 2) * (4 + i);
+
+      y >>= 7;
+      if (y > 0)
+	val = (log (y) * scale);
+      else
+	val = 0;
+      datas.data360[angle][i] = val;
+      datas.data1[i] = val;
+    }
+
+  datas.loudness /= (NUM_BANDS * 4);
+
+  beat = detect_beat (datas.loudness);
+  if (beat)
+    {
+      beat_before = config.flash_speed;
+      if (dps <= 90.0)
+	y_speed += 0.7;
+
+      if (config.bgc_random)
+	{
+	  config.bgc_red = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.bgc_green = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.bgc_blue = 1.0 * rand () / (RAND_MAX + 1.0);
+	}
+
+      if (config.color12_random)
+	{
+	  config.color1_red = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color1_green = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color1_blue = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color2_red = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color2_green = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color2_blue = 1.0 * rand () / (RAND_MAX + 1.0);
+	}
+
+      if (config.color_random)
+	{
+	  config.color_red = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color_green = 1.0 * rand () / (RAND_MAX + 1.0);
+	  config.color_blue = 1.0 * rand () / (RAND_MAX + 1.0);
+	}
+    }
+
+  if (beat_before > 0)
+    beat_before--;
+
+  angle++;
+  if (angle == 360)
+    angle = 0;
+}