misc.c revision fa3f02f3
1/* $XTermId: misc.c,v 1.703 2014/03/07 01:43:32 tom Exp $ */
2
3/*
4 * Copyright 1999-2013,2014 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
59#include <sys/stat.h>
60#include <stdio.h>
61#include <stdarg.h>
62#include <signal.h>
63#include <ctype.h>
64#include <pwd.h>
65#include <sys/wait.h>
66
67#include <X11/keysym.h>
68#include <X11/Xatom.h>
69#include <X11/cursorfont.h>
70#include <X11/Xlocale.h>
71
72#include <X11/Xmu/Error.h>
73#include <X11/Xmu/SysUtil.h>
74#include <X11/Xmu/WinUtil.h>
75#include <X11/Xmu/Xmu.h>
76#if HAVE_X11_SUNKEYSYM_H
77#include <X11/Sunkeysym.h>
78#endif
79
80#ifdef HAVE_LIBXPM
81#include <X11/xpm.h>
82#endif
83
84#ifdef HAVE_LANGINFO_CODESET
85#include <langinfo.h>
86#endif
87
88#include <xutf8.h>
89
90#include <data.h>
91#include <error.h>
92#include <menu.h>
93#include <fontutils.h>
94#include <xstrings.h>
95#include <xtermcap.h>
96#include <VTparse.h>
97#include <graphics.h>
98
99#include <assert.h>
100
101#if (XtSpecificationRelease < 6)
102#ifndef X_GETTIMEOFDAY
103#define X_GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *)0)
104#endif
105#endif
106
107#ifdef VMS
108#define XTERM_VMS_LOGFILE "SYS$SCRATCH:XTERM_LOG.TXT"
109#ifdef ALLOWLOGFILEEXEC
110#undef ALLOWLOGFILEEXEC
111#endif
112#endif /* VMS */
113
114#if OPT_TEK4014
115#define OUR_EVENT(event,Type) \
116		(event.type == Type && \
117		  (event.xcrossing.window == XtWindow(XtParent(xw)) || \
118		    (tekWidget && \
119		     event.xcrossing.window == XtWindow(XtParent(tekWidget)))))
120#else
121#define OUR_EVENT(event,Type) \
122		(event.type == Type && \
123		   (event.xcrossing.window == XtWindow(XtParent(xw))))
124#endif
125
126static Boolean xtermAllocColor(XtermWidget, XColor *, const char *);
127static Cursor make_hidden_cursor(XtermWidget);
128
129static char emptyString[] = "";
130
131#if OPT_EXEC_XTERM
132/* Like readlink(2), but returns a malloc()ed buffer, or NULL on
133   error; adapted from libc docs */
134static char *
135Readlink(const char *filename)
136{
137    char *buf = NULL;
138    size_t size = 100;
139    int n;
140
141    for (;;) {
142	buf = TypeRealloc(char, size, buf);
143	memset(buf, 0, size);
144
145	n = (int) readlink(filename, buf, size);
146	if (n < 0) {
147	    free(buf);
148	    return NULL;
149	}
150
151	if ((unsigned) n < size) {
152	    return buf;
153	}
154
155	size *= 2;
156    }
157}
158#endif /* OPT_EXEC_XTERM */
159
160static void
161Sleep(int msec)
162{
163    static struct timeval select_timeout;
164
165    select_timeout.tv_sec = 0;
166    select_timeout.tv_usec = msec * 1000;
167    select(0, 0, 0, 0, &select_timeout);
168}
169
170static void
171selectwindow(XtermWidget xw, int flag)
172{
173    TScreen *screen = TScreenOf(xw);
174
175    TRACE(("selectwindow(%d) flag=%d\n", screen->select, flag));
176
177#if OPT_TEK4014
178    if (TEK4014_ACTIVE(xw)) {
179	if (!Ttoggled)
180	    TCursorToggle(tekWidget, TOGGLE);
181	screen->select |= flag;
182	if (!Ttoggled)
183	    TCursorToggle(tekWidget, TOGGLE);
184    } else
185#endif
186    {
187#if OPT_I18N_SUPPORT && OPT_INPUT_METHOD
188	TInput *input = lookupTInput(xw, (Widget) xw);
189	if (input && input->xic)
190	    XSetICFocus(input->xic);
191#endif
192
193	if (screen->cursor_state && CursorMoved(screen))
194	    HideCursor();
195	screen->select |= flag;
196	if (screen->cursor_state)
197	    ShowCursor();
198    }
199    GetScrollLock(screen);
200}
201
202static void
203unselectwindow(XtermWidget xw, int flag)
204{
205    TScreen *screen = TScreenOf(xw);
206
207    TRACE(("unselectwindow(%d) flag=%d\n", screen->select, flag));
208
209    if (screen->hide_pointer && screen->pointer_mode < pFocused) {
210	screen->hide_pointer = False;
211	xtermDisplayCursor(xw);
212    }
213
214    if (!screen->always_highlight) {
215#if OPT_TEK4014
216	if (TEK4014_ACTIVE(xw)) {
217	    if (!Ttoggled)
218		TCursorToggle(tekWidget, TOGGLE);
219	    screen->select &= ~flag;
220	    if (!Ttoggled)
221		TCursorToggle(tekWidget, TOGGLE);
222	} else
223#endif
224	{
225#if OPT_I18N_SUPPORT && OPT_INPUT_METHOD
226	    TInput *input = lookupTInput(xw, (Widget) xw);
227	    if (input && input->xic)
228		XUnsetICFocus(input->xic);
229#endif
230
231	    screen->select &= ~flag;
232	    if (screen->cursor_state && CursorMoved(screen))
233		HideCursor();
234	    if (screen->cursor_state)
235		ShowCursor();
236	}
237    }
238}
239
240static void
241DoSpecialEnterNotify(XtermWidget xw, XEnterWindowEvent * ev)
242{
243    TScreen *screen = TScreenOf(xw);
244
245    TRACE(("DoSpecialEnterNotify(%d)\n", screen->select));
246    TRACE_FOCUS(xw, ev);
247    if (((ev->detail) != NotifyInferior) &&
248	ev->focus &&
249	!(screen->select & FOCUS))
250	selectwindow(xw, INWINDOW);
251}
252
253static void
254DoSpecialLeaveNotify(XtermWidget xw, XEnterWindowEvent * ev)
255{
256    TScreen *screen = TScreenOf(xw);
257
258    TRACE(("DoSpecialLeaveNotify(%d)\n", screen->select));
259    TRACE_FOCUS(xw, ev);
260    if (((ev->detail) != NotifyInferior) &&
261	ev->focus &&
262	!(screen->select & FOCUS))
263	unselectwindow(xw, INWINDOW);
264}
265
266#ifndef XUrgencyHint
267#define XUrgencyHint (1L << 8)	/* X11R5 does not define */
268#endif
269
270static void
271setXUrgency(XtermWidget xw, Bool enable)
272{
273    TScreen *screen = TScreenOf(xw);
274
275    if (screen->bellIsUrgent) {
276	XWMHints *h = XGetWMHints(screen->display, VShellWindow(xw));
277	if (h != 0) {
278	    if (enable && !(screen->select & FOCUS)) {
279		h->flags |= XUrgencyHint;
280	    } else {
281		h->flags &= ~XUrgencyHint;
282	    }
283	    XSetWMHints(screen->display, VShellWindow(xw), h);
284	}
285    }
286}
287
288void
289do_xevents(void)
290{
291    TScreen *screen = TScreenOf(term);
292
293    if (xtermAppPending()
294	||
295#if defined(VMS) || defined(__VMS)
296	screen->display->qlen > 0
297#else
298	GetBytesAvailable(ConnectionNumber(screen->display)) > 0
299#endif
300	)
301	xevents();
302}
303
304void
305xtermDisplayCursor(XtermWidget xw)
306{
307    TScreen *screen = TScreenOf(xw);
308
309    if (screen->Vshow) {
310	if (screen->hide_pointer) {
311	    TRACE(("Display hidden_cursor\n"));
312	    XDefineCursor(screen->display, VWindow(screen), screen->hidden_cursor);
313	} else {
314	    TRACE(("Display pointer_cursor\n"));
315	    recolor_cursor(screen,
316			   screen->pointer_cursor,
317			   T_COLOR(screen, MOUSE_FG),
318			   T_COLOR(screen, MOUSE_BG));
319	    XDefineCursor(screen->display, VWindow(screen), screen->pointer_cursor);
320	}
321    }
322}
323
324void
325xtermShowPointer(XtermWidget xw, Bool enable)
326{
327    static int tried = -1;
328    TScreen *screen = TScreenOf(xw);
329
330#if OPT_TEK4014
331    if (TEK4014_SHOWN(xw))
332	enable = True;
333#endif
334
335    /*
336     * Whether we actually hide the pointer depends on the pointer-mode and
337     * the mouse-mode:
338     */
339    if (!enable) {
340	switch (screen->pointer_mode) {
341	case pNever:
342	    enable = True;
343	    break;
344	case pNoMouse:
345	    if (screen->send_mouse_pos != MOUSE_OFF)
346		enable = True;
347	    break;
348	case pAlways:
349	case pFocused:
350	    break;
351	}
352    }
353
354    if (enable) {
355	if (screen->hide_pointer) {
356	    screen->hide_pointer = False;
357	    xtermDisplayCursor(xw);
358	    switch (screen->send_mouse_pos) {
359	    case ANY_EVENT_MOUSE:
360		break;
361	    default:
362		MotionOff(screen, xw);
363		break;
364	    }
365	}
366    } else if (!(screen->hide_pointer) && (tried <= 0)) {
367	if (screen->hidden_cursor == 0) {
368	    screen->hidden_cursor = make_hidden_cursor(xw);
369	}
370	if (screen->hidden_cursor == 0) {
371	    tried = 1;
372	} else {
373	    tried = 0;
374	    screen->hide_pointer = True;
375	    xtermDisplayCursor(xw);
376	    MotionOn(screen, xw);
377	}
378    }
379}
380
381#if OPT_TRACE
382static void
383TraceExposeEvent(XEvent * arg)
384{
385    XExposeEvent *event = (XExposeEvent *) arg;
386
387    TRACE(("pending Expose %ld %d: %d,%d %dx%d %#lx\n",
388	   event->serial,
389	   event->count,
390	   event->y,
391	   event->x,
392	   event->height,
393	   event->width,
394	   event->window));
395}
396
397#else
398#define TraceExposeEvent(event)	/* nothing */
399#endif
400
401/* true if p contains q */
402#define ExposeContains(p,q) \
403	    ((p)->y <= (q)->y \
404	  && (p)->x <= (q)->x \
405	  && ((p)->y + (p)->height) >= ((q)->y + (q)->height) \
406	  && ((p)->x + (p)->width) >= ((q)->x + (q)->width))
407
408static XtInputMask
409mergeExposeEvents(XEvent * target)
410{
411    XEvent next_event;
412    XExposeEvent *p, *q;
413
414    TRACE(("pending Expose...?\n"));
415    TraceExposeEvent(target);
416    XtAppNextEvent(app_con, target);
417    p = (XExposeEvent *) target;
418
419    while (XtAppPending(app_con)
420	   && XtAppPeekEvent(app_con, &next_event)
421	   && next_event.type == Expose) {
422	Boolean merge_this = False;
423
424	TraceExposeEvent(&next_event);
425	q = (XExposeEvent *) (&next_event);
426	XtAppNextEvent(app_con, &next_event);
427
428	/*
429	 * If either window is contained within the other, merge the events.
430	 * The traces show that there are also cases where a full repaint of
431	 * a window is broken into 3 or more rectangles, which do not arrive
432	 * in the same instant.  We could merge those if xterm were modified
433	 * to skim several events ahead.
434	 */
435	if (p->window == q->window) {
436	    if (ExposeContains(p, q)) {
437		TRACE(("pending Expose...merged forward\n"));
438		merge_this = True;
439		next_event = *target;
440	    } else if (ExposeContains(q, p)) {
441		TRACE(("pending Expose...merged backward\n"));
442		merge_this = True;
443	    }
444	}
445	if (!merge_this) {
446	    XtDispatchEvent(target);
447	}
448	*target = next_event;
449    }
450    XtDispatchEvent(target);
451    return XtAppPending(app_con);
452}
453
454#if OPT_TRACE
455static void
456TraceConfigureEvent(XEvent * arg)
457{
458    XConfigureEvent *event = (XConfigureEvent *) arg;
459
460    TRACE(("pending Configure %ld %d,%d %dx%d %#lx\n",
461	   event->serial,
462	   event->y,
463	   event->x,
464	   event->height,
465	   event->width,
466	   event->window));
467}
468
469#else
470#define TraceConfigureEvent(event)	/* nothing */
471#endif
472
473/*
474 * On entry, we have peeked at the event queue and see a configure-notify
475 * event.  Remove that from the queue so we can look further.
476 *
477 * Then, as long as there is a configure-notify event in the queue, remove
478 * that.  If the adjacent events are for different windows, process the older
479 * event and update the event used for comparing windows.  If they are for the
480 * same window, only the newer event is of interest.
481 *
482 * Finally, process the (remaining) configure-notify event.
483 */
484static XtInputMask
485mergeConfigureEvents(XEvent * target)
486{
487    XEvent next_event;
488    XConfigureEvent *p, *q;
489
490    XtAppNextEvent(app_con, target);
491    p = (XConfigureEvent *) target;
492
493    TRACE(("pending Configure...?%s\n", XtAppPending(app_con) ? "yes" : "no"));
494    TraceConfigureEvent(target);
495
496    if (XtAppPending(app_con)
497	&& XtAppPeekEvent(app_con, &next_event)
498	&& next_event.type == ConfigureNotify) {
499	Boolean merge_this = False;
500
501	TraceConfigureEvent(&next_event);
502	XtAppNextEvent(app_con, &next_event);
503	q = (XConfigureEvent *) (&next_event);
504
505	if (p->window == q->window) {
506	    TRACE(("pending Configure...merged\n"));
507	    merge_this = True;
508	}
509	if (!merge_this) {
510	    TRACE(("pending Configure...skipped\n"));
511	    XtDispatchEvent(target);
512	}
513	*target = next_event;
514    }
515    XtDispatchEvent(target);
516    return XtAppPending(app_con);
517}
518
519/*
520 * Filter redundant Expose- and ConfigureNotify-events.  This is limited to
521 * adjacent events because there could be other event-loop processing.  Absent
522 * that limitation, it might be possible to scan ahead to find when the screen
523 * would be completely updated, skipping unnecessary re-repainting before that
524 * point.
525 *
526 * Note: all cases should allow doing XtAppNextEvent if result is true.
527 */
528XtInputMask
529xtermAppPending(void)
530{
531    XtInputMask result = XtAppPending(app_con);
532    XEvent this_event;
533
534    while (result && XtAppPeekEvent(app_con, &this_event)) {
535	if (this_event.type == Expose) {
536	    result = mergeExposeEvents(&this_event);
537	    TRACE(("got merged expose events\n"));
538	} else if (this_event.type == ConfigureNotify) {
539	    result = mergeConfigureEvents(&this_event);
540	    TRACE(("got merged configure notify events\n"));
541	} else {
542	    TRACE(("pending %s\n", visibleEventType(this_event.type)));
543	    break;
544	}
545    }
546    return result;
547}
548
549void
550xevents(void)
551{
552    XtermWidget xw = term;
553    TScreen *screen = TScreenOf(xw);
554    XEvent event;
555    XtInputMask input_mask;
556
557    if (need_cleanup)
558	NormalExit();
559
560    if (screen->scroll_amt)
561	FlushScroll(xw);
562    /*
563     * process timeouts, relying on the fact that XtAppProcessEvent
564     * will process the timeout and return without blockng on the
565     * XEvent queue.  Other sources i.e., the pty are handled elsewhere
566     * with select().
567     */
568    while ((input_mask = xtermAppPending()) != 0) {
569	if (input_mask & XtIMTimer)
570	    XtAppProcessEvent(app_con, (XtInputMask) XtIMTimer);
571#if OPT_SESSION_MGT
572	/*
573	 * Session management events are alternative input events. Deal with
574	 * them in the same way.
575	 */
576	else if (input_mask & XtIMAlternateInput)
577	    XtAppProcessEvent(app_con, (XtInputMask) XtIMAlternateInput);
578#endif
579	else
580	    break;
581    }
582
583    /*
584     * If there's no XEvents, don't wait around...
585     */
586    if ((input_mask & XtIMXEvent) != XtIMXEvent)
587	return;
588    do {
589	/*
590	 * This check makes xterm hang when in mouse hilite tracking mode.
591	 * We simply ignore all events except for those not passed down to
592	 * this function, e.g., those handled in in_put().
593	 */
594	if (screen->waitingForTrackInfo) {
595	    Sleep(10);
596	    return;
597	}
598	XtAppNextEvent(app_con, &event);
599	/*
600	 * Hack to get around problems with the toolkit throwing away
601	 * eventing during the exclusive grab of the menu popup.  By
602	 * looking at the event ourselves we make sure that we can
603	 * do the right thing.
604	 */
605	if (OUR_EVENT(event, EnterNotify)) {
606	    DoSpecialEnterNotify(xw, &event.xcrossing);
607	} else if (OUR_EVENT(event, LeaveNotify)) {
608	    DoSpecialLeaveNotify(xw, &event.xcrossing);
609	} else if ((screen->send_mouse_pos == ANY_EVENT_MOUSE
610#if OPT_DEC_LOCATOR
611		    || screen->send_mouse_pos == DEC_LOCATOR
612#endif /* OPT_DEC_LOCATOR */
613		   )
614		   && event.xany.type == MotionNotify
615		   && event.xcrossing.window == XtWindow(xw)) {
616	    SendMousePosition(xw, &event);
617	    xtermShowPointer(xw, True);
618	    continue;
619	}
620
621	/*
622	 * If the event is interesting (and not a keyboard event), turn the
623	 * mouse pointer back on.
624	 */
625	if (screen->hide_pointer) {
626	    if (screen->pointer_mode >= pFocused) {
627		switch (event.xany.type) {
628		case MotionNotify:
629		    xtermShowPointer(xw, True);
630		    break;
631		}
632	    } else {
633		switch (event.xany.type) {
634		case KeyPress:
635		case KeyRelease:
636		case ButtonPress:
637		case ButtonRelease:
638		    /* also these... */
639		case Expose:
640		case NoExpose:
641		case PropertyNotify:
642		case ClientMessage:
643		    break;
644		default:
645		    xtermShowPointer(xw, True);
646		    break;
647		}
648	    }
649	}
650
651	if (!event.xany.send_event ||
652	    screen->allowSendEvents ||
653	    ((event.xany.type != KeyPress) &&
654	     (event.xany.type != KeyRelease) &&
655	     (event.xany.type != ButtonPress) &&
656	     (event.xany.type != ButtonRelease))) {
657
658	    XtDispatchEvent(&event);
659	}
660    } while (xtermAppPending() & XtIMXEvent);
661}
662
663static Cursor
664make_hidden_cursor(XtermWidget xw)
665{
666    TScreen *screen = TScreenOf(xw);
667    Cursor c;
668    Display *dpy = screen->display;
669    XFontStruct *fn;
670
671    static XColor dummy;
672
673    /*
674     * Prefer nil2 (which is normally available) to "fixed" (which is supposed
675     * to be "always" available), since it's a smaller glyph in case the
676     * server insists on drawing _something_.
677     */
678    TRACE(("Ask for nil2 font\n"));
679    if ((fn = XLoadQueryFont(dpy, "nil2")) == 0) {
680	TRACE(("...Ask for fixed font\n"));
681	fn = XLoadQueryFont(dpy, DEFFONT);
682    }
683
684    if (fn != 0) {
685	/* a space character seems to work as a cursor (dots are not needed) */
686	c = XCreateGlyphCursor(dpy, fn->fid, fn->fid, 'X', ' ', &dummy, &dummy);
687	XFreeFont(dpy, fn);
688    } else {
689	c = 0;
690    }
691    TRACE(("XCreateGlyphCursor ->%#lx\n", c));
692    return (c);
693}
694
695/*
696 * Xlib uses Xcursor to customize cursor coloring, which interferes with
697 * xterm's pointerColor resource.  Work around this by providing our own
698 * default theme.  Testing seems to show that we only have to provide this
699 * until the window is initialized.
700 */
701void
702init_colored_cursor(void)
703{
704#ifdef HAVE_LIB_XCURSOR
705    const char *theme = "index.theme";
706    const char *pattern = "xtermXXXXXX";
707    const char *tmp_dir;
708    char *filename;
709    char *env = getenv("XCURSOR_THEME");
710    size_t needed;
711    FILE *fp;
712
713    xterm_cursor_theme = 0;
714    if (IsEmpty(env)) {
715	if ((tmp_dir = getenv("TMPDIR")) == 0) {
716	    tmp_dir = P_tmpdir;
717	}
718	needed = strlen(tmp_dir) + 4 + strlen(theme) + strlen(pattern);
719	if ((filename = malloc(needed)) != 0) {
720	    sprintf(filename, "%s/%s", tmp_dir, pattern);
721
722#ifdef HAVE_MKDTEMP
723	    xterm_cursor_theme = mkdtemp(filename);
724#else
725	    if (mktemp(filename) != 0
726		&& mkdir(filename, 0700) == 0) {
727		xterm_cursor_theme = filename;
728	    }
729#endif
730	    /*
731	     * Actually, Xcursor does what _we_ want just by steering its
732	     * search path away from home.  We are setting up the complete
733	     * theme just in case the library ever acquires a maintainer.
734	     */
735	    if (xterm_cursor_theme != 0) {
736		char *leaf = xterm_cursor_theme + strlen(xterm_cursor_theme);
737		strcat(leaf, "/");
738		strcat(leaf, theme);
739		if ((fp = fopen(xterm_cursor_theme, "w")) != 0) {
740		    fprintf(fp, "[Icon Theme]\n");
741		    fclose(fp);
742		    *leaf = '\0';
743		    xtermSetenv("XCURSOR_PATH", xterm_cursor_theme);
744		    *leaf = '/';
745		}
746		atexit(cleanup_colored_cursor);
747	    }
748	}
749    }
750#endif /* HAVE_LIB_XCURSOR */
751}
752
753/*
754 * Once done, discard the file and directory holding it.
755 */
756void
757cleanup_colored_cursor(void)
758{
759#ifdef HAVE_LIB_XCURSOR
760    if (xterm_cursor_theme != 0) {
761	char *my_path = getenv("XCURSOR_PATH");
762	struct stat sb;
763	if (!IsEmpty(my_path)
764	    && stat(my_path, &sb) == 0
765	    && (sb.st_mode & S_IFMT) == S_IFDIR) {
766	    unlink(xterm_cursor_theme);
767	    rmdir(my_path);
768	    free(xterm_cursor_theme);
769	    xterm_cursor_theme = 0;
770	}
771    }
772#endif /* HAVE_LIB_XCURSOR */
773}
774
775Cursor
776make_colored_cursor(unsigned cursorindex,	/* index into font */
777		    unsigned long fg,	/* pixel value */
778		    unsigned long bg)	/* pixel value */
779{
780    TScreen *screen = TScreenOf(term);
781    Cursor c;
782    Display *dpy = screen->display;
783
784    c = XCreateFontCursor(dpy, cursorindex);
785    if (c != None) {
786	recolor_cursor(screen, c, fg, bg);
787    }
788    return (c);
789}
790
791/* ARGSUSED */
792void
793HandleKeyPressed(Widget w GCC_UNUSED,
794		 XEvent * event,
795		 String *params GCC_UNUSED,
796		 Cardinal *nparams GCC_UNUSED)
797{
798    TRACE(("Handle insert-seven-bit for %p\n", (void *) w));
799    Input(term, &event->xkey, False);
800}
801
802/* ARGSUSED */
803void
804HandleEightBitKeyPressed(Widget w GCC_UNUSED,
805			 XEvent * event,
806			 String *params GCC_UNUSED,
807			 Cardinal *nparams GCC_UNUSED)
808{
809    TRACE(("Handle insert-eight-bit for %p\n", (void *) w));
810    Input(term, &event->xkey, True);
811}
812
813/* ARGSUSED */
814void
815HandleStringEvent(Widget w GCC_UNUSED,
816		  XEvent * event GCC_UNUSED,
817		  String *params,
818		  Cardinal *nparams)
819{
820
821    if (*nparams != 1)
822	return;
823
824    if ((*params)[0] == '0' && (*params)[1] == 'x' && (*params)[2] != '\0') {
825	const char *abcdef = "ABCDEF";
826	const char *xxxxxx;
827	Char c;
828	UString p;
829	unsigned value = 0;
830
831	for (p = (UString) (*params + 2); (c = CharOf(x_toupper(*p))) !=
832	     '\0'; p++) {
833	    value *= 16;
834	    if (c >= '0' && c <= '9')
835		value += (unsigned) (c - '0');
836	    else if ((xxxxxx = (strchr) (abcdef, c)) != 0)
837		value += (unsigned) (xxxxxx - abcdef) + 10;
838	    else
839		break;
840	}
841	if (c == '\0') {
842	    Char hexval[2];
843	    hexval[0] = (Char) value;
844	    hexval[1] = 0;
845	    StringInput(term, hexval, (size_t) 1);
846	}
847    } else {
848	StringInput(term, (const Char *) *params, strlen(*params));
849    }
850}
851
852#if OPT_EXEC_XTERM
853
854#ifndef PROCFS_ROOT
855#define PROCFS_ROOT "/proc"
856#endif
857
858/* ARGSUSED */
859void
860HandleSpawnTerminal(Widget w GCC_UNUSED,
861		    XEvent * event GCC_UNUSED,
862		    String *params,
863		    Cardinal *nparams)
864{
865    TScreen *screen = TScreenOf(term);
866    char *child_cwd = NULL;
867    char *child_exe;
868    pid_t pid;
869
870    /*
871     * Try to find the actual program which is running in the child process.
872     * This works for Linux.  If we cannot find the program, fall back to the
873     * xterm program (which is usually adequate).  Give up if we are given only
874     * a relative path to xterm, since that would not always match $PATH.
875     */
876    child_exe = Readlink(PROCFS_ROOT "/self/exe");
877    if (!child_exe) {
878	if (strncmp(ProgramName, "./", (size_t) 2)
879	    && strncmp(ProgramName, "../", (size_t) 3)) {
880	    child_exe = xtermFindShell(ProgramName, True);
881	} else {
882	    xtermWarning("Cannot exec-xterm given \"%s\"\n", ProgramName);
883	}
884	if (child_exe == 0)
885	    return;
886    }
887
888    /*
889     * Determine the current working directory of the child so that we can
890     * spawn a new terminal in the same directory.
891     *
892     * If we cannot get the CWD of the child, just use our own.
893     */
894    if (screen->pid) {
895	char child_cwd_link[sizeof(PROCFS_ROOT) + 80];
896	sprintf(child_cwd_link, PROCFS_ROOT "/%lu/cwd", (unsigned long) screen->pid);
897	child_cwd = Readlink(child_cwd_link);
898    }
899
900    /* The reaper will take care of cleaning up the child */
901    pid = fork();
902    if (pid == -1) {
903	xtermWarning("Could not fork: %s\n", SysErrorMsg(errno));
904    } else if (!pid) {
905	/* We are the child */
906	if (child_cwd) {
907	    IGNORE_RC(chdir(child_cwd));	/* We don't care if this fails */
908	}
909
910	if (setuid(screen->uid) == -1
911	    || setgid(screen->gid) == -1) {
912	    xtermWarning("Cannot reset uid/gid\n");
913	} else {
914	    unsigned myargc = *nparams + 1;
915	    char **myargv = TypeMallocN(char *, myargc + 1);
916	    unsigned n = 0;
917
918	    myargv[n++] = child_exe;
919
920	    while (n < myargc) {
921		myargv[n++] = (char *) *params++;
922	    }
923
924	    myargv[n] = 0;
925	    execv(child_exe, myargv);
926
927	    /* If we get here, we've failed */
928	    xtermWarning("exec of '%s': %s\n", child_exe, SysErrorMsg(errno));
929	}
930	_exit(0);
931    }
932
933    /* We are the parent; clean up */
934    if (child_cwd)
935	free(child_cwd);
936    free(child_exe);
937}
938#endif /* OPT_EXEC_XTERM */
939
940/*
941 * Rather than sending characters to the host, put them directly into our
942 * input queue.  That lets a user have access to any of the control sequences
943 * for a key binding.  This is the equivalent of local function key support.
944 *
945 * NOTE:  This code does not support the hexadecimal kludge used in
946 * HandleStringEvent because it prevents us from sending an arbitrary string
947 * (but it appears in a lot of examples - so we are stuck with it).  The
948 * standard string converter does recognize "\" for newline ("\n") and for
949 * octal constants (e.g., "\007" for BEL).  So we assume the user can make do
950 * without a specialized converter.  (Don't try to use \000, though).
951 */
952/* ARGSUSED */
953void
954HandleInterpret(Widget w GCC_UNUSED,
955		XEvent * event GCC_UNUSED,
956		String *params,
957		Cardinal *param_count)
958{
959    if (*param_count == 1) {
960	const char *value = params[0];
961	int need = (int) strlen(value);
962	int used = (int) (VTbuffer->next - VTbuffer->buffer);
963	int have = (int) (VTbuffer->last - VTbuffer->buffer);
964
965	if (have - used + need < BUF_SIZE) {
966
967	    fillPtyData(term, VTbuffer, value, (int) strlen(value));
968
969	    TRACE(("Interpret %s\n", value));
970	    VTbuffer->update++;
971	}
972    }
973}
974
975/*ARGSUSED*/
976void
977HandleEnterWindow(Widget w GCC_UNUSED,
978		  XtPointer eventdata GCC_UNUSED,
979		  XEvent * event GCC_UNUSED,
980		  Boolean *cont GCC_UNUSED)
981{
982    /* NOP since we handled it above */
983    TRACE(("HandleEnterWindow ignored\n"));
984    TRACE_FOCUS(w, event);
985}
986
987/*ARGSUSED*/
988void
989HandleLeaveWindow(Widget w GCC_UNUSED,
990		  XtPointer eventdata GCC_UNUSED,
991		  XEvent * event GCC_UNUSED,
992		  Boolean *cont GCC_UNUSED)
993{
994    /* NOP since we handled it above */
995    TRACE(("HandleLeaveWindow ignored\n"));
996    TRACE_FOCUS(w, event);
997}
998
999/*ARGSUSED*/
1000void
1001HandleFocusChange(Widget w GCC_UNUSED,
1002		  XtPointer eventdata GCC_UNUSED,
1003		  XEvent * ev,
1004		  Boolean *cont GCC_UNUSED)
1005{
1006    XFocusChangeEvent *event = (XFocusChangeEvent *) ev;
1007    XtermWidget xw = term;
1008    TScreen *screen = TScreenOf(xw);
1009
1010    TRACE(("HandleFocusChange type=%s, mode=%s, detail=%s\n",
1011	   visibleEventType(event->type),
1012	   visibleNotifyMode(event->mode),
1013	   visibleNotifyDetail(event->detail)));
1014    TRACE_FOCUS(xw, event);
1015
1016    if (screen->quiet_grab
1017	&& (event->mode == NotifyGrab || event->mode == NotifyUngrab)) {
1018	/* EMPTY */ ;
1019    } else if ((event->type == FocusIn || event->type == FocusOut)
1020	       && event->detail == NotifyPointer) {
1021	/*
1022	 * NotifyPointer is sent to the window where the pointer is, and is
1023	 * in addition to events sent to the old/new focus-windows.
1024	 */
1025	/* EMPTY */ ;
1026    } else if (event->type == FocusIn) {
1027	setXUrgency(xw, False);
1028
1029	/*
1030	 * NotifyNonlinear only happens (on FocusIn) if the pointer was not in
1031	 * one of our windows.  Use this to reset a case where one xterm is
1032	 * partly obscuring another, and X gets (us) confused about whether the
1033	 * pointer was in the window.  In particular, this can happen if the
1034	 * user is resizing the obscuring window, causing some events to not be
1035	 * delivered to the obscured window.
1036	 */
1037	if (event->detail == NotifyNonlinear
1038	    && (screen->select & INWINDOW) != 0) {
1039	    unselectwindow(xw, INWINDOW);
1040	}
1041	selectwindow(xw,
1042		     ((event->detail == NotifyPointer)
1043		      ? INWINDOW
1044		      : FOCUS));
1045	SendFocusButton(xw, event);
1046    } else {
1047#if OPT_FOCUS_EVENT
1048	if (event->type == FocusOut) {
1049	    SendFocusButton(xw, event);
1050	}
1051#endif
1052	/*
1053	 * XGrabKeyboard() will generate NotifyGrab event that we want to
1054	 * ignore.
1055	 */
1056	if (event->mode != NotifyGrab) {
1057	    unselectwindow(xw,
1058			   ((event->detail == NotifyPointer)
1059			    ? INWINDOW
1060			    : FOCUS));
1061	}
1062	if (screen->grabbedKbd && (event->mode == NotifyUngrab)) {
1063	    Bell(xw, XkbBI_Info, 100);
1064	    ReverseVideo(xw);
1065	    screen->grabbedKbd = False;
1066	    update_securekbd();
1067	}
1068    }
1069}
1070
1071static long lastBellTime;	/* in milliseconds */
1072
1073#if defined(HAVE_XKB_BELL_EXT)
1074static Atom
1075AtomBell(XtermWidget xw, int which)
1076{
1077#define DATA(name) { XkbBI_##name, XkbBN_##name }
1078    static struct {
1079	int value;
1080	const char *name;
1081    } table[] = {
1082	DATA(Info),
1083	    DATA(MarginBell),
1084	    DATA(MinorError),
1085	    DATA(TerminalBell)
1086    };
1087    Cardinal n;
1088    Atom result = None;
1089
1090    for (n = 0; n < XtNumber(table); ++n) {
1091	if (table[n].value == which) {
1092	    result = XInternAtom(XtDisplay(xw), table[n].name, False);
1093	    break;
1094	}
1095    }
1096    return result;
1097}
1098#endif
1099
1100void
1101xtermBell(XtermWidget xw, int which, int percent)
1102{
1103    TScreen *screen = TScreenOf(xw);
1104#if defined(HAVE_XKB_BELL_EXT)
1105    Atom tony = AtomBell(xw, which);
1106#endif
1107
1108    switch (which) {
1109    case XkbBI_Info:
1110    case XkbBI_MinorError:
1111    case XkbBI_MajorError:
1112    case XkbBI_TerminalBell:
1113	switch (screen->warningVolume) {
1114	case bvOff:
1115	    percent = -100;
1116	    break;
1117	case bvLow:
1118	    break;
1119	case bvHigh:
1120	    percent = 100;
1121	    break;
1122	}
1123	break;
1124    case XkbBI_MarginBell:
1125	switch (screen->marginVolume) {
1126	case bvOff:
1127	    percent = -100;
1128	    break;
1129	case bvLow:
1130	    break;
1131	case bvHigh:
1132	    percent = 100;
1133	    break;
1134	}
1135	break;
1136    default:
1137	break;
1138    }
1139
1140#if defined(HAVE_XKB_BELL_EXT)
1141    if (tony != None) {
1142	XkbBell(screen->display, VShellWindow(xw), percent, tony);
1143    } else
1144#endif
1145	XBell(screen->display, percent);
1146}
1147
1148void
1149Bell(XtermWidget xw, int which, int percent)
1150{
1151    TScreen *screen = TScreenOf(xw);
1152    struct timeval curtime;
1153    long now_msecs;
1154
1155    TRACE(("BELL %d %d%%\n", which, percent));
1156    if (!XtIsRealized((Widget) xw)) {
1157	return;
1158    }
1159
1160    setXUrgency(xw, True);
1161
1162    /* has enough time gone by that we are allowed to ring
1163       the bell again? */
1164    if (screen->bellSuppressTime) {
1165	if (screen->bellInProgress) {
1166	    do_xevents();
1167	    if (screen->bellInProgress) {	/* even after new events? */
1168		return;
1169	    }
1170	}
1171	X_GETTIMEOFDAY(&curtime);
1172	now_msecs = 1000 * curtime.tv_sec + curtime.tv_usec / 1000;
1173	if (lastBellTime != 0 && now_msecs - lastBellTime >= 0 &&
1174	    now_msecs - lastBellTime < screen->bellSuppressTime) {
1175	    return;
1176	}
1177	lastBellTime = now_msecs;
1178    }
1179
1180    if (screen->visualbell) {
1181	VisualBell();
1182    } else {
1183	xtermBell(xw, which, percent);
1184    }
1185
1186    if (screen->poponbell)
1187	XRaiseWindow(screen->display, VShellWindow(xw));
1188
1189    if (screen->bellSuppressTime) {
1190	/* now we change a property and wait for the notify event to come
1191	   back.  If the server is suspending operations while the bell
1192	   is being emitted (problematic for audio bell), this lets us
1193	   know when the previous bell has finished */
1194	Widget w = CURRENT_EMU();
1195	XChangeProperty(XtDisplay(w), XtWindow(w),
1196			XA_NOTICE, XA_NOTICE, 8, PropModeAppend, NULL, 0);
1197	screen->bellInProgress = True;
1198    }
1199}
1200
1201#define VB_DELAY screen->visualBellDelay
1202
1203static void
1204flashWindow(TScreen *screen, Window window, GC visualGC, unsigned width, unsigned height)
1205{
1206    int y = 0;
1207    int x = 0;
1208
1209    if (screen->flash_line) {
1210	y = CursorY(screen, screen->cur_row);
1211	height = (unsigned) FontHeight(screen);
1212    }
1213    XFillRectangle(screen->display, window, visualGC, x, y, width, height);
1214    XFlush(screen->display);
1215    Sleep(VB_DELAY);
1216    XFillRectangle(screen->display, window, visualGC, x, y, width, height);
1217}
1218
1219void
1220VisualBell(void)
1221{
1222    TScreen *screen = TScreenOf(term);
1223
1224    if (VB_DELAY > 0) {
1225	Pixel xorPixel = (T_COLOR(screen, TEXT_FG) ^
1226			  T_COLOR(screen, TEXT_BG));
1227	XGCValues gcval;
1228	GC visualGC;
1229
1230	gcval.function = GXxor;
1231	gcval.foreground = xorPixel;
1232	visualGC = XtGetGC((Widget) term, GCFunction + GCForeground, &gcval);
1233#if OPT_TEK4014
1234	if (TEK4014_ACTIVE(term)) {
1235	    TekScreen *tekscr = TekScreenOf(tekWidget);
1236	    flashWindow(screen, TWindow(tekscr), visualGC,
1237			TFullWidth(tekscr),
1238			TFullHeight(tekscr));
1239	} else
1240#endif
1241	{
1242	    flashWindow(screen, VWindow(screen), visualGC,
1243			FullWidth(screen),
1244			FullHeight(screen));
1245	}
1246	XtReleaseGC((Widget) term, visualGC);
1247    }
1248}
1249
1250/* ARGSUSED */
1251void
1252HandleBellPropertyChange(Widget w GCC_UNUSED,
1253			 XtPointer data GCC_UNUSED,
1254			 XEvent * ev,
1255			 Boolean *more GCC_UNUSED)
1256{
1257    TScreen *screen = TScreenOf(term);
1258
1259    if (ev->xproperty.atom == XA_NOTICE) {
1260	screen->bellInProgress = False;
1261    }
1262}
1263
1264void
1265xtermWarning(const char *fmt,...)
1266{
1267    int save_err = errno;
1268    va_list ap;
1269
1270    TRACE(("xtermWarning fmt='%s'\n", fmt));
1271    fprintf(stderr, "%s: ", ProgramName);
1272    va_start(ap, fmt);
1273    vfprintf(stderr, fmt, ap);
1274    (void) fflush(stderr);
1275
1276    va_end(ap);
1277    errno = save_err;
1278}
1279
1280void
1281xtermPerror(const char *fmt,...)
1282{
1283    int save_err = errno;
1284    char *msg = strerror(errno);
1285    va_list ap;
1286
1287    TRACE(("xtermPerror fmt='%s', msg='%s'\n", fmt, NonNull(msg)));
1288    fprintf(stderr, "%s: ", ProgramName);
1289    va_start(ap, fmt);
1290    vfprintf(stderr, fmt, ap);
1291    fprintf(stderr, ": %s\n", msg);
1292    (void) fflush(stderr);
1293
1294    va_end(ap);
1295    errno = save_err;
1296}
1297
1298Window
1299WMFrameWindow(XtermWidget xw)
1300{
1301    Window win_root, win_current, *children;
1302    Window win_parent = 0;
1303    unsigned int nchildren;
1304
1305    win_current = XtWindow(xw);
1306
1307    /* find the parent which is child of root */
1308    do {
1309	if (win_parent)
1310	    win_current = win_parent;
1311	XQueryTree(TScreenOf(xw)->display,
1312		   win_current,
1313		   &win_root,
1314		   &win_parent,
1315		   &children,
1316		   &nchildren);
1317	XFree(children);
1318    } while (win_root != win_parent);
1319
1320    return win_current;
1321}
1322
1323#if OPT_DABBREV
1324/*
1325 * The following code implements `dynamic abbreviation' expansion a la
1326 * Emacs.  It looks in the preceding visible screen and its scrollback
1327 * to find expansions of a typed word.  It compares consecutive
1328 * expansions and ignores one of them if they are identical.
1329 * (Tomasz J. Cholewo, t.cholewo@ieee.org)
1330 */
1331
1332#define IS_WORD_CONSTITUENT(x) ((x) != ' ' && (x) != '\0')
1333#define MAXWLEN 1024		/* maximum word length as in tcsh */
1334
1335static int
1336dabbrev_prev_char(TScreen *screen, CELL *cell, LineData **ld)
1337{
1338    int result = -1;
1339    int firstLine = -(screen->savedlines);
1340
1341    *ld = getLineData(screen, cell->row);
1342    while (cell->row >= firstLine) {
1343	if (--(cell->col) >= 0) {
1344	    result = (int) (*ld)->charData[cell->col];
1345	    break;
1346	}
1347	if (--(cell->row) < firstLine)
1348	    break;		/* ...there is no previous line */
1349	*ld = getLineData(screen, cell->row);
1350	cell->col = MaxCols(screen);
1351	if (!LineTstWrapped(*ld)) {
1352	    result = ' ';	/* treat lines as separate */
1353	    break;
1354	}
1355    }
1356    return result;
1357}
1358
1359static char *
1360dabbrev_prev_word(TScreen *screen, CELL *cell, LineData **ld)
1361{
1362    static char ab[MAXWLEN];
1363
1364    char *abword;
1365    int c;
1366    char *ab_end = (ab + MAXWLEN - 1);
1367    char *result = 0;
1368
1369    abword = ab_end;
1370    *abword = '\0';		/* end of string marker */
1371
1372    while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1373	   IS_WORD_CONSTITUENT(c)) {
1374	if (abword > ab)	/* store only |MAXWLEN| last chars */
1375	    *(--abword) = (char) c;
1376    }
1377
1378    if (c >= 0) {
1379	result = abword;
1380    } else if (abword != ab_end) {
1381	result = abword;
1382    }
1383
1384    if (result != 0) {
1385	while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1386	       !IS_WORD_CONSTITUENT(c)) {
1387	    ;			/* skip preceding spaces */
1388	}
1389	(cell->col)++;		/* can be | > screen->max_col| */
1390    }
1391    return result;
1392}
1393
1394static int
1395dabbrev_expand(TScreen *screen)
1396{
1397    int pty = screen->respond;	/* file descriptor of pty */
1398
1399    static CELL cell;
1400    static char *dabbrev_hint = 0, *lastexpansion = 0;
1401    static unsigned int expansions;
1402
1403    char *expansion;
1404    Char *copybuffer;
1405    size_t hint_len;
1406    size_t del_cnt;
1407    size_t buf_cnt;
1408    int result = 0;
1409    LineData *ld;
1410
1411    if (!screen->dabbrev_working) {	/* initialize */
1412	expansions = 0;
1413	cell.col = screen->cur_col;
1414	cell.row = screen->cur_row;
1415
1416	if (dabbrev_hint != 0)
1417	    free(dabbrev_hint);
1418
1419	if ((dabbrev_hint = dabbrev_prev_word(screen, &cell, &ld)) != 0) {
1420
1421	    if (lastexpansion != 0)
1422		free(lastexpansion);
1423
1424	    if ((lastexpansion = strdup(dabbrev_hint)) != 0) {
1425
1426		/* make own copy */
1427		if ((dabbrev_hint = strdup(dabbrev_hint)) != 0) {
1428		    screen->dabbrev_working = True;
1429		    /* we are in the middle of dabbrev process */
1430		}
1431	    } else {
1432		return result;
1433	    }
1434	} else {
1435	    return result;
1436	}
1437	if (!screen->dabbrev_working) {
1438	    if (lastexpansion != 0) {
1439		free(lastexpansion);
1440		lastexpansion = 0;
1441	    }
1442	    return result;
1443	}
1444    }
1445
1446    if (dabbrev_hint == 0)
1447	return result;
1448
1449    hint_len = strlen(dabbrev_hint);
1450    for (;;) {
1451	if ((expansion = dabbrev_prev_word(screen, &cell, &ld)) == 0) {
1452	    if (expansions >= 2) {
1453		expansions = 0;
1454		cell.col = screen->cur_col;
1455		cell.row = screen->cur_row;
1456		continue;
1457	    }
1458	    break;
1459	}
1460	if (!strncmp(dabbrev_hint, expansion, hint_len) &&	/* empty hint matches everything */
1461	    strlen(expansion) > hint_len &&	/* trivial expansion disallowed */
1462	    strcmp(expansion, lastexpansion))	/* different from previous */
1463	    break;
1464    }
1465
1466    if (expansion != 0) {
1467	del_cnt = strlen(lastexpansion) - hint_len;
1468	buf_cnt = del_cnt + strlen(expansion) - hint_len;
1469
1470	if ((copybuffer = TypeMallocN(Char, buf_cnt)) != 0) {
1471	    /* delete previous expansion */
1472	    memset(copybuffer, screen->dabbrev_erase_char, del_cnt);
1473	    memmove(copybuffer + del_cnt,
1474		    expansion + hint_len,
1475		    strlen(expansion) - hint_len);
1476	    v_write(pty, copybuffer, (unsigned) buf_cnt);
1477	    /* v_write() just reset our flag */
1478	    screen->dabbrev_working = True;
1479	    free(copybuffer);
1480
1481	    free(lastexpansion);
1482
1483	    if ((lastexpansion = strdup(expansion)) != 0) {
1484		result = 1;
1485		expansions++;
1486	    }
1487	}
1488    }
1489
1490    return result;
1491}
1492
1493/*ARGSUSED*/
1494void
1495HandleDabbrevExpand(Widget w,
1496		    XEvent * event GCC_UNUSED,
1497		    String *params GCC_UNUSED,
1498		    Cardinal *nparams GCC_UNUSED)
1499{
1500    XtermWidget xw;
1501
1502    TRACE(("Handle dabbrev-expand for %p\n", (void *) w));
1503    if ((xw = getXtermWidget(w)) != 0) {
1504	TScreen *screen = TScreenOf(xw);
1505	if (!dabbrev_expand(screen))
1506	    Bell(xw, XkbBI_TerminalBell, 0);
1507    }
1508}
1509#endif /* OPT_DABBREV */
1510
1511#if OPT_MAXIMIZE
1512/*ARGSUSED*/
1513void
1514HandleDeIconify(Widget w,
1515		XEvent * event GCC_UNUSED,
1516		String *params GCC_UNUSED,
1517		Cardinal *nparams GCC_UNUSED)
1518{
1519    XtermWidget xw;
1520
1521    if ((xw = getXtermWidget(w)) != 0) {
1522	TScreen *screen = TScreenOf(xw);
1523	XMapWindow(screen->display, VShellWindow(xw));
1524    }
1525}
1526
1527/*ARGSUSED*/
1528void
1529HandleIconify(Widget w,
1530	      XEvent * event GCC_UNUSED,
1531	      String *params GCC_UNUSED,
1532	      Cardinal *nparams GCC_UNUSED)
1533{
1534    XtermWidget xw;
1535
1536    if ((xw = getXtermWidget(w)) != 0) {
1537	TScreen *screen = TScreenOf(xw);
1538	XIconifyWindow(screen->display,
1539		       VShellWindow(xw),
1540		       DefaultScreen(screen->display));
1541    }
1542}
1543
1544int
1545QueryMaximize(XtermWidget xw, unsigned *width, unsigned *height)
1546{
1547    TScreen *screen = TScreenOf(xw);
1548    XSizeHints hints;
1549    long supp = 0;
1550    Window root_win;
1551    int root_x = -1;		/* saved co-ordinates */
1552    int root_y = -1;
1553    unsigned root_border;
1554    unsigned root_depth;
1555    int code;
1556
1557    if (XGetGeometry(screen->display,
1558		     RootWindowOfScreen(XtScreen(xw)),
1559		     &root_win,
1560		     &root_x,
1561		     &root_y,
1562		     width,
1563		     height,
1564		     &root_border,
1565		     &root_depth)) {
1566	TRACE(("QueryMaximize: XGetGeometry position %d,%d size %d,%d border %d\n",
1567	       root_x,
1568	       root_y,
1569	       *width,
1570	       *height,
1571	       root_border));
1572
1573	*width -= (root_border * 2);
1574	*height -= (root_border * 2);
1575
1576	hints.flags = PMaxSize;
1577	if (XGetWMNormalHints(screen->display,
1578			      VShellWindow(xw),
1579			      &hints,
1580			      &supp)
1581	    && (hints.flags & PMaxSize) != 0) {
1582
1583	    TRACE(("QueryMaximize: WM hints max_w %#x max_h %#x\n",
1584		   hints.max_width,
1585		   hints.max_height));
1586
1587	    if ((unsigned) hints.max_width < *width)
1588		*width = (unsigned) hints.max_width;
1589	    if ((unsigned) hints.max_height < *height)
1590		*height = (unsigned) hints.max_height;
1591	}
1592	code = 1;
1593    } else {
1594	*width = 0;
1595	*height = 0;
1596	code = 0;
1597    }
1598    return code;
1599}
1600
1601void
1602RequestMaximize(XtermWidget xw, int maximize)
1603{
1604    TScreen *screen = TScreenOf(xw);
1605    XWindowAttributes wm_attrs, vshell_attrs;
1606    unsigned root_width, root_height;
1607    Boolean success = False;
1608
1609    TRACE(("RequestMaximize %d:%s\n",
1610	   maximize,
1611	   (maximize
1612	    ? "maximize"
1613	    : "restore")));
1614
1615    /*
1616     * Before any maximize, ensure that we can capture the current screensize
1617     * as well as the estimated root-window size.
1618     */
1619    if (maximize
1620	&& QueryMaximize(xw, &root_width, &root_height)
1621	&& xtermGetWinAttrs(screen->display,
1622			    WMFrameWindow(xw),
1623			    &wm_attrs)
1624	&& xtermGetWinAttrs(screen->display,
1625			    VShellWindow(xw),
1626			    &vshell_attrs)) {
1627
1628	if (screen->restore_data != True
1629	    || screen->restore_width != root_width
1630	    || screen->restore_height != root_height) {
1631	    screen->restore_data = True;
1632	    screen->restore_x = wm_attrs.x + wm_attrs.border_width;
1633	    screen->restore_y = wm_attrs.y + wm_attrs.border_width;
1634	    screen->restore_width = (unsigned) vshell_attrs.width;
1635	    screen->restore_height = (unsigned) vshell_attrs.height;
1636	    TRACE(("RequestMaximize: save window position %d,%d size %d,%d\n",
1637		   screen->restore_x,
1638		   screen->restore_y,
1639		   screen->restore_width,
1640		   screen->restore_height));
1641	}
1642
1643	/* subtract wm decoration dimensions */
1644	root_width -= (unsigned) ((wm_attrs.width - vshell_attrs.width)
1645				  + (wm_attrs.border_width * 2));
1646	root_height -= (unsigned) ((wm_attrs.height - vshell_attrs.height)
1647				   + (wm_attrs.border_width * 2));
1648	success = True;
1649    } else if (screen->restore_data) {
1650	success = True;
1651	maximize = 0;
1652    }
1653
1654    if (success) {
1655	switch (maximize) {
1656	case 3:
1657	    FullScreen(xw, 3);	/* depends on EWMH */
1658	    break;
1659	case 2:
1660	    FullScreen(xw, 2);	/* depends on EWMH */
1661	    break;
1662	case 1:
1663	    FullScreen(xw, 0);	/* overrides any EWMH hint */
1664	    XMoveResizeWindow(screen->display, VShellWindow(xw),
1665			      0 + wm_attrs.border_width,	/* x */
1666			      0 + wm_attrs.border_width,	/* y */
1667			      root_width,
1668			      root_height);
1669	    break;
1670
1671	default:
1672	    FullScreen(xw, 0);	/* reset any EWMH hint */
1673	    if (screen->restore_data) {
1674		screen->restore_data = False;
1675
1676		TRACE(("HandleRestoreSize: position %d,%d size %d,%d\n",
1677		       screen->restore_x,
1678		       screen->restore_y,
1679		       screen->restore_width,
1680		       screen->restore_height));
1681
1682		XMoveResizeWindow(screen->display,
1683				  VShellWindow(xw),
1684				  screen->restore_x,
1685				  screen->restore_y,
1686				  screen->restore_width,
1687				  screen->restore_height);
1688	    }
1689	    break;
1690	}
1691    }
1692}
1693
1694/*ARGSUSED*/
1695void
1696HandleMaximize(Widget w,
1697	       XEvent * event GCC_UNUSED,
1698	       String *params GCC_UNUSED,
1699	       Cardinal *nparams GCC_UNUSED)
1700{
1701    XtermWidget xw;
1702
1703    if ((xw = getXtermWidget(w)) != 0) {
1704	RequestMaximize(xw, 1);
1705    }
1706}
1707
1708/*ARGSUSED*/
1709void
1710HandleRestoreSize(Widget w,
1711		  XEvent * event GCC_UNUSED,
1712		  String *params GCC_UNUSED,
1713		  Cardinal *nparams GCC_UNUSED)
1714{
1715    XtermWidget xw;
1716
1717    if ((xw = getXtermWidget(w)) != 0) {
1718	RequestMaximize(xw, 0);
1719    }
1720}
1721#endif /* OPT_MAXIMIZE */
1722
1723void
1724Redraw(void)
1725{
1726    TScreen *screen = TScreenOf(term);
1727    XExposeEvent event;
1728
1729    TRACE(("Redraw\n"));
1730
1731    event.type = Expose;
1732    event.display = screen->display;
1733    event.x = 0;
1734    event.y = 0;
1735    event.count = 0;
1736
1737    if (VWindow(screen)) {
1738	event.window = VWindow(screen);
1739	event.width = term->core.width;
1740	event.height = term->core.height;
1741	(*term->core.widget_class->core_class.expose) ((Widget) term,
1742						       (XEvent *) & event,
1743						       NULL);
1744	if (ScrollbarWidth(screen)) {
1745	    (screen->scrollWidget->core.widget_class->core_class.expose)
1746		(screen->scrollWidget, (XEvent *) & event, NULL);
1747	}
1748    }
1749#if OPT_TEK4014
1750    if (TEK4014_SHOWN(term)) {
1751	TekScreen *tekscr = TekScreenOf(tekWidget);
1752	event.window = TWindow(tekscr);
1753	event.width = tekWidget->core.width;
1754	event.height = tekWidget->core.height;
1755	TekExpose((Widget) tekWidget, (XEvent *) & event, NULL);
1756    }
1757#endif
1758}
1759
1760#ifdef VMS
1761#define TIMESTAMP_FMT "%s%d-%02d-%02d-%02d-%02d-%02d"
1762#else
1763#define TIMESTAMP_FMT "%s%d-%02d-%02d.%02d:%02d:%02d"
1764#endif
1765
1766void
1767timestamp_filename(char *dst, const char *src)
1768{
1769    time_t tstamp;
1770    struct tm *tstruct;
1771
1772    tstamp = time((time_t *) 0);
1773    tstruct = localtime(&tstamp);
1774    sprintf(dst, TIMESTAMP_FMT,
1775	    src,
1776	    (int) tstruct->tm_year + 1900,
1777	    tstruct->tm_mon + 1,
1778	    tstruct->tm_mday,
1779	    tstruct->tm_hour,
1780	    tstruct->tm_min,
1781	    tstruct->tm_sec);
1782}
1783
1784int
1785open_userfile(uid_t uid, gid_t gid, char *path, Bool append)
1786{
1787    int fd;
1788    struct stat sb;
1789
1790#ifdef VMS
1791    if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
1792	int the_error = errno;
1793	xtermWarning("cannot open %s: %d:%s\n",
1794		     path,
1795		     the_error,
1796		     SysErrorMsg(the_error));
1797	return -1;
1798    }
1799    chown(path, uid, gid);
1800#else
1801    if ((access(path, F_OK) != 0 && (errno != ENOENT))
1802	|| (creat_as(uid, gid, append, path, 0644) <= 0)
1803	|| ((fd = open(path, O_WRONLY | O_APPEND)) < 0)) {
1804	int the_error = errno;
1805	xtermWarning("cannot open %s: %d:%s\n",
1806		     path,
1807		     the_error,
1808		     SysErrorMsg(the_error));
1809	return -1;
1810    }
1811#endif
1812
1813    /*
1814     * Doublecheck that the user really owns the file that we've opened before
1815     * we do any damage, and that it is not world-writable.
1816     */
1817    if (fstat(fd, &sb) < 0
1818	|| sb.st_uid != uid
1819	|| (sb.st_mode & 022) != 0) {
1820	xtermWarning("you do not own %s\n", path);
1821	close(fd);
1822	return -1;
1823    }
1824    return fd;
1825}
1826
1827#ifndef VMS
1828/*
1829 * Create a file only if we could with the permissions of the real user id.
1830 * We could emulate this with careful use of access() and following
1831 * symbolic links, but that is messy and has race conditions.
1832 * Forking is messy, too, but we can't count on setreuid() or saved set-uids
1833 * being available.
1834 *
1835 * Note: When called for user logging, we have ensured that the real and
1836 * effective user ids are the same, so this remains as a convenience function
1837 * for the debug logs.
1838 *
1839 * Returns
1840 *	 1 if we can proceed to open the file in relative safety,
1841 *	-1 on error, e.g., cannot fork
1842 *	 0 otherwise.
1843 */
1844int
1845creat_as(uid_t uid, gid_t gid, Bool append, char *pathname, unsigned mode)
1846{
1847    int fd;
1848    pid_t pid;
1849    int retval = 0;
1850    int childstat = 0;
1851#ifndef HAVE_WAITPID
1852    int waited;
1853    void (*chldfunc) (int);
1854
1855    chldfunc = signal(SIGCHLD, SIG_DFL);
1856#endif /* HAVE_WAITPID */
1857
1858    TRACE(("creat_as(uid=%d/%d, gid=%d/%d, append=%d, pathname=%s, mode=%#o)\n",
1859	   (int) uid, (int) geteuid(),
1860	   (int) gid, (int) getegid(),
1861	   append,
1862	   pathname,
1863	   mode));
1864
1865    if (uid == geteuid() && gid == getegid()) {
1866	fd = open(pathname,
1867		  O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1868		  mode);
1869	if (fd >= 0)
1870	    close(fd);
1871	return (fd >= 0);
1872    }
1873
1874    pid = fork();
1875    switch (pid) {
1876    case 0:			/* child */
1877	if (setgid(gid) == -1
1878	    || setuid(uid) == -1) {
1879	    /* we cannot report an error here via stderr, just quit */
1880	    retval = 1;
1881	} else {
1882	    fd = open(pathname,
1883		      O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1884		      mode);
1885	    if (fd >= 0) {
1886		close(fd);
1887		retval = 0;
1888	    } else {
1889		retval = 1;
1890	    }
1891	}
1892	_exit(retval);
1893	/* NOTREACHED */
1894    case -1:			/* error */
1895	return retval;
1896    default:			/* parent */
1897#ifdef HAVE_WAITPID
1898	while (waitpid(pid, &childstat, 0) < 0) {
1899#ifdef EINTR
1900	    if (errno == EINTR)
1901		continue;
1902#endif /* EINTR */
1903#ifdef ERESTARTSYS
1904	    if (errno == ERESTARTSYS)
1905		continue;
1906#endif /* ERESTARTSYS */
1907	    break;
1908	}
1909#else /* HAVE_WAITPID */
1910	waited = wait(&childstat);
1911	signal(SIGCHLD, chldfunc);
1912	/*
1913	   Since we had the signal handler uninstalled for a while,
1914	   we might have missed the termination of our screen child.
1915	   If we can check for this possibility without hanging, do so.
1916	 */
1917	do
1918	    if (waited == TScreenOf(term)->pid)
1919		NormalExit();
1920	while ((waited = nonblocking_wait()) > 0) ;
1921#endif /* HAVE_WAITPID */
1922#ifndef WIFEXITED
1923#define WIFEXITED(status) ((status & 0xff) != 0)
1924#endif
1925	if (WIFEXITED(childstat))
1926	    retval = 1;
1927	return retval;
1928    }
1929}
1930#endif /* !VMS */
1931
1932int
1933xtermResetIds(TScreen *screen)
1934{
1935    int result = 0;
1936    if (setgid(screen->gid) == -1) {
1937	xtermWarning("unable to reset group-id\n");
1938	result = -1;
1939    }
1940    if (setuid(screen->uid) == -1) {
1941	xtermWarning("unable to reset user-id\n");
1942	result = -1;
1943    }
1944    return result;
1945}
1946
1947#ifdef ALLOWLOGGING
1948
1949/*
1950 * Logging is a security hole, since it allows a setuid program to write
1951 * arbitrary data to an arbitrary file.  So it is disabled by default.
1952 */
1953
1954#ifdef ALLOWLOGFILEEXEC
1955static void
1956logpipe(int sig GCC_UNUSED)
1957{
1958    XtermWidget xw = term;
1959    TScreen *screen = TScreenOf(xw);
1960
1961    DEBUG_MSG("handle:logpipe\n");
1962#ifdef SYSV
1963    (void) signal(SIGPIPE, SIG_IGN);
1964#endif /* SYSV */
1965    if (screen->logging)
1966	CloseLog(xw);
1967}
1968#endif /* ALLOWLOGFILEEXEC */
1969
1970void
1971StartLog(XtermWidget xw)
1972{
1973    static char *log_default;
1974    TScreen *screen = TScreenOf(xw);
1975
1976    if (screen->logging || (screen->inhibit & I_LOG))
1977	return;
1978#ifdef VMS			/* file name is fixed in VMS variant */
1979    screen->logfd = open(XTERM_VMS_LOGFILE,
1980			 O_CREAT | O_TRUNC | O_APPEND | O_RDWR,
1981			 0640);
1982    if (screen->logfd < 0)
1983	return;			/* open failed */
1984#else /*VMS */
1985    if (screen->logfile == NULL || *screen->logfile == 0) {
1986	if (screen->logfile)
1987	    free(screen->logfile);
1988	if (log_default == NULL) {
1989#if defined(HAVE_GETHOSTNAME) && defined(HAVE_STRFTIME)
1990	    char log_def_name[512];	/* see sprintf below */
1991	    char hostname[255 + 1];	/* Internet standard limit (RFC 1035):
1992					   ``To simplify implementations, the
1993					   total length of a domain name (i.e.,
1994					   label octets and label length
1995					   octets) is restricted to 255 octets
1996					   or less.'' */
1997	    char yyyy_mm_dd_hh_mm_ss[4 + 5 * (1 + 2) + 1];
1998	    time_t now;
1999	    struct tm *ltm;
2000
2001	    now = time((time_t *) 0);
2002	    ltm = (struct tm *) localtime(&now);
2003	    if ((gethostname(hostname, sizeof(hostname)) == 0) &&
2004		(strftime(yyyy_mm_dd_hh_mm_ss,
2005			  sizeof(yyyy_mm_dd_hh_mm_ss),
2006			  "%Y.%m.%d.%H.%M.%S", ltm) > 0)) {
2007		(void) sprintf(log_def_name, "Xterm.log.%.255s.%.20s.%d",
2008			       hostname, yyyy_mm_dd_hh_mm_ss, (int) getpid());
2009	    }
2010	    if ((log_default = x_strdup(log_def_name)) == NULL)
2011		return;
2012#else
2013	    const char *log_def_name = "XtermLog.XXXXXX";
2014	    if ((log_default = x_strdup(log_def_name)) == NULL)
2015		return;
2016
2017	    mktemp(log_default);
2018#endif
2019	}
2020	if ((screen->logfile = x_strdup(log_default)) == 0)
2021	    return;
2022    }
2023    if (*screen->logfile == '|') {	/* exec command */
2024#ifdef ALLOWLOGFILEEXEC
2025	/*
2026	 * Warning, enabling this "feature" allows arbitrary programs
2027	 * to be run.  If ALLOWLOGFILECHANGES is enabled, this can be
2028	 * done through escape sequences....  You have been warned.
2029	 */
2030	int pid;
2031	int p[2];
2032	static char *shell;
2033	struct passwd pw;
2034
2035	if ((shell = x_getenv("SHELL")) == NULL) {
2036
2037	    if (x_getpwuid(screen->uid, &pw)) {
2038		char *name = x_getlogin(screen->uid, &pw);
2039		if (*(pw.pw_shell)) {
2040		    shell = pw.pw_shell;
2041		}
2042		free(name);
2043	    }
2044	}
2045
2046	if (shell == 0) {
2047	    static char dummy[] = "/bin/sh";
2048	    shell = dummy;
2049	}
2050
2051	if (access(shell, X_OK) != 0) {
2052	    xtermPerror("Can't execute `%s'\n", shell);
2053	    return;
2054	}
2055
2056	if (pipe(p) < 0) {
2057	    xtermPerror("Can't make a pipe connection\n");
2058	    return;
2059	} else if ((pid = fork()) < 0) {
2060	    xtermPerror("Can't fork...\n");
2061	    return;
2062	}
2063	if (pid == 0) {		/* child */
2064	    /*
2065	     * Close our output (we won't be talking back to the
2066	     * parent), and redirect our child's output to the
2067	     * original stderr.
2068	     */
2069	    close(p[1]);
2070	    dup2(p[0], 0);
2071	    close(p[0]);
2072	    dup2(fileno(stderr), 1);
2073	    dup2(fileno(stderr), 2);
2074
2075	    close(fileno(stderr));
2076	    close(ConnectionNumber(screen->display));
2077	    close(screen->respond);
2078
2079	    signal(SIGHUP, SIG_DFL);
2080	    signal(SIGCHLD, SIG_DFL);
2081
2082	    /* (this is redundant) */
2083	    if (xtermResetIds(screen) < 0)
2084		exit(ERROR_SETUID);
2085
2086	    if (access(shell, X_OK) == 0) {
2087		execl(shell, shell, "-c", &screen->logfile[1], (void *) 0);
2088		xtermWarning("Can't exec `%s'\n", &screen->logfile[1]);
2089	    } else {
2090		xtermWarning("Can't execute `%s'\n", shell);
2091	    }
2092	    exit(ERROR_LOGEXEC);
2093	}
2094	close(p[0]);
2095	screen->logfd = p[1];
2096	signal(SIGPIPE, logpipe);
2097#else
2098	Bell(xw, XkbBI_Info, 0);
2099	Bell(xw, XkbBI_Info, 0);
2100	return;
2101#endif
2102    } else {
2103	if ((screen->logfd = open_userfile(screen->uid,
2104					   screen->gid,
2105					   screen->logfile,
2106					   (log_default != 0))) < 0)
2107	    return;
2108    }
2109#endif /*VMS */
2110    screen->logstart = VTbuffer->next;
2111    screen->logging = True;
2112    update_logging();
2113}
2114
2115void
2116CloseLog(XtermWidget xw)
2117{
2118    TScreen *screen = TScreenOf(xw);
2119
2120    if (!screen->logging || (screen->inhibit & I_LOG))
2121	return;
2122    FlushLog(xw);
2123    close(screen->logfd);
2124    screen->logging = False;
2125    update_logging();
2126}
2127
2128void
2129FlushLog(XtermWidget xw)
2130{
2131    TScreen *screen = TScreenOf(xw);
2132
2133    if (screen->logging && !(screen->inhibit & I_LOG)) {
2134	Char *cp;
2135	int i;
2136
2137#ifdef VMS			/* avoid logging output loops which otherwise occur sometimes
2138				   when there is no output and cp/screen->logstart are 1 apart */
2139	if (!tt_new_output)
2140	    return;
2141	tt_new_output = False;
2142#endif /* VMS */
2143	cp = VTbuffer->next;
2144	if (screen->logstart != 0
2145	    && (i = (int) (cp - screen->logstart)) > 0) {
2146	    IGNORE_RC(write(screen->logfd, screen->logstart, (size_t) i));
2147	}
2148	screen->logstart = VTbuffer->next;
2149    }
2150}
2151
2152#endif /* ALLOWLOGGING */
2153
2154/***====================================================================***/
2155
2156#if OPT_ISO_COLORS
2157static void
2158ReportAnsiColorRequest(XtermWidget xw, int colornum, int final)
2159{
2160    if (AllowColorOps(xw, ecGetAnsiColor)) {
2161	XColor color;
2162	Colormap cmap = xw->core.colormap;
2163	char buffer[80];
2164
2165	TRACE(("ReportAnsiColorRequest %d\n", colornum));
2166	color.pixel = GET_COLOR_RES(xw, TScreenOf(xw)->Acolors[colornum]);
2167	XQueryColor(TScreenOf(xw)->display, cmap, &color);
2168	sprintf(buffer, "4;%d;rgb:%04x/%04x/%04x",
2169		colornum,
2170		color.red,
2171		color.green,
2172		color.blue);
2173	unparseputc1(xw, ANSI_OSC);
2174	unparseputs(xw, buffer);
2175	unparseputc1(xw, final);
2176	unparse_end(xw);
2177    }
2178}
2179
2180int
2181getVisualInfo(XtermWidget xw)
2182{
2183#define MYFMT "getVisualInfo \
2184depth %d, \
2185type %d (%s), \
2186size %d \
2187rgb masks (%04lx/%04lx/%04lx)\n"
2188#define MYARG \
2189       vi->depth,\
2190       vi->class,\
2191       ((vi->class & 1) ? "dynamic" : "static"),\
2192       vi->colormap_size,\
2193       vi->red_mask,\
2194       vi->green_mask,\
2195       vi->blue_mask
2196
2197    TScreen *screen = TScreenOf(xw);
2198    Display *dpy = screen->display;
2199    XVisualInfo myTemplate;
2200
2201    if (xw->visInfo == 0 && xw->numVisuals == 0) {
2202	myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,
2203								XDefaultScreen(dpy)));
2204	xw->visInfo = XGetVisualInfo(dpy, (long) VisualIDMask,
2205				     &myTemplate, &xw->numVisuals);
2206
2207	if ((xw->visInfo != 0) && (xw->numVisuals > 0)) {
2208	    XVisualInfo *vi = xw->visInfo;
2209	    if (resource.reportColors) {
2210		printf(MYFMT, MYARG);
2211	    }
2212	    TRACE((MYFMT, MYARG));
2213	}
2214    }
2215    return (xw->visInfo != 0) && (xw->numVisuals > 0);
2216#undef MYFMT
2217#undef MYARG
2218}
2219
2220static void
2221getColormapInfo(XtermWidget xw, unsigned *typep, unsigned *sizep)
2222{
2223    if (getVisualInfo(xw)) {
2224	*typep = (unsigned) xw->visInfo->class;
2225	*sizep = (unsigned) xw->visInfo->colormap_size;
2226    } else {
2227	*typep = 0;
2228	*sizep = 0;
2229    }
2230}
2231
2232#define MAX_COLORTABLE 4096
2233
2234/*
2235 * Make only one call to XQueryColors(), since it can be slow.
2236 */
2237static Boolean
2238loadColorTable(XtermWidget xw, unsigned length)
2239{
2240    Colormap cmap = xw->core.colormap;
2241    TScreen *screen = TScreenOf(xw);
2242    unsigned i;
2243    Boolean result = (screen->cmap_data != 0);
2244
2245    if (!result
2246	&& length != 0
2247	&& length < MAX_COLORTABLE) {
2248	screen->cmap_data = TypeMallocN(XColor, (size_t) length);
2249	if (screen->cmap_data != 0) {
2250	    screen->cmap_size = length;
2251
2252	    for (i = 0; i < screen->cmap_size; i++) {
2253		screen->cmap_data[i].pixel = (unsigned long) i;
2254	    }
2255	    result = (Boolean) (XQueryColors(screen->display,
2256					     cmap,
2257					     screen->cmap_data,
2258					     (int) screen->cmap_size) != 0);
2259	}
2260    }
2261    return result;
2262}
2263
2264/*
2265 * Find closest color for "def" in "cmap".
2266 * Set "def" to the resulting color.
2267 *
2268 * Based on Monish Shah's "find_closest_color()" for Vim 6.0,
2269 * modified with ideas from David Tong's "noflash" library.
2270 * The code from Vim in turn was derived from FindClosestColor() in Tcl/Tk.
2271 *
2272 * These provide some introduction:
2273 *	http://en.wikipedia.org/wiki/YIQ
2274 *		for an introduction to YIQ weights.
2275 *	http://en.wikipedia.org/wiki/Luminance_(video)
2276 *		for a discussion of luma.
2277 *	http://en.wikipedia.org/wiki/YUV
2278 *
2279 * Return False if not able to find or allocate a color.
2280 */
2281static Boolean
2282allocateClosestRGB(XtermWidget xw, Colormap cmap, XColor * def)
2283{
2284    TScreen *screen = TScreenOf(xw);
2285    Boolean result = False;
2286    char *tried;
2287    double diff, thisRGB, bestRGB;
2288    unsigned attempts;
2289    unsigned bestInx;
2290    unsigned cmap_type;
2291    unsigned cmap_size;
2292    unsigned i;
2293
2294    getColormapInfo(xw, &cmap_type, &cmap_size);
2295
2296    if ((cmap_type & 1) != 0) {
2297
2298	if (loadColorTable(xw, cmap_size)) {
2299
2300	    tried = TypeCallocN(char, (size_t) cmap_size);
2301	    if (tried != 0) {
2302
2303		/*
2304		 * Try (possibly each entry in the color map) to find the best
2305		 * approximation to the requested color.
2306		 */
2307		for (attempts = 0; attempts < cmap_size; attempts++) {
2308		    Boolean first = True;
2309
2310		    bestRGB = 0.0;
2311		    bestInx = 0;
2312		    for (i = 0; i < cmap_size; i++) {
2313			if (!tried[bestInx]) {
2314			    /*
2315			     * Look for the best match based on luminance.
2316			     * Measure this by the least-squares difference of
2317			     * the weighted R/G/B components from the color map
2318			     * versus the requested color.  Use the Y (luma)
2319			     * component of the YIQ color space model for
2320			     * weights that correspond to the luminance.
2321			     */
2322#define AddColorWeight(weight, color) \
2323			    diff = weight * (int) ((def->color) - screen->cmap_data[i].color); \
2324			    thisRGB = diff * diff
2325
2326			    AddColorWeight(0.30, red);
2327			    AddColorWeight(0.61, green);
2328			    AddColorWeight(0.11, blue);
2329
2330			    if (first || (thisRGB < bestRGB)) {
2331				first = False;
2332				bestInx = i;
2333				bestRGB = thisRGB;
2334			    }
2335			}
2336		    }
2337		    if (XAllocColor(screen->display, cmap,
2338				    &screen->cmap_data[bestInx]) != 0) {
2339			*def = screen->cmap_data[bestInx];
2340			TRACE(("...closest %x/%x/%x\n", def->red,
2341			       def->green, def->blue));
2342			result = True;
2343			break;
2344		    }
2345		    /*
2346		     * It failed - either the color map entry was readonly, or
2347		     * another client has allocated the entry.  Mark the entry
2348		     * so we will ignore it
2349		     */
2350		    tried[bestInx] = True;
2351		}
2352		free(tried);
2353	    }
2354	}
2355    }
2356    return result;
2357}
2358
2359#ifndef ULONG_MAX
2360#define ULONG_MAX (unsigned long)(~(0L))
2361#endif
2362
2363#define CheckColor(result, value) \
2364	    result = 0; \
2365	    if (value.red) \
2366		result |= 1; \
2367	    if (value.green) \
2368		result |= 2; \
2369	    if (value.blue) \
2370		result |= 4
2371
2372#define SelectColor(state, value, result) \
2373	switch (state) { \
2374	default: \
2375	case 1: \
2376	    result = value.red; \
2377	    break; \
2378	case 2: \
2379	    result = value.green; \
2380	    break; \
2381	case 4: \
2382	    result = value.blue; \
2383	    break; \
2384	}
2385
2386/*
2387 * Check if the color map consists of values in exactly one of the red, green
2388 * or blue columns.  If it is not, we do not know how to use it for the exact
2389 * match.
2390 */
2391static int
2392simpleColors(XColor * colortable, unsigned length)
2393{
2394    unsigned n;
2395    int state = 0;
2396    int check;
2397
2398    for (n = 0; n < length; ++n) {
2399	if (state > 0) {
2400	    CheckColor(check, colortable[n]);
2401	    if (check > 0 && check != state) {
2402		state = 0;
2403		break;
2404	    }
2405	} else {
2406	    CheckColor(state, colortable[n]);
2407	}
2408    }
2409    switch (state) {
2410    case 1:
2411    case 2:
2412    case 4:
2413	break;
2414    default:
2415	state = 0;
2416	break;
2417    }
2418    return state;
2419}
2420
2421/*
2422 * Shift the mask left or right to put its most significant bit at the 16-bit
2423 * mark.
2424 */
2425static unsigned
2426normalizeMask(unsigned mask)
2427{
2428    while (mask < 0x8000) {
2429	mask <<= 1;
2430    }
2431    while (mask >= 0x10000) {
2432	mask >>= 1;
2433    }
2434    return mask;
2435}
2436
2437static unsigned
2438searchColors(XColor * colortable, unsigned mask, unsigned length, unsigned
2439	     color, int state)
2440{
2441    unsigned result = 0;
2442    unsigned n;
2443    unsigned long best = ULONG_MAX;
2444    unsigned long diff;
2445    unsigned value;
2446
2447    mask = normalizeMask(mask);
2448    for (n = 0; n < length; ++n) {
2449	SelectColor(state, colortable[n], value);
2450	diff = ((color & mask) - (value & mask));
2451	diff *= diff;
2452	if (diff < best) {
2453#if 0
2454	    TRACE(("...%d:looking for %x, found %x/%x/%x (%lx)\n",
2455		   n, color,
2456		   colortable[n].red,
2457		   colortable[n].green,
2458		   colortable[n].blue,
2459		   diff));
2460#endif
2461	    result = n;
2462	    best = diff;
2463	}
2464    }
2465    SelectColor(state, colortable[result], value);
2466    return value;
2467}
2468
2469/*
2470 * This is a workaround for a longstanding defect in the X libraries.
2471 *
2472 * According to
2473 * http://www.unix.com/man-page/all/3x/XAllocColoA/
2474 *
2475 *     XAllocColor() acts differently on static and dynamic visuals.  On Pseu-
2476 *     doColor, DirectColor, and GrayScale  visuals,  XAllocColor()  fails  if
2477 *     there  are  no  unallocated  colorcells and no allocated read-only cell
2478 *     exactly matches the requested RGB values.  On  StaticColor,  TrueColor,
2479 *     and  StaticGray  visuals,  XAllocColor() returns the closest RGB values
2480 *     available in the colormap.  The colorcell_in_out structure returns  the
2481 *     actual RGB values allocated.
2482 *
2483 * That is, XAllocColor() should suffice unless the color map is full.  In that
2484 * case, allocateClosestRGB() is useful for the dynamic display classes such as
2485 * PseudoColor.  It is not useful for TrueColor, since XQueryColors() does not
2486 * return regular RGB triples (unless a different scheme was used for
2487 * specifying the pixel values); only the blue value is filled in.  However, it
2488 * is filled in with the colors that the server supports.
2489 *
2490 * Also (the reason for this function), XAllocColor() does not really work as
2491 * described.  For some TrueColor configurations it merely returns a close
2492 * approximation, but not the closest.
2493 */
2494static Boolean
2495allocateExactRGB(XtermWidget xw, Colormap cmap, XColor * def)
2496{
2497    XColor save = *def;
2498    TScreen *screen = TScreenOf(xw);
2499    Boolean result = (Boolean) (XAllocColor(screen->display, cmap, def) != 0);
2500
2501    /*
2502     * If this is a statically allocated display with too many items to store
2503     * in our array, i.e., TrueColor, see if we can improve on the result by
2504     * using the color values actually supported by the server.
2505     */
2506    if (result) {
2507	unsigned cmap_type;
2508	unsigned cmap_size;
2509	int state;
2510
2511	getColormapInfo(xw, &cmap_type, &cmap_size);
2512
2513	if (cmap_type == TrueColor) {
2514	    XColor temp = *def;
2515
2516	    if (loadColorTable(xw, cmap_size)
2517		&& (state = simpleColors(screen->cmap_data, cmap_size)) > 0) {
2518#define SearchColors(which) \
2519	temp.which = (unsigned short) searchColors(screen->cmap_data, \
2520						   (unsigned) xw->visInfo->which##_mask,\
2521						   cmap_size, \
2522						   save.which, \
2523						   state)
2524		SearchColors(red);
2525		SearchColors(green);
2526		SearchColors(blue);
2527		if (XAllocColor(screen->display, cmap, &temp) != 0) {
2528#if OPT_TRACE
2529		    if (temp.red != save.red
2530			|| temp.green != save.green
2531			|| temp.blue != save.blue) {
2532			TRACE(("...improved %x/%x/%x ->%x/%x/%x\n",
2533			       save.red, save.green, save.blue,
2534			       temp.red, temp.green, temp.blue));
2535		    } else {
2536			TRACE(("...no improvement for %x/%x/%x\n",
2537			       save.red, save.green, save.blue));
2538		    }
2539#endif
2540		    *def = temp;
2541		}
2542	    }
2543	}
2544    }
2545
2546    return result;
2547}
2548
2549/*
2550 * Allocate a color for the "ANSI" colors.  That actually includes colors up
2551 * to 256.
2552 *
2553 * Returns
2554 *	-1 on error
2555 *	0 on no change
2556 *	1 if a new color was allocated.
2557 */
2558static int
2559AllocateAnsiColor(XtermWidget xw,
2560		  ColorRes * res,
2561		  const char *spec)
2562{
2563    int result;
2564    XColor def;
2565
2566    if (xtermAllocColor(xw, &def, spec)) {
2567	if (
2568#if OPT_COLOR_RES
2569	       res->mode == True &&
2570#endif
2571	       EQL_COLOR_RES(res, def.pixel)) {
2572	    result = 0;
2573	} else {
2574	    result = 1;
2575	    SET_COLOR_RES(res, def.pixel);
2576	    res->red = def.red;
2577	    res->green = def.green;
2578	    res->blue = def.blue;
2579	    TRACE(("AllocateAnsiColor[%d] %s (rgb:%04x/%04x/%04x, pixel 0x%06lx)\n",
2580		   (int) (res - TScreenOf(xw)->Acolors), spec,
2581		   def.red,
2582		   def.green,
2583		   def.blue,
2584		   def.pixel));
2585#if OPT_COLOR_RES
2586	    if (!res->mode)
2587		result = 0;
2588	    res->mode = True;
2589#endif
2590	}
2591    } else {
2592	TRACE(("AllocateAnsiColor %s (failed)\n", spec));
2593	result = -1;
2594    }
2595    return (result);
2596}
2597
2598#if OPT_COLOR_RES
2599Pixel
2600xtermGetColorRes(XtermWidget xw, ColorRes * res)
2601{
2602    Pixel result = 0;
2603
2604    if (res->mode) {
2605	result = res->value;
2606    } else {
2607	TRACE(("xtermGetColorRes for Acolors[%d]\n",
2608	       (int) (res - TScreenOf(xw)->Acolors)));
2609
2610	if (res >= TScreenOf(xw)->Acolors) {
2611	    assert(res - TScreenOf(xw)->Acolors < MAXCOLORS);
2612
2613	    if (AllocateAnsiColor(xw, res, res->resource) < 0) {
2614		res->value = TScreenOf(xw)->Tcolors[TEXT_FG].value;
2615		res->mode = -True;
2616		xtermWarning("Cannot allocate color \"%s\"\n",
2617			     NonNull(res->resource));
2618	    }
2619	    result = res->value;
2620	} else {
2621	    result = 0;
2622	}
2623    }
2624    return result;
2625}
2626#endif
2627
2628static int
2629ChangeOneAnsiColor(XtermWidget xw, int color, const char *name)
2630{
2631    int code;
2632
2633    if (color < 0 || color >= MAXCOLORS) {
2634	code = -1;
2635    } else {
2636	ColorRes *res = &(TScreenOf(xw)->Acolors[color]);
2637
2638	TRACE(("ChangeAnsiColor for Acolors[%d]\n", color));
2639	code = AllocateAnsiColor(xw, res, name);
2640    }
2641    return code;
2642}
2643
2644/*
2645 * Set or query entries in the Acolors[] array by parsing pairs of color/name
2646 * values from the given buffer.
2647 *
2648 * The color can be any legal index into Acolors[], which consists of the
2649 * 16/88/256 "ANSI" colors, followed by special color values for the various
2650 * colorXX resources.  The indices for the special color values are not
2651 * simple to work with, so an alternative is to use the calls which pass in
2652 * 'first' set to the beginning of those indices.
2653 *
2654 * If the name is "?", report to the host the current value for the color.
2655 */
2656static Bool
2657ChangeAnsiColorRequest(XtermWidget xw,
2658		       char *buf,
2659		       int first,
2660		       int final)
2661{
2662    char *name;
2663    int color;
2664    int repaint = False;
2665    int code;
2666    int last = (MAXCOLORS - first);
2667
2668    TRACE(("ChangeAnsiColorRequest string='%s'\n", buf));
2669
2670    while (buf && *buf) {
2671	name = strchr(buf, ';');
2672	if (name == NULL)
2673	    break;
2674	*name = '\0';
2675	name++;
2676	color = atoi(buf);
2677	if (color < 0 || color >= last)
2678	    break;		/* quit on any error */
2679	buf = strchr(name, ';');
2680	if (buf) {
2681	    *buf = '\0';
2682	    buf++;
2683	}
2684	if (!strcmp(name, "?")) {
2685	    ReportAnsiColorRequest(xw, color + first, final);
2686	} else {
2687	    code = ChangeOneAnsiColor(xw, color + first, name);
2688	    if (code < 0) {
2689		/* stop on any error */
2690		break;
2691	    } else if (code > 0) {
2692		repaint = True;
2693	    }
2694	    /* FIXME:  free old color somehow?  We aren't for the other color
2695	     * change style (dynamic colors).
2696	     */
2697	}
2698    }
2699
2700    return (repaint);
2701}
2702
2703static Bool
2704ResetOneAnsiColor(XtermWidget xw, int color, int start)
2705{
2706    Bool repaint = False;
2707    int last = MAXCOLORS - start;
2708
2709    if (color >= 0 && color < last) {
2710	ColorRes *res = &(TScreenOf(xw)->Acolors[color + start]);
2711
2712	if (res->mode) {
2713	    /* a color has been allocated for this slot - test further... */
2714	    if (ChangeOneAnsiColor(xw, color + start, res->resource) > 0) {
2715		repaint = True;
2716	    }
2717	}
2718    }
2719    return repaint;
2720}
2721
2722int
2723ResetAnsiColorRequest(XtermWidget xw, char *buf, int start)
2724{
2725    int repaint = 0;
2726    int color;
2727
2728    TRACE(("ResetAnsiColorRequest(%s)\n", buf));
2729    if (*buf != '\0') {
2730	/* reset specific colors */
2731	while (!IsEmpty(buf)) {
2732	    char *next;
2733
2734	    color = (int) strtol(buf, &next, 10);
2735	    if ((next == buf) || (color < 0))
2736		break;		/* no number at all */
2737	    if (next != 0) {
2738		if (strchr(";", *next) == 0)
2739		    break;	/* unexpected delimiter */
2740		++next;
2741	    }
2742
2743	    if (ResetOneAnsiColor(xw, color, start)) {
2744		++repaint;
2745	    }
2746	    buf = next;
2747	}
2748    } else {
2749	TRACE(("...resetting all %d colors\n", MAXCOLORS));
2750	for (color = 0; color < MAXCOLORS; ++color) {
2751	    if (ResetOneAnsiColor(xw, color, start)) {
2752		++repaint;
2753	    }
2754	}
2755    }
2756    TRACE(("...ResetAnsiColorRequest ->%d\n", repaint));
2757    return repaint;
2758}
2759#else
2760#define allocateClosestRGB(xw, cmap, def) 0
2761#define allocateExactRGB(xw, cmap, def) XAllocColor(TScreenOf(xw)->display, cmap, def)
2762#endif /* OPT_ISO_COLORS */
2763
2764Boolean
2765allocateBestRGB(XtermWidget xw, XColor * def)
2766{
2767    Colormap cmap = xw->core.colormap;
2768
2769    return allocateExactRGB(xw, cmap, def) || allocateClosestRGB(xw, cmap, def);
2770}
2771
2772static Boolean
2773xtermAllocColor(XtermWidget xw, XColor * def, const char *spec)
2774{
2775    Boolean result = False;
2776    TScreen *screen = TScreenOf(xw);
2777    Colormap cmap = xw->core.colormap;
2778
2779    if (XParseColor(screen->display, cmap, spec, def)) {
2780	XColor save_def = *def;
2781	if (resource.reportColors) {
2782	    printf("color  %04x/%04x/%04x = \"%s\"\n",
2783		   def->red, def->green, def->blue,
2784		   spec);
2785	}
2786	if (allocateBestRGB(xw, def)) {
2787	    if (resource.reportColors) {
2788		if (def->red != save_def.red ||
2789		    def->green != save_def.green ||
2790		    def->blue != save_def.blue) {
2791		    printf("color  %04x/%04x/%04x ~ \"%s\"\n",
2792			   def->red, def->green, def->blue,
2793			   spec);
2794		}
2795	    }
2796	    TRACE(("xtermAllocColor -> %x/%x/%x\n",
2797		   def->red, def->green, def->blue));
2798	    result = True;
2799	}
2800    }
2801    return result;
2802}
2803
2804/*
2805 * This provides an approximation (the closest color from xterm's palette)
2806 * rather than the "exact" color (whatever the display could provide, actually)
2807 * because of the context in which it is used.
2808 */
2809#define ColorDiff(given,cache) ((long) ((cache) >> 8) - (long) (given))
2810int
2811xtermClosestColor(XtermWidget xw, int find_red, int find_green, int find_blue)
2812{
2813    int result = -1;
2814#if OPT_COLOR_RES && OPT_ISO_COLORS
2815    int n;
2816    int best_index = -1;
2817    unsigned long best_value = 0;
2818    unsigned long this_value;
2819    long diff_red, diff_green, diff_blue;
2820
2821    TRACE(("xtermClosestColor(%x/%x/%x)\n", find_red, find_green, find_blue));
2822
2823    for (n = NUM_ANSI_COLORS - 1; n >= 0; --n) {
2824	ColorRes *res = &(TScreenOf(xw)->Acolors[n]);
2825
2826	/* ensure that we have a value for each of the colors */
2827	if (!res->mode) {
2828	    (void) AllocateAnsiColor(xw, res, res->resource);
2829	}
2830
2831	/* find the closest match */
2832	if (res->mode == True) {
2833	    TRACE2(("...lookup %lx -> %x/%x/%x\n",
2834		    res->value, res->red, res->green, res->blue));
2835	    diff_red = ColorDiff(find_red, res->red);
2836	    diff_green = ColorDiff(find_green, res->green);
2837	    diff_blue = ColorDiff(find_blue, res->blue);
2838	    this_value = (unsigned long) ((diff_red * diff_red)
2839					  + (diff_green * diff_green)
2840					  + (diff_blue * diff_blue));
2841	    if (best_index < 0 || this_value < best_value) {
2842		best_index = n;
2843		best_value = this_value;
2844	    }
2845	}
2846    }
2847    TRACE(("...best match at %d with diff %lx\n", best_index, best_value));
2848    result = best_index;
2849#else
2850    (void) xw;
2851    (void) find_red;
2852    (void) find_green;
2853    (void) find_blue;
2854#endif
2855    return result;
2856}
2857
2858#if OPT_PASTE64
2859static void
2860ManipulateSelectionData(XtermWidget xw, TScreen *screen, char *buf, int final)
2861{
2862#define PDATA(a,b) { a, #b }
2863    static struct {
2864	char given;
2865	String result;
2866    } table[] = {
2867	PDATA('s', SELECT),
2868	    PDATA('p', PRIMARY),
2869	    PDATA('c', CLIPBOARD),
2870	    PDATA('0', CUT_BUFFER0),
2871	    PDATA('1', CUT_BUFFER1),
2872	    PDATA('2', CUT_BUFFER2),
2873	    PDATA('3', CUT_BUFFER3),
2874	    PDATA('4', CUT_BUFFER4),
2875	    PDATA('5', CUT_BUFFER5),
2876	    PDATA('6', CUT_BUFFER6),
2877	    PDATA('7', CUT_BUFFER7),
2878    };
2879
2880    const char *base = buf;
2881    char *used;
2882    Cardinal j, n = 0;
2883    String *select_args;
2884
2885    TRACE(("Manipulate selection data\n"));
2886
2887    while (*buf != ';' && *buf != '\0') {
2888	++buf;
2889    }
2890
2891    if (*buf == ';') {
2892	*buf++ = '\0';
2893
2894	if (*base == '\0')
2895	    base = "s0";
2896
2897	if ((used = x_strdup(base)) != 0) {
2898	    if ((select_args = TypeCallocN(String, 2 + strlen(base))) != 0) {
2899		while (*base != '\0') {
2900		    for (j = 0; j < XtNumber(table); ++j) {
2901			if (*base == table[j].given) {
2902			    used[n] = *base;
2903			    select_args[n++] = table[j].result;
2904			    TRACE(("atom[%d] %s\n", n, table[j].result));
2905			    break;
2906			}
2907		    }
2908		    ++base;
2909		}
2910		used[n] = 0;
2911
2912		if (!strcmp(buf, "?")) {
2913		    if (AllowWindowOps(xw, ewGetSelection)) {
2914			TRACE(("Getting selection\n"));
2915			unparseputc1(xw, ANSI_OSC);
2916			unparseputs(xw, "52");
2917			unparseputc(xw, ';');
2918
2919			unparseputs(xw, used);
2920			unparseputc(xw, ';');
2921
2922			/* Tell xtermGetSelection data is base64 encoded */
2923			screen->base64_paste = n;
2924			screen->base64_final = final;
2925
2926			/* terminator will be written in this call */
2927			xtermGetSelection((Widget) xw,
2928					  XtLastTimestampProcessed(TScreenOf(xw)->display),
2929					  select_args, n,
2930					  NULL);
2931		    }
2932		} else {
2933		    if (AllowWindowOps(xw, ewSetSelection)) {
2934			TRACE(("Setting selection with %s\n", buf));
2935			ClearSelectionBuffer(screen);
2936			while (*buf != '\0')
2937			    AppendToSelectionBuffer(screen, CharOf(*buf++));
2938			CompleteSelection(xw, select_args, n);
2939		    }
2940		}
2941		free(select_args);
2942	    }
2943	    free(used);
2944	}
2945    }
2946}
2947#endif /* OPT_PASTE64 */
2948
2949/***====================================================================***/
2950
2951#define IsSetUtf8Title(xw) (IsTitleMode(xw, tmSetUtf8) || (xw->screen.utf8_title))
2952
2953static Bool
2954xtermIsPrintable(XtermWidget xw, Char **bufp, Char *last)
2955{
2956    TScreen *screen = TScreenOf(xw);
2957    Bool result = False;
2958    Char *cp = *bufp;
2959    Char *next = cp;
2960
2961    (void) screen;
2962    (void) last;
2963
2964#if OPT_WIDE_CHARS
2965    if (xtermEnvUTF8() && IsSetUtf8Title(xw)) {
2966	PtyData data;
2967
2968	if (decodeUtf8(fakePtyData(&data, cp, last))) {
2969	    if (data.utf_data != UCS_REPL
2970		&& (data.utf_data >= 128 ||
2971		    ansi_table[data.utf_data] == CASE_PRINT)) {
2972		next += (data.utf_size - 1);
2973		result = True;
2974	    } else {
2975		result = False;
2976	    }
2977	} else {
2978	    result = False;
2979	}
2980    } else
2981#endif
2982#if OPT_C1_PRINT
2983	if (screen->c1_printable
2984	    && (*cp >= 128 && *cp < 160)) {
2985	result = True;
2986    } else
2987#endif
2988    if (ansi_table[*cp] == CASE_PRINT) {
2989	result = True;
2990    }
2991    *bufp = next;
2992    return result;
2993}
2994
2995/***====================================================================***/
2996
2997/*
2998 * Enum corresponding to the actual OSC codes rather than the internal
2999 * array indices.  Compare with TermColors.
3000 */
3001typedef enum {
3002    OSC_TEXT_FG = 10
3003    ,OSC_TEXT_BG
3004    ,OSC_TEXT_CURSOR
3005    ,OSC_MOUSE_FG
3006    ,OSC_MOUSE_BG
3007#if OPT_TEK4014
3008    ,OSC_TEK_FG = 15
3009    ,OSC_TEK_BG
3010#endif
3011#if OPT_HIGHLIGHT_COLOR
3012    ,OSC_HIGHLIGHT_BG = 17
3013#endif
3014#if OPT_TEK4014
3015    ,OSC_TEK_CURSOR = 18
3016#endif
3017#if OPT_HIGHLIGHT_COLOR
3018    ,OSC_HIGHLIGHT_FG = 19
3019#endif
3020    ,OSC_NCOLORS
3021} OscTextColors;
3022
3023/*
3024 * Map codes to OSC controls that can reset colors.
3025 */
3026#define OSC_RESET 100
3027#define OSC_Reset(code) (code) + OSC_RESET
3028
3029static ScrnColors *pOldColors = NULL;
3030
3031static Bool
3032GetOldColors(XtermWidget xw)
3033{
3034    int i;
3035    if (pOldColors == NULL) {
3036	pOldColors = TypeXtMalloc(ScrnColors);
3037	if (pOldColors == NULL) {
3038	    xtermWarning("allocation failure in GetOldColors\n");
3039	    return (False);
3040	}
3041	pOldColors->which = 0;
3042	for (i = 0; i < NCOLORS; i++) {
3043	    pOldColors->colors[i] = 0;
3044	    pOldColors->names[i] = NULL;
3045	}
3046	GetColors(xw, pOldColors);
3047    }
3048    return (True);
3049}
3050
3051static int
3052oppositeColor(int n)
3053{
3054    switch (n) {
3055    case TEXT_FG:
3056	n = TEXT_BG;
3057	break;
3058    case TEXT_BG:
3059	n = TEXT_FG;
3060	break;
3061    case MOUSE_FG:
3062	n = MOUSE_BG;
3063	break;
3064    case MOUSE_BG:
3065	n = MOUSE_FG;
3066	break;
3067#if OPT_TEK4014
3068    case TEK_FG:
3069	n = TEK_BG;
3070	break;
3071    case TEK_BG:
3072	n = TEK_FG;
3073	break;
3074#endif
3075#if OPT_HIGHLIGHT_COLOR
3076    case HIGHLIGHT_FG:
3077	n = HIGHLIGHT_BG;
3078	break;
3079    case HIGHLIGHT_BG:
3080	n = HIGHLIGHT_FG;
3081	break;
3082#endif
3083    default:
3084	break;
3085    }
3086    return n;
3087}
3088
3089static void
3090ReportColorRequest(XtermWidget xw, int ndx, int final)
3091{
3092    if (AllowColorOps(xw, ecGetColor)) {
3093	XColor color;
3094	Colormap cmap = xw->core.colormap;
3095	char buffer[80];
3096
3097	/*
3098	 * ChangeColorsRequest() has "always" chosen the opposite color when
3099	 * reverse-video is set.  Report this as the original color index, but
3100	 * reporting the opposite color which would be used.
3101	 */
3102	int i = (xw->misc.re_verse) ? oppositeColor(ndx) : ndx;
3103
3104	GetOldColors(xw);
3105	color.pixel = pOldColors->colors[ndx];
3106	XQueryColor(TScreenOf(xw)->display, cmap, &color);
3107	sprintf(buffer, "%d;rgb:%04x/%04x/%04x", i + 10,
3108		color.red,
3109		color.green,
3110		color.blue);
3111	TRACE(("ReportColorRequest #%d: 0x%06lx as %s\n",
3112	       ndx, pOldColors->colors[ndx], buffer));
3113	unparseputc1(xw, ANSI_OSC);
3114	unparseputs(xw, buffer);
3115	unparseputc1(xw, final);
3116	unparse_end(xw);
3117    }
3118}
3119
3120static Bool
3121UpdateOldColors(XtermWidget xw GCC_UNUSED, ScrnColors * pNew)
3122{
3123    int i;
3124
3125    /* if we were going to free old colors, this would be the place to
3126     * do it.   I've decided not to (for now), because it seems likely
3127     * that we'd have a small set of colors we use over and over, and that
3128     * we could save some overhead this way.   The only case in which this
3129     * (clearly) fails is if someone is trying a boatload of colors, in
3130     * which case they can restart xterm
3131     */
3132    for (i = 0; i < NCOLORS; i++) {
3133	if (COLOR_DEFINED(pNew, i)) {
3134	    if (pOldColors->names[i] != NULL) {
3135		XtFree(pOldColors->names[i]);
3136		pOldColors->names[i] = NULL;
3137	    }
3138	    if (pNew->names[i]) {
3139		pOldColors->names[i] = pNew->names[i];
3140	    }
3141	    pOldColors->colors[i] = pNew->colors[i];
3142	}
3143    }
3144    return (True);
3145}
3146
3147/*
3148 * OSC codes are constant, but the indices for the color arrays depend on how
3149 * xterm is compiled.
3150 */
3151static int
3152OscToColorIndex(OscTextColors mode)
3153{
3154    int result = 0;
3155
3156#define CASE(name) case OSC_##name: result = name; break
3157    switch (mode) {
3158	CASE(TEXT_FG);
3159	CASE(TEXT_BG);
3160	CASE(TEXT_CURSOR);
3161	CASE(MOUSE_FG);
3162	CASE(MOUSE_BG);
3163#if OPT_TEK4014
3164	CASE(TEK_FG);
3165	CASE(TEK_BG);
3166#endif
3167#if OPT_HIGHLIGHT_COLOR
3168	CASE(HIGHLIGHT_BG);
3169	CASE(HIGHLIGHT_FG);
3170#endif
3171#if OPT_TEK4014
3172	CASE(TEK_CURSOR);
3173#endif
3174    case OSC_NCOLORS:
3175	break;
3176    }
3177    return result;
3178}
3179
3180static Bool
3181ChangeColorsRequest(XtermWidget xw,
3182		    int start,
3183		    char *names,
3184		    int final)
3185{
3186    Bool result = False;
3187    char *thisName;
3188    ScrnColors newColors;
3189    int i, ndx;
3190
3191    TRACE(("ChangeColorsRequest start=%d, names='%s'\n", start, names));
3192
3193    if (GetOldColors(xw)) {
3194	newColors.which = 0;
3195	for (i = 0; i < NCOLORS; i++) {
3196	    newColors.names[i] = NULL;
3197	}
3198	for (i = start; i < OSC_NCOLORS; i++) {
3199	    ndx = OscToColorIndex((OscTextColors) i);
3200	    if (xw->misc.re_verse)
3201		ndx = oppositeColor(ndx);
3202
3203	    if (IsEmpty(names)) {
3204		newColors.names[ndx] = NULL;
3205	    } else {
3206		if (names[0] == ';')
3207		    thisName = NULL;
3208		else
3209		    thisName = names;
3210		names = strchr(names, ';');
3211		if (names != NULL) {
3212		    *names++ = '\0';
3213		}
3214		if (thisName != 0) {
3215		    if (!strcmp(thisName, "?")) {
3216			ReportColorRequest(xw, ndx, final);
3217		    } else if (!pOldColors->names[ndx]
3218			       || strcmp(thisName, pOldColors->names[ndx])) {
3219			AllocateTermColor(xw, &newColors, ndx, thisName, False);
3220		    }
3221		}
3222	    }
3223	}
3224
3225	if (newColors.which != 0) {
3226	    ChangeColors(xw, &newColors);
3227	    UpdateOldColors(xw, &newColors);
3228	}
3229	result = True;
3230    }
3231    return result;
3232}
3233
3234static Bool
3235ResetColorsRequest(XtermWidget xw,
3236		   int code)
3237{
3238    Bool result = False;
3239    const char *thisName;
3240    ScrnColors newColors;
3241    int ndx;
3242
3243    TRACE(("ResetColorsRequest code=%d\n", code));
3244
3245#if OPT_COLOR_RES
3246    if (GetOldColors(xw)) {
3247	ndx = OscToColorIndex((OscTextColors) (code - OSC_RESET));
3248	if (xw->misc.re_verse)
3249	    ndx = oppositeColor(ndx);
3250
3251	thisName = xw->screen.Tcolors[ndx].resource;
3252
3253	newColors.which = 0;
3254	newColors.names[ndx] = NULL;
3255
3256	if (thisName != 0
3257	    && pOldColors->names[ndx] != 0
3258	    && strcmp(thisName, pOldColors->names[ndx])) {
3259	    AllocateTermColor(xw, &newColors, ndx, thisName, False);
3260
3261	    if (newColors.which != 0) {
3262		ChangeColors(xw, &newColors);
3263		UpdateOldColors(xw, &newColors);
3264	    }
3265	}
3266	result = True;
3267    }
3268#endif
3269    return result;
3270}
3271
3272#if OPT_SHIFT_FONTS
3273/*
3274 * Initially, 'source' points to '#' or '?'.
3275 *
3276 * Look for an optional sign and optional number.  If those are found, lookup
3277 * the corresponding menu font entry.
3278 */
3279static int
3280ParseShiftedFont(XtermWidget xw, String source, String *target)
3281{
3282    TScreen *screen = TScreenOf(xw);
3283    int num = screen->menu_font_number;
3284    int rel = 0;
3285
3286    if (*++source == '+') {
3287	rel = 1;
3288	source++;
3289    } else if (*source == '-') {
3290	rel = -1;
3291	source++;
3292    }
3293
3294    if (isdigit(CharOf(*source))) {
3295	int val = atoi(source);
3296	if (rel > 0)
3297	    rel = val;
3298	else if (rel < 0)
3299	    rel = -val;
3300	else
3301	    num = val;
3302    }
3303
3304    if (rel != 0) {
3305	num = lookupRelativeFontSize(xw,
3306				     screen->menu_font_number, rel);
3307
3308    }
3309    TRACE(("ParseShiftedFont(%s) ->%d (%s)\n", *target, num, source));
3310    *target = source;
3311    return num;
3312}
3313
3314static void
3315QueryFontRequest(XtermWidget xw, String buf, int final)
3316{
3317    if (AllowFontOps(xw, efGetFont)) {
3318	TScreen *screen = TScreenOf(xw);
3319	Bool success = True;
3320	int num;
3321	String base = buf + 1;
3322	const char *name = 0;
3323	char temp[10];
3324
3325	num = ParseShiftedFont(xw, buf, &buf);
3326	if (num < 0
3327	    || num > fontMenu_lastBuiltin) {
3328	    Bell(xw, XkbBI_MinorError, 0);
3329	    success = False;
3330	} else {
3331#if OPT_RENDERFONT
3332	    if (UsingRenderFont(xw)) {
3333		name = getFaceName(xw, False);
3334	    } else
3335#endif
3336	    if ((name = screen->MenuFontName(num)) == 0) {
3337		success = False;
3338	    }
3339	}
3340
3341	unparseputc1(xw, ANSI_OSC);
3342	unparseputs(xw, "50");
3343
3344	if (success) {
3345	    unparseputc(xw, ';');
3346	    if (buf >= base) {
3347		/* identify the font-entry, unless it is the current one */
3348		if (*buf != '\0') {
3349		    unparseputc(xw, '#');
3350		    sprintf(temp, "%d", num);
3351		    unparseputs(xw, temp);
3352		    if (*name != '\0')
3353			unparseputc(xw, ' ');
3354		}
3355	    }
3356	    unparseputs(xw, name);
3357	}
3358
3359	unparseputc1(xw, final);
3360	unparse_end(xw);
3361    }
3362}
3363
3364static void
3365ChangeFontRequest(XtermWidget xw, String buf)
3366{
3367    if (AllowFontOps(xw, efSetFont)) {
3368	TScreen *screen = TScreenOf(xw);
3369	Bool success = True;
3370	int num;
3371	VTFontNames fonts;
3372	char *name;
3373
3374	/*
3375	 * If the font specification is a "#", followed by an optional sign and
3376	 * optional number, lookup the corresponding menu font entry.
3377	 *
3378	 * Further, if the "#", etc., is followed by a font name, use that
3379	 * to load the font entry.
3380	 */
3381	if (*buf == '#') {
3382	    num = ParseShiftedFont(xw, buf, &buf);
3383
3384	    if (num < 0
3385		|| num > fontMenu_lastBuiltin) {
3386		Bell(xw, XkbBI_MinorError, 0);
3387		success = False;
3388	    } else {
3389		/*
3390		 * Skip past the optional number, and any whitespace to look
3391		 * for a font specification within the control.
3392		 */
3393		while (isdigit(CharOf(*buf))) {
3394		    ++buf;
3395		}
3396		while (isspace(CharOf(*buf))) {
3397		    ++buf;
3398		}
3399#if OPT_RENDERFONT
3400		if (UsingRenderFont(xw)) {
3401		    /* EMPTY */
3402		    /* there is only one font entry to load */
3403		    ;
3404		} else
3405#endif
3406		{
3407		    /*
3408		     * Normally there is no font specified in the control.
3409		     * But if there is, simply overwrite the font entry.
3410		     */
3411		    if (*buf == '\0') {
3412			if ((buf = screen->MenuFontName(num)) == 0) {
3413			    success = False;
3414			}
3415		    }
3416		}
3417	    }
3418	} else {
3419	    num = screen->menu_font_number;
3420	}
3421	name = x_strtrim(buf);
3422	if (success && !IsEmpty(name)) {
3423#if OPT_RENDERFONT
3424	    if (UsingRenderFont(xw)) {
3425		setFaceName(xw, name);
3426		xtermUpdateFontInfo(xw, True);
3427	    } else
3428#endif
3429	    {
3430		memset(&fonts, 0, sizeof(fonts));
3431		fonts.f_n = name;
3432		SetVTFont(xw, num, True, &fonts);
3433	    }
3434	} else {
3435	    Bell(xw, XkbBI_MinorError, 0);
3436	}
3437	free(name);
3438    }
3439}
3440#endif /* OPT_SHIFT_FONTS */
3441
3442/***====================================================================***/
3443
3444void
3445do_osc(XtermWidget xw, Char *oscbuf, size_t len, int final)
3446{
3447    TScreen *screen = TScreenOf(xw);
3448    int mode;
3449    Char *cp;
3450    int state = 0;
3451    char *buf = 0;
3452    char temp[2];
3453#if OPT_ISO_COLORS
3454    int ansi_colors = 0;
3455#endif
3456    Bool need_data = True;
3457    Bool optional_data = False;
3458
3459    TRACE(("do_osc %s\n", oscbuf));
3460
3461    (void) screen;
3462
3463    /*
3464     * Lines should be of the form <OSC> number ; string <ST>, however
3465     * older xterms can accept <BEL> as a final character.  We will respond
3466     * with the same final character as the application sends to make this
3467     * work better with shell scripts, which may have trouble reading an
3468     * <ESC><backslash>, which is the 7-bit equivalent to <ST>.
3469     */
3470    mode = 0;
3471    for (cp = oscbuf; *cp != '\0'; cp++) {
3472	switch (state) {
3473	case 0:
3474	    if (isdigit(*cp)) {
3475		mode = 10 * mode + (*cp - '0');
3476		if (mode > 65535) {
3477		    TRACE(("do_osc found unknown mode %d\n", mode));
3478		    return;
3479		}
3480		break;
3481	    }
3482	    /* FALLTHRU */
3483	case 1:
3484	    if (*cp != ';') {
3485		TRACE(("do_osc did not find semicolon offset %d\n",
3486		       (int) (cp - oscbuf)));
3487		return;
3488	    }
3489	    state = 2;
3490	    break;
3491	case 2:
3492	    buf = (char *) cp;
3493	    state = 3;
3494	    /* FALLTHRU */
3495	default:
3496	    if (!xtermIsPrintable(xw, &cp, oscbuf + len)) {
3497		switch (mode) {
3498		case 0:
3499		case 1:
3500		case 2:
3501		    break;
3502		default:
3503		    TRACE(("do_osc found nonprinting char %02X offset %d\n",
3504			   CharOf(*cp),
3505			   (int) (cp - oscbuf)));
3506		    return;
3507		}
3508	    }
3509	}
3510    }
3511
3512    /*
3513     * Check if the palette changed and there are no more immediate changes
3514     * that could be deferred to the next repaint.
3515     */
3516    if (xw->misc.palette_changed) {
3517	switch (mode) {
3518	case 3:		/* change X property */
3519	case 30:		/* Konsole (unused) */
3520	case 31:		/* Konsole (unused) */
3521	case 50:		/* font operations */
3522	case 51:		/* Emacs (unused) */
3523#if OPT_PASTE64
3524	case 52:		/* selection data */
3525#endif
3526	    TRACE(("forced repaint after palette changed\n"));
3527	    xw->misc.palette_changed = False;
3528	    xtermRepaint(xw);
3529	    break;
3530	}
3531    }
3532
3533    /*
3534     * Most OSC controls other than resets require data.  Handle the others as
3535     * a special case.
3536     */
3537    switch (mode) {
3538#if OPT_ISO_COLORS
3539    case OSC_Reset(4):
3540    case OSC_Reset(5):
3541	need_data = False;
3542	optional_data = True;
3543	break;
3544    case OSC_Reset(OSC_TEXT_FG):
3545    case OSC_Reset(OSC_TEXT_BG):
3546    case OSC_Reset(OSC_TEXT_CURSOR):
3547    case OSC_Reset(OSC_MOUSE_FG):
3548    case OSC_Reset(OSC_MOUSE_BG):
3549#if OPT_HIGHLIGHT_COLOR
3550    case OSC_Reset(OSC_HIGHLIGHT_BG):
3551    case OSC_Reset(OSC_HIGHLIGHT_FG):
3552#endif
3553#if OPT_TEK4014
3554    case OSC_Reset(OSC_TEK_FG):
3555    case OSC_Reset(OSC_TEK_BG):
3556    case OSC_Reset(OSC_TEK_CURSOR):
3557#endif
3558	need_data = False;
3559	break;
3560#endif
3561    default:
3562	break;
3563    }
3564
3565    /*
3566     * Check if we have data when we want, and not when we do not want it.
3567     * Either way, that is a malformed control sequence, and will be ignored.
3568     */
3569    if (IsEmpty(buf)) {
3570	if (need_data) {
3571	    TRACE(("do_osc found no data\n"));
3572	    return;
3573	}
3574	temp[0] = '\0';
3575	buf = temp;
3576    } else if (!need_data && !optional_data) {
3577	TRACE(("do_osc found unwanted data\n"));
3578	return;
3579    }
3580
3581    switch (mode) {
3582    case 0:			/* new icon name and title */
3583	ChangeIconName(xw, buf);
3584	ChangeTitle(xw, buf);
3585	break;
3586
3587    case 1:			/* new icon name only */
3588	ChangeIconName(xw, buf);
3589	break;
3590
3591    case 2:			/* new title only */
3592	ChangeTitle(xw, buf);
3593	break;
3594
3595#ifdef notdef
3596    case 3:			/* change X property */
3597	if (AllowWindowOps(xw, ewSetXprop))
3598	    ChangeXprop(buf);
3599	break;
3600#endif
3601#if OPT_ISO_COLORS
3602    case 5:
3603	ansi_colors = NUM_ANSI_COLORS;
3604	/* FALLTHRU */
3605    case 4:
3606	if (ChangeAnsiColorRequest(xw, buf, ansi_colors, final))
3607	    xw->misc.palette_changed = True;
3608	break;
3609    case OSC_Reset(5):
3610	ansi_colors = NUM_ANSI_COLORS;
3611	/* FALLTHRU */
3612    case OSC_Reset(4):
3613	if (ResetAnsiColorRequest(xw, buf, ansi_colors))
3614	    xw->misc.palette_changed = True;
3615	break;
3616#endif
3617    case OSC_TEXT_FG:
3618    case OSC_TEXT_BG:
3619    case OSC_TEXT_CURSOR:
3620    case OSC_MOUSE_FG:
3621    case OSC_MOUSE_BG:
3622#if OPT_HIGHLIGHT_COLOR
3623    case OSC_HIGHLIGHT_BG:
3624    case OSC_HIGHLIGHT_FG:
3625#endif
3626#if OPT_TEK4014
3627    case OSC_TEK_FG:
3628    case OSC_TEK_BG:
3629    case OSC_TEK_CURSOR:
3630#endif
3631	if (xw->misc.dynamicColors) {
3632	    ChangeColorsRequest(xw, mode, buf, final);
3633	}
3634	break;
3635    case OSC_Reset(OSC_TEXT_FG):
3636    case OSC_Reset(OSC_TEXT_BG):
3637    case OSC_Reset(OSC_TEXT_CURSOR):
3638    case OSC_Reset(OSC_MOUSE_FG):
3639    case OSC_Reset(OSC_MOUSE_BG):
3640#if OPT_HIGHLIGHT_COLOR
3641    case OSC_Reset(OSC_HIGHLIGHT_BG):
3642    case OSC_Reset(OSC_HIGHLIGHT_FG):
3643#endif
3644#if OPT_TEK4014
3645    case OSC_Reset(OSC_TEK_FG):
3646    case OSC_Reset(OSC_TEK_BG):
3647    case OSC_Reset(OSC_TEK_CURSOR):
3648#endif
3649	if (xw->misc.dynamicColors) {
3650	    ResetColorsRequest(xw, mode);
3651	}
3652	break;
3653
3654    case 30:
3655    case 31:
3656	/* reserved for Konsole (Stephan Binner <Stephan.Binner@gmx.de>) */
3657	break;
3658
3659#ifdef ALLOWLOGGING
3660    case 46:			/* new log file */
3661#ifdef ALLOWLOGFILECHANGES
3662	/*
3663	 * Warning, enabling this feature allows people to overwrite
3664	 * arbitrary files accessible to the person running xterm.
3665	 */
3666	if (strcmp(buf, "?")
3667	    && (cp = CastMallocN(char, strlen(buf)) != NULL)) {
3668	    strcpy(cp, buf);
3669	    if (screen->logfile)
3670		free(screen->logfile);
3671	    screen->logfile = cp;
3672	    break;
3673	}
3674#endif
3675	Bell(xw, XkbBI_Info, 0);
3676	Bell(xw, XkbBI_Info, 0);
3677	break;
3678#endif /* ALLOWLOGGING */
3679
3680    case 50:
3681#if OPT_SHIFT_FONTS
3682	if (*buf == '?') {
3683	    QueryFontRequest(xw, buf, final);
3684	} else if (xw->misc.shift_fonts) {
3685	    ChangeFontRequest(xw, buf);
3686	}
3687#endif /* OPT_SHIFT_FONTS */
3688	break;
3689    case 51:
3690	/* reserved for Emacs shell (Rob Mayoff <mayoff@dqd.com>) */
3691	break;
3692
3693#if OPT_PASTE64
3694    case 52:
3695	ManipulateSelectionData(xw, screen, buf, final);
3696	break;
3697#endif
3698	/*
3699	 * One could write code to send back the display and host names,
3700	 * but that could potentially open a fairly nasty security hole.
3701	 */
3702    default:
3703	TRACE(("do_osc - unrecognized code\n"));
3704	break;
3705    }
3706    unparse_end(xw);
3707}
3708
3709#ifdef SunXK_F36
3710#define MAX_UDK 37
3711#else
3712#define MAX_UDK 35
3713#endif
3714static struct {
3715    char *str;
3716    int len;
3717} user_keys[MAX_UDK];
3718
3719/*
3720 * Parse one nibble of a hex byte from the OSC string.  We have removed the
3721 * string-terminator (replacing it with a null), so the only other delimiter
3722 * that is expected is semicolon.  Ignore other characters (Ray Neuman says
3723 * "real" terminals accept commas in the string definitions).
3724 */
3725static int
3726udk_value(const char **cp)
3727{
3728    int result = -1;
3729    int c;
3730
3731    for (;;) {
3732	if ((c = **cp) != '\0')
3733	    *cp = *cp + 1;
3734	if (c == ';' || c == '\0')
3735	    break;
3736	if ((result = x_hex2int(c)) >= 0)
3737	    break;
3738    }
3739
3740    return result;
3741}
3742
3743void
3744reset_decudk(void)
3745{
3746    int n;
3747    for (n = 0; n < MAX_UDK; n++) {
3748	if (user_keys[n].str != 0) {
3749	    free(user_keys[n].str);
3750	    user_keys[n].str = 0;
3751	    user_keys[n].len = 0;
3752	}
3753    }
3754}
3755
3756/*
3757 * Parse the data for DECUDK (user-defined keys).
3758 */
3759static void
3760parse_decudk(const char *cp)
3761{
3762    while (*cp) {
3763	const char *base = cp;
3764	char *str = CastMallocN(char, strlen(cp) + 2);
3765	unsigned key = 0;
3766	int lo, hi;
3767	int len = 0;
3768
3769	while (isdigit(CharOf(*cp)))
3770	    key = (key * 10) + (unsigned) (*cp++ - '0');
3771	if (*cp == '/') {
3772	    cp++;
3773	    while ((hi = udk_value(&cp)) >= 0
3774		   && (lo = udk_value(&cp)) >= 0) {
3775		str[len++] = (char) ((hi << 4) | lo);
3776	    }
3777	}
3778	if (len > 0 && key < MAX_UDK) {
3779	    str[len] = '\0';
3780	    if (user_keys[key].str != 0)
3781		free(user_keys[key].str);
3782	    user_keys[key].str = str;
3783	    user_keys[key].len = len;
3784	} else {
3785	    free(str);
3786	}
3787	if (*cp == ';')
3788	    cp++;
3789	if (cp == base)		/* badly-formed sequence - bail out */
3790	    break;
3791    }
3792}
3793
3794/*
3795 * Parse numeric parameters.  Normally we use a state machine to simplify
3796 * interspersing with control characters, but have the string already.
3797 */
3798static void
3799parse_ansi_params(ANSI *params, const char **string)
3800{
3801    const char *cp = *string;
3802    ParmType nparam = 0;
3803    int last_empty = 1;
3804
3805    memset(params, 0, sizeof(*params));
3806    while (*cp != '\0') {
3807	Char ch = CharOf(*cp++);
3808
3809	if (isdigit(ch)) {
3810	    last_empty = 0;
3811	    if (nparam < NPARAM) {
3812		params->a_param[nparam] =
3813		    (ParmType) ((params->a_param[nparam] * 10)
3814				+ (ch - '0'));
3815	    }
3816	} else if (ch == ';') {
3817	    last_empty = 1;
3818	    nparam++;
3819	} else if (ch < 32) {
3820	    /* EMPTY */ ;
3821	} else {
3822	    /* should be 0x30 to 0x7e */
3823	    params->a_final = ch;
3824	    break;
3825	}
3826    }
3827
3828    *string = cp;
3829    if (!last_empty)
3830	nparam++;
3831    if (nparam > NPARAM)
3832	params->a_nparam = NPARAM;
3833    else
3834	params->a_nparam = nparam;
3835}
3836
3837#if OPT_TRACE
3838#define SOFT_WIDE 10
3839#define SOFT_HIGH 20
3840
3841static void
3842parse_decdld(ANSI *params, const char *string)
3843{
3844    char DscsName[8];
3845    int len;
3846    int Pfn = params->a_param[0];
3847    int Pcn = params->a_param[1];
3848    int Pe = params->a_param[2];
3849    int Pcmw = params->a_param[3];
3850    int Pw = params->a_param[4];
3851    int Pt = params->a_param[5];
3852    int Pcmh = params->a_param[6];
3853    int Pcss = params->a_param[7];
3854
3855    int start_char = Pcn + 0x20;
3856    int char_wide = ((Pcmw == 0)
3857		     ? (Pcss ? 6 : 10)
3858		     : (Pcmw > 4
3859			? Pcmw
3860			: (Pcmw + 3)));
3861    int char_high = ((Pcmh == 0)
3862		     ? ((Pcmw >= 2 && Pcmw <= 4)
3863			? 10
3864			: 20)
3865		     : Pcmh);
3866    Char ch;
3867    Char bits[SOFT_HIGH][SOFT_WIDE];
3868    Bool first = True;
3869    Bool prior = False;
3870    int row = 0, col = 0;
3871
3872    TRACE(("Parsing DECDLD\n"));
3873    TRACE(("  font number   %d\n", Pfn));
3874    TRACE(("  starting char %d\n", Pcn));
3875    TRACE(("  erase control %d\n", Pe));
3876    TRACE(("  char-width    %d\n", Pcmw));
3877    TRACE(("  font-width    %d\n", Pw));
3878    TRACE(("  text/full     %d\n", Pt));
3879    TRACE(("  char-height   %d\n", Pcmh));
3880    TRACE(("  charset-size  %d\n", Pcss));
3881
3882    if (Pfn > 1
3883	|| Pcn > 95
3884	|| Pe > 2
3885	|| Pcmw > 10
3886	|| Pcmw == 1
3887	|| Pt > 2
3888	|| Pcmh > 20
3889	|| Pcss > 1
3890	|| char_wide > SOFT_WIDE
3891	|| char_high > SOFT_HIGH) {
3892	TRACE(("DECDLD illegal parameter\n"));
3893	return;
3894    }
3895
3896    len = 0;
3897    while (*string != '\0') {
3898	ch = CharOf(*string++);
3899	if (ch >= ANSI_SPA && ch <= 0x2f) {
3900	    if (len < 2)
3901		DscsName[len++] = (char) ch;
3902	} else if (ch >= 0x30 && ch <= 0x7e) {
3903	    DscsName[len++] = (char) ch;
3904	    break;
3905	}
3906    }
3907    DscsName[len] = 0;
3908    TRACE(("  Dscs name     '%s'\n", DscsName));
3909
3910    TRACE(("  character matrix %dx%d\n", char_high, char_wide));
3911    while (*string != '\0') {
3912	if (first) {
3913	    TRACE(("Char %d:\n", start_char));
3914	    if (prior) {
3915		for (row = 0; row < char_high; ++row) {
3916		    TRACE(("%.*s\n", char_wide, bits[row]));
3917		}
3918	    }
3919	    prior = False;
3920	    first = False;
3921	    for (row = 0; row < char_high; ++row) {
3922		for (col = 0; col < char_wide; ++col) {
3923		    bits[row][col] = '.';
3924		}
3925	    }
3926	    row = col = 0;
3927	}
3928	ch = CharOf(*string++);
3929	if (ch >= 0x3f && ch <= 0x7e) {
3930	    int n;
3931
3932	    ch = CharOf(ch - 0x3f);
3933	    for (n = 0; n < 6; ++n) {
3934		bits[row + n][col] = CharOf((ch & (1 << n)) ? '*' : '.');
3935	    }
3936	    col += 1;
3937	    prior = True;
3938	} else if (ch == '/') {
3939	    row += 6;
3940	    col = 0;
3941	} else if (ch == ';') {
3942	    first = True;
3943	    ++start_char;
3944	}
3945    }
3946}
3947#else
3948#define parse_decdld(p,q)	/* nothing */
3949#endif
3950
3951void
3952do_dcs(XtermWidget xw, Char *dcsbuf, size_t dcslen)
3953{
3954    TScreen *screen = TScreenOf(xw);
3955    char reply[BUFSIZ];
3956    const char *cp = (const char *) dcsbuf;
3957    Bool okay;
3958    ANSI params;
3959
3960    TRACE(("do_dcs(%s:%lu)\n", (char *) dcsbuf, (unsigned long) dcslen));
3961
3962    if (dcslen != strlen(cp))
3963	/* shouldn't have nulls in the string */
3964	return;
3965
3966    switch (*cp) {		/* intermediate character, or parameter */
3967    case '$':			/* DECRQSS */
3968	okay = True;
3969
3970	cp++;
3971	if (*cp++ == 'q') {
3972	    if (!strcmp(cp, "\"q")) {	/* DECSCA */
3973		sprintf(reply, "%d%s",
3974			(screen->protected_mode == DEC_PROTECT)
3975			&& (xw->flags & PROTECTED) ? 1 : 0,
3976			cp);
3977	    } else if (!strcmp(cp, "\"p")) {	/* DECSCL */
3978		if (screen->vtXX_level < 2) {
3979		    /* actually none of DECRQSS is valid for vt100's */
3980		    break;
3981		}
3982		sprintf(reply, "%d%s%s",
3983			(screen->vtXX_level ?
3984			 screen->vtXX_level : 1) + 60,
3985			(screen->vtXX_level >= 2)
3986			? (screen->control_eight_bits
3987			   ? ";0" : ";1")
3988			: "",
3989			cp);
3990	    } else if (!strcmp(cp, "r")) {	/* DECSTBM */
3991		sprintf(reply, "%d;%dr",
3992			screen->top_marg + 1,
3993			screen->bot_marg + 1);
3994	    } else if (!strcmp(cp, "s")) {	/* DECSLRM */
3995		if (screen->vtXX_level >= 4) {	/* VT420 */
3996		    sprintf(reply, "%d;%ds",
3997			    screen->lft_marg + 1,
3998			    screen->rgt_marg + 1);
3999		}
4000	    } else if (!strcmp(cp, "m")) {	/* SGR */
4001		strcpy(reply, "0");
4002		if (xw->flags & BOLD)
4003		    strcat(reply, ";1");
4004		if (xw->flags & UNDERLINE)
4005		    strcat(reply, ";4");
4006		if (xw->flags & BLINK)
4007		    strcat(reply, ";5");
4008		if (xw->flags & INVERSE)
4009		    strcat(reply, ";7");
4010		if (xw->flags & INVISIBLE)
4011		    strcat(reply, ";8");
4012#if OPT_256_COLORS || OPT_88_COLORS
4013		if_OPT_ISO_COLORS(screen, {
4014		    if (xw->flags & FG_COLOR) {
4015			if (xw->cur_foreground >= 16)
4016			    sprintf(reply + strlen(reply),
4017				    ";38;5;%d", xw->cur_foreground);
4018			else
4019			    sprintf(reply + strlen(reply),
4020				    ";%d%d",
4021				    xw->cur_foreground >= 8 ? 9 : 3,
4022				    xw->cur_foreground >= 8 ?
4023				    xw->cur_foreground - 8 :
4024				    xw->cur_foreground);
4025		    }
4026		    if (xw->flags & BG_COLOR) {
4027			if (xw->cur_background >= 16)
4028			    sprintf(reply + strlen(reply),
4029				    ";48;5;%d", xw->cur_foreground);
4030			else
4031			    sprintf(reply + strlen(reply),
4032				    ";%d%d",
4033				    xw->cur_background >= 8 ? 10 : 4,
4034				    xw->cur_background >= 8 ?
4035				    xw->cur_background - 8 :
4036				    xw->cur_background);
4037		    }
4038		});
4039#elif OPT_ISO_COLORS
4040		if_OPT_ISO_COLORS(screen, {
4041		    if (xw->flags & FG_COLOR)
4042			sprintf(reply + strlen(reply),
4043				";%d%d",
4044				xw->cur_foreground >= 8 ? 9 : 3,
4045				xw->cur_foreground >= 8 ?
4046				xw->cur_foreground - 8 :
4047				xw->cur_foreground);
4048		    if (xw->flags & BG_COLOR)
4049			sprintf(reply + strlen(reply),
4050				";%d%d",
4051				xw->cur_background >= 8 ? 10 : 4,
4052				xw->cur_background >= 8 ?
4053				xw->cur_background - 8 :
4054				xw->cur_background);
4055		});
4056#endif
4057		strcat(reply, "m");
4058	    } else if (!strcmp(cp, " q")) {	/* DECSCUSR */
4059		int code = STEADY_BLOCK;
4060		if (isCursorUnderline(screen))
4061		    code = STEADY_UNDERLINE;
4062		else if (isCursorBar(screen))
4063		    code = STEADY_BAR;
4064#if OPT_BLINK_CURS
4065		if (screen->cursor_blink_esc == 0)
4066		    code -= 1;
4067#endif
4068		sprintf(reply, "%d%s", code, cp);
4069	    } else
4070		okay = False;
4071
4072	    if (okay) {
4073		unparseputc1(xw, ANSI_DCS);
4074		unparseputc(xw, '1');
4075		unparseputc(xw, '$');
4076		unparseputc(xw, 'r');
4077		cp = reply;
4078		unparseputs(xw, cp);
4079		unparseputc1(xw, ANSI_ST);
4080	    } else {
4081		unparseputc(xw, ANSI_CAN);
4082	    }
4083	} else {
4084	    unparseputc(xw, ANSI_CAN);
4085	}
4086	break;
4087#if OPT_TCAP_QUERY
4088    case '+':
4089	cp++;
4090	switch (*cp) {
4091	case 'p':
4092	    if (AllowTcapOps(xw, etSetTcap)) {
4093		set_termcap(xw, cp + 1);
4094	    }
4095	    break;
4096	case 'q':
4097	    if (AllowTcapOps(xw, etGetTcap)) {
4098		Bool fkey;
4099		unsigned state;
4100		int code;
4101		const char *tmp;
4102		const char *parsed = ++cp;
4103
4104		code = xtermcapKeycode(xw, &parsed, &state, &fkey);
4105
4106		unparseputc1(xw, ANSI_DCS);
4107
4108		unparseputc(xw, code >= 0 ? '1' : '0');
4109
4110		unparseputc(xw, '+');
4111		unparseputc(xw, 'r');
4112
4113		while (*cp != 0 && (code >= -1)) {
4114		    if (cp == parsed)
4115			break;	/* no data found, error */
4116
4117		    for (tmp = cp; tmp != parsed; ++tmp)
4118			unparseputc(xw, *tmp);
4119
4120		    if (code >= 0) {
4121			unparseputc(xw, '=');
4122			screen->tc_query_code = code;
4123			screen->tc_query_fkey = fkey;
4124#if OPT_ISO_COLORS
4125			/* XK_COLORS is a fake code for the "Co" entry (maximum
4126			 * number of colors) */
4127			if (code == XK_COLORS) {
4128			    unparseputn(xw, NUM_ANSI_COLORS);
4129			} else
4130#endif
4131			if (code == XK_TCAPNAME) {
4132			    unparseputs(xw, resource.term_name);
4133			} else {
4134			    XKeyEvent event;
4135			    event.state = state;
4136			    Input(xw, &event, False);
4137			}
4138			screen->tc_query_code = -1;
4139		    } else {
4140			break;	/* no match found, error */
4141		    }
4142
4143		    cp = parsed;
4144		    if (*parsed == ';') {
4145			unparseputc(xw, *parsed++);
4146			cp = parsed;
4147			code = xtermcapKeycode(xw, &parsed, &state, &fkey);
4148		    }
4149		}
4150		unparseputc1(xw, ANSI_ST);
4151	    }
4152	    break;
4153	}
4154	break;
4155#endif
4156    default:
4157	if (screen->terminal_id == 125 ||
4158	    screen->vtXX_level >= 2) {	/* VT220 */
4159	    parse_ansi_params(&params, &cp);
4160	    switch (params.a_final) {
4161#if OPT_SIXEL_GRAPHICS
4162	    case 'p':
4163		if (screen->terminal_id == 125 ||
4164		    screen->terminal_id == 240 ||
4165		    screen->terminal_id == 241 ||
4166		    screen->terminal_id == 330 ||
4167		    screen->terminal_id == 340) {
4168		    parse_regis(xw, &params, cp);
4169		}
4170		break;
4171	    case 'q':
4172		if (screen->terminal_id == 125 ||
4173		    screen->terminal_id == 240 ||
4174		    screen->terminal_id == 241 ||
4175		    screen->terminal_id == 330 ||
4176		    screen->terminal_id == 340) {
4177		    parse_sixel(xw, &params, cp);
4178		}
4179		break;
4180#endif
4181	    case '|':		/* DECUDK */
4182		if (params.a_param[0] == 0)
4183		    reset_decudk();
4184		parse_decudk(cp);
4185		break;
4186	    case '{':		/* DECDLD (no '}' case though) */
4187		parse_decdld(&params, cp);
4188		break;
4189	    }
4190	}
4191	break;
4192    }
4193    unparse_end(xw);
4194}
4195
4196#if OPT_DEC_RECTOPS
4197enum {
4198    mdUnknown = 0,
4199    mdMaybeSet = 1,
4200    mdMaybeReset = 2,
4201    mdAlwaysSet = 3,
4202    mdAlwaysReset = 4
4203};
4204
4205#define MdBool(bool)      ((bool) ? mdMaybeSet : mdMaybeReset)
4206#define MdFlag(mode,flag) MdBool((mode) & (flag))
4207
4208/*
4209 * Reply is the same format as the query, with pair of mode/value:
4210 * 0 - not recognized
4211 * 1 - set
4212 * 2 - reset
4213 * 3 - permanently set
4214 * 4 - permanently reset
4215 * Only one mode can be reported at a time.
4216 */
4217void
4218do_rpm(XtermWidget xw, int nparams, int *params)
4219{
4220    ANSI reply;
4221    int result = 0;
4222    int count = 0;
4223
4224    TRACE(("do_rpm %d:%d\n", nparams, params[0]));
4225    memset(&reply, 0, sizeof(reply));
4226    if (nparams >= 1) {
4227	switch (params[0]) {
4228	case 1:		/* GATM */
4229	    result = mdAlwaysReset;
4230	    break;
4231	case 2:
4232	    result = MdFlag(xw->keyboard.flags, MODE_KAM);
4233	    break;
4234	case 3:		/* CRM */
4235	    result = mdMaybeReset;
4236	    break;
4237	case 4:
4238	    result = MdFlag(xw->flags, INSERT);
4239	    break;
4240	case 5:		/* SRTM */
4241	case 7:		/* VEM */
4242	case 10:		/* HEM */
4243	case 11:		/* PUM */
4244	    result = mdAlwaysReset;
4245	    break;
4246	case 12:
4247	    result = MdFlag(xw->keyboard.flags, MODE_SRM);
4248	    break;
4249	case 13:		/* FEAM */
4250	case 14:		/* FETM */
4251	case 15:		/* MATM */
4252	case 16:		/* TTM */
4253	case 17:		/* SATM */
4254	case 18:		/* TSM */
4255	case 19:		/* EBM */
4256	    result = mdAlwaysReset;
4257	    break;
4258	case 20:
4259	    result = MdFlag(xw->flags, LINEFEED);
4260	    break;
4261	}
4262	reply.a_param[count++] = (ParmType) params[0];
4263	reply.a_param[count++] = (ParmType) result;
4264    }
4265    reply.a_type = ANSI_CSI;
4266    reply.a_nparam = (ParmType) count;
4267    reply.a_inters = '$';
4268    reply.a_final = 'y';
4269    unparseseq(xw, &reply);
4270}
4271
4272void
4273do_decrpm(XtermWidget xw, int nparams, int *params)
4274{
4275    ANSI reply;
4276    int result = 0;
4277    int count = 0;
4278
4279    TRACE(("do_decrpm %d:%d\n", nparams, params[0]));
4280    memset(&reply, 0, sizeof(reply));
4281    if (nparams >= 1) {
4282	TScreen *screen = TScreenOf(xw);
4283
4284	switch (params[0]) {
4285	case srm_DECCKM:
4286	    result = MdFlag(xw->keyboard.flags, MODE_DECCKM);
4287	    break;
4288	case srm_DECANM:	/* ANSI/VT52 mode      */
4289#if OPT_VT52_MODE
4290	    result = MdBool(screen->vtXX_level >= 1);
4291#else
4292	    result = mdMaybeSet;
4293#endif
4294	    break;
4295	case srm_DECCOLM:
4296	    result = MdFlag(xw->flags, IN132COLUMNS);
4297	    break;
4298	case srm_DECSCLM:	/* (slow scroll)        */
4299	    result = MdFlag(xw->flags, SMOOTHSCROLL);
4300	    break;
4301	case srm_DECSCNM:
4302	    result = MdFlag(xw->flags, REVERSE_VIDEO);
4303	    break;
4304	case srm_DECOM:
4305	    result = MdFlag(xw->flags, ORIGIN);
4306	    break;
4307	case srm_DECAWM:
4308	    result = MdFlag(xw->flags, WRAPAROUND);
4309	    break;
4310	case srm_DECARM:
4311	    result = mdAlwaysReset;
4312	    break;
4313	case srm_X10_MOUSE:	/* X10 mouse                    */
4314	    result = MdBool(screen->send_mouse_pos == X10_MOUSE);
4315	    break;
4316#if OPT_TOOLBAR
4317	case srm_RXVT_TOOLBAR:
4318	    result = MdBool(resource.toolBar);
4319	    break;
4320#endif
4321#if OPT_BLINK_CURS
4322	case srm_ATT610_BLINK:	/* att610: Start/stop blinking cursor */
4323	    result = MdBool(screen->cursor_blink_res);
4324	    break;
4325#endif
4326	case srm_DECPFF:	/* print form feed */
4327	    result = MdBool(PrinterOf(screen).printer_formfeed);
4328	    break;
4329	case srm_DECPEX:	/* print extent */
4330	    result = MdBool(PrinterOf(screen).printer_extent);
4331	    break;
4332	case srm_DECTCEM:	/* Show/hide cursor (VT200) */
4333	    result = MdBool(screen->cursor_set);
4334	    break;
4335	case srm_RXVT_SCROLLBAR:
4336	    result = MdBool(screen->fullVwin.sb_info.width != OFF);
4337	    break;
4338#if OPT_SHIFT_FONTS
4339	case srm_RXVT_FONTSIZE:
4340	    result = MdBool(xw->misc.shift_fonts);
4341	    break;
4342#endif
4343#if OPT_TEK4014
4344	case srm_DECTEK:
4345	    result = MdBool(TEK4014_ACTIVE(xw));
4346	    break;
4347#endif
4348	case srm_132COLS:
4349	    result = MdBool(screen->c132);
4350	    break;
4351	case srm_CURSES_HACK:
4352	    result = MdBool(screen->curses);
4353	    break;
4354	case srm_DECNRCM:	/* national charset (VT220) */
4355	    result = MdFlag(xw->flags, NATIONAL);
4356	    break;
4357	case srm_MARGIN_BELL:	/* margin bell                  */
4358	    result = MdBool(screen->marginbell);
4359	    break;
4360	case srm_REVERSEWRAP:	/* reverse wraparound   */
4361	    result = MdFlag(xw->flags, REVERSEWRAP);
4362	    break;
4363#ifdef ALLOWLOGGING
4364	case srm_ALLOWLOGGING:	/* logging              */
4365#ifdef ALLOWLOGFILEONOFF
4366	    result = MdBool(screen->logging);
4367#endif /* ALLOWLOGFILEONOFF */
4368	    break;
4369#endif
4370	case srm_OPT_ALTBUF_CURSOR:	/* alternate buffer & cursor */
4371	    /* FALLTHRU */
4372	case srm_OPT_ALTBUF:
4373	    /* FALLTHRU */
4374	case srm_ALTBUF:
4375	    result = MdBool(screen->whichBuf);
4376	    break;
4377	case srm_DECNKM:
4378	    result = MdFlag(xw->keyboard.flags, MODE_DECKPAM);
4379	    break;
4380	case srm_DECBKM:
4381	    result = MdFlag(xw->keyboard.flags, MODE_DECBKM);
4382	    break;
4383	case srm_DECLRMM:
4384	    result = MdFlag(xw->flags, LEFT_RIGHT);
4385	    break;
4386#if OPT_SIXEL_GRAPHICS
4387	case srm_DECSDM:
4388	    result = MdFlag(xw->keyboard.flags, MODE_DECSDM);
4389	    break;
4390#endif
4391	case srm_DECNCSM:
4392	    result = MdFlag(xw->flags, NOCLEAR_COLM);
4393	    break;
4394	case srm_VT200_MOUSE:	/* xterm bogus sequence         */
4395	    result = MdBool(screen->send_mouse_pos == VT200_MOUSE);
4396	    break;
4397	case srm_VT200_HIGHLIGHT_MOUSE:	/* xterm sequence w/hilite tracking */
4398	    result = MdBool(screen->send_mouse_pos == VT200_HIGHLIGHT_MOUSE);
4399	    break;
4400	case srm_BTN_EVENT_MOUSE:
4401	    result = MdBool(screen->send_mouse_pos == BTN_EVENT_MOUSE);
4402	    break;
4403	case srm_ANY_EVENT_MOUSE:
4404	    result = MdBool(screen->send_mouse_pos == ANY_EVENT_MOUSE);
4405	    break;
4406#if OPT_FOCUS_EVENT
4407	case srm_FOCUS_EVENT_MOUSE:
4408	    result = MdBool(screen->send_focus_pos);
4409	    break;
4410#endif
4411	case srm_EXT_MODE_MOUSE:
4412	    /* FALLTHRU */
4413	case srm_SGR_EXT_MODE_MOUSE:
4414	    /* FALLTHRU */
4415	case srm_URXVT_EXT_MODE_MOUSE:
4416	    result = MdBool(screen->extend_coords == params[0]);
4417	    break;
4418	case srm_ALTERNATE_SCROLL:
4419	    result = MdBool(screen->alternateScroll);
4420	    break;
4421	case srm_RXVT_SCROLL_TTY_OUTPUT:
4422	    result = MdBool(screen->scrollttyoutput);
4423	    break;
4424	case srm_RXVT_SCROLL_TTY_KEYPRESS:
4425	    result = MdBool(screen->scrollkey);
4426	    break;
4427	case srm_EIGHT_BIT_META:
4428	    result = MdBool(screen->eight_bit_meta);
4429	    break;
4430#if OPT_NUM_LOCK
4431	case srm_REAL_NUMLOCK:
4432	    result = MdBool(xw->misc.real_NumLock);
4433	    break;
4434	case srm_META_SENDS_ESC:
4435	    result = MdBool(screen->meta_sends_esc);
4436	    break;
4437#endif
4438	case srm_DELETE_IS_DEL:
4439	    result = MdBool(screen->delete_is_del);
4440	    break;
4441#if OPT_NUM_LOCK
4442	case srm_ALT_SENDS_ESC:
4443	    result = MdBool(screen->alt_sends_esc);
4444	    break;
4445#endif
4446	case srm_KEEP_SELECTION:
4447	    result = MdBool(screen->keepSelection);
4448	    break;
4449	case srm_SELECT_TO_CLIPBOARD:
4450	    result = MdBool(screen->selectToClipboard);
4451	    break;
4452	case srm_BELL_IS_URGENT:
4453	    result = MdBool(screen->bellIsUrgent);
4454	    break;
4455	case srm_POP_ON_BELL:
4456	    result = MdBool(screen->poponbell);
4457	    break;
4458	case srm_TITE_INHIBIT:
4459	    result = MdBool(screen->sc[screen->whichBuf].saved);
4460	    break;
4461#if OPT_TCAP_FKEYS
4462	case srm_TCAP_FKEYS:
4463	    result = MdBool(xw->keyboard.type == keyboardIsTermcap);
4464	    break;
4465#endif
4466#if OPT_SUN_FUNC_KEYS
4467	case srm_SUN_FKEYS:
4468	    result = MdBool(xw->keyboard.type == keyboardIsSun);
4469	    break;
4470#endif
4471#if OPT_HP_FUNC_KEYS
4472	case srm_HP_FKEYS:
4473	    result = MdBool(xw->keyboard.type == keyboardIsHP);
4474	    break;
4475#endif
4476#if OPT_SCO_FUNC_KEYS
4477	case srm_SCO_FKEYS:
4478	    result = MdBool(xw->keyboard.type == keyboardIsSCO);
4479	    break;
4480#endif
4481	case srm_LEGACY_FKEYS:
4482	    result = MdBool(xw->keyboard.type == keyboardIsLegacy);
4483	    break;
4484#if OPT_SUNPC_KBD
4485	case srm_VT220_FKEYS:
4486	    result = MdBool(xw->keyboard.type == keyboardIsVT220);
4487	    break;
4488#endif
4489#if OPT_READLINE
4490	case srm_BUTTON1_MOVE_POINT:
4491	    result = MdBool(screen->click1_moves);
4492	    break;
4493	case srm_BUTTON2_MOVE_POINT:
4494	    result = MdBool(screen->paste_moves);
4495	    break;
4496	case srm_DBUTTON3_DELETE:
4497	    result = MdBool(screen->dclick3_deletes);
4498	    break;
4499	case srm_PASTE_IN_BRACKET:
4500	    result = MdBool(screen->paste_brackets);
4501	    break;
4502	case srm_PASTE_QUOTE:
4503	    result = MdBool(screen->paste_quotes);
4504	    break;
4505	case srm_PASTE_LITERAL_NL:
4506	    result = MdBool(screen->paste_literal_nl);
4507	    break;
4508#endif /* OPT_READLINE */
4509	}
4510	reply.a_param[count++] = (ParmType) params[0];
4511	reply.a_param[count++] = (ParmType) result;
4512    }
4513    reply.a_type = ANSI_CSI;
4514    reply.a_pintro = '?';
4515    reply.a_nparam = (ParmType) count;
4516    reply.a_inters = '$';
4517    reply.a_final = 'y';
4518    unparseseq(xw, &reply);
4519}
4520#endif /* OPT_DEC_RECTOPS */
4521
4522char *
4523udk_lookup(int keycode, int *len)
4524{
4525    if (keycode >= 0 && keycode < MAX_UDK) {
4526	*len = user_keys[keycode].len;
4527	return user_keys[keycode].str;
4528    }
4529    return 0;
4530}
4531
4532#ifdef HAVE_LIBXPM
4533
4534#ifndef PIXMAP_ROOTDIR
4535#define PIXMAP_ROOTDIR "/usr/share/pixmaps/"
4536#endif
4537
4538typedef struct {
4539    const char *name;
4540    const char *const *data;
4541} XPM_DATA;
4542
4543static char *
4544x_find_icon(char **work, int *state, const char *suffix)
4545{
4546    const char *filename = resource.icon_hint;
4547    const char *prefix = PIXMAP_ROOTDIR;
4548    const char *larger = "_48x48";
4549    char *result = 0;
4550    size_t length;
4551
4552    if (*state >= 0) {
4553	if ((*state & 1) == 0)
4554	    suffix = "";
4555	if ((*state & 2) == 0)
4556	    larger = "";
4557	if ((*state & 4) == 0) {
4558	    prefix = "";
4559	} else if (!strncmp(filename, "/", (size_t) 1) ||
4560		   !strncmp(filename, "./", (size_t) 2) ||
4561		   !strncmp(filename, "../", (size_t) 3)) {
4562	    *state = -1;
4563	} else if (*state >= 8) {
4564	    *state = -1;
4565	}
4566    }
4567
4568    if (*state >= 0) {
4569	if (*work) {
4570	    free(*work);
4571	    *work = 0;
4572	}
4573	length = 3 + strlen(prefix) + strlen(filename) + strlen(larger) +
4574	    strlen(suffix);
4575	if ((result = malloc(length)) != 0) {
4576	    sprintf(result, "%s%s%s%s", prefix, filename, larger, suffix);
4577	    *work = result;
4578	}
4579	*state += 1;
4580	TRACE(("x_find_icon %d:%s\n", *state, result));
4581    }
4582    return result;
4583}
4584
4585#if OPT_BUILTIN_XPMS
4586static const XPM_DATA *
4587BuiltInXPM(const XPM_DATA * table, Cardinal length)
4588{
4589    const char *find = resource.icon_hint;
4590    const XPM_DATA *result = 0;
4591    if (!IsEmpty(find)) {
4592	Cardinal n;
4593	for (n = 0; n < length; ++n) {
4594	    if (!x_strcasecmp(find, table[n].name)) {
4595		result = table + n;
4596		break;
4597	    }
4598	}
4599
4600	/*
4601	 * As a fallback, check if the icon name matches without the lengths,
4602	 * which are all _HHxWW format.
4603	 */
4604	if (result == 0) {
4605	    const char *base = table[0].name;
4606	    const char *last = strchr(base, '_');
4607	    if (last != 0
4608		&& !x_strncasecmp(find, base, (unsigned) (last - base))) {
4609		result = table + length - 1;
4610	    }
4611	}
4612    }
4613    return result;
4614}
4615#endif /* OPT_BUILTIN_XPMS */
4616
4617typedef enum {
4618    eHintDefault = 0		/* use the largest builtin-icon */
4619    ,eHintNone
4620    ,eHintSearch
4621} ICON_HINT;
4622
4623static ICON_HINT
4624which_icon_hint(void)
4625{
4626    ICON_HINT result = eHintDefault;
4627    if (!IsEmpty(resource.icon_hint)) {
4628	if (!x_strcasecmp(resource.icon_hint, "none")) {
4629	    result = eHintNone;
4630	} else {
4631	    result = eHintSearch;
4632	}
4633    }
4634    return result;
4635}
4636#endif /* HAVE_LIBXPM */
4637
4638int
4639getVisualDepth(XtermWidget xw)
4640{
4641    int result = 0;
4642
4643    if (getVisualInfo(xw)) {
4644	result = xw->visInfo->depth;
4645    }
4646    return result;
4647}
4648
4649/*
4650 * WM_ICON_SIZE should be honored if possible.
4651 */
4652void
4653xtermLoadIcon(XtermWidget xw)
4654{
4655#ifdef HAVE_LIBXPM
4656    Display *dpy = XtDisplay(xw);
4657    Pixmap myIcon = 0;
4658    Pixmap myMask = 0;
4659    char *workname = 0;
4660    ICON_HINT hint = which_icon_hint();
4661#include <builtin_icons.h>
4662
4663    TRACE(("xtermLoadIcon %p:%s\n", (void *) xw, NonNull(resource.icon_hint)));
4664
4665    if (hint == eHintSearch) {
4666	int state = 0;
4667	while (x_find_icon(&workname, &state, ".xpm") != 0) {
4668	    Pixmap resIcon = 0;
4669	    Pixmap shapemask = 0;
4670	    XpmAttributes attributes;
4671
4672	    attributes.depth = (unsigned) getVisualDepth(xw);
4673	    attributes.valuemask = XpmDepth;
4674
4675	    if (XpmReadFileToPixmap(dpy,
4676				    DefaultRootWindow(dpy),
4677				    workname,
4678				    &resIcon,
4679				    &shapemask,
4680				    &attributes) == XpmSuccess) {
4681		myIcon = resIcon;
4682		myMask = shapemask;
4683		TRACE(("...success\n"));
4684		break;
4685	    }
4686	}
4687    }
4688
4689    /*
4690     * If no external file was found, look for the name in the built-in table.
4691     * If that fails, just use the biggest mini-icon.
4692     */
4693    if (myIcon == 0 && hint != eHintNone) {
4694	char **data;
4695#if OPT_BUILTIN_XPMS
4696	const XPM_DATA *myData = 0;
4697	myData = BuiltInXPM(mini_xterm_xpms, XtNumber(mini_xterm_xpms));
4698	if (myData == 0)
4699	    myData = BuiltInXPM(filled_xterm_xpms, XtNumber(filled_xterm_xpms));
4700	if (myData == 0)
4701	    myData = BuiltInXPM(xterm_color_xpms, XtNumber(xterm_color_xpms));
4702	if (myData == 0)
4703	    myData = BuiltInXPM(xterm_xpms, XtNumber(xterm_xpms));
4704	if (myData == 0)
4705	    myData = &mini_xterm_xpms[XtNumber(mini_xterm_xpms) - 1];
4706	data = (char **) myData->data,
4707#else
4708	data = (char **) &mini_xterm_48x48_xpm;
4709#endif
4710	if (XpmCreatePixmapFromData(dpy,
4711				    DefaultRootWindow(dpy),
4712				    data,
4713				    &myIcon, &myMask, 0) != 0) {
4714	    myIcon = 0;
4715	    myMask = 0;
4716	}
4717    }
4718
4719    if (myIcon != 0) {
4720	XWMHints *hints = XGetWMHints(dpy, VShellWindow(xw));
4721	if (!hints)
4722	    hints = XAllocWMHints();
4723
4724	if (hints) {
4725	    hints->flags |= IconPixmapHint;
4726	    hints->icon_pixmap = myIcon;
4727	    if (myMask) {
4728		hints->flags |= IconMaskHint;
4729		hints->icon_mask = myMask;
4730	    }
4731
4732	    XSetWMHints(dpy, VShellWindow(xw), hints);
4733	    XFree(hints);
4734	    TRACE(("...loaded icon\n"));
4735	}
4736    }
4737
4738    if (workname != 0)
4739	free(workname);
4740
4741#else
4742    (void) xw;
4743#endif
4744}
4745
4746void
4747ChangeGroup(XtermWidget xw, const char *attribute, char *value)
4748{
4749#if OPT_WIDE_CHARS
4750    static Char *converted;	/* NO_LEAKS */
4751#endif
4752
4753    Arg args[1];
4754    Boolean changed = True;
4755    Widget w = CURRENT_EMU();
4756    Widget top = SHELL_OF(w);
4757
4758    char *my_attr;
4759    char *name;
4760    size_t limit;
4761    Char *c1;
4762    Char *cp;
4763
4764    if (!AllowTitleOps(xw))
4765	return;
4766
4767    if (value == 0)
4768	value = emptyString;
4769    if (IsTitleMode(xw, tmSetBase16)) {
4770	const char *temp;
4771	char *test;
4772
4773	value = x_decode_hex(value, &temp);
4774	if (*temp != '\0') {
4775	    free(value);
4776	    return;
4777	}
4778	for (test = value; *test != '\0'; ++test) {
4779	    if (CharOf(*test) < 32) {
4780		*test = '\0';
4781		break;
4782	    }
4783	}
4784    }
4785
4786    c1 = (Char *) value;
4787    name = value;
4788    limit = strlen(name);
4789    my_attr = x_strdup(attribute);
4790
4791    TRACE(("ChangeGroup(attribute=%s, value=%s)\n", my_attr, name));
4792
4793    /*
4794     * Ignore titles that are too long to be plausible requests.
4795     */
4796    if (limit > 0 && limit < 1024) {
4797
4798	/*
4799	 * After all decoding, overwrite nonprintable characters with '?'.
4800	 */
4801	for (cp = c1; *cp != 0; ++cp) {
4802	    Char *c2 = cp;
4803	    if (!xtermIsPrintable(xw, &cp, c1 + limit)) {
4804		memset(c2, '?', (size_t) (cp + 1 - c2));
4805	    }
4806	}
4807
4808#if OPT_WIDE_CHARS
4809	/*
4810	 * If we're running in UTF-8 mode, and have not been told that the
4811	 * title string is in UTF-8, it is likely that non-ASCII text in the
4812	 * string will be rejected because it is not printable in the current
4813	 * locale.  So we convert it to UTF-8, allowing the X library to
4814	 * convert it back.
4815	 */
4816	if (xtermEnvUTF8() && !IsSetUtf8Title(xw)) {
4817	    int n;
4818
4819	    for (n = 0; name[n] != '\0'; ++n) {
4820		if (CharOf(name[n]) > 127) {
4821		    if (converted != 0)
4822			free(converted);
4823		    if ((converted = TypeMallocN(Char, 1 + (6 * limit))) != 0) {
4824			Char *temp = converted;
4825			while (*name != 0) {
4826			    temp = convertToUTF8(temp, CharOf(*name));
4827			    ++name;
4828			}
4829			*temp = 0;
4830			name = (char *) converted;
4831			TRACE(("...converted{%s}\n", name));
4832		    }
4833		    break;
4834		}
4835	    }
4836	}
4837#endif
4838
4839#if OPT_SAME_NAME
4840	/* If the attribute isn't going to change, then don't bother... */
4841
4842	if (resource.sameName) {
4843	    char *buf = 0;
4844	    XtSetArg(args[0], my_attr, &buf);
4845	    XtGetValues(top, args, 1);
4846	    TRACE(("...comparing{%s}\n", buf));
4847	    if (buf != 0 && strcmp(name, buf) == 0)
4848		changed = False;
4849	}
4850#endif /* OPT_SAME_NAME */
4851
4852	if (changed) {
4853	    TRACE(("...updating %s\n", my_attr));
4854	    TRACE(("...value is %s\n", name));
4855	    XtSetArg(args[0], my_attr, name);
4856	    XtSetValues(top, args, 1);
4857
4858#if OPT_WIDE_CHARS
4859	    if (xtermEnvUTF8()) {
4860		Display *dpy = XtDisplay(xw);
4861		Atom my_atom;
4862
4863		const char *propname = (!strcmp(my_attr, XtNtitle)
4864					? "_NET_WM_NAME"
4865					: "_NET_WM_ICON_NAME");
4866		if ((my_atom = XInternAtom(dpy, propname, False)) != None) {
4867		    if (IsSetUtf8Title(xw)) {
4868			TRACE(("...updating %s\n", propname));
4869			TRACE(("...value is %s\n", value));
4870			XChangeProperty(dpy, VShellWindow(xw), my_atom,
4871					XA_UTF8_STRING(dpy), 8,
4872					PropModeReplace,
4873					(Char *) value,
4874					(int) strlen(value));
4875		    } else {
4876			TRACE(("...deleting %s\n", propname));
4877			XDeleteProperty(dpy, VShellWindow(xw), my_atom);
4878		    }
4879		}
4880	    }
4881#endif
4882	}
4883    }
4884    if (IsTitleMode(xw, tmSetBase16)) {
4885	free(value);
4886    }
4887    free(my_attr);
4888
4889    return;
4890}
4891
4892void
4893ChangeIconName(XtermWidget xw, char *name)
4894{
4895    if (name == 0) {
4896	name = emptyString;
4897    }
4898    if (!showZIconBeep(xw, name))
4899	ChangeGroup(xw, XtNiconName, name);
4900}
4901
4902void
4903ChangeTitle(XtermWidget xw, char *name)
4904{
4905    ChangeGroup(xw, XtNtitle, name);
4906}
4907
4908#define Strlen(s) strlen((const char *)(s))
4909
4910void
4911ChangeXprop(char *buf)
4912{
4913    Display *dpy = XtDisplay(toplevel);
4914    Window w = XtWindow(toplevel);
4915    XTextProperty text_prop;
4916    Atom aprop;
4917    Char *pchEndPropName = (Char *) strchr(buf, '=');
4918
4919    if (pchEndPropName)
4920	*pchEndPropName = '\0';
4921    aprop = XInternAtom(dpy, buf, False);
4922    if (pchEndPropName == NULL) {
4923	/* no "=value" given, so delete the property */
4924	XDeleteProperty(dpy, w, aprop);
4925    } else {
4926	text_prop.value = pchEndPropName + 1;
4927	text_prop.encoding = XA_STRING;
4928	text_prop.format = 8;
4929	text_prop.nitems = Strlen(text_prop.value);
4930	XSetTextProperty(dpy, w, &text_prop, aprop);
4931    }
4932}
4933
4934/***====================================================================***/
4935
4936/*
4937 * This is part of ReverseVideo().  It reverses the data stored for the old
4938 * "dynamic" colors that might have been retrieved using OSC 10-18.
4939 */
4940void
4941ReverseOldColors(void)
4942{
4943    ScrnColors *pOld = pOldColors;
4944    Pixel tmpPix;
4945    char *tmpName;
4946
4947    if (pOld) {
4948	/* change text cursor, if necesary */
4949	if (pOld->colors[TEXT_CURSOR] == pOld->colors[TEXT_FG]) {
4950	    pOld->colors[TEXT_CURSOR] = pOld->colors[TEXT_BG];
4951	    if (pOld->names[TEXT_CURSOR]) {
4952		XtFree(pOldColors->names[TEXT_CURSOR]);
4953		pOld->names[TEXT_CURSOR] = NULL;
4954	    }
4955	    if (pOld->names[TEXT_BG]) {
4956		if ((tmpName = x_strdup(pOld->names[TEXT_BG])) != 0) {
4957		    pOld->names[TEXT_CURSOR] = tmpName;
4958		}
4959	    }
4960	}
4961
4962	EXCHANGE(pOld->colors[TEXT_FG], pOld->colors[TEXT_BG], tmpPix);
4963	EXCHANGE(pOld->names[TEXT_FG], pOld->names[TEXT_BG], tmpName);
4964
4965	EXCHANGE(pOld->colors[MOUSE_FG], pOld->colors[MOUSE_BG], tmpPix);
4966	EXCHANGE(pOld->names[MOUSE_FG], pOld->names[MOUSE_BG], tmpName);
4967
4968#if OPT_TEK4014
4969	EXCHANGE(pOld->colors[TEK_FG], pOld->colors[TEK_BG], tmpPix);
4970	EXCHANGE(pOld->names[TEK_FG], pOld->names[TEK_BG], tmpName);
4971#endif
4972    }
4973    return;
4974}
4975
4976Bool
4977AllocateTermColor(XtermWidget xw,
4978		  ScrnColors * pNew,
4979		  int ndx,
4980		  const char *name,
4981		  Bool always)
4982{
4983    Bool result = False;
4984
4985    if (always || AllowColorOps(xw, ecSetColor)) {
4986	XColor def;
4987	char *newName;
4988
4989	result = True;
4990	if (!x_strcasecmp(name, XtDefaultForeground)) {
4991	    def.pixel = xw->old_foreground;
4992	} else if (!x_strcasecmp(name, XtDefaultBackground)) {
4993	    def.pixel = xw->old_background;
4994	} else if (!xtermAllocColor(xw, &def, name)) {
4995	    result = False;
4996	}
4997
4998	if (result
4999	    && (newName = x_strdup(name)) != 0) {
5000	    if (COLOR_DEFINED(pNew, ndx)) {
5001		free(pNew->names[ndx]);
5002	    }
5003	    SET_COLOR_VALUE(pNew, ndx, def.pixel);
5004	    SET_COLOR_NAME(pNew, ndx, newName);
5005	    TRACE(("AllocateTermColor #%d: %s (pixel 0x%06lx)\n",
5006		   ndx, newName, def.pixel));
5007	} else {
5008	    TRACE(("AllocateTermColor #%d: %s (failed)\n", ndx, name));
5009	    result = False;
5010	}
5011    }
5012    return result;
5013}
5014/***====================================================================***/
5015
5016/* ARGSUSED */
5017void
5018Panic(const char *s GCC_UNUSED, int a GCC_UNUSED)
5019{
5020    if_DEBUG({
5021	xtermWarning(s, a);
5022    });
5023}
5024
5025const char *
5026SysErrorMsg(int code)
5027{
5028    static char unknown[] = "unknown error";
5029    char *s = strerror(code);
5030    return s ? s : unknown;
5031}
5032
5033const char *
5034SysReasonMsg(int code)
5035{
5036    /* *INDENT-OFF* */
5037    static const struct {
5038	int code;
5039	const char *name;
5040    } table[] = {
5041	{ ERROR_FIONBIO,	"main:  ioctl() failed on FIONBIO" },
5042	{ ERROR_F_GETFL,	"main: ioctl() failed on F_GETFL" },
5043	{ ERROR_F_SETFL,	"main: ioctl() failed on F_SETFL", },
5044	{ ERROR_OPDEVTTY,	"spawn: open() failed on /dev/tty", },
5045	{ ERROR_TIOCGETP,	"spawn: ioctl() failed on TIOCGETP", },
5046	{ ERROR_PTSNAME,	"spawn: ptsname() failed", },
5047	{ ERROR_OPPTSNAME,	"spawn: open() failed on ptsname", },
5048	{ ERROR_PTEM,		"spawn: ioctl() failed on I_PUSH/\"ptem\"" },
5049	{ ERROR_CONSEM,		"spawn: ioctl() failed on I_PUSH/\"consem\"" },
5050	{ ERROR_LDTERM,		"spawn: ioctl() failed on I_PUSH/\"ldterm\"" },
5051	{ ERROR_TTCOMPAT,	"spawn: ioctl() failed on I_PUSH/\"ttcompat\"" },
5052	{ ERROR_TIOCSETP,	"spawn: ioctl() failed on TIOCSETP" },
5053	{ ERROR_TIOCSETC,	"spawn: ioctl() failed on TIOCSETC" },
5054	{ ERROR_TIOCSETD,	"spawn: ioctl() failed on TIOCSETD" },
5055	{ ERROR_TIOCSLTC,	"spawn: ioctl() failed on TIOCSLTC" },
5056	{ ERROR_TIOCLSET,	"spawn: ioctl() failed on TIOCLSET" },
5057	{ ERROR_INIGROUPS,	"spawn: initgroups() failed" },
5058	{ ERROR_FORK,		"spawn: fork() failed" },
5059	{ ERROR_EXEC,		"spawn: exec() failed" },
5060	{ ERROR_PTYS,		"get_pty: not enough ptys" },
5061	{ ERROR_PTY_EXEC,	"waiting for initial map" },
5062	{ ERROR_SETUID,		"spawn: setuid() failed" },
5063	{ ERROR_INIT,		"spawn: can't initialize window" },
5064	{ ERROR_TIOCKSET,	"spawn: ioctl() failed on TIOCKSET" },
5065	{ ERROR_TIOCKSETC,	"spawn: ioctl() failed on TIOCKSETC" },
5066	{ ERROR_LUMALLOC,	"luit: command-line malloc failed" },
5067	{ ERROR_SELECT,		"in_put: select() failed" },
5068	{ ERROR_VINIT,		"VTInit: can't initialize window" },
5069	{ ERROR_KMMALLOC1,	"HandleKeymapChange: malloc failed" },
5070	{ ERROR_TSELECT,	"Tinput: select() failed" },
5071	{ ERROR_TINIT,		"TekInit: can't initialize window" },
5072	{ ERROR_BMALLOC2,	"SaltTextAway: malloc() failed" },
5073	{ ERROR_LOGEXEC,	"StartLog: exec() failed" },
5074	{ ERROR_XERROR,		"xerror: XError event" },
5075	{ ERROR_XIOERROR,	"xioerror: X I/O error" },
5076	{ ERROR_SCALLOC,	"Alloc: calloc() failed on base" },
5077	{ ERROR_SCALLOC2,	"Alloc: calloc() failed on rows" },
5078	{ ERROR_SAVE_PTR,	"ScrnPointers: malloc/realloc() failed" },
5079    };
5080    /* *INDENT-ON* */
5081
5082    Cardinal n;
5083    const char *result = "?";
5084
5085    for (n = 0; n < XtNumber(table); ++n) {
5086	if (code == table[n].code) {
5087	    result = table[n].name;
5088	    break;
5089	}
5090    }
5091    return result;
5092}
5093
5094void
5095SysError(int code)
5096{
5097    int oerrno = errno;
5098
5099    fprintf(stderr, "%s: Error %d, errno %d: ", ProgramName, code, oerrno);
5100    fprintf(stderr, "%s\n", SysErrorMsg(oerrno));
5101    fprintf(stderr, "Reason: %s\n", SysReasonMsg(code));
5102
5103    Cleanup(code);
5104}
5105
5106void
5107NormalExit(void)
5108{
5109    static Bool cleaning;
5110
5111    /*
5112     * Process "-hold" and session cleanup only for a normal exit.
5113     */
5114    if (cleaning) {
5115	hold_screen = 0;
5116	return;
5117    }
5118
5119    cleaning = True;
5120    need_cleanup = False;
5121
5122    if (hold_screen) {
5123	hold_screen = 2;
5124	while (hold_screen) {
5125	    xevents();
5126	    Sleep(10);
5127	}
5128    }
5129#if OPT_SESSION_MGT
5130    if (resource.sessionMgt) {
5131	XtVaSetValues(toplevel,
5132		      XtNjoinSession, False,
5133		      (void *) 0);
5134    }
5135#endif
5136    Cleanup(0);
5137}
5138
5139/*
5140 * cleanup by sending SIGHUP to client processes
5141 */
5142void
5143Cleanup(int code)
5144{
5145    TScreen *screen = TScreenOf(term);
5146
5147    TRACE(("Cleanup %d\n", code));
5148
5149    if (screen->pid > 1) {
5150	(void) kill_process_group(screen->pid, SIGHUP);
5151    }
5152    Exit(code);
5153}
5154
5155#ifndef S_IXOTH
5156#define S_IXOTH 1
5157#endif
5158
5159Boolean
5160validProgram(const char *pathname)
5161{
5162    Boolean result = False;
5163    struct stat sb;
5164
5165    if (!IsEmpty(pathname)
5166	&& *pathname == '/'
5167	&& strstr(pathname, "/..") == 0
5168	&& stat(pathname, &sb) == 0
5169	&& (sb.st_mode & S_IFMT) == S_IFREG
5170	&& (sb.st_mode & S_IXOTH) != 0) {
5171	result = True;
5172    }
5173    return result;
5174}
5175
5176#ifndef VMS
5177#ifndef PATH_MAX
5178#define PATH_MAX 512		/* ... is not defined consistently in Xos.h */
5179#endif
5180char *
5181xtermFindShell(char *leaf, Bool warning)
5182{
5183    char *s0;
5184    char *s;
5185    char *d;
5186    char *tmp;
5187    char *result = leaf;
5188    Bool allocated = False;
5189
5190    TRACE(("xtermFindShell(%s)\n", leaf));
5191
5192    if (!strncmp("./", result, (size_t) 2)
5193	|| !strncmp("../", result, (size_t) 3)) {
5194	size_t need = PATH_MAX;
5195	size_t used = strlen(result) + 2;
5196	char *buffer = malloc(used + need);
5197	if (buffer != 0) {
5198	    if (getcwd(buffer, need) != 0) {
5199		sprintf(buffer + strlen(buffer), "/%s", result);
5200		result = buffer;
5201		allocated = True;
5202	    } else {
5203		free(buffer);
5204	    }
5205	}
5206    } else if (*result != '\0' && strchr("+/-", *result) == 0) {
5207	/* find it in $PATH */
5208	if ((s = s0 = x_getenv("PATH")) != 0) {
5209	    if ((tmp = TypeMallocN(char, strlen(leaf) + strlen(s) + 2)) != 0) {
5210		Bool found = False;
5211		while (*s != '\0') {
5212		    strcpy(tmp, s);
5213		    for (d = tmp;; ++d) {
5214			if (*d == ':' || *d == '\0') {
5215			    int skip = (*d != '\0');
5216			    *d = '/';
5217			    strcpy(d + 1, leaf);
5218			    if (skip)
5219				++d;
5220			    s += (d - tmp);
5221			    if (validProgram(tmp)) {
5222				result = x_strdup(tmp);
5223				found = True;
5224				allocated = True;
5225			    }
5226			    break;
5227			}
5228		    }
5229		    if (found)
5230			break;
5231		}
5232		free(tmp);
5233	    }
5234	    free(s0);
5235	}
5236    }
5237    TRACE(("...xtermFindShell(%s)\n", result));
5238    if (!validProgram(result)) {
5239	if (warning)
5240	    xtermWarning("No absolute path found for shell: %s\n", result);
5241	if (allocated)
5242	    free(result);
5243	result = 0;
5244    }
5245    /* be consistent, so that caller can always free the result */
5246    if (result != 0 && !allocated)
5247	result = x_strdup(result);
5248    return result;
5249}
5250#endif /* VMS */
5251
5252#define ENV_HUNK(n)	(unsigned) ((((n) + 1) | 31) + 1)
5253
5254/*
5255 * If we do not have unsetenv(), make consistent updates for environ[].
5256 * This could happen on some older machines due to the uneven standardization
5257 * process for the two functions.
5258 *
5259 * That is, putenv() makes a copy of environ, and some implementations do not
5260 * update the environ pointer, so the fallback when unsetenv() is missing would
5261 * not work as intended.  Likewise, the reverse could be true, i.e., unsetenv
5262 * could copy environ.
5263 */
5264#if defined(HAVE_PUTENV) && !defined(HAVE_UNSETENV)
5265#undef HAVE_PUTENV
5266#elif !defined(HAVE_PUTENV) && defined(HAVE_UNSETENV)
5267#undef HAVE_UNSETENV
5268#endif
5269
5270/*
5271 * copy the environment before Setenv'ing.
5272 */
5273void
5274xtermCopyEnv(char **oldenv)
5275{
5276#ifdef HAVE_PUTENV
5277    (void) oldenv;
5278#else
5279    unsigned size;
5280    char **newenv;
5281
5282    for (size = 0; oldenv[size] != NULL; size++) {
5283	;
5284    }
5285
5286    newenv = TypeCallocN(char *, ENV_HUNK(size));
5287    memmove(newenv, oldenv, size * sizeof(char *));
5288    environ = newenv;
5289#endif
5290}
5291
5292#if !defined(HAVE_PUTENV) || !defined(HAVE_UNSETENV)
5293static int
5294findEnv(const char *var, int *lengthp)
5295{
5296    char *test;
5297    int envindex = 0;
5298    size_t len = strlen(var);
5299    int found = -1;
5300
5301    TRACE(("findEnv(%s=..)\n", var));
5302
5303    while ((test = environ[envindex]) != NULL) {
5304	if (strncmp(test, var, len) == 0 && test[len] == '=') {
5305	    found = envindex;
5306	    break;
5307	}
5308	envindex++;
5309    }
5310    *lengthp = envindex;
5311    return found;
5312}
5313#endif
5314
5315/*
5316 * sets the value of var to be arg in the Unix 4.2 BSD environment env.
5317 * Var should end with '=' (bindings are of the form "var=value").
5318 * This procedure assumes the memory for the first level of environ
5319 * was allocated using calloc, with enough extra room at the end so not
5320 * to have to do a realloc().
5321 */
5322void
5323xtermSetenv(const char *var, const char *value)
5324{
5325    if (value != 0) {
5326#ifdef HAVE_PUTENV
5327	char *both = malloc(2 + strlen(var) + strlen(value));
5328	TRACE(("xtermSetenv(%s=%s)\n", var, value));
5329	if (both) {
5330	    sprintf(both, "%s=%s", var, value);
5331	    putenv(both);
5332	}
5333#else
5334	size_t len = strlen(var);
5335	int envindex;
5336	int found = findEnv(var, &envindex);
5337
5338	TRACE(("xtermSetenv(%s=%s)\n", var, value));
5339
5340	if (found < 0) {
5341	    unsigned need = ENV_HUNK(envindex + 1);
5342	    unsigned have = ENV_HUNK(envindex);
5343
5344	    if (need > have) {
5345		char **newenv;
5346		newenv = TypeMallocN(char *, need);
5347		if (newenv == 0) {
5348		    xtermWarning("Cannot increase environment\n");
5349		    return;
5350		}
5351		memmove(newenv, environ, have * sizeof(*newenv));
5352		free(environ);
5353		environ = newenv;
5354	    }
5355
5356	    found = envindex;
5357	    environ[found + 1] = NULL;
5358	    environ = environ;
5359	}
5360
5361	environ[found] = CastMallocN(char, 1 + len + strlen(value));
5362	if (environ[found] == 0) {
5363	    xtermWarning("Cannot allocate environment %s\n", var);
5364	    return;
5365	}
5366	sprintf(environ[found], "%s=%s", var, value);
5367#endif
5368    }
5369}
5370
5371void
5372xtermUnsetenv(const char *var)
5373{
5374    TRACE(("xtermUnsetenv(%s)\n", var));
5375#ifdef HAVE_UNSETENV
5376    unsetenv(var);
5377#else
5378    {
5379	int ignore;
5380	int item = findEnv(var, &ignore);
5381	if (item >= 0) {
5382	    while ((environ[item] = environ[item + 1]) != 0) {
5383		++item;
5384	    }
5385	}
5386    }
5387#endif
5388}
5389
5390/*ARGSUSED*/
5391int
5392xerror(Display *d, XErrorEvent * ev)
5393{
5394    xtermWarning("warning, error event received:\n");
5395    (void) XmuPrintDefaultErrorMessage(d, ev, stderr);
5396    Exit(ERROR_XERROR);
5397    return 0;			/* appease the compiler */
5398}
5399
5400void
5401ice_error(IceConn iceConn)
5402{
5403    (void) iceConn;
5404
5405    xtermWarning("ICE IO error handler doing an exit(), pid = %ld, errno = %d\n",
5406		 (long) getpid(), errno);
5407
5408    Exit(ERROR_ICEERROR);
5409}
5410
5411/*ARGSUSED*/
5412int
5413xioerror(Display *dpy)
5414{
5415    int the_error = errno;
5416
5417    xtermWarning("fatal IO error %d (%s) or KillClient on X server \"%s\"\r\n",
5418		 the_error, SysErrorMsg(the_error),
5419		 DisplayString(dpy));
5420
5421    Exit(ERROR_XIOERROR);
5422    return 0;			/* appease the compiler */
5423}
5424
5425void
5426xt_error(String message)
5427{
5428    xtermWarning("Xt error: %s\n", message);
5429
5430    /*
5431     * Check for the obvious - Xt does a poor job of reporting this.
5432     */
5433    if (x_getenv("DISPLAY") == 0) {
5434	xtermWarning("DISPLAY is not set\n");
5435    }
5436    exit(1);
5437}
5438
5439int
5440XStrCmp(char *s1, char *s2)
5441{
5442    if (s1 && s2)
5443	return (strcmp(s1, s2));
5444    if (s1 && *s1)
5445	return (1);
5446    if (s2 && *s2)
5447	return (-1);
5448    return (0);
5449}
5450
5451#if OPT_TEK4014
5452static void
5453withdraw_window(Display *dpy, Window w, int scr)
5454{
5455    TRACE(("withdraw_window %#lx\n", (long) w));
5456    (void) XmuUpdateMapHints(dpy, w, NULL);
5457    XWithdrawWindow(dpy, w, scr);
5458    return;
5459}
5460#endif
5461
5462void
5463set_vt_visibility(Bool on)
5464{
5465    XtermWidget xw = term;
5466    TScreen *screen = TScreenOf(xw);
5467
5468    TRACE(("set_vt_visibility(%d)\n", on));
5469    if (on) {
5470	if (!screen->Vshow && xw) {
5471	    VTInit(xw);
5472	    XtMapWidget(XtParent(xw));
5473#if OPT_TOOLBAR
5474	    /* we need both of these during initialization */
5475	    XtMapWidget(SHELL_OF(xw));
5476	    ShowToolbar(resource.toolBar);
5477#endif
5478	    screen->Vshow = True;
5479	}
5480    }
5481#if OPT_TEK4014
5482    else {
5483	if (screen->Vshow && xw) {
5484	    withdraw_window(XtDisplay(xw),
5485			    VShellWindow(xw),
5486			    XScreenNumberOfScreen(XtScreen(xw)));
5487	    screen->Vshow = False;
5488	}
5489    }
5490    set_vthide_sensitivity();
5491    set_tekhide_sensitivity();
5492    update_vttekmode();
5493    update_tekshow();
5494    update_vtshow();
5495#endif
5496    return;
5497}
5498
5499#if OPT_TEK4014
5500void
5501set_tek_visibility(Bool on)
5502{
5503    TRACE(("set_tek_visibility(%d)\n", on));
5504
5505    if (on) {
5506	if (!TEK4014_SHOWN(term)) {
5507	    if (tekWidget == 0) {
5508		TekInit();	/* will exit on failure */
5509	    }
5510	    if (tekWidget != 0) {
5511		Widget tekParent = SHELL_OF(tekWidget);
5512		XtRealizeWidget(tekParent);
5513		XtMapWidget(XtParent(tekWidget));
5514#if OPT_TOOLBAR
5515		/* we need both of these during initialization */
5516		XtMapWidget(tekParent);
5517		XtMapWidget(tekWidget);
5518#endif
5519		XtOverrideTranslations(tekParent,
5520				       XtParseTranslationTable
5521				       ("<Message>WM_PROTOCOLS: DeleteWindow()"));
5522		(void) XSetWMProtocols(XtDisplay(tekParent),
5523				       XtWindow(tekParent),
5524				       &wm_delete_window, 1);
5525		TEK4014_SHOWN(term) = True;
5526	    }
5527	}
5528    } else {
5529	if (TEK4014_SHOWN(term) && tekWidget) {
5530	    withdraw_window(XtDisplay(tekWidget),
5531			    TShellWindow,
5532			    XScreenNumberOfScreen(XtScreen(tekWidget)));
5533	    TEK4014_SHOWN(term) = False;
5534	}
5535    }
5536    set_tekhide_sensitivity();
5537    set_vthide_sensitivity();
5538    update_vtshow();
5539    update_tekshow();
5540    update_vttekmode();
5541    return;
5542}
5543
5544void
5545end_tek_mode(void)
5546{
5547    XtermWidget xw = term;
5548
5549    if (TEK4014_ACTIVE(xw)) {
5550	FlushLog(xw);
5551	longjmp(Tekend, 1);
5552    }
5553    return;
5554}
5555
5556void
5557end_vt_mode(void)
5558{
5559    XtermWidget xw = term;
5560
5561    if (!TEK4014_ACTIVE(xw)) {
5562	FlushLog(xw);
5563	TEK4014_ACTIVE(xw) = True;
5564	longjmp(VTend, 1);
5565    }
5566    return;
5567}
5568
5569void
5570switch_modes(Bool tovt)		/* if true, then become vt mode */
5571{
5572    if (tovt) {
5573	if (tekRefreshList)
5574	    TekRefresh(tekWidget);
5575	end_tek_mode();		/* WARNING: this does a longjmp... */
5576    } else {
5577	end_vt_mode();		/* WARNING: this does a longjmp... */
5578    }
5579}
5580
5581void
5582hide_vt_window(void)
5583{
5584    set_vt_visibility(False);
5585    if (!TEK4014_ACTIVE(term))
5586	switch_modes(False);	/* switch to tek mode */
5587}
5588
5589void
5590hide_tek_window(void)
5591{
5592    set_tek_visibility(False);
5593    tekRefreshList = (TekLink *) 0;
5594    if (TEK4014_ACTIVE(term))
5595	switch_modes(True);	/* does longjmp to vt mode */
5596}
5597#endif /* OPT_TEK4014 */
5598
5599static const char *
5600skip_punct(const char *s)
5601{
5602    while (*s == '-' || *s == '/' || *s == '+' || *s == '#' || *s == '%') {
5603	++s;
5604    }
5605    return s;
5606}
5607
5608static int
5609cmp_options(const void *a, const void *b)
5610{
5611    const char *s1 = skip_punct(((const OptionHelp *) a)->opt);
5612    const char *s2 = skip_punct(((const OptionHelp *) b)->opt);
5613    return strcmp(s1, s2);
5614}
5615
5616static int
5617cmp_resources(const void *a, const void *b)
5618{
5619    return strcmp(((const XrmOptionDescRec *) a)->option,
5620		  ((const XrmOptionDescRec *) b)->option);
5621}
5622
5623XrmOptionDescRec *
5624sortedOptDescs(XrmOptionDescRec * descs, Cardinal res_count)
5625{
5626    static XrmOptionDescRec *res_array = 0;
5627
5628#ifdef NO_LEAKS
5629    if (descs == 0) {
5630	if (res_array != 0) {
5631	    free(res_array);
5632	    res_array = 0;
5633	}
5634    } else
5635#endif
5636    if (res_array == 0) {
5637	Cardinal j;
5638
5639	/* make a sorted index to 'resources' */
5640	res_array = TypeCallocN(XrmOptionDescRec, res_count);
5641	if (res_array != 0) {
5642	    for (j = 0; j < res_count; j++)
5643		res_array[j] = descs[j];
5644	    qsort(res_array, (size_t) res_count, sizeof(*res_array), cmp_resources);
5645	}
5646    }
5647    return res_array;
5648}
5649
5650/*
5651 * The first time this is called, construct sorted index to the main program's
5652 * list of options, taking into account the on/off options which will be
5653 * compressed into one token.  It's a lot simpler to do it this way than
5654 * maintain the list in sorted form with lots of ifdef's.
5655 */
5656OptionHelp *
5657sortedOpts(OptionHelp * options, XrmOptionDescRec * descs, Cardinal numDescs)
5658{
5659    static OptionHelp *opt_array = 0;
5660
5661#ifdef NO_LEAKS
5662    if (descs == 0 && opt_array != 0) {
5663	sortedOptDescs(descs, numDescs);
5664	free(opt_array);
5665	opt_array = 0;
5666	return 0;
5667    } else if (options == 0 || descs == 0) {
5668	return 0;
5669    }
5670#endif
5671
5672    if (opt_array == 0) {
5673	size_t opt_count, j;
5674#if OPT_TRACE
5675	Cardinal k;
5676	XrmOptionDescRec *res_array = sortedOptDescs(descs, numDescs);
5677	int code;
5678	const char *mesg;
5679#else
5680	(void) descs;
5681	(void) numDescs;
5682#endif
5683
5684	/* count 'options' and make a sorted index to it */
5685	for (opt_count = 0; options[opt_count].opt != 0; ++opt_count) {
5686	    ;
5687	}
5688	opt_array = TypeCallocN(OptionHelp, opt_count + 1);
5689	for (j = 0; j < opt_count; j++)
5690	    opt_array[j] = options[j];
5691	qsort(opt_array, opt_count, sizeof(OptionHelp), cmp_options);
5692
5693	/* supply the "turn on/off" strings if needed */
5694#if OPT_TRACE
5695	for (j = 0; j < opt_count; j++) {
5696	    if (!strncmp(opt_array[j].opt, "-/+", (size_t) 3)) {
5697		char temp[80];
5698		const char *name = opt_array[j].opt + 3;
5699		for (k = 0; k < numDescs; ++k) {
5700		    const char *value = res_array[k].value;
5701		    if (res_array[k].option[0] == '-') {
5702			code = -1;
5703		    } else if (res_array[k].option[0] == '+') {
5704			code = 1;
5705		    } else {
5706			code = 0;
5707		    }
5708		    sprintf(temp, "%.*s",
5709			    (int) sizeof(temp) - 2,
5710			    opt_array[j].desc);
5711		    if (x_strindex(temp, "inhibit") != 0)
5712			code = -code;
5713		    if (code != 0
5714			&& res_array[k].value != 0
5715			&& !strcmp(name, res_array[k].option + 1)) {
5716			if (((code < 0) && !strcmp(value, "on"))
5717			    || ((code > 0) && !strcmp(value, "off"))
5718			    || ((code > 0) && !strcmp(value, "0"))) {
5719			    mesg = "turn on/off";
5720			} else {
5721			    mesg = "turn off/on";
5722			}
5723			if (strncmp(mesg, opt_array[j].desc, strlen(mesg))) {
5724			    if (strncmp(opt_array[j].desc, "turn ", (size_t) 5)) {
5725				char *s = CastMallocN(char,
5726						      strlen(mesg)
5727						      + 1
5728						      + strlen(opt_array[j].desc));
5729				if (s != 0) {
5730				    sprintf(s, "%s %s", mesg, opt_array[j].desc);
5731				    opt_array[j].desc = s;
5732				}
5733			    } else {
5734				TRACE(("OOPS "));
5735			    }
5736			}
5737			TRACE(("%s: %s %s: %s (%s)\n",
5738			       mesg,
5739			       res_array[k].option,
5740			       res_array[k].value,
5741			       opt_array[j].opt,
5742			       opt_array[j].desc));
5743			break;
5744		    }
5745		}
5746	    }
5747	}
5748#endif
5749    }
5750    return opt_array;
5751}
5752
5753/*
5754 * Report the character-type locale that xterm was started in.
5755 */
5756String
5757xtermEnvLocale(void)
5758{
5759    static String result;
5760
5761    if (result == 0) {
5762	if ((result = x_nonempty(setlocale(LC_CTYPE, 0))) == 0) {
5763	    result = x_strdup("C");
5764	} else {
5765	    result = x_strdup(result);
5766	}
5767	TRACE(("xtermEnvLocale ->%s\n", result));
5768    }
5769    return result;
5770}
5771
5772char *
5773xtermEnvEncoding(void)
5774{
5775    static char *result;
5776
5777    if (result == 0) {
5778#ifdef HAVE_LANGINFO_CODESET
5779	result = nl_langinfo(CODESET);
5780#else
5781	char *locale = xtermEnvLocale();
5782	if (!strcmp(locale, "C") || !strcmp(locale, "POSIX")) {
5783	    result = "ASCII";
5784	} else {
5785	    result = "ISO-8859-1";
5786	}
5787#endif
5788	TRACE(("xtermEnvEncoding ->%s\n", result));
5789    }
5790    return result;
5791}
5792
5793#if OPT_WIDE_CHARS
5794/*
5795 * Tell whether xterm was started in a locale that uses UTF-8 encoding for
5796 * characters.  That environment is inherited by subprocesses and used in
5797 * various library calls.
5798 */
5799Bool
5800xtermEnvUTF8(void)
5801{
5802    static Bool init = False;
5803    static Bool result = False;
5804
5805    if (!init) {
5806	init = True;
5807#ifdef HAVE_LANGINFO_CODESET
5808	result = (strcmp(xtermEnvEncoding(), "UTF-8") == 0);
5809#else
5810	{
5811	    char *locale = x_strdup(xtermEnvLocale());
5812	    int n;
5813	    for (n = 0; locale[n] != 0; ++n) {
5814		locale[n] = x_toupper(locale[n]);
5815	    }
5816	    if (strstr(locale, "UTF-8") != 0)
5817		result = True;
5818	    else if (strstr(locale, "UTF8") != 0)
5819		result = True;
5820	    free(locale);
5821	}
5822#endif
5823	TRACE(("xtermEnvUTF8 ->%s\n", BtoS(result)));
5824    }
5825    return result;
5826}
5827#endif /* OPT_WIDE_CHARS */
5828
5829/*
5830 * Check if the current widget, or any parent, is the VT100 "xterm" widget.
5831 */
5832XtermWidget
5833getXtermWidget(Widget w)
5834{
5835    XtermWidget xw;
5836
5837    if (w == 0) {
5838	xw = (XtermWidget) CURRENT_EMU();
5839	if (!IsXtermWidget(xw)) {
5840	    xw = 0;
5841	}
5842    } else if (IsXtermWidget(w)) {
5843	xw = (XtermWidget) w;
5844    } else {
5845	xw = getXtermWidget(XtParent(w));
5846    }
5847    TRACE2(("getXtermWidget %p -> %p\n", w, xw));
5848    return xw;
5849}
5850
5851#if OPT_SESSION_MGT
5852static void
5853die_callback(Widget w GCC_UNUSED,
5854	     XtPointer client_data GCC_UNUSED,
5855	     XtPointer call_data GCC_UNUSED)
5856{
5857    NormalExit();
5858}
5859
5860static void
5861save_callback(Widget w GCC_UNUSED,
5862	      XtPointer client_data GCC_UNUSED,
5863	      XtPointer call_data)
5864{
5865    XtCheckpointToken token = (XtCheckpointToken) call_data;
5866    /* we have nothing to save */
5867    token->save_success = True;
5868}
5869
5870static void
5871icewatch(IceConn iceConn,
5872	 IcePointer clientData GCC_UNUSED,
5873	 Bool opening,
5874	 IcePointer * watchData GCC_UNUSED)
5875{
5876    if (opening) {
5877	ice_fd = IceConnectionNumber(iceConn);
5878	TRACE(("got IceConnectionNumber %d\n", ice_fd));
5879    } else {
5880	ice_fd = -1;
5881	TRACE(("reset IceConnectionNumber\n"));
5882    }
5883}
5884
5885void
5886xtermOpenSession(void)
5887{
5888    if (resource.sessionMgt) {
5889	TRACE(("Enabling session-management callbacks\n"));
5890	XtAddCallback(toplevel, XtNdieCallback, die_callback, NULL);
5891	XtAddCallback(toplevel, XtNsaveCallback, save_callback, NULL);
5892    }
5893}
5894
5895void
5896xtermCloseSession(void)
5897{
5898    IceRemoveConnectionWatch(icewatch, NULL);
5899}
5900#endif /* OPT_SESSION_MGT */
5901
5902Widget
5903xtermOpenApplication(XtAppContext * app_context_return,
5904		     String my_class,
5905		     XrmOptionDescRec * options,
5906		     Cardinal num_options,
5907		     int *argc_in_out,
5908		     String *argv_in_out,
5909		     String *fallback_resources,
5910		     WidgetClass widget_class,
5911		     ArgList args,
5912		     Cardinal num_args)
5913{
5914    Widget result;
5915
5916    XtSetErrorHandler(xt_error);
5917#if OPT_SESSION_MGT
5918    result = XtOpenApplication(app_context_return,
5919			       my_class,
5920			       options,
5921			       num_options,
5922			       argc_in_out,
5923			       argv_in_out,
5924			       fallback_resources,
5925			       widget_class,
5926			       args,
5927			       num_args);
5928    IceAddConnectionWatch(icewatch, NULL);
5929#else
5930    result = XtAppInitialize(app_context_return,
5931			     my_class,
5932			     options,
5933			     num_options,
5934			     argc_in_out,
5935			     argv_in_out,
5936			     fallback_resources,
5937			     NULL, 0);
5938#endif /* OPT_SESSION_MGT */
5939    XtSetErrorHandler((XtErrorHandler) 0);
5940
5941    return result;
5942}
5943
5944static int x11_errors;
5945
5946static int
5947catch_x11_error(Display *display, XErrorEvent * error_event)
5948{
5949    (void) display;
5950    (void) error_event;
5951    ++x11_errors;
5952    return 0;
5953}
5954
5955Boolean
5956xtermGetWinAttrs(Display *dpy, Window win, XWindowAttributes * attrs)
5957{
5958    Boolean result = False;
5959    Status code;
5960
5961    memset(attrs, 0, sizeof(*attrs));
5962    if (win != None) {
5963	XErrorHandler save = XSetErrorHandler(catch_x11_error);
5964	x11_errors = 0;
5965	code = XGetWindowAttributes(dpy, win, attrs);
5966	XSetErrorHandler(save);
5967	result = (Boolean) ((code != 0) && !x11_errors);
5968	if (result) {
5969	    TRACE_WIN_ATTRS(attrs);
5970	} else {
5971	    xtermWarning("invalid window-id %ld\n", (long) win);
5972	}
5973    }
5974    return result;
5975}
5976
5977Boolean
5978xtermGetWinProp(Display *display,
5979		Window win,
5980		Atom property,
5981		long long_offset,
5982		long long_length,
5983		Atom req_type,
5984		Atom * actual_type_return,
5985		int *actual_format_return,
5986		unsigned long *nitems_return,
5987		unsigned long *bytes_after_return,
5988		unsigned char **prop_return)
5989{
5990    Boolean result = True;
5991
5992    if (win != None) {
5993	XErrorHandler save = XSetErrorHandler(catch_x11_error);
5994	x11_errors = 0;
5995	if (XGetWindowProperty(display,
5996			       win,
5997			       property,
5998			       long_offset,
5999			       long_length,
6000			       False,
6001			       req_type,
6002			       actual_type_return,
6003			       actual_format_return,
6004			       nitems_return,
6005			       bytes_after_return,
6006			       prop_return) == Success
6007	    && x11_errors == 0) {
6008	    result = True;
6009	}
6010	XSetErrorHandler(save);
6011    }
6012    return result;
6013}
6014
6015void
6016xtermEmbedWindow(Window winToEmbedInto)
6017{
6018    Display *dpy = XtDisplay(toplevel);
6019    XWindowAttributes attrs;
6020
6021    TRACE(("checking winToEmbedInto %#lx\n", winToEmbedInto));
6022    if (xtermGetWinAttrs(dpy, winToEmbedInto, &attrs)) {
6023	XtermWidget xw = term;
6024	TScreen *screen = TScreenOf(xw);
6025
6026	XtRealizeWidget(toplevel);
6027
6028	TRACE(("...reparenting toplevel %#lx into %#lx\n",
6029	       XtWindow(toplevel),
6030	       winToEmbedInto));
6031	XReparentWindow(dpy,
6032			XtWindow(toplevel),
6033			winToEmbedInto, 0, 0);
6034
6035	screen->embed_high = (Dimension) attrs.height;
6036	screen->embed_wide = (Dimension) attrs.width;
6037    }
6038}
6039