/*
 * Copyright (C) 2001-2017 the xine project
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * gtk xine video widget
 *
 * some code originating from totem's gtkxine widget
 *
 * dbus/gnome-screensaver interaction code originating from totem's
 * screensaver object.
 * Original copyright for this code, according to the commit log:
 *   Copyright (C) 2005-2006 William Jon McCann <mccann@jhu.edu>
 */

#include "config.h"
#include "i18n.h"

#include "defs.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <sys/types.h>
#include <pthread.h>
#include <sched.h>
#include <sys/time.h>

#ifdef HAVE_X11
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#ifdef HAVE_DPMS_EXTENSION
#include <X11/extensions/dpms.h>
#endif
#endif
#ifdef HAVE_XTESTEXTENSION
#include <X11/extensions/XTest.h>
#endif
#ifdef HAVE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif

#ifdef HAVE_XCB
#include <xcb/xcb.h>
#endif

#ifdef WITH_DBUS
#include <dbus/dbus-glib.h>

#define GS_LEGACY_SERVICE	"org.gnome.screensaver"
#define GS_LEGACY_PATH		"/org/gnome/screensaver"
#define GS_LEGACY_INTERFACE	"org.gnome.screensaver"

#define GS_SERVICE		"org.gnome.ScreenSaver"
#define GS_PATH			"/org/gnome/ScreenSaver"
#define GS_INTERFACE		"org.gnome.ScreenSaver"
#endif

#include <gdk/gdk.h>
#include <gtk/gtk.h>
#if defined(GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined(GDK_WINDOWING_WIN32)
#include <windows.h>
#include <gdk/gdkwin32.h>
#endif

#include <xine.h>
/* for get_capabilities() */
#include <xine/video_out.h>

#include "gtkvideo.h"
#include "post.h"
#include "playlist.h"
#include "utils.h"

#ifdef HAVE_X11
/* missing stuff from X includes */
#ifndef XShmGetEventBase
extern int XShmGetEventBase(Display *);
#endif
#endif

static void gtk_video_class_init    (gpointer,        gpointer);
static void gtk_video_instance_init (GTypeInstance *, gpointer);

static void gtk_video_finalize      (GObject        *object);
static void gtk_video_realize       (GtkWidget      *widget);
static void gtk_video_unrealize     (GtkWidget      *widget);
static void gtk_video_map (GtkWidget *);

/*
static void gtk_video_size_request (GtkWidget      *widget,
				    GtkRequisition *requisition);
*/
static void gtk_video_size_allocate (GtkWidget      *widget,
                                     GtkAllocation  *allocation);

static gboolean gtv_unblank_screen (GtkVideo *);
static gboolean gtv_update_resize_factor (GtkVideo *);

#ifdef SCREENSAVER_COMMANDS
static int screensaver_poke (const char *const [], const char *);
#endif
#ifdef XDG_SCREENSAVER_COMMAND
static int xdg_screensaver_poke (const char *, Window);
#endif

static GtkWidgetClass *parent_class = NULL;
static pthread_mutex_t resize_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t resize_add_lock = PTHREAD_MUTEX_INITIALIZER;

#define RESIZE_LOCK() pthread_mutex_lock (&resize_lock)
#define RESIZE_UNLOCK() pthread_mutex_unlock (&resize_lock)

#define RESIZE_ADD(resize) \
  do { \
    pthread_mutex_lock (&resize_add_lock); \
    if (!gtv->priv->resizing && gtk_widget_get_realized (&gtv->widget)) \
      gtv->priv->resizing = \
	g_idle_add_full (G_PRIORITY_HIGH_IDLE, \
			 (GSourceFunc) ((resize) \
			   ? gtk_video_idle_resize : gtk_video_idle_no_resize), \
			 gtv, NULL); \
    pthread_mutex_unlock (&resize_add_lock); \
  } while (0)

/*
 * private data
 */

typedef void (*frame_cb_t) (gpointer);

struct gtk_video_private_s {

  xine_t                  *xine;
  xine_stream_t           *stream;
  xine_post_out_t         *out;
  const char              *video_driver_id;
  xine_video_port_t       *video_port;

#if defined(GDK_WINDOWING_X11)
  Window                   video_window;
#elif defined(GDK_WINDOWING_WIN32)
  HWND                     video_window;
  win32_visual_t           win32_visual; /* XXX: better to code frame callbacks
                                                 to vo plugin */
#endif

  double                   display_ratio;

  /* On exit from full-screen mode, we get multiple allocate events.
   * Consequently, there may be multiple rescalings.
   * We *don't* necessarily want to signal them.
   */
  gboolean		   block_next_scale_change:8;

  gboolean                 auto_resize:8, auto_rescale:8, pending_resize:8;
  GtkRequisition	   pos, size, old_size, video_size, old_video_size;
  float                    resize_factor;
  guint			   resizing, minimising;

  int			   button_press_mask; /* pass to front end? */
  int			   button_release_mask; /* pass to front end? */
  int			   button_event_shifted; /* was shifted? */

  gboolean		   idle_resized:8;
  gboolean		   hinted:8;

  /* fullscreen & pointer stuff */

  gboolean		   fullscreen_mode:8, pointer_visible:8;
  GtkRequisition	   fullscreen;
  GdkCursor		   *pointer[3];
  uint8_t		   pointer_shape, pointer_shape_old;
  struct {
    struct timeval	   time;
    gboolean		   in:8;
  }			   spu;
  guint			   pointer_hide_timeout;
  guint			   screen_blanker_timeout;
  guint			   unblock_timeout;

  gint			   screen_signal;

#ifdef HAVE_XTESTEXTENSION
  Bool                   have_xtest;
  KeyCode                kc_shift[2]; /* fake keypresses */
#endif

#ifdef WITH_DBUS
  DBusGConnection	*connection;
  DBusGProxy		*gs_proxy;
  guint32		 cookie;
#endif

#ifdef XDG_SCREENSAVER_COMMAND
  Window		 xdg_active;
  gboolean		 have_xdg_screensaver:8;
#endif

  gboolean		 playing_no_blank:8;

  /* visualisation & post-plugins*/
  gboolean		   have_tvtime:8;

  gboolean		   vis_active:8;
  xine_post_t		  *vis_plugin;
  char			  *vis_plugin_id;
  xine_audio_port_t       *vis_audio;

  struct {
    GList	*list;
    gboolean	 enable;
  }			   post_deinterlace, post_video, post_audio;

  /* callbacks */

  guint			   frame_cb_id;
  frame_cb_t		   frame_cb;
  gpointer		   frame_cb_data;

  guint			   end_time_id;
  gint			   end_time; /* for triggering early "playback finished" */
};

static int gtv_table_signals[LAST_SIGNAL] = { 0 };

#ifdef HAVE_XTESTEXTENSION
static int shift = 0; /* Shift key state */
#endif

static inline GdkDisplay *gtv_get_display (GtkVideo *gtv)
{
  return gtk_widget_get_display (&gtv->widget);
}

static inline GdkScreen *gtv_get_screen (GtkVideo *gtv)
{
  return gtk_widget_get_screen (&gtv->widget);
}

#ifdef HAVE_X11
static inline Display *gtv_get_xdisplay (GtkVideo *gtv)
{
  GdkWindow *window = gtk_widget_get_window (&gtv->widget);
  return GDK_WINDOW_XDISPLAY (window);
}

static inline Screen *gtv_get_xscreen (GtkVideo *gtv)
{
  return GDK_SCREEN_XSCREEN (gtv_get_screen (gtv));
}

static inline int gtv_get_xscreen_num (GtkVideo *gtv)
{
  return GDK_SCREEN_XNUMBER (gtv_get_screen (gtv));
}
#endif

static inline gboolean is_fullscreen (gtk_video_private_t *priv)
{
  return priv->fullscreen_mode == 1;
}

static gboolean gtv_test_inhibit_blank (GtkVideo *gtv)
{
  /* (fullscreen || (deny blank && drawable && playing && !(shaded || hidden)))
   * && !logo && !paused
   */
  return (is_fullscreen (gtv->priv)
          || (gtv->priv->playing_no_blank
              && gtk_widget_is_drawable (&gtv->widget)
	      && xine_get_status (gtv->priv->stream) == XINE_STATUS_PLAY
              && (gdk_window_get_state (gtk_widget_get_window (gtk_widget_get_toplevel (&gtv->widget))) & 3) == 0))
	 && !playlist_showing_logo ()
	 && xine_get_param (gtv->priv->stream, XINE_PARAM_SPEED);
}

#ifdef WITH_DBUS
static inline gboolean
screensaver_is_running_dbus (gtk_video_private_t *priv)
{
  return priv->connection && priv->gs_proxy;
}

static void
gtv_screensaver_inhibit_dbus (gtk_video_private_t *priv, gboolean inhibit)
{
  GError *error = NULL;
  gboolean res;

  if (!screensaver_is_running_dbus (priv))
    return;

  if (inhibit)
  {
    char *application;
    char *reason;
    guint32 cookie;

    application = g_strdup ("gxine");
    reason = g_strdup (_("gxine is active"));

    res = dbus_g_proxy_call (priv->gs_proxy, "Inhibit", &error,
			     G_TYPE_STRING, application,
			     G_TYPE_STRING, reason,
			     G_TYPE_INVALID,
			     G_TYPE_UINT, &cookie,
			     G_TYPE_INVALID);
    if (res)
      /* save the cookie */
      priv->cookie = cookie;
    else
      /* old API */
      res = dbus_g_proxy_call (priv->gs_proxy, "InhibitActivation", &error,
			       G_TYPE_STRING, reason,
			       G_TYPE_INVALID, G_TYPE_INVALID);
    g_free (reason);
    g_free (application);
  }
  else
  {
    res = dbus_g_proxy_call (priv->gs_proxy, "UnInhibit", &error,
			     G_TYPE_UINT, priv->cookie,
			     G_TYPE_INVALID, G_TYPE_INVALID);
    if (res)
      /* clear the cookie */
      priv->cookie = 0;
    else
      /* old API */
      res = dbus_g_proxy_call (priv->gs_proxy, "AllowActivation", &error,
			      G_TYPE_INVALID, G_TYPE_INVALID, G_TYPE_INVALID);
  }
  if (!res && error)
    g_printerr (
	     _("gtkvideo: problem when inhibiting the GNOME screensaver: %s\n"),
	     error->message);
  if (error)
    g_error_free (error);
}

static void
gtv_screensaver_maybe_inhibit_dbus (gtk_video_private_t *priv, gboolean inhibit)
{
  if (screensaver_is_running_dbus (priv))
    gtv_screensaver_inhibit_dbus (priv, inhibit);
}

static void
gtv_gs_proxy_destroy_cb (GObject *obj, gtk_video_private_t *priv)
{
  g_printerr (_("gtkvideo: the GNOME screensaver has left the bus\n"));
  priv->gs_proxy = NULL; /* invalidate only */
}

static void
gtv_init_dbus (GtkVideo *gtv)
{
  GError *error = NULL;
  gtk_video_private_t *priv = gtv->priv;
  priv->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);

  if (!priv->connection)
  {
    if (error)
    {
      g_printerr (
	       _("gtkvideo: failed to connect to the dbus session bus: %s\n"),
	       error->message);
      g_error_free (error);
    }
    return;
  }

  priv->gs_proxy =
    dbus_g_proxy_new_for_name_owner (priv->connection, GS_SERVICE, GS_PATH,
				     GS_INTERFACE, NULL)
    ? : dbus_g_proxy_new_for_name_owner (priv->connection,
					 GS_LEGACY_SERVICE, GS_LEGACY_PATH,
					 GS_LEGACY_INTERFACE, NULL);
  if (priv->gs_proxy)
    g_signal_connect_object (priv->gs_proxy, "destroy",
			     G_CALLBACK (gtv_gs_proxy_destroy_cb), gtv, 0);
  else
    g_printerr (
	     _("gtkvideo: failed to get a proxy for gnome-screensaver\n"));
}

static void
gtv_finalise_dbus (gtk_video_private_t *priv)
{
  if (priv->gs_proxy)
  {
    g_object_unref (priv->gs_proxy);
    priv->gs_proxy = NULL;
  }
}
#else
#define screensaver_is_running_dbus(PRIV) (FALSE)
#define gtv_screensaver_maybe_inhibit_dbus(PRIV, INHIBIT)
#define gtv_init_dbus(PRIV)
#define gtv_finalise_dbus(PRIV)
#endif

GType gtk_video_get_type (void)
{
  static GType gtk_video_type = 0;

  if (!gtk_video_type)
  {
    static const GTypeInfo gtk_video_info = {
      .class_size       = sizeof (GtkVideoClass),
      .class_init       = gtk_video_class_init,
      .instance_size    = sizeof (GtkVideo),
      .instance_init    = gtk_video_instance_init,
    };

    gtk_video_type = g_type_register_static (GTK_TYPE_WIDGET,
					     "GtkVideo", &gtk_video_info, (GTypeFlags)0);
  }

  return gtk_video_type;
}

static void gtv_set_pointer (GtkVideo *gtv, gboolean state)
{
  if (gtv->priv->pointer_visible != state || gtv->priv->pointer_shape != gtv->priv->pointer_shape_old)
  {
    gtv->priv->pointer_visible = state;
    gtv->priv->pointer_shape_old = gtv->priv->pointer_shape;
    gdk_window_set_cursor(gtk_widget_get_window (&gtv->widget),
                          gtv->priv->pointer[state ? gtv->priv->pointer_shape + 1 : 0]);
  }
}

static gboolean gtv_hide_pointer_cb (GtkVideo *gtv)
{
  gtv->priv->pointer_hide_timeout = 0;
  gtv_set_pointer (gtv, FALSE);
  return FALSE;
}

static gboolean gtv_set_pointer_cb (GtkVideo *gtv)
{
  gtv_set_pointer (gtv, gtv->priv->pointer_visible);
  return FALSE;
}

static void gtv_show_pointer (GtkVideo *gtv)
{
  if (gtv->priv->pointer_hide_timeout)
  {
    g_source_remove (gtv->priv->pointer_hide_timeout);
    gtv->priv->pointer_hide_timeout = 0;
  }
  gtv_set_pointer (gtv, TRUE);
  if (is_fullscreen (gtv->priv))
    gtv->priv->pointer_hide_timeout =
      g_timeout_add (4000, (GSourceFunc)gtv_hide_pointer_cb, gtv);
}

static void gtv_hide_pointer (GtkVideo *gtv)
{
  if (gtv->priv->pointer_hide_timeout)
  {
    g_source_remove (gtv->priv->pointer_hide_timeout);
    gtv->priv->pointer_hide_timeout = 0;
  }
  gtv_set_pointer (gtv, FALSE);
}

static void gtv_set_pointer_shape (GtkVideo *gtv, int shape)
{
  gtv->priv->pointer_shape = shape;
  gtv->priv->pointer_visible = TRUE;
  g_idle_add ((GSourceFunc)gtv_set_pointer_cb, gtv);
}

#ifdef HAVE_XTESTEXTENSION
/* Track the Shift keys: we need to ensure that they "remain pressed" when
 * blocking the screen blanker when in full-screen mode.
 */
static guint key_snoop = 0; /* handler ID */
static gboolean key_snoop_cb (GtkWidget *w, GdkEventKey *e, gpointer d)
{
  if (e->type == GDK_KEY_PRESS)
    switch (e->keyval)
    {
    case GDK_Shift_L: shift |= 1; break;
    case GDK_Shift_R: shift |= 2; break;
    }
  else
    switch (e->keyval)
    {
    case GDK_Shift_L: shift &= ~1; break;
    case GDK_Shift_R: shift &= ~2; break;
    }
  return FALSE;
}

#endif

static void gtk_video_class_init (gpointer g_class, gpointer class_data)
{
  GObjectClass    *object_class;
  GtkWidgetClass  *widget_class;

  object_class = (GObjectClass *) g_class;
  widget_class = (GtkWidgetClass *) g_class;

  parent_class = g_type_class_ref (GTK_TYPE_WIDGET);

  /* GtkWidget */
  widget_class->realize       = gtk_video_realize;
  widget_class->map	      = gtk_video_map;
  widget_class->unrealize     = gtk_video_unrealize;
  widget_class->size_allocate = gtk_video_size_allocate;
  /*widget_class->size_request  = gtk_video_size_request;*/
#if !GTK_CHECK_VERSION(3, 0, 0)
  widget_class->expose_event  = (typeof (widget_class->expose_event)) gtk_true;
#else
  widget_class->draw  = (typeof (widget_class->draw)) gtk_true;
#endif

  /* GObject */
  object_class->set_property  = NULL;
  object_class->get_property  = NULL;
  object_class->finalize      = gtk_video_finalize;

  gtv_table_signals[GTK_VIDEO_SCALE_FACTOR] =
    g_signal_new ("scale-factor-changed",
	G_TYPE_FROM_CLASS (object_class),
	G_SIGNAL_RUN_LAST,
	G_STRUCT_OFFSET (GtkVideoClass, scale_factor),
	NULL, NULL,
	g_cclosure_marshal_VOID__DOUBLE,
	G_TYPE_NONE, 1, G_TYPE_DOUBLE);

#ifdef HAVE_XTESTEXTENSION
  if (!key_snoop)
    key_snoop = gtk_key_snooper_install (key_snoop_cb, NULL);
#endif
}

static void gtk_video_instance_init (GTypeInstance *g_instance, gpointer g_class)
{
  GtkVideo *this = (GtkVideo *)g_instance;
#if !GTK_CHECK_VERSION(2, 24, 0)
  this->widget.requisition.width  = GTK_VIDEO_MIN_WIDTH;
  this->widget.requisition.height = GTK_VIDEO_MIN_HEIGHT;
#else
  g_object_set (this,
                "width-request", GTK_VIDEO_MIN_WIDTH,
                "height-request", GTK_VIDEO_MIN_HEIGHT,
                NULL);
#endif
}

static void gtk_video_finalize (GObject *object)
{
  GtkVideo *gtv = (GtkVideo *) object;
  gtv_finalise_dbus (gtv->priv);
  G_OBJECT_CLASS (parent_class)->finalize (object);
  gtv = NULL;
}

static void gtv_do_resize (GtkVideo *gtv, const GtkRequisition *size)
{
  gtk_widget_set_size_request (&gtv->widget, size->width, size->height);
  if (gtk_widget_get_realized(&gtv->widget) && gtk_widget_get_parent(&gtv->widget))
  {
    GdkGeometry geom;
    GtkWindow *toplevel = GTK_WINDOW (gtk_widget_get_toplevel (&gtv->widget));
    GtkWidget *child    = gtk_bin_get_child (GTK_BIN (toplevel));

    if (child == &gtv->widget)
    {
      geom.min_width = size->width;
      geom.min_height = size->height;
      gtk_window_set_geometry_hints (toplevel, &gtv->widget, &geom,
				     GDK_HINT_MIN_SIZE);
    }
    gtk_window_resize (GTK_WINDOW (gtk_widget_get_toplevel (&gtv->widget)),
		       size->width, size->height); /* note controlling geometry */
    if (child == &gtv->widget)
    {
      geom.min_aspect = geom.max_aspect = (double)size->width / (double)size->height;
      gtk_window_set_geometry_hints (toplevel, &gtv->widget, &geom,
				     GDK_HINT_ASPECT);
    }
    gtk_video_allow_shrink (gtv);
  }
}

static void gtv_do_rescale (GtkVideo *gtv)
{
  gtv_do_resize (gtv, &gtv->priv->size);
  gtv->priv->old_size = gtv->priv->size;
  gtv->priv->pending_resize = FALSE;
}

#define SCALE(factor) \
  (GtkRequisition) { \
		     round (priv->video_size.width * (factor) / 100), \
		     round (priv->video_size.height * (factor) / 100) \
		   }

#define IS_DOUBLE(W,H) (priv->auto_rescale \
			&& (W) <= priv->fullscreen.width / 3.0 \
			&& (H) <= priv->fullscreen.height / 3.0)
#define IS_DOUBLE_STD() \
  (IS_DOUBLE(priv->video_size.width, priv->video_size.height))

#define DOUBLE(RF,W,H) ((RF) * (1 + IS_DOUBLE((W),(H))))
#define DOUBLE_STD() \
  (DOUBLE(priv->resize_factor, priv->video_size.width, priv->video_size.height))

static void dest_size_cb (void *gv_gen,
			  int video_width, int video_height,
			  double video_pixel_aspect,
			  int *dest_width, int *dest_height,
			  double *dest_pixel_aspect)
{
  GtkVideo            *gv = (GtkVideo *) gv_gen;
  gtk_video_private_t *priv = gv->priv;

  if (is_fullscreen (priv))
  {
    *dest_width  = priv->fullscreen.width;
    *dest_height = priv->fullscreen.height;
  }
  else
  {
    GtkAllocation allocation = { 0 };
    gtk_widget_get_allocation (&gv->widget, &allocation);
    *dest_width  = allocation.width;
    *dest_height = allocation.height;
  }

  *dest_pixel_aspect = priv->display_ratio;
}

static gboolean gtv_idle_resize (GtkVideo *gtv, gboolean resize)
{
  gdk_threads_enter ();
  RESIZE_LOCK ();

  gboolean send_signal = FALSE;
  gtk_video_private_t *priv = gtv->priv;
  double factor = DOUBLE_STD();
  priv->size = SCALE (factor);

  logprintf ("gtkvideo: idle resize to %d x %d (factor %lf)\n",
	     priv->size.width, priv->size.height, factor);

  if (is_fullscreen (priv) || !gtk_widget_get_visible (&gtv->widget))
  {
    priv->pending_resize = TRUE;
    logprintf ("gtkvideo: idle deferred (full-screen mode or widget hidden)\n");
  }
  else if (!priv->pending_resize)
  {
    if (resize)
      gtv_do_resize (gtv, &priv->size);
    else
      send_signal = gtv_update_resize_factor (gtv);
    logprintf ("gtkvideo: idle signal done\n");
  }
  else
  {
    if (resize)
      gtv_do_rescale (gtv);
    else
      send_signal = gtv_update_resize_factor (gtv);
    logprintf ("gtkvideo: idle signal done, pending resize handled\n");
  }

  if (send_signal)
    g_signal_emit ((GObject *)gtv,
		   gtv_table_signals[GTK_VIDEO_SCALE_FACTOR], 0,
		   priv->resize_factor);

  priv->resizing = 0;
  priv->idle_resized = TRUE;

  RESIZE_UNLOCK ();
  gdk_threads_leave ();

  return FALSE;
}

static gboolean gtk_video_idle_resize (GtkVideo *gtv)
{
  return gtv_idle_resize (gtv, TRUE);
}

static gboolean gtk_video_idle_no_resize (GtkVideo *gtv)
{
  return gtv_idle_resize (gtv, FALSE);
}

static gboolean end_time_check (gtk_video_private_t *priv)
{
  gint pos_stream, pos_time, length_time;
  priv->end_time_id = 0;
  xine_stream_t *const stream = priv->stream;

  if (!xine_get_pos_length (stream, &pos_stream, &pos_time, &length_time))
    pos_stream = pos_time = length_time = 0;
  if (priv->end_time && pos_time >= priv->end_time)
  {
    logprintf ("gtkvideo: end time passed, sending stop event\n");
    priv->end_time = 0;
    xine_set_param (stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
    xine_event_t event = {
      .type = XINE_EVENT_UI_PLAYBACK_FINISHED,
      .stream = stream,
      .data = NULL
    };
    xine_event_send (stream, &event);
  }
  return FALSE;
}

static gboolean frame_priv_cb (gtk_video_private_t *priv)
{
  priv->frame_cb_id = 0;
  if (priv->frame_cb)
    priv->frame_cb (priv->frame_cb_data);
  return FALSE;
}

static void frame_output_cb (void *gv_gen,
			     int video_width, int video_height,
			     double video_pixel_aspect,
			     int *dest_x, int *dest_y,
			     int *dest_width, int *dest_height,
			     double *dest_pixel_aspect,
			     int *win_x, int *win_y)
{
  GtkVideo            *gtv = (GtkVideo *) gv_gen;
  gtk_video_private_t *priv;
  static gboolean      first = TRUE; /* handle hw which can't scale video */

  if (gtv == NULL)
    return;

  priv = gtv->priv;

  /* correct size with video_pixel_aspect */
  if (video_pixel_aspect >= priv->display_ratio)
    video_width  = video_width * video_pixel_aspect / priv->display_ratio + .5;
  else
    video_height = video_height * priv->display_ratio / video_pixel_aspect + .5;

  priv->video_size = (GtkRequisition){ video_width, video_height };
  *dest_y = *dest_x = 0;

  {
    RESIZE_LOCK ();
    {
      double factor = DOUBLE(priv->resize_factor, video_width, video_height);
      video_width = round (video_width * factor / 100);
      video_height = round (video_height * factor / 100);

      /* size changed? */
      if (first ||
	  video_width != priv->size.width || video_height != priv->size.height)
      {
	if (is_fullscreen (priv) || !gtk_widget_get_visible (&gtv->widget))
	{
	  priv->size = (GtkRequisition){ video_width, video_height };
	  priv->pending_resize = TRUE;
	}
	else
	  RESIZE_ADD (first || priv->auto_resize);
      }
    }
    RESIZE_UNLOCK ();
    first = FALSE;
  }

  if (is_fullscreen (priv))
  {
    *win_x = 0;
    *win_y = 0;
    *dest_width  = priv->fullscreen.width;
    *dest_height = priv->fullscreen.height;
  }
  else
  {
    GtkAllocation allocation = { 0 };
    gtk_widget_get_allocation (&gtv->widget, &allocation);

    *win_x = priv->pos.width;
    *win_y = priv->pos.height;
    *dest_width  = allocation.width;
    *dest_height = allocation.height;
  }

  *dest_pixel_aspect = priv->display_ratio;

   if (priv->end_time && !priv->end_time_id)
     priv->end_time_id = g_idle_add ((GSourceFunc) end_time_check, priv);

  if (priv->frame_cb && !priv->frame_cb_id)
    priv->frame_cb_id = g_idle_add ((GSourceFunc) frame_priv_cb, priv);
}

#ifdef HAVE_XCB
# ifdef ENABLE_X11_VO
static xine_video_port_t *
gxine_open_video_driver (xine_t * self, const char *id, void *xcb, void *x11)
{
  return xine_open_video_driver (self, id, XINE_VISUAL_TYPE_XCB, xcb)
     ? : xine_open_video_driver (self, id, XINE_VISUAL_TYPE_X11, x11);
}
# else /* ! ENABLE_X11_VO */
#  define gxine_open_video_driver(SELF,ID,XCB,X11) \
  xine_open_video_driver ((SELF), (ID), XINE_VISUAL_TYPE_XCB, (XCB))
# endif
#else /* ! HAVE_XCB */
# define gxine_open_video_driver(SELF,ID,X11,UNUSED) \
  xine_open_video_driver ((SELF), (ID), XINE_VISUAL_TYPE_X11, (X11))
#endif

static xine_video_port_t *load_video_out_driver (GtkVideo *this)
{
  double                   res_h, res_v;
  int                      mm_h, mm_v, mm;
  const char              *video_driver_id;
  xine_video_port_t       *video_port = NULL;
  gtk_video_private_t     *priv = this->priv;
  GdkScreen               *gdkscreen;
  gchar                   *desc;

#ifdef HAVE_X11
#ifndef HAVE_XCB
  x11_visual_t vis;

  vis.display           = gtv_get_xdisplay (this);
  vis.screen            = gtv_get_xscreen_num (this);
  vis.d                 = priv->video_window;
#else /* HAVE_XCB */
  xcb_visual_t vis;
  int screen_no = 0;
  xcb_connection_t *connection = xcb_connect(NULL, &screen_no);
  xcb_screen_iterator_t screen_it;

#ifdef ENABLE_X11_VO
  x11_visual_t x11vis;
#endif

  screen_it = xcb_setup_roots_iterator (xcb_get_setup (connection));
  while (screen_it.rem > 1 && screen_no > 0)
  {
    xcb_screen_next (&screen_it);
    --screen_no;
  }

  vis.connection        = connection;
  vis.screen            = screen_it.data;
  vis.window            = priv->video_window;
  vis.dest_size_cb      = dest_size_cb;
  vis.frame_output_cb   = frame_output_cb;
  vis.user_data         = this;

#ifdef ENABLE_X11_VO
  x11vis.display        = gtv_get_xdisplay (this);
  x11vis.screen         = gtv_get_xscreen_num (this);
  x11vis.d              = priv->video_window;
  x11vis.dest_size_cb    = dest_size_cb;
  x11vis.frame_output_cb = frame_output_cb;
  x11vis.user_data       = this;
#endif
#endif /* HAVE_XCB */

#elif defined(WIN32) /* HAVE_X11 */
  HWND desktop;
  HDC hdc;
  COLORREF colorkey;

  memset(&priv->win32_visual, 0, sizeof priv->win32_visual);

  priv->win32_visual.WndHnd = priv->video_window;
  priv->win32_visual.HInst = GetModuleHandle(NULL);

  desktop = GetDesktopWindow();
  hdc = GetDC(desktop);
  for(colorkey = 5; colorkey < 0xFF; colorkey++) {
    if(colorkey == GetNearestColor(hdc, colorkey))
      break;
  }
  priv->win32_visual.Brush = CreateSolidBrush(colorkey);
  priv->win32_visual.ColorKey = (int)colorkey;
  ReleaseDC(desktop, hdc);
#endif


  gdkscreen = gtv_get_screen(this);
  mm_h = gdk_screen_get_width_mm(gdkscreen);
  mm_v = gdk_screen_get_height_mm(gdkscreen);
  res_h = gdk_screen_get_width(gdkscreen);
  res_v = gdk_screen_get_height(gdkscreen);

  desc = g_strdup_printf (_("The reported value is %dmm."), mm_h);
  mm = xine_config_register_range (priv->xine, "video.display_width",
				   0, 0, 100000,
				   N_("display width in mm\n(0 = autodetect)"), desc,
				   10, NULL, NULL);
  res_h = res_h * 1000 / (mm ? mm : mm_h);
  g_free (desc);

  desc = g_strdup_printf (_("The reported value is %dmm."), mm_v);
  mm = xine_config_register_range (priv->xine, "video.display_height",
				   0, -1, 100000,
				   N_("display height in mm\n(0 = autodetect, -1 = assume square pixels)"), desc,
				   10, NULL, NULL);
  res_v = mm < 0 ? res_h : (res_v * 1000 / (mm ? mm : mm_v));
  g_free (desc);

  priv->display_ratio   = (res_h <= 0.0 || res_v <= 0.0) ? 1 : res_v / res_h;
  if (fabs(priv->display_ratio - 1.0) < 0.01)
    priv->display_ratio   = 1.0;

  const char * const *choices = gxine_list_video_output_plugins (priv->xine);
  /* try to init video with stored information */
  int i = xine_config_register_enum (priv->xine,
                                     "video.driver", 0, (char **)choices,
				     N_("video driver to use"),
				     NULL, 10, NULL, NULL);
  video_driver_id = priv->video_driver_id ? : choices[i];

#ifdef HAVE_X11
  if (strcmp (video_driver_id, "auto"))
  {
    video_port = gxine_open_video_driver (priv->xine, video_driver_id,
					  (void *) &vis, (void *) &x11vis);
    if (video_port)
      return video_port;

    g_printerr (_("gtkvideo: video driver %s failed.\n"),
	       video_driver_id); /* => auto-detect */
  }

  video_port = gxine_open_video_driver (priv->xine, NULL,
					(void *) &vis, (void *) &x11vis);
#elif defined(WIN32)
  video_port = xine_open_video_driver(priv->xine, video_driver_id, XINE_VISUAL_TYPE_DIRECTX, &priv->win32_visual);
#endif

  if (video_port)
    return video_port;

  g_printerr (_("gtkvideo: video driver %s failed.\n"), "auto");
  return xine_open_video_driver (priv->xine, "none", XINE_VISUAL_TYPE_NONE,
                                 NULL);
}

uint32_t gtk_video_get_capabilities (GtkVideo *this)
{
  gtk_video_private_t *priv = this->priv;
  return priv->video_port->get_capabilities (priv->video_port);
}

static inline double mkdtime (const struct timeval *tv)
{
  return tv->tv_sec + tv->tv_usec / 1000000.0;
}

gboolean gtk_video_in_spu_button (GtkVideo *gtv, int dir)
{
  gtk_video_private_t *priv = gtv->priv;

  if (dir >= 0)
  {
    priv->spu.in = !!dir;
    gtv_set_pointer_shape (gtv, !!dir);
    if (dir)
      gettimeofday (&priv->spu.time, NULL);
  }

  if (priv->spu.in)
    return TRUE;

  /* timeout of 1s from when a button is entered */
  struct timeval now;
  gettimeofday (&now, NULL);

  return mkdtime (&now) - mkdtime (&priv->spu.time) <= 1.0;
}

#ifdef HAVE_X11
static void gtv_send_xine_mouse_event (gtk_video_private_t *priv,
				       int x, int y, int button)
{
  x11_rectangle_t   rect;
  xine_event_t      xev;
  xine_input_data_t input;

  rect.x = x;
  rect.y = y;
  rect.w = 0;
  rect.h = 0;

  xine_port_send_gui_data (priv->video_port,
			   XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO,
			   (void *) &rect);

  xev.type	  = button ? XINE_EVENT_INPUT_MOUSE_BUTTON
			   : XINE_EVENT_INPUT_MOUSE_MOVE;
  xev.data	  = &input;
  xev.data_length = sizeof (input);
  input.button    = button;
  input.x	  = rect.x;
  input.y	  = rect.y;

  xine_event_send (priv->stream, &xev);
}

static GdkFilterReturn xevent_filter_cb (XEvent *const xev, GdkEvent *const ev,
					 GtkVideo *const gtv)
{
  gtk_video_private_t *const priv = ((GtkVideo *) gtv)->priv;

  if (xev->xany.window != GDK_WINDOW_XID (gtk_widget_get_window (&gtv->widget)))
    return GDK_FILTER_CONTINUE;

  switch (xev->type)
  {
  case Expose:
    if (!xev->xexpose.count)
    {
#ifndef HAVE_XCB
      xine_port_send_gui_data (priv->video_port,
			       XINE_GUI_SEND_EXPOSE_EVENT, xev);
#else
      xcb_expose_event_t xcb_event;
      memset(&xcb_event, 0, sizeof(xcb_event));

      xcb_event.window = xev->xexpose.window;
      xcb_event.x      = xev->xexpose.x;
      xcb_event.y      = xev->xexpose.y;
      xcb_event.width  = xev->xexpose.width;
      xcb_event.height = xev->xexpose.height;
      xcb_event.count  = xev->xexpose.count;

      xine_port_send_gui_data (priv->video_port,
			       XINE_GUI_SEND_EXPOSE_EVENT, &xcb_event);
#endif
    }
    logprintf ("gtkvideo: expose event: %d,%d %dx%d %d\n",
	       xev->xexpose.x, xev->xexpose.y,
	       xev->xexpose.width, xev->xexpose.height, xev->xexpose.count);
    return GDK_FILTER_REMOVE;

  case FocusIn:
    return GDK_FILTER_REMOVE; /* otherwise the window might be blanked :-| */

  case FocusOut:
    return GDK_FILTER_REMOVE;

  case MotionNotify:
    logprintf ("gtkvideo: mouse event: mx=%d my=%d\n",
	       xev->xmotion.x, xev->xmotion.y);
    gtv_send_xine_mouse_event (priv, xev->xmotion.x, xev->xmotion.y, 0);
    if (priv->fullscreen_mode)
      gtv_show_pointer (gtv);
    return GDK_FILTER_CONTINUE;

  case ButtonPress:
    logprintf ("gtkvideo: mouse button press: mx=%d my=%d, b=%d\n",
	       xev->xbutton.x, xev->xbutton.y, xev->xbutton.button);

    /* permit the GTK event if allowed and neither Shift is pressed;
     * otherwise, tell libxine (unless we're sending GTK keyrelease events
     * for gtv button)
     */
    if (!(xev->xbutton.state & ShiftMask)
	&& (priv->button_press_mask & (1 << xev->xbutton.button)))
      return GDK_FILTER_CONTINUE;

    if (!(priv->button_release_mask & (1 << xev->xbutton.button)))
    {
      gtv_send_xine_mouse_event (priv, xev->xbutton.x, xev->xbutton.y,
				 xev->xbutton.button);
      /* record if either Shift is pressed for gtv button
       * (so that we know *not* to send the release event to the front end)
       */
      if (xev->xbutton.state & ShiftMask)
	priv->button_event_shifted |= 1 << xev->xbutton.button;
    }
    return gtk_video_in_spu_button (gtv, -1) ? GDK_FILTER_REMOVE : GDK_FILTER_CONTINUE;

  case ButtonRelease:
    /* permit the GTK event if allowed *and* Shift wasn't pressed when
     * the corresponding press event was received
     */
    logprintf("gtkvideo: mouse button release: mx=%d my=%d, b=%d\n",
	      xev->xbutton.x, xev->xbutton.y, xev->xbutton.button);

    if (priv->button_event_shifted & (1 << xev->xbutton.button))
      priv->button_event_shifted &= ~(1 << xev->xbutton.button);
    else if (priv->button_release_mask & (1 << xev->xbutton.button))
      return GDK_FILTER_CONTINUE;

    return GDK_FILTER_REMOVE;

#if defined HAVE_XTESTEXTENSION || defined LOG
  case KeyPress:
    {
      static char    buffer [20];
      KeySym         keysym;
      XComposeStatus compose;

      XLookupString (&xev->xkey, buffer, sizeof (buffer), &keysym, &compose);
      logprintf ("gtkvideo: key press %ld\n", (long) keysym);
#ifdef HAVE_XTESTEXTENSION
      switch (keysym)
      {
      case XK_Shift_L: shift |= 1; break;
      case XK_Shift_R: shift |= 2; break;
      }
#endif
    }
    return GDK_FILTER_CONTINUE;

  case KeyRelease:
    {
      static char    buffer [20];
      KeySym         keysym;
      XComposeStatus compose;

      XLookupString (&xev->xkey, buffer, sizeof (buffer), &keysym, &compose);
      logprintf ("gtkvideo: key release %ld\n", (long) keysym);
#ifdef HAVE_XTESTEXTENSION
      switch (keysym)
      {
      case XK_Shift_L: shift &= ~1; break;
      case XK_Shift_R: shift &= ~2; break;
      }
#endif
    }
    return GDK_FILTER_CONTINUE;
#endif /* XTEST && LOG */

  default:
    if (xev->type < LASTEvent) {
      logprintf ("gtkvideo: received event %d\n", xev->type);
    }
  }

  return GDK_FILTER_CONTINUE;
}
#endif

static gboolean configure_cb (GtkWidget *widget, GdkEventConfigure *event,
			      GtkVideo *this)
{
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (&this->widget, &allocation);

  this->priv->pos.width = event->x + allocation.width;
  this->priv->pos.height = event->y + allocation.height;
#if !defined(HAVE_X11) && defined(WIN32)
  xine_port_send_gui_data (this->priv->video_port, GUI_WIN32_MOVED_OR_RESIZED, NULL);
#endif
  return FALSE;
}

static void show_cb (GtkWidget *w, gpointer d)
{
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (w, &allocation);

  gtk_widget_set_size_request (w, allocation.width, allocation.height);
  gtk_video_allow_shrink (GTK_VIDEO (w));
}

static gboolean gtv_screen_blanker_cb (GtkVideo *gtv)
{
  gtv_unblank_screen (gtv);
  return TRUE;
}

#define MWM_HINTS_DECORATIONS   (1L << 1)
#define PROP_MWM_HINTS_ELEMENTS 5
typedef struct {
  unsigned long	flags;
  unsigned long	functions;
  unsigned long	decorations;
  long		input_mode;
  unsigned long	status;
} MWMHints;

/*
#define gtv_atom_clear(D,W,A) \
  gtv_atom_op ((D), (W), PropModeReplace, (A), NULL)
#define gtv_atom_set(D,W,A,...) \
  gtv_atom_op ((D), (W), PropModeReplace, (A), ##__VA_ARGS__, NULL)
#define gtv_atom_append(D,W,A,...) \
  gtv_atom_op ((D), (W), PropModeAppend, (A), ##__VA_ARGS__, NULL)

static void gtv_atom_op (Display *display, Window window, int mode,
			 const char *atom, ...)
{
  Atom prop = XInternAtom (display, atom, False);
  size_t size = 0, alloc = 8;
  Atom *propvalues = malloc (alloc * sizeof (Atom));
  va_list ap;
  const char *arg;

  va_start (ap, atom);
  while ((arg = va_arg (ap, const char *)))
  {
    if (size + 1 >= alloc)
      propvalues = realloc (propvalues, (alloc += 8) * sizeof (Atom));
    propvalues[size++] = XInternAtom (display, arg, False);
  }
  propvalues[size] = 0;

  XLockDisplay (display);
  XChangeProperty (display, window, prop, XA_ATOM, 32, mode,
		   (unsigned char *) &propvalues, size);
  XUnlockDisplay (display);
  free (propvalues);
}

#define gtv_layer_set(D,W,L) \
  gtv_atom_cardinal ((D), (W), "_WIN_LAYER", (L))

static void gtv_atom_cardinal (Display *display, Window window,
			       const char *atom, long value)
{
  XLockDisplay (display);
  XChangeProperty (display, window, XInternAtom (display, atom, False),
		   XA_CARDINAL, 32, PropModeReplace,
		   (unsigned char *) &value, 1);
  XUnlockDisplay (display);
}
*/

const GtkRequisition *gtk_video_get_fullscreen_geometry (GtkVideo *gtv)
{
  GdkScreen *screen = gtv_get_screen (gtv);
  GdkRectangle geom;
  int monitor = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (&gtv->widget));
  gdk_screen_get_monitor_geometry (screen, monitor, &geom);

#if defined(HAVE_X11) && defined(HAVE_XINERAMA)
  Display *display = gtv_get_xdisplay (gtv);

  if (XineramaIsActive (display))
  {
    int count;
    XineramaScreenInfo *xinerama = XineramaQueryScreens (display, &count);

    /* default first */
    gtv->priv->fullscreen.width = xinerama[monitor].width;
    gtv->priv->fullscreen.height = xinerama[monitor].height;

    /* find the right one :-) */
    for (monitor = 0; monitor < count; ++monitor)
    {
      if (xinerama[monitor].x_org  == geom.x &&
          xinerama[monitor].y_org  == geom.y &&
          xinerama[monitor].width  == geom.width &&
          xinerama[monitor].height == geom.height)
      {
        gtv->priv->fullscreen.width = xinerama[monitor].width;
        gtv->priv->fullscreen.height = xinerama[monitor].height;
        break;
      }
    }
    XFree (xinerama);
  }
  else
#endif
  {
    gtv->priv->fullscreen.width = geom.width;
    gtv->priv->fullscreen.height = geom.height;
  }
  return &gtv->priv->fullscreen;
}

static void fs_resize_cb (GdkScreen *screen, GtkVideo *gtv)
{
  gtk_video_get_fullscreen_geometry (gtv);
  logprintf ("gtkvideo: (GDK) screen resized to %dx%d\n",
	     gdk_screen_get_width (screen), gdk_screen_get_height (screen));
  if (is_fullscreen (gtv->priv))
    gtk_window_resize ((GtkWindow *)gtk_widget_get_toplevel (&gtv->widget),
		       gtv->priv->fullscreen.width,
		       gtv->priv->fullscreen.height);
}

static void fs_reattach_screen_cb (GtkVideo *gtv, GdkScreen *screen,
				   gpointer data)
{
  /* CHECKME: should connect to the new screen */
  g_signal_handler_disconnect (gtv_get_screen (gtv), gtv->priv->screen_signal);
  gtv->priv->screen_signal = g_signal_connect (screen, "size-changed",
					       G_CALLBACK (fs_resize_cb), gtv);
}

static void gtk_video_realize (GtkWidget *widget)
{
  /* GdkWindowAttr attributes; */
  /* gint          attributes_mask; */
  GtkVideo            *this;
  gtk_video_private_t *priv;
  GdkDisplay	      *display;
  GdkScreen	      *screen;
#if defined(HAVE_X11) && !defined(USE_GDK_WINDOW)
  Display	      *xdisplay;
  XColor               black_pixel;
#endif

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_VIDEO(widget));

  this = (GtkVideo *) widget;
  priv = this->priv;

  /* set realized flag */
  gtk_widget_set_realized (widget, TRUE);

  display = gdk_display_open (gdk_display_get_name (gdk_display_get_default ()));
  if (!display)
  {
    g_printerr (_("gtkvideo: XOpenDisplay failed!\n"));
    return;
  }

  screen = gdk_display_get_default_screen (display);

#if defined(HAVE_X11) && !defined(USE_GDK_WINDOW)
  xdisplay = GDK_DISPLAY_XDISPLAY (display);
  XLockDisplay (xdisplay);

  /*
   * create our own video window
   */
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (widget, &allocation);

  black_pixel.pixel = BlackPixel (xdisplay, GDK_SCREEN_XNUMBER (screen));

  priv->video_window = XCreateSimpleWindow (xdisplay,
					    GDK_WINDOW_XID (gtk_widget_get_parent_window(widget)),
                                            allocation.x,
                                            allocation.y,
                                            allocation.width,
                                            allocation.height, 0,
					    black_pixel.pixel, black_pixel.pixel);

  /* not sure why this is needed in some situations... */
  XEvent event;
  XCheckWindowEvent (xdisplay, priv->video_window, -1, &event);

#  if !GTK_CHECK_VERSION(2, 24, 0)
  GdkWindow *new_window = gdk_window_foreign_new_for_display (display, priv->video_window);
#  else
  GdkWindow *new_window = gdk_x11_window_foreign_new_for_display (display, priv->video_window);
#  endif
  gtk_widget_set_window (widget, new_window);

#else /* HAVE_X11 && !USE_GDK_WINDOW */
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (widget, &allocation);

  GdkWindowAttr attrs = {
       x: allocation.x,
       y: allocation.y,
       width: allocation.width,
       height: allocation.height,
       wclass: GDK_INPUT_OUTPUT,
       window_type: GDK_WINDOW_CHILD,
       type_hint: GDK_WINDOW_TYPE_HINT_NORMAL,
       event_mask: GDK_ALL_EVENTS_MASK,
  };
  static GdkColor black = {};
  GtkStyle *style;
  int i;

  memset(&black, 0, sizeof black);
  GdkWindow *new_window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs, GDK_WA_X | GDK_WA_Y | GDK_WA_TYPE_HINT);
  gtk_widget_set_window (widget, new_window);
#if defined(GDK_WINDOWING_X11)
  priv->video_window = GDK_WINDOW_XID( gtk_widget_get_window (widget));
#elif defined(GDK_WINDOWING_WIN32)
  priv->video_window = GDK_WINDOW_HWND( gtk_widget_get_window (widget));
#endif
#endif /* HAVE_X11 && !USE_GDK_WINDOW */

  GdkWindow *window = gtk_widget_get_window(widget);
  if (!window)
  {
    g_printerr (_("gtkvideo: couldn't get a GDK handle for the video window!\n"));
    return;
  }
  gdk_window_set_user_data (window, widget); /* allow GTK events */
  {
    /* enforce background colour */
    static const GdkColor black = {};
    for (int i = 4; i >= 0; --i)
      gtk_widget_modify_bg (widget, i, &black);
  }

  /*
   * prepare for fullscreen playback
   */
  gtk_video_get_fullscreen_geometry (this);
  priv->fullscreen_mode = 0;
  priv->screen_signal = g_signal_connect (screen, "size-changed",
					  G_CALLBACK (fs_resize_cb), this);

  /*
   * track configure events of toplevel window
   */
  g_signal_connect (gtk_widget_get_toplevel (widget), "configure-event",
		    G_CALLBACK (configure_cb), this);

  /*
   * handle show events etc.
   */
  g_object_connect (G_OBJECT (widget),
	"signal::show", G_CALLBACK (show_cb), NULL,
	"signal::screen-changed", G_CALLBACK (fs_reattach_screen_cb), NULL,
	NULL);

#ifdef HAVE_X11
#ifdef HAVE_XTESTEXTENSION
  {
    int dummy1 = 0, dummy2 = 0, dummy3 = 0, dummy4 = 0;

    priv->have_xtest = XTestQueryExtension (xdisplay, &dummy1, &dummy2, &dummy3, &dummy4);
    priv->kc_shift[0] = XKeysymToKeycode (xdisplay, XK_Shift_L);
    priv->kc_shift[1] = XKeysymToKeycode (xdisplay, XK_Shift_R);
  }
#endif

  gdk_window_set_events (window, GDK_ALL_EVENTS_MASK);
#ifndef USE_GDK_WINDOW
  XSelectInput (xdisplay, priv->video_window,
		StructureNotifyMask | ExposureMask | FocusChangeMask |
		KeyPressMask | KeyReleaseMask | PointerMotionMask |
		ButtonPressMask | ButtonReleaseMask | ButtonMotionMask);
#endif
  gdk_window_add_filter (window, (GdkFilterFunc) xevent_filter_cb,
			 widget);

#ifndef USE_GDK_WINDOW
  XUnlockDisplay (xdisplay);
#endif
#endif /* HAVE_X11 */

  /*
   * load audio, video drivers
   */

  priv->video_port = load_video_out_driver (this);
  if (!priv->video_port)
  {
    display_error_modal (FROM_GXINE, _("Fatal error"),
			 _("No video output driver could be loaded."));
    exit (2);
  }

  /*
   * create mouse pointers
   */
#if !GTK_CHECK_VERSION(3, 16, 0)
  priv->pointer[0] = gdk_cursor_new(GDK_BLANK_CURSOR);
  priv->pointer[1] = gdk_cursor_new(GDK_LEFT_PTR);
  priv->pointer[2] = gdk_cursor_new(GDK_HAND1);
#else
  priv->pointer[0] = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
  priv->pointer[1] = gdk_cursor_new_for_display(display, GDK_LEFT_PTR);
  priv->pointer[2] = gdk_cursor_new_for_display(display, GDK_HAND1);
#endif
  priv->pointer_visible = TRUE;

#ifdef XDG_SCREENSAVER_COMMAND
  priv->xdg_active = 0;
  priv->have_xdg_screensaver = !xdg_screensaver_poke ("--help", 0);
#endif
  priv->screen_blanker_timeout =
    g_timeout_add (4000, (GSourceFunc) gtv_screen_blanker_cb, this);
}


xine_video_port_t *gtk_video_get_port (GtkVideo *gtv)
{
  gtk_video_private_t *priv = gtv->priv;
  return priv->video_port;
}

static void gtk_video_unrealize (GtkWidget *widget)
{
  GtkVideo            *this;
  gtk_video_private_t *priv;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_VIDEO(widget));

  this = (GtkVideo *) widget;
  priv = this->priv;

  if (priv->screen_blanker_timeout)
  {
    g_source_remove (priv->screen_blanker_timeout);
    priv->screen_blanker_timeout = 0;
  }

  /* Hide all windows */
  if (gtk_widget_get_mapped (widget))
    gtk_widget_unmap (widget);

  gtk_widget_set_mapped (widget, FALSE);

  /* This destroys widget->window and unsets the realized flag */
  if (GTK_WIDGET_CLASS(parent_class)->unrealize)
    (* GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
}

static void gtk_video_map (GtkWidget *widget)
{
  GdkGeometry geom;
  parent_class->map (widget);
  if (!((GtkVideo *)widget)->priv->hinted)
  {
    ((GtkVideo *)widget)->priv->hinted = TRUE;
    geom.min_width = geom.min_height = -1;
    gtk_window_set_geometry_hints (GTK_WINDOW (gtk_widget_get_toplevel (widget)),
				   widget, &geom, GDK_HINT_MIN_SIZE);
  }
}

GtkWidget *gtk_video_new (xine_t *xine, xine_stream_t *stream,
			  xine_post_out_t *out,
                          const char *video_driver_id,
			  int default_width, int default_height,
			  int button_press_mask, int button_release_mask)
{
  GtkWidget	      *this = g_object_new (gtk_video_get_type (), NULL);
  GtkVideo            *gtv = (GtkVideo *) this;
  gtk_video_private_t *priv;

  gtv->priv = priv = g_malloc0 (sizeof (gtk_video_private_t));

  priv->xine = xine;
  priv->stream = stream;
  priv->out = out;
  priv->resize_factor = 50;
  priv->auto_resize = TRUE;
  priv->size = (GtkRequisition){ default_width, default_height };
  priv->video_size = priv->size;
  priv->button_press_mask = button_press_mask;
  priv->button_release_mask = button_release_mask;
  priv->vis_plugin_id = NULL;
  priv->vis_plugin = NULL;
  priv->post_deinterlace.list = NULL;
  priv->post_video.list = NULL;
  priv->post_audio.list = NULL;
  priv->frame_cb = NULL;
  priv->pointer_shape_old = -1;

  priv->video_driver_id = video_driver_id;

  gtv_init_dbus (gtv);

  return this;
}

void gtk_video_set_frame_cb (GtkVideo *gtv, gtk_video_frame_cb_t cb,
			     gpointer data)
{
   g_return_if_fail (GTK_IS_VIDEO(gtv));
   gtv->priv->frame_cb = cb;
   gtv->priv->frame_cb_data = data;
}

void gtk_video_set_end_time (GtkVideo *gtv, gint end_time)
{
   g_return_if_fail (GTK_IS_VIDEO(gtv));
   gtv->priv->end_time = end_time;
}

static gboolean gtv_update_resize_factor (GtkVideo *this)
{
  gboolean send_signal = FALSE;
  gtk_video_private_t *const priv = this->priv;
  double new_factor;

  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (&this->widget, &allocation);

  if (priv->pending_resize)
    new_factor = priv->resize_factor;
  else
  {
    gboolean is_double =
      IS_DOUBLE(priv->video_size.width, priv->video_size.height);
    new_factor = allocation.width * 100
		 / (double)priv->video_size.width / (1 + is_double);
    double new_factor_V = allocation.height * 100
			  / (double)priv->video_size.height
			  / (1 + is_double);
    if (new_factor > new_factor_V)
      new_factor= new_factor_V;
  }
  /* tolerate rounding error when the video size has been changed... */
  if (priv->pending_resize
      || (round (priv->resize_factor * 64) != round (new_factor * 64)
	  && (fabs (new_factor - priv->resize_factor)
		>= 25.0 / allocation.height
	      || priv->old_video_size.height == priv->video_size.height)))
  {
    if (!priv->block_next_scale_change && gtk_widget_get_visible (&this->widget))
    {
      priv->resize_factor = new_factor;
      send_signal = TRUE;
      /* stop frame_output_cb from scheduling a resize */
      double factor = DOUBLE_STD();
      priv->size = SCALE (factor);
      logprintf ("gxine: video rescaled to %lf%%\n", new_factor);
    }
    else {
      logprintf ("blocked rescaling to %lf%%\n", new_factor);
    }
  }
  return send_signal;
}

static void gtk_video_size_allocate (GtkWidget *widget,
                                     GtkAllocation *p_allocation)
{
  GtkVideo *this;

  g_return_if_fail (widget != NULL);
  g_return_if_fail(GTK_IS_VIDEO(widget));

  this = (GtkVideo *) widget;

  if (gtk_widget_get_realized (widget))
  {
    gboolean send_signal = FALSE;
    GtkAllocation allocation = *p_allocation;

    if (is_fullscreen (this->priv))
    {
      allocation.width  = this->priv->fullscreen.width;
      allocation.height = this->priv->fullscreen.height;
    }

    gtk_widget_set_allocation (widget, &allocation);

    gdk_window_move_resize (gtk_widget_get_window (widget),
                            allocation.x, allocation.y,
                            allocation.width, allocation.height);
    if (this->priv->old_video_size.width && !this->priv->resizing
	&& !this->priv->idle_resized
	&& !is_fullscreen (this->priv)
	&& gtk_widget_get_visible (widget)
        && allocation.width > GTK_VIDEO_MIN_WIDTH
        && allocation.height > GTK_VIDEO_MIN_HEIGHT)
      send_signal = gtv_update_resize_factor (this);
    if (!this->priv->resizing)
      this->priv->old_video_size = this->priv->video_size;
    this->priv->idle_resized = FALSE;

    if (send_signal)
      g_signal_emit ((GObject *)this,
		     gtv_table_signals[GTK_VIDEO_SCALE_FACTOR], 0,
		     this->priv->resize_factor);
  }
  else
    gtk_widget_set_allocation (widget, p_allocation);

  if (gtk_widget_get_window(widget))
  {
    /* FIXME: stop something unknown (as of 20051014) from blanking the window
     * (no map/unmap event handlers are expected)
     */
    gtk_widget_unmap (widget);
    gtk_widget_map (widget);
  }
}

void gtk_video_resize (GtkVideo *gtv,
		       gint x, gint y,
		       gint width, gint height)
{
  GtkAllocation allocation = { x, y, width, height };
  gtk_video_size_allocate (&gtv->widget, &allocation);
}

void gtk_video_rescale (GtkVideo *gtv, double scale)
{
  RESIZE_LOCK ();
  {
    gtk_video_private_t *priv = gtv->priv;
    if (scale == -1)
      scale = priv->resize_factor;
    double factor = DOUBLE (scale, priv->video_size.width,
				  priv->video_size.height);
    priv->size = SCALE (factor);
    priv->resize_factor = scale;
    logprintf ("rescale: size = %d,%d factor=%f\n",
	       priv->size.width, priv->size.height, scale);
    if (is_fullscreen (priv) || !gtk_widget_get_visible (&gtv->widget))
      priv->pending_resize = TRUE;
    else
      gtv_do_rescale (gtv);
  }
  RESIZE_UNLOCK ();
}

static gboolean unblock_cb (gpointer gtv)
{
  logprintf ("unblocking rescale\n");
  ((GtkVideo *)gtv)->priv->block_next_scale_change = FALSE;
  ((GtkVideo *)gtv)->priv->unblock_timeout = 0;
  return FALSE;
}

static void gtv_unblock_timeout (GtkVideo *gtv)
{
  if (gtv->priv->unblock_timeout)
    g_source_remove (gtv->priv->unblock_timeout);
  gtv->priv->block_next_scale_change = TRUE;
  gtv->priv->unblock_timeout = g_timeout_add (250, unblock_cb, gtv);
}

void gtk_video_reshow (GtkVideo *gtv)
{
  if (gtv && GTK_IS_VIDEO (gtv))
  {
    gtv->priv->block_next_scale_change = TRUE;
    gtk_widget_show (&gtv->widget);
    gtk_widget_set_size_request (&gtv->widget,
				 gtv->priv->size.width, gtv->priv->size.height);
    gdk_flush ();
    gtv_unblock_timeout (gtv);
    gtk_video_allow_shrink (gtv);
  }
}

void gtk_video_set_fullscreen (GtkVideo *gtv, gint fullscreen,
                               GRecMutex *const lock)
{
  gtk_video_private_t  *priv;
  GtkWindow	       *toplevel;
#ifdef HAVE_X11
  Display	       *display;
#endif

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  if (fullscreen == is_fullscreen (priv))
    return;

  g_rec_mutex_lock (lock);
#ifdef HAVE_X11
  display = gtv_get_xdisplay (gtv);
  XLockDisplay (display);
#endif
  toplevel = GTK_WINDOW (gtk_widget_get_toplevel (&gtv->widget));

  if (fullscreen)
  {
    GtkAllocation allocation = { 0 };
    gtk_widget_get_allocation (&gtv->widget, &allocation);

    priv->fullscreen_mode = fullscreen;

    do_pending_events (); /* there may be pending resize events... */
    priv->old_size.width = allocation.width;
    priv->old_size.height = allocation.height;
    /* just fullscreen it - beyond that, rely on window-state-event */
    gtk_window_fullscreen (toplevel);
    do_pending_events (); /* events have been generated - process them */
    gtk_video_get_fullscreen_geometry (gtv);
    gtv_hide_pointer (gtv);
    gtv_screensaver_maybe_inhibit_dbus (gtv->priv, TRUE);
  }
  else
  {
    priv->fullscreen_mode = 2; /* special state :-) */

    priv->block_next_scale_change = TRUE;
    gtk_widget_set_size_request (&gtv->widget,
				 priv->size.width, priv->size.height);
    gdk_flush ();
    /* just unfullscreen it - beyond that, rely on window-state-event */
    gtk_window_unfullscreen (toplevel);
    gtv_screensaver_maybe_inhibit_dbus (gtv->priv, gtv_test_inhibit_blank (gtv));
    gtv_show_pointer (gtv);
    gdk_flush ();
    sched_yield (); /* hmm, seems to be needed here... */

    /* resize the window, one way or another */
    if (priv->pending_resize)
      gtv_do_rescale (gtv);

    do_pending_events (); /* events have been generated - process them */
    gtv_unblock_timeout (gtv); /* idle may be called early :-\ */
    gtk_video_allow_shrink (gtv);

    priv->fullscreen_mode = FALSE;
  }
#ifdef HAVE_X11
/* pointer motion will take care of this
  struct {
    Window root, child;
    int rx, ry, x, y, mask;
  } pos;
  XQueryPointer (display, GDK_WINDOW_XWINDOW (gtv->widget.window),
		 &pos.root, &pos.child, &pos.rx, &pos.ry,
		 &pos.x, &pos.y, &pos.mask);
  gtv_send_xine_mouse_event (priv, pos.x, pos.y, 0);
*/

  XUnlockDisplay (display);
#endif
  g_rec_mutex_unlock (lock);
}

gint gtk_video_is_fullscreen (GtkVideo *gtv)
{
  g_return_val_if_fail (gtv != NULL, 0);
  g_return_val_if_fail (GTK_IS_VIDEO (gtv), 0);

  return !!gtv->priv->fullscreen_mode;
}

void gtk_video_set_auto_resize (GtkVideo *gtv, gboolean resize)
{
  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));

  gtv->priv->auto_resize = resize;
}

gboolean gtk_video_get_auto_resize (GtkVideo *gtv)
{
  g_return_val_if_fail (gtv != NULL, 0);
  g_return_val_if_fail (GTK_IS_VIDEO (gtv), 0);

  return gtv->priv->auto_resize;
}

void gtk_video_set_windowed_unblank (GtkVideo *gtv, gboolean unblank)
{
  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));

  gtv->priv->playing_no_blank = unblank;
}

gboolean gtk_video_get_windowed_unblank (GtkVideo *gtv)
{
  g_return_val_if_fail (gtv != NULL, 0);
  g_return_val_if_fail (GTK_IS_VIDEO (gtv), 0);

  return gtv->priv->playing_no_blank;
}

void
gtk_video_deny_shrink (GtkVideo *gtv)
{
  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));

  if (gtv->priv->minimising)
  {
    g_source_remove (gtv->priv->minimising);
    gtv->priv->minimising = 0;
  }

  GtkRequisition requisition = gtv->priv->pending_resize
                               ? gtv->priv->size
                               : gtv->priv->old_size;
#if !GTK_CHECK_VERSION(2, 24, 0)
  gtv->widget.requisition = requisition;
#endif
  g_object_set (gtv,
		"width-request", requisition.width,
		"height-request", requisition.height,
		NULL);
}

static gboolean
gtv_allow_shrink (gpointer gtv)
{
  ((GtkVideo *)gtv)->priv->minimising = 0;

  if (is_fullscreen (((GtkVideo *)gtv)->priv))
    return FALSE;

#if !GTK_CHECK_VERSION(2, 24, 0)
  ((GtkVideo *)gtv)->widget.requisition =
    (GtkRequisition){ GTK_VIDEO_MIN_WIDTH, GTK_VIDEO_MIN_HEIGHT };
#endif
  g_object_set (gtv, "width-request", -1, "height-request", -1, NULL);
  return FALSE;
}

gboolean
gtk_video_allow_shrink (GtkVideo *gtv)
{
  if (gtv)
  {
    if (gtv->priv->minimising)
      g_source_remove (gtv->priv->minimising);
    gtv->priv->minimising = g_timeout_add (1000, gtv_allow_shrink, gtv);
  }
  return FALSE;
}

/* Plugins: shared post-plugin code */

static GList *gtv_create_plugins (gtk_video_private_t *priv,
				  xine_audio_port_t **audio,
				  xine_video_port_t **video,
				  const char *plugins, int type,
				  gboolean is_deinterlace)
{
  GList *list = NULL;

  if (is_deinterlace)
    priv->have_tvtime = FALSE;

  if (!plugins || !plugins[0])
    return NULL;

  for (;;)
  {
    char *next = strchr (plugins, ';') ? : (char *)plugins + strlen (plugins);
    char *args = strchr (plugins, ':');

    if (*plugins != '-')
    {
      char *name;
      xine_post_t *plugin;

      if (args > next)
	args = NULL;

      logprintf ("plugin: %p %.*s\n", plugins, (int)((args?:next) - plugins), plugins);
      if (args) {
	logprintf ("args  : %p %.*s\n", args ? args + 1 : NULL,
		   (int)(args ? next - args - 1 : 0), args ? args + 1 : NULL);
      }
      logprintf ("next  : %p\n", next);

      name = g_strndup (plugins, (args ? : next) - plugins);
      if (is_deinterlace && !strcasecmp (name, "tvtime"))
	priv->have_tvtime = TRUE;
      plugin = xine_post_init (priv->xine, name, 0, audio, video);
      if (plugin)
      {
	if (plugin->type == type)
	{
	  list = g_list_append (list, plugin);
	  if (args)
	  {
	    args = g_strndup (args + 1, next - args - 1);
	    post_parse_set_parameters (plugin, args);
	    g_free (args);
	  }
	}
	else
	{
	  g_printerr (("gtkvideo: post-plugin '%s': wrong type (wanted %d, got %d)\n"), name, type, plugin->type);
	  xine_post_dispose (priv->xine, plugin);
	}
      }
      else
	g_printerr (_("gtkvideo: unknown post-plugin ‘%s’\n"), name);
      g_free (name);
    }

    if (!*next)
      break;
    plugins = next + 1;
  }

  return list;
}

static void gtv_dispose_plugin (xine_post_t *post, xine_t *xine)
{
  xine_post_dispose (xine, post);
}

static void gtv_dispose_plugins (gtk_video_private_t *priv, GList *list)
{
  g_list_foreach (list, (GFunc) gtv_dispose_plugin, priv->xine);
  g_list_free (list);
}

static xine_post_out_t *gtv_post_output (xine_post_t *post,
					 const char *const s[])
{
  return xine_post_output (post, s[0])
	 ? : xine_post_output (post, s[1])
	     ? : xine_post_output (post, xine_post_list_outputs (post)[0]);
}

static xine_post_in_t *gtv_post_input (xine_post_t *post,
				       const char *const s[])
{
  return xine_post_input (post, s[0])
	 ? : xine_post_input (post, s[1])
	     ? : xine_post_input (post, xine_post_list_inputs (post)[0]);
}

/* Plugins: video post-plugins */

static const char *const video_io[] = { "video out", "video", "video in" };

static xine_post_out_t *gtv_post_output_video (xine_post_t *post)
{
  return gtv_post_output (post, video_io);
}

static xine_post_in_t *gtv_post_input_video (xine_post_t *post)
{
  return gtv_post_input (post, video_io + 1);
}

static void gtv_wire_plugins_video (gtk_video_private_t *priv)
{
  /* note: assumes default wiring */
  GList *list = priv->post_video.enable
		? g_list_copy (priv->post_video.list) : NULL;
  GList *item, *next = NULL;

  if (priv->post_deinterlace.list && priv->post_deinterlace.enable)
    list = g_list_concat (g_list_copy (priv->post_deinterlace.list), list);

  xine_post_wire_video_port (xine_get_video_source (priv->stream),
			     priv->video_port);

  item = g_list_last (list);
  while (item)
  {
    xine_post_out_t *out = gtv_post_output_video (item->data);
    if (next)
      xine_post_wire (out, gtv_post_input_video (next->data));
    else
      xine_post_wire_video_port (out, priv->video_port);
    next = item;
    item = g_list_previous (item);
  }

  if (next)
    xine_post_wire (xine_get_video_source (priv->stream),
		    gtv_post_input_video (next->data));

  g_list_free (list);

  xine_set_param (priv->stream, XINE_PARAM_VO_DEINTERLACE,
		  priv->post_deinterlace.enable &&
		    !priv->post_deinterlace.list);
}

static void gtv_unwire_plugins_video (gtk_video_private_t *priv)
{
  GList *item;
  xine_post_wire_video_port (xine_get_video_source (priv->stream),
			     priv->video_port);
  item = g_list_last (priv->post_video.list);
  while (item)
  {
    xine_post_wire (NULL, gtv_post_input_video (item->data));
    item = g_list_previous (item);
  }
}

void gtk_video_set_post_plugins_video (GtkVideo *gtv, const char *params)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  gtv_unwire_plugins_video (priv);
  gtv_dispose_plugins (priv, priv->post_video.list);
  priv->post_video.list =
    gtv_create_plugins (priv, NULL, &priv->video_port, params,
			XINE_POST_TYPE_VIDEO_FILTER, FALSE);
  gtv_wire_plugins_video (priv);
}

void gtk_video_set_use_post_plugins_video (GtkVideo *gtv, gboolean on)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  gtv_unwire_plugins_video (priv);
  priv->post_video.enable = on;
  gtv_wire_plugins_video (priv);
}

gboolean gtk_video_get_use_post_plugins_video (GtkVideo *gtv)
{
  return (gtv && gtv->priv) ? gtv->priv->post_video.enable : FALSE;
}


/* Plugins: audio post-plugins */

static const char *const audio_io[] = { "audio out", "audio", "audio in" };

static xine_post_out_t *gtv_post_output_audio (xine_post_t *post)
{
  return gtv_post_output (post, audio_io);
}

static xine_post_in_t *gtv_post_input_audio (xine_post_t *post)
{
  return gtv_post_input (post, audio_io + 1);
}

static void gtv_wire_plugins_audio (gtk_video_private_t *priv,
				    xine_audio_port_t *audio_port)
{
  /* note: assumes default wiring */
  GList *list = priv->post_audio.enable
		? g_list_copy (priv->post_audio.list) : NULL;
  GList *item, *next = NULL;

  if (priv->vis_active && priv->vis_plugin)
    list = g_list_prepend (list, priv->vis_plugin);

  item = g_list_last (list);
  while (item)
  {
    xine_post_out_t *out = gtv_post_output_audio (item->data);
    if (next)
      xine_post_wire (out, gtv_post_input_audio (next->data));
    else
      xine_post_wire_audio_port (out, audio_port);
    next = item;
    item = g_list_previous (item);
  }

  if (next)
    xine_post_wire (xine_get_audio_source (priv->stream),
		    gtv_post_input_audio (next->data));

  g_list_free (list);
}

static void gtv_unwire_plugins_audio (gtk_video_private_t *priv,
				      xine_audio_port_t *audio_port)
{
  GList *item;
  xine_post_wire_audio_port (xine_get_audio_source (priv->stream), audio_port);
  item = g_list_last (priv->post_audio.list);
  while (item)
  {
    xine_post_wire (NULL, gtv_post_input_audio (item->data));
    item = g_list_previous (item);
  }
  if (priv->vis_plugin)
    xine_post_wire (NULL, gtv_post_input_audio (priv->vis_plugin));
}

void gtk_video_set_post_plugins_audio (GtkVideo *gtv, const char *params,
				       xine_audio_port_t *audio_port)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  gtv_unwire_plugins_audio (priv, audio_port);
  gtv_dispose_plugins (priv, priv->post_audio.list);
  priv->post_audio.list =
    gtv_create_plugins (priv, &audio_port, NULL, params,
			XINE_POST_TYPE_AUDIO_FILTER, FALSE);
  gtv_wire_plugins_audio (priv, audio_port);
}

void gtk_video_set_use_post_plugins_audio (GtkVideo *gtv, gboolean on,
					   xine_audio_port_t *audio_port)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  priv->post_audio.enable = on;
  gtv_unwire_plugins_audio (priv, audio_port);
  gtv_wire_plugins_audio (priv, audio_port);
}

gboolean gtk_video_get_use_post_plugins_audio (GtkVideo *gtv)
{
  return (gtv && gtv->priv) ? gtv->priv->post_audio.enable : FALSE;
}


/* Plugins: audio visualisation post-plugins */

static void
gtv_wire_plugin_vis (gtk_video_private_t *priv, xine_audio_port_t *audio_port)
{
  if (!priv->vis_plugin)
    return;

  if (priv->post_audio.list)
    xine_post_wire (gtv_post_output_audio (priv->vis_plugin),
		    gtv_post_input_audio (priv->post_audio.list->data));
  else
    xine_post_wire_audio_port (gtv_post_output_audio (priv->vis_plugin),
			       audio_port);
  xine_post_wire (xine_get_audio_source (priv->stream),
		  gtv_post_input_audio (priv->vis_plugin));
}

static void
gtv_unwire_plugin_vis (gtk_video_private_t *priv, xine_audio_port_t *audio_port)
{
  if (!priv->vis_plugin)
    return;

  if (priv->post_audio.list)
    xine_post_wire (xine_get_audio_source (priv->stream),
		    gtv_post_input_audio (priv->post_audio.list->data));
  else
    xine_post_wire_audio_port (xine_get_audio_source (priv->stream),
			       audio_port);
  xine_post_wire (NULL, gtv_post_input_audio (priv->vis_plugin));
}

static void gtv_recreate_vis (gtk_video_private_t *priv,
			      xine_audio_port_t **audio_port)
{
  if (priv->vis_plugin)
    xine_post_dispose (priv->xine, priv->vis_plugin);
  if (priv->vis_active && priv->vis_plugin_id)
    priv->vis_plugin = xine_post_init (priv->xine, priv->vis_plugin_id, 0,
				       audio_port, &priv->video_port);
  else
    priv->vis_plugin = NULL;
}

gboolean gtk_video_select_vis (GtkVideo *gtv, const char *id,
			       xine_audio_port_t **audio_port)
{
  gtk_video_private_t *priv;

  g_return_val_if_fail (gtv != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_VIDEO (gtv), FALSE);
  priv = gtv->priv;
  g_return_val_if_fail (priv->stream != NULL, FALSE);

  if (!priv->vis_plugin_id && (!id || !strcasecmp (id, "none")))
    return FALSE; /* same lack of plugin => nothing to do */

  if (priv->vis_plugin_id && id && !strcasecmp (priv->vis_plugin_id, id))
    return priv->vis_plugin != NULL; /* same plugin => nothing to do */

  if (priv->vis_active)
    gtv_unwire_plugin_vis (priv, *audio_port);
  free (priv->vis_plugin_id);
  priv->vis_plugin_id = id ? strdup (id) : NULL;
  gtv_recreate_vis (priv, audio_port);
  if (priv->vis_active)
    gtv_wire_plugin_vis (priv, *audio_port);

  return priv->vis_plugin != NULL;
}

gboolean gtk_video_set_vis (GtkVideo *gtv, xine_audio_port_t **audio_port,
			    gboolean active)
{
  gtk_video_private_t  *priv;

  g_return_val_if_fail (gtv != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_VIDEO (gtv), FALSE);
  priv = gtv->priv;
  g_return_val_if_fail (priv->stream != NULL, FALSE);

  if (priv->vis_active == active)
    return priv->vis_plugin != NULL; /* same state => nothing to do */

  if (priv->vis_active)
    gtv_unwire_plugin_vis (priv, *audio_port);
  priv->vis_active = active;
  gtv_recreate_vis (priv, audio_port);
  if (priv->vis_active)
    gtv_wire_plugin_vis (priv, *audio_port);

  return priv->vis_plugin != NULL;
}


/* Plugins: deinterlace post-plugin */

void gtk_video_set_post_plugins_deinterlace (GtkVideo *gtv, const char *params)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  gtv_unwire_plugins_video (priv);
  gtv_dispose_plugins (priv, priv->post_deinterlace.list);
  priv->post_deinterlace.list =
    gtv_create_plugins (priv, NULL, &priv->video_port, params,
			XINE_POST_TYPE_VIDEO_FILTER, TRUE);
  gtv_wire_plugins_video (priv);

  /* allow the old method if there are no created deinterlace plugins */
  xine_set_param (priv->stream, XINE_PARAM_VO_DEINTERLACE,
		  priv->post_deinterlace.enable &&
		    (!priv->post_deinterlace.list || priv->have_tvtime));
}

void gtk_video_set_use_post_plugins_deinterlace (GtkVideo *gtv, gboolean on)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  gtv_unwire_plugins_video (priv);
  priv->post_deinterlace.enable = on;
  gtv_wire_plugins_video (priv);

  /* allow the old method if there are no created deinterlace plugins */
  xine_set_param (priv->stream, XINE_PARAM_VO_DEINTERLACE,
		  on && (!priv->post_deinterlace.list || priv->have_tvtime));
}

gboolean gtk_video_get_use_post_plugins_deinterlace (GtkVideo *gtv)
{
  return (gtv && gtv->priv) ? gtv->priv->post_deinterlace.enable : FALSE;
}


/* Other bits */

void gtk_video_set_auto_rescale (GtkVideo *gtv, gboolean set)
{
  gtk_video_private_t *priv;

  g_return_if_fail (gtv != NULL);
  g_return_if_fail (GTK_IS_VIDEO (gtv));
  priv = gtv->priv;
  g_return_if_fail (priv->stream != NULL);

  priv->auto_rescale = set;

  RESIZE_ADD (1);
}

gboolean gtk_video_get_auto_rescale (GtkVideo *gtv)
{
  return (gtv && gtv->priv) ? gtv->priv->auto_rescale : FALSE;
}

#if defined(XDG_SCREENSAVER_COMMAND) || defined(GNOME_SCREENSAVER_COMMAND) || defined(X_SCREENSAVER_COMMAND)
static int screensaver_poke (const char *const argv[], const char *name)
{
  int ret = 60; /* default return = try again in 4 minutes */
  GError *err = NULL;

  g_spawn_async (NULL, (gchar **)argv, NULL,
		 G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
		 NULL, NULL, NULL, &err);
  if (err)
  {
    g_printerr (_("gtkvideo: can't poke %s: %s\n"), name, err->message);
    if (err->domain == G_SPAWN_ERROR)
      switch (err->code)
      {
      case G_SPAWN_ERROR_ACCES:
      case G_SPAWN_ERROR_PERM:
      case G_SPAWN_ERROR_NOEXEC:
      case G_SPAWN_ERROR_NOENT:
      case G_SPAWN_ERROR_ISDIR:
	g_printerr (_("gtkvideo: not trying again\n"));
	ret = -1;
	break;
      default:
	ret = 60;
	break;
      }
    g_error_free (err);
  }
  else
    ret = 0;

  return ret;
}
#endif

#ifdef XDG_SCREENSAVER_COMMAND
static int xdg_screensaver_poke (const char *arg, Window id)
{
  char id_str[16];
  const char *const argv[] = { XDG_SCREENSAVER_COMMAND, arg,
			       id ? id_str : NULL, NULL };
  snprintf (id_str, sizeof(id_str), "%#x", (int) id);
  /* screensaver name is used in "can't poke <name>" */
  return screensaver_poke (argv, "xdg-screensaver");
}
#endif

static gboolean gtv_unblank_screen (GtkVideo *gtv)
{
  static gboolean dbus_deny = FALSE;
  gboolean inhibit = gtv_test_inhibit_blank (gtv);

#ifdef XDG_SCREENSAVER_COMMAND
  if (gtv->priv->have_xdg_screensaver)
  {
    if (inhibit != !!gtv->priv->xdg_active)
    {
      Window id = inhibit
                ? gdk_x11_drawable_get_xid (gtk_widget_get_window (gtk_widget_get_toplevel (&gtv->widget)))
		: gtv->priv->xdg_active;
      int ret = 0;
      if (!id || !(ret = xdg_screensaver_poke (inhibit ? "suspend" : "resume", id)))
      {
	if (ret < 0) /* if this happens, xdg-screensaver isn't there */
	  gtv->priv->have_xdg_screensaver = 0;
	gtv->priv->xdg_active = inhibit ? id : 0;
	return FALSE;
      }
    }
    else
      return FALSE; /* nothing to do */
  }
#endif

  if (!inhibit)
  {
    if (dbus_deny)
      gtv_screensaver_maybe_inhibit_dbus (gtv->priv, dbus_deny = FALSE);
    return FALSE;
  }
  else if (!dbus_deny)
    gtv_screensaver_maybe_inhibit_dbus (gtv->priv, dbus_deny = TRUE);

#ifdef HAVE_X11
  Display *display = gtv_get_xdisplay (gtv);
  XLockDisplay (display);
#ifdef HAVE_DPMS_EXTENSION
  union { int i; CARD16 c; } dummy;
  BOOL enabled;
  if (DPMSQueryExtension (display, &dummy.i, &dummy.i) && DPMSCapable (display)
      && DPMSInfo (display, &dummy.c, &enabled) && enabled)
    DPMSForceLevel (display, DPMSModeOn);
#endif
#ifdef HAVE_XTESTEXTENSION
  gtk_video_private_t *priv = gtv->priv;
  if (priv->have_xtest == True)
  {
    static unsigned char flip = 0;
    int state = !(shift & ((flip = !flip) + 1));
    XTestFakeKeyEvent (display, priv->kc_shift[flip], state, CurrentTime);
    XTestFakeKeyEvent (display, priv->kc_shift[flip], !state, CurrentTime);
    XSync (display, False);
  }
  else
#endif
  {
    XForceScreenSaver (display, ScreenSaverReset);
  }
#endif /* HAVE_X11 */

  if (!screensaver_is_running_dbus (gtv->priv))
  {
#ifdef GNOME_SCREENSAVER_COMMAND
    /* poke GNOME screensaver (assume that $PATH is sane) */
    static int gnome_screensaver = 0;
    if (gnome_screensaver > 0)
      --gnome_screensaver;
    if (!gnome_screensaver)
    {
      static const char *const argv[] = { GNOME_SCREENSAVER_COMMAND, "--poke", NULL };
      /* screensaver name is used in "can't poke <name>" */
      gnome_screensaver = screensaver_poke (argv, "gnome-screensaver");
    }
#endif

#ifdef X_SCREENSAVER_COMMAND
    /* similarly, poke xscreensaver */
    static int x_screensaver = 0;
    if (x_screensaver > 0)
      --x_screensaver;
    if (!x_screensaver)
    {
      static const char *const argv[] = { X_SCREENSAVER_COMMAND, "--deactivate", NULL };
      x_screensaver = screensaver_poke (argv, "xscreensaver");
    }
#endif
  }

#ifdef HAVE_X11
  XUnlockDisplay (display);
#endif

  return FALSE;
}

void gtk_video_unblank_screen (GtkVideo *gtv)
{
  g_idle_add ((GSourceFunc) gtv_unblank_screen, gtv);
}
