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