/* event.c- event loop and handling
 * 
 *  Window Maker window manager
 * 
 *  Copyright (c) 1997-2002 Alfredo K. Kojima
 * 
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */


#include "wconfig.h"


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>



#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef SHAPE
# include <X11/extensions/shape.h>
#endif
#ifdef XDND
#include "xdnd.h"
#endif

#ifdef KEEP_XKB_LOCK_STATUS     
#include <X11/XKBlib.h>         
#endif /* KEEP_XKB_LOCK_STATUS */

#include "WindowMaker.h"
#include "window.h"
#include "actions.h"
#include "client.h"
#include "funcs.h"
#include "keybind.h"
#include "application.h"
#include "stacking.h"
#include "defaults.h"
#include "workspace.h"
#include "dock.h"
#include "framewin.h"
#include "properties.h"
#include "balloon.h"

#ifdef GNOME_STUFF
# include "gnome.h"
#endif
#ifdef KWM_HINTS
# include "kwm.h"
#endif

/******** Global Variables **********/
extern XContext wWinContext;

extern Cursor wCursor[WCUR_LAST];

extern WShortKey wKeyBindings[WKBD_LAST];
extern int wScreenCount;
extern Time LastTimestamp;
extern Time LastFocusChange;

extern WPreferences wPreferences;

#define MOD_MASK wPreferences.modifier_mask

extern Atom _XA_WM_COLORMAP_NOTIFY;

extern Atom _XA_WM_CHANGE_STATE;
extern Atom _XA_WM_DELETE_WINDOW;
extern Atom _XA_GNUSTEP_WM_ATTR;
extern Atom _XA_GNUSTEP_WM_MINIATURIZE_WINDOW;
extern Atom _XA_GNUSTEP_TITLEBAR_STATE;
extern Atom _XA_WINDOWMAKER_WM_FUNCTION;
extern Atom _XA_WINDOWMAKER_COMMAND;

#ifdef OFFIX_DND
extern Atom _XA_DND_PROTOCOL;
#endif


#ifdef SHAPE
extern Bool wShapeSupported;
extern int wShapeEventBase;
#endif

#ifdef KEEP_XKB_LOCK_STATUS     
extern int wXkbEventBase;
#endif

/* special flags */
extern char WDelayedActionSet;


/************ Local stuff ***********/


static void saveTimestamp(XEvent *event);
static void handleColormapNotify();
static void handleMapNotify(), handleUnmapNotify();
static void handleButtonPress(), handleExpose();
static void handleDestroyNotify();
static void handleConfigureRequest();
static void handleMapRequest();
static void handlePropertyNotify();
static void handleEnterNotify();
static void handleLeaveNotify();
static void handleExtensions();
static void handleClientMessage();
static void handleKeyPress();
static void handleFocusIn();
static void handleMotionNotify();
static void handleVisibilityNotify();


#ifdef SHAPE
static void handleShapeNotify();
#endif

/* called from the signal handler */
void NotifyDeadProcess(pid_t pid, unsigned char status);

/* real dead process handler */
static void handleDeadProcess(void *foo);


typedef struct DeadProcesses {
    pid_t 		pid;
    unsigned char 	exit_status;
} DeadProcesses;

/* stack of dead processes */
static DeadProcesses deadProcesses[MAX_DEAD_PROCESSES];
static int deadProcessPtr=0;


typedef struct DeathHandler {
    WDeathHandler	*callback;
    pid_t 		pid;
    void 		*client_data;
} DeathHandler;

static WMArray *deathHandlers=NULL;



WMagicNumber
wAddDeathHandler(pid_t pid, WDeathHandler *callback, void *cdata)
{
    DeathHandler *handler;
    
    handler = malloc(sizeof(DeathHandler));
    if (!handler)
      return 0;

    handler->pid = pid;
    handler->callback = callback;
    handler->client_data = cdata;

    if (!deathHandlers)
	deathHandlers = WMCreateArrayWithDestructor(8, wfree);

    WMAddToArray(deathHandlers, handler);

    return handler;
}



void 
wDeleteDeathHandler(WMagicNumber id)
{
    DeathHandler *handler=(DeathHandler*)id;

    if (!handler || !deathHandlers)
      return;

    /* array destructor will call wfree(handler) */
    WMRemoveFromArray(deathHandlers, handler);
}


void
DispatchEvent(XEvent *event)
{
    if (deathHandlers)
	handleDeadProcess(NULL);

    if (WCHECK_STATE(WSTATE_NEED_EXIT)) {
	WCHANGE_STATE(WSTATE_EXITING);
	/* received SIGTERM */
	/* 
	 * WMHandleEvent() can't be called from anything
	 * executed inside here, or we can get in a infinite
	 * recursive loop.
	 */
	Shutdown(WSExitMode);

    } else if (WCHECK_STATE(WSTATE_NEED_RESTART)) {
	WCHANGE_STATE(WSTATE_RESTARTING);

	Shutdown(WSRestartPreparationMode);
	/* received SIGHUP */
	Restart(NULL, True);
    } else if (WCHECK_STATE(WSTATE_NEED_REREAD)) {
        WCHANGE_STATE(WSTATE_NORMAL);
        wDefaultsCheckDomains("bla");
    }

    /* for the case that all that is wanted to be dispatched is
     * the stuff above */
    if (!event)
	return;

    saveTimestamp(event);
    switch (event->type) {
     case MapRequest:
	handleMapRequest(event);
	break;

     case KeyPress:
	handleKeyPress(event);
	break;

     case MotionNotify:
         handleMotionNotify(event);
         break;

     case ConfigureRequest:
	handleConfigureRequest(event);
	break;

     case DestroyNotify:
	handleDestroyNotify(event);
	break;
	
     case MapNotify:
	handleMapNotify(event);
	break;
	
     case UnmapNotify:
	handleUnmapNotify(event);
	break;

     case ButtonPress:
	handleButtonPress(event);
	break;
	
     case Expose:
	handleExpose(event);
	break;
	
     case PropertyNotify:
	handlePropertyNotify(event);
	break;

     case EnterNotify:
	handleEnterNotify(event);
	break;

     case LeaveNotify:
        handleLeaveNotify(event);
	break;

     case ClientMessage:
	handleClientMessage(event);
	break;
	
     case ColormapNotify:
	handleColormapNotify(event);
	break;

     case MappingNotify:
	if (event->xmapping.request == MappingKeyboard 
	    || event->xmapping.request == MappingModifier)
	  XRefreshKeyboardMapping(&event->xmapping);
	break;
	
     case FocusIn:
	handleFocusIn(event);
	break;

     case VisibilityNotify:
	handleVisibilityNotify(event);
	break;
     default:
	handleExtensions(event);
	break;
    }
}


/*
 *----------------------------------------------------------------------
 * EventLoop-
 * 	Processes X and internal events indefinitely.
 * 
 * Returns:
 * 	Never returns
 * 
 * Side effects:
 * 	The LastTimestamp global variable is updated.
 *---------------------------------------------------------------------- 
 */
void
EventLoop()
{
    XEvent event;
   
    for(;;) {
	WMNextEvent(dpy, &event);
	WMHandleEvent(&event);
    }
}



Bool 
IsDoubleClick(WScreen *scr, XEvent *event)
{
    if ((scr->last_click_time>0) && 
	(event->xbutton.time-scr->last_click_time<=wPreferences.dblclick_time)
	&& (event->xbutton.button == scr->last_click_button)
	&& (event->xbutton.window == scr->last_click_window)) {

	scr->flags.next_click_is_not_double = 1;
	scr->last_click_time = 0;
	scr->last_click_window = event->xbutton.window;

	return True;
    }
    return False;
}


void
NotifyDeadProcess(pid_t pid, unsigned char status)
{
    if (deadProcessPtr>=MAX_DEAD_PROCESSES-1) {
	wwarning("stack overflow: too many dead processes");
	return;
    }
    /* stack the process to be handled later,
     * as this is called from the signal handler */
    deadProcesses[deadProcessPtr].pid = pid;
    deadProcesses[deadProcessPtr].exit_status = status;
    deadProcessPtr++;
}


static void
handleDeadProcess(void *foo)
{
    DeathHandler *tmp;
    int i;

    for (i=0; i<deadProcessPtr; i++) {
	wWindowDeleteSavedStatesForPID(deadProcesses[i].pid);
    }

    if (!deathHandlers) {
	deadProcessPtr=0;
	return;
    }
	
    /* get the pids on the queue and call handlers */
    while (deadProcessPtr>0) {
	deadProcessPtr--;

	for (i = WMGetArrayItemCount(deathHandlers)-1; i >= 0; i--) {
	    tmp = WMGetFromArray(deathHandlers, i);
	    if (!tmp)
		continue;

	    if (tmp->pid == deadProcesses[deadProcessPtr].pid) {
		(*tmp->callback)(tmp->pid, 
				 deadProcesses[deadProcessPtr].exit_status,
				 tmp->client_data);
		wDeleteDeathHandler(tmp);
	    }
	}
    }
}


static void
saveTimestamp(XEvent *event)
{
    LastTimestamp = CurrentTime;

    switch (event->type) {
     case ButtonRelease:
     case ButtonPress:
	LastTimestamp = event->xbutton.time;
	break;
     case KeyPress:
     case KeyRelease:
	LastTimestamp = event->xkey.time;
	break;
     case MotionNotify:
	LastTimestamp = event->xmotion.time;
	break;
     case PropertyNotify:
	LastTimestamp = event->xproperty.time;
	break;
     case EnterNotify:
     case LeaveNotify:
	LastTimestamp = event->xcrossing.time;
	break;
     case SelectionClear:
	LastTimestamp = event->xselectionclear.time;
	break;
     case SelectionRequest:
	LastTimestamp = event->xselectionrequest.time;
	break;
     case SelectionNotify:
	LastTimestamp = event->xselection.time;
#ifdef XDND
    wXDNDProcessSelection(event);
#endif
	break;
    }
}


static int
matchWindow(void *item, void *cdata)
{
    return (((WFakeGroupLeader*)item)->origLeader == (Window)cdata);
}


static void
handleExtensions(XEvent *event)
{
#ifdef KEEP_XKB_LOCK_STATUS   
    XkbEvent *xkbevent;
    xkbevent = (XkbEvent *)event;
#endif /*KEEP_XKB_LOCK_STATUS*/
#ifdef SHAPE
    if (wShapeSupported && event->type == (wShapeEventBase+ShapeNotify)) {
	handleShapeNotify(event);
    }
#endif	
#ifdef KEEP_XKB_LOCK_STATUS   
    if (wPreferences.modelock && (xkbevent->type == wXkbEventBase)){
        handleXkbIndicatorStateNotify(event);
    }
#endif /*KEEP_XKB_LOCK_STATUS*/
}


static void 
handleMapRequest(XEvent *ev)
{
    WWindow *wwin;
    WScreen *scr = NULL;
    Window window = ev->xmaprequest.window;

#ifdef DEBUG
    L("got map request for %x\n", (unsigned)window);
#endif
    if ((wwin = wWindowFor(window))) {
	if (wwin->flags.shaded) {
	    wUnshadeWindow(wwin);
	}
	/* deiconify window */
	if (wwin->flags.miniaturized) {
	    wDeiconifyWindow(wwin);
	} else if (wwin->flags.hidden) {
            WApplication *wapp = wApplicationOf(wwin->main_window);
            /* go to the last workspace that the user worked on the app */
            if (wapp) {
                wWorkspaceChange(wwin->screen_ptr, wapp->last_workspace);
            }
	    wUnhideApplication(wapp, False, False);
	}
	return;
    }

    scr = wScreenForRootWindow(ev->xmaprequest.parent);

    wwin = wManageWindow(scr, window);

    /* 
     * This is to let the Dock know that the application it launched
     * has already been mapped (eg: it has finished launching). 
     * It is not necessary for normally docked apps, but is needed for
     * apps that were forcedly docked (like with dockit).
     */
    if (scr->last_dock) {
	if (wwin && wwin->main_window!=None && wwin->main_window!=window)
	  wDockTrackWindowLaunch(scr->last_dock, wwin->main_window);
	else
	  wDockTrackWindowLaunch(scr->last_dock, window);
    }

    if (wwin) {
	wClientSetState(wwin, NormalState, None);
	if (wwin->flags.maximized) {
	    wMaximizeWindow(wwin, wwin->flags.maximized);
	}
	if (wwin->flags.shaded) {
	    wwin->flags.shaded = 0;
	    wwin->flags.skip_next_animation = 1;
	    wShadeWindow(wwin);
	}
	if (wwin->flags.miniaturized) {
	    wwin->flags.miniaturized = 0;
	    wwin->flags.skip_next_animation = 1;
	    wIconifyWindow(wwin);
	}
	if (wwin->flags.hidden) {
	    WApplication *wapp = wApplicationOf(wwin->main_window);

	    wwin->flags.hidden = 0;
	    wwin->flags.skip_next_animation = 1;
	    if (wapp) {
		wHideApplication(wapp);
	    }
	}
    }
}


static void
handleDestroyNotify(XEvent *event)
{
    WWindow *wwin;
    WApplication *app;
    Window window = event->xdestroywindow.window;
    WScreen *scr = wScreenForRootWindow(event->xdestroywindow.event);
    int index;

#ifdef DEBUG
    L("got destroy notify");
#endif
    wwin = wWindowFor(window);
    if (wwin) {
	wUnmanageWindow(wwin, False, True);
    }

    if (scr != NULL) {
        while ((index = WMFindInArray(scr->fakeGroupLeaders, matchWindow,
                                      (void*)window)) != WANotFound) {
            WFakeGroupLeader *fPtr;

            fPtr = WMGetFromArray(scr->fakeGroupLeaders, index);
            if (fPtr->retainCount > 0) {
                fPtr->retainCount--;
                if (fPtr->retainCount==0 && fPtr->leader!=None) {
                    XDestroyWindow(dpy, fPtr->leader);
                    fPtr->leader = None;
                    XFlush(dpy);
                }
            }
            fPtr->origLeader = None;
        }
    }

    app = wApplicationOf(window);
    if (app) {
	if (window == app->main_window) {
	    app->refcount = 0;
	    wwin = app->main_window_desc->screen_ptr->focused_window;
	    while (wwin) {
		if (wwin->main_window == window) {
		    wwin->main_window = None;
		}
		wwin = wwin->prev;
	    }
	}
	wApplicationDestroy(app);
    }
    
#ifdef KWM_HINTS
    wKWMCheckDestroy(&event->xdestroywindow);
#endif
}



static void 
handleExpose(XEvent *event)
{
    WObjDescriptor *desc;
    XEvent ev;

#ifdef DEBUG
    L("got expose");
#endif
    while (XCheckTypedWindowEvent(dpy, event->xexpose.window, Expose, &ev));

    if (XFindContext(dpy, event->xexpose.window, wWinContext, 
		     (XPointer *)&desc)==XCNOENT) {
	return;
    }
    
    if (desc->handle_expose) {
	(*desc->handle_expose)(desc, event);
    }
}

static void
executeButtonAction(WScreen *scr, XEvent *event, int action)
{
    switch(action) {
     case WA_SELECT_WINDOWS:
        wUnselectWindows(scr);
        wSelectWindows(scr, event);
        break;
     case WA_OPEN_APPMENU:
        OpenRootMenu(scr, event->xbutton.x_root, event->xbutton.y_root, False);
        /* ugly hack */
        if (scr->root_menu) {
            if (scr->root_menu->brother->flags.mapped)
                event->xbutton.window = scr->root_menu->brother->frame->core->window;
            else
                event->xbutton.window = scr->root_menu->frame->core->window;
        }
        break;
     case WA_OPEN_WINLISTMENU:
        OpenSwitchMenu(scr, event->xbutton.x_root, event->xbutton.y_root, False);
        if (scr->switch_menu) {
            if (scr->switch_menu->brother->flags.mapped)
                event->xbutton.window = scr->switch_menu->brother->frame->core->window;
            else
                event->xbutton.window = scr->switch_menu->frame->core->window;
        }
        break;
    default:
        break;
    }
}


/* bindable */
static void
handleButtonPress(XEvent *event)
{
    WObjDescriptor *desc;
    WScreen *scr;
    
#ifdef DEBUG
    L("got button press");
#endif
    scr = wScreenForRootWindow(event->xbutton.root);
    
#ifdef BALLOON_TEXT
    wBalloonHide(scr);
#endif

    
#ifndef LITE
    if (event->xbutton.window==scr->root_win) {	
        if (event->xbutton.button==Button1 &&
            wPreferences.mouse_button1!=WA_NONE) {
            executeButtonAction(scr, event, wPreferences.mouse_button1);
        } else if (event->xbutton.button==Button2 &&
                   wPreferences.mouse_button2!=WA_NONE) {
            executeButtonAction(scr, event, wPreferences.mouse_button2);
        } else if (event->xbutton.button==Button3 &&
                   wPreferences.mouse_button3!=WA_NONE) {
            executeButtonAction(scr, event, wPreferences.mouse_button3);
        } else if (event->xbutton.button==Button4 &&
                   wPreferences.mouse_wheel!=WA_NONE) {
	    wWorkspaceRelativeChange(scr, 1);
        } else if (event->xbutton.button==Button5 &&
                   wPreferences.mouse_wheel!=WA_NONE) {
            wWorkspaceRelativeChange(scr, -1);
        }
#ifdef GNOME_STUFF
	else if (wGNOMEProxyizeButtonEvent(scr, event))
	    return;
#endif
    }
#endif /* !LITE */

    desc = NULL;
    if (XFindContext(dpy, event->xbutton.subwindow, wWinContext,
		     (XPointer *)&desc)==XCNOENT) {
	if (XFindContext(dpy, event->xbutton.window, wWinContext,
			 (XPointer *)&desc)==XCNOENT) {
	    return;
	}
    }

    if (desc->parent_type == WCLASS_WINDOW) {
	XSync(dpy, 0);
    
	if (event->xbutton.state & MOD_MASK) {
	    XAllowEvents(dpy, AsyncPointer, CurrentTime);
	}
     
/*	if (wPreferences.focus_mode == WKF_CLICK) {*/
	    if (wPreferences.ignore_focus_click) {
		XAllowEvents(dpy, AsyncPointer, CurrentTime);
	    }
	    XAllowEvents(dpy, ReplayPointer, CurrentTime);
/*	}*/
	XSync(dpy, 0);
    } else if (desc->parent_type == WCLASS_APPICON
	       || desc->parent_type == WCLASS_MINIWINDOW
	       || desc->parent_type == WCLASS_DOCK_ICON) {
	if (event->xbutton.state & MOD_MASK) {
	    XSync(dpy, 0);
	    XAllowEvents(dpy, AsyncPointer, CurrentTime);
	    XSync(dpy, 0);
	}
    }

    if (desc->handle_mousedown!=NULL) {
	(*desc->handle_mousedown)(desc, event);
    }

    /* save double-click information */
    if (scr->flags.next_click_is_not_double) {
	scr->flags.next_click_is_not_double = 0;
    } else {
	scr->last_click_time = event->xbutton.time;
	scr->last_click_button = event->xbutton.button;
	scr->last_click_window = event->xbutton.window;
    }
}


static void
handleMapNotify(XEvent *event)
{
    WWindow *wwin;
#ifdef DEBUG
    L("got map");
#endif
    wwin = wWindowFor(event->xmap.event);
    if (wwin && wwin->client_win == event->xmap.event) {
	if (wwin->flags.miniaturized) {
	    wDeiconifyWindow(wwin);
	} else {
	    XGrabServer(dpy);
	    wWindowMap(wwin);
	    wClientSetState(wwin, NormalState, None);
	    XUngrabServer(dpy);
	}
    }
}


static void
handleUnmapNotify(XEvent *event)
{
    WWindow *wwin;
    XEvent ev;
    Bool withdraw = False;
#ifdef DEBUG
    L("got unmap");
#endif
    /* only process windows with StructureNotify selected 
     * (ignore SubstructureNotify) */
    wwin = wWindowFor(event->xunmap.window);
    if (!wwin)
	return;

    /* whether the event is a Withdrawal request */
    if (event->xunmap.event == wwin->screen_ptr->root_win
	&& event->xunmap.send_event)
	withdraw = True;

    if (wwin->client_win != event->xunmap.event && !withdraw)
	return;

    if (!wwin->flags.mapped && !withdraw
	&& wwin->frame->workspace == wwin->screen_ptr->current_workspace
	&& !wwin->flags.miniaturized && !wwin->flags.hidden)
	return;

    XGrabServer(dpy);
    XUnmapWindow(dpy, wwin->frame->core->window);
    wwin->flags.mapped = 0;
    XSync(dpy, 0);
    /* check if the window was destroyed */
    if (XCheckTypedWindowEvent(dpy, wwin->client_win, DestroyNotify,&ev)) {
	DispatchEvent(&ev);
    } else {
	Bool reparented = False;

	if (XCheckTypedWindowEvent(dpy, wwin->client_win, ReparentNotify, &ev))
	    reparented = True;

	/* withdraw window */
	wwin->flags.mapped = 0;
	if (!reparented)
	    wClientSetState(wwin, WithdrawnState, None);
	
	/* if the window was reparented, do not reparent it back to the
	 * root window */
	wUnmanageWindow(wwin, !reparented, False);
    }
    XUngrabServer(dpy);
}


static void
handleConfigureRequest(XEvent *event)
{
    WWindow *wwin;
#ifdef DEBUG
    L("got configure request");
#endif
    if (!(wwin=wWindowFor(event->xconfigurerequest.window))) {
	/*
	 * Configure request for unmapped window
	 */
	wClientConfigure(NULL, &(event->xconfigurerequest));
    } else {
	wClientConfigure(wwin, &(event->xconfigurerequest));
    }
}


static void
handlePropertyNotify(XEvent *event)
{
    WWindow *wwin;
    WApplication *wapp;
    Window jr;
    int ji;
    unsigned int ju;
    WScreen *scr;
#ifdef DEBUG
    L("got property notify");
#endif
    if ((wwin=wWindowFor(event->xproperty.window))) {
	if (!XGetGeometry(dpy, wwin->client_win, &jr, &ji, &ji,
			  &ju, &ju, &ju, &ju)) {
	    return;
	}
	wClientCheckProperty(wwin, &event->xproperty);
    }
    wapp = wApplicationOf(event->xproperty.window);
    if (wapp) {
	wClientCheckProperty(wapp->main_window_desc, &event->xproperty);
    }
    
    scr = wScreenForWindow(event->xproperty.window);
    if (scr && scr->root_win == event->xproperty.window) {
#ifdef KWM_HINTS
	wKWMCheckRootHintChange(scr, &event->xproperty);
#endif
    }
}


static void
handleClientMessage(XEvent *event)
{
    WWindow *wwin;
    WObjDescriptor *desc;
#ifdef DEBUG
    L("got client message");
#endif
    /* handle transition from Normal to Iconic state */
    if (event->xclient.message_type == _XA_WM_CHANGE_STATE
	&& event->xclient.format == 32 
	&& event->xclient.data.l[0] == IconicState) {
	
	wwin = wWindowFor(event->xclient.window);
	if (!wwin) return;
	if (!wwin->flags.miniaturized)
	    wIconifyWindow(wwin);
    } else if (event->xclient.message_type == _XA_WM_COLORMAP_NOTIFY
	       && event->xclient.format == 32) {
	WScreen *scr = wScreenSearchForRootWindow(event->xclient.window);

	if (!scr)
	    return;

	if (event->xclient.data.l[1] == 1) {   /* starting */
	    wColormapAllowClientInstallation(scr, True);
	} else {		       /* stopping */
	    wColormapAllowClientInstallation(scr, False);
	}
    } else if (event->xclient.message_type == _XA_WINDOWMAKER_COMMAND) {

	wDefaultsCheckDomains("bla");

    } else if (event->xclient.message_type == _XA_WINDOWMAKER_WM_FUNCTION) {
	WApplication *wapp;
	int done=0;
	wapp = wApplicationOf(event->xclient.window);
	if (wapp) {
	    switch (event->xclient.data.l[0]) {
	     case WMFHideOtherApplications:
		wHideOtherApplications(wapp->main_window_desc);
		done = 1;
		break;

	     case WMFHideApplication:
		wHideApplication(wapp);
		done = 1;
		break;
	    }
	}
	if (!done) {
	    wwin = wWindowFor(event->xclient.window);
	    if (wwin) {
		switch (event->xclient.data.l[0]) {
		 case WMFHideOtherApplications:
		    wHideOtherApplications(wwin);
		    break;

		 case WMFHideApplication:
		    wHideApplication(wApplicationOf(wwin->main_window));
		    break;
		}
	    }
	}
    } else if (event->xclient.message_type == _XA_GNUSTEP_WM_ATTR) {
	wwin = wWindowFor(event->xclient.window);
	if (!wwin) return;
	switch (event->xclient.data.l[0]) {
	 case GSWindowLevelAttr:
	   {
	     int level = (int)event->xclient.data.l[1];

	     if (WINDOW_LEVEL(wwin) != level) {
	       ChangeStackingLevel(wwin->frame->core, level);
	     }
	   }
	   break;
	}
    } else if (event->xclient.message_type == _XA_GNUSTEP_TITLEBAR_STATE) {
	wwin = wWindowFor(event->xclient.window);
	if (!wwin) return;
	switch (event->xclient.data.l[0]) {
	 case WMTitleBarNormal:
	    wFrameWindowChangeState(wwin->frame, WS_UNFOCUSED);
	    break;
	 case WMTitleBarMain:
	    wFrameWindowChangeState(wwin->frame, WS_PFOCUSED);
	    break;
	 case WMTitleBarKey:
	    wFrameWindowChangeState(wwin->frame, WS_FOCUSED);
	    break;
	}
#ifdef GNOME_STUFF
    } else if (wGNOMEProcessClientMessage(&event->xclient)) {
	/* do nothing */
#endif /* GNOME_STUFF */
#ifdef KWM_HINTS
    } else if (wKWMProcessClientMessage(&event->xclient)) {
	/* do nothing */
#endif /* KWM_HINTS */
#ifdef XDND
    } else if (wXDNDProcessClientMessage(&event->xclient)) {
	/* do nothing */
#endif /* XDND */
#ifdef OFFIX_DND
    } else if (event->xclient.message_type==_XA_DND_PROTOCOL) {
	WScreen *scr = wScreenForWindow(event->xclient.window);
        if (scr && wDockReceiveDNDDrop(scr,event))
	    goto redirect_message;
#endif /* OFFIX_DND */
    } else {
#ifdef OFFIX_DND
     redirect_message:
#endif
	/*
	 * Non-standard thing, but needed by OffiX DND.
	 * For when the icon frame gets a ClientMessage
	 * that should have gone to the icon_window.
	 */
	if (XFindContext(dpy, event->xbutton.window, wWinContext, 
			 (XPointer *)&desc)!=XCNOENT) {
	    struct WIcon *icon=NULL;

	    if (desc->parent_type == WCLASS_MINIWINDOW) {
		icon = (WIcon*)desc->parent;
	    } else if (desc->parent_type == WCLASS_DOCK_ICON
		     || desc->parent_type == WCLASS_APPICON) {
		icon = ((WAppIcon*)desc->parent)->icon;
	    }
	    if (icon && (wwin=icon->owner)) {
		if (wwin->client_win!=event->xclient.window) {
		    event->xclient.window = wwin->client_win;
		    XSendEvent(dpy, wwin->client_win, False, NoEventMask, 
			       event);
		}
	    }
	}
    }
}


static void
raiseWindow(WScreen *scr)
{
    WWindow *wwin;

    scr->autoRaiseTimer = NULL;

    wwin = wWindowFor(scr->autoRaiseWindow);    
    if (!wwin)
	return;

    if (!wwin->flags.destroyed && wwin->flags.focused) {
	wRaiseFrame(wwin->frame->core);
	/* this is needed or a race condition will occur */
	XSync(dpy, False);
    }
}


static void 
handleEnterNotify(XEvent *event)
{
    WWindow *wwin;
    WObjDescriptor *desc = NULL;
    XEvent ev;
    WScreen *scr = wScreenForRootWindow(event->xcrossing.root);
#ifdef DEBUG
    L("got enter notify");
#endif

#ifdef VIRTUAL_DESKTOP
    /* TODO: acceleration code */
    if (wPreferences.vedge_thickness) {
        int x,y;
        if (event->xcrossing.window == scr->virtual_edge_r) {
            XWarpPointer(dpy, None, scr->root_win, 0,0,0,0, scr->scr_width - wPreferences.vedge_thickness - 1, event->xcrossing.y_root);
            wWorkspaceGetViewPosition(scr, scr->current_workspace, &x, &y);
            wWorkspaceSetViewPort(scr, scr->current_workspace, x + VIRTUALEDGE_SCROLL_HSTEP, y);
        } else if (event->xcrossing.window == scr->virtual_edge_l) {
            XWarpPointer(dpy, None, scr->root_win, 0,0,0,0, wPreferences.vedge_thickness + 1, event->xcrossing.y_root);
            wWorkspaceGetViewPosition(scr, scr->current_workspace, &x, &y);
            wWorkspaceSetViewPort(scr, scr->current_workspace, x - VIRTUALEDGE_SCROLL_HSTEP, y);
        } else if (event->xcrossing.window == scr->virtual_edge_u) {
            XWarpPointer(dpy, None, scr->root_win, 0,0,0,0, event->xcrossing.x_root, wPreferences.vedge_thickness + 1);
            wWorkspaceGetViewPosition(scr, scr->current_workspace, &x, &y);
            wWorkspaceSetViewPort(scr, scr->current_workspace, x, y - VIRTUALEDGE_SCROLL_VSTEP);
        } else if (event->xcrossing.window == scr->virtual_edge_d) {
            printf("enter bottom\n");
            XWarpPointer(dpy, None, scr->root_win, 0,0,0,0, event->xcrossing.x_root, scr->scr_height - wPreferences.vedge_thickness - 1);
            wWorkspaceGetViewPosition(scr, scr->current_workspace, &x, &y);
            wWorkspaceSetViewPort(scr, scr->current_workspace, x, y + VIRTUALEDGE_SCROLL_VSTEP);
        }
    }
#endif

    if (XCheckTypedWindowEvent(dpy, event->xcrossing.window, LeaveNotify,
			       &ev)) {
	/* already left the window... */
	saveTimestamp(&ev);
	if (ev.xcrossing.mode==event->xcrossing.mode
	    && ev.xcrossing.detail==event->xcrossing.detail) {
	    return;
	}
    }

    if (XFindContext(dpy, event->xcrossing.window, wWinContext,
                     (XPointer *)&desc)!=XCNOENT) {
        if(desc->handle_enternotify)
            (*desc->handle_enternotify)(desc, event);
    }

    /* enter to window */
    wwin = wWindowFor(event->xcrossing.window);
    if (!wwin) {
	if (wPreferences.colormap_mode==WCM_POINTER) {
	    wColormapInstallForWindow(scr, NULL);
	}
	if (scr->autoRaiseTimer 
	    && event->xcrossing.root==event->xcrossing.window) {
	    WMDeleteTimerHandler(scr->autoRaiseTimer);
	    scr->autoRaiseTimer = NULL;
	}
    } else {
	/* set auto raise timer even if in focus-follows-mouse mode
	 * and the event is for the frame window, even if the window
	 * has focus already.  useful if you move the pointer from a focused
	 * window to the root window and back pretty fast 
	 *
	 * set focus if in focus-follows-mouse mode and the event
	 * is for the frame window and window doesn't have focus yet */
	if (wPreferences.focus_mode==WKF_SLOPPY
	    && wwin->frame->core->window==event->xcrossing.window
	    && !scr->flags.doing_alt_tab) {
	    
	    if (!wwin->flags.focused && !WFLAGP(wwin, no_focusable))
		wSetFocusTo(scr, wwin);
	    
	    if (scr->autoRaiseTimer)
		WMDeleteTimerHandler(scr->autoRaiseTimer);
	    scr->autoRaiseTimer = NULL;
	    
	    if (wPreferences.raise_delay && !WFLAGP(wwin, no_focusable)) {
		scr->autoRaiseWindow = wwin->frame->core->window;
		scr->autoRaiseTimer
		    = WMAddTimerHandler(wPreferences.raise_delay,
					(WMCallback*)raiseWindow, scr);
	    }
	}
	/* Install colormap for window, if the colormap installation mode
	 * is colormap_follows_mouse */
	if (wPreferences.colormap_mode==WCM_POINTER) {
	    if (wwin->client_win==event->xcrossing.window)
		wColormapInstallForWindow(scr, wwin);
	    else
		wColormapInstallForWindow(scr, NULL);
	}
    }
    
    /* a little kluge to hide the clip balloon */
    if (!wPreferences.flags.noclip && scr->flags.clip_balloon_mapped) {
	if (!desc) {
	    XUnmapWindow(dpy, scr->clip_balloon);
	    scr->flags.clip_balloon_mapped = 0;
	} else {
	    if (desc->parent_type!=WCLASS_DOCK_ICON
		|| scr->clip_icon != desc->parent) {
		XUnmapWindow(dpy, scr->clip_balloon);
		scr->flags.clip_balloon_mapped = 0;
	    }
	}
    }

    if (event->xcrossing.window == event->xcrossing.root
	&& event->xcrossing.detail == NotifyNormal
	&& event->xcrossing.detail != NotifyInferior
	&& wPreferences.focus_mode != WKF_CLICK) {

	wSetFocusTo(scr, scr->focused_window);
    }

#ifdef BALLOON_TEXT
    wBalloonEnteredObject(scr, desc);
#endif
}


static void
handleLeaveNotify(XEvent *event)
{
    WObjDescriptor *desc = NULL;

    if (XFindContext(dpy, event->xcrossing.window, wWinContext, 
		     (XPointer *)&desc)!=XCNOENT) {
        if(desc->handle_leavenotify)
	    (*desc->handle_leavenotify)(desc, event);
    }
    if (event->xcrossing.window == event->xcrossing.root
	&& event->xcrossing.mode == NotifyNormal
	&& event->xcrossing.detail != NotifyInferior
	&& wPreferences.focus_mode != WKF_CLICK) {

	WScreen *scr = wScreenForRootWindow(event->xcrossing.root);

	wSetFocusTo(scr, NULL);
    }
}


#ifdef SHAPE
static void
handleShapeNotify(XEvent *event)
{
    XShapeEvent *shev = (XShapeEvent*)event;
    WWindow *wwin;
    XEvent ev;
#ifdef DEBUG
    L("got shape notify");
#endif
    while (XCheckTypedWindowEvent(dpy, shev->window, event->type, &ev)) {
	XShapeEvent *sev = (XShapeEvent*)&ev;

	if (sev->kind == ShapeBounding) {
	    if (sev->shaped == shev->shaped) {
		*shev = *sev;
	    } else {
		XPutBackEvent(dpy, &ev);
		break;
	    }
	}
    }

    wwin = wWindowFor(shev->window);
    if (!wwin || shev->kind != ShapeBounding)
      return;

    if (!shev->shaped && wwin->flags.shaped) {

	wwin->flags.shaped = 0;
	wWindowClearShape(wwin);

    } else if (shev->shaped) {

	wwin->flags.shaped = 1;
	wWindowSetShape(wwin);
    }
}
#endif /* SHAPE */

#ifdef KEEP_XKB_LOCK_STATUS   
/* please help ]d if you know what to do */
handleXkbIndicatorStateNotify(XEvent *event)
{
    WWindow *wwin;
    WScreen *scr;
    XkbStateRec staterec;
    int i;

    for (i=0; i<wScreenCount; i++) {
        scr = wScreenWithNumber(i);
	wwin = scr->focused_window;
	if (wwin && wwin->flags.focused) {
        XkbGetState(dpy,XkbUseCoreKbd,&staterec);
        if (wwin->frame->languagemode != staterec.group) {
            wwin->frame->last_languagemode = wwin->frame->languagemode;
            wwin->frame->languagemode = staterec.group;
        }
#ifdef XKB_BUTTON_HINT
        if (wwin->frame->titlebar) {
            wFrameWindowPaint(wwin->frame);
        }
#endif
	}
    }
}
#endif /*KEEP_XKB_LOCK_STATUS*/

static void 
handleColormapNotify(XEvent *event)
{
    WWindow *wwin;
    WScreen *scr;
    Bool reinstall = False;
    
    wwin = wWindowFor(event->xcolormap.window);
    if (!wwin)
	return;
    
    scr = wwin->screen_ptr;

    do {
	if (wwin) {
	    if (event->xcolormap.new) {
		XWindowAttributes attr;
		
		XGetWindowAttributes(dpy, wwin->client_win, &attr);

		if (wwin == scr->cmap_window && wwin->cmap_window_no == 0)
		    scr->current_colormap = attr.colormap;

		reinstall = True;
	    } else if (event->xcolormap.state == ColormapUninstalled &&
		       scr->current_colormap == event->xcolormap.colormap) {

		/* some bastard app (like XV) removed our colormap */
		/* 
		 * can't enforce or things like xscreensaver wont work
		 * reinstall = True;
		 */
	    } else if (event->xcolormap.state == ColormapInstalled &&
		       scr->current_colormap == event->xcolormap.colormap) {

		/* someone has put our colormap back */
		reinstall = False;
	    }
	}
    } while (XCheckTypedEvent(dpy, ColormapNotify, event)
	     && ((wwin = wWindowFor(event->xcolormap.window)) || 1));

    if (reinstall && scr->current_colormap!=None) {
	if (!scr->flags.colormap_stuff_blocked)
	    XInstallColormap(dpy, scr->current_colormap);
    }
}



static void
handleFocusIn(XEvent *event)
{
    WWindow *wwin;

    /*
     * For applications that like stealing the focus.
     */
    while (XCheckTypedEvent(dpy, FocusIn, event));    
    saveTimestamp(event);
    if (event->xfocus.mode == NotifyUngrab
	|| event->xfocus.mode == NotifyGrab
	|| event->xfocus.detail > NotifyNonlinearVirtual) {
	return;
    }

    wwin = wWindowFor(event->xfocus.window);
    if (wwin && !wwin->flags.focused) {
	if (wwin->flags.mapped)
	    wSetFocusTo(wwin->screen_ptr, wwin);
	else
	    wSetFocusTo(wwin->screen_ptr, NULL);
    } else if (!wwin) {
	WScreen *scr = wScreenForWindow(event->xfocus.window);
	if (scr)
	    wSetFocusTo(scr, NULL);
    }
}


static WWindow*
windowUnderPointer(WScreen *scr)
{
    unsigned int mask;
    int foo;
    Window bar, win;
    
    if (XQueryPointer(dpy, scr->root_win, &bar, &win, &foo, &foo, &foo, &foo,
		      &mask)) 
	return wWindowFor(win);
    return NULL;
}




static void
handleKeyPress(XEvent *event)
{
    WScreen *scr = wScreenForRootWindow(event->xkey.root);
    WWindow *wwin = scr->focused_window;
    int i;
    int modifiers;
    int command=-1, index;
#ifdef KEEP_XKB_LOCK_STATUS   
    XkbStateRec staterec;     
#endif /*KEEP_XKB_LOCK_STATUS*/

    /* ignore CapsLock */
    modifiers = event->xkey.state & ValidModMask;

    for (i=0; i<WKBD_LAST; i++) {
	if (wKeyBindings[i].keycode==0) 
	    continue;
	
	if (wKeyBindings[i].keycode==event->xkey.keycode
	    && (/*wKeyBindings[i].modifier==0 
		||*/ wKeyBindings[i].modifier==modifiers)) {
	    command = i;
	    break;
	}
    }


    if (command < 0) {
#ifdef LITE
	{ 
#if 0
	}
#endif
#else
	if (!wRootMenuPerformShortcut(event)) {
#endif
	    static int dontLoop = 0;
	    
	    if (dontLoop > 10) {
		wwarning("problem with key event processing code");
		return;
	    }
	    dontLoop++;
	    /* if the focused window is an internal window, try redispatching
	     * the event to the managed window, as it can be a WINGs window */
	    if (wwin && wwin->flags.internal_window
		&& wwin->client_leader!=None) {
		/* client_leader contains the WINGs toplevel */
		event->xany.window = wwin->client_leader;
		WMHandleEvent(event);
	    }
	    dontLoop--;
	}
	return;
    }

#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
#define ISFOCUSED(w) ((w) && (w)->flags.focused)

    switch (command) {
#ifndef LITE
     case WKBD_ROOTMENU:
	/*OpenRootMenu(scr, event->xkey.x_root, event->xkey.y_root, True);*/
        OpenRootMenu(scr, scr->scr_width/2, scr->scr_height/2, True);
        break;
     case WKBD_WINDOWLIST:
        OpenSwitchMenu(scr, scr->scr_width/2, scr->scr_height/2, True);
        break;
#endif /* !LITE */
     case WKBD_WINDOWMENU:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin))
	    OpenWindowMenu(wwin, wwin->frame_x, 
			   wwin->frame_y+wwin->frame->top_width, True);
	break;
     case WKBD_MINIATURIZE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) 
	    && !WFLAGP(wwin, no_miniaturizable)) {
	    CloseWindowMenu(scr);
	    
	    if (wwin->protocols.MINIATURIZE_WINDOW)
	      	wClientSendProtocol(wwin, _XA_GNUSTEP_WM_MINIATURIZE_WINDOW,
				    event->xbutton.time);
	    else {
		wIconifyWindow(wwin);
	    }
	}
	break;
     case WKBD_HIDE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    WApplication *wapp = wApplicationOf(wwin->main_window);
	    CloseWindowMenu(scr);
	    
	    if (wapp && !WFLAGP(wapp->main_window_desc, no_appicon)) {
		wHideApplication(wapp);
	    }
	}
	break;
     case WKBD_HIDE_OTHERS:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    CloseWindowMenu(scr);
	    
            wHideOtherApplications(wwin);
	}
	break;
     case WKBD_MAXIMIZE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_resizable)) {
	    CloseWindowMenu(scr);
	    
	    if (wwin->flags.maximized) {
		wUnmaximizeWindow(wwin);
	    } else {
		wMaximizeWindow(wwin, MAX_VERTICAL|MAX_HORIZONTAL);
	    }
	}
	break;
     case WKBD_VMAXIMIZE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_resizable)) {
	    CloseWindowMenu(scr);
	    
	    if (wwin->flags.maximized) {
		wUnmaximizeWindow(wwin);
	    } else {
		wMaximizeWindow(wwin, MAX_VERTICAL);
	    }
	}
	break;
     case WKBD_HMAXIMIZE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_resizable)) {
	    CloseWindowMenu(scr);
	    
	    if (wwin->flags.maximized) {
		wUnmaximizeWindow(wwin);
	    } else {
		wMaximizeWindow(wwin, MAX_HORIZONTAL);
	    }
	}
	break;
     case WKBD_RAISE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    CloseWindowMenu(scr);
	    
	    wRaiseFrame(wwin->frame->core);
	}
	break;
     case WKBD_LOWER:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    CloseWindowMenu(scr);

	    wLowerFrame(wwin->frame->core);
	}
	break;
     case WKBD_RAISELOWER:
	/* raise or lower the window under the pointer, not the
	 * focused one 
	 */
	wwin = windowUnderPointer(scr);
	if (wwin)
	    wRaiseLowerFrame(wwin->frame->core);
	break;
     case WKBD_SHADE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_shadeable)) {
	    if (wwin->flags.shaded)
		wUnshadeWindow(wwin);
	    else
		wShadeWindow(wwin);
	}
	break;
     case WKBD_MOVERESIZE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    CloseWindowMenu(scr);
	    
	    wKeyboardMoveResizeWindow(wwin);
	}
	break;
     case WKBD_CLOSE:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_closable)) {
	    CloseWindowMenu(scr);
	    if (wwin->protocols.DELETE_WINDOW)
	      wClientSendProtocol(wwin, _XA_WM_DELETE_WINDOW,
				  event->xkey.time);
	}
	break;
    case WKBD_SELECT:
	if (ISMAPPED(wwin) && ISFOCUSED(wwin)) {
	    wSelectWindow(wwin, !wwin->flags.selected);
	}
        break;
     case WKBD_FOCUSNEXT:
	StartWindozeCycle(wwin, event, True);
	break;

     case WKBD_FOCUSPREV:
	StartWindozeCycle(wwin, event, False);
	break;

#if (defined(__STDC__) && !defined(UNIXCPP)) || defined(ANSICPP)
#define GOTOWORKS(wk)	case WKBD_WORKSPACE##wk:\
			i = (scr->current_workspace/10)*10 + wk - 1;\
			if (wPreferences.ws_advance || i<scr->workspace_count)\
			    wWorkspaceChange(scr, i);\
			break
#else
#define GOTOWORKS(wk)	case WKBD_WORKSPACE/**/wk:\
			i = (scr->current_workspace/10)*10 + wk - 1;\
			if (wPreferences.ws_advance || i<scr->workspace_count)\
			    wWorkspaceChange(scr, i);\
			break
#endif
    GOTOWORKS(1);
    GOTOWORKS(2);
    GOTOWORKS(3);
    GOTOWORKS(4);
    GOTOWORKS(5);
    GOTOWORKS(6);
    GOTOWORKS(7);
    GOTOWORKS(8);
    GOTOWORKS(9);
    GOTOWORKS(10);
#undef GOTOWORKS
     case WKBD_NEXTWORKSPACE:
	wWorkspaceRelativeChange(scr, 1);
	break;
     case WKBD_PREVWORKSPACE:
	wWorkspaceRelativeChange(scr, -1);
	break;
     case WKBD_WINDOW1:
     case WKBD_WINDOW2:
     case WKBD_WINDOW3:
     case WKBD_WINDOW4:
     case WKBD_WINDOW5:
     case WKBD_WINDOW6:
     case WKBD_WINDOW7:
     case WKBD_WINDOW8:
     case WKBD_WINDOW9:
     case WKBD_WINDOW10:

	index = command-WKBD_WINDOW1;
	
        if (scr->shortcutWindows[index]) {
            WMArray *list = scr->shortcutWindows[index];
            int cw;
	    int count = WMGetArrayItemCount(list);
	    WWindow *twin;
	    WMArrayIterator iter;
	    WWindow *wwin;

            wUnselectWindows(scr);
            cw = scr->current_workspace;

            WM_ETARETI_ARRAY(list, wwin, iter) {
		if (count > 1)
		    wWindowChangeWorkspace(wwin, cw);

                wMakeWindowVisible(wwin);

		if (count > 1)
		    wSelectWindow(wwin, True);
            }
	    
	    /* rotate the order of windows, to create a cycling effect */
	    twin = WMGetFromArray(list, 0);
	    WMDeleteFromArray(list, 0);
	    WMAddToArray(list, twin);

        } else if (wwin && ISMAPPED(wwin) && ISFOCUSED(wwin)) {
            if (scr->shortcutWindows[index]) {
                WMFreeArray(scr->shortcutWindows[index]);
                scr->shortcutWindows[index] = NULL;
            }

            if (wwin->flags.selected && scr->selected_windows) {
                scr->shortcutWindows[index] =
                    WMDuplicateArray(scr->selected_windows);
                /*WMRemoveFromArray(scr->shortcutWindows[index], wwin);
                WMInsertInArray(scr->shortcutWindows[index], 0, wwin);*/
            } else {
                scr->shortcutWindows[index] = WMCreateArray(4);
                WMAddToArray(scr->shortcutWindows[index], wwin);
            }

            wSelectWindow(wwin, !wwin->flags.selected);
            XFlush(dpy);
            wusleep(3000);
            wSelectWindow(wwin, !wwin->flags.selected);
            XFlush(dpy);

        } else if (scr->selected_windows 
		   && WMGetArrayItemCount(scr->selected_windows)) {

            if (wwin->flags.selected && scr->selected_windows) {
                if (scr->shortcutWindows[index]) {
                    WMFreeArray(scr->shortcutWindows[index]);
                }
                scr->shortcutWindows[index] =
                    WMDuplicateArray(scr->selected_windows);
            }
        }

        break;
	
     case WKBD_SWITCH_SCREEN:
	if (wScreenCount > 1) {
	    WScreen *scr2;
	    int i;
	    
	    /* find index of this screen */
	    for (i = 0; i < wScreenCount; i++) {
		if (wScreenWithNumber(i) == scr)
		    break;
	    }
	    i++;
	    if (i >= wScreenCount) {
		i = 0;
	    }
	    scr2 = wScreenWithNumber(i);
	    
	    if (scr2) {
		XWarpPointer(dpy, scr->root_win, scr2->root_win, 0, 0, 0, 0,
			     scr2->scr_width/2, scr2->scr_height/2);
	    }
	}
	break;
	
     case WKBD_NEXTWSLAYER:
     case WKBD_PREVWSLAYER:
	{
	    int row, column;
	    
	    row = scr->current_workspace/10;
	    column = scr->current_workspace%10;
	    
	    if (command==WKBD_NEXTWSLAYER) {
		if ((row+1)*10 < scr->workspace_count)
		    wWorkspaceChange(scr, column+(row+1)*10);
	    } else {
		if (row > 0)
		    wWorkspaceChange(scr, column+(row-1)*10);
	    }
	}
	break;
    case WKBD_CLIPLOWER:
        if (!wPreferences.flags.noclip)
            wDockLower(scr->workspaces[scr->current_workspace]->clip);
        break;
     case WKBD_CLIPRAISE:
        if (!wPreferences.flags.noclip)
            wDockRaise(scr->workspaces[scr->current_workspace]->clip);
        break;
     case WKBD_CLIPRAISELOWER:
        if (!wPreferences.flags.noclip)
            wDockRaiseLower(scr->workspaces[scr->current_workspace]->clip);
        break;
#ifdef KEEP_XKB_LOCK_STATUS
     case WKBD_TOGGLE:
        if(wPreferences.modelock) {
            /*toggle*/
            wwin = scr->focused_window;

            if (wwin && wwin->flags.mapped
                && wwin->frame->workspace == wwin->screen_ptr->current_workspace
                && !wwin->flags.miniaturized && !wwin->flags.hidden) {
                XkbGetState(dpy,XkbUseCoreKbd,&staterec);

                wwin->frame->languagemode = wwin->frame->last_languagemode;
                wwin->frame->last_languagemode = staterec.group;
                XkbLockGroup(dpy,XkbUseCoreKbd, wwin->frame->languagemode);
                                 
            }
        }
        break;
#endif /* KEEP_XKB_LOCK_STATUS */

    }
}


static void 
handleMotionNotify(XEvent *event)
{
    WMenu *menu;
    WScreen *scr = wScreenForRootWindow(event->xmotion.root);

    if (wPreferences.scrollable_menus) {
        if (scr->flags.jump_back_pending ||
            event->xmotion.x_root <= 1 ||
            event->xmotion.x_root >= (scr->scr_width - 2) ||
            event->xmotion.y_root <= 1 ||
            event->xmotion.y_root >= (scr->scr_height - 2)) {
#ifdef DEBUG
            L("pointer at screen edge");
#endif
            menu = wMenuUnderPointer(scr);
            if (menu!=NULL)
                wMenuScroll(menu, event);
        }
    }
#if 0
    if (event->xmotion.subwindow == None)
	return;

    if (scr->scrolledFMaximize != None) {
	    WWindow *twin;

	    twin = wWindowFor(scr->scrolledFMaximize);
	    if (twin && twin->frame_y ==) {
		
		
	    }
	    scr->scrolledFMaximize = NULL;
	    
	} else {

    /* scroll full maximized window */
    if (event->xmotion.y_root < 1
	|| event->xmotion.y_root > scr->scr_height - 1) {

	wwin = wWindowFor(event->xmotion.subwindow);

	if (wwin && (wwin->flags.maximized & MAX_VERTICAL)
	    && WFLAGP(wwin, full_maximize) 
	    && event->xmotion.x_root >= wwin->frame_x
	    && event->xmotion.x_root <= wwin->frame_x + wwin->frame->core->width) {

	    if (!WFLAGP(wwin, no_titlebar)
		&& wwin->frame_y <= - wwin->frame->top_width) {

		wWindowMove(wwin, wwin->frame_x, 0);
		wwin->flags.dragged_while_fmaximized = 0;

	    } else if (!WFLAGP(wwin, no_resizebar)
		       && wwin->frame_y + wwin->frame->core->height >=
		       scr->scr_height + wwin->frame->bottom_width) {
		
		int y = scr->scr_height + wwin->frame->bottom_width;

		y = scr->scr_height - wwin->frame_y - wwin->frame->core->height;

		wWindowMove(wwin, wwin->frame_x, y);
		wwin->flags.dragged_while_fmaximized = 0;
	    }
	}
    }
#endif
}


static void
handleVisibilityNotify(XEvent *event)
{
    WWindow *wwin;

    wwin = wWindowFor(event->xvisibility.window);
    if (!wwin)
	return;
    wwin->flags.obscured = 
	(event->xvisibility.state == VisibilityFullyObscured);
}
