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