/*
        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; You may only use version 2 of the License,
        you have no option to use any other 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., 675 Mass Ave, Cambridge, MA 02139, USA.

        oroborus - (c) 2001 Ken Lynch
        xfwm4    - (c) 2002 Olivier Fourdan

 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include "main.h"
#include "workspaces.h"
#include "settings.h"
#include "frame.h"
#include "client.h"
#include "menu.h"
#include "gtktoxevent.h"
#include "debug.h"

static guint raise_timeout = 0;
static gulong button_handler_id = 0;
static GdkAtom atom_rcfiles = GDK_NONE;
static Window menu_event_window = None;

static void menu_callback(Menu * menu, MenuOp op, Window client_xwindow, gpointer menu_data, gpointer item_data);
static gboolean show_popup_cb(GtkWidget * widget, GdkEventButton * ev, gpointer data);
static gboolean client_event_cb(GtkWidget * widget, GdkEventClient * ev);

typedef enum
{
  XFWM_BUTTON_UNDEFINED      = 0,
  XFWM_BUTTON_DRAG           = 1,
  XFWM_BUTTON_CLICK          = 2,
  XFWM_BUTTON_CLICK_AND_DRAG = 3,
  XFWM_BUTTON_DOUBLE_CLICK   = 4
} XfwmButtonClickType;

static inline XfwmButtonClickType typeOfClick(Window w, XEvent * ev)
{
    int xcurrent, ycurrent, x, y, total = 0;
    int g = GrabSuccess;
    int clicks = 1;
    Time t0;
    
    g_return_val_if_fail(ev != NULL, XFWM_BUTTON_UNDEFINED);
    g_return_val_if_fail(w != None, XFWM_BUTTON_UNDEFINED);

    g = XGrabPointer(dpy, w, False, ButtonMotionMask | PointerMotionMask | PointerMotionHintMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, ev->xbutton.time);    
    if (g != GrabSuccess)
    {
        DBG("grab failed in typeOfClick\n");
        gdk_beep();
        return XFWM_BUTTON_UNDEFINED;
    }
    x = xcurrent = ev->xbutton.x_root;
    y = ycurrent = ev->xbutton.y_root;
    t0 = CurrentTime;
    
    while ((ABS(x - xcurrent) < 1) && (ABS(y - ycurrent) < 1) && (total < 250) && ((CurrentTime - t0) < 250))
    {
        g_usleep (10);
        total += 10;
        if (XCheckMaskEvent (dpy, ButtonReleaseMask | ButtonPressMask, ev))
        {
            clicks++;
        }
        if (XCheckMaskEvent (dpy, ButtonMotionMask | PointerMotionMask | PointerMotionHintMask, ev))
        {
            xcurrent = ev->xmotion.x_root;
            ycurrent = ev->xmotion.y_root;
        }
        if ((XfwmButtonClickType) clicks == XFWM_BUTTON_DOUBLE_CLICK)
        {
            break;
        }
    }
    XUngrabPointer(dpy, ev->xbutton.time);
    return (XfwmButtonClickType) clicks;
}

static void clear_timeout(void)
{
    if(raise_timeout)
    {
        gtk_timeout_remove(raise_timeout);
        raise_timeout = 0;
    }
}

static gboolean raise_cb(gpointer data)
{
    Client *c;
    DBG("entering raise_cb\n");

    clear_timeout();
    c = clientGetFocus();
    if(c)
    {
        clientRaise(c);
    }
    return (TRUE);
}

static void reset_timeout(void)
{
    if(raise_timeout)
    {
        gtk_timeout_remove(raise_timeout);
    }
    raise_timeout = gtk_timeout_add(raise_delay, (GtkFunction) raise_cb, NULL);
}

static inline void handleKeyPress(XKeyEvent * ev)
{
    Client *c;
    int state, key;
    XEvent e;

    DBG("entering handleKeyEvent\n");

    c = clientGetFocus();
    state = ev->state & (ShiftMask | ControlMask | AltMask | MetaMask);
    for(key = 0; key < KEY_COUNT; key++)
    {
        if((keys[key].keycode == ev->keycode) && (keys[key].modifier == state))
        {
            break;
        }
    }

    if(c)
    {
        switch (key)
        {
            case KEY_MOVE_UP:
            case KEY_MOVE_DOWN:
            case KEY_MOVE_LEFT:
            case KEY_MOVE_RIGHT:
                if((c->has_border) && !(c->fullscreen))
                {
                    clientMove(c, (XEvent *) ev);
                }
                break;
            case KEY_RESIZE_UP:
            case KEY_RESIZE_DOWN:
            case KEY_RESIZE_LEFT:
            case KEY_RESIZE_RIGHT:
                clientResize(c, CORNER_BOTTOM_RIGHT, (XEvent *) ev);
                break;
            case KEY_CYCLE_WINDOWS:
                clientCycle(c);
                break;
            case KEY_CLOSE_WINDOW:
                clientClose(c);
                break;
            case KEY_HIDE_WINDOW:
                clientHide(c, True);
                break;
            case KEY_MAXIMIZE_WINDOW:
                clientToggleMaximized(c, WIN_STATE_MAXIMIZED);
                break;
            case KEY_MAXIMIZE_VERT:
                clientToggleMaximized(c, WIN_STATE_MAXIMIZED_VERT);
                break;
            case KEY_MAXIMIZE_HORIZ:
                clientToggleMaximized(c, WIN_STATE_MAXIMIZED_HORIZ);
                break;
            case KEY_SHADE_WINDOW:
                clientToggleShaded(c);
                break;
            case KEY_RAISE_WINDOW_LAYER:
                clientSetLayer(c, c->win_layer + 1);
                break;
            case KEY_LOWER_WINDOW_LAYER:
                clientSetLayer(c, c->win_layer - 1);
                break;
            case KEY_NEXT_WORKSPACE:
                workspaceSwitch(workspace + 1, NULL);
                break;
            case KEY_PREV_WORKSPACE:
                workspaceSwitch(workspace - 1, NULL);
                break;
            case KEY_ADD_WORKSPACE:
                workspaceSetCount(workspace_count + 1);
                break;
            case KEY_DEL_WORKSPACE:
                workspaceSetCount(workspace_count - 1);
                break;
            case KEY_STICK_WINDOW:
                clientToggleSticky(c);
                break;
            case KEY_WORKSPACE_1:
                workspaceSwitch(0, NULL);
                break;
            case KEY_WORKSPACE_2:
                workspaceSwitch(1, NULL);
                break;
            case KEY_WORKSPACE_3:
                workspaceSwitch(2, NULL);
                break;
            case KEY_WORKSPACE_4:
                workspaceSwitch(3, NULL);
                break;
            case KEY_WORKSPACE_5:
                workspaceSwitch(4, NULL);
                break;
            case KEY_WORKSPACE_6:
                workspaceSwitch(5, NULL);
                break;
            case KEY_WORKSPACE_7:
                workspaceSwitch(6, NULL);
                break;
            case KEY_WORKSPACE_8:
                workspaceSwitch(7, NULL);
                break;
            case KEY_WORKSPACE_9:
                workspaceSwitch(8, NULL);
                break;
            case KEY_MOVE_NEXT_WORKSPACE:
                workspaceSwitch(workspace + 1, c);
                break;
            case KEY_MOVE_PREV_WORKSPACE:
                workspaceSwitch(workspace - 1, c);
                break;
            case KEY_MOVE_WORKSPACE_1:
                workspaceSwitch(0, c);
                break;
            case KEY_MOVE_WORKSPACE_2:
                workspaceSwitch(1, c);
                break;
            case KEY_MOVE_WORKSPACE_3:
                workspaceSwitch(2, c);
                break;
            case KEY_MOVE_WORKSPACE_4:
                workspaceSwitch(3, c);
                break;
            case KEY_MOVE_WORKSPACE_5:
                workspaceSwitch(4, c);
                break;
            case KEY_MOVE_WORKSPACE_6:
                workspaceSwitch(5, c);
                break;
            case KEY_MOVE_WORKSPACE_7:
                workspaceSwitch(6, c);
                break;
            case KEY_MOVE_WORKSPACE_8:
                workspaceSwitch(7, c);
                break;
            case KEY_MOVE_WORKSPACE_9:
                workspaceSwitch(8, c);
                break;
        }
    }
    else
    {
        switch (key)
        {
            case KEY_CYCLE_WINDOWS:
                if(clients)
                {
                    clientCycle(clients->prev);
                }
                break;
            case KEY_NEXT_WORKSPACE:
                workspaceSwitch(workspace + 1, NULL);
                break;
            case KEY_PREV_WORKSPACE:
                workspaceSwitch(workspace - 1, NULL);
                break;
            case KEY_ADD_WORKSPACE:
                workspaceSetCount(workspace_count + 1);
                break;
            case KEY_DEL_WORKSPACE:
                workspaceSetCount(workspace_count - 1);
                break;
            case KEY_WORKSPACE_1:
                workspaceSwitch(0, NULL);
                break;
            case KEY_WORKSPACE_2:
                workspaceSwitch(1, NULL);
                break;
            case KEY_WORKSPACE_3:
                workspaceSwitch(2, NULL);
                break;
            case KEY_WORKSPACE_4:
                workspaceSwitch(3, NULL);
                break;
            case KEY_WORKSPACE_5:
                workspaceSwitch(4, NULL);
                break;
            case KEY_WORKSPACE_6:
                workspaceSwitch(5, NULL);
                break;
            case KEY_WORKSPACE_7:
                workspaceSwitch(6, NULL);
                break;
            case KEY_WORKSPACE_8:
                workspaceSwitch(7, NULL);
                break;
            case KEY_WORKSPACE_9:
                workspaceSwitch(8, NULL);
                break;
        }
    }

    while(XCheckTypedEvent(dpy, EnterNotify, &e));
}

static inline void handleButtonPress(XButtonEvent * ev)
{
    Client *c;
    Window win;
    int state, replay = False;

    DBG("entering handleButtonPress\n");

    /* Clear timeout */
    clear_timeout();

    c = clientGetFromWindow(ev->window, ANY);
    if(c)
    {
        state = ev->state & (ShiftMask | ControlMask | AltMask | MetaMask);
        win = ev->subwindow;
        
        if((win == c->buttons[HIDE_BUTTON]) || (win == c->buttons[CLOSE_BUTTON]) || (win == c->buttons[MAXIMIZE_BUTTON]) || (win == c->buttons[SHADE_BUTTON]) || (win == c->buttons[STICK_BUTTON]))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientButtonPress(c, win, ev);
        }
        else if(((win == c->title) && (ev->button == Button3)) || ((win == c->buttons[MENU_BUTTON]) && (ev->button == Button1)))
        {
            /*
               We need to copy the event to keep the original event untouched
               for gtk to handle it (in case we open up the menu)
             */
            XEvent copy_event = (XEvent) *ev;
            XfwmButtonClickType tclick;
            
            if ((win == c->buttons[MENU_BUTTON]) && ((tclick = typeOfClick(c->frame, &copy_event)) && (tclick == XFWM_BUTTON_DOUBLE_CLICK)))
            {
                clientClose(c);
            }
            else
            {
                clientSetFocus(c, True);
                clientRaise(c);
                ev->window = ev->root;
                if(button_handler_id)
                {
                    g_signal_handler_disconnect(GTK_OBJECT(getDefaultGtkWidget()), button_handler_id);
                }
                button_handler_id = g_signal_connect(GTK_OBJECT(getDefaultGtkWidget()), "button_press_event", GTK_SIGNAL_FUNC(show_popup_cb), (gpointer) c);
                /* Let GTK handle this for us. */
            }
        }
        else if(((win == c->title) && ((ev->button == Button1) && (state == 0))) || ((ev->button == Button1) && (state == AltMask)))
        {
            XEvent copy_event = (XEvent) *ev;
            XfwmButtonClickType tclick;
            
            if (win == c->title)
            {
                clientSetFocus(c, True);
                clientRaise(c);
            }
            tclick = typeOfClick(c->frame, &copy_event);
            if((tclick == XFWM_BUTTON_DRAG) || (tclick == XFWM_BUTTON_CLICK_AND_DRAG))
            {
                if((c->has_border) && !(c->fullscreen))
                {
                    clientMove(c, (XEvent *) ev);
                }
            }
            else if(tclick == XFWM_BUTTON_DOUBLE_CLICK)
            {
                switch (double_click_action)
                {
                    case ACTION_MAXIMIZE:
                        clientToggleMaximized(c, WIN_STATE_MAXIMIZED);
                        break;
                    case ACTION_SHADE:
                        clientToggleShaded(c);
                        break;
                    case ACTION_HIDE:
                        clientHide(c, True);
                        break;
                }
            }
        }
        else if((win == c->corners[CORNER_TOP_LEFT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, CORNER_TOP_LEFT, (XEvent *) ev);
        }
        else if((win == c->corners[CORNER_TOP_RIGHT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, CORNER_TOP_RIGHT, (XEvent *) ev);
        }
        else if((win == c->corners[CORNER_BOTTOM_LEFT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, CORNER_BOTTOM_LEFT, (XEvent *) ev);
        }
        else if((win == c->corners[CORNER_BOTTOM_RIGHT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, CORNER_BOTTOM_RIGHT, (XEvent *) ev);
        }
        else if((win == c->sides[SIDE_BOTTOM]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, 4 + SIDE_BOTTOM, (XEvent *) ev);
        }
        else if((win == c->sides[SIDE_LEFT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, 4 + SIDE_LEFT, (XEvent *) ev);
        }
        else if((win == c->sides[SIDE_RIGHT]) && (ev->button == Button1) && (state == 0))
        {
            clientSetFocus(c, True);
            clientRaise(c);
            clientResize(c, 4 + SIDE_RIGHT, (XEvent *) ev);
        }
        else if(((ev->window != c->window) && (ev->button == Button2) && (state == 0)) || ((ev->button == Button2) && (state == (AltMask | ControlMask))))
        {
            clientLower(c);
        }
        else
        {
            if (ev->button == Button1)
            {
                clientSetFocus(c, True);
                clientRaise(c);
            }
            if(ev->window == c->window)
            {
                replay = True;
            }
        }

        if(replay)
        {
            XAllowEvents(dpy, ReplayPointer, ev->time);
        }
        else
        {
            XAllowEvents(dpy, SyncPointer, ev->time);
        }
    }
    else
    {
        XUngrabPointer(dpy, CurrentTime);
        XSendEvent(dpy, gnome_win, False, SubstructureNotifyMask, (XEvent *) ev);
    }
}

static inline void handleButtonRelease(XButtonEvent * ev)
{
    DBG("entering handleButtonRelease\n");

    XSendEvent(dpy, gnome_win, False, SubstructureNotifyMask, (XEvent *) ev);
}

static inline void handleDestroyNotify(XDestroyWindowEvent * ev)
{
    Client *c;

    DBG("entering handleDestroyNotify\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        clientUnframe(c, False);
        if(clients)
        {
            clientSetFocus(clientGetNext(clients->prev, 0), True);
        }
        else
        {
            clientSetFocus(NULL, True);
        }
    }
}

static inline void handleUnmapNotify(XUnmapEvent * ev)
{
    Client *c;

    DBG("entering handleUnmapNotify\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        if(c->ignore_unmap)
        {
            c->ignore_unmap--;
        }
        else
        {
            clientUnframe(c, False);
            if(clients)
            {
                clientSetFocus(clientGetNext(clients->prev, 0), True);
            }
            else
            {
                clientSetFocus(NULL, True);
            }
        }
    }
}

static inline void handleMapRequest(XMapRequestEvent * ev)
{
    Client *c;

    DBG("entering handleMapRequest\n");

    if(ev->window == None)
    {
        DBG("Mapping None ???\n");
        return;
    }
    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        clientShow(c, True);
    }
    else
    {
        clientFrame(ev->window);
    }
}

static inline void handleConfigureRequest(XConfigureRequestEvent * ev)
{
    Client *c;
    XWindowChanges wc;

    DBG("entering handleConfigureRequest\n");

    wc.x = ev->x;
    wc.y = ev->y;
    wc.width = ev->width;
    wc.height = ev->height;
    wc.sibling = ev->above;
    wc.stack_mode = ev->detail;
    wc.border_width = ev->border_width;

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        if(c->type == WINDOW_DESKTOP)
        {
            /* Ignore stacking request for DESKTOP windows */
            wc.stack_mode &= ~CWStackMode;
        }
        clientCoordGravitate(c, APPLY, &wc.x, &wc.y);
        if ((ev->value_mask & (CWX | CWY | CWWidth | CWHeight)) && c->maximized)
        {
            clientRemoveMaximizeFlag(c);
        }
        clientConfigure(c, &wc, ev->value_mask);
    }
    else
    {
        XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
    }
}

static inline void handleEnterNotify(XCrossingEvent * ev)
{
    Client *c;

    DBG("entering handleEnterNotify\n");

    while(XCheckTypedEvent(dpy, EnterNotify, (XEvent *) ev));

    DBG("EnterNotify window is (%#lx)\n", ev->window);

    c = clientGetFromWindow(ev->window, FRAME);
    if(c && !(click_to_focus) && (clientAcceptFocus(c)))
    {
        DBG("EnterNotify window is \"%s\"\n", c->name);
        if((c->type != WINDOW_DOCK) && (c->type != WINDOW_DESKTOP))
        {
            clientSetFocus(c, True);
        }
    }
}

static inline void handleFocusIn(XFocusChangeEvent * ev)
{
    Client *c;

    DBG("entering handleFocusIn\n");
    DBG("FocusIn window is (%#lx)\n", ev->window);

    if(ev->window == gnome_win)
    {
        /* Don't get fooled by our own gtk window ! */
        return;
    }

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        DBG("focus set to \"%s\" (%#lx)\n", c->name, c->window);
        clientUpdateFocus(c);
        frameDraw(c);
        if(raise_on_focus && !click_to_focus)
        {
            reset_timeout();
        }
    }
    else if(clients)
    {
        DBG("focus set to top window in list\n");
        clientSetFocus(clientGetNext(clients->prev, 0), True);
    }
    else
    {
        DBG("focus set to default fallback window\n");
        clientSetFocus(NULL, True);
    }
}

static inline void handleFocusOut(XFocusChangeEvent * ev)
{
    DBG("entering handleFocusOut\n");
}

static inline void handlePropertyNotify(XPropertyEvent * ev)
{
    Client *c;
    long dummy;

    DBG("entering handlePropertyNotify\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        if(ev->atom == XA_WM_NORMAL_HINTS)
        {
            DBG("client \"%s\" (%#lx) has received a XA_WM_NORMAL_HINTS notify\n", c->name, c->window);
            XGetWMNormalHints(dpy, c->window, c->size, &dummy);
        }
        else if((ev->atom == XA_WM_NAME) || (ev->atom == net_wm_name))
        {
            DBG("client \"%s\" (%#lx) has received a XA_WM_NAME notify\n", c->name, c->window);
            if(c->name)
            {
                free(c->name);
            }
            getWindowName(dpy, c->window, &c->name);
            frameDraw(c);
        }
        else if(ev->atom == win_hints)
        {
            DBG("client \"%s\" (%#lx) has received a win_hints notify\n", c->name, c->window);
            getGnomeHint(dpy, c->window, win_hints, &c->win_hints);
        }
        else if(ev->atom == win_layer)
        {
            DBG("client \"%s\" (%#lx) has received a win_layer notify\n", c->name, c->window);
            getGnomeHint(dpy, c->window, win_layer, &dummy);
            clientSetLayer(c, dummy);
            clientSetNetState(c);
        }
        else if(ev->atom == net_wm_window_type)
        {
            DBG("client \"%s\" (%#lx) has received a net_wm_window_type notify\n", c->name, c->window);
            clientGetNetWmType(c);
        }
        else if((ev->atom == win_workspace) && !(c->transient_for))
        {
            DBG("client \"%s\" (%#lx) has received a win_workspace notify\n", c->name, c->window);
            getGnomeHint(dpy, c->window, win_workspace, &dummy);
            clientSetWorkspace(c, dummy, TRUE);
        }
        else if(ev->atom == net_wm_strut)
        {
            DBG("client \"%s\" (%#lx) has received a net_wm_strut notify\n", c->name, c->window);
            clientGetNetStruts(c);
        }
        else if(ev->atom == wm_colormap_windows)
        {
            clientUpdateColormaps(c);
            if(c == clientGetFocus())
            {
                clientInstallColormaps(c);
            }
        }
    }
    else
    {
        if(ev->atom == win_workspace_count)
        {
            DBG("root has received a win_workspace_count notify\n");
            getGnomeHint(dpy, root, win_workspace_count, &dummy);
            workspaceSetCount(dummy);
        }
        else if(ev->atom == gnome_panel_desktop_area)
        {
            DBG("root has received a gnome_panel_desktop_area notify\n");
            getGnomeDesktopMargins(dpy, gnome_margins);
            workspaceUpdateArea(margins, gnome_margins);
        }
    }
}

static inline void handleClientMessage(XClientMessageEvent * ev)
{
    Client *c;

    DBG("entering handleClientMessage\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        if((ev->message_type == wm_change_state) && (ev->format == 32) && (ev->data.l[0] == IconicState))
        {
            DBG("client \"%s\" (%#lx) has received a wm_change_state event\n", c->name, c->window);
            clientHide(c, True);
        }
        else if((ev->message_type == win_state) && (ev->format == 32) && (ev->data.l[0] & WIN_STATE_SHADED))
        {
            DBG("client \"%s\" (%#lx) has received a win_state/shaded event\n", c->name, c->window);
            clientToggleShaded(c);
        }
        else if((ev->message_type == win_state) && (ev->format == 32) && (ev->data.l[0] & WIN_STATE_STICKY))
        {
            DBG("client \"%s\" (%#lx) has received a win_state/stick event\n", c->name, c->window);
            clientToggleSticky(c);
        }
        else if((ev->message_type == win_layer) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a win_layer event\n", c->name, c->window);
            clientSetLayer(c, ev->data.l[0]);
        }
        else if((ev->message_type == win_workspace) && (ev->format == 32) && !(c->transient_for))
        {
            DBG("client \"%s\" (%#lx) has received a win_workspace event\n", c->name, c->window);
            clientSetWorkspace(c, ev->data.l[0], TRUE);
        }
        else if((ev->message_type == net_wm_desktop) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a net_wm_desktop event\n", c->name, c->window);
            if((ev->data.l[0] == (int)0xFFFFFFFF))
            {
                clientStick(c);
            }
            else if (!(c->transient_for))
            {
                clientSetWorkspace(c, ev->data.l[0], TRUE);
            }
        }
        else if((ev->message_type == net_close_window) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a net_close_window event\n", c->name, c->window);
            clientClose(c);
        }
        else if((ev->message_type == net_wm_state) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a net_wm_state event\n", c->name, c->window);
            clientUpdateNetState(c, ev);
        }
        else if((ev->message_type == net_wm_moveresize) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a net_wm_moveresize event\n", c->name, c->window);
            g_message("Operation not supported (yet)\n");
            /* TBD */
        }
        else if((ev->message_type == net_active_window) && (ev->format == 32))
        {
            DBG("client \"%s\" (%#lx) has received a net_active_window event\n", c->name, c->window);
            workspaceSwitch(c->win_workspace, NULL);
            clientShow(c, True);
            clientRaise(c);
            clientSetFocus(c, True);
        }
    }
    else
    {
        if(((ev->message_type == win_workspace) || (ev->message_type == net_current_desktop)) && (ev->format == 32))
        {
            DBG("root has received a win_workspace or a net_current_desktop event\n");
            workspaceSwitch(ev->data.l[0], NULL);
        }
        else if(((ev->message_type == win_workspace_count) || (ev->message_type == net_number_of_desktops)) && (ev->format == 32))
        {
            DBG("root has received a win_workspace_count event\n");
            workspaceSetCount(ev->data.l[0]);
        }
    }
}

static inline void handleShape(XShapeEvent * ev)
{
    Client *c;

    DBG("entering handleShape\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if(c)
    {
        frameDraw(c);
    }
}

static inline void handleColormapNotify(XColormapEvent * ev)
{
    Client *c;

    DBG("entering handleColormapNotify\n");

    c = clientGetFromWindow(ev->window, WINDOW);
    if((c) && (ev->window == c->window) && (ev->new))
    {
        if(c == clientGetFocus())
        {
            clientInstallColormaps(c);
        }
    }
}

void handleEvent(XEvent * ev)
{
    DBG("entering handleEvent\n");

    switch (ev->type)
    {
        case KeyPress:
            handleKeyPress((XKeyEvent *) ev);
            break;
        case ButtonPress:
            handleButtonPress((XButtonEvent *) ev);
            break;
        case ButtonRelease:
            handleButtonRelease((XButtonEvent *) ev);
            break;
        case DestroyNotify:
            handleDestroyNotify((XDestroyWindowEvent *) ev);
            break;
        case UnmapNotify:
            handleUnmapNotify((XUnmapEvent *) ev);
            break;
        case MapRequest:
            handleMapRequest((XMapRequestEvent *) ev);
            break;
        case ConfigureRequest:
            handleConfigureRequest((XConfigureRequestEvent *) ev);
            break;
        case EnterNotify:
            handleEnterNotify((XCrossingEvent *) ev);
            break;
        case FocusIn:
            handleFocusIn((XFocusChangeEvent *) ev);
            break;
        case FocusOut:
            handleFocusOut((XFocusChangeEvent *) ev);
            break;
        case PropertyNotify:
            handlePropertyNotify((XPropertyEvent *) ev);
            break;
        case ClientMessage:
            handleClientMessage((XClientMessageEvent *) ev);
            break;
        case ColormapNotify:
            handleColormapNotify((XColormapEvent *) ev);
            break;
        default:
            if(shape && (ev->type == shape_event))
            {
                handleShape((XShapeEvent *) ev);
            }
    }
    if(!gdk_events_pending() && !XPending(dpy))
    {
        if(reload)
        {
            reloadSettings();
            reload = False;
        }
        else if(quit)
        {
            gtk_main_quit();
        }
    }
}


GtkToXEventFilterStatus xfwm4_event_filter(XEvent * xevent, gpointer data)
{
    DBG("entering xfwm4_event_filter\n");
    handleEvent(xevent);
    DBG("leaving xfwm4_event_filter\n");
    return XEV_FILTER_STOP;
}

/* GTK stuff (menu, etc...) */

static void menu_callback(Menu * menu, MenuOp op, Window client_xwindow, gpointer menu_data, gpointer item_data)
{
    Client *c = NULL;

    DBG("entering menu_callback\n");

    if (menu_event_window)
    {
        removeTmpEventWin (menu_event_window);
        menu_event_window = None;
    }

    if(menu_data)
    {
        c = (Client *) menu_data;
        c = clientGetFromWindow(c->window, WINDOW);
        if(c)
        {
            c->button_pressed[MENU_BUTTON] = False;
        }
    }

    switch (op)
    {
        case MENU_OP_QUIT:
            gtk_main_quit();
            break;
        case MENU_OP_MAXIMIZE:
        case MENU_OP_UNMAXIMIZE:
            if(c)
            {
                clientToggleMaximized(c, WIN_STATE_MAXIMIZED);
            }
            break;
        case MENU_OP_MINIMIZE:
            if(c)
            {
                clientHide(c, True);
            }
            break;
        case MENU_OP_MINIMIZE_ALL:
            clientHideAll(c);
            break;
        case MENU_OP_UNMINIMIZE:
            if(c)
            {
                clientShow(c, True);
            }
            break;
        case MENU_OP_SHADE:
        case MENU_OP_UNSHADE:
            if(c)
            {
                clientToggleShaded(c);
            }
            break;
        case MENU_OP_STICK:
        case MENU_OP_UNSTICK:
            if(c)
            {
                clientToggleSticky(c);
                frameDraw(c);
            }
            break;
        case MENU_OP_DELETE:
            if(c)
            {
                clientClose(c);
                frameDraw(c);
            }
            break;
        case MENU_OP_DESTROY:
            if(c)
            {
                clientKill(c);
                frameDraw(c);
            }
            break;
        default:
            if(c)
            {
                frameDraw(c);
            }
            break;
    }
    menu_free(menu);
}

static gboolean show_popup_cb(GtkWidget * widget, GdkEventButton * ev, gpointer data)
{
    Menu *menu;
    MenuOp ops;
    MenuOp insensitive;
    Client *c = NULL;
    gint x = ev->x_root;
    gint y = ev->y_root;

    DBG("entering show_popup_cb\n");

    if(((ev->button == 1) || (ev->button == 3)) && (c = (Client *) data))
    {
        c->button_pressed[MENU_BUTTON] = True;
        frameDraw(c);
        y = c->y;
        ops = MENU_OP_DELETE | MENU_OP_DESTROY | MENU_OP_MINIMIZE_ALL;
        insensitive = 0;

        if(c->win_state & (WIN_STATE_MAXIMIZED | WIN_STATE_MAXIMIZED_HORIZ | WIN_STATE_MAXIMIZED_VERT))
        {
            ops |= MENU_OP_UNMAXIMIZE;
        }
        else
        {
            ops |= MENU_OP_MAXIMIZE;
        }

        if(c->hidden)
        {
            ops |= MENU_OP_UNMINIMIZE;
        }
        else
        {
            ops |= MENU_OP_MINIMIZE;
        }

        if(c->win_state & WIN_STATE_SHADED)
        {
            ops |= MENU_OP_UNSHADE;
        }
        else
        {
            ops |= MENU_OP_SHADE;
        }

        if(c->sticky)
        {
            ops |= MENU_OP_UNSTICK;
        }
        else
        {
            ops |= MENU_OP_STICK;
        }
    }
    else
    {
        return (TRUE);
    }

    if(button_handler_id)
    {
        g_signal_handler_disconnect(GTK_OBJECT(getDefaultGtkWidget()), button_handler_id);
    }
    button_handler_id = g_signal_connect(GTK_OBJECT(getDefaultGtkWidget()), "button_press_event", GTK_SIGNAL_FUNC(show_popup_cb), (gpointer) NULL);

    if (menu_event_window)
    {
        removeTmpEventWin (menu_event_window);
        menu_event_window = None;
    }
    menu_event_window = setTmpEventWin(NoEventMask);
    menu = menu_default(ops, insensitive, menu_callback, c);
    if(!menu_popup(menu, x, y, ev->button, ev->time))
    {
        DBG("Cannot open menu\n");
        gdk_beep();
        c->button_pressed[MENU_BUTTON] = False;
        frameDraw(c);
        removeTmpEventWin (menu_event_window);
        menu_event_window = None;
        menu_free(menu);
    }
    return (TRUE);
}

static gboolean set_reload(void)
{
    DBG("setting reload flag so all prefs will be reread at next event loop\n");
    reload = True;
    return (TRUE);
}

static gboolean client_event_cb(GtkWidget * widget, GdkEventClient * ev)
{
    DBG("entering client_event_cb\n");

    if(!atom_rcfiles)
    {
        atom_rcfiles = gdk_atom_intern("_GTK_READ_RCFILES", FALSE);
    }

    if(ev->message_type == atom_rcfiles)
    {
        set_reload();
    }

    return (FALSE);
}

void initGtkCallbacks(void)
{
    GtkSettings *settings;

    button_handler_id = g_signal_connect(GTK_OBJECT(getDefaultGtkWidget()), "button_press_event", GTK_SIGNAL_FUNC(show_popup_cb), (gpointer) NULL);
    g_signal_connect(GTK_OBJECT(getDefaultGtkWidget()), "client_event", GTK_SIGNAL_FUNC(client_event_cb), (gpointer) NULL);

    settings = gtk_settings_get_default();
    if(settings)
    {
        g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(set_reload), NULL);
        g_signal_connect(settings, "notify::gtk-font-name", G_CALLBACK(set_reload), NULL);
    }
}