/*
 * Copyright (C) 2003-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
 *
 * standard, non-skinned main window
 */

#include "globals.h"

#include "noskin_window.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#ifdef HAVE_X11
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#endif

#include <gtk/gtk.h>

#include "ui.h"
#include "utils.h"
#include "engine.h"
#include "key_events.h"
#include "player.h"
#include "playlist.h"
#include "mediamarks.h"
#include "menu.h"
#include "drag_drop.h"
#include "gtkvideo.h"
#include "vis.h"
#include "systray.h"
#include "xml_widgets.h"

/*
#define VIS_WIDGET
*/
#undef USE_NET_WM_STATE_SHADED

#define GUI_DEFAULT_WIDTH  320
#define GUI_DEFAULT_HEIGHT 240

/* global */
GtkWidget *gtv;

/* private/local */
static int             have_video = -1, have_audio = -1;
static struct app_prop_s {
  gboolean	       sticky, fs, real_sticky, shaded;
  signed char          position; /* <0 = below, >0 = above */
} app_prop = { FALSE, FALSE, FALSE, FALSE, 0 };

static GtkWidget *fs_toolbar, *wm_toolbar = NULL;
#ifdef HAVE_X11
static Atom opacity_lock;
#endif

static void gtk_window_set_shade (GtkWindow *w, gboolean shade)
{
#ifdef HAVE_X11
  GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (w));
  Display *display = GDK_WINDOW_XDISPLAY (window);
  XEvent xev;

  xev.xclient.type = ClientMessage;
  xev.xclient.serial = 0;
  xev.xclient.send_event = True;
  xev.xclient.window = GDK_WINDOW_XID (window);
  xev.xclient.message_type = XInternAtom (display, "_NET_WM_STATE", False);
  xev.xclient.format = 32;
  xev.xclient.data.l[0] = shade ? 1 : 0; /* _NET_WM_STATE_{ADD,REMOVE} */
  xev.xclient.data.l[1] = XInternAtom (display, "_NET_WM_STATE_SHADED", False);
  xev.xclient.data.l[2] = 0;
  xev.xclient.data.l[3] = 0;
  xev.xclient.data.l[4] = 0;

  XSendEvent (display, DefaultRootWindow (display) /*FIXME:screen*/, False,
	      SubstructureRedirectMask | SubstructureNotifyMask, &xev);
#endif
}

/*
 * audio only / vis check
 */

void window_check_vis (gboolean force)
{
  xine_cfg_entry_t entry;
  int hv_new = xine_get_stream_info (stream, XINE_STREAM_INFO_HAS_VIDEO);
  if (!have_video && playlist_showing_logo ()
      && xine_config_lookup_entry (xine, "gui.post_plugins.always_show_logo", &entry)
      && !entry.num_value)
    hv_new = 0;
  int ha_new = xine_get_stream_info (stream, XINE_STREAM_INFO_HAS_AUDIO);
  gboolean fs = gtk_video_is_fullscreen ((GtkVideo *)gtv);
  gboolean was_shown = gtk_widget_is_drawable (gtv);
  gboolean is_shown = was_shown;

  if (force || have_video != hv_new || have_audio != ha_new)
  {
    have_video = hv_new;
    have_audio = ha_new;
    if (hv_new)
    {
      logprintf ("gxine: stream has video\n");
      vis_hide ((GtkVideo *)gtv, &audio_port);
#ifdef USE_NET_WM_STATE_SHADED
      if (wm_toolbar)
	gtk_window_set_shade (GTK_WINDOW (app), FALSE);
      else
#endif
	gtk_video_reshow ((GtkVideo *)gtv);
      is_shown = TRUE;
    }
    else if (!ha_new)
    {
      logprintf ("gxine: stream has no audio\n");
      /* stream probably not initialised yet - don't show or hide */
    }
    else if (vis_show ((GtkVideo *)gtv, &audio_port))
    {
      logprintf ("gxine: stream has audio (visualisation active)\n");
#ifdef USE_NET_WM_STATE_SHADED
      if (wm_toolbar)
	gtk_window_set_shade (GTK_WINDOW (app), FALSE);
      else
#endif
	gtk_video_reshow ((GtkVideo *)gtv);
      is_shown = TRUE;
    }
    else
    {
      logprintf ("gxine: stream has audio\n");
      if (fs)
      {
	fs = FALSE;
	gtk_action_activate ((GtkAction *)action_items.fullscreen);
      }
#ifdef USE_NET_WM_STATE_SHADED
      if (wm_toolbar)
	gtk_window_set_shade (GTK_WINDOW (app), TRUE);
      else
#endif
	gtk_widget_hide (gtv);
      is_shown = FALSE;
    }
    if (action_items.fullscreen)
      gtk_action_set_sensitive ((GtkAction *)action_items.fullscreen, is_shown);
    /* Resize if the video widget visibility has been changed.
     * Note that the GtkVideo widget imposes a geometry constraint...
     */
#ifdef USE_NET_WM_STATE_SHADED
    if (!fs && !wm_toolbar && is_shown != was_shown)
      gtk_window_resize (GTK_WINDOW(app), GTK_VIDEO_MIN_WIDTH, 1);
#else
    if (!fs && is_shown != was_shown)
      gtk_window_resize (GTK_WINDOW(app), GTK_VIDEO_MIN_WIDTH, 1);
#endif
  }
}

/*
 * slider
 */

static gboolean update_slider_cb (gpointer data)
{
  gint pos_stream, pos_time, length_time;

  if (pthread_mutex_trylock (&widgets_update_lock))
    return TRUE;

  window_check_vis (FALSE);
  if (xine_get_status (stream) == XINE_STATUS_PLAY
      && xine_get_pos_length (stream, &pos_stream, &pos_time, &length_time))
  {
    ui_set_control_adjustment (Control_SEEKER, pos_stream);
    /* update the control buttons while we're here */
    ui_set_status (UI_CURRENT_STATE);
  }

  pthread_mutex_unlock (&widgets_update_lock);
  return TRUE;
}

static gint
close_application (GtkWidget *widget, GdkEvent *event, gpointer data)
{
  engine_exec ("exit();", NULL, NULL, NULL);
  return FALSE;
}

/*
 * full-screen toolbar
 */

static gint cw_height = 1;

static gboolean window_fs_toolbar_move (GdkEventConfigure *event)
{
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (app, &allocation);

  int h = fs_toolbar_at_top
	  ? 1
          : (event ? event->height : allocation.height) - cw_height - 1;
  int x, y;
  gtk_window_get_position (GTK_WINDOW(app), &x, &y);
  gtk_window_move (GTK_WINDOW(fs_toolbar),
		   event ? event->x : x, (event ? event->y : y) + h);
  return FALSE;
}

static void window_fs_toolbar_set_geometry (void)
{
  GdkGeometry    cw_geom = {0};
  if (gtv)
  {
    const GtkRequisition *size = gtk_video_get_fullscreen_geometry ((GtkVideo *)gtv);
    cw_geom.max_width = cw_geom.min_width = size->width;
  }
  else
    cw_geom.max_width = cw_geom.min_width = gdk_screen_width ();
  cw_geom.max_height = cw_geom.min_height = -1;
  cw_geom.win_gravity = GDK_GRAVITY_SOUTH_WEST;
  gtk_window_set_geometry_hints
    (GTK_WINDOW(fs_toolbar), fs_toolbar, &cw_geom,
     GDK_HINT_POS | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_BASE_SIZE
     | GDK_HINT_USER_POS);
}

static gboolean window_fs_toolbar_show_int (void)
{
  window_fs_toolbar_set_geometry ();
  window_fs_toolbar_move (NULL);
  window_show (fs_toolbar, NULL);
  if (fs_is_sticky)
    gtk_window_stick (GTK_WINDOW(fs_toolbar));
#ifdef HAVE_X11
  GdkWindow *window = gtk_widget_get_window(fs_toolbar);
  XChangeProperty (GDK_WINDOW_XDISPLAY (window),
                   GDK_WINDOW_XID (window), opacity_lock,
		   XA_CARDINAL, 32, PropModeReplace,
		   (unsigned char *)&have_video, 1);
#endif
  return FALSE;
}

void window_fs_toolbar_show (gboolean show)
{
  fs_toolbar_visible = show;

  if (!show)
    window_fs_toolbar_reset ();
  else if (gtk_video_is_fullscreen ((GtkVideo *)gtv))
    window_fs_toolbar_show_int ();

  ui_set_status (UI_FS_TOOLBAR);
}

void window_fs_toolbar_reset (void)
{
  gtk_widget_hide (fs_toolbar);
}

void window_fs_toolbar_restore (void)
{
  if (fs_toolbar_visible)
    window_fs_toolbar_show_int ();
}

void window_fs_toolbar_position (gboolean top)
{
  fs_toolbar_at_top = top;
  window_fs_toolbar_move (NULL);
  ui_set_status (UI_FS_TOOLBAR_POS);
}

static void cw_show_cb (GtkWidget *widget)
{
  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION(action_items.fs_toolbar)))
    window_fs_toolbar_show (TRUE);
}

static gboolean fs_toolbar_configure_cb (GtkWidget *widget,
					 GdkEventConfigure *event,
					 gpointer data)
{
  if (cw_height == event->height)
    return FALSE;
  cw_height = event->height;
  if (fs_toolbar_visible)
    g_idle_add ((GSourceFunc) window_fs_toolbar_move, NULL);
  return FALSE;
}

/*
 * windowed-mode toolbar
 */

void window_wm_toolbar_show (gboolean show)
{
  if (!wm_toolbar)
    return;

  wm_toolbar_visible = show;

  if (!show)
    window_wm_toolbar_reset ();
  else if (!gtk_video_is_fullscreen ((GtkVideo *)gtv))
    window_wm_toolbar_restore ();

  ui_set_status (UI_WM_TOOLBAR);
}

void window_wm_toolbar_reset (void)
{
  if (wm_toolbar)
    gtk_widget_hide (wm_toolbar);
}

void window_wm_toolbar_restore (void)
{
  if (wm_toolbar && gtk_widget_get_mapped (app))
    gtk_window_present (GTK_WINDOW (wm_toolbar));
}

void window_wm_toolbar_set_snap (gboolean snap)
{
  if (wm_toolbar)
  {
    wm_toolbar_snap = snap;
    if (snap)
      window_wm_toolbar_snap ();
  }
}

/* callbacks */

void window_fs_stickiness_update (gboolean data)
{
  printf ("Marking as %ssticky [%ssticky]\n", data ? "" : "non-", app_prop.sticky ? "" : "non-");
  fs_is_sticky = data;

  if (gtk_video_is_fullscreen ((GtkVideo *) gtv))
  {
    if (data || app_prop.sticky)
    {
      puts ("calling gtk_window_stick");
      gtk_window_stick ((GtkWindow *) app);
    }
    else
    {
      puts ("calling gtk_window_unstick");
      gtk_window_unstick ((GtkWindow *) app);
    }
  }
}

#ifdef WINDOW_STACKING_HACK
/* whether gdk_window_lower() is used; present in case the wm toolbar window
 * should somehow gain the input focus, since calling this may move the focus
 * elsewhere
 */
static gboolean can_lower = FALSE;
static inline gboolean set_can_lower (gboolean v)
{
  return can_lower = v;
}
#else
#define can_lower (FALSE)
static inline gboolean set_can_lower (gboolean v)
{
  return v;
}
#endif /* !WINDOW_STACKING_HACK */

gboolean window_wm_toolbar_snap (void)
{
  GdkRectangle appframe, tbframe;
  int x, y;

  if (!wm_toolbar)
    return FALSE;

  gdk_window_get_frame_extents (gtk_widget_get_window(app), &appframe);
  gdk_window_get_frame_extents (gtk_widget_get_window(wm_toolbar), &tbframe);

  GdkRectangle monitor;
  GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(app));
  gdk_screen_get_monitor_geometry (screen,
                                   gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window(app)),
				   &monitor);

  /* centre horizontally wrt video window */
  x = appframe.x + (appframe.width - tbframe.width) / 2;
  /* ensure fully on-screen unless the video window isn't fully on-screen */
  if (appframe.width >= tbframe.width)
  {
    if (x < monitor.x)
      x = MIN (monitor.x, appframe.x + appframe.width - tbframe.width);
    else if (x + tbframe.width > monitor.x + monitor.width)
      x = MAX (monitor.x + monitor.width - tbframe.width, appframe.x);
  }
  else
  {
    if (x < monitor.x)
      x = MIN (monitor.x, appframe.x);
    else if (x + tbframe.width > monitor.x + monitor.width)
      x = MAX (monitor.x + monitor.width - tbframe.width,
	       appframe.x + appframe.width - tbframe.width);
  }

  /* position just below the video window, or above if insufficient space */
  y = appframe.y + appframe.height;
  if (y + tbframe.height > monitor.y + monitor.height)
    y = appframe.y - tbframe.height;

  /* reposition if needed */
  if (x != tbframe.x || y != tbframe.y)
  {
    if (can_lower)
      gdk_window_lower (gtk_widget_get_window (wm_toolbar));
    gtk_window_move (GTK_WINDOW (wm_toolbar), x, y);
  }

  return FALSE;
}

static gboolean popup_cb (GtkWidget *widget, gpointer data)
{
  if (gtk_widget_get_visible (wm_toolbar ? : menubar))
    gtk_menu_shell_select_first (GTK_MENU_SHELL (menubar), FALSE);
  else
    gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, NULL, NULL, 0,
		    gtk_get_current_event_time ());
  return TRUE;
}

static void wm_close_cb (GtkWidget *widget, gpointer data)
{
  if (gtk_toggle_action_get_active (action_items.wm_toolbar))
    gtk_action_activate (&action_items.wm_toolbar->parent);
}

static void fs_show_hide (GtkWidget *widget, gpointer show)
{
  if (widget == gtv)
    return;
  if (show)
    gtk_widget_show (widget);
  else
    gtk_widget_hide (widget);
}

static gboolean refocus_cb (GtkWidget *widget)
{
  if (gtk_widget_get_visible (widget) && !app_prop.shaded)
    gdk_window_focus (gtk_widget_get_window (widget), 0);
  return FALSE;
}

static gboolean appfocus_cb (GtkWidget *w, GdkEventFocus *e, gpointer d)
{
  g_idle_add ((GSourceFunc)refocus_cb, app);
  return TRUE;
}

static gboolean
app_state_cb (GtkWindow *window, GdkEventWindowState *event, gpointer data)
{
  if (!(event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN))
    return FALSE;

  int fs = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;

  if (!fs)
  {
    gtk_video_deny_shrink ((GtkVideo *) gtv);

    /* restore stickiness as was, pre-fullscreen */
    if (fs_is_sticky)
    {
      if (app_prop.sticky)
	gtk_window_stick (window);
      else
	gtk_window_unstick (window);
    }

    /* restore handling wrt window stack */
    gtk_window_set_keep_below (window, app_prop.position < 0);
    gtk_window_set_keep_above (window, app_prop.position > 0);

    /* reshow the toolbar (if needed) */
    if (!wm_toolbar)
    {
      gdk_flush ();
      gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (window))),
			     fs_show_hide, window);
      gdk_flush ();
    }
    else if (wm_toolbar_visible)
      window_wm_toolbar_restore ();

    gtk_toggle_action_set_active (action_items.fullscreen, fs);
  }
  else if (!gtk_widget_get_visible (gtv))
  {
    if (playlist_showing_logo ())
    {
      gtk_widget_show (gtv);
      gtk_action_set_sensitive ((GtkAction *)action_items.fullscreen, TRUE);
    }
    else
      gtk_window_unfullscreen (window);
  }
  else
  {
    gtk_window_set_shade (window, FALSE);
    if (fs_is_sticky)
      gtk_window_stick (window);
    if (!wm_toolbar)
      gtk_container_foreach (GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (window))),
			     fs_show_hide, NULL);
    else
      window_wm_toolbar_reset ();
    gtk_window_set_keep_above (window, TRUE);
    gdk_window_focus (gtk_widget_get_window (app), 0);

    gtk_toggle_action_set_active (action_items.fullscreen, fs);
  }

  return FALSE;
}

void app_show (void)
{
  gtk_action_set_visible (action_items.hidemain, TRUE);
  gtk_action_set_visible (action_items.showmain, FALSE);
  gtk_widget_show (app);
  if (wm_toolbar && wm_toolbar_visible)
    gtk_widget_show (wm_toolbar);
}

void app_hide (void)
{
  /* can't do this in full-screen mode or when we have no systray */
  if (gtk_video_is_fullscreen ((GtkVideo *)gtv) || !systray_present ())
    return;

  gtk_action_set_visible (action_items.hidemain, FALSE);
  gtk_action_set_visible (action_items.showmain, TRUE);
  gtk_widget_hide (app);
  if (wm_toolbar && wm_toolbar_visible)
    gtk_widget_hide (wm_toolbar);
}

#ifdef HAVE_X11
static void
xevent_properties (void)
{
  gdk_error_trap_push ();

  GdkWindow  *window = gtk_widget_get_window (app);
  Display    *xd     = GDK_WINDOW_XDISPLAY (window);
  GdkDisplay *d      = gdk_x11_lookup_xdisplay (xd);
  Window xw = GDK_WINDOW_XID (window);

  Atom type;
  int fmt;
  unsigned long items, bytes;
  unsigned char *prop;

  XGetWindowProperty
    (xd, xw, gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE"),
     0, G_MAXLONG, False, XA_ATOM, &type, &fmt, &items, &bytes, &prop);

  if (type == None)
    goto unchanged;

  struct app_prop_s new_prop = { .sticky = app_prop.sticky };

  Atom sticky =
    gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE_STICKY");
  Atom fs =
    gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE_FULLSCREEN");
  Atom shaded =
    gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE_SHADED");
  Atom above = gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE_ABOVE");
  Atom below = gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_STATE_BELOW");

  Atom *atoms = (Atom *) prop;
  unsigned long i;
  for (i = 0; i < items; ++i)
    if (atoms[i] == sticky)
      new_prop.real_sticky = TRUE;
    else if (atoms[i] == fs)
      new_prop.fs = TRUE;
    else if (atoms[i] == shaded)
      new_prop.shaded = TRUE;
    else if (atoms[i] == above)
      ++new_prop.position;
    else if (atoms[i] == below)
      --new_prop.position;

  XFree (prop);

  if (gtk_video_is_fullscreen ((GtkVideo *)gtv))
    new_prop.position = app_prop.position; /* will be on top regardless */

  if (new_prop.real_sticky)
  {
    /* sticky only if "sticky" property set & window on all desktops */
    XGetWindowProperty
      (xd, xw, gdk_x11_get_xatom_by_name_for_display (d, "_NET_WM_DESKTOP"),
       0, G_MAXLONG, False, XA_CARDINAL, &type, &fmt, &items, &bytes, &prop);
    if (type != None)
    {
      new_prop.real_sticky = *(int32_t *) prop == -1;
      XFree (prop);
    }
  }

  if (app_prop.real_sticky != new_prop.real_sticky)
  {
    if (!new_prop.fs || !fs_is_sticky)
      new_prop.sticky = new_prop.real_sticky;
    else if (!new_prop.real_sticky)
    {
      /* prevent unstick in fullscreen */
      new_prop.real_sticky = TRUE;
      gtk_window_stick ((GtkWindow *)app);
    }
  }

  if (app_prop.shaded != new_prop.shaded && wm_toolbar_snap)
    g_idle_add ((GSourceFunc) window_wm_toolbar_snap, NULL);

  app_prop = new_prop;

unchanged:
  gdk_error_trap_pop ();
}

static GdkFilterReturn
xevent_filter_cb (GdkXEvent *gx, GdkEvent *e, gpointer d)
{
  const XEvent *x = (XEvent *)gx;
  switch (x->type)
  {
  case ClientMessage:
    /* Lower the toolbar */
    if (wm_toolbar && wm_toolbar_snap && can_lower)
      gdk_window_lower (gtk_widget_get_window (wm_toolbar));
    break;

  case FocusIn:
    return GDK_FILTER_REMOVE; /* else the screen goes black :-| */

  case FocusOut:
    if (gtk_video_is_fullscreen ((GtkVideo *) gtv)
        && gtk_widget_get_mapped (gtv))
    {
      Display *xd = GDK_WINDOW_XDISPLAY (gtk_widget_get_window (GTK_WIDGET (d)));
      Window fw = None;
      int fr;
      gdk_error_trap_push ();
      XGetInputFocus (xd, &fw, &fr);
#if !GTK_CHECK_VERSION(2, 24, 0)
      GdkWindow *gw = gdk_window_lookup (fw);
#else
      GdkWindow *gw = gdk_x11_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (d)), fw);
#endif
#ifdef HAVE_XINERAMA
      if (gw ? gw == gtk_widget_get_window(fs_toolbar) : !XineramaIsActive(xd))
#else
      if (gtk_widget_get_window(fs_toolbar) && gw == gtk_widget_get_window(fs_toolbar))
#endif
	XSetInputFocus (xd, x->xany.window, RevertToParent, CurrentTime);
      gdk_error_trap_pop ();
    }
    return GDK_FILTER_REMOVE;

  case MapNotify:
    gtk_action_set_sensitive (action_items.hidemain, TRUE);
    break;

  case UnmapNotify:
    /* "Hide main" should already be hidden.
     * If not, the window is being iconised.
     * (Ideally, we want "Show main" to be able to de-iconise.)
     */
    gtk_action_set_sensitive (action_items.hidemain, FALSE);
    break;

  case PropertyNotify:
    if (gtk_widget_get_window (app))
      xevent_properties ();
    break;
  }

  return GDK_FILTER_CONTINUE;
}
#endif

void noskin_main_post_init (gint fullscreen)
{
  gtk_widget_add_events (app, GDK_KEY_RELEASE_MASK);
  g_object_connect (G_OBJECT (app),
	"signal::key-press-event", G_CALLBACK (keypress_cb), wm_toolbar,
	"signal::popup-menu", G_CALLBACK(popup_cb), NULL,
	NULL);
  if (wm_toolbar)
    g_signal_connect (G_OBJECT (wm_toolbar),
		      "key-press-event", G_CALLBACK (keypress_cb), NULL);
  g_object_connect (G_OBJECT (fs_toolbar),
		    "signal::focus-in-event", G_CALLBACK (appfocus_cb), NULL,
		    "signal::key-press-event", G_CALLBACK (keypress_cb), NULL,
		    NULL);
  ui_set_status (xine_get_param (stream, XINE_PARAM_AUDIO_MUTE)
		 ? UI_AUDIO_MUTE : UI_AUDIO_UNMUTE);
#ifdef HAVE_X11
  GdkWindow *window = gtk_widget_get_window(app);
  gdk_window_set_events (window, GDK_ALL_EVENTS_MASK); /* seems to work */
  gdk_window_add_filter (window, xevent_filter_cb, app);
#endif
  gtk_widget_grab_focus (app);

  gtk_video_set_frame_cb ((GtkVideo *)gtv, (gtk_video_frame_cb_t)gxineinfo_update, timewidgets);

  if (fullscreen)
    gtk_window_fullscreen (GTK_WINDOW (app));

/*
  XSetInputFocus (GDK_WINDOW_XDISPLAY (app->window),
		  GDK_WINDOW_XWINDOW (app->window),
		  RevertToParent, CurrentTime);
*/
}

/* initialisation */

static gboolean app_configure_cb (GtkWidget *widget, GdkEventConfigure *event,
				  gpointer data)
{
  /* redisplay/reposition the toolbar (XRANDR support) */
  if (gtk_video_is_fullscreen ((GtkVideo *)gtv))
  {
#ifdef HAVE_XRANDR
    GtkAllocation allocation = { 0 };
    gtk_widget_get_allocation (fs_toolbar, &allocation);
    if (fs_toolbar_visible)
      window_fs_toolbar_move (event);
    window_fs_toolbar_set_geometry ();
    gtk_window_resize (GTK_WINDOW (fs_toolbar), event->width,
                       allocation.height);
#endif
  }
  else if (wm_toolbar_snap)
  {
    window_wm_toolbar_snap ();
    if (event->width != GTK_VIDEO_MIN_WIDTH &&
	event->height != GTK_VIDEO_MIN_HEIGHT)
      set_can_lower (TRUE); /* we've done the initial resize */
  }
  return FALSE;
}

static int wm_hide_count = 0;

static void
wm_show_cb (GtkWidget *w, gpointer data)
{
  if (wm_hide_count && !--wm_hide_count)
    g_idle_add ((GSourceFunc) gtk_video_allow_shrink, gtv);
}

static void
wm_hide_cb (GtkWidget *w, gpointer data)
{
  ++wm_hide_count;
}

static void
wm_show_hide_setup (gpointer object)
{
  ++wm_hide_count;
  g_object_connect (object,
		    "signal::show", G_CALLBACK (wm_show_cb), NULL,
		    "signal::hide", G_CALLBACK (wm_hide_cb), NULL,
		    NULL);
}

void noskin_post_init (void)
{
  xine_cfg_entry_t entry;
  if (xine_config_lookup_entry (xine, "gui.window_size", &entry))
  {
    static const gdouble scale[] = { 50, 75, 100, 150, 200 };
    gtk_video_rescale ((GtkVideo *)gtv, scale[entry.num_value]);
  }
  if (xine_config_lookup_entry (xine, "gui.windowed_mode.magnify_lowres_video", &entry))
    gtk_video_set_auto_rescale ((GtkVideo *)gtv, entry.num_value);
}


void noskin_main_init (const char *video_driver_id, const gchar *geometry, gboolean fullscreen)
{
  GtkWidget     *vbox, *w;
  GtkWindow     *W;
  xine_cfg_entry_t entry;

  /* create app windows */

  app = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_name (app, "video");
  fs_toolbar = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_name (fs_toolbar, "fs_toolbar");

  if (!xine_config_lookup_entry (xine, "gui.windowed_mode.separate_toolbar",
				 &entry)
      || entry.num_value)
  {
    wm_toolbar = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_name (wm_toolbar, "wm_toolbar");
    drag_drop_setup (wm_toolbar, TRUE);
  }

  /*
   * toolbar window (full-screen mode)
   */

  g_object_connect (G_OBJECT (fs_toolbar),
	"signal::delete-event", G_CALLBACK (wm_close_cb), NULL,
	"signal::configure-event", G_CALLBACK (fs_toolbar_configure_cb), NULL,
	/* cope with WMs which allow other means of closing windows */
	"signal::unmap-event", G_CALLBACK (cw_show_cb), NULL,
	NULL);

  W = GTK_WINDOW (fs_toolbar);

  /* toolbar window title */
  gtk_window_set_title (W, _("gxine controls"));
  gtk_window_set_resizable (W, FALSE);
  gtk_window_set_type_hint (W, GDK_WINDOW_TYPE_HINT_TOOLBAR);
  gtk_window_set_decorated (W, FALSE);
  /* I'd like to have the toolbar window not accept the input focus, but
   * some programs show but don't re-hide their toolbars if that's so...
   */
  /* gtk_window_set_focus_on_map (W, FALSE); */
  /* gtk_window_set_accept_focus (W, FALSE); */
  gtk_window_set_position (W, GTK_WIN_POS_NONE);
  gtk_window_set_skip_pager_hint (W, TRUE);
  gtk_window_set_skip_taskbar_hint (W, TRUE);

//  window_fs_toolbar_set_geometry ();
  drag_drop_setup (fs_toolbar, TRUE);

  w = widget_create_from_xml ("toolbar-fullscreen.xml", "toolbar_fs",
			      NULL, FALSE, TRUE);
  if (!w)
    exit (2); /* no point in continuing */
  gtk_container_add (GTK_CONTAINER (fs_toolbar), w);

  /*
   * video window
   */

  gtk_window_set_default_size (GTK_WINDOW (app), GUI_DEFAULT_WIDTH, GUI_DEFAULT_HEIGHT);

  /*
   * video widget
   */

  gtv = gtk_video_new (xine, stream, xine_get_video_source (stream),
		       video_driver_id, GUI_DEFAULT_WIDTH, GUI_DEFAULT_HEIGHT,
		       0x04 /* press: button 2 */,
		       0x08 /* release: button 3 */);
  if (xine_config_lookup_entry (xine, "gui.windowed_mode.auto_resize", &entry))
    gtk_video_set_auto_resize ((GtkVideo *)gtv, entry.num_value);

  if (!wm_toolbar)
  {
    /*
     * main vbox, menu bar (combined window)
     * also add the video widget
     */

    /* this vbox will also contain the info widgets and controls */
    vbox = gtk_vbox_new (0, 0);
    gtk_container_add (GTK_CONTAINER (app), vbox);
    create_menus (app);

    gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0);
    wm_show_hide_setup (menubar);

    gtk_container_add (GTK_CONTAINER (vbox), gtv);

    w = gtk_vbox_new (0, 0);
    gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);
    vbox = w;
  }
  else
  {
    /*
     * menu/info/tools window (separate windows)
     * also add the video widget
     */

    W = GTK_WINDOW (wm_toolbar);

    gtk_window_set_title (W, _("gxine controls"));
    gtk_window_set_type_hint (W, GDK_WINDOW_TYPE_HINT_TOOLBAR);
    gtk_window_set_decorated (W, TRUE);
    gtk_window_set_transient_for (W, GTK_WINDOW (app));
    gtk_window_set_position (W, GTK_WIN_POS_NONE);
    gtk_window_set_focus_on_map (W, FALSE);
    gtk_window_set_accept_focus (W, FALSE);
    gtk_window_set_skip_pager_hint (W, FALSE);
    gtk_window_set_skip_taskbar_hint (W, TRUE);

    {
      GdkGeometry geom;
      geom.min_width = -1;
      geom.min_height = -1;
      geom.max_width = 32767; /* hmm... */
      geom.max_height = -1;
      gtk_window_set_geometry_hints (W, wm_toolbar, &geom,
				     GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
    }

    g_object_connect (G_OBJECT (wm_toolbar),
	"signal::focus-in-event", G_CALLBACK (appfocus_cb), NULL,
	/*"signal::key-press-event", G_CALLBACK (keypress_cb), NULL,*/
	"signal::map-event", G_CALLBACK (window_wm_toolbar_snap), NULL,
	"signal::delete-event", G_CALLBACK (wm_close_cb), NULL,
	NULL);

    /* this vbox will also contain the info widgets and controls */
    vbox = gtk_vbox_new (0, 0);
    gtk_container_add (GTK_CONTAINER (wm_toolbar), vbox);

    create_menus (wm_toolbar);
    gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0);

    gtk_container_add (GTK_CONTAINER (app), gtv);
  }

  /* time_widget / infobar */

  w = widget_create_from_xml ("toolbar-window.xml", "toolbar_wm",
			      NULL, FALSE, TRUE);
  if (!w)
    exit (2); /* no point in continuing */
  gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);

  if (!wm_toolbar)
  {
    wm_show_hide_setup (w);

    /* hack: widget to take the focus in place of the video widget (which can't) */
    w = gtk_label_new ("");
    gtk_widget_set_size_request (w, 1, 1);
    gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);
    gtk_label_set_selectable ((GtkLabel *)w, TRUE);
    g_object_connect (G_OBJECT (w),
	"signal::button_press_event", G_CALLBACK (gtk_true), NULL,
	NULL);
    gtk_widget_grab_focus (w);
    wm_show_hide_setup (w);
  }

  /* Show the app window (and ensure that it's resized properly) */

/*
  gtk_window_resize (GTK_WINDOW (app),
		     GTK_VIDEO_MIN_WIDTH, GTK_VIDEO_MIN_HEIGHT);
 */
  gtk_widget_show_all (gtk_bin_get_child (GTK_BIN (app)));
  if (geometry)
    gtk_window_parse_geometry (GTK_WINDOW (app), geometry);
  gtk_widget_show (app);

  GdkWindow *window = gtk_widget_get_window(app);
  gdk_window_set_decorations (window, GDK_DECOR_ALL | GDK_DECOR_MAXIMIZE);
  gdk_window_set_functions (window, GDK_FUNC_ALL | GDK_FUNC_MAXIMIZE);

  if (wm_toolbar)
  {
    gtk_widget_show_all (wm_toolbar);
#ifdef HAVE_X11
    XSelectInput (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XID (window),
		  VisibilityChangeMask);
#endif
    if (fullscreen)
      gtk_widget_hide (wm_toolbar);
  }

  g_object_set (G_OBJECT (app), "allow-shrink", TRUE, NULL);
  g_object_connect (G_OBJECT (app),
	"signal::delete-event", G_CALLBACK (close_application), NULL,
	"signal::configure-event", G_CALLBACK (app_configure_cb), NULL,
	"signal::window-state-event", G_CALLBACK (app_state_cb), NULL,
	NULL);
  drag_drop_setup (app, TRUE);

  g_object_connect (G_OBJECT (gtv),
	/*"signal::key-press-event", G_CALLBACK (keypress_cb), wm_toolbar,*/
	"signal::button-press-event", G_CALLBACK (buttonpress_cb), NULL,
	"signal::button-release-event", G_CALLBACK (buttonrelease_cb), NULL,
	"signal::scale-factor-changed", G_CALLBACK (scale_changed_cb), NULL,
	NULL);

  have_video = 1;

#ifdef HAVE_X11
  Display *d = GDK_WINDOW_XDISPLAY (window);
  opacity_lock = XInternAtom (d, "_NET_WM_WINDOW_OPACITY_LOCKED", False);
  XChangeProperty (d, GDK_WINDOW_XID (window), opacity_lock,
		   XA_CARDINAL, 32, PropModeReplace,
		   (unsigned char *)&have_video, 1);
#endif

  g_timeout_add (500, update_slider_cb, NULL);
}
