misc.c revision cd3331d0
1/* $XTermId: misc.c,v 1.503 2010/06/20 21:33:49 tom Exp $ */
2
3/*
4 * Copyright 1999-2009,2010 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 <signal.h>
62#include <ctype.h>
63#include <pwd.h>
64#include <sys/wait.h>
65
66#include <X11/keysym.h>
67#include <X11/Xatom.h>
68#include <X11/cursorfont.h>
69#include <X11/Xlocale.h>
70
71#include <X11/Xmu/Error.h>
72#include <X11/Xmu/SysUtil.h>
73#include <X11/Xmu/WinUtil.h>
74#include <X11/Xmu/Xmu.h>
75#if HAVE_X11_SUNKEYSYM_H
76#include <X11/Sunkeysym.h>
77#endif
78
79#ifdef HAVE_LANGINFO_CODESET
80#include <langinfo.h>
81#endif
82
83#include <xutf8.h>
84
85#include <data.h>
86#include <error.h>
87#include <menu.h>
88#include <fontutils.h>
89#include <xcharmouse.h>
90#include <xstrings.h>
91#include <xtermcap.h>
92#include <VTparse.h>
93
94#include <assert.h>
95
96#if (XtSpecificationRelease < 6)
97#ifndef X_GETTIMEOFDAY
98#define X_GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *)0)
99#endif
100#endif
101
102#ifdef VMS
103#define XTERM_VMS_LOGFILE "SYS$SCRATCH:XTERM_LOG.TXT"
104#ifdef ALLOWLOGFILEEXEC
105#undef ALLOWLOGFILEEXEC
106#endif
107#endif /* VMS */
108
109#if OPT_TEK4014
110#define OUR_EVENT(event,Type) \
111		(event.type == Type && \
112		  (event.xcrossing.window == XtWindow(XtParent(xw)) || \
113		    (tekWidget && \
114		     event.xcrossing.window == XtWindow(XtParent(tekWidget)))))
115#else
116#define OUR_EVENT(event,Type) \
117		(event.type == Type && \
118		   (event.xcrossing.window == XtWindow(XtParent(xw))))
119#endif
120
121static Cursor make_hidden_cursor(XtermWidget);
122
123#if OPT_EXEC_XTERM
124/* Like readlink(2), but returns a malloc()ed buffer, or NULL on
125   error; adapted from libc docs */
126static char *
127Readlink(const char *filename)
128{
129    char *buf = NULL;
130    size_t size = 100;
131    int n;
132
133    for (;;) {
134	buf = TypeRealloc(char, size, buf);
135	memset(buf, 0, size);
136
137	n = (int) readlink(filename, buf, size);
138	if (n < 0) {
139	    free(buf);
140	    return NULL;
141	}
142
143	if ((unsigned) n < size) {
144	    return buf;
145	}
146
147	size *= 2;
148    }
149}
150#endif /* OPT_EXEC_XTERM */
151
152static void
153Sleep(int msec)
154{
155    static struct timeval select_timeout;
156
157    select_timeout.tv_sec = 0;
158    select_timeout.tv_usec = msec * 1000;
159    select(0, 0, 0, 0, &select_timeout);
160}
161
162static void
163selectwindow(TScreen * screen, int flag)
164{
165    TRACE(("selectwindow(%d) flag=%d\n", screen->select, flag));
166
167#if OPT_TEK4014
168    if (TEK4014_ACTIVE(term)) {
169	if (!Ttoggled)
170	    TCursorToggle(tekWidget, TOGGLE);
171	screen->select |= flag;
172	if (!Ttoggled)
173	    TCursorToggle(tekWidget, TOGGLE);
174    } else
175#endif
176    {
177	if (screen->xic)
178	    XSetICFocus(screen->xic);
179
180	if (screen->cursor_state && CursorMoved(screen))
181	    HideCursor();
182	screen->select |= flag;
183	if (screen->cursor_state)
184	    ShowCursor();
185    }
186    GetScrollLock(screen);
187}
188
189static void
190unselectwindow(TScreen * screen, int flag)
191{
192    TRACE(("unselectwindow(%d) flag=%d\n", screen->select, flag));
193
194    if (screen->hide_pointer) {
195	screen->hide_pointer = False;
196	xtermDisplayCursor(term);
197    }
198
199    if (!screen->always_highlight) {
200#if OPT_TEK4014
201	if (TEK4014_ACTIVE(term)) {
202	    if (!Ttoggled)
203		TCursorToggle(tekWidget, TOGGLE);
204	    screen->select &= ~flag;
205	    if (!Ttoggled)
206		TCursorToggle(tekWidget, TOGGLE);
207	} else
208#endif
209	{
210	    if (screen->xic)
211		XUnsetICFocus(screen->xic);
212
213	    screen->select &= ~flag;
214	    if (screen->cursor_state && CursorMoved(screen))
215		HideCursor();
216	    if (screen->cursor_state)
217		ShowCursor();
218	}
219    }
220}
221
222static void
223DoSpecialEnterNotify(XtermWidget xw, XEnterWindowEvent * ev)
224{
225    TScreen *screen = TScreenOf(xw);
226
227    TRACE(("DoSpecialEnterNotify(%d)\n", screen->select));
228    TRACE_FOCUS(xw, ev);
229    if (((ev->detail) != NotifyInferior) &&
230	ev->focus &&
231	!(screen->select & FOCUS))
232	selectwindow(screen, INWINDOW);
233}
234
235static void
236DoSpecialLeaveNotify(XtermWidget xw, XEnterWindowEvent * ev)
237{
238    TScreen *screen = TScreenOf(xw);
239
240    TRACE(("DoSpecialLeaveNotify(%d)\n", screen->select));
241    TRACE_FOCUS(xw, ev);
242    if (((ev->detail) != NotifyInferior) &&
243	ev->focus &&
244	!(screen->select & FOCUS))
245	unselectwindow(screen, INWINDOW);
246}
247
248#ifndef XUrgencyHint
249#define XUrgencyHint (1L << 8)	/* X11R5 does not define */
250#endif
251
252static void
253setXUrgency(TScreen * screen, Bool enable)
254{
255    if (screen->bellIsUrgent) {
256	XWMHints *h = XGetWMHints(screen->display, VShellWindow);
257	if (h != 0) {
258	    if (enable) {
259		h->flags |= XUrgencyHint;
260	    } else {
261		h->flags &= ~XUrgencyHint;
262	    }
263	    XSetWMHints(screen->display, VShellWindow, h);
264	}
265    }
266}
267
268void
269do_xevents(void)
270{
271    TScreen *screen = TScreenOf(term);
272
273    if (XtAppPending(app_con)
274	||
275#if defined(VMS) || defined(__VMS)
276	screen->display->qlen > 0
277#else
278	GetBytesAvailable(ConnectionNumber(screen->display)) > 0
279#endif
280	)
281	xevents();
282}
283
284void
285xtermDisplayCursor(XtermWidget xw)
286{
287    TScreen *screen = TScreenOf(xw);
288
289    if (screen->Vshow) {
290	if (screen->hide_pointer) {
291	    TRACE(("Display hidden_cursor\n"));
292	    XDefineCursor(screen->display, VWindow(screen), screen->hidden_cursor);
293	} else {
294	    TRACE(("Display pointer_cursor\n"));
295	    recolor_cursor(screen,
296			   screen->pointer_cursor,
297			   T_COLOR(screen, MOUSE_FG),
298			   T_COLOR(screen, MOUSE_BG));
299	    XDefineCursor(screen->display, VWindow(screen), screen->pointer_cursor);
300	}
301    }
302}
303
304void
305xtermShowPointer(XtermWidget xw, Bool enable)
306{
307    static int tried = -1;
308    TScreen *screen = TScreenOf(xw);
309
310#if OPT_TEK4014
311    if (TEK4014_SHOWN(xw))
312	enable = True;
313#endif
314
315    /*
316     * Whether we actually hide the pointer depends on the pointer-mode and
317     * the mouse-mode:
318     */
319    if (!enable) {
320	switch (screen->pointer_mode) {
321	case pNever:
322	    enable = True;
323	    break;
324	case pNoMouse:
325	    if (screen->send_mouse_pos != MOUSE_OFF)
326		enable = True;
327	    break;
328	case pAlways:
329	    break;
330	}
331    }
332
333    if (enable) {
334	if (screen->hide_pointer) {
335	    screen->hide_pointer = False;
336	    xtermDisplayCursor(xw);
337	    switch (screen->send_mouse_pos) {
338	    case ANY_EVENT_MOUSE:
339		break;
340	    default:
341		MotionOff(screen, xw);
342		break;
343	    }
344	}
345    } else if (!(screen->hide_pointer) && (tried <= 0)) {
346	if (screen->hidden_cursor == 0) {
347	    screen->hidden_cursor = make_hidden_cursor(xw);
348	}
349	if (screen->hidden_cursor == 0) {
350	    tried = 1;
351	} else {
352	    tried = 0;
353	    screen->hide_pointer = True;
354	    xtermDisplayCursor(xw);
355	    MotionOn(screen, xw);
356	}
357    }
358}
359
360void
361xevents(void)
362{
363    XtermWidget xw = term;
364    TScreen *screen = TScreenOf(xw);
365    XEvent event;
366    XtInputMask input_mask;
367
368    if (need_cleanup)
369	Cleanup(0);
370
371    if (screen->scroll_amt)
372	FlushScroll(xw);
373    /*
374     * process timeouts, relying on the fact that XtAppProcessEvent
375     * will process the timeout and return without blockng on the
376     * XEvent queue.  Other sources i.e., the pty are handled elsewhere
377     * with select().
378     */
379    while ((input_mask = XtAppPending(app_con)) != 0) {
380	if (input_mask & XtIMTimer)
381	    XtAppProcessEvent(app_con, (XtInputMask) XtIMTimer);
382#if OPT_SESSION_MGT
383	/*
384	 * Session management events are alternative input events. Deal with
385	 * them in the same way.
386	 */
387	else if (input_mask & XtIMAlternateInput)
388	    XtAppProcessEvent(app_con, (XtInputMask) XtIMAlternateInput);
389#endif
390	else
391	    break;
392    }
393
394    /*
395     * If there's no XEvents, don't wait around...
396     */
397    if ((input_mask & XtIMXEvent) != XtIMXEvent)
398	return;
399    do {
400	/*
401	 * This check makes xterm hang when in mouse hilite tracking mode.
402	 * We simply ignore all events except for those not passed down to
403	 * this function, e.g., those handled in in_put().
404	 */
405	if (screen->waitingForTrackInfo) {
406	    Sleep(10);
407	    return;
408	}
409	XtAppNextEvent(app_con, &event);
410	/*
411	 * Hack to get around problems with the toolkit throwing away
412	 * eventing during the exclusive grab of the menu popup.  By
413	 * looking at the event ourselves we make sure that we can
414	 * do the right thing.
415	 */
416	if (OUR_EVENT(event, EnterNotify)) {
417	    DoSpecialEnterNotify(xw, &event.xcrossing);
418	} else if (OUR_EVENT(event, LeaveNotify)) {
419	    DoSpecialLeaveNotify(xw, &event.xcrossing);
420	} else if ((screen->send_mouse_pos == ANY_EVENT_MOUSE
421#if OPT_DEC_LOCATOR
422		    || screen->send_mouse_pos == DEC_LOCATOR
423#endif /* OPT_DEC_LOCATOR */
424		   )
425		   && event.xany.type == MotionNotify
426		   && event.xcrossing.window == XtWindow(xw)) {
427	    SendMousePosition(xw, &event);
428	    continue;
429	}
430
431	if (!event.xany.send_event ||
432	    screen->allowSendEvents ||
433	    ((event.xany.type != KeyPress) &&
434	     (event.xany.type != KeyRelease) &&
435	     (event.xany.type != ButtonPress) &&
436	     (event.xany.type != ButtonRelease))) {
437
438	    /*
439	     * If the event is interesting (and not a keyboard event), turn the
440	     * mouse pointer back on.
441	     */
442	    if (screen->hide_pointer) {
443		switch (event.xany.type) {
444		case KeyPress:
445		case KeyRelease:
446		case ButtonPress:
447		case ButtonRelease:
448		    /* also these... */
449		case Expose:
450		case NoExpose:
451		case PropertyNotify:
452		case ClientMessage:
453		    break;
454		default:
455		    xtermShowPointer(xw, True);
456		    break;
457		}
458	    }
459
460	    XtDispatchEvent(&event);
461	}
462    } while (XtAppPending(app_con) & XtIMXEvent);
463}
464
465static Cursor
466make_hidden_cursor(XtermWidget xw)
467{
468    TScreen *screen = TScreenOf(xw);
469    Cursor c;
470    Display *dpy = screen->display;
471    XFontStruct *fn;
472
473    static XColor dummy;
474
475    /*
476     * Prefer nil2 (which is normally available) to "fixed" (which is supposed
477     * to be "always" available), since it's a smaller glyph in case the
478     * server insists on drawing _something_.
479     */
480    TRACE(("Ask for nil2 font\n"));
481    if ((fn = XLoadQueryFont(dpy, "nil2")) == 0) {
482	TRACE(("...Ask for fixed font\n"));
483	fn = XLoadQueryFont(dpy, DEFFONT);
484    }
485
486    if (fn != 0) {
487	/* a space character seems to work as a cursor (dots are not needed) */
488	c = XCreateGlyphCursor(dpy, fn->fid, fn->fid, 'X', ' ', &dummy, &dummy);
489	XFreeFont(dpy, fn);
490    } else {
491	c = 0;
492    }
493    TRACE(("XCreateGlyphCursor ->%#lx\n", c));
494    return (c);
495}
496
497Cursor
498make_colored_cursor(unsigned cursorindex,	/* index into font */
499		    unsigned long fg,	/* pixel value */
500		    unsigned long bg)	/* pixel value */
501{
502    TScreen *screen = TScreenOf(term);
503    Cursor c;
504    Display *dpy = screen->display;
505
506    c = XCreateFontCursor(dpy, cursorindex);
507    if (c != None) {
508	recolor_cursor(screen, c, fg, bg);
509    }
510    return (c);
511}
512
513/* ARGSUSED */
514void
515HandleKeyPressed(Widget w GCC_UNUSED,
516		 XEvent * event,
517		 String * params GCC_UNUSED,
518		 Cardinal *nparams GCC_UNUSED)
519{
520    TRACE(("Handle insert-seven-bit for %p\n", (void *) w));
521    Input(term, &event->xkey, False);
522}
523
524/* ARGSUSED */
525void
526HandleEightBitKeyPressed(Widget w GCC_UNUSED,
527			 XEvent * event,
528			 String * params GCC_UNUSED,
529			 Cardinal *nparams GCC_UNUSED)
530{
531    TRACE(("Handle insert-eight-bit for %p\n", (void *) w));
532    Input(term, &event->xkey, True);
533}
534
535/* ARGSUSED */
536void
537HandleStringEvent(Widget w GCC_UNUSED,
538		  XEvent * event GCC_UNUSED,
539		  String * params,
540		  Cardinal *nparams)
541{
542
543    if (*nparams != 1)
544	return;
545
546    if ((*params)[0] == '0' && (*params)[1] == 'x' && (*params)[2] != '\0') {
547	const char *abcdef = "ABCDEF";
548	const char *xxxxxx;
549	Char c;
550	UString p;
551	unsigned value = 0;
552
553	for (p = (UString) (*params + 2); (c = CharOf(x_toupper(*p))) !=
554	     '\0'; p++) {
555	    value *= 16;
556	    if (c >= '0' && c <= '9')
557		value += (unsigned) (c - '0');
558	    else if ((xxxxxx = strchr(abcdef, c)) != 0)
559		value += (unsigned) (xxxxxx - abcdef) + 10;
560	    else
561		break;
562	}
563	if (c == '\0') {
564	    Char hexval[2];
565	    hexval[0] = (Char) value;
566	    hexval[1] = 0;
567	    StringInput(term, hexval, (size_t) 1);
568	}
569    } else {
570	StringInput(term, (const Char *) *params, strlen(*params));
571    }
572}
573
574#if OPT_EXEC_XTERM
575
576#ifndef PROCFS_ROOT
577#define PROCFS_ROOT "/proc"
578#endif
579
580/* ARGSUSED */
581void
582HandleSpawnTerminal(Widget w GCC_UNUSED,
583		    XEvent * event GCC_UNUSED,
584		    String * params,
585		    Cardinal *nparams)
586{
587    TScreen *screen = TScreenOf(term);
588    char *child_cwd = NULL;
589    char *child_exe;
590    pid_t pid;
591
592    /*
593     * Try to find the actual program which is running in the child process.
594     * This works for Linux.  If we cannot find the program, fall back to the
595     * xterm program (which is usually adequate).  Give up if we are given only
596     * a relative path to xterm, since that would not always match $PATH.
597     */
598    child_exe = Readlink(PROCFS_ROOT "/self/exe");
599    if (!child_exe) {
600	if (strncmp(ProgramName, "./", (size_t) 2)
601	    && strncmp(ProgramName, "../", (size_t) 3)) {
602	    child_exe = xtermFindShell(ProgramName, True);
603	} else {
604	    fprintf(stderr, "Cannot exec-xterm given %s\n", ProgramName);
605	}
606	if (child_exe == 0)
607	    return;
608    }
609
610    /*
611     * Determine the current working directory of the child so that we can
612     * spawn a new terminal in the same directory.
613     *
614     * If we cannot get the CWD of the child, just use our own.
615     */
616    if (screen->pid) {
617	char child_cwd_link[sizeof(PROCFS_ROOT) + 80];
618	sprintf(child_cwd_link, PROCFS_ROOT "/%lu/cwd", (unsigned long) screen->pid);
619	child_cwd = Readlink(child_cwd_link);
620    }
621
622    /* The reaper will take care of cleaning up the child */
623    pid = fork();
624    if (pid == -1) {
625	fprintf(stderr, "Could not fork: %s\n", SysErrorMsg(errno));
626    } else if (!pid) {
627	/* We are the child */
628	if (child_cwd) {
629	    IGNORE_RC(chdir(child_cwd));	/* We don't care if this fails */
630	}
631
632	if (setuid(screen->uid) == -1
633	    || setgid(screen->gid) == -1) {
634	    fprintf(stderr, "Cannot reset uid/gid\n");
635	} else {
636	    unsigned myargc = *nparams + 1;
637	    char **myargv = TypeMallocN(char *, myargc + 1);
638	    unsigned n = 0;
639
640	    myargv[n++] = child_exe;
641
642	    while (n < myargc) {
643		myargv[n++] = *params++;
644	    }
645
646	    myargv[n] = 0;
647	    execv(child_exe, myargv);
648
649	    /* If we get here, we've failed */
650	    fprintf(stderr, "exec of '%s': %s\n", child_exe, SysErrorMsg(errno));
651	}
652	_exit(0);
653    } else {
654	/* We are the parent; clean up */
655	if (child_cwd)
656	    free(child_cwd);
657	if (child_exe)
658	    free(child_exe);
659    }
660}
661#endif /* OPT_EXEC_XTERM */
662
663/*
664 * Rather than sending characters to the host, put them directly into our
665 * input queue.  That lets a user have access to any of the control sequences
666 * for a key binding.  This is the equivalent of local function key support.
667 *
668 * NOTE:  This code does not support the hexadecimal kludge used in
669 * HandleStringEvent because it prevents us from sending an arbitrary string
670 * (but it appears in a lot of examples - so we are stuck with it).  The
671 * standard string converter does recognize "\" for newline ("\n") and for
672 * octal constants (e.g., "\007" for BEL).  So we assume the user can make do
673 * without a specialized converter.  (Don't try to use \000, though).
674 */
675/* ARGSUSED */
676void
677HandleInterpret(Widget w GCC_UNUSED,
678		XEvent * event GCC_UNUSED,
679		String * params,
680		Cardinal *param_count)
681{
682    if (*param_count == 1) {
683	const char *value = params[0];
684	int need = (int) strlen(value);
685	int used = (int) (VTbuffer->next - VTbuffer->buffer);
686	int have = (int) (VTbuffer->last - VTbuffer->buffer);
687
688	if (have - used + need < BUF_SIZE) {
689
690	    fillPtyData(term, VTbuffer, value, (int) strlen(value));
691
692	    TRACE(("Interpret %s\n", value));
693	    VTbuffer->update++;
694	}
695    }
696}
697
698/*ARGSUSED*/
699void
700HandleEnterWindow(Widget w GCC_UNUSED,
701		  XtPointer eventdata GCC_UNUSED,
702		  XEvent * event GCC_UNUSED,
703		  Boolean * cont GCC_UNUSED)
704{
705    /* NOP since we handled it above */
706    TRACE(("HandleEnterWindow ignored\n"));
707    TRACE_FOCUS(w, event);
708}
709
710/*ARGSUSED*/
711void
712HandleLeaveWindow(Widget w GCC_UNUSED,
713		  XtPointer eventdata GCC_UNUSED,
714		  XEvent * event GCC_UNUSED,
715		  Boolean * cont GCC_UNUSED)
716{
717    /* NOP since we handled it above */
718    TRACE(("HandleLeaveWindow ignored\n"));
719    TRACE_FOCUS(w, event);
720}
721
722/*ARGSUSED*/
723void
724HandleFocusChange(Widget w GCC_UNUSED,
725		  XtPointer eventdata GCC_UNUSED,
726		  XEvent * ev,
727		  Boolean * cont GCC_UNUSED)
728{
729    XFocusChangeEvent *event = (XFocusChangeEvent *) ev;
730    XtermWidget xw = term;
731    TScreen *screen = TScreenOf(xw);
732
733    TRACE(("HandleFocusChange type=%s, mode=%d, detail=%d\n",
734	   visibleEventType(event->type),
735	   event->mode,
736	   event->detail));
737    TRACE_FOCUS(xw, event);
738
739    if (screen->quiet_grab
740	&& (event->mode == NotifyGrab || event->mode == NotifyUngrab)) {
741	;
742    } else if (event->type == FocusIn) {
743	setXUrgency(screen, False);
744
745	/*
746	 * NotifyNonlinear only happens (on FocusIn) if the pointer was not in
747	 * one of our windows.  Use this to reset a case where one xterm is
748	 * partly obscuring another, and X gets (us) confused about whether the
749	 * pointer was in the window.  In particular, this can happen if the
750	 * user is resizing the obscuring window, causing some events to not be
751	 * delivered to the obscured window.
752	 */
753	if (event->detail == NotifyNonlinear
754	    && (screen->select & INWINDOW) != 0) {
755	    unselectwindow(screen, INWINDOW);
756	}
757	selectwindow(screen,
758		     ((event->detail == NotifyPointer)
759		      ? INWINDOW
760		      : FOCUS));
761	SendFocusButton(xw, event);
762    } else {
763#if OPT_FOCUS_EVENT
764	if (event->type == FocusOut) {
765	    SendFocusButton(xw, event);
766	}
767#endif
768	/*
769	 * XGrabKeyboard() will generate NotifyGrab event that we want to
770	 * ignore.
771	 */
772	if (event->mode != NotifyGrab) {
773	    unselectwindow(screen,
774			   ((event->detail == NotifyPointer)
775			    ? INWINDOW
776			    : FOCUS));
777	}
778	if (screen->grabbedKbd && (event->mode == NotifyUngrab)) {
779	    Bell(xw, XkbBI_Info, 100);
780	    ReverseVideo(xw);
781	    screen->grabbedKbd = False;
782	    update_securekbd();
783	}
784    }
785}
786
787static long lastBellTime;	/* in milliseconds */
788
789#if defined(HAVE_XKB_BELL_EXT)
790static Atom
791AtomBell(XtermWidget xw, int which)
792{
793#define DATA(name) { XkbBI_##name, XkbBN_##name }
794    static struct {
795	int value;
796	const char *name;
797    } table[] = {
798	DATA(Info),
799	    DATA(MarginBell),
800	    DATA(MinorError),
801	    DATA(TerminalBell)
802    };
803    Cardinal n;
804    Atom result = None;
805
806    for (n = 0; n < XtNumber(table); ++n) {
807	if (table[n].value == which) {
808	    result = XInternAtom(XtDisplay(xw), table[n].name, False);
809	    break;
810	}
811    }
812    return result;
813}
814#endif
815
816void
817xtermBell(XtermWidget xw, int which, int percent)
818{
819    TScreen *screen = TScreenOf(xw);
820#if defined(HAVE_XKB_BELL_EXT)
821    Atom tony = AtomBell(xw, which);
822#endif
823
824    switch (which) {
825    case XkbBI_Info:
826    case XkbBI_MinorError:
827    case XkbBI_MajorError:
828    case XkbBI_TerminalBell:
829	switch (screen->warningVolume) {
830	case bvOff:
831	    percent = -100;
832	    break;
833	case bvLow:
834	    break;
835	case bvHigh:
836	    percent = 100;
837	    break;
838	}
839	break;
840    case XkbBI_MarginBell:
841	switch (screen->marginVolume) {
842	case bvOff:
843	    percent = -100;
844	    break;
845	case bvLow:
846	    break;
847	case bvHigh:
848	    percent = 100;
849	    break;
850	}
851	break;
852    default:
853	break;
854    }
855
856#if defined(HAVE_XKB_BELL_EXT)
857    if (tony != None) {
858	XkbBell(screen->display, VShellWindow, percent, tony);
859    } else
860#endif
861	XBell(screen->display, percent);
862}
863
864void
865Bell(XtermWidget xw, int which, int percent)
866{
867    TScreen *screen = TScreenOf(xw);
868    struct timeval curtime;
869    long now_msecs;
870
871    TRACE(("BELL %d %d%%\n", which, percent));
872    if (!XtIsRealized((Widget) xw)) {
873	return;
874    }
875
876    setXUrgency(screen, True);
877
878    /* has enough time gone by that we are allowed to ring
879       the bell again? */
880    if (screen->bellSuppressTime) {
881	if (screen->bellInProgress) {
882	    do_xevents();
883	    if (screen->bellInProgress) {	/* even after new events? */
884		return;
885	    }
886	}
887	X_GETTIMEOFDAY(&curtime);
888	now_msecs = 1000 * curtime.tv_sec + curtime.tv_usec / 1000;
889	if (lastBellTime != 0 && now_msecs - lastBellTime >= 0 &&
890	    now_msecs - lastBellTime < screen->bellSuppressTime) {
891	    return;
892	}
893	lastBellTime = now_msecs;
894    }
895
896    if (screen->visualbell) {
897	VisualBell();
898    } else {
899	xtermBell(xw, which, percent);
900    }
901
902    if (screen->poponbell)
903	XRaiseWindow(screen->display, VShellWindow);
904
905    if (screen->bellSuppressTime) {
906	/* now we change a property and wait for the notify event to come
907	   back.  If the server is suspending operations while the bell
908	   is being emitted (problematic for audio bell), this lets us
909	   know when the previous bell has finished */
910	Widget w = CURRENT_EMU();
911	XChangeProperty(XtDisplay(w), XtWindow(w),
912			XA_NOTICE, XA_NOTICE, 8, PropModeAppend, NULL, 0);
913	screen->bellInProgress = True;
914    }
915}
916
917#define VB_DELAY screen->visualBellDelay
918
919static void
920flashWindow(TScreen * screen, Window window, GC visualGC, unsigned width, unsigned height)
921{
922    XFillRectangle(screen->display, window, visualGC, 0, 0, width, height);
923    XFlush(screen->display);
924    Sleep(VB_DELAY);
925    XFillRectangle(screen->display, window, visualGC, 0, 0, width, height);
926}
927
928void
929VisualBell(void)
930{
931    TScreen *screen = TScreenOf(term);
932
933    if (VB_DELAY > 0) {
934	Pixel xorPixel = (T_COLOR(screen, TEXT_FG) ^
935			  T_COLOR(screen, TEXT_BG));
936	XGCValues gcval;
937	GC visualGC;
938
939	gcval.function = GXxor;
940	gcval.foreground = xorPixel;
941	visualGC = XtGetGC((Widget) term, GCFunction + GCForeground, &gcval);
942#if OPT_TEK4014
943	if (TEK4014_ACTIVE(term)) {
944	    TekScreen *tekscr = TekScreenOf(tekWidget);
945	    flashWindow(screen, TWindow(tekscr), visualGC,
946			TFullWidth(tekscr),
947			TFullHeight(tekscr));
948	} else
949#endif
950	{
951	    flashWindow(screen, VWindow(screen), visualGC,
952			FullWidth(screen),
953			FullHeight(screen));
954	}
955	XtReleaseGC((Widget) term, visualGC);
956    }
957}
958
959/* ARGSUSED */
960void
961HandleBellPropertyChange(Widget w GCC_UNUSED,
962			 XtPointer data GCC_UNUSED,
963			 XEvent * ev,
964			 Boolean * more GCC_UNUSED)
965{
966    TScreen *screen = TScreenOf(term);
967
968    if (ev->xproperty.atom == XA_NOTICE) {
969	screen->bellInProgress = False;
970    }
971}
972
973Window
974WMFrameWindow(XtermWidget termw)
975{
976    Window win_root, win_current, *children;
977    Window win_parent = 0;
978    unsigned int nchildren;
979
980    win_current = XtWindow(termw);
981
982    /* find the parent which is child of root */
983    do {
984	if (win_parent)
985	    win_current = win_parent;
986	XQueryTree(TScreenOf(termw)->display,
987		   win_current,
988		   &win_root,
989		   &win_parent,
990		   &children,
991		   &nchildren);
992	XFree(children);
993    } while (win_root != win_parent);
994
995    return win_current;
996}
997
998#if OPT_DABBREV
999/*
1000 * The following code implements `dynamic abbreviation' expansion a la
1001 * Emacs.  It looks in the preceding visible screen and its scrollback
1002 * to find expansions of a typed word.  It compares consecutive
1003 * expansions and ignores one of them if they are identical.
1004 * (Tomasz J. Cholewo, t.cholewo@ieee.org)
1005 */
1006
1007#define IS_WORD_CONSTITUENT(x) ((x) != ' ' && (x) != '\0')
1008#define MAXWLEN 1024		/* maximum word length as in tcsh */
1009
1010static int
1011dabbrev_prev_char(TScreen * screen, CELL * cell, LineData ** ld)
1012{
1013    int result = -1;
1014    int firstLine = -(screen->savedlines);
1015
1016    *ld = getLineData(screen, cell->row);
1017    while (cell->row >= firstLine) {
1018	if (--(cell->col) >= 0) {
1019	    result = (int) (*ld)->charData[cell->col];
1020	    break;
1021	}
1022	if (--(cell->row) < firstLine)
1023	    break;		/* ...there is no previous line */
1024	*ld = getLineData(screen, cell->row);
1025	cell->col = MaxCols(screen);
1026	if (!LineTstWrapped(*ld)) {
1027	    result = ' ';	/* treat lines as separate */
1028	    break;
1029	}
1030    }
1031    return result;
1032}
1033
1034static char *
1035dabbrev_prev_word(TScreen * screen, CELL * cell, LineData ** ld)
1036{
1037    static char ab[MAXWLEN];
1038
1039    char *abword;
1040    int c;
1041    char *ab_end = (ab + MAXWLEN - 1);
1042    char *result = 0;
1043
1044    abword = ab_end;
1045    *abword = '\0';		/* end of string marker */
1046
1047    while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1048	   IS_WORD_CONSTITUENT(c)) {
1049	if (abword > ab)	/* store only |MAXWLEN| last chars */
1050	    *(--abword) = (char) c;
1051    }
1052
1053    if (c >= 0) {
1054	result = abword;
1055    } else if (abword != ab_end) {
1056	result = abword;
1057    }
1058
1059    if (result != 0) {
1060	while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 &&
1061	       !IS_WORD_CONSTITUENT(c)) {
1062	    ;			/* skip preceding spaces */
1063	}
1064	(cell->col)++;		/* can be | > screen->max_col| */
1065    }
1066    return result;
1067}
1068
1069static int
1070dabbrev_expand(TScreen * screen)
1071{
1072    int pty = screen->respond;	/* file descriptor of pty */
1073
1074    static CELL cell;
1075    static char *dabbrev_hint = 0, *lastexpansion = 0;
1076    static unsigned int expansions;
1077
1078    char *expansion;
1079    Char *copybuffer;
1080    size_t hint_len;
1081    size_t del_cnt;
1082    size_t buf_cnt;
1083    int result = 0;
1084    LineData *ld;
1085
1086    if (!screen->dabbrev_working) {	/* initialize */
1087	expansions = 0;
1088	cell.col = screen->cur_col;
1089	cell.row = screen->cur_row;
1090
1091	if (dabbrev_hint != 0)
1092	    free(dabbrev_hint);
1093
1094	if ((dabbrev_hint = dabbrev_prev_word(screen, &cell, &ld)) != 0) {
1095
1096	    if (lastexpansion != 0)
1097		free(lastexpansion);
1098
1099	    if ((lastexpansion = strdup(dabbrev_hint)) != 0) {
1100
1101		/* make own copy */
1102		if ((dabbrev_hint = strdup(dabbrev_hint)) != 0) {
1103		    screen->dabbrev_working = True;
1104		    /* we are in the middle of dabbrev process */
1105		}
1106	    } else {
1107		return result;
1108	    }
1109	} else {
1110	    return result;
1111	}
1112	if (!screen->dabbrev_working) {
1113	    if (lastexpansion != 0) {
1114		free(lastexpansion);
1115		lastexpansion = 0;
1116	    }
1117	    return result;
1118	}
1119    }
1120
1121    if (dabbrev_hint == 0)
1122	return result;
1123
1124    hint_len = strlen(dabbrev_hint);
1125    for (;;) {
1126	if ((expansion = dabbrev_prev_word(screen, &cell, &ld)) == 0) {
1127	    if (expansions >= 2) {
1128		expansions = 0;
1129		cell.col = screen->cur_col;
1130		cell.row = screen->cur_row;
1131		continue;
1132	    }
1133	    break;
1134	}
1135	if (!strncmp(dabbrev_hint, expansion, hint_len) &&	/* empty hint matches everything */
1136	    strlen(expansion) > hint_len &&	/* trivial expansion disallowed */
1137	    strcmp(expansion, lastexpansion))	/* different from previous */
1138	    break;
1139    }
1140
1141    if (expansion != 0) {
1142	del_cnt = strlen(lastexpansion) - hint_len;
1143	buf_cnt = del_cnt + strlen(expansion) - hint_len;
1144
1145	if ((copybuffer = TypeMallocN(Char, buf_cnt)) != 0) {
1146	    /* delete previous expansion */
1147	    memset(copybuffer, screen->dabbrev_erase_char, del_cnt);
1148	    memmove(copybuffer + del_cnt,
1149		    expansion + hint_len,
1150		    strlen(expansion) - hint_len);
1151	    v_write(pty, copybuffer, (unsigned) buf_cnt);
1152	    /* v_write() just reset our flag */
1153	    screen->dabbrev_working = True;
1154	    free(copybuffer);
1155
1156	    free(lastexpansion);
1157
1158	    if ((lastexpansion = strdup(expansion)) != 0) {
1159		result = 1;
1160		expansions++;
1161	    }
1162	}
1163    }
1164
1165    return result;
1166}
1167
1168/*ARGSUSED*/
1169void
1170HandleDabbrevExpand(Widget w,
1171		    XEvent * event GCC_UNUSED,
1172		    String * params GCC_UNUSED,
1173		    Cardinal *nparams GCC_UNUSED)
1174{
1175    XtermWidget xw;
1176
1177    TRACE(("Handle dabbrev-expand for %p\n", (void *) w));
1178    if ((xw = getXtermWidget(w)) != 0) {
1179	TScreen *screen = TScreenOf(xw);
1180	if (!dabbrev_expand(screen))
1181	    Bell(xw, XkbBI_TerminalBell, 0);
1182    }
1183}
1184#endif /* OPT_DABBREV */
1185
1186#if OPT_MAXIMIZE
1187/*ARGSUSED*/
1188void
1189HandleDeIconify(Widget w,
1190		XEvent * event GCC_UNUSED,
1191		String * params GCC_UNUSED,
1192		Cardinal *nparams GCC_UNUSED)
1193{
1194    XtermWidget xw;
1195
1196    if ((xw = getXtermWidget(w)) != 0) {
1197	TScreen *screen = TScreenOf(xw);
1198	XMapWindow(screen->display, VShellWindow);
1199    }
1200}
1201
1202/*ARGSUSED*/
1203void
1204HandleIconify(Widget w,
1205	      XEvent * event GCC_UNUSED,
1206	      String * params GCC_UNUSED,
1207	      Cardinal *nparams GCC_UNUSED)
1208{
1209    XtermWidget xw;
1210
1211    if ((xw = getXtermWidget(w)) != 0) {
1212	TScreen *screen = TScreenOf(xw);
1213	XIconifyWindow(screen->display,
1214		       VShellWindow,
1215		       DefaultScreen(screen->display));
1216    }
1217}
1218
1219int
1220QueryMaximize(XtermWidget termw, unsigned *width, unsigned *height)
1221{
1222    TScreen *screen = TScreenOf(termw);
1223    XSizeHints hints;
1224    long supp = 0;
1225    Window root_win;
1226    int root_x = -1;		/* saved co-ordinates */
1227    int root_y = -1;
1228    unsigned root_border;
1229    unsigned root_depth;
1230
1231    if (XGetGeometry(screen->display,
1232		     RootWindowOfScreen(XtScreen(termw)),
1233		     &root_win,
1234		     &root_x,
1235		     &root_y,
1236		     width,
1237		     height,
1238		     &root_border,
1239		     &root_depth)) {
1240	TRACE(("QueryMaximize: XGetGeometry position %d,%d size %d,%d border %d\n",
1241	       root_x,
1242	       root_y,
1243	       *width,
1244	       *height,
1245	       root_border));
1246
1247	*width -= (root_border * 2);
1248	*height -= (root_border * 2);
1249
1250	hints.flags = PMaxSize;
1251	if (XGetWMNormalHints(screen->display,
1252			      VShellWindow,
1253			      &hints,
1254			      &supp)
1255	    && (hints.flags & PMaxSize) != 0) {
1256
1257	    TRACE(("QueryMaximize: WM hints max_w %#x max_h %#x\n",
1258		   hints.max_width,
1259		   hints.max_height));
1260
1261	    if ((unsigned) hints.max_width < *width)
1262		*width = (unsigned) hints.max_width;
1263	    if ((unsigned) hints.max_height < *height)
1264		*height = (unsigned) hints.max_height;
1265	}
1266	return 1;
1267    }
1268    return 0;
1269}
1270
1271void
1272RequestMaximize(XtermWidget termw, int maximize)
1273{
1274    TScreen *screen = TScreenOf(termw);
1275    XWindowAttributes wm_attrs, vshell_attrs;
1276    unsigned root_width, root_height;
1277
1278    TRACE(("RequestMaximize %s\n", maximize ? "maximize" : "restore"));
1279
1280    if (maximize) {
1281
1282	if (QueryMaximize(termw, &root_width, &root_height)) {
1283
1284	    if (XGetWindowAttributes(screen->display,
1285				     WMFrameWindow(termw),
1286				     &wm_attrs)) {
1287
1288		if (XGetWindowAttributes(screen->display,
1289					 VShellWindow,
1290					 &vshell_attrs)) {
1291
1292		    if (screen->restore_data != True
1293			|| screen->restore_width != root_width
1294			|| screen->restore_height != root_height) {
1295			screen->restore_data = True;
1296			screen->restore_x = wm_attrs.x + wm_attrs.border_width;
1297			screen->restore_y = wm_attrs.y + wm_attrs.border_width;
1298			screen->restore_width = (unsigned) vshell_attrs.width;
1299			screen->restore_height = (unsigned) vshell_attrs.height;
1300			TRACE(("HandleMaximize: save window position %d,%d size %d,%d\n",
1301			       screen->restore_x,
1302			       screen->restore_y,
1303			       screen->restore_width,
1304			       screen->restore_height));
1305		    }
1306
1307		    /* subtract wm decoration dimensions */
1308		    root_width -=
1309			(unsigned) ((wm_attrs.width - vshell_attrs.width)
1310				    + (wm_attrs.border_width * 2));
1311		    root_height -=
1312			(unsigned) ((wm_attrs.height - vshell_attrs.height)
1313				    + (wm_attrs.border_width * 2));
1314
1315		    XMoveResizeWindow(screen->display, VShellWindow,
1316				      0 + wm_attrs.border_width,	/* x */
1317				      0 + wm_attrs.border_width,	/* y */
1318				      root_width,
1319				      root_height);
1320		}
1321	    }
1322	}
1323    } else {
1324	if (screen->restore_data) {
1325	    TRACE(("HandleRestoreSize: position %d,%d size %d,%d\n",
1326		   screen->restore_x,
1327		   screen->restore_y,
1328		   screen->restore_width,
1329		   screen->restore_height));
1330	    screen->restore_data = False;
1331
1332	    XMoveResizeWindow(screen->display,
1333			      VShellWindow,
1334			      screen->restore_x,
1335			      screen->restore_y,
1336			      screen->restore_width,
1337			      screen->restore_height);
1338	}
1339    }
1340}
1341
1342/*ARGSUSED*/
1343void
1344HandleMaximize(Widget w,
1345	       XEvent * event GCC_UNUSED,
1346	       String * params GCC_UNUSED,
1347	       Cardinal *nparams GCC_UNUSED)
1348{
1349    XtermWidget xw;
1350
1351    if ((xw = getXtermWidget(w)) != 0) {
1352	RequestMaximize(xw, 1);
1353    }
1354}
1355
1356/*ARGSUSED*/
1357void
1358HandleRestoreSize(Widget w,
1359		  XEvent * event GCC_UNUSED,
1360		  String * params GCC_UNUSED,
1361		  Cardinal *nparams GCC_UNUSED)
1362{
1363    XtermWidget xw;
1364
1365    if ((xw = getXtermWidget(w)) != 0) {
1366	RequestMaximize(xw, 0);
1367    }
1368}
1369#endif /* OPT_MAXIMIZE */
1370
1371void
1372Redraw(void)
1373{
1374    TScreen *screen = TScreenOf(term);
1375    XExposeEvent event;
1376
1377    TRACE(("Redraw\n"));
1378
1379    event.type = Expose;
1380    event.display = screen->display;
1381    event.x = 0;
1382    event.y = 0;
1383    event.count = 0;
1384
1385    if (VWindow(screen)) {
1386	event.window = VWindow(screen);
1387	event.width = term->core.width;
1388	event.height = term->core.height;
1389	(*term->core.widget_class->core_class.expose) ((Widget) term,
1390						       (XEvent *) & event,
1391						       NULL);
1392	if (ScrollbarWidth(screen)) {
1393	    (screen->scrollWidget->core.widget_class->core_class.expose)
1394		(screen->scrollWidget, (XEvent *) & event, NULL);
1395	}
1396    }
1397#if OPT_TEK4014
1398    if (TEK4014_SHOWN(term)) {
1399	TekScreen *tekscr = TekScreenOf(tekWidget);
1400	event.window = TWindow(tekscr);
1401	event.width = tekWidget->core.width;
1402	event.height = tekWidget->core.height;
1403	TekExpose((Widget) tekWidget, (XEvent *) & event, NULL);
1404    }
1405#endif
1406}
1407
1408#ifdef VMS
1409#define TIMESTAMP_FMT "%s%d-%02d-%02d-%02d-%02d-%02d"
1410#else
1411#define TIMESTAMP_FMT "%s%d-%02d-%02d.%02d:%02d:%02d"
1412#endif
1413
1414void
1415timestamp_filename(char *dst, const char *src)
1416{
1417    time_t tstamp;
1418    struct tm *tstruct;
1419
1420    tstamp = time((time_t *) 0);
1421    tstruct = localtime(&tstamp);
1422    sprintf(dst, TIMESTAMP_FMT,
1423	    src,
1424	    tstruct->tm_year + 1900,
1425	    tstruct->tm_mon + 1,
1426	    tstruct->tm_mday,
1427	    tstruct->tm_hour,
1428	    tstruct->tm_min,
1429	    tstruct->tm_sec);
1430}
1431
1432int
1433open_userfile(uid_t uid, gid_t gid, char *path, Bool append)
1434{
1435    int fd;
1436    struct stat sb;
1437
1438#ifdef VMS
1439    if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
1440	int the_error = errno;
1441	fprintf(stderr, "%s: cannot open %s: %d:%s\n",
1442		xterm_name,
1443		path,
1444		the_error,
1445		SysErrorMsg(the_error));
1446	return -1;
1447    }
1448    chown(path, uid, gid);
1449#else
1450    if ((access(path, F_OK) != 0 && (errno != ENOENT))
1451	|| (creat_as(uid, gid, append, path, 0644) <= 0)
1452	|| ((fd = open(path, O_WRONLY | O_APPEND)) < 0)) {
1453	int the_error = errno;
1454	fprintf(stderr, "%s: cannot open %s: %d:%s\n",
1455		xterm_name,
1456		path,
1457		the_error,
1458		SysErrorMsg(the_error));
1459	return -1;
1460    }
1461#endif
1462
1463    /*
1464     * Doublecheck that the user really owns the file that we've opened before
1465     * we do any damage, and that it is not world-writable.
1466     */
1467    if (fstat(fd, &sb) < 0
1468	|| sb.st_uid != uid
1469	|| (sb.st_mode & 022) != 0) {
1470	fprintf(stderr, "%s: you do not own %s\n", xterm_name, path);
1471	close(fd);
1472	return -1;
1473    }
1474    return fd;
1475}
1476
1477#ifndef VMS
1478/*
1479 * Create a file only if we could with the permissions of the real user id.
1480 * We could emulate this with careful use of access() and following
1481 * symbolic links, but that is messy and has race conditions.
1482 * Forking is messy, too, but we can't count on setreuid() or saved set-uids
1483 * being available.
1484 *
1485 * Note: When called for user logging, we have ensured that the real and
1486 * effective user ids are the same, so this remains as a convenience function
1487 * for the debug logs.
1488 *
1489 * Returns
1490 *	 1 if we can proceed to open the file in relative safety,
1491 *	-1 on error, e.g., cannot fork
1492 *	 0 otherwise.
1493 */
1494int
1495creat_as(uid_t uid, gid_t gid, Bool append, char *pathname, int mode)
1496{
1497    int fd;
1498    pid_t pid;
1499    int retval = 0;
1500    int childstat = 0;
1501#ifndef HAVE_WAITPID
1502    int waited;
1503    SIGNAL_T(*chldfunc) (int);
1504
1505    chldfunc = signal(SIGCHLD, SIG_DFL);
1506#endif /* HAVE_WAITPID */
1507
1508    TRACE(("creat_as(uid=%d/%d, gid=%d/%d, append=%d, pathname=%s, mode=%#o)\n",
1509	   (int) uid, (int) geteuid(),
1510	   (int) gid, (int) getegid(),
1511	   append,
1512	   pathname,
1513	   mode));
1514
1515    if (uid == geteuid() && gid == getegid()) {
1516	fd = open(pathname,
1517		  O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1518		  mode);
1519	if (fd >= 0)
1520	    close(fd);
1521	return (fd >= 0);
1522    }
1523
1524    pid = fork();
1525    switch (pid) {
1526    case 0:			/* child */
1527	if (setgid(gid) == -1
1528	    || setuid(uid) == -1) {
1529	    /* we cannot report an error here via stderr, just quit */
1530	    retval = 1;
1531	} else {
1532	    fd = open(pathname,
1533		      O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL),
1534		      mode);
1535	    if (fd >= 0) {
1536		close(fd);
1537		retval = 0;
1538	    } else {
1539		retval = 1;
1540	    }
1541	}
1542	_exit(retval);
1543	/* NOTREACHED */
1544    case -1:			/* error */
1545	return retval;
1546    default:			/* parent */
1547#ifdef HAVE_WAITPID
1548	while (waitpid(pid, &childstat, 0) < 0) {
1549#ifdef EINTR
1550	    if (errno == EINTR)
1551		continue;
1552#endif /* EINTR */
1553#ifdef ERESTARTSYS
1554	    if (errno == ERESTARTSYS)
1555		continue;
1556#endif /* ERESTARTSYS */
1557	    break;
1558	}
1559#else /* HAVE_WAITPID */
1560	waited = wait(&childstat);
1561	signal(SIGCHLD, chldfunc);
1562	/*
1563	   Since we had the signal handler uninstalled for a while,
1564	   we might have missed the termination of our screen child.
1565	   If we can check for this possibility without hanging, do so.
1566	 */
1567	do
1568	    if (waited == TScreenOf(term)->pid)
1569		Cleanup(0);
1570	while ((waited = nonblocking_wait()) > 0) ;
1571#endif /* HAVE_WAITPID */
1572#ifndef WIFEXITED
1573#define WIFEXITED(status) ((status & 0xff) != 0)
1574#endif
1575	if (WIFEXITED(childstat))
1576	    retval = 1;
1577	return retval;
1578    }
1579}
1580#endif /* !VMS */
1581
1582int
1583xtermResetIds(TScreen * screen)
1584{
1585    int result = 0;
1586    if (setgid(screen->gid) == -1) {
1587	fprintf(stderr, "%s: unable to reset group-id\n", ProgramName);
1588	result = -1;
1589    }
1590    if (setuid(screen->uid) == -1) {
1591	fprintf(stderr, "%s: unable to reset user-id\n", ProgramName);
1592	result = -1;
1593    }
1594    return result;
1595}
1596
1597#ifdef ALLOWLOGGING
1598
1599/*
1600 * Logging is a security hole, since it allows a setuid program to write
1601 * arbitrary data to an arbitrary file.  So it is disabled by default.
1602 */
1603
1604#ifdef ALLOWLOGFILEEXEC
1605static SIGNAL_T
1606logpipe(int sig GCC_UNUSED)
1607{
1608    XtermWidget xw = term;
1609    TScreen *screen = TScreenOf(xw);
1610
1611#ifdef SYSV
1612    (void) signal(SIGPIPE, SIG_IGN);
1613#endif /* SYSV */
1614    if (screen->logging)
1615	CloseLog(xw);
1616}
1617#endif /* ALLOWLOGFILEEXEC */
1618
1619void
1620StartLog(XtermWidget xw)
1621{
1622    static char *log_default;
1623#ifdef ALLOWLOGFILEEXEC
1624    char *cp;
1625#endif /* ALLOWLOGFILEEXEC */
1626    TScreen *screen = TScreenOf(xw);
1627
1628    if (screen->logging || (screen->inhibit & I_LOG))
1629	return;
1630#ifdef VMS			/* file name is fixed in VMS variant */
1631    screen->logfd = open(XTERM_VMS_LOGFILE,
1632			 O_CREAT | O_TRUNC | O_APPEND | O_RDWR,
1633			 0640);
1634    if (screen->logfd < 0)
1635	return;			/* open failed */
1636#else /*VMS */
1637    if (screen->logfile == NULL || *screen->logfile == 0) {
1638	if (screen->logfile)
1639	    free(screen->logfile);
1640	if (log_default == NULL) {
1641#if defined(HAVE_GETHOSTNAME) && defined(HAVE_STRFTIME)
1642	    char log_def_name[512];	/* see sprintf below */
1643	    char hostname[255 + 1];	/* Internet standard limit (RFC 1035):
1644					   ``To simplify implementations, the
1645					   total length of a domain name (i.e.,
1646					   label octets and label length
1647					   octets) is restricted to 255 octets
1648					   or less.'' */
1649	    char yyyy_mm_dd_hh_mm_ss[4 + 5 * (1 + 2) + 1];
1650	    time_t now;
1651	    struct tm *ltm;
1652
1653	    now = time((time_t *) 0);
1654	    ltm = (struct tm *) localtime(&now);
1655	    if ((gethostname(hostname, sizeof(hostname)) == 0) &&
1656		(strftime(yyyy_mm_dd_hh_mm_ss,
1657			  sizeof(yyyy_mm_dd_hh_mm_ss),
1658			  "%Y.%m.%d.%H.%M.%S", ltm) > 0)) {
1659		(void) sprintf(log_def_name, "Xterm.log.%.255s.%.20s.%d",
1660			       hostname, yyyy_mm_dd_hh_mm_ss, (int) getpid());
1661	    }
1662	    if ((log_default = x_strdup(log_def_name)) == NULL)
1663		return;
1664#else
1665	    const char *log_def_name = "XtermLog.XXXXXX";
1666	    if ((log_default = x_strdup(log_def_name)) == NULL)
1667		return;
1668
1669	    mktemp(log_default);
1670#endif
1671	}
1672	if ((screen->logfile = x_strdup(log_default)) == 0)
1673	    return;
1674    }
1675    if (*screen->logfile == '|') {	/* exec command */
1676#ifdef ALLOWLOGFILEEXEC
1677	/*
1678	 * Warning, enabling this "feature" allows arbitrary programs
1679	 * to be run.  If ALLOWLOGFILECHANGES is enabled, this can be
1680	 * done through escape sequences....  You have been warned.
1681	 */
1682	int pid;
1683	int p[2];
1684	static char *shell;
1685	struct passwd *pw;
1686
1687	if (pipe(p) < 0 || (pid = fork()) < 0)
1688	    return;
1689	if (pid == 0) {		/* child */
1690	    /*
1691	     * Close our output (we won't be talking back to the
1692	     * parent), and redirect our child's output to the
1693	     * original stderr.
1694	     */
1695	    close(p[1]);
1696	    dup2(p[0], 0);
1697	    close(p[0]);
1698	    dup2(fileno(stderr), 1);
1699	    dup2(fileno(stderr), 2);
1700
1701	    close(fileno(stderr));
1702	    close(ConnectionNumber(screen->display));
1703	    close(screen->respond);
1704
1705	    if ((((cp = x_getenv("SHELL")) == NULL)
1706		 && ((pw = getpwuid(screen->uid)) == NULL
1707		     || *(cp = pw->pw_shell) == 0))
1708		|| (shell = CastMallocN(char, strlen(cp))) == 0) {
1709		static char dummy[] = "/bin/sh";
1710		shell = dummy;
1711	    } else {
1712		strcpy(shell, cp);
1713	    }
1714
1715	    signal(SIGHUP, SIG_DFL);
1716	    signal(SIGCHLD, SIG_DFL);
1717
1718	    /* (this is redundant) */
1719	    if (xtermResetIds(screen) < 0)
1720		exit(ERROR_SETUID);
1721
1722	    execl(shell, shell, "-c", &screen->logfile[1], (void *) 0);
1723
1724	    fprintf(stderr, "%s: Can't exec `%s'\n", xterm_name,
1725		    &screen->logfile[1]);
1726	    exit(ERROR_LOGEXEC);
1727	}
1728	close(p[0]);
1729	screen->logfd = p[1];
1730	signal(SIGPIPE, logpipe);
1731#else
1732	Bell(xw, XkbBI_Info, 0);
1733	Bell(xw, XkbBI_Info, 0);
1734	return;
1735#endif
1736    } else {
1737	if ((screen->logfd = open_userfile(screen->uid,
1738					   screen->gid,
1739					   screen->logfile,
1740					   (log_default != 0))) < 0)
1741	    return;
1742    }
1743#endif /*VMS */
1744    screen->logstart = VTbuffer->next;
1745    screen->logging = True;
1746    update_logging();
1747}
1748
1749void
1750CloseLog(XtermWidget xw)
1751{
1752    TScreen *screen = TScreenOf(xw);
1753
1754    if (!screen->logging || (screen->inhibit & I_LOG))
1755	return;
1756    FlushLog(xw);
1757    close(screen->logfd);
1758    screen->logging = False;
1759    update_logging();
1760}
1761
1762void
1763FlushLog(XtermWidget xw)
1764{
1765    TScreen *screen = TScreenOf(xw);
1766
1767    if (screen->logging && !(screen->inhibit & I_LOG)) {
1768	Char *cp;
1769	int i;
1770
1771#ifdef VMS			/* avoid logging output loops which otherwise occur sometimes
1772				   when there is no output and cp/screen->logstart are 1 apart */
1773	if (!tt_new_output)
1774	    return;
1775	tt_new_output = False;
1776#endif /* VMS */
1777	cp = VTbuffer->next;
1778	if (screen->logstart != 0
1779	    && (i = (int) (cp - screen->logstart)) > 0) {
1780	    IGNORE_RC(write(screen->logfd, screen->logstart, (size_t) i));
1781	}
1782	screen->logstart = VTbuffer->next;
1783    }
1784}
1785
1786#endif /* ALLOWLOGGING */
1787
1788/***====================================================================***/
1789
1790#if OPT_ISO_COLORS
1791static void
1792ReportAnsiColorRequest(XtermWidget xw, int colornum, int final)
1793{
1794    if (AllowColorOps(xw, ecGetAnsiColor)) {
1795	XColor color;
1796	Colormap cmap = xw->core.colormap;
1797	char buffer[80];
1798
1799	TRACE(("ReportAnsiColorRequest %d\n", colornum));
1800	color.pixel = GET_COLOR_RES(xw, TScreenOf(xw)->Acolors[colornum]);
1801	XQueryColor(TScreenOf(xw)->display, cmap, &color);
1802	sprintf(buffer, "4;%d;rgb:%04x/%04x/%04x",
1803		colornum,
1804		color.red,
1805		color.green,
1806		color.blue);
1807	unparseputc1(xw, ANSI_OSC);
1808	unparseputs(xw, buffer);
1809	unparseputc1(xw, final);
1810	unparse_end(xw);
1811    }
1812}
1813
1814static unsigned
1815getColormapSize(Display * display)
1816{
1817    unsigned result;
1818    int numFound;
1819    XVisualInfo myTemplate, *visInfoPtr;
1820
1821    myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(display,
1822							    XDefaultScreen(display)));
1823    visInfoPtr = XGetVisualInfo(display, (long) VisualIDMask,
1824				&myTemplate, &numFound);
1825    result = (numFound >= 1) ? (unsigned) visInfoPtr->colormap_size : 0;
1826
1827    XFree((char *) visInfoPtr);
1828    return result;
1829}
1830
1831/*
1832 * Find closest color for "def" in "cmap".
1833 * Set "def" to the resulting color.
1834 *
1835 * Based on Monish Shah's "find_closest_color()" for Vim 6.0,
1836 * modified with ideas from David Tong's "noflash" library.
1837 * The code from Vim in turn was derived from FindClosestColor() in Tcl/Tk.
1838 *
1839 * These provide some introduction:
1840 *	http://en.wikipedia.org/wiki/YIQ
1841 *		for an introduction to YIQ weights.
1842 *	http://en.wikipedia.org/wiki/Luminance_(video)
1843 *		for a discussion of luma.
1844 *	http://en.wikipedia.org/wiki/YUV
1845 *
1846 * Return False if not able to find or allocate a color.
1847 */
1848static Boolean
1849find_closest_color(Display * dpy, Colormap cmap, XColor * def)
1850{
1851    Boolean result = False;
1852    XColor *colortable;
1853    char *tried;
1854    double diff, thisRGB, bestRGB;
1855    unsigned attempts;
1856    unsigned bestInx;
1857    unsigned cmap_size;
1858    unsigned i;
1859
1860    cmap_size = getColormapSize(dpy);
1861    if (cmap_size != 0) {
1862
1863	colortable = TypeMallocN(XColor, (size_t) cmap_size);
1864	if (colortable != 0) {
1865
1866	    tried = TypeCallocN(char, (size_t) cmap_size);
1867	    if (tried != 0) {
1868
1869		for (i = 0; i < cmap_size; i++) {
1870		    colortable[i].pixel = (unsigned long) i;
1871		}
1872		XQueryColors(dpy, cmap, colortable, (int) cmap_size);
1873
1874		/*
1875		 * Try (possibly each entry in the color map) to find the best
1876		 * approximation to the requested color.
1877		 */
1878		for (attempts = 0; attempts < cmap_size; attempts++) {
1879		    Boolean first = True;
1880
1881		    bestRGB = 0.0;
1882		    bestInx = 0;
1883		    for (i = 0; i < cmap_size; i++) {
1884			if (!tried[bestInx]) {
1885			    /*
1886			     * Look for the best match based on luminance.
1887			     * Measure this by the least-squares difference of
1888			     * the weighted R/G/B components from the color map
1889			     * versus the requested color.  Use the Y (luma)
1890			     * component of the YIQ color space model for
1891			     * weights that correspond to the luminance.
1892			     */
1893#define AddColorWeight(weight, color) \
1894			    diff = weight * (int) ((def->color) - colortable[i].color); \
1895			    thisRGB = diff * diff
1896
1897			    AddColorWeight(0.30, red);
1898			    AddColorWeight(0.61, green);
1899			    AddColorWeight(0.11, blue);
1900
1901			    if (first || (thisRGB < bestRGB)) {
1902				first = False;
1903				bestInx = i;
1904				bestRGB = thisRGB;
1905			    }
1906			}
1907		    }
1908		    if (XAllocColor(dpy, cmap, &colortable[bestInx]) != 0) {
1909			*def = colortable[bestInx];
1910			result = True;
1911			break;
1912		    }
1913		    /*
1914		     * It failed - either the color map entry was readonly, or
1915		     * another client has allocated the entry.  Mark the entry
1916		     * so we will ignore it
1917		     */
1918		    tried[bestInx] = True;
1919		}
1920		free(tried);
1921	    }
1922	    free(colortable);
1923	}
1924    }
1925    return result;
1926}
1927
1928/*
1929 * Allocate a color for the "ANSI" colors.  That actually includes colors up
1930 * to 256.
1931 *
1932 * Returns
1933 *	-1 on error
1934 *	0 on no change
1935 *	1 if a new color was allocated.
1936 */
1937static int
1938AllocateAnsiColor(XtermWidget xw,
1939		  ColorRes * res,
1940		  const char *spec)
1941{
1942    int result;
1943    XColor def;
1944    TScreen *screen = TScreenOf(xw);
1945    Colormap cmap = xw->core.colormap;
1946
1947    if (XParseColor(screen->display, cmap, spec, &def)
1948	&& (XAllocColor(screen->display, cmap, &def)
1949	    || find_closest_color(screen->display, cmap, &def))) {
1950	if (
1951#if OPT_COLOR_RES
1952	       res->mode == True &&
1953#endif
1954	       EQL_COLOR_RES(res, def.pixel)) {
1955	    result = 0;
1956	} else {
1957	    result = 1;
1958	    SET_COLOR_RES(res, def.pixel);
1959	    TRACE(("AllocateAnsiColor[%d] %s (pixel %#lx)\n",
1960		   (int) (res - screen->Acolors), spec, def.pixel));
1961#if OPT_COLOR_RES
1962	    if (!res->mode)
1963		result = 0;
1964	    res->mode = True;
1965#endif
1966	}
1967    } else {
1968	TRACE(("AllocateAnsiColor %s (failed)\n", spec));
1969	result = -1;
1970    }
1971    return (result);
1972}
1973
1974#if OPT_COLOR_RES
1975Pixel
1976xtermGetColorRes(XtermWidget xw, ColorRes * res)
1977{
1978    Pixel result = 0;
1979
1980    if (res->mode) {
1981	result = res->value;
1982    } else {
1983	TRACE(("xtermGetColorRes for Acolors[%d]\n",
1984	       (int) (res - TScreenOf(xw)->Acolors)));
1985
1986	if (res >= TScreenOf(xw)->Acolors) {
1987	    assert(res - TScreenOf(xw)->Acolors < MAXCOLORS);
1988
1989	    if (AllocateAnsiColor(xw, res, res->resource) < 0) {
1990		res->value = TScreenOf(xw)->Tcolors[TEXT_FG].value;
1991		res->mode = -True;
1992		fprintf(stderr,
1993			"%s: Cannot allocate color %s\n",
1994			xterm_name,
1995			NonNull(res->resource));
1996	    }
1997	    result = res->value;
1998	} else {
1999	    result = 0;
2000	}
2001    }
2002    return result;
2003}
2004#endif
2005
2006static int
2007ChangeOneAnsiColor(XtermWidget xw, int color, const char *name)
2008{
2009    int code;
2010
2011    if (color < 0 || color >= MAXCOLORS) {
2012	code = -1;
2013    } else {
2014	ColorRes *res = &(TScreenOf(xw)->Acolors[color]);
2015
2016	TRACE(("ChangeAnsiColor for Acolors[%d]\n", color));
2017	code = AllocateAnsiColor(xw, res, name);
2018    }
2019    return code;
2020}
2021
2022/*
2023 * Set or query entries in the Acolors[] array by parsing pairs of color/name
2024 * values from the given buffer.
2025 *
2026 * The color can be any legal index into Acolors[], which consists of the
2027 * 16/88/256 "ANSI" colors, followed by special color values for the various
2028 * colorXX resources.  The indices for the special color values are not
2029 * simple to work with, so an alternative is to use the calls which pass in
2030 * 'first' set to the beginning of those indices.
2031 *
2032 * If the name is "?", report to the host the current value for the color.
2033 */
2034static Bool
2035ChangeAnsiColorRequest(XtermWidget xw,
2036		       char *buf,
2037		       int first,
2038		       int final)
2039{
2040    char *name;
2041    int color;
2042    int repaint = False;
2043    int code;
2044    int last = (MAXCOLORS - first);
2045
2046    TRACE(("ChangeAnsiColorRequest string='%s'\n", buf));
2047
2048    while (buf && *buf) {
2049	name = strchr(buf, ';');
2050	if (name == NULL)
2051	    break;
2052	*name = '\0';
2053	name++;
2054	color = atoi(buf);
2055	if (color < 0 || color >= last)
2056	    break;		/* quit on any error */
2057	buf = strchr(name, ';');
2058	if (buf) {
2059	    *buf = '\0';
2060	    buf++;
2061	}
2062	if (!strcmp(name, "?")) {
2063	    ReportAnsiColorRequest(xw, color + first, final);
2064	} else {
2065	    code = ChangeOneAnsiColor(xw, color + first, name);
2066	    if (code < 0) {
2067		/* stop on any error */
2068		break;
2069	    } else if (code > 0) {
2070		repaint = True;
2071	    }
2072	    /* FIXME:  free old color somehow?  We aren't for the other color
2073	     * change style (dynamic colors).
2074	     */
2075	}
2076    }
2077
2078    return (repaint);
2079}
2080
2081static Bool
2082ResetOneAnsiColor(XtermWidget xw, int color, int start)
2083{
2084    Bool repaint = False;
2085    int last = MAXCOLORS - start;
2086
2087    if (color >= 0 && color < last) {
2088	ColorRes *res = &(TScreenOf(xw)->Acolors[color + start]);
2089
2090	if (res->mode) {
2091	    /* a color has been allocated for this slot - test further... */
2092	    if (ChangeOneAnsiColor(xw, color + start, res->resource) > 0) {
2093		repaint = True;
2094	    }
2095	}
2096    }
2097    return repaint;
2098}
2099
2100int
2101ResetAnsiColorRequest(XtermWidget xw, char *buf, int start)
2102{
2103    int repaint = 0;
2104    int color;
2105
2106    TRACE(("ResetAnsiColorRequest(%s)\n", buf));
2107    if (*buf != '\0') {
2108	/* reset specific colors */
2109	while (!IsEmpty(buf)) {
2110	    char *next;
2111
2112	    color = (int) strtol(buf, &next, 10);
2113	    if ((next == buf) || (color < 0))
2114		break;		/* no number at all */
2115	    if (next != 0) {
2116		if (strchr(";", *next) == 0)
2117		    break;	/* unexpected delimiter */
2118		++next;
2119	    }
2120
2121	    if (ResetOneAnsiColor(xw, color, start)) {
2122		++repaint;
2123	    }
2124	    buf = next;
2125	}
2126    } else {
2127	TRACE(("...resetting all %d colors\n", MAXCOLORS));
2128	for (color = 0; color < MAXCOLORS; ++color) {
2129	    if (ResetOneAnsiColor(xw, color, start)) {
2130		++repaint;
2131	    }
2132	}
2133    }
2134    TRACE(("...ResetAnsiColorRequest ->%d\n", repaint));
2135    return repaint;
2136}
2137#else
2138#define find_closest_color(display, cmap, def) 0
2139#endif /* OPT_ISO_COLORS */
2140
2141#if OPT_PASTE64
2142static void
2143ManipulateSelectionData(XtermWidget xw, TScreen * screen, char *buf, int final)
2144{
2145#define PDATA(a,b) { a, #b }
2146    static struct {
2147	char given;
2148	String result;
2149    } table[] = {
2150	PDATA('s', SELECT),
2151	    PDATA('p', PRIMARY),
2152	    PDATA('c', CLIPBOARD),
2153	    PDATA('0', CUT_BUFFER0),
2154	    PDATA('1', CUT_BUFFER1),
2155	    PDATA('2', CUT_BUFFER2),
2156	    PDATA('3', CUT_BUFFER3),
2157	    PDATA('4', CUT_BUFFER4),
2158	    PDATA('5', CUT_BUFFER5),
2159	    PDATA('6', CUT_BUFFER6),
2160	    PDATA('7', CUT_BUFFER7),
2161    };
2162
2163    const char *base = buf;
2164    char *used = x_strdup(base);
2165    Cardinal j, n = 0;
2166    String *select_args = 0;
2167
2168    TRACE(("Manipulate selection data\n"));
2169
2170    while (*buf != ';' && *buf != '\0') {
2171	++buf;
2172    }
2173
2174    if (*buf == ';') {
2175	*buf++ = '\0';
2176
2177	if (*base == '\0')
2178	    base = "s0";
2179	if ((select_args = TypeCallocN(String, 1 + strlen(base))) == 0)
2180	    return;
2181	while (*base != '\0') {
2182	    for (j = 0; j < XtNumber(table); ++j) {
2183		if (*base == table[j].given) {
2184		    used[n] = *base;
2185		    select_args[n++] = table[j].result;
2186		    TRACE(("atom[%d] %s\n", n, table[j].result));
2187		    break;
2188		}
2189	    }
2190	    ++base;
2191	}
2192	used[n] = 0;
2193
2194	if (!strcmp(buf, "?")) {
2195	    if (AllowWindowOps(xw, ewGetSelection)) {
2196		TRACE(("Getting selection\n"));
2197		unparseputc1(xw, ANSI_OSC);
2198		unparseputs(xw, "52");
2199		unparseputc(xw, ';');
2200
2201		unparseputs(xw, used);
2202		unparseputc(xw, ';');
2203
2204		/* Tell xtermGetSelection data is base64 encoded */
2205		screen->base64_paste = n;
2206		screen->base64_final = final;
2207
2208		/* terminator will be written in this call */
2209		xtermGetSelection((Widget) xw, (Time) 0, select_args, n, NULL);
2210	    }
2211	} else {
2212	    if (AllowWindowOps(xw, ewSetSelection)) {
2213		TRACE(("Setting selection with %s\n", buf));
2214		ClearSelectionBuffer(screen);
2215		while (*buf != '\0')
2216		    AppendToSelectionBuffer(screen, CharOf(*buf++));
2217		CompleteSelection(xw, select_args, n);
2218	    }
2219	}
2220    }
2221}
2222#endif /* OPT_PASTE64 */
2223
2224/***====================================================================***/
2225
2226#define IsSetUtf8Title(xw) (IsTitleMode(xw, tmSetUtf8) || (xw->screen.utf8_title))
2227
2228static Bool
2229xtermIsPrintable(XtermWidget xw, Char ** bufp, Char * last)
2230{
2231    TScreen *screen = TScreenOf(xw);
2232    Bool result = False;
2233    Char *cp = *bufp;
2234    Char *next = cp;
2235
2236    (void) screen;
2237    (void) last;
2238
2239#if OPT_WIDE_CHARS
2240    if (xtermEnvUTF8() && IsSetUtf8Title(xw)) {
2241	PtyData data;
2242
2243	if (decodeUtf8(fakePtyData(&data, cp, last))) {
2244	    if (data.utf_data != UCS_REPL
2245		&& (data.utf_data >= 128 ||
2246		    ansi_table[data.utf_data] == CASE_PRINT)) {
2247		next += (data.utf_size - 1);
2248		result = True;
2249	    } else {
2250		result = False;
2251	    }
2252	} else {
2253	    result = False;
2254	}
2255    } else
2256#endif
2257#if OPT_C1_PRINT
2258	if (screen->c1_printable
2259	    && (*cp >= 128 && *cp < 160)) {
2260	result = True;
2261    } else
2262#endif
2263    if (ansi_table[*cp] == CASE_PRINT) {
2264	result = True;
2265    }
2266    *bufp = next;
2267    return result;
2268}
2269
2270/***====================================================================***/
2271
2272/*
2273 * Enum corresponding to the actual OSC codes rather than the internal
2274 * array indices.  Compare with TermColors.
2275 */
2276typedef enum {
2277    OSC_TEXT_FG = 10
2278    ,OSC_TEXT_BG
2279    ,OSC_TEXT_CURSOR
2280    ,OSC_MOUSE_FG
2281    ,OSC_MOUSE_BG
2282#if OPT_TEK4014
2283    ,OSC_TEK_FG = 15
2284    ,OSC_TEK_BG
2285#endif
2286#if OPT_HIGHLIGHT_COLOR
2287    ,OSC_HIGHLIGHT_BG = 17
2288#endif
2289#if OPT_TEK4014
2290    ,OSC_TEK_CURSOR = 18
2291#endif
2292#if OPT_HIGHLIGHT_COLOR
2293    ,OSC_HIGHLIGHT_FG = 19
2294#endif
2295    ,OSC_NCOLORS
2296} OscTextColors;
2297
2298/*
2299 * Map codes to OSC controls that can reset colors.
2300 */
2301#define OSC_RESET 100
2302#define OSC_Reset(code) (code) + OSC_RESET
2303
2304static ScrnColors *pOldColors = NULL;
2305
2306static Bool
2307GetOldColors(XtermWidget xw)
2308{
2309    int i;
2310    if (pOldColors == NULL) {
2311	pOldColors = (ScrnColors *) XtMalloc((Cardinal) sizeof(ScrnColors));
2312	if (pOldColors == NULL) {
2313	    fprintf(stderr, "allocation failure in GetOldColors\n");
2314	    return (False);
2315	}
2316	pOldColors->which = 0;
2317	for (i = 0; i < NCOLORS; i++) {
2318	    pOldColors->colors[i] = 0;
2319	    pOldColors->names[i] = NULL;
2320	}
2321	GetColors(xw, pOldColors);
2322    }
2323    return (True);
2324}
2325
2326static int
2327oppositeColor(int n)
2328{
2329    switch (n) {
2330    case TEXT_FG:
2331	n = TEXT_BG;
2332	break;
2333    case TEXT_BG:
2334	n = TEXT_FG;
2335	break;
2336    case MOUSE_FG:
2337	n = MOUSE_BG;
2338	break;
2339    case MOUSE_BG:
2340	n = MOUSE_FG;
2341	break;
2342#if OPT_TEK4014
2343    case TEK_FG:
2344	n = TEK_BG;
2345	break;
2346    case TEK_BG:
2347	n = TEK_FG;
2348	break;
2349#endif
2350#if OPT_HIGHLIGHT_COLOR
2351    case HIGHLIGHT_FG:
2352	n = HIGHLIGHT_BG;
2353	break;
2354    case HIGHLIGHT_BG:
2355	n = HIGHLIGHT_FG;
2356	break;
2357#endif
2358    default:
2359	break;
2360    }
2361    return n;
2362}
2363
2364static void
2365ReportColorRequest(XtermWidget xw, int ndx, int final)
2366{
2367    if (AllowColorOps(xw, ecGetColor)) {
2368	XColor color;
2369	Colormap cmap = xw->core.colormap;
2370	char buffer[80];
2371
2372	/*
2373	 * ChangeColorsRequest() has "always" chosen the opposite color when
2374	 * reverse-video is set.  Report this as the original color index, but
2375	 * reporting the opposite color which would be used.
2376	 */
2377	int i = (xw->misc.re_verse) ? oppositeColor(ndx) : ndx;
2378
2379	GetOldColors(xw);
2380	color.pixel = pOldColors->colors[ndx];
2381	XQueryColor(TScreenOf(xw)->display, cmap, &color);
2382	sprintf(buffer, "%d;rgb:%04x/%04x/%04x", i + 10,
2383		color.red,
2384		color.green,
2385		color.blue);
2386	TRACE(("ReportColors %d: %#lx as %s\n",
2387	       ndx, pOldColors->colors[ndx], buffer));
2388	unparseputc1(xw, ANSI_OSC);
2389	unparseputs(xw, buffer);
2390	unparseputc1(xw, final);
2391	unparse_end(xw);
2392    }
2393}
2394
2395static Bool
2396UpdateOldColors(XtermWidget xw GCC_UNUSED, ScrnColors * pNew)
2397{
2398    int i;
2399
2400    /* if we were going to free old colors, this would be the place to
2401     * do it.   I've decided not to (for now), because it seems likely
2402     * that we'd have a small set of colors we use over and over, and that
2403     * we could save some overhead this way.   The only case in which this
2404     * (clearly) fails is if someone is trying a boatload of colors, in
2405     * which case they can restart xterm
2406     */
2407    for (i = 0; i < NCOLORS; i++) {
2408	if (COLOR_DEFINED(pNew, i)) {
2409	    if (pOldColors->names[i] != NULL) {
2410		XtFree(pOldColors->names[i]);
2411		pOldColors->names[i] = NULL;
2412	    }
2413	    if (pNew->names[i]) {
2414		pOldColors->names[i] = pNew->names[i];
2415	    }
2416	    pOldColors->colors[i] = pNew->colors[i];
2417	}
2418    }
2419    return (True);
2420}
2421
2422/*
2423 * OSC codes are constant, but the indices for the color arrays depend on how
2424 * xterm is compiled.
2425 */
2426static int
2427OscToColorIndex(OscTextColors mode)
2428{
2429    int result = 0;
2430
2431#define CASE(name) case OSC_##name: result = name; break
2432    switch (mode) {
2433	CASE(TEXT_FG);
2434	CASE(TEXT_BG);
2435	CASE(TEXT_CURSOR);
2436	CASE(MOUSE_FG);
2437	CASE(MOUSE_BG);
2438#if OPT_TEK4014
2439	CASE(TEK_FG);
2440	CASE(TEK_BG);
2441#endif
2442#if OPT_HIGHLIGHT_COLOR
2443	CASE(HIGHLIGHT_BG);
2444	CASE(HIGHLIGHT_FG);
2445#endif
2446#if OPT_TEK4014
2447	CASE(TEK_CURSOR);
2448#endif
2449    case OSC_NCOLORS:
2450	break;
2451    }
2452    return result;
2453}
2454
2455static Bool
2456ChangeColorsRequest(XtermWidget xw,
2457		    int start,
2458		    char *names,
2459		    int final)
2460{
2461    Bool result = False;
2462    char *thisName;
2463    ScrnColors newColors;
2464    int i, ndx;
2465
2466    TRACE(("ChangeColorsRequest start=%d, names='%s'\n", start, names));
2467
2468    if (GetOldColors(xw)) {
2469	newColors.which = 0;
2470	for (i = 0; i < NCOLORS; i++) {
2471	    newColors.names[i] = NULL;
2472	}
2473	for (i = start; i < OSC_NCOLORS; i++) {
2474	    ndx = OscToColorIndex((OscTextColors) i);
2475	    if (xw->misc.re_verse)
2476		ndx = oppositeColor(ndx);
2477
2478	    if (IsEmpty(names)) {
2479		newColors.names[ndx] = NULL;
2480	    } else {
2481		if (names[0] == ';')
2482		    thisName = NULL;
2483		else
2484		    thisName = names;
2485		names = strchr(names, ';');
2486		if (names != NULL) {
2487		    *names++ = '\0';
2488		}
2489		if (thisName != 0 && !strcmp(thisName, "?")) {
2490		    ReportColorRequest(xw, ndx, final);
2491		} else if (!pOldColors->names[ndx]
2492			   || (thisName
2493			       && strcmp(thisName, pOldColors->names[ndx]))) {
2494		    AllocateTermColor(xw, &newColors, ndx, thisName, False);
2495		}
2496	    }
2497	}
2498
2499	if (newColors.which != 0) {
2500	    ChangeColors(xw, &newColors);
2501	    UpdateOldColors(xw, &newColors);
2502	}
2503	result = True;
2504    }
2505    return result;
2506}
2507
2508static Bool
2509ResetColorsRequest(XtermWidget xw,
2510		   int code)
2511{
2512    Bool result = False;
2513    const char *thisName;
2514    ScrnColors newColors;
2515    int ndx;
2516
2517    TRACE(("ResetColorsRequest code=%d\n", code));
2518
2519#if OPT_COLOR_RES
2520    if (GetOldColors(xw)) {
2521	ndx = OscToColorIndex((OscTextColors) (code - OSC_RESET));
2522	if (xw->misc.re_verse)
2523	    ndx = oppositeColor(ndx);
2524
2525	thisName = xw->screen.Tcolors[ndx].resource;
2526
2527	newColors.which = 0;
2528	newColors.names[ndx] = NULL;
2529
2530	if (thisName != 0
2531	    && pOldColors->names[ndx] != 0
2532	    && strcmp(thisName, pOldColors->names[ndx])) {
2533	    AllocateTermColor(xw, &newColors, ndx, thisName, False);
2534
2535	    if (newColors.which != 0) {
2536		ChangeColors(xw, &newColors);
2537		UpdateOldColors(xw, &newColors);
2538	    }
2539	}
2540	result = True;
2541    }
2542#endif
2543    return result;
2544}
2545
2546#if OPT_SHIFT_FONTS
2547/*
2548 * Initially, 'source' points to '#' or '?'.
2549 *
2550 * Look for an optional sign and optional number.  If those are found, lookup
2551 * the corresponding menu font entry.
2552 */
2553static int
2554ParseShiftedFont(XtermWidget xw, char *source, char **target)
2555{
2556    TScreen *screen = TScreenOf(xw);
2557    int num = screen->menu_font_number;
2558    int rel = 0;
2559
2560    if (*++source == '+') {
2561	rel = 1;
2562	source++;
2563    } else if (*source == '-') {
2564	rel = -1;
2565	source++;
2566    }
2567
2568    if (isdigit(CharOf(*source))) {
2569	int val = atoi(source);
2570	if (rel > 0)
2571	    rel = val;
2572	else if (rel < 0)
2573	    rel = -val;
2574	else
2575	    num = val;
2576    }
2577
2578    if (rel != 0) {
2579	num = lookupRelativeFontSize(xw,
2580				     screen->menu_font_number, rel);
2581
2582    }
2583    TRACE(("ParseShiftedFont(%s) ->%d (%s)\n", *target, num, source));
2584    *target = source;
2585    return num;
2586}
2587
2588static void
2589QueryFontRequest(XtermWidget xw, char *buf, int final)
2590{
2591    if (AllowFontOps(xw, efGetFont)) {
2592	TScreen *screen = TScreenOf(xw);
2593	Bool success = True;
2594	int num;
2595	char *base = buf + 1;
2596	const char *name = 0;
2597	char temp[10];
2598
2599	num = ParseShiftedFont(xw, buf, &buf);
2600	if (num < 0
2601	    || num > fontMenu_lastBuiltin) {
2602	    Bell(xw, XkbBI_MinorError, 0);
2603	    success = False;
2604	} else {
2605#if OPT_RENDERFONT
2606	    if (UsingRenderFont(xw)) {
2607		name = getFaceName(xw, False);
2608	    } else
2609#endif
2610	    if ((name = screen->MenuFontName(num)) == 0) {
2611		success = False;
2612	    }
2613	}
2614
2615	unparseputc1(xw, ANSI_OSC);
2616	unparseputs(xw, "50");
2617
2618	if (success) {
2619	    unparseputc(xw, ';');
2620	    if (buf >= base) {
2621		/* identify the font-entry, unless it is the current one */
2622		if (*buf != '\0') {
2623		    unparseputc(xw, '#');
2624		    sprintf(temp, "%d", num);
2625		    unparseputs(xw, temp);
2626		    if (*name != '\0')
2627			unparseputc(xw, ' ');
2628		}
2629	    }
2630	    unparseputs(xw, name);
2631	}
2632
2633	unparseputc1(xw, final);
2634	unparse_end(xw);
2635    }
2636}
2637
2638static void
2639ChangeFontRequest(XtermWidget xw, char *buf)
2640{
2641    if (AllowFontOps(xw, efSetFont)) {
2642	TScreen *screen = TScreenOf(xw);
2643	Bool success = True;
2644	int num;
2645	VTFontNames fonts;
2646	char *name;
2647
2648	/*
2649	 * If the font specification is a "#", followed by an optional sign and
2650	 * optional number, lookup the corresponding menu font entry.
2651	 *
2652	 * Further, if the "#", etc., is followed by a font name, use that
2653	 * to load the font entry.
2654	 */
2655	if (*buf == '#') {
2656	    num = ParseShiftedFont(xw, buf, &buf);
2657
2658	    if (num < 0
2659		|| num > fontMenu_lastBuiltin) {
2660		Bell(xw, XkbBI_MinorError, 0);
2661		success = False;
2662	    } else {
2663		/*
2664		 * Skip past the optional number, and any whitespace to look
2665		 * for a font specification within the control.
2666		 */
2667		while (isdigit(CharOf(*buf))) {
2668		    ++buf;
2669		}
2670		while (isspace(CharOf(*buf))) {
2671		    ++buf;
2672		}
2673#if OPT_RENDERFONT
2674		if (UsingRenderFont(xw)) {
2675		    ;		/* there is only one font entry to load */
2676		} else
2677#endif
2678		{
2679		    /*
2680		     * Normally there is no font specified in the control.
2681		     * But if there is, simply overwrite the font entry.
2682		     */
2683		    if (*buf == '\0') {
2684			if ((buf = screen->MenuFontName(num)) == 0) {
2685			    success = False;
2686			}
2687		    }
2688		}
2689	    }
2690	} else {
2691	    num = screen->menu_font_number;
2692	}
2693	name = x_strtrim(buf);
2694	if (success && !IsEmpty(name)) {
2695#if OPT_RENDERFONT
2696	    if (UsingRenderFont(xw)) {
2697		setFaceName(xw, name);
2698		xtermUpdateFontInfo(xw, True);
2699	    } else
2700#endif
2701	    {
2702		memset(&fonts, 0, sizeof(fonts));
2703		fonts.f_n = name;
2704		SetVTFont(xw, num, True, &fonts);
2705	    }
2706	} else {
2707	    Bell(xw, XkbBI_MinorError, 0);
2708	}
2709	free(name);
2710    }
2711}
2712#endif /* OPT_SHIFT_FONTS */
2713
2714/***====================================================================***/
2715
2716void
2717do_osc(XtermWidget xw, Char * oscbuf, size_t len, int final)
2718{
2719    TScreen *screen = TScreenOf(xw);
2720    int mode;
2721    Char *cp;
2722    int state = 0;
2723    char *buf = 0;
2724    char temp[2];
2725#if OPT_ISO_COLORS
2726    int ansi_colors = 0;
2727#endif
2728    Bool need_data = True;
2729
2730    TRACE(("do_osc %s\n", oscbuf));
2731
2732    /*
2733     * Lines should be of the form <OSC> number ; string <ST>, however
2734     * older xterms can accept <BEL> as a final character.  We will respond
2735     * with the same final character as the application sends to make this
2736     * work better with shell scripts, which may have trouble reading an
2737     * <ESC><backslash>, which is the 7-bit equivalent to <ST>.
2738     */
2739    mode = 0;
2740    for (cp = oscbuf; *cp != '\0'; cp++) {
2741	switch (state) {
2742	case 0:
2743	    if (isdigit(*cp)) {
2744		mode = 10 * mode + (*cp - '0');
2745		if (mode > 65535) {
2746		    TRACE(("do_osc found unknown mode %d\n", mode));
2747		    return;
2748		}
2749		break;
2750	    }
2751	    /* FALLTHRU */
2752	case 1:
2753	    if (*cp != ';') {
2754		TRACE(("do_osc did not find semicolon offset %d\n",
2755		       (int) (cp - oscbuf)));
2756		return;
2757	    }
2758	    state = 2;
2759	    break;
2760	case 2:
2761	    buf = (char *) cp;
2762	    state = 3;
2763	    /* FALLTHRU */
2764	default:
2765	    if (!xtermIsPrintable(xw, &cp, oscbuf + len)) {
2766		switch (mode) {
2767		case 0:
2768		case 1:
2769		case 2:
2770		    break;
2771		default:
2772		    TRACE(("do_osc found nonprinting char %02X offset %d\n",
2773			   CharOf(*cp),
2774			   (int) (cp - oscbuf)));
2775		    return;
2776		}
2777	    }
2778	}
2779    }
2780
2781    /*
2782     * Most OSC controls other than resets require data.  Handle the others as
2783     * a special case.
2784     */
2785    switch (mode) {
2786#if OPT_ISO_COLORS
2787    case OSC_Reset(4):
2788    case OSC_Reset(5):
2789    case OSC_Reset(OSC_TEXT_FG):
2790    case OSC_Reset(OSC_TEXT_BG):
2791    case OSC_Reset(OSC_TEXT_CURSOR):
2792    case OSC_Reset(OSC_MOUSE_FG):
2793    case OSC_Reset(OSC_MOUSE_BG):
2794#if OPT_HIGHLIGHT_COLOR
2795    case OSC_Reset(OSC_HIGHLIGHT_BG):
2796    case OSC_Reset(OSC_HIGHLIGHT_FG):
2797#endif
2798#if OPT_TEK4014
2799    case OSC_Reset(OSC_TEK_FG):
2800    case OSC_Reset(OSC_TEK_BG):
2801    case OSC_Reset(OSC_TEK_CURSOR):
2802#endif
2803	need_data = False;
2804	break;
2805#endif
2806    default:
2807	break;
2808    }
2809
2810    /*
2811     * Check if we have data when we want, and not when we do not want it.
2812     * Either way, that is a malformed control sequence, and will be ignored.
2813     */
2814    if (IsEmpty(buf)) {
2815	if (need_data) {
2816	    TRACE(("do_osc found no data\n"));
2817	    return;
2818	}
2819	temp[0] = '\0';
2820	buf = temp;
2821    } else if (!need_data) {
2822	TRACE(("do_osc found found unwanted data\n"));
2823	return;
2824    }
2825
2826    switch (mode) {
2827    case 0:			/* new icon name and title */
2828	ChangeIconName(xw, buf);
2829	ChangeTitle(xw, buf);
2830	break;
2831
2832    case 1:			/* new icon name only */
2833	ChangeIconName(xw, buf);
2834	break;
2835
2836    case 2:			/* new title only */
2837	ChangeTitle(xw, buf);
2838	break;
2839
2840#ifdef notdef
2841    case 3:			/* change X property */
2842	if (AllowWindowOps(xw, ewSetXprop))
2843	    ChangeXprop(buf);
2844	break;
2845#endif
2846#if OPT_ISO_COLORS
2847    case 5:
2848	ansi_colors = NUM_ANSI_COLORS;
2849	/* FALLTHRU */
2850    case 4:
2851	if (ChangeAnsiColorRequest(xw, buf, ansi_colors, final))
2852	    xtermRepaint(xw);
2853	break;
2854    case OSC_Reset(5):
2855	ansi_colors = NUM_ANSI_COLORS;
2856	/* FALLTHRU */
2857    case OSC_Reset(4):
2858	if (ResetAnsiColorRequest(xw, buf, ansi_colors))
2859	    xtermRepaint(xw);
2860	break;
2861#endif
2862    case OSC_TEXT_FG:
2863    case OSC_TEXT_BG:
2864    case OSC_TEXT_CURSOR:
2865    case OSC_MOUSE_FG:
2866    case OSC_MOUSE_BG:
2867#if OPT_HIGHLIGHT_COLOR
2868    case OSC_HIGHLIGHT_BG:
2869    case OSC_HIGHLIGHT_FG:
2870#endif
2871#if OPT_TEK4014
2872    case OSC_TEK_FG:
2873    case OSC_TEK_BG:
2874    case OSC_TEK_CURSOR:
2875#endif
2876	if (xw->misc.dynamicColors) {
2877	    ChangeColorsRequest(xw, mode, buf, final);
2878	}
2879	break;
2880    case OSC_Reset(OSC_TEXT_FG):
2881    case OSC_Reset(OSC_TEXT_BG):
2882    case OSC_Reset(OSC_TEXT_CURSOR):
2883    case OSC_Reset(OSC_MOUSE_FG):
2884    case OSC_Reset(OSC_MOUSE_BG):
2885#if OPT_HIGHLIGHT_COLOR
2886    case OSC_Reset(OSC_HIGHLIGHT_BG):
2887    case OSC_Reset(OSC_HIGHLIGHT_FG):
2888#endif
2889#if OPT_TEK4014
2890    case OSC_Reset(OSC_TEK_FG):
2891    case OSC_Reset(OSC_TEK_BG):
2892    case OSC_Reset(OSC_TEK_CURSOR):
2893#endif
2894	if (xw->misc.dynamicColors) {
2895	    ResetColorsRequest(xw, mode);
2896	}
2897	break;
2898
2899    case 30:
2900    case 31:
2901	/* reserved for Konsole (Stephan Binner <Stephan.Binner@gmx.de>) */
2902	break;
2903
2904#ifdef ALLOWLOGGING
2905    case 46:			/* new log file */
2906#ifdef ALLOWLOGFILECHANGES
2907	/*
2908	 * Warning, enabling this feature allows people to overwrite
2909	 * arbitrary files accessible to the person running xterm.
2910	 */
2911	if (strcmp(buf, "?")
2912	    && (cp = CastMallocN(char, strlen(buf)) != NULL)) {
2913	    strcpy(cp, buf);
2914	    if (screen->logfile)
2915		free(screen->logfile);
2916	    screen->logfile = cp;
2917	    break;
2918	}
2919#endif
2920	Bell(xw, XkbBI_Info, 0);
2921	Bell(xw, XkbBI_Info, 0);
2922	break;
2923#endif /* ALLOWLOGGING */
2924
2925    case 50:
2926#if OPT_SHIFT_FONTS
2927	if (*buf == '?') {
2928	    QueryFontRequest(xw, buf, final);
2929	} else if (xw->misc.shift_fonts) {
2930	    ChangeFontRequest(xw, buf);
2931	}
2932#endif /* OPT_SHIFT_FONTS */
2933	break;
2934    case 51:
2935	/* reserved for Emacs shell (Rob Mayoff <mayoff@dqd.com>) */
2936	break;
2937
2938#if OPT_PASTE64
2939    case 52:
2940	ManipulateSelectionData(xw, screen, buf, final);
2941	break;
2942#endif
2943	/*
2944	 * One could write code to send back the display and host names,
2945	 * but that could potentially open a fairly nasty security hole.
2946	 */
2947    default:
2948	TRACE(("do_osc - unrecognized code\n"));
2949	break;
2950    }
2951    unparse_end(xw);
2952}
2953
2954#ifdef SunXK_F36
2955#define MAX_UDK 37
2956#else
2957#define MAX_UDK 35
2958#endif
2959static struct {
2960    char *str;
2961    int len;
2962} user_keys[MAX_UDK];
2963
2964/*
2965 * Parse one nibble of a hex byte from the OSC string.  We have removed the
2966 * string-terminator (replacing it with a null), so the only other delimiter
2967 * that is expected is semicolon.  Ignore other characters (Ray Neuman says
2968 * "real" terminals accept commas in the string definitions).
2969 */
2970static int
2971udk_value(const char **cp)
2972{
2973    int result = -1;
2974    int c;
2975
2976    for (;;) {
2977	if ((c = **cp) != '\0')
2978	    *cp = *cp + 1;
2979	if (c == ';' || c == '\0')
2980	    break;
2981	if ((result = x_hex2int(c)) >= 0)
2982	    break;
2983    }
2984
2985    return result;
2986}
2987
2988void
2989reset_decudk(void)
2990{
2991    int n;
2992    for (n = 0; n < MAX_UDK; n++) {
2993	if (user_keys[n].str != 0) {
2994	    free(user_keys[n].str);
2995	    user_keys[n].str = 0;
2996	    user_keys[n].len = 0;
2997	}
2998    }
2999}
3000
3001/*
3002 * Parse the data for DECUDK (user-defined keys).
3003 */
3004static void
3005parse_decudk(const char *cp)
3006{
3007    while (*cp) {
3008	const char *base = cp;
3009	char *str = CastMallocN(char, strlen(cp) + 1);
3010	unsigned key = 0;
3011	int lo, hi;
3012	int len = 0;
3013
3014	while (isdigit(CharOf(*cp)))
3015	    key = (key * 10) + (unsigned) (*cp++ - '0');
3016	if (*cp == '/') {
3017	    cp++;
3018	    while ((hi = udk_value(&cp)) >= 0
3019		   && (lo = udk_value(&cp)) >= 0) {
3020		str[len++] = (char) ((hi << 4) | lo);
3021	    }
3022	}
3023	if (len > 0 && key < MAX_UDK) {
3024	    if (user_keys[key].str != 0)
3025		free(user_keys[key].str);
3026	    user_keys[key].str = str;
3027	    user_keys[key].len = len;
3028	} else {
3029	    free(str);
3030	}
3031	if (*cp == ';')
3032	    cp++;
3033	if (cp == base)		/* badly-formed sequence - bail out */
3034	    break;
3035    }
3036}
3037
3038#if OPT_TRACE
3039#define SOFT_WIDE 10
3040#define SOFT_HIGH 20
3041
3042static void
3043parse_decdld(ANSI * params, const char *string)
3044{
3045    char DscsName[8];
3046    int len;
3047    int Pfn = params->a_param[0];
3048    int Pcn = params->a_param[1];
3049    int Pe = params->a_param[2];
3050    int Pcmw = params->a_param[3];
3051    int Pw = params->a_param[4];
3052    int Pt = params->a_param[5];
3053    int Pcmh = params->a_param[6];
3054    int Pcss = params->a_param[7];
3055
3056    int start_char = Pcn + 0x20;
3057    int char_wide = ((Pcmw == 0)
3058		     ? (Pcss ? 6 : 10)
3059		     : (Pcmw > 4
3060			? Pcmw
3061			: (Pcmw + 3)));
3062    int char_high = ((Pcmh == 0)
3063		     ? ((Pcmw >= 2 || Pcmw <= 4)
3064			? 10
3065			: 20)
3066		     : Pcmh);
3067    Char ch;
3068    Char bits[SOFT_HIGH][SOFT_WIDE];
3069    Bool first = True;
3070    Bool prior = False;
3071    int row = 0, col = 0;
3072
3073    TRACE(("Parsing DECDLD\n"));
3074    TRACE(("  font number   %d\n", Pfn));
3075    TRACE(("  starting char %d\n", Pcn));
3076    TRACE(("  erase control %d\n", Pe));
3077    TRACE(("  char-width    %d\n", Pcmw));
3078    TRACE(("  font-width    %d\n", Pw));
3079    TRACE(("  text/full     %d\n", Pt));
3080    TRACE(("  char-height   %d\n", Pcmh));
3081    TRACE(("  charset-size  %d\n", Pcss));
3082
3083    if (Pfn > 1
3084	|| Pcn > 95
3085	|| Pe > 2
3086	|| Pcmw > 10
3087	|| Pcmw == 1
3088	|| Pt > 2
3089	|| Pcmh > 20
3090	|| Pcss > 1
3091	|| char_wide > SOFT_WIDE
3092	|| char_high > SOFT_HIGH) {
3093	TRACE(("DECDLD illegal parameter\n"));
3094	return;
3095    }
3096
3097    len = 0;
3098    while (*string != '\0') {
3099	ch = CharOf(*string++);
3100	if (ch >= ANSI_SPA && ch <= 0x2f) {
3101	    if (len < 2)
3102		DscsName[len++] = (char) ch;
3103	} else if (ch >= 0x30 && ch <= 0x7e) {
3104	    DscsName[len++] = (char) ch;
3105	    break;
3106	}
3107    }
3108    DscsName[len] = 0;
3109    TRACE(("  Dscs name     '%s'\n", DscsName));
3110
3111    TRACE(("  character matrix %dx%d\n", char_high, char_wide));
3112    while (*string != '\0') {
3113	if (first) {
3114	    TRACE(("Char %d:\n", start_char));
3115	    if (prior) {
3116		for (row = 0; row < char_high; ++row) {
3117		    TRACE(("%.*s\n", char_wide, bits[row]));
3118		}
3119	    }
3120	    prior = False;
3121	    first = False;
3122	    for (row = 0; row < char_high; ++row) {
3123		for (col = 0; col < char_wide; ++col) {
3124		    bits[row][col] = '.';
3125		}
3126	    }
3127	    row = col = 0;
3128	}
3129	ch = CharOf(*string++);
3130	if (ch >= 0x3f && ch <= 0x7e) {
3131	    int n;
3132
3133	    ch = CharOf(ch - 0x3f);
3134	    for (n = 0; n < 6; ++n) {
3135		bits[row + n][col] = CharOf((ch & (1 << n)) ? '*' : '.');
3136	    }
3137	    col += 1;
3138	    prior = True;
3139	} else if (ch == '/') {
3140	    row += 6;
3141	    col = 0;
3142	} else if (ch == ';') {
3143	    first = True;
3144	    ++start_char;
3145	}
3146    }
3147}
3148#else
3149#define parse_decdld(p,q)	/* nothing */
3150#endif
3151
3152/*
3153 * Parse numeric parameters.  Normally we use a state machine to simplify
3154 * interspersing with control characters, but have the string already.
3155 */
3156static void
3157parse_ansi_params(ANSI * params, const char **string)
3158{
3159    const char *cp = *string;
3160    ParmType nparam = 0;
3161
3162    memset(params, 0, sizeof(*params));
3163    while (*cp != '\0') {
3164	Char ch = CharOf(*cp++);
3165
3166	if (isdigit(ch)) {
3167	    if (nparam < NPARAM) {
3168		params->a_param[nparam] =
3169		    (ParmType) ((params->a_param[nparam] * 10)
3170				+ (ch - '0'));
3171	    }
3172	} else if (ch == ';') {
3173	    if (++nparam < NPARAM)
3174		params->a_nparam = nparam;
3175	} else if (ch < 32) {
3176	    ;
3177	} else {
3178	    /* should be 0x30 to 0x7e */
3179	    params->a_final = ch;
3180	    break;
3181	}
3182    }
3183    *string = cp;
3184}
3185
3186void
3187do_dcs(XtermWidget xw, Char * dcsbuf, size_t dcslen)
3188{
3189    TScreen *screen = TScreenOf(xw);
3190    char reply[BUFSIZ];
3191    const char *cp = (const char *) dcsbuf;
3192    Bool okay;
3193    ANSI params;
3194
3195    TRACE(("do_dcs(%s:%lu)\n", (char *) dcsbuf, (unsigned long) dcslen));
3196
3197    if (dcslen != strlen(cp))
3198	/* shouldn't have nulls in the string */
3199	return;
3200
3201    switch (*cp) {		/* intermediate character, or parameter */
3202    case '$':			/* DECRQSS */
3203	okay = True;
3204
3205	cp++;
3206	if (*cp++ == 'q') {
3207	    if (!strcmp(cp, "\"q")) {	/* DECSCA */
3208		sprintf(reply, "%d%s",
3209			(screen->protected_mode == DEC_PROTECT)
3210			&& (xw->flags & PROTECTED) ? 1 : 0,
3211			cp);
3212	    } else if (!strcmp(cp, "\"p")) {	/* DECSCL */
3213		sprintf(reply, "%d%s%s",
3214			(screen->vtXX_level ?
3215			 screen->vtXX_level : 1) + 60,
3216			(screen->vtXX_level >= 2)
3217			? (screen->control_eight_bits
3218			   ? ";0" : ";1")
3219			: "",
3220			cp);
3221	    } else if (!strcmp(cp, "r")) {	/* DECSTBM */
3222		sprintf(reply, "%d;%dr",
3223			screen->top_marg + 1,
3224			screen->bot_marg + 1);
3225	    } else if (!strcmp(cp, "m")) {	/* SGR */
3226		strcpy(reply, "0");
3227		if (xw->flags & BOLD)
3228		    strcat(reply, ";1");
3229		if (xw->flags & UNDERLINE)
3230		    strcat(reply, ";4");
3231		if (xw->flags & BLINK)
3232		    strcat(reply, ";5");
3233		if (xw->flags & INVERSE)
3234		    strcat(reply, ";7");
3235		if (xw->flags & INVISIBLE)
3236		    strcat(reply, ";8");
3237#if OPT_256_COLORS || OPT_88_COLORS
3238		if_OPT_ISO_COLORS(screen, {
3239		    if (xw->flags & FG_COLOR) {
3240			if (xw->cur_foreground >= 16)
3241			    sprintf(reply + strlen(reply),
3242				    ";38;5;%d", xw->cur_foreground);
3243			else
3244			    sprintf(reply + strlen(reply),
3245				    ";%d%d",
3246				    xw->cur_foreground >= 8 ? 9 : 3,
3247				    xw->cur_foreground >= 8 ?
3248				    xw->cur_foreground - 8 :
3249				    xw->cur_foreground);
3250		    }
3251		    if (xw->flags & BG_COLOR) {
3252			if (xw->cur_background >= 16)
3253			    sprintf(reply + strlen(reply),
3254				    ";48;5;%d", xw->cur_foreground);
3255			else
3256			    sprintf(reply + strlen(reply),
3257				    ";%d%d",
3258				    xw->cur_background >= 8 ? 10 : 4,
3259				    xw->cur_background >= 8 ?
3260				    xw->cur_background - 8 :
3261				    xw->cur_background);
3262		    }
3263		});
3264#elif OPT_ISO_COLORS
3265		if_OPT_ISO_COLORS(screen, {
3266		    if (xw->flags & FG_COLOR)
3267			sprintf(reply + strlen(reply),
3268				";%d%d",
3269				xw->cur_foreground >= 8 ? 9 : 3,
3270				xw->cur_foreground >= 8 ?
3271				xw->cur_foreground - 8 :
3272				xw->cur_foreground);
3273		    if (xw->flags & BG_COLOR)
3274			sprintf(reply + strlen(reply),
3275				";%d%d",
3276				xw->cur_background >= 8 ? 10 : 4,
3277				xw->cur_background >= 8 ?
3278				xw->cur_background - 8 :
3279				xw->cur_background);
3280		});
3281#endif
3282		strcat(reply, "m");
3283	    } else
3284		okay = False;
3285
3286	    if (okay) {
3287		unparseputc1(xw, ANSI_DCS);
3288		unparseputc(xw, okay ? '1' : '0');
3289		unparseputc(xw, '$');
3290		unparseputc(xw, 'r');
3291		cp = reply;
3292		unparseputs(xw, cp);
3293		unparseputc1(xw, ANSI_ST);
3294	    } else {
3295		unparseputc(xw, ANSI_CAN);
3296	    }
3297	} else {
3298	    unparseputc(xw, ANSI_CAN);
3299	}
3300	break;
3301#if OPT_TCAP_QUERY
3302    case '+':
3303	cp++;
3304	switch (*cp) {
3305	case 'p':
3306	    if (AllowTcapOps(xw, etSetTcap)) {
3307		set_termcap(xw, cp + 1);
3308	    }
3309	    break;
3310	case 'q':
3311	    if (AllowTcapOps(xw, etGetTcap)) {
3312		Bool fkey;
3313		unsigned state;
3314		int code;
3315		const char *tmp;
3316		const char *parsed = ++cp;
3317
3318		code = xtermcapKeycode(xw, &parsed, &state, &fkey);
3319
3320		unparseputc1(xw, ANSI_DCS);
3321
3322		unparseputc(xw, code >= 0 ? '1' : '0');
3323
3324		unparseputc(xw, '+');
3325		unparseputc(xw, 'r');
3326
3327		while (*cp != 0 && (code >= -1)) {
3328		    if (cp == parsed)
3329			break;	/* no data found, error */
3330
3331		    for (tmp = cp; tmp != parsed; ++tmp)
3332			unparseputc(xw, *tmp);
3333
3334		    if (code >= 0) {
3335			unparseputc(xw, '=');
3336			screen->tc_query_code = code;
3337			screen->tc_query_fkey = fkey;
3338#if OPT_ISO_COLORS
3339			/* XK_COLORS is a fake code for the "Co" entry (maximum
3340			 * number of colors) */
3341			if (code == XK_COLORS) {
3342			    unparseputn(xw, NUM_ANSI_COLORS);
3343			} else
3344#endif
3345			if (code == XK_TCAPNAME) {
3346			    unparseputs(xw, xterm_name);
3347			} else {
3348			    XKeyEvent event;
3349			    event.state = state;
3350			    Input(xw, &event, False);
3351			}
3352			screen->tc_query_code = -1;
3353		    } else {
3354			break;	/* no match found, error */
3355		    }
3356
3357		    cp = parsed;
3358		    if (*parsed == ';') {
3359			unparseputc(xw, *parsed++);
3360			cp = parsed;
3361			code = xtermcapKeycode(xw, &parsed, &state, &fkey);
3362		    }
3363		}
3364		unparseputc1(xw, ANSI_ST);
3365	    }
3366	    break;
3367	}
3368	break;
3369#endif
3370    default:
3371	if (screen->terminal_id >= 200) {	/* VT220 */
3372	    parse_ansi_params(&params, &cp);
3373	    switch (params.a_final) {
3374	    case '|':		/* DECUDK */
3375		if (params.a_param[0] == 0)
3376		    reset_decudk();
3377		parse_decudk(cp);
3378		break;
3379	    case '{':		/* DECDLD (no '}' case though) */
3380		parse_decdld(&params, cp);
3381		break;
3382	    }
3383	}
3384	break;
3385    }
3386    unparse_end(xw);
3387}
3388
3389char *
3390udk_lookup(int keycode, int *len)
3391{
3392    if (keycode >= 0 && keycode < MAX_UDK) {
3393	*len = user_keys[keycode].len;
3394	return user_keys[keycode].str;
3395    }
3396    return 0;
3397}
3398
3399static void
3400ChangeGroup(XtermWidget xw, const char *attribute, char *value)
3401{
3402#if OPT_WIDE_CHARS
3403    static Char *converted;	/* NO_LEAKS */
3404#endif
3405    static char empty[1];
3406
3407    Arg args[1];
3408    Boolean changed = True;
3409    Widget w = CURRENT_EMU();
3410    Widget top = SHELL_OF(w);
3411
3412    char *my_attr;
3413    char *name;
3414    size_t limit;
3415    Char *c1;
3416    Char *cp;
3417
3418    if (!AllowTitleOps(xw))
3419	return;
3420
3421    if (value == 0)
3422	value = empty;
3423    if (IsTitleMode(xw, tmSetBase16)) {
3424	const char *temp;
3425	char *test;
3426
3427	value = x_decode_hex(value, &temp);
3428	if (*temp != '\0')
3429	    return;
3430	for (test = value; *test != '\0'; ++test) {
3431	    if (CharOf(*test) < 32) {
3432		*test = '\0';
3433		break;
3434	    }
3435	}
3436    }
3437
3438    c1 = (Char *) value;
3439    name = value;
3440    limit = strlen(name);
3441    my_attr = x_strdup(attribute);
3442
3443    TRACE(("ChangeGroup(attribute=%s, value=%s)\n", my_attr, name));
3444
3445    /*
3446     * Ignore titles that are too long to be plausible requests.
3447     */
3448    if (limit > 0 && limit < 1024) {
3449
3450	/*
3451	 * After all decoding, overwrite nonprintable characters with '?'.
3452	 */
3453	for (cp = c1; *cp != 0; ++cp) {
3454	    Char *c2 = cp;
3455	    if (!xtermIsPrintable(xw, &cp, c1 + limit)) {
3456		memset(c2, '?', (size_t) (cp + 1 - c2));
3457	    }
3458	}
3459
3460#if OPT_WIDE_CHARS
3461	/*
3462	 * If we're running in UTF-8 mode, and have not been told that the
3463	 * title string is in UTF-8, it is likely that non-ASCII text in the
3464	 * string will be rejected because it is not printable in the current
3465	 * locale.  So we convert it to UTF-8, allowing the X library to
3466	 * convert it back.
3467	 */
3468	if (xtermEnvUTF8() && !IsSetUtf8Title(xw)) {
3469	    int n;
3470
3471	    for (n = 0; name[n] != '\0'; ++n) {
3472		if (CharOf(name[n]) > 127) {
3473		    if (converted != 0)
3474			free(converted);
3475		    if ((converted = TypeMallocN(Char, 1 + (6 * limit))) != 0) {
3476			Char *temp = converted;
3477			while (*name != 0) {
3478			    temp = convertToUTF8(temp, CharOf(*name));
3479			    ++name;
3480			}
3481			*temp = 0;
3482			name = (char *) converted;
3483			TRACE(("...converted{%s}\n", name));
3484		    }
3485		    break;
3486		}
3487	    }
3488	}
3489#endif
3490
3491#if OPT_SAME_NAME
3492	/* If the attribute isn't going to change, then don't bother... */
3493
3494	if (resource.sameName) {
3495	    char *buf = 0;
3496	    XtSetArg(args[0], my_attr, &buf);
3497	    XtGetValues(top, args, 1);
3498	    TRACE(("...comparing{%s}\n", buf));
3499	    if (buf != 0 && strcmp(name, buf) == 0)
3500		changed = False;
3501	}
3502#endif /* OPT_SAME_NAME */
3503
3504	if (changed) {
3505	    TRACE(("...updating %s\n", my_attr));
3506	    TRACE(("...value is %s\n", name));
3507	    XtSetArg(args[0], my_attr, name);
3508	    XtSetValues(top, args, 1);
3509
3510#if OPT_WIDE_CHARS
3511	    if (xtermEnvUTF8()) {
3512		Display *dpy = XtDisplay(xw);
3513		Atom my_atom;
3514
3515		const char *propname = (!strcmp(my_attr, XtNtitle)
3516					? "_NET_WM_NAME"
3517					: "_NET_WM_ICON_NAME");
3518		if ((my_atom = XInternAtom(dpy, propname, False)) != None) {
3519		    if (IsSetUtf8Title(xw)) {
3520			TRACE(("...updating %s\n", propname));
3521			TRACE(("...value is %s\n", value));
3522			XChangeProperty(dpy, VShellWindow, my_atom,
3523					XA_UTF8_STRING(dpy), 8,
3524					PropModeReplace,
3525					(Char *) value,
3526					(int) strlen(value));
3527		    } else {
3528			TRACE(("...deleting %s\n", propname));
3529			XDeleteProperty(dpy, VShellWindow, my_atom);
3530		    }
3531		}
3532	    }
3533#endif
3534	}
3535
3536	free(my_attr);
3537
3538	if (IsTitleMode(xw, tmSetBase16))
3539	    free(value);
3540
3541    }
3542    return;
3543}
3544
3545void
3546ChangeIconName(XtermWidget xw, char *name)
3547{
3548    if (name == 0) {
3549	static char dummy[] = "";
3550	name = dummy;
3551    }
3552#if OPT_ZICONBEEP		/* If warning should be given then give it */
3553    if (resource.zIconBeep && TScreenOf(xw)->zIconBeep_flagged) {
3554	char *newname = CastMallocN(char, strlen(name) + 4);
3555	if (!newname) {
3556	    fprintf(stderr, "malloc failed in ChangeIconName\n");
3557	    return;
3558	}
3559	strcpy(newname, "*** ");
3560	strcat(newname, name);
3561	ChangeGroup(xw, XtNiconName, newname);
3562	free(newname);
3563    } else
3564#endif /* OPT_ZICONBEEP */
3565	ChangeGroup(xw, XtNiconName, name);
3566}
3567
3568void
3569ChangeTitle(XtermWidget xw, char *name)
3570{
3571    ChangeGroup(xw, XtNtitle, name);
3572}
3573
3574#define Strlen(s) strlen((char *)(s))
3575
3576void
3577ChangeXprop(char *buf)
3578{
3579    Display *dpy = XtDisplay(toplevel);
3580    Window w = XtWindow(toplevel);
3581    XTextProperty text_prop;
3582    Atom aprop;
3583    Char *pchEndPropName = (Char *) strchr(buf, '=');
3584
3585    if (pchEndPropName)
3586	*pchEndPropName = '\0';
3587    aprop = XInternAtom(dpy, buf, False);
3588    if (pchEndPropName == NULL) {
3589	/* no "=value" given, so delete the property */
3590	XDeleteProperty(dpy, w, aprop);
3591    } else {
3592	text_prop.value = pchEndPropName + 1;
3593	text_prop.encoding = XA_STRING;
3594	text_prop.format = 8;
3595	text_prop.nitems = Strlen(text_prop.value);
3596	XSetTextProperty(dpy, w, &text_prop, aprop);
3597    }
3598}
3599
3600/***====================================================================***/
3601
3602/*
3603 * This is part of ReverseVideo().  It reverses the data stored for the old
3604 * "dynamic" colors that might have been retrieved using OSC 10-18.
3605 */
3606void
3607ReverseOldColors(void)
3608{
3609    ScrnColors *pOld = pOldColors;
3610    Pixel tmpPix;
3611    char *tmpName;
3612
3613    if (pOld) {
3614	/* change text cursor, if necesary */
3615	if (pOld->colors[TEXT_CURSOR] == pOld->colors[TEXT_FG]) {
3616	    pOld->colors[TEXT_CURSOR] = pOld->colors[TEXT_BG];
3617	    if (pOld->names[TEXT_CURSOR]) {
3618		XtFree(pOldColors->names[TEXT_CURSOR]);
3619		pOld->names[TEXT_CURSOR] = NULL;
3620	    }
3621	    if (pOld->names[TEXT_BG]) {
3622		if ((tmpName = x_strdup(pOld->names[TEXT_BG])) != 0) {
3623		    pOld->names[TEXT_CURSOR] = tmpName;
3624		}
3625	    }
3626	}
3627
3628	EXCHANGE(pOld->colors[TEXT_FG], pOld->colors[TEXT_BG], tmpPix);
3629	EXCHANGE(pOld->names[TEXT_FG], pOld->names[TEXT_BG], tmpName);
3630
3631	EXCHANGE(pOld->colors[MOUSE_FG], pOld->colors[MOUSE_BG], tmpPix);
3632	EXCHANGE(pOld->names[MOUSE_FG], pOld->names[MOUSE_BG], tmpName);
3633
3634#if OPT_TEK4014
3635	EXCHANGE(pOld->colors[TEK_FG], pOld->colors[TEK_BG], tmpPix);
3636	EXCHANGE(pOld->names[TEK_FG], pOld->names[TEK_BG], tmpName);
3637#endif
3638    }
3639    return;
3640}
3641
3642Bool
3643AllocateTermColor(XtermWidget xw,
3644		  ScrnColors * pNew,
3645		  int ndx,
3646		  const char *name,
3647		  Bool always)
3648{
3649    Bool result = False;
3650
3651    if (always || AllowColorOps(xw, ecSetColor)) {
3652	XColor def;
3653	TScreen *screen = TScreenOf(xw);
3654	Colormap cmap = xw->core.colormap;
3655	char *newName;
3656
3657	if (XParseColor(screen->display, cmap, name, &def)
3658	    && (XAllocColor(screen->display, cmap, &def)
3659		|| find_closest_color(screen->display, cmap, &def))
3660	    && (newName = x_strdup(name)) != 0) {
3661	    if (COLOR_DEFINED(pNew, ndx))
3662		free(pNew->names[ndx]);
3663	    SET_COLOR_VALUE(pNew, ndx, def.pixel);
3664	    SET_COLOR_NAME(pNew, ndx, newName);
3665	    TRACE(("AllocateTermColor #%d: %s (pixel %#lx)\n", ndx, newName, def.pixel));
3666	    result = True;
3667	} else {
3668	    TRACE(("AllocateTermColor #%d: %s (failed)\n", ndx, name));
3669	}
3670    }
3671    return result;
3672}
3673/***====================================================================***/
3674
3675/* ARGSUSED */
3676void
3677Panic(const char *s GCC_UNUSED, int a GCC_UNUSED)
3678{
3679#ifdef DEBUG
3680    if (debug) {
3681	fprintf(stderr, "%s: PANIC!\t", xterm_name);
3682	fprintf(stderr, s, a);
3683	fputs("\r\n", stderr);
3684	fflush(stderr);
3685    }
3686#endif /* DEBUG */
3687}
3688
3689const char *
3690SysErrorMsg(int code)
3691{
3692    static char unknown[] = "unknown error";
3693    char *s = strerror(code);
3694    return s ? s : unknown;
3695}
3696
3697const char *
3698SysReasonMsg(int code)
3699{
3700    /* *INDENT-OFF* */
3701    static const struct {
3702	int code;
3703	const char *name;
3704    } table[] = {
3705	{ ERROR_FIONBIO,	"main:  ioctl() failed on FIONBIO" },
3706	{ ERROR_F_GETFL,	"main: ioctl() failed on F_GETFL" },
3707	{ ERROR_F_SETFL,	"main: ioctl() failed on F_SETFL", },
3708	{ ERROR_OPDEVTTY,	"spawn: open() failed on /dev/tty", },
3709	{ ERROR_TIOCGETP,	"spawn: ioctl() failed on TIOCGETP", },
3710	{ ERROR_PTSNAME,	"spawn: ptsname() failed", },
3711	{ ERROR_OPPTSNAME,	"spawn: open() failed on ptsname", },
3712	{ ERROR_PTEM,		"spawn: ioctl() failed on I_PUSH/\"ptem\"" },
3713	{ ERROR_CONSEM,		"spawn: ioctl() failed on I_PUSH/\"consem\"" },
3714	{ ERROR_LDTERM,		"spawn: ioctl() failed on I_PUSH/\"ldterm\"" },
3715	{ ERROR_TTCOMPAT,	"spawn: ioctl() failed on I_PUSH/\"ttcompat\"" },
3716	{ ERROR_TIOCSETP,	"spawn: ioctl() failed on TIOCSETP" },
3717	{ ERROR_TIOCSETC,	"spawn: ioctl() failed on TIOCSETC" },
3718	{ ERROR_TIOCSETD,	"spawn: ioctl() failed on TIOCSETD" },
3719	{ ERROR_TIOCSLTC,	"spawn: ioctl() failed on TIOCSLTC" },
3720	{ ERROR_TIOCLSET,	"spawn: ioctl() failed on TIOCLSET" },
3721	{ ERROR_INIGROUPS,	"spawn: initgroups() failed" },
3722	{ ERROR_FORK,		"spawn: fork() failed" },
3723	{ ERROR_EXEC,		"spawn: exec() failed" },
3724	{ ERROR_PTYS,		"get_pty: not enough ptys" },
3725	{ ERROR_PTY_EXEC,	"waiting for initial map" },
3726	{ ERROR_SETUID,		"spawn: setuid() failed" },
3727	{ ERROR_INIT,		"spawn: can't initialize window" },
3728	{ ERROR_TIOCKSET,	"spawn: ioctl() failed on TIOCKSET" },
3729	{ ERROR_TIOCKSETC,	"spawn: ioctl() failed on TIOCKSETC" },
3730	{ ERROR_LUMALLOC,	"luit: command-line malloc failed" },
3731	{ ERROR_SELECT,		"in_put: select() failed" },
3732	{ ERROR_VINIT,		"VTInit: can't initialize window" },
3733	{ ERROR_KMMALLOC1,	"HandleKeymapChange: malloc failed" },
3734	{ ERROR_TSELECT,	"Tinput: select() failed" },
3735	{ ERROR_TINIT,		"TekInit: can't initialize window" },
3736	{ ERROR_BMALLOC2,	"SaltTextAway: malloc() failed" },
3737	{ ERROR_LOGEXEC,	"StartLog: exec() failed" },
3738	{ ERROR_XERROR,		"xerror: XError event" },
3739	{ ERROR_XIOERROR,	"xioerror: X I/O error" },
3740	{ ERROR_SCALLOC,	"Alloc: calloc() failed on base" },
3741	{ ERROR_SCALLOC2,	"Alloc: calloc() failed on rows" },
3742	{ ERROR_SAVE_PTR,	"ScrnPointers: malloc/realloc() failed" },
3743	{ ERROR_MMALLOC,	"my_memmove: malloc/realloc failed" },
3744    };
3745    /* *INDENT-ON* */
3746
3747    Cardinal n;
3748    const char *result = "?";
3749
3750    for (n = 0; n < XtNumber(table); ++n) {
3751	if (code == table[n].code) {
3752	    result = table[n].name;
3753	    break;
3754	}
3755    }
3756    return result;
3757}
3758
3759void
3760SysError(int code)
3761{
3762    int oerrno = errno;
3763
3764    fprintf(stderr, "%s: Error %d, errno %d: ", xterm_name, code, oerrno);
3765    fprintf(stderr, "%s\n", SysErrorMsg(oerrno));
3766    fprintf(stderr, "Reason: %s\n", SysReasonMsg(code));
3767
3768    Cleanup(code);
3769}
3770
3771/*
3772 * cleanup by sending SIGHUP to client processes
3773 */
3774void
3775Cleanup(int code)
3776{
3777    static Bool cleaning;
3778    TScreen *screen = TScreenOf(term);
3779
3780    /*
3781     * Process "-hold" and session cleanup only for a normal exit.
3782     */
3783    if (code == 0) {
3784	if (cleaning) {
3785	    hold_screen = 0;
3786	    return;
3787	}
3788
3789	cleaning = True;
3790	need_cleanup = False;
3791
3792	TRACE(("Cleanup %d\n", code));
3793
3794	if (hold_screen) {
3795	    hold_screen = 2;
3796	    while (hold_screen) {
3797		xevents();
3798		Sleep(10);
3799	    }
3800	}
3801#if OPT_SESSION_MGT
3802	if (resource.sessionMgt) {
3803	    XtVaSetValues(toplevel,
3804			  XtNjoinSession, False,
3805			  NULL);
3806	}
3807#endif
3808    }
3809
3810    if (screen->pid > 1) {
3811	(void) kill_process_group(screen->pid, SIGHUP);
3812    }
3813    Exit(code);
3814}
3815
3816#ifndef VMS
3817char *
3818xtermFindShell(char *leaf, Bool warning)
3819{
3820    char *s;
3821    char *d;
3822    char *tmp;
3823    char *result = leaf;
3824
3825    TRACE(("xtermFindShell(%s)\n", leaf));
3826    if (*result != '\0' && strchr("+/-", *result) == 0) {
3827	/* find it in $PATH */
3828	if ((s = x_getenv("PATH")) != 0) {
3829	    if ((tmp = TypeMallocN(char, strlen(leaf) + strlen(s) + 2)) != 0) {
3830		Bool found = False;
3831		while (*s != '\0') {
3832		    strcpy(tmp, s);
3833		    for (d = tmp;; ++d) {
3834			if (*d == ':' || *d == '\0') {
3835			    int skip = (*d != '\0');
3836			    *d = '/';
3837			    strcpy(d + 1, leaf);
3838			    if (skip)
3839				++d;
3840			    s += (d - tmp);
3841			    if (*tmp == '/'
3842				&& strstr(tmp, "..") == 0
3843				&& access(tmp, X_OK) == 0) {
3844				result = x_strdup(tmp);
3845				found = True;
3846			    }
3847			    break;
3848			}
3849			if (found)
3850			    break;
3851		    }
3852		    if (found)
3853			break;
3854		}
3855		free(tmp);
3856	    }
3857	}
3858    }
3859    TRACE(("...xtermFindShell(%s)\n", result));
3860    if (*result != '/'
3861	|| strstr(result, "..") != 0
3862	|| access(result, X_OK) != 0) {
3863	if (warning)
3864	    fprintf(stderr, "No absolute path found for shell: %s\n", result);
3865	result = 0;
3866    }
3867    return result;
3868}
3869#endif /* VMS */
3870
3871#define ENV_HUNK(n)	(unsigned) ((((n) + 1) | 31) + 1)
3872
3873/*
3874 * copy the environment before Setenv'ing.
3875 */
3876void
3877xtermCopyEnv(char **oldenv)
3878{
3879    unsigned size;
3880    char **newenv;
3881
3882    for (size = 0; oldenv[size] != NULL; size++) {
3883	;
3884    }
3885
3886    newenv = TypeCallocN(char *, ENV_HUNK(size));
3887    memmove(newenv, oldenv, size * sizeof(char *));
3888    environ = newenv;
3889}
3890
3891/*
3892 * sets the value of var to be arg in the Unix 4.2 BSD environment env.
3893 * Var should end with '=' (bindings are of the form "var=value").
3894 * This procedure assumes the memory for the first level of environ
3895 * was allocated using calloc, with enough extra room at the end so not
3896 * to have to do a realloc().
3897 */
3898void
3899xtermSetenv(const char *var, const char *value)
3900{
3901    if (value != 0) {
3902	char *test;
3903	int envindex = 0;
3904	size_t len = strlen(var);
3905	int found = -1;
3906
3907	TRACE(("xtermSetenv(%s=%s)\n", var, value));
3908
3909	while ((test = environ[envindex]) != NULL) {
3910	    if (strncmp(test, var, len) == 0 && test[len] == '=') {
3911		found = envindex;
3912		break;
3913	    }
3914	    envindex++;
3915	}
3916
3917	if (found < 0) {
3918	    unsigned need = ENV_HUNK(envindex + 1);
3919	    unsigned have = ENV_HUNK(envindex);
3920
3921	    if (need > have) {
3922		char **newenv;
3923		newenv = TypeMallocN(char *, need);
3924		if (newenv == 0) {
3925		    fprintf(stderr, "Cannot increase environment\n");
3926		    return;
3927		}
3928		memmove(newenv, environ, have * sizeof(*newenv));
3929		free(environ);
3930		environ = newenv;
3931	    }
3932
3933	    found = envindex;
3934	    environ[found + 1] = NULL;
3935	    environ = environ;
3936	}
3937
3938	environ[found] = CastMallocN(char, 1 + len + strlen(value));
3939	if (environ[found] == 0) {
3940	    fprintf(stderr, "Cannot allocate environment %s\n", var);
3941	    return;
3942	}
3943	sprintf(environ[found], "%s=%s", var, value);
3944    }
3945}
3946
3947/*ARGSUSED*/
3948int
3949xerror(Display * d, XErrorEvent * ev)
3950{
3951    fprintf(stderr, "%s:  warning, error event received:\n", xterm_name);
3952    (void) XmuPrintDefaultErrorMessage(d, ev, stderr);
3953    Exit(ERROR_XERROR);
3954    return 0;			/* appease the compiler */
3955}
3956
3957/*ARGSUSED*/
3958int
3959xioerror(Display * dpy)
3960{
3961    int the_error = errno;
3962
3963    (void) fprintf(stderr,
3964		   "%s:  fatal IO error %d (%s) or KillClient on X server \"%s\"\r\n",
3965		   xterm_name, the_error, SysErrorMsg(the_error),
3966		   DisplayString(dpy));
3967
3968    Exit(ERROR_XIOERROR);
3969    return 0;			/* appease the compiler */
3970}
3971
3972void
3973xt_error(String message)
3974{
3975    (void) fprintf(stderr, "%s Xt error: %s\n", ProgramName, message);
3976
3977    /*
3978     * Check for the obvious - Xt does a poor job of reporting this.
3979     */
3980    if (x_getenv("DISPLAY") == 0) {
3981	fprintf(stderr, "%s:  DISPLAY is not set\n", ProgramName);
3982    }
3983    exit(1);
3984}
3985
3986int
3987XStrCmp(char *s1, char *s2)
3988{
3989    if (s1 && s2)
3990	return (strcmp(s1, s2));
3991    if (s1 && *s1)
3992	return (1);
3993    if (s2 && *s2)
3994	return (-1);
3995    return (0);
3996}
3997
3998#if OPT_TEK4014
3999static void
4000withdraw_window(Display * dpy, Window w, int scr)
4001{
4002    TRACE(("withdraw_window %#lx\n", (long) w));
4003    (void) XmuUpdateMapHints(dpy, w, NULL);
4004    XWithdrawWindow(dpy, w, scr);
4005    return;
4006}
4007#endif
4008
4009void
4010set_vt_visibility(Bool on)
4011{
4012    TScreen *screen = TScreenOf(term);
4013
4014    TRACE(("set_vt_visibility(%d)\n", on));
4015    if (on) {
4016	if (!screen->Vshow && term) {
4017	    VTInit(term);
4018	    XtMapWidget(XtParent(term));
4019#if OPT_TOOLBAR
4020	    /* we need both of these during initialization */
4021	    XtMapWidget(SHELL_OF(term));
4022	    ShowToolbar(resource.toolBar);
4023#endif
4024	    screen->Vshow = True;
4025	}
4026    }
4027#if OPT_TEK4014
4028    else {
4029	if (screen->Vshow && term) {
4030	    withdraw_window(XtDisplay(term),
4031			    VShellWindow,
4032			    XScreenNumberOfScreen(XtScreen(term)));
4033	    screen->Vshow = False;
4034	}
4035    }
4036    set_vthide_sensitivity();
4037    set_tekhide_sensitivity();
4038    update_vttekmode();
4039    update_tekshow();
4040    update_vtshow();
4041#endif
4042    return;
4043}
4044
4045#if OPT_TEK4014
4046void
4047set_tek_visibility(Bool on)
4048{
4049    TRACE(("set_tek_visibility(%d)\n", on));
4050
4051    if (on) {
4052	if (!TEK4014_SHOWN(term)) {
4053	    if (tekWidget == 0) {
4054		TekInit();	/* will exit on failure */
4055	    }
4056	    if (tekWidget != 0) {
4057		Widget tekParent = SHELL_OF(tekWidget);
4058		XtRealizeWidget(tekParent);
4059		XtMapWidget(XtParent(tekWidget));
4060#if OPT_TOOLBAR
4061		/* we need both of these during initialization */
4062		XtMapWidget(tekParent);
4063		XtMapWidget(tekWidget);
4064#endif
4065		XtOverrideTranslations(tekParent,
4066				       XtParseTranslationTable
4067				       ("<Message>WM_PROTOCOLS: DeleteWindow()"));
4068		(void) XSetWMProtocols(XtDisplay(tekParent),
4069				       XtWindow(tekParent),
4070				       &wm_delete_window, 1);
4071		TEK4014_SHOWN(term) = True;
4072	    }
4073	}
4074    } else {
4075	if (TEK4014_SHOWN(term) && tekWidget) {
4076	    withdraw_window(XtDisplay(tekWidget),
4077			    TShellWindow,
4078			    XScreenNumberOfScreen(XtScreen(tekWidget)));
4079	    TEK4014_SHOWN(term) = False;
4080	}
4081    }
4082    set_tekhide_sensitivity();
4083    set_vthide_sensitivity();
4084    update_vtshow();
4085    update_tekshow();
4086    update_vttekmode();
4087    return;
4088}
4089
4090void
4091end_tek_mode(void)
4092{
4093    XtermWidget xw = term;
4094
4095    if (TEK4014_ACTIVE(xw)) {
4096	FlushLog(xw);
4097	longjmp(Tekend, 1);
4098    }
4099    return;
4100}
4101
4102void
4103end_vt_mode(void)
4104{
4105    XtermWidget xw = term;
4106
4107    if (!TEK4014_ACTIVE(xw)) {
4108	FlushLog(xw);
4109	TEK4014_ACTIVE(xw) = True;
4110	longjmp(VTend, 1);
4111    }
4112    return;
4113}
4114
4115void
4116switch_modes(Bool tovt)		/* if true, then become vt mode */
4117{
4118    if (tovt) {
4119	if (tekRefreshList)
4120	    TekRefresh(tekWidget);
4121	end_tek_mode();		/* WARNING: this does a longjmp... */
4122    } else {
4123	end_vt_mode();		/* WARNING: this does a longjmp... */
4124    }
4125}
4126
4127void
4128hide_vt_window(void)
4129{
4130    set_vt_visibility(False);
4131    if (!TEK4014_ACTIVE(term))
4132	switch_modes(False);	/* switch to tek mode */
4133}
4134
4135void
4136hide_tek_window(void)
4137{
4138    set_tek_visibility(False);
4139    tekRefreshList = (TekLink *) 0;
4140    if (TEK4014_ACTIVE(term))
4141	switch_modes(True);	/* does longjmp to vt mode */
4142}
4143#endif /* OPT_TEK4014 */
4144
4145static const char *
4146skip_punct(const char *s)
4147{
4148    while (*s == '-' || *s == '/' || *s == '+' || *s == '#' || *s == '%') {
4149	++s;
4150    }
4151    return s;
4152}
4153
4154static int
4155cmp_options(const void *a, const void *b)
4156{
4157    const char *s1 = skip_punct(((const OptionHelp *) a)->opt);
4158    const char *s2 = skip_punct(((const OptionHelp *) b)->opt);
4159    return strcmp(s1, s2);
4160}
4161
4162static int
4163cmp_resources(const void *a, const void *b)
4164{
4165    return strcmp(((const XrmOptionDescRec *) a)->option,
4166		  ((const XrmOptionDescRec *) b)->option);
4167}
4168
4169XrmOptionDescRec *
4170sortedOptDescs(XrmOptionDescRec * descs, Cardinal res_count)
4171{
4172    static XrmOptionDescRec *res_array = 0;
4173
4174#ifdef NO_LEAKS
4175    if (descs == 0) {
4176	if (res_array != 0) {
4177	    free(res_array);
4178	    res_array = 0;
4179	}
4180    } else
4181#endif
4182    if (res_array == 0) {
4183	Cardinal j;
4184
4185	/* make a sorted index to 'resources' */
4186	res_array = TypeCallocN(XrmOptionDescRec, res_count);
4187	if (res_array != 0) {
4188	    for (j = 0; j < res_count; j++)
4189		res_array[j] = descs[j];
4190	    qsort(res_array, (size_t) res_count, sizeof(*res_array), cmp_resources);
4191	}
4192    }
4193    return res_array;
4194}
4195
4196/*
4197 * The first time this is called, construct sorted index to the main program's
4198 * list of options, taking into account the on/off options which will be
4199 * compressed into one token.  It's a lot simpler to do it this way than
4200 * maintain the list in sorted form with lots of ifdef's.
4201 */
4202OptionHelp *
4203sortedOpts(OptionHelp * options, XrmOptionDescRec * descs, Cardinal numDescs)
4204{
4205    static OptionHelp *opt_array = 0;
4206
4207#ifdef NO_LEAKS
4208    if (descs == 0 && opt_array != 0) {
4209	sortedOptDescs(descs, numDescs);
4210	free(opt_array);
4211	opt_array = 0;
4212	return 0;
4213    } else if (options == 0 || descs == 0) {
4214	return 0;
4215    }
4216#endif
4217
4218    if (opt_array == 0) {
4219	size_t opt_count, j;
4220#if OPT_TRACE
4221	Cardinal k;
4222	XrmOptionDescRec *res_array = sortedOptDescs(descs, numDescs);
4223	int code;
4224	const char *mesg;
4225#else
4226	(void) descs;
4227	(void) numDescs;
4228#endif
4229
4230	/* count 'options' and make a sorted index to it */
4231	for (opt_count = 0; options[opt_count].opt != 0; ++opt_count) {
4232	    ;
4233	}
4234	opt_array = TypeCallocN(OptionHelp, opt_count + 1);
4235	for (j = 0; j < opt_count; j++)
4236	    opt_array[j] = options[j];
4237	qsort(opt_array, opt_count, sizeof(OptionHelp), cmp_options);
4238
4239	/* supply the "turn on/off" strings if needed */
4240#if OPT_TRACE
4241	for (j = 0; j < opt_count; j++) {
4242	    if (!strncmp(opt_array[j].opt, "-/+", 3)) {
4243		const char *name = opt_array[j].opt + 3;
4244		for (k = 0; k < numDescs; ++k) {
4245		    const char *value = res_array[k].value;
4246		    if (res_array[k].option[0] == '-') {
4247			code = -1;
4248		    } else if (res_array[k].option[0] == '+') {
4249			code = 1;
4250		    } else {
4251			code = 0;
4252		    }
4253		    if (x_strindex(opt_array[j].desc, "inhibit") != 0)
4254			code = -code;
4255		    if (code != 0
4256			&& res_array[k].value != 0
4257			&& !strcmp(name, res_array[k].option + 1)) {
4258			if (((code < 0) && !strcmp(value, "on"))
4259			    || ((code > 0) && !strcmp(value, "off"))
4260			    || ((code > 0) && !strcmp(value, "0"))) {
4261			    mesg = "turn on/off";
4262			} else {
4263			    mesg = "turn off/on";
4264			}
4265			if (strncmp(mesg, opt_array[j].desc, strlen(mesg))) {
4266			    if (strncmp(opt_array[j].desc, "turn ", 5)) {
4267				char *s = CastMallocN(char,
4268						      strlen(mesg)
4269						      + 1
4270						      + strlen(opt_array[j].desc));
4271				if (s != 0) {
4272				    sprintf(s, "%s %s", mesg, opt_array[j].desc);
4273				    opt_array[j].desc = s;
4274				}
4275			    } else {
4276				TRACE(("OOPS "));
4277			    }
4278			}
4279			TRACE(("%s: %s %s: %s (%s)\n",
4280			       mesg,
4281			       res_array[k].option,
4282			       res_array[k].value,
4283			       opt_array[j].opt,
4284			       opt_array[j].desc));
4285			break;
4286		    }
4287		}
4288	    }
4289	}
4290#endif
4291    }
4292    return opt_array;
4293}
4294
4295/*
4296 * Report the character-type locale that xterm was started in.
4297 */
4298char *
4299xtermEnvLocale(void)
4300{
4301    static char *result;
4302
4303    if (result == 0) {
4304	if ((result = x_nonempty(setlocale(LC_CTYPE, 0))) == 0) {
4305	    result = x_strdup("C");
4306	} else {
4307	    result = x_strdup(result);
4308	}
4309	TRACE(("xtermEnvLocale ->%s\n", result));
4310    }
4311    return result;
4312}
4313
4314char *
4315xtermEnvEncoding(void)
4316{
4317    static char *result;
4318
4319    if (result == 0) {
4320#ifdef HAVE_LANGINFO_CODESET
4321	result = nl_langinfo(CODESET);
4322#else
4323	char *locale = xtermEnvLocale();
4324	if (!strcmp(locale, "C") || !strcmp(locale, "POSIX")) {
4325	    result = "ASCII";
4326	} else {
4327	    result = "ISO-8859-1";
4328	}
4329#endif
4330	TRACE(("xtermEnvEncoding ->%s\n", result));
4331    }
4332    return result;
4333}
4334
4335#if OPT_WIDE_CHARS
4336/*
4337 * Tell whether xterm was started in a locale that uses UTF-8 encoding for
4338 * characters.  That environment is inherited by subprocesses and used in
4339 * various library calls.
4340 */
4341Bool
4342xtermEnvUTF8(void)
4343{
4344    static Bool init = False;
4345    static Bool result = False;
4346
4347    if (!init) {
4348	init = True;
4349#ifdef HAVE_LANGINFO_CODESET
4350	result = (strcmp(xtermEnvEncoding(), "UTF-8") == 0);
4351#else
4352	result = (strstr(xtermEnvLocale(), "UTF-8") != NULL);
4353#endif
4354	TRACE(("xtermEnvUTF8 ->%s\n", BtoS(result)));
4355    }
4356    return result;
4357}
4358#endif /* OPT_WIDE_CHARS */
4359
4360/*
4361 * Returns the version-string used in the "-v' message as well as a few other
4362 * places.  It is derived (when possible) from the __vendorversion__ symbol
4363 * that some newer imake configurations define.
4364 */
4365char *
4366xtermVersion(void)
4367{
4368    static char vendor_version[] = __vendorversion__;
4369    static char *result;
4370
4371    if (result == 0) {
4372	char *vendor = vendor_version;
4373	char first[BUFSIZ];
4374	char second[BUFSIZ];
4375
4376	result = CastMallocN(char, strlen(vendor) + 9);
4377	if (result == 0)
4378	    result = vendor;
4379	else {
4380	    /* some vendors leave trash in this string */
4381	    for (;;) {
4382		if (!strncmp(vendor, "Version ", (size_t) 8))
4383		    vendor += 8;
4384		else if (isspace(CharOf(*vendor)))
4385		    ++vendor;
4386		else
4387		    break;
4388	    }
4389	    if (strlen(vendor) < BUFSIZ &&
4390		sscanf(vendor, "%[0-9.] %[A-Za-z_0-9.]", first, second) == 2)
4391		sprintf(result, "%s %s(%d)", second, first, XTERM_PATCH);
4392	    else
4393		sprintf(result, "%s(%d)", vendor, XTERM_PATCH);
4394	}
4395    }
4396    return result;
4397}
4398
4399/*
4400 * Check if the current widget, or any parent, is the VT100 "xterm" widget.
4401 */
4402XtermWidget
4403getXtermWidget(Widget w)
4404{
4405    XtermWidget xw;
4406
4407    if (w == 0) {
4408	xw = (XtermWidget) CURRENT_EMU();
4409	if (!IsXtermWidget(xw)) {
4410	    xw = 0;
4411	}
4412    } else if (IsXtermWidget(w)) {
4413	xw = (XtermWidget) w;
4414    } else {
4415	xw = getXtermWidget(XtParent(w));
4416    }
4417    TRACE2(("getXtermWidget %p -> %p\n", w, xw));
4418    return xw;
4419}
4420