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