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