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