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