
/* eggtrayicon.c           serial-0085-0 ***************************************
 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
 *
 * Modified by James Scott, Jr <skoona@users.sourceforge.net>
 * - To enhance events and size management  4/2006
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <string.h>
#include <libintl.h>

#include <gdkconfig.h>

#include <gdk/gdkx.h>
#include <X11/Xatom.h>

#if defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#endif

#include "eggtrayicon.h"


#define _(x) x
#define N_(x) x

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2

#define SYSTEM_TRAY_ORIENTATION_HORZ 0
#define SYSTEM_TRAY_ORIENTATION_VERT 1

enum {
   PROP_0,
   PROP_ORIENTATION
};

static GtkPlugClass *parent_class = NULL;

static void egg_tray_icon_init(EggTrayIcon * icon);
static void egg_tray_icon_class_init(EggTrayIconClass * klass);

static void egg_tray_icon_get_property(GObject * object,
   guint prop_id, GValue * value, GParamSpec * pspec);

static void egg_tray_icon_realize(GtkWidget * widget);
static void egg_tray_icon_unrealize(GtkWidget * widget);

static void egg_tray_icon_add (GtkContainer *container,
			       GtkWidget    *widget);

static void egg_tray_icon_update_manager_window(EggTrayIcon * icon,
   gboolean dock_if_realized);
static void egg_tray_icon_manager_window_destroyed(EggTrayIcon * icon);

GType egg_tray_icon_get_type(void)
{
   static GType our_type = 0;

   if (our_type == 0) {
      static const GTypeInfo our_info = {
         sizeof(EggTrayIconClass),
         (GBaseInitFunc) NULL,
         (GBaseFinalizeFunc) NULL,
         (GClassInitFunc) egg_tray_icon_class_init,
         NULL,                     /* class_finalize */
         NULL,                     /* class_data */
         sizeof(EggTrayIcon),
         0,                        /* n_preallocs */
         (GInstanceInitFunc) egg_tray_icon_init
      };

      our_type = g_type_register_static(GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
   }

   return our_type;
}

static void egg_tray_icon_init(EggTrayIcon * icon)
{
   icon->stamp = 1;
   icon->orientation = GTK_ORIENTATION_HORIZONTAL;

   gtk_widget_add_events(GTK_WIDGET(icon), GDK_PROPERTY_CHANGE_MASK |
      GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
}

static void egg_tray_icon_class_init(EggTrayIconClass * klass)
{
   GObjectClass *gobject_class = (GObjectClass *) klass;
   GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
   GtkContainerClass *container_class = (GtkContainerClass *)klass;

   parent_class = g_type_class_peek_parent(klass);

   gobject_class->get_property = egg_tray_icon_get_property;

   widget_class->realize = egg_tray_icon_realize;
   widget_class->unrealize = egg_tray_icon_unrealize;

   container_class->add = egg_tray_icon_add;

   g_object_class_install_property(gobject_class,
      PROP_ORIENTATION,
      g_param_spec_enum("orientation",
         "Orientation",
         "The orientation of the tray.",
         GTK_TYPE_ORIENTATION, GTK_ORIENTATION_HORIZONTAL, G_PARAM_READABLE));

#if defined (GDK_WINDOWING_WIN32)
   g_warning("Port eggtrayicon to Win32");
#endif
}

static void
egg_tray_icon_get_property(GObject * object,
   guint prop_id, GValue * value, GParamSpec * pspec)
{
   EggTrayIcon *icon = EGG_TRAY_ICON(object);

   switch (prop_id) {
   case PROP_ORIENTATION:
      g_value_set_enum(value, icon->orientation);
      break;
   default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
   }
}

static void egg_tray_icon_get_orientation_property(EggTrayIcon * icon)
{
   Display *xdisplay;
   Atom type;
   int format;
   union {
      gulong *prop;
      guchar *prop_ch;
   } prop = {
   NULL};
   gulong nitems;
   gulong bytes_after;
   int error, result;

   g_assert(icon->manager_window != None);

   xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

   gdk_error_trap_push();
   type = None;
   result = XGetWindowProperty(xdisplay,
      icon->manager_window,
      icon->orientation_atom,
      0, G_MAXLONG, FALSE,
      XA_CARDINAL, &type, &format, &nitems, &bytes_after, &(prop.prop_ch));
   error = gdk_error_trap_pop();

   if (error || result != Success)
      return;

   if (type == XA_CARDINAL) {
      GtkOrientation orientation;

      orientation = (prop.prop[0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
         GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;

      if (icon->orientation != orientation) {
         icon->orientation = orientation;

         g_object_notify(G_OBJECT(icon), "orientation");
      }
   }

   if (prop.prop)
      XFree(prop.prop);
}

static GdkFilterReturn
egg_tray_icon_manager_filter(GdkXEvent * xevent, GdkEvent * event,
   gpointer user_data)
{
   EggTrayIcon *icon = user_data;
   XEvent *xev = (XEvent *) xevent;

   if (xev->xany.type == ClientMessage &&
      xev->xclient.message_type == icon->manager_atom &&
      xev->xclient.data.l[1] == icon->selection_atom) {
      egg_tray_icon_update_manager_window(icon, TRUE);
   } else if (xev->xany.window == icon->manager_window) {
      if (xev->xany.type == PropertyNotify &&
         xev->xproperty.atom == icon->orientation_atom) {
         egg_tray_icon_get_orientation_property(icon);
      }
      if (xev->xany.type == DestroyNotify) {
         egg_tray_icon_manager_window_destroyed(icon);
      }
   }
   return GDK_FILTER_CONTINUE;
}

static void egg_tray_icon_unrealize(GtkWidget * widget)
{
   EggTrayIcon *icon = EGG_TRAY_ICON(widget);
   GdkWindow *root_window;

   if (icon->manager_window != None) {
      GdkWindow *gdkwin;

      gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(widget),
         icon->manager_window);

      gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon);
   }

   root_window = gdk_screen_get_root_window(gtk_widget_get_screen(widget));

   gdk_window_remove_filter(root_window, egg_tray_icon_manager_filter, icon);

   if (GTK_WIDGET_CLASS(parent_class)->unrealize)
      (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
}

static void
egg_tray_icon_send_manager_message(EggTrayIcon * icon,
   long message, Window window, long data1, long data2, long data3)
{
   XClientMessageEvent ev;
   Display *display;

   ev.type = ClientMessage;
   ev.window = window;
   ev.message_type = icon->system_tray_opcode_atom;
   ev.format = 32;
   ev.data.l[0] = gdk_x11_get_server_time(GTK_WIDGET(icon)->window);
   ev.data.l[1] = message;
   ev.data.l[2] = data1;
   ev.data.l[3] = data2;
   ev.data.l[4] = data3;

   display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

   gdk_error_trap_push();
   XSendEvent(display, icon->manager_window, False, NoEventMask, (XEvent *) & ev);
   XSync(display, False);
   gdk_error_trap_pop();
}

static void egg_tray_icon_send_dock_request(EggTrayIcon * icon)
{
   egg_tray_icon_send_manager_message(icon,
      SYSTEM_TRAY_REQUEST_DOCK,
      icon->manager_window, gtk_plug_get_id(GTK_PLUG(icon)), 0, 0);
}

static void
egg_tray_icon_update_manager_window(EggTrayIcon * icon, gboolean dock_if_realized)
{
   Display *xdisplay;

   if (icon->manager_window != None)
      return;

   xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

   XGrabServer(xdisplay);

   icon->manager_window = XGetSelectionOwner(xdisplay, icon->selection_atom);

   if (icon->manager_window != None)
      XSelectInput(xdisplay,
         icon->manager_window, StructureNotifyMask | PropertyChangeMask);

   XUngrabServer(xdisplay);
   XFlush(xdisplay);

   if (icon->manager_window != None) {
      GdkWindow *gdkwin;

      gdkwin =
         gdk_window_lookup_for_display(gtk_widget_get_display(GTK_WIDGET(icon)),
         icon->manager_window);

      gdk_window_add_filter(gdkwin, egg_tray_icon_manager_filter, icon);

      if (dock_if_realized && GTK_WIDGET_REALIZED(icon))
         egg_tray_icon_send_dock_request(icon);

      egg_tray_icon_get_orientation_property(icon);
   }
}

static gboolean
transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
{
  gdk_window_clear_area (widget->window, event->area.x, event->area.y,
			 event->area.width, event->area.height);
  return FALSE;
}

static void
make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
			gpointer user_data)
{
  gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
}

static void
make_transparent (GtkWidget *widget, gpointer user_data)
{
  if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
    return;

  gtk_widget_set_app_paintable (widget, TRUE);
  gtk_widget_set_double_buffered (widget, FALSE);
  gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
  g_signal_connect (widget, "expose_event",
		    G_CALLBACK (transparent_expose_event), NULL);
  g_signal_connect_after (widget, "style_set",
			  G_CALLBACK (make_transparent_again), NULL);
}	

static void egg_tray_icon_manager_window_destroyed(EggTrayIcon * icon)
{
   GdkWindow *gdkwin;

   g_return_if_fail(icon->manager_window != None);

   gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(GTK_WIDGET(icon)),
      icon->manager_window);

   gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon);

   icon->manager_window = None;

   egg_tray_icon_update_manager_window(icon, TRUE);
}


static void egg_tray_icon_realize(GtkWidget * widget)
{
   EggTrayIcon *icon = EGG_TRAY_ICON(widget);
   GdkScreen *screen;
   GdkDisplay *display;
   Display *xdisplay;
   char buffer[256];
   GdkWindow *root_window;

   if (GTK_WIDGET_CLASS(parent_class)->realize)
      GTK_WIDGET_CLASS(parent_class)->realize(widget);

   make_transparent (widget, NULL);

   screen = gtk_widget_get_screen(widget);
   display = gdk_screen_get_display(screen);
   xdisplay = gdk_x11_display_get_xdisplay(display);

   /* Now see if there's a manager window around */
   g_snprintf(buffer, sizeof(buffer),
      "_NET_SYSTEM_TRAY_S%d", gdk_screen_get_number(screen));

   icon->selection_atom = XInternAtom(xdisplay, buffer, False);

   icon->manager_atom = XInternAtom(xdisplay, "MANAGER", False);

   icon->system_tray_opcode_atom = XInternAtom(xdisplay,
      "_NET_SYSTEM_TRAY_OPCODE", False);

   icon->orientation_atom = XInternAtom(xdisplay,
      "_NET_SYSTEM_TRAY_ORIENTATION", False);

   egg_tray_icon_update_manager_window(icon, FALSE);
   egg_tray_icon_send_dock_request(icon);

   root_window = gdk_screen_get_root_window(screen);

   /* Add a root window filter so that we get changes on MANAGER */
   gdk_window_add_filter(root_window, egg_tray_icon_manager_filter, icon);
}

static void
egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
{
  g_signal_connect (widget, "realize",
		    G_CALLBACK (make_transparent), NULL);
  GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
}

EggTrayIcon *egg_tray_icon_new_for_screen(GdkScreen * screen, const char *name)
{
   g_return_val_if_fail(GDK_IS_SCREEN(screen), NULL);

   return g_object_new(EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
}

EggTrayIcon *egg_tray_icon_new(const gchar * name)
{
   return g_object_new(EGG_TYPE_TRAY_ICON, "title", name, NULL);
}

guint
egg_tray_icon_send_message(EggTrayIcon * icon,
   gint timeout, const gchar * message, gint len)
{
   guint stamp;

   g_return_val_if_fail(EGG_IS_TRAY_ICON(icon), 0);
   g_return_val_if_fail(timeout >= 0, 0);
   g_return_val_if_fail(message != NULL, 0);

   if (icon->manager_window == None)
      return 0;

   if (len < 0)
      len = strlen(message);

   stamp = icon->stamp++;

   /* Get ready to send the message */
   egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_BEGIN_MESSAGE,
      (Window) gtk_plug_get_id(GTK_PLUG(icon)), timeout, len, stamp);

   /* Now to send the actual message */
   gdk_error_trap_push();
   while (len > 0) {
      XClientMessageEvent ev;
      Display *xdisplay;

      xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

      ev.type = ClientMessage;
      ev.window = (Window) gtk_plug_get_id(GTK_PLUG(icon));
      ev.format = 8;
      ev.message_type = XInternAtom(xdisplay,
         "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
      if (len > 20) {
         memcpy(&ev.data, message, 20);
         len -= 20;
         message += 20;
      } else {
         memcpy(&ev.data, message, len);
         len = 0;
      }

      XSendEvent(xdisplay,
         icon->manager_window, False, StructureNotifyMask, (XEvent *) & ev);
      XSync(xdisplay, False);
   }
   gdk_error_trap_pop();

   return stamp;
}

void egg_tray_icon_cancel_message(EggTrayIcon * icon, guint id)
{
   g_return_if_fail(EGG_IS_TRAY_ICON(icon));
   g_return_if_fail(id > 0);

   egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_CANCEL_MESSAGE,
      (Window) gtk_plug_get_id(GTK_PLUG(icon)), id, 0, 0);
}

GtkOrientation egg_tray_icon_get_orientation(EggTrayIcon * icon)
{
   g_return_val_if_fail(EGG_IS_TRAY_ICON(icon), GTK_ORIENTATION_HORIZONTAL);

   return icon->orientation;
}
