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