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