/*
 * Copyright (c) 2003 Benedikt Meurer <benedikt.meurer@unix-ag.uni-siegen.de>
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 *
 * http://www.freedesktop.org/standards/systemtray.html
 *
 * TODO:
 *
 *      - Use gdk functions where possible (e.g. Atoms and Events)
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <string.h>

#include "netk-private.h"

#include <libxfcegui4/netk-trayicon.h>
#include <libxfcegui4/xfce_systemtray.h>

/* prototypes */
static void netk_tray_icon_init (NetkTrayIcon *);
static void netk_tray_icon_class_init (NetkTrayIconClass *);
static void netk_tray_icon_unrealize (GtkWidget *);
static void netk_tray_icon_realize (GtkWidget *);
static GdkFilterReturn netk_tray_icon_filter (GdkXEvent *, GdkEvent *,
                                              gpointer);
static void netk_tray_icon_update (NetkTrayIcon *);
static void netk_tray_icon_opcode (NetkTrayIcon *, Window, glong,
                                   glong, glong, glong);

/* */
static GObjectClass *parent_class;

GType
netk_tray_icon_get_type (void)
{
    static GType type = G_TYPE_INVALID;

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (GTK_TYPE_PLUG,
                                             "NetkTrayIcon",
                                             sizeof (NetkTrayIconClass),
                                             netk_tray_icon_class_init,
                                             sizeof (NetkTrayIcon),
                                             netk_tray_icon_init,
                                             0);
    }

    return type;
}

static void
netk_tray_icon_init (NetkTrayIcon * icon)
{
    icon->count = 0;

    /*
     * look for property changes
     */
    gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
}

static void
netk_tray_icon_class_init (NetkTrayIconClass * klass)
{
    GtkWidgetClass *widget_class;

    parent_class = g_type_class_peek_parent (klass);

    widget_class = GTK_WIDGET_CLASS (klass);
    widget_class->realize = netk_tray_icon_realize;
    widget_class->unrealize = netk_tray_icon_unrealize;
}

static void
netk_tray_icon_unrealize (GtkWidget * widget)
{
    NetkTrayIcon *icon;
    GdkWindow *window;

    icon = NETK_TRAY_ICON (widget);

    if (icon->tray != None)
    {
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
        window =
            gdk_window_lookup_for_display (gtk_widget_get_display (widget),
                                           icon->tray);
#else
        window = gdk_window_lookup (icon->tray);
#endif
        gdk_window_remove_filter (window, netk_tray_icon_filter, icon);
    }

    /*
     * Remove GDK event filter from the root window
     */
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
#else
    window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ());
#endif
    gdk_window_remove_filter (window, netk_tray_icon_filter, icon);

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

static void
netk_tray_icon_realize (GtkWidget * widget)
{
    gchar selection[32];
        NetkTrayIcon *icon;
    GdkWindow *window;
        Screen *xscreen;
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    GdkScreen *screen;
#endif

    /* */
    GTK_WIDGET_CLASS (parent_class)->realize (widget);

#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    screen = gtk_widget_get_screen(widget);
    xscreen = GDK_SCREEN_XSCREEN(screen);
#else
    xscreen = DefaultScreenOfDisplay(GDK_DISPLAY());
#endif

    icon = NETK_TRAY_ICON(widget);

    /* look for a manager out there */
    (void) g_snprintf (selection, sizeof (selection), "_NET_SYSTEM_TRAY_S%d",
                       XScreenNumberOfScreen (xscreen));

    icon->atoms.data = XInternAtom (DisplayOfScreen (xscreen),
                                    "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);

    icon->atoms.manager = XInternAtom (DisplayOfScreen (xscreen),
                                       "MANAGER", False);

    icon->atoms.opcode = XInternAtom (DisplayOfScreen (xscreen),
                                      "_NET_SYSTEM_TRAY_OPCODE", False);

    icon->atoms.selection = XInternAtom (DisplayOfScreen (xscreen),
                                         selection, False);

    /*
     * Look for system tray running
     */
    netk_tray_icon_update (icon);

    /*
     * Register event filter for root window, so that we are notified
     * of changes on MANAGER
     */
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    window = gdk_screen_get_root_window (screen);
#else
    window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ());
#endif
    gdk_window_add_filter (window, netk_tray_icon_filter, icon);
}

static GdkFilterReturn
netk_tray_icon_filter (GdkXEvent * xevent, GdkEvent * event, gpointer data)
{
    XClientMessageEvent *xev;
    NetkTrayIcon *icon;

    icon = NETK_TRAY_ICON (data);
    xev = (XClientMessageEvent *) xevent;

    switch (xev->type)
    {
        case ClientMessage:
            if (xev->message_type == icon->atoms.manager
                && xev->data.l[1] == (long) icon->atoms.selection)
            {
                /*
                 * tray appeared, update the icon window
                 */
                netk_tray_icon_update (icon);
            }
            break;

        case DestroyNotify:
            if (xev->window == icon->tray)
            {
                /*
                 * tray disappeard, update the icon window
                 */
                netk_tray_icon_update (icon);
            }
            break;
    }

    return (GDK_FILTER_CONTINUE);
}

static void
netk_tray_icon_update (NetkTrayIcon * icon)
{
    GdkWindow *window;
    Display *xdisplay;
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    GdkDisplay *display;

    display = gtk_widget_get_display (GTK_WIDGET (icon));
    xdisplay = GDK_DISPLAY_XDISPLAY (display);
#else
    xdisplay = gdk_display;
#endif

    if (icon->tray != None)
    {
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
        window = gdk_window_lookup_for_display (display, icon->tray);
#else
        window = gdk_window_lookup (icon->tray);
#endif
        gdk_window_remove_filter (window, netk_tray_icon_filter, icon);
    }

    XGrabServer (xdisplay);

    /* look for the system tray window */
    icon->tray = XGetSelectionOwner (xdisplay, icon->atoms.selection);

    if (icon->tray != None)
    {
        /* found it */
        XSelectInput (xdisplay, icon->tray, StructureNotifyMask);
    }

    XUngrabServer (xdisplay);
    XFlush (xdisplay);

    if (icon->tray != None)
    {
        /*
         * Register window event filter
         */
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
        window = gdk_window_lookup_for_display (display, icon->tray);
#else
        window = gdk_window_lookup (icon->tray);
#endif

        gdk_window_add_filter (window, netk_tray_icon_filter, icon);

        /*
         * send a dock request to the system tray
         */
        netk_tray_icon_opcode (icon, icon->tray,
                               SYSTEM_TRAY_REQUEST_DOCK,
                               gtk_plug_get_id (GTK_PLUG (icon)), 0, 0);
    }
}

static void
netk_tray_icon_opcode (NetkTrayIcon * icon, Window window, glong data1,
                       glong data2, glong data3, glong data4)
{
    XClientMessageEvent xev;
    Display *xdisplay;
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    GdkDisplay *display;

    display = gtk_widget_get_display (GTK_WIDGET (icon));
    xdisplay = GDK_DISPLAY_XDISPLAY (display);
#else
    xdisplay = gdk_display;
#endif
    memset (&xev, 0, sizeof (xev));

    /*
     * OPCODES are ClientMessages of format 32
     */
    xev.type = ClientMessage;
    xev.window = window;
    xev.message_type = icon->atoms.opcode;
    xev.format = 32;
    xev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
    xev.data.l[1] = data1;
    xev.data.l[2] = data2;
    xev.data.l[3] = data3;
    xev.data.l[4] = data4;

    gdk_error_trap_push ();
    XSendEvent (xdisplay, icon->tray, False, NoEventMask, (XEvent *) & xev);
    XSync (xdisplay, FALSE);
    gdk_error_trap_pop ();
}

void
netk_tray_icon_set_screen (NetkTrayIcon *icon, Screen *xscreen)
{
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    GdkDisplay *display;
    GdkScreen *screen;

    g_return_if_fail (xscreen != NULL);

#endif

#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION(2,2,0)
    display = gdk_x11_lookup_xdisplay (DisplayOfScreen (xscreen));
    screen =
        gdk_display_get_screen (display, XScreenNumberOfScreen (xscreen));

    gtk_plug_construct_for_display (GTK_PLUG (icon), display, 0);

    gtk_window_set_screen (GTK_WINDOW (icon), screen);
#else
    gtk_plug_construct (GTK_PLUG (icon), 0);
#endif

    gtk_widget_realize (GTK_WIDGET (icon));
}

GtkWidget *
netk_tray_icon_new (Screen * xscreen)
{
    NetkTrayIcon *icon;

    icon = NETK_TRAY_ICON (g_object_new (NETK_TYPE_TRAY_ICON, NULL));
    netk_tray_icon_set_screen (icon, xscreen);
    g_object_ref (G_OBJECT (icon));

    return (GTK_WIDGET (icon));
}

glong
netk_tray_icon_message_new (NetkTrayIcon * icon, glong timeout,
                            const gchar * text)
{
    XClientMessageEvent xev;
    Display *xdisplay;
    glong length;
    glong id;
    glong n;

    g_return_val_if_fail (NETK_IS_TRAY_ICON (icon), -1);
    g_return_val_if_fail (timeout > -1, -1);
    g_return_val_if_fail (text != NULL, -1);

    /*
     * First check if theres a system tray around
     */
    if (icon->tray == None)
        return (-1);

    id = icon->count++;
    length = strlen (text);
#if defined (GTK_CHECK_VERSION) && GTK_CHECK_VERSION (2,2,0)
    xdisplay =
        GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
#else
    xdisplay = gdk_display;
#endif

    netk_tray_icon_opcode (icon, gtk_plug_get_id (GTK_PLUG (icon)),
                           SYSTEM_TRAY_BEGIN_MESSAGE, timeout, length, id);

    xev.type = ClientMessage;
    xev.window = gtk_plug_get_id (GTK_PLUG (icon));
    xev.format = 8;
    xev.message_type = icon->atoms.data;

    gdk_error_trap_push ();
    while (length > 0)
    {
        n = MIN (length, 20);

        memcpy (&xev.data, text, n);
        length -= n;
        text += n;

        XSendEvent (xdisplay, icon->tray, False,
                    StructureNotifyMask, (XEvent *) & xev);

        XSync (xdisplay, False);
    }
    gdk_error_trap_pop ();
    return (id);
}

void
netk_tray_icon_message_cancel (NetkTrayIcon * icon, glong id)
{
    g_return_if_fail (NETK_IS_TRAY_ICON (icon));
    g_return_if_fail (id > -1);

    /*
     * First check if theres a system tray around
     */
    if (icon->tray == None)
        return;

    netk_tray_icon_opcode (icon, gtk_plug_get_id (GTK_PLUG (icon)),
                           SYSTEM_TRAY_CANCEL_MESSAGE, id, 0, 0);
}

NetkTrayIcon *netk_tray_icon_new_gdk (GdkScreen *screen)
{
    return NETK_TRAY_ICON(netk_tray_icon_new(GDK_SCREEN_XSCREEN(screen)));
}

void netk_tray_icon_set_gdkscreen (NetkTrayIcon *trayicon, GdkScreen *screen)
{
    netk_tray_icon_set_screen(trayicon, GDK_SCREEN_XSCREEN(screen));
}

