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