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