/* $TOG: button.c /main/76 1997/07/30 16:56:19 kaleb $ */
/*
 * Copyright 1999 by Thomas E. Dickey <dickey@clark.net>
 *
 *                         All Rights Reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization.
 *
 *
 * Copyright 1987 by Digital Equipment Corporation, Maynard, 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 permission notice appear in
 * supporting documentation, and that the name of Digital Equipment
 * Corporation not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 *
 *
 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES 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.
 */
/* $XFree86: xc/programs/xterm/button.c,v 3.44 1999/11/19 13:55:15 hohndel Exp $ */

/*
button.c	Handles button events in the terminal emulator.
		does cut/paste operations, change modes via menu,
		passes button events through to some applications.
				J. Gettys.
*/

#include <xterm.h>

#include <X11/Xatom.h>

#include <stdio.h>

#ifdef MINIX
#include <X11/Xos.h>
#endif

#include <X11/Xmu/Atoms.h>
#include <X11/Xmu/StdSel.h>

#include <data.h>
#include <error.h>
#include <menu.h>
#include <xcharmouse.h>

#define XTERM_CELL(row,col) getXtermCell(screen, row + screen->topline, col)

      /*
       * We reserve shift modifier for cut/paste operations.  In principle we
       * can pass through control and meta modifiers, but in practice, the
       * popup menu uses control, and the window manager is likely to use meta,
       * so those events are not delivered to SendMousePosition.
       */
#define OurModifiers (ShiftMask | ControlMask | Mod1Mask)
#define AllModifiers (ShiftMask | LockMask | ControlMask | Mod1Mask | \
		      Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask)

#define KeyModifiers (event->xbutton.state & OurModifiers)

#define KeyState(x) (((x) & ControlMask) + (((x) & Mod1Mask) ? 2 : 0))
    /* adds together the bits:
	(1 used to be for shift, is now used for buttons 4 and 5)
	meta key  -> 2
	control key -> 4 */

#define	Coordinate(r,c)		((r) * (term->screen.max_col+1) + (c))

static ANSI reply;

/* Selection/extension variables */

/* Raw char position where the selection started */
static int rawRow, rawCol;

/* Selected area before CHAR, WORD, LINE selectUnit processing */
static int startRRow, startRCol, endRRow, endRCol = 0;

/* Selected area after CHAR, WORD, LINE selectUnit processing */
static int startSRow, startSCol, endSRow, endSCol = 0;

/* Valid rows for selection clipping */
static int firstValidRow, lastValidRow;

/* Start, end of extension */
static int startERow, startECol, endERow, endECol;

/* Saved values of raw selection for extend to restore to */
static int saveStartRRow, saveStartRCol, saveEndRRow, saveEndRCol;

/* Saved value of WORD selection for LINE processing to restore to */
static int saveStartWRow, saveStartWCol;

/* Multi-click handling */
static int numberOfClicks = 0;
static Time lastButtonUpTime = 0;
typedef int SelectUnit;
#define SELECTCHAR 0
#define SELECTWORD 1
#define SELECTLINE 2
#define NSELECTUNITS 3
static SelectUnit selectUnit;

/* Send emacs escape code when done selecting or extending? */
static int replyToEmacs;

static Char *SaveText (TScreen *screen, int row, int scol, int ecol, Char *lp, int *eol);
static int Length (TScreen *screen, int row, int scol, int ecol);
static void ComputeSelect (int startRow, int startCol, int endRow, int endCol, Bool extend);
static void EditorButton (XButtonEvent *event);
static void EndExtend (Widget w, XEvent *event, String *params, Cardinal num_params, Bool use_cursor_loc);
static void ExtendExtend (int row, int col);
static void PointToRowCol (int y, int x, int *r, int *c);
static void ReHiliteText (int frow, int fcol, int trow, int tcol);
static void SaltTextAway (int crow, int ccol, int row, int col, String *params, Cardinal num_params);
static void SelectSet (Widget w, XEvent *event, String *params, Cardinal num_params);
static void SelectionReceived PROTO_XT_SEL_CB_ARGS;
static void StartSelect (int startrow, int startcol);
static void TrackDown (XButtonEvent *event);
static void _OwnSelection (XtermWidget termw, String *selections, Cardinal count);

Boolean SendMousePosition(Widget w, XEvent* event)
{
    TScreen *screen;

    if (!IsXtermWidget(w))
	return False;

    screen = &((XtermWidget)w)->screen;

    /* If send_mouse_pos mode isn't on, we shouldn't be here */
    if (screen->send_mouse_pos == MOUSE_OFF)
	return False;

#if OPT_DEC_LOCATOR
    if (screen->send_mouse_pos == DEC_LOCATOR) {
	return( SendLocatorPosition( w, event ) );
    }
#endif	/* OPT_DEC_LOCATOR */

    /* Make sure the event is an appropriate type */
    if ((screen->send_mouse_pos != BTN_EVENT_MOUSE)
     && (screen->send_mouse_pos != ANY_EVENT_MOUSE)
     && event->type != ButtonPress
     && event->type != ButtonRelease)
	return False;

    switch (screen->send_mouse_pos) {
      case X10_MOUSE: /* X10 compatibility sequences */

	if (KeyModifiers == 0) {
	    if (event->type == ButtonPress)
		EditorButton((XButtonEvent *)event);
	    return True;
	}
	return False;

      case VT200_HIGHLIGHT_MOUSE: /* DEC vt200 hilite tracking */
	if (  event->type == ButtonPress &&
	      KeyModifiers == 0 &&
	      event->xbutton.button == Button1 ) {
	    TrackDown((XButtonEvent *)event);
	    return True;
	}
	if (KeyModifiers == 0 || KeyModifiers == ControlMask) {
	    EditorButton((XButtonEvent *)event);
	    return True;
	}
	return False;

      case VT200_MOUSE:	/* DEC vt200 compatible */

      /* xterm extension for motion reporting. June 1998 */
      /* EditorButton() will distinguish between the modes */
      case BTN_EVENT_MOUSE:
      case ANY_EVENT_MOUSE:
	if (KeyModifiers == 0 || KeyModifiers == ControlMask) {
	    EditorButton((XButtonEvent *)event);
	    return True;
	}
	return False;

      default:
	return False;
    }
}

#if OPT_DEC_LOCATOR

#define	LocatorCoords( row, col, x, y, oor )			\
    if( screen->locator_pixels ) {				\
	(oor)=FALSE; (row) = (y)+1; (col) = (x)+1;		\
	/* Limit to screen dimensions */			\
	if ((row) < 1) (row) = 1,(oor)=TRUE;			\
	else if ((row) > screen->border*2+Height(screen))	\
	    (row) = screen->border*2+Height(screen),(oor)=TRUE;	\
	if ((col) < 1) (col) = 1,(oor)=TRUE;			\
	else if ((col) > OriginX(screen)*2+Width(screen))	\
	    (col) = OriginX(screen)*2+Width(screen),(oor)=TRUE;	\
    } else {							\
	(oor)=FALSE;						\
	/* Compute character position of mouse pointer */	\
	(row) = ((y) - screen->border) / FontHeight(screen);	\
	(col) = ((x) - OriginX(screen)) / FontWidth(screen);	\
	/* Limit to screen dimensions */			\
	if ((row) < 0) (row) = 0,(oor)=TRUE;			\
	else if ((row) > screen->max_row)			\
	    (row) = screen->max_row,(oor)=TRUE;			\
	if ((col) < 0) (col) = 0,(oor)=TRUE;			\
	else if ((col) > screen->max_col)			\
	    (col) = screen->max_col,(oor)=TRUE;			\
	(row)++; (col)++;					\
    }

#define	MotionOff( s, t ) {						\
	    (s)->event_mask |= ButtonMotionMask;			\
	    (s)->event_mask &= ~PointerMotionMask;			\
	    XSelectInput(XtDisplay((t)), XtWindow((t)), (s)->event_mask); }

#define	MotionOn( s, t ) {						\
	    (s)->event_mask &= ~ButtonMotionMask;			\
	    (s)->event_mask |= PointerMotionMask;			\
	    XSelectInput(XtDisplay((t)), XtWindow((t)), (s)->event_mask); }

Boolean
SendLocatorPosition(Widget w, XEvent* event)
{
    TScreen	*screen = &((XtermWidget)w)->screen;
    int		row, col;
    Boolean	oor;
    int		button;
    int		state;

    /* Make sure the event is an appropriate type */
    if ((event->type != ButtonPress &&
	 event->type != ButtonRelease &&
	 !screen->loc_filter) ||
	(KeyModifiers != 0 && KeyModifiers != ControlMask))
	return( False );

    if ((event->type == ButtonPress &&
	 !(screen->locator_events & LOC_BTNS_DN)) ||
	(event->type == ButtonRelease &&
	 !(screen->locator_events & LOC_BTNS_UP)))
	return( True );

    if( event->type == MotionNotify ) {
	CheckLocatorPosition( w, event );
	return( True );
    }

    /* get button # */
    button = event->xbutton.button - 1;

    LocatorCoords( row, col, event->xbutton.x, event->xbutton.y, oor );

    /*
    * DECterm mouse:
    *
    * ESCAPE '[' event ; mask ; row ; column '&' 'w'
    */
    reply.a_type   = CSI;

    if( oor) {
	reply.a_nparam = 1;
	reply.a_param[0] = 0; /* Event - 0 = locator unavailable */
	reply.a_inters = '&';
	reply.a_final  = 'w';
	unparseseq(&reply, screen->respond);

	if( screen->locator_reset ) {
	    MotionOff( screen, term );
	    screen->send_mouse_pos = MOUSE_OFF;
	}
	return( True );
    }

    /*
    * event:
    *	1	no buttons
    *	2	left button down
    *	3	left button up
    *	4	middle button down
    *	5	middle button up
    *	6	right button down
    *	7	right button up
    *	8	M4 down
    *	9	M4 up
    */
    reply.a_nparam = 4;
    switch(event->type)
    {
	case ButtonPress:
	    reply.a_param[0] = 2 + (button<<1);
	    break;
	case ButtonRelease:
	    reply.a_param[0] = 3 + (button<<1);
	    break;
	default:
	    return( True );
    }
    /*
    * mask:
    * bit 7   bit 6   bit 5   bit 4   bit 3   bit 2       bit 1         bit 0
    *                                 M4 down left down   middle down   right down
    *
    * Notice that Button1 (left) and Button3 (right) are swapped in the mask.
    * Also, mask should be the state after the button press/release,
    * X provides the state not including the button press/release.
    */
    state = (event->xbutton.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask)) >> 8;
    state ^= 1 << button;	/* update mask to "after" state */
    state = (state & ~(4|1)) | ((state&1)?4:0) | ((state&4)?1:0);	/* swap Button1 & Button3 */

    reply.a_param[1] = state;
    reply.a_param[2] = row;
    reply.a_param[3] = col;
    reply.a_inters = '&';
    reply.a_final  = 'w';

    unparseseq(&reply, screen->respond);

    if( screen->locator_reset ) {
	MotionOff( screen, term );
	screen->send_mouse_pos = MOUSE_OFF;
    }

    /*
    * DECterm turns the Locator off if a button is pressed while a filter rectangle
    * is active. This might be a bug, but I don't know, so I'll emulate it anyways.
    */
    if( screen->loc_filter ) {
	screen->send_mouse_pos = MOUSE_OFF;
	screen->loc_filter = FALSE;
	screen->locator_events = 0;
	MotionOff( screen, term );
    }

    return( True );
}

/*
* mask:
* bit 7   bit 6   bit 5   bit 4   bit 3   bit 2       bit 1         bit 0
*                                 M4 down left down   middle down   right down
*
* Button1 (left) and Button3 (right) are swapped in the mask relative to X.
*/
#define	ButtonState(state, mask)	\
{ (state) = ((mask) & (Button1Mask | Button2Mask | Button3Mask | Button4Mask)) >> 8;	\
  /* swap Button1 & Button3 */								\
  (state) = ((state) & ~(4|1)) | (((state)&1)?4:0) | (((state)&4)?1:0);			\
}

void
GetLocatorPosition(XtermWidget w)
{
    TScreen		*screen = &w->screen;
    Window		root, child;
    int			rx, ry, x, y;
    unsigned int	mask;
    int			row = 0, col = 0;
    Boolean		oor = FALSE;
    Bool		ret = FALSE;
    int			state;

    /*
    * DECterm turns the Locator off if the position is requested while a filter rectangle
    * is active.  This might be a bug, but I don't know, so I'll emulate it anyways.
    */
    if( screen->loc_filter ) {
	screen->send_mouse_pos = MOUSE_OFF;
	screen->loc_filter = FALSE;
	screen->locator_events = 0;
	MotionOff( screen, term );
    }

    reply.a_type   = CSI;

    if (screen->send_mouse_pos == DEC_LOCATOR) {
	ret = XQueryPointer( screen->display, VWindow(screen), &root,
			&child, &rx, &ry, &x, &y, &mask );
	if (ret) {
	    LocatorCoords( row, col, x, y, oor );
	}
    }
    if( ret == FALSE || oor )
    {
	reply.a_nparam = 1;
	reply.a_param[0] = 0; /* Event - 0 = locator unavailable */
	reply.a_inters = '&';
	reply.a_final  = 'w';
	unparseseq(&reply, screen->respond);

	if( screen->locator_reset ) {
	    MotionOff( screen, term );
	    screen->send_mouse_pos = MOUSE_OFF;
	}
	return;
    }

    ButtonState( state, mask );

    reply.a_nparam = 4;
    reply.a_param[0] = 1; /* Event - 1 = response to locator request */
    reply.a_param[1] = state;
    reply.a_param[2] = row;
    reply.a_param[3] = col;
    reply.a_inters = '&';
    reply.a_final  = 'w';
    unparseseq(&reply, screen->respond);

    if( screen->locator_reset ) {
	MotionOff( screen, term );
	screen->send_mouse_pos = MOUSE_OFF;
    }
}

void
InitLocatorFilter( XtermWidget w )
{
    TScreen		*screen = &w->screen;
    Window		root, child;
    int			rx, ry, x, y;
    unsigned int	mask;
    int			row, col;
    Boolean		oor;
    Bool		ret;
    int			state;

    ret = XQueryPointer( screen->display, VWindow(screen),
			    &root, &child, &rx, &ry, &x, &y, &mask );
    if (ret) {
	LocatorCoords( row, col, x, y, oor );
    }
    if( ret == FALSE || oor )
    {
	/* Locator is unavailable */

	if( screen->loc_filter_top    != LOC_FILTER_POS ||
	    screen->loc_filter_left   != LOC_FILTER_POS ||
	    screen->loc_filter_bottom != LOC_FILTER_POS ||
	    screen->loc_filter_right  != LOC_FILTER_POS )
	{
	    /*
	    * If any explicit coordinates were received,
	    * report immediately with no coordinates.
	    */
	    reply.a_type   = CSI;
	    reply.a_nparam = 1;
	    reply.a_param[0] = 0; /* Event - 0 = locator unavailable */
	    reply.a_inters = '&';
	    reply.a_final  = 'w';
	    unparseseq(&reply, screen->respond);

	    if( screen->locator_reset ) {
		MotionOff( screen, term );
		screen->send_mouse_pos = MOUSE_OFF;
	    }
	} else {
	    /*
	    * No explicit coordinates were received, and the pointer is
	    * unavailable.  Report when the pointer re-enters the window.
	    */
	    screen->loc_filter = TRUE;
	    MotionOn( screen, term );
	}
	return;
    }

    /*
    * Adjust rectangle coordinates:
    *  1. Replace "LOC_FILTER_POS" with current coordinates
    *  2. Limit coordinates to screen size
    *  3. make sure top and left are less than bottom and right, resp.
    */
    if( screen->locator_pixels ) {
	rx = OriginX(screen)*2+Width(screen);
	ry = screen->border*2+Height(screen);
    } else {
	rx = screen->max_col;
	ry = screen->max_row;
    }

#define	Adjust( coord, def, max )				\
	if( (coord) == LOC_FILTER_POS )	(coord) = (def);	\
	else if ((coord) < 1)		(coord) = 1;		\
	else if ((coord) > (max))	(coord) = (max)

    Adjust( screen->loc_filter_top, row, ry );
    Adjust( screen->loc_filter_left, col, rx );
    Adjust( screen->loc_filter_bottom, row, ry );
    Adjust( screen->loc_filter_right, col, rx );

    if( screen->loc_filter_top > screen->loc_filter_bottom ) {
	ry = screen->loc_filter_top;
	screen->loc_filter_top = screen->loc_filter_bottom;
	screen->loc_filter_bottom = ry;
    }

    if( screen->loc_filter_left > screen->loc_filter_right ) {
	rx = screen->loc_filter_left;
	screen->loc_filter_left = screen->loc_filter_right;
	screen->loc_filter_right = rx;
    }

    if( (col < screen->loc_filter_left) ||
	(col > screen->loc_filter_right) ||
	(row < screen->loc_filter_top) ||
	(row > screen->loc_filter_bottom) )
    {
	/* Pointer is already outside the rectangle - report immediately */
	ButtonState( state, mask );

	reply.a_type   = CSI;
	reply.a_nparam = 4;
	reply.a_param[0] = 10; /* Event - 10 = locator outside filter */
	reply.a_param[1] = state;
	reply.a_param[2] = row;
	reply.a_param[3] = col;
	reply.a_inters = '&';
	reply.a_final  = 'w';
	unparseseq(&reply, screen->respond);

	if( screen->locator_reset ) {
	    MotionOff( screen, term );
	    screen->send_mouse_pos = MOUSE_OFF;
	}
	return;
    }

    /*
    * Rectangle is set up.  Allow pointer tracking
    * to detect if the mouse leaves the rectangle.
    */
    screen->loc_filter = TRUE;
    MotionOn( screen, term );
}

void
CheckLocatorPosition( Widget w, XEvent *event )
{
    TScreen		*screen = &((XtermWidget)w)->screen;
    int			row, col;
    Boolean		oor;
    int			state;

    LocatorCoords( row, col, event->xbutton.x, event->xbutton.y, oor );

    /*
    * Send report if the pointer left the filter rectangle, if
    * the pointer left the window, or if the filter rectangle
    * had no coordinates and the pointer re-entered the window.
    */
    if (oor || (screen->loc_filter_top == LOC_FILTER_POS) ||
	(col < screen->loc_filter_left)  ||
	(col > screen->loc_filter_right) ||
	(row < screen->loc_filter_top)   ||
	(row > screen->loc_filter_bottom))
    {
	/* Filter triggered - disable it */
	screen->loc_filter = FALSE;
	MotionOff( screen, term );

	reply.a_type   = CSI;
	if (oor) {
	    reply.a_nparam = 1;
	    reply.a_param[0] = 0; /* Event - 0 = locator unavailable */
	} else {
	    ButtonState( state, event->xbutton.state );

	    reply.a_nparam = 4;
	    reply.a_param[0] = 10; /* Event - 10 = locator outside filter */
	    reply.a_param[1] = state;
	    reply.a_param[2] = row;
	    reply.a_param[3] = col;
	}

	reply.a_inters = '&';
	reply.a_final  = 'w';
	unparseseq(&reply, screen->respond);

	if( screen->locator_reset ) {
	    MotionOff( screen, term );
	    screen->send_mouse_pos = MOUSE_OFF;
	}
    }
}
#endif	/* OPT_DEC_LOCATOR */

void
DiredButton(
	Widget w GCC_UNUSED,
	XEvent *event,			/* must be XButtonEvent */
	String *params GCC_UNUSED,	/* selections */
	Cardinal *num_params GCC_UNUSED)
{	/* ^XM-G<line+' '><col+' '> */
    register TScreen *screen = &term->screen;
    Char Line[ 6 ];
    register unsigned line, col;

    if (event->type == ButtonPress || event->type == ButtonRelease) {
	line = ( event->xbutton.y - screen->border ) / FontHeight( screen );
	col  = ( event->xbutton.x - OriginX(screen)) / FontWidth( screen );
	Line[0] = CONTROL('X');
	Line[1] = ESC;
	Line[2] = 'G';
	Line[3] = ' ' + col;
	Line[4] = ' ' + line;
	v_write(screen->respond, Line, 5 );
    }
}

void
ViButton(
	Widget w GCC_UNUSED,
	XEvent *event,			/* must be XButtonEvent */
	String *params GCC_UNUSED,	/* selections */
	Cardinal *num_params GCC_UNUSED)
{	/* ^XM-G<line+' '><col+' '> */
    register TScreen *screen = &term->screen;
    int pty = screen->respond;
    Char Line[ 6 ];
    register int line;

    if (event->type == ButtonPress || event->type == ButtonRelease) {

	line = screen->cur_row -
		(( event->xbutton.y - screen->border ) / FontHeight( screen ));
	if (line != 0) {
	    Line[0] = ESC;	/* force an exit from insert-mode */
	    v_write(pty, Line, 1 );

	    if ( line < 0 ) {
		line = -line;
		Line[0] = CONTROL('n');
	    } else {
		Line[0] = CONTROL('p');
	    }
	    while ( --line >= 0 )
		v_write(pty, Line, 1 );
	}
    }
}


/*
 * This function handles button-motion events
 */
/*ARGSUSED*/
void HandleSelectExtend(
	Widget w,
	XEvent *event,		/* must be XMotionEvent */
	String *params GCC_UNUSED,
	Cardinal *num_params GCC_UNUSED)
{
	register TScreen *screen;
	int row, col;

	if (!IsXtermWidget(w))
		return;

	screen = &((XtermWidget)w)->screen;
	screen->selection_time = event->xmotion.time;
	switch (eventMode) {
		/* If not in one of the DEC mouse-reporting modes */
		case LEFTEXTENSION :
		case RIGHTEXTENSION :
			PointToRowCol (event->xmotion.y, event->xmotion.x,
				       &row, &col);
			ExtendExtend (row, col);
			break;

		/* If in motion reporting mode, send mouse position to
		   character process as a key sequence \E[M... */
		case NORMAL :
			/* will get here if send_mouse_pos != MOUSE_OFF */
			if ( screen->send_mouse_pos == BTN_EVENT_MOUSE
			 ||  screen->send_mouse_pos == ANY_EVENT_MOUSE )
			    SendMousePosition(w,event);
			break;
	}
}

static void do_select_end (
	Widget w,
	XEvent *event,		/* must be XButtonEvent */
	String *params,		/* selections */
	Cardinal *num_params,
	Bool use_cursor_loc)
{
	if (!IsXtermWidget(w))
		return;

	((XtermWidget)w)->screen.selection_time = event->xbutton.time;
	switch (eventMode) {
		case NORMAL :
		    (void) SendMousePosition(w, event);
		    break;
		case LEFTEXTENSION :
		case RIGHTEXTENSION :
		    EndExtend(w, event, params, *num_params, use_cursor_loc);
		    break;
	}
}


void HandleSelectEnd(
	Widget w,
	XEvent *event,		/* must be XButtonEvent */
	String *params,		/* selections */
	Cardinal *num_params)
{
	do_select_end (w, event, params, num_params, False);
}


void HandleKeyboardSelectEnd(
	Widget w,
	XEvent *event,		/* must be XButtonEvent */
	String *params,		/* selections */
	Cardinal *num_params)
{
	do_select_end (w, event, params, num_params, True);
}

#if OPT_WIDE_CHARS
static Atom XA_UTF8_STRING(Display *dpy)
{
    static AtomPtr p = NULL;

    if(p == NULL)
	p = XmuMakeAtom("UTF8_STRING");
    return XmuInternAtom(dpy, p);
}
#endif

struct _SelectionList {
    String *params;
    Cardinal count;
    Bool utf8_failed;	/* only used for UTF-8, but a nuisance to ifdef */
    Time time;
};

/* convert a UTF-8 string to Latin-1, replacing non Latin-1 characters
 * by `#'. */

#if OPT_WIDE_CHARS
static XtPointer
UTF8toLatin1(Char *s, int len, unsigned long *result)
{
    static Char *buffer;
    static size_t used;

    Char *p = s;
    Char *q;

    if (used == 0) {
	buffer = (Char*)XtMalloc(used = len);
    } else if (len > (int) used) {
	buffer = (Char*)XtRealloc((char*)buffer, used = len);
    }
    q = buffer;

     /* We're assuming that the xterm widget never contains Unicode
	control characters. */

    while (p < s + len) {
	if ((*p & 0x80) == 0) {
	    *q++ = *p++;
	} else if ((*p & 0x7C) == 0x40 && p < s + len - 1) {
	    *q++ = (*p & 0x03) << 6 | (p[1] & 0x3F);
	    p += 2;
	} else if ((*p & 0x60) == 0x40) {
	    *q++ = '#';
	    p += 2;
	} else if ((*p & 0x50) == 0x40) {
	    *q++ = '#';
	    p += 3;
	} else {		/* this cannot happen */
	    *q++ = '#';
	    p++;
	}
    }
    *result = q - buffer;
    return (XtPointer)buffer;
}

/* Convert a Latin-1 string to UTF-8 */

static int
Latin1toUTF8(Char *t, Char *s, int len)
{
    Char *p = s;
    Char *q = t;
    while (p < s + len) {
	if ((*p & 0x80) == 0) {
	    *q++ = *p++;
	} else {
	    *q++ = 0xC0 | ((*p >> 6) & 0x3);
	    *q++ = 0x80 | (*p & 0x3F);
	    p++;
	}
    }
    return q - t;
}

/* Eliminate all control characters from a UTF-8 string, doing
   something reasonable with PS and LS */

static int
filterUTF8(Char *t, Char *s, int len)
{
    Char *p=s;
    Char *q=t;
    unsigned codepoint;
    int size;

    while (p < (s + len) && q < (t + len)) {
	if ((*p & 0x80) == 0) {
	    codepoint = *p & 0x7F;
	    size = 1;
	} else if ((*p & 0x60) == 0x40 && p < s + len - 1) {
	    codepoint = (p[0] & 0x1F) << 6 | (p[1] & 0x3F);
	    size = 2;
	} else if ((*p & 0x70) == 0x60 && p < s + len - 2) {
	    codepoint = (p[0] & 0x0F) << 12
		      | (p[1] & 0x3F) << 6
		      | (p[2] & 0x3F);
	    size = 3;
	} else if ((*p & 0x78) == 0x70 && p < s + len - 3) {
	    p += 4;		/* eliminate surrogates */
	    continue;
	} else if ((*p & 0x7C) == 0x78 && p < s + len - 4) {
	    p += 5;
	    continue;
	} else if ((*p & 0x7E) == 0x7C && p < s + len - 5) {
	    p += 6;
	    continue;
	} else {		/* wrong UTF-8?  Silently discard. */
	    p++;
	    continue;
	}

	if(codepoint == 0x2028) {
	    /* line separator -- replace by NL*/
	    p += size;
	    *q++ = 0x0A;
	} else if (codepoint == 0x2029) {
	    /* paragraph separator -- replace by NL NL */
	    p += size;
	    *q++ = 0x0A;
	    if (q < t + len)
		*q++ = 0x0A;
	} else if (codepoint >= 0x202A && codepoint <= 0x202E) {
	    /* ignore Unicode control characters; surrogates have already
	       been eliminated */
	    p += size;
	} else {
	    /* just copy the UTF-8 */
	  while (size--)
	    *q++ = *p++;
	}
    }
    return q - t;
}
#endif /* OPT_WIDE_CHARS */

static void _GetSelection(
	Widget w,
	Time ev_time,
	String *params,			/* selections in precedence order */
	Cardinal num_params,
	Bool utf8_failed GCC_UNUSED)	/* already tried UTF-8 */
{
    TScreen *screen;
    Atom selection;
    int cutbuffer;

    if (!IsXtermWidget(w))
	return;

    screen = &((XtermWidget)w)->screen;

    XmuInternStrings(XtDisplay(w), params, (Cardinal)1, &selection);
    switch (selection) {
      case XA_CUT_BUFFER0: cutbuffer = 0; break;
      case XA_CUT_BUFFER1: cutbuffer = 1; break;
      case XA_CUT_BUFFER2: cutbuffer = 2; break;
      case XA_CUT_BUFFER3: cutbuffer = 3; break;
      case XA_CUT_BUFFER4: cutbuffer = 4; break;
      case XA_CUT_BUFFER5: cutbuffer = 5; break;

      case XA_CUT_BUFFER6: cutbuffer = 6; break;
      case XA_CUT_BUFFER7: cutbuffer = 7; break;
      default:		   cutbuffer = -1;
    }
    TRACE(("Cutbuffer: %d, utf8_failed: %d\n", cutbuffer, utf8_failed))
    if (cutbuffer >= 0) {
	int inbytes;
	unsigned long nbytes;
	int fmt8 = 8;
	Atom type = XA_STRING;
	char *line = XFetchBuffer(XtDisplay(w), &inbytes, cutbuffer);
	nbytes = (unsigned long) inbytes;
	if (nbytes > 0)
	    SelectionReceived(w, NULL, &selection, &type, (XtPointer)line,
			      &nbytes, &fmt8);
	else if (num_params > 1)
	    _GetSelection(w, ev_time, params+1, num_params-1, False);
    } else {
	struct _SelectionList* list;
#if OPT_WIDE_CHARS
	if (!screen->wide_chars || utf8_failed) {
	    params++;
	    num_params--;
	    utf8_failed = False;
	} else {
	    utf8_failed = True;
	}
#else
	params++;
	num_params--;
#endif

	if (num_params) {
	    list = XtNew(struct _SelectionList);
	    list->params = params;
	    list->count = num_params;
#if OPT_WIDE_CHARS
	    list->utf8_failed = utf8_failed;
#endif
	    list->time = ev_time;
	} else list = NULL;
	    XtGetSelectionValue(w, selection,
#if OPT_WIDE_CHARS
				(screen->wide_chars && utf8_failed) ?
				XA_UTF8_STRING(XtDisplay(w)) :
#endif
				XA_STRING,
				SelectionReceived,
				(XtPointer)list, ev_time);
    }
}

#if OPT_TRACE && OPT_WIDE_CHARS
static void GettingSelection(char *tag, char *line, int len)
{
    char *cp;

    Trace("Getting %s\n", tag);
    for (cp = line; cp < line + len; cp++)
	Trace("%c\n", *cp);
}
#else
#define GettingSelection(tag,line,len) /* nothing */
#endif

/* SelectionReceived: stuff received selection text into pty */

/* ARGSUSED */
static void SelectionReceived(
	Widget w,
	XtPointer client_data,
	Atom *selection GCC_UNUSED,
	Atom *type,
	XtPointer value,
	unsigned long *length,
	int *format GCC_UNUSED)
{
    int pty;
    register Char *lag, *cp, *end;
    Char *line = (Char*)value;
    Char *buf;
    int len;
    TScreen *screen;

    if (!IsXtermWidget(w))
	return;
    screen = &((XtermWidget)w)->screen;

    pty = ((XtermWidget)w)->screen.respond;	/* file descriptor of pty */
    if (*type == 0 /*XT_CONVERT_FAIL*/ || *length == 0 || value == NULL) {
	/* could not get this selection, so see if there are more to try */
	struct _SelectionList* list = (struct _SelectionList*)client_data;
	if (list != NULL) {
	    _GetSelection(w, list->time,
			  list->params, list->count, list->utf8_failed);
	    XtFree((char *)client_data);
	}
	return;
    }

    buf = line;
    len = *length;

    if_OPT_WIDE_CHARS(screen,{
	if (*type == XA_UTF8_STRING(XtDisplay(w))) {
	    buf = (Char*)XtMalloc(*length);
	    GettingSelection("UTF8_STRING", line, *length);
	    len = filterUTF8(buf, line, *length);
	} else {
	    buf = (Char *)XtMalloc(2* *length);
	    GettingSelection("Latin-1", line, *length);
	    len = Latin1toUTF8(buf, line, *length);
	}
    })

    /* Write data to pty a line at a time. */
    /* Doing this one line at a time may no longer be necessary
       because v_write has been re-written. */

    end = &buf[len];
    lag = buf;
    for (cp = buf; cp != end; cp++)
    {
	if (*cp == '\n') {
	    *cp = '\r';
	    v_write(pty, lag, cp - lag + 1);
	    lag = cp + 1;
	}
    }
    if (lag != end)
	v_write(pty, lag, end - lag);

    if_OPT_WIDE_CHARS(screen,{
	XtFree((char*)buf);
    })
    XtFree((char *)client_data);
    XtFree((char *)value);
}


void
HandleInsertSelection(
	Widget w,
	XEvent *event,			/* assumed to be XButtonEvent* */
	String *params,			/* selections in precedence order */
	Cardinal *num_params)
{
    if (SendMousePosition(w, event)) return;
    _GetSelection(w, event->xbutton.time, params, *num_params, False);
}


static SelectUnit
EvalSelectUnit(Time buttonDownTime, SelectUnit defaultUnit)
{
    int delta;

    if (lastButtonUpTime == (Time) 0) /* first time and once in a blue moon */
	delta = term->screen.multiClickTime + 1;
    else if (buttonDownTime > lastButtonUpTime) /* most of the time */
	delta = buttonDownTime - lastButtonUpTime;
    else /* time has rolled over since lastButtonUpTime */
	delta = (((Time) ~0) - lastButtonUpTime) + buttonDownTime;

    if (delta > term->screen.multiClickTime) {
	numberOfClicks = 1;
	return defaultUnit;
    } else {
	++numberOfClicks;
	return ((selectUnit + 1) % NSELECTUNITS);
    }
}

static void do_select_start (
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	int startrow,
	int startcol)
{
	if (SendMousePosition(w, event)) return;
	selectUnit = EvalSelectUnit(event->xbutton.time, SELECTCHAR);
	replyToEmacs = FALSE;
	StartSelect(startrow, startcol);
}

/* ARGSUSED */
void
HandleSelectStart(
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	String *params GCC_UNUSED,
	Cardinal *num_params GCC_UNUSED)
{
	register TScreen *screen;
	int startrow, startcol;

	if (!IsXtermWidget(w))
		return;

	screen = &((XtermWidget)w)->screen;
	firstValidRow = 0;
	lastValidRow  = screen->max_row;
	PointToRowCol(event->xbutton.y, event->xbutton.x, &startrow, &startcol);
	do_select_start (w, event, startrow, startcol);
}


/* ARGSUSED */
void
HandleKeyboardSelectStart(
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	String *params GCC_UNUSED,
	Cardinal *num_params GCC_UNUSED)
{
	register TScreen *screen;

	if (!IsXtermWidget(w))
		return;

	screen = &((XtermWidget)w)->screen;
	do_select_start (w, event, screen->cursor_row, screen->cursor_col);
}


static void
TrackDown(register XButtonEvent *event)
{
	int startrow, startcol;

	selectUnit = EvalSelectUnit(event->time, SELECTCHAR);
	if (numberOfClicks > 1 ) {
		PointToRowCol(event->y, event->x, &startrow, &startcol);
		replyToEmacs = TRUE;
		StartSelect(startrow, startcol);
	} else {
		waitingForTrackInfo = 1;
		EditorButton((XButtonEvent *)event);
	}
}


#define boundsCheck(x)	if (x < 0) \
			    x = 0; \
			else if (x >= screen->max_row) \
			    x = screen->max_row;

void
TrackMouse(int func, int startrow, int startcol, int firstrow, int lastrow)
{
	TScreen *screen = &term->screen;

	if (!waitingForTrackInfo) {	/* Timed out, so ignore */
		return;
	}
	waitingForTrackInfo = 0;
	if (func == 0) return;
	boundsCheck (startrow)
	boundsCheck (firstrow)
	boundsCheck (lastrow)
	firstValidRow = firstrow;
	lastValidRow  = lastrow;
	replyToEmacs = TRUE;
	StartSelect(startrow, startcol);
}

static void
StartSelect(int startrow, int startcol)
{
	TScreen *screen = &term->screen;

	TRACE(("StartSelect row=%d, col=%d\n", startrow, startcol))
	if (screen->cursor_state)
	    HideCursor ();
	if (numberOfClicks == 1) {
		/* set start of selection */
		rawRow = startrow;
		rawCol = startcol;

	} /* else use old values in rawRow, Col */

	saveStartRRow = startERow = rawRow;
	saveStartRCol = startECol = rawCol;
	saveEndRRow   = endERow   = rawRow;
	saveEndRCol   = endECol   = rawCol;
	if (Coordinate(startrow, startcol) < Coordinate(rawRow, rawCol)) {
		eventMode = LEFTEXTENSION;
		startERow = startrow;
		startECol = startcol;
	} else {
		eventMode = RIGHTEXTENSION;
		endERow = startrow;
		endECol = startcol;
	}
	ComputeSelect(startERow, startECol, endERow, endECol, False);

}

static void
EndExtend(
	Widget w,
	XEvent *event,			/* must be XButtonEvent */
	String *params,			/* selections */
	Cardinal num_params,
	Bool use_cursor_loc)
{
	int	row, col, count;
	TScreen *screen = &term->screen;
	Char line[9];

	if (use_cursor_loc) {
	    row = screen->cursor_row;
	    col = screen->cursor_col;
	} else {
	    PointToRowCol(event->xbutton.y, event->xbutton.x, &row, &col);
	}
	ExtendExtend (row, col);
	lastButtonUpTime = event->xbutton.time;
	if (startSRow != endSRow || startSCol != endSCol) {
		if (replyToEmacs) {
			count = 0;
			if (screen->control_eight_bits) {
				line[count++] = CSI;
			} else {
				line[count++] = ESC;
				line[count++] = '[';
			}
			if (rawRow == startSRow && rawCol == startSCol
			    && row == endSRow && col == endSCol) {
				/* Use short-form emacs select */
				line[count++] = 't';
				line[count++] = ' ' + endSCol + 1;
				line[count++] = ' ' + endSRow + 1;
			} else {
				/* long-form, specify everything */
				line[count++] = 'T';
				line[count++] = ' ' + startSCol + 1;
				line[count++] = ' ' + startSRow + 1;
				line[count++] = ' ' + endSCol + 1;
				line[count++] = ' ' + endSRow + 1;
				line[count++] = ' ' + col + 1;
				line[count++] = ' ' + row + 1;
			}
			v_write(screen->respond, line, count);
			TrackText(0, 0, 0, 0);
		}
	}
	SelectSet(w, event, params, num_params);
	eventMode = NORMAL;
}

void
HandleSelectSet(
	Widget w,
	XEvent *event,
	String *params,
	Cardinal *num_params)
{
	SelectSet (w, event, params, *num_params);
}

/* ARGSUSED */
static void
SelectSet (
	Widget	w GCC_UNUSED,
	XEvent	*event GCC_UNUSED,
	String	*params,
	Cardinal    num_params)
{
	/* Only do select stuff if non-null select */
	if (startSRow != endSRow || startSCol != endSCol) {
		SaltTextAway(startSRow, startSCol, endSRow, endSCol,
			     params, num_params);
	} else
		DisownSelection(term);
}

#define Abs(x)		((x) < 0 ? -(x) : (x))

/* ARGSUSED */
static void do_start_extend (
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	String *params GCC_UNUSED,
	Cardinal *num_params GCC_UNUSED,
	Bool use_cursor_loc)
{
	TScreen *screen;
	int row, col, coord;

	if (!IsXtermWidget(w))
		return;

	screen = &((XtermWidget)w)->screen;
	if (SendMousePosition(w, event)) return;
	firstValidRow = 0;
	lastValidRow  = screen->max_row;
	selectUnit = EvalSelectUnit(event->xbutton.time, selectUnit);
	replyToEmacs = FALSE;

	if (numberOfClicks == 1) {
		/* Save existing selection so we can reestablish it if the guy
		   extends past the other end of the selection */
		saveStartRRow = startERow = startRRow;
		saveStartRCol = startECol = startRCol;
		saveEndRRow   = endERow   = endRRow;
		saveEndRCol   = endECol   = endRCol;
	} else {
		/* He just needed the selection mode changed, use old values. */
		startERow = startRRow = saveStartRRow;
		startECol = startRCol = saveStartRCol;
		endERow   = endRRow   = saveEndRRow;
		endECol   = endRCol   = saveEndRCol;

	}
	if (use_cursor_loc) {
	    row = screen->cursor_row;
	    col = screen->cursor_col;
	} else {
	    PointToRowCol(event->xbutton.y, event->xbutton.x, &row, &col);
	}
	coord = Coordinate(row, col);

	if (Abs(coord - Coordinate(startSRow, startSCol))
	     < Abs(coord - Coordinate(endSRow, endSCol))
	    || coord < Coordinate(startSRow, startSCol)) {
		/* point is close to left side of selection */
		eventMode = LEFTEXTENSION;
		startERow = row;
		startECol = col;
	} else {
		/* point is close to left side of selection */
		eventMode = RIGHTEXTENSION;
		endERow = row;
		endECol = col;
	}
	ComputeSelect(startERow, startECol, endERow, endECol, True);
}

static void
ExtendExtend (int row, int col)
{
	int coord = Coordinate(row, col);

	TRACE(("ExtendExtend row=%d, col=%d\n", row, col))
	if (eventMode == LEFTEXTENSION
	 && (coord + (selectUnit!=SELECTCHAR)) > Coordinate(endSRow, endSCol)) {
		/* Whoops, he's changed his mind.  Do RIGHTEXTENSION */
		eventMode = RIGHTEXTENSION;
		startERow = saveStartRRow;
		startECol = saveStartRCol;
	} else if (eventMode == RIGHTEXTENSION
	 && coord < Coordinate(startSRow, startSCol)) {
		/* Whoops, he's changed his mind.  Do LEFTEXTENSION */
		eventMode = LEFTEXTENSION;
		endERow   = saveEndRRow;
		endECol   = saveEndRCol;
	}
	if (eventMode == LEFTEXTENSION) {
		startERow = row;
		startECol = col;
	} else {
		endERow = row;
		endECol = col;
	}
	ComputeSelect(startERow, startECol, endERow, endECol, False);
}


void HandleStartExtend(
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	String *params,			/* unused */
	Cardinal *num_params)		/* unused */
{
    do_start_extend (w, event, params, num_params, False);
}

void HandleKeyboardStartExtend(
	Widget w,
	XEvent *event,			/* must be XButtonEvent* */
	String *params,			/* unused */
	Cardinal *num_params)		/* unused */
{
    do_start_extend (w, event, params, num_params, True);
}

void
ScrollSelection(register TScreen* screen, register int amount)
{
    register int minrow = -screen->savedlines - screen->topline;
    register int maxrow = screen->max_row - screen->topline;
    register int maxcol = screen->max_col;

#define scroll_update_one(row, col) \
	row += amount; \
	if (row < minrow) { \
	    row = minrow; \
	    col = 0; \
	} \
	if (row > maxrow) { \
	    row = maxrow; \
	    col = maxcol; \
	}

    scroll_update_one(startRRow, startRCol);
    scroll_update_one(endRRow,   endRCol);
    scroll_update_one(startSRow, startSCol);
    scroll_update_one(endSRow,   endSCol);

    scroll_update_one(rawRow, rawCol);

    scroll_update_one(screen->startHRow, screen->startHCol);
    scroll_update_one(screen->endHRow,   screen->endHCol);

    screen->startHCoord = Coordinate (screen->startHRow, screen->startHCol);
    screen->endHCoord   = Coordinate (screen->endHRow,   screen->endHCol);
}


/*ARGSUSED*/
void
ResizeSelection (TScreen *screen GCC_UNUSED, int rows, int cols)
{
    rows--;				/* decr to get 0-max */
    cols--;

    if (startRRow > rows) startRRow = rows;
    if (startSRow > rows) startSRow = rows;
    if (endRRow > rows) endRRow = rows;
    if (endSRow > rows) endSRow = rows;
    if (rawRow > rows) rawRow = rows;

    if (startRCol > cols) startRCol = cols;
    if (startSCol > cols) startSCol = cols;
    if (endRCol > cols) endRCol = cols;
    if (endSCol > cols) endSCol = cols;
    if (rawCol > cols) rawCol = cols;
}

static void
PointToRowCol(
    register int y,
    register int x,
    int *r,
    int *c)
/* Convert pixel coordinates to character coordinates.
   Rows are clipped between firstValidRow and lastValidRow.
   Columns are clipped between to be 0 or greater, but are not clipped to some
       maximum value. */
{
	register TScreen *screen = &term->screen;
	register int row, col;

	row = (y - screen->border) / FontHeight(screen);
	if(row < firstValidRow)
		row = firstValidRow;
	else if(row > lastValidRow)
		row = lastValidRow;
	col = (x - OriginX(screen)) / FontWidth(screen);
	if(col < 0)
		col = 0;
	else if(col > screen->max_col+1) {
		col = screen->max_col+1;
	}
	*r = row;
	*c = col;
}

static int
LastTextCol(register int row)
{
	register TScreen *screen =  &term->screen;
	register int i;
	register Char *ch;

	if ((row += screen->topline) + screen->savedlines >= 0) {
		for ( i = screen->max_col,
			ch = SCRN_BUF_ATTRS(screen, row) + i ;
		      i >= 0 && !(*ch & CHARDRAWN) ;
		      ch--, i--)
		    ;
#if OPT_DEC_CHRSET
		if (CSET_DOUBLE(SCRN_BUF_CSETS(screen, row)[0])) {
			i *= 2;
		}
#endif
	} else {
		i = -1;
	}
	return(i);
}

/*
** double click table for cut and paste in 8 bits
**
** This table is divided in four parts :
**
**	- control characters	[0,0x1f] U [0x80,0x9f]
**	- separators		[0x20,0x3f] U [0xa0,0xb9]
**	- binding characters	[0x40,0x7f] U [0xc0,0xff]
**	- exceptions
*/
static int charClass[256] = {
/* NUL  SOH  STX  ETX  EOT  ENQ  ACK  BEL */
    32,   1,   1,   1,   1,   1,   1,   1,
/*  BS   HT   NL   VT   NP   CR   SO   SI */
     1,  32,   1,   1,   1,   1,   1,   1,
/* DLE  DC1  DC2  DC3  DC4  NAK  SYN  ETB */
     1,   1,   1,   1,   1,   1,   1,   1,
/* CAN   EM  SUB  ESC   FS   GS   RS   US */
     1,   1,   1,   1,   1,   1,   1,   1,
/*  SP    !    "    #    $    %    &    ' */
    32,  33,  34,  35,  36,  37,  38,  39,
/*   (    )    *    +    ,    -    .    / */
    40,  41,  42,  43,  44,  45,  46,  47,
/*   0    1    2    3    4    5    6    7 */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   8    9    :    ;    <    =    >    ? */
    48,  48,  58,  59,  60,  61,  62,  63,
/*   @    A    B    C    D    E    F    G */
    64,  48,  48,  48,  48,  48,  48,  48,
/*   H    I    J    K    L    M    N    O */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   P    Q    R    S    T    U    V    W */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   X    Y    Z    [    \    ]    ^    _ */
    48,  48,  48,  91,  92,  93,  94,  48,
/*   `    a    b    c    d    e    f    g */
    96,  48,  48,  48,  48,  48,  48,  48,
/*   h    i    j    k    l    m    n    o */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   p    q    r    s    t    u    v    w */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   x    y    z    {    |    }    ~  DEL */
    48,  48,  48, 123, 124, 125, 126,   1,
/* x80  x81  x82  x83  IND  NEL  SSA  ESA */
     1,   1,   1,   1,   1,   1,   1,   1,
/* HTS  HTJ  VTS  PLD  PLU   RI  SS2  SS3 */
     1,   1,   1,   1,   1,   1,   1,   1,
/* DCS  PU1  PU2  STS  CCH   MW  SPA  EPA */
     1,   1,   1,   1,   1,   1,   1,   1,
/* x98  x99  x9A  CSI   ST  OSC   PM  APC */
     1,   1,   1,   1,   1,   1,   1,   1,
/*   -    i   c/    L   ox   Y-    |   So */
   160, 161, 162, 163, 164, 165, 166, 167,
/*  ..   c0   ip   <<    _        R0    - */
   168, 169, 170, 171, 172, 173, 174, 175,
/*   o   +-    2    3    '    u   q|    . */
   176, 177, 178, 179, 180, 181, 182, 183,
/*   ,    1    2   >>  1/4  1/2  3/4    ? */
   184, 185, 186, 187, 188, 189, 190, 191,
/*  A`   A'   A^   A~   A:   Ao   AE   C, */
    48,  48,  48,  48,  48,  48,  48,  48,
/*  E`   E'   E^   E:   I`   I'   I^   I: */
    48,  48,  48,  48,  48,  48,  48,  48,
/*  D-   N~   O`   O'   O^   O~   O:    X */
    48,  48,  48,  48,  48,  48,  48, 216,
/*  O/   U`   U'   U^   U:   Y'    P    B */
    48,  48,  48,  48,  48,  48,  48,  48,
/*  a`   a'   a^   a~   a:   ao   ae   c, */
    48,  48,  48,  48,  48,  48,  48,  48,
/*  e`   e'   e^   e:    i`  i'   i^   i: */
    48,  48,  48,  48,  48,  48,  48,  48,
/*   d   n~   o`   o'   o^   o~   o:   -: */
    48,  48,  48,  48,  48,  48,  48,  248,
/*  o/   u`   u'   u^   u:   y'    P   y: */
    48,  48,  48,  48,  48,  48,  48,  48};

int SetCharacterClassRange (
    register int low,			/* in range of [0..255] */
    register int high,
    register int value)			/* arbitrary */
{

    if (low < 0 || high > 255 || high < low) return (-1);

    for (; low <= high; low++) charClass[low] = value;

    return (0);
}

#if OPT_WIDE_CHARS
static int class_of(TScreen *screen, int row, int col)
{
    unsigned value;
#if OPT_DEC_CHRSET
    if (CSET_DOUBLE(SCRN_BUF_CSETS(screen, row + screen->topline)[0])) {
	col /= 2;
    }
#endif
    value = XTERM_CELL(row, col);
    if_OPT_WIDE_CHARS(screen,{
	/*FIXME: extend the character-class table */
    })
    return charClass[value & ((sizeof(charClass)/sizeof(charClass[0]))-1)];
}
#else
#define class_of(screen,row,col) charClass[XTERM_CELL(row, col)]
#endif

/*
 * sets startSRow startSCol endSRow endSCol
 * ensuring that they have legal values
 */

static void
ComputeSelect(
	int startRow,
	int startCol,
	int endRow,
	int endCol,
	Bool extend)
{
	register TScreen *screen = &term->screen;
	register int length;
	register int cclass;

	if (Coordinate(startRow, startCol) <= Coordinate(endRow, endCol)) {
		startSRow = startRRow = startRow;
		startSCol = startRCol = startCol;
		endSRow   = endRRow   = endRow;
		endSCol   = endRCol   = endCol;
	} else {	/* Swap them */
		startSRow = startRRow = endRow;
		startSCol = startRCol = endCol;
		endSRow   = endRRow   = startRow;
		endSCol   = endRCol   = startCol;
	}

	switch (selectUnit) {
		case SELECTCHAR :
			if (startSCol > (LastTextCol(startSRow) + 1)) {
				startSCol = 0;
				startSRow++;
			}
			if (endSCol > (LastTextCol(endSRow) + 1)) {
				endSCol = 0;
				endSRow++;
			}
			break;
		case SELECTWORD :
			if (startSCol > (LastTextCol(startSRow) + 1)) {
				startSCol = 0;
				startSRow++;
			} else {
				cclass = class_of(screen,startSRow,startSCol);
				do {
				    --startSCol;
				    if (startSCol <= 0
				     && ScrnTstWrapped(screen, startSRow - 1)) {
					--startSRow;
					startSCol = LastTextCol(startSRow);
				    }
				} while (startSCol >= 0
				 && class_of(screen,startSRow,startSCol) == cclass);
				++startSCol;
			}
			if (endSCol > (LastTextCol(endSRow) + 1)) {
				endSCol = 0;
				endSRow++;
			} else {
				length = LastTextCol(endSRow);
				cclass = class_of(screen,endSRow,endSCol);
				do {
				    ++endSCol;
				    if (endSCol > length
				     && ScrnTstWrapped(screen, endSRow)) {
					endSCol = 0;
					++endSRow;
					length = LastTextCol(endSRow);
				    }
				} while (endSCol <= length
				 && class_of(screen,endSRow,endSCol) == cclass);
				/* Word select selects if pointing to any char
				   in "word", especially in that it includes
				   the last character in a word.  So no --endSCol
				   and do special eol handling */
				if (endSCol > length+1) {
					endSCol = 0;
					++endSRow;
				}
			}
			saveStartWRow = startSRow;
			saveStartWCol = startSCol;
			break;
		case SELECTLINE :
			while (ScrnTstWrapped(screen, endSRow)) {
				++endSRow;
			}
			if (term->screen.cutToBeginningOfLine
			    || startSRow < saveStartWRow) {
			    startSCol = 0;
			    while (ScrnTstWrapped(screen, startSRow - 1)) {
				--startSRow;
			    }
			} else if (!extend) {
			    if ((startRow < saveStartWRow)
				|| (startRow == saveStartWRow
				    && startCol < saveStartWCol)) {
				startSCol = 0;
				while (ScrnTstWrapped(screen, startSRow - 1)) {
				    --startSRow;
				}
			    } else {
				startSRow = saveStartWRow;
				startSCol = saveStartWCol;
			    }
			}
			if (term->screen.cutNewline) {
			    endSCol = 0;
			    ++endSRow;
			} else {
			    endSCol = LastTextCol(endSRow) + 1;
			}
			break;
	}

	TrackText(startSRow, startSCol, endSRow, endSCol);
	return;
}

void
TrackText(
	register int frow,
	register int fcol,
	register int trow,
	register int tcol)
    /* Guaranteed (frow, fcol) <= (trow, tcol) */
{
	register int from, to;
	register TScreen *screen = &term->screen;
	int old_startrow, old_startcol, old_endrow, old_endcol;

	old_startrow = screen->startHRow;
	old_startcol = screen->startHCol;
	old_endrow = screen->endHRow;
	old_endcol = screen->endHCol;
	if (frow == old_startrow && fcol == old_startcol &&
	    trow == old_endrow   && tcol == old_endcol) return;
	screen->startHRow = frow;
	screen->startHCol = fcol;
	screen->endHRow   = trow;
	screen->endHCol   = tcol;
	from = Coordinate(frow, fcol);
	to = Coordinate(trow, tcol);
	if (to <= screen->startHCoord || from > screen->endHCoord) {
	    /* No overlap whatsoever between old and new hilite */
	    ReHiliteText(old_startrow, old_startcol, old_endrow, old_endcol);
	    ReHiliteText(frow, fcol, trow, tcol);
	} else {
	    if (from < screen->startHCoord) {
		    /* Extend left end */
		    ReHiliteText(frow, fcol, old_startrow, old_startcol);
	    } else if (from > screen->startHCoord) {
		    /* Shorten left end */
		    ReHiliteText(old_startrow, old_startcol, frow, fcol);
	    }
	    if (to > screen->endHCoord) {
		    /* Extend right end */
		    ReHiliteText(old_endrow, old_endcol, trow, tcol);
	    } else if (to < screen->endHCoord) {
		    /* Shorten right end */
		    ReHiliteText(trow, tcol, old_endrow, old_endcol);
	    }
	}
	screen->startHCoord = from;
	screen->endHCoord = to;
}

static void
ReHiliteText(
    register int frow,
    register int fcol,
    register int trow,
    register int tcol)
    /* Guaranteed that (frow, fcol) <= (trow, tcol) */
{
	register TScreen *screen = &term->screen;
	register int i;

	if (frow < 0)
	    frow = fcol = 0;
	else if (frow > screen->max_row)
	    return;		/* nothing to do, since trow >= frow */

	if (trow < 0)
	    return;		/* nothing to do, since frow <= trow */
	else if (trow > screen->max_row) {
	    trow = screen->max_row;
	    tcol = screen->max_col+1;
	}
	if (frow == trow && fcol == tcol)
		return;

	if(frow != trow) {	/* do multiple rows */
		if((i = screen->max_col - fcol + 1) > 0) {     /* first row */
		    ScrnRefresh(screen, frow, fcol, 1, i, True);
		}
		if((i = trow - frow - 1) > 0) {		       /* middle rows*/
		    ScrnRefresh(screen, frow+1, 0,i, screen->max_col+1, True);
		}
		if(tcol > 0 && trow <= screen->max_row) {      /* last row */
		    ScrnRefresh(screen, trow, 0, 1, tcol, True);
		}
	} else {		/* do single row */
		ScrnRefresh(screen, frow, fcol, 1, tcol - fcol, True);
	}
}

static void
SaltTextAway(
    int crow, int ccol, int row, int col,
    String *params,			/* selections */
    Cardinal num_params)
    /* Guaranteed that (crow, ccol) <= (row, col), and that both points are valid
       (may have row = screen->max_row+1, col = 0) */
{
    register TScreen *screen = &term->screen;
    register int i, j = 0;
    int eol;
    char *line;
    Char *lp;

    if (crow == row && ccol > col) {
	int tmp = ccol;
	ccol = col;
	col = tmp;
    }

    --col;
    /* first we need to know how long the string is before we can save it*/

    if ( row == crow ) {
	j = Length(screen, crow, ccol, col);
    } else { /* two cases, cut is on same line, cut spans multiple lines */
	j += Length(screen, crow, ccol, screen->max_col) + 1;
	for (i = crow + 1; i < row; i++)
	    j += Length(screen, i, 0, screen->max_col) + 1;
	if (col >= 0)
	    j += Length(screen, row, 0, col);
    }

    /* UTF-8 may require more space */
    if_OPT_WIDE_CHARS(screen,{j *= 4;})

    /* now get some memory to save it in */

    if (screen->selection_size <= j) {
	if((line = (char *)malloc((unsigned) j + 1)) == 0)
	    SysError(ERROR_BMALLOC2);
	XtFree(screen->selection_data);
	screen->selection_data = line;
	screen->selection_size = j + 1;
    } else {
	line = screen->selection_data;
    }

    if ((line == 0)
     || (j < 0))
	return;

    line[j] = '\0';		/* make sure it is null terminated */
    lp = (Char *)line;		/* lp points to where to save the text */
    if ( row == crow ) {
	lp = SaveText(screen, row, ccol, col, lp, &eol);
    } else {
	lp = SaveText(screen, crow, ccol, screen->max_col, lp, &eol);
	if (eol)
	    *lp ++ = '\n';	/* put in newline at end of line */
	for(i = crow +1; i < row; i++) {
	    lp = SaveText(screen, i, 0, screen->max_col, lp, &eol);
	    if (eol)
		*lp ++ = '\n';
	}
	if (col >= 0)
	    lp = SaveText(screen, row, 0, col, lp, &eol);
    }
    *lp = '\0';			/* make sure we have end marked */

    TRACE(("Salted TEXT:%.*s\n", (char *)lp - line, line))
    screen->selection_length = ((char *)lp - line);
    _OwnSelection(term, params, num_params);
}

static Boolean
ConvertSelection(
	Widget w,
	Atom *selection,
	Atom *target,
	Atom *type,
	XtPointer *value,
	unsigned long *length,
	int *format)
{
    Display* d = XtDisplay(w);
    TScreen *screen;

    if (!IsXtermWidget(w))
	return False;

    screen = &((XtermWidget)w)->screen;

    if (screen->selection_data == NULL)
	return False;		/* can this happen? */

    if (*target == XA_TARGETS(d)) {
	Atom* targetP;
	Atom* std_targets;
	unsigned long std_length;
	XmuConvertStandardSelection(w, screen->selection_time, selection,
				    target, type, (XPointer *)&std_targets,
				    &std_length, format);
	*length = std_length + 5;
	targetP = (Atom*)XtMalloc(sizeof(Atom)*(*length));
	*value = (XtPointer) targetP;
	*targetP++ = XA_STRING;
	*targetP++ = XA_TEXT(d);
	*targetP = XA_COMPOUND_TEXT(d);
	if_OPT_WIDE_CHARS(screen, {
	  *targetP = XA_UTF8_STRING(d);
	})
	targetP++;
	*targetP++ = XA_LENGTH(d);
	*targetP++ = XA_LIST_LENGTH(d);
	memcpy ( (char*)targetP, (char*)std_targets, sizeof(Atom)*std_length);
	XtFree((char*)std_targets);
	*type = XA_ATOM;
	*format = 32;
	return True;
    }

    if_OPT_WIDE_CHARS(screen,{
	if (*target == XA_STRING) {
	    *value = UTF8toLatin1((Char*)screen->selection_data,
				  screen->selection_length, length);
	    *type = XA_STRING;
	    *format = 8;
	    return True;
	}
	if (*target == XA_TEXT(d)) {
	    char *p;
	    /* walk the string, searching for non ISO 8859-1 characters */
	    for (p = screen->selection_data;
		    p < screen->selection_data+screen->selection_length;
		    p++) {
		if ((*p & 0xC0) == 0xC0
		 && ((*p & 0x20) || (!(*p & 0x20) && (*p & 0x1C))))
		    break;
	    }
	    if (p < screen->selection_data + screen->selection_length) {
		/* non ISO 8859-1 character found -- return UTF-8 */
		*type = XA_UTF8_STRING(d);
		*value = screen->selection_data;
		*length = screen->selection_length;
		*format = 8;
	    } else {
		/* none found -- return STRING */
		*value = UTF8toLatin1((Char*)screen->selection_data,
				      screen->selection_length, length);
		*type = XA_STRING;
		*format = 8;
	    }
	    return True;
	}
	if (*target == XA_UTF8_STRING(d)) {
	    *type = XA_UTF8_STRING(d);
	    *value = screen->selection_data;
	    *length = screen->selection_length;
	    *format = 8;
	    return True;
	}
    })

    /* We covered the XA_TEXT and XA_STRING cases for wide_chars above */
    if ((*target == XA_TEXT(d) || *target == XA_STRING)) {
	*type = XA_STRING;
	*value = screen->selection_data;
	*length = screen->selection_length;
	*format = 8;
	return True;
    }

    if (*target == XA_COMPOUND_TEXT(d)
#if OPT_WIDE_CHARS
    && !screen->wide_chars
#endif
	) {
	XTextProperty textprop;

	*value = (XtPointer) screen->selection_data;
	if (XmbTextListToTextProperty (d, (char**)value, 1,
				       XCompoundTextStyle,
				       &textprop) < Success)
	    return False;
	*value = (XtPointer) textprop.value;
	*length = textprop.nitems;
	*type = XA_COMPOUND_TEXT(d);
	*format = 8;
	return True;
    }

    if (*target == XA_LIST_LENGTH(d)) {
	*value = XtMalloc(4);
	if (sizeof(long) == 4)
	    *(long*)*value = 1;
	else {
	    long temp = 1;
	    memcpy ( (char*)*value, ((char*)&temp)+sizeof(long)-4, 4);
	}
	*type = XA_INTEGER;
	*length = 1;
	*format = 32;
	return True;
    }

    if (*target == XA_LENGTH(d)) {
	/* This value is wrong if we have UTF-8 text */
	*value = XtMalloc(4);
	if (sizeof(long) == 4)
	    *(long*)*value = screen->selection_length;
	else {
	    long temp = screen->selection_length;
	    memcpy ( (char*)*value, ((char*)&temp)+sizeof(long)-4, 4);
	}
	*type = XA_INTEGER;
	*length = 1;
	*format = 32;
	return True;
    }

    if (XmuConvertStandardSelection(w, screen->selection_time, selection,
				    target, type, (XPointer *)value,
				    length, format))
	return True;

    /* else */
    return False;
}


static void
LoseSelection(Widget w, Atom *selection)
{
    register TScreen* screen;
    register Atom* atomP;
    Cardinal i;

    if (!IsXtermWidget(w))
	return;

    screen = &((XtermWidget)w)->screen;
    for (i = 0, atomP = screen->selection_atoms;
	 i < screen->selection_count; i++, atomP++)
    {
	if (*selection == *atomP) *atomP = (Atom)0;
	switch (*atomP) {
	  case XA_CUT_BUFFER0:
	  case XA_CUT_BUFFER1:
	  case XA_CUT_BUFFER2:
	  case XA_CUT_BUFFER3:
	  case XA_CUT_BUFFER4:
	  case XA_CUT_BUFFER5:
	  case XA_CUT_BUFFER6:
	  case XA_CUT_BUFFER7:	*atomP = (Atom)0;
	}
    }

    for (i = screen->selection_count; i; i--) {
	if (screen->selection_atoms[i-1] != 0) break;
    }
    screen->selection_count = i;

    for (i = 0, atomP = screen->selection_atoms;
	 i < screen->selection_count; i++, atomP++)
    {
	if (*atomP == (Atom)0) {
	    *atomP = screen->selection_atoms[--screen->selection_count];
	}
    }

    if (screen->selection_count == 0)
	TrackText(0, 0, 0, 0);
}


/* ARGSUSED */
static void SelectionDone(
	Widget w GCC_UNUSED,
	Atom *selection GCC_UNUSED,
	Atom *target GCC_UNUSED)
{
    /* empty proc so Intrinsics know we want to keep storage */
}


static void
_OwnSelection(
	register XtermWidget termw,
	String *selections,
	Cardinal count)
{
    Atom* atoms = termw->screen.selection_atoms;
    Cardinal i;
    Boolean have_selection = False;

    if (termw->screen.selection_length < 0) return;

    if (count > termw->screen.sel_atoms_size) {
	XtFree((char*)atoms);
	atoms = (Atom*)XtMalloc(count*sizeof(Atom));
	termw->screen.selection_atoms = atoms;
	termw->screen.sel_atoms_size = count;
    }
    XmuInternStrings( XtDisplay((Widget)termw), selections, count, atoms );
    for (i = 0; i < count; i++) {
	int cutbuffer;
	switch (atoms[i]) {
	  case XA_CUT_BUFFER0: cutbuffer = 0; break;
	  case XA_CUT_BUFFER1: cutbuffer = 1; break;
	  case XA_CUT_BUFFER2: cutbuffer = 2; break;
	  case XA_CUT_BUFFER3: cutbuffer = 3; break;
	  case XA_CUT_BUFFER4: cutbuffer = 4; break;
	  case XA_CUT_BUFFER5: cutbuffer = 5; break;
	  case XA_CUT_BUFFER6: cutbuffer = 6; break;
	  case XA_CUT_BUFFER7: cutbuffer = 7; break;
	  default:	       cutbuffer = -1;
	}
	if (cutbuffer >= 0) {
	    if ( termw->screen.selection_length >
		 4*XMaxRequestSize(XtDisplay((Widget)termw))-32) {
		fprintf(stderr,
			"%s: selection too big (%d bytes), not storing in CUT_BUFFER%d\n",
			xterm_name, termw->screen.selection_length, cutbuffer);
	    } else {
	      /* Cutbuffers are untyped, so in the wide chars case, we
		 just store the raw UTF-8 data.	 It is unlikely it
		 will be useful to anyone. */
		XStoreBuffer( XtDisplay((Widget)termw),
			      termw->screen.selection_data,
			      termw->screen.selection_length, cutbuffer );
	    }
	} else if (!replyToEmacs) {
	    have_selection |=
		XtOwnSelection( (Widget)termw, atoms[i],
			    termw->screen.selection_time,
			    ConvertSelection, LoseSelection, SelectionDone );
	}
    }
    if (!replyToEmacs)
	termw->screen.selection_count = count;
    if (!have_selection)
	TrackText(0, 0, 0, 0);
}

void
DisownSelection(register XtermWidget termw)
{
    Atom* atoms = termw->screen.selection_atoms;
    Cardinal count = termw->screen.selection_count;
    Cardinal i;

    for (i = 0; i < count; i++) {
	int cutbuffer;
	switch (atoms[i]) {
	  case XA_CUT_BUFFER0: cutbuffer = 0; break;
	  case XA_CUT_BUFFER1: cutbuffer = 1; break;
	  case XA_CUT_BUFFER2: cutbuffer = 2; break;
	  case XA_CUT_BUFFER3: cutbuffer = 3; break;
	  case XA_CUT_BUFFER4: cutbuffer = 4; break;
	  case XA_CUT_BUFFER5: cutbuffer = 5; break;
	  case XA_CUT_BUFFER6: cutbuffer = 6; break;
	  case XA_CUT_BUFFER7: cutbuffer = 7; break;
	  default:	       cutbuffer = -1;
	}
	if (cutbuffer < 0)
	    XtDisownSelection( (Widget)termw, atoms[i],
			       termw->screen.selection_time );
    }
    termw->screen.selection_count = 0;
    termw->screen.startHRow = termw->screen.startHCol = 0;
    termw->screen.endHRow = termw->screen.endHCol = 0;
}


/* returns number of chars in line from scol to ecol out */
/* ARGSUSED */
static int
Length(
    register TScreen *screen GCC_UNUSED,
    register int row,
    register int scol,
    register int ecol)
{
	register int lastcol = LastTextCol(row);

	if (ecol > lastcol)
	    ecol = lastcol;
	return (ecol - scol + 1);
}

/* copies text into line, preallocated */
static Char *
SaveText(
    TScreen *screen,
    int row,
    int scol,
    int ecol,
    register Char *lp,		/* pointer to where to put the text */
    int *eol)
{
    int i = 0;
    unsigned c;
    Char *result = lp;

    i = Length(screen, row, scol, ecol);
    ecol = scol + i;
#if OPT_DEC_CHRSET
    if (CSET_DOUBLE(SCRN_BUF_CSETS(screen, row + screen->topline)[0])) {
	scol = (scol + 0) / 2;
	ecol = (ecol + 1) / 2;
    }
#endif
    *eol = !ScrnTstWrapped(screen, row);
    for (i = scol; i < ecol; i++) {
	c = E2A(XTERM_CELL(row, i));
#if OPT_WIDE_CHARS
	if (screen->utf8_mode)
	    lp = convertToUTF8(lp, c);
	else
#endif
	{
	    if (c == 0) {
		c = E2A(' ');
	    } else if (c < E2A(' ')) {
		if (c == XPOUND)
		    c = 0x23;	/* char on screen is pound sterling */
		else
		    c += 0x5f;	/* char is from DEC drawing set */
	    } else if (c == 0x7f) {
		c = 0x5f;
	    }
	    *lp++ = A2E(c);
	}
	if (c != E2A(' '))
	    result = lp;
    }

    /*
     * If requested, trim trailing blanks from selected lines.  Do not do this
     * if the line is wrapped.
     */
    if (!*eol || !screen->trim_selection)
	result = lp;

    return(result);
}

static int
BtnCode(XButtonEvent *event, int button)
{
	if (button < 0 || button > 5)
		button = 3;
	return ' ' + (KeyState(event->state) << 2) + button;
}

#define MOUSE_LIMIT (255 - 32)

static void
EditorButton(register XButtonEvent *event)
{
	TScreen *screen = &term->screen;
	int pty = screen->respond;
	Char line[6];
	int row, col;
	int button, count = 0;

	/* If button event, get button # adjusted for DEC compatibility */
	button = event->button - 1;
	if (button >= 3) button++;

	/* Compute character position of mouse pointer */
	row = (event->y - screen->border) / FontHeight(screen);
	col = (event->x - OriginX(screen)) / FontWidth(screen);

	/* Limit to screen dimensions */
	if (row < 0)
		row = 0;
	else if (row > screen->max_row)
		row = screen->max_row;
	else if (row > MOUSE_LIMIT)
		row = MOUSE_LIMIT;

	if (col < 0)
		col = 0;
	else if (col > screen->max_col)
		col = screen->max_col;
	else if (col > MOUSE_LIMIT)
		col = MOUSE_LIMIT;

	/* Build key sequence starting with \E[M */
	if (screen->control_eight_bits) {
		line[count++] = CSI;
	} else {
		line[count++] = ESC;
		line[count++] = '[';
	}
	line[count++] = 'M';

	/* Add event code to key sequence */
	if (screen->send_mouse_pos == X10_MOUSE) {
		line[count++] = ' ' + button;
	}
	else
	{
	    /* Button-Motion events */
	    switch(event->type)
	    {
	    case ButtonPress:
		line[count++] = BtnCode(event, screen->mouse_button = button);
		break;
	    case ButtonRelease:
		/*
		 * Wheel mouse interface generates release-events for buttons
		 * 4 and 5, coded here as 3 and 4 respectively.  We change the
		 * release for buttons 1..3 to a -1.
		 */
		if (button < 3)
			button = -1;
		line[count++] = BtnCode(event, screen->mouse_button = button);
		break;
	    case MotionNotify:
		/* BTN_EVENT_MOUSE and ANY_EVENT_MOUSE modes send motion
		 * events only if character cell has changed.
		 */
		if ((row == screen->mouse_row)
		 && (col == screen->mouse_col))
			return;
		line[count++] = BtnCode(event, screen->mouse_button) + 32;
		break;
	    default:
		return;
	    }
	}

	screen->mouse_row = row;
	screen->mouse_col = col;

	/* Add pointer position to key sequence */
	line[count++] = ' ' + col + 1;
	line[count++] = ' ' + row + 1;

	TRACE(("mouse at %d,%d button+mask = %#x\n", row, col,
		(screen->control_eight_bits) ? line[2] : line[3]))

	/* Transmit key sequence to process running under xterm */
	v_write(pty, line, count);
}


/*ARGSUSED*/
#if OPT_TEK4014
void HandleGINInput (
    Widget w GCC_UNUSED,
    XEvent *event GCC_UNUSED,
    String *param_list,
    Cardinal *nparamsp)
{
    if (term->screen.TekGIN && *nparamsp == 1) {
	int c = param_list[0][0];
	switch (c) {
	  case 'l': case 'm': case 'r':
	  case 'L': case 'M': case 'R':
	    break;
	  default:
	    Bell (XkbBI_MinorError,0);	/* let them know they goofed */
	    c = 'l';				/* provide a default */
	}
	TekEnqMouse (c | 0x80);
	TekGINoff();
    } else {
	Bell (XkbBI_MinorError,0);
    }
}
#endif /* OPT_TEK4014 */


/* ARGSUSED */
void HandleSecure(
    Widget w GCC_UNUSED,
    XEvent *event,			/* unused */
    String *params GCC_UNUSED,		/* [0] = volume */
    Cardinal *param_count GCC_UNUSED)	/* 0 or 1 */
{
    Time ev_time = CurrentTime;

    if ((event->xany.type == KeyPress) ||
	(event->xany.type == KeyRelease))
	ev_time = event->xkey.time;
    else if ((event->xany.type == ButtonPress) ||
	     (event->xany.type == ButtonRelease))
	ev_time = event->xbutton.time;
    DoSecureKeyboard (ev_time);
}
