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