misc.c revision 22d8e007
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#ifdef notdef
2351    case 3:			/* change X property */
2352	ChangeXprop(buf);
2353	break;
2354#endif
2355#if OPT_ISO_COLORS
2356    case 4:
2357	ChangeAnsiColorRequest(xw, buf, final);
2358	break;
2359#endif
2360    case OSC_TEXT_FG:
2361    case OSC_TEXT_BG:
2362    case OSC_TEXT_CURSOR:
2363    case OSC_MOUSE_FG:
2364    case OSC_MOUSE_BG:
2365#if OPT_HIGHLIGHT_COLOR
2366    case OSC_HIGHLIGHT_BG:
2367#endif
2368#if OPT_TEK4014
2369    case OSC_TEK_FG:
2370    case OSC_TEK_BG:
2371    case OSC_TEK_CURSOR:
2372#endif
2373	if (xw->misc.dynamicColors)
2374	    ChangeColorsRequest(xw, mode, buf, final);
2375	break;
2376
2377    case 30:
2378    case 31:
2379	/* reserved for Konsole (Stephan Binner <Stephan.Binner@gmx.de>) */
2380	break;
2381
2382#ifdef ALLOWLOGGING
2383    case 46:			/* new log file */
2384#ifdef ALLOWLOGFILECHANGES
2385	/*
2386	 * Warning, enabling this feature allows people to overwrite
2387	 * arbitrary files accessible to the person running xterm.
2388	 */
2389	if (buf != 0
2390	    && strcmp(buf, "?")
2391	    && (cp = CastMallocN(char, strlen(buf)) != NULL)) {
2392	    strcpy(cp, buf);
2393	    if (screen->logfile)
2394		free(screen->logfile);
2395	    screen->logfile = cp;
2396	    break;
2397	}
2398#endif
2399	Bell(XkbBI_Info, 0);
2400	Bell(XkbBI_Info, 0);
2401	break;
2402#endif /* ALLOWLOGGING */
2403
2404    case 50:
2405#if OPT_SHIFT_FONTS
2406	if (buf != 0 && !strcmp(buf, "?")) {
2407	    int num = screen->menu_font_number;
2408
2409	    unparseputc1(xw, ANSI_OSC);
2410	    unparseputs(xw, "50");
2411
2412	    if ((buf = screen->MenuFontName(num)) != 0) {
2413		unparseputc(xw, ';');
2414		unparseputs(xw, buf);
2415	    }
2416	    unparseputc1(xw, final);
2417	    unparse_end(xw);
2418	} else if (buf != 0) {
2419	    int num = screen->menu_font_number;
2420	    VTFontNames fonts;
2421
2422	    memset(&fonts, 0, sizeof(fonts));
2423
2424	    /*
2425	     * If the font specification is a "#", followed by an
2426	     * optional sign and optional number, lookup the
2427	     * corresponding menu font entry.
2428	     */
2429	    if (*buf == '#') {
2430		int rel = 0;
2431
2432		if (*++buf == '+') {
2433		    rel = 1;
2434		    buf++;
2435		} else if (*buf == '-') {
2436		    rel = -1;
2437		    buf++;
2438		}
2439
2440		if (isdigit(CharOf(*buf))) {
2441		    int val = atoi(buf);
2442		    if (rel > 0)
2443			rel = val;
2444		    else if (rel < 0)
2445			rel = -val;
2446		    else
2447			num = val;
2448		} else if (rel == 0) {
2449		    num = 0;
2450		}
2451
2452		if (rel != 0) {
2453		    num = lookupRelativeFontSize(xw,
2454						 screen->menu_font_number, rel);
2455
2456		}
2457		if (num < 0
2458		    || num > fontMenu_lastBuiltin
2459		    || (buf = screen->MenuFontName(num)) == 0) {
2460		    Bell(XkbBI_MinorError, 0);
2461		    break;
2462		}
2463	    } else {
2464		num = fontMenu_fontescape;
2465	    }
2466	    fonts.f_n = buf;
2467	    SetVTFont(xw, num, True, &fonts);
2468	}
2469#endif /* OPT_SHIFT_FONTS */
2470	break;
2471    case 51:
2472	/* reserved for Emacs shell (Rob Mayoff <mayoff@dqd.com>) */
2473	break;
2474
2475#if OPT_PASTE64
2476    case 52:
2477	if (screen->allowWindowOps && (buf != 0))
2478	    ManipulateSelectionData(xw, screen, buf, final);
2479	break;
2480#endif
2481	/*
2482	 * One could write code to send back the display and host names,
2483	 * but that could potentially open a fairly nasty security hole.
2484	 */
2485    }
2486    unparse_end(xw);
2487}
2488
2489#ifdef SunXK_F36
2490#define MAX_UDK 37
2491#else
2492#define MAX_UDK 35
2493#endif
2494static struct {
2495    char *str;
2496    int len;
2497} user_keys[MAX_UDK];
2498
2499/*
2500 * Parse one nibble of a hex byte from the OSC string.  We have removed the
2501 * string-terminator (replacing it with a null), so the only other delimiter
2502 * that is expected is semicolon.  Ignore other characters (Ray Neuman says
2503 * "real" terminals accept commas in the string definitions).
2504 */
2505static int
2506udk_value(char **cp)
2507{
2508    int c;
2509
2510    for (;;) {
2511	if ((c = **cp) != '\0')
2512	    *cp = *cp + 1;
2513	if (c == ';' || c == '\0')
2514	    return -1;
2515	if (c >= '0' && c <= '9')
2516	    return c - '0';
2517	if (c >= 'A' && c <= 'F')
2518	    return c - 'A' + 10;
2519	if (c >= 'a' && c <= 'f')
2520	    return c - 'a' + 10;
2521    }
2522}
2523
2524void
2525reset_decudk(void)
2526{
2527    int n;
2528    for (n = 0; n < MAX_UDK; n++) {
2529	if (user_keys[n].str != 0) {
2530	    free(user_keys[n].str);
2531	    user_keys[n].str = 0;
2532	    user_keys[n].len = 0;
2533	}
2534    }
2535}
2536
2537/*
2538 * Parse the data for DECUDK (user-defined keys).
2539 */
2540static void
2541parse_decudk(char *cp)
2542{
2543    while (*cp) {
2544	char *base = cp;
2545	char *str = CastMallocN(char, strlen(cp) + 1);
2546	unsigned key = 0;
2547	int lo, hi;
2548	int len = 0;
2549
2550	while (isdigit(CharOf(*cp)))
2551	    key = (key * 10) + (*cp++ - '0');
2552	if (*cp == '/') {
2553	    cp++;
2554	    while ((hi = udk_value(&cp)) >= 0
2555		   && (lo = udk_value(&cp)) >= 0) {
2556		str[len++] = (hi << 4) | lo;
2557	    }
2558	}
2559	if (len > 0 && key < MAX_UDK) {
2560	    if (user_keys[key].str != 0)
2561		free(user_keys[key].str);
2562	    user_keys[key].str = str;
2563	    user_keys[key].len = len;
2564	} else {
2565	    free(str);
2566	}
2567	if (*cp == ';')
2568	    cp++;
2569	if (cp == base)		/* badly-formed sequence - bail out */
2570	    break;
2571    }
2572}
2573
2574#if OPT_TRACE
2575#define SOFT_WIDE 10
2576#define SOFT_HIGH 20
2577
2578static void
2579parse_decdld(ANSI * params, char *string)
2580{
2581    char DscsName[8];
2582    int len;
2583    int Pfn = params->a_param[0];
2584    int Pcn = params->a_param[1];
2585    int Pe = params->a_param[2];
2586    int Pcmw = params->a_param[3];
2587    int Pw = params->a_param[4];
2588    int Pt = params->a_param[5];
2589    int Pcmh = params->a_param[6];
2590    int Pcss = params->a_param[7];
2591
2592    int start_char = Pcn + 0x20;
2593    int char_wide = ((Pcmw == 0)
2594		     ? (Pcss ? 6 : 10)
2595		     : (Pcmw > 4
2596			? Pcmw
2597			: (Pcmw + 3)));
2598    int char_high = ((Pcmh == 0)
2599		     ? ((Pcmw >= 2 || Pcmw <= 4)
2600			? 10
2601			: 20)
2602		     : Pcmh);
2603    Char ch;
2604    Char bits[SOFT_HIGH][SOFT_WIDE];
2605    Bool first = True;
2606    Bool prior = False;
2607    int row = 0, col = 0;
2608
2609    TRACE(("Parsing DECDLD\n"));
2610    TRACE(("  font number   %d\n", Pfn));
2611    TRACE(("  starting char %d\n", Pcn));
2612    TRACE(("  erase control %d\n", Pe));
2613    TRACE(("  char-width    %d\n", Pcmw));
2614    TRACE(("  font-width    %d\n", Pw));
2615    TRACE(("  text/full     %d\n", Pt));
2616    TRACE(("  char-height   %d\n", Pcmh));
2617    TRACE(("  charset-size  %d\n", Pcss));
2618
2619    if (Pfn > 1
2620	|| Pcn > 95
2621	|| Pe > 2
2622	|| Pcmw > 10
2623	|| Pcmw == 1
2624	|| Pt > 2
2625	|| Pcmh > 20
2626	|| Pcss > 1
2627	|| char_wide > SOFT_WIDE
2628	|| char_high > SOFT_HIGH) {
2629	TRACE(("DECDLD illegal parameter\n"));
2630	return;
2631    }
2632
2633    len = 0;
2634    while (*string != '\0') {
2635	ch = CharOf(*string++);
2636	if (ch >= ANSI_SPA && ch <= 0x2f) {
2637	    if (len < 2)
2638		DscsName[len++] = ch;
2639	} else if (ch >= 0x30 && ch <= 0x7e) {
2640	    DscsName[len++] = ch;
2641	    break;
2642	}
2643    }
2644    DscsName[len] = 0;
2645    TRACE(("  Dscs name     '%s'\n", DscsName));
2646
2647    TRACE(("  character matrix %dx%d\n", char_high, char_wide));
2648    while (*string != '\0') {
2649	if (first) {
2650	    TRACE(("Char %d:\n", start_char));
2651	    if (prior) {
2652		for (row = 0; row < char_high; ++row) {
2653		    TRACE(("%.*s\n", char_wide, bits[row]));
2654		}
2655	    }
2656	    prior = False;
2657	    first = False;
2658	    for (row = 0; row < char_high; ++row) {
2659		for (col = 0; col < char_wide; ++col) {
2660		    bits[row][col] = '.';
2661		}
2662	    }
2663	    row = col = 0;
2664	}
2665	ch = CharOf(*string++);
2666	if (ch >= 0x3f && ch <= 0x7e) {
2667	    int n;
2668
2669	    ch -= 0x3f;
2670	    for (n = 0; n < 6; ++n) {
2671		bits[row + n][col] = (ch & (1 << n)) ? '*' : '.';
2672	    }
2673	    col += 1;
2674	    prior = True;
2675	} else if (ch == '/') {
2676	    row += 6;
2677	    col = 0;
2678	} else if (ch == ';') {
2679	    first = True;
2680	    ++start_char;
2681	}
2682    }
2683}
2684#else
2685#define parse_decdld(p,q)	/* nothing */
2686#endif
2687
2688/*
2689 * Parse numeric parameters.  Normally we use a state machine to simplify
2690 * interspersing with control characters, but have the string already.
2691 */
2692static void
2693parse_ansi_params(ANSI * params, char **string)
2694{
2695    char *cp = *string;
2696    short nparam = 0;
2697
2698    memset(params, 0, sizeof(*params));
2699    while (*cp != '\0') {
2700	Char ch = CharOf(*cp++);
2701
2702	if (isdigit(ch)) {
2703	    if (nparam < NPARAM) {
2704		params->a_param[nparam] *= 10;
2705		params->a_param[nparam] += (ch - '0');
2706	    }
2707	} else if (ch == ';') {
2708	    if (++nparam < NPARAM)
2709		params->a_nparam = nparam;
2710	} else if (ch < 32) {
2711	    ;
2712	} else {
2713	    /* should be 0x30 to 0x7e */
2714	    params->a_final = ch;
2715	    break;
2716	}
2717    }
2718    *string = cp;
2719}
2720
2721void
2722do_dcs(XtermWidget xw, Char * dcsbuf, size_t dcslen)
2723{
2724    TScreen *screen = &xw->screen;
2725    char reply[BUFSIZ];
2726    char *cp = (char *) dcsbuf;
2727    Bool okay;
2728    ANSI params;
2729
2730    TRACE(("do_dcs(%s:%d)\n", (char *) dcsbuf, dcslen));
2731
2732    if (dcslen != strlen(cp))
2733	/* shouldn't have nulls in the string */
2734	return;
2735
2736    switch (*cp) {		/* intermediate character, or parameter */
2737    case '$':			/* DECRQSS */
2738	okay = True;
2739
2740	cp++;
2741	if (*cp++ == 'q') {
2742	    if (!strcmp(cp, "\"q")) {	/* DECSCA */
2743		sprintf(reply, "%d%s",
2744			(screen->protected_mode == DEC_PROTECT)
2745			&& (xw->flags & PROTECTED) ? 1 : 0,
2746			cp);
2747	    } else if (!strcmp(cp, "\"p")) {	/* DECSCL */
2748		sprintf(reply, "%d%s%s",
2749			(screen->vtXX_level ?
2750			 screen->vtXX_level : 1) + 60,
2751			(screen->vtXX_level >= 2)
2752			? (screen->control_eight_bits
2753			   ? ";0" : ";1")
2754			: "",
2755			cp);
2756	    } else if (!strcmp(cp, "r")) {	/* DECSTBM */
2757		sprintf(reply, "%d;%dr",
2758			screen->top_marg + 1,
2759			screen->bot_marg + 1);
2760	    } else if (!strcmp(cp, "m")) {	/* SGR */
2761		strcpy(reply, "0");
2762		if (xw->flags & BOLD)
2763		    strcat(reply, ";1");
2764		if (xw->flags & UNDERLINE)
2765		    strcat(reply, ";4");
2766		if (xw->flags & BLINK)
2767		    strcat(reply, ";5");
2768		if (xw->flags & INVERSE)
2769		    strcat(reply, ";7");
2770		if (xw->flags & INVISIBLE)
2771		    strcat(reply, ";8");
2772		if_OPT_EXT_COLORS(screen, {
2773		    if (xw->flags & FG_COLOR) {
2774			if (xw->cur_foreground >= 16)
2775			    sprintf(reply + strlen(reply),
2776				    ";38;5;%d", xw->cur_foreground);
2777			else
2778			    sprintf(reply + strlen(reply),
2779				    ";%d%d",
2780				    xw->cur_foreground >= 8 ? 9 : 3,
2781				    xw->cur_foreground >= 8 ?
2782				    xw->cur_foreground - 8 :
2783				    xw->cur_foreground);
2784		    }
2785		    if (xw->flags & BG_COLOR) {
2786			if (xw->cur_background >= 16)
2787			    sprintf(reply + strlen(reply),
2788				    ";48;5;%d", xw->cur_foreground);
2789			else
2790			    sprintf(reply + strlen(reply),
2791				    ";%d%d",
2792				    xw->cur_background >= 8 ? 10 : 4,
2793				    xw->cur_background >= 8 ?
2794				    xw->cur_background - 8 :
2795				    xw->cur_background);
2796		    }
2797		});
2798		if_OPT_ISO_TRADITIONAL_COLORS(screen, {
2799		    if (xw->flags & FG_COLOR)
2800			sprintf(reply + strlen(reply),
2801				";%d%d",
2802				xw->cur_foreground >= 8 ? 9 : 3,
2803				xw->cur_foreground >= 8 ?
2804				xw->cur_foreground - 8 :
2805				xw->cur_foreground);
2806		    if (xw->flags & BG_COLOR)
2807			sprintf(reply + strlen(reply),
2808				";%d%d",
2809				xw->cur_background >= 8 ? 10 : 4,
2810				xw->cur_background >= 8 ?
2811				xw->cur_background - 8 :
2812				xw->cur_background);
2813		});
2814		strcat(reply, "m");
2815	    } else
2816		okay = False;
2817
2818	    unparseputc1(xw, ANSI_DCS);
2819	    unparseputc(xw, okay ? '1' : '0');
2820	    unparseputc(xw, '$');
2821	    unparseputc(xw, 'r');
2822	    if (okay) {
2823		cp = reply;
2824		unparseputs(xw, cp);
2825	    }
2826	    unparseputc1(xw, ANSI_ST);
2827	} else {
2828	    unparseputc(xw, ANSI_CAN);
2829	}
2830	break;
2831#if OPT_TCAP_QUERY
2832    case '+':
2833	cp++;
2834	if (*cp == 'q') {
2835	    Bool fkey;
2836	    unsigned state;
2837	    int code;
2838	    char *tmp;
2839	    char *parsed = ++cp;
2840
2841	    unparseputc1(xw, ANSI_DCS);
2842
2843	    code = xtermcapKeycode(xw, &parsed, &state, &fkey);
2844
2845	    unparseputc(xw, code >= 0 ? '1' : '0');
2846
2847	    unparseputc(xw, '+');
2848	    unparseputc(xw, 'r');
2849
2850	    while (*cp != 0) {
2851		if (cp == parsed)
2852		    break;	/* no data found, error */
2853
2854		for (tmp = cp; tmp != parsed; ++tmp)
2855		    unparseputc(xw, *tmp);
2856
2857		if (code >= 0) {
2858		    unparseputc(xw, '=');
2859		    screen->tc_query_code = code;
2860		    screen->tc_query_fkey = fkey;
2861#if OPT_ISO_COLORS
2862		    /* XK_COLORS is a fake code for the "Co" entry (maximum
2863		     * number of colors) */
2864		    if (code == XK_COLORS) {
2865			unparseputn(xw, NUM_ANSI_COLORS);
2866		    } else
2867#endif
2868#if OPT_TCAP_FKEYS
2869			/*
2870			 * First ensure that we handle the extended cursor- and
2871			 * editing-keypad keys.
2872			 */
2873			if ((code <= XK_Fn(MAX_FKEY))
2874			    || xtermcapString(xw, CodeToXkey(code), 0) == 0)
2875#endif
2876		    {
2877			XKeyEvent event;
2878			event.state = state;
2879			Input(xw, &event, False);
2880		    }
2881		    screen->tc_query_code = -1;
2882		} else {
2883		    break;	/* no match found, error */
2884		}
2885
2886		cp = parsed;
2887		if (*parsed == ';') {
2888		    unparseputc(xw, *parsed++);
2889		    cp = parsed;
2890		    code = xtermcapKeycode(xw, &parsed, &state, &fkey);
2891		}
2892	    }
2893	    unparseputc1(xw, ANSI_ST);
2894	}
2895	break;
2896#endif
2897    default:
2898	parse_ansi_params(&params, &cp);
2899	switch (params.a_final) {
2900	case '|':		/* DECUDK */
2901	    if (params.a_param[0] == 0)
2902		reset_decudk();
2903#ifdef notdef
2904	    parse_decudk(cp);
2905#endif
2906	    break;
2907	case '{':		/* DECDLD (no '}' case though) */
2908	    parse_decdld(&params, cp);
2909	    break;
2910	}
2911	break;
2912    }
2913    unparse_end(xw);
2914}
2915
2916char *
2917udk_lookup(int keycode, int *len)
2918{
2919    if (keycode >= 0 && keycode < MAX_UDK) {
2920	*len = user_keys[keycode].len;
2921	return user_keys[keycode].str;
2922    }
2923    return 0;
2924}
2925
2926static void
2927ChangeGroup(String attribute, char *value)
2928{
2929#if OPT_WIDE_CHARS
2930    static Char *converted;	/* NO_LEAKS */
2931#endif
2932    static char empty[1];
2933
2934    Arg args[1];
2935    char *original = (value != 0) ? value : empty;
2936    char *name = original;
2937    TScreen *screen = TScreenOf(term);
2938    Widget w = CURRENT_EMU();
2939    Widget top = SHELL_OF(w);
2940    unsigned limit = strlen(name);
2941    Char *c1 = (Char *) original;
2942    Char *cp;
2943
2944    TRACE(("ChangeGroup(attribute=%s, value=%s)\n", attribute, name));
2945
2946    if (!screen->allowTitleOps)
2947	return;
2948
2949    /*
2950     * Ignore titles that are too long to be plausible requests.
2951     */
2952    if (limit >= 1024)
2953	return;
2954
2955    for (cp = c1; *cp != 0; ++cp) {
2956	Char *c2 = cp;
2957	if (!xtermIsPrintable(screen, &cp, c1 + limit)) {
2958	    memset(c2, '?', (unsigned) (cp + 1 - c2));
2959	}
2960    }
2961
2962#if OPT_WIDE_CHARS
2963    /*
2964     * Title strings are limited to ISO-8859-1, which is consistent with the
2965     * printable data in sos_table.  However, if we're running in UTF-8 mode,
2966     * it is likely that non-ASCII text in the string will be rejected because
2967     * it is not printable in the current locale.  So we convert it to UTF-8,
2968     * allowing the X library to convert it back.
2969     */
2970    if (xtermEnvUTF8() && !screen->utf8_title) {
2971	int n;
2972
2973	for (n = 0; name[n] != '\0'; ++n) {
2974	    if (CharOf(name[n]) > 127) {
2975		if (converted != 0)
2976		    free(converted);
2977		if ((converted = TypeMallocN(Char, 1 + (5 * limit))) != 0) {
2978		    Char *temp = converted;
2979		    while (*name != 0) {
2980			temp = convertToUTF8(temp, CharOf(*name));
2981			++name;
2982		    }
2983		    *temp = 0;
2984		    name = (char *) converted;
2985		    TRACE(("...converted{%s}\n", name));
2986		}
2987		break;
2988	    }
2989	}
2990    }
2991#endif
2992
2993#if OPT_SAME_NAME
2994    /* If the attribute isn't going to change, then don't bother... */
2995
2996    if (resource.sameName) {
2997	char *buf;
2998	XtSetArg(args[0], attribute, &buf);
2999	XtGetValues(top, args, 1);
3000	TRACE(("...comparing{%s}\n", buf));
3001	if (strcmp(name, buf) == 0)
3002	    return;
3003    }
3004#endif /* OPT_SAME_NAME */
3005
3006    TRACE(("...updating %s\n", attribute));
3007    TRACE(("...value is %s\n", name));
3008    XtSetArg(args[0], attribute, name);
3009    XtSetValues(top, args, 1);
3010
3011#if OPT_WIDE_CHARS
3012    if (xtermEnvUTF8()) {
3013	Display *dpy = XtDisplay(term);
3014	Atom my_atom;
3015
3016	const char *propname = (!strcmp(attribute, XtNtitle)
3017				? "_NET_WM_NAME"
3018				: "_NET_WM_ICON_NAME");
3019	if ((my_atom = XInternAtom(dpy, propname, False)) != None) {
3020	    if (screen->utf8_title) {	/* FIXME - redundant? */
3021		TRACE(("...updating %s\n", propname));
3022		TRACE(("...value is %s\n", original));
3023		XChangeProperty(dpy, VShellWindow,
3024				my_atom, XA_UTF8_STRING(dpy), 8,
3025				PropModeReplace,
3026				(Char *) original, (int) strlen(original));
3027	    } else {
3028		TRACE(("...deleting %s\n", propname));
3029		XDeleteProperty(dpy, VShellWindow, my_atom);
3030	    }
3031	}
3032    }
3033#endif
3034}
3035
3036void
3037ChangeIconName(char *name)
3038{
3039    if (name == 0)
3040	name = "";
3041#if OPT_ZICONBEEP		/* If warning should be given then give it */
3042    if (resource.zIconBeep && term->screen.zIconBeep_flagged) {
3043	char *newname = CastMallocN(char, strlen(name) + 4);
3044	if (!newname) {
3045	    fprintf(stderr, "malloc failed in ChangeIconName\n");
3046	    return;
3047	}
3048	strcpy(newname, "*** ");
3049	strcat(newname, name);
3050	ChangeGroup(XtNiconName, newname);
3051	free(newname);
3052    } else
3053#endif /* OPT_ZICONBEEP */
3054	ChangeGroup(XtNiconName, name);
3055}
3056
3057void
3058ChangeTitle(char *name)
3059{
3060    ChangeGroup(XtNtitle, name);
3061}
3062
3063#define Strlen(s) strlen((char *)(s))
3064
3065void
3066ChangeXprop(char *buf)
3067{
3068    Display *dpy = XtDisplay(toplevel);
3069    Window w = XtWindow(toplevel);
3070    XTextProperty text_prop;
3071    Atom aprop;
3072    Char *pchEndPropName = (Char *) strchr(buf, '=');
3073
3074    if (pchEndPropName)
3075	*pchEndPropName = '\0';
3076    aprop = XInternAtom(dpy, buf, False);
3077    if (pchEndPropName == NULL) {
3078	/* no "=value" given, so delete the property */
3079	XDeleteProperty(dpy, w, aprop);
3080    } else {
3081	text_prop.value = pchEndPropName + 1;
3082	text_prop.encoding = XA_STRING;
3083	text_prop.format = 8;
3084	text_prop.nitems = Strlen(text_prop.value);
3085	XSetTextProperty(dpy, w, &text_prop, aprop);
3086    }
3087}
3088
3089/***====================================================================***/
3090
3091/*
3092 * This is part of ReverseVideo().  It reverses the data stored for the old
3093 * "dynamic" colors that might have been retrieved using OSC 10-18.
3094 */
3095void
3096ReverseOldColors(void)
3097{
3098    ScrnColors *pOld = pOldColors;
3099    Pixel tmpPix;
3100    char *tmpName;
3101
3102    if (pOld) {
3103	/* change text cursor, if necesary */
3104	if (pOld->colors[TEXT_CURSOR] == pOld->colors[TEXT_FG]) {
3105	    pOld->colors[TEXT_CURSOR] = pOld->colors[TEXT_BG];
3106	    if (pOld->names[TEXT_CURSOR]) {
3107		XtFree(pOldColors->names[TEXT_CURSOR]);
3108		pOld->names[TEXT_CURSOR] = NULL;
3109	    }
3110	    if (pOld->names[TEXT_BG]) {
3111		if ((tmpName = x_strdup(pOld->names[TEXT_BG])) != 0) {
3112		    pOld->names[TEXT_CURSOR] = tmpName;
3113		}
3114	    }
3115	}
3116
3117	EXCHANGE(pOld->colors[TEXT_FG], pOld->colors[TEXT_BG], tmpPix);
3118	EXCHANGE(pOld->names[TEXT_FG], pOld->names[TEXT_BG], tmpName);
3119
3120	EXCHANGE(pOld->colors[MOUSE_FG], pOld->colors[MOUSE_BG], tmpPix);
3121	EXCHANGE(pOld->names[MOUSE_FG], pOld->names[MOUSE_BG], tmpName);
3122
3123#if OPT_TEK4014
3124	EXCHANGE(pOld->colors[TEK_FG], pOld->colors[TEK_BG], tmpPix);
3125	EXCHANGE(pOld->names[TEK_FG], pOld->names[TEK_BG], tmpName);
3126#endif
3127    }
3128    return;
3129}
3130
3131Bool
3132AllocateTermColor(XtermWidget xw,
3133		  ScrnColors * pNew,
3134		  int ndx,
3135		  const char *name)
3136{
3137    XColor def;
3138    TScreen *screen = &xw->screen;
3139    Colormap cmap = xw->core.colormap;
3140    char *newName;
3141
3142    if (XParseColor(screen->display, cmap, name, &def)
3143	&& (XAllocColor(screen->display, cmap, &def)
3144	    || find_closest_color(screen->display, cmap, &def))
3145	&& (newName = x_strdup(name)) != 0) {
3146	if (COLOR_DEFINED(pNew, ndx))
3147	    free(pNew->names[ndx]);
3148	SET_COLOR_VALUE(pNew, ndx, def.pixel);
3149	SET_COLOR_NAME(pNew, ndx, newName);
3150	TRACE(("AllocateTermColor #%d: %s (pixel %#lx)\n", ndx, newName, def.pixel));
3151	return (True);
3152    }
3153    TRACE(("AllocateTermColor #%d: %s (failed)\n", ndx, name));
3154    return (False);
3155}
3156/***====================================================================***/
3157
3158/* ARGSUSED */
3159void
3160Panic(char *s GCC_UNUSED, int a GCC_UNUSED)
3161{
3162#ifdef DEBUG
3163    if (debug) {
3164	fprintf(stderr, "%s: PANIC!\t", xterm_name);
3165	fprintf(stderr, s, a);
3166	fputs("\r\n", stderr);
3167	fflush(stderr);
3168    }
3169#endif /* DEBUG */
3170}
3171
3172const char *
3173SysErrorMsg(int code)
3174{
3175    static char unknown[] = "unknown error";
3176    char *s = strerror(code);
3177    return s ? s : unknown;
3178}
3179
3180const char *
3181SysReasonMsg(int code)
3182{
3183    /* *INDENT-OFF* */
3184    static const struct {
3185	int code;
3186	const char *name;
3187    } table[] = {
3188	{ ERROR_FIONBIO,	"main:  ioctl() failed on FIONBIO" },
3189	{ ERROR_F_GETFL,	"main: ioctl() failed on F_GETFL" },
3190	{ ERROR_F_SETFL,	"main: ioctl() failed on F_SETFL", },
3191	{ ERROR_OPDEVTTY,	"spawn: open() failed on /dev/tty", },
3192	{ ERROR_TIOCGETP,	"spawn: ioctl() failed on TIOCGETP", },
3193	{ ERROR_PTSNAME,	"spawn: ptsname() failed", },
3194	{ ERROR_OPPTSNAME,	"spawn: open() failed on ptsname", },
3195	{ ERROR_PTEM,		"spawn: ioctl() failed on I_PUSH/\"ptem\"" },
3196	{ ERROR_CONSEM,		"spawn: ioctl() failed on I_PUSH/\"consem\"" },
3197	{ ERROR_LDTERM,		"spawn: ioctl() failed on I_PUSH/\"ldterm\"" },
3198	{ ERROR_TTCOMPAT,	"spawn: ioctl() failed on I_PUSH/\"ttcompat\"" },
3199	{ ERROR_TIOCSETP,	"spawn: ioctl() failed on TIOCSETP" },
3200	{ ERROR_TIOCSETC,	"spawn: ioctl() failed on TIOCSETC" },
3201	{ ERROR_TIOCSETD,	"spawn: ioctl() failed on TIOCSETD" },
3202	{ ERROR_TIOCSLTC,	"spawn: ioctl() failed on TIOCSLTC" },
3203	{ ERROR_TIOCLSET,	"spawn: ioctl() failed on TIOCLSET" },
3204	{ ERROR_INIGROUPS,	"spawn: initgroups() failed" },
3205	{ ERROR_FORK,		"spawn: fork() failed" },
3206	{ ERROR_EXEC,		"spawn: exec() failed" },
3207	{ ERROR_PTYS,		"get_pty: not enough ptys" },
3208	{ ERROR_PTY_EXEC,	"waiting for initial map" },
3209	{ ERROR_SETUID,		"spawn: setuid() failed" },
3210	{ ERROR_INIT,		"spawn: can't initialize window" },
3211	{ ERROR_TIOCKSET,	"spawn: ioctl() failed on TIOCKSET" },
3212	{ ERROR_TIOCKSETC,	"spawn: ioctl() failed on TIOCKSETC" },
3213	{ ERROR_SPREALLOC,	"spawn: realloc of ttydev failed" },
3214	{ ERROR_LUMALLOC,	"luit: command-line malloc failed" },
3215	{ ERROR_SELECT,		"in_put: select() failed" },
3216	{ ERROR_VINIT,		"VTInit: can't initialize window" },
3217	{ ERROR_KMMALLOC1,	"HandleKeymapChange: malloc failed" },
3218	{ ERROR_TSELECT,	"Tinput: select() failed" },
3219	{ ERROR_TINIT,		"TekInit: can't initialize window" },
3220	{ ERROR_BMALLOC2,	"SaltTextAway: malloc() failed" },
3221	{ ERROR_LOGEXEC,	"StartLog: exec() failed" },
3222	{ ERROR_XERROR,		"xerror: XError event" },
3223	{ ERROR_XIOERROR,	"xioerror: X I/O error" },
3224	{ ERROR_SCALLOC,	"Alloc: calloc() failed on base" },
3225	{ ERROR_SCALLOC2,	"Alloc: calloc() failed on rows" },
3226	{ ERROR_SREALLOC,	"ScreenResize: realloc() failed on alt base" },
3227	{ ERROR_RESIZE,		"ScreenResize: malloc() or realloc() failed" },
3228	{ ERROR_SAVE_PTR,	"ScrnPointers: malloc/realloc() failed" },
3229	{ ERROR_SBRALLOC,	"ScrollBarOn: realloc() failed on base" },
3230	{ ERROR_SBRALLOC2,	"ScrollBarOn: realloc() failed on rows" },
3231	{ ERROR_MMALLOC,	"my_memmove: malloc/realloc failed" },
3232    };
3233    /* *INDENT-ON* */
3234
3235    Cardinal n;
3236    const char *result = "?";
3237
3238    for (n = 0; n < XtNumber(table); ++n) {
3239	if (code == table[n].code) {
3240	    result = table[n].name;
3241	    break;
3242	}
3243    }
3244    return result;
3245}
3246
3247void
3248SysError(int code)
3249{
3250    int oerrno = errno;
3251
3252    fprintf(stderr, "%s: Error %d, errno %d: ", xterm_name, code, oerrno);
3253    fprintf(stderr, "%s\n", SysErrorMsg(oerrno));
3254    fprintf(stderr, "Reason: %s\n", SysReasonMsg(code));
3255
3256    Cleanup(code);
3257}
3258
3259/*
3260 * cleanup by sending SIGHUP to client processes
3261 */
3262void
3263Cleanup(int code)
3264{
3265    static Bool cleaning;
3266    TScreen *screen = TScreenOf(term);
3267
3268    /*
3269     * Process "-hold" and session cleanup only for a normal exit.
3270     */
3271    if (code == 0) {
3272	if (cleaning) {
3273	    hold_screen = 0;
3274	    return;
3275	}
3276
3277	cleaning = True;
3278	need_cleanup = False;
3279
3280	TRACE(("Cleanup %d\n", code));
3281
3282	if (hold_screen) {
3283	    hold_screen = 2;
3284	    while (hold_screen) {
3285		xevents();
3286		Sleep(10);
3287	    }
3288	}
3289#if OPT_SESSION_MGT
3290	if (resource.sessionMgt) {
3291	    XtVaSetValues(toplevel,
3292			  XtNjoinSession, False,
3293			  (XtPointer *) 0);
3294	}
3295#endif
3296    }
3297
3298    if (screen->pid > 1) {
3299	(void) kill_process_group(screen->pid, SIGHUP);
3300    }
3301    Exit(code);
3302}
3303
3304#ifndef VMS
3305char *
3306xtermFindShell(char *leaf, Bool warning)
3307{
3308    char *s;
3309    char *d;
3310    char *tmp;
3311    char *result = leaf;
3312
3313    TRACE(("xtermFindShell(%s)\n", leaf));
3314    if (*result != '\0' && strchr("+/-", *result) == 0) {
3315	/* find it in $PATH */
3316	if ((s = x_getenv("PATH")) != 0) {
3317	    if ((tmp = TypeMallocN(char, strlen(leaf) + strlen(s) + 1)) != 0) {
3318		Bool found = False;
3319		while (*s != '\0') {
3320		    strcpy(tmp, s);
3321		    for (d = tmp;; ++d) {
3322			if (*d == ':' || *d == '\0') {
3323			    int skip = (*d != '\0');
3324			    *d = '/';
3325			    strcpy(d + 1, leaf);
3326			    if (skip)
3327				++d;
3328			    s += (d - tmp);
3329			    if (*tmp == '/'
3330				&& strstr(tmp, "..") == 0
3331				&& access(tmp, X_OK) == 0) {
3332				result = x_strdup(tmp);
3333				found = True;
3334			    }
3335			    break;
3336			}
3337			if (found)
3338			    break;
3339		    }
3340		    if (found)
3341			break;
3342		}
3343		free(tmp);
3344	    }
3345	}
3346    }
3347    TRACE(("...xtermFindShell(%s)\n", result));
3348    if (*result != '/'
3349	|| strstr(result, "..") != 0
3350	|| access(result, X_OK) != 0) {
3351	if (warning)
3352	    fprintf(stderr, "No absolute path found for shell: %s\n", result);
3353	result = 0;
3354    }
3355    return result;
3356}
3357#endif /* VMS */
3358
3359#define ENV_HUNK(n)	((((n) + 1) | 31) + 1)
3360
3361/*
3362 * copy the environment before Setenv'ing.
3363 */
3364void
3365xtermCopyEnv(char **oldenv)
3366{
3367    unsigned size;
3368    char **newenv;
3369
3370    for (size = 0; oldenv[size] != NULL; size++) {
3371	;
3372    }
3373
3374    newenv = TypeCallocN(char *, ENV_HUNK(size));
3375    memmove(newenv, oldenv, size * sizeof(char *));
3376    environ = newenv;
3377}
3378
3379/*
3380 * sets the value of var to be arg in the Unix 4.2 BSD environment env.
3381 * Var should end with '=' (bindings are of the form "var=value").
3382 * This procedure assumes the memory for the first level of environ
3383 * was allocated using calloc, with enough extra room at the end so not
3384 * to have to do a realloc().
3385 */
3386void
3387xtermSetenv(char *var, char *value)
3388{
3389    if (value != 0) {
3390	char *test;
3391	int envindex = 0;
3392	size_t len = strlen(var);
3393	int found = -1;
3394
3395	TRACE(("xtermSetenv(%s=%s)\n", var, value));
3396
3397	while ((test = environ[envindex]) != NULL) {
3398	    if (strncmp(test, var, len) == 0 && test[len] == '=') {
3399		found = envindex;
3400		break;
3401	    }
3402	    envindex++;
3403	}
3404
3405	if (found < 0) {
3406	    unsigned need = ENV_HUNK(envindex + 1);
3407	    unsigned have = ENV_HUNK(envindex);
3408
3409	    if (need > have) {
3410		char **newenv;
3411		newenv = TypeMallocN(char *, need);
3412		if (newenv == 0) {
3413		    fprintf(stderr, "Cannot increase environment\n");
3414		    return;
3415		}
3416		memmove(newenv, environ, have * sizeof(*newenv));
3417		free(environ);
3418		environ = newenv;
3419	    }
3420
3421	    found = envindex;
3422	    environ[found + 1] = NULL;
3423	    environ = environ;
3424	}
3425
3426	environ[found] = CastMallocN(char, 1 + len + strlen(value));
3427	if (environ[found] == 0) {
3428	    fprintf(stderr, "Cannot allocate environment %s\n", var);
3429	    return;
3430	}
3431	sprintf(environ[found], "%s=%s", var, value);
3432    }
3433}
3434
3435/*ARGSUSED*/
3436int
3437xerror(Display * d, XErrorEvent * ev)
3438{
3439    fprintf(stderr, "%s:  warning, error event received:\n", xterm_name);
3440    (void) XmuPrintDefaultErrorMessage(d, ev, stderr);
3441    Exit(ERROR_XERROR);
3442    return 0;			/* appease the compiler */
3443}
3444
3445/*ARGSUSED*/
3446int
3447xioerror(Display * dpy)
3448{
3449    int the_error = errno;
3450
3451    (void) fprintf(stderr,
3452		   "%s:  fatal IO error %d (%s) or KillClient on X server \"%s\"\r\n",
3453		   xterm_name, the_error, SysErrorMsg(the_error),
3454		   DisplayString(dpy));
3455
3456    Exit(ERROR_XIOERROR);
3457    return 0;			/* appease the compiler */
3458}
3459
3460void
3461xt_error(String message)
3462{
3463    (void) fprintf(stderr, "%s Xt error: %s\n", ProgramName, message);
3464
3465    /*
3466     * Check for the obvious - Xt does a poor job of reporting this.
3467     */
3468    if (x_getenv("DISPLAY") == 0) {
3469	fprintf(stderr, "%s:  DISPLAY is not set\n", ProgramName);
3470    }
3471    exit(1);
3472}
3473
3474int
3475XStrCmp(char *s1, char *s2)
3476{
3477    if (s1 && s2)
3478	return (strcmp(s1, s2));
3479    if (s1 && *s1)
3480	return (1);
3481    if (s2 && *s2)
3482	return (-1);
3483    return (0);
3484}
3485
3486#if OPT_TEK4014
3487static void
3488withdraw_window(Display * dpy, Window w, int scr)
3489{
3490    TRACE(("withdraw_window %#lx\n", (long) w));
3491    (void) XmuUpdateMapHints(dpy, w, NULL);
3492    XWithdrawWindow(dpy, w, scr);
3493    return;
3494}
3495#endif
3496
3497void
3498set_vt_visibility(Bool on)
3499{
3500    TScreen *screen = TScreenOf(term);
3501
3502    TRACE(("set_vt_visibility(%d)\n", on));
3503    if (on) {
3504	if (!screen->Vshow && term) {
3505	    VTInit();
3506	    XtMapWidget(XtParent(term));
3507#if OPT_TOOLBAR
3508	    /* we need both of these during initialization */
3509	    XtMapWidget(SHELL_OF(term));
3510	    ShowToolbar(resource.toolBar);
3511#endif
3512	    screen->Vshow = True;
3513	}
3514    }
3515#if OPT_TEK4014
3516    else {
3517	if (screen->Vshow && term) {
3518	    withdraw_window(XtDisplay(term),
3519			    VShellWindow,
3520			    XScreenNumberOfScreen(XtScreen(term)));
3521	    screen->Vshow = False;
3522	}
3523    }
3524    set_vthide_sensitivity();
3525    set_tekhide_sensitivity();
3526    update_vttekmode();
3527    update_tekshow();
3528    update_vtshow();
3529#endif
3530    return;
3531}
3532
3533#if OPT_TEK4014
3534void
3535set_tek_visibility(Bool on)
3536{
3537    TRACE(("set_tek_visibility(%d)\n", on));
3538
3539    if (on) {
3540	if (!TEK4014_SHOWN(term) && (tekWidget || TekInit())) {
3541	    Widget tekParent = SHELL_OF(tekWidget);
3542	    XtRealizeWidget(tekParent);
3543	    XtMapWidget(XtParent(tekWidget));
3544#if OPT_TOOLBAR
3545	    /* we need both of these during initialization */
3546	    XtMapWidget(tekParent);
3547	    XtMapWidget(tekWidget);
3548#endif
3549	    XtOverrideTranslations(tekParent,
3550				   XtParseTranslationTable
3551				   ("<Message>WM_PROTOCOLS: DeleteWindow()"));
3552	    (void) XSetWMProtocols(XtDisplay(tekParent),
3553				   XtWindow(tekParent),
3554				   &wm_delete_window, 1);
3555	    TEK4014_SHOWN(term) = True;
3556	}
3557    } else {
3558	if (TEK4014_SHOWN(term) && tekWidget) {
3559	    withdraw_window(XtDisplay(tekWidget),
3560			    TShellWindow,
3561			    XScreenNumberOfScreen(XtScreen(tekWidget)));
3562	    TEK4014_SHOWN(term) = False;
3563	}
3564    }
3565    set_tekhide_sensitivity();
3566    set_vthide_sensitivity();
3567    update_vtshow();
3568    update_tekshow();
3569    update_vttekmode();
3570    return;
3571}
3572
3573void
3574end_tek_mode(void)
3575{
3576    if (TEK4014_ACTIVE(term)) {
3577	FlushLog(&(term->screen));
3578	longjmp(Tekend, 1);
3579    }
3580    return;
3581}
3582
3583void
3584end_vt_mode(void)
3585{
3586    if (!TEK4014_ACTIVE(term)) {
3587	FlushLog(&(term->screen));
3588	TEK4014_ACTIVE(term) = True;
3589	longjmp(VTend, 1);
3590    }
3591    return;
3592}
3593
3594void
3595switch_modes(Bool tovt)		/* if true, then become vt mode */
3596{
3597    if (tovt) {
3598	if (tekRefreshList)
3599	    TekRefresh(tekWidget);
3600	end_tek_mode();		/* WARNING: this does a longjmp... */
3601    } else {
3602	end_vt_mode();		/* WARNING: this does a longjmp... */
3603    }
3604}
3605
3606void
3607hide_vt_window(void)
3608{
3609    set_vt_visibility(False);
3610    if (!TEK4014_ACTIVE(term))
3611	switch_modes(False);	/* switch to tek mode */
3612}
3613
3614void
3615hide_tek_window(void)
3616{
3617    set_tek_visibility(False);
3618    tekRefreshList = (TekLink *) 0;
3619    if (TEK4014_ACTIVE(term))
3620	switch_modes(True);	/* does longjmp to vt mode */
3621}
3622#endif /* OPT_TEK4014 */
3623
3624static const char *
3625skip_punct(const char *s)
3626{
3627    while (*s == '-' || *s == '/' || *s == '+' || *s == '#' || *s == '%') {
3628	++s;
3629    }
3630    return s;
3631}
3632
3633static int
3634cmp_options(const void *a, const void *b)
3635{
3636    const char *s1 = skip_punct(((const OptionHelp *) a)->opt);
3637    const char *s2 = skip_punct(((const OptionHelp *) b)->opt);
3638    return strcmp(s1, s2);
3639}
3640
3641static int
3642cmp_resources(const void *a, const void *b)
3643{
3644    return strcmp(((const XrmOptionDescRec *) a)->option,
3645		  ((const XrmOptionDescRec *) b)->option);
3646}
3647
3648XrmOptionDescRec *
3649sortedOptDescs(XrmOptionDescRec * descs, Cardinal res_count)
3650{
3651    static XrmOptionDescRec *res_array = 0;
3652
3653#ifdef NO_LEAKS
3654    if (descs == 0 && res_array != 0) {
3655	free(res_array);
3656	res_array = 0;
3657    } else
3658#endif
3659    if (res_array == 0) {
3660	Cardinal j;
3661
3662	/* make a sorted index to 'resources' */
3663	res_array = TypeCallocN(XrmOptionDescRec, res_count);
3664	for (j = 0; j < res_count; j++)
3665	    res_array[j] = descs[j];
3666	qsort(res_array, res_count, sizeof(*res_array), cmp_resources);
3667    }
3668    return res_array;
3669}
3670
3671/*
3672 * The first time this is called, construct sorted index to the main program's
3673 * list of options, taking into account the on/off options which will be
3674 * compressed into one token.  It's a lot simpler to do it this way than
3675 * maintain the list in sorted form with lots of ifdef's.
3676 */
3677OptionHelp *
3678sortedOpts(OptionHelp * options, XrmOptionDescRec * descs, Cardinal numDescs)
3679{
3680    static OptionHelp *opt_array = 0;
3681
3682#ifdef NO_LEAKS
3683    if (descs == 0 && opt_array != 0) {
3684	sortedOptDescs(descs, numDescs);
3685	free(opt_array);
3686	opt_array = 0;
3687	return 0;
3688    } else if (options == 0 || descs == 0) {
3689	return 0;
3690    }
3691#endif
3692
3693    if (opt_array == 0) {
3694	Cardinal opt_count, j;
3695#if OPT_TRACE
3696	Cardinal k;
3697	XrmOptionDescRec *res_array = sortedOptDescs(descs, numDescs);
3698	int code;
3699	char *mesg;
3700#else
3701	(void) descs;
3702	(void) numDescs;
3703#endif
3704
3705	/* count 'options' and make a sorted index to it */
3706	for (opt_count = 0; options[opt_count].opt != 0; ++opt_count) {
3707	    ;
3708	}
3709	opt_array = TypeCallocN(OptionHelp, opt_count + 1);
3710	for (j = 0; j < opt_count; j++)
3711	    opt_array[j] = options[j];
3712	qsort(opt_array, opt_count, sizeof(OptionHelp), cmp_options);
3713
3714	/* supply the "turn on/off" strings if needed */
3715#if OPT_TRACE
3716	for (j = 0; j < opt_count; j++) {
3717	    if (!strncmp(opt_array[j].opt, "-/+", 3)) {
3718		char *name = opt_array[j].opt + 3;
3719		for (k = 0; k < numDescs; ++k) {
3720		    char *value = res_array[k].value;
3721		    if (res_array[k].option[0] == '-') {
3722			code = -1;
3723		    } else if (res_array[k].option[0] == '+') {
3724			code = 1;
3725		    } else {
3726			code = 0;
3727		    }
3728		    if (x_strindex(opt_array[j].desc, "inhibit") != 0)
3729			code = -code;
3730		    if (code != 0
3731			&& res_array[k].value != 0
3732			&& !strcmp(name, res_array[k].option + 1)) {
3733			if (((code < 0) && !strcmp(value, "on"))
3734			    || ((code > 0) && !strcmp(value, "off"))
3735			    || ((code > 0) && !strcmp(value, "0"))) {
3736			    mesg = "turn on/off";
3737			} else {
3738			    mesg = "turn off/on";
3739			}
3740			if (strncmp(mesg, opt_array[j].desc, strlen(mesg))) {
3741			    if (strncmp(opt_array[j].desc, "turn ", 5)) {
3742				char *s = CastMallocN(char,
3743						      strlen(mesg)
3744						      + 1
3745						      + strlen(opt_array[j].desc));
3746				if (s != 0) {
3747				    sprintf(s, "%s %s", mesg, opt_array[j].desc);
3748				    opt_array[j].desc = s;
3749				}
3750			    } else {
3751				TRACE(("OOPS "));
3752			    }
3753			}
3754			TRACE(("%s: %s %s: %s (%s)\n",
3755			       mesg,
3756			       res_array[k].option,
3757			       res_array[k].value,
3758			       opt_array[j].opt,
3759			       opt_array[j].desc));
3760			break;
3761		    }
3762		}
3763	    }
3764	}
3765#endif
3766    }
3767    return opt_array;
3768}
3769
3770/*
3771 * Report the character-type locale that xterm was started in.
3772 */
3773char *
3774xtermEnvLocale(void)
3775{
3776    static char *result;
3777
3778    if (result == 0) {
3779	if ((result = x_nonempty(setlocale(LC_CTYPE, 0))) == 0) {
3780	    result = "C";
3781	}
3782	TRACE(("xtermEnvLocale ->%s\n", result));
3783    }
3784    return result;
3785}
3786
3787char *
3788xtermEnvEncoding(void)
3789{
3790    static char *result;
3791
3792    if (result == 0) {
3793#ifdef HAVE_LANGINFO_CODESET
3794	result = nl_langinfo(CODESET);
3795#else
3796	char *locale = xtermEnvLocale();
3797	if (!strcmp(locale, "C") || !strcmp(locale, "POSIX")) {
3798	    result = "ASCII";
3799	} else {
3800	    result = "ISO-8859-1";
3801	}
3802#endif
3803	TRACE(("xtermEnvEncoding ->%s\n", result));
3804    }
3805    return result;
3806}
3807
3808#if OPT_WIDE_CHARS
3809/*
3810 * Tell whether xterm was started in a locale that uses UTF-8 encoding for
3811 * characters.  That environment is inherited by subprocesses and used in
3812 * various library calls.
3813 */
3814Bool
3815xtermEnvUTF8(void)
3816{
3817    static Bool init = False;
3818    static Bool result = False;
3819
3820    if (!init) {
3821	init = True;
3822#ifdef HAVE_LANGINFO_CODESET
3823	result = (strcmp(xtermEnvEncoding(), "UTF-8") == 0);
3824#else
3825	result = (strstr(xtermEnvLocale(), "UTF-8") != NULL);
3826#endif
3827	TRACE(("xtermEnvUTF8 ->%s\n", BtoS(result)));
3828    }
3829    return result;
3830}
3831#endif /* OPT_WIDE_CHARS */
3832
3833/*
3834 * Returns the version-string used in the "-v' message as well as a few other
3835 * places.  It is derived (when possible) from the __vendorversion__ symbol
3836 * that some newer imake configurations define.
3837 */
3838char *
3839xtermVersion(void)
3840{
3841    static char *result;
3842    if (result == 0) {
3843	char *vendor = __vendorversion__;
3844	char first[BUFSIZ];
3845	char second[BUFSIZ];
3846
3847	result = CastMallocN(char, strlen(vendor) + 9);
3848	if (result == 0)
3849	    result = vendor;
3850	else {
3851	    /* some vendors leave trash in this string */
3852	    for (;;) {
3853		if (!strncmp(vendor, "Version ", 8))
3854		    vendor += 8;
3855		else if (isspace(CharOf(*vendor)))
3856		    ++vendor;
3857		else
3858		    break;
3859	    }
3860	    if (strlen(vendor) < BUFSIZ &&
3861		sscanf(vendor, "%[0-9.] %[A-Za-z_0-9.]", first, second) == 2)
3862		sprintf(result, "%s %s(%d)", second, first, XTERM_PATCH);
3863	    else
3864		sprintf(result, "%s(%d)", vendor, XTERM_PATCH);
3865	}
3866    }
3867    return result;
3868}
3869