/*
 * Copyright (C) 2007-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
 *
 * System tray icon for gxine.
 */
 
/*
 * Using X11 functions (_NET_SYSTEM_TRAY_* atoms) if possible, to support
 * drag&drop.
 * Otherwise portable GtkStatusIcon is used.
 */

#include "globals.h"
#include "systray.h"

#include <stdlib.h>

#include "drag_drop.h"
#include "engine.h"
#include "menu.h"
#include "noskin_window.h"
#include "ui.h"

#include <gtk/gtk.h>
#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#if GTK_CHECK_VERSION(3, 0, 0)
#include <gtk/gtkx.h>
#endif
#endif

#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
static GtkWidget *tray_icon = NULL;
static Window tray = None;

static struct {
  Atom data, manager, opcode, selection;
} atoms;
#else
static GtkStatusIcon *tray_icon = NULL;
#endif
static GtkWidget *tray_menu;

#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
static GdkFilterReturn systray_filter (GdkXEvent *, GdkEvent *, gpointer);
#endif
static gboolean button_cb (GtkWidget *, GdkEventButton *, gpointer);


#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
static void
send_opcode (glong d1, glong d2, glong d3, glong d4)
{
  GdkDisplay *display = gtk_widget_get_display (tray_icon);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  XEvent xev = {
    .xclient = {
      ClientMessage, 0, True,
      xdisplay, tray, atoms.opcode,
      32, { .l = { gdk_x11_get_server_time (gtk_widget_get_window (tray_icon)), d1, d2, d3, d4 } }
    }
  };

  gdk_error_trap_push ();
  XSendEvent (xdisplay, tray, False, NoEventMask, &xev);
  XSync (xdisplay, FALSE);
  gdk_error_trap_pop ();
}
#endif

static inline GdkScreen *
create_tray_icon (void)
{
#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
  tray_icon = gtk_plug_new (0);
  gtk_container_add (GTK_CONTAINER (tray_icon),
		     gtk_image_new_from_icon_name ("gxine", GTK_ICON_SIZE_LARGE_TOOLBAR));
  gtk_widget_show_all (tray_icon);
  drag_drop_setup (tray_icon, TRUE);

  GdkScreen *screen = gtk_widget_get_screen (tray_icon);
  Screen *xscreen = GDK_SCREEN_XSCREEN (screen);
  GdkDisplay *display = gtk_widget_get_display (tray_icon);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  char selection[32];
  snprintf (selection, sizeof (selection), "_NET_SYSTEM_TRAY_S%d",
	    XScreenNumberOfScreen (xscreen));
  atoms.data = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
  atoms.manager = XInternAtom (xdisplay, "MANAGER", False);
  atoms.opcode = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_OPCODE", False);
  atoms.selection = XInternAtom (xdisplay, selection, False);

  return screen;
#else
  tray_icon = gtk_status_icon_new_from_icon_name("gxine");
  if (tray_icon) {
    gtk_status_icon_set_visible(tray_icon, TRUE);
    return gtk_status_icon_get_screen(tray_icon);
  } else
    return NULL;
#endif
}

static void
systray_delete_icon (void)
{
#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
  tray = None;
  if (!tray_icon)
    return;

  gtk_widget_destroy (tray_icon);
  tray_icon = NULL;

  GdkDisplay *display = gtk_widget_get_display (app);
#if !GTK_CHECK_VERSION(3, 0, 0)
  GdkWindow *window = gdk_window_lookup_for_display (display, tray);
#else
  GdkWindow *window = gdk_x11_window_lookup_for_display (display, tray);
#endif
  gdk_window_remove_filter (window, systray_filter, NULL);

  if (!gtk_widget_get_mapped (app))
    app_show ();

  gtk_action_set_visible (action_items.showmain, FALSE);
  gtk_action_set_visible (action_items.hidemain, FALSE);
#endif
}

static void
systray_create_icon (void)
{
  systray_delete_icon ();

  /* Create the tray icon */
  GdkScreen *tray_screen = create_tray_icon ();

#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
  GdkDisplay *display = gtk_widget_get_display (app);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  /* Get the tray id */
  XGrabServer (xdisplay);
  tray = XGetSelectionOwner (xdisplay, atoms.selection);
  if (tray != None)
    XSelectInput (xdisplay, tray, StructureNotifyMask);
  XUngrabServer (xdisplay);
  XFlush (xdisplay);

  /* No tray? */
  if (tray == None)
    return;

  /* Dock it */
#if !GTK_CHECK_VERSION(3, 0, 0)
  GdkWindow *window = gdk_window_lookup_for_display (display, tray);
#else
  GdkWindow *window = gdk_x11_window_lookup_for_display (display, tray);
#endif
  gdk_window_add_filter (window, systray_filter, NULL);
  /* request dock */
  send_opcode (0, gtk_plug_get_id (GTK_PLUG (tray_icon)), 0, 0);

  /* Filtering & events */
  gdk_window_add_filter (gdk_screen_get_root_window (tray_screen),
			 systray_filter, NULL);
  GdkWindow *icon_window = gtk_widget_get_window (tray_icon);
  gdk_window_set_events (icon_window,
                         gdk_window_get_events (icon_window) | GDK_BUTTON_PRESS_MASK);
#endif
  /* Filtering & events */
  g_signal_connect (G_OBJECT (tray_icon), "button-press-event",
		    G_CALLBACK (button_cb), NULL);

  /* Handle menu items */
  gtk_action_set_visible (action_items.showmain, FALSE);
  gtk_action_set_visible (action_items.hidemain, TRUE);
}

#if defined(HAVE_X11) && !defined(USE_GTK_SYSTRAY)
static GdkFilterReturn
systray_filter (GdkXEvent *gxev, GdkEvent *ev, gpointer data)
{
  XEvent *xev = (XEvent *) gxev;

  switch (xev->type)
  {
  case ClientMessage:
    /* tray appeared? */
    if (xev->xclient.message_type == atoms.manager
	&& xev->xclient.data.l[1] == atoms.selection)
      systray_create_icon ();
    break;

  case DestroyNotify:
    /* tray disappeared? */
    if (xev->xclient.window == tray)
      systray_delete_icon ();
    break;
  }

  return GDK_FILTER_CONTINUE;
}
#endif

static const char popup_menu_xml[] =
	"<ui><popup name='tray'>\n"
	 "<menuitem action='PrevTrack' />\n"
	 "<menuitem action='Play' />\n"
	 "<menuitem action='Pause' />\n"
	 "<menuitem action='FFwd' />\n"
	 "<menuitem action='Stop' />\n"
	 "<menuitem action='NextTrack' />\n"
	 "<menuitem action='Playlist' />\n"
	 "<separator />\n"
	 "<menuitem action='HideMain' />\n"
	 "<menuitem action='ShowMain' />\n"
	 "<menuitem action='Quit' />\n"
	"</popup></ui>";

static gboolean
button_cb (GtkWidget *widget, GdkEventButton *ev, gpointer data)
{
  switch (ev->button)
  {
  case 1:
    gtk_menu_popup (GTK_MENU (tray_menu), NULL, NULL, NULL, NULL,
		    ev->button, ev->time);
    break;
  case 2:
    if (gtk_widget_get_mapped (app))
      app_hide ();
    else
      app_show ();
    break;
  case 3:
    gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, NULL, NULL,
		    ev->button, ev->time);
    break;
  }

  return TRUE;
}

static JSBool js_minimise (JSContext *cx, uintN argc, jsval *vp)
{
  int32 tray_state=-1;
  jsval *argv = JS_ARGV (cx, vp);

  se_log_fncall_checkinit ("set_minimised");
  se_argc_check_max (1, "set_minimised");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_minimised");
    JS_ValueToInt32 (cx, argv[0], &tray_state);
  }

  switch (tray_state)
  {
  default: /* toggle */
    if (gtk_widget_get_mapped (app))
  case 1:
      app_hide ();
    else
  case 0:
      app_show ();
  }

  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

void systray_init (void)
{
  static const se_f_def_t defs[] = {
    { "set_minimised", js_minimise, 0, JSFUN_FAST_NATIVE,
      SE_GROUP_ENGINE, N_("[bool]"),
      N_("true to hide, false to show") },
    { NULL }
  };
  se_defuns (gse, NULL, defs);

  GError *error = NULL;
  gtk_ui_manager_add_ui_from_string (ui, popup_menu_xml, -1, &error);
  tray_menu = gtk_ui_manager_get_widget (ui, "/ui/tray");

  static const char items[][16] = {
    [Control_STOP] = "/ui/tray/Stop",
    [Control_PAUSE] = "/ui/tray/Pause",
    [Control_PLAY] = "/ui/tray/Play",
    [Control_FASTFWD] = "/ui/tray/FFwd",
   };
  for (size_t i = 0; i < G_N_ELEMENTS (items); ++i)
    if (items[i][0])
      ui_register_control_button (i, gtk_ui_manager_get_widget (ui, items[i]));

  systray_create_icon ();
}

gboolean systray_present (void)
{
  return tray_icon != NULL;
}
