/*****************************************************************************/
/**       Copyright 1988 by Evans & Sutherland Computer Corporation,        **/
/**                          Salt Lake City, Utah                           **/
/**  Portions Copyright 1989 by the Massachusetts Institute of Technology   **/
/**                        Cambridge, Massachusetts                         **/
/**                                                                         **/
/**                           All Rights Reserved                           **/
/**                                                                         **/
/**    Permission to use, copy, modify, and distribute this software and    **/
/**    its documentation  for  any  purpose  and  without  fee is hereby    **/
/**    granted, provided that the above copyright notice appear  in  all    **/
/**    copies and that both  that  copyright  notice  and  this  permis-    **/
/**    sion  notice appear in supporting  documentation,  and  that  the    **/
/**    names of Evans & Sutherland and M.I.T. not be used in advertising    **/
/**    in publicity pertaining to distribution of the  software  without    **/
/**    specific, written prior permission.                                  **/
/**                                                                         **/
/**    EVANS & SUTHERLAND AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD    **/
/**    TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES  OF  MERCHANT-    **/
/**    ABILITY  AND  FITNESS,  IN  NO  EVENT SHALL EVANS & SUTHERLAND OR    **/
/**    M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAM-    **/
/**    AGES OR  ANY DAMAGES WHATSOEVER  RESULTING FROM LOSS OF USE, DATA    **/
/**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
/**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
/**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
/*****************************************************************************/
/****************************************************************************
 * This module is based on Twm, but has been siginificantly modified 
 * by Rob Nation 
 ****************************************************************************/
/***********************************************************************
 * The rest of it is all my fault -- MLM
 * mwm - "LessTif Window Manager"
 ***********************************************************************/

#include "mwm.h"

#ifdef ISC
#include <sys/bsdtypes.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#ifdef TIME_WITH_SYS_TIME
#include <time.h>
#endif
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

/*
 * shorthand defines
 */
#define MAX_NAME_LEN 200L	/* truncate to this many */
#define MAX_ICON_NAME_LEN 200L	/* ditto */
#define MOD_MASK	(ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|\
			 Mod4Mask | Mod5Mask)

extern int      menuFromFrameOrWindowOrTitlebar;
MwmWindow      *win;	/* the current mwm window */
static int      ShapeEventBase, ShapeErrorBase;
extern volatile int alarmed;
extern Bool     EnablePagerRedraw;

/*
 * Waits Mwm.click_time, or until it is evident that the user is not
 * clicking, but is moving the cursor.
 */
static Boolean
is_click(ScreenInfo *scr, int x, int y, XEvent *d)
{
    int             xcurrent, ycurrent, total = 0;
    XEvent          trash;

    xcurrent = x;
    ycurrent = y;
#if 0
    XAllowEvents(dpy, ReplayPointer, CurrentTime);
#endif
    while ((total < Mwm.click_time) &&
	   (x - xcurrent < Mwm.move_threshold) &&
	   (x - xcurrent > -Mwm.move_threshold) &&
	   (y - ycurrent < Mwm.move_threshold) &&
	   (y - ycurrent > -Mwm.move_threshold)) {

	MISC_usleep(10000);
	total += 10;

	if (XCheckMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask, &trash)) {
	    MISC_StashEventTime(&trash);
	    if (trash.xbutton.button == d->xbutton.button) {
		*d = trash;
		return True;
	    }
	}

	if (XCheckMaskEvent(dpy, ButtonMotionMask | PointerMotionMask, &trash)) {
	    xcurrent = trash.xmotion.x_root;
	    ycurrent = trash.xmotion.y_root;
	    MISC_StashEventTime(&trash);
	}
    }
    return False;
}

/*
 * This procedure handles both a client changing its own colormap, and
 * a client explicitly installing its colormap itself (only the window
 * manager should do that, so we must set it correctly).
 */
static void
color_map_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XColormapEvent *cevent = (XColormapEvent *)event;
    Boolean reinstall = False;


    if (!win)
	return;

    if (cevent->new) {
	XGetWindowAttributes(dpy, win->w, &(win->attr));
	if (win == scr->mwm_colormap && win->number_cmap_windows == 0)
	    scr->last_cmap = win->attr.colormap;
	reinstall = True;
    }
    else if ((cevent->state == ColormapUninstalled) &&
	     (scr->last_cmap == cevent->colormap)) {
	/* Some window installed its colormap, change it back */
	reinstall = True;
    }

    while (XCheckTypedEvent(dpy, ColormapNotify, event)) {
	if (XFindContext(dpy, cevent->window,
			 MwmContext, (XPointer *)&win) == XCNOENT)
	    win = NULL;
	if ((win) && (cevent->new)) {
	    XGetWindowAttributes(dpy, win->w, &(win->attr));
	    if (win == scr->mwm_colormap &&
		win->number_cmap_windows == 0)
		scr->last_cmap = win->attr.colormap;
	    reinstall = True;
	}
	else if ((win) &&
		 (cevent->state == ColormapUninstalled) &&
		 (scr->last_cmap == cevent->colormap)) {
	    /* Some window installed its colormap, change it back */
	    reinstall = True;
	}
	else if ((win) &&
		 (cevent->state == ColormapInstalled) &&
		 (scr->last_cmap == cevent->colormap)) {
	    /* The last color map installed was the correct one. Don't 
	     * change anything */
	    reinstall = False;
	}
    }

    if (reinstall)
	XInstallColormap(dpy, scr->last_cmap);
}

/*
 * handles focus in events
 */
static void
focus_in(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XEvent          d;
    Window          w;

    w = event->xany.window;
    while (XCheckTypedEvent(dpy, FocusIn, &d))
	w = d.xany.window;

    if (XFindContext(dpy, w, MwmContext, (XPointer *) &win) == XCNOENT)
	win = NULL;

    if (!win)
	DEC_DrawDecorations(scr, scr->mwm_highlight, False, True, True, None);
    else if (win != scr->mwm_highlight)
	DEC_DrawDecorations(scr, win, True, True, True, None);

    if (win && Mwm.colormap_focus_policy == XmKEYBOARD)
	COLOR_InstallWindowColorMap(scr, win);
}

/*
 * key press event handler
 */
static void
key_press(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    FuncKey        *key;
    unsigned int    modifier;
    Window          dummy;

    if (event->xkey.window == scr->shield_win) {
	XBell(dpy, 100);
	return;
    }

    scr->event_context = EVENT_GetContext(scr, win, event, &dummy);

    modifier = (event->xkey.state & MOD_MASK);
    for (key = scr->keys; key != NULL; key = key->next) {
	scr->mwm_event = win;
	/* Here's a real hack - some systems have two keys with the
	 * same keysym and different keycodes. This converts all
	 * the cases to one keycode. */
	event->xkey.keycode =
		XKeysymToKeycode(dpy,
				 XKeycodeToKeysym(dpy, event->xkey.keycode, 0));

	if ((key->keycode == event->xkey.keycode) &&
	    ((key->mods == (modifier & (~LockMask))) ||
	     (key->mods == AnyModifier)) &&
	    (key->cont & scr->event_context)) {
	    FUNC_Execute(scr, key->func, key->action, event->xany.window, win,
			    event, scr->event_context, key->val1, key->val2,
			    key->val1_unit, key->val2_unit,
			    key->menu);
	    return;
	}
    }

    /* if we get here, no function key was bound to the key.  Send it
     * to the client if it was in a window we know about.
     */
    if (win) {
	if (event->xkey.window != win->w) {
	    event->xkey.window = win->w;
	    XSendEvent(dpy, win->w, False, KeyPressMask, event);
	}
    }
    scr->mwm_event = NULL;
}

/*
 * property notify event handler
 */
static void
property_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    char           *prop = NULL;
    Atom            actual = None;
    int             actual_format;
    unsigned long   nitems, bytesafter;

    if ((!win) || (XGetGeometry(dpy, win->w, &JunkRoot, &JunkX, &JunkY,
			&JunkWidth, &JunkHeight, &JunkBW, &JunkDepth) == 0))
	return;

    switch (event->xproperty.atom) {
    case XA_WM_NAME:
	if (XGetWindowProperty(dpy, win->w, event->xproperty.atom, 0L,
			       MAX_NAME_LEN, False, XA_STRING, &actual,
			       &actual_format, &nitems, &bytesafter,
			       (unsigned char **) &prop) != Success ||
	    actual == None)
	    return;
	if (!prop)
	    prop = NoName;
	WIN_FreeNames(win, True, False);

	win->name = prop;

	/* fix the name in the title bar */
	if (!(win->flags & ICONIFIED))
	    DEC_DrawTitleBar(scr, win, (scr->mwm_highlight == win), True);

	/*
	 * if the icon name is NoName, set the name of the icon to be
	 * the same as the window 
	 */
	if (win->icon_active_label == NoName) {
	    win->icon_active_label = win->name;
	    ICON_UpdateWindow(scr, win, False);
	}
	break;

    case XA_WM_ICON_NAME:
	if (XGetWindowProperty(dpy, win->w, event->xproperty.atom, 0,
			       MAX_ICON_NAME_LEN, False, XA_STRING, &actual,
			       &actual_format, &nitems, &bytesafter,
			       (unsigned char **) &prop) != Success ||
	    actual == None)
	    return;
	if (!prop)
	    prop = NoName;
	WIN_FreeNames(win, False, True);
	win->icon_label = prop;
	ICON_UpdateWindow(scr, win, False);
	break;

    case XA_WM_HINTS:
	if (win->wmhints)
	    XFree((char *) win->wmhints);
	win->wmhints = XGetWMHints(dpy, event->xany.window);

	if (win->wmhints == NULL)
	    return;

	if ((win->wmhints->flags & IconPixmapHint) ||
	    (win->wmhints->flags & IconWindowHint)) {
	    if (win->icon_w)
		XDestroyWindow(dpy, win->icon_w);
	    XDeleteContext(dpy, win->icon_w, MwmContext);
	    if (win->flags & ICON_OURS) {
		if (win->icon_pixmap_w != None) {
		    XDestroyWindow(dpy, win->icon_pixmap_w);
		    XDeleteContext(dpy, win->icon_pixmap_w, MwmContext);
		}
	    }
	    else
		XUnmapWindow(dpy, win->icon_pixmap_w);
	    win->icon_w = None;
	    win->icon_pixmap_w = None;
	    win->icon_pixmap = (Window) NULL;
	    if (win->flags & ICONIFIED) {
		win->flags &= ~ICONIFIED;
		win->flags &= ~ICON_UNMAPPED;
		ICON_CreateWindow(scr, win,
				  win->icon_x_loc, win->icon_y_loc);

		WIN_Lower(scr, win);
		ICON_AutoPlace(scr, win);
		if (win->Desk == scr->current_desk) {
		    if (win->icon_w)
			XMapWindow(dpy, win->icon_w);
		    if (win->icon_pixmap_w != None)
			XMapWindow(dpy, win->icon_pixmap_w);
		}
		win->flags |= ICONIFIED;
		ICON_DrawWindow(scr, win);
	    }
	}
	break;

    case XA_WM_NORMAL_HINTS:
	PROP_GetWindowSizeHints(win);
	break;

    default:
	if (event->xproperty.atom == XA_WM_PROTOCOLS) {
	    PROP_GetWmProtocols(win);
	}
	else if (event->xproperty.atom == XA_WM_COLORMAP_WINDOWS) {
	    PROP_GetWmColormapWindows(win);	/* frees old data */
	    COLOR_InstallWindowColorMap(scr, scr->mwm_colormap);
	}
	else if (event->xproperty.atom == XA_WM_STATE) {
	    if ((Mwm.keyboard_focus_policy == XmEXPLICIT) &&
		(win == scr->mwm_focus) && (win != NULL)) {
		scr->mwm_focus = NULL;
		WIN_SetFocusInTree(win);
		WIN_SetFocus(scr, win->w, win);
		MISC_SetFocusSequence(scr);
	    }
	}
	else if (event->xproperty.atom == XA_MWM_HINTS) {
	    if (win) {
		int width, height;

		PROP_GetMwmHints(win);

		DEC_ReselectDecorations(scr, win);

		win->frame_width = win->attr.width +
					 2 * win->boundary_width +
					 2 * win->matte_width;
		win->frame_height = win->attr.height +
					 win->title_height +
					 2 * win->boundary_width +
					 2 * win->matte_width;

		WIN_ConstrainWindow(scr, win,
				    &win->frame_width,
				    &win->frame_height);

		width = win->frame_width;
		win->frame_width = 0;
		height = win->frame_height;
		win->frame_height = 0;

		DEC_ConfigureDecorations(scr, win,
					 win->frame_x, win->frame_y,
					 width, height,
					 True);

		if (scr->mwm_highlight == win) {
		    scr->mwm_highlight = NULL;
		    DEC_DrawDecorations(scr, win, True, True, True, None);
		}
		else
		    DEC_DrawDecorations(scr, win, False, False, True, None);
	    }
	}
	else if (event->xproperty.atom == XA_MWM_MENU) {
	    if (win) {
		if (win->mwm_menu)
		    XFree((char *)win->mwm_menu);

		MENU_DestroyWindowMenu(scr, win);

		PROP_GetMwmMenu(win);

		MENU_BuildWindowMenu(scr, win);
	    }
	}
	else if (event->xproperty.atom == XA_MWM_MESSAGES) {
	    if (win) {

		if (win->mwm_messages)
		    XFree((char *)win->mwm_messages);

		PROP_GetMwmMessages(win);
	    }
	}
    }
}

/*
 * client message event handler
 */
static void
client_message(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XEvent          button;

    if ((event->xclient.message_type == XA_WM_CHANGE_STATE) &&
	(win) && (event->xclient.data.l[0] == IconicState) &&
	!(win->flags & ICONIFIED)) {
	XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
		      &(button.xmotion.x_root),
		      &(button.xmotion.y_root),
		      &JunkX, &JunkY, &JunkMask);
	button.type = 0;
	FUNC_Execute(scr, F_ICONIFY, NULLSTR, event->xany.window,
			win, &button, C_FRAME, 0, 0, 0, 0,
			(MenuRoot *) 0);
    }
}

/*
 * expose event handler
 */
static void
expose(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    if (event->xexpose.count != 0)
	return;

    if (win) {
	if ((win->w == scr->pager_win) ||
	    (win->w == scr->pager_child_win)) {
	    PAGER_Redraw(scr);
	}
	if ((event->xany.window == win->title)) {
	    DEC_DrawTitleBar(scr, win, (scr->mwm_highlight == win), False);
	}
	else {
	    DEC_DrawDecorations(scr, win,
				(scr->mwm_highlight == win),
				True, True, event->xany.window);
	}
    }
    else {
	if (WIN_WindowToStruct(scr, event->xany.window))
	    PAGER_Redraw(scr);
    }
    return;
}

/*
 * DestroyNotify event handler
 */
static void
destroy_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    WIN_DestroyWindow(scr, win);
}

/*
 * MapRequest event handler
 */
static void
map_request(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    WIN_MapWindow(scr, event->xmaprequest.window);
}

/*
 * MapNotify event handler
 */
static void
map_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    if (!win)
	return;

    /*
     * Need to do the grab to avoid race condition of having server send
     * MapNotify to client before the frame gets mapped; this is bad because
     * the client would think that the window has a chance of being viewable
     * when it really isn't.
     */
    XGrabServer(dpy);
    if (win->icon_w)
	XUnmapWindow(dpy, win->icon_w);
    if (win->icon_pixmap_w != None)
	XUnmapWindow(dpy, win->icon_pixmap_w);

    if (win->mwm_hints &&
	(win->mwm_hints->flags & MWM_HINTS_INPUT_MODE) &&
	(win->mwm_hints->input_mode == MWM_INPUT_SYSTEM_MODAL)) {
	XMapRaised(dpy, scr->shield_win);
	WIN_Raise(scr, win);
    }

    XMapSubwindows(dpy, win->frame);

    if (win->Desk == scr->current_desk) {
	XMapWindow(dpy, win->frame);
    }

    if (Mwm.keyboard_focus_policy == XmEXPLICIT && Mwm.startup_key_focus) {
	WIN_SetFocusInTree(win);
	WIN_SetFocus(scr, win->w, win);
	MISC_SetFocusSequence(scr);
    }
    if ((!(win->decorations & (MWM_DECOR_BORDER | MWM_DECOR_TITLE))) &&
	(win->boundary_width < 2)) {
	DEC_DrawDecorations(scr, win, False, True, True, win->frame);
    }
    XSync(dpy, 0);
    XUngrabServer(dpy);
    XFlush(dpy);
    win->flags |= MAPPED;
    win->flags &= ~MAP_PENDING;
    win->flags &= ~ICONIFIED;
    win->flags &= ~ICON_UNMAPPED;
}

/*
 * UnmapNotify event handler
 */
static void
unmap_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    int             dstx, dsty;
    Window          dumwin;
    XEvent          dummy;

    /*
     * The July 27, 1988 ICCCM spec states that a client wishing to switch
     * to WithdrawnState should send a synthetic UnmapNotify with the
     * event field set to (pseudo-)root, in case the window is already
     * unmapped (which is the case for mwm for IconicState).  Unfortunately,
     * we looked for the MwmContext using that field, so try the window
     * field also.
     */
    if (!win) {
	event->xany.window = event->xunmap.window;
	if (XFindContext(dpy, event->xany.window,
			 MwmContext, (XPointer *) & win) == XCNOENT)
	    win = NULL;
    }

    if (!win)
	return;

    if (win->mwm_hints &&
	(win->mwm_hints->flags & MWM_HINTS_INPUT_MODE) &&
	(win->mwm_hints->input_mode == MWM_INPUT_SYSTEM_MODAL)) {
	XUnmapWindow(dpy, scr->shield_win);
    }

    if (win == scr->mwm_highlight)
	scr->mwm_highlight = NULL;

    if (scr->mwm_last_focus == win)
	scr->mwm_last_focus = NULL;

    if (win == scr->mwm_focus && Mwm.keyboard_focus_policy == XmEXPLICIT &&
	Mwm.auto_key_focus) {
	if (win->next) {
	    WIN_SetFocusInTree(win->next);
	    WIN_SetFocus(scr, win->next->w, win->next);
	}
	else if (win->ancestor) {
	    WIN_SetFocusInTree(win->ancestor);
	    WIN_SetFocus(scr, win->ancestor->w, win->ancestor);
	}
	else
	    WIN_SetFocus(scr, scr->no_focus_win, NULL);
    }

    if (scr->mwm_focus == win)
	WIN_SetFocus(scr, scr->no_focus_win, NULL);

    if (win == scr->mwm_pushed)
	scr->mwm_pushed = NULL;

    MISC_SetFocusSequence(scr);
    if (win == scr->mwm_colormap)
	scr->mwm_colormap = NULL;

    if (!(win->flags & MAPPED) && !(win->flags & ICONIFIED))
	return;

    XGrabServer(dpy);

    if (XCheckTypedWindowEvent(dpy, event->xunmap.window,
			       DestroyNotify, &dummy)) {
	WIN_DestroyWindow(scr, win);
	XUngrabServer(dpy);
	return;
    }

    /*
     * The program may have unmapped the client window, from either
     * NormalState or IconicState.  Handle the transition to WithdrawnState.
     *
     * We need to reparent the window back to the root (so that mwm exiting 
     * won't cause it to get mapped) and then throw away all state (pretend 
     * that we've received a DestroyNotify).
     */
    if (XTranslateCoordinates(dpy, event->xunmap.window, scr->root_win,
			      0, 0, &dstx, &dsty, &dumwin)) {
	XEvent          ev;
	Bool            reparented;

	reparented = XCheckTypedWindowEvent(dpy, event->xunmap.window,
					    ReparentNotify, &ev);
	PROP_SetState(win, WithdrawnState);
	if (reparented) {
	    if (win->old_bw)
		XSetWindowBorderWidth(dpy, event->xunmap.window,
				      win->old_bw);
	    if (win->wmhints && (win->wmhints->flags & IconWindowHint))
		XUnmapWindow(dpy, win->wmhints->icon_window);
	}
	else
	    WIN_RestoreWithdrawn(scr, win, False);

	if (event->xunmap.window != scr->restart_win &&
	    event->xunmap.window != scr->quit_win &&
	    event->xunmap.window != scr->toggle_win) {
	    XRemoveFromSaveSet(dpy, event->xunmap.window);
	    XSelectInput(dpy, event->xunmap.window, NoEventMask);
	}
	else
	    XDeleteContext(dpy, event->xunmap.window, MwmContext);

	WIN_DestroyWindow(scr, win);	/* do not need to mash event before */
    }
    /* else window no longer exists and we'll get a destroy notify */

    XUngrabServer(dpy);

    XFlush(dpy);
}

/*
 * MotionNotify event handler
 */
static void
motion_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    /* here is the code for dragging the viewport around within the pager. */
    if ((win) && (win->w == scr->pager_win) &&
	(!(win->flags & ICONIFIED))) {
	if (event->xmotion.state == Button3MotionMask) {
	    EnablePagerRedraw = False;
	    PAGER_SwitchPage(scr, False, False, event);
	}
    }
}

/*
 * ButtonPress event handler
 */
static void
button_press(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    unsigned int    modifier;
    MouseButton    *MouseEntry;
    char           *Action;
    Window          x;
    int             LocalContext;
    static Time     last_time = 0;
    Boolean         click = False, click2 = False, match = False;

    if (event->xbutton.window == scr->shield_win) {
	XBell(dpy, 100);
	return;
    }

    /* click to focus stuff goes here */
    if ((Mwm.keyboard_focus_policy == XmEXPLICIT) &&
	(win != scr->mwm_grabbing) &&
	((event->xbutton.state &
	  (ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask)) == 0)) {
	if (win) {
	    WIN_SetFocusInTree(win);
	    WIN_SetFocus(scr, win->w, win);
	    MISC_SetFocusSequence(scr);

	    if (win->focus_auto_raise)
		WIN_Raise(scr, win);

	    /* Why is this here? Seems to cause breakage with
	     * non-focusing windows! */
	    if (!(win->flags & ICONIFIED)) {
		XSync(dpy, 0);
		XAllowEvents(dpy, ReplayPointer, CurrentTime);
		XSync(dpy, 0);
		return;
	    }

	}
    }
    XSync(dpy, 0);
    XAllowEvents(dpy, ReplayPointer, CurrentTime);
    XSync(dpy, 0);

    /* here is the code for moving windows in the pager. */
    if ((win) && (win->w == event->xbutton.window) &&
	(win->w == scr->pager_win) && (event->xbutton.button == Button2)) {
	PAGER_Update(scr, event);
	return;
    }
    if ((win) && (win->w == scr->pager_win) &&
	(!(win->flags & ICONIFIED)) &&
	(event->xbutton.button == Button3) &&
	(event->xany.window == scr->pager_win)) {
	EnablePagerRedraw = False;
	PAGER_SwitchPage(scr, False, False, event);
    }

    scr->event_context = EVENT_GetContext(scr, win, event, &scr->pressed_win);
    LocalContext = scr->event_context;
    x = scr->pressed_win;
    if (scr->event_context == C_TITLE)
	DEC_DrawTitleBar(scr, win, (scr->mwm_highlight == win), False);
    else
	DEC_DrawDecorations(scr, win, (scr->mwm_highlight == win), True, True, scr->pressed_win);

    scr->mwm_event = win;

    /* we have to execute a function or pop up a menu
     */
    modifier = (event->xbutton.state & MOD_MASK);
    /* need to search for an appropriate mouse binding */

    click = is_click(scr, event->xbutton.x, event->xbutton.y, event);
    if (click && (MISC_FetchEventTime() - last_time) < Mwm.double_click_time)
	click2 = True;

    last_time = MISC_FetchEventTime();

    MouseEntry = scr->buttons;
    while (click2 && MouseEntry != (MouseButton *) 0) {
	if ((MouseEntry->button == event->xbutton.button ||
	     MouseEntry->button == 0) &&
	    (MouseEntry->context & scr->event_context) &&
	    (MouseEntry->modifier == AnyModifier ||
	     MouseEntry->modifier == (modifier & (~LockMask))) &&
	    (MouseEntry->mask & (ButtonPressMask|ButtonReleaseMask))) {

	    /* got a match, now process it */
	    if (MouseEntry->func != F_NOP) {
		if (MouseEntry->count == 2) {
		    match = True;
		    Action = MouseEntry->item ? MouseEntry->item->action : NULL;
		    FUNC_Execute(scr, MouseEntry->func, Action,
				 event->xany.window, win, event,
				 scr->event_context,
				 MouseEntry->val1, MouseEntry->val2,
				 MouseEntry->val1_unit, MouseEntry->val2_unit,
				 MouseEntry->menu);
		}
	    }
	}
	MouseEntry = MouseEntry->next;
    }
    if (match) {
	scr->pressed_win = None;
	if (LocalContext != C_TITLE)
	    DEC_DrawDecorations(scr, scr->mwm_event, (scr->mwm_highlight == scr->mwm_event), True, True, x);
	else
	    DEC_DrawTitleBar(scr, scr->mwm_event, (scr->mwm_highlight == scr->mwm_event), False);
	scr->mwm_event = NULL;
	return;
    }

    MouseEntry = scr->buttons;
    while (MouseEntry != (MouseButton *) 0) {
	if ((MouseEntry->button == event->xbutton.button ||
	     MouseEntry->button == 0) &&
	    (MouseEntry->context & scr->event_context) &&
	    (MouseEntry->modifier == AnyModifier ||
	     MouseEntry->modifier == (modifier & (~LockMask))) &&
	    (MouseEntry->mask & ButtonPressMask)) {

	    /* got a match, now process it, unless the user clicked, and it's
	     * interactive */
	    if (MouseEntry->func != F_NOP &&
		!(click && (MouseEntry->func == F_MOVE ||
			    MouseEntry->func == F_RESIZE ||
			    MouseEntry->func == F_WINDOWLIST ||
			    MouseEntry->func == F_POPUP ||
			    MouseEntry->func == F_W_POPUP))) {

		if (!(MouseEntry->mask & ButtonReleaseMask)) {
		    Action = MouseEntry->item ? MouseEntry->item->action : NULL;
		    FUNC_Execute(scr, MouseEntry->func, Action,
				 event->xany.window, win, event,
				 scr->event_context,
				 MouseEntry->val1, MouseEntry->val2,
				 MouseEntry->val1_unit, MouseEntry->val2_unit,
				 MouseEntry->menu);
		}
	    }
	}
	MouseEntry = MouseEntry->next;
    }

    MouseEntry = scr->buttons;
    while (click && MouseEntry != (MouseButton *) 0) {
	if ((MouseEntry->button == event->xbutton.button ||
	     MouseEntry->button == 0) &&
	    (MouseEntry->context & scr->event_context) &&
	    (MouseEntry->modifier == AnyModifier ||
	     MouseEntry->modifier == (modifier & (~LockMask))) &&
	    (MouseEntry->mask & (ButtonPressMask|ButtonReleaseMask))) {

	    /* got a match, now process it */
	    if (MouseEntry->func != F_NOP) {

		if (MouseEntry->count == 1) {
		    Action = MouseEntry->item ? MouseEntry->item->action : NULL;
		    FUNC_Execute(scr, MouseEntry->func, Action,
				 event->xany.window, win, event,
				 scr->event_context,
				 MouseEntry->val1, MouseEntry->val2,
				 MouseEntry->val1_unit, MouseEntry->val2_unit,
				 MouseEntry->menu);
		}
	    }
	}
	MouseEntry = MouseEntry->next;
    }

    scr->pressed_win = None;
    if (LocalContext != C_TITLE)
	DEC_DrawDecorations(scr, scr->mwm_event, (scr->mwm_highlight == scr->mwm_event), True, True, x);
    else
	DEC_DrawTitleBar(scr, scr->mwm_event, (scr->mwm_highlight == scr->mwm_event), False);
    scr->mwm_event = NULL;
}

/*
 * ButtonRelease event handler
 */
static void
button_release(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    unsigned int    modifier;
    MouseButton    *MouseEntry;
    char           *Action;

    if ((win) && (event->xany.window == scr->pager_win) &&
	(!(win->flags & ICONIFIED))) {
	switch (event->xbutton.button) {
	default:
	case Button1:
	    PAGER_SwitchPage(scr, True, True, event);
	    break;
	case Button3:
	    EnablePagerRedraw = True;
	    PAGER_SwitchPage(scr, False, False, event);
	    PAGER_Clear(scr);
	    break;
	}
    }

    scr->event_context = EVENT_GetContext(scr, win, event, &scr->pressed_win);
    modifier = (event->xbutton.state & MOD_MASK);
    /* need to search for an appropriate mouse binding */
    MouseEntry = scr->buttons;
    while (MouseEntry != (MouseButton *) 0) {
	if ((MouseEntry->button == event->xbutton.button ||
	     MouseEntry->button == 0) &&
	    (MouseEntry->context & scr->event_context) &&
	    (MouseEntry->modifier == AnyModifier ||
	     MouseEntry->modifier == (modifier & (~LockMask))) &&
	    (MouseEntry->mask & ButtonReleaseMask)) {

	    /* got a match, now process it */
	    if (MouseEntry->func != F_NOP) {
		Action = MouseEntry->item ? MouseEntry->item->action : NULL;
		FUNC_Execute(scr, MouseEntry->func, Action,
			     event->xany.window, win, event,
			     scr->event_context,
			     MouseEntry->val1, MouseEntry->val2,
			     MouseEntry->val1_unit, MouseEntry->val2_unit,
			     MouseEntry->menu);
		break;
	    }
	}
	MouseEntry = MouseEntry->next;
    }
}

/*
 * EnterNotify event handler
 */
static void
enter_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XEnterWindowEvent *ewp = &event->xcrossing;
    XEvent          d;

    /* look for a matching leaveNotify which would nullify this enterNotify */
    if (XCheckTypedWindowEvent(dpy, ewp->window, LeaveNotify, &d)) {
	MISC_StashEventTime(&d);
	if ((d.xcrossing.mode == NotifyNormal) &&
	    (d.xcrossing.detail != NotifyInferior))
	    return;
    }

    /* an EnterEvent in one of the PanFrameWindows activates the Paging */
    if (PAN_IsPannerWindow(scr, ewp->window))
    {
	int             delta_x = 0, delta_y = 0;

	/* this was in the HandleMotionNotify before, HEDU */
	PAN_PanDesktop(scr, scr->edge_scroll_x, scr->edge_scroll_y,
		     &event->xcrossing.x_root, &event->xcrossing.y_root,
		     &delta_x, &delta_y, True, event);
	return;
    }

    if (event->xany.window == scr->root_win) {
	if ((!(Mwm.keyboard_focus_policy == XmEXPLICIT)) &&
	    (!(Mwm.keyboard_focus_policy == XmPOINTER))) {
	    WIN_SetFocus(scr, scr->no_focus_win, NULL);
	    MISC_SetFocusSequence(scr);
	}
	if (Mwm.colormap_focus_policy != XmKEYBOARD)
	    COLOR_InstallWindowColorMap(scr, NULL);
	return;
    }

    /* make sure its for one of our windows */
    if (!win)
	return;

    if (!(Mwm.keyboard_focus_policy == XmEXPLICIT)) {
	if (scr->mwm_focus != win) {
	    if ((Mwm.auto_raise_delay > 0) && (!(win->flags & VISIBLE)))
		MISC_SetTimer(Mwm.auto_raise_delay);
	    WIN_SetFocusInTree(win);
	    WIN_SetFocus(scr, win->w, win);
	    MISC_SetFocusSequence(scr);
	}
	else {
	    WIN_SetFocusInTree(win);
	    WIN_SetFocus(scr, win->w, win);
	}
    }
    if (Mwm.colormap_focus_policy == XmPOINTER) {
	if ((!(win->flags & ICONIFIED)) && (event->xany.window == win->w))
	    COLOR_InstallWindowColorMap(scr, win);
	else
	    COLOR_InstallWindowColorMap(scr, NULL);
    }

    return;
}

/*
 * LeaveNotify event handler
 */
static void
leave_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    /* If we leave the root window, then we're really moving
     * another screen on a multiple screen display, and we
     * need to de-focus and unhighlight to make sure that we
     * don't end up with more than one highlighted window at a time */
    if (event->xcrossing.window == scr->root_win) {
	if (event->xcrossing.mode == NotifyNormal) {
	    if (event->xcrossing.detail != NotifyInferior) {
		if (scr->mwm_focus != NULL) {
		    WIN_SetFocus(scr, scr->no_focus_win, NULL);
		}
		if (scr->mwm_highlight != NULL)
		    DEC_DrawDecorations(scr, scr->mwm_highlight, False, True, True, None);
	    }
	}
    }
}

/*
 * ConfigureRequest event handler
 */
static void
configure_request(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XWindowChanges  xwc;
    unsigned long   xwcm;
    int             x, y, width, height;
    XConfigureRequestEvent *cre = &event->xconfigurerequest;

    /*
     * event->xany.window is event->xconfigurerequest.parent, so win will
     * be wrong
     */
    event->xany.window = cre->window;	/* mash parent field */
    if (XFindContext(dpy, cre->window, MwmContext, (XPointer *) & win) ==
	XCNOENT)
	win = NULL;

    /*
     * According to the July 27, 1988 ICCCM draft, we should ignore size and
     * position fields in the WM_NORMAL_HINTS property when we map a window.
     * Instead, we'll read the current geometry.  Therefore, we should respond
     * to configuration requests for windows which have never been mapped.
     */
    if (!win || (win->icon_w == cre->window)) {
	xwcm = cre->value_mask &
	    (CWX | CWY | CWWidth | CWHeight | CWBorderWidth);
	xwc.x = cre->x;
	xwc.y = cre->y;
	if ((win) && ((win->icon_w == cre->window))) {
	    win->icon_xl_loc = cre->x;
	    win->icon_x_loc = cre->x +
		(win->icon_w_width - win->icon_p_width) / 2;
	    win->icon_y_loc = cre->y - win->icon_p_height;
	}
	xwc.width = cre->width;
	xwc.height = cre->height;
	xwc.border_width = cre->border_width;
	XConfigureWindow(dpy, event->xany.window, xwcm, &xwc);

	if (win) {
	    xwc.x = win->icon_x_loc;
	    xwc.y = win->icon_y_loc - win->icon_p_height;
	    xwcm = cre->value_mask & (CWX | CWY);
	    if (win->icon_pixmap_w != None)
		XConfigureWindow(dpy, win->icon_pixmap_w, xwcm, &xwc);
	    xwc.x = win->icon_x_loc;
	    xwc.y = win->icon_y_loc;
	    xwcm = cre->value_mask & (CWX | CWY);
	    if (win->icon_w != None)
		XConfigureWindow(dpy, win->icon_w, xwcm, &xwc);
	}
	return;
    }

    if (cre->value_mask & CWStackMode) {
	MwmWindow      *otherwin;

	xwc.sibling = (((cre->value_mask & CWSibling) &&
			(XFindContext(dpy, cre->above, MwmContext,
				      (XPointer *) & otherwin) == XCSUCCESS))
		       ? otherwin->frame : cre->above);
	xwc.stack_mode = cre->detail;
	XConfigureWindow(dpy, win->frame,
			 cre->value_mask & (CWSibling | CWStackMode), &xwc);
    }

    {
	int             xws, yws, xbs, ybs;
	unsigned        wws, hws, wbs, hbs;
	int             boundingShaped, clipShaped;

	XShapeQueryExtents(dpy, win->w, &boundingShaped, &xws, &yws, &wws,
			   &hws, &clipShaped, &xbs, &ybs, &wbs, &hbs);
	win->wShaped = boundingShaped;
    }

    /* Don't modify frame_XXX fields before calling SetupWindow! */
    x = win->frame_x;
    y = win->frame_y;
    width = win->frame_width;
    height = win->frame_height;

    /* for restoring */
    if (cre->value_mask & CWBorderWidth) {
	win->old_bw = cre->border_width;
    }
    /* override even if border change */

    if (cre->value_mask & CWX)
	x = cre->x - win->boundary_width - win->bw - win->matte_width;
    if (cre->value_mask & CWY)
	y = cre->y - win->boundary_width - win->title_height - win->bw - win->matte_width;
    if (cre->value_mask & CWWidth)
	width = cre->width + 2 * win->boundary_width +
			2 * win->matte_width;
    if (cre->value_mask & CWHeight)
	height = cre->height + win->title_height + 2 * win->boundary_width +
			2 * win->matte_width;

    /*
     * SetupWindow (x,y) are the location of the upper-left outer corner and
     * are passed directly to XMoveResizeWindow (frame).  The (width,height)
     * are the inner size of the frame.  The inner width is the same as the 
     * requested client window width; the inner height is the same as the
     * requested client window height plus any title bar slop.
     */
    DEC_ConfigureDecorations(scr, win, x, y, width, height, False);

    PAGER_Clear(scr);
}

/*
 * record fully visible windows for use in the RaiseLower function and the
 * OnTop type windows.
 */
static void
visibility_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XVisibilityEvent *vevent = (XVisibilityEvent *)event;

    if (win) {
	if (vevent->state == VisibilityUnobscured)
	    win->flags |= VISIBLE;
	else
	    win->flags &= ~VISIBLE;
    }
}

/*
 * shape notification event handler
 */
static void
shape_notify(ScreenInfo *scr, MwmWindow *win, XEvent *event)
{
    XShapeEvent    *sev = (XShapeEvent *)&event;

    if (!win)
	return;
    if (sev->kind != ShapeBounding)
	return;
    win->wShaped = sev->shaped;

    DEC_SetShape(win, win->frame_width);
}

/*
 * event handling initialization
 */
void
EVENT_Initialize(void)
{
    XShapeQueryExtension(dpy, &ShapeEventBase, &ShapeErrorBase);
}

/*
 * return the window context for an XEvent.
 */
int
EVENT_GetContext(ScreenInfo *scr, MwmWindow *t, XEvent *e, Window *w)
{
    int ctxt, i;

    if (!t)
	return C_ROOT;

    ctxt = C_NO_CONTEXT;
    *w = e->xany.window;

    if (*w == scr->no_focus_win)
	return C_ROOT;

    /* Since key presses and button presses are grabbed in the frame
     * when we have re-parented windows, we need to find out the real
     * window where the event occured */
    if ((e->type == KeyPress) && (e->xkey.subwindow != None))
	*w = e->xkey.subwindow;

    if ((e->type == ButtonPress) && (e->xbutton.subwindow != None) &&
    ((e->xbutton.subwindow == t->w) || (e->xbutton.subwindow == t->parent)))
	*w = e->xbutton.subwindow;

    if (*w == scr->root_win)
	ctxt = C_ROOT;
    if (t) {
	if (*w == t->title)
	    ctxt = C_TITLE;
	if ((*w == t->w) || (*w == t->parent))
	    ctxt = C_WINDOW;
	if (*w == t->icon_w)
	    ctxt = C_ICON;
	if (*w == t->icon_pixmap_w)
	    ctxt = C_ICON;
	if (*w == t->frame)
	    ctxt = C_FRAME;
	for (i = 0; i < 4; i++)
	    if (*w == t->corners[i]) {
		ctxt = C_FRAME;
	    }
	for (i = 0; i < 4; i++)
	    if (*w == t->sides[i]) {
		ctxt = C_FRAME;
	    }
	if (*w == t->menub) {
	    ctxt = C_MENUB;
	}
	if (*w == t->minimizeb) {
	    ctxt = C_MINIMIZEB;
	}
	if (*w == t->maximizeb) {
	    ctxt = C_MAXIMIZEB;
	}
    }
    return ctxt;
}

/*
 * Waits for next X event, or for an auto-raise timeout.
 */
int
EVENT_Next(XEvent *event)
{
#ifndef HAVE_GETITIMER
    struct itimerval
    { 
      struct timeval it_value;
    };
#endif
    struct itimerval value;
    fd_set          in_fdset, out_fdset;
    Window          child;
    int             retval, i;
    ScreenInfo      *scr;

    /* Do this prior to the select() call, in case the timer already expired,
     * in which case the select would never return. */
    if (alarmed) {
	alarmed = False;

	for (i = 0; i < Mwm.number_of_screens; i++)
	{
	    scr = Mwm.screen_info[i];
	    XQueryPointer(dpy, scr->root_win, &JunkRoot, &child, &JunkX, &JunkY,
			  &JunkX, &JunkY, &JunkMask);
	    if ((scr->mwm_focus != NULL) && (child == scr->mwm_focus->frame)) {
		if (!(scr->mwm_focus->flags & VISIBLE) &&
		    scr->mwm_focus->focus_auto_raise) {
		    WIN_Raise(scr, scr->mwm_focus);
		    PAGER_Clear(scr);
		}
	    }
	}
	return 0;
    }

#ifndef HAVE_GETITIMER
    value.it_value.tv_usec = 10000;
    value.it_value.tv_sec = 0;
#else
    getitimer(ITIMER_REAL, &value);
#endif

    FD_ZERO(&in_fdset);
    FD_SET(x_fd, &in_fdset);
    FD_ZERO(&out_fdset);

    /* Do this IMMEDIATELY prior to select, to prevent any nasty
     * queued up X events from just hanging around waiting to be
     * flushed */
    XFlush(dpy);
    if (XPending(dpy)) {
	XNextEvent(dpy, event);
	MISC_StashEventTime(event);
	return 1;
    }

    /* Zap all those zombies! */
    /* If we get to here, then there are no X events waiting to be processed.
     * Just take a moment to check for dead children. */
    ReapChildren();

    XFlush(dpy);
    if ((value.it_value.tv_usec != 0) ||
	(value.it_value.tv_sec != 0)) {
#ifdef __hpux
	retval = select(fd_width, (int *) &in_fdset, 0, 0, &value.it_value);
#else
	retval = select(fd_width, &in_fdset, 0, 0, &value.it_value);
#endif
    }
    else {
#ifdef __hpux
	retval = select(fd_width, (int *) &in_fdset, (int *) &out_fdset,
			0, NULL);
#else
	retval = select(fd_width, &in_fdset, &out_fdset, 0, NULL);
#endif
    }

    return 0;
}

/*
 * handle a single X event stored in global var Event
 */
void
EVENT_Dispatch(XEvent *event)
{
    Window w = event->xany.window;
    Widget wid;
    ScreenInfo *scr;
    MwmWindow *win;

    MISC_StashEventTime(event);
    scr = SCREEN_EventToStruct(event);

    if (XFindContext(dpy, w, MwmContext, (XPointer *)&win) == XCNOENT)
	win = NULL;

    if ((wid = XtWindowToWidget(event->xany.display, w)) != NULL)
    {
	Boolean ret;

	if (event->xany.type == MapNotify && XtIsShell(wid)) {
	    map_request(scr, win, event);
	    map_notify(scr, win, event);
	}

	if (event->xany.type == UnmapNotify && XtIsShell(wid))
	    unmap_notify(scr, win, event);

#if XtSpecificationRelease >= 6
	ret = XtDispatchEventToWidget(wid, event);
#else
	ret = XtDispatchEvent(event);
#endif

	if (ret)
	    return;
    }

    switch (event->type) {
    case Expose:
	expose(scr, win, event);
	break;

    case DestroyNotify:
	destroy_notify(scr, win, event);
	break;

    case MapRequest:
	map_request(scr, win, event);
	break;

    case MapNotify:
	map_notify(scr, win, event);
	break;

    case UnmapNotify:
	unmap_notify(scr, win, event);
	break;

    case MotionNotify:
	motion_notify(scr, win, event);
	break;

    case ButtonPress:
	button_press(scr, win, event);
	break;

    case ButtonRelease:
	button_release(scr, win, event);
	break;

    case EnterNotify:
	enter_notify(scr, win, event);
	break;

    case LeaveNotify:
	leave_notify(scr, win, event);
	break;

    case FocusIn:
	focus_in(scr, win, event);
	break;

    case ConfigureRequest:
	configure_request(scr, win, event);
	break;

    case ClientMessage:
	client_message(scr, win, event);
	break;

    case PropertyNotify:
	property_notify(scr, win, event);
	break;

    case KeyPress:
	key_press(scr, win, event);
	break;

    case VisibilityNotify:
	visibility_notify(scr, win, event);
	break;

    case ColormapNotify:
	color_map_notify(scr, win, event);
	break;

    default:
	if (event->type == (ShapeEventBase + ShapeNotify))
	    shape_notify(scr, win, event);
	break;
    }

    return;
}
