button.c revision f2e35a3a
1/* $XTermId: button.c,v 1.636 2021/02/10 01:14:51 tom Exp $ */
2
3/*
4 * Copyright 1999-2020,2021 by Thomas E. Dickey
5 *
6 *                         All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 *
32 *
33 * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
34 *
35 *                         All Rights Reserved
36 *
37 * Permission to use, copy, modify, and distribute this software and its
38 * documentation for any purpose and without fee is hereby granted,
39 * provided that the above copyright notice appear in all copies and that
40 * both that copyright notice and this permission notice appear in
41 * supporting documentation, and that the name of Digital Equipment
42 * Corporation not be used in advertising or publicity pertaining to
43 * distribution of the software without specific, written prior permission.
44 *
45 *
46 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
47 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
48 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
49 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
50 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
51 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
52 * SOFTWARE.
53 */
54
55/*
56button.c	Handles button events in the terminal emulator.
57		does cut/paste operations, change modes via menu,
58		passes button events through to some applications.
59				J. Gettys.
60*/
61
62#include <xterm.h>
63
64#include <stdio.h>
65#include <ctype.h>
66#include <assert.h>
67
68#include <X11/Xatom.h>
69#include <X11/Xmu/Atoms.h>
70#include <X11/Xmu/StdSel.h>
71
72#include <xutf8.h>
73#include <fontutils.h>
74
75#include <data.h>
76#include <error.h>
77#include <menu.h>
78#include <charclass.h>
79#include <xstrings.h>
80
81#if OPT_SELECT_REGEX
82#ifdef HAVE_PCRE2POSIX_H
83#include <pcre2posix.h>
84#else
85#ifdef HAVE_PCREPOSIX_H
86#include <pcreposix.h>
87#else /* POSIX regex.h */
88#include <sys/types.h>
89#include <regex.h>
90#endif
91#endif
92#endif
93
94#ifdef HAVE_X11_TRANSLATEI_H
95#include <X11/ConvertI.h>
96#include <X11/TranslateI.h>
97#else
98extern String _XtPrintXlations(Widget w,
99			       XtTranslations xlations,
100			       Widget accelWidget,
101			       _XtBoolean includeRHS);
102#endif
103
104#define PRIMARY_NAME    "PRIMARY"
105#define CLIPBOARD_NAME  "CLIPBOARD"
106#define SECONDARY_NAME  "SECONDARY"
107
108#define AtomToSelection(d,n) \
109		 (((n) == XA_CLIPBOARD(d)) \
110		  ? CLIPBOARD_CODE \
111		  : (((n) == XA_SECONDARY) \
112		     ? SECONDARY_CODE \
113		     : PRIMARY_CODE))
114
115#define isSelectionCode(n) ((n) >= PRIMARY_CODE)
116#define CutBufferToCode(n) ((n) +  MAX_SELECTION_CODES)
117#define okSelectionCode(n) (isSelectionCode(n) ? (n) : PRIMARY_CODE)
118
119#if OPT_WIDE_CHARS
120#include <ctype.h>
121#include <wcwidth.h>
122#else
123#define CharacterClass(value) \
124	charClass[(value) & (int)((sizeof(charClass)/sizeof(charClass[0]))-1)]
125#endif
126
127    /*
128     * We'll generally map rows to indices when doing selection.
129     * Simplify that with a macro.
130     *
131     * Note that ROW2INX() is safe to use with auto increment/decrement for
132     * the row expression since that is evaluated once.
133     */
134#define GET_LINEDATA(screen, row) \
135	getLineData(screen, ROW2INX(screen, row))
136
137#define MaxMouseBtn  5
138
139#define IsBtnEvent(event) ((event)->type == ButtonPress || (event)->type == ButtonRelease)
140#define IsKeyEvent(event) ((event)->type == KeyPress    || (event)->type == KeyRelease)
141
142#define	Coordinate(s,c)	((c)->row * MaxCols(s) + (c)->col)
143
144static const CELL zeroCELL =
145{0, 0};
146
147#if OPT_DEC_LOCATOR
148static Bool SendLocatorPosition(XtermWidget xw, XButtonEvent *event);
149static void CheckLocatorPosition(XtermWidget xw, XButtonEvent *event);
150#endif /* OPT_DEC_LOCATOR */
151
152/* Multi-click handling */
153#if OPT_READLINE
154static Time lastButtonDownTime = 0;
155static int ExtendingSelection = 0;
156static Time lastButton3UpTime = 0;
157static Time lastButton3DoubleDownTime = 0;
158static CELL lastButton3;	/* At the release time */
159#endif /* OPT_READLINE */
160
161static Char *SaveText(TScreen *screen, int row, int scol, int ecol,
162		      Char *lp, int *eol);
163static int Length(TScreen *screen, int row, int scol, int ecol);
164static void ComputeSelect(XtermWidget xw, CELL *startc, CELL *endc, Bool
165			  extend, Bool normal);
166static void EditorButton(XtermWidget xw, XButtonEvent *event);
167static void EndExtend(XtermWidget w, XEvent *event, String *params, Cardinal
168		      num_params, Bool use_cursor_loc);
169static void ExtendExtend(XtermWidget xw, const CELL *cell);
170static void PointToCELL(TScreen *screen, int y, int x, CELL *cell);
171static void ReHiliteText(XtermWidget xw, CELL *first, CELL *last);
172static void SaltTextAway(XtermWidget xw, int which, CELL *cellc, CELL *cell);
173static void SelectSet(XtermWidget xw, XEvent *event, String *params, Cardinal num_params);
174static void SelectionReceived PROTO_XT_SEL_CB_ARGS;
175static void StartSelect(XtermWidget xw, const CELL *cell);
176static void TrackDown(XtermWidget xw, XButtonEvent *event);
177static void TrackText(XtermWidget xw, const CELL *first, const CELL *last);
178static void UnHiliteText(XtermWidget xw);
179static void _OwnSelection(XtermWidget xw, String *selections, Cardinal count);
180static void do_select_end(XtermWidget xw, XEvent *event, String *params,
181			  Cardinal *num_params, Bool use_cursor_loc);
182
183#define MOUSE_LIMIT (255 - 32)
184
185/* Send SET_EXT_SIZE_MOUSE to enable offsets up to EXT_MOUSE_LIMIT */
186#define EXT_MOUSE_LIMIT (2047 - 32)
187#define EXT_MOUSE_START (127 - 32)
188
189static int
190MouseLimit(TScreen *screen)
191{
192    int mouse_limit;
193
194    switch (screen->extend_coords) {
195    default:
196	mouse_limit = MOUSE_LIMIT;
197	break;
198    case SET_EXT_MODE_MOUSE:
199	mouse_limit = EXT_MOUSE_LIMIT;
200	break;
201    case SET_SGR_EXT_MODE_MOUSE:
202    case SET_URXVT_EXT_MODE_MOUSE:
203    case SET_PIXEL_POSITION_MOUSE:
204	mouse_limit = -1;
205	break;
206    }
207    return mouse_limit;
208}
209
210static unsigned
211EmitMousePosition(TScreen *screen, Char line[], unsigned count, int value)
212{
213    int mouse_limit = MouseLimit(screen);
214
215    /*
216     * Add pointer position to key sequence
217     *
218     * In extended mode we encode large positions as two-byte UTF-8.
219     *
220     * NOTE: historically, it was possible to emit 256, which became
221     * zero by truncation to 8 bits. While this was arguably a bug,
222     * it's also somewhat useful as a past-end marker. We preserve
223     * this behavior for both normal and extended mouse modes.
224     */
225    switch (screen->extend_coords) {
226    default:
227	if (value == mouse_limit) {
228	    line[count++] = CharOf(0);
229	} else {
230	    line[count++] = CharOf(' ' + value + 1);
231	}
232	break;
233    case SET_EXT_MODE_MOUSE:
234	if (value == mouse_limit) {
235	    line[count++] = CharOf(0);
236	} else if (value < EXT_MOUSE_START) {
237	    line[count++] = CharOf(' ' + value + 1);
238	} else {
239	    value += ' ' + 1;
240	    line[count++] = CharOf(0xC0 + (value >> 6));
241	    line[count++] = CharOf(0x80 + (value & 0x3F));
242	}
243	break;
244    case SET_SGR_EXT_MODE_MOUSE:
245    case SET_URXVT_EXT_MODE_MOUSE:
246    case SET_PIXEL_POSITION_MOUSE:
247	count += (unsigned) sprintf((char *) line + count, "%d", value + 1);
248	break;
249    }
250    return count;
251}
252
253static unsigned
254EmitMousePositionSeparator(TScreen *screen, Char line[], unsigned count)
255{
256    switch (screen->extend_coords) {
257    case SET_SGR_EXT_MODE_MOUSE:
258    case SET_URXVT_EXT_MODE_MOUSE:
259    case SET_PIXEL_POSITION_MOUSE:
260	line[count++] = ';';
261	break;
262    }
263    return count;
264}
265
266enum {
267    scanMods,
268    scanKey,
269    scanColon,
270    scanFunc,
271    scanArgs
272};
273
274#if OPT_TRACE > 1
275static const char *
276visibleScan(int mode)
277{
278    const char *result = "?";
279#define DATA(name) case name: result = #name; break
280    switch (mode) {
281	DATA(scanMods);
282	DATA(scanKey);
283	DATA(scanColon);
284	DATA(scanFunc);
285	DATA(scanArgs);
286    }
287#undef DATA
288    return result;
289}
290#endif
291
292#define L_BRACK '<'
293#define R_BRACK '>'
294#define L_PAREN '('
295#define R_PAREN ')'
296
297static char *
298scanTrans(char *source, int *this_is, int *next_is, unsigned *first, unsigned *last)
299{
300    char ch;
301    char *target = source;
302
303    *first = *last = 0;
304    if (IsEmpty(target)) {
305	target = 0;
306    } else {
307	do {
308	    while (IsSpace(*target))
309		target++;
310	    *first = (unsigned) (target - source);
311	    switch (*this_is = *next_is) {
312	    case scanMods:
313		while ((ch = *target)) {
314		    if (IsSpace(ch)) {
315			break;
316		    } else if (ch == L_BRACK) {
317			*next_is = scanKey;
318			break;
319		    } else if (ch == ':') {
320			*next_is = scanColon;
321			break;
322		    } else if (ch == '~' && target != source) {
323			break;
324		    }
325		    target++;
326		}
327		break;
328	    case scanKey:
329		while ((ch = *target)) {
330		    if (IsSpace(ch)) {
331			break;
332		    } else if (ch == ':') {
333			*next_is = scanColon;
334			break;
335		    }
336		    target++;
337		    if (ch == R_BRACK)
338			break;
339		}
340		break;
341	    case scanColon:
342		*next_is = scanFunc;
343		target++;
344		break;
345	    case scanFunc:
346		while ((ch = *target)) {
347		    if (IsSpace(ch)) {
348			break;
349		    } else if (ch == L_PAREN) {
350			*next_is = scanArgs;
351			break;
352		    }
353		    target++;
354		}
355		break;
356	    case scanArgs:
357		while ((ch = *target)) {
358		    if (ch == R_PAREN) {
359			target++;
360			*next_is = scanFunc;
361			break;
362		    }
363		    target++;
364		}
365		break;
366	    }
367	    *last = (unsigned) (target - source);
368	    if (*target == '\n') {
369		*next_is = scanMods;
370		target++;
371	    }
372	} while (*first == *last);
373    }
374    return target;
375}
376
377void
378xtermButtonInit(XtermWidget xw)
379{
380    Widget w = (Widget) xw;
381    XErrorHandler save = XSetErrorHandler(ignore_x11_error);
382    XtTranslations xlations;
383    Widget xcelerat;
384    String result;
385
386    XtVaGetValues(w,
387		  XtNtranslations, &xlations,
388		  XtNaccelerators, &xcelerat,
389		  (XtPointer) 0);
390    result = _XtPrintXlations(w, xlations, xcelerat, True);
391    if (result) {
392	static const char *table[] =
393	{
394	    "insert-selection",
395	    "select-end",
396	    "select-extend",
397	    "select-start",
398	    "start-extend",
399	};
400	char *data = x_strdup(result);
401	char *next;
402	int state = scanMods;
403	int state2 = scanMods;
404	unsigned first;
405	unsigned last;
406	int have_button = -1;
407	Bool want_button = False;
408	Bool have_shift = False;
409	unsigned allowed = 0;
410	unsigned disallow = 0;
411
412	TRACE(("xtermButtonInit length %ld\n", (long) strlen(result)));
413	xw->keyboard.print_translations = data;
414	while ((next = scanTrans(data, &state, &state2, &first, &last)) != 0) {
415	    unsigned len = (last - first);
416	    TRACE2(("parse %s:%d..%d '%.*s'\n",
417		    visibleScan(state), first, last,
418		    len, data + first));
419	    if (state == scanMods) {
420		if (len > 1 && data[first] == '~') {
421		    len--;
422		    first++;
423		}
424		if (len == 7 && !x_strncasecmp(data + first, "button", len - 1)) {
425		    have_button = data[first + 6] - '0';
426		} else if (len == 5 && !x_strncasecmp(data + first, "shift", len)) {
427		    have_shift = True;
428		}
429	    } else if (state == scanKey) {
430		if (!x_strncasecmp(data + first, "<buttonpress>", len) ||
431		    !x_strncasecmp(data + first, "<buttonrelease>", len)) {
432		    want_button = True;
433		} else if (want_button) {
434		    have_button = data[first] - '0';
435		    want_button = False;
436		}
437	    } else if (state == scanFunc && have_button > 0) {
438		Cardinal n;
439		unsigned bmask = 1U << (have_button - 1);
440		for (n = 0; n < XtNumber(table); ++n) {
441		    if (!x_strncasecmp(table[n], data + first, len)) {
442			TRACE(("...button %d: %s%s\n",
443			       have_button, table[n],
444			       have_shift ? " (disallow)" : ""));
445			if (have_shift)
446			    disallow |= bmask;
447			else
448			    allowed |= bmask;
449			break;
450		    }
451		}
452	    }
453	    if (state2 == scanMods && state >= scanColon) {
454		have_button = -1;
455		want_button = False;
456		have_shift = False;
457	    }
458	    state = state2;
459	    data = next;
460	}
461	XFree((char *) result);
462	xw->keyboard.shift_buttons = allowed & ~disallow;
463#if OPT_TRACE
464	if (xw->keyboard.shift_buttons) {
465	    int button = 0;
466	    unsigned mask = xw->keyboard.shift_buttons;
467	    TRACE(("...Buttons used for selection that can be overridden:"));
468	    while (mask != 0) {
469		++button;
470		if ((mask & 1) != 0)
471		    TRACE((" %d", button));
472		mask >>= 1;
473	    }
474	    TRACE(("\n"));
475	} else {
476	    TRACE(("...No buttons used with selection can be overridden\n"));
477	}
478#endif
479    }
480    XSetErrorHandler(save);
481}
482
483/*
484 * Shift and control are regular X11 modifiers, but meta is not:
485 * + X10 (which had no xmodmap utility) had a meta mask, but X11 did not.
486 * + X11R1 introduced xmodmap, along with the current set of modifier masks.
487 *   The meta key has been assumed to be mod1 since X11R1.
488 *   The initial xterm logic in X11 was different, but gave the same result.
489 * + X11R2 modified xterm was to eliminate the X10 table which provided part of
490 *   the meta logic.
491 * + X11R3 modified Xt, making Meta_L and Meta_R assignable via xmodmap, and
492 *   equating Alt with Meta.  Neither Alt/Meta are modifiers, but Alt is more
493 *   likely to be on the keyboard.  This release also added keymap tables for
494 *   the server; Meta was used frequently in HP keymaps, which were the most
495 *   extensive set of keymaps.
496 * + X11R4 mentions Meta in the ICCCM, stating that if Meta_L or Meta_R are
497 *   found in the keysyms for a given modifier, that the client should use
498 *   that modifier.
499 *
500 * This function follows the ICCCM, picking the modifier which contains the
501 * Meta_L/Meta_R keysyms (if available), falling back to the Alt_L/Alt_R
502 * (as per X11R3), and ultimately to mod1 (per X11R1).
503 */
504static unsigned
505MetaMask(XtermWidget xw)
506{
507#if OPT_NUM_LOCK
508    unsigned meta = xw->work.meta_mods;
509    if (meta == 0)
510	meta = xw->work.alt_mods;
511    if (meta == 0)
512	meta = Mod1Mask;
513#else
514    unsigned meta = Mod1Mask;
515    (void) xw;
516#endif
517    return meta;
518}
519
520/*
521 * Returns a mask of the modifiers we may use for modifying the mouse protocol
522 * response strings.
523 */
524static unsigned
525OurModifiers(XtermWidget xw)
526{
527    return (ShiftMask
528	    | ControlMask
529	    | MetaMask(xw));
530}
531
532/*
533 * The actual check for the shift-mask, to see if it should tell xterm to
534 * override mouse-protocol in favor of select/paste actions depends upon
535 * whether the shiftEscape resource is set to true/always vs false/never.
536 */
537static Boolean
538ShiftOverride(XtermWidget xw, unsigned state, int button)
539{
540    unsigned check = (state & OurModifiers(xw));
541    Boolean result = False;
542
543    if (check & ShiftMask) {
544	if (xw->keyboard.shift_escape == ssFalse ||
545	    xw->keyboard.shift_escape == ssNever) {
546	    result = True;
547	} else if (xw->keyboard.shift_escape == ssTrue) {
548	    /*
549	     * Check if the button is one that we found does not directly use
550	     * the shift-modifier in its bindings to select/copy actions.
551	     */
552	    if (button > 0 && button <= MaxMouseBtn) {
553		if (xw->keyboard.shift_buttons & (1U << (button - 1))) {
554		    result = True;
555		}
556	    } else {
557		result = True;	/* unlikely, and we don't care */
558	    }
559	}
560    }
561    TRACE2(("ShiftOverride ( %#x -> %#x ) %d\n", state, check, result));
562    return result;
563}
564
565/*
566 * Normally xterm treats the shift-modifier specially when the mouse protocol
567 * is active.  The translations resource binds otherwise unmodified button
568 * for these mouse-related events:
569 *
570 *         ~Meta <Btn1Down>:select-start() \n\
571 *       ~Meta <Btn1Motion>:select-extend() \n\
572 *     ~Ctrl ~Meta <Btn2Up>:insert-selection(SELECT, CUT_BUFFER0) \n\
573 *   ~Ctrl ~Meta <Btn3Down>:start-extend() \n\
574 *       ~Meta <Btn3Motion>:select-extend() \n\
575 *                  <BtnUp>:select-end(SELECT, CUT_BUFFER0) \n\
576 *
577 * There is no API in the X libraries which would tell us if a given mouse
578 * button is bound to one of these actions.  These functions make the choice
579 * configurable.
580 */
581static Bool
582InterpretButton(XtermWidget xw, XButtonEvent *event)
583{
584    Bool result = False;
585
586    if (ShiftOverride(xw, event->state, (int) event->button)) {
587	TRACE(("...shift-button #%d overrides mouse-protocol\n", event->button));
588	result = True;
589    }
590    return result;
591}
592
593#define Button1Index 8		/* X.h should have done this */
594
595static int
596MotionButton(unsigned state)
597{
598    unsigned bmask = state >> Button1Index;
599    int result = 1;
600
601    if (bmask != 0) {
602	while (!(bmask & 1)) {
603	    ++result;
604	    bmask >>= 1;
605	}
606    }
607    return result;
608}
609
610static Bool
611InterpretEvent(XtermWidget xw, XEvent *event)
612{
613    Bool result = False;	/* if not a button, is motion */
614
615    if (IsBtnEvent(event)) {
616	result = InterpretButton(xw, (XButtonEvent *) event);
617    } else if (event->type == MotionNotify) {
618	unsigned state = event->xmotion.state;
619	int button = MotionButton(state);
620
621	if (ShiftOverride(xw, state, button)) {
622	    TRACE(("...shift-motion #%d (%d,%d) overrides mouse-protocol\n",
623		   button,
624		   event->xmotion.y,
625		   event->xmotion.x));
626	    result = True;
627	}
628    }
629    return result;
630}
631
632#define OverrideEvent(event)  InterpretEvent(xw, event)
633#define OverrideButton(event) InterpretButton(xw, event)
634
635/*
636 * Returns true if we handled the event here, and nothing more is needed.
637 */
638Bool
639SendMousePosition(XtermWidget xw, XEvent *event)
640{
641    XButtonEvent *my_event = (XButtonEvent *) event;
642    Bool result = False;
643
644    switch (okSendMousePos(xw)) {
645    case MOUSE_OFF:
646	/* If send_mouse_pos mode isn't on, we shouldn't be here */
647	break;
648
649    case BTN_EVENT_MOUSE:
650    case ANY_EVENT_MOUSE:
651	if (!OverrideEvent(event)) {
652	    /* xterm extension for motion reporting. June 1998 */
653	    /* EditorButton() will distinguish between the modes */
654	    switch (event->type) {
655	    case MotionNotify:
656		my_event->button = 0;
657		/* FALLTHRU */
658	    case ButtonPress:
659		/* FALLTHRU */
660	    case ButtonRelease:
661		EditorButton(xw, my_event);
662		result = True;
663		break;
664	    }
665	}
666	break;
667
668    case X10_MOUSE:		/* X10 compatibility sequences */
669	if (IsBtnEvent(event)) {
670	    if (!OverrideButton(my_event)) {
671		if (my_event->type == ButtonPress)
672		    EditorButton(xw, my_event);
673		result = True;
674	    }
675	}
676	break;
677
678    case VT200_HIGHLIGHT_MOUSE:	/* DEC vt200 hilite tracking */
679	if (IsBtnEvent(event)) {
680	    if (!OverrideButton(my_event)) {
681		if (my_event->type == ButtonPress &&
682		    my_event->button == Button1) {
683		    TrackDown(xw, my_event);
684		} else {
685		    EditorButton(xw, my_event);
686		}
687		result = True;
688	    }
689	}
690	break;
691
692    case VT200_MOUSE:		/* DEC vt200 compatible */
693	if (IsBtnEvent(event)) {
694	    if (!OverrideButton(my_event)) {
695		EditorButton(xw, my_event);
696		result = True;
697	    }
698	}
699	break;
700
701    case DEC_LOCATOR:
702#if OPT_DEC_LOCATOR
703	if (IsBtnEvent(event) || event->type == MotionNotify) {
704	    result = SendLocatorPosition(xw, my_event);
705	}
706#endif /* OPT_DEC_LOCATOR */
707	break;
708    }
709    return result;
710}
711
712#if OPT_DEC_LOCATOR
713
714#define	LocatorCoords( row, col, x, y, oor )			\
715    if( screen->locator_pixels ) {				\
716	(oor)=False; (row) = (y)+1; (col) = (x)+1;		\
717	/* Limit to screen dimensions */			\
718	if ((row) < 1) (row) = 1,(oor)=True;			\
719	else if ((row) > screen->border*2+Height(screen))	\
720	    (row) = screen->border*2+Height(screen),(oor)=True;	\
721	if ((col) < 1) (col) = 1,(oor)=True;			\
722	else if ((col) > OriginX(screen)*2+Width(screen))	\
723	    (col) = OriginX(screen)*2+Width(screen),(oor)=True;	\
724    } else {							\
725	(oor)=False;						\
726	/* Compute character position of mouse pointer */	\
727	(row) = ((y) - screen->border) / FontHeight(screen);	\
728	(col) = ((x) - OriginX(screen)) / FontWidth(screen);	\
729	/* Limit to screen dimensions */			\
730	if ((row) < 0) (row) = 0,(oor)=True;			\
731	else if ((row) > screen->max_row)			\
732	    (row) = screen->max_row,(oor)=True;			\
733	if ((col) < 0) (col) = 0,(oor)=True;			\
734	else if ((col) > screen->max_col)			\
735	    (col) = screen->max_col,(oor)=True;			\
736	(row)++; (col)++;					\
737    }
738
739static Bool
740SendLocatorPosition(XtermWidget xw, XButtonEvent *event)
741{
742    ANSI reply;
743    TScreen *screen = TScreenOf(xw);
744    int row, col;
745    Bool oor;
746    int button;
747    unsigned state;
748
749    /* Make sure the event is an appropriate type */
750    if (IsBtnEvent(event)) {
751	if (OverrideButton(event))
752	    return (False);
753    } else {
754	if (!screen->loc_filter)
755	    return (False);
756    }
757
758    if ((event->type == ButtonPress &&
759	 !(screen->locator_events & LOC_BTNS_DN)) ||
760	(event->type == ButtonRelease &&
761	 !(screen->locator_events & LOC_BTNS_UP)))
762	return (True);
763
764    if (event->type == MotionNotify) {
765	CheckLocatorPosition(xw, event);
766	return (True);
767    }
768
769    /* get button # */
770    button = (int) event->button - 1;
771
772    LocatorCoords(row, col, event->x, event->y, oor);
773
774    /*
775     * DECterm mouse:
776     *
777     * ESCAPE '[' event ; mask ; row ; column '&' 'w'
778     */
779    memset(&reply, 0, sizeof(reply));
780    reply.a_type = ANSI_CSI;
781
782    if (oor) {
783	reply.a_nparam = 1;
784	reply.a_param[0] = 0;	/* Event - 0 = locator unavailable */
785	reply.a_inters = '&';
786	reply.a_final = 'w';
787	unparseseq(xw, &reply);
788
789	if (screen->locator_reset) {
790	    MotionOff(screen, xw);
791	    screen->send_mouse_pos = MOUSE_OFF;
792	}
793	return (True);
794    }
795
796    /*
797     * event:
798     *        1       no buttons
799     *        2       left button down
800     *        3       left button up
801     *        4       middle button down
802     *        5       middle button up
803     *        6       right button down
804     *        7       right button up
805     *        8       M4 down
806     *        9       M4 up
807     */
808    reply.a_nparam = 4;
809    switch (event->type) {
810    case ButtonPress:
811	reply.a_param[0] = (ParmType) (2 + (button << 1));
812	break;
813    case ButtonRelease:
814	reply.a_param[0] = (ParmType) (3 + (button << 1));
815	break;
816    default:
817	return (True);
818    }
819    /*
820     * mask:
821     * bit7   bit6   bit5   bit4   bit3     bit2       bit1         bit0
822     *                             M4 down  left down  middle down  right down
823     *
824     * Notice that Button1 (left) and Button3 (right) are swapped in the mask.
825     * Also, mask should be the state after the button press/release,
826     * X provides the state not including the button press/release.
827     */
828    state = (event->state
829	     & (Button1Mask | Button2Mask | Button3Mask | Button4Mask)) >> 8;
830    /* update mask to "after" state */
831    state ^= ((unsigned) (1 << button));
832    /* swap Button1 & Button3 */
833    state = ((state & (unsigned) ~(4 | 1))
834	     | ((state & 1) ? 4 : 0)
835	     | ((state & 4) ? 1 : 0));
836
837    reply.a_param[1] = (ParmType) state;
838    reply.a_param[2] = (ParmType) row;
839    reply.a_param[3] = (ParmType) col;
840    reply.a_inters = '&';
841    reply.a_final = 'w';
842
843    unparseseq(xw, &reply);
844
845    if (screen->locator_reset) {
846	MotionOff(screen, xw);
847	screen->send_mouse_pos = MOUSE_OFF;
848    }
849
850    /*
851     * DECterm turns the Locator off if a button is pressed while a filter
852     * rectangle is active.  This might be a bug, but I don't know, so I'll
853     * emulate it anyway.
854     */
855    if (screen->loc_filter) {
856	screen->send_mouse_pos = MOUSE_OFF;
857	screen->loc_filter = False;
858	screen->locator_events = 0;
859	MotionOff(screen, xw);
860    }
861
862    return (True);
863}
864
865/*
866 * mask:
867 * bit 7   bit 6   bit 5   bit 4   bit 3   bit 2       bit 1         bit 0
868 *                                 M4 down left down   middle down   right down
869 *
870 * Button1 (left) and Button3 (right) are swapped in the mask relative to X.
871 */
872#define	ButtonState(state, mask)	\
873{ int stemp = (int) (((mask) & (Button1Mask | Button2Mask | Button3Mask | Button4Mask)) >> 8);	\
874  /* swap Button1 & Button3 */								\
875  (state) = (stemp & ~(4|1)) | ((stemp & 1) ? 4 : 0) | ((stemp & 4) ? 1 : 0);			\
876}
877
878void
879GetLocatorPosition(XtermWidget xw)
880{
881    ANSI reply;
882    TScreen *screen = TScreenOf(xw);
883    Window root, child;
884    int rx, ry, x, y;
885    unsigned int mask = 0;
886    int row = 0, col = 0;
887    Bool oor = False;
888    Bool ret = False;
889    int state;
890
891    /*
892     * DECterm turns the Locator off if the position is requested while a
893     * filter rectangle is active.  This might be a bug, but I don't know, so
894     * I'll emulate it anyways.
895     */
896    if (screen->loc_filter) {
897	screen->send_mouse_pos = MOUSE_OFF;
898	screen->loc_filter = False;
899	screen->locator_events = 0;
900	MotionOff(screen, xw);
901    }
902
903    memset(&reply, 0, sizeof(reply));
904    reply.a_type = ANSI_CSI;
905
906    if (okSendMousePos(xw) == DEC_LOCATOR) {
907	ret = XQueryPointer(screen->display, VWindow(screen), &root,
908			    &child, &rx, &ry, &x, &y, &mask);
909	if (ret) {
910	    LocatorCoords(row, col, x, y, oor);
911	}
912    }
913    if (ret == False || oor) {
914	reply.a_nparam = 1;
915	reply.a_param[0] = 0;	/* Event - 0 = locator unavailable */
916	reply.a_inters = '&';
917	reply.a_final = 'w';
918	unparseseq(xw, &reply);
919
920	if (screen->locator_reset) {
921	    MotionOff(screen, xw);
922	    screen->send_mouse_pos = MOUSE_OFF;
923	}
924	return;
925    }
926
927    ButtonState(state, mask);
928
929    reply.a_nparam = 4;
930    reply.a_param[0] = 1;	/* Event - 1 = response to locator request */
931    reply.a_param[1] = (ParmType) state;
932    reply.a_param[2] = (ParmType) row;
933    reply.a_param[3] = (ParmType) col;
934    reply.a_inters = '&';
935    reply.a_final = 'w';
936    unparseseq(xw, &reply);
937
938    if (screen->locator_reset) {
939	MotionOff(screen, xw);
940	screen->send_mouse_pos = MOUSE_OFF;
941    }
942}
943
944void
945InitLocatorFilter(XtermWidget xw)
946{
947    ANSI reply;
948    TScreen *screen = TScreenOf(xw);
949    Window root, child;
950    int rx, ry, x, y;
951    unsigned int mask;
952    int row = 0, col = 0;
953    Bool oor = 0;
954    Bool ret;
955
956    ret = XQueryPointer(screen->display, VWindow(screen),
957			&root, &child, &rx, &ry, &x, &y, &mask);
958    if (ret) {
959	LocatorCoords(row, col, x, y, oor);
960    }
961    if (ret == False || oor) {
962	/* Locator is unavailable */
963
964	if (screen->loc_filter_top != LOC_FILTER_POS ||
965	    screen->loc_filter_left != LOC_FILTER_POS ||
966	    screen->loc_filter_bottom != LOC_FILTER_POS ||
967	    screen->loc_filter_right != LOC_FILTER_POS) {
968	    /*
969	     * If any explicit coordinates were received,
970	     * report immediately with no coordinates.
971	     */
972	    memset(&reply, 0, sizeof(reply));
973	    reply.a_type = ANSI_CSI;
974	    reply.a_nparam = 1;
975	    reply.a_param[0] = 0;	/* Event - 0 = locator unavailable */
976	    reply.a_inters = '&';
977	    reply.a_final = 'w';
978	    unparseseq(xw, &reply);
979
980	    if (screen->locator_reset) {
981		MotionOff(screen, xw);
982		screen->send_mouse_pos = MOUSE_OFF;
983	    }
984	} else {
985	    /*
986	     * No explicit coordinates were received, and the pointer is
987	     * unavailable.  Report when the pointer re-enters the window.
988	     */
989	    screen->loc_filter = True;
990	    MotionOn(screen, xw);
991	}
992	return;
993    }
994
995    /*
996     * Adjust rectangle coordinates:
997     *  1. Replace "LOC_FILTER_POS" with current coordinates
998     *  2. Limit coordinates to screen size
999     *  3. make sure top and left are less than bottom and right, resp.
1000     */
1001    if (screen->locator_pixels) {
1002	rx = OriginX(screen) * 2 + Width(screen);
1003	ry = screen->border * 2 + Height(screen);
1004    } else {
1005	rx = screen->max_col;
1006	ry = screen->max_row;
1007    }
1008
1009#define	Adjust( coord, def, max )				\
1010	if( (coord) == LOC_FILTER_POS )	(coord) = (def);	\
1011	else if ((coord) < 1)		(coord) = 1;		\
1012	else if ((coord) > (max))	(coord) = (max)
1013
1014    Adjust(screen->loc_filter_top, row, ry);
1015    Adjust(screen->loc_filter_left, col, rx);
1016    Adjust(screen->loc_filter_bottom, row, ry);
1017    Adjust(screen->loc_filter_right, col, rx);
1018
1019    if (screen->loc_filter_top > screen->loc_filter_bottom) {
1020	ry = screen->loc_filter_top;
1021	screen->loc_filter_top = screen->loc_filter_bottom;
1022	screen->loc_filter_bottom = ry;
1023    }
1024
1025    if (screen->loc_filter_left > screen->loc_filter_right) {
1026	rx = screen->loc_filter_left;
1027	screen->loc_filter_left = screen->loc_filter_right;
1028	screen->loc_filter_right = rx;
1029    }
1030
1031    if ((col < screen->loc_filter_left) ||
1032	(col > screen->loc_filter_right) ||
1033	(row < screen->loc_filter_top) ||
1034	(row > screen->loc_filter_bottom)) {
1035	int state;
1036
1037	/* Pointer is already outside the rectangle - report immediately */
1038	ButtonState(state, mask);
1039
1040	memset(&reply, 0, sizeof(reply));
1041	reply.a_type = ANSI_CSI;
1042	reply.a_nparam = 4;
1043	reply.a_param[0] = 10;	/* Event - 10 = locator outside filter */
1044	reply.a_param[1] = (ParmType) state;
1045	reply.a_param[2] = (ParmType) row;
1046	reply.a_param[3] = (ParmType) col;
1047	reply.a_inters = '&';
1048	reply.a_final = 'w';
1049	unparseseq(xw, &reply);
1050
1051	if (screen->locator_reset) {
1052	    MotionOff(screen, xw);
1053	    screen->send_mouse_pos = MOUSE_OFF;
1054	}
1055	return;
1056    }
1057
1058    /*
1059     * Rectangle is set up.  Allow pointer tracking
1060     * to detect if the mouse leaves the rectangle.
1061     */
1062    screen->loc_filter = True;
1063    MotionOn(screen, xw);
1064}
1065
1066static void
1067CheckLocatorPosition(XtermWidget xw, XButtonEvent *event)
1068{
1069    ANSI reply;
1070    TScreen *screen = TScreenOf(xw);
1071    int row, col;
1072    Bool oor;
1073
1074    LocatorCoords(row, col, event->x, event->y, oor);
1075
1076    /*
1077     * Send report if the pointer left the filter rectangle, if
1078     * the pointer left the window, or if the filter rectangle
1079     * had no coordinates and the pointer re-entered the window.
1080     */
1081    if (oor || (screen->loc_filter_top == LOC_FILTER_POS) ||
1082	(col < screen->loc_filter_left) ||
1083	(col > screen->loc_filter_right) ||
1084	(row < screen->loc_filter_top) ||
1085	(row > screen->loc_filter_bottom)) {
1086	/* Filter triggered - disable it */
1087	screen->loc_filter = False;
1088	MotionOff(screen, xw);
1089
1090	memset(&reply, 0, sizeof(reply));
1091	reply.a_type = ANSI_CSI;
1092	if (oor) {
1093	    reply.a_nparam = 1;
1094	    reply.a_param[0] = 0;	/* Event - 0 = locator unavailable */
1095	} else {
1096	    int state;
1097
1098	    ButtonState(state, event->state);
1099
1100	    reply.a_nparam = 4;
1101	    reply.a_param[0] = 10;	/* Event - 10 = locator outside filter */
1102	    reply.a_param[1] = (ParmType) state;
1103	    reply.a_param[2] = (ParmType) row;
1104	    reply.a_param[3] = (ParmType) col;
1105	}
1106
1107	reply.a_inters = '&';
1108	reply.a_final = 'w';
1109	unparseseq(xw, &reply);
1110
1111	if (screen->locator_reset) {
1112	    MotionOff(screen, xw);
1113	    screen->send_mouse_pos = MOUSE_OFF;
1114	}
1115    }
1116}
1117#endif /* OPT_DEC_LOCATOR */
1118
1119#if OPT_READLINE
1120static int
1121isClick1_clean(XtermWidget xw, XButtonEvent *event)
1122{
1123    TScreen *screen = TScreenOf(xw);
1124    int delta;
1125
1126    /* Disable on Shift-Click-1, including the application-mouse modes */
1127    if (OverrideButton(event)
1128	|| (okSendMousePos(xw) != MOUSE_OFF)
1129	|| ExtendingSelection)	/* Was moved */
1130	return 0;
1131
1132    if (event->type != ButtonRelease)
1133	return 0;
1134
1135    if (lastButtonDownTime == (Time) 0) {
1136	/* first time or once in a blue moon */
1137	delta = screen->multiClickTime + 1;
1138    } else if (event->time > lastButtonDownTime) {
1139	/* most of the time */
1140	delta = (int) (event->time - lastButtonDownTime);
1141    } else {
1142	/* time has rolled over since lastButtonUpTime */
1143	delta = (int) ((((Time) ~ 0) - lastButtonDownTime) + event->time);
1144    }
1145
1146    return delta <= screen->multiClickTime;
1147}
1148
1149static int
1150isDoubleClick3(XtermWidget xw, TScreen *screen, XButtonEvent *event)
1151{
1152    int delta;
1153
1154    if (event->type != ButtonRelease
1155	|| OverrideButton(event)
1156	|| event->button != Button3) {
1157	lastButton3UpTime = 0;	/* Disable the cached info */
1158	return 0;
1159    }
1160    /* Process Btn3Release. */
1161    if (lastButton3DoubleDownTime == (Time) 0) {
1162	/* No previous click or once in a blue moon */
1163	delta = screen->multiClickTime + 1;
1164    } else if (event->time > lastButton3DoubleDownTime) {
1165	/* most of the time */
1166	delta = (int) (event->time - lastButton3DoubleDownTime);
1167    } else {
1168	/* time has rolled over since lastButton3DoubleDownTime */
1169	delta = (int) ((((Time) ~ 0) - lastButton3DoubleDownTime) + event->time);
1170    }
1171    if (delta <= screen->multiClickTime) {
1172	/* Double click */
1173	CELL cell;
1174
1175	/* Cannot check ExtendingSelection, since mouse-3 always sets it */
1176	PointToCELL(screen, event->y, event->x, &cell);
1177	if (isSameCELL(&cell, &lastButton3)) {
1178	    lastButton3DoubleDownTime = 0;	/* Disable the third click */
1179	    return 1;
1180	}
1181    }
1182    /* Not a double click, memorize for future check. */
1183    lastButton3UpTime = event->time;
1184    PointToCELL(screen, event->y, event->x, &lastButton3);
1185    return 0;
1186}
1187
1188static int
1189CheckSecondPress3(XtermWidget xw, TScreen *screen, XEvent *event)
1190{
1191    int delta;
1192
1193    if (event->type != ButtonPress
1194	|| OverrideEvent(event)
1195	|| event->xbutton.button != Button3) {
1196	lastButton3DoubleDownTime = 0;	/* Disable the cached info */
1197	return 0;
1198    }
1199    /* Process Btn3Press. */
1200    if (lastButton3UpTime == (Time) 0) {
1201	/* No previous click or once in a blue moon */
1202	delta = screen->multiClickTime + 1;
1203    } else if (event->xbutton.time > lastButton3UpTime) {
1204	/* most of the time */
1205	delta = (int) (event->xbutton.time - lastButton3UpTime);
1206    } else {
1207	/* time has rolled over since lastButton3UpTime */
1208	delta = (int) ((((Time) ~ 0) - lastButton3UpTime) + event->xbutton.time);
1209    }
1210    if (delta <= screen->multiClickTime) {
1211	CELL cell;
1212
1213	PointToCELL(screen, event->xbutton.y, event->xbutton.x, &cell);
1214	if (isSameCELL(&cell, &lastButton3)) {
1215	    /* A candidate for a double-click */
1216	    lastButton3DoubleDownTime = event->xbutton.time;
1217	    PointToCELL(screen, event->xbutton.y, event->xbutton.x, &lastButton3);
1218	    return 1;
1219	}
1220	lastButton3UpTime = 0;	/* Disable the info about the previous click */
1221    }
1222    /* Either too long, or moved, disable. */
1223    lastButton3DoubleDownTime = 0;
1224    return 0;
1225}
1226
1227static int
1228rowOnCurrentLine(TScreen *screen,
1229		 int line,
1230		 int *deltap)	/* must be XButtonEvent */
1231{
1232    int result = 1;
1233
1234    *deltap = 0;
1235
1236    if (line != screen->cur_row) {
1237	int l1, l2;
1238
1239	if (line < screen->cur_row) {
1240	    l1 = line;
1241	    l2 = screen->cur_row;
1242	} else {
1243	    l2 = line;
1244	    l1 = screen->cur_row;
1245	}
1246	l1--;
1247	while (++l1 < l2) {
1248	    LineData *ld = GET_LINEDATA(screen, l1);
1249	    if (!LineTstWrapped(ld)) {
1250		result = 0;
1251		break;
1252	    }
1253	}
1254	if (result) {
1255	    /* Everything is on one "wrapped line" now */
1256	    *deltap = line - screen->cur_row;
1257	}
1258    }
1259    return result;
1260}
1261
1262static int
1263eventRow(TScreen *screen, XEvent *event)	/* must be XButtonEvent */
1264{
1265    return (event->xbutton.y - screen->border) / FontHeight(screen);
1266}
1267
1268static int
1269eventColBetween(TScreen *screen, XEvent *event)		/* must be XButtonEvent */
1270{
1271    /* Correct by half a width - we are acting on a boundary, not on a cell. */
1272    return ((event->xbutton.x - OriginX(screen) + (FontWidth(screen) - 1) / 2)
1273	    / FontWidth(screen));
1274}
1275
1276static int
1277ReadLineMovePoint(TScreen *screen, int col, int ldelta)
1278{
1279    Char line[6];
1280    unsigned count = 0;
1281
1282    col += ldelta * MaxCols(screen) - screen->cur_col;
1283    if (col == 0)
1284	return 0;
1285    if (screen->control_eight_bits) {
1286	line[count++] = ANSI_CSI;
1287    } else {
1288	line[count++] = ANSI_ESC;
1289	line[count++] = '[';	/* XXX maybe sometimes O is better? */
1290    }
1291    line[count] = CharOf(col > 0 ? 'C' : 'D');
1292    if (col < 0)
1293	col = -col;
1294    while (col--)
1295	v_write(screen->respond, line, 3);
1296    return 1;
1297}
1298
1299static int
1300ReadLineDelete(TScreen *screen, CELL *cell1, CELL *cell2)
1301{
1302    int del;
1303
1304    del = (cell2->col - cell1->col) + ((cell2->row - cell1->row) * MaxCols(screen));
1305    if (del <= 0)		/* Just in case... */
1306	return 0;
1307    while (del--)
1308	v_write(screen->respond, (const Char *) "\177", 1);
1309    return 1;
1310}
1311
1312static void
1313readlineExtend(XtermWidget xw, XEvent *event)
1314{
1315    TScreen *screen = TScreenOf(xw);
1316    int ldelta1, ldelta2;
1317
1318    if (IsBtnEvent(event)) {
1319	XButtonEvent *my_event = (XButtonEvent *) event;
1320	if (isClick1_clean(xw, my_event)
1321	    && SCREEN_FLAG(screen, click1_moves)
1322	    && rowOnCurrentLine(screen, eventRow(screen, event), &ldelta1)) {
1323	    ReadLineMovePoint(screen, eventColBetween(screen, event), ldelta1);
1324	}
1325	if (isDoubleClick3(xw, screen, my_event)
1326	    && SCREEN_FLAG(screen, dclick3_deletes)
1327	    && rowOnCurrentLine(screen, screen->startSel.row, &ldelta1)
1328	    && rowOnCurrentLine(screen, screen->endSel.row, &ldelta2)) {
1329	    ReadLineMovePoint(screen, screen->endSel.col, ldelta2);
1330	    ReadLineDelete(screen, &screen->startSel, &(screen->endSel));
1331	}
1332    }
1333}
1334#endif /* OPT_READLINE */
1335
1336/* ^XM-G<line+' '><col+' '> */
1337void
1338DiredButton(Widget w,
1339	    XEvent *event,	/* must be XButtonEvent */
1340	    String *params GCC_UNUSED,	/* selections */
1341	    Cardinal *num_params GCC_UNUSED)
1342{
1343    XtermWidget xw;
1344
1345    if ((xw = getXtermWidget(w)) != 0) {
1346	TScreen *screen = TScreenOf(xw);
1347
1348	if (IsBtnEvent(event)
1349	    && (event->xbutton.y >= screen->border)
1350	    && (event->xbutton.x >= OriginX(screen))) {
1351	    Char Line[6];
1352	    unsigned line, col;
1353
1354	    line = (unsigned) ((event->xbutton.y - screen->border)
1355			       / FontHeight(screen));
1356	    col = (unsigned) ((event->xbutton.x - OriginX(screen))
1357			      / FontWidth(screen));
1358	    Line[0] = CONTROL('X');
1359	    Line[1] = ANSI_ESC;
1360	    Line[2] = 'G';
1361	    Line[3] = CharOf(' ' + col);
1362	    Line[4] = CharOf(' ' + line);
1363	    v_write(screen->respond, Line, 5);
1364	}
1365    }
1366}
1367
1368#if OPT_READLINE
1369void
1370ReadLineButton(Widget w,
1371	       XEvent *event,	/* must be XButtonEvent */
1372	       String *params,	/* selections */
1373	       Cardinal *num_params)
1374{
1375    XtermWidget xw;
1376
1377    if ((xw = getXtermWidget(w)) != 0) {
1378	TScreen *screen = TScreenOf(xw);
1379	Char Line[6];
1380	int line, col, ldelta = 0;
1381
1382	if (!IsBtnEvent(event)
1383	    || (okSendMousePos(xw) != MOUSE_OFF) || ExtendingSelection)
1384	    goto finish;
1385	if (event->type == ButtonRelease) {
1386	    int delta;
1387
1388	    if (lastButtonDownTime == (Time) 0) {
1389		/* first time and once in a blue moon */
1390		delta = screen->multiClickTime + 1;
1391	    } else if (event->xbutton.time > lastButtonDownTime) {
1392		/* most of the time */
1393		delta = (int) (event->xbutton.time - lastButtonDownTime);
1394	    } else {
1395		/* time has rolled over since lastButtonUpTime */
1396		delta = (int) ((((Time) ~ 0) - lastButtonDownTime) + event->xbutton.time);
1397	    }
1398	    if (delta > screen->multiClickTime)
1399		goto finish;	/* All this work for this... */
1400	}
1401	line = (event->xbutton.y - screen->border) / FontHeight(screen);
1402	if (!rowOnCurrentLine(screen, line, &ldelta))
1403	    goto finish;
1404	/* Correct by half a width - we are acting on a boundary, not on a cell. */
1405	col = (event->xbutton.x - OriginX(screen) + (FontWidth(screen) - 1)
1406	       / 2)
1407	    / FontWidth(screen) - screen->cur_col + ldelta * MaxCols(screen);
1408	if (col == 0)
1409	    goto finish;
1410	Line[0] = ANSI_ESC;
1411	/* XXX: sometimes it is better to send '['? */
1412	Line[1] = 'O';
1413	Line[2] = CharOf(col > 0 ? 'C' : 'D');
1414	if (col < 0)
1415	    col = -col;
1416	while (col--)
1417	    v_write(screen->respond, Line, 3);
1418      finish:
1419	if (event->type == ButtonRelease)
1420	    do_select_end(xw, event, params, num_params, False);
1421    }
1422}
1423#endif /* OPT_READLINE */
1424
1425/* repeats <ESC>n or <ESC>p */
1426void
1427ViButton(Widget w,
1428	 XEvent *event,		/* must be XButtonEvent */
1429	 String *params GCC_UNUSED,	/* selections */
1430	 Cardinal *num_params GCC_UNUSED)
1431{
1432    XtermWidget xw;
1433
1434    if ((xw = getXtermWidget(w)) != 0) {
1435	TScreen *screen = TScreenOf(xw);
1436	int pty = screen->respond;
1437
1438	if (IsBtnEvent(event)) {
1439	    int line;
1440
1441	    line = screen->cur_row -
1442		((event->xbutton.y - screen->border) / FontHeight(screen));
1443
1444	    if (line != 0) {
1445		Char Line[6];
1446
1447		Line[0] = ANSI_ESC;	/* force an exit from insert-mode */
1448		v_write(pty, Line, 1);
1449
1450		if (line < 0) {
1451		    line = -line;
1452		    Line[0] = CONTROL('n');
1453		} else {
1454		    Line[0] = CONTROL('p');
1455		}
1456		while (--line >= 0)
1457		    v_write(pty, Line, 1);
1458	    }
1459	}
1460    }
1461}
1462
1463/*
1464 * This function handles button-motion events
1465 */
1466/*ARGSUSED*/
1467void
1468HandleSelectExtend(Widget w,
1469		   XEvent *event,	/* must be XMotionEvent */
1470		   String *params GCC_UNUSED,
1471		   Cardinal *num_params GCC_UNUSED)
1472{
1473    XtermWidget xw;
1474
1475    if ((xw = getXtermWidget(w)) != 0) {
1476	TScreen *screen = TScreenOf(xw);
1477	CELL cell;
1478
1479	TRACE_EVENT("HandleSelectExtend", event, params, num_params);
1480
1481	screen->selection_time = event->xmotion.time;
1482	switch (screen->eventMode) {
1483	    /* If not in one of the DEC mouse-reporting modes */
1484	case LEFTEXTENSION:
1485	case RIGHTEXTENSION:
1486	    PointToCELL(screen, event->xmotion.y, event->xmotion.x, &cell);
1487	    ExtendExtend(xw, &cell);
1488	    break;
1489
1490	    /* If in motion reporting mode, send mouse position to
1491	       character process as a key sequence \E[M... */
1492	case NORMAL:
1493	    /* will get here if send_mouse_pos != MOUSE_OFF */
1494	    if (okSendMousePos(xw) == BTN_EVENT_MOUSE
1495		|| okSendMousePos(xw) == ANY_EVENT_MOUSE) {
1496		(void) SendMousePosition(xw, event);
1497	    }
1498	    break;
1499	}
1500    }
1501}
1502
1503void
1504HandleKeyboardSelectExtend(Widget w,
1505			   XEvent *event GCC_UNUSED,	/* must be XButtonEvent */
1506			   String *params GCC_UNUSED,
1507			   Cardinal *num_params GCC_UNUSED)
1508{
1509    XtermWidget xw;
1510
1511    if ((xw = getXtermWidget(w)) != 0) {
1512	TScreen *screen = TScreenOf(xw);
1513
1514	TRACE_EVENT("HandleKeyboardSelectExtend", event, params, num_params);
1515	ExtendExtend(xw, &screen->cursorp);
1516    }
1517}
1518
1519static void
1520do_select_end(XtermWidget xw,
1521	      XEvent *event,	/* must be XButtonEvent */
1522	      String *params,	/* selections */
1523	      Cardinal *num_params,
1524	      Bool use_cursor_loc)
1525{
1526    TScreen *screen = TScreenOf(xw);
1527
1528    screen->selection_time = event->xbutton.time;
1529
1530    TRACE(("do_select_end %s @%ld\n",
1531	   visibleEventMode(screen->eventMode),
1532	   screen->selection_time));
1533
1534    switch (screen->eventMode) {
1535    case NORMAL:
1536	(void) SendMousePosition(xw, event);
1537	break;
1538    case LEFTEXTENSION:
1539    case RIGHTEXTENSION:
1540	EndExtend(xw, event, params, *num_params, use_cursor_loc);
1541#if OPT_READLINE
1542	readlineExtend(xw, event);
1543#endif /* OPT_READLINE */
1544	break;
1545    }
1546}
1547
1548void
1549HandleSelectEnd(Widget w,
1550		XEvent *event,	/* must be XButtonEvent */
1551		String *params,	/* selections */
1552		Cardinal *num_params)
1553{
1554    XtermWidget xw;
1555
1556    if ((xw = getXtermWidget(w)) != 0) {
1557	TRACE(("HandleSelectEnd\n"));
1558	do_select_end(xw, event, params, num_params, False);
1559    }
1560}
1561
1562void
1563HandleKeyboardSelectEnd(Widget w,
1564			XEvent *event,	/* must be XButtonEvent */
1565			String *params,		/* selections */
1566			Cardinal *num_params)
1567{
1568    XtermWidget xw;
1569
1570    if ((xw = getXtermWidget(w)) != 0) {
1571	TRACE(("HandleKeyboardSelectEnd\n"));
1572	do_select_end(xw, event, params, num_params, True);
1573    }
1574}
1575
1576void
1577HandlePointerMotion(Widget w,
1578		    XEvent *event,
1579		    String *params,	/* selections */
1580		    Cardinal *num_params)
1581{
1582    XtermWidget xw;
1583
1584    (void) params;
1585    (void) num_params;
1586    if ((xw = getXtermWidget(w)) != 0) {
1587	TRACE(("HandlePointerMotion\n"));
1588	if (event->type == MotionNotify)
1589	    (void) SendMousePosition(xw, event);
1590    }
1591}
1592
1593void
1594HandlePointerButton(Widget w,
1595		    XEvent *event,
1596		    String *params,	/* selections */
1597		    Cardinal *num_params)
1598{
1599    XtermWidget xw;
1600
1601    (void) params;
1602    (void) num_params;
1603    if ((xw = getXtermWidget(w)) != 0) {
1604	TRACE(("HandlePointerButton\n"));
1605	if (IsBtnEvent(event))
1606	    (void) SendMousePosition(xw, event);
1607    }
1608}
1609
1610/*
1611 * Copy the selection data to the given target(s).
1612 */
1613void
1614HandleCopySelection(Widget w,
1615		    XEvent *event,
1616		    String *params,	/* list of targets */
1617		    Cardinal *num_params)
1618{
1619    XtermWidget xw;
1620
1621    if ((xw = getXtermWidget(w)) != 0) {
1622	TRACE_EVENT("HandleCopySelection", event, params, num_params);
1623	SelectSet(xw, event, params, *num_params);
1624    }
1625}
1626
1627struct _SelectionList {
1628    String *params;
1629    Cardinal count;
1630    Atom *targets;
1631    Time time;
1632};
1633
1634static unsigned
1635DECtoASCII(unsigned ch)
1636{
1637    if (xtermIsDecGraphic(ch)) {
1638	ch = CharOf("###########+++++##-##++++|######"[ch]);
1639	/*           01234567890123456789012345678901 */
1640    }
1641    return ch;
1642}
1643
1644#if OPT_WIDE_CHARS
1645static Cardinal
1646addXtermChar(Char **buffer, Cardinal *used, Cardinal offset, unsigned value)
1647{
1648    if (offset + 1 >= *used) {
1649	*used = 1 + (2 * (offset + 1));
1650	allocXtermChars(buffer, *used);
1651    }
1652    (*buffer)[offset++] = (Char) value;
1653    return offset;
1654}
1655#define AddChar(buffer, used, offset, value) \
1656	offset = addXtermChar(buffer, used, offset, (unsigned) value)
1657
1658/*
1659 * Convert a UTF-8 string to Latin-1, replacing non Latin-1 characters by `#',
1660 * or ASCII/Latin-1 equivalents for special cases.
1661 */
1662static Char *
1663UTF8toLatin1(TScreen *screen, Char *s, unsigned long len, unsigned long *result)
1664{
1665    static Char *buffer;
1666    static Cardinal used;
1667
1668    Cardinal offset = 0;
1669
1670    if (len != 0) {
1671	PtyData data;
1672
1673	fakePtyData(&data, s, s + len);
1674	while (decodeUtf8(screen, &data)) {
1675	    Bool fails = False;
1676	    Bool extra = False;
1677	    IChar value;
1678	    skipPtyData(&data, value);
1679	    if (value == UCS_REPL) {
1680		fails = True;
1681	    } else if (value < 256) {
1682		AddChar(&buffer, &used, offset, CharOf(value));
1683	    } else {
1684		unsigned eqv = ucs2dec(screen, value);
1685		if (xtermIsDecGraphic(eqv)) {
1686		    AddChar(&buffer, &used, offset, DECtoASCII(eqv));
1687		} else {
1688		    eqv = AsciiEquivs(value);
1689		    if (eqv == value) {
1690			fails = True;
1691		    } else {
1692			AddChar(&buffer, &used, offset, eqv);
1693		    }
1694		    if (isWide((wchar_t) value))
1695			extra = True;
1696		}
1697	    }
1698
1699	    /*
1700	     * If we're not able to plug in a single-byte result, insert the
1701	     * defaultString (which normally is a single "#", but could be
1702	     * whatever the user wants).
1703	     */
1704	    if (fails) {
1705		const Char *p;
1706
1707		for (p = (const Char *) screen->default_string; *p != '\0'; ++p) {
1708		    AddChar(&buffer, &used, offset, *p);
1709		}
1710	    }
1711	    if (extra)
1712		AddChar(&buffer, &used, offset, ' ');
1713	}
1714	AddChar(&buffer, &used, offset, '\0');
1715	*result = (unsigned long) (offset - 1);
1716    } else {
1717	*result = 0;
1718    }
1719    return buffer;
1720}
1721
1722int
1723xtermUtf8ToTextList(XtermWidget xw,
1724		    XTextProperty * text_prop,
1725		    char ***text_list,
1726		    int *text_list_count)
1727{
1728    TScreen *screen = TScreenOf(xw);
1729    Display *dpy = screen->display;
1730    int rc = -1;
1731
1732    if (text_prop->format == 8
1733	&& (rc = Xutf8TextPropertyToTextList(dpy, text_prop,
1734					     text_list,
1735					     text_list_count)) >= 0) {
1736	if (*text_list != NULL && *text_list_count != 0) {
1737	    int i;
1738	    Char *data;
1739	    char **new_text_list, *tmp;
1740	    unsigned long size, new_size;
1741
1742	    TRACE(("xtermUtf8ToTextList size %d\n", *text_list_count));
1743
1744	    /*
1745	     * XLib StringList actually uses only two pointers, one for the
1746	     * list itself, and one for the data.  Pointer to the data is the
1747	     * first element of the list, the rest (if any) list elements point
1748	     * to the same memory block as the first element
1749	     */
1750	    new_size = 0;
1751	    for (i = 0; i < *text_list_count; ++i) {
1752		data = (Char *) (*text_list)[i];
1753		size = strlen((*text_list)[i]) + 1;
1754		(void) UTF8toLatin1(screen, data, size, &size);
1755		new_size += size + 1;
1756	    }
1757	    new_text_list = TypeXtMallocN(char *, *text_list_count);
1758	    new_text_list[0] = tmp = XtMalloc((Cardinal) new_size);
1759	    for (i = 0; i < (*text_list_count); ++i) {
1760		data = (Char *) (*text_list)[i];
1761		size = strlen((*text_list)[i]) + 1;
1762		if ((data = UTF8toLatin1(screen, data, size, &size)) != 0) {
1763		    memcpy(tmp, data, size + 1);
1764		    new_text_list[i] = tmp;
1765		    tmp += size + 1;
1766		}
1767	    }
1768	    XFreeStringList((*text_list));
1769	    *text_list = new_text_list;
1770	} else {
1771	    rc = -1;
1772	}
1773    }
1774    return rc;
1775}
1776#endif /* OPT_WIDE_CHARS */
1777
1778static char *
1779parseItem(char *value, char *nextc)
1780{
1781    char *nextp = value;
1782    while (*nextp != '\0' && *nextp != ',') {
1783	*nextp = x_toupper(*nextp);
1784	++nextp;
1785    }
1786    *nextc = *nextp;
1787    *nextp = '\0';
1788
1789    return nextp;
1790}
1791
1792/*
1793 * All of the wanted strings are unique in the first character, so we can
1794 * use simple abbreviations.
1795 */
1796static Bool
1797sameItem(const char *actual, const char *wanted)
1798{
1799    Bool result = False;
1800    size_t have = strlen(actual);
1801    size_t need = strlen(wanted);
1802
1803    if (have != 0 && have <= need) {
1804	if (!strncmp(actual, wanted, have)) {
1805	    TRACE(("...matched \"%s\"\n", wanted));
1806	    result = True;
1807	}
1808    }
1809
1810    return result;
1811}
1812
1813/*
1814 * Handle the eightBitSelectTypes or utf8SelectTypes resource values.
1815 */
1816static Bool
1817overrideTargets(Widget w, String value, Atom **resultp)
1818{
1819    Bool override = False;
1820    XtermWidget xw;
1821
1822    if ((xw = getXtermWidget(w)) != 0) {
1823	TScreen *screen = TScreenOf(xw);
1824
1825	if (!IsEmpty(value)) {
1826	    char *copied = x_strdup(value);
1827	    if (copied != 0) {
1828		Atom *result = 0;
1829		Cardinal count = 1;
1830		int n;
1831
1832		TRACE(("decoding SelectTypes \"%s\"\n", value));
1833		for (n = 0; copied[n] != '\0'; ++n) {
1834		    if (copied[n] == ',')
1835			++count;
1836		}
1837		result = TypeXtMallocN(Atom, (2 * count) + 1);
1838		if (result == NULL) {
1839		    TRACE(("Couldn't allocate selection types\n"));
1840		} else {
1841		    char nextc = '?';
1842		    char *listp = (char *) copied;
1843		    count = 0;
1844		    do {
1845			char *nextp = parseItem(listp, &nextc);
1846			char *item = x_strtrim(listp);
1847			size_t len = (item ? strlen(item) : 0);
1848
1849			if (len == 0) {
1850			    /* EMPTY */ ;
1851			}
1852#if OPT_WIDE_CHARS
1853			else if (sameItem(item, "UTF8")) {
1854			    result[count++] = XA_UTF8_STRING(XtDisplay(w));
1855			}
1856#endif
1857			else if (sameItem(item, "I18N")) {
1858			    if (screen->i18nSelections) {
1859				result[count++] = XA_TEXT(XtDisplay(w));
1860				result[count++] = XA_COMPOUND_TEXT(XtDisplay(w));
1861			    }
1862			} else if (sameItem(item, "TEXT")) {
1863			    result[count++] = XA_TEXT(XtDisplay(w));
1864			} else if (sameItem(item, "COMPOUND_TEXT")) {
1865			    result[count++] = XA_COMPOUND_TEXT(XtDisplay(w));
1866			} else if (sameItem(item, "STRING")) {
1867			    result[count++] = XA_STRING;
1868			}
1869			*nextp++ = nextc;
1870			listp = nextp;
1871			free(item);
1872		    } while (nextc != '\0');
1873		    if (count) {
1874			result[count] = None;
1875			override = True;
1876			*resultp = result;
1877		    } else {
1878			XtFree((char *) result);
1879		    }
1880		}
1881		free(copied);
1882	    } else {
1883		TRACE(("Couldn't allocate copy of selection types\n"));
1884	    }
1885	}
1886    }
1887    return override;
1888}
1889
1890#if OPT_WIDE_CHARS
1891static Atom *
1892allocUtf8Targets(Widget w, TScreen *screen)
1893{
1894    Atom **resultp = &(screen->selection_targets_utf8);
1895
1896    if (*resultp == 0) {
1897	Atom *result;
1898
1899	if (!overrideTargets(w, screen->utf8_select_types, &result)) {
1900	    result = TypeXtMallocN(Atom, 5);
1901	    if (result == NULL) {
1902		TRACE(("Couldn't allocate utf-8 selection targets\n"));
1903	    } else {
1904		int n = 0;
1905
1906		if (XSupportsLocale()) {
1907		    result[n++] = XA_UTF8_STRING(XtDisplay(w));
1908#ifdef X_HAVE_UTF8_STRING
1909		    if (screen->i18nSelections) {
1910			result[n++] = XA_TEXT(XtDisplay(w));
1911			result[n++] = XA_COMPOUND_TEXT(XtDisplay(w));
1912		    }
1913#endif
1914		}
1915		result[n++] = XA_STRING;
1916		result[n] = None;
1917	    }
1918	}
1919
1920	*resultp = result;
1921    }
1922
1923    return *resultp;
1924}
1925#endif
1926
1927static Atom *
1928alloc8bitTargets(Widget w, TScreen *screen)
1929{
1930    Atom **resultp = &(screen->selection_targets_8bit);
1931
1932    if (*resultp == 0) {
1933	Atom *result = 0;
1934
1935	if (!overrideTargets(w, screen->eightbit_select_types, &result)) {
1936	    result = TypeXtMallocN(Atom, 5);
1937	    if (result == NULL) {
1938		TRACE(("Couldn't allocate 8bit selection targets\n"));
1939	    } else {
1940		int n = 0;
1941
1942		if (XSupportsLocale()) {
1943#ifdef X_HAVE_UTF8_STRING
1944		    result[n++] = XA_UTF8_STRING(XtDisplay(w));
1945#endif
1946		    if (screen->i18nSelections) {
1947			result[n++] = XA_TEXT(XtDisplay(w));
1948			result[n++] = XA_COMPOUND_TEXT(XtDisplay(w));
1949		    }
1950		}
1951		result[n++] = XA_STRING;
1952		result[n] = None;
1953	    }
1954	}
1955
1956	*resultp = result;
1957    }
1958
1959    return *resultp;
1960}
1961
1962static Atom *
1963_SelectionTargets(Widget w)
1964{
1965    Atom *result;
1966    XtermWidget xw;
1967
1968    if ((xw = getXtermWidget(w)) == 0) {
1969	result = NULL;
1970    } else {
1971	TScreen *screen = TScreenOf(xw);
1972
1973#if OPT_WIDE_CHARS
1974	if (screen->wide_chars) {
1975	    result = allocUtf8Targets(w, screen);
1976	} else
1977#endif
1978	{
1979	    /* not screen->wide_chars */
1980	    result = alloc8bitTargets(w, screen);
1981	}
1982    }
1983
1984    return result;
1985}
1986
1987#define isSELECT(value) (!strcmp(value, "SELECT"))
1988
1989static int
1990DefaultSelection(TScreen *screen)
1991{
1992    return (screen->selectToClipboard ? 1 : 0);
1993}
1994
1995static int
1996TargetToSelection(TScreen *screen, String name)
1997{
1998    int result = -1;
1999    int cutb;
2000
2001    if (isSELECT(name)) {
2002	result = DefaultSelection(screen);
2003    } else if (!strcmp(name, PRIMARY_NAME)) {
2004	result = PRIMARY_CODE;
2005    } else if (!strcmp(name, CLIPBOARD_NAME)) {
2006	result = CLIPBOARD_CODE;
2007    } else if (!strcmp(name, SECONDARY_NAME)) {
2008	result = SECONDARY_CODE;
2009    } else if (sscanf(name, "CUT_BUFFER%d", &cutb) == 1) {
2010	if (cutb >= 0 && cutb < MAX_CUT_BUFFER) {
2011	    result = CutBufferToCode(cutb);
2012	} else {
2013	    xtermWarning("unexpected cut-buffer code: %d\n", cutb);
2014	}
2015    } else {
2016	xtermWarning("unexpected selection target: %s\n", name);
2017    }
2018    TRACE2(("TargetToSelection(%s) ->%d\n", name, result));
2019    return result;
2020}
2021
2022void
2023UnmapSelections(XtermWidget xw)
2024{
2025    TScreen *screen = TScreenOf(xw);
2026    Cardinal n;
2027
2028    if (screen->mappedSelect) {
2029	for (n = 0; screen->mappedSelect[n] != 0; ++n)
2030	    free((void *) screen->mappedSelect[n]);
2031	FreeAndNull(screen->mappedSelect);
2032    }
2033}
2034
2035/*
2036 * xterm generally uses the primary selection.  Some applications prefer
2037 * (or are limited to) the clipboard.  Since the translations resource is
2038 * complicated, users seldom change the way it affects selection.  But it
2039 * is simple to remap the choice between primary and clipboard before the
2040 * call to XmuInternStrings().
2041 */
2042static String *
2043MapSelections(XtermWidget xw, String *params, Cardinal num_params)
2044{
2045    String *result = params;
2046
2047    if (params != 0 && num_params > 0) {
2048	Cardinal j;
2049	Boolean map = False;
2050
2051	for (j = 0; j < num_params; ++j) {
2052	    TRACE(("param[%d]:%s\n", j, params[j]));
2053	    if (isSELECT(params[j])) {
2054		map = True;
2055		break;
2056	    }
2057	}
2058	if (map) {
2059	    TScreen *screen = TScreenOf(xw);
2060	    const char *mapTo = (screen->selectToClipboard
2061				 ? CLIPBOARD_NAME
2062				 : PRIMARY_NAME);
2063
2064	    UnmapSelections(xw);
2065	    if ((result = TypeMallocN(String, num_params + 1)) != 0) {
2066		result[num_params] = 0;
2067		for (j = 0; j < num_params; ++j) {
2068		    result[j] = x_strdup((isSELECT(params[j])
2069					  ? mapTo
2070					  : params[j]));
2071		    if (result[j] == 0) {
2072			UnmapSelections(xw);
2073			while (j != 0) {
2074			    free((void *) result[--j]);
2075			}
2076			FreeAndNull(result);
2077			break;
2078		    }
2079		}
2080		screen->mappedSelect = result;
2081	    }
2082	}
2083    }
2084    return result;
2085}
2086
2087/*
2088 * Lookup the cut-buffer number, which will be in the range 0-7.
2089 * If it is not a cut-buffer, it is a type of selection, e.g., primary.
2090 */
2091static int
2092CutBuffer(Atom code)
2093{
2094    int cutbuffer;
2095    switch ((unsigned) code) {
2096    case XA_CUT_BUFFER0:
2097	cutbuffer = 0;
2098	break;
2099    case XA_CUT_BUFFER1:
2100	cutbuffer = 1;
2101	break;
2102    case XA_CUT_BUFFER2:
2103	cutbuffer = 2;
2104	break;
2105    case XA_CUT_BUFFER3:
2106	cutbuffer = 3;
2107	break;
2108    case XA_CUT_BUFFER4:
2109	cutbuffer = 4;
2110	break;
2111    case XA_CUT_BUFFER5:
2112	cutbuffer = 5;
2113	break;
2114    case XA_CUT_BUFFER6:
2115	cutbuffer = 6;
2116	break;
2117    case XA_CUT_BUFFER7:
2118	cutbuffer = 7;
2119	break;
2120    default:
2121	cutbuffer = -1;
2122	break;
2123    }
2124    TRACE2(("CutBuffer(%d) = %d\n", (int) code, cutbuffer));
2125    return cutbuffer;
2126}
2127
2128#if OPT_PASTE64
2129static void
2130FinishPaste64(XtermWidget xw)
2131{
2132    TScreen *screen = TScreenOf(xw);
2133
2134    TRACE(("FinishPaste64(%d)\n", screen->base64_paste));
2135    if (screen->base64_paste) {
2136	screen->base64_paste = 0;
2137	unparseputc1(xw, screen->base64_final);
2138	unparse_end(xw);
2139    }
2140}
2141#endif
2142
2143#if !OPT_PASTE64
2144static
2145#endif
2146void
2147xtermGetSelection(Widget w,
2148		  Time ev_time,
2149		  String *params,	/* selections in precedence order */
2150		  Cardinal num_params,
2151		  Atom *targets)
2152{
2153    Atom selection;
2154    int cutbuffer;
2155    Atom target;
2156
2157    XtermWidget xw;
2158
2159    if (num_params == 0)
2160	return;
2161    if ((xw = getXtermWidget(w)) == 0)
2162	return;
2163
2164    TRACE(("xtermGetSelection num_params %d @%ld\n", num_params, ev_time));
2165    params = MapSelections(xw, params, num_params);
2166
2167    XmuInternStrings(XtDisplay(w), params, (Cardinal) 1, &selection);
2168    cutbuffer = CutBuffer(selection);
2169
2170    TRACE(("Cutbuffer: %d, target: %s\n", cutbuffer,
2171	   (targets
2172	    ? visibleSelectionTarget(XtDisplay(w), targets[0])
2173	    : "None")));
2174
2175    if (cutbuffer >= 0) {
2176	int inbytes;
2177	unsigned long nbytes;
2178	int fmt8 = 8;
2179	Atom type = XA_STRING;
2180	char *line;
2181
2182	/* 'line' is freed in SelectionReceived */
2183	line = XFetchBuffer(XtDisplay(w), &inbytes, cutbuffer);
2184	nbytes = (unsigned long) inbytes;
2185
2186	if (nbytes > 0) {
2187	    SelectionReceived(w, NULL, &selection, &type, (XtPointer) line,
2188			      &nbytes, &fmt8);
2189	} else if (num_params > 1) {
2190	    xtermGetSelection(w, ev_time, params + 1, num_params - 1, NULL);
2191	}
2192#if OPT_PASTE64
2193	else {
2194	    FinishPaste64(xw);
2195	}
2196#endif
2197    } else {
2198
2199	if (targets == NULL || targets[0] == None) {
2200	    targets = _SelectionTargets(w);
2201	}
2202
2203	if (targets != 0) {
2204	    struct _SelectionList *list;
2205
2206	    target = targets[0];
2207
2208	    if (targets[1] == None) {	/* last target in list */
2209		params++;
2210		num_params--;
2211		targets = _SelectionTargets(w);
2212	    } else {
2213		targets = &(targets[1]);
2214	    }
2215
2216	    if (num_params) {
2217		/* 'list' is freed in SelectionReceived */
2218		list = TypeXtMalloc(struct _SelectionList);
2219		if (list != 0) {
2220		    list->params = params;
2221		    list->count = num_params;
2222		    list->targets = targets;
2223		    list->time = ev_time;
2224		}
2225	    } else {
2226		list = NULL;
2227	    }
2228
2229	    XtGetSelectionValue(w, selection,
2230				target,
2231				SelectionReceived,
2232				(XtPointer) list, ev_time);
2233	}
2234    }
2235}
2236
2237#if OPT_TRACE && OPT_WIDE_CHARS
2238static void
2239GettingSelection(Display *dpy, Atom type, Char *line, unsigned long len)
2240{
2241    Char *cp;
2242    const char *name = TraceAtomName(dpy, type);
2243
2244    TRACE(("Getting %s (type=%ld, length=%ld)\n", name, (long int) type, len));
2245    for (cp = line; cp < line + len; cp++) {
2246	TRACE(("[%d:%lu]", (int) (cp + 1 - line), len));
2247	if (isprint(*cp)) {
2248	    TRACE(("%c\n", *cp));
2249	} else {
2250	    TRACE(("\\x%02x\n", *cp));
2251	}
2252    }
2253}
2254#else
2255#define GettingSelection(dpy,type,line,len)	/* nothing */
2256#endif
2257
2258#ifdef VMS
2259#  define tty_vwrite(pty,lag,l)		tt_write(lag,l)
2260#else /* !( VMS ) */
2261#  define tty_vwrite(pty,lag,l)		v_write(pty,lag,l)
2262#endif /* defined VMS */
2263
2264#if OPT_PASTE64
2265/* Return base64 code character given 6-bit number */
2266static const char base64_code[] = "\
2267ABCDEFGHIJKLMNOPQRSTUVWXYZ\
2268abcdefghijklmnopqrstuvwxyz\
22690123456789+/";
2270static void
2271base64_flush(TScreen *screen)
2272{
2273    Char x;
2274
2275    TRACE(("base64_flush count %d, pad %d (%d)\n",
2276	   screen->base64_count,
2277	   screen->base64_pad,
2278	   screen->base64_pad & 3));
2279
2280    switch (screen->base64_count) {
2281    case 0:
2282	break;
2283    case 2:
2284	x = CharOf(base64_code[screen->base64_accu << 4]);
2285	tty_vwrite(screen->respond, &x, 1);
2286	break;
2287    case 4:
2288	x = CharOf(base64_code[screen->base64_accu << 2]);
2289	tty_vwrite(screen->respond, &x, 1);
2290	break;
2291    }
2292    if (screen->base64_pad & 3) {
2293	tty_vwrite(screen->respond,
2294		   (const Char *) "===",
2295		   (unsigned) (3 - (screen->base64_pad & 3)));
2296    }
2297    screen->base64_count = 0;
2298    screen->base64_accu = 0;
2299    screen->base64_pad = 0;
2300}
2301#endif /* OPT_PASTE64 */
2302
2303/*
2304 * Translate ISO-8859-1 or UTF-8 data to NRCS.
2305 */
2306static void
2307ToNational(XtermWidget xw, Char *buffer, unsigned *length)
2308{
2309    TScreen *screen = TScreenOf(xw);
2310    DECNRCM_codes gsetL = screen->gsets[screen->curgl];
2311    DECNRCM_codes gsetR = screen->gsets[screen->curgr];
2312
2313#if OPT_WIDE_CHARS
2314    if ((screen->utf8_nrc_mode | screen->utf8_mode) != uFalse) {
2315	Char *p;
2316	PtyData *data = TypeXtMallocX(PtyData, *length);
2317
2318	memset(data, 0, sizeof(*data));
2319	data->next = data->buffer;
2320	data->last = data->buffer + *length;
2321	memcpy(data->buffer, buffer, (size_t) *length);
2322	p = buffer;
2323	while (data->next < data->last) {
2324	    unsigned chr, out, gl, gr;
2325
2326	    if (!decodeUtf8(screen, data)) {
2327		data->utf_size = 1;
2328		data->utf_data = data->next[0];
2329	    }
2330	    data->next += data->utf_size;
2331	    chr = data->utf_data;
2332	    out = chr;
2333	    if ((gl = xtermCharSetIn(xw, chr, gsetL)) != chr) {
2334		out = gl;
2335	    } else if ((gr = xtermCharSetIn(xw, chr, gsetR)) != chr) {
2336		out = gr;
2337	    }
2338	    *p++ = (Char) ((out < 256) ? out : ' ');
2339	}
2340	*length = (unsigned) (p - buffer);
2341	free(data);
2342    } else
2343#endif
2344    {
2345	Char *p;
2346
2347	for (p = buffer; (int) (p - buffer) < (int) *length; ++p) {
2348	    unsigned gl, gr;
2349	    unsigned chr = *p;
2350	    unsigned out = chr;
2351	    if ((gl = xtermCharSetIn(xw, chr, gsetL)) != chr) {
2352		out = gl;
2353	    } else if ((gr = xtermCharSetIn(xw, chr, gsetR)) != chr) {
2354		out = gr;
2355	    }
2356	    *p = (Char) out;
2357	}
2358    }
2359}
2360
2361static void
2362_qWriteSelectionData(XtermWidget xw, Char *lag, unsigned length)
2363{
2364    TScreen *screen = TScreenOf(xw);
2365
2366    /*
2367     * If we are pasting into a window which is using NRCS, we want to map
2368     * the text from the normal encoding (ISO-8859-1 or UTF-8) into the coding
2369     * that an application would use to write characters with NRCS.
2370     *
2371     * TODO: handle conversion from UTF-8, and adjust length.  This can be done
2372     * in the same buffer because the target is always 8-bit.
2373     */
2374    if ((xw->flags & NATIONAL) && (length != 0)) {
2375	ToNational(xw, lag, &length);
2376    }
2377#if OPT_PASTE64
2378    if (screen->base64_paste) {
2379	/* Send data as base64 */
2380	Char *p = lag;
2381	Char buf[64];
2382	unsigned x = 0;
2383
2384	TRACE(("convert to base64 %d:%s\n", length, visibleChars(p, length)));
2385
2386	/*
2387	 * Handle the case where the selection is from _this_ xterm, which
2388	 * puts part of the reply in the buffer before the selection callback
2389	 * happens.
2390	 */
2391	if (screen->base64_paste && screen->unparse_len) {
2392	    unparse_end(xw);
2393	}
2394	while (length--) {
2395	    switch (screen->base64_count) {
2396	    case 0:
2397		buf[x++] = CharOf(base64_code[*p >> 2]);
2398		screen->base64_accu = (unsigned) (*p & 0x3);
2399		screen->base64_count = 2;
2400		++p;
2401		break;
2402	    case 2:
2403		buf[x++] = CharOf(base64_code[(screen->base64_accu << 4) +
2404					      (*p >> 4)]);
2405		screen->base64_accu = (unsigned) (*p & 0xF);
2406		screen->base64_count = 4;
2407		++p;
2408		break;
2409	    case 4:
2410		buf[x++] = CharOf(base64_code[(screen->base64_accu << 2) +
2411					      (*p >> 6)]);
2412		buf[x++] = CharOf(base64_code[*p & 0x3F]);
2413		screen->base64_accu = 0;
2414		screen->base64_count = 0;
2415		++p;
2416		break;
2417	    }
2418	    if (x >= 63) {
2419		/* Write 63 or 64 characters */
2420		screen->base64_pad += x;
2421		TRACE(("writing base64 interim %s\n", visibleChars(buf, x)));
2422		tty_vwrite(screen->respond, buf, x);
2423		x = 0;
2424	    }
2425	}
2426	if (x != 0) {
2427	    screen->base64_pad += x;
2428	    TRACE(("writing base64 finish %s\n", visibleChars(buf, x)));
2429	    tty_vwrite(screen->respond, buf, x);
2430	}
2431    } else
2432#endif /* OPT_PASTE64 */
2433#if OPT_READLINE
2434    if (SCREEN_FLAG(screen, paste_quotes)) {
2435	while (length--) {
2436	    tty_vwrite(screen->respond, (const Char *) "\026", 1);	/* Control-V */
2437	    tty_vwrite(screen->respond, lag++, 1);
2438	}
2439    } else
2440#endif
2441    {
2442	TRACE(("writing base64 padding %s\n", visibleChars(lag, length)));
2443	tty_vwrite(screen->respond, lag, length);
2444    }
2445}
2446
2447static void
2448_WriteSelectionData(XtermWidget xw, Char *line, size_t length)
2449{
2450    /* Write data to pty a line at a time. */
2451    /* Doing this one line at a time may no longer be necessary
2452       because v_write has been re-written. */
2453
2454#if OPT_PASTE64
2455    TScreen *screen = TScreenOf(xw);
2456#endif
2457    Char *lag, *end;
2458
2459    /* in the VMS version, if tt_pasting isn't set to True then qio
2460       reads aren't blocked and an infinite loop is entered, where the
2461       pasted text shows up as new input, goes in again, shows up
2462       again, ad nauseum. */
2463#ifdef VMS
2464    tt_pasting = True;
2465#endif
2466
2467    end = &line[length];
2468    lag = line;
2469
2470#if OPT_PASTE64
2471    if (screen->base64_paste) {
2472	_qWriteSelectionData(xw, lag, (unsigned) (end - lag));
2473	base64_flush(screen);
2474    } else
2475#endif
2476    {
2477	if (!SCREEN_FLAG(screen, paste_literal_nl)) {
2478	    Char *cp;
2479	    for (cp = line; cp != end; cp++) {
2480		if (*cp == '\n') {
2481		    *cp = '\r';
2482		    _qWriteSelectionData(xw, lag, (unsigned) (cp - lag + 1));
2483		    lag = cp + 1;
2484		}
2485	    }
2486	}
2487
2488	if (lag != end) {
2489	    _qWriteSelectionData(xw, lag, (unsigned) (end - lag));
2490	}
2491    }
2492#ifdef VMS
2493    tt_pasting = False;
2494    tt_start_read();		/* reenable reads or a character may be lost */
2495#endif
2496}
2497
2498#if OPT_PASTE64 || OPT_READLINE
2499static void
2500_WriteKey(TScreen *screen, const Char *in)
2501{
2502    Char line[16];
2503    unsigned count = 0;
2504    size_t length = strlen((const char *) in);
2505
2506    if (screen->control_eight_bits) {
2507	line[count++] = ANSI_CSI;
2508    } else {
2509	line[count++] = ANSI_ESC;
2510	line[count++] = '[';
2511    }
2512    while (length--)
2513	line[count++] = *in++;
2514    line[count++] = '~';
2515    tty_vwrite(screen->respond, line, count);
2516}
2517#endif /* OPT_READLINE */
2518
2519/*
2520 * Unless enabled by the user, strip control characters other than formatting.
2521 */
2522static size_t
2523removeControls(XtermWidget xw, char *value)
2524{
2525    TScreen *screen = TScreenOf(xw);
2526    size_t dst = 0;
2527
2528    if (screen->allowPasteControls) {
2529	dst = strlen(value);
2530    } else {
2531	size_t src = 0;
2532	while ((value[dst] = value[src]) != '\0') {
2533	    int ch = CharOf(value[src++]);
2534
2535#define ReplacePaste(n) \
2536	    if (screen->disallow_paste_controls[n]) \
2537		value[dst] = ' '
2538
2539	    if (ch < 32) {
2540		ReplacePaste(epC0);
2541		ReplacePaste(ch);
2542		++dst;
2543	    } else if (ch == ANSI_DEL) {
2544		ReplacePaste(epDEL);
2545		++dst;
2546	    }
2547#if OPT_WIDE_CHARS
2548	    else if (screen->utf8_inparse || screen->utf8_nrc_mode)
2549		++dst;
2550#endif
2551#if OPT_C1_PRINT || OPT_WIDE_CHARS
2552	    else if (screen->c1_printable)
2553		++dst;
2554#endif
2555	    else if (ch >= 128 && ch < 160)
2556		continue;
2557	    else
2558		++dst;
2559	}
2560    }
2561    return dst;
2562}
2563
2564#if OPT_SELECTION_OPS
2565static void
2566beginInternalSelect(XtermWidget xw)
2567{
2568    TScreen *screen = TScreenOf(xw);
2569    InternalSelect *mydata = &(screen->internal_select);
2570
2571    (void) mydata;
2572    /* override flags so that SelectionReceived only updates a buffer */
2573#if OPT_PASTE64
2574    mydata->base64_paste = screen->base64_paste;
2575    screen->base64_paste = 0;
2576#endif
2577#if OPT_PASTE64 || OPT_READLINE
2578    mydata->paste_brackets = screen->paste_brackets;
2579    SCREEN_FLAG_unset(screen, paste_brackets);
2580#endif
2581}
2582
2583static void
2584finishInternalSelect(XtermWidget xw)
2585{
2586    TScreen *screen = TScreenOf(xw);
2587    InternalSelect *mydata = &(screen->internal_select);
2588
2589    (void) mydata;
2590#if OPT_PASTE64
2591    screen->base64_paste = mydata->base64_paste;
2592#endif
2593#if OPT_PASTE64 || OPT_READLINE
2594    screen->paste_brackets = mydata->paste_brackets;
2595#endif
2596}
2597
2598#else
2599#define finishInternalSelect(xw)	/* nothing */
2600#endif /* OPT_SELECTION_OPS */
2601
2602/* SelectionReceived: stuff received selection text into pty */
2603
2604/* ARGSUSED */
2605static void
2606SelectionReceived(Widget w,
2607		  XtPointer client_data,
2608		  Atom *selection GCC_UNUSED,
2609		  Atom *type,
2610		  XtPointer value,
2611		  unsigned long *length,
2612		  int *format)
2613{
2614    char **text_list = NULL;
2615    int text_list_count = 0;
2616    XTextProperty text_prop;
2617    TScreen *screen;
2618    Display *dpy;
2619#if OPT_TRACE && OPT_WIDE_CHARS
2620    Char *line = (Char *) value;
2621#endif
2622
2623    XtermWidget xw;
2624
2625    if ((xw = getXtermWidget(w)) == 0)
2626	return;
2627
2628    screen = TScreenOf(xw);
2629    dpy = XtDisplay(w);
2630
2631    if (*type == 0		/*XT_CONVERT_FAIL */
2632	|| *length == 0
2633	|| value == NULL) {
2634	TRACE(("...no data to convert\n"));
2635	goto fail;
2636    }
2637
2638    text_prop.value = (unsigned char *) value;
2639    text_prop.encoding = *type;
2640    text_prop.format = *format;
2641    text_prop.nitems = *length;
2642
2643    TRACE(("SelectionReceived %s %s format %d, nitems %ld\n",
2644	   TraceAtomName(screen->display, *selection),
2645	   visibleSelectionTarget(dpy, text_prop.encoding),
2646	   text_prop.format,
2647	   text_prop.nitems));
2648
2649#if OPT_WIDE_CHARS
2650    if (XSupportsLocale() && screen->wide_chars) {
2651	if (*type == XA_UTF8_STRING(dpy) ||
2652	    *type == XA_STRING ||
2653	    *type == XA_COMPOUND_TEXT(dpy)) {
2654	    GettingSelection(dpy, *type, line, *length);
2655	    if (Xutf8TextPropertyToTextList(dpy, &text_prop,
2656					    &text_list,
2657					    &text_list_count) < 0) {
2658		TRACE(("default Xutf8 Conversion failed\n"));
2659		text_list = NULL;
2660	    }
2661	}
2662    } else
2663#endif /* OPT_WIDE_CHARS */
2664    {
2665	/* Convert the selection to locale's multibyte encoding. */
2666
2667	if (*type == XA_UTF8_STRING(dpy) ||
2668	    *type == XA_STRING ||
2669	    *type == XA_COMPOUND_TEXT(dpy)) {
2670	    Status rc;
2671
2672	    GettingSelection(dpy, *type, line, *length);
2673
2674#if OPT_WIDE_CHARS
2675	    if (*type == XA_UTF8_STRING(dpy) &&
2676		!(screen->wide_chars || screen->c1_printable)) {
2677		rc = xtermUtf8ToTextList(xw, &text_prop,
2678					 &text_list, &text_list_count);
2679	    } else
2680#endif
2681	    if (*type == XA_STRING && (!XSupportsLocale() || screen->brokenSelections)) {
2682		rc = XTextPropertyToStringList(&text_prop,
2683					       &text_list, &text_list_count);
2684	    } else {
2685		rc = XmbTextPropertyToTextList(dpy, &text_prop,
2686					       &text_list,
2687					       &text_list_count);
2688	    }
2689	    if (rc < 0) {
2690		TRACE(("Conversion failed\n"));
2691		text_list = NULL;
2692	    }
2693	}
2694    }
2695
2696    if (text_list != NULL && text_list_count != 0) {
2697	int i;
2698
2699#if OPT_PASTE64
2700	if (screen->base64_paste) {
2701	    /* EMPTY */ ;
2702	} else
2703#endif
2704#if OPT_PASTE64 || OPT_READLINE
2705	if (SCREEN_FLAG(screen, paste_brackets) && !screen->selectToBuffer) {
2706	    _WriteKey(screen, (const Char *) "200");
2707	}
2708#endif
2709	for (i = 0; i < text_list_count; i++) {
2710	    size_t len = removeControls(xw, text_list[i]);
2711
2712	    if (screen->selectToBuffer) {
2713		InternalSelect *mydata = &(screen->internal_select);
2714		if (!mydata->done) {
2715		    size_t have = (mydata->buffer
2716				   ? strlen(mydata->buffer)
2717				   : 0);
2718		    size_t need = have + len + 1;
2719		    char *buffer = realloc(mydata->buffer, need);
2720
2721		    if (buffer != 0) {
2722			strcpy(buffer + have, text_list[i]);
2723			mydata->buffer = buffer;
2724		    }
2725		    TRACE(("FormatSelect %d.%d .. %d.%d %s\n",
2726			   screen->startSel.row,
2727			   screen->startSel.col,
2728			   screen->endSel.row,
2729			   screen->endSel.col,
2730			   mydata->buffer));
2731		    mydata->format_select(w, mydata->format, mydata->buffer,
2732					  &(screen->startSel),
2733					  &(screen->endSel));
2734		    mydata->done = True;
2735		}
2736
2737	    } else {
2738		_WriteSelectionData(xw, (Char *) text_list[i], len);
2739	    }
2740	}
2741#if OPT_PASTE64
2742	if (screen->base64_paste) {
2743	    FinishPaste64(xw);
2744	} else
2745#endif
2746#if OPT_PASTE64 || OPT_READLINE
2747	if (SCREEN_FLAG(screen, paste_brackets) && !screen->selectToBuffer) {
2748	    _WriteKey(screen, (const Char *) "201");
2749	}
2750#endif
2751	if (screen->selectToBuffer) {
2752	    InternalSelect *mydata = &(screen->internal_select);
2753	    finishInternalSelect(xw);
2754	    if (mydata->done) {
2755		free(mydata->format);
2756		free(mydata->buffer);
2757		memset(mydata, 0, sizeof(*mydata));
2758	    }
2759	    screen->selectToBuffer = False;
2760	}
2761	XFreeStringList(text_list);
2762    } else {
2763	TRACE(("...empty text-list\n"));
2764	goto fail;
2765    }
2766
2767    XtFree((char *) client_data);
2768    XtFree((char *) value);
2769
2770    return;
2771
2772  fail:
2773    if (client_data != 0) {
2774	struct _SelectionList *list = (struct _SelectionList *) client_data;
2775
2776	TRACE(("SelectionReceived ->xtermGetSelection\n"));
2777	xtermGetSelection(w, list->time,
2778			  list->params, list->count, list->targets);
2779	XtFree((char *) client_data);
2780#if OPT_PASTE64
2781    } else {
2782	FinishPaste64(xw);
2783#endif
2784    }
2785    return;
2786}
2787
2788void
2789HandleInsertSelection(Widget w,
2790		      XEvent *event,	/* assumed to be XButtonEvent* */
2791		      String *params,	/* selections in precedence order */
2792		      Cardinal *num_params)
2793{
2794    XtermWidget xw;
2795
2796    if ((xw = getXtermWidget(w)) != 0) {
2797	TRACE_EVENT("HandleInsertSelection", event, params, num_params);
2798	if (!SendMousePosition(xw, event)) {
2799#if OPT_READLINE
2800	    int ldelta;
2801	    TScreen *screen = TScreenOf(xw);
2802	    if (IsBtnEvent(event)
2803		&& !OverrideEvent(event)
2804		&& (okSendMousePos(xw) == MOUSE_OFF)
2805		&& SCREEN_FLAG(screen, paste_moves)
2806		&& rowOnCurrentLine(screen, eventRow(screen, event), &ldelta))
2807		ReadLineMovePoint(screen, eventColBetween(screen, event), ldelta);
2808#endif /* OPT_READLINE */
2809
2810	    xtermGetSelection(w, event->xbutton.time, params, *num_params, NULL);
2811	}
2812    }
2813}
2814
2815static SelectUnit
2816EvalSelectUnit(XtermWidget xw,
2817	       Time buttonDownTime,
2818	       SelectUnit defaultUnit,
2819	       unsigned int button)
2820{
2821    TScreen *screen = TScreenOf(xw);
2822    SelectUnit result;
2823    int delta;
2824
2825    if (button != screen->lastButton) {
2826	delta = screen->multiClickTime + 1;
2827    } else if (screen->lastButtonUpTime == (Time) 0) {
2828	/* first time and once in a blue moon */
2829	delta = screen->multiClickTime + 1;
2830    } else if (buttonDownTime > screen->lastButtonUpTime) {
2831	/* most of the time */
2832	delta = (int) (buttonDownTime - screen->lastButtonUpTime);
2833    } else {
2834	/* time has rolled over since lastButtonUpTime */
2835	delta = (int) ((((Time) ~ 0) - screen->lastButtonUpTime) + buttonDownTime);
2836    }
2837
2838    if (delta > screen->multiClickTime) {
2839	screen->numberOfClicks = 1;
2840	result = defaultUnit;
2841    } else {
2842	result = screen->selectMap[screen->numberOfClicks % screen->maxClicks];
2843	screen->numberOfClicks += 1;
2844    }
2845    TRACE(("EvalSelectUnit(%d) = %d\n", screen->numberOfClicks, result));
2846    return result;
2847}
2848
2849static void
2850do_select_start(XtermWidget xw,
2851		XEvent *event,	/* must be XButtonEvent* */
2852		CELL *cell)
2853{
2854    TScreen *screen = TScreenOf(xw);
2855
2856    if (SendMousePosition(xw, event))
2857	return;
2858    screen->selectUnit = EvalSelectUnit(xw,
2859					event->xbutton.time,
2860					Select_CHAR,
2861					event->xbutton.button);
2862    screen->replyToEmacs = False;
2863
2864#if OPT_READLINE
2865    lastButtonDownTime = event->xbutton.time;
2866#endif
2867
2868    StartSelect(xw, cell);
2869}
2870
2871/* ARGSUSED */
2872void
2873HandleSelectStart(Widget w,
2874		  XEvent *event,	/* must be XButtonEvent* */
2875		  String *params GCC_UNUSED,
2876		  Cardinal *num_params GCC_UNUSED)
2877{
2878    XtermWidget xw;
2879
2880    if ((xw = getXtermWidget(w)) != 0) {
2881	TScreen *screen = TScreenOf(xw);
2882	CELL cell;
2883
2884	TRACE_EVENT("HandleSelectStart", event, params, num_params);
2885	screen->firstValidRow = 0;
2886	screen->lastValidRow = screen->max_row;
2887	PointToCELL(screen, event->xbutton.y, event->xbutton.x, &cell);
2888
2889#if OPT_READLINE
2890	ExtendingSelection = 0;
2891#endif
2892
2893	do_select_start(xw, event, &cell);
2894    }
2895}
2896
2897/* ARGSUSED */
2898void
2899HandleKeyboardSelectStart(Widget w,
2900			  XEvent *event,	/* must be XButtonEvent* */
2901			  String *params GCC_UNUSED,
2902			  Cardinal *num_params GCC_UNUSED)
2903{
2904    XtermWidget xw;
2905
2906    if ((xw = getXtermWidget(w)) != 0) {
2907	TScreen *screen = TScreenOf(xw);
2908
2909	TRACE_EVENT("HandleKeyboardSelectStart", event, params, num_params);
2910	do_select_start(xw, event, &screen->cursorp);
2911    }
2912}
2913
2914static void
2915TrackDown(XtermWidget xw, XButtonEvent *event)
2916{
2917    TScreen *screen = TScreenOf(xw);
2918    CELL cell;
2919
2920    screen->selectUnit = EvalSelectUnit(xw,
2921					event->time,
2922					Select_CHAR,
2923					event->button);
2924    if (screen->numberOfClicks > 1) {
2925	PointToCELL(screen, event->y, event->x, &cell);
2926	screen->replyToEmacs = True;
2927	StartSelect(xw, &cell);
2928    } else {
2929	screen->waitingForTrackInfo = True;
2930	EditorButton(xw, event);
2931    }
2932}
2933
2934#define boundsCheck(x)	if (x < 0) \
2935			    x = 0; \
2936			else if (x >= screen->max_row) \
2937			    x = screen->max_row
2938
2939void
2940TrackMouse(XtermWidget xw,
2941	   int func,
2942	   CELL *start,
2943	   int firstrow,
2944	   int lastrow)
2945{
2946    TScreen *screen = TScreenOf(xw);
2947
2948    if (screen->waitingForTrackInfo) {	/* if Timed, ignore */
2949	screen->waitingForTrackInfo = False;
2950
2951	if (func != 0) {
2952	    CELL first = *start;
2953
2954	    boundsCheck(first.row);
2955	    boundsCheck(firstrow);
2956	    boundsCheck(lastrow);
2957	    screen->firstValidRow = firstrow;
2958	    screen->lastValidRow = lastrow;
2959	    screen->replyToEmacs = True;
2960	    StartSelect(xw, &first);
2961	}
2962    }
2963}
2964
2965static void
2966StartSelect(XtermWidget xw, const CELL *cell)
2967{
2968    TScreen *screen = TScreenOf(xw);
2969
2970    TRACE(("StartSelect row=%d, col=%d\n", cell->row, cell->col));
2971    if (screen->cursor_state)
2972	HideCursor(xw);
2973    if (screen->numberOfClicks == 1) {
2974	/* set start of selection */
2975	screen->rawPos = *cell;
2976    }
2977    /* else use old values in rawPos */
2978    screen->saveStartR = screen->startExt = screen->rawPos;
2979    screen->saveEndR = screen->endExt = screen->rawPos;
2980    if (Coordinate(screen, cell) < Coordinate(screen, &(screen->rawPos))) {
2981	screen->eventMode = LEFTEXTENSION;
2982	screen->startExt = *cell;
2983    } else {
2984	screen->eventMode = RIGHTEXTENSION;
2985	screen->endExt = *cell;
2986    }
2987    ComputeSelect(xw, &(screen->startExt), &(screen->endExt), False, True);
2988}
2989
2990static void
2991EndExtend(XtermWidget xw,
2992	  XEvent *event,	/* must be XButtonEvent */
2993	  String *params,	/* selections */
2994	  Cardinal num_params,
2995	  Bool use_cursor_loc)
2996{
2997    CELL cell;
2998    TScreen *screen = TScreenOf(xw);
2999
3000    TRACE_EVENT("EndExtend", event, params, &num_params);
3001    if (use_cursor_loc) {
3002	cell = screen->cursorp;
3003    } else {
3004	PointToCELL(screen, event->xbutton.y, event->xbutton.x, &cell);
3005    }
3006    ExtendExtend(xw, &cell);
3007
3008    screen->lastButtonUpTime = event->xbutton.time;
3009    screen->lastButton = event->xbutton.button;
3010
3011    if (!isSameCELL(&(screen->startSel), &(screen->endSel))) {
3012	if (screen->replyToEmacs) {
3013	    Char line[64];
3014	    unsigned count = 0;
3015
3016	    if (screen->control_eight_bits) {
3017		line[count++] = ANSI_CSI;
3018	    } else {
3019		line[count++] = ANSI_ESC;
3020		line[count++] = '[';
3021	    }
3022	    if (isSameCELL(&(screen->rawPos), &(screen->startSel))
3023		&& isSameCELL(&cell, &(screen->endSel))) {
3024		/* Use short-form emacs select */
3025
3026		switch (screen->extend_coords) {
3027		case 0:
3028		case SET_EXT_MODE_MOUSE:
3029		    line[count++] = 't';
3030		    break;
3031		case SET_SGR_EXT_MODE_MOUSE:
3032		case SET_PIXEL_POSITION_MOUSE:
3033		    line[count++] = '<';
3034		    break;
3035		}
3036
3037		count = EmitMousePosition(screen, line, count, screen->endSel.col);
3038		count = EmitMousePositionSeparator(screen, line, count);
3039		count = EmitMousePosition(screen, line, count, screen->endSel.row);
3040
3041		switch (screen->extend_coords) {
3042		case SET_SGR_EXT_MODE_MOUSE:
3043		case SET_URXVT_EXT_MODE_MOUSE:
3044		case SET_PIXEL_POSITION_MOUSE:
3045		    line[count++] = 't';
3046		    break;
3047		}
3048	    } else {
3049		/* long-form, specify everything */
3050
3051		switch (screen->extend_coords) {
3052		case 0:
3053		case SET_EXT_MODE_MOUSE:
3054		    line[count++] = 'T';
3055		    break;
3056		case SET_SGR_EXT_MODE_MOUSE:
3057		case SET_PIXEL_POSITION_MOUSE:
3058		    line[count++] = '<';
3059		    break;
3060		}
3061
3062		count = EmitMousePosition(screen, line, count, screen->startSel.col);
3063		count = EmitMousePositionSeparator(screen, line, count);
3064		count = EmitMousePosition(screen, line, count, screen->startSel.row);
3065		count = EmitMousePositionSeparator(screen, line, count);
3066		count = EmitMousePosition(screen, line, count, screen->endSel.col);
3067		count = EmitMousePositionSeparator(screen, line, count);
3068		count = EmitMousePosition(screen, line, count, screen->endSel.row);
3069		count = EmitMousePositionSeparator(screen, line, count);
3070		count = EmitMousePosition(screen, line, count, cell.col);
3071		count = EmitMousePositionSeparator(screen, line, count);
3072		count = EmitMousePosition(screen, line, count, cell.row);
3073
3074		switch (screen->extend_coords) {
3075		case SET_SGR_EXT_MODE_MOUSE:
3076		case SET_URXVT_EXT_MODE_MOUSE:
3077		case SET_PIXEL_POSITION_MOUSE:
3078		    line[count++] = 'T';
3079		    break;
3080		}
3081	    }
3082	    v_write(screen->respond, line, count);
3083	    UnHiliteText(xw);
3084	}
3085    }
3086    SelectSet(xw, event, params, num_params);
3087    screen->eventMode = NORMAL;
3088}
3089
3090void
3091HandleSelectSet(Widget w,
3092		XEvent *event,
3093		String *params,
3094		Cardinal *num_params)
3095{
3096    XtermWidget xw;
3097
3098    if ((xw = getXtermWidget(w)) != 0) {
3099	TRACE_EVENT("HandleSelectSet", event, params, num_params);
3100	SelectSet(xw, event, params, *num_params);
3101    }
3102}
3103
3104/* ARGSUSED */
3105static void
3106SelectSet(XtermWidget xw,
3107	  XEvent *event GCC_UNUSED,
3108	  String *params,
3109	  Cardinal num_params)
3110{
3111    TScreen *screen = TScreenOf(xw);
3112
3113    TRACE(("SelectSet\n"));
3114    /* Only do select stuff if non-null select */
3115    if (!isSameCELL(&(screen->startSel), &(screen->endSel))) {
3116	Cardinal n;
3117	for (n = 0; n < num_params; ++n) {
3118	    SaltTextAway(xw,
3119			 TargetToSelection(screen, params[n]),
3120			 &(screen->startSel), &(screen->endSel));
3121	}
3122	_OwnSelection(xw, params, num_params);
3123    } else {
3124	ScrnDisownSelection(xw);
3125    }
3126}
3127
3128#define Abs(x)		((x) < 0 ? -(x) : (x))
3129
3130/* ARGSUSED */
3131static void
3132do_start_extend(XtermWidget xw,
3133		XEvent *event,	/* must be XButtonEvent* */
3134		String *params GCC_UNUSED,
3135		Cardinal *num_params GCC_UNUSED,
3136		Bool use_cursor_loc)
3137{
3138    TScreen *screen = TScreenOf(xw);
3139    int coord;
3140    CELL cell;
3141
3142    if (SendMousePosition(xw, event))
3143	return;
3144
3145    screen->firstValidRow = 0;
3146    screen->lastValidRow = screen->max_row;
3147#if OPT_READLINE
3148    if (OverrideEvent(event)
3149	|| event->xbutton.button != Button3
3150	|| !(SCREEN_FLAG(screen, dclick3_deletes)))
3151#endif
3152	screen->selectUnit = EvalSelectUnit(xw,
3153					    event->xbutton.time,
3154					    screen->selectUnit,
3155					    event->xbutton.button);
3156    screen->replyToEmacs = False;
3157
3158#if OPT_READLINE
3159    CheckSecondPress3(xw, screen, event);
3160#endif
3161
3162    if (screen->numberOfClicks == 1
3163	|| (SCREEN_FLAG(screen, dclick3_deletes)
3164	    && !OverrideEvent(event))) {
3165	/* Save existing selection so we can reestablish it if the guy
3166	   extends past the other end of the selection */
3167	screen->saveStartR = screen->startExt = screen->startRaw;
3168	screen->saveEndR = screen->endExt = screen->endRaw;
3169    } else {
3170	/* He just needed the selection mode changed, use old values. */
3171	screen->startExt = screen->startRaw = screen->saveStartR;
3172	screen->endExt = screen->endRaw = screen->saveEndR;
3173    }
3174    if (use_cursor_loc) {
3175	cell = screen->cursorp;
3176    } else {
3177	PointToCELL(screen, event->xbutton.y, event->xbutton.x, &cell);
3178    }
3179    coord = Coordinate(screen, &cell);
3180
3181    if (Abs(coord - Coordinate(screen, &(screen->startSel)))
3182	< Abs(coord - Coordinate(screen, &(screen->endSel)))
3183	|| coord < Coordinate(screen, &(screen->startSel))) {
3184	/* point is close to left side of selection */
3185	screen->eventMode = LEFTEXTENSION;
3186	screen->startExt = cell;
3187    } else {
3188	/* point is close to left side of selection */
3189	screen->eventMode = RIGHTEXTENSION;
3190	screen->endExt = cell;
3191    }
3192    ComputeSelect(xw, &(screen->startExt), &(screen->endExt), True, True);
3193
3194#if OPT_READLINE
3195    if (!isSameCELL(&(screen->startSel), &(screen->endSel)))
3196	ExtendingSelection = 1;
3197#endif
3198}
3199
3200static void
3201ExtendExtend(XtermWidget xw, const CELL *cell)
3202{
3203    TScreen *screen = TScreenOf(xw);
3204    int coord = Coordinate(screen, cell);
3205
3206    TRACE(("ExtendExtend row=%d, col=%d\n", cell->row, cell->col));
3207    if (screen->eventMode == LEFTEXTENSION
3208	&& ((coord + (screen->selectUnit != Select_CHAR))
3209	    > Coordinate(screen, &(screen->endSel)))) {
3210	/* Whoops, he's changed his mind.  Do RIGHTEXTENSION */
3211	screen->eventMode = RIGHTEXTENSION;
3212	screen->startExt = screen->saveStartR;
3213    } else if (screen->eventMode == RIGHTEXTENSION
3214	       && coord < Coordinate(screen, &(screen->startSel))) {
3215	/* Whoops, he's changed his mind.  Do LEFTEXTENSION */
3216	screen->eventMode = LEFTEXTENSION;
3217	screen->endExt = screen->saveEndR;
3218    }
3219    if (screen->eventMode == LEFTEXTENSION) {
3220	screen->startExt = *cell;
3221    } else {
3222	screen->endExt = *cell;
3223    }
3224    ComputeSelect(xw, &(screen->startExt), &(screen->endExt), False, True);
3225
3226#if OPT_READLINE
3227    if (!isSameCELL(&(screen->startSel), &(screen->endSel)))
3228	ExtendingSelection = 1;
3229#endif
3230}
3231
3232void
3233HandleStartExtend(Widget w,
3234		  XEvent *event,	/* must be XButtonEvent* */
3235		  String *params,	/* unused */
3236		  Cardinal *num_params)		/* unused */
3237{
3238    XtermWidget xw;
3239
3240    if ((xw = getXtermWidget(w)) != 0) {
3241	TRACE_EVENT("HandleStartExtend", event, params, num_params);
3242	do_start_extend(xw, event, params, num_params, False);
3243    }
3244}
3245
3246void
3247HandleKeyboardStartExtend(Widget w,
3248			  XEvent *event,	/* must be XButtonEvent* */
3249			  String *params,	/* unused */
3250			  Cardinal *num_params)		/* unused */
3251{
3252    XtermWidget xw;
3253
3254    if ((xw = getXtermWidget(w)) != 0) {
3255	TRACE_EVENT("HandleKeyboardStartExtend", event, params, num_params);
3256	do_start_extend(xw, event, params, num_params, True);
3257    }
3258}
3259
3260void
3261ScrollSelection(TScreen *screen, int amount, Bool always)
3262{
3263    int minrow = INX2ROW(screen, -screen->savedlines);
3264    int maxrow = INX2ROW(screen, screen->max_row);
3265    int maxcol = screen->max_col;
3266
3267#define scroll_update_one(cell) \
3268	(cell)->row += amount; \
3269	if ((cell)->row < minrow) { \
3270	    (cell)->row = minrow; \
3271	    (cell)->col = 0; \
3272	} \
3273	if ((cell)->row > maxrow) { \
3274	    (cell)->row = maxrow; \
3275	    (cell)->col = maxcol; \
3276	}
3277
3278    scroll_update_one(&(screen->startRaw));
3279    scroll_update_one(&(screen->endRaw));
3280    scroll_update_one(&(screen->startSel));
3281    scroll_update_one(&(screen->endSel));
3282
3283    scroll_update_one(&(screen->rawPos));
3284
3285    /*
3286     * If we are told to scroll the selection but it lies outside the scrolling
3287     * margins, then that could cause the selection to move (bad).  It is not
3288     * simple to fix, because this function is called both for the scrollbar
3289     * actions as well as application scrolling.  The 'always' flag is set in
3290     * the former case.  The rest of the logic handles the latter.
3291     */
3292    if (ScrnHaveSelection(screen)) {
3293	int adjust;
3294
3295	adjust = ROW2INX(screen, screen->startH.row);
3296	if (always
3297	    || !ScrnHaveRowMargins(screen)
3298	    || ScrnIsRowInMargins(screen, adjust)) {
3299	    scroll_update_one(&screen->startH);
3300	}
3301	adjust = ROW2INX(screen, screen->endH.row);
3302	if (always
3303	    || !ScrnHaveRowMargins(screen)
3304	    || ScrnIsRowInMargins(screen, adjust)) {
3305	    scroll_update_one(&screen->endH);
3306	}
3307    }
3308
3309    screen->startHCoord = Coordinate(screen, &screen->startH);
3310    screen->endHCoord = Coordinate(screen, &screen->endH);
3311}
3312
3313/*ARGSUSED*/
3314void
3315ResizeSelection(TScreen *screen, int rows, int cols)
3316{
3317    rows--;			/* decr to get 0-max */
3318    cols--;
3319
3320    if (screen->startRaw.row > rows)
3321	screen->startRaw.row = rows;
3322    if (screen->startSel.row > rows)
3323	screen->startSel.row = rows;
3324    if (screen->endRaw.row > rows)
3325	screen->endRaw.row = rows;
3326    if (screen->endSel.row > rows)
3327	screen->endSel.row = rows;
3328    if (screen->rawPos.row > rows)
3329	screen->rawPos.row = rows;
3330
3331    if (screen->startRaw.col > cols)
3332	screen->startRaw.col = cols;
3333    if (screen->startSel.col > cols)
3334	screen->startSel.col = cols;
3335    if (screen->endRaw.col > cols)
3336	screen->endRaw.col = cols;
3337    if (screen->endSel.col > cols)
3338	screen->endSel.col = cols;
3339    if (screen->rawPos.col > cols)
3340	screen->rawPos.col = cols;
3341}
3342
3343#if OPT_WIDE_CHARS
3344#define isWideCell(row, col) isWideFrg((int)XTERM_CELL(row, col))
3345#endif
3346
3347static void
3348PointToCELL(TScreen *screen,
3349	    int y,
3350	    int x,
3351	    CELL *cell)
3352/* Convert pixel coordinates to character coordinates.
3353   Rows are clipped between firstValidRow and lastValidRow.
3354   Columns are clipped between to be 0 or greater, but are not clipped to some
3355       maximum value. */
3356{
3357    cell->row = (y - screen->border) / FontHeight(screen);
3358    if (cell->row < screen->firstValidRow)
3359	cell->row = screen->firstValidRow;
3360    else if (cell->row > screen->lastValidRow)
3361	cell->row = screen->lastValidRow;
3362    cell->col = (x - OriginX(screen)) / FontWidth(screen);
3363    if (cell->col < 0)
3364	cell->col = 0;
3365    else if (cell->col > MaxCols(screen)) {
3366	cell->col = MaxCols(screen);
3367    }
3368#if OPT_WIDE_CHARS
3369    /*
3370     * If we got a click on the right half of a doublewidth character,
3371     * pretend it happened on the left half.
3372     */
3373    if (cell->col > 0
3374	&& isWideCell(cell->row, cell->col - 1)
3375	&& (XTERM_CELL(cell->row, cell->col) == HIDDEN_CHAR)) {
3376	cell->col -= 1;
3377    }
3378#endif
3379}
3380
3381/*
3382 * Find the last column at which text was drawn on the given row.
3383 */
3384static int
3385LastTextCol(TScreen *screen, CLineData *ld, int row)
3386{
3387    int i = -1;
3388
3389    if (ld != 0) {
3390	if (okScrnRow(screen, row)) {
3391	    const IAttr *ch;
3392	    for (i = screen->max_col,
3393		 ch = ld->attribs + i;
3394		 i >= 0 && !(*ch & CHARDRAWN);
3395		 ch--, i--) {
3396		;
3397	    }
3398#if OPT_DEC_CHRSET
3399	    if (CSET_DOUBLE(GetLineDblCS(ld))) {
3400		i *= 2;
3401	    }
3402#endif
3403	}
3404    }
3405    return (i);
3406}
3407
3408#if !OPT_WIDE_CHARS
3409/*
3410** double click table for cut and paste in 8 bits
3411**
3412** This table is divided in four parts :
3413**
3414**	- control characters	[0,0x1f] U [0x80,0x9f]
3415**	- separators		[0x20,0x3f] U [0xa0,0xb9]
3416**	- binding characters	[0x40,0x7f] U [0xc0,0xff]
3417**	- exceptions
3418*/
3419/* *INDENT-OFF* */
3420static int charClass[256] =
3421{
3422/* NUL  SOH  STX  ETX  EOT  ENQ  ACK  BEL */
3423    32,  1,    1,   1,   1,   1,   1,   1,
3424/*  BS   HT   NL   VT   FF   CR   SO   SI */
3425     1,  32,   1,   1,   1,   1,   1,   1,
3426/* DLE  DC1  DC2  DC3  DC4  NAK  SYN  ETB */
3427     1,   1,   1,   1,   1,   1,   1,   1,
3428/* CAN   EM  SUB  ESC   FS   GS   RS   US */
3429     1,   1,   1,   1,   1,   1,   1,   1,
3430/*  SP    !    "    #    $    %    &    ' */
3431    32,  33,  34,  35,  36,  37,  38,  39,
3432/*   (    )    *    +    ,    -    .    / */
3433    40,  41,  42,  43,  44,  45,  46,  47,
3434/*   0    1    2    3    4    5    6    7 */
3435    48,  48,  48,  48,  48,  48,  48,  48,
3436/*   8    9    :    ;    <    =    >    ? */
3437    48,  48,  58,  59,  60,  61,  62,  63,
3438/*   @    A    B    C    D    E    F    G */
3439    64,  48,  48,  48,  48,  48,  48,  48,
3440/*   H    I    J    K    L    M    N    O */
3441    48,  48,  48,  48,  48,  48,  48,  48,
3442/*   P    Q    R    S    T    U    V    W */
3443    48,  48,  48,  48,  48,  48,  48,  48,
3444/*   X    Y    Z    [    \    ]    ^    _ */
3445    48,  48,  48,  91,  92,  93,  94,  48,
3446/*   `    a    b    c    d    e    f    g */
3447    96,  48,  48,  48,  48,  48,  48,  48,
3448/*   h    i    j    k    l    m    n    o */
3449    48,  48,  48,  48,  48,  48,  48,  48,
3450/*   p    q    r    s    t    u    v    w */
3451    48,  48,  48,  48,  48,  48,  48,  48,
3452/*   x    y    z    {    |    }    ~  DEL */
3453    48,  48,  48, 123, 124, 125, 126,   1,
3454/* x80  x81  x82  x83  IND  NEL  SSA  ESA */
3455    1,    1,   1,   1,   1,   1,   1,   1,
3456/* HTS  HTJ  VTS  PLD  PLU   RI  SS2  SS3 */
3457    1,    1,   1,   1,   1,   1,   1,   1,
3458/* DCS  PU1  PU2  STS  CCH   MW  SPA  EPA */
3459    1,    1,   1,   1,   1,   1,   1,   1,
3460/* x98  x99  x9A  CSI   ST  OSC   PM  APC */
3461    1,    1,   1,   1,   1,   1,   1,   1,
3462/*   -    i   c/    L   ox   Y-    |   So */
3463    160, 161, 162, 163, 164, 165, 166, 167,
3464/*  ..   c0   ip   <<    _        R0    - */
3465    168, 169, 170, 171, 172, 173, 174, 175,
3466/*   o   +-    2    3    '    u   q|    . */
3467    176, 177, 178, 179, 180, 181, 182, 183,
3468/*   ,    1    2   >>  1/4  1/2  3/4    ? */
3469    184, 185, 186, 187, 188, 189, 190, 191,
3470/*  A`   A'   A^   A~   A:   Ao   AE   C, */
3471     48,  48,  48,  48,  48,  48,  48,  48,
3472/*  E`   E'   E^   E:   I`   I'   I^   I: */
3473     48,  48,  48,  48,  48,  48,  48,  48,
3474/*  D-   N~   O`   O'   O^   O~   O:    X */
3475     48,  48,  48,  48,  48,  48,  48, 215,
3476/*  O/   U`   U'   U^   U:   Y'    P    B */
3477     48,  48,  48,  48,  48,  48,  48,  48,
3478/*  a`   a'   a^   a~   a:   ao   ae   c, */
3479     48,  48,  48,  48,  48,  48,  48,  48,
3480/*  e`   e'   e^   e:    i`  i'   i^   i: */
3481     48,  48,  48,  48,  48,  48,  48,  48,
3482/*   d   n~   o`   o'   o^   o~   o:   -: */
3483     48,  48,  48,  48,  48,  48,  48, 247,
3484/*  o/   u`   u'   u^   u:   y'    P   y: */
3485     48,  48,  48,  48,  48,  48,  48,  48};
3486/* *INDENT-ON* */
3487
3488int
3489SetCharacterClassRange(int low,	/* in range of [0..255] */
3490		       int high,
3491		       int value)	/* arbitrary */
3492{
3493
3494    if (low < 0 || high > 255 || high < low)
3495	return (-1);
3496
3497    for (; low <= high; low++)
3498	charClass[low] = value;
3499
3500    return (0);
3501}
3502#endif
3503
3504static int
3505class_of(LineData *ld, CELL *cell)
3506{
3507    CELL temp = *cell;
3508    int result = 0;
3509
3510#if OPT_DEC_CHRSET
3511    if (CSET_DOUBLE(GetLineDblCS(ld))) {
3512	temp.col /= 2;
3513    }
3514#endif
3515    if (temp.col < (int) ld->lineSize)
3516	result = CharacterClass((int) (ld->charData[temp.col]));
3517    return result;
3518}
3519
3520#if OPT_WIDE_CHARS
3521#define CClassSelects(name, cclass) \
3522	 (CClassOf(name) == cclass \
3523	 || XTERM_CELL(screen->name.row, screen->name.col) == HIDDEN_CHAR)
3524#else
3525#define CClassSelects(name, cclass) \
3526	 (class_of(ld.name, &((screen->name))) == cclass)
3527#endif
3528
3529#define CClassOf(name) class_of(ld.name, &((screen->name)))
3530
3531#if OPT_REPORT_CCLASS
3532static int
3533show_cclass_range(int lo, int hi)
3534{
3535    int cclass = CharacterClass(lo);
3536    int ident = (cclass == lo);
3537    int more = 0;
3538    if (ident) {
3539	int ch;
3540	for (ch = lo + 1; ch <= hi; ch++) {
3541	    if (CharacterClass(ch) != ch) {
3542		ident = 0;
3543		break;
3544	    }
3545	}
3546	if (ident && (hi < 255)) {
3547	    ch = hi + 1;
3548	    if (CharacterClass(ch) == ch) {
3549		if (ch >= 255 || CharacterClass(ch + 1) != ch) {
3550		    more = 1;
3551		}
3552	    }
3553	}
3554    }
3555    if (!more) {
3556	if (lo == hi) {
3557	    printf("\t%d", lo);
3558	} else {
3559	    printf("\t%d-%d", lo, hi);
3560	}
3561	if (!ident)
3562	    printf(":%d", cclass);
3563	if (hi < 255)
3564	    printf(", \\");
3565	printf("\n");
3566    }
3567    return !more;
3568}
3569
3570void
3571report_char_class(XtermWidget xw)
3572{
3573    /* simple table, to match documentation */
3574    static const char charnames[] =
3575    "NUL\0" "SOH\0" "STX\0" "ETX\0" "EOT\0" "ENQ\0" "ACK\0" "BEL\0"
3576    " BS\0" " HT\0" " NL\0" " VT\0" " NP\0" " CR\0" " SO\0" " SI\0"
3577    "DLE\0" "DC1\0" "DC2\0" "DC3\0" "DC4\0" "NAK\0" "SYN\0" "ETB\0"
3578    "CAN\0" " EM\0" "SUB\0" "ESC\0" " FS\0" " GS\0" " RS\0" " US\0"
3579    " SP\0" "  !\0" "  \"\0" "  #\0" "  $\0" "  %\0" "  &\0" "  '\0"
3580    "  (\0" "  )\0" "  *\0" "  +\0" "  ,\0" "  -\0" "  .\0" "  /\0"
3581    "  0\0" "  1\0" "  2\0" "  3\0" "  4\0" "  5\0" "  6\0" "  7\0"
3582    "  8\0" "  9\0" "  :\0" "  ;\0" "  <\0" "  =\0" "  >\0" "  ?\0"
3583    "  @\0" "  A\0" "  B\0" "  C\0" "  D\0" "  E\0" "  F\0" "  G\0"
3584    "  H\0" "  I\0" "  J\0" "  K\0" "  L\0" "  M\0" "  N\0" "  O\0"
3585    "  P\0" "  Q\0" "  R\0" "  S\0" "  T\0" "  U\0" "  V\0" "  W\0"
3586    "  X\0" "  Y\0" "  Z\0" "  [\0" "  \\\0" "  ]\0" "  ^\0" "  _\0"
3587    "  `\0" "  a\0" "  b\0" "  c\0" "  d\0" "  e\0" "  f\0" "  g\0"
3588    "  h\0" "  i\0" "  j\0" "  k\0" "  l\0" "  m\0" "  n\0" "  o\0"
3589    "  p\0" "  q\0" "  r\0" "  s\0" "  t\0" "  u\0" "  v\0" "  w\0"
3590    "  x\0" "  y\0" "  z\0" "  {\0" "  |\0" "  }\0" "  ~\0" "DEL\0"
3591    "x80\0" "x81\0" "x82\0" "x83\0" "IND\0" "NEL\0" "SSA\0" "ESA\0"
3592    "HTS\0" "HTJ\0" "VTS\0" "PLD\0" "PLU\0" " RI\0" "SS2\0" "SS3\0"
3593    "DCS\0" "PU1\0" "PU2\0" "STS\0" "CCH\0" " MW\0" "SPA\0" "EPA\0"
3594    "x98\0" "x99\0" "x9A\0" "CSI\0" " ST\0" "OSC\0" " PM\0" "APC\0"
3595    "  -\0" "  i\0" " c/\0" "  L\0" " ox\0" " Y-\0" "  |\0" " So\0"
3596    " ..\0" " c0\0" " ip\0" " <<\0" "  _\0" "   \0" " R0\0" "  -\0"
3597    "  o\0" " +-\0" "  2\0" "  3\0" "  '\0" "  u\0" " q|\0" "  .\0"
3598    "  ,\0" "  1\0" "  2\0" " >>\0" "1/4\0" "1/2\0" "3/4\0" "  ?\0"
3599    " A`\0" " A'\0" " A^\0" " A~\0" " A:\0" " Ao\0" " AE\0" " C,\0"
3600    " E`\0" " E'\0" " E^\0" " E:\0" " I`\0" " I'\0" " I^\0" " I:\0"
3601    " D-\0" " N~\0" " O`\0" " O'\0" " O^\0" " O~\0" " O:\0" "  X\0"
3602    " O/\0" " U`\0" " U'\0" " U^\0" " U:\0" " Y'\0" "  P\0" "  B\0"
3603    " a`\0" " a'\0" " a^\0" " a~\0" " a:\0" " ao\0" " ae\0" " c,\0"
3604    " e`\0" " e'\0" " e^\0" " e:\0" " i`\0" " i'\0" " i^\0" " i:\0"
3605    "  d\0" " n~\0" " o`\0" " o'\0" " o^\0" " o~\0" " o:\0" " -:\0"
3606    " o/\0" " u`\0" " u'\0" " u^\0" " u:\0" " y'\0" "  P\0" " y:\0";
3607    int ch, dh;
3608    int class_p;
3609
3610    (void) xw;
3611
3612    printf("static int charClass[256] = {\n");
3613    for (ch = 0; ch < 256; ++ch) {
3614	const char *s = charnames + (ch * 4);
3615	if ((ch & 7) == 0)
3616	    printf("/*");
3617	printf(" %s ", s);
3618	if (((ch + 1) & 7) == 0) {
3619	    printf("*/\n  ");
3620	    for (dh = ch - 7; dh <= ch; ++dh) {
3621		printf(" %3d%s", CharacterClass(dh), dh == 255 ? "};" : ",");
3622	    }
3623	    printf("\n");
3624	}
3625    }
3626
3627    /* print the table as if it were the charClass resource */
3628    printf("\n");
3629    printf("The table is equivalent to this \"charClass\" resource:\n");
3630    class_p = CharacterClass(dh = 0);
3631    for (ch = 0; ch < 256; ++ch) {
3632	int class_c = CharacterClass(ch);
3633	if (class_c != class_p) {
3634	    if (show_cclass_range(dh, ch - 1)) {
3635		dh = ch;
3636		class_p = class_c;
3637	    }
3638	}
3639    }
3640    if (dh < 255) {
3641	show_cclass_range(dh, 255);
3642    }
3643
3644    if_OPT_WIDE_CHARS(TScreenOf(xw), {
3645	/* if this is a wide-character configuration, print all intervals */
3646	report_wide_char_class();
3647    });
3648}
3649#endif
3650
3651/*
3652 * If the given column is past the end of text on the given row, bump to the
3653 * beginning of the next line.
3654 */
3655static Boolean
3656okPosition(TScreen *screen,
3657	   LineData **ld,
3658	   CELL *cell)
3659{
3660    Boolean result = True;
3661
3662    if (cell->row > screen->max_row) {
3663	result = False;
3664	TRACE(("okPosition cell row %d > screen max %d\n", cell->row, screen->max_row));
3665    } else if (cell->col > (LastTextCol(screen, *ld, cell->row) + 1)) {
3666	TRACE(("okPosition cell col %d > screen max %d\n", cell->col,
3667	       (LastTextCol(screen, *ld, cell->row) + 1)));
3668	if (cell->row < screen->max_row) {
3669	    TRACE(("okPosition cell row %d < screen max %d\n", cell->row, screen->max_row));
3670	    cell->col = 0;
3671	    *ld = GET_LINEDATA(screen, ++cell->row);
3672	    result = False;
3673	}
3674    }
3675    return result;
3676}
3677
3678static void
3679trimLastLine(TScreen *screen,
3680	     LineData **ld,
3681	     CELL *last)
3682{
3683    if (screen->cutNewline && last->row < screen->max_row) {
3684	last->col = 0;
3685	*ld = GET_LINEDATA(screen, ++last->row);
3686    } else {
3687	last->col = LastTextCol(screen, *ld, last->row) + 1;
3688    }
3689}
3690
3691#if OPT_SELECT_REGEX
3692/*
3693 * Returns the first row of a wrapped line.
3694 */
3695static int
3696firstRowOfLine(TScreen *screen, int row, Bool visible)
3697{
3698    LineData *ld = 0;
3699    int limit = visible ? 0 : -screen->savedlines;
3700
3701    while (row > limit &&
3702	   (ld = GET_LINEDATA(screen, row - 1)) != 0 &&
3703	   LineTstWrapped(ld)) {
3704	--row;
3705    }
3706    return row;
3707}
3708
3709/*
3710 * Returns the last row of a wrapped line.
3711 */
3712static int
3713lastRowOfLine(TScreen *screen, int row)
3714{
3715    LineData *ld;
3716
3717    while (row < screen->max_row &&
3718	   (ld = GET_LINEDATA(screen, row)) != 0 &&
3719	   LineTstWrapped(ld)) {
3720	++row;
3721    }
3722    return row;
3723}
3724
3725/*
3726 * Returns the number of cells on the range of rows.
3727 */
3728static unsigned
3729lengthOfLines(TScreen *screen, int firstRow, int lastRow)
3730{
3731    unsigned length = 0;
3732    int n;
3733
3734    for (n = firstRow; n <= lastRow; ++n) {
3735	LineData *ld = GET_LINEDATA(screen, n);
3736	int value = LastTextCol(screen, ld, n);
3737	if (value >= 0)
3738	    length += (unsigned) (value + 1);
3739    }
3740    return length;
3741}
3742
3743/*
3744 * Make a copy of the wrapped-line which corresponds to the given row as a
3745 * string of bytes.  Construct an index for the columns from the beginning of
3746 * the line.
3747 */
3748static char *
3749make_indexed_text(TScreen *screen, int row, unsigned length, int *indexed)
3750{
3751    Char *result = 0;
3752    size_t need = (length + 1);
3753
3754    /*
3755     * Get a quick upper bound to the number of bytes needed, if the whole
3756     * string were UTF-8.
3757     */
3758    if_OPT_WIDE_CHARS(screen, {
3759	need *= ((screen->lineExtra + 1) * 6);
3760    });
3761
3762    if ((result = TypeCallocN(Char, need + 1)) != 0) {
3763	LineData *ld = GET_LINEDATA(screen, row);
3764	unsigned used = 0;
3765	Char *last = result;
3766
3767	do {
3768	    int col = 0;
3769	    int limit = LastTextCol(screen, ld, row);
3770
3771	    while (col <= limit) {
3772		Char *next = last;
3773		unsigned data = ld->charData[col];
3774
3775		assert(col < (int) ld->lineSize);
3776		/* some internal points may not be drawn */
3777		if (data == 0)
3778		    data = ' ';
3779
3780		if_WIDE_OR_NARROW(screen, {
3781		    next = convertToUTF8(last, data);
3782		}
3783		, {
3784		    *next++ = CharOf(data);
3785		});
3786
3787		if_OPT_WIDE_CHARS(screen, {
3788		    size_t off;
3789		    for_each_combData(off, ld) {
3790			data = ld->combData[off][col];
3791			if (data == 0)
3792			    break;
3793			next = convertToUTF8(next, data);
3794		    }
3795		});
3796
3797		indexed[used] = (int) (last - result);
3798		*next = 0;
3799		/* TRACE(("index[%d.%d] %d:%s\n", row, used, indexed[used], last)); */
3800		last = next;
3801		++used;
3802		++col;
3803		indexed[used] = (int) (next - result);
3804	    }
3805	} while (used < length &&
3806		 LineTstWrapped(ld) &&
3807		 (ld = GET_LINEDATA(screen, ++row)) != 0 &&
3808		 row < screen->max_row);
3809    }
3810    /* TRACE(("result:%s\n", result)); */
3811    return (char *) result;
3812}
3813
3814/*
3815 * Find the column given an offset into the character string by using the
3816 * index constructed in make_indexed_text().
3817 */
3818static int
3819indexToCol(int *indexed, int len, int off)
3820{
3821    int col = 0;
3822    while (indexed[col] < len) {
3823	if (indexed[col] >= off)
3824	    break;
3825	++col;
3826    }
3827    return col;
3828}
3829
3830/*
3831 * Given a row number, and a column offset from that (which may be wrapped),
3832 * set the cell to the actual row/column values.
3833 */
3834static void
3835columnToCell(TScreen *screen, int row, int col, CELL *cell)
3836{
3837    while (row < screen->max_row) {
3838	CLineData *ld = GET_LINEDATA(screen, row);
3839	int last = LastTextCol(screen, ld, row);
3840
3841	/* TRACE(("last(%d) = %d, have %d\n", row, last, col)); */
3842	if (col <= last) {
3843	    break;
3844	}
3845	/*
3846	 * Stop if the current row does not wrap (does not continue the current
3847	 * line).
3848	 */
3849	if (!LineTstWrapped(ld)) {
3850	    col = last + 1;
3851	    break;
3852	}
3853	col -= (last + 1);
3854	++row;
3855    }
3856    if (col < 0)
3857	col = 0;
3858    cell->row = row;
3859    cell->col = col;
3860}
3861
3862/*
3863 * Given a cell, find the corresponding column offset.
3864 */
3865static int
3866cellToColumn(TScreen *screen, CELL *cell)
3867{
3868    CLineData *ld = 0;
3869    int col = cell->col;
3870    int row = firstRowOfLine(screen, cell->row, False);
3871    while (row < cell->row) {
3872	ld = GET_LINEDATA(screen, row);
3873	col += LastTextCol(screen, ld, row++);
3874    }
3875#if OPT_DEC_CHRSET
3876    if (ld == 0)
3877	ld = GET_LINEDATA(screen, row);
3878    if (CSET_DOUBLE(GetLineDblCS(ld)))
3879	col /= 2;
3880#endif
3881    return col;
3882}
3883
3884static void
3885do_select_regex(TScreen *screen, CELL *startc, CELL *endc)
3886{
3887    LineData *ld = GET_LINEDATA(screen, startc->row);
3888    int inx = ((screen->numberOfClicks - 1) % screen->maxClicks);
3889    char *expr = screen->selectExpr[inx];
3890    regex_t preg;
3891    regmatch_t match;
3892
3893    TRACE(("Select_REGEX[%d]:%s\n", inx, NonNull(expr)));
3894    if (okPosition(screen, &ld, startc) && expr != 0) {
3895	if (regcomp(&preg, expr, REG_EXTENDED) == 0) {
3896	    int firstRow = firstRowOfLine(screen, startc->row, True);
3897	    int lastRow = lastRowOfLine(screen, firstRow);
3898	    unsigned size = lengthOfLines(screen, firstRow, lastRow);
3899	    int actual = cellToColumn(screen, startc);
3900	    int *indexed;
3901
3902	    TRACE(("regcomp ok rows %d..%d bytes %d\n",
3903		   firstRow, lastRow, size));
3904
3905	    if ((indexed = TypeCallocN(int, size + 1)) != 0) {
3906		char *search;
3907		if ((search = make_indexed_text(screen,
3908						firstRow,
3909						size,
3910						indexed)) != 0) {
3911		    int len = (int) strlen(search);
3912		    int col;
3913		    int best_col = -1;
3914		    int best_len = -1;
3915
3916		    startc->row = 0;
3917		    startc->col = 0;
3918		    endc->row = 0;
3919		    endc->col = 0;
3920
3921		    for (col = 0; indexed[col] < len; ++col) {
3922			if (regexec(&preg,
3923				    search + indexed[col],
3924				    (size_t) 1, &match, 0) == 0) {
3925			    int start_inx = (int) (match.rm_so + indexed[col]);
3926			    int finis_inx = (int) (match.rm_eo + indexed[col]);
3927			    int start_col = indexToCol(indexed, len, start_inx);
3928			    int finis_col = indexToCol(indexed, len, finis_inx);
3929
3930			    if (start_col <= actual &&
3931				actual <= finis_col) {
3932				int test = finis_col - start_col;
3933				if (best_len < test) {
3934				    best_len = test;
3935				    best_col = start_col;
3936				    TRACE(("match column %d len %d\n",
3937					   best_col,
3938					   best_len));
3939				}
3940			    }
3941			}
3942		    }
3943		    if (best_col >= 0) {
3944			int best_nxt = best_col + best_len;
3945			columnToCell(screen, firstRow, best_col, startc);
3946			columnToCell(screen, firstRow, best_nxt, endc);
3947			TRACE(("search::%s\n", search));
3948			TRACE(("indexed:%d..%d -> %d..%d\n",
3949			       best_col, best_nxt,
3950			       indexed[best_col],
3951			       indexed[best_nxt]));
3952			TRACE(("matched:%d:%s\n",
3953			       indexed[best_nxt] + 1 -
3954			       indexed[best_col],
3955			       visibleChars((Char *) (search + indexed[best_col]),
3956					    (unsigned) (indexed[best_nxt] +
3957							1 -
3958							indexed[best_col]))));
3959		    }
3960		    free(search);
3961		}
3962		free(indexed);
3963#if OPT_DEC_CHRSET
3964		if ((ld = GET_LINEDATA(screen, startc->row)) != 0) {
3965		    if (CSET_DOUBLE(GetLineDblCS(ld)))
3966			startc->col *= 2;
3967		}
3968		if ((ld = GET_LINEDATA(screen, endc->row)) != 0) {
3969		    if (CSET_DOUBLE(GetLineDblCS(ld)))
3970			endc->col *= 2;
3971		}
3972#endif
3973	    }
3974	    regfree(&preg);
3975	}
3976    }
3977}
3978#endif /* OPT_SELECT_REGEX */
3979
3980#define InitRow(name) \
3981	ld.name = GET_LINEDATA(screen, screen->name.row)
3982
3983#define NextRow(name) \
3984	ld.name = GET_LINEDATA(screen, ++screen->name.row)
3985
3986#define PrevRow(name) \
3987	ld.name = GET_LINEDATA(screen, --screen->name.row)
3988
3989#define MoreRows(name) \
3990	(screen->name.row < screen->max_row)
3991
3992#define isPrevWrapped(name) \
3993	(screen->name.row > 0 \
3994	   && (ltmp = GET_LINEDATA(screen, screen->name.row - 1)) != 0 \
3995	   && LineTstWrapped(ltmp))
3996
3997/*
3998 * sets startSel endSel
3999 * ensuring that they have legal values
4000 */
4001static void
4002ComputeSelect(XtermWidget xw,
4003	      CELL *startc,
4004	      CELL *endc,
4005	      Bool extend,
4006	      Bool normal)
4007{
4008    TScreen *screen = TScreenOf(xw);
4009
4010    int cclass;
4011    CELL first = *startc;
4012    CELL last = *endc;
4013    Boolean ignored = False;
4014
4015    struct {
4016	LineData *startSel;
4017	LineData *endSel;
4018    } ld;
4019    LineData *ltmp;
4020
4021    TRACE(("ComputeSelect(startRow=%d, startCol=%d, endRow=%d, endCol=%d, %sextend)\n",
4022	   first.row, first.col,
4023	   last.row, last.col,
4024	   extend ? "" : "no"));
4025
4026#if OPT_WIDE_CHARS
4027    if (first.col > 1
4028	&& isWideCell(first.row, first.col - 1)
4029	&& XTERM_CELL(first.row, first.col - 0) == HIDDEN_CHAR) {
4030	TRACE(("Adjusting start. Changing downwards from %i.\n", first.col));
4031	first.col -= 1;
4032	if (last.col == (first.col + 1))
4033	    last.col--;
4034    }
4035
4036    if (last.col > 1
4037	&& isWideCell(last.row, last.col - 1)
4038	&& XTERM_CELL(last.row, last.col) == HIDDEN_CHAR) {
4039	last.col += 1;
4040    }
4041#endif
4042
4043    if (Coordinate(screen, &first) <= Coordinate(screen, &last)) {
4044	screen->startSel = screen->startRaw = first;
4045	screen->endSel = screen->endRaw = last;
4046    } else {			/* Swap them */
4047	screen->startSel = screen->startRaw = last;
4048	screen->endSel = screen->endRaw = first;
4049    }
4050
4051    InitRow(startSel);
4052    InitRow(endSel);
4053
4054    switch (screen->selectUnit) {
4055    case Select_CHAR:
4056	(void) okPosition(screen, &(ld.startSel), &(screen->startSel));
4057	(void) okPosition(screen, &(ld.endSel), &(screen->endSel));
4058	break;
4059
4060    case Select_WORD:
4061	TRACE(("Select_WORD\n"));
4062	if (okPosition(screen, &(ld.startSel), &(screen->startSel))) {
4063	    CELL mark;
4064	    cclass = CClassOf(startSel);
4065	    TRACE(("...starting with class %d\n", cclass));
4066	    do {
4067		mark = screen->startSel;
4068		--screen->startSel.col;
4069		if (screen->startSel.col < 0
4070		    && isPrevWrapped(startSel)) {
4071		    PrevRow(startSel);
4072		    screen->startSel.col = LastTextCol(screen, ld.startSel, screen->startSel.row);
4073		}
4074	    } while (screen->startSel.col >= 0
4075		     && CClassSelects(startSel, cclass));
4076	    if (normal)
4077		++screen->startSel.col;
4078	    else
4079		screen->startSel = mark;
4080	}
4081#if OPT_WIDE_CHARS
4082#define SkipHiddenCell(mark) \
4083	if (mark.col && XTERM_CELL(mark.row, mark.col) == HIDDEN_CHAR) \
4084	    mark.col++
4085#else
4086#define SkipHiddenCell(mark)	/* nothing */
4087#endif
4088	SkipHiddenCell(screen->startSel);
4089
4090	if (!normal) {
4091	    screen->endSel = screen->startSel;
4092	    ld.endSel = ld.startSel;
4093	}
4094
4095	if (okPosition(screen, &(ld.endSel), &(screen->endSel))) {
4096	    int length = LastTextCol(screen, ld.endSel, screen->endSel.row);
4097	    cclass = CClassOf(endSel);
4098	    TRACE(("...ending with class %d\n", cclass));
4099	    do {
4100		++screen->endSel.col;
4101		if (screen->endSel.col > length
4102		    && LineTstWrapped(ld.endSel)) {
4103		    if (!MoreRows(endSel))
4104			break;
4105		    screen->endSel.col = 0;
4106		    NextRow(endSel);
4107		    length = LastTextCol(screen, ld.endSel, screen->endSel.row);
4108		}
4109	    } while (screen->endSel.col <= length
4110		     && CClassSelects(endSel, cclass));
4111	    if (normal
4112		&& screen->endSel.col > length + 1
4113		&& MoreRows(endSel)) {
4114		screen->endSel.col = 0;
4115		NextRow(endSel);
4116	    }
4117	}
4118	SkipHiddenCell(screen->endSel);
4119
4120	screen->saveStartW = screen->startSel;
4121	break;
4122
4123    case Select_LINE:
4124	TRACE(("Select_LINE\n"));
4125	while (LineTstWrapped(ld.endSel)
4126	       && MoreRows(endSel)) {
4127	    NextRow(endSel);
4128	}
4129	if (screen->cutToBeginningOfLine
4130	    || screen->startSel.row < screen->saveStartW.row) {
4131	    screen->startSel.col = 0;
4132	    while (isPrevWrapped(startSel)) {
4133		PrevRow(startSel);
4134	    }
4135	} else if (!extend) {
4136	    if ((first.row < screen->saveStartW.row)
4137		|| (isSameRow(&first, &(screen->saveStartW))
4138		    && first.col < screen->saveStartW.col)) {
4139		screen->startSel.col = 0;
4140		while (isPrevWrapped(startSel)) {
4141		    PrevRow(startSel);
4142		}
4143	    } else {
4144		screen->startSel = screen->saveStartW;
4145	    }
4146	}
4147	trimLastLine(screen, &(ld.endSel), &(screen->endSel));
4148	break;
4149
4150    case Select_GROUP:		/* paragraph */
4151	TRACE(("Select_GROUP\n"));
4152	if (okPosition(screen, &(ld.startSel), &(screen->startSel))) {
4153	    /* scan backward for beginning of group */
4154	    while (screen->startSel.row > 0 &&
4155		   (LastTextCol(screen, ld.startSel, screen->startSel.row -
4156				1) > 0 ||
4157		    isPrevWrapped(startSel))) {
4158		PrevRow(startSel);
4159	    }
4160	    screen->startSel.col = 0;
4161	    /* scan forward for end of group */
4162	    while (MoreRows(endSel) &&
4163		   (LastTextCol(screen, ld.endSel, screen->endSel.row + 1) >
4164		    0 ||
4165		    LineTstWrapped(ld.endSel))) {
4166		NextRow(endSel);
4167	    }
4168	    trimLastLine(screen, &(ld.endSel), &(screen->endSel));
4169	}
4170	break;
4171
4172    case Select_PAGE:		/* everything one can see */
4173	TRACE(("Select_PAGE\n"));
4174	screen->startSel.row = 0;
4175	screen->startSel.col = 0;
4176	screen->endSel.row = MaxRows(screen);
4177	screen->endSel.col = 0;
4178	break;
4179
4180    case Select_ALL:		/* counts scrollback if in normal screen */
4181	TRACE(("Select_ALL\n"));
4182	screen->startSel.row = -screen->savedlines;
4183	screen->startSel.col = 0;
4184	screen->endSel.row = MaxRows(screen);
4185	screen->endSel.col = 0;
4186	break;
4187
4188#if OPT_SELECT_REGEX
4189    case Select_REGEX:
4190	do_select_regex(screen, &(screen->startSel), &(screen->endSel));
4191	break;
4192#endif
4193
4194    case NSELECTUNITS:		/* always ignore */
4195	ignored = True;
4196	break;
4197    }
4198
4199    if (!ignored) {
4200	/* check boundaries */
4201	ScrollSelection(screen, 0, False);
4202	TrackText(xw, &(screen->startSel), &(screen->endSel));
4203    }
4204
4205    return;
4206}
4207
4208/* Guaranteed (first.row, first.col) <= (last.row, last.col) */
4209static void
4210TrackText(XtermWidget xw,
4211	  const CELL *firstp,
4212	  const CELL *lastp)
4213{
4214    TScreen *screen = TScreenOf(xw);
4215    int from, to;
4216    CELL old_start, old_end;
4217    CELL first = *firstp;
4218    CELL last = *lastp;
4219
4220    TRACE(("TrackText(first=%d,%d, last=%d,%d)\n",
4221	   first.row, first.col, last.row, last.col));
4222
4223    old_start = screen->startH;
4224    old_end = screen->endH;
4225    TRACE(("...previous(first=%d,%d, last=%d,%d)\n",
4226	   old_start.row, old_start.col,
4227	   old_end.row, old_end.col));
4228    if (isSameCELL(&first, &old_start) &&
4229	isSameCELL(&last, &old_end)) {
4230	return;
4231    }
4232
4233    screen->startH = first;
4234    screen->endH = last;
4235    from = Coordinate(screen, &screen->startH);
4236    to = Coordinate(screen, &screen->endH);
4237    if (to <= screen->startHCoord || from > screen->endHCoord) {
4238	/* No overlap whatsoever between old and new hilite */
4239	ReHiliteText(xw, &old_start, &old_end);
4240	ReHiliteText(xw, &first, &last);
4241    } else {
4242	if (from < screen->startHCoord) {
4243	    /* Extend left end */
4244	    ReHiliteText(xw, &first, &old_start);
4245	} else if (from > screen->startHCoord) {
4246	    /* Shorten left end */
4247	    ReHiliteText(xw, &old_start, &first);
4248	}
4249	if (to > screen->endHCoord) {
4250	    /* Extend right end */
4251	    ReHiliteText(xw, &old_end, &last);
4252	} else if (to < screen->endHCoord) {
4253	    /* Shorten right end */
4254	    ReHiliteText(xw, &last, &old_end);
4255	}
4256    }
4257    screen->startHCoord = from;
4258    screen->endHCoord = to;
4259}
4260
4261static void
4262UnHiliteText(XtermWidget xw)
4263{
4264    TrackText(xw, &zeroCELL, &zeroCELL);
4265}
4266
4267/* Guaranteed that (first->row, first->col) <= (last->row, last->col) */
4268static void
4269ReHiliteText(XtermWidget xw,
4270	     CELL *firstp,
4271	     CELL *lastp)
4272{
4273    TScreen *screen = TScreenOf(xw);
4274    CELL first = *firstp;
4275    CELL last = *lastp;
4276
4277    TRACE(("ReHiliteText from %d.%d to %d.%d\n",
4278	   first.row, first.col, last.row, last.col));
4279
4280    if (first.row < 0)
4281	first.row = first.col = 0;
4282    else if (first.row > screen->max_row)
4283	return;			/* nothing to do, since last.row >= first.row */
4284
4285    if (last.row < 0)
4286	return;			/* nothing to do, since first.row <= last.row */
4287    else if (last.row > screen->max_row) {
4288	last.row = screen->max_row;
4289	last.col = MaxCols(screen);
4290    }
4291    if (isSameCELL(&first, &last))
4292	return;
4293
4294    if (!isSameRow(&first, &last)) {	/* do multiple rows */
4295	int i;
4296	if ((i = screen->max_col - first.col + 1) > 0) {	/* first row */
4297	    ScrnRefresh(xw, first.row, first.col, 1, i, True);
4298	}
4299	if ((i = last.row - first.row - 1) > 0) {	/* middle rows */
4300	    ScrnRefresh(xw, first.row + 1, 0, i, MaxCols(screen), True);
4301	}
4302	if (last.col > 0 && last.row <= screen->max_row) {	/* last row */
4303	    ScrnRefresh(xw, last.row, 0, 1, last.col, True);
4304	}
4305    } else {			/* do single row */
4306	ScrnRefresh(xw, first.row, first.col, 1, last.col - first.col, True);
4307    }
4308}
4309
4310/*
4311 * Guaranteed that (cellc->row, cellc->col) <= (cell->row, cell->col),
4312 * and that both points are valid
4313 * (may have cell->row = screen->max_row+1, cell->col = 0).
4314 */
4315static void
4316SaltTextAway(XtermWidget xw,
4317	     int which,
4318	     CELL *cellc,
4319	     CELL *cell)
4320{
4321    TScreen *screen = TScreenOf(xw);
4322    SelectedCells *scp;
4323    int i;
4324    int eol;
4325    int need = 0;
4326    size_t have = 0;
4327    Char *line;
4328    Char *lp;
4329    CELL first = *cellc;
4330    CELL last = *cell;
4331
4332    if (which < 0 || which >= MAX_SELECTIONS) {
4333	TRACE(("SaltTextAway - which selection?\n"));
4334	return;
4335    }
4336    scp = &(screen->selected_cells[which]);
4337
4338    TRACE(("SaltTextAway which=%d, first=%d,%d, last=%d,%d\n",
4339	   which, first.row, first.col, last.row, last.col));
4340
4341    if (isSameRow(&first, &last) && first.col > last.col) {
4342	int tmp;
4343	EXCHANGE(first.col, last.col, tmp);
4344    }
4345
4346    --last.col;
4347    /* first we need to know how long the string is before we can save it */
4348
4349    if (isSameRow(&last, &first)) {
4350	need = Length(screen, first.row, first.col, last.col);
4351    } else {			/* two cases, cut is on same line, cut spans multiple lines */
4352	need += Length(screen, first.row, first.col, screen->max_col) + 1;
4353	for (i = first.row + 1; i < last.row; i++)
4354	    need += Length(screen, i, 0, screen->max_col) + 1;
4355	if (last.col >= 0)
4356	    need += Length(screen, last.row, 0, last.col);
4357    }
4358
4359    /* UTF-8 may require more space */
4360    if_OPT_WIDE_CHARS(screen, {
4361	if (need > 0) {
4362	    if (screen->max_combining > 0)
4363		need += screen->max_combining;
4364	    need *= 6;
4365	}
4366    });
4367
4368    /* now get some memory to save it in */
4369    if (need < 0)
4370	return;
4371
4372    if (scp->data_limit <= (unsigned) need) {
4373	if ((line = (Char *) malloc((size_t) need + 1)) == 0)
4374	    SysError(ERROR_BMALLOC2);
4375	free(scp->data_buffer);
4376	scp->data_buffer = line;
4377	scp->data_limit = (size_t) (need + 1);
4378    } else {
4379	line = scp->data_buffer;
4380    }
4381
4382    if (line == 0)
4383	return;
4384
4385    line[need] = '\0';		/* make sure it is null terminated */
4386    lp = line;			/* lp points to where to save the text */
4387    if (isSameRow(&last, &first)) {
4388	lp = SaveText(screen, last.row, first.col, last.col, lp, &eol);
4389    } else {
4390	lp = SaveText(screen, first.row, first.col, screen->max_col, lp, &eol);
4391	if (eol)
4392	    *lp++ = '\n';	/* put in newline at end of line */
4393	for (i = first.row + 1; i < last.row; i++) {
4394	    lp = SaveText(screen, i, 0, screen->max_col, lp, &eol);
4395	    if (eol)
4396		*lp++ = '\n';
4397	}
4398	if (last.col >= 0)
4399	    lp = SaveText(screen, last.row, 0, last.col, lp, &eol);
4400    }
4401    *lp = '\0';			/* make sure we have end marked */
4402
4403    have = (size_t) (lp - line);
4404    /*
4405     * Scanning the buffer twice is unnecessary.  Discard unwanted memory if
4406     * the estimate is too-far off.
4407     */
4408    if ((have * 2) < (size_t) need) {
4409	Char *next;
4410	scp->data_limit = have + 1;
4411	next = realloc(line, scp->data_limit);
4412	if (next == NULL) {
4413	    free(line);
4414	    scp->data_length = 0;
4415	    scp->data_limit = 0;
4416	}
4417	scp->data_buffer = next;
4418    }
4419    scp->data_length = have;
4420
4421    TRACE(("Salted TEXT:%u:%s\n", (unsigned) have,
4422	   visibleChars(scp->data_buffer, (unsigned) have)));
4423}
4424
4425#if OPT_PASTE64
4426void
4427ClearSelectionBuffer(TScreen *screen, String selection)
4428{
4429    int which = TargetToSelection(screen, selection);
4430    SelectedCells *scp = &(screen->selected_cells[okSelectionCode(which)]);
4431    FreeAndNull(scp->data_buffer);
4432    scp->data_limit = 0;
4433    scp->data_length = 0;
4434    screen->base64_count = 0;
4435}
4436
4437static void
4438AppendStrToSelectionBuffer(SelectedCells * scp, Char *text, size_t len)
4439{
4440    if (len != 0) {
4441	size_t j = (scp->data_length + len);
4442	size_t k = j + (j >> 2) + 80;
4443	if (j + 1 >= scp->data_limit) {
4444	    Char *line;
4445	    if (!scp->data_length) {
4446		line = (Char *) malloc(k);
4447	    } else {
4448		line = (Char *) realloc(scp->data_buffer, k);
4449	    }
4450	    if (line == 0)
4451		SysError(ERROR_BMALLOC2);
4452	    scp->data_buffer = line;
4453	    scp->data_limit = k;
4454	}
4455	if (scp->data_buffer != 0) {
4456	    memcpy(scp->data_buffer + scp->data_length, text, len);
4457	    scp->data_length += len;
4458	    scp->data_buffer[scp->data_length] = 0;
4459	}
4460    }
4461}
4462
4463void
4464AppendToSelectionBuffer(TScreen *screen, unsigned c, String selection)
4465{
4466    int which = TargetToSelection(screen, selection);
4467    SelectedCells *scp = &(screen->selected_cells[okSelectionCode(which)]);
4468    unsigned six;
4469    Char ch;
4470
4471    /* Decode base64 character */
4472    if (c >= 'A' && c <= 'Z')
4473	six = c - 'A';
4474    else if (c >= 'a' && c <= 'z')
4475	six = c - 'a' + 26;
4476    else if (c >= '0' && c <= '9')
4477	six = c - '0' + 52;
4478    else if (c == '+')
4479	six = 62;
4480    else if (c == '/')
4481	six = 63;
4482    else
4483	return;
4484
4485    /* Accumulate bytes */
4486    switch (screen->base64_count) {
4487    case 0:
4488	screen->base64_accu = six;
4489	screen->base64_count = 6;
4490	break;
4491
4492    case 2:
4493	ch = CharOf((screen->base64_accu << 6) + six);
4494	screen->base64_count = 0;
4495	AppendStrToSelectionBuffer(scp, &ch, (size_t) 1);
4496	break;
4497
4498    case 4:
4499	ch = CharOf((screen->base64_accu << 4) + (six >> 2));
4500	screen->base64_accu = (six & 0x3);
4501	screen->base64_count = 2;
4502	AppendStrToSelectionBuffer(scp, &ch, (size_t) 1);
4503	break;
4504
4505    case 6:
4506	ch = CharOf((screen->base64_accu << 2) + (six >> 4));
4507	screen->base64_accu = (six & 0xF);
4508	screen->base64_count = 4;
4509	AppendStrToSelectionBuffer(scp, &ch, (size_t) 1);
4510	break;
4511    }
4512}
4513
4514void
4515CompleteSelection(XtermWidget xw, String *args, Cardinal len)
4516{
4517    TScreen *screen = TScreenOf(xw);
4518
4519    screen->base64_count = 0;
4520    screen->base64_accu = 0;
4521    _OwnSelection(xw, args, len);
4522}
4523#endif /* OPT_PASTE64 */
4524
4525static Bool
4526_ConvertSelectionHelper(Widget w,
4527			SelectedCells * scp,
4528			Atom *type,
4529			XtPointer *value,
4530			unsigned long *length,
4531			int *format,
4532			int (*conversion_function) (Display *,
4533						    char **, int,
4534						    XICCEncodingStyle,
4535						    XTextProperty *),
4536			XICCEncodingStyle conversion_style)
4537{
4538    *value = 0;
4539    *length = 0;
4540    *type = 0;
4541    *format = 0;
4542
4543    if (getXtermWidget(w) != 0) {
4544	Display *dpy = XtDisplay(w);
4545	XTextProperty textprop;
4546	int out_n = 0;
4547	char *result = 0;
4548	char *the_data = (char *) scp->data_buffer;
4549	char *the_next;
4550	unsigned long remaining = scp->data_length;
4551
4552	TRACE(("converting %ld:'%s'\n",
4553	       (long) scp->data_length,
4554	       visibleChars(scp->data_buffer, (unsigned) scp->data_length)));
4555	/*
4556	 * For most selections, we can convert in one pass.  It is possible
4557	 * that some applications contain embedded nulls, e.g., using xterm's
4558	 * paste64 feature.  For those cases, we will build up the result in
4559	 * parts.
4560	 */
4561	if (memchr(the_data, 0, scp->data_length) != 0) {
4562	    TRACE(("selection contains embedded nulls\n"));
4563	    result = calloc(scp->data_length + 1, sizeof(char));
4564	}
4565
4566      next_try:
4567	memset(&textprop, 0, sizeof(textprop));
4568	if (conversion_function(dpy, &the_data, 1,
4569				conversion_style,
4570				&textprop) >= Success) {
4571	    if ((result != 0)
4572		&& (textprop.value != 0)
4573		&& (textprop.format == 8)) {
4574		char *text_values = (char *) textprop.value;
4575		unsigned long in_n;
4576
4577		if (out_n == 0) {
4578		    *value = result;
4579		    *type = textprop.encoding;
4580		    *format = textprop.format;
4581		}
4582		for (in_n = 0; in_n < textprop.nitems; ++in_n) {
4583		    result[out_n++] = text_values[in_n];
4584		}
4585		*length += textprop.nitems;
4586		if ((the_next = memchr(the_data, 0, remaining)) != 0) {
4587		    unsigned long this_was = (unsigned long) (the_next - the_data);
4588		    this_was++;
4589		    the_data += this_was;
4590		    remaining -= this_was;
4591		    result[out_n++] = 0;
4592		    *length += 1;
4593		    if (remaining)
4594			goto next_try;
4595		}
4596		return True;
4597	    } else {
4598		free(result);
4599		*value = (XtPointer) textprop.value;
4600		*length = textprop.nitems;
4601		*type = textprop.encoding;
4602		*format = textprop.format;
4603		return True;
4604	    }
4605	}
4606	free(result);
4607    }
4608    return False;
4609}
4610
4611static Boolean
4612SaveConvertedLength(XtPointer *target, unsigned long source)
4613{
4614    Boolean result = False;
4615
4616    *target = XtMalloc(4);
4617    if (*target != 0) {
4618	result = True;
4619	if (sizeof(unsigned long) == 4) {
4620	    *(unsigned long *) *target = source;
4621	} else if (sizeof(unsigned) == 4) {
4622	    *(unsigned *) *target = (unsigned) source;
4623	} else if (sizeof(unsigned short) == 4) {
4624	    *(unsigned short *) *target = (unsigned short) source;
4625	} else {
4626	    /* FIXME - does this depend on byte-order? */
4627	    unsigned long temp = source;
4628	    memcpy((char *) *target,
4629		   ((char *) &temp) + sizeof(temp) - 4,
4630		   (size_t) 4);
4631	}
4632    }
4633    return result;
4634}
4635
4636#define keepClipboard(d,atom) ((screen->keepClipboard) && \
4637	 (atom == XA_CLIPBOARD(d)))
4638
4639static Boolean
4640ConvertSelection(Widget w,
4641		 Atom *selection,
4642		 Atom *target,
4643		 Atom *type,
4644		 XtPointer *value,
4645		 unsigned long *length,
4646		 int *format)
4647{
4648    Display *dpy = XtDisplay(w);
4649    TScreen *screen;
4650    SelectedCells *scp;
4651    Bool result = False;
4652
4653    Char *data;
4654    unsigned long data_length;
4655
4656    XtermWidget xw;
4657
4658    if ((xw = getXtermWidget(w)) == 0)
4659	return False;
4660
4661    screen = TScreenOf(xw);
4662
4663    TRACE(("ConvertSelection %s -> %s\n",
4664	   TraceAtomName(screen->display, *selection),
4665	   visibleSelectionTarget(dpy, *target)));
4666
4667    if (keepClipboard(dpy, *selection)) {
4668	TRACE(("asked for clipboard\n"));
4669	scp = &(screen->clipboard_data);
4670    } else {
4671	TRACE(("asked for selection\n"));
4672	scp = &(screen->selected_cells[AtomToSelection(dpy, *selection)]);
4673    }
4674
4675    data = scp->data_buffer;
4676    data_length = scp->data_length;
4677    if (data == NULL) {
4678	TRACE(("...no selection-data\n"));
4679	return False;
4680    }
4681
4682    if (*target == XA_TARGETS(dpy)) {
4683	Atom *targetP;
4684	XPointer std_return = 0;
4685	unsigned long std_length;
4686
4687	if (XmuConvertStandardSelection(w, screen->selection_time, selection,
4688					target, type, &std_return,
4689					&std_length, format)) {
4690	    Atom *my_targets = _SelectionTargets(w);
4691	    Atom *allocP;
4692	    Atom *std_targets;
4693
4694	    TRACE(("XmuConvertStandardSelection - success\n"));
4695	    std_targets = (Atom *) (void *) (std_return);
4696	    *length = std_length + 6;
4697
4698	    targetP = TypeXtMallocN(Atom, *length);
4699	    allocP = targetP;
4700
4701	    *value = (XtPointer) targetP;
4702
4703	    if (my_targets != 0) {
4704		while (*my_targets != None) {
4705		    *targetP++ = *my_targets++;
4706		}
4707	    }
4708	    *targetP++ = XA_LENGTH(dpy);
4709	    *targetP++ = XA_LIST_LENGTH(dpy);
4710
4711	    *length = std_length + (unsigned long) (targetP - allocP);
4712
4713	    memcpy(targetP, std_targets, sizeof(Atom) * std_length);
4714	    XtFree((char *) std_targets);
4715	    *type = XA_ATOM;
4716	    *format = 32;
4717	    result = True;
4718	} else {
4719	    TRACE(("XmuConvertStandardSelection - failed\n"));
4720	}
4721    }
4722#if OPT_WIDE_CHARS
4723    else if (screen->wide_chars && *target == XA_STRING) {
4724	result =
4725	    _ConvertSelectionHelper(w, scp,
4726				    type, value, length, format,
4727				    Xutf8TextListToTextProperty,
4728				    XStringStyle);
4729	TRACE(("...Xutf8TextListToTextProperty:%d\n", result));
4730    } else if (screen->wide_chars && *target == XA_UTF8_STRING(dpy)) {
4731	result =
4732	    _ConvertSelectionHelper(w, scp,
4733				    type, value, length, format,
4734				    Xutf8TextListToTextProperty,
4735				    XUTF8StringStyle);
4736	TRACE(("...Xutf8TextListToTextProperty:%d\n", result));
4737    } else if (screen->wide_chars && *target == XA_TEXT(dpy)) {
4738	result =
4739	    _ConvertSelectionHelper(w, scp,
4740				    type, value, length, format,
4741				    Xutf8TextListToTextProperty,
4742				    XStdICCTextStyle);
4743	TRACE(("...Xutf8TextListToTextProperty:%d\n", result));
4744    } else if (screen->wide_chars && *target == XA_COMPOUND_TEXT(dpy)) {
4745	result =
4746	    _ConvertSelectionHelper(w, scp,
4747				    type, value, length, format,
4748				    Xutf8TextListToTextProperty,
4749				    XCompoundTextStyle);
4750	TRACE(("...Xutf8TextListToTextProperty:%d\n", result));
4751    }
4752#endif
4753
4754    else if (*target == XA_STRING) {	/* not wide_chars */
4755	/* We can only reach this point if the selection requestor
4756	   requested STRING before any of TEXT, COMPOUND_TEXT or
4757	   UTF8_STRING.  We therefore assume that the requestor is not
4758	   properly internationalised, and dump raw eight-bit data
4759	   with no conversion into the selection.  Yes, this breaks
4760	   the ICCCM in non-Latin-1 locales. */
4761	*type = XA_STRING;
4762	*value = (XtPointer) data;
4763	*length = data_length;
4764	*format = 8;
4765	result = True;
4766	TRACE(("...raw 8-bit data:%d\n", result));
4767    } else if (*target == XA_TEXT(dpy)) {	/* not wide_chars */
4768	result =
4769	    _ConvertSelectionHelper(w, scp,
4770				    type, value, length, format,
4771				    XmbTextListToTextProperty,
4772				    XStdICCTextStyle);
4773	TRACE(("...XmbTextListToTextProperty(StdICC):%d\n", result));
4774    } else if (*target == XA_COMPOUND_TEXT(dpy)) {	/* not wide_chars */
4775	result =
4776	    _ConvertSelectionHelper(w, scp,
4777				    type, value, length, format,
4778				    XmbTextListToTextProperty,
4779				    XCompoundTextStyle);
4780	TRACE(("...XmbTextListToTextProperty(Compound):%d\n", result));
4781    }
4782#ifdef X_HAVE_UTF8_STRING
4783    else if (*target == XA_UTF8_STRING(dpy)) {	/* not wide_chars */
4784	result =
4785	    _ConvertSelectionHelper(w, scp,
4786				    type, value, length, format,
4787				    XmbTextListToTextProperty,
4788				    XUTF8StringStyle);
4789	TRACE(("...XmbTextListToTextProperty(UTF8):%d\n", result));
4790    }
4791#endif
4792    else if (*target == XA_LIST_LENGTH(dpy)) {
4793	result = SaveConvertedLength(value, (unsigned long) 1);
4794	*type = XA_INTEGER;
4795	*length = 1;
4796	*format = 32;
4797	TRACE(("...list of values:%d\n", result));
4798    } else if (*target == XA_LENGTH(dpy)) {
4799	/* This value is wrong if we have UTF-8 text */
4800	result = SaveConvertedLength(value, scp->data_length);
4801	*type = XA_INTEGER;
4802	*length = 1;
4803	*format = 32;
4804	TRACE(("...list of values:%d\n", result));
4805    } else if (XmuConvertStandardSelection(w,
4806					   screen->selection_time, selection,
4807					   target, type, (XPointer *) value,
4808					   length, format)) {
4809	result = True;
4810	TRACE(("...XmuConvertStandardSelection:%d\n", result));
4811    }
4812
4813    /* else */
4814    return (Boolean) result;
4815}
4816
4817static void
4818LoseSelection(Widget w, Atom *selection)
4819{
4820    TScreen *screen;
4821    Atom *atomP;
4822    Cardinal i;
4823
4824    XtermWidget xw;
4825
4826    if ((xw = getXtermWidget(w)) == 0)
4827	return;
4828
4829    screen = TScreenOf(xw);
4830    TRACE(("LoseSelection %s\n", TraceAtomName(screen->display, *selection)));
4831
4832    for (i = 0, atomP = screen->selection_atoms;
4833	 i < screen->selection_count; i++, atomP++) {
4834	if (*selection == *atomP)
4835	    *atomP = (Atom) 0;
4836	if (CutBuffer(*atomP) >= 0) {
4837	    *atomP = (Atom) 0;
4838	}
4839    }
4840
4841    for (i = screen->selection_count; i; i--) {
4842	if (screen->selection_atoms[i - 1] != 0)
4843	    break;
4844    }
4845    screen->selection_count = i;
4846
4847    for (i = 0, atomP = screen->selection_atoms;
4848	 i < screen->selection_count; i++, atomP++) {
4849	if (*atomP == (Atom) 0) {
4850	    *atomP = screen->selection_atoms[--screen->selection_count];
4851	}
4852    }
4853
4854    if (screen->selection_count == 0)
4855	UnHiliteText(xw);
4856}
4857
4858/* ARGSUSED */
4859static void
4860SelectionDone(Widget w GCC_UNUSED,
4861	      Atom *selection GCC_UNUSED,
4862	      Atom *target GCC_UNUSED)
4863{
4864    /* empty proc so Intrinsics know we want to keep storage */
4865    TRACE(("SelectionDone\n"));
4866}
4867
4868static void
4869_OwnSelection(XtermWidget xw,
4870	      String *selections,
4871	      Cardinal count)
4872{
4873    TScreen *screen = TScreenOf(xw);
4874    Display *dpy = screen->display;
4875    Atom *atoms = screen->selection_atoms;
4876    Cardinal i;
4877    Bool have_selection = False;
4878    SelectedCells *scp;
4879
4880    if (count == 0)
4881	return;
4882
4883    TRACE(("_OwnSelection count %d\n", count));
4884    selections = MapSelections(xw, selections, count);
4885
4886    if (count > screen->sel_atoms_size) {
4887	XtFree((char *) atoms);
4888	atoms = TypeXtMallocN(Atom, count);
4889	screen->selection_atoms = atoms;
4890	screen->sel_atoms_size = count;
4891    }
4892    XmuInternStrings(dpy, selections, count, atoms);
4893    for (i = 0; i < count; i++) {
4894	int cutbuffer = CutBuffer(atoms[i]);
4895	if (cutbuffer >= 0) {
4896	    unsigned long limit =
4897	    (unsigned long) (4 * XMaxRequestSize(dpy) - 32);
4898	    scp = &(screen->selected_cells[CutBufferToCode(cutbuffer)]);
4899	    if (scp->data_length > limit) {
4900		TRACE(("selection too big (%lu bytes), not storing in CUT_BUFFER%d\n",
4901		       (unsigned long) scp->data_length, cutbuffer));
4902		xtermWarning("selection too big (%lu bytes), not storing in CUT_BUFFER%d\n",
4903			     (unsigned long) scp->data_length, cutbuffer);
4904	    } else {
4905		/* This used to just use the UTF-8 data, which was totally
4906		 * broken as not even the corresponding paste code in xterm
4907		 * understood this!  So now it converts to Latin1 first.
4908		 *   Robert Brady, 2000-09-05
4909		 */
4910		unsigned long length = scp->data_length;
4911		Char *data = scp->data_buffer;
4912		if_OPT_WIDE_CHARS((screen), {
4913		    data = UTF8toLatin1(screen, data, length, &length);
4914		});
4915		TRACE(("XStoreBuffer(%d)\n", cutbuffer));
4916		XStoreBuffer(dpy,
4917			     (char *) data,
4918			     (int) length,
4919			     cutbuffer);
4920	    }
4921	} else {
4922	    int which = AtomToSelection(dpy, atoms[i]);
4923	    if (keepClipboard(dpy, atoms[i])) {
4924		Char *buf;
4925		SelectedCells *tcp = &(screen->clipboard_data);
4926		TRACE(("saving selection to clipboard buffer\n"));
4927		scp = &(screen->selected_cells[CLIPBOARD_CODE]);
4928		if ((buf = (Char *) malloc((size_t) scp->data_length)) == 0)
4929		    SysError(ERROR_BMALLOC2);
4930
4931		free(tcp->data_buffer);
4932		memcpy(buf, scp->data_buffer, scp->data_length);
4933		tcp->data_buffer = buf;
4934		tcp->data_limit = scp->data_length;
4935		tcp->data_length = scp->data_length;
4936	    }
4937	    scp = &(screen->selected_cells[which]);
4938	    if (scp->data_length == 0) {
4939		TRACE(("XtDisownSelection(%s, @%ld)\n",
4940		       TraceAtomName(screen->display, atoms[i]),
4941		       (long) screen->selection_time));
4942		XtDisownSelection((Widget) xw,
4943				  atoms[i],
4944				  screen->selection_time);
4945	    } else if (!screen->replyToEmacs && atoms[i] != 0) {
4946		TRACE(("XtOwnSelection(%s, @%ld)\n",
4947		       TraceAtomName(screen->display, atoms[i]),
4948		       (long) screen->selection_time));
4949		have_selection |=
4950		    XtOwnSelection((Widget) xw, atoms[i],
4951				   screen->selection_time,
4952				   ConvertSelection,
4953				   LoseSelection,
4954				   SelectionDone);
4955	    }
4956	}
4957	TRACE(("... _OwnSelection used length %lu value %s\n",
4958	       (unsigned long) scp->data_length,
4959	       visibleChars(scp->data_buffer,
4960			    (unsigned) scp->data_length)));
4961    }
4962    if (!screen->replyToEmacs)
4963	screen->selection_count = count;
4964    if (!have_selection)
4965	UnHiliteText(xw);
4966}
4967
4968static void
4969ResetSelectionState(TScreen *screen)
4970{
4971    screen->selection_count = 0;
4972    screen->startH = zeroCELL;
4973    screen->endH = zeroCELL;
4974}
4975
4976void
4977DisownSelection(XtermWidget xw)
4978{
4979    TScreen *screen = TScreenOf(xw);
4980    Atom *atoms = screen->selection_atoms;
4981    Cardinal count = screen->selection_count;
4982    Cardinal i;
4983
4984    TRACE(("DisownSelection count %d, start %d.%d, end %d.%d\n",
4985	   count,
4986	   screen->startH.row,
4987	   screen->startH.col,
4988	   screen->endH.row,
4989	   screen->endH.col));
4990
4991    for (i = 0; i < count; i++) {
4992	int cutbuffer = CutBuffer(atoms[i]);
4993	if (cutbuffer < 0) {
4994	    XtDisownSelection((Widget) xw, atoms[i],
4995			      screen->selection_time);
4996	}
4997    }
4998    /*
4999     * If none of the callbacks via XtDisownSelection() reset highlighting
5000     * do it now.
5001     */
5002    if (ScrnHaveSelection(screen)) {
5003	/* save data which will be reset */
5004	CELL first = screen->startH;
5005	CELL last = screen->endH;
5006
5007	ResetSelectionState(screen);
5008	ReHiliteText(xw, &first, &last);
5009    } else {
5010	ResetSelectionState(screen);
5011    }
5012}
5013
5014void
5015UnhiliteSelection(XtermWidget xw)
5016{
5017    TScreen *screen = TScreenOf(xw);
5018
5019    if (ScrnHaveSelection(screen)) {
5020	CELL first = screen->startH;
5021	CELL last = screen->endH;
5022
5023	screen->startH = zeroCELL;
5024	screen->endH = zeroCELL;
5025	ReHiliteText(xw, &first, &last);
5026    }
5027}
5028
5029/* returns number of chars in line from scol to ecol out */
5030/* ARGSUSED */
5031static int
5032Length(TScreen *screen,
5033       int row,
5034       int scol,
5035       int ecol)
5036{
5037    CLineData *ld = GET_LINEDATA(screen, row);
5038    const int lastcol = LastTextCol(screen, ld, row);
5039
5040    if (ecol > lastcol)
5041	ecol = lastcol;
5042    return (ecol - scol + 1);
5043}
5044
5045/* copies text into line, preallocated */
5046static Char *
5047SaveText(TScreen *screen,
5048	 int row,
5049	 int scol,
5050	 int ecol,
5051	 Char *lp,		/* pointer to where to put the text */
5052	 int *eol)
5053{
5054    LineData *ld;
5055    int i = 0;
5056    Char *result = lp;
5057#if OPT_WIDE_CHARS
5058    unsigned previous = 0;
5059#endif
5060
5061    ld = GET_LINEDATA(screen, row);
5062    i = Length(screen, row, scol, ecol);
5063    ecol = scol + i;
5064#if OPT_DEC_CHRSET
5065    if (CSET_DOUBLE(GetLineDblCS(ld))) {
5066	scol = (scol + 0) / 2;
5067	ecol = (ecol + 1) / 2;
5068    }
5069#endif
5070    *eol = !LineTstWrapped(ld);
5071    for (i = scol; i < ecol; i++) {
5072	unsigned c;
5073	assert(i < (int) ld->lineSize);
5074	c = E2A(ld->charData[i]);
5075#if OPT_WIDE_CHARS
5076	/* We want to strip out every occurrence of HIDDEN_CHAR AFTER a
5077	 * wide character.
5078	 */
5079	if (c == HIDDEN_CHAR) {
5080	    if (isWide((int) previous)) {
5081		previous = c;
5082		/* Combining characters attached to double-width characters
5083		   are in memory attached to the HIDDEN_CHAR */
5084		if_OPT_WIDE_CHARS(screen, {
5085		    if ((screen->utf8_nrc_mode | screen->utf8_mode) != uFalse) {
5086			size_t off;
5087			for_each_combData(off, ld) {
5088			    unsigned ch = ld->combData[off][i];
5089			    if (ch == 0)
5090				break;
5091			    lp = convertToUTF8(lp, ch);
5092			}
5093		    }
5094		});
5095		continue;
5096	    } else {
5097		c = ' ';	/* should not happen, but just in case... */
5098	    }
5099	}
5100	previous = c;
5101	if ((screen->utf8_nrc_mode | screen->utf8_mode) != uFalse) {
5102	    lp = convertToUTF8(lp, (c != 0) ? c : ' ');
5103	    if_OPT_WIDE_CHARS(screen, {
5104		size_t off;
5105		for_each_combData(off, ld) {
5106		    unsigned ch = ld->combData[off][i];
5107		    if (ch == 0)
5108			break;
5109		    lp = convertToUTF8(lp, ch);
5110		}
5111	    });
5112	} else
5113#endif
5114	{
5115	    if (c == 0) {
5116		c = E2A(' ');
5117	    } else if (c < E2A(' ')) {
5118		c = DECtoASCII(c);
5119	    } else if (c == 0x7f) {
5120		c = 0x5f;
5121	    }
5122	    *lp++ = CharOf(A2E(c));
5123	}
5124	if (c != E2A(' '))
5125	    result = lp;
5126    }
5127
5128    /*
5129     * If requested, trim trailing blanks from selected lines.  Do not do this
5130     * if the line is wrapped.
5131     */
5132    if (!*eol || !screen->trim_selection)
5133	result = lp;
5134
5135    return (result);
5136}
5137
5138/*
5139 * This adds together the bits:
5140 *   shift key   -> 1
5141 *   meta key    -> 2
5142 *   control key -> 4
5143 */
5144static unsigned
5145KeyState(XtermWidget xw, unsigned x)
5146{
5147    return ((((x) & (ShiftMask | ControlMask)))
5148	    + (((x) & MetaMask(xw)) ? 2 : 0));
5149}
5150
5151/* 32 + following 8-bit word:
5152
5153   1:0  Button no: 0, 1, 2.  3=release.
5154     2  shift
5155     3  meta
5156     4  ctrl
5157     5  set for motion notify
5158     6  set for wheel (and button 6 and 7)
5159     7  set for buttons 8 to 11
5160*/
5161
5162/* Position: 32 - 255. */
5163static int
5164BtnCode(XtermWidget xw, XButtonEvent *event, int button)
5165{
5166    int result = (int) (32 + (KeyState(xw, event->state) << 2));
5167
5168    if (event->type == MotionNotify)
5169	result += 32;
5170
5171    if (button < 0) {
5172	result += 3;
5173    } else {
5174	result += button & 3;
5175	if (button & 4)
5176	    result += 64;
5177	if (button & 8)
5178	    result += 128;
5179    }
5180    TRACE(("BtnCode button %d, %s state " FMT_MODIFIER_NAMES " ->%#x\n",
5181	   button,
5182	   visibleEventType(event->type),
5183	   ARG_MODIFIER_NAMES(event->state),
5184	   result));
5185    return result;
5186}
5187
5188static unsigned
5189EmitButtonCode(XtermWidget xw,
5190	       Char *line,
5191	       unsigned count,
5192	       XButtonEvent *event,
5193	       int button)
5194{
5195    TScreen *screen = TScreenOf(xw);
5196    int value;
5197
5198    if (okSendMousePos(xw) == X10_MOUSE) {
5199	value = CharOf(' ' + button);
5200    } else {
5201	value = BtnCode(xw, event, button);
5202    }
5203
5204    switch (screen->extend_coords) {
5205    default:
5206	line[count++] = CharOf(value);
5207	break;
5208    case SET_SGR_EXT_MODE_MOUSE:
5209    case SET_PIXEL_POSITION_MOUSE:
5210	value -= 32;		/* encoding starts at zero */
5211	/* FALLTHRU */
5212    case SET_URXVT_EXT_MODE_MOUSE:
5213	count += (unsigned) sprintf((char *) line + count, "%d", value);
5214	break;
5215    case SET_EXT_MODE_MOUSE:
5216	if (value < 128) {
5217	    line[count++] = CharOf(value);
5218	} else {
5219	    line[count++] = CharOf(0xC0 + (value >> 6));
5220	    line[count++] = CharOf(0x80 + (value & 0x3F));
5221	}
5222	break;
5223    }
5224    return count;
5225}
5226
5227static int
5228FirstBitN(int bits)
5229{
5230    int result = -1;
5231    if (bits > 0) {
5232	result = 0;
5233	while (!(bits & 1)) {
5234	    bits /= 2;
5235	    ++result;
5236	}
5237    }
5238    return result;
5239}
5240
5241#define ButtonBit(button) ((button >= 0) ? (1 << (button)) : 0)
5242
5243#define EMIT_BUTTON(button) EmitButtonCode(xw, line, count, event, button)
5244
5245static void
5246EditorButton(XtermWidget xw, XButtonEvent *event)
5247{
5248    TScreen *screen = TScreenOf(xw);
5249    int pty = screen->respond;
5250    int mouse_limit = MouseLimit(screen);
5251    Char line[32];
5252    Char final = 'M';
5253    int row, col;
5254    int button;
5255    unsigned count = 0;
5256    Boolean changed = True;
5257
5258    /* If button event, get button # adjusted for DEC compatibility */
5259    button = (int) (event->button - 1);
5260    if (button >= 3)
5261	button++;
5262
5263    /* Ignore buttons that cannot be encoded */
5264    if (screen->send_mouse_pos == X10_MOUSE) {
5265	if (button > 3)
5266	    return;
5267    } else if (screen->extend_coords == SET_SGR_EXT_MODE_MOUSE
5268	       || screen->extend_coords == SET_URXVT_EXT_MODE_MOUSE
5269	       || screen->extend_coords == SET_PIXEL_POSITION_MOUSE) {
5270	if (button > 15) {
5271	    return;
5272	}
5273    } else {
5274	if (button > 11) {
5275	    return;
5276	}
5277    }
5278
5279    if (screen->extend_coords == SET_PIXEL_POSITION_MOUSE) {
5280	row = event->y - OriginY(screen);
5281	col = event->x - OriginX(screen);
5282    } else {
5283	/* Compute character position of mouse pointer */
5284	row = (event->y - screen->border) / FontHeight(screen);
5285	col = (event->x - OriginX(screen)) / FontWidth(screen);
5286
5287	/* Limit to screen dimensions */
5288	if (row < 0)
5289	    row = 0;
5290	else if (row > screen->max_row)
5291	    row = screen->max_row;
5292
5293	if (col < 0)
5294	    col = 0;
5295	else if (col > screen->max_col)
5296	    col = screen->max_col;
5297
5298	if (mouse_limit > 0) {
5299	    /* Limit to representable mouse dimensions */
5300	    if (row > mouse_limit)
5301		row = mouse_limit;
5302	    if (col > mouse_limit)
5303		col = mouse_limit;
5304	}
5305    }
5306
5307    /* Build key sequence starting with \E[M */
5308    if (screen->control_eight_bits) {
5309	line[count++] = ANSI_CSI;
5310    } else {
5311	line[count++] = ANSI_ESC;
5312	line[count++] = '[';
5313    }
5314    switch (screen->extend_coords) {
5315    case 0:
5316    case SET_EXT_MODE_MOUSE:
5317#if OPT_SCO_FUNC_KEYS
5318	if (xw->keyboard.type == keyboardIsSCO) {
5319	    /*
5320	     * SCO function key F1 is \E[M, which would conflict with xterm's
5321	     * normal kmous.
5322	     */
5323	    line[count++] = '>';
5324	}
5325#endif
5326	line[count++] = final;
5327	break;
5328    case SET_SGR_EXT_MODE_MOUSE:
5329    case SET_PIXEL_POSITION_MOUSE:
5330	line[count++] = '<';
5331	break;
5332    }
5333
5334    /* Add event code to key sequence */
5335    if (okSendMousePos(xw) == X10_MOUSE) {
5336	count = EMIT_BUTTON(button);
5337    } else {
5338	/* Button-Motion events */
5339	switch (event->type) {
5340	case ButtonPress:
5341	    screen->mouse_button |= ButtonBit(button);
5342	    count = EMIT_BUTTON(button);
5343	    break;
5344	case ButtonRelease:
5345	    /*
5346	     * The (vertical) wheel mouse interface generates release-events
5347	     * for buttons 4 and 5.
5348	     *
5349	     * The X10/X11 xterm protocol maps the release for buttons 1..3 to
5350	     * a -1, which will be later mapped into a "0" (some button was
5351	     * released),  At this point, buttons 1..3 are encoded 0..2 (the
5352	     * code 3 is unused).
5353	     *
5354	     * The SGR (extended) xterm mouse protocol keeps the button number
5355	     * and uses a "m" to indicate button release.
5356	     *
5357	     * The behavior for mice with more buttons is unclear, and may be
5358	     * revised -TD
5359	     */
5360	    screen->mouse_button &= ~ButtonBit(button);
5361	    if (button < 3 || button > 5) {
5362		switch (screen->extend_coords) {
5363		case SET_SGR_EXT_MODE_MOUSE:
5364		case SET_PIXEL_POSITION_MOUSE:
5365		    final = 'm';
5366		    break;
5367		default:
5368		    button = -1;
5369		    break;
5370		}
5371	    }
5372	    count = EMIT_BUTTON(button);
5373	    break;
5374	case MotionNotify:
5375	    /* BTN_EVENT_MOUSE and ANY_EVENT_MOUSE modes send motion
5376	     * events only if character cell has changed.
5377	     */
5378	    if ((row == screen->mouse_row)
5379		&& (col == screen->mouse_col)) {
5380		changed = False;
5381	    } else {
5382		count = EMIT_BUTTON(FirstBitN(screen->mouse_button));
5383	    }
5384	    break;
5385	default:
5386	    changed = False;
5387	    break;
5388	}
5389    }
5390
5391    if (changed) {
5392	screen->mouse_row = row;
5393	screen->mouse_col = col;
5394
5395	TRACE(("mouse at %d,%d button+mask = %#x\n", row, col, line[count - 1]));
5396
5397	/* Add pointer position to key sequence */
5398	count = EmitMousePositionSeparator(screen, line, count);
5399	count = EmitMousePosition(screen, line, count, col);
5400	count = EmitMousePositionSeparator(screen, line, count);
5401	count = EmitMousePosition(screen, line, count, row);
5402
5403	switch (screen->extend_coords) {
5404	case SET_SGR_EXT_MODE_MOUSE:
5405	case SET_URXVT_EXT_MODE_MOUSE:
5406	case SET_PIXEL_POSITION_MOUSE:
5407	    line[count++] = final;
5408	    break;
5409	}
5410
5411	/* Transmit key sequence to process running under xterm */
5412	TRACE(("EditorButton -> %s\n", visibleChars(line, count)));
5413	v_write(pty, line, count);
5414    }
5415    return;
5416}
5417
5418/*
5419 * Check the current send_mouse_pos against allowed mouse-operations, returning
5420 * none if it is disallowed.
5421 */
5422XtermMouseModes
5423okSendMousePos(XtermWidget xw)
5424{
5425    TScreen *screen = TScreenOf(xw);
5426    XtermMouseModes result = (XtermMouseModes) screen->send_mouse_pos;
5427
5428    switch ((int) result) {
5429    case MOUSE_OFF:
5430	break;
5431    case X10_MOUSE:
5432	if (!AllowMouseOps(xw, emX10))
5433	    result = MOUSE_OFF;
5434	break;
5435    case VT200_MOUSE:
5436	if (!AllowMouseOps(xw, emVT200Click))
5437	    result = MOUSE_OFF;
5438	break;
5439    case VT200_HIGHLIGHT_MOUSE:
5440	if (!AllowMouseOps(xw, emVT200Hilite))
5441	    result = MOUSE_OFF;
5442	break;
5443    case BTN_EVENT_MOUSE:
5444	if (!AllowMouseOps(xw, emAnyButton))
5445	    result = MOUSE_OFF;
5446	break;
5447    case ANY_EVENT_MOUSE:
5448	if (!AllowMouseOps(xw, emAnyEvent))
5449	    result = MOUSE_OFF;
5450	break;
5451    case DEC_LOCATOR:
5452	if (!AllowMouseOps(xw, emLocator))
5453	    result = MOUSE_OFF;
5454	break;
5455    }
5456    return result;
5457}
5458
5459#if OPT_FOCUS_EVENT
5460/*
5461 * Check the current send_focus_pos against allowed mouse-operations, returning
5462 * none if it is disallowed.
5463 */
5464static int
5465okSendFocusPos(XtermWidget xw)
5466{
5467    TScreen *screen = TScreenOf(xw);
5468    int result = screen->send_focus_pos;
5469
5470    if (!AllowMouseOps(xw, emFocusEvent)) {
5471	result = False;
5472    }
5473    return result;
5474}
5475
5476void
5477SendFocusButton(XtermWidget xw, XFocusChangeEvent *event)
5478{
5479    if (okSendFocusPos(xw)) {
5480	ANSI reply;
5481
5482	memset(&reply, 0, sizeof(reply));
5483	reply.a_type = ANSI_CSI;
5484
5485#if OPT_SCO_FUNC_KEYS
5486	if (xw->keyboard.type == keyboardIsSCO) {
5487	    reply.a_pintro = '>';
5488	}
5489#endif
5490	reply.a_final = CharOf((event->type == FocusIn) ? 'I' : 'O');
5491	unparseseq(xw, &reply);
5492    }
5493    return;
5494}
5495#endif /* OPT_FOCUS_EVENT */
5496
5497#if OPT_SELECTION_OPS
5498/*
5499 * Get the event-time, needed to process selections.
5500 */
5501static Time
5502getEventTime(XEvent *event)
5503{
5504    Time result;
5505
5506    if (IsBtnEvent(event)) {
5507	result = ((XButtonEvent *) event)->time;
5508    } else if (IsKeyEvent(event)) {
5509	result = ((XKeyEvent *) event)->time;
5510    } else {
5511	result = 0;
5512    }
5513
5514    return result;
5515}
5516
5517/* obtain the selection string, passing the endpoints to caller's parameters */
5518static void
5519doSelectionFormat(XtermWidget xw,
5520		  Widget w,
5521		  XEvent *event,
5522		  String *params,
5523		  Cardinal *num_params,
5524		  FormatSelect format_select)
5525{
5526    TScreen *screen = TScreenOf(xw);
5527    InternalSelect *mydata = &(screen->internal_select);
5528
5529    memset(mydata, 0, sizeof(*mydata));
5530    mydata->format = x_strdup(params[0]);
5531    mydata->format_select = format_select;
5532
5533    screen->selectToBuffer = True;
5534    beginInternalSelect(xw);
5535
5536    xtermGetSelection(w, getEventTime(event), params + 1, *num_params - 1, NULL);
5537
5538    if (screen->selectToBuffer)
5539	finishInternalSelect(xw);
5540}
5541
5542/* obtain data from the screen, passing the endpoints to caller's parameters */
5543static char *
5544getDataFromScreen(XtermWidget xw, XEvent *event, String method, CELL *start, CELL *finish)
5545{
5546    TScreen *screen = TScreenOf(xw);
5547
5548    CELL save_old_start = screen->startH;
5549    CELL save_old_end = screen->endH;
5550
5551    CELL save_startSel = screen->startSel;
5552    CELL save_startRaw = screen->startRaw;
5553    CELL save_finishSel = screen->endSel;
5554    CELL save_finishRaw = screen->endRaw;
5555
5556    int save_firstValidRow = screen->firstValidRow;
5557    int save_lastValidRow = screen->lastValidRow;
5558
5559    const Cardinal noClick = 0;
5560    int save_numberOfClicks = screen->numberOfClicks;
5561
5562    SelectUnit saveUnits = screen->selectUnit;
5563    SelectUnit saveMap = screen->selectMap[noClick];
5564#if OPT_SELECT_REGEX
5565    char *saveExpr = screen->selectExpr[noClick];
5566#endif
5567    SelectedCells *scp = &(screen->selected_cells[PRIMARY_CODE]);
5568    SelectedCells save_selection = *scp;
5569
5570    char *result = 0;
5571
5572    TRACE(("getDataFromScreen %s\n", method));
5573
5574    memset(scp, 0, sizeof(*scp));
5575
5576    screen->numberOfClicks = 1;
5577    lookupSelectUnit(xw, noClick, method);
5578    screen->selectUnit = screen->selectMap[noClick];
5579
5580    memset(start, 0, sizeof(*start));
5581    if (IsBtnEvent(event)) {
5582	XButtonEvent *btn_event = (XButtonEvent *) event;
5583	CELL cell;
5584	screen->firstValidRow = 0;
5585	screen->lastValidRow = screen->max_row;
5586	PointToCELL(screen, btn_event->y, btn_event->x, &cell);
5587	start->row = cell.row;
5588	start->col = cell.col;
5589	finish->row = cell.row;
5590	finish->col = screen->max_col;
5591    } else {
5592	start->row = screen->cur_row;
5593	start->col = screen->cur_col;
5594	finish->row = screen->cur_row;
5595	finish->col = screen->max_col;
5596    }
5597
5598    ComputeSelect(xw, start, finish, False, False);
5599    SaltTextAway(xw,
5600		 TargetToSelection(screen, PRIMARY_NAME),
5601		 &(screen->startSel), &(screen->endSel));
5602
5603    if (scp->data_limit && scp->data_buffer) {
5604	TRACE(("...getDataFromScreen selection-data %.*s\n",
5605	       (int) scp->data_limit,
5606	       scp->data_buffer));
5607	result = malloc(scp->data_limit + 1);
5608	if (result) {
5609	    memcpy(result, scp->data_buffer, scp->data_limit);
5610	    result[scp->data_limit] = 0;
5611	}
5612	free(scp->data_buffer);
5613	scp->data_limit = 0;
5614    }
5615
5616    TRACE(("...getDataFromScreen restoring previous selection\n"));
5617
5618    screen->startSel = save_startSel;
5619    screen->startRaw = save_startRaw;
5620    screen->endSel = save_finishSel;
5621    screen->endRaw = save_finishRaw;
5622
5623    screen->firstValidRow = save_firstValidRow;
5624    screen->lastValidRow = save_lastValidRow;
5625
5626    screen->numberOfClicks = save_numberOfClicks;
5627    screen->selectUnit = saveUnits;
5628    screen->selectMap[noClick] = saveMap;
5629#if OPT_SELECT_REGEX
5630    screen->selectExpr[noClick] = saveExpr;
5631#endif
5632
5633    screen->selected_cells[0] = save_selection;
5634
5635    TrackText(xw, &save_old_start, &save_old_end);
5636
5637    TRACE(("...getDataFromScreen done\n"));
5638    return result;
5639}
5640
5641/*
5642 * Split-up the format before substituting data, to avoid quoting issues.
5643 * The resource mechanism has a limited ability to handle escapes.  We take
5644 * the result as if it were an sh-type string and parse it into a regular
5645 * argv array.
5646 */
5647static char **
5648tokenizeFormat(String format)
5649{
5650    char **result = 0;
5651
5652    format = x_skip_blanks(format);
5653    if (*format != '\0') {
5654	char *blob = x_strdup(format);
5655	int pass;
5656
5657	for (pass = 0; pass < 2; ++pass) {
5658	    int used = 0;
5659	    int first = 1;
5660	    int escaped = 0;
5661	    int squoted = 0;
5662	    int dquoted = 0;
5663	    int n;
5664	    int argc = 0;
5665
5666	    for (n = 0; format[n] != '\0'; ++n) {
5667		if (escaped) {
5668		    blob[used++] = format[n];
5669		    escaped = 0;
5670		} else if (format[n] == '"') {
5671		    if (!squoted) {
5672			if (!dquoted)
5673			    blob[used++] = format[n];
5674			dquoted = !dquoted;
5675		    }
5676		} else if (format[n] == '\'') {
5677		    if (!dquoted) {
5678			if (!squoted)
5679			    blob[used++] = format[n];
5680			squoted = !squoted;
5681		    }
5682		} else if (format[n] == '\\') {
5683		    blob[used++] = format[n];
5684		    escaped = 1;
5685		} else {
5686		    if (first) {
5687			first = 0;
5688			if (pass) {
5689			    result[argc] = &blob[n];
5690			}
5691			++argc;
5692		    }
5693		    if (isspace((Char) format[n])) {
5694			first = !isspace((Char) format[n + 1]);
5695			if (squoted || dquoted) {
5696			    blob[used++] = format[n];
5697			} else if (first) {
5698			    blob[used++] = '\0';
5699			}
5700		    } else {
5701			blob[used++] = format[n];
5702		    }
5703		}
5704	    }
5705	    blob[used] = '\0';
5706	    assert(strlen(blob) <= strlen(format));
5707	    if (!pass) {
5708		result = TypeCallocN(char *, argc + 1);
5709		if (result == 0) {
5710		    free(blob);
5711		    break;
5712		}
5713	    }
5714	}
5715    }
5716#if OPT_TRACE
5717    if (result) {
5718	int n;
5719	TRACE(("tokenizeFormat %s\n", format));
5720	for (n = 0; result[n]; ++n) {
5721	    TRACE(("argv[%d] = %s\n", n, result[n]));
5722	}
5723    }
5724#endif
5725
5726    return result;
5727}
5728
5729static void
5730formatVideoAttrs(XtermWidget xw, char *buffer, CELL *cell)
5731{
5732    TScreen *screen = TScreenOf(xw);
5733    LineData *ld = GET_LINEDATA(screen, cell->row);
5734
5735    *buffer = '\0';
5736    if (ld != 0 && cell->col < (int) ld->lineSize) {
5737	IAttr attribs = ld->attribs[cell->col];
5738	const char *delim = "";
5739
5740	if (attribs & INVERSE) {
5741	    buffer += sprintf(buffer, "7");
5742	    delim = ";";
5743	}
5744	if (attribs & UNDERLINE) {
5745	    buffer += sprintf(buffer, "%s4", delim);
5746	    delim = ";";
5747	}
5748	if (attribs & BOLD) {
5749	    buffer += sprintf(buffer, "%s1", delim);
5750	    delim = ";";
5751	}
5752	if (attribs & BLINK) {
5753	    buffer += sprintf(buffer, "%s5", delim);
5754	    delim = ";";
5755	}
5756#if OPT_ISO_COLORS
5757	if (attribs & FG_COLOR) {
5758	    Pixel fg = extract_fg(xw, ld->color[cell->col], attribs);
5759	    if (fg < 8) {
5760		fg += 30;
5761	    } else if (fg < 16) {
5762		fg += 90;
5763	    } else {
5764		buffer += sprintf(buffer, "%s38;5", delim);
5765		delim = ";";
5766	    }
5767	    buffer += sprintf(buffer, "%s%lu", delim, fg);
5768	    delim = ";";
5769	}
5770	if (attribs & BG_COLOR) {
5771	    Pixel bg = extract_bg(xw, ld->color[cell->col], attribs);
5772	    if (bg < 8) {
5773		bg += 40;
5774	    } else if (bg < 16) {
5775		bg += 100;
5776	    } else {
5777		buffer += sprintf(buffer, "%s48;5", delim);
5778		delim = ";";
5779	    }
5780	    (void) sprintf(buffer, "%s%lu", delim, bg);
5781	}
5782#endif
5783    }
5784}
5785
5786static char *
5787formatStrlen(char *target, char *source, int freeit)
5788{
5789    if (source != 0) {
5790	sprintf(target, "%u", (unsigned) strlen(source));
5791	if (freeit) {
5792	    free(source);
5793	}
5794    } else {
5795	strcpy(target, "0");
5796    }
5797    return target;
5798}
5799
5800/* substitute data into format, reallocating the result */
5801static char *
5802expandFormat(XtermWidget xw,
5803	     const char *format,
5804	     char *data,
5805	     CELL *start,
5806	     CELL *finish)
5807{
5808    char *result = 0;
5809    if (!IsEmpty(format)) {
5810	static char empty[1];
5811	int pass;
5812	int n;
5813	char numbers[80];
5814
5815	if (data == 0)
5816	    data = empty;
5817
5818	for (pass = 0; pass < 2; ++pass) {
5819	    size_t need = 0;
5820
5821	    for (n = 0; format[n] != '\0'; ++n) {
5822
5823		if (format[n] == '%') {
5824		    char *value = 0;
5825
5826		    switch (format[++n]) {
5827		    case '%':
5828			if (pass) {
5829			    result[need] = format[n];
5830			}
5831			++need;
5832			break;
5833		    case 'P':
5834			sprintf(numbers, "%d;%d",
5835				TScreenOf(xw)->topline + start->row + 1,
5836				start->col + 1);
5837			value = numbers;
5838			break;
5839		    case 'p':
5840			sprintf(numbers, "%d;%d",
5841				TScreenOf(xw)->topline + finish->row + 1,
5842				finish->col + 1);
5843			value = numbers;
5844			break;
5845		    case 'R':
5846			value = formatStrlen(numbers, x_strrtrim(data), 1);
5847			break;
5848		    case 'r':
5849			value = x_strrtrim(data);
5850			break;
5851		    case 'S':
5852			value = formatStrlen(numbers, data, 0);
5853			break;
5854		    case 's':
5855			value = data;
5856			break;
5857		    case 'T':
5858			value = formatStrlen(numbers, x_strtrim(data), 1);
5859			break;
5860		    case 't':
5861			value = x_strtrim(data);
5862			break;
5863		    case 'V':
5864			formatVideoAttrs(xw, numbers, start);
5865			value = numbers;
5866			break;
5867		    case 'v':
5868			formatVideoAttrs(xw, numbers, finish);
5869			value = numbers;
5870			break;
5871		    default:
5872			if (pass) {
5873			    result[need] = format[n];
5874			}
5875			--n;
5876			++need;
5877			break;
5878		    }
5879		    if (value != 0) {
5880			if (pass) {
5881			    strcpy(result + need, value);
5882			}
5883			need += strlen(value);
5884			if (value != numbers && value != data) {
5885			    free(value);
5886			}
5887		    }
5888		} else {
5889		    if (pass) {
5890			result[need] = format[n];
5891		    }
5892		    ++need;
5893		}
5894	    }
5895	    if (pass) {
5896		result[need] = '\0';
5897	    } else {
5898		++need;
5899		result = malloc(need);
5900		if (result == 0) {
5901		    break;
5902		}
5903	    }
5904	}
5905    }
5906    TRACE(("expandFormat(%s) = %s\n", NonNull(format), NonNull(result)));
5907    return result;
5908}
5909
5910/* execute the command after forking.  The main process frees its data */
5911static void
5912executeCommand(pid_t pid, char **argv)
5913{
5914    (void) pid;
5915    if (argv != 0 && argv[0] != 0) {
5916	char *child_cwd = ProcGetCWD(pid);
5917
5918	if (fork() == 0) {
5919	    if (child_cwd) {
5920		IGNORE_RC(chdir(child_cwd));	/* We don't care if this fails */
5921	    }
5922	    execvp(argv[0], argv);
5923	    exit(EXIT_FAILURE);
5924	}
5925	free(child_cwd);
5926    }
5927}
5928
5929static void
5930freeArgv(char *blob, char **argv)
5931{
5932    if (blob) {
5933	free(blob);
5934	if (argv) {
5935	    int n;
5936	    for (n = 0; argv[n]; ++n)
5937		free(argv[n]);
5938	    free(argv);
5939	}
5940    }
5941}
5942
5943static void
5944reallyExecFormatted(Widget w, char *format, char *data, CELL *start, CELL *finish)
5945{
5946    XtermWidget xw;
5947
5948    if ((xw = getXtermWidget(w)) != 0) {
5949	char **argv;
5950
5951	if ((argv = tokenizeFormat(format)) != 0) {
5952	    char *blob = argv[0];
5953	    int argc;
5954
5955	    for (argc = 0; argv[argc] != 0; ++argc) {
5956		argv[argc] = expandFormat(xw, argv[argc], data, start, finish);
5957	    }
5958	    executeCommand(TScreenOf(xw)->pid, argv);
5959	    freeArgv(blob, argv);
5960	}
5961    }
5962}
5963
5964void
5965HandleExecFormatted(Widget w,
5966		    XEvent *event,
5967		    String *params,	/* selections */
5968		    Cardinal *num_params)
5969{
5970    XtermWidget xw;
5971
5972    TRACE_EVENT("HandleExecFormatted", event, params, num_params);
5973    if ((xw = getXtermWidget(w)) != 0 &&
5974	(*num_params > 1)) {
5975	doSelectionFormat(xw, w, event, params, num_params, reallyExecFormatted);
5976    }
5977}
5978
5979void
5980HandleExecSelectable(Widget w,
5981		     XEvent *event,
5982		     String *params,	/* selections */
5983		     Cardinal *num_params)
5984{
5985    XtermWidget xw;
5986
5987    if ((xw = getXtermWidget(w)) != 0) {
5988	TRACE_EVENT("HandleExecSelectable", event, params, num_params);
5989
5990	if (*num_params == 2) {
5991	    CELL start, finish;
5992	    char *data;
5993	    char **argv;
5994
5995	    data = getDataFromScreen(xw, event, params[1], &start, &finish);
5996	    if (data != 0) {
5997		if ((argv = tokenizeFormat(params[0])) != 0) {
5998		    char *blob = argv[0];
5999		    int argc;
6000
6001		    for (argc = 0; argv[argc] != 0; ++argc) {
6002			argv[argc] = expandFormat(xw, argv[argc], data,
6003						  &start, &finish);
6004		    }
6005		    executeCommand(TScreenOf(xw)->pid, argv);
6006		    freeArgv(blob, argv);
6007		}
6008		free(data);
6009	    }
6010	}
6011    }
6012}
6013
6014static void
6015reallyInsertFormatted(Widget w, char *format, char *data, CELL *start, CELL *finish)
6016{
6017    XtermWidget xw;
6018
6019    if ((xw = getXtermWidget(w)) != 0) {
6020	char *exps;
6021
6022	if ((exps = expandFormat(xw, format, data, start, finish)) != 0) {
6023	    unparseputs(xw, exps);
6024	    unparse_end(xw);
6025	    free(exps);
6026	}
6027    }
6028}
6029
6030void
6031HandleInsertFormatted(Widget w,
6032		      XEvent *event,
6033		      String *params,	/* selections */
6034		      Cardinal *num_params)
6035{
6036    XtermWidget xw;
6037
6038    TRACE_EVENT("HandleInsertFormatted", event, params, num_params);
6039    if ((xw = getXtermWidget(w)) != 0 &&
6040	(*num_params > 1)) {
6041	doSelectionFormat(xw, w, event, params, num_params, reallyInsertFormatted);
6042    }
6043}
6044
6045void
6046HandleInsertSelectable(Widget w,
6047		       XEvent *event,
6048		       String *params,	/* selections */
6049		       Cardinal *num_params)
6050{
6051    XtermWidget xw;
6052
6053    if ((xw = getXtermWidget(w)) != 0) {
6054	TRACE_EVENT("HandleInsertSelectable", event, params, num_params);
6055
6056	if (*num_params == 2) {
6057	    CELL start, finish;
6058	    char *data;
6059	    char *temp = x_strdup(params[0]);
6060
6061	    data = getDataFromScreen(xw, event, params[1], &start, &finish);
6062	    if (data != 0) {
6063		char *exps = expandFormat(xw, temp, data, &start, &finish);
6064		if (exps != 0) {
6065		    unparseputs(xw, exps);
6066		    unparse_end(xw);
6067		    free(exps);
6068		}
6069		free(data);
6070	    }
6071	    free(temp);
6072	}
6073    }
6074}
6075#endif /* OPT_SELECTION_OPS */
6076