/* event.c- event loop and handling
 * 
 *  WindowMaker window manager
 * 
 *  Copyright (c) 1997 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include "wconfig.h"

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

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

#include "generic/wwmlib.h"
#include "WindowMaker.h"
#include "window.h"
#include "actions.h"
#include "client.h"
#include "icon.h"
#include "funcs.h"
#include "dialog.h"
#include "keybind.h"
#include "application.h"
#include "stacking.h"
#include "appicon.h"
#include "dock.h"

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

extern Cursor wCursor[WCUR_LAST];

extern WShortKey *wKeyBindings[WKBD_LAST];

extern WScreen wScreen;

extern Time LastTimestamp;

extern WPreferences wPreferences;

extern Atom _XA_WM_CHANGE_STATE;
extern Atom _XA_WM_DELETE_WINDOW;
extern Atom _XA_GNUSTEP_WM_MINIATURIZE_WINDOW;
extern Atom _XA_GNUSTEP_WM_FUNCTION;

#ifdef SHAPE
extern int ShapeEventBase;
#endif

/************ 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 handleExtensions();
static void handleClientMessage();
static void handleKeyPress();
#ifdef SHAPE
static void handleShapeNotify();
#endif

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

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

/* stack of dead processes */
static int corpseStackPtr=0;
static pid_t corpseStack[MAX_DEAD_PROCESSES];
static unsigned char corpseStatStack[MAX_DEAD_PROCESSES];
#endif



typedef struct TimerHandler {
    WCallBack	*callback;	       /* procedure to call */
    int countdown;		       /* when this counter (ms) reaches 0 */
    struct TimerHandler *next;
    void *client_data;
} TimerHandler;

/* queue of timer event handlers */
static TimerHandler *timerHandler=NULL;

/* how many milliseconds since last time timerhandlers were checked */
static int timerTicks=0;




#define TimerPending(ticks)	(timerHandler \
				&& (timerHandler->countdown <= (ticks)))

WMagicNumber 
wAddTimerHandler(int ms, WCallBack *callback, void *cdata)
{
    TimerHandler *handler, *tmp;
    
    handler = malloc(sizeof(TimerHandler));
    if (!handler)
      return 0;
    
    handler->countdown = ms;
    handler->callback = callback;
    handler->client_data = cdata;
    /* insert callback in queue, sorted by time left */
    if (!timerHandler || timerHandler->countdown >= ms) {
	handler->next = timerHandler;
	timerHandler = handler;
    } else {
	tmp = timerHandler;
	while (tmp->next && tmp->next->countdown <= ms) {
	    tmp = tmp->next;
	}
	handler->next = tmp->next;
	tmp->next = handler;
    }
    return handler;
}


void 
wDeleteTimerHandler(WMagicNumber timer_id)
{
    TimerHandler *tmp, *handler=(TimerHandler*)timer_id;

    if (!handler || !timerHandler) 
      return;

    tmp = timerHandler;
    if (tmp==handler) {
	timerHandler = handler->next;
	wfree(handler);
    } else {
	while (tmp->next) {
	    if (tmp->next==handler) {
		tmp->next=handler->next;
		wfree(handler);
		break;
	    }
	    tmp = tmp->next;
	}
    }
}

static void
checkTimerHandlers()
{
    TimerHandler *handler, *next;
     
    handler = timerHandler;
    while (handler) {
	handler->countdown-= timerTicks;
	next = handler->next;
	if (handler->countdown <= 0) {
	    (*handler->callback)(handler->client_data);
	    wDeleteTimerHandler(handler);
	} 
	handler = next;
    }
    timerTicks = 0;
}

void
wNextEvent(XEvent *event)
{
    static int last_time = 0;
    int t;
    
    t = time(NULL);
    if (last_time && t - last_time>0) {
	timerTicks += (t-last_time)*1000;
	last_time = t;
    }
    if (TimerPending(timerTicks)) {
	checkTimerHandlers();
    }
    XFlush(dpy);
    if (timerHandler) {
	while (XPending(dpy)==0) {
	    wmsleep(1000);
	    timerTicks++;
	    if (TimerPending(timerTicks)) {
		checkTimerHandlers();
	    }
	}
    }
    XNextEvent(dpy, event);
}


void
DispatchEvent(XEvent *event)
{
#ifdef DOCK
    /* handle dead processes */
    handleDeadProcess();
#endif
    saveTimestamp(event);    
    switch (event->type) {
     case MapRequest:
	handleMapRequest(event->xmaprequest.window);
	break;

     case KeyPress:
	handleKeyPress(event);
	break;

     case ConfigureRequest:
	handleConfigureRequest(event);
	break;
	
     case DestroyNotify:
	handleDestroyNotify(event->xdestroywindow.window);
	break;
	
     case MapNotify:
	handleMapNotify(event->xmap.window);
	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 ClientMessage:
	handleClientMessage(event);
	break;
	
     case ColormapNotify:
	handleColormapNotify(event);
	break;

     default:
	handleExtensions(event);
    }
}


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



#ifdef DOCK
void
NotifyDeadProcess(pid_t pid, unsigned char status)
{
    if (corpseStackPtr>=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 */
    corpseStack[corpseStackPtr] = pid;
    corpseStatStack[corpseStackPtr] = status;
    corpseStackPtr++;
}

static void
handleDeadProcess()
{
    if (wScreen.dock) {
	/* get the pids on the queue and call handlers */
	while (corpseStackPtr>0) {
	    corpseStackPtr--;
	    wDockTrackDeadProcess(wScreen.dock, corpseStack[corpseStackPtr],
				  corpseStatStack[corpseStackPtr]);
	}
    }
}
#endif


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;
	break;
    }
}


static void
handleExtensions(XEvent *event)
{
#ifdef SHAPE
    if (event->type == (ShapeEventBase+ShapeNotify)) {
	handleShapeNotify(event);
    }
#endif	
}

static void 
handleMapRequest(Window window)
{
    WWindow *wwin;
    
#ifdef DEBUG
    puts("got map request for");
#endif

    if ((wwin=wWindowFor(window))) {
	/* deiconify window */
	if (wwin->flags.shaded) {
	    wUnshadeWindow(wwin);
	} else if (wwin->flags.miniaturized) {
	    wDeiconifyWindow(wwin);
	} else if (wwin->flags.hidden) {
	    wUnhideApplication(wApplicationOf(wwin->main_window));
	}
	return;
    }

    wwin = wManageWindow(&wScreen, window);
#ifdef DOCK
    if (wScreen.dock) {
	if (wwin && wwin->main_window!=None && wwin->main_window!=window)
	  wDockTrackWindowLaunch(wScreen.dock, wwin->main_window);
	else
	  wDockTrackWindowLaunch(wScreen.dock, window);
    }
#endif
    if (wwin) {
	int state;
#if 0
	if (wwin->window_flags.start_withdrawn) {
	    wwin->window_flags.start_withdrawn = 0;
	    state = WithdrawnState;
	} else
#endif
	{
	    if (wwin->wm_hints && (wwin->wm_hints->flags & StateHint))
	      state = wwin->wm_hints->initial_state;
	    else
	      state = NormalState;
	}
	switch (state) {	    
	 case WithdrawnState:
	    wwin->flags.mapped = 0;
	    wClientSetState(wwin, WithdrawnState, None);
	    XSelectInput(dpy, wwin->client_win, NoEventMask);
	    XRemoveFromSaveSet(dpy, wwin->client_win);
	    wUnmanageWindow(wwin, True);
	    break;
	    
	 case IconicState:
	    wwin->flags.ignore_next_unmap=1;
	    wIconifyWindow(wwin);
	    break;
/*
	 case DontCareState:
	 case NormalState:
 */
	 default:
	    wClientSetState(wwin, NormalState, None);
	    wWindowPaint(wwin, BORDER_ALL, False);
	    break;
	}
    }
}


static void
handleDestroyNotify(Window window)
{
    WWindow *wwin;
    WApplication *app;
#ifdef DEBUG
    puts("got destroy notify");
#endif	    

    wwin = wWindowFor(window);
    if (wwin) {
	wUnmanageWindow(wwin, False);
    }

    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);
    }
}



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

#ifdef DEBUG    
    puts("got expose");
#endif
    if (event->xexpose.count!=0) {
      return;
    }
    
    if (XFindContext(dpy, event->xexpose.window, wWinContext, 
		     (XPointer *)&desc)==XCNOENT) {
	return;
    }
    /* Do everything here, instead of in a event handler,
     * because it's simple and faster this way.
     * 
     * Getting too big. Move to event handler way.
     */
    switch (desc->parent_type) {
     case WCLASS_WINDOW:
	wwin = desc->parent;	
	/* 
	 * ignore expose requests to subwindows
	 */
	if (wwin->window_flags.resizable
	    && event->xexpose.window==wwin->resizebar->window)
	  wWindowPaint(wwin, BORDER_BOTTOM, True);
	else if (wwin->window_flags.titlebar 
	    /* && event->xexpose.window==wwin->titlebar->window*/)
	  wWindowPaint(wwin, BORDER_TOP, True);
	
	break;

     case WCLASS_MENU:
	wMenuPaint(desc->parent);
	break;
	
     case WCLASS_DOCK_ICON:
     case WCLASS_APPICON:
	wAppIconPaint(desc->parent);
	break;

     case WCLASS_MINIWINDOW:
	wIconPaint(desc->parent);
	break;
	
     case WCLASS_DIALOG:
	wDialogPaint(desc->parent);
	break;

     default:
	wWarning("bad object in handleExpose()");
	break;
    }
}


/* bindable */
static void
handleButtonPress(XEvent *event)
{
    WObjDescriptor *desc;
      
#ifdef DEBUG    
    puts("got button press");
#endif

    if (event->xbutton.window==wScreen.root_win) {
	switch (event->xbutton.button) {
	 case Button3:
	    OpenRootMenu(&wScreen, event->xbutton.x_root,
			 event->xbutton.y_root);
	    /* ugly hack */
	    if (wScreen.root_menu) {
		event->xbutton.send_event = True;
		if (wScreen.root_menu->brother->flags.mapped)
		  event->xbutton.window = wScreen.root_menu->brother->frame->window;
		else
		  event->xbutton.window = wScreen.root_menu->frame->window;
	    }
	    break;
	 case Button1:
	    wSelectWindows(&wScreen, event);
	    break;
#if 0
	 case Button2:
	    {
		WCoreWindow *tmp;
		
		tmp = wScreen.stacking_list;
		puts("--------");
		while (tmp) {
		    if (tmp->descriptor.parent_type==WCLASS_WINDOW)
		      puts(((WWindow*)tmp->descriptor.parent)->title);
		    else
		      puts("not window");
		    tmp = tmp->stacking->under;
		}
	    }
	    break;
#else
	 case Button2:
	    OpenSwitchMenu(&wScreen, event->xbutton.x_root,
			   event->xbutton.y_root);
	    if (wScreen.switch_menu) {
		event->xbutton.send_event = True;
		if (wScreen.switch_menu->brother->flags.mapped)
		  event->xbutton.window = wScreen.switch_menu->brother->frame->window;
		else
		  event->xbutton.window = wScreen.switch_menu->frame->window;
	    }
	    break;
#endif
	}
    }
    if (XFindContext(dpy, event->xbutton.window, wWinContext, 
		 (XPointer *)&desc)==XCNOENT) {
	return;
    }
    if (desc->parent_type == WCLASS_WINDOW) {
	XSync(dpy, 0);
    
	if (event->xbutton.state & Mod1Mask) {
	    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) {
	XSync(dpy, 0);
	XAllowEvents(dpy, AsyncPointer, CurrentTime);
	XSync(dpy, 0);
    }

    /* detect double click */
    if ((desc->click_timestamp>0) 
	&& (LastTimestamp - desc->click_timestamp <= DEF_DBLCLICK_TIME)) {
	desc->click_timestamp = -1;
	
	if (desc->methods && desc->methods->dbl_click!=NULL) {
	    wUnselectWindows();
	    (*desc->methods->dbl_click)(desc, event);
	    return;
	}
    }
    desc->click_timestamp = LastTimestamp;
    /* mouse down. Single click handler is invoked by the mouse_down handler */
    if (desc->methods && desc->methods->mouse_down!=NULL) {
	(*desc->methods->mouse_down)(desc, event);
	return;
    }
}


static void
handleMapNotify(Window window)
{
    WWindow *wwin;
    
#ifdef DEBUG
    puts("got map");
#endif
    wwin= wWindowFor(window);
    if (wwin && wwin->client_win==window) {
	if (wwin->flags.ignore_next_unmap) {
	    wwin->flags.ignore_next_unmap=0;	    
	    return;
	}
	if (wwin->flags.miniaturized) {
	    wDeiconifyWindow(wwin);
	} else {
	    XGrabServer(dpy);
	    XSync(dpy,0);
	    XMapWindow(dpy, wwin->client_win);
	    XMapWindow(dpy, wwin->frame->window);
	    wwin->flags.mapped=1;
	    wClientSetState(wwin, NormalState, None);
	    XUngrabServer(dpy);
	}
    }
}


static void
handleUnmapNotify(XEvent *event)
{
    WWindow *wwin;
    XEvent ev;
    
#ifdef DEBUG
    puts("got unmap");
#endif
    wwin = wWindowFor(event->xunmap.window);
    if (!wwin || wwin->client_win!=event->xunmap.window 
	|| (!wwin->flags.mapped 
	    && !(wwin->flags.miniaturized || wwin->flags.hidden)))
      return;
    if (wwin->flags.ignore_next_unmap) {
	return;
    }
    XGrabServer(dpy);
    XSync(dpy, 0);
    /* check if the window was destroyed */
    if (XCheckTypedWindowEvent(dpy, wwin->client_win, DestroyNotify,&ev)) {
	DispatchEvent(&ev);
    } else {
	/* withdraw window */
	wwin->flags.mapped = 0;
	XSelectInput(dpy, wwin->client_win, NoEventMask);
	XRemoveFromSaveSet(dpy, wwin->client_win);
	wClientSetState(wwin, WithdrawnState, None);
	wUnmanageWindow(wwin, True);
    }
    XUngrabServer(dpy);
}


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

#ifdef DEBUG
    puts("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;

#ifdef DEBUG
    puts("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);
    }
}


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

#ifdef DEBUG
    puts("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;
	wIconifyWindow(wwin);
    } else if (event->xclient.message_type == _XA_GNUSTEP_WM_FUNCTION) {
	WApplication *wapp;
	int done=0;
	
	wapp = wApplicationOf(event->xclient.window);
	if (wapp) {
	    switch (event->xclient.data.l[0]) {
	     case GSFHideOtherApplications:
		wHideOtherApplications(wapp->main_window_desc);
		done = 1;
		break;

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

		 case GSFHideApplication:
		    wHideApplication(wApplicationOf(wwin->main_window));
		    break;
	
		}
	    }
	}
    } else {
	WObjDescriptor *desc;
	/*
	 * 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) {
	    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 
handleEnterNotify(XEvent *event)
{
    WWindow *wwin;
    static WMagicNumber *prev_window=NULL;
    
#ifdef DEBUG
    puts("got enter notify");
#endif
        
    /* enter to window */
    wwin = wWindowFor(event->xcrossing.window);
    if (!wwin) {
	if (wPreferences.focus_mode==WKF_POINTER
	    /* until I fix the dialog box stuff */
	    && event->xcrossing.window==event->xcrossing.root) {
	    wSetFocusTo(&wScreen, NULL);
	}
	if (wPreferences.colormap_follows_mouse)
	    InstallWindowColormaps(NULL);
	
	if (prev_window && event->xcrossing.root==event->xcrossing.window) {
	    wDeleteTimerHandler(prev_window);
	    prev_window = NULL;
	}
	return;
    }
    /* Install colormap for window, if the colormap installation mode
     * is colormap_follows_mouse */
    if (wPreferences.colormap_follows_mouse) {
	if (wwin->frame->window==event->xcrossing.window)
	  InstallWindowColormaps(wwin);
    }
    
    /* 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_POINTER
	 || wPreferences.focus_mode==WKF_SLOPPY)
	&& wwin->frame->window==event->xcrossing.window
	&& !wwin->flags.focused) {
	wDeleteTimerHandler(prev_window);
	prev_window = NULL;

	wSetFocusTo(wwin->screen_ptr, wwin);
	if (wPreferences.raise_delay) {
	    prev_window = wAddTimerHandler(wPreferences.raise_delay,
					   (WCallBack*)wRaiseFrame,
					   wwin->frame);
	}
    }
}



#ifdef SHAPE
static void
handleShapeNotify(XEvent *event)
{
    XShapeEvent *shev = (XShapeEvent*)event;
    WWindow *wwin;
    
#ifdef DEBUG
    puts("got shape notify");
#endif
    wwin = wWindowFor(event->xany.window);
    if (!wwin || shev->kind != ShapeBounding)
      return;
    
    wwin->flags.shaped = shev->shaped;
    wWindowSetShape(wwin);
}
#endif /* SHAPE */


extern HandleColormapNotify();

static void 
handleColormapNotify(XEvent *event)
{
    WWindow *wwin;
    
    wwin = wWindowFor(event->xcolormap.window);
    HandleColormapNotify(wwin, event);
}



static int
match(WShortKey *bindings, XKeyEvent *kev)
{
    if (!bindings)
      return 0;
    if ((!bindings->modifier || (bindings->modifier==kev->state))
	&& bindings->keycode==kev->keycode)
      return 1;
    else
      return match(bindings->alternate, kev);
}


static void
goWorkspace(int wspace)
{
    /* add new workspaces if they do not exist */
    if (wspace > wScreen.last_workspace) {
	int i;
	for (i=wScreen.last_workspace+1; i<=wspace; i++) {
	    wNewWorkspace(&wScreen);
	}
    } else {
	wChangeWorkspace(&wScreen, wspace);
    }
    RefreshWorkspaceMenu(&wScreen);    
}

static void
handleKeyPress(XEvent *event)
{
    WWindow *wwin = wScreen.focused_window;
    int i;

    /* ignore CapsLock */
    event->xkey.state &= ~LockMask;
    /* TODO: totally remake keyboard stuff */
    for (i=WKBD_WORKSPACE1; i<=WKBD_WORKSPACE9; i++) {
	if (match(wKeyBindings[i], &event->xkey)) {
	    goWorkspace(i-WKBD_WORKSPACE1);
	    break;
	}
    }
    if (match(wKeyBindings[WKBD_ROOTMENU], &event->xkey)) {
	OpenRootMenu(&wScreen, event->xkey.x_root, event->xkey.y_root);
    } else if (match(wKeyBindings[WKBD_PREVWORKSPACE], &event->xkey)) {
	if (wScreen.workspace>0)
	  goWorkspace(wScreen.workspace-1);
    } else if (match(wKeyBindings[WKBD_NEXTWORKSPACE], &event->xkey)) {
	if (wScreen.workspace<wScreen.last_workspace)
	  goWorkspace(wScreen.workspace+1);
    } else if (wwin && (wwin->flags.mapped || wwin->flags.shaded)) {
	if (match(wKeyBindings[WKBD_MINIATURIZE], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);

	    if (wwin->protocols.MINIATURIZE_WINDOW)
	      	wClientSendProtocol(wwin, _XA_GNUSTEP_WM_MINIATURIZE_WINDOW,
				    event->xbutton.time);
	    else {
		if (wwin->window_flags.miniaturizable)
		  wIconifyWindow(wwin);
	    }
	} else if (match(wKeyBindings[WKBD_HIDE], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);

	    if (wwin->main_window 
	       && !wwin->window_flags.not_application) {
		wHideApplication(wApplicationOf(wwin->main_window));
	    }
	} else if (wwin->window_flags.resizable
		   && match(wKeyBindings[WKBD_MAXIMIZE], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);

	    if (wwin->flags.maximized) {
		wUnmaximizeWindow(wwin);
	    } else {
		wMaximizeWindow(wwin, False);
	    }
	} else if (match(wKeyBindings[WKBD_RAISE], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);
	    
	    wRaiseFrame(wwin->frame);

	} else if (match(wKeyBindings[WKBD_LOWER], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);

	    wLowerFrame(wwin->frame);

	} else if (match(wKeyBindings[WKBD_RAISELOWER], &event->xkey)) {
	    wRaiseLowerFrame(wwin->frame);

	} else if (wwin->window_flags.shadeable
		   && match(wKeyBindings[WKBD_SHADE], &event->xkey)) {
	    if (wwin->flags.shaded)
	      wUnshadeWindow(wwin);
	    else
	      wShadeWindow(wwin);
       	} else if (wwin->window_flags.closable
		   && match(wKeyBindings[WKBD_CLOSE], &event->xkey)) {
	    if (wwin->screen_ptr->window_menu)
	      wMenuUnmap(wwin->screen_ptr->window_menu);
	    if (wwin->protocols.DELETE_WINDOW)
	      wClientSendProtocol(wwin, _XA_WM_DELETE_WINDOW,
				  event->xkey.time);

       	} else if (wPreferences.focus_mode==WKF_CLICK
		   && match(wKeyBindings[WKBD_FOCUSNEXT], &event->xkey)) {
	    wwin = NextFocusWindow(&wScreen);
	    wSetFocusTo(&wScreen, wwin);
	    wRaiseFrame(wwin->frame); 
       	} else if (wPreferences.focus_mode==WKF_CLICK
		   && match(wKeyBindings[WKBD_FOCUSPREV], &event->xkey)) {
	    wwin = PrevFocusWindow(&wScreen);
	    wSetFocusTo(&wScreen, wwin);
	    wRaiseFrame(wwin->frame);
	}
    }
}


