misc.c revision 3367019c
1/* $XTermId: misc.c,v 1.660 2013/05/26 21:16:20 tom Exp $ */
2
3/*
4 * Copyright 1999-2012,2013 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#include <version.h>
56#include <main.h>
57#include <xterm.h>
58
59#include <sys/stat.h>
60#include <stdio.h>
61#include <stdarg.h>
62#include <signal.h>
63#include <ctype.h>
64#include <pwd.h>
65#include <sys/wait.h>
66
67#include <X11/keysym.h>
68#include <X11/Xatom.h>
69#include <X11/cursorfont.h>
70#include <X11/Xlocale.h>
71
72#include <X11/Xmu/Error.h>
73#include <X11/Xmu/SysUtil.h>
74#include <X11/Xmu/WinUtil.h>
75#include <X11/Xmu/Xmu.h>
76#if HAVE_X11_SUNKEYSYM_H
77#include <X11/Sunkeysym.h>
78#endif
79
80#ifdef HAVE_LIBXPM
81#include <X11/xpm.h>
82#endif
83
84#ifdef HAVE_LANGINFO_CODESET
85#include <langinfo.h>
86#endif
87
88#include <xutf8.h>
89
90#include <data.h>
91#include <error.h>
92#include <menu.h>
93#include <fontutils.h>
94#include <xcharmouse.h>
95#include <xstrings.h>
96#include <xtermcap.h>
97#include <VTparse.h>
98
99#include <assert.h>
100
101#if (XtSpecificationRelease < 6)
102#ifndef X_GETTIMEOFDAY
103#define X_GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *)0)
104#endif
105#endif
106
107#ifdef VMS
108#define XTERM_VMS_LOGFILE "SYS$SCRATCH:XTERM_LOG.TXT"
109#ifdef ALLOWLOGFILEEXEC
110#undef ALLOWLOGFILEEXEC
111#endif
112#endif /* VMS */
113
114#if OPT_TEK4014
115#define OUR_EVENT(event,Type) \
116		(event.type == Type && \
117		  (event.xcrossing.window == XtWindow(XtParent(xw)) || \
118		    (tekWidget && \
119		     event.xcrossing.window == XtWindow(XtParent(tekWidget)))))
120#else
121#define OUR_EVENT(event,Type) \
122		(event.type == Type && \
123		   (event.xcrossing.window == XtWindow(XtParent(xw))))
124#endif
125
126static Boolean xtermAllocColor(XtermWidget, XColor *, const char *);
127static Cursor make_hidden_cursor(XtermWidget);
128
129static char emptyString[] = "";
130
131#if OPT_EXEC_XTERM
132/* Like readlink(2), but returns a malloc()ed buffer, or NULL on
133   error; adapted from libc docs */
134static char *
135Readlink(const char *filename)
136{
137    char *buf = NULL;
138    size_t size = 100;
139    int n;
140
141    for (;;) {
142	buf = TypeRealloc(char, size, buf);
143	memset(buf, 0, size);
144
145	n = (int) readlink(filename, buf, size);
146	if (n < 0) {
147	    free(buf);
148	    return NULL;
149	}
150
151	if ((unsigned) n < size) {
152	    return buf;
153	}
154
155	size *= 2;
156    }
157}
158#endif /* OPT_EXEC_XTERM */
159
160static void
161Sleep(int msec)
162{
163    static struct timeval select_timeout;
164
165    select_timeout.tv_sec = 0;
166    select_timeout.tv_usec = msec * 1000;
167    select(0, 0, 0, 0, &select_timeout);
168}
169
170static void
171selectwindow(XtermWidget xw, int flag)
172{
173    TScreen *screen = TScreenOf(xw);
174
175    TRACE(("selectwindow(%d) flag=%d\n", screen->select, flag));
176
177#if OPT_TEK4014
178    if (TEK4014_ACTIVE(xw)) {
179	if (!Ttoggled)
180	    TCursorToggle(tekWidget, TOGGLE);
181	screen->select |= flag;
182	if (!Ttoggled)
183	    TCursorToggle(tekWidget, TOGGLE);
184    } else
185#endif
186    {
187#if OPT_I18N_SUPPORT && OPT_INPUT_METHOD
188	TInput *input = lookupTInput(xw, (Widget) xw);
189	if (input && input->xic)
190	    XSetICFocus(input->xic);
191#endif
192
193	if (screen->cursor_state && CursorMoved(screen))
194	    HideCursor();
195	screen->select |= flag;
196	if (screen->cursor_state)
197	    ShowCursor();
198    }
199    GetScrollLock(screen);
200}
201
202static void
203unselectwindow(XtermWidget xw, int flag)
204{
205    TScreen *screen = TScreenOf(xw);
206
207    TRACE(("unselectwindow(%d) flag=%d\n", screen->select, flag));
208
209    if (screen->hide_pointer && screen->pointer_mode < pFocused) {
210	screen->hide_pointer = False;
211	xtermDisplayCursor(xw);
212    }
213
214    if (!screen->always_highlight) {
215#if OPT_TEK4014
216	if (TEK4014_ACTIVE(xw)) {
217	    if (!Ttoggled)
218		TCursorToggle(tekWidget, TOGGLE);
219	    screen->select &= ~flag;
220	    if (!Ttoggled)
221		TCursorToggle(tekWidget, TOGGLE);
222	} else
223#endif
224	{
225#if OPT_I18N_SUPPORT && OPT_INPUT_METHOD
226	    TInput *input = lookupTInput(xw, (Widget) xw);
227	    if (input && input->xic)
228		XUnsetICFocus(input->xic);
229#endif
230
231	    screen->select &= ~flag;
232	    if (screen->cursor_state && CursorMoved(screen))
233		HideCursor();
234	    if (screen->cursor_state)
235		ShowCursor();
236	}
237    }
238}
239
240static void
241DoSpecialEnterNotify(XtermWidget xw, XEnterWindowEvent * ev)
242{
243    TScreen *screen = TScreenOf(xw);
244
245    TRACE(("DoSpecialEnterNotify(%d)\n", screen->select));
246    TRACE_FOCUS(xw, ev);
247    if (((ev->detail) != NotifyInferior) &&
248	ev->focus &&
249	!(screen->select & FOCUS))
250	selectwindow(xw, INWINDOW);
251}
252
253static void
254DoSpecialLeaveNotify(XtermWidget xw, XEnterWindowEvent * ev)
255{
256    TScreen *screen = TScreenOf(xw);
257
258    TRACE(("DoSpecialLeaveNotify(%d)\n", screen->select));
259    TRACE_FOCUS(xw, ev);
260    if (((ev->detail) != NotifyInferior) &&
261	ev->focus &&
262	!(screen->select & FOCUS))
263	unselectwindow(xw, INWINDOW);
264}
265
266#ifndef XUrgencyHint
267#define XUrgencyHint (1L << 8)	/* X11R5 does not define */
268#endif
269
270static void
271setXUrgency(XtermWidget xw, Bool enable)
272{
273    TScreen *screen = TScreenOf(xw);
274
275    if (screen->bellIsUrgent) {
276	XWMHints *h = XGetWMHints(screen->display, VShellWindow(xw));
277	if (h != 0) {
278	    if (enable && !(screen->select & FOCUS)) {
279		h->flags |= XUrgencyHint;
280	    } else {
281		h->flags &= ~XUrgencyHint;
282	    }
283	    XSetWMHints(screen->display, VShellWindow(xw), h);
284	}
285    }
286}
287
288void
289do_xevents(void)
290{
291    TScreen *screen = TScreenOf(term);
292
293    if (xtermAppPending()
294	||
295#if defined(VMS) || defined(__VMS)
296	screen->display->qlen > 0
297#else
298	GetBytesAvailable(ConnectionNumber(screen->display)) > 0
299#endif
300	)
301	xevents();
302}
303
304void
305xtermDisplayCursor(XtermWidget xw)
306{
307    TScreen *screen = TScreenOf(xw);
308
309    if (screen->Vshow) {
310	if (screen->hide_pointer) {
311	    TRACE(("Display hidden_cursor\n"));
312	    XDefineCursor(screen->display, VWindow(screen), screen->hidden_cursor);
313	} else {
314	    TRACE(("Display pointer_cursor\n"));
315	    recolor_cursor(screen,
316			   screen->pointer_cursor,
317			   T_COLOR(screen, MOUSE_FG),
318			   T_COLOR(screen, MOUSE_BG));
319	    XDefineCursor(screen->display, VWindow(screen), screen->pointer_cursor);
320	}
321    }
322}
323
324void
325xtermShowPointer(XtermWidget xw, Bool enable)
326{
327    static int tried = -1;
328    TScreen *screen = TScreenOf(xw);
329
330#if OPT_TEK4014
331    if (TEK4014_SHOWN(xw))
332	enable = True;
333#endif
334
335    /*
336     * Whether we actually hide the pointer depends on the pointer-mode and
337     * the mouse-mode:
338     */
339    if (!enable) {
340	switch (screen->pointer_mode) {
341	case pNever:
342	    enable = True;
343	    break;
344	case pNoMouse:
345	    if (screen->send_mouse_pos != MOUSE_OFF)
346		enable = True;
347	    break;
348	case pAlways:
349	case pFocused:
350	    break;
351	}
352    }
353
354    if (enable) {
355	if (screen->hide_pointer) {
356	    screen->hide_pointer = False;
357	    xtermDisplayCursor(xw);
358	    switch (screen->send_mouse_pos) {
359	    case ANY_EVENT_MOUSE:
360		break;
361	    default:
362		MotionOff(screen, xw);
363		break;
364	    }
365	}
366    } else if (!(screen->hide_pointer) && (tried <= 0)) {
367	if (screen->hidden_cursor == 0) {
368	    screen->hidden_cursor = make_hidden_cursor(xw);
369	}
370	if (screen->hidden_cursor == 0) {
371	    tried = 1;
372	} else {
373	    tried = 0;
374	    screen->hide_pointer = True;
375	    xtermDisplayCursor(xw);
376	    MotionOn(screen, xw);
377	}
378    }
379}
380
381#if OPT_TRACE
382static void
383TraceExposeEvent(XEvent * arg)
384{
385    XExposeEvent *event = (XExposeEvent *) arg;
386
387    TRACE(("pending Expose %ld %d: %d,%d %dx%d %#lx\n",
388	   event->serial,
389	   event->count,
390	   event->y,
391	   event->x,
392	   event->height,
393	   event->width,
394	   event->window));
395}
396
397#else
398#define TraceExposeEvent(event)	/* nothing */
399#endif
400
401/* true if p contains q */
402#define ExposeContains(p,q) \
403	    ((p)->y <= (q)->y \
404	  && (p)->x <= (q)->x \
405	  && ((p)->y + (p)->height) >= ((q)->y + (q)->height) \
406	  && ((p)->x + (p)->width) >= ((q)->x + (q)->width))
407
408static XtInputMask
409mergeExposeEvents(XEvent * target)
410{
411    XEvent next_event;
412    XExposeEvent *p, *q;
413
414    TRACE(("pending Expose...?\n"));
415    TraceExposeEvent(target);
416    XtAppNextEvent(app_con, target);
417    p = (XExposeEvent *) target;
418
419    while (XtAppPending(app_con)
420	   && XtAppPeekEvent(app_con, &next_event)
421	   && next_event.type == Expose) {
422	Boolean merge_this = False;
423
424	TraceExposeEvent(&next_event);
425	q = (XExposeEvent *) (&next_event);
426	XtAppNextEvent(app_con, &next_event);
427
428	/*
429	 * If either window is contained within the other, merge the events.
430	 * The traces show that there are also cases where a full repaint of
431	 * a window is broken into 3 or more rectangles, which do not arrive
432	 * in the same instant.  We could merge those if xterm were modified
433	 * to skim several events ahead.
434	 */
435	if (p->window == q->window) {
436	    if (ExposeContains(p, q)) {
437		TRACE(("pending Expose...merged forward\n"));
438		merge_this = True;
439		next_event = *target;
440	    } else if (ExposeContains(q, p)) {
441		TRACE(("pending Expose...merged backward\n"));
442		merge_this = True;
443	    }
444	}
445	if (!merge_this) {
446	    XtDispatchEvent(target);
447	}
448	*target = next_event;
449    }
450    XtDispatchEvent(target);
451    return XtAppPending(app_con);
452}
453
454#if OPT_TRACE
455static void
456TraceConfigureEvent(XEvent * arg)
457{
458    XConfigureEvent *event = (XConfigureEvent *) arg;
459
460    TRACE(("pending Configure %ld %d,%d %dx%d %#lx\n",
461	   event->serial,
462	   event->y,
463	   event->x,
464	   event->height,
465	   event->width,
466	   event->window));
467}
468
469#else
470#define TraceConfigureEvent(event)	/* nothing */
471#endif
472
473/*
474 * On entry, we have peeked at the event queue and see a configure-notify
475 * event.  Remove that from the queue so we can look further.
476 *
477 * Then, as long as there is a configure-notify event in the queue, remove
478 * that.  If the adjacent events are for different windows, process the older
479 * event and update the event used for comparing windows.  If they are for the
480 * same window, only the newer event is of interest.
481 *
482 * Finally, process the (remaining) configure-notify event.
483 */
484static XtInputMask
485mergeConfigureEvents(XEvent * target)
486{
487    XEvent next_event;
488    XConfigureEvent *p, *q;
489
490    XtAppNextEvent(app_con, target);
491    p = (XConfigureEvent *) target;
492
493    TRACE(("pending Configure...?%s\n", XtAppPending(app_con) ? "yes" : "no"));
494    TraceConfigureEvent(target);
495
496    if (XtAppPending(app_con)
497	&& XtAppPeekEvent(app_con, &next_event)
498	&& next_event.type == ConfigureNotify) {
499	Boolean merge_this = False;
500
501	TraceConfigureEvent(&next_event);
502	XtAppNextEvent(app_con, &next_event);
503	q = (XConfigureEvent *) (&next_event);
504
505	if (p->window == q->window) {
506	    TRACE(("pending Configure...merged\n"));
507	    merge_this = True;
508	}
509	if (!merge_this) {
510	    TRACE(("pending Configure...skipped\n"));
511	    XtDispatchEvent(target);
512	}
513	*target = next_event;
514    }
515    XtDispatchEvent(target);
516    return XtAppPending(app_con);
517}
518
519/*
520 * Filter redundant Expose- and ConfigureNotify-events.  This is limited to
521 * adjacent events because there could be other event-loop processing.  Absent
522 * that limitation, it might be possible to scan ahead to find when the screen
523 * would be completely updated, skipping unnecessary re-repainting before that
524 * point.
525 *
526 * Note: all cases should allow doing XtAppNextEvent if result is true.
527 */
528XtInputMask
529xtermAppPending(void)
530{
531    XtInputMask result = XtAppPending(app_con);
532    XEvent this_event;
533
534    while (result && XtAppPeekEvent(app_con, &this_event)) {
535	if (this_event.type == Expose) {
536	    result = mergeExposeEvents(&this_event);
537	} else if (this_event.type == ConfigureNotify) {
538	    result = mergeConfigureEvents(&this_event);
539	} else {
540	    TRACE(("pending %s\n", visibleEventType(this_event.type)));
541	    break;
542	}
543    }
544    return result;
545}
546
547void
548xevents(void)
549{
550    XtermWidget xw = term;
551    TScreen *screen = TScreenOf(xw);
552    XEvent event;
553    XtInputMask input_mask;
554
555    if (need_cleanup)
556	NormalExit();
557
558    if (screen->scroll_amt)
559	FlushScroll(xw);
560    /*
561     * process timeouts, relying on the fact that XtAppProcessEvent
562     * will process the timeout and return without blockng on the
563     * XEvent queue.  Other sources i.e., the pty are handled elsewhere
564     * with select().
565     */
566    while ((input_mask = xtermAppPending()) != 0) {
567	if (input_mask & XtIMTimer)
568	    XtAppProcessEvent(app_con, (XtInputMask) XtIMTimer);
569#if OPT_SESSION_MGT
570	/*
571	 * Session management events are alternative input events. Deal with
572	 * them in the same way.
573	 */
574	else if (input_mask & XtIMAlternateInput)
575	    XtAppProcessEvent(app_con, (XtInputMask) XtIMAlternateInput);
576#endif
577	else
578	    break;
579    }
580
581    /*
582     * If there's no XEvents, don't wait around...
583     */
584    if ((input_mask & XtIMXEvent) != XtIMXEvent)
585	return;
586    do {
587	/*
588	 * This check makes xterm hang when in mouse hilite tracking mode.
589	 * We simply ignore all events except for those not passed down to
590	 * this function, e.g., those handled in in_put().
591	 */
592	if (screen->waitingForTrackInfo) {
593	    Sleep(10);
594	    return;
595	}
596	XtAppNextEvent(app_con, &event);
597	/*
598	 * Hack to get around problems with the toolkit throwing away
599	 * eventing during the exclusive grab of the menu popup.  By
600	 * looking at the event ourselves we make sure that we can
601	 * do the right thing.
602	 */
603	if (OUR_EVENT(event, EnterNotify)) {
604	    DoSpecialEnterNotify(xw, &event.xcrossing);
605	} else if (OUR_EVENT(event, LeaveNotify)) {
606	    DoSpecialLeaveNotify(xw, &event.xcrossing);
607	} else if ((screen->send_mouse_pos == ANY_EVENT_MOUSE
608#if OPT_DEC_LOCATOR
609		    || screen->send_mouse_pos == DEC_LOCATOR
610#endif /* OPT_DEC_LOCATOR */
611		   )
612		   && event.xany.type == MotionNotify
613		   && event.xcrossing.window == XtWindow(xw)) {
614	    SendMousePosition(xw, &event);
615	    xtermShowPointer(xw, True);
616	    continue;
617	}
618
619	/*
620	 * If the event is interesting (and not a keyboard event), turn the
621	 * mouse pointer back on.
622	 */
623	if (screen->hide_pointer) {
624	    if (screen->pointer_mode >= pFocused) {
625		switch (event.xany.type) {
626		case MotionNotify:
627		    xtermShowPointer(xw, True);
628		    break;
629		}
630	    } else {
631		switch (event.xany.type) {
632		case KeyPress:
633		case KeyRelease:
634		case ButtonPress:
635		case ButtonRelease:
636		    /* also these... */
637		case Expose:
638		case NoExpose:
639		case PropertyNotify:
640		case ClientMessage:
641		    break;
642		default:
643		    xtermShowPointer(xw, True);
644		    break;
645		}
646	    }
647	}
648
649	if (!event.xany.send_event ||
650	    screen->allowSendEvents ||
651	    ((event.xany.type != KeyPress) &&
652	     (event.xany.type != KeyRelease) &&
653	     (event.xany.type != ButtonPress) &&
654	     (event.xany.type != ButtonRelease))) {
655
656	    XtDispatchEvent(&event);
657	}
658    } while (xtermAppPending() & XtIMXEvent);
659}
660
661static Cursor
662make_hidden_cursor(XtermWidget xw)
663{
664    TScreen *screen = TScreenOf(xw);
665    Cursor c;
666    Display *dpy = screen->display;
667    XFontStruct *fn;
668
669    static XColor dummy;
670
671    /*
672     * Prefer nil2 (which is normally available) to "fixed" (which is supposed
673     * to be "always" available), since it's a smaller glyph in case the
674     * server insists on drawing _something_.
675     */
676    TRACE(("Ask for nil2 font\n"));
677    if ((fn = XLoadQueryFont(dpy, "nil2")) == 0) {
678	TRACE(("...Ask for fixed font\n"));
679	fn = XLoadQueryFont(dpy, DEFFONT);
680    }
681
682    if (fn != 0) {
683	/* a space character seems to work as a cursor (dots are not needed) */
684	c = XCreateGlyphCursor(dpy, fn->fid, fn->fid, 'X', ' ', &dummy, &dummy);
685	XFreeFont(dpy, fn);
686    } else {
687	c = 0;
688    }
689    TRACE(("XCreateGlyphCursor ->%#lx\n", c));
690    return (c);
691}
692
693Cursor
694make_colored_cursor(unsigned cursorindex,	/* index into font */
695		    unsigned long fg,	/* pixel value */
696		    unsigned long bg)	/* pixel value */
697{
698    TScreen *screen = TScreenOf(term);
699    Cursor c;
700    Display *dpy = screen->display;
701
702    c = XCreateFontCursor(dpy, cursorindex);
703    if (c != None) {
704	recolor_cursor(screen, c, fg, bg);
705    }
706    return (c);
707}
708
709/* ARGSUSED */
710void
711HandleKeyPressed(Widget w GCC_UNUSED,
712		 XEvent * event,
713		 String * params GCC_UNUSED,
714		 Cardinal *nparams GCC_UNUSED)
715{
716    TRACE(("Handle insert-seven-bit for %p\n", (void *) w));
717    Input(term, &event->xkey, False);
718}
719
720/* ARGSUSED */
721void
722HandleEightBitKeyPressed(Widget w GCC_UNUSED,
723			 XEvent * event,
724			 String * params GCC_UNUSED,
725			 Cardinal *nparams GCC_UNUSED)
726{
727    TRACE(("Handle insert-eight-bit for %p\n", (void *) w));
728    Input(term, &event->xkey, True);
729}
730
731/* ARGSUSED */
732void
733HandleStringEvent(Widget w GCC_UNUSED,
734		  XEvent * event GCC_UNUSED,
735		  String * params,
736		  Cardinal *nparams)
737{
738
739    if (*nparams != 1)
740	return;
741
742    if ((*params)[0] == '0' && (*params)[1] == 'x' && (*params)[2] != '\0') {
743	const char *abcdef = "ABCDEF";
744	const char *xxxxxx;
745	Char c;
746	UString p;
747	unsigned value = 0;
748
749	for (p = (UString) (*params + 2); (c = CharOf(x_toupper(*p))) !=
750	     '\0'; p++) {
751	    value *= 16;
752	    if (c >= '0' && c <= '9')
753		value += (unsigned) (c - '0');
754	    else if ((xxxxxx = strchr(abcdef, c)) != 0)
755		value += (unsigned) (xxxxxx - abcdef) + 10;
756	    else
757		break;
758	}
759	if (c == '\0') {
760	    Char hexval[2];
761	    hexval[0] = (Char) value;
762	    hexval[1] = 0;
763	    StringInput(term, hexval, (size_t) 1);
764	}
765    } else {
766	StringInput(term, (const Char *) *params, strlen(*params));
767    }
768}
769
770#if OPT_EXEC_XTERM
771
772#ifndef PROCFS_ROOT
773#define PROCFS_ROOT "/proc"
774#endif
775
776/* ARGSUSED */
777void
778HandleSpawnTerminal(Widget w GCC_UNUSED,
779		    XEvent * event GCC_UNUSED,
780		    String * params,
781		    Cardinal *nparams)
782{
783    TScreen *screen = TScreenOf(term);
784    char *child_cwd = NULL;
785    char *child_exe;
786    pid_t pid;
787
788    /*
789     * Try to find the actual program which is running in the child process.
790     * This works for Linux.  If we cannot find the program, fall back to the
791     * xterm program (which is usually adequate).  Give up if we are given only
792     * a relative path to xterm, since that would not always match $PATH.
793     */
794    child_exe = Readlink(PROCFS_ROOT "/self/exe");
795    if (!child_exe) {
796	if (strncmp(ProgramName, "./", (size_t) 2)
797	    && strncmp(ProgramName, "../", (size_t) 3)) {
798	    child_exe = xtermFindShell(ProgramName, True);
799	} else {
800	    xtermWarning("Cannot exec-xterm given \"%s\"\n", ProgramName);
801	}
802	if (child_exe == 0)
803	    return;
804    }
805
806    /*
807     * Determine the current working directory of the child so that we can
808     * spawn a new terminal in the same directory.
809     *
810     * If we cannot get the CWD of the child, just use our own.
811     */
812    if (screen->pid) {
813	char child_cwd_link[sizeof(PROCFS_ROOT) + 80];
814	sprintf(child_cwd_link, PROCFS_ROOT "/%lu/cwd", (unsigned long) screen->pid);
815	child_cwd = Readlink(child_cwd_link);
816    }
817
818    /* The reaper will take care of cleaning up the child */
819    pid = fork();
820    if (pid == -1) {
821	xtermWarning("Could not fork: %s\n", SysErrorMsg(errno));
822    } else if (!pid) {
823	/* We are the child */
824	if (child_cwd) {
825	    IGNORE_RC(chdir(child_cwd));	/* We don't care if this fails */
826	}
827
828	if (setuid(screen->uid) == -1
829	    || setgid(screen->gid) == -1) {
830	    xtermWarning("Cannot reset uid/gid\n");
831	} else {
832	    unsigned myargc = *nparams + 1;
833	    char **myargv = TypeMallocN(char *, myargc + 1);
834	    unsigned n = 0;
835
836	    myargv[n++] = child_exe;
837
838	    while (n < myargc) {
839		myargv[n++] = (char *) *params++;
840	    }
841
842	    myargv[n] = 0;
843	    execv(child_exe, myargv);
844
845	    /* If we get here, we've failed */
846	    xtermWarning("exec of '%s': %s\n", child_exe, SysErrorMsg(errno));
847	}
848	_exit(0);
849    }
850
851    /* We are the parent; clean up */
852    if (child_cwd)
853	free(child_cwd);
854    free(child_exe);
855}
856#endif /* OPT_EXEC_XTERM */
857
858/*
859 * Rather than sending characters to the host, put them directly into our
860 * input queue.  That lets a user have access to any of the control sequences
861 * for a key binding.  This is the equivalent of local function key support.
862 *
863 * NOTE:  This code does not support the hexadecimal kludge used in
864 * HandleStringEvent because it prevents us from sending an arbitrary string
865 * (but it appears in a lot of examples - so we are stuck with it).  The
866 * standard string converter does recognize "\" for newline ("\n") and for
867 * octal constants (e.g., "\007" for BEL).  So we assume the user can make do
868 * without a specialized converter.  (Don't try to use \000, though).
869 */
870/* ARGSUSED */
871void
872HandleInterpret(Widget w GCC_UNUSED,
873		XEvent * event GCC_UNUSED,
874		String * params,
875		Cardinal *param_count)
876{
877    if (*param_count == 1) {
878	const char *value = params[0];
879	int need = (int) strlen(value);
880	int used = (int) (VTbuffer->next - VTbuffer->buffer);
881	int have = (int) (VTbuffer->last - VTbuffer->buffer);
882
883	if (have - used + need < BUF_SIZE) {
884
885	    fillPtyData(term, VTbuffer, value, (int) strlen(value));
886
887	    TRACE(("Interpret %s\n", value));
888	    VTbuffer->update++;
889	}
890    }
891}
892
893/*ARGSUSED*/
894void
895HandleEnterWindow(Widget w GCC_UNUSED,
896		  XtPointer eventdata GCC_UNUSED,
897		  XEvent * event GCC_UNUSED,
898		  Boolean * cont GCC_UNUSED)
899{
900    /* NOP since we handled it above */
901    TRACE(("HandleEnterWindow ignored\n"));
902    TRACE_FOCUS(w, event);
903}
904
905/*ARGSUSED*/
906void
907HandleLeaveWindow(Widget w GCC_UNUSED,
908		  XtPointer eventdata GCC_UNUSED,
909		  XEvent * event GCC_UNUSED,
910		  Boolean * cont GCC_UNUSED)
911{
912    /* NOP since we handled it above */
913    TRACE(("HandleLeaveWindow ignored\n"));
914    TRACE_FOCUS(w, event);
915}
916
917/*ARGSUSED*/
918void
919HandleFocusChange(Widget w GCC_UNUSED,
920		  XtPointer eventdata GCC_UNUSED,
921		  XEvent * ev,
922		  Boolean * cont GCC_UNUSED)
923{
924    XFocusChangeEvent *event = (XFocusChangeEvent *) ev;
925    XtermWidget xw = term;
926    TScreen *screen = TScreenOf(xw);
927
928    TRACE(("HandleFocusChange type=%s, mode=%s, detail=%s\n",
929	   visibleEventType(event->type),
930	   visibleNotifyMode(event->mode),
931	   visibleNotifyDetail(event->detail)));
932    TRACE_FOCUS(xw, event);
933
934    if (screen->quiet_grab
935	&& (event->mode == NotifyGrab || event->mode == NotifyUngrab)) {
936	/* EMPTY */ ;
937    } else if ((event->type == FocusIn || event->type == FocusOut)
938	       && event->detail == NotifyPointer) {
939	/*
940	 * NotifyPointer is sent to the window where the pointer is, and is
941	 * in addition to events sent to the old/new focus-windows.
942	 */
943	/* EMPTY */ ;
944    } else if (event->type == FocusIn) {
945	setXUrgency(xw, False);
946
947	/*
948	 * NotifyNonlinear only happens (on FocusIn) if the pointer was not in
949	 * one of our windows.  Use this to reset a case where one xterm is
950	 * partly obscuring another, and X gets (us) confused about whether the
951	 * pointer was in the window.  In particular, this can happen if the
952	 * user is resizing the obscuring window, causing some events to not be
953	 * delivered to the obscured window.
954	 */
955	if (event->detail == NotifyNonlinear
956	    && (screen->select & INWINDOW) != 0) {
957	    unselectwindow(xw, INWINDOW);
958	}
959	selectwindow(xw,
960		     ((event->detail == NotifyPointer)
961		      ? INWINDOW
962		      : FOCUS));
963	SendFocusButton(xw, event);
964    } else {
965#if OPT_FOCUS_EVENT
966	if (event->type == FocusOut) {
967	    SendFocusButton(xw, event);
968	}
969#endif
970	/*
971	 * XGrabKeyboard() will generate NotifyGrab event that we want to
972	 * ignore.
973	 */
974	if (event->mode != NotifyGrab) {
975	    unselectwindow(xw,
976			   ((event->detail == NotifyPointer)
977			    ? INWINDOW
978			    : FOCUS));
979	}
980	if (screen->grabbedKbd && (event->mode == NotifyUngrab)) {
981	    Bell(xw, XkbBI_Info, 100);
982	    ReverseVideo(xw);
983	    screen->grabbedKbd = False;
984	    update_securekbd();
985	}
986    }
987}
988
989static long lastBellTime;	/* in milliseconds */
990
991#if defined(HAVE_XKB_BELL_EXT)
992static Atom
993AtomBell(XtermWidget xw, int which)
994{
995#define DATA(name) { XkbBI_##name, XkbBN_##name }
996    static struct {
997	int value;
998	const char *name;
999    } table[] = {
1000	DATA(Info),
1001	    DATA(MarginBell),
1002	    DATA(MinorError),
1003	    DATA(TerminalBell)
1004    };
1005    Cardinal n;
1006    Atom result = None;
1007
1008    for (n = 0; n < XtNumber(table); ++n) {
1009	if (table[n].value == which) {
1010	    result = XInternAtom(XtDisplay(xw), table[n].name, False);
1011	    break;
1012	}
1013    }
1014    return result;
1015}
1016#endif
1017
1018void
1019xtermBell(XtermWidget xw, int which, int percent)
1020{
1021    TScreen *screen = TScreenOf(xw);
1022#if defined(HAVE_XKB_BELL_EXT)
1023    Atom tony = AtomBell(xw, which);
1024#endif
1025
1026    switch (which) {
1027    case XkbBI_Info:
1028    case XkbBI_MinorError:
1029    case XkbBI_MajorError:
1030    case XkbBI_TerminalBell:
1031	switch (screen->warningVolume) {
1032	case bvOff:
1033	    percent = -100;
1034	    break;
1035	case bvLow:
1036	    break;
1037	case bvHigh:
1038	    percent = 100;
1039	    break;
1040	}
1041	break;
1042    case XkbBI_MarginBell:
1043	switch (screen->marginVolume) {
1044	case bvOff:
1045	    percent = -100;
1046	    break;
1047	case bvLow:
1048	    break;
1049	case bvHigh:
1050	    percent = 100;
1051	    break;
1052	}
1053	break;
1054    default:
1055	break;
1056    }
1057
1058#if defined(HAVE_XKB_BELL_EXT)
1059    if (tony != None) {
1060	XkbBell(screen->display, VShellWindow(xw), percent, tony);
1061    } else
1062#endif
1063	XBell(screen->display, percent);
1064}
1065
1066void
1067Bell(XtermWidget xw, int which, int percent)
1068{
1069    TScreen *screen = TScreenOf(xw);
1070    struct timeval curtime;
1071    long now_msecs;
1072
1073    TRACE(("BELL %d %d%%\n", which, percent));
1074    if (!XtIsRealized((Widget) xw)) {
1075	return;
1076    }
1077
1078    setXUrgency(xw, True);
1079
1080    /* has enough time gone by that we are allowed to ring
1081       the bell again? */
1082    if (screen->bellSuppressTime) {
1083	if (screen->bellInProgress) {
1084	    do_xevents();
1085	    if (screen->bellInProgress) {	/* even after new events? */
1086		return;
1087	    }
1088	}
1089	X_GETTIMEOFDAY(&curtime);
1090	now_msecs = 1000 * curtime.tv_sec + curtime.tv_usec / 1000;
1091	if (lastBellTime != 0 && now_msecs - lastBellTime >= 0 &&
1092	    now_msecs - lastBellTime < screen->bellSuppressTime) {
1093	    return;
1094	}
1095	lastBellTime = now_msecs;
1096    }
1097
1098    if (screen->visualbell) {
1099	VisualBell();
1100    } else {
1101	xtermBell(xw, which, percent);
1102    }
1103
1104    if (screen->poponbell)
1105	XRaiseWindow(screen->display, VShellWindow(xw));
1106
1107    if (screen->bellSuppressTime) {
1108	/* now we change a property and wait for the notify event to come
1109	   back.  If the server is suspending operations while the bell
1110	   is being emitted (problematic for audio bell), this lets us
1111	   know when the previous bell has finished */
1112	Widget w = CURRENT_EMU();
1113	XChangeProperty(XtDisplay(w), XtWindow(w),
1114			XA_NOTICE, XA_NOTICE, 8, PropModeAppend, NULL, 0);
1115	screen->bellInProgress = True;
1116    }
1117}
1118
1119#define VB_DELAY screen->visualBellDelay
1120
1121static void
1122flashWindow(TScreen * screen, Window window, GC visualGC, unsigned width, unsigned height)
1123{
1124    int y = 0;
1125    int x = 0;
1126
1127    if (screen->flash_line) {
1128	y = CursorY(screen, screen->cur_row);
1129	height = (unsigned) FontHeight(screen);
1130    }
1131    XFillRectangle(screen->display, window, visualGC, x, y, width, height);
1132    XFlush(screen->display);
1133    Sleep(VB_DELAY);
1134    XFillRectangle(screen->display, window, visualGC, x, y, width, height);
1135}
1136
1137void
1138VisualBell(void)
1139{
1140    TScreen *screen = TScreenOf(term);
1141
1142    if (VB_DELAY > 0) {
1143	Pixel xorPixel = (T_COLOR(screen, TEXT_FG) ^
1144			  T_COLOR(screen, TEXT_BG));
1145	XGCValues gcval;
1146	GC visualGC;
1147
1148	gcval.function = GXxor;
1149	gcval.foreground = xorPixel;
1150	visualGC = XtGetGC((Widget) term, GCFunction + GCForeground, &gcval);
1151#if OPT_TEK4014
1152	if (TEK4014_ACTIVE(term)) {
1153	    TekScreen *tekscr = TekScreenOf(tekWidget);
1154	    flashWindow(screen, TWindow(tekscr), visualGC,
1155			TFullWidth(tekscr),
1156			TFullHeight(tekscr));
1157	} else
1158#endif
1159	{
1160	    flashWindow(screen, VWindow(screen), visualGC,
1161			FullWidth(screen),
1162			FullHeight(screen));
1163	}
1164	XtReleaseGC((Widget) term, visualGC);
1165    }
1166}
1167
1168/* ARGSUSED */
1169void
1170HandleBellPropertyChange(Widget w GCC_UNUSED,
1171			 XtPointer data GCC_UNUSED,
1172			 XEvent * ev,
1173			 Boolean * more GCC_UNUSED)
1174{
1175    TScreen *screen = TScreenOf(term);
1176
1177    if (ev->xproperty.atom == XA_NOTICE) {
1178	screen->bellInProgress = False;
1179    }
1180}
1181
1182void
1183xtermWarning(const char *fmt,...)
1184{
1185    int save_err = errno;
1186    va_list ap;
1187
1188    fprintf(stderr, "%s: ", ProgramName);
1189    va_start(ap, fmt);
1190    vfprintf(stderr, fmt, ap);
1191    (void) fflush(stderr);
1192
1193    va_end(ap);
1194    errno = save_err;
1195}
1196
1197void
1198xtermPerror(const char *fmt,...)
1199{
1200    int save_err = errno;
1201    char *msg = strerror(errno);
1202    va_list ap;
1203
1204    fprintf(stderr, "%s: ", ProgramName);
1205    va_start(ap, fmt);
1206    vfprintf(stderr, fmt, ap);
1207    fprintf(stderr, ": %s\n", msg);
1208    (void) fflush(stderr);
1209
1210    va_end(ap);
1211    errno = save_err;
1212}
1213
1214Window
1215WMFrameWindow(XtermWidget xw)
1216{
1217    Window win_root, win_current, *children;
1218    Window win_parent = 0;
1219    unsigned int nchildren;
1220
1221    win_current = XtWindow(xw);
1222
1223    /* find the parent which is child of root */
1224    do {
1225	if (win_parent)
1226	    win_current = win_parent;
1227	XQueryTree(TScreenOf(xw)->display,
1228		   win_current,
1229		   &win_root,
1230		   &win_parent,
1231		   &children,
1232		   &nchildren);
1233	XFree(children);
1234    } while (win_root != win_parent);
1235
1236    return win_current;
1237}
1238
1239#if OPT_DABBREV
1240/*
1241 * The following code implements `dynamic abbreviation' expansion a la
1242 * Emacs.  It looks in the preceding visible screen and its scrollback
1243 * to find expansions of a typed word.  It compares consecutive
1244 * expansions and ignores one of them if they are identical.
1245 * (Tomasz J. Cholewo, t.cholewo@ieee.org)
1246 */
1247
1248#define IS_WORD_CONSTITUENT(x) ((x) != ' ' && (x) != '\0')
1249#define MAXWLEN 1024		/* maximum word length as in tcsh */
1250
1251static int
1252dabbrev_prev_char(TScreen * screen, CELL * cell, LineData ** ld)
1253{
1254    int result = -1;
1255    int firstLine = -(screen->savedlines);
1256
1257    *ld = getLineData(screen, cell->row);
1258    while (cell->row >= firstLine) {
1259	if (--(cell->col) >= 0) {
1260	    result = (int) (*ld)->charData[cell->col];
1261	    break;
1262	}
1263	if (--(cell->row) < firstLine)
1264	    break;		/* ...there is no previous line */
1265	*ld = getLineData(screen, cell->row);
1266	cell->col = MaxCols(screen);
1267	if (!LineTstWrapped(*ld)) {
1268	    result = ' ';	/* treat lines as separate */
1269	    break;
1270	}
1271    }
1272    return result;
1273}
1274
1275static char *
1276dabbrev_prev_word(TScreen * screen, CELL * cell, LineData ** ld)
1277{
1278    static char ab[MAXWLEN];
1279
1280    char *abword;
1281    int c;
1282    char *ab_end = (ab + MAXWLEN - 1);
1283    char *result = 0;
1284
1285    abword = ab_end;
1286    *abword = '\0';		/* end of string marker */
1287
1288    while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1289	   IS_WORD_CONSTITUENT(c)) {
1290	if (abword > ab)	/* store only |MAXWLEN| last chars */
1291	    *(--abword) = (char) c;
1292    }
1293
1294    if (c >= 0) {
1295	result = abword;
1296    } else if (abword != ab_end) {
1297	result = abword;
1298    }
1299
1300    if (result != 0) {
1301	while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1302	       !IS_WORD_CONSTITUENT(c)) {
1303	    ;			/* skip preceding spaces */
1304	}
1305	(cell->col)++;		/* can be | > screen->max_col| */
1306    }
1307    return result;
1308}
1309
1310static int
1311dabbrev_expand(TScreen * screen)
1312{
1313    int pty = screen->respond;	/* file descriptor of pty */
1314
1315    static CELL cell;
1316    static char *dabbrev_hint = 0, *lastexpansion = 0;
1317    static unsigned int expansions;
1318
1319    char *expansion;
1320    Char *copybuffer;
1321    size_t hint_len;
1322    size_t del_cnt;
1323    size_t buf_cnt;
1324    int result = 0;
1325    LineData *ld;
1326
1327    if (!screen->dabbrev_working) {	/* initialize */
1328	expansions = 0;
1329	cell.col = screen->cur_col;
1330	cell.row = screen->cur_row;
1331
1332	if (dabbrev_hint != 0)
1333	    free(dabbrev_hint);
1334
1335	if ((dabbrev_hint = dabbrev_prev_word(screen, &cell, &ld)) != 0) {
1336
1337	    if (lastexpansion != 0)
1338		free(lastexpansion);
1339
1340	    if ((lastexpansion = strdup(dabbrev_hint)) != 0) {
1341
1342		/* make own copy */
1343		if ((dabbrev_hint = strdup(dabbrev_hint)) != 0) {
1344		    screen->dabbrev_working = True;
1345		    /* we are in the middle of dabbrev process */
1346		}
1347	    } else {
1348		return result;
1349	    }
1350	} else {
1351	    return result;
1352	}
1353	if (!screen->dabbrev_working) {
1354	    if (lastexpansion != 0) {
1355		free(lastexpansion);
1356		lastexpansion = 0;
1357	    }
1358	    return result;
1359	}
1360    }
1361
1362    if (dabbrev_hint == 0)
1363	return result;
1364
1365    hint_len = strlen(dabbrev_hint);
1366    for (;;) {
1367	if ((expansion = dabbrev_prev_word(screen, &cell, &ld)) == 0) {
1368	    if (expansions >= 2) {
1369		expansions = 0;
1370		cell.col = screen->cur_col;
1371		cell.row = screen->cur_row;
1372		continue;
1373	    }
1374	    break;
1375	}
1376	if (!strncmp(dabbrev_hint, expansion, hint_len) &&	/* empty hint matches everything */
1377	    strlen(expansion) > hint_len &&	/* trivial expansion disallowed */
1378	    strcmp(expansion, lastexpansion))	/* different from previous */
1379	    break;
1380    }
1381
1382    if (expansion != 0) {
1383	del_cnt = strlen(lastexpansion) - hint_len;
1384	buf_cnt = del_cnt + strlen(expansion) - hint_len;
1385
1386	if ((copybuffer = TypeMallocN(Char, buf_cnt)) != 0) {
1387	    /* delete previous expansion */
1388	    memset(copybuffer, screen->dabbrev_erase_char, del_cnt);
1389	    memmove(copybuffer + del_cnt,
1390		    expansion + hint_len,
1391		    strlen(expansion) - hint_len);
1392	    v_write(pty, copybuffer, (unsigned) buf_cnt);
1393	    /* v_write() just reset our flag */
1394	    screen->dabbrev_working = True;
1395	    free(copybuffer);
1396
1397	    free(lastexpansion);
1398
1399	    if ((lastexpansion = strdup(expansion)) != 0) {
1400		result = 1;
1401		expansions++;
1402	    }
1403	}
1404    }
1405
1406    return result;
1407}
1408
1409/*ARGSUSED*/
1410void
1411HandleDabbrevExpand(Widget w,
1412		    XEvent * event GCC_UNUSED,
1413		    String * params GCC_UNUSED,
1414		    Cardinal *nparams GCC_UNUSED)
1415{
1416    XtermWidget xw;
1417
1418    TRACE(("Handle dabbrev-expand for %p\n", (void *) w));
1419    if ((xw = getXtermWidget(w)) != 0) {
1420	TScreen *screen = TScreenOf(xw);
1421	if (!dabbrev_expand(screen))
1422	    Bell(xw, XkbBI_TerminalBell, 0);
1423    }
1424}
1425#endif /* OPT_DABBREV */
1426
1427#if OPT_MAXIMIZE
1428/*ARGSUSED*/
1429void
1430HandleDeIconify(Widget w,
1431		XEvent * event GCC_UNUSED,
1432		String * params GCC_UNUSED,
1433		Cardinal *nparams GCC_UNUSED)
1434{
1435    XtermWidget xw;
1436
1437    if ((xw = getXtermWidget(w)) != 0) {
1438	TScreen *screen = TScreenOf(xw);
1439	XMapWindow(screen->display, VShellWindow(xw));
1440    }
1441}
1442
1443/*ARGSUSED*/
1444void
1445HandleIconify(Widget w,
1446	      XEvent * event GCC_UNUSED,
1447	      String * params GCC_UNUSED,
1448	      Cardinal *nparams GCC_UNUSED)
1449{
1450    XtermWidget xw;
1451
1452    if ((xw = getXtermWidget(w)) != 0) {
1453	TScreen *screen = TScreenOf(xw);
1454	XIconifyWindow(screen->display,
1455		       VShellWindow(xw),
1456		       DefaultScreen(screen->display));
1457    }
1458}
1459
1460int
1461QueryMaximize(XtermWidget xw, unsigned *width, unsigned *height)
1462{
1463    TScreen *screen = TScreenOf(xw);
1464    XSizeHints hints;
1465    long supp = 0;
1466    Window root_win;
1467    int root_x = -1;		/* saved co-ordinates */
1468    int root_y = -1;
1469    unsigned root_border;
1470    unsigned root_depth;
1471    int code;
1472
1473    if (XGetGeometry(screen->display,
1474		     RootWindowOfScreen(XtScreen(xw)),
1475		     &root_win,
1476		     &root_x,
1477		     &root_y,
1478		     width,
1479		     height,
1480		     &root_border,
1481		     &root_depth)) {
1482	TRACE(("QueryMaximize: XGetGeometry position %d,%d size %d,%d border %d\n",
1483	       root_x,
1484	       root_y,
1485	       *width,
1486	       *height,
1487	       root_border));
1488
1489	*width -= (root_border * 2);
1490	*height -= (root_border * 2);
1491
1492	hints.flags = PMaxSize;
1493	if (XGetWMNormalHints(screen->display,
1494			      VShellWindow(xw),
1495			      &hints,
1496			      &supp)
1497	    && (hints.flags & PMaxSize) != 0) {
1498
1499	    TRACE(("QueryMaximize: WM hints max_w %#x max_h %#x\n",
1500		   hints.max_width,
1501		   hints.max_height));
1502
1503	    if ((unsigned) hints.max_width < *width)
1504		*width = (unsigned) hints.max_width;
1505	    if ((unsigned) hints.max_height < *height)
1506		*height = (unsigned) hints.max_height;
1507	}
1508	code = 1;
1509    } else {
1510	*width = 0;
1511	*height = 0;
1512	code = 0;
1513    }
1514    return code;
1515}
1516
1517void
1518RequestMaximize(XtermWidget xw, int maximize)
1519{
1520    TScreen *screen = TScreenOf(xw);
1521    XWindowAttributes wm_attrs, vshell_attrs;
1522    unsigned root_width, root_height;
1523    Boolean success = False;
1524
1525    TRACE(("RequestMaximize %d:%s\n",
1526	   maximize,
1527	   (maximize
1528	    ? "maximize"
1529	    : "restore")));
1530
1531    /*
1532     * Before any maximize, ensure that we can capture the current screensize
1533     * as well as the estimated root-window size.
1534     */
1535    if (maximize
1536	&& QueryMaximize(xw, &root_width, &root_height)
1537	&& xtermGetWinAttrs(screen->display,
1538			    WMFrameWindow(xw),
1539			    &wm_attrs)
1540	&& xtermGetWinAttrs(screen->display,
1541			    VShellWindow(xw),
1542			    &vshell_attrs)) {
1543
1544	if (screen->restore_data != True
1545	    || screen->restore_width != root_width
1546	    || screen->restore_height != root_height) {
1547	    screen->restore_data = True;
1548	    screen->restore_x = wm_attrs.x + wm_attrs.border_width;
1549	    screen->restore_y = wm_attrs.y + wm_attrs.border_width;
1550	    screen->restore_width = (unsigned) vshell_attrs.width;
1551	    screen->restore_height = (unsigned) vshell_attrs.height;
1552	    TRACE(("RequestMaximize: save window position %d,%d size %d,%d\n",
1553		   screen->restore_x,
1554		   screen->restore_y,
1555		   screen->restore_width,
1556		   screen->restore_height));
1557	}
1558
1559	/* subtract wm decoration dimensions */
1560	root_width -= (unsigned) ((wm_attrs.width - vshell_attrs.width)
1561				  + (wm_attrs.border_width * 2));
1562	root_height -= (unsigned) ((wm_attrs.height - vshell_attrs.height)
1563				   + (wm_attrs.border_width * 2));
1564	success = True;
1565    } else if (screen->restore_data) {
1566	success = True;
1567	maximize = 0;
1568    }
1569
1570    if (success) {
1571	switch (maximize) {
1572	case 3:
1573	    FullScreen(xw, 3);	/* depends on EWMH */
1574	    break;
1575	case 2:
1576	    FullScreen(xw, 2);	/* depends on EWMH */
1577	    break;
1578	case 1:
1579	    FullScreen(xw, 0);	/* overrides any EWMH hint */
1580	    XMoveResizeWindow(screen->display, VShellWindow(xw),
1581			      0 + wm_attrs.border_width,	/* x */
1582			      0 + wm_attrs.border_width,	/* y */
1583			      root_width,
1584			      root_height);
1585	    break;
1586
1587	default:
1588	    FullScreen(xw, 0);	/* reset any EWMH hint */
1589	    if (screen->restore_data) {
1590		screen->restore_data = False;
1591
1592		TRACE(("HandleRestoreSize: position %d,%d size %d,%d\n",
1593		       screen->restore_x,
1594		       screen->restore_y,
1595		       screen->restore_width,
1596		       screen->restore_height));
1597
1598		XMoveResizeWindow(screen->display,
1599				  VShellWindow(xw),
1600				  screen->restore_x,
1601				  screen->restore_y,
1602				  screen->restore_width,
1603				  screen->restore_height);
1604	    }
1605	    break;
1606	}
1607    }
1608}
1609
1610/*ARGSUSED*/
1611void
1612HandleMaximize(Widget w,
1613	       XEvent * event GCC_UNUSED,
1614	       String * params GCC_UNUSED,
1615	       Cardinal *nparams GCC_UNUSED)
1616{
1617    XtermWidget xw;
1618
1619    if ((xw = getXtermWidget(w)) != 0) {
1620	RequestMaximize(xw, 1);
1621    }
1622}
1623
1624/*ARGSUSED*/
1625void
1626HandleRestoreSize(Widget w,
1627		  XEvent * event GCC_UNUSED,
1628		  String * params GCC_UNUSED,
1629		  Cardinal *nparams GCC_UNUSED)
1630{
1631    XtermWidget xw;
1632
1633    if ((xw = getXtermWidget(w)) != 0) {
1634	RequestMaximize(xw, 0);
1635    }
1636}
1637#endif /* OPT_MAXIMIZE */
1638
1639void
1640Redraw(void)
1641{
1642    TScreen *screen = TScreenOf(term);
1643    XExposeEvent event;
1644
1645    TRACE(("Redraw\n"));
1646
1647    event.type = Expose;
1648    event.display = screen->display;
1649    event.x = 0;
1650    event.y = 0;
1651    event.count = 0;
1652
1653    if (VWindow(screen)) {
1654	event.window = VWindow(screen);
1655	event.width = term->core.width;
1656	event.height = term->core.height;
1657	(*term->core.widget_class->core_class.expose) ((Widget) term,
1658						       (XEvent *) & event,
1659						       NULL);
1660	if (ScrollbarWidth(screen)) {
1661	    (screen->scrollWidget->core.widget_class->core_class.expose)
1662		(screen->scrollWidget, (XEvent *) & event, NULL);
1663	}
1664    }
1665#if OPT_TEK4014
1666    if (TEK4014_SHOWN(term)) {
1667	TekScreen *tekscr = TekScreenOf(tekWidget);
1668	event.window = TWindow(tekscr);
1669	event.width = tekWidget->core.width;
1670	event.height = tekWidget->core.height;
1671	TekExpose((Widget) tekWidget, (XEvent *) & event, NULL);
1672    }
1673#endif
1674}
1675
1676#ifdef VMS
1677#define TIMESTAMP_FMT "%s%d-%02d-%02d-%02d-%02d-%02d"
1678#else
1679#define TIMESTAMP_FMT "%s%d-%02d-%02d.%02d:%02d:%02d"
1680#endif
1681
1682void
1683timestamp_filename(char *dst, const char *src)
1684{
1685    time_t tstamp;
1686    struct tm *tstruct;
1687
1688    tstamp = time((time_t *) 0);
1689    tstruct = localtime(&tstamp);
1690    sprintf(dst, TIMESTAMP_FMT,
1691	    src,
1692	    (int) tstruct->tm_year + 1900,
1693	    tstruct->tm_mon + 1,
1694	    tstruct->tm_mday,
1695	    tstruct->tm_hour,
1696	    tstruct->tm_min,
1697	    tstruct->tm_sec);
1698}
1699
1700int
1701open_userfile(uid_t uid, gid_t gid, char *path, Bool append)
1702{
1703    int fd;
1704    struct stat sb;
1705
1706#ifdef VMS
1707    if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
1708	int the_error = errno;
1709	xtermWarning("cannot open %s: %d:%s\n",
1710		     path,
1711		     the_error,
1712		     SysErrorMsg(the_error));
1713	return -1;
1714    }
1715    chown(path, uid, gid);
1716#else
1717    if ((access(path, F_OK) != 0 && (errno != ENOENT))
1718	|| (creat_as(uid, gid, append, path, 0644) <= 0)
1719	|| ((fd = open(path, O_WRONLY | O_APPEND)) < 0)) {
1720	int the_error = errno;
1721	xtermWarning("cannot open %s: %d:%s\n",
1722		     path,
1723		     the_error,
1724		     SysErrorMsg(the_error));
1725	return -1;
1726    }
1727#endif
1728
1729    /*
1730     * Doublecheck that the user really owns the file that we've opened before
1731     * we do any damage, and that it is not world-writable.
1732     */
1733    if (fstat(fd, &sb) < 0
1734	|| sb.st_uid != uid
1735	|| (sb.st_mode & 022) != 0) {
1736	xtermWarning("you do not own %s\n", path);
1737	close(fd);
1738	return -1;
1739    }
1740    return fd;
1741}
1742
1743#ifndef VMS
1744/*
1745 * Create a file only if we could with the permissions of the real user id.
1746 * We could emulate this with careful use of access() and following
1747 * symbolic links, but that is messy and has race conditions.
1748 * Forking is messy, too, but we can't count on setreuid() or saved set-uids
1749 * being available.
1750 *
1751 * Note: When called for user logging, we have ensured that the real and
1752 * effective user ids are the same, so this remains as a convenience function
1753 * for the debug logs.
1754 *
1755 * Returns
1756 *	 1 if we can proceed to open the file in relative safety,
1757 *	-1 on error, e.g., cannot fork
1758 *	 0 otherwise.
1759 */
1760int
1761creat_as(uid_t uid, gid_t gid, Bool append, char *pathname, unsigned mode)
1762{
1763    int fd;
1764    pid_t pid;
1765    int retval = 0;
1766    int childstat = 0;
1767#ifndef HAVE_WAITPID
1768    int waited;
1769    void (*chldfunc) (int);
1770
1771    chldfunc = signal(SIGCHLD, SIG_DFL);
1772#endif /* HAVE_WAITPID */
1773
1774    TRACE(("creat_as(uid=%d/%d, gid=%d/%d, append=%d, pathname=%s, mode=%#o)\n",
1775	   (int) uid, (int) geteuid(),
1776	   (int) gid, (int) getegid(),
1777	   append,
1778	   pathname,
1779	   mode));
1780
1781    if (uid == geteuid() && gid == getegid()) {
1782	fd = open(pathname,
1783		  O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1784		  mode);
1785	if (fd >= 0)
1786	    close(fd);
1787	return (fd >= 0);
1788    }
1789
1790    pid = fork();
1791    switch (pid) {
1792    case 0:			/* child */
1793	if (setgid(gid) == -1
1794	    || setuid(uid) == -1) {
1795	    /* we cannot report an error here via stderr, just quit */
1796	    retval = 1;
1797	} else {
1798	    fd = open(pathname,
1799		      O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1800		      mode);
1801	    if (fd >= 0) {
1802		close(fd);
1803		retval = 0;
1804	    } else {
1805		retval = 1;
1806	    }
1807	}
1808	_exit(retval);
1809	/* NOTREACHED */
1810    case -1:			/* error */
1811	return retval;
1812    default:			/* parent */
1813#ifdef HAVE_WAITPID
1814	while (waitpid(pid, &childstat, 0) < 0) {
1815#ifdef EINTR
1816	    if (errno == EINTR)
1817		continue;
1818#endif /* EINTR */
1819#ifdef ERESTARTSYS
1820	    if (errno == ERESTARTSYS)
1821		continue;
1822#endif /* ERESTARTSYS */
1823	    break;
1824	}
1825#else /* HAVE_WAITPID */
1826	waited = wait(&childstat);
1827	signal(SIGCHLD, chldfunc);
1828	/*
1829	   Since we had the signal handler uninstalled for a while,
1830	   we might have missed the termination of our screen child.
1831	   If we can check for this possibility without hanging, do so.
1832	 */
1833	do
1834	    if (waited == TScreenOf(term)->pid)
1835		NormalExit();
1836	while ((waited = nonblocking_wait()) > 0) ;
1837#endif /* HAVE_WAITPID */
1838#ifndef WIFEXITED
1839#define WIFEXITED(status) ((status & 0xff) != 0)
1840#endif
1841	if (WIFEXITED(childstat))
1842	    retval = 1;
1843	return retval;
1844    }
1845}
1846#endif /* !VMS */
1847
1848int
1849xtermResetIds(TScreen * screen)
1850{
1851    int result = 0;
1852    if (setgid(screen->gid) == -1) {
1853	xtermWarning("unable to reset group-id\n");
1854	result = -1;
1855    }
1856    if (setuid(screen->uid) == -1) {
1857	xtermWarning("unable to reset user-id\n");
1858	result = -1;
1859    }
1860    return result;
1861}
1862
1863#ifdef ALLOWLOGGING
1864
1865/*
1866 * Logging is a security hole, since it allows a setuid program to write
1867 * arbitrary data to an arbitrary file.  So it is disabled by default.
1868 */
1869
1870#ifdef ALLOWLOGFILEEXEC
1871static void
1872logpipe(int sig GCC_UNUSED)
1873{
1874    XtermWidget xw = term;
1875    TScreen *screen = TScreenOf(xw);
1876
1877    DEBUG_MSG("handle:logpipe\n");
1878#ifdef SYSV
1879    (void) signal(SIGPIPE, SIG_IGN);
1880#endif /* SYSV */
1881    if (screen->logging)
1882	CloseLog(xw);
1883}
1884#endif /* ALLOWLOGFILEEXEC */
1885
1886void
1887StartLog(XtermWidget xw)
1888{
1889    static char *log_default;
1890    TScreen *screen = TScreenOf(xw);
1891
1892    if (screen->logging || (screen->inhibit & I_LOG))
1893	return;
1894#ifdef VMS			/* file name is fixed in VMS variant */
1895    screen->logfd = open(XTERM_VMS_LOGFILE,
1896			 O_CREAT | O_TRUNC | O_APPEND | O_RDWR,
1897			 0640);
1898    if (screen->logfd < 0)
1899	return;			/* open failed */
1900#else /*VMS */
1901    if (screen->logfile == NULL || *screen->logfile == 0) {
1902	if (screen->logfile)
1903	    free(screen->logfile);
1904	if (log_default == NULL) {
1905#if defined(HAVE_GETHOSTNAME) && defined(HAVE_STRFTIME)
1906	    char log_def_name[512];	/* see sprintf below */
1907	    char hostname[255 + 1];	/* Internet standard limit (RFC 1035):
1908					   ``To simplify implementations, the
1909					   total length of a domain name (i.e.,
1910					   label octets and label length
1911					   octets) is restricted to 255 octets
1912					   or less.'' */
1913	    char yyyy_mm_dd_hh_mm_ss[4 + 5 * (1 + 2) + 1];
1914	    time_t now;
1915	    struct tm *ltm;
1916
1917	    now = time((time_t *) 0);
1918	    ltm = (struct tm *) localtime(&now);
1919	    if ((gethostname(hostname, sizeof(hostname)) == 0) &&
1920		(strftime(yyyy_mm_dd_hh_mm_ss,
1921			  sizeof(yyyy_mm_dd_hh_mm_ss),
1922			  "%Y.%m.%d.%H.%M.%S", ltm) > 0)) {
1923		(void) sprintf(log_def_name, "Xterm.log.%.255s.%.20s.%d",
1924			       hostname, yyyy_mm_dd_hh_mm_ss, (int) getpid());
1925	    }
1926	    if ((log_default = x_strdup(log_def_name)) == NULL)
1927		return;
1928#else
1929	    const char *log_def_name = "XtermLog.XXXXXX";
1930	    if ((log_default = x_strdup(log_def_name)) == NULL)
1931		return;
1932
1933	    mktemp(log_default);
1934#endif
1935	}
1936	if ((screen->logfile = x_strdup(log_default)) == 0)
1937	    return;
1938    }
1939    if (*screen->logfile == '|') {	/* exec command */
1940#ifdef ALLOWLOGFILEEXEC
1941	/*
1942	 * Warning, enabling this "feature" allows arbitrary programs
1943	 * to be run.  If ALLOWLOGFILECHANGES is enabled, this can be
1944	 * done through escape sequences....  You have been warned.
1945	 */
1946	int pid;
1947	int p[2];
1948	static char *shell;
1949	struct passwd pw;
1950
1951	if ((shell = x_getenv("SHELL")) == NULL) {
1952
1953	    if (x_getpwuid(screen->uid, &pw)) {
1954		char *name = x_getlogin(screen->uid, &pw);
1955		if (*(pw.pw_shell)) {
1956		    shell = pw.pw_shell;
1957		}
1958		free(name);
1959	    }
1960	}
1961
1962	if (shell == 0) {
1963	    static char dummy[] = "/bin/sh";
1964	    shell = dummy;
1965	}
1966
1967	if (access(shell, X_OK) != 0) {
1968	    xtermPerror("Can't execute `%s'\n", shell);
1969	    return;
1970	}
1971
1972	if (pipe(p) < 0) {
1973	    xtermPerror("Can't make a pipe connection\n");
1974	    return;
1975	} else if ((pid = fork()) < 0) {
1976	    xtermPerror("Can't fork...\n");
1977	    return;
1978	}
1979	if (pid == 0) {		/* child */
1980	    /*
1981	     * Close our output (we won't be talking back to the
1982	     * parent), and redirect our child's output to the
1983	     * original stderr.
1984	     */
1985	    close(p[1]);
1986	    dup2(p[0], 0);
1987	    close(p[0]);
1988	    dup2(fileno(stderr), 1);
1989	    dup2(fileno(stderr), 2);
1990
1991	    close(fileno(stderr));
1992	    close(ConnectionNumber(screen->display));
1993	    close(screen->respond);
1994
1995	    signal(SIGHUP, SIG_DFL);
1996	    signal(SIGCHLD, SIG_DFL);
1997
1998	    /* (this is redundant) */
1999	    if (xtermResetIds(screen) < 0)
2000		exit(ERROR_SETUID);
2001
2002	    if (access(shell, X_OK) == 0) {
2003		execl(shell, shell, "-c", &screen->logfile[1], (void *) 0);
2004		xtermWarning("Can't exec `%s'\n", &screen->logfile[1]);
2005	    } else {
2006		xtermWarning("Can't execute `%s'\n", shell);
2007	    }
2008	    exit(ERROR_LOGEXEC);
2009	}
2010	close(p[0]);
2011	screen->logfd = p[1];
2012	signal(SIGPIPE, logpipe);
2013#else
2014	Bell(xw, XkbBI_Info, 0);
2015	Bell(xw, XkbBI_Info, 0);
2016	return;
2017#endif
2018    } else {
2019	if ((screen->logfd = open_userfile(screen->uid,
2020					   screen->gid,
2021					   screen->logfile,
2022					   (log_default != 0))) < 0)
2023	    return;
2024    }
2025#endif /*VMS */
2026    screen->logstart = VTbuffer->next;
2027    screen->logging = True;
2028    update_logging();
2029}
2030
2031void
2032CloseLog(XtermWidget xw)
2033{
2034    TScreen *screen = TScreenOf(xw);
2035
2036    if (!screen->logging || (screen->inhibit & I_LOG))
2037	return;
2038    FlushLog(xw);
2039    close(screen->logfd);
2040    screen->logging = False;
2041    update_logging();
2042}
2043
2044void
2045FlushLog(XtermWidget xw)
2046{
2047    TScreen *screen = TScreenOf(xw);
2048
2049    if (screen->logging && !(screen->inhibit & I_LOG)) {
2050	Char *cp;
2051	int i;
2052
2053#ifdef VMS			/* avoid logging output loops which otherwise occur sometimes
2054				   when there is no output and cp/screen->logstart are 1 apart */
2055	if (!tt_new_output)
2056	    return;
2057	tt_new_output = False;
2058#endif /* VMS */
2059	cp = VTbuffer->next;
2060	if (screen->logstart != 0
2061	    && (i = (int) (cp - screen->logstart)) > 0) {
2062	    IGNORE_RC(write(screen->logfd, screen->logstart, (size_t) i));
2063	}
2064	screen->logstart = VTbuffer->next;
2065    }
2066}
2067
2068#endif /* ALLOWLOGGING */
2069
2070/***====================================================================***/
2071
2072#if OPT_ISO_COLORS
2073static void
2074ReportAnsiColorRequest(XtermWidget xw, int colornum, int final)
2075{
2076    if (AllowColorOps(xw, ecGetAnsiColor)) {
2077	XColor color;
2078	Colormap cmap = xw->core.colormap;
2079	char buffer[80];
2080
2081	TRACE(("ReportAnsiColorRequest %d\n", colornum));
2082	color.pixel = GET_COLOR_RES(xw, TScreenOf(xw)->Acolors[colornum]);
2083	XQueryColor(TScreenOf(xw)->display, cmap, &color);
2084	sprintf(buffer, "4;%d;rgb:%04x/%04x/%04x",
2085		colornum,
2086		color.red,
2087		color.green,
2088		color.blue);
2089	unparseputc1(xw, ANSI_OSC);
2090	unparseputs(xw, buffer);
2091	unparseputc1(xw, final);
2092	unparse_end(xw);
2093    }
2094}
2095
2096static void
2097getColormapInfo(Display * display, unsigned *typep, unsigned *sizep)
2098{
2099    int numFound;
2100    XVisualInfo myTemplate, *visInfoPtr;
2101
2102    myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(display,
2103							    XDefaultScreen(display)));
2104    visInfoPtr = XGetVisualInfo(display, (long) VisualIDMask,
2105				&myTemplate, &numFound);
2106    *typep = (numFound >= 1) ? (unsigned) visInfoPtr->class : 0;
2107    *sizep = (numFound >= 1) ? (unsigned) visInfoPtr->colormap_size : 0;
2108
2109    XFree((char *) visInfoPtr);
2110
2111    TRACE(("getColormapInfo type %d (%s), size %d\n",
2112	   *typep, ((*typep & 1) ? "dynamic" : "static"), *sizep));
2113}
2114
2115#define MAX_COLORTABLE 4096
2116
2117/*
2118 * Make only one call to XQueryColors(), since it can be slow.
2119 */
2120static Boolean
2121loadColorTable(XtermWidget xw, unsigned length)
2122{
2123    Colormap cmap = xw->core.colormap;
2124    TScreen *screen = TScreenOf(xw);
2125    unsigned i;
2126    Boolean result = False;
2127
2128    if (screen->cmap_data == 0
2129	&& length != 0
2130	&& length < MAX_COLORTABLE) {
2131	screen->cmap_data = TypeMallocN(XColor, (size_t) length);
2132	if (screen->cmap_data != 0) {
2133	    screen->cmap_size = length;
2134
2135	    for (i = 0; i < screen->cmap_size; i++) {
2136		screen->cmap_data[i].pixel = (unsigned long) i;
2137	    }
2138	    result = (Boolean) (XQueryColors(screen->display,
2139					     cmap,
2140					     screen->cmap_data,
2141					     (int) screen->cmap_size) != 0);
2142	}
2143    }
2144    return result;
2145}
2146
2147/*
2148 * Find closest color for "def" in "cmap".
2149 * Set "def" to the resulting color.
2150 *
2151 * Based on Monish Shah's "find_closest_color()" for Vim 6.0,
2152 * modified with ideas from David Tong's "noflash" library.
2153 * The code from Vim in turn was derived from FindClosestColor() in Tcl/Tk.
2154 *
2155 * These provide some introduction:
2156 *	http://en.wikipedia.org/wiki/YIQ
2157 *		for an introduction to YIQ weights.
2158 *	http://en.wikipedia.org/wiki/Luminance_(video)
2159 *		for a discussion of luma.
2160 *	http://en.wikipedia.org/wiki/YUV
2161 *
2162 * Return False if not able to find or allocate a color.
2163 */
2164static Boolean
2165allocateClosestRGB(XtermWidget xw, Colormap cmap, XColor * def)
2166{
2167    TScreen *screen = TScreenOf(xw);
2168    Boolean result = False;
2169    char *tried;
2170    double diff, thisRGB, bestRGB;
2171    unsigned attempts;
2172    unsigned bestInx;
2173    unsigned cmap_type;
2174    unsigned cmap_size;
2175    unsigned i;
2176
2177    getColormapInfo(screen->display, &cmap_type, &cmap_size);
2178
2179    if ((cmap_type & 1) != 0) {
2180
2181	if (loadColorTable(xw, cmap_size)) {
2182
2183	    tried = TypeCallocN(char, (size_t) cmap_size);
2184	    if (tried != 0) {
2185
2186		/*
2187		 * Try (possibly each entry in the color map) to find the best
2188		 * approximation to the requested color.
2189		 */
2190		for (attempts = 0; attempts < cmap_size; attempts++) {
2191		    Boolean first = True;
2192
2193		    bestRGB = 0.0;
2194		    bestInx = 0;
2195		    for (i = 0; i < cmap_size; i++) {
2196			if (!tried[bestInx]) {
2197			    /*
2198			     * Look for the best match based on luminance.
2199			     * Measure this by the least-squares difference of
2200			     * the weighted R/G/B components from the color map
2201			     * versus the requested color.  Use the Y (luma)
2202			     * component of the YIQ color space model for
2203			     * weights that correspond to the luminance.
2204			     */
2205#define AddColorWeight(weight, color) \
2206			    diff = weight * (int) ((def->color) - screen->cmap_data[i].color); \
2207			    thisRGB = diff * diff
2208
2209			    AddColorWeight(0.30, red);
2210			    AddColorWeight(0.61, green);
2211			    AddColorWeight(0.11, blue);
2212
2213			    if (first || (thisRGB < bestRGB)) {
2214				first = False;
2215				bestInx = i;
2216				bestRGB = thisRGB;
2217			    }
2218			}
2219		    }
2220		    if (XAllocColor(screen->display, cmap,
2221				    &screen->cmap_data[bestInx]) != 0) {
2222			*def = screen->cmap_data[bestInx];
2223			TRACE(("...closest %x/%x/%x\n", def->red,
2224			       def->green, def->blue));
2225			result = True;
2226			break;
2227		    }
2228		    /*
2229		     * It failed - either the color map entry was readonly, or
2230		     * another client has allocated the entry.  Mark the entry
2231		     * so we will ignore it
2232		     */
2233		    tried[bestInx] = True;
2234		}
2235		free(tried);
2236	    }
2237	}
2238    }
2239    return result;
2240}
2241
2242#ifndef ULONG_MAX
2243#define ULONG_MAX (unsigned long)(~(0L))
2244#endif
2245
2246#define CheckColor(result, value) \
2247	    result = 0; \
2248	    if (value.red) \
2249		result |= 1; \
2250	    if (value.green) \
2251		result |= 2; \
2252	    if (value.blue) \
2253		result |= 4
2254
2255#define SelectColor(state, value, result) \
2256	switch (state) { \
2257	default: \
2258	case 1: \
2259	    result = value.red; \
2260	    break; \
2261	case 2: \
2262	    result = value.green; \
2263	    break; \
2264	case 4: \
2265	    result = value.blue; \
2266	    break; \
2267	}
2268
2269/*
2270 * Check if the color map consists of values in exactly one of the red, green
2271 * or blue columns.  If it is not, we do not know how to use it for the exact
2272 * match.
2273 */
2274static int
2275simpleColors(XColor * colortable, unsigned length)
2276{
2277    unsigned n;
2278    int state = -1;
2279    int check;
2280
2281    for (n = 0; n < length; ++n) {
2282	if (state == -1) {
2283	    CheckColor(state, colortable[n]);
2284	    if (state == 0)
2285		state = -1;
2286	}
2287	if (state > 0) {
2288	    CheckColor(check, colortable[n]);
2289	    if (check > 0 && check != state) {
2290		state = 0;
2291		break;
2292	    }
2293	}
2294    }
2295    switch (state) {
2296    case 1:
2297    case 2:
2298    case 4:
2299	break;
2300    default:
2301	state = 0;
2302	break;
2303    }
2304    return state;
2305}
2306
2307static unsigned
2308searchColors(XColor * colortable, unsigned length, unsigned color, int state)
2309{
2310    unsigned result = 0;
2311    unsigned n;
2312    unsigned long best = ULONG_MAX;
2313    unsigned long diff;
2314    unsigned value;
2315
2316    for (n = 0; n < length; ++n) {
2317	SelectColor(state, colortable[n], value);
2318	diff = (color - value);
2319	diff *= diff;
2320	if (diff < best) {
2321#if 0
2322	    TRACE(("...%d:looking for %x, found %x/%x/%x (%lx)\n",
2323		   n, color,
2324		   colortable[n].red,
2325		   colortable[n].green,
2326		   colortable[n].blue,
2327		   diff));
2328#endif
2329	    result = n;
2330	    best = diff;
2331	}
2332    }
2333    SelectColor(state, colortable[result], value);
2334    return value;
2335}
2336
2337/*
2338 * This is a workaround for a longstanding defect in the X libraries.
2339 *
2340 * According to
2341 * http://www.unix.com/man-page/all/3x/XAllocColoA/
2342 *
2343 *     XAllocColor() acts differently on static and dynamic visuals.  On Pseu-
2344 *     doColor, DirectColor, and GrayScale  visuals,  XAllocColor()  fails  if
2345 *     there  are  no  unallocated  colorcells and no allocated read-only cell
2346 *     exactly matches the requested RGB values.  On  StaticColor,  TrueColor,
2347 *     and  StaticGray  visuals,  XAllocColor() returns the closest RGB values
2348 *     available in the colormap.  The colorcell_in_out structure returns  the
2349 *     actual RGB values allocated.
2350 *
2351 * That is, XAllocColor() should suffice unless the color map is full.  In that
2352 * case, allocateClosesRGB() is useful for the dynamic display classes such as
2353 * PseudoColor.  It is not useful for TrueColor, since XQueryColors() does not
2354 * return regular RGB triples (unless a different scheme was used for
2355 * specifying the pixel values); only the blue value is filled in.  However, it
2356 * is filled in with the colors that the server supports.
2357 *
2358 * Also (the reason for this function), XAllocColor() does not really work as
2359 * described.  For some TrueColor configurations it merely returns a close
2360 * approximation, but not the closest.
2361 */
2362static Boolean
2363allocateExactRGB(XtermWidget xw, Colormap cmap, XColor * def)
2364{
2365    XColor save = *def;
2366    TScreen *screen = TScreenOf(xw);
2367    Boolean result = (Boolean) (XAllocColor(screen->display, cmap, def) != 0);
2368
2369    /*
2370     * If this is a statically allocated display, e.g., TrueColor, see if we
2371     * can improve on the result by using the color values actually supported
2372     * by the server.
2373     */
2374    if (result) {
2375	unsigned cmap_type;
2376	unsigned cmap_size;
2377	int state;
2378
2379	getColormapInfo(screen->display, &cmap_type, &cmap_size);
2380
2381	if ((cmap_type & 1) == 0) {
2382	    XColor temp = *def;
2383
2384	    if (loadColorTable(xw, cmap_size)
2385		&& (state = simpleColors(screen->cmap_data, cmap_size)) > 0) {
2386#define SearchColors(which) temp.which = (unsigned short) searchColors(screen->cmap_data, cmap_size, save.which, state)
2387		SearchColors(red);
2388		SearchColors(green);
2389		SearchColors(blue);
2390		if (XAllocColor(screen->display, cmap, &temp) != 0) {
2391#if OPT_TRACE
2392		    if (temp.red != save.red
2393			|| temp.green != save.green
2394			|| temp.blue != save.blue) {
2395			TRACE(("...improved %x/%x/%x ->%x/%x/%x\n",
2396			       save.red, save.green, save.blue,
2397			       temp.red, temp.green, temp.blue));
2398		    } else {
2399			TRACE(("...no improvement for %x/%x/%x\n",
2400			       save.red, save.green, save.blue));
2401		    }
2402#endif
2403		    *def = temp;
2404		}
2405	    }
2406	}
2407    }
2408
2409    return result;
2410}
2411
2412/*
2413 * Allocate a color for the "ANSI" colors.  That actually includes colors up
2414 * to 256.
2415 *
2416 * Returns
2417 *	-1 on error
2418 *	0 on no change
2419 *	1 if a new color was allocated.
2420 */
2421static int
2422AllocateAnsiColor(XtermWidget xw,
2423		  ColorRes * res,
2424		  const char *spec)
2425{
2426    int result;
2427    XColor def;
2428
2429    if (xtermAllocColor(xw, &def, spec)) {
2430	if (
2431#if OPT_COLOR_RES
2432	       res->mode == True &&
2433#endif
2434	       EQL_COLOR_RES(res, def.pixel)) {
2435	    result = 0;
2436	} else {
2437	    result = 1;
2438	    SET_COLOR_RES(res, def.pixel);
2439	    res->red = def.red;
2440	    res->green = def.green;
2441	    res->blue = def.blue;
2442	    TRACE(("AllocateAnsiColor[%d] %s (rgb:%04x/%04x/%04x, pixel 0x%06lx)\n",
2443		   (int) (res - TScreenOf(xw)->Acolors), spec,
2444		   def.red,
2445		   def.green,
2446		   def.blue,
2447		   def.pixel));
2448#if OPT_COLOR_RES
2449	    if (!res->mode)
2450		result = 0;
2451	    res->mode = True;
2452#endif
2453	}
2454    } else {
2455	TRACE(("AllocateAnsiColor %s (failed)\n", spec));
2456	result = -1;
2457    }
2458    return (result);
2459}
2460
2461#if OPT_COLOR_RES
2462Pixel
2463xtermGetColorRes(XtermWidget xw, ColorRes * res)
2464{
2465    Pixel result = 0;
2466
2467    if (res->mode) {
2468	result = res->value;
2469    } else {
2470	TRACE(("xtermGetColorRes for Acolors[%d]\n",
2471	       (int) (res - TScreenOf(xw)->Acolors)));
2472
2473	if (res >= TScreenOf(xw)->Acolors) {
2474	    assert(res - TScreenOf(xw)->Acolors < MAXCOLORS);
2475
2476	    if (AllocateAnsiColor(xw, res, res->resource) < 0) {
2477		res->value = TScreenOf(xw)->Tcolors[TEXT_FG].value;
2478		res->mode = -True;
2479		xtermWarning("Cannot allocate color \"%s\"\n",
2480			     NonNull(res->resource));
2481	    }
2482	    result = res->value;
2483	} else {
2484	    result = 0;
2485	}
2486    }
2487    return result;
2488}
2489#endif
2490
2491static int
2492ChangeOneAnsiColor(XtermWidget xw, int color, const char *name)
2493{
2494    int code;
2495
2496    if (color < 0 || color >= MAXCOLORS) {
2497	code = -1;
2498    } else {
2499	ColorRes *res = &(TScreenOf(xw)->Acolors[color]);
2500
2501	TRACE(("ChangeAnsiColor for Acolors[%d]\n", color));
2502	code = AllocateAnsiColor(xw, res, name);
2503    }
2504    return code;
2505}
2506
2507/*
2508 * Set or query entries in the Acolors[] array by parsing pairs of color/name
2509 * values from the given buffer.
2510 *
2511 * The color can be any legal index into Acolors[], which consists of the
2512 * 16/88/256 "ANSI" colors, followed by special color values for the various
2513 * colorXX resources.  The indices for the special color values are not
2514 * simple to work with, so an alternative is to use the calls which pass in
2515 * 'first' set to the beginning of those indices.
2516 *
2517 * If the name is "?", report to the host the current value for the color.
2518 */
2519static Bool
2520ChangeAnsiColorRequest(XtermWidget xw,
2521		       char *buf,
2522		       int first,
2523		       int final)
2524{
2525    char *name;
2526    int color;
2527    int repaint = False;
2528    int code;
2529    int last = (MAXCOLORS - first);
2530
2531    TRACE(("ChangeAnsiColorRequest string='%s'\n", buf));
2532
2533    while (buf && *buf) {
2534	name = strchr(buf, ';');
2535	if (name == NULL)
2536	    break;
2537	*name = '\0';
2538	name++;
2539	color = atoi(buf);
2540	if (color < 0 || color >= last)
2541	    break;		/* quit on any error */
2542	buf = strchr(name, ';');
2543	if (buf) {
2544	    *buf = '\0';
2545	    buf++;
2546	}
2547	if (!strcmp(name, "?")) {
2548	    ReportAnsiColorRequest(xw, color + first, final);
2549	} else {
2550	    code = ChangeOneAnsiColor(xw, color + first, name);
2551	    if (code < 0) {
2552		/* stop on any error */
2553		break;
2554	    } else if (code > 0) {
2555		repaint = True;
2556	    }
2557	    /* FIXME:  free old color somehow?  We aren't for the other color
2558	     * change style (dynamic colors).
2559	     */
2560	}
2561    }
2562
2563    return (repaint);
2564}
2565
2566static Bool
2567ResetOneAnsiColor(XtermWidget xw, int color, int start)
2568{
2569    Bool repaint = False;
2570    int last = MAXCOLORS - start;
2571
2572    if (color >= 0 && color < last) {
2573	ColorRes *res = &(TScreenOf(xw)->Acolors[color + start]);
2574
2575	if (res->mode) {
2576	    /* a color has been allocated for this slot - test further... */
2577	    if (ChangeOneAnsiColor(xw, color + start, res->resource) > 0) {
2578		repaint = True;
2579	    }
2580	}
2581    }
2582    return repaint;
2583}
2584
2585int
2586ResetAnsiColorRequest(XtermWidget xw, char *buf, int start)
2587{
2588    int repaint = 0;
2589    int color;
2590
2591    TRACE(("ResetAnsiColorRequest(%s)\n", buf));
2592    if (*buf != '\0') {
2593	/* reset specific colors */
2594	while (!IsEmpty(buf)) {
2595	    char *next;
2596
2597	    color = (int) strtol(buf, &next, 10);
2598	    if ((next == buf) || (color < 0))
2599		break;		/* no number at all */
2600	    if (next != 0) {
2601		if (strchr(";", *next) == 0)
2602		    break;	/* unexpected delimiter */
2603		++next;
2604	    }
2605
2606	    if (ResetOneAnsiColor(xw, color, start)) {
2607		++repaint;
2608	    }
2609	    buf = next;
2610	}
2611    } else {
2612	TRACE(("...resetting all %d colors\n", MAXCOLORS));
2613	for (color = 0; color < MAXCOLORS; ++color) {
2614	    if (ResetOneAnsiColor(xw, color, start)) {
2615		++repaint;
2616	    }
2617	}
2618    }
2619    TRACE(("...ResetAnsiColorRequest ->%d\n", repaint));
2620    return repaint;
2621}
2622#else
2623#define allocateClosestRGB(xw, cmap, def) 0
2624#define allocateExactRGB(xw, cmap, def) XAllocColor(TScreenOf(xw)->display, cmap, def)
2625#endif /* OPT_ISO_COLORS */
2626
2627static Boolean
2628xtermAllocColor(XtermWidget xw, XColor * def, const char *spec)
2629{
2630    Boolean result = False;
2631    TScreen *screen = TScreenOf(xw);
2632    Colormap cmap = xw->core.colormap;
2633
2634    if (XParseColor(screen->display, cmap, spec, def)
2635	&& (allocateExactRGB(xw, cmap, def)
2636	    || allocateClosestRGB(xw, cmap, def))) {
2637	TRACE(("xtermAllocColor -> %x/%x/%x\n",
2638	       def->red, def->green, def->blue));
2639	result = True;
2640    }
2641    return result;
2642}
2643
2644/*
2645 * This provides an approximation (the closest color from xterm's palette)
2646 * rather than the "exact" color (whatever the display could provide, actually)
2647 * because of the context in which it is used.
2648 */
2649#define ColorDiff(given,cache) ((long) ((cache) >> 8) - (long) (given))
2650int
2651xtermClosestColor(XtermWidget xw, int find_red, int find_green, int find_blue)
2652{
2653    int result = -1;
2654#if OPT_COLOR_RES && OPT_ISO_COLORS
2655    int n;
2656    int best_index = -1;
2657    unsigned long best_value = 0;
2658    unsigned long this_value;
2659    long diff_red, diff_green, diff_blue;
2660
2661    TRACE(("xtermClosestColor(%x/%x/%x)\n", find_red, find_green, find_blue));
2662
2663    for (n = NUM_ANSI_COLORS - 1; n >= 0; --n) {
2664	ColorRes *res = &(TScreenOf(xw)->Acolors[n]);
2665
2666	/* ensure that we have a value for each of the colors */
2667	if (!res->mode) {
2668	    (void) AllocateAnsiColor(xw, res, res->resource);
2669	}
2670
2671	/* find the closest match */
2672	if (res->mode == True) {
2673	    TRACE2(("...lookup %lx -> %x/%x/%x\n",
2674		    res->value, res->red, res->green, res->blue));
2675	    diff_red = ColorDiff(find_red, res->red);
2676	    diff_green = ColorDiff(find_green, res->green);
2677	    diff_blue = ColorDiff(find_blue, res->blue);
2678	    this_value = (unsigned long) ((diff_red * diff_red)
2679					  + (diff_green * diff_green)
2680					  + (diff_blue * diff_blue));
2681	    if (best_index < 0 || this_value < best_value) {
2682		best_index = n;
2683		best_value = this_value;
2684	    }
2685	}
2686    }
2687    TRACE(("...best match at %d with diff %lx\n", best_index, best_value));
2688    result = best_index;
2689#else
2690    (void) xw;
2691    (void) find_red;
2692    (void) find_green;
2693    (void) find_blue;
2694#endif
2695    return result;
2696}
2697
2698#if OPT_PASTE64
2699static void
2700ManipulateSelectionData(XtermWidget xw, TScreen * screen, char *buf, int final)
2701{
2702#define PDATA(a,b) { a, #b }
2703    static struct {
2704	char given;
2705	String result;
2706    } table[] = {
2707	PDATA('s', SELECT),
2708	    PDATA('p', PRIMARY),
2709	    PDATA('c', CLIPBOARD),
2710	    PDATA('0', CUT_BUFFER0),
2711	    PDATA('1', CUT_BUFFER1),
2712	    PDATA('2', CUT_BUFFER2),
2713	    PDATA('3', CUT_BUFFER3),
2714	    PDATA('4', CUT_BUFFER4),
2715	    PDATA('5', CUT_BUFFER5),
2716	    PDATA('6', CUT_BUFFER6),
2717	    PDATA('7', CUT_BUFFER7),
2718    };
2719
2720    const char *base = buf;
2721    char *used;
2722    Cardinal j, n = 0;
2723    String *select_args;
2724
2725    TRACE(("Manipulate selection data\n"));
2726
2727    while (*buf != ';' && *buf != '\0') {
2728	++buf;
2729    }
2730
2731    if (*buf == ';') {
2732	*buf++ = '\0';
2733
2734	if (*base == '\0')
2735	    base = "s0";
2736
2737	if ((used = x_strdup(base)) != 0) {
2738	    if ((select_args = TypeCallocN(String, 2 + strlen(base))) != 0) {
2739		while (*base != '\0') {
2740		    for (j = 0; j < XtNumber(table); ++j) {
2741			if (*base == table[j].given) {
2742			    used[n] = *base;
2743			    select_args[n++] = table[j].result;
2744			    TRACE(("atom[%d] %s\n", n, table[j].result));
2745			    break;
2746			}
2747		    }
2748		    ++base;
2749		}
2750		used[n] = 0;
2751
2752		if (!strcmp(buf, "?")) {
2753		    if (AllowWindowOps(xw, ewGetSelection)) {
2754			TRACE(("Getting selection\n"));
2755			unparseputc1(xw, ANSI_OSC);
2756			unparseputs(xw, "52");
2757			unparseputc(xw, ';');
2758
2759			unparseputs(xw, used);
2760			unparseputc(xw, ';');
2761
2762			/* Tell xtermGetSelection data is base64 encoded */
2763			screen->base64_paste = n;
2764			screen->base64_final = final;
2765
2766			/* terminator will be written in this call */
2767			xtermGetSelection((Widget) xw,
2768					  (Time) 0,
2769					  select_args, n,
2770					  NULL);
2771		    }
2772		} else {
2773		    if (AllowWindowOps(xw, ewSetSelection)) {
2774			TRACE(("Setting selection with %s\n", buf));
2775			ClearSelectionBuffer(screen);
2776			while (*buf != '\0')
2777			    AppendToSelectionBuffer(screen, CharOf(*buf++));
2778			CompleteSelection(xw, select_args, n);
2779		    }
2780		}
2781		free(select_args);
2782	    }
2783	    free(used);
2784	}
2785    }
2786}
2787#endif /* OPT_PASTE64 */
2788
2789/***====================================================================***/
2790
2791#define IsSetUtf8Title(xw) (IsTitleMode(xw, tmSetUtf8) || (xw->screen.utf8_title))
2792
2793static Bool
2794xtermIsPrintable(XtermWidget xw, Char ** bufp, Char * last)
2795{
2796    TScreen *screen = TScreenOf(xw);
2797    Bool result = False;
2798    Char *cp = *bufp;
2799    Char *next = cp;
2800
2801    (void) screen;
2802    (void) last;
2803
2804#if OPT_WIDE_CHARS
2805    if (xtermEnvUTF8() && IsSetUtf8Title(xw)) {
2806	PtyData data;
2807
2808	if (decodeUtf8(fakePtyData(&data, cp, last))) {
2809	    if (data.utf_data != UCS_REPL
2810		&& (data.utf_data >= 128 ||
2811		    ansi_table[data.utf_data] == CASE_PRINT)) {
2812		next += (data.utf_size - 1);
2813		result = True;
2814	    } else {
2815		result = False;
2816	    }
2817	} else {
2818	    result = False;
2819	}
2820    } else
2821#endif
2822#if OPT_C1_PRINT
2823	if (screen->c1_printable
2824	    && (*cp >= 128 && *cp < 160)) {
2825	result = True;
2826    } else
2827#endif
2828    if (ansi_table[*cp] == CASE_PRINT) {
2829	result = True;
2830    }
2831    *bufp = next;
2832    return result;
2833}
2834
2835/***====================================================================***/
2836
2837/*
2838 * Enum corresponding to the actual OSC codes rather than the internal
2839 * array indices.  Compare with TermColors.
2840 */
2841typedef enum {
2842    OSC_TEXT_FG = 10
2843    ,OSC_TEXT_BG
2844    ,OSC_TEXT_CURSOR
2845    ,OSC_MOUSE_FG
2846    ,OSC_MOUSE_BG
2847#if OPT_TEK4014
2848    ,OSC_TEK_FG = 15
2849    ,OSC_TEK_BG
2850#endif
2851#if OPT_HIGHLIGHT_COLOR
2852    ,OSC_HIGHLIGHT_BG = 17
2853#endif
2854#if OPT_TEK4014
2855    ,OSC_TEK_CURSOR = 18
2856#endif
2857#if OPT_HIGHLIGHT_COLOR
2858    ,OSC_HIGHLIGHT_FG = 19
2859#endif
2860    ,OSC_NCOLORS
2861} OscTextColors;
2862
2863/*
2864 * Map codes to OSC controls that can reset colors.
2865 */
2866#define OSC_RESET 100
2867#define OSC_Reset(code) (code) + OSC_RESET
2868
2869static ScrnColors *pOldColors = NULL;
2870
2871static Bool
2872GetOldColors(XtermWidget xw)
2873{
2874    int i;
2875    if (pOldColors == NULL) {
2876	pOldColors = TypeXtMalloc(ScrnColors);
2877	if (pOldColors == NULL) {
2878	    xtermWarning("allocation failure in GetOldColors\n");
2879	    return (False);
2880	}
2881	pOldColors->which = 0;
2882	for (i = 0; i < NCOLORS; i++) {
2883	    pOldColors->colors[i] = 0;
2884	    pOldColors->names[i] = NULL;
2885	}
2886	GetColors(xw, pOldColors);
2887    }
2888    return (True);
2889}
2890
2891static int
2892oppositeColor(int n)
2893{
2894    switch (n) {
2895    case TEXT_FG:
2896	n = TEXT_BG;
2897	break;
2898    case TEXT_BG:
2899	n = TEXT_FG;
2900	break;
2901    case MOUSE_FG:
2902	n = MOUSE_BG;
2903	break;
2904    case MOUSE_BG:
2905	n = MOUSE_FG;
2906	break;
2907#if OPT_TEK4014
2908    case TEK_FG:
2909	n = TEK_BG;
2910	break;
2911    case TEK_BG:
2912	n = TEK_FG;
2913	break;
2914#endif
2915#if OPT_HIGHLIGHT_COLOR
2916    case HIGHLIGHT_FG:
2917	n = HIGHLIGHT_BG;
2918	break;
2919    case HIGHLIGHT_BG:
2920	n = HIGHLIGHT_FG;
2921	break;
2922#endif
2923    default:
2924	break;
2925    }
2926    return n;
2927}
2928
2929static void
2930ReportColorRequest(XtermWidget xw, int ndx, int final)
2931{
2932    if (AllowColorOps(xw, ecGetColor)) {
2933	XColor color;
2934	Colormap cmap = xw->core.colormap;
2935	char buffer[80];
2936
2937	/*
2938	 * ChangeColorsRequest() has "always" chosen the opposite color when
2939	 * reverse-video is set.  Report this as the original color index, but
2940	 * reporting the opposite color which would be used.
2941	 */
2942	int i = (xw->misc.re_verse) ? oppositeColor(ndx) : ndx;
2943
2944	GetOldColors(xw);
2945	color.pixel = pOldColors->colors[ndx];
2946	XQueryColor(TScreenOf(xw)->display, cmap, &color);
2947	sprintf(buffer, "%d;rgb:%04x/%04x/%04x", i + 10,
2948		color.red,
2949		color.green,
2950		color.blue);
2951	TRACE(("ReportColorRequest #%d: 0x%06lx as %s\n",
2952	       ndx, pOldColors->colors[ndx], buffer));
2953	unparseputc1(xw, ANSI_OSC);
2954	unparseputs(xw, buffer);
2955	unparseputc1(xw, final);
2956	unparse_end(xw);
2957    }
2958}
2959
2960static Bool
2961UpdateOldColors(XtermWidget xw GCC_UNUSED, ScrnColors * pNew)
2962{
2963    int i;
2964
2965    /* if we were going to free old colors, this would be the place to
2966     * do it.   I've decided not to (for now), because it seems likely
2967     * that we'd have a small set of colors we use over and over, and that
2968     * we could save some overhead this way.   The only case in which this
2969     * (clearly) fails is if someone is trying a boatload of colors, in
2970     * which case they can restart xterm
2971     */
2972    for (i = 0; i < NCOLORS; i++) {
2973	if (COLOR_DEFINED(pNew, i)) {
2974	    if (pOldColors->names[i] != NULL) {
2975		XtFree(pOldColors->names[i]);
2976		pOldColors->names[i] = NULL;
2977	    }
2978	    if (pNew->names[i]) {
2979		pOldColors->names[i] = pNew->names[i];
2980	    }
2981	    pOldColors->colors[i] = pNew->colors[i];
2982	}
2983    }
2984    return (True);
2985}
2986
2987/*
2988 * OSC codes are constant, but the indices for the color arrays depend on how
2989 * xterm is compiled.
2990 */
2991static int
2992OscToColorIndex(OscTextColors mode)
2993{
2994    int result = 0;
2995
2996#define CASE(name) case OSC_##name: result = name; break
2997    switch (mode) {
2998	CASE(TEXT_FG);
2999	CASE(TEXT_BG);
3000	CASE(TEXT_CURSOR);
3001	CASE(MOUSE_FG);
3002	CASE(MOUSE_BG);
3003#if OPT_TEK4014
3004	CASE(TEK_FG);
3005	CASE(TEK_BG);
3006#endif
3007#if OPT_HIGHLIGHT_COLOR
3008	CASE(HIGHLIGHT_BG);
3009	CASE(HIGHLIGHT_FG);
3010#endif
3011#if OPT_TEK4014
3012	CASE(TEK_CURSOR);
3013#endif
3014    case OSC_NCOLORS:
3015	break;
3016    }
3017    return result;
3018}
3019
3020static Bool
3021ChangeColorsRequest(XtermWidget xw,
3022		    int start,
3023		    char *names,
3024		    int final)
3025{
3026    Bool result = False;
3027    char *thisName;
3028    ScrnColors newColors;
3029    int i, ndx;
3030
3031    TRACE(("ChangeColorsRequest start=%d, names='%s'\n", start, names));
3032
3033    if (GetOldColors(xw)) {
3034	newColors.which = 0;
3035	for (i = 0; i < NCOLORS; i++) {
3036	    newColors.names[i] = NULL;
3037	}
3038	for (i = start; i < OSC_NCOLORS; i++) {
3039	    ndx = OscToColorIndex((OscTextColors) i);
3040	    if (xw->misc.re_verse)
3041		ndx = oppositeColor(ndx);
3042
3043	    if (IsEmpty(names)) {
3044		newColors.names[ndx] = NULL;
3045	    } else {
3046		if (names[0] == ';')
3047		    thisName = NULL;
3048		else
3049		    thisName = names;
3050		names = strchr(names, ';');
3051		if (names != NULL) {
3052		    *names++ = '\0';
3053		}
3054		if (thisName != 0 && !strcmp(thisName, "?")) {
3055		    ReportColorRequest(xw, ndx, final);
3056		} else if (!pOldColors->names[ndx]
3057			   || (thisName
3058			       && strcmp(thisName, pOldColors->names[ndx]))) {
3059		    AllocateTermColor(xw, &newColors, ndx, thisName, False);
3060		}
3061	    }
3062	}
3063
3064	if (newColors.which != 0) {
3065	    ChangeColors(xw, &newColors);
3066	    UpdateOldColors(xw, &newColors);
3067	}
3068	result = True;
3069    }
3070    return result;
3071}
3072
3073static Bool
3074ResetColorsRequest(XtermWidget xw,
3075		   int code)
3076{
3077    Bool result = False;
3078    const char *thisName;
3079    ScrnColors newColors;
3080    int ndx;
3081
3082    TRACE(("ResetColorsRequest code=%d\n", code));
3083
3084#if OPT_COLOR_RES
3085    if (GetOldColors(xw)) {
3086	ndx = OscToColorIndex((OscTextColors) (code - OSC_RESET));
3087	if (xw->misc.re_verse)
3088	    ndx = oppositeColor(ndx);
3089
3090	thisName = xw->screen.Tcolors[ndx].resource;
3091
3092	newColors.which = 0;
3093	newColors.names[ndx] = NULL;
3094
3095	if (thisName != 0
3096	    && pOldColors->names[ndx] != 0
3097	    && strcmp(thisName, pOldColors->names[ndx])) {
3098	    AllocateTermColor(xw, &newColors, ndx, thisName, False);
3099
3100	    if (newColors.which != 0) {
3101		ChangeColors(xw, &newColors);
3102		UpdateOldColors(xw, &newColors);
3103	    }
3104	}
3105	result = True;
3106    }
3107#endif
3108    return result;
3109}
3110
3111#if OPT_SHIFT_FONTS
3112/*
3113 * Initially, 'source' points to '#' or '?'.
3114 *
3115 * Look for an optional sign and optional number.  If those are found, lookup
3116 * the corresponding menu font entry.
3117 */
3118static int
3119ParseShiftedFont(XtermWidget xw, String source, String * target)
3120{
3121    TScreen *screen = TScreenOf(xw);
3122    int num = screen->menu_font_number;
3123    int rel = 0;
3124
3125    if (*++source == '+') {
3126	rel = 1;
3127	source++;
3128    } else if (*source == '-') {
3129	rel = -1;
3130	source++;
3131    }
3132
3133    if (isdigit(CharOf(*source))) {
3134	int val = atoi(source);
3135	if (rel > 0)
3136	    rel = val;
3137	else if (rel < 0)
3138	    rel = -val;
3139	else
3140	    num = val;
3141    }
3142
3143    if (rel != 0) {
3144	num = lookupRelativeFontSize(xw,
3145				     screen->menu_font_number, rel);
3146
3147    }
3148    TRACE(("ParseShiftedFont(%s) ->%d (%s)\n", *target, num, source));
3149    *target = source;
3150    return num;
3151}
3152
3153static void
3154QueryFontRequest(XtermWidget xw, String buf, int final)
3155{
3156    if (AllowFontOps(xw, efGetFont)) {
3157	TScreen *screen = TScreenOf(xw);
3158	Bool success = True;
3159	int num;
3160	String base = buf + 1;
3161	const char *name = 0;
3162	char temp[10];
3163
3164	num = ParseShiftedFont(xw, buf, &buf);
3165	if (num < 0
3166	    || num > fontMenu_lastBuiltin) {
3167	    Bell(xw, XkbBI_MinorError, 0);
3168	    success = False;
3169	} else {
3170#if OPT_RENDERFONT
3171	    if (UsingRenderFont(xw)) {
3172		name = getFaceName(xw, False);
3173	    } else
3174#endif
3175	    if ((name = screen->MenuFontName(num)) == 0) {
3176		success = False;
3177	    }
3178	}
3179
3180	unparseputc1(xw, ANSI_OSC);
3181	unparseputs(xw, "50");
3182
3183	if (success) {
3184	    unparseputc(xw, ';');
3185	    if (buf >= base) {
3186		/* identify the font-entry, unless it is the current one */
3187		if (*buf != '\0') {
3188		    unparseputc(xw, '#');
3189		    sprintf(temp, "%d", num);
3190		    unparseputs(xw, temp);
3191		    if (*name != '\0')
3192			unparseputc(xw, ' ');
3193		}
3194	    }
3195	    unparseputs(xw, name);
3196	}
3197
3198	unparseputc1(xw, final);
3199	unparse_end(xw);
3200    }
3201}
3202
3203static void
3204ChangeFontRequest(XtermWidget xw, String buf)
3205{
3206    if (AllowFontOps(xw, efSetFont)) {
3207	TScreen *screen = TScreenOf(xw);
3208	Bool success = True;
3209	int num;
3210	VTFontNames fonts;
3211	char *name;
3212
3213	/*
3214	 * If the font specification is a "#", followed by an optional sign and
3215	 * optional number, lookup the corresponding menu font entry.
3216	 *
3217	 * Further, if the "#", etc., is followed by a font name, use that
3218	 * to load the font entry.
3219	 */
3220	if (*buf == '#') {
3221	    num = ParseShiftedFont(xw, buf, &buf);
3222
3223	    if (num < 0
3224		|| num > fontMenu_lastBuiltin) {
3225		Bell(xw, XkbBI_MinorError, 0);
3226		success = False;
3227	    } else {
3228		/*
3229		 * Skip past the optional number, and any whitespace to look
3230		 * for a font specification within the control.
3231		 */
3232		while (isdigit(CharOf(*buf))) {
3233		    ++buf;
3234		}
3235		while (isspace(CharOf(*buf))) {
3236		    ++buf;
3237		}
3238#if OPT_RENDERFONT
3239		if (UsingRenderFont(xw)) {
3240		    /* EMPTY */
3241		    /* there is only one font entry to load */
3242		    ;
3243		} else
3244#endif
3245		{
3246		    /*
3247		     * Normally there is no font specified in the control.
3248		     * But if there is, simply overwrite the font entry.
3249		     */
3250		    if (*buf == '\0') {
3251			if ((buf = screen->MenuFontName(num)) == 0) {
3252			    success = False;
3253			}
3254		    }
3255		}
3256	    }
3257	} else {
3258	    num = screen->menu_font_number;
3259	}
3260	name = x_strtrim(buf);
3261	if (success && !IsEmpty(name)) {
3262#if OPT_RENDERFONT
3263	    if (UsingRenderFont(xw)) {
3264		setFaceName(xw, name);
3265		xtermUpdateFontInfo(xw, True);
3266	    } else
3267#endif
3268	    {
3269		memset(&fonts, 0, sizeof(fonts));
3270		fonts.f_n = name;
3271		SetVTFont(xw, num, True, &fonts);
3272	    }
3273	} else {
3274	    Bell(xw, XkbBI_MinorError, 0);
3275	}
3276	free(name);
3277    }
3278}
3279#endif /* OPT_SHIFT_FONTS */
3280
3281/***====================================================================***/
3282
3283void
3284do_osc(XtermWidget xw, Char * oscbuf, size_t len, int final)
3285{
3286    TScreen *screen = TScreenOf(xw);
3287    int mode;
3288    Char *cp;
3289    int state = 0;
3290    char *buf = 0;
3291    char temp[2];
3292#if OPT_ISO_COLORS
3293    int ansi_colors = 0;
3294#endif
3295    Bool need_data = True;
3296
3297    TRACE(("do_osc %s\n", oscbuf));
3298
3299    (void) screen;
3300
3301    /*
3302     * Lines should be of the form <OSC> number ; string <ST>, however
3303     * older xterms can accept <BEL> as a final character.  We will respond
3304     * with the same final character as the application sends to make this
3305     * work better with shell scripts, which may have trouble reading an
3306     * <ESC><backslash>, which is the 7-bit equivalent to <ST>.
3307     */
3308    mode = 0;
3309    for (cp = oscbuf; *cp != '\0'; cp++) {
3310	switch (state) {
3311	case 0:
3312	    if (isdigit(*cp)) {
3313		mode = 10 * mode + (*cp - '0');
3314		if (mode > 65535) {
3315		    TRACE(("do_osc found unknown mode %d\n", mode));
3316		    return;
3317		}
3318		break;
3319	    }
3320	    /* FALLTHRU */
3321	case 1:
3322	    if (*cp != ';') {
3323		TRACE(("do_osc did not find semicolon offset %d\n",
3324		       (int) (cp - oscbuf)));
3325		return;
3326	    }
3327	    state = 2;
3328	    break;
3329	case 2:
3330	    buf = (char *) cp;
3331	    state = 3;
3332	    /* FALLTHRU */
3333	default:
3334	    if (!xtermIsPrintable(xw, &cp, oscbuf + len)) {
3335		switch (mode) {
3336		case 0:
3337		case 1:
3338		case 2:
3339		    break;
3340		default:
3341		    TRACE(("do_osc found nonprinting char %02X offset %d\n",
3342			   CharOf(*cp),
3343			   (int) (cp - oscbuf)));
3344		    return;
3345		}
3346	    }
3347	}
3348    }
3349
3350    /*
3351     * Check if the palette changed and there are no more immediate changes
3352     * that could be deferred to the next repaint.
3353     */
3354    if (xw->misc.palette_changed) {
3355	switch (mode) {
3356	case 3:		/* change X property */
3357	case 30:		/* Konsole (unused) */
3358	case 31:		/* Konsole (unused) */
3359	case 50:		/* font operations */
3360	case 51:		/* Emacs (unused) */
3361#if OPT_PASTE64
3362	case 52:		/* selection data */
3363#endif
3364	    TRACE(("forced repaint after palette changed\n"));
3365	    xw->misc.palette_changed = False;
3366	    xtermRepaint(xw);
3367	    break;
3368	}
3369    }
3370
3371    /*
3372     * Most OSC controls other than resets require data.  Handle the others as
3373     * a special case.
3374     */
3375    switch (mode) {
3376#if OPT_ISO_COLORS
3377    case OSC_Reset(4):
3378    case OSC_Reset(5):
3379    case OSC_Reset(OSC_TEXT_FG):
3380    case OSC_Reset(OSC_TEXT_BG):
3381    case OSC_Reset(OSC_TEXT_CURSOR):
3382    case OSC_Reset(OSC_MOUSE_FG):
3383    case OSC_Reset(OSC_MOUSE_BG):
3384#if OPT_HIGHLIGHT_COLOR
3385    case OSC_Reset(OSC_HIGHLIGHT_BG):
3386    case OSC_Reset(OSC_HIGHLIGHT_FG):
3387#endif
3388#if OPT_TEK4014
3389    case OSC_Reset(OSC_TEK_FG):
3390    case OSC_Reset(OSC_TEK_BG):
3391    case OSC_Reset(OSC_TEK_CURSOR):
3392#endif
3393	need_data = False;
3394	break;
3395#endif
3396    default:
3397	break;
3398    }
3399
3400    /*
3401     * Check if we have data when we want, and not when we do not want it.
3402     * Either way, that is a malformed control sequence, and will be ignored.
3403     */
3404    if (IsEmpty(buf)) {
3405	if (need_data) {
3406	    TRACE(("do_osc found no data\n"));
3407	    return;
3408	}
3409	temp[0] = '\0';
3410	buf = temp;
3411    } else if (!need_data) {
3412	TRACE(("do_osc found found unwanted data\n"));
3413	return;
3414    }
3415
3416    switch (mode) {
3417    case 0:			/* new icon name and title */
3418	ChangeIconName(xw, buf);
3419	ChangeTitle(xw, buf);
3420	break;
3421
3422    case 1:			/* new icon name only */
3423	ChangeIconName(xw, buf);
3424	break;
3425
3426    case 2:			/* new title only */
3427	ChangeTitle(xw, buf);
3428	break;
3429
3430#ifdef notdef
3431    case 3:			/* change X property */
3432	if (AllowWindowOps(xw, ewSetXprop))
3433	    ChangeXprop(buf);
3434	break;
3435#endif
3436#if OPT_ISO_COLORS
3437    case 5:
3438	ansi_colors = NUM_ANSI_COLORS;
3439	/* FALLTHRU */
3440    case 4:
3441	if (ChangeAnsiColorRequest(xw, buf, ansi_colors, final))
3442	    xw->misc.palette_changed = True;
3443	break;
3444    case OSC_Reset(5):
3445	ansi_colors = NUM_ANSI_COLORS;
3446	/* FALLTHRU */
3447    case OSC_Reset(4):
3448	if (ResetAnsiColorRequest(xw, buf, ansi_colors))
3449	    xw->misc.palette_changed = True;
3450	break;
3451#endif
3452    case OSC_TEXT_FG:
3453    case OSC_TEXT_BG:
3454    case OSC_TEXT_CURSOR:
3455    case OSC_MOUSE_FG:
3456    case OSC_MOUSE_BG:
3457#if OPT_HIGHLIGHT_COLOR
3458    case OSC_HIGHLIGHT_BG:
3459    case OSC_HIGHLIGHT_FG:
3460#endif
3461#if OPT_TEK4014
3462    case OSC_TEK_FG:
3463    case OSC_TEK_BG:
3464    case OSC_TEK_CURSOR:
3465#endif
3466	if (xw->misc.dynamicColors) {
3467	    ChangeColorsRequest(xw, mode, buf, final);
3468	}
3469	break;
3470    case OSC_Reset(OSC_TEXT_FG):
3471    case OSC_Reset(OSC_TEXT_BG):
3472    case OSC_Reset(OSC_TEXT_CURSOR):
3473    case OSC_Reset(OSC_MOUSE_FG):
3474    case OSC_Reset(OSC_MOUSE_BG):
3475#if OPT_HIGHLIGHT_COLOR
3476    case OSC_Reset(OSC_HIGHLIGHT_BG):
3477    case OSC_Reset(OSC_HIGHLIGHT_FG):
3478#endif
3479#if OPT_TEK4014
3480    case OSC_Reset(OSC_TEK_FG):
3481    case OSC_Reset(OSC_TEK_BG):
3482    case OSC_Reset(OSC_TEK_CURSOR):
3483#endif
3484	if (xw->misc.dynamicColors) {
3485	    ResetColorsRequest(xw, mode);
3486	}
3487	break;
3488
3489    case 30:
3490    case 31:
3491	/* reserved for Konsole (Stephan Binner <Stephan.Binner@gmx.de>) */
3492	break;
3493
3494#ifdef ALLOWLOGGING
3495    case 46:			/* new log file */
3496#ifdef ALLOWLOGFILECHANGES
3497	/*
3498	 * Warning, enabling this feature allows people to overwrite
3499	 * arbitrary files accessible to the person running xterm.
3500	 */
3501	if (strcmp(buf, "?")
3502	    && (cp = CastMallocN(char, strlen(buf)) != NULL)) {
3503	    strcpy(cp, buf);
3504	    if (screen->logfile)
3505		free(screen->logfile);
3506	    screen->logfile = cp;
3507	    break;
3508	}
3509#endif
3510	Bell(xw, XkbBI_Info, 0);
3511	Bell(xw, XkbBI_Info, 0);
3512	break;
3513#endif /* ALLOWLOGGING */
3514
3515    case 50:
3516#if OPT_SHIFT_FONTS
3517	if (*buf == '?') {
3518	    QueryFontRequest(xw, buf, final);
3519	} else if (xw->misc.shift_fonts) {
3520	    ChangeFontRequest(xw, buf);
3521	}
3522#endif /* OPT_SHIFT_FONTS */
3523	break;
3524    case 51:
3525	/* reserved for Emacs shell (Rob Mayoff <mayoff@dqd.com>) */
3526	break;
3527
3528#if OPT_PASTE64
3529    case 52:
3530	ManipulateSelectionData(xw, screen, buf, final);
3531	break;
3532#endif
3533	/*
3534	 * One could write code to send back the display and host names,
3535	 * but that could potentially open a fairly nasty security hole.
3536	 */
3537    default:
3538	TRACE(("do_osc - unrecognized code\n"));
3539	break;
3540    }
3541    unparse_end(xw);
3542}
3543
3544#ifdef SunXK_F36
3545#define MAX_UDK 37
3546#else
3547#define MAX_UDK 35
3548#endif
3549static struct {
3550    char *str;
3551    int len;
3552} user_keys[MAX_UDK];
3553
3554/*
3555 * Parse one nibble of a hex byte from the OSC string.  We have removed the
3556 * string-terminator (replacing it with a null), so the only other delimiter
3557 * that is expected is semicolon.  Ignore other characters (Ray Neuman says
3558 * "real" terminals accept commas in the string definitions).
3559 */
3560static int
3561udk_value(const char **cp)
3562{
3563    int result = -1;
3564    int c;
3565
3566    for (;;) {
3567	if ((c = **cp) != '\0')
3568	    *cp = *cp + 1;
3569	if (c == ';' || c == '\0')
3570	    break;
3571	if ((result = x_hex2int(c)) >= 0)
3572	    break;
3573    }
3574
3575    return result;
3576}
3577
3578void
3579reset_decudk(void)
3580{
3581    int n;
3582    for (n = 0; n < MAX_UDK; n++) {
3583	if (user_keys[n].str != 0) {
3584	    free(user_keys[n].str);
3585	    user_keys[n].str = 0;
3586	    user_keys[n].len = 0;
3587	}
3588    }
3589}
3590
3591/*
3592 * Parse the data for DECUDK (user-defined keys).
3593 */
3594static void
3595parse_decudk(const char *cp)
3596{
3597    while (*cp) {
3598	const char *base = cp;
3599	char *str = CastMallocN(char, strlen(cp) + 2);
3600	unsigned key = 0;
3601	int lo, hi;
3602	int len = 0;
3603
3604	while (isdigit(CharOf(*cp)))
3605	    key = (key * 10) + (unsigned) (*cp++ - '0');
3606	if (*cp == '/') {
3607	    cp++;
3608	    while ((hi = udk_value(&cp)) >= 0
3609		   && (lo = udk_value(&cp)) >= 0) {
3610		str[len++] = (char) ((hi << 4) | lo);
3611	    }
3612	}
3613	if (len > 0 && key < MAX_UDK) {
3614	    str[len] = '\0';
3615	    if (user_keys[key].str != 0)
3616		free(user_keys[key].str);
3617	    user_keys[key].str = str;
3618	    user_keys[key].len = len;
3619	} else {
3620	    free(str);
3621	}
3622	if (*cp == ';')
3623	    cp++;
3624	if (cp == base)		/* badly-formed sequence - bail out */
3625	    break;
3626    }
3627}
3628
3629#if OPT_TRACE
3630#define SOFT_WIDE 10
3631#define SOFT_HIGH 20
3632
3633static void
3634parse_decdld(ANSI * params, const char *string)
3635{
3636    char DscsName[8];
3637    int len;
3638    int Pfn = params->a_param[0];
3639    int Pcn = params->a_param[1];
3640    int Pe = params->a_param[2];
3641    int Pcmw = params->a_param[3];
3642    int Pw = params->a_param[4];
3643    int Pt = params->a_param[5];
3644    int Pcmh = params->a_param[6];
3645    int Pcss = params->a_param[7];
3646
3647    int start_char = Pcn + 0x20;
3648    int char_wide = ((Pcmw == 0)
3649		     ? (Pcss ? 6 : 10)
3650		     : (Pcmw > 4
3651			? Pcmw
3652			: (Pcmw + 3)));
3653    int char_high = ((Pcmh == 0)
3654		     ? ((Pcmw >= 2 && Pcmw <= 4)
3655			? 10
3656			: 20)
3657		     : Pcmh);
3658    Char ch;
3659    Char bits[SOFT_HIGH][SOFT_WIDE];
3660    Bool first = True;
3661    Bool prior = False;
3662    int row = 0, col = 0;
3663
3664    TRACE(("Parsing DECDLD\n"));
3665    TRACE(("  font number   %d\n", Pfn));
3666    TRACE(("  starting char %d\n", Pcn));
3667    TRACE(("  erase control %d\n", Pe));
3668    TRACE(("  char-width    %d\n", Pcmw));
3669    TRACE(("  font-width    %d\n", Pw));
3670    TRACE(("  text/full     %d\n", Pt));
3671    TRACE(("  char-height   %d\n", Pcmh));
3672    TRACE(("  charset-size  %d\n", Pcss));
3673
3674    if (Pfn > 1
3675	|| Pcn > 95
3676	|| Pe > 2
3677	|| Pcmw > 10
3678	|| Pcmw == 1
3679	|| Pt > 2
3680	|| Pcmh > 20
3681	|| Pcss > 1
3682	|| char_wide > SOFT_WIDE
3683	|| char_high > SOFT_HIGH) {
3684	TRACE(("DECDLD illegal parameter\n"));
3685	return;
3686    }
3687
3688    len = 0;
3689    while (*string != '\0') {
3690	ch = CharOf(*string++);
3691	if (ch >= ANSI_SPA && ch <= 0x2f) {
3692	    if (len < 2)
3693		DscsName[len++] = (char) ch;
3694	} else if (ch >= 0x30 && ch <= 0x7e) {
3695	    DscsName[len++] = (char) ch;
3696	    break;
3697	}
3698    }
3699    DscsName[len] = 0;
3700    TRACE(("  Dscs name     '%s'\n", DscsName));
3701
3702    TRACE(("  character matrix %dx%d\n", char_high, char_wide));
3703    while (*string != '\0') {
3704	if (first) {
3705	    TRACE(("Char %d:\n", start_char));
3706	    if (prior) {
3707		for (row = 0; row < char_high; ++row) {
3708		    TRACE(("%.*s\n", char_wide, bits[row]));
3709		}
3710	    }
3711	    prior = False;
3712	    first = False;
3713	    for (row = 0; row < char_high; ++row) {
3714		for (col = 0; col < char_wide; ++col) {
3715		    bits[row][col] = '.';
3716		}
3717	    }
3718	    row = col = 0;
3719	}
3720	ch = CharOf(*string++);
3721	if (ch >= 0x3f && ch <= 0x7e) {
3722	    int n;
3723
3724	    ch = CharOf(ch - 0x3f);
3725	    for (n = 0; n < 6; ++n) {
3726		bits[row + n][col] = CharOf((ch & (1 << n)) ? '*' : '.');
3727	    }
3728	    col += 1;
3729	    prior = True;
3730	} else if (ch == '/') {
3731	    row += 6;
3732	    col = 0;
3733	} else if (ch == ';') {
3734	    first = True;
3735	    ++start_char;
3736	}
3737    }
3738}
3739#else
3740#define parse_decdld(p,q)	/* nothing */
3741#endif
3742
3743/*
3744 * Parse numeric parameters.  Normally we use a state machine to simplify
3745 * interspersing with control characters, but have the string already.
3746 */
3747static void
3748parse_ansi_params(ANSI * params, const char **string)
3749{
3750    const char *cp = *string;
3751    ParmType nparam = 0;
3752
3753    memset(params, 0, sizeof(*params));
3754    while (*cp != '\0') {
3755	Char ch = CharOf(*cp++);
3756
3757	if (isdigit(ch)) {
3758	    if (nparam < NPARAM) {
3759		params->a_param[nparam] =
3760		    (ParmType) ((params->a_param[nparam] * 10)
3761				+ (ch - '0'));
3762	    }
3763	} else if (ch == ';') {
3764	    if (++nparam < NPARAM)
3765		params->a_nparam = nparam;
3766	} else if (ch < 32) {
3767	    /* EMPTY */ ;
3768	} else {
3769	    /* should be 0x30 to 0x7e */
3770	    params->a_final = ch;
3771	    break;
3772	}
3773    }
3774    *string = cp;
3775}
3776
3777void
3778do_dcs(XtermWidget xw, Char * dcsbuf, size_t dcslen)
3779{
3780    TScreen *screen = TScreenOf(xw);
3781    char reply[BUFSIZ];
3782    const char *cp = (const char *) dcsbuf;
3783    Bool okay;
3784    ANSI params;
3785
3786    TRACE(("do_dcs(%s:%lu)\n", (char *) dcsbuf, (unsigned long) dcslen));
3787
3788    if (dcslen != strlen(cp))
3789	/* shouldn't have nulls in the string */
3790	return;
3791
3792    switch (*cp) {		/* intermediate character, or parameter */
3793    case '$':			/* DECRQSS */
3794	okay = True;
3795
3796	cp++;
3797	if (*cp++ == 'q') {
3798	    if (!strcmp(cp, "\"q")) {	/* DECSCA */
3799		sprintf(reply, "%d%s",
3800			(screen->protected_mode == DEC_PROTECT)
3801			&& (xw->flags & PROTECTED) ? 1 : 0,
3802			cp);
3803	    } else if (!strcmp(cp, "\"p")) {	/* DECSCL */
3804		if (screen->vtXX_level < 2) {
3805		    /* actually none of DECRQSS is valid for vt100's */
3806		    break;
3807		}
3808		sprintf(reply, "%d%s%s",
3809			(screen->vtXX_level ?
3810			 screen->vtXX_level : 1) + 60,
3811			(screen->vtXX_level >= 2)
3812			? (screen->control_eight_bits
3813			   ? ";0" : ";1")
3814			: "",
3815			cp);
3816	    } else if (!strcmp(cp, "r")) {	/* DECSTBM */
3817		sprintf(reply, "%d;%dr",
3818			screen->top_marg + 1,
3819			screen->bot_marg + 1);
3820	    } else if (!strcmp(cp, "s")) {	/* DECSLRM */
3821		if (screen->vtXX_level >= 4) {	/* VT420 */
3822		    sprintf(reply, "%d;%ds",
3823			    screen->lft_marg + 1,
3824			    screen->rgt_marg + 1);
3825		}
3826	    } else if (!strcmp(cp, "m")) {	/* SGR */
3827		strcpy(reply, "0");
3828		if (xw->flags & BOLD)
3829		    strcat(reply, ";1");
3830		if (xw->flags & UNDERLINE)
3831		    strcat(reply, ";4");
3832		if (xw->flags & BLINK)
3833		    strcat(reply, ";5");
3834		if (xw->flags & INVERSE)
3835		    strcat(reply, ";7");
3836		if (xw->flags & INVISIBLE)
3837		    strcat(reply, ";8");
3838#if OPT_256_COLORS || OPT_88_COLORS
3839		if_OPT_ISO_COLORS(screen, {
3840		    if (xw->flags & FG_COLOR) {
3841			if (xw->cur_foreground >= 16)
3842			    sprintf(reply + strlen(reply),
3843				    ";38;5;%d", xw->cur_foreground);
3844			else
3845			    sprintf(reply + strlen(reply),
3846				    ";%d%d",
3847				    xw->cur_foreground >= 8 ? 9 : 3,
3848				    xw->cur_foreground >= 8 ?
3849				    xw->cur_foreground - 8 :
3850				    xw->cur_foreground);
3851		    }
3852		    if (xw->flags & BG_COLOR) {
3853			if (xw->cur_background >= 16)
3854			    sprintf(reply + strlen(reply),
3855				    ";48;5;%d", xw->cur_foreground);
3856			else
3857			    sprintf(reply + strlen(reply),
3858				    ";%d%d",
3859				    xw->cur_background >= 8 ? 10 : 4,
3860				    xw->cur_background >= 8 ?
3861				    xw->cur_background - 8 :
3862				    xw->cur_background);
3863		    }
3864		});
3865#elif OPT_ISO_COLORS
3866		if_OPT_ISO_COLORS(screen, {
3867		    if (xw->flags & FG_COLOR)
3868			sprintf(reply + strlen(reply),
3869				";%d%d",
3870				xw->cur_foreground >= 8 ? 9 : 3,
3871				xw->cur_foreground >= 8 ?
3872				xw->cur_foreground - 8 :
3873				xw->cur_foreground);
3874		    if (xw->flags & BG_COLOR)
3875			sprintf(reply + strlen(reply),
3876				";%d%d",
3877				xw->cur_background >= 8 ? 10 : 4,
3878				xw->cur_background >= 8 ?
3879				xw->cur_background - 8 :
3880				xw->cur_background);
3881		});
3882#endif
3883		strcat(reply, "m");
3884	    } else if (!strcmp(cp, " q")) {	/* DECSCUSR */
3885		int code = STEADY_BLOCK;
3886		if (isCursorUnderline(screen))
3887		    code = STEADY_UNDERLINE;
3888		else if (isCursorBar(screen))
3889		    code = STEADY_BAR;
3890#if OPT_BLINK_CURS
3891		if (screen->cursor_blink_esc == 0)
3892		    code -= 1;
3893#endif
3894		sprintf(reply, "%d%s", code, cp);
3895	    } else
3896		okay = False;
3897
3898	    if (okay) {
3899		unparseputc1(xw, ANSI_DCS);
3900		unparseputc(xw, '1');
3901		unparseputc(xw, '$');
3902		unparseputc(xw, 'r');
3903		cp = reply;
3904		unparseputs(xw, cp);
3905		unparseputc1(xw, ANSI_ST);
3906	    } else {
3907		unparseputc(xw, ANSI_CAN);
3908	    }
3909	} else {
3910	    unparseputc(xw, ANSI_CAN);
3911	}
3912	break;
3913#if OPT_TCAP_QUERY
3914    case '+':
3915	cp++;
3916	switch (*cp) {
3917	case 'p':
3918	    if (AllowTcapOps(xw, etSetTcap)) {
3919		set_termcap(xw, cp + 1);
3920	    }
3921	    break;
3922	case 'q':
3923	    if (AllowTcapOps(xw, etGetTcap)) {
3924		Bool fkey;
3925		unsigned state;
3926		int code;
3927		const char *tmp;
3928		const char *parsed = ++cp;
3929
3930		code = xtermcapKeycode(xw, &parsed, &state, &fkey);
3931
3932		unparseputc1(xw, ANSI_DCS);
3933
3934		unparseputc(xw, code >= 0 ? '1' : '0');
3935
3936		unparseputc(xw, '+');
3937		unparseputc(xw, 'r');
3938
3939		while (*cp != 0 && (code >= -1)) {
3940		    if (cp == parsed)
3941			break;	/* no data found, error */
3942
3943		    for (tmp = cp; tmp != parsed; ++tmp)
3944			unparseputc(xw, *tmp);
3945
3946		    if (code >= 0) {
3947			unparseputc(xw, '=');
3948			screen->tc_query_code = code;
3949			screen->tc_query_fkey = fkey;
3950#if OPT_ISO_COLORS
3951			/* XK_COLORS is a fake code for the "Co" entry (maximum
3952			 * number of colors) */
3953			if (code == XK_COLORS) {
3954			    unparseputn(xw, NUM_ANSI_COLORS);
3955			} else
3956#endif
3957			if (code == XK_TCAPNAME) {
3958			    unparseputs(xw, resource.term_name);
3959			} else {
3960			    XKeyEvent event;
3961			    event.state = state;
3962			    Input(xw, &event, False);
3963			}
3964			screen->tc_query_code = -1;
3965		    } else {
3966			break;	/* no match found, error */
3967		    }
3968
3969		    cp = parsed;
3970		    if (*parsed == ';') {
3971			unparseputc(xw, *parsed++);
3972			cp = parsed;
3973			code = xtermcapKeycode(xw, &parsed, &state, &fkey);
3974		    }
3975		}
3976		unparseputc1(xw, ANSI_ST);
3977	    }
3978	    break;
3979	}
3980	break;
3981#endif
3982    default:
3983	if (screen->vtXX_level >= 2) {	/* VT220 */
3984	    parse_ansi_params(&params, &cp);
3985	    switch (params.a_final) {
3986	    case '|':		/* DECUDK */
3987		if (params.a_param[0] == 0)
3988		    reset_decudk();
3989		parse_decudk(cp);
3990		break;
3991	    case '{':		/* DECDLD (no '}' case though) */
3992		parse_decdld(&params, cp);
3993		break;
3994	    }
3995	}
3996	break;
3997    }
3998    unparse_end(xw);
3999}
4000
4001#if OPT_DEC_RECTOPS
4002enum {
4003    mdUnknown = 0,
4004    mdMaybeSet = 1,
4005    mdMaybeReset = 2,
4006    mdAlwaysSet = 3,
4007    mdAlwaysReset = 4
4008};
4009
4010#define MdBool(bool)      ((bool) ? mdMaybeSet : mdMaybeReset)
4011#define MdFlag(mode,flag) MdBool((mode) & (flag))
4012
4013/*
4014 * Reply is the same format as the query, with pair of mode/value:
4015 * 0 - not recognized
4016 * 1 - set
4017 * 2 - reset
4018 * 3 - permanently set
4019 * 4 - permanently reset
4020 * Only one mode can be reported at a time.
4021 */
4022void
4023do_rpm(XtermWidget xw, int nparams, int *params)
4024{
4025    ANSI reply;
4026    int result = 0;
4027    int count = 0;
4028
4029    TRACE(("do_rpm %d:%d\n", nparams, params[0]));
4030    memset(&reply, 0, sizeof(reply));
4031    if (nparams >= 1) {
4032	switch (params[0]) {
4033	case 1:		/* GATM */
4034	    result = mdAlwaysReset;
4035	    break;
4036	case 2:
4037	    result = MdFlag(xw->keyboard.flags, MODE_KAM);
4038	    break;
4039	case 3:		/* CRM */
4040	    result = mdMaybeReset;
4041	    break;
4042	case 4:
4043	    result = MdFlag(xw->flags, INSERT);
4044	    break;
4045	case 5:		/* SRTM */
4046	case 7:		/* VEM */
4047	case 10:		/* HEM */
4048	case 11:		/* PUM */
4049	    result = mdAlwaysReset;
4050	    break;
4051	case 12:
4052	    result = MdFlag(xw->keyboard.flags, MODE_SRM);
4053	    break;
4054	case 13:		/* FEAM */
4055	case 14:		/* FETM */
4056	case 15:		/* MATM */
4057	case 16:		/* TTM */
4058	case 17:		/* SATM */
4059	case 18:		/* TSM */
4060	case 19:		/* EBM */
4061	    result = mdAlwaysReset;
4062	    break;
4063	case 20:
4064	    result = MdFlag(xw->flags, LINEFEED);
4065	    break;
4066	}
4067	reply.a_param[count++] = (ParmType) params[0];
4068	reply.a_param[count++] = (ParmType) result;
4069    }
4070    reply.a_type = ANSI_CSI;
4071    reply.a_nparam = (ParmType) count;
4072    reply.a_inters = '$';
4073    reply.a_final = 'y';
4074    unparseseq(xw, &reply);
4075}
4076
4077void
4078do_decrpm(XtermWidget xw, int nparams, int *params)
4079{
4080    ANSI reply;
4081    int result = 0;
4082    int count = 0;
4083
4084    TRACE(("do_decrpm %d:%d\n", nparams, params[0]));
4085    memset(&reply, 0, sizeof(reply));
4086    if (nparams >= 1) {
4087	TScreen *screen = TScreenOf(xw);
4088
4089	switch (params[0]) {
4090	case 1:		/* DECCKM                       */
4091	    result = MdFlag(xw->keyboard.flags, MODE_DECCKM);
4092	    break;
4093	case 2:		/* DECANM - ANSI/VT52 mode      */
4094#if OPT_VT52_MODE
4095	    result = MdBool(screen->vtXX_level >= 1);
4096#else
4097	    result = mdMaybeSet;
4098#endif
4099	    break;
4100	case 3:		/* DECCOLM                      */
4101	    result = MdFlag(xw->flags, IN132COLUMNS);
4102	    break;
4103	case 4:		/* DECSCLM (slow scroll)        */
4104	    result = MdFlag(xw->flags, SMOOTHSCROLL);
4105	    break;
4106	case 5:		/* DECSCNM                      */
4107	    result = MdFlag(xw->flags, REVERSE_VIDEO);
4108	    break;
4109	case 6:		/* DECOM                        */
4110	    result = MdFlag(xw->flags, ORIGIN);
4111	    break;
4112	case 7:		/* DECAWM                       */
4113	    result = MdFlag(xw->flags, WRAPAROUND);
4114	    break;
4115	case 8:		/* DECARM                       */
4116	    result = mdAlwaysReset;
4117	    break;
4118	case SET_X10_MOUSE:	/* X10 mouse                    */
4119	    result = MdBool(screen->send_mouse_pos == X10_MOUSE);
4120	    break;
4121#if OPT_TOOLBAR
4122	case 10:		/* rxvt */
4123	    result = MdBool(resource.toolBar);
4124	    break;
4125#endif
4126#if OPT_BLINK_CURS
4127	case 12:		/* att610: Start/stop blinking cursor */
4128	    result = MdBool(screen->cursor_blink_res);
4129	    break;
4130#endif
4131	case 18:		/* DECPFF: print form feed */
4132	    result = MdBool(PrinterOf(screen).printer_formfeed);
4133	    break;
4134	case 19:		/* DECPEX: print extent */
4135	    result = MdBool(PrinterOf(screen).printer_extent);
4136	    break;
4137	case 25:		/* DECTCEM: Show/hide cursor (VT200) */
4138	    result = MdBool(screen->cursor_set);
4139	    break;
4140	case 30:		/* rxvt */
4141	    result = MdBool(screen->fullVwin.sb_info.width != OFF);
4142	    break;
4143#if OPT_SHIFT_FONTS
4144	case 35:		/* rxvt */
4145	    result = MdBool(xw->misc.shift_fonts);
4146	    break;
4147#endif
4148#if OPT_TEK4014
4149	case 38:		/* DECTEK                       */
4150	    result = MdBool(TEK4014_ACTIVE(xw));
4151	    break;
4152#endif
4153	case 40:		/* 132 column mode              */
4154	    result = MdBool(screen->c132);
4155	    break;
4156	case 41:		/* curses hack                  */
4157	    result = MdBool(screen->curses);
4158	    break;
4159	case 42:		/* DECNRCM national charset (VT220) */
4160	    result = MdFlag(xw->flags, NATIONAL);
4161	    break;
4162	case 44:		/* margin bell                  */
4163	    result = MdBool(screen->marginbell);
4164	    break;
4165	case 45:		/* reverse wraparound   */
4166	    result = MdFlag(xw->flags, REVERSEWRAP);
4167	    break;
4168#ifdef ALLOWLOGGING
4169	case 46:		/* logging              */
4170#ifdef ALLOWLOGFILEONOFF
4171	    result = MdBool(screen->logging);
4172#endif /* ALLOWLOGFILEONOFF */
4173	    break;
4174#endif
4175	case 1049:		/* alternate buffer & cursor */
4176	    /* FALLTHRU */
4177	case 1047:
4178	    /* FALLTHRU */
4179	case 47:		/* alternate buffer */
4180	    result = MdBool(screen->whichBuf);
4181	    break;
4182	case 66:		/* DECNKM */
4183	    result = MdFlag(xw->keyboard.flags, MODE_DECKPAM);
4184	    break;
4185	case 67:		/* DECBKM */
4186	    result = MdFlag(xw->keyboard.flags, MODE_DECBKM);
4187	    break;
4188	case 69:		/* DECLRMM */
4189	    result = MdFlag(xw->flags, LEFT_RIGHT);
4190	    break;
4191	case 95:		/* DECNCSM */
4192	    result = MdFlag(xw->flags, NOCLEAR_COLM);
4193	    break;
4194	case SET_VT200_MOUSE:	/* xterm bogus sequence         */
4195	    result = MdBool(screen->send_mouse_pos == VT200_MOUSE);
4196	    break;
4197	case SET_VT200_HIGHLIGHT_MOUSE:	/* xterm sequence w/hilite tracking */
4198	    result = MdBool(screen->send_mouse_pos == VT200_HIGHLIGHT_MOUSE);
4199	    break;
4200	case SET_BTN_EVENT_MOUSE:
4201	    result = MdBool(screen->send_mouse_pos == BTN_EVENT_MOUSE);
4202	    break;
4203	case SET_ANY_EVENT_MOUSE:
4204	    result = MdBool(screen->send_mouse_pos == ANY_EVENT_MOUSE);
4205	    break;
4206#if OPT_FOCUS_EVENT
4207	case SET_FOCUS_EVENT_MOUSE:
4208	    result = MdBool(screen->send_focus_pos);
4209	    break;
4210#endif
4211	case SET_EXT_MODE_MOUSE:
4212	    /* FALLTHRU */
4213	case SET_SGR_EXT_MODE_MOUSE:
4214	    /* FALLTHRU */
4215	case SET_URXVT_EXT_MODE_MOUSE:
4216	    result = MdBool(screen->extend_coords == params[0]);
4217	    break;
4218	case SET_ALTERNATE_SCROLL:
4219	    result = MdBool(screen->alternateScroll);
4220	    break;
4221	case 1010:		/* rxvt */
4222	    result = MdBool(screen->scrollttyoutput);
4223	    break;
4224	case 1011:		/* rxvt */
4225	    result = MdBool(screen->scrollkey);
4226	    break;
4227	case 1034:
4228	    result = MdBool(screen->eight_bit_meta);
4229	    break;
4230#if OPT_NUM_LOCK
4231	case 1035:
4232	    result = MdBool(xw->misc.real_NumLock);
4233	    break;
4234	case 1036:
4235	    result = MdBool(screen->meta_sends_esc);
4236	    break;
4237#endif
4238	case 1037:
4239	    result = MdBool(screen->delete_is_del);
4240	    break;
4241#if OPT_NUM_LOCK
4242	case 1039:
4243	    result = MdBool(screen->alt_sends_esc);
4244	    break;
4245#endif
4246	case 1040:
4247	    result = MdBool(screen->keepSelection);
4248	    break;
4249	case 1041:
4250	    result = MdBool(screen->selectToClipboard);
4251	    break;
4252	case 1042:
4253	    result = MdBool(screen->bellIsUrgent);
4254	    break;
4255	case 1043:
4256	    result = MdBool(screen->poponbell);
4257	    break;
4258	case 1048:
4259	    result = MdBool(screen->sc[screen->whichBuf].saved);
4260	    break;
4261#if OPT_TCAP_FKEYS
4262	case 1050:
4263	    result = MdBool(xw->keyboard.type == keyboardIsTermcap);
4264	    break;
4265#endif
4266#if OPT_SUN_FUNC_KEYS
4267	case 1051:
4268	    result = MdBool(xw->keyboard.type == keyboardIsSun);
4269	    break;
4270#endif
4271#if OPT_HP_FUNC_KEYS
4272	case 1052:
4273	    result = MdBool(xw->keyboard.type == keyboardIsHP);
4274	    break;
4275#endif
4276#if OPT_SCO_FUNC_KEYS
4277	case 1053:
4278	    result = MdBool(xw->keyboard.type == keyboardIsSCO);
4279	    break;
4280#endif
4281	case 1060:
4282	    result = MdBool(xw->keyboard.type == keyboardIsLegacy);
4283	    break;
4284#if OPT_SUNPC_KBD
4285	case 1061:
4286	    result = MdBool(xw->keyboard.type == keyboardIsVT220);
4287	    break;
4288#endif
4289#if OPT_READLINE
4290	case SET_BUTTON1_MOVE_POINT:
4291	    result = MdBool(screen->click1_moves);
4292	    break;
4293	case SET_BUTTON2_MOVE_POINT:
4294	    result = MdBool(screen->paste_moves);
4295	    break;
4296	case SET_DBUTTON3_DELETE:
4297	    result = MdBool(screen->dclick3_deletes);
4298	    break;
4299	case SET_PASTE_IN_BRACKET:
4300	    result = MdBool(screen->paste_brackets);
4301	    break;
4302	case SET_PASTE_QUOTE:
4303	    result = MdBool(screen->paste_quotes);
4304	    break;
4305	case SET_PASTE_LITERAL_NL:
4306	    result = MdBool(screen->paste_literal_nl);
4307	    break;
4308#endif /* OPT_READLINE */
4309	}
4310	reply.a_param[count++] = (ParmType) params[0];
4311	reply.a_param[count++] = (ParmType) result;
4312    }
4313    reply.a_type = ANSI_CSI;
4314    reply.a_pintro = '?';
4315    reply.a_nparam = (ParmType) count;
4316    reply.a_inters = '$';
4317    reply.a_final = 'y';
4318    unparseseq(xw, &reply);
4319}
4320#endif /* OPT_DEC_RECTOPS */
4321
4322char *
4323udk_lookup(int keycode, int *len)
4324{
4325    if (keycode >= 0 && keycode < MAX_UDK) {
4326	*len = user_keys[keycode].len;
4327	return user_keys[keycode].str;
4328    }
4329    return 0;
4330}
4331
4332#ifdef HAVE_LIBXPM
4333
4334#ifndef PIXMAP_ROOTDIR
4335#define PIXMAP_ROOTDIR "/usr/share/pixmaps/"
4336#endif
4337
4338typedef struct {
4339    const char *name;
4340    const char *const *data;
4341} XPM_DATA;
4342
4343static char *
4344x_find_icon(char **work, int *state, const char *suffix)
4345{
4346    const char *filename = resource.icon_hint;
4347    const char *prefix = PIXMAP_ROOTDIR;
4348    const char *larger = "_48x48";
4349    char *result = 0;
4350    size_t length;
4351
4352    if (*state >= 0) {
4353	if ((*state & 1) == 0)
4354	    suffix = "";
4355	if ((*state & 2) == 0)
4356	    larger = "";
4357	if ((*state & 4) == 0) {
4358	    prefix = "";
4359	} else if (!strncmp(filename, "/", (size_t) 1) ||
4360		   !strncmp(filename, "./", (size_t) 2) ||
4361		   !strncmp(filename, "../", (size_t) 3)) {
4362	    *state = -1;
4363	} else if (*state >= 8) {
4364	    *state = -1;
4365	}
4366    }
4367
4368    if (*state >= 0) {
4369	if (*work) {
4370	    free(*work);
4371	    *work = 0;
4372	}
4373	length = 3 + strlen(prefix) + strlen(filename) + strlen(larger) +
4374	    strlen(suffix);
4375	if ((result = malloc(length)) != 0) {
4376	    sprintf(result, "%s%s%s%s", prefix, filename, larger, suffix);
4377	    *work = result;
4378	}
4379	*state += 1;
4380	TRACE(("x_find_icon %d:%s\n", *state, result));
4381    }
4382    return result;
4383}
4384
4385#if OPT_BUILTIN_XPMS
4386static const XPM_DATA *
4387BuiltInXPM(const XPM_DATA * table, Cardinal length)
4388{
4389    const char *find = resource.icon_hint;
4390    const XPM_DATA *result = 0;
4391    if (!IsEmpty(find)) {
4392	Cardinal n;
4393	for (n = 0; n < length; ++n) {
4394	    if (!x_strcasecmp(find, table[n].name)) {
4395		result = table + n;
4396		break;
4397	    }
4398	}
4399
4400	/*
4401	 * As a fallback, check if the icon name matches without the lengths,
4402	 * which are all _HHxWW format.
4403	 */
4404	if (result == 0) {
4405	    const char *base = table[0].name;
4406	    const char *last = strchr(base, '_');
4407	    if (last != 0
4408		&& !x_strncasecmp(find, base, (unsigned) (last - base))) {
4409		result = table + length - 1;
4410	    }
4411	}
4412    }
4413    return result;
4414}
4415#endif /* OPT_BUILTIN_XPMS */
4416
4417typedef enum {
4418    eHintDefault = 0		/* use the largest builtin-icon */
4419    ,eHintNone
4420    ,eHintSearch
4421} ICON_HINT;
4422
4423static ICON_HINT
4424which_icon_hint(void)
4425{
4426    ICON_HINT result = eHintDefault;
4427    if (!IsEmpty(resource.icon_hint)) {
4428	if (!x_strcasecmp(resource.icon_hint, "none")) {
4429	    result = eHintNone;
4430	} else {
4431	    result = eHintSearch;
4432	}
4433    }
4434    return result;
4435}
4436#endif /* HAVE_LIBXPM */
4437
4438int
4439getVisualDepth(XtermWidget xw)
4440{
4441    Display *display = TScreenOf(xw)->display;
4442    XVisualInfo myTemplate, *visInfoPtr;
4443    int numFound;
4444    int result = 0;
4445
4446    myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(display,
4447							    XDefaultScreen(display)));
4448    visInfoPtr = XGetVisualInfo(display, (long) VisualIDMask,
4449				&myTemplate, &numFound);
4450    if (visInfoPtr != 0) {
4451	if (numFound != 0) {
4452	    result = visInfoPtr->depth;
4453	}
4454	XFree(visInfoPtr);
4455    }
4456    return result;
4457}
4458
4459/*
4460 * WM_ICON_SIZE should be honored if possible.
4461 */
4462void
4463xtermLoadIcon(XtermWidget xw)
4464{
4465#ifdef HAVE_LIBXPM
4466    Display *dpy = XtDisplay(xw);
4467    Pixmap myIcon = 0;
4468    Pixmap myMask = 0;
4469    char *workname = 0;
4470    ICON_HINT hint = which_icon_hint();
4471#if OPT_BUILTIN_XPMS
4472#include <icons/mini.xterm.xpms>
4473#include <icons/filled-xterm.xpms>
4474#include <icons/xterm.xpms>
4475#include <icons/xterm-color.xpms>
4476#else
4477#include <icons/mini.xterm_48x48.xpm>
4478#endif
4479
4480    TRACE(("xtermLoadIcon %p:%s\n", (void *) xw, NonNull(resource.icon_hint)));
4481
4482    if (hint == eHintSearch) {
4483	int state = 0;
4484	while (x_find_icon(&workname, &state, ".xpm") != 0) {
4485	    Pixmap resIcon = 0;
4486	    Pixmap shapemask = 0;
4487	    XpmAttributes attributes;
4488
4489	    attributes.depth = (unsigned) getVisualDepth(xw);
4490	    attributes.valuemask = XpmDepth;
4491
4492	    if (XpmReadFileToPixmap(dpy,
4493				    DefaultRootWindow(dpy),
4494				    workname,
4495				    &resIcon,
4496				    &shapemask,
4497				    &attributes) == XpmSuccess) {
4498		myIcon = resIcon;
4499		myMask = shapemask;
4500		TRACE(("...success\n"));
4501		break;
4502	    }
4503	}
4504    }
4505
4506    /*
4507     * If no external file was found, look for the name in the built-in table.
4508     * If that fails, just use the biggest mini-icon.
4509     */
4510    if (myIcon == 0 && hint != eHintNone) {
4511	char **data;
4512#if OPT_BUILTIN_XPMS
4513	const XPM_DATA *myData = 0;
4514	myData = BuiltInXPM(mini_xterm_xpms, XtNumber(mini_xterm_xpms));
4515	if (myData == 0)
4516	    myData = BuiltInXPM(filled_xterm_xpms, XtNumber(filled_xterm_xpms));
4517	if (myData == 0)
4518	    myData = BuiltInXPM(xterm_color_xpms, XtNumber(xterm_color_xpms));
4519	if (myData == 0)
4520	    myData = BuiltInXPM(xterm_xpms, XtNumber(xterm_xpms));
4521	if (myData == 0)
4522	    myData = &mini_xterm_xpms[XtNumber(mini_xterm_xpms) - 1];
4523	data = (char **) myData->data,
4524#else
4525	data = (char **) &mini_xterm_48x48_xpm;
4526#endif
4527	if (XpmCreatePixmapFromData(dpy,
4528				    DefaultRootWindow(dpy),
4529				    data,
4530				    &myIcon, &myMask, 0) != 0) {
4531	    myIcon = 0;
4532	    myMask = 0;
4533	}
4534    }
4535
4536    if (myIcon != 0) {
4537	XWMHints *hints = XGetWMHints(dpy, VShellWindow(xw));
4538	if (!hints)
4539	    hints = XAllocWMHints();
4540
4541	if (hints) {
4542	    hints->flags |= IconPixmapHint;
4543	    hints->icon_pixmap = myIcon;
4544	    if (myMask) {
4545		hints->flags |= IconMaskHint;
4546		hints->icon_mask = myMask;
4547	    }
4548
4549	    XSetWMHints(dpy, VShellWindow(xw), hints);
4550	    XFree(hints);
4551	    TRACE(("...loaded icon\n"));
4552	}
4553    }
4554
4555    if (workname != 0)
4556	free(workname);
4557
4558#else
4559    (void) xw;
4560#endif
4561}
4562
4563void
4564ChangeGroup(XtermWidget xw, const char *attribute, char *value)
4565{
4566#if OPT_WIDE_CHARS
4567    static Char *converted;	/* NO_LEAKS */
4568#endif
4569
4570    Arg args[1];
4571    Boolean changed = True;
4572    Widget w = CURRENT_EMU();
4573    Widget top = SHELL_OF(w);
4574
4575    char *my_attr;
4576    char *name;
4577    size_t limit;
4578    Char *c1;
4579    Char *cp;
4580
4581    if (!AllowTitleOps(xw))
4582	return;
4583
4584    if (value == 0)
4585	value = emptyString;
4586    if (IsTitleMode(xw, tmSetBase16)) {
4587	const char *temp;
4588	char *test;
4589
4590	value = x_decode_hex(value, &temp);
4591	if (*temp != '\0') {
4592	    free(value);
4593	    return;
4594	}
4595	for (test = value; *test != '\0'; ++test) {
4596	    if (CharOf(*test) < 32) {
4597		*test = '\0';
4598		break;
4599	    }
4600	}
4601    }
4602
4603    c1 = (Char *) value;
4604    name = value;
4605    limit = strlen(name);
4606    my_attr = x_strdup(attribute);
4607
4608    TRACE(("ChangeGroup(attribute=%s, value=%s)\n", my_attr, name));
4609
4610    /*
4611     * Ignore titles that are too long to be plausible requests.
4612     */
4613    if (limit > 0 && limit < 1024) {
4614
4615	/*
4616	 * After all decoding, overwrite nonprintable characters with '?'.
4617	 */
4618	for (cp = c1; *cp != 0; ++cp) {
4619	    Char *c2 = cp;
4620	    if (!xtermIsPrintable(xw, &cp, c1 + limit)) {
4621		memset(c2, '?', (size_t) (cp + 1 - c2));
4622	    }
4623	}
4624
4625#if OPT_WIDE_CHARS
4626	/*
4627	 * If we're running in UTF-8 mode, and have not been told that the
4628	 * title string is in UTF-8, it is likely that non-ASCII text in the
4629	 * string will be rejected because it is not printable in the current
4630	 * locale.  So we convert it to UTF-8, allowing the X library to
4631	 * convert it back.
4632	 */
4633	if (xtermEnvUTF8() && !IsSetUtf8Title(xw)) {
4634	    int n;
4635
4636	    for (n = 0; name[n] != '\0'; ++n) {
4637		if (CharOf(name[n]) > 127) {
4638		    if (converted != 0)
4639			free(converted);
4640		    if ((converted = TypeMallocN(Char, 1 + (6 * limit))) != 0) {
4641			Char *temp = converted;
4642			while (*name != 0) {
4643			    temp = convertToUTF8(temp, CharOf(*name));
4644			    ++name;
4645			}
4646			*temp = 0;
4647			name = (char *) converted;
4648			TRACE(("...converted{%s}\n", name));
4649		    }
4650		    break;
4651		}
4652	    }
4653	}
4654#endif
4655
4656#if OPT_SAME_NAME
4657	/* If the attribute isn't going to change, then don't bother... */
4658
4659	if (resource.sameName) {
4660	    char *buf = 0;
4661	    XtSetArg(args[0], my_attr, &buf);
4662	    XtGetValues(top, args, 1);
4663	    TRACE(("...comparing{%s}\n", buf));
4664	    if (buf != 0 && strcmp(name, buf) == 0)
4665		changed = False;
4666	}
4667#endif /* OPT_SAME_NAME */
4668
4669	if (changed) {
4670	    TRACE(("...updating %s\n", my_attr));
4671	    TRACE(("...value is %s\n", name));
4672	    XtSetArg(args[0], my_attr, name);
4673	    XtSetValues(top, args, 1);
4674
4675#if OPT_WIDE_CHARS
4676	    if (xtermEnvUTF8()) {
4677		Display *dpy = XtDisplay(xw);
4678		Atom my_atom;
4679
4680		const char *propname = (!strcmp(my_attr, XtNtitle)
4681					? "_NET_WM_NAME"
4682					: "_NET_WM_ICON_NAME");
4683		if ((my_atom = XInternAtom(dpy, propname, False)) != None) {
4684		    if (IsSetUtf8Title(xw)) {
4685			TRACE(("...updating %s\n", propname));
4686			TRACE(("...value is %s\n", value));
4687			XChangeProperty(dpy, VShellWindow(xw), my_atom,
4688					XA_UTF8_STRING(dpy), 8,
4689					PropModeReplace,
4690					(Char *) value,
4691					(int) strlen(value));
4692		    } else {
4693			TRACE(("...deleting %s\n", propname));
4694			XDeleteProperty(dpy, VShellWindow(xw), my_atom);
4695		    }
4696		}
4697	    }
4698#endif
4699	}
4700    }
4701    if (IsTitleMode(xw, tmSetBase16)) {
4702	free(value);
4703    }
4704    free(my_attr);
4705
4706    return;
4707}
4708
4709void
4710ChangeIconName(XtermWidget xw, char *name)
4711{
4712    if (name == 0) {
4713	name = emptyString;
4714    }
4715    if (!showZIconBeep(xw, name))
4716	ChangeGroup(xw, XtNiconName, name);
4717}
4718
4719void
4720ChangeTitle(XtermWidget xw, char *name)
4721{
4722    ChangeGroup(xw, XtNtitle, name);
4723}
4724
4725#define Strlen(s) strlen((const char *)(s))
4726
4727void
4728ChangeXprop(char *buf)
4729{
4730    Display *dpy = XtDisplay(toplevel);
4731    Window w = XtWindow(toplevel);
4732    XTextProperty text_prop;
4733    Atom aprop;
4734    Char *pchEndPropName = (Char *) strchr(buf, '=');
4735
4736    if (pchEndPropName)
4737	*pchEndPropName = '\0';
4738    aprop = XInternAtom(dpy, buf, False);
4739    if (pchEndPropName == NULL) {
4740	/* no "=value" given, so delete the property */
4741	XDeleteProperty(dpy, w, aprop);
4742    } else {
4743	text_prop.value = pchEndPropName + 1;
4744	text_prop.encoding = XA_STRING;
4745	text_prop.format = 8;
4746	text_prop.nitems = Strlen(text_prop.value);
4747	XSetTextProperty(dpy, w, &text_prop, aprop);
4748    }
4749}
4750
4751/***====================================================================***/
4752
4753/*
4754 * This is part of ReverseVideo().  It reverses the data stored for the old
4755 * "dynamic" colors that might have been retrieved using OSC 10-18.
4756 */
4757void
4758ReverseOldColors(void)
4759{
4760    ScrnColors *pOld = pOldColors;
4761    Pixel tmpPix;
4762    char *tmpName;
4763
4764    if (pOld) {
4765	/* change text cursor, if necesary */
4766	if (pOld->colors[TEXT_CURSOR] == pOld->colors[TEXT_FG]) {
4767	    pOld->colors[TEXT_CURSOR] = pOld->colors[TEXT_BG];
4768	    if (pOld->names[TEXT_CURSOR]) {
4769		XtFree(pOldColors->names[TEXT_CURSOR]);
4770		pOld->names[TEXT_CURSOR] = NULL;
4771	    }
4772	    if (pOld->names[TEXT_BG]) {
4773		if ((tmpName = x_strdup(pOld->names[TEXT_BG])) != 0) {
4774		    pOld->names[TEXT_CURSOR] = tmpName;
4775		}
4776	    }
4777	}
4778
4779	EXCHANGE(pOld->colors[TEXT_FG], pOld->colors[TEXT_BG], tmpPix);
4780	EXCHANGE(pOld->names[TEXT_FG], pOld->names[TEXT_BG], tmpName);
4781
4782	EXCHANGE(pOld->colors[MOUSE_FG], pOld->colors[MOUSE_BG], tmpPix);
4783	EXCHANGE(pOld->names[MOUSE_FG], pOld->names[MOUSE_BG], tmpName);
4784
4785#if OPT_TEK4014
4786	EXCHANGE(pOld->colors[TEK_FG], pOld->colors[TEK_BG], tmpPix);
4787	EXCHANGE(pOld->names[TEK_FG], pOld->names[TEK_BG], tmpName);
4788#endif
4789    }
4790    return;
4791}
4792
4793Bool
4794AllocateTermColor(XtermWidget xw,
4795		  ScrnColors * pNew,
4796		  int ndx,
4797		  const char *name,
4798		  Bool always)
4799{
4800    Bool result = False;
4801
4802    if (always || AllowColorOps(xw, ecSetColor)) {
4803	XColor def;
4804	char *newName;
4805
4806	result = True;
4807	if (!x_strcasecmp(name, XtDefaultForeground)) {
4808	    def.pixel = xw->old_foreground;
4809	} else if (!x_strcasecmp(name, XtDefaultBackground)) {
4810	    def.pixel = xw->old_background;
4811	} else if (!xtermAllocColor(xw, &def, name)) {
4812	    result = False;
4813	}
4814
4815	if (result
4816	    && (newName = x_strdup(name)) != 0) {
4817	    if (COLOR_DEFINED(pNew, ndx)) {
4818		free(pNew->names[ndx]);
4819	    }
4820	    SET_COLOR_VALUE(pNew, ndx, def.pixel);
4821	    SET_COLOR_NAME(pNew, ndx, newName);
4822	    TRACE(("AllocateTermColor #%d: %s (pixel 0x%06lx)\n",
4823		   ndx, newName, def.pixel));
4824	} else {
4825	    TRACE(("AllocateTermColor #%d: %s (failed)\n", ndx, name));
4826	    result = False;
4827	}
4828    }
4829    return result;
4830}
4831/***====================================================================***/
4832
4833/* ARGSUSED */
4834void
4835Panic(const char *s GCC_UNUSED, int a GCC_UNUSED)
4836{
4837    if_DEBUG({
4838	xtermWarning(s, a);
4839    });
4840}
4841
4842const char *
4843SysErrorMsg(int code)
4844{
4845    static char unknown[] = "unknown error";
4846    char *s = strerror(code);
4847    return s ? s : unknown;
4848}
4849
4850const char *
4851SysReasonMsg(int code)
4852{
4853    /* *INDENT-OFF* */
4854    static const struct {
4855	int code;
4856	const char *name;
4857    } table[] = {
4858	{ ERROR_FIONBIO,	"main:  ioctl() failed on FIONBIO" },
4859	{ ERROR_F_GETFL,	"main: ioctl() failed on F_GETFL" },
4860	{ ERROR_F_SETFL,	"main: ioctl() failed on F_SETFL", },
4861	{ ERROR_OPDEVTTY,	"spawn: open() failed on /dev/tty", },
4862	{ ERROR_TIOCGETP,	"spawn: ioctl() failed on TIOCGETP", },
4863	{ ERROR_PTSNAME,	"spawn: ptsname() failed", },
4864	{ ERROR_OPPTSNAME,	"spawn: open() failed on ptsname", },
4865	{ ERROR_PTEM,		"spawn: ioctl() failed on I_PUSH/\"ptem\"" },
4866	{ ERROR_CONSEM,		"spawn: ioctl() failed on I_PUSH/\"consem\"" },
4867	{ ERROR_LDTERM,		"spawn: ioctl() failed on I_PUSH/\"ldterm\"" },
4868	{ ERROR_TTCOMPAT,	"spawn: ioctl() failed on I_PUSH/\"ttcompat\"" },
4869	{ ERROR_TIOCSETP,	"spawn: ioctl() failed on TIOCSETP" },
4870	{ ERROR_TIOCSETC,	"spawn: ioctl() failed on TIOCSETC" },
4871	{ ERROR_TIOCSETD,	"spawn: ioctl() failed on TIOCSETD" },
4872	{ ERROR_TIOCSLTC,	"spawn: ioctl() failed on TIOCSLTC" },
4873	{ ERROR_TIOCLSET,	"spawn: ioctl() failed on TIOCLSET" },
4874	{ ERROR_INIGROUPS,	"spawn: initgroups() failed" },
4875	{ ERROR_FORK,		"spawn: fork() failed" },
4876	{ ERROR_EXEC,		"spawn: exec() failed" },
4877	{ ERROR_PTYS,		"get_pty: not enough ptys" },
4878	{ ERROR_PTY_EXEC,	"waiting for initial map" },
4879	{ ERROR_SETUID,		"spawn: setuid() failed" },
4880	{ ERROR_INIT,		"spawn: can't initialize window" },
4881	{ ERROR_TIOCKSET,	"spawn: ioctl() failed on TIOCKSET" },
4882	{ ERROR_TIOCKSETC,	"spawn: ioctl() failed on TIOCKSETC" },
4883	{ ERROR_LUMALLOC,	"luit: command-line malloc failed" },
4884	{ ERROR_SELECT,		"in_put: select() failed" },
4885	{ ERROR_VINIT,		"VTInit: can't initialize window" },
4886	{ ERROR_KMMALLOC1,	"HandleKeymapChange: malloc failed" },
4887	{ ERROR_TSELECT,	"Tinput: select() failed" },
4888	{ ERROR_TINIT,		"TekInit: can't initialize window" },
4889	{ ERROR_BMALLOC2,	"SaltTextAway: malloc() failed" },
4890	{ ERROR_LOGEXEC,	"StartLog: exec() failed" },
4891	{ ERROR_XERROR,		"xerror: XError event" },
4892	{ ERROR_XIOERROR,	"xioerror: X I/O error" },
4893	{ ERROR_SCALLOC,	"Alloc: calloc() failed on base" },
4894	{ ERROR_SCALLOC2,	"Alloc: calloc() failed on rows" },
4895	{ ERROR_SAVE_PTR,	"ScrnPointers: malloc/realloc() failed" },
4896    };
4897    /* *INDENT-ON* */
4898
4899    Cardinal n;
4900    const char *result = "?";
4901
4902    for (n = 0; n < XtNumber(table); ++n) {
4903	if (code == table[n].code) {
4904	    result = table[n].name;
4905	    break;
4906	}
4907    }
4908    return result;
4909}
4910
4911void
4912SysError(int code)
4913{
4914    int oerrno = errno;
4915
4916    fprintf(stderr, "%s: Error %d, errno %d: ", ProgramName, code, oerrno);
4917    fprintf(stderr, "%s\n", SysErrorMsg(oerrno));
4918    fprintf(stderr, "Reason: %s\n", SysReasonMsg(code));
4919
4920    Cleanup(code);
4921}
4922
4923void
4924NormalExit(void)
4925{
4926    static Bool cleaning;
4927
4928    /*
4929     * Process "-hold" and session cleanup only for a normal exit.
4930     */
4931    if (cleaning) {
4932	hold_screen = 0;
4933	return;
4934    }
4935
4936    cleaning = True;
4937    need_cleanup = False;
4938
4939    if (hold_screen) {
4940	hold_screen = 2;
4941	while (hold_screen) {
4942	    xevents();
4943	    Sleep(10);
4944	}
4945    }
4946#if OPT_SESSION_MGT
4947    if (resource.sessionMgt) {
4948	XtVaSetValues(toplevel,
4949		      XtNjoinSession, False,
4950		      (void *) 0);
4951    }
4952#endif
4953    Cleanup(0);
4954}
4955
4956/*
4957 * cleanup by sending SIGHUP to client processes
4958 */
4959void
4960Cleanup(int code)
4961{
4962    TScreen *screen = TScreenOf(term);
4963
4964    TRACE(("Cleanup %d\n", code));
4965
4966    if (screen->pid > 1) {
4967	(void) kill_process_group(screen->pid, SIGHUP);
4968    }
4969    Exit(code);
4970}
4971
4972#ifndef VMS
4973#ifndef PATH_MAX
4974#define PATH_MAX 512		/* ... is not defined consistently in Xos.h */
4975#endif
4976char *
4977xtermFindShell(char *leaf, Bool warning)
4978{
4979    char *s0;
4980    char *s;
4981    char *d;
4982    char *tmp;
4983    char *result = leaf;
4984    Bool allocated = False;
4985
4986    TRACE(("xtermFindShell(%s)\n", leaf));
4987
4988    if (!strncmp("./", result, (size_t) 2)
4989	|| !strncmp("../", result, (size_t) 3)) {
4990	size_t need = PATH_MAX;
4991	size_t used = strlen(result) + 2;
4992	char *buffer = malloc(used + need);
4993	if (buffer != 0) {
4994	    if (getcwd(buffer, need) != 0) {
4995		sprintf(buffer + strlen(buffer), "/%s", result);
4996		result = buffer;
4997		allocated = True;
4998	    } else {
4999		free(buffer);
5000	    }
5001	}
5002    } else if (*result != '\0' && strchr("+/-", *result) == 0) {
5003	/* find it in $PATH */
5004	if ((s = s0 = x_getenv("PATH")) != 0) {
5005	    if ((tmp = TypeMallocN(char, strlen(leaf) + strlen(s) + 2)) != 0) {
5006		Bool found = False;
5007		while (*s != '\0') {
5008		    strcpy(tmp, s);
5009		    for (d = tmp;; ++d) {
5010			if (*d == ':' || *d == '\0') {
5011			    int skip = (*d != '\0');
5012			    *d = '/';
5013			    strcpy(d + 1, leaf);
5014			    if (skip)
5015				++d;
5016			    s += (d - tmp);
5017			    if (*tmp == '/'
5018				&& strstr(tmp, "..") == 0
5019				&& access(tmp, X_OK) == 0) {
5020				result = x_strdup(tmp);
5021				found = True;
5022				allocated = True;
5023			    }
5024			    break;
5025			}
5026		    }
5027		    if (found)
5028			break;
5029		}
5030		free(tmp);
5031	    }
5032	    free(s0);
5033	}
5034    }
5035    TRACE(("...xtermFindShell(%s)\n", result));
5036    if (*result != '/'
5037	|| strstr(result, "..") != 0
5038	|| access(result, X_OK) != 0) {
5039	if (warning)
5040	    xtermWarning("No absolute path found for shell: %s\n", result);
5041	if (allocated)
5042	    free(result);
5043	result = 0;
5044    }
5045    /* be consistent, so that caller can always free the result */
5046    if (result != 0 && !allocated)
5047	result = x_strdup(result);
5048    return result;
5049}
5050#endif /* VMS */
5051
5052#define ENV_HUNK(n)	(unsigned) ((((n) + 1) | 31) + 1)
5053
5054/*
5055 * If we do not have unsetenv(), make consistent updates for environ[].
5056 * This could happen on some older machines due to the uneven standardization
5057 * process for the two functions.
5058 *
5059 * That is, putenv() makes a copy of environ, and some implementations do not
5060 * update the environ pointer, so the fallback when unsetenv() is missing would
5061 * not work as intended.  Likewise, the reverse could be true, i.e., unsetenv
5062 * could copy environ.
5063 */
5064#if defined(HAVE_PUTENV) && !defined(HAVE_UNSETENV)
5065#undef HAVE_PUTENV
5066#elif !defined(HAVE_PUTENV) && defined(HAVE_UNSETENV)
5067#undef HAVE_UNSETENV
5068#endif
5069
5070/*
5071 * copy the environment before Setenv'ing.
5072 */
5073void
5074xtermCopyEnv(char **oldenv)
5075{
5076#ifdef HAVE_PUTENV
5077    (void) oldenv;
5078#else
5079    unsigned size;
5080    char **newenv;
5081
5082    for (size = 0; oldenv[size] != NULL; size++) {
5083	;
5084    }
5085
5086    newenv = TypeCallocN(char *, ENV_HUNK(size));
5087    memmove(newenv, oldenv, size * sizeof(char *));
5088    environ = newenv;
5089#endif
5090}
5091
5092#if !defined(HAVE_PUTENV) || !defined(HAVE_UNSETENV)
5093static int
5094findEnv(const char *var, int *lengthp)
5095{
5096    char *test;
5097    int envindex = 0;
5098    size_t len = strlen(var);
5099    int found = -1;
5100
5101    TRACE(("findEnv(%s=..)\n", var));
5102
5103    while ((test = environ[envindex]) != NULL) {
5104	if (strncmp(test, var, len) == 0 && test[len] == '=') {
5105	    found = envindex;
5106	    break;
5107	}
5108	envindex++;
5109    }
5110    *lengthp = envindex;
5111    return found;
5112}
5113#endif
5114
5115/*
5116 * sets the value of var to be arg in the Unix 4.2 BSD environment env.
5117 * Var should end with '=' (bindings are of the form "var=value").
5118 * This procedure assumes the memory for the first level of environ
5119 * was allocated using calloc, with enough extra room at the end so not
5120 * to have to do a realloc().
5121 */
5122void
5123xtermSetenv(const char *var, const char *value)
5124{
5125    if (value != 0) {
5126#ifdef HAVE_PUTENV
5127	char *both = malloc(2 + strlen(var) + strlen(value));
5128	TRACE(("xtermSetenv(%s=%s)\n", var, value));
5129	if (both) {
5130	    sprintf(both, "%s=%s", var, value);
5131	    putenv(both);
5132	}
5133#else
5134	size_t len = strlen(var);
5135	int envindex;
5136	int found = findEnv(var, &envindex);
5137
5138	TRACE(("xtermSetenv(%s=%s)\n", var, value));
5139
5140	if (found < 0) {
5141	    unsigned need = ENV_HUNK(envindex + 1);
5142	    unsigned have = ENV_HUNK(envindex);
5143
5144	    if (need > have) {
5145		char **newenv;
5146		newenv = TypeMallocN(char *, need);
5147		if (newenv == 0) {
5148		    xtermWarning("Cannot increase environment\n");
5149		    return;
5150		}
5151		memmove(newenv, environ, have * sizeof(*newenv));
5152		free(environ);
5153		environ = newenv;
5154	    }
5155
5156	    found = envindex;
5157	    environ[found + 1] = NULL;
5158	    environ = environ;
5159	}
5160
5161	environ[found] = CastMallocN(char, 1 + len + strlen(value));
5162	if (environ[found] == 0) {
5163	    xtermWarning("Cannot allocate environment %s\n", var);
5164	    return;
5165	}
5166	sprintf(environ[found], "%s=%s", var, value);
5167#endif
5168    }
5169}
5170
5171void
5172xtermUnsetenv(const char *var)
5173{
5174    TRACE(("xtermUnsetenv(%s)\n", var));
5175#ifdef HAVE_UNSETENV
5176    unsetenv(var);
5177#else
5178    {
5179	int ignore;
5180	int item = findEnv(var, &ignore);
5181	if (item >= 0) {
5182	    while ((environ[item] = environ[item + 1]) != 0) {
5183		++item;
5184	    }
5185	}
5186    }
5187#endif
5188}
5189
5190/*ARGSUSED*/
5191int
5192xerror(Display * d, XErrorEvent * ev)
5193{
5194    xtermWarning("warning, error event received:\n");
5195    (void) XmuPrintDefaultErrorMessage(d, ev, stderr);
5196    Exit(ERROR_XERROR);
5197    return 0;			/* appease the compiler */
5198}
5199
5200void
5201ice_error(IceConn iceConn)
5202{
5203    (void) iceConn;
5204
5205    xtermWarning("ICE IO error handler doing an exit(), pid = %ld, errno = %d\n",
5206		 (long) getpid(), errno);
5207
5208    Exit(ERROR_ICEERROR);
5209}
5210
5211/*ARGSUSED*/
5212int
5213xioerror(Display * dpy)
5214{
5215    int the_error = errno;
5216
5217    xtermWarning("fatal IO error %d (%s) or KillClient on X server \"%s\"\r\n",
5218		 the_error, SysErrorMsg(the_error),
5219		 DisplayString(dpy));
5220
5221    Exit(ERROR_XIOERROR);
5222    return 0;			/* appease the compiler */
5223}
5224
5225void
5226xt_error(String message)
5227{
5228    xtermWarning("Xt error: %s\n", message);
5229
5230    /*
5231     * Check for the obvious - Xt does a poor job of reporting this.
5232     */
5233    if (x_getenv("DISPLAY") == 0) {
5234	xtermWarning("DISPLAY is not set\n");
5235    }
5236    exit(1);
5237}
5238
5239int
5240XStrCmp(char *s1, char *s2)
5241{
5242    if (s1 && s2)
5243	return (strcmp(s1, s2));
5244    if (s1 && *s1)
5245	return (1);
5246    if (s2 && *s2)
5247	return (-1);
5248    return (0);
5249}
5250
5251#if OPT_TEK4014
5252static void
5253withdraw_window(Display * dpy, Window w, int scr)
5254{
5255    TRACE(("withdraw_window %#lx\n", (long) w));
5256    (void) XmuUpdateMapHints(dpy, w, NULL);
5257    XWithdrawWindow(dpy, w, scr);
5258    return;
5259}
5260#endif
5261
5262void
5263set_vt_visibility(Bool on)
5264{
5265    XtermWidget xw = term;
5266    TScreen *screen = TScreenOf(xw);
5267
5268    TRACE(("set_vt_visibility(%d)\n", on));
5269    if (on) {
5270	if (!screen->Vshow && xw) {
5271	    VTInit(xw);
5272	    XtMapWidget(XtParent(xw));
5273#if OPT_TOOLBAR
5274	    /* we need both of these during initialization */
5275	    XtMapWidget(SHELL_OF(xw));
5276	    ShowToolbar(resource.toolBar);
5277#endif
5278	    screen->Vshow = True;
5279	}
5280    }
5281#if OPT_TEK4014
5282    else {
5283	if (screen->Vshow && xw) {
5284	    withdraw_window(XtDisplay(xw),
5285			    VShellWindow(xw),
5286			    XScreenNumberOfScreen(XtScreen(xw)));
5287	    screen->Vshow = False;
5288	}
5289    }
5290    set_vthide_sensitivity();
5291    set_tekhide_sensitivity();
5292    update_vttekmode();
5293    update_tekshow();
5294    update_vtshow();
5295#endif
5296    return;
5297}
5298
5299#if OPT_TEK4014
5300void
5301set_tek_visibility(Bool on)
5302{
5303    TRACE(("set_tek_visibility(%d)\n", on));
5304
5305    if (on) {
5306	if (!TEK4014_SHOWN(term)) {
5307	    if (tekWidget == 0) {
5308		TekInit();	/* will exit on failure */
5309	    }
5310	    if (tekWidget != 0) {
5311		Widget tekParent = SHELL_OF(tekWidget);
5312		XtRealizeWidget(tekParent);
5313		XtMapWidget(XtParent(tekWidget));
5314#if OPT_TOOLBAR
5315		/* we need both of these during initialization */
5316		XtMapWidget(tekParent);
5317		XtMapWidget(tekWidget);
5318#endif
5319		XtOverrideTranslations(tekParent,
5320				       XtParseTranslationTable
5321				       ("<Message>WM_PROTOCOLS: DeleteWindow()"));
5322		(void) XSetWMProtocols(XtDisplay(tekParent),
5323				       XtWindow(tekParent),
5324				       &wm_delete_window, 1);
5325		TEK4014_SHOWN(term) = True;
5326	    }
5327	}
5328    } else {
5329	if (TEK4014_SHOWN(term) && tekWidget) {
5330	    withdraw_window(XtDisplay(tekWidget),
5331			    TShellWindow,
5332			    XScreenNumberOfScreen(XtScreen(tekWidget)));
5333	    TEK4014_SHOWN(term) = False;
5334	}
5335    }
5336    set_tekhide_sensitivity();
5337    set_vthide_sensitivity();
5338    update_vtshow();
5339    update_tekshow();
5340    update_vttekmode();
5341    return;
5342}
5343
5344void
5345end_tek_mode(void)
5346{
5347    XtermWidget xw = term;
5348
5349    if (TEK4014_ACTIVE(xw)) {
5350	FlushLog(xw);
5351	longjmp(Tekend, 1);
5352    }
5353    return;
5354}
5355
5356void
5357end_vt_mode(void)
5358{
5359    XtermWidget xw = term;
5360
5361    if (!TEK4014_ACTIVE(xw)) {
5362	FlushLog(xw);
5363	TEK4014_ACTIVE(xw) = True;
5364	longjmp(VTend, 1);
5365    }
5366    return;
5367}
5368
5369void
5370switch_modes(Bool tovt)		/* if true, then become vt mode */
5371{
5372    if (tovt) {
5373	if (tekRefreshList)
5374	    TekRefresh(tekWidget);
5375	end_tek_mode();		/* WARNING: this does a longjmp... */
5376    } else {
5377	end_vt_mode();		/* WARNING: this does a longjmp... */
5378    }
5379}
5380
5381void
5382hide_vt_window(void)
5383{
5384    set_vt_visibility(False);
5385    if (!TEK4014_ACTIVE(term))
5386	switch_modes(False);	/* switch to tek mode */
5387}
5388
5389void
5390hide_tek_window(void)
5391{
5392    set_tek_visibility(False);
5393    tekRefreshList = (TekLink *) 0;
5394    if (TEK4014_ACTIVE(term))
5395	switch_modes(True);	/* does longjmp to vt mode */
5396}
5397#endif /* OPT_TEK4014 */
5398
5399static const char *
5400skip_punct(const char *s)
5401{
5402    while (*s == '-' || *s == '/' || *s == '+' || *s == '#' || *s == '%') {
5403	++s;
5404    }
5405    return s;
5406}
5407
5408static int
5409cmp_options(const void *a, const void *b)
5410{
5411    const char *s1 = skip_punct(((const OptionHelp *) a)->opt);
5412    const char *s2 = skip_punct(((const OptionHelp *) b)->opt);
5413    return strcmp(s1, s2);
5414}
5415
5416static int
5417cmp_resources(const void *a, const void *b)
5418{
5419    return strcmp(((const XrmOptionDescRec *) a)->option,
5420		  ((const XrmOptionDescRec *) b)->option);
5421}
5422
5423XrmOptionDescRec *
5424sortedOptDescs(XrmOptionDescRec * descs, Cardinal res_count)
5425{
5426    static XrmOptionDescRec *res_array = 0;
5427
5428#ifdef NO_LEAKS
5429    if (descs == 0) {
5430	if (res_array != 0) {
5431	    free(res_array);
5432	    res_array = 0;
5433	}
5434    } else
5435#endif
5436    if (res_array == 0) {
5437	Cardinal j;
5438
5439	/* make a sorted index to 'resources' */
5440	res_array = TypeCallocN(XrmOptionDescRec, res_count);
5441	if (res_array != 0) {
5442	    for (j = 0; j < res_count; j++)
5443		res_array[j] = descs[j];
5444	    qsort(res_array, (size_t) res_count, sizeof(*res_array), cmp_resources);
5445	}
5446    }
5447    return res_array;
5448}
5449
5450/*
5451 * The first time this is called, construct sorted index to the main program's
5452 * list of options, taking into account the on/off options which will be
5453 * compressed into one token.  It's a lot simpler to do it this way than
5454 * maintain the list in sorted form with lots of ifdef's.
5455 */
5456OptionHelp *
5457sortedOpts(OptionHelp * options, XrmOptionDescRec * descs, Cardinal numDescs)
5458{
5459    static OptionHelp *opt_array = 0;
5460
5461#ifdef NO_LEAKS
5462    if (descs == 0 && opt_array != 0) {
5463	sortedOptDescs(descs, numDescs);
5464	free(opt_array);
5465	opt_array = 0;
5466	return 0;
5467    } else if (options == 0 || descs == 0) {
5468	return 0;
5469    }
5470#endif
5471
5472    if (opt_array == 0) {
5473	size_t opt_count, j;
5474#if OPT_TRACE
5475	Cardinal k;
5476	XrmOptionDescRec *res_array = sortedOptDescs(descs, numDescs);
5477	int code;
5478	const char *mesg;
5479#else
5480	(void) descs;
5481	(void) numDescs;
5482#endif
5483
5484	/* count 'options' and make a sorted index to it */
5485	for (opt_count = 0; options[opt_count].opt != 0; ++opt_count) {
5486	    ;
5487	}
5488	opt_array = TypeCallocN(OptionHelp, opt_count + 1);
5489	for (j = 0; j < opt_count; j++)
5490	    opt_array[j] = options[j];
5491	qsort(opt_array, opt_count, sizeof(OptionHelp), cmp_options);
5492
5493	/* supply the "turn on/off" strings if needed */
5494#if OPT_TRACE
5495	for (j = 0; j < opt_count; j++) {
5496	    if (!strncmp(opt_array[j].opt, "-/+", (size_t) 3)) {
5497		char temp[80];
5498		const char *name = opt_array[j].opt + 3;
5499		for (k = 0; k < numDescs; ++k) {
5500		    const char *value = res_array[k].value;
5501		    if (res_array[k].option[0] == '-') {
5502			code = -1;
5503		    } else if (res_array[k].option[0] == '+') {
5504			code = 1;
5505		    } else {
5506			code = 0;
5507		    }
5508		    sprintf(temp, "%.*s",
5509			    (int) sizeof(temp) - 2,
5510			    opt_array[j].desc);
5511		    if (x_strindex(temp, "inhibit") != 0)
5512			code = -code;
5513		    if (code != 0
5514			&& res_array[k].value != 0
5515			&& !strcmp(name, res_array[k].option + 1)) {
5516			if (((code < 0) && !strcmp(value, "on"))
5517			    || ((code > 0) && !strcmp(value, "off"))
5518			    || ((code > 0) && !strcmp(value, "0"))) {
5519			    mesg = "turn on/off";
5520			} else {
5521			    mesg = "turn off/on";
5522			}
5523			if (strncmp(mesg, opt_array[j].desc, strlen(mesg))) {
5524			    if (strncmp(opt_array[j].desc, "turn ", (size_t) 5)) {
5525				char *s = CastMallocN(char,
5526						      strlen(mesg)
5527						      + 1
5528						      + strlen(opt_array[j].desc));
5529				if (s != 0) {
5530				    sprintf(s, "%s %s", mesg, opt_array[j].desc);
5531				    opt_array[j].desc = s;
5532				}
5533			    } else {
5534				TRACE(("OOPS "));
5535			    }
5536			}
5537			TRACE(("%s: %s %s: %s (%s)\n",
5538			       mesg,
5539			       res_array[k].option,
5540			       res_array[k].value,
5541			       opt_array[j].opt,
5542			       opt_array[j].desc));
5543			break;
5544		    }
5545		}
5546	    }
5547	}
5548#endif
5549    }
5550    return opt_array;
5551}
5552
5553/*
5554 * Report the character-type locale that xterm was started in.
5555 */
5556String
5557xtermEnvLocale(void)
5558{
5559    static String result;
5560
5561    if (result == 0) {
5562	if ((result = x_nonempty(setlocale(LC_CTYPE, 0))) == 0) {
5563	    result = x_strdup("C");
5564	} else {
5565	    result = x_strdup(result);
5566	}
5567	TRACE(("xtermEnvLocale ->%s\n", result));
5568    }
5569    return result;
5570}
5571
5572char *
5573xtermEnvEncoding(void)
5574{
5575    static char *result;
5576
5577    if (result == 0) {
5578#ifdef HAVE_LANGINFO_CODESET
5579	result = nl_langinfo(CODESET);
5580#else
5581	char *locale = xtermEnvLocale();
5582	if (!strcmp(locale, "C") || !strcmp(locale, "POSIX")) {
5583	    result = "ASCII";
5584	} else {
5585	    result = "ISO-8859-1";
5586	}
5587#endif
5588	TRACE(("xtermEnvEncoding ->%s\n", result));
5589    }
5590    return result;
5591}
5592
5593#if OPT_WIDE_CHARS
5594/*
5595 * Tell whether xterm was started in a locale that uses UTF-8 encoding for
5596 * characters.  That environment is inherited by subprocesses and used in
5597 * various library calls.
5598 */
5599Bool
5600xtermEnvUTF8(void)
5601{
5602    static Bool init = False;
5603    static Bool result = False;
5604
5605    if (!init) {
5606	init = True;
5607#ifdef HAVE_LANGINFO_CODESET
5608	result = (strcmp(xtermEnvEncoding(), "UTF-8") == 0);
5609#else
5610	result = (strstr(xtermEnvLocale(), "UTF-8") != NULL);
5611#endif
5612	TRACE(("xtermEnvUTF8 ->%s\n", BtoS(result)));
5613    }
5614    return result;
5615}
5616#endif /* OPT_WIDE_CHARS */
5617
5618/*
5619 * Check if the current widget, or any parent, is the VT100 "xterm" widget.
5620 */
5621XtermWidget
5622getXtermWidget(Widget w)
5623{
5624    XtermWidget xw;
5625
5626    if (w == 0) {
5627	xw = (XtermWidget) CURRENT_EMU();
5628	if (!IsXtermWidget(xw)) {
5629	    xw = 0;
5630	}
5631    } else if (IsXtermWidget(w)) {
5632	xw = (XtermWidget) w;
5633    } else {
5634	xw = getXtermWidget(XtParent(w));
5635    }
5636    TRACE2(("getXtermWidget %p -> %p\n", w, xw));
5637    return xw;
5638}
5639
5640#if OPT_SESSION_MGT
5641static void
5642die_callback(Widget w GCC_UNUSED,
5643	     XtPointer client_data GCC_UNUSED,
5644	     XtPointer call_data GCC_UNUSED)
5645{
5646    NormalExit();
5647}
5648
5649static void
5650save_callback(Widget w GCC_UNUSED,
5651	      XtPointer client_data GCC_UNUSED,
5652	      XtPointer call_data)
5653{
5654    XtCheckpointToken token = (XtCheckpointToken) call_data;
5655    /* we have nothing to save */
5656    token->save_success = True;
5657}
5658
5659static void
5660icewatch(IceConn iceConn,
5661	 IcePointer clientData GCC_UNUSED,
5662	 Bool opening,
5663	 IcePointer * watchData GCC_UNUSED)
5664{
5665    if (opening) {
5666	ice_fd = IceConnectionNumber(iceConn);
5667	TRACE(("got IceConnectionNumber %d\n", ice_fd));
5668    } else {
5669	ice_fd = -1;
5670	TRACE(("reset IceConnectionNumber\n"));
5671    }
5672}
5673
5674void
5675xtermOpenSession(void)
5676{
5677    if (resource.sessionMgt) {
5678	TRACE(("Enabling session-management callbacks\n"));
5679	XtAddCallback(toplevel, XtNdieCallback, die_callback, NULL);
5680	XtAddCallback(toplevel, XtNsaveCallback, save_callback, NULL);
5681    }
5682}
5683
5684void
5685xtermCloseSession(void)
5686{
5687    IceRemoveConnectionWatch(icewatch, NULL);
5688}
5689#endif /* OPT_SESSION_MGT */
5690
5691Widget
5692xtermOpenApplication(XtAppContext * app_context_return,
5693		     String my_class,
5694		     XrmOptionDescRec * options,
5695		     Cardinal num_options,
5696		     int *argc_in_out,
5697		     String * argv_in_out,
5698		     String * fallback_resources,
5699		     WidgetClass widget_class,
5700		     ArgList args,
5701		     Cardinal num_args)
5702{
5703    Widget result;
5704
5705    XtSetErrorHandler(xt_error);
5706#if OPT_SESSION_MGT
5707    result = XtOpenApplication(app_context_return,
5708			       my_class,
5709			       options,
5710			       num_options,
5711			       argc_in_out,
5712			       argv_in_out,
5713			       fallback_resources,
5714			       widget_class,
5715			       args,
5716			       num_args);
5717    IceAddConnectionWatch(icewatch, NULL);
5718#else
5719    result = XtAppInitialize(app_context_return,
5720			     my_class,
5721			     options,
5722			     num_options,
5723			     argc_in_out,
5724			     argv_in_out,
5725			     fallback_resources,
5726			     NULL, 0);
5727#endif /* OPT_SESSION_MGT */
5728    XtSetErrorHandler((XtErrorHandler) 0);
5729
5730    return result;
5731}
5732
5733static int x11_errors;
5734
5735static int
5736catch_x11_error(Display * display, XErrorEvent * error_event)
5737{
5738    (void) display;
5739    (void) error_event;
5740    ++x11_errors;
5741    return 0;
5742}
5743
5744Boolean
5745xtermGetWinAttrs(Display * dpy, Window win, XWindowAttributes * attrs)
5746{
5747    Boolean result = False;
5748    Status code;
5749
5750    memset(attrs, 0, sizeof(*attrs));
5751    if (win != None) {
5752	XErrorHandler save = XSetErrorHandler(catch_x11_error);
5753	x11_errors = 0;
5754	code = XGetWindowAttributes(dpy, win, attrs);
5755	XSetErrorHandler(save);
5756	result = (Boolean) ((code != 0) && !x11_errors);
5757	if (result) {
5758	    TRACE_WIN_ATTRS(attrs);
5759	} else {
5760	    xtermWarning("invalid window-id %ld\n", (long) win);
5761	}
5762    }
5763    return result;
5764}
5765
5766Boolean
5767xtermGetWinProp(Display * display,
5768		Window win,
5769		Atom property,
5770		long long_offset,
5771		long long_length,
5772		Atom req_type,
5773		Atom * actual_type_return,
5774		int *actual_format_return,
5775		unsigned long *nitems_return,
5776		unsigned long *bytes_after_return,
5777		unsigned char **prop_return)
5778{
5779    Boolean result = True;
5780
5781    if (win != None) {
5782	XErrorHandler save = XSetErrorHandler(catch_x11_error);
5783	x11_errors = 0;
5784	if (XGetWindowProperty(display,
5785			       win,
5786			       property,
5787			       long_offset,
5788			       long_length,
5789			       False,
5790			       req_type,
5791			       actual_type_return,
5792			       actual_format_return,
5793			       nitems_return,
5794			       bytes_after_return,
5795			       prop_return) == Success
5796	    && x11_errors == 0) {
5797	    result = True;
5798	}
5799	XSetErrorHandler(save);
5800    }
5801    return result;
5802}
5803
5804void
5805xtermEmbedWindow(Window winToEmbedInto)
5806{
5807    Display *dpy = XtDisplay(toplevel);
5808    XWindowAttributes attrs;
5809
5810    TRACE(("checking winToEmbedInto %#lx\n", winToEmbedInto));
5811    if (xtermGetWinAttrs(dpy, winToEmbedInto, &attrs)) {
5812	XtermWidget xw = term;
5813	TScreen *screen = TScreenOf(xw);
5814
5815	XtRealizeWidget(toplevel);
5816
5817	TRACE(("...reparenting toplevel %#lx into %#lx\n",
5818	       XtWindow(toplevel),
5819	       winToEmbedInto));
5820	XReparentWindow(dpy,
5821			XtWindow(toplevel),
5822			winToEmbedInto, 0, 0);
5823
5824	screen->embed_high = (Dimension) attrs.height;
5825	screen->embed_wide = (Dimension) attrs.width;
5826    }
5827}
5828