Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* $XTermId: misc.c,v 1.658 2013/02/03 23:18:38 tom Exp $ */ | |
| /* | |
| * Copyright 1999-2012,2013 by Thomas E. Dickey | |
| * | |
| * All Rights Reserved | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the | |
| * "Software"), to deal in the Software without restriction, including | |
| * without limitation the rights to use, copy, modify, merge, publish, | |
| * distribute, sublicense, and/or sell copies of the Software, and to | |
| * permit persons to whom the Software is furnished to do so, subject to | |
| * the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included | |
| * in all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY | |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * Except as contained in this notice, the name(s) of the above copyright | |
| * holders shall not be used in advertising or otherwise to promote the | |
| * sale, use or other dealings in this Software without prior written | |
| * authorization. | |
| * | |
| * | |
| * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. | |
| * | |
| * All Rights Reserved | |
| * | |
| * Permission to use, copy, modify, and distribute this software and its | |
| * documentation for any purpose and without fee is hereby granted, | |
| * provided that the above copyright notice appear in all copies and that | |
| * both that copyright notice and this permission notice appear in | |
| * supporting documentation, and that the name of Digital Equipment | |
| * Corporation not be used in advertising or publicity pertaining to | |
| * distribution of the software without specific, written prior permission. | |
| * | |
| * | |
| * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING | |
| * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL | |
| * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR | |
| * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
| * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, | |
| * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS | |
| * SOFTWARE. | |
| */ | |
| #include <version.h> | |
| #include <main.h> | |
| #include <xterm.h> | |
| #include <sys/stat.h> | |
| #include <stdio.h> | |
| #include <stdarg.h> | |
| #include <signal.h> | |
| #include <ctype.h> | |
| #include <pwd.h> | |
| #include <sys/wait.h> | |
| #include <X11/keysym.h> | |
| #include <X11/Xatom.h> | |
| #include <X11/cursorfont.h> | |
| #include <X11/Xlocale.h> | |
| #include <X11/Xmu/Error.h> | |
| #include <X11/Xmu/SysUtil.h> | |
| #include <X11/Xmu/WinUtil.h> | |
| #include <X11/Xmu/Xmu.h> | |
| #if HAVE_X11_SUNKEYSYM_H | |
| #include <X11/Sunkeysym.h> | |
| #endif | |
| #ifdef HAVE_LIBXPM | |
| #include <X11/xpm.h> | |
| #endif | |
| #ifdef HAVE_LANGINFO_CODESET | |
| #include <langinfo.h> | |
| #endif | |
| #include <xutf8.h> | |
| #include <data.h> | |
| #include <error.h> | |
| #include <menu.h> | |
| #include <fontutils.h> | |
| #include <xcharmouse.h> | |
| #include <xstrings.h> | |
| #include <xtermcap.h> | |
| #include <VTparse.h> | |
| #include <assert.h> | |
| #if (XtSpecificationRelease < 6) | |
| #ifndef X_GETTIMEOFDAY | |
| #define X_GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *)0) | |
| #endif | |
| #endif | |
| #ifdef VMS | |
| #define XTERM_VMS_LOGFILE "SYS$SCRATCH:XTERM_LOG.TXT" | |
| #ifdef ALLOWLOGFILEEXEC | |
| #undef ALLOWLOGFILEEXEC | |
| #endif | |
| #endif /* VMS */ | |
| #if OPT_TEK4014 | |
| #define OUR_EVENT(event,Type) \ | |
| (event.type == Type && \ | |
| (event.xcrossing.window == XtWindow(XtParent(xw)) || \ | |
| (tekWidget && \ | |
| event.xcrossing.window == XtWindow(XtParent(tekWidget))))) | |
| #else | |
| #define OUR_EVENT(event,Type) \ | |
| (event.type == Type && \ | |
| (event.xcrossing.window == XtWindow(XtParent(xw)))) | |
| #endif | |
| static Boolean xtermAllocColor(XtermWidget, XColor *, const char *); | |
| static Cursor make_hidden_cursor(XtermWidget); | |
| static char emptyString[] = ""; | |
| #if OPT_EXEC_XTERM | |
| /* Like readlink(2), but returns a malloc()ed buffer, or NULL on | |
| error; adapted from libc docs */ | |
| static char * | |
| Readlink(const char *filename) | |
| { | |
| char *buf = NULL; | |
| size_t size = 100; | |
| int n; | |
| for (;;) { | |
| buf = TypeRealloc(char, size, buf); | |
| memset(buf, 0, size); | |
| n = (int) readlink(filename, buf, size); | |
| if (n < 0) { | |
| free(buf); | |
| return NULL; | |
| } | |
| if ((unsigned) n < size) { | |
| return buf; | |
| } | |
| size *= 2; | |
| } | |
| } | |
| #endif /* OPT_EXEC_XTERM */ | |
| static void | |
| Sleep(int msec) | |
| { | |
| static struct timeval select_timeout; | |
| select_timeout.tv_sec = 0; | |
| select_timeout.tv_usec = msec * 1000; | |
| select(0, 0, 0, 0, &select_timeout); | |
| } | |
| static void | |
| selectwindow(XtermWidget xw, int flag) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("selectwindow(%d) flag=%d\n", screen->select, flag)); | |
| #if OPT_TEK4014 | |
| if (TEK4014_ACTIVE(xw)) { | |
| if (!Ttoggled) | |
| TCursorToggle(tekWidget, TOGGLE); | |
| screen->select |= flag; | |
| if (!Ttoggled) | |
| TCursorToggle(tekWidget, TOGGLE); | |
| } else | |
| #endif | |
| { | |
| #if OPT_I18N_SUPPORT && OPT_INPUT_METHOD | |
| TInput *input = lookupTInput(xw, (Widget) xw); | |
| if (input && input->xic) | |
| XSetICFocus(input->xic); | |
| #endif | |
| if (screen->cursor_state && CursorMoved(screen)) | |
| HideCursor(); | |
| screen->select |= flag; | |
| if (screen->cursor_state) | |
| ShowCursor(); | |
| } | |
| GetScrollLock(screen); | |
| } | |
| static void | |
| unselectwindow(XtermWidget xw, int flag) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("unselectwindow(%d) flag=%d\n", screen->select, flag)); | |
| if (screen->hide_pointer && screen->pointer_mode < pFocused) { | |
| screen->hide_pointer = False; | |
| xtermDisplayCursor(xw); | |
| } | |
| if (!screen->always_highlight) { | |
| #if OPT_TEK4014 | |
| if (TEK4014_ACTIVE(xw)) { | |
| if (!Ttoggled) | |
| TCursorToggle(tekWidget, TOGGLE); | |
| screen->select &= ~flag; | |
| if (!Ttoggled) | |
| TCursorToggle(tekWidget, TOGGLE); | |
| } else | |
| #endif | |
| { | |
| #if OPT_I18N_SUPPORT && OPT_INPUT_METHOD | |
| TInput *input = lookupTInput(xw, (Widget) xw); | |
| if (input && input->xic) | |
| XUnsetICFocus(input->xic); | |
| #endif | |
| screen->select &= ~flag; | |
| if (screen->cursor_state && CursorMoved(screen)) | |
| HideCursor(); | |
| if (screen->cursor_state) | |
| ShowCursor(); | |
| } | |
| } | |
| } | |
| static void | |
| DoSpecialEnterNotify(XtermWidget xw, XEnterWindowEvent * ev) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("DoSpecialEnterNotify(%d)\n", screen->select)); | |
| TRACE_FOCUS(xw, ev); | |
| if (((ev->detail) != NotifyInferior) && | |
| ev->focus && | |
| !(screen->select & FOCUS)) | |
| selectwindow(xw, INWINDOW); | |
| } | |
| static void | |
| DoSpecialLeaveNotify(XtermWidget xw, XEnterWindowEvent * ev) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("DoSpecialLeaveNotify(%d)\n", screen->select)); | |
| TRACE_FOCUS(xw, ev); | |
| if (((ev->detail) != NotifyInferior) && | |
| ev->focus && | |
| !(screen->select & FOCUS)) | |
| unselectwindow(xw, INWINDOW); | |
| } | |
| #ifndef XUrgencyHint | |
| #define XUrgencyHint (1L << 8) /* X11R5 does not define */ | |
| #endif | |
| static void | |
| setXUrgency(XtermWidget xw, Bool enable) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| if (screen->bellIsUrgent) { | |
| XWMHints *h = XGetWMHints(screen->display, VShellWindow(xw)); | |
| if (h != 0) { | |
| if (enable && !(screen->select & FOCUS)) { | |
| h->flags |= XUrgencyHint; | |
| } else { | |
| h->flags &= ~XUrgencyHint; | |
| } | |
| XSetWMHints(screen->display, VShellWindow(xw), h); | |
| } | |
| } | |
| } | |
| void | |
| do_xevents(void) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| if (xtermAppPending() | |
| || | |
| #if defined(VMS) || defined(__VMS) | |
| screen->display->qlen > 0 | |
| #else | |
| GetBytesAvailable(ConnectionNumber(screen->display)) > 0 | |
| #endif | |
| ) | |
| xevents(); | |
| } | |
| void | |
| xtermDisplayCursor(XtermWidget xw) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| if (screen->Vshow) { | |
| if (screen->hide_pointer) { | |
| TRACE(("Display hidden_cursor\n")); | |
| XDefineCursor(screen->display, VWindow(screen), screen->hidden_cursor); | |
| } else { | |
| TRACE(("Display pointer_cursor\n")); | |
| recolor_cursor(screen, | |
| screen->pointer_cursor, | |
| T_COLOR(screen, MOUSE_FG), | |
| T_COLOR(screen, MOUSE_BG)); | |
| XDefineCursor(screen->display, VWindow(screen), screen->pointer_cursor); | |
| } | |
| } | |
| } | |
| void | |
| xtermShowPointer(XtermWidget xw, Bool enable) | |
| { | |
| static int tried = -1; | |
| TScreen *screen = TScreenOf(xw); | |
| #if OPT_TEK4014 | |
| if (TEK4014_SHOWN(xw)) | |
| enable = True; | |
| #endif | |
| /* | |
| * Whether we actually hide the pointer depends on the pointer-mode and | |
| * the mouse-mode: | |
| */ | |
| if (!enable) { | |
| switch (screen->pointer_mode) { | |
| case pNever: | |
| enable = True; | |
| break; | |
| case pNoMouse: | |
| if (screen->send_mouse_pos != MOUSE_OFF) | |
| enable = True; | |
| break; | |
| case pAlways: | |
| case pFocused: | |
| break; | |
| } | |
| } | |
| if (enable) { | |
| if (screen->hide_pointer) { | |
| screen->hide_pointer = False; | |
| xtermDisplayCursor(xw); | |
| switch (screen->send_mouse_pos) { | |
| case ANY_EVENT_MOUSE: | |
| break; | |
| default: | |
| MotionOff(screen, xw); | |
| break; | |
| } | |
| } | |
| } else if (!(screen->hide_pointer) && (tried <= 0)) { | |
| if (screen->hidden_cursor == 0) { | |
| screen->hidden_cursor = make_hidden_cursor(xw); | |
| } | |
| if (screen->hidden_cursor == 0) { | |
| tried = 1; | |
| } else { | |
| tried = 0; | |
| screen->hide_pointer = True; | |
| xtermDisplayCursor(xw); | |
| MotionOn(screen, xw); | |
| } | |
| } | |
| } | |
| #if OPT_TRACE | |
| static void | |
| TraceExposeEvent(XEvent * arg) | |
| { | |
| XExposeEvent *event = (XExposeEvent *) arg; | |
| TRACE(("pending Expose %ld %d: %d,%d %dx%d %#lx\n", | |
| event->serial, | |
| event->count, | |
| event->y, | |
| event->x, | |
| event->height, | |
| event->width, | |
| event->window)); | |
| } | |
| #else | |
| #define TraceExposeEvent(event) /* nothing */ | |
| #endif | |
| /* true if p contains q */ | |
| #define ExposeContains(p,q) \ | |
| ((p)->y <= (q)->y \ | |
| && (p)->x <= (q)->x \ | |
| && ((p)->y + (p)->height) >= ((q)->y + (q)->height) \ | |
| && ((p)->x + (p)->width) >= ((q)->x + (q)->width)) | |
| static XtInputMask | |
| mergeExposeEvents(XEvent * target) | |
| { | |
| XEvent next_event; | |
| XExposeEvent *p, *q; | |
| TRACE(("pending Expose...?\n")); | |
| TraceExposeEvent(target); | |
| XtAppNextEvent(app_con, target); | |
| p = (XExposeEvent *) target; | |
| while (XtAppPending(app_con) | |
| && XtAppPeekEvent(app_con, &next_event) | |
| && next_event.type == Expose) { | |
| Boolean merge_this = False; | |
| TraceExposeEvent(&next_event); | |
| q = (XExposeEvent *) (&next_event); | |
| XtAppNextEvent(app_con, &next_event); | |
| /* | |
| * If either window is contained within the other, merge the events. | |
| * The traces show that there are also cases where a full repaint of | |
| * a window is broken into 3 or more rectangles, which do not arrive | |
| * in the same instant. We could merge those if xterm were modified | |
| * to skim several events ahead. | |
| */ | |
| if (p->window == q->window) { | |
| if (ExposeContains(p, q)) { | |
| TRACE(("pending Expose...merged forward\n")); | |
| merge_this = True; | |
| next_event = *target; | |
| } else if (ExposeContains(q, p)) { | |
| TRACE(("pending Expose...merged backward\n")); | |
| merge_this = True; | |
| } | |
| } | |
| if (!merge_this) { | |
| XtDispatchEvent(target); | |
| } | |
| *target = next_event; | |
| } | |
| XtDispatchEvent(target); | |
| return XtAppPending(app_con); | |
| } | |
| #if OPT_TRACE | |
| static void | |
| TraceConfigureEvent(XEvent * arg) | |
| { | |
| XConfigureEvent *event = (XConfigureEvent *) arg; | |
| TRACE(("pending Configure %ld %d,%d %dx%d %#lx\n", | |
| event->serial, | |
| event->y, | |
| event->x, | |
| event->height, | |
| event->width, | |
| event->window)); | |
| } | |
| #else | |
| #define TraceConfigureEvent(event) /* nothing */ | |
| #endif | |
| /* | |
| * On entry, we have peeked at the event queue and see a configure-notify | |
| * event. Remove that from the queue so we can look further. | |
| * | |
| * Then, as long as there is a configure-notify event in the queue, remove | |
| * that. If the adjacent events are for different windows, process the older | |
| * event and update the event used for comparing windows. If they are for the | |
| * same window, only the newer event is of interest. | |
| * | |
| * Finally, process the (remaining) configure-notify event. | |
| */ | |
| static XtInputMask | |
| mergeConfigureEvents(XEvent * target) | |
| { | |
| XEvent next_event; | |
| XConfigureEvent *p, *q; | |
| XtAppNextEvent(app_con, target); | |
| p = (XConfigureEvent *) target; | |
| TRACE(("pending Configure...?%s\n", XtAppPending(app_con) ? "yes" : "no")); | |
| TraceConfigureEvent(target); | |
| if (XtAppPending(app_con) | |
| && XtAppPeekEvent(app_con, &next_event) | |
| && next_event.type == ConfigureNotify) { | |
| Boolean merge_this = False; | |
| TraceConfigureEvent(&next_event); | |
| XtAppNextEvent(app_con, &next_event); | |
| q = (XConfigureEvent *) (&next_event); | |
| if (p->window == q->window) { | |
| TRACE(("pending Configure...merged\n")); | |
| merge_this = True; | |
| } | |
| if (!merge_this) { | |
| TRACE(("pending Configure...skipped\n")); | |
| XtDispatchEvent(target); | |
| } | |
| *target = next_event; | |
| } | |
| XtDispatchEvent(target); | |
| return XtAppPending(app_con); | |
| } | |
| /* | |
| * Filter redundant Expose- and ConfigureNotify-events. This is limited to | |
| * adjacent events because there could be other event-loop processing. Absent | |
| * that limitation, it might be possible to scan ahead to find when the screen | |
| * would be completely updated, skipping unnecessary re-repainting before that | |
| * point. | |
| * | |
| * Note: all cases should allow doing XtAppNextEvent if result is true. | |
| */ | |
| XtInputMask | |
| xtermAppPending(void) | |
| { | |
| XtInputMask result = XtAppPending(app_con); | |
| XEvent this_event; | |
| while (result && XtAppPeekEvent(app_con, &this_event)) { | |
| if (this_event.type == Expose) { | |
| result = mergeExposeEvents(&this_event); | |
| } else if (this_event.type == ConfigureNotify) { | |
| result = mergeConfigureEvents(&this_event); | |
| } else { | |
| TRACE(("pending %s\n", visibleEventType(this_event.type))); | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| void | |
| xevents(void) | |
| { | |
| XtermWidget xw = term; | |
| TScreen *screen = TScreenOf(xw); | |
| XEvent event; | |
| XtInputMask input_mask; | |
| if (need_cleanup) | |
| NormalExit(); | |
| if (screen->scroll_amt) | |
| FlushScroll(xw); | |
| /* | |
| * process timeouts, relying on the fact that XtAppProcessEvent | |
| * will process the timeout and return without blockng on the | |
| * XEvent queue. Other sources i.e., the pty are handled elsewhere | |
| * with select(). | |
| */ | |
| while ((input_mask = xtermAppPending()) != 0) { | |
| if (input_mask & XtIMTimer) | |
| XtAppProcessEvent(app_con, (XtInputMask) XtIMTimer); | |
| #if OPT_SESSION_MGT | |
| /* | |
| * Session management events are alternative input events. Deal with | |
| * them in the same way. | |
| */ | |
| else if (input_mask & XtIMAlternateInput) | |
| XtAppProcessEvent(app_con, (XtInputMask) XtIMAlternateInput); | |
| #endif | |
| else | |
| break; | |
| } | |
| /* | |
| * If there's no XEvents, don't wait around... | |
| */ | |
| if ((input_mask & XtIMXEvent) != XtIMXEvent) | |
| return; | |
| do { | |
| /* | |
| * This check makes xterm hang when in mouse hilite tracking mode. | |
| * We simply ignore all events except for those not passed down to | |
| * this function, e.g., those handled in in_put(). | |
| */ | |
| if (screen->waitingForTrackInfo) { | |
| Sleep(10); | |
| return; | |
| } | |
| XtAppNextEvent(app_con, &event); | |
| /* | |
| * Hack to get around problems with the toolkit throwing away | |
| * eventing during the exclusive grab of the menu popup. By | |
| * looking at the event ourselves we make sure that we can | |
| * do the right thing. | |
| */ | |
| if (OUR_EVENT(event, EnterNotify)) { | |
| DoSpecialEnterNotify(xw, &event.xcrossing); | |
| } else if (OUR_EVENT(event, LeaveNotify)) { | |
| DoSpecialLeaveNotify(xw, &event.xcrossing); | |
| } else if ((screen->send_mouse_pos == ANY_EVENT_MOUSE | |
| #if OPT_DEC_LOCATOR | |
| || screen->send_mouse_pos == DEC_LOCATOR | |
| #endif /* OPT_DEC_LOCATOR */ | |
| ) | |
| && event.xany.type == MotionNotify | |
| && event.xcrossing.window == XtWindow(xw)) { | |
| SendMousePosition(xw, &event); | |
| xtermShowPointer(xw, True); | |
| continue; | |
| } | |
| /* | |
| * If the event is interesting (and not a keyboard event), turn the | |
| * mouse pointer back on. | |
| */ | |
| if (screen->hide_pointer) { | |
| if (screen->pointer_mode >= pFocused) { | |
| switch (event.xany.type) { | |
| case MotionNotify: | |
| xtermShowPointer(xw, True); | |
| break; | |
| } | |
| } else { | |
| switch (event.xany.type) { | |
| case KeyPress: | |
| case KeyRelease: | |
| case ButtonPress: | |
| case ButtonRelease: | |
| /* also these... */ | |
| case Expose: | |
| case NoExpose: | |
| case PropertyNotify: | |
| case ClientMessage: | |
| break; | |
| default: | |
| xtermShowPointer(xw, True); | |
| break; | |
| } | |
| } | |
| } | |
| if (!event.xany.send_event || | |
| screen->allowSendEvents || | |
| ((event.xany.type != KeyPress) && | |
| (event.xany.type != KeyRelease) && | |
| (event.xany.type != ButtonPress) && | |
| (event.xany.type != ButtonRelease))) { | |
| XtDispatchEvent(&event); | |
| } | |
| } while (xtermAppPending() & XtIMXEvent); | |
| } | |
| static Cursor | |
| make_hidden_cursor(XtermWidget xw) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| Cursor c; | |
| Display *dpy = screen->display; | |
| XFontStruct *fn; | |
| static XColor dummy; | |
| /* | |
| * Prefer nil2 (which is normally available) to "fixed" (which is supposed | |
| * to be "always" available), since it's a smaller glyph in case the | |
| * server insists on drawing _something_. | |
| */ | |
| TRACE(("Ask for nil2 font\n")); | |
| if ((fn = XLoadQueryFont(dpy, "nil2")) == 0) { | |
| TRACE(("...Ask for fixed font\n")); | |
| fn = XLoadQueryFont(dpy, DEFFONT); | |
| } | |
| if (fn != 0) { | |
| /* a space character seems to work as a cursor (dots are not needed) */ | |
| c = XCreateGlyphCursor(dpy, fn->fid, fn->fid, 'X', ' ', &dummy, &dummy); | |
| XFreeFont(dpy, fn); | |
| } else { | |
| c = 0; | |
| } | |
| TRACE(("XCreateGlyphCursor ->%#lx\n", c)); | |
| return (c); | |
| } | |
| Cursor | |
| make_colored_cursor(unsigned cursorindex, /* index into font */ | |
| unsigned long fg, /* pixel value */ | |
| unsigned long bg) /* pixel value */ | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| Cursor c; | |
| Display *dpy = screen->display; | |
| c = XCreateFontCursor(dpy, cursorindex); | |
| if (c != None) { | |
| recolor_cursor(screen, c, fg, bg); | |
| } | |
| return (c); | |
| } | |
| /* ARGSUSED */ | |
| void | |
| HandleKeyPressed(Widget w GCC_UNUSED, | |
| XEvent * event, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| TRACE(("Handle insert-seven-bit for %p\n", (void *) w)); | |
| Input(term, &event->xkey, False); | |
| } | |
| /* ARGSUSED */ | |
| void | |
| HandleEightBitKeyPressed(Widget w GCC_UNUSED, | |
| XEvent * event, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| TRACE(("Handle insert-eight-bit for %p\n", (void *) w)); | |
| Input(term, &event->xkey, True); | |
| } | |
| /* ARGSUSED */ | |
| void | |
| HandleStringEvent(Widget w GCC_UNUSED, | |
| XEvent * event GCC_UNUSED, | |
| String * params, | |
| Cardinal *nparams) | |
| { | |
| if (*nparams != 1) | |
| return; | |
| if ((*params)[0] == '0' && (*params)[1] == 'x' && (*params)[2] != '\0') { | |
| const char *abcdef = "ABCDEF"; | |
| const char *xxxxxx; | |
| Char c; | |
| UString p; | |
| unsigned value = 0; | |
| for (p = (UString) (*params + 2); (c = CharOf(x_toupper(*p))) != | |
| '\0'; p++) { | |
| value *= 16; | |
| if (c >= '0' && c <= '9') | |
| value += (unsigned) (c - '0'); | |
| else if ((xxxxxx = strchr(abcdef, c)) != 0) | |
| value += (unsigned) (xxxxxx - abcdef) + 10; | |
| else | |
| break; | |
| } | |
| if (c == '\0') { | |
| Char hexval[2]; | |
| hexval[0] = (Char) value; | |
| hexval[1] = 0; | |
| StringInput(term, hexval, (size_t) 1); | |
| } | |
| } else { | |
| StringInput(term, (const Char *) *params, strlen(*params)); | |
| } | |
| } | |
| #if OPT_EXEC_XTERM | |
| #ifndef PROCFS_ROOT | |
| #define PROCFS_ROOT "/proc" | |
| #endif | |
| /* ARGSUSED */ | |
| void | |
| HandleSpawnTerminal(Widget w GCC_UNUSED, | |
| XEvent * event GCC_UNUSED, | |
| String * params, | |
| Cardinal *nparams) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| char *child_cwd = NULL; | |
| char *child_exe; | |
| pid_t pid; | |
| /* | |
| * Try to find the actual program which is running in the child process. | |
| * This works for Linux. If we cannot find the program, fall back to the | |
| * xterm program (which is usually adequate). Give up if we are given only | |
| * a relative path to xterm, since that would not always match $PATH. | |
| */ | |
| child_exe = Readlink(PROCFS_ROOT "/self/exe"); | |
| if (!child_exe) { | |
| if (strncmp(ProgramName, "./", (size_t) 2) | |
| && strncmp(ProgramName, "../", (size_t) 3)) { | |
| child_exe = xtermFindShell(ProgramName, True); | |
| } else { | |
| xtermWarning("Cannot exec-xterm given \"%s\"\n", ProgramName); | |
| } | |
| if (child_exe == 0) | |
| return; | |
| } | |
| /* | |
| * Determine the current working directory of the child so that we can | |
| * spawn a new terminal in the same directory. | |
| * | |
| * If we cannot get the CWD of the child, just use our own. | |
| */ | |
| if (screen->pid) { | |
| char child_cwd_link[sizeof(PROCFS_ROOT) + 80]; | |
| sprintf(child_cwd_link, PROCFS_ROOT "/%lu/cwd", (unsigned long) screen->pid); | |
| child_cwd = Readlink(child_cwd_link); | |
| } | |
| /* The reaper will take care of cleaning up the child */ | |
| pid = fork(); | |
| if (pid == -1) { | |
| xtermWarning("Could not fork: %s\n", SysErrorMsg(errno)); | |
| } else if (!pid) { | |
| /* We are the child */ | |
| if (child_cwd) { | |
| IGNORE_RC(chdir(child_cwd)); /* We don't care if this fails */ | |
| } | |
| if (setuid(screen->uid) == -1 | |
| || setgid(screen->gid) == -1) { | |
| xtermWarning("Cannot reset uid/gid\n"); | |
| } else { | |
| unsigned myargc = *nparams + 1; | |
| char **myargv = TypeMallocN(char *, myargc + 1); | |
| unsigned n = 0; | |
| myargv[n++] = child_exe; | |
| while (n < myargc) { | |
| myargv[n++] = (char *) *params++; | |
| } | |
| myargv[n] = 0; | |
| execv(child_exe, myargv); | |
| /* If we get here, we've failed */ | |
| xtermWarning("exec of '%s': %s\n", child_exe, SysErrorMsg(errno)); | |
| } | |
| _exit(0); | |
| } | |
| /* We are the parent; clean up */ | |
| if (child_cwd) | |
| free(child_cwd); | |
| free(child_exe); | |
| } | |
| #endif /* OPT_EXEC_XTERM */ | |
| /* | |
| * Rather than sending characters to the host, put them directly into our | |
| * input queue. That lets a user have access to any of the control sequences | |
| * for a key binding. This is the equivalent of local function key support. | |
| * | |
| * NOTE: This code does not support the hexadecimal kludge used in | |
| * HandleStringEvent because it prevents us from sending an arbitrary string | |
| * (but it appears in a lot of examples - so we are stuck with it). The | |
| * standard string converter does recognize "\" for newline ("\n") and for | |
| * octal constants (e.g., "\007" for BEL). So we assume the user can make do | |
| * without a specialized converter. (Don't try to use \000, though). | |
| */ | |
| /* ARGSUSED */ | |
| void | |
| HandleInterpret(Widget w GCC_UNUSED, | |
| XEvent * event GCC_UNUSED, | |
| String * params, | |
| Cardinal *param_count) | |
| { | |
| if (*param_count == 1) { | |
| const char *value = params[0]; | |
| int need = (int) strlen(value); | |
| int used = (int) (VTbuffer->next - VTbuffer->buffer); | |
| int have = (int) (VTbuffer->last - VTbuffer->buffer); | |
| if (have - used + need < BUF_SIZE) { | |
| fillPtyData(term, VTbuffer, value, (int) strlen(value)); | |
| TRACE(("Interpret %s\n", value)); | |
| VTbuffer->update++; | |
| } | |
| } | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleEnterWindow(Widget w GCC_UNUSED, | |
| XtPointer eventdata GCC_UNUSED, | |
| XEvent * event GCC_UNUSED, | |
| Boolean * cont GCC_UNUSED) | |
| { | |
| /* NOP since we handled it above */ | |
| TRACE(("HandleEnterWindow ignored\n")); | |
| TRACE_FOCUS(w, event); | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleLeaveWindow(Widget w GCC_UNUSED, | |
| XtPointer eventdata GCC_UNUSED, | |
| XEvent * event GCC_UNUSED, | |
| Boolean * cont GCC_UNUSED) | |
| { | |
| /* NOP since we handled it above */ | |
| TRACE(("HandleLeaveWindow ignored\n")); | |
| TRACE_FOCUS(w, event); | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleFocusChange(Widget w GCC_UNUSED, | |
| XtPointer eventdata GCC_UNUSED, | |
| XEvent * ev, | |
| Boolean * cont GCC_UNUSED) | |
| { | |
| XFocusChangeEvent *event = (XFocusChangeEvent *) ev; | |
| XtermWidget xw = term; | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("HandleFocusChange type=%s, mode=%s, detail=%s\n", | |
| visibleEventType(event->type), | |
| visibleNotifyMode(event->mode), | |
| visibleNotifyDetail(event->detail))); | |
| TRACE_FOCUS(xw, event); | |
| if (screen->quiet_grab | |
| && (event->mode == NotifyGrab || event->mode == NotifyUngrab)) { | |
| /* EMPTY */ ; | |
| } else if ((event->type == FocusIn || event->type == FocusOut) | |
| && event->detail == NotifyPointer) { | |
| /* | |
| * NotifyPointer is sent to the window where the pointer is, and is | |
| * in addition to events sent to the old/new focus-windows. | |
| */ | |
| /* EMPTY */ ; | |
| } else if (event->type == FocusIn) { | |
| setXUrgency(xw, False); | |
| /* | |
| * NotifyNonlinear only happens (on FocusIn) if the pointer was not in | |
| * one of our windows. Use this to reset a case where one xterm is | |
| * partly obscuring another, and X gets (us) confused about whether the | |
| * pointer was in the window. In particular, this can happen if the | |
| * user is resizing the obscuring window, causing some events to not be | |
| * delivered to the obscured window. | |
| */ | |
| if (event->detail == NotifyNonlinear | |
| && (screen->select & INWINDOW) != 0) { | |
| unselectwindow(xw, INWINDOW); | |
| } | |
| selectwindow(xw, | |
| ((event->detail == NotifyPointer) | |
| ? INWINDOW | |
| : FOCUS)); | |
| SendFocusButton(xw, event); | |
| } else { | |
| #if OPT_FOCUS_EVENT | |
| if (event->type == FocusOut) { | |
| SendFocusButton(xw, event); | |
| } | |
| #endif | |
| /* | |
| * XGrabKeyboard() will generate NotifyGrab event that we want to | |
| * ignore. | |
| */ | |
| if (event->mode != NotifyGrab) { | |
| unselectwindow(xw, | |
| ((event->detail == NotifyPointer) | |
| ? INWINDOW | |
| : FOCUS)); | |
| } | |
| if (screen->grabbedKbd && (event->mode == NotifyUngrab)) { | |
| Bell(xw, XkbBI_Info, 100); | |
| ReverseVideo(xw); | |
| screen->grabbedKbd = False; | |
| update_securekbd(); | |
| } | |
| } | |
| } | |
| static long lastBellTime; /* in milliseconds */ | |
| #if defined(HAVE_XKB_BELL_EXT) | |
| static Atom | |
| AtomBell(XtermWidget xw, int which) | |
| { | |
| #define DATA(name) { XkbBI_##name, XkbBN_##name } | |
| static struct { | |
| int value; | |
| const char *name; | |
| } table[] = { | |
| DATA(Info), | |
| DATA(MarginBell), | |
| DATA(MinorError), | |
| DATA(TerminalBell) | |
| }; | |
| Cardinal n; | |
| Atom result = None; | |
| for (n = 0; n < XtNumber(table); ++n) { | |
| if (table[n].value == which) { | |
| result = XInternAtom(XtDisplay(xw), table[n].name, False); | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| #endif | |
| void | |
| xtermBell(XtermWidget xw, int which, int percent) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| #if defined(HAVE_XKB_BELL_EXT) | |
| Atom tony = AtomBell(xw, which); | |
| #endif | |
| switch (which) { | |
| case XkbBI_Info: | |
| case XkbBI_MinorError: | |
| case XkbBI_MajorError: | |
| case XkbBI_TerminalBell: | |
| switch (screen->warningVolume) { | |
| case bvOff: | |
| percent = -100; | |
| break; | |
| case bvLow: | |
| break; | |
| case bvHigh: | |
| percent = 100; | |
| break; | |
| } | |
| break; | |
| case XkbBI_MarginBell: | |
| switch (screen->marginVolume) { | |
| case bvOff: | |
| percent = -100; | |
| break; | |
| case bvLow: | |
| break; | |
| case bvHigh: | |
| percent = 100; | |
| break; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| #if defined(HAVE_XKB_BELL_EXT) | |
| if (tony != None) { | |
| XkbBell(screen->display, VShellWindow(xw), percent, tony); | |
| } else | |
| #endif | |
| XBell(screen->display, percent); | |
| } | |
| void | |
| Bell(XtermWidget xw, int which, int percent) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| struct timeval curtime; | |
| long now_msecs; | |
| TRACE(("BELL %d %d%%\n", which, percent)); | |
| if (!XtIsRealized((Widget) xw)) { | |
| return; | |
| } | |
| setXUrgency(xw, True); | |
| /* has enough time gone by that we are allowed to ring | |
| the bell again? */ | |
| if (screen->bellSuppressTime) { | |
| if (screen->bellInProgress) { | |
| do_xevents(); | |
| if (screen->bellInProgress) { /* even after new events? */ | |
| return; | |
| } | |
| } | |
| X_GETTIMEOFDAY(&curtime); | |
| now_msecs = 1000 * curtime.tv_sec + curtime.tv_usec / 1000; | |
| if (lastBellTime != 0 && now_msecs - lastBellTime >= 0 && | |
| now_msecs - lastBellTime < screen->bellSuppressTime) { | |
| return; | |
| } | |
| lastBellTime = now_msecs; | |
| } | |
| if (screen->visualbell) { | |
| VisualBell(); | |
| } else { | |
| xtermBell(xw, which, percent); | |
| } | |
| if (screen->poponbell) | |
| XRaiseWindow(screen->display, VShellWindow(xw)); | |
| if (screen->bellSuppressTime) { | |
| /* now we change a property and wait for the notify event to come | |
| back. If the server is suspending operations while the bell | |
| is being emitted (problematic for audio bell), this lets us | |
| know when the previous bell has finished */ | |
| Widget w = CURRENT_EMU(); | |
| XChangeProperty(XtDisplay(w), XtWindow(w), | |
| XA_NOTICE, XA_NOTICE, 8, PropModeAppend, NULL, 0); | |
| screen->bellInProgress = True; | |
| } | |
| } | |
| #define VB_DELAY screen->visualBellDelay | |
| static void | |
| flashWindow(TScreen * screen, Window window, GC visualGC, unsigned width, unsigned height) | |
| { | |
| int y = 0; | |
| int x = 0; | |
| if (screen->flash_line) { | |
| y = CursorY(screen, screen->cur_row); | |
| height = (unsigned) FontHeight(screen); | |
| } | |
| XFillRectangle(screen->display, window, visualGC, x, y, width, height); | |
| XFlush(screen->display); | |
| Sleep(VB_DELAY); | |
| XFillRectangle(screen->display, window, visualGC, x, y, width, height); | |
| } | |
| void | |
| VisualBell(void) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| if (VB_DELAY > 0) { | |
| Pixel xorPixel = (T_COLOR(screen, TEXT_FG) ^ | |
| T_COLOR(screen, TEXT_BG)); | |
| XGCValues gcval; | |
| GC visualGC; | |
| gcval.function = GXxor; | |
| gcval.foreground = xorPixel; | |
| visualGC = XtGetGC((Widget) term, GCFunction + GCForeground, &gcval); | |
| #if OPT_TEK4014 | |
| if (TEK4014_ACTIVE(term)) { | |
| TekScreen *tekscr = TekScreenOf(tekWidget); | |
| flashWindow(screen, TWindow(tekscr), visualGC, | |
| TFullWidth(tekscr), | |
| TFullHeight(tekscr)); | |
| } else | |
| #endif | |
| { | |
| flashWindow(screen, VWindow(screen), visualGC, | |
| FullWidth(screen), | |
| FullHeight(screen)); | |
| } | |
| XtReleaseGC((Widget) term, visualGC); | |
| } | |
| } | |
| /* ARGSUSED */ | |
| void | |
| HandleBellPropertyChange(Widget w GCC_UNUSED, | |
| XtPointer data GCC_UNUSED, | |
| XEvent * ev, | |
| Boolean * more GCC_UNUSED) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| if (ev->xproperty.atom == XA_NOTICE) { | |
| screen->bellInProgress = False; | |
| } | |
| } | |
| void | |
| xtermWarning(const char *fmt,...) | |
| { | |
| int save_err = errno; | |
| va_list ap; | |
| fprintf(stderr, "%s: ", ProgramName); | |
| va_start(ap, fmt); | |
| vfprintf(stderr, fmt, ap); | |
| (void) fflush(stderr); | |
| va_end(ap); | |
| errno = save_err; | |
| } | |
| void | |
| xtermPerror(const char *fmt,...) | |
| { | |
| int save_err = errno; | |
| char *msg = strerror(errno); | |
| va_list ap; | |
| fprintf(stderr, "%s: ", ProgramName); | |
| va_start(ap, fmt); | |
| vfprintf(stderr, fmt, ap); | |
| fprintf(stderr, ": %s\n", msg); | |
| (void) fflush(stderr); | |
| va_end(ap); | |
| errno = save_err; | |
| } | |
| Window | |
| WMFrameWindow(XtermWidget xw) | |
| { | |
| Window win_root, win_current, *children; | |
| Window win_parent = 0; | |
| unsigned int nchildren; | |
| win_current = XtWindow(xw); | |
| /* find the parent which is child of root */ | |
| do { | |
| if (win_parent) | |
| win_current = win_parent; | |
| XQueryTree(TScreenOf(xw)->display, | |
| win_current, | |
| &win_root, | |
| &win_parent, | |
| &children, | |
| &nchildren); | |
| XFree(children); | |
| } while (win_root != win_parent); | |
| return win_current; | |
| } | |
| #if OPT_DABBREV | |
| /* | |
| * The following code implements `dynamic abbreviation' expansion a la | |
| * Emacs. It looks in the preceding visible screen and its scrollback | |
| * to find expansions of a typed word. It compares consecutive | |
| * expansions and ignores one of them if they are identical. | |
| * (Tomasz J. Cholewo, t.cholewo@ieee.org) | |
| */ | |
| #define IS_WORD_CONSTITUENT(x) ((x) != ' ' && (x) != '\0') | |
| #define MAXWLEN 1024 /* maximum word length as in tcsh */ | |
| static int | |
| dabbrev_prev_char(TScreen * screen, CELL * cell, LineData ** ld) | |
| { | |
| int result = -1; | |
| int firstLine = -(screen->savedlines); | |
| *ld = getLineData(screen, cell->row); | |
| while (cell->row >= firstLine) { | |
| if (--(cell->col) >= 0) { | |
| result = (int) (*ld)->charData[cell->col]; | |
| break; | |
| } | |
| if (--(cell->row) < firstLine) | |
| break; /* ...there is no previous line */ | |
| *ld = getLineData(screen, cell->row); | |
| cell->col = MaxCols(screen); | |
| if (!LineTstWrapped(*ld)) { | |
| result = ' '; /* treat lines as separate */ | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| static char * | |
| dabbrev_prev_word(TScreen * screen, CELL * cell, LineData ** ld) | |
| { | |
| static char ab[MAXWLEN]; | |
| char *abword; | |
| int c; | |
| char *ab_end = (ab + MAXWLEN - 1); | |
| char *result = 0; | |
| abword = ab_end; | |
| *abword = '\0'; /* end of string marker */ | |
| while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 && | |
| IS_WORD_CONSTITUENT(c)) { | |
| if (abword > ab) /* store only |MAXWLEN| last chars */ | |
| *(--abword) = (char) c; | |
| } | |
| if (c >= 0) { | |
| result = abword; | |
| } else if (abword != ab_end) { | |
| result = abword; | |
| } | |
| if (result != 0) { | |
| while ((c = dabbrev_prev_char(screen, cell, ld)) >= 0 && | |
| !IS_WORD_CONSTITUENT(c)) { | |
| ; /* skip preceding spaces */ | |
| } | |
| (cell->col)++; /* can be | > screen->max_col| */ | |
| } | |
| return result; | |
| } | |
| static int | |
| dabbrev_expand(TScreen * screen) | |
| { | |
| int pty = screen->respond; /* file descriptor of pty */ | |
| static CELL cell; | |
| static char *dabbrev_hint = 0, *lastexpansion = 0; | |
| static unsigned int expansions; | |
| char *expansion; | |
| Char *copybuffer; | |
| size_t hint_len; | |
| size_t del_cnt; | |
| size_t buf_cnt; | |
| int result = 0; | |
| LineData *ld; | |
| if (!screen->dabbrev_working) { /* initialize */ | |
| expansions = 0; | |
| cell.col = screen->cur_col; | |
| cell.row = screen->cur_row; | |
| if (dabbrev_hint != 0) | |
| free(dabbrev_hint); | |
| if ((dabbrev_hint = dabbrev_prev_word(screen, &cell, &ld)) != 0) { | |
| if (lastexpansion != 0) | |
| free(lastexpansion); | |
| if ((lastexpansion = strdup(dabbrev_hint)) != 0) { | |
| /* make own copy */ | |
| if ((dabbrev_hint = strdup(dabbrev_hint)) != 0) { | |
| screen->dabbrev_working = True; | |
| /* we are in the middle of dabbrev process */ | |
| } | |
| } else { | |
| return result; | |
| } | |
| } else { | |
| return result; | |
| } | |
| if (!screen->dabbrev_working) { | |
| if (lastexpansion != 0) { | |
| free(lastexpansion); | |
| lastexpansion = 0; | |
| } | |
| return result; | |
| } | |
| } | |
| if (dabbrev_hint == 0) | |
| return result; | |
| hint_len = strlen(dabbrev_hint); | |
| for (;;) { | |
| if ((expansion = dabbrev_prev_word(screen, &cell, &ld)) == 0) { | |
| if (expansions >= 2) { | |
| expansions = 0; | |
| cell.col = screen->cur_col; | |
| cell.row = screen->cur_row; | |
| continue; | |
| } | |
| break; | |
| } | |
| if (!strncmp(dabbrev_hint, expansion, hint_len) && /* empty hint matches everything */ | |
| strlen(expansion) > hint_len && /* trivial expansion disallowed */ | |
| strcmp(expansion, lastexpansion)) /* different from previous */ | |
| break; | |
| } | |
| if (expansion != 0) { | |
| del_cnt = strlen(lastexpansion) - hint_len; | |
| buf_cnt = del_cnt + strlen(expansion) - hint_len; | |
| if ((copybuffer = TypeMallocN(Char, buf_cnt)) != 0) { | |
| /* delete previous expansion */ | |
| memset(copybuffer, screen->dabbrev_erase_char, del_cnt); | |
| memmove(copybuffer + del_cnt, | |
| expansion + hint_len, | |
| strlen(expansion) - hint_len); | |
| v_write(pty, copybuffer, (unsigned) buf_cnt); | |
| /* v_write() just reset our flag */ | |
| screen->dabbrev_working = True; | |
| free(copybuffer); | |
| free(lastexpansion); | |
| if ((lastexpansion = strdup(expansion)) != 0) { | |
| result = 1; | |
| expansions++; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleDabbrevExpand(Widget w, | |
| XEvent * event GCC_UNUSED, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| XtermWidget xw; | |
| TRACE(("Handle dabbrev-expand for %p\n", (void *) w)); | |
| if ((xw = getXtermWidget(w)) != 0) { | |
| TScreen *screen = TScreenOf(xw); | |
| if (!dabbrev_expand(screen)) | |
| Bell(xw, XkbBI_TerminalBell, 0); | |
| } | |
| } | |
| #endif /* OPT_DABBREV */ | |
| #if OPT_MAXIMIZE | |
| /*ARGSUSED*/ | |
| void | |
| HandleDeIconify(Widget w, | |
| XEvent * event GCC_UNUSED, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| XtermWidget xw; | |
| if ((xw = getXtermWidget(w)) != 0) { | |
| TScreen *screen = TScreenOf(xw); | |
| XMapWindow(screen->display, VShellWindow(xw)); | |
| } | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleIconify(Widget w, | |
| XEvent * event GCC_UNUSED, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| XtermWidget xw; | |
| if ((xw = getXtermWidget(w)) != 0) { | |
| TScreen *screen = TScreenOf(xw); | |
| XIconifyWindow(screen->display, | |
| VShellWindow(xw), | |
| DefaultScreen(screen->display)); | |
| } | |
| } | |
| int | |
| QueryMaximize(XtermWidget xw, unsigned *width, unsigned *height) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| XSizeHints hints; | |
| long supp = 0; | |
| Window root_win; | |
| int root_x = -1; /* saved co-ordinates */ | |
| int root_y = -1; | |
| unsigned root_border; | |
| unsigned root_depth; | |
| int code; | |
| if (XGetGeometry(screen->display, | |
| RootWindowOfScreen(XtScreen(xw)), | |
| &root_win, | |
| &root_x, | |
| &root_y, | |
| width, | |
| height, | |
| &root_border, | |
| &root_depth)) { | |
| TRACE(("QueryMaximize: XGetGeometry position %d,%d size %d,%d border %d\n", | |
| root_x, | |
| root_y, | |
| *width, | |
| *height, | |
| root_border)); | |
| *width -= (root_border * 2); | |
| *height -= (root_border * 2); | |
| hints.flags = PMaxSize; | |
| if (XGetWMNormalHints(screen->display, | |
| VShellWindow(xw), | |
| &hints, | |
| &supp) | |
| && (hints.flags & PMaxSize) != 0) { | |
| TRACE(("QueryMaximize: WM hints max_w %#x max_h %#x\n", | |
| hints.max_width, | |
| hints.max_height)); | |
| if ((unsigned) hints.max_width < *width) | |
| *width = (unsigned) hints.max_width; | |
| if ((unsigned) hints.max_height < *height) | |
| *height = (unsigned) hints.max_height; | |
| } | |
| code = 1; | |
| } else { | |
| *width = 0; | |
| *height = 0; | |
| code = 0; | |
| } | |
| return code; | |
| } | |
| void | |
| RequestMaximize(XtermWidget xw, int maximize) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| XWindowAttributes wm_attrs, vshell_attrs; | |
| unsigned root_width, root_height; | |
| Boolean success = False; | |
| TRACE(("RequestMaximize %d:%s\n", | |
| maximize, | |
| (maximize | |
| ? "maximize" | |
| : "restore"))); | |
| /* | |
| * Before any maximize, ensure that we can capture the current screensize | |
| * as well as the estimated root-window size. | |
| */ | |
| if (maximize | |
| && QueryMaximize(xw, &root_width, &root_height) | |
| && xtermGetWinAttrs(screen->display, | |
| WMFrameWindow(xw), | |
| &wm_attrs) | |
| && xtermGetWinAttrs(screen->display, | |
| VShellWindow(xw), | |
| &vshell_attrs)) { | |
| if (screen->restore_data != True | |
| || screen->restore_width != root_width | |
| || screen->restore_height != root_height) { | |
| screen->restore_data = True; | |
| screen->restore_x = wm_attrs.x + wm_attrs.border_width; | |
| screen->restore_y = wm_attrs.y + wm_attrs.border_width; | |
| screen->restore_width = (unsigned) vshell_attrs.width; | |
| screen->restore_height = (unsigned) vshell_attrs.height; | |
| TRACE(("RequestMaximize: save window position %d,%d size %d,%d\n", | |
| screen->restore_x, | |
| screen->restore_y, | |
| screen->restore_width, | |
| screen->restore_height)); | |
| } | |
| /* subtract wm decoration dimensions */ | |
| root_width -= (unsigned) ((wm_attrs.width - vshell_attrs.width) | |
| + (wm_attrs.border_width * 2)); | |
| root_height -= (unsigned) ((wm_attrs.height - vshell_attrs.height) | |
| + (wm_attrs.border_width * 2)); | |
| success = True; | |
| } else if (screen->restore_data) { | |
| success = True; | |
| maximize = 0; | |
| } | |
| if (success) { | |
| switch (maximize) { | |
| case 3: | |
| FullScreen(xw, 3); /* depends on EWMH */ | |
| break; | |
| case 2: | |
| FullScreen(xw, 2); /* depends on EWMH */ | |
| break; | |
| case 1: | |
| FullScreen(xw, 0); /* overrides any EWMH hint */ | |
| XMoveResizeWindow(screen->display, VShellWindow(xw), | |
| 0 + wm_attrs.border_width, /* x */ | |
| 0 + wm_attrs.border_width, /* y */ | |
| root_width, | |
| root_height); | |
| break; | |
| default: | |
| FullScreen(xw, 0); /* reset any EWMH hint */ | |
| if (screen->restore_data) { | |
| screen->restore_data = False; | |
| TRACE(("HandleRestoreSize: position %d,%d size %d,%d\n", | |
| screen->restore_x, | |
| screen->restore_y, | |
| screen->restore_width, | |
| screen->restore_height)); | |
| XMoveResizeWindow(screen->display, | |
| VShellWindow(xw), | |
| screen->restore_x, | |
| screen->restore_y, | |
| screen->restore_width, | |
| screen->restore_height); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleMaximize(Widget w, | |
| XEvent * event GCC_UNUSED, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| XtermWidget xw; | |
| if ((xw = getXtermWidget(w)) != 0) { | |
| RequestMaximize(xw, 1); | |
| } | |
| } | |
| /*ARGSUSED*/ | |
| void | |
| HandleRestoreSize(Widget w, | |
| XEvent * event GCC_UNUSED, | |
| String * params GCC_UNUSED, | |
| Cardinal *nparams GCC_UNUSED) | |
| { | |
| XtermWidget xw; | |
| if ((xw = getXtermWidget(w)) != 0) { | |
| RequestMaximize(xw, 0); | |
| } | |
| } | |
| #endif /* OPT_MAXIMIZE */ | |
| void | |
| Redraw(void) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| XExposeEvent event; | |
| TRACE(("Redraw\n")); | |
| event.type = Expose; | |
| event.display = screen->display; | |
| event.x = 0; | |
| event.y = 0; | |
| event.count = 0; | |
| if (VWindow(screen)) { | |
| event.window = VWindow(screen); | |
| event.width = term->core.width; | |
| event.height = term->core.height; | |
| (*term->core.widget_class->core_class.expose) ((Widget) term, | |
| (XEvent *) & event, | |
| NULL); | |
| if (ScrollbarWidth(screen)) { | |
| (screen->scrollWidget->core.widget_class->core_class.expose) | |
| (screen->scrollWidget, (XEvent *) & event, NULL); | |
| } | |
| } | |
| #if OPT_TEK4014 | |
| if (TEK4014_SHOWN(term)) { | |
| TekScreen *tekscr = TekScreenOf(tekWidget); | |
| event.window = TWindow(tekscr); | |
| event.width = tekWidget->core.width; | |
| event.height = tekWidget->core.height; | |
| TekExpose((Widget) tekWidget, (XEvent *) & event, NULL); | |
| } | |
| #endif | |
| } | |
| #ifdef VMS | |
| #define TIMESTAMP_FMT "%s%d-%02d-%02d-%02d-%02d-%02d" | |
| #else | |
| #define TIMESTAMP_FMT "%s%d-%02d-%02d.%02d:%02d:%02d" | |
| #endif | |
| void | |
| timestamp_filename(char *dst, const char *src) | |
| { | |
| time_t tstamp; | |
| struct tm *tstruct; | |
| tstamp = time((time_t *) 0); | |
| tstruct = localtime(&tstamp); | |
| sprintf(dst, TIMESTAMP_FMT, | |
| src, | |
| (int) tstruct->tm_year + 1900, | |
| tstruct->tm_mon + 1, | |
| tstruct->tm_mday, | |
| tstruct->tm_hour, | |
| tstruct->tm_min, | |
| tstruct->tm_sec); | |
| } | |
| int | |
| open_userfile(uid_t uid, gid_t gid, char *path, Bool append) | |
| { | |
| int fd; | |
| struct stat sb; | |
| #ifdef VMS | |
| if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) { | |
| int the_error = errno; | |
| xtermWarning("cannot open %s: %d:%s\n", | |
| path, | |
| the_error, | |
| SysErrorMsg(the_error)); | |
| return -1; | |
| } | |
| chown(path, uid, gid); | |
| #else | |
| if ((access(path, F_OK) != 0 && (errno != ENOENT)) | |
| || (creat_as(uid, gid, append, path, 0644) <= 0) | |
| || ((fd = open(path, O_WRONLY | O_APPEND)) < 0)) { | |
| int the_error = errno; | |
| xtermWarning("cannot open %s: %d:%s\n", | |
| path, | |
| the_error, | |
| SysErrorMsg(the_error)); | |
| return -1; | |
| } | |
| #endif | |
| /* | |
| * Doublecheck that the user really owns the file that we've opened before | |
| * we do any damage, and that it is not world-writable. | |
| */ | |
| if (fstat(fd, &sb) < 0 | |
| || sb.st_uid != uid | |
| || (sb.st_mode & 022) != 0) { | |
| xtermWarning("you do not own %s\n", path); | |
| close(fd); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| #ifndef VMS | |
| /* | |
| * Create a file only if we could with the permissions of the real user id. | |
| * We could emulate this with careful use of access() and following | |
| * symbolic links, but that is messy and has race conditions. | |
| * Forking is messy, too, but we can't count on setreuid() or saved set-uids | |
| * being available. | |
| * | |
| * Note: When called for user logging, we have ensured that the real and | |
| * effective user ids are the same, so this remains as a convenience function | |
| * for the debug logs. | |
| * | |
| * Returns | |
| * 1 if we can proceed to open the file in relative safety, | |
| * -1 on error, e.g., cannot fork | |
| * 0 otherwise. | |
| */ | |
| int | |
| creat_as(uid_t uid, gid_t gid, Bool append, char *pathname, unsigned mode) | |
| { | |
| int fd; | |
| pid_t pid; | |
| int retval = 0; | |
| int childstat = 0; | |
| #ifndef HAVE_WAITPID | |
| int waited; | |
| void (*chldfunc) (int); | |
| chldfunc = signal(SIGCHLD, SIG_DFL); | |
| #endif /* HAVE_WAITPID */ | |
| TRACE(("creat_as(uid=%d/%d, gid=%d/%d, append=%d, pathname=%s, mode=%#o)\n", | |
| (int) uid, (int) geteuid(), | |
| (int) gid, (int) getegid(), | |
| append, | |
| pathname, | |
| mode)); | |
| if (uid == geteuid() && gid == getegid()) { | |
| fd = open(pathname, | |
| O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL), | |
| mode); | |
| if (fd >= 0) | |
| close(fd); | |
| return (fd >= 0); | |
| } | |
| pid = fork(); | |
| switch (pid) { | |
| case 0: /* child */ | |
| if (setgid(gid) == -1 | |
| || setuid(uid) == -1) { | |
| /* we cannot report an error here via stderr, just quit */ | |
| retval = 1; | |
| } else { | |
| fd = open(pathname, | |
| O_WRONLY | O_CREAT | (append ? O_APPEND : O_EXCL), | |
| mode); | |
| if (fd >= 0) { | |
| close(fd); | |
| retval = 0; | |
| } else { | |
| retval = 1; | |
| } | |
| } | |
| _exit(retval); | |
| /* NOTREACHED */ | |
| case -1: /* error */ | |
| return retval; | |
| default: /* parent */ | |
| #ifdef HAVE_WAITPID | |
| while (waitpid(pid, &childstat, 0) < 0) { | |
| #ifdef EINTR | |
| if (errno == EINTR) | |
| continue; | |
| #endif /* EINTR */ | |
| #ifdef ERESTARTSYS | |
| if (errno == ERESTARTSYS) | |
| continue; | |
| #endif /* ERESTARTSYS */ | |
| break; | |
| } | |
| #else /* HAVE_WAITPID */ | |
| waited = wait(&childstat); | |
| signal(SIGCHLD, chldfunc); | |
| /* | |
| Since we had the signal handler uninstalled for a while, | |
| we might have missed the termination of our screen child. | |
| If we can check for this possibility without hanging, do so. | |
| */ | |
| do | |
| if (waited == TScreenOf(term)->pid) | |
| NormalExit(); | |
| while ((waited = nonblocking_wait()) > 0) ; | |
| #endif /* HAVE_WAITPID */ | |
| #ifndef WIFEXITED | |
| #define WIFEXITED(status) ((status & 0xff) != 0) | |
| #endif | |
| if (WIFEXITED(childstat)) | |
| retval = 1; | |
| return retval; | |
| } | |
| } | |
| #endif /* !VMS */ | |
| int | |
| xtermResetIds(TScreen * screen) | |
| { | |
| int result = 0; | |
| if (setgid(screen->gid) == -1) { | |
| xtermWarning("unable to reset group-id\n"); | |
| result = -1; | |
| } | |
| if (setuid(screen->uid) == -1) { | |
| xtermWarning("unable to reset user-id\n"); | |
| result = -1; | |
| } | |
| return result; | |
| } | |
| #ifdef ALLOWLOGGING | |
| /* | |
| * Logging is a security hole, since it allows a setuid program to write | |
| * arbitrary data to an arbitrary file. So it is disabled by default. | |
| */ | |
| #ifdef ALLOWLOGFILEEXEC | |
| static void | |
| logpipe(int sig GCC_UNUSED) | |
| { | |
| XtermWidget xw = term; | |
| TScreen *screen = TScreenOf(xw); | |
| #ifdef SYSV | |
| (void) signal(SIGPIPE, SIG_IGN); | |
| #endif /* SYSV */ | |
| if (screen->logging) | |
| CloseLog(xw); | |
| } | |
| #endif /* ALLOWLOGFILEEXEC */ | |
| void | |
| StartLog(XtermWidget xw) | |
| { | |
| static char *log_default; | |
| TScreen *screen = TScreenOf(xw); | |
| if (screen->logging || (screen->inhibit & I_LOG)) | |
| return; | |
| #ifdef VMS /* file name is fixed in VMS variant */ | |
| screen->logfd = open(XTERM_VMS_LOGFILE, | |
| O_CREAT | O_TRUNC | O_APPEND | O_RDWR, | |
| 0640); | |
| if (screen->logfd < 0) | |
| return; /* open failed */ | |
| #else /*VMS */ | |
| if (screen->logfile == NULL || *screen->logfile == 0) { | |
| if (screen->logfile) | |
| free(screen->logfile); | |
| if (log_default == NULL) { | |
| #if defined(HAVE_GETHOSTNAME) && defined(HAVE_STRFTIME) | |
| char log_def_name[512]; /* see sprintf below */ | |
| char hostname[255 + 1]; /* Internet standard limit (RFC 1035): | |
| ``To simplify implementations, the | |
| total length of a domain name (i.e., | |
| label octets and label length | |
| octets) is restricted to 255 octets | |
| or less.'' */ | |
| char yyyy_mm_dd_hh_mm_ss[4 + 5 * (1 + 2) + 1]; | |
| time_t now; | |
| struct tm *ltm; | |
| now = time((time_t *) 0); | |
| ltm = (struct tm *) localtime(&now); | |
| if ((gethostname(hostname, sizeof(hostname)) == 0) && | |
| (strftime(yyyy_mm_dd_hh_mm_ss, | |
| sizeof(yyyy_mm_dd_hh_mm_ss), | |
| "%Y.%m.%d.%H.%M.%S", ltm) > 0)) { | |
| (void) sprintf(log_def_name, "Xterm.log.%.255s.%.20s.%d", | |
| hostname, yyyy_mm_dd_hh_mm_ss, (int) getpid()); | |
| } | |
| if ((log_default = x_strdup(log_def_name)) == NULL) | |
| return; | |
| #else | |
| const char *log_def_name = "XtermLog.XXXXXX"; | |
| if ((log_default = x_strdup(log_def_name)) == NULL) | |
| return; | |
| mktemp(log_default); | |
| #endif | |
| } | |
| if ((screen->logfile = x_strdup(log_default)) == 0) | |
| return; | |
| } | |
| if (*screen->logfile == '|') { /* exec command */ | |
| #ifdef ALLOWLOGFILEEXEC | |
| /* | |
| * Warning, enabling this "feature" allows arbitrary programs | |
| * to be run. If ALLOWLOGFILECHANGES is enabled, this can be | |
| * done through escape sequences.... You have been warned. | |
| */ | |
| int pid; | |
| int p[2]; | |
| static char *shell; | |
| struct passwd pw; | |
| if ((shell = x_getenv("SHELL")) == NULL) { | |
| if (x_getpwuid(screen->uid, &pw)) { | |
| char *name = x_getlogin(screen->uid, &pw); | |
| if (*(pw.pw_shell)) { | |
| shell = pw.pw_shell; | |
| } | |
| free(name); | |
| } | |
| } | |
| if (shell == 0) { | |
| static char dummy[] = "/bin/sh"; | |
| shell = dummy; | |
| } | |
| if (access(shell, X_OK) != 0) { | |
| xtermPerror("Can't execute `%s'\n", shell); | |
| return; | |
| } | |
| if (pipe(p) < 0) { | |
| xtermPerror("Can't make a pipe connection\n"); | |
| return; | |
| } else if ((pid = fork()) < 0) { | |
| xtermPerror("Can't fork...\n"); | |
| return; | |
| } | |
| if (pid == 0) { /* child */ | |
| /* | |
| * Close our output (we won't be talking back to the | |
| * parent), and redirect our child's output to the | |
| * original stderr. | |
| */ | |
| close(p[1]); | |
| dup2(p[0], 0); | |
| close(p[0]); | |
| dup2(fileno(stderr), 1); | |
| dup2(fileno(stderr), 2); | |
| close(fileno(stderr)); | |
| close(ConnectionNumber(screen->display)); | |
| close(screen->respond); | |
| signal(SIGHUP, SIG_DFL); | |
| signal(SIGCHLD, SIG_DFL); | |
| /* (this is redundant) */ | |
| if (xtermResetIds(screen) < 0) | |
| exit(ERROR_SETUID); | |
| if (access(shell, X_OK) == 0) { | |
| execl(shell, shell, "-c", &screen->logfile[1], (void *) 0); | |
| xtermWarning("Can't exec `%s'\n", &screen->logfile[1]); | |
| } else { | |
| xtermWarning("Can't execute `%s'\n", shell); | |
| } | |
| exit(ERROR_LOGEXEC); | |
| } | |
| close(p[0]); | |
| screen->logfd = p[1]; | |
| signal(SIGPIPE, logpipe); | |
| #else | |
| Bell(xw, XkbBI_Info, 0); | |
| Bell(xw, XkbBI_Info, 0); | |
| return; | |
| #endif | |
| } else { | |
| if ((screen->logfd = open_userfile(screen->uid, | |
| screen->gid, | |
| screen->logfile, | |
| (log_default != 0))) < 0) | |
| return; | |
| } | |
| #endif /*VMS */ | |
| screen->logstart = VTbuffer->next; | |
| screen->logging = True; | |
| update_logging(); | |
| } | |
| void | |
| CloseLog(XtermWidget xw) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| if (!screen->logging || (screen->inhibit & I_LOG)) | |
| return; | |
| FlushLog(xw); | |
| close(screen->logfd); | |
| screen->logging = False; | |
| update_logging(); | |
| } | |
| void | |
| FlushLog(XtermWidget xw) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| if (screen->logging && !(screen->inhibit & I_LOG)) { | |
| Char *cp; | |
| int i; | |
| #ifdef VMS /* avoid logging output loops which otherwise occur sometimes | |
| when there is no output and cp/screen->logstart are 1 apart */ | |
| if (!tt_new_output) | |
| return; | |
| tt_new_output = False; | |
| #endif /* VMS */ | |
| cp = VTbuffer->next; | |
| if (screen->logstart != 0 | |
| && (i = (int) (cp - screen->logstart)) > 0) { | |
| IGNORE_RC(write(screen->logfd, screen->logstart, (size_t) i)); | |
| } | |
| screen->logstart = VTbuffer->next; | |
| } | |
| } | |
| #endif /* ALLOWLOGGING */ | |
| /***====================================================================***/ | |
| #if OPT_ISO_COLORS | |
| static void | |
| ReportAnsiColorRequest(XtermWidget xw, int colornum, int final) | |
| { | |
| if (AllowColorOps(xw, ecGetAnsiColor)) { | |
| XColor color; | |
| Colormap cmap = xw->core.colormap; | |
| char buffer[80]; | |
| TRACE(("ReportAnsiColorRequest %d\n", colornum)); | |
| color.pixel = GET_COLOR_RES(xw, TScreenOf(xw)->Acolors[colornum]); | |
| XQueryColor(TScreenOf(xw)->display, cmap, &color); | |
| sprintf(buffer, "4;%d;rgb:%04x/%04x/%04x", | |
| colornum, | |
| color.red, | |
| color.green, | |
| color.blue); | |
| unparseputc1(xw, ANSI_OSC); | |
| unparseputs(xw, buffer); | |
| unparseputc1(xw, final); | |
| unparse_end(xw); | |
| } | |
| } | |
| static void | |
| getColormapInfo(Display * display, unsigned *typep, unsigned *sizep) | |
| { | |
| int numFound; | |
| XVisualInfo myTemplate, *visInfoPtr; | |
| myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(display, | |
| XDefaultScreen(display))); | |
| visInfoPtr = XGetVisualInfo(display, (long) VisualIDMask, | |
| &myTemplate, &numFound); | |
| *typep = (numFound >= 1) ? (unsigned) visInfoPtr->class : 0; | |
| *sizep = (numFound >= 1) ? (unsigned) visInfoPtr->colormap_size : 0; | |
| XFree((char *) visInfoPtr); | |
| TRACE(("getColormapInfo type %d (%s), size %d\n", | |
| *typep, ((*typep & 1) ? "dynamic" : "static"), *sizep)); | |
| } | |
| #define MAX_COLORTABLE 4096 | |
| /* | |
| * Make only one call to XQueryColors(), since it can be slow. | |
| */ | |
| static Boolean | |
| loadColorTable(XtermWidget xw, unsigned length) | |
| { | |
| Colormap cmap = xw->core.colormap; | |
| TScreen *screen = TScreenOf(xw); | |
| unsigned i; | |
| Boolean result = False; | |
| if (screen->cmap_data == 0 | |
| && length != 0 | |
| && length < MAX_COLORTABLE) { | |
| screen->cmap_data = TypeMallocN(XColor, (size_t) length); | |
| if (screen->cmap_data != 0) { | |
| screen->cmap_size = length; | |
| for (i = 0; i < screen->cmap_size; i++) { | |
| screen->cmap_data[i].pixel = (unsigned long) i; | |
| } | |
| result = (Boolean) (XQueryColors(screen->display, | |
| cmap, | |
| screen->cmap_data, | |
| (int) screen->cmap_size) != 0); | |
| } | |
| } | |
| return result; | |
| } | |
| /* | |
| * Find closest color for "def" in "cmap". | |
| * Set "def" to the resulting color. | |
| * | |
| * Based on Monish Shah's "find_closest_color()" for Vim 6.0, | |
| * modified with ideas from David Tong's "noflash" library. | |
| * The code from Vim in turn was derived from FindClosestColor() in Tcl/Tk. | |
| * | |
| * These provide some introduction: | |
| * http://en.wikipedia.org/wiki/YIQ | |
| * for an introduction to YIQ weights. | |
| * http://en.wikipedia.org/wiki/Luminance_(video) | |
| * for a discussion of luma. | |
| * http://en.wikipedia.org/wiki/YUV | |
| * | |
| * Return False if not able to find or allocate a color. | |
| */ | |
| static Boolean | |
| allocateClosestRGB(XtermWidget xw, Colormap cmap, XColor * def) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| Boolean result = False; | |
| char *tried; | |
| double diff, thisRGB, bestRGB; | |
| unsigned attempts; | |
| unsigned bestInx; | |
| unsigned cmap_type; | |
| unsigned cmap_size; | |
| unsigned i; | |
| getColormapInfo(screen->display, &cmap_type, &cmap_size); | |
| if ((cmap_type & 1) != 0) { | |
| if (loadColorTable(xw, cmap_size)) { | |
| tried = TypeCallocN(char, (size_t) cmap_size); | |
| if (tried != 0) { | |
| /* | |
| * Try (possibly each entry in the color map) to find the best | |
| * approximation to the requested color. | |
| */ | |
| for (attempts = 0; attempts < cmap_size; attempts++) { | |
| Boolean first = True; | |
| bestRGB = 0.0; | |
| bestInx = 0; | |
| for (i = 0; i < cmap_size; i++) { | |
| if (!tried[bestInx]) { | |
| /* | |
| * Look for the best match based on luminance. | |
| * Measure this by the least-squares difference of | |
| * the weighted R/G/B components from the color map | |
| * versus the requested color. Use the Y (luma) | |
| * component of the YIQ color space model for | |
| * weights that correspond to the luminance. | |
| */ | |
| #define AddColorWeight(weight, color) \ | |
| diff = weight * (int) ((def->color) - screen->cmap_data[i].color); \ | |
| thisRGB = diff * diff | |
| AddColorWeight(0.30, red); | |
| AddColorWeight(0.61, green); | |
| AddColorWeight(0.11, blue); | |
| if (first || (thisRGB < bestRGB)) { | |
| first = False; | |
| bestInx = i; | |
| bestRGB = thisRGB; | |
| } | |
| } | |
| } | |
| if (XAllocColor(screen->display, cmap, | |
| &screen->cmap_data[bestInx]) != 0) { | |
| *def = screen->cmap_data[bestInx]; | |
| TRACE(("...closest %x/%x/%x\n", def->red, | |
| def->green, def->blue)); | |
| result = True; | |
| break; | |
| } | |
| /* | |
| * It failed - either the color map entry was readonly, or | |
| * another client has allocated the entry. Mark the entry | |
| * so we will ignore it | |
| */ | |
| tried[bestInx] = True; | |
| } | |
| free(tried); | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| #ifndef ULONG_MAX | |
| #define ULONG_MAX (unsigned long)(~(0L)) | |
| #endif | |
| #define CheckColor(result, value) \ | |
| result = 0; \ | |
| if (value.red) \ | |
| result |= 1; \ | |
| if (value.green) \ | |
| result |= 2; \ | |
| if (value.blue) \ | |
| result |= 4 | |
| #define SelectColor(state, value, result) \ | |
| switch (state) { \ | |
| default: \ | |
| case 1: \ | |
| result = value.red; \ | |
| break; \ | |
| case 2: \ | |
| result = value.green; \ | |
| break; \ | |
| case 4: \ | |
| result = value.blue; \ | |
| break; \ | |
| } | |
| /* | |
| * Check if the color map consists of values in exactly one of the red, green | |
| * or blue columns. If it is not, we do not know how to use it for the exact | |
| * match. | |
| */ | |
| static int | |
| simpleColors(XColor * colortable, unsigned length) | |
| { | |
| unsigned n; | |
| int state = -1; | |
| int check; | |
| for (n = 0; n < length; ++n) { | |
| if (state == -1) { | |
| CheckColor(state, colortable[n]); | |
| if (state == 0) | |
| state = -1; | |
| } | |
| if (state > 0) { | |
| CheckColor(check, colortable[n]); | |
| if (check > 0 && check != state) { | |
| state = 0; | |
| break; | |
| } | |
| } | |
| } | |
| switch (state) { | |
| case 1: | |
| case 2: | |
| case 4: | |
| break; | |
| default: | |
| state = 0; | |
| break; | |
| } | |
| return state; | |
| } | |
| static unsigned | |
| searchColors(XColor * colortable, unsigned length, unsigned color, int state) | |
| { | |
| unsigned result = 0; | |
| unsigned n; | |
| unsigned long best = ULONG_MAX; | |
| unsigned long diff; | |
| unsigned value; | |
| for (n = 0; n < length; ++n) { | |
| SelectColor(state, colortable[n], value); | |
| diff = (color - value); | |
| diff *= diff; | |
| if (diff < best) { | |
| #if 0 | |
| TRACE(("...%d:looking for %x, found %x/%x/%x (%lx)\n", | |
| n, color, | |
| colortable[n].red, | |
| colortable[n].green, | |
| colortable[n].blue, | |
| diff)); | |
| #endif | |
| result = n; | |
| best = diff; | |
| } | |
| } | |
| SelectColor(state, colortable[result], value); | |
| return value; | |
| } | |
| /* | |
| * This is a workaround for a longstanding defect in the X libraries. | |
| * | |
| * According to | |
| * http://www.unix.com/man-page/all/3x/XAllocColoA/ | |
| * | |
| * XAllocColor() acts differently on static and dynamic visuals. On Pseu- | |
| * doColor, DirectColor, and GrayScale visuals, XAllocColor() fails if | |
| * there are no unallocated colorcells and no allocated read-only cell | |
| * exactly matches the requested RGB values. On StaticColor, TrueColor, | |
| * and StaticGray visuals, XAllocColor() returns the closest RGB values | |
| * available in the colormap. The colorcell_in_out structure returns the | |
| * actual RGB values allocated. | |
| * | |
| * That is, XAllocColor() should suffice unless the color map is full. In that | |
| * case, allocateClosesRGB() is useful for the dynamic display classes such as | |
| * PseudoColor. It is not useful for TrueColor, since XQueryColors() does not | |
| * return regular RGB triples (unless a different scheme was used for | |
| * specifying the pixel values); only the blue value is filled in. However, it | |
| * is filled in with the colors that the server supports. | |
| * | |
| * Also (the reason for this function), XAllocColor() does not really work as | |
| * described. For some TrueColor configurations it merely returns a close | |
| * approximation, but not the closest. | |
| */ | |
| static Boolean | |
| allocateExactRGB(XtermWidget xw, Colormap cmap, XColor * def) | |
| { | |
| XColor save = *def; | |
| TScreen *screen = TScreenOf(xw); | |
| Boolean result = (Boolean) (XAllocColor(screen->display, cmap, def) != 0); | |
| /* | |
| * If this is a statically allocated display, e.g., TrueColor, see if we | |
| * can improve on the result by using the color values actually supported | |
| * by the server. | |
| */ | |
| if (result) { | |
| unsigned cmap_type; | |
| unsigned cmap_size; | |
| int state; | |
| getColormapInfo(screen->display, &cmap_type, &cmap_size); | |
| if ((cmap_type & 1) == 0) { | |
| XColor temp = *def; | |
| if (loadColorTable(xw, cmap_size) | |
| && (state = simpleColors(screen->cmap_data, cmap_size)) > 0) { | |
| #define SearchColors(which) temp.which = (unsigned short) searchColors(screen->cmap_data, cmap_size, save.which, state) | |
| SearchColors(red); | |
| SearchColors(green); | |
| SearchColors(blue); | |
| if (XAllocColor(screen->display, cmap, &temp) != 0) { | |
| #if OPT_TRACE | |
| if (temp.red != save.red | |
| || temp.green != save.green | |
| || temp.blue != save.blue) { | |
| TRACE(("...improved %x/%x/%x ->%x/%x/%x\n", | |
| save.red, save.green, save.blue, | |
| temp.red, temp.green, temp.blue)); | |
| } else { | |
| TRACE(("...no improvement for %x/%x/%x\n", | |
| save.red, save.green, save.blue)); | |
| } | |
| #endif | |
| *def = temp; | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /* | |
| * Allocate a color for the "ANSI" colors. That actually includes colors up | |
| * to 256. | |
| * | |
| * Returns | |
| * -1 on error | |
| * 0 on no change | |
| * 1 if a new color was allocated. | |
| */ | |
| static int | |
| AllocateAnsiColor(XtermWidget xw, | |
| ColorRes * res, | |
| const char *spec) | |
| { | |
| int result; | |
| XColor def; | |
| if (xtermAllocColor(xw, &def, spec)) { | |
| if ( | |
| #if OPT_COLOR_RES | |
| res->mode == True && | |
| #endif | |
| EQL_COLOR_RES(res, def.pixel)) { | |
| result = 0; | |
| } else { | |
| result = 1; | |
| SET_COLOR_RES(res, def.pixel); | |
| res->red = def.red; | |
| res->green = def.green; | |
| res->blue = def.blue; | |
| TRACE(("AllocateAnsiColor[%d] %s (rgb:%04x/%04x/%04x, pixel 0x%06lx)\n", | |
| (int) (res - TScreenOf(xw)->Acolors), spec, | |
| def.red, | |
| def.green, | |
| def.blue, | |
| def.pixel)); | |
| #if OPT_COLOR_RES | |
| if (!res->mode) | |
| result = 0; | |
| res->mode = True; | |
| #endif | |
| } | |
| } else { | |
| TRACE(("AllocateAnsiColor %s (failed)\n", spec)); | |
| result = -1; | |
| } | |
| return (result); | |
| } | |
| #if OPT_COLOR_RES | |
| Pixel | |
| xtermGetColorRes(XtermWidget xw, ColorRes * res) | |
| { | |
| Pixel result = 0; | |
| if (res->mode) { | |
| result = res->value; | |
| } else { | |
| TRACE(("xtermGetColorRes for Acolors[%d]\n", | |
| (int) (res - TScreenOf(xw)->Acolors))); | |
| if (res >= TScreenOf(xw)->Acolors) { | |
| assert(res - TScreenOf(xw)->Acolors < MAXCOLORS); | |
| if (AllocateAnsiColor(xw, res, res->resource) < 0) { | |
| res->value = TScreenOf(xw)->Tcolors[TEXT_FG].value; | |
| res->mode = -True; | |
| xtermWarning("Cannot allocate color \"%s\"\n", | |
| NonNull(res->resource)); | |
| } | |
| result = res->value; | |
| } else { | |
| result = 0; | |
| } | |
| } | |
| return result; | |
| } | |
| #endif | |
| static int | |
| ChangeOneAnsiColor(XtermWidget xw, int color, const char *name) | |
| { | |
| int code; | |
| if (color < 0 || color >= MAXCOLORS) { | |
| code = -1; | |
| } else { | |
| ColorRes *res = &(TScreenOf(xw)->Acolors[color]); | |
| TRACE(("ChangeAnsiColor for Acolors[%d]\n", color)); | |
| code = AllocateAnsiColor(xw, res, name); | |
| } | |
| return code; | |
| } | |
| /* | |
| * Set or query entries in the Acolors[] array by parsing pairs of color/name | |
| * values from the given buffer. | |
| * | |
| * The color can be any legal index into Acolors[], which consists of the | |
| * 16/88/256 "ANSI" colors, followed by special color values for the various | |
| * colorXX resources. The indices for the special color values are not | |
| * simple to work with, so an alternative is to use the calls which pass in | |
| * 'first' set to the beginning of those indices. | |
| * | |
| * If the name is "?", report to the host the current value for the color. | |
| */ | |
| static Bool | |
| ChangeAnsiColorRequest(XtermWidget xw, | |
| char *buf, | |
| int first, | |
| int final) | |
| { | |
| char *name; | |
| int color; | |
| int repaint = False; | |
| int code; | |
| int last = (MAXCOLORS - first); | |
| TRACE(("ChangeAnsiColorRequest string='%s'\n", buf)); | |
| while (buf && *buf) { | |
| name = strchr(buf, ';'); | |
| if (name == NULL) | |
| break; | |
| *name = '\0'; | |
| name++; | |
| color = atoi(buf); | |
| if (color < 0 || color >= last) | |
| break; /* quit on any error */ | |
| buf = strchr(name, ';'); | |
| if (buf) { | |
| *buf = '\0'; | |
| buf++; | |
| } | |
| if (!strcmp(name, "?")) { | |
| ReportAnsiColorRequest(xw, color + first, final); | |
| } else { | |
| code = ChangeOneAnsiColor(xw, color + first, name); | |
| if (code < 0) { | |
| /* stop on any error */ | |
| break; | |
| } else if (code > 0) { | |
| repaint = True; | |
| } | |
| /* FIXME: free old color somehow? We aren't for the other color | |
| * change style (dynamic colors). | |
| */ | |
| } | |
| } | |
| return (repaint); | |
| } | |
| static Bool | |
| ResetOneAnsiColor(XtermWidget xw, int color, int start) | |
| { | |
| Bool repaint = False; | |
| int last = MAXCOLORS - start; | |
| if (color >= 0 && color < last) { | |
| ColorRes *res = &(TScreenOf(xw)->Acolors[color + start]); | |
| if (res->mode) { | |
| /* a color has been allocated for this slot - test further... */ | |
| if (ChangeOneAnsiColor(xw, color + start, res->resource) > 0) { | |
| repaint = True; | |
| } | |
| } | |
| } | |
| return repaint; | |
| } | |
| int | |
| ResetAnsiColorRequest(XtermWidget xw, char *buf, int start) | |
| { | |
| int repaint = 0; | |
| int color; | |
| TRACE(("ResetAnsiColorRequest(%s)\n", buf)); | |
| if (*buf != '\0') { | |
| /* reset specific colors */ | |
| while (!IsEmpty(buf)) { | |
| char *next; | |
| color = (int) strtol(buf, &next, 10); | |
| if ((next == buf) || (color < 0)) | |
| break; /* no number at all */ | |
| if (next != 0) { | |
| if (strchr(";", *next) == 0) | |
| break; /* unexpected delimiter */ | |
| ++next; | |
| } | |
| if (ResetOneAnsiColor(xw, color, start)) { | |
| ++repaint; | |
| } | |
| buf = next; | |
| } | |
| } else { | |
| TRACE(("...resetting all %d colors\n", MAXCOLORS)); | |
| for (color = 0; color < MAXCOLORS; ++color) { | |
| if (ResetOneAnsiColor(xw, color, start)) { | |
| ++repaint; | |
| } | |
| } | |
| } | |
| TRACE(("...ResetAnsiColorRequest ->%d\n", repaint)); | |
| return repaint; | |
| } | |
| #else | |
| #define allocateClosestRGB(xw, cmap, def) 0 | |
| #define allocateExactRGB(xw, cmap, def) XAllocColor(TScreenOf(xw)->display, cmap, def) | |
| #endif /* OPT_ISO_COLORS */ | |
| static Boolean | |
| xtermAllocColor(XtermWidget xw, XColor * def, const char *spec) | |
| { | |
| Boolean result = False; | |
| TScreen *screen = TScreenOf(xw); | |
| Colormap cmap = xw->core.colormap; | |
| if (XParseColor(screen->display, cmap, spec, def) | |
| && (allocateExactRGB(xw, cmap, def) | |
| || allocateClosestRGB(xw, cmap, def))) { | |
| TRACE(("xtermAllocColor -> %x/%x/%x\n", | |
| def->red, def->green, def->blue)); | |
| result = True; | |
| } | |
| return result; | |
| } | |
| /* | |
| * This provides an approximation (the closest color from xterm's palette) | |
| * rather than the "exact" color (whatever the display could provide, actually) | |
| * because of the context in which it is used. | |
| */ | |
| #define ColorDiff(given,cache) ((long) ((cache) >> 8) - (long) (given)) | |
| int | |
| xtermClosestColor(XtermWidget xw, int find_red, int find_green, int find_blue) | |
| { | |
| int result = -1; | |
| #if OPT_COLOR_RES && OPT_ISO_COLORS | |
| int n; | |
| int best_index = -1; | |
| unsigned long best_value = 0; | |
| unsigned long this_value; | |
| long diff_red, diff_green, diff_blue; | |
| TRACE(("xtermClosestColor(%x/%x/%x)\n", find_red, find_green, find_blue)); | |
| for (n = NUM_ANSI_COLORS - 1; n >= 0; --n) { | |
| ColorRes *res = &(TScreenOf(xw)->Acolors[n]); | |
| /* ensure that we have a value for each of the colors */ | |
| if (!res->mode) { | |
| (void) AllocateAnsiColor(xw, res, res->resource); | |
| } | |
| /* find the closest match */ | |
| if (res->mode == True) { | |
| TRACE2(("...lookup %lx -> %x/%x/%x\n", | |
| res->value, res->red, res->green, res->blue)); | |
| diff_red = ColorDiff(find_red, res->red); | |
| diff_green = ColorDiff(find_green, res->green); | |
| diff_blue = ColorDiff(find_blue, res->blue); | |
| this_value = (unsigned long) ((diff_red * diff_red) | |
| + (diff_green * diff_green) | |
| + (diff_blue * diff_blue)); | |
| if (best_index < 0 || this_value < best_value) { | |
| best_index = n; | |
| best_value = this_value; | |
| } | |
| } | |
| } | |
| TRACE(("...best match at %d with diff %lx\n", best_index, best_value)); | |
| result = best_index; | |
| #else | |
| (void) xw; | |
| (void) find_red; | |
| (void) find_green; | |
| (void) find_blue; | |
| #endif | |
| return result; | |
| } | |
| #if OPT_PASTE64 | |
| static void | |
| ManipulateSelectionData(XtermWidget xw, TScreen * screen, char *buf, int final) | |
| { | |
| #define PDATA(a,b) { a, #b } | |
| static struct { | |
| char given; | |
| String result; | |
| } table[] = { | |
| PDATA('s', SELECT), | |
| PDATA('p', PRIMARY), | |
| PDATA('c', CLIPBOARD), | |
| PDATA('0', CUT_BUFFER0), | |
| PDATA('1', CUT_BUFFER1), | |
| PDATA('2', CUT_BUFFER2), | |
| PDATA('3', CUT_BUFFER3), | |
| PDATA('4', CUT_BUFFER4), | |
| PDATA('5', CUT_BUFFER5), | |
| PDATA('6', CUT_BUFFER6), | |
| PDATA('7', CUT_BUFFER7), | |
| }; | |
| const char *base = buf; | |
| char *used; | |
| Cardinal j, n = 0; | |
| String *select_args; | |
| TRACE(("Manipulate selection data\n")); | |
| while (*buf != ';' && *buf != '\0') { | |
| ++buf; | |
| } | |
| if (*buf == ';') { | |
| *buf++ = '\0'; | |
| if (*base == '\0') | |
| base = "s0"; | |
| if ((used = x_strdup(base)) != 0) { | |
| if ((select_args = TypeCallocN(String, 2 + strlen(base))) != 0) { | |
| while (*base != '\0') { | |
| for (j = 0; j < XtNumber(table); ++j) { | |
| if (*base == table[j].given) { | |
| used[n] = *base; | |
| select_args[n++] = table[j].result; | |
| TRACE(("atom[%d] %s\n", n, table[j].result)); | |
| break; | |
| } | |
| } | |
| ++base; | |
| } | |
| used[n] = 0; | |
| if (!strcmp(buf, "?")) { | |
| if (AllowWindowOps(xw, ewGetSelection)) { | |
| TRACE(("Getting selection\n")); | |
| unparseputc1(xw, ANSI_OSC); | |
| unparseputs(xw, "52"); | |
| unparseputc(xw, ';'); | |
| unparseputs(xw, used); | |
| unparseputc(xw, ';'); | |
| /* Tell xtermGetSelection data is base64 encoded */ | |
| screen->base64_paste = n; | |
| screen->base64_final = final; | |
| /* terminator will be written in this call */ | |
| xtermGetSelection((Widget) xw, | |
| (Time) 0, | |
| select_args, n, | |
| NULL); | |
| } | |
| } else { | |
| if (AllowWindowOps(xw, ewSetSelection)) { | |
| TRACE(("Setting selection with %s\n", buf)); | |
| ClearSelectionBuffer(screen); | |
| while (*buf != '\0') | |
| AppendToSelectionBuffer(screen, CharOf(*buf++)); | |
| CompleteSelection(xw, select_args, n); | |
| } | |
| } | |
| free(select_args); | |
| } | |
| free(used); | |
| } | |
| } | |
| } | |
| #endif /* OPT_PASTE64 */ | |
| /***====================================================================***/ | |
| #define IsSetUtf8Title(xw) (IsTitleMode(xw, tmSetUtf8) || (xw->screen.utf8_title)) | |
| static Bool | |
| xtermIsPrintable(XtermWidget xw, Char ** bufp, Char * last) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| Bool result = False; | |
| Char *cp = *bufp; | |
| Char *next = cp; | |
| (void) screen; | |
| (void) last; | |
| #if OPT_WIDE_CHARS | |
| if (xtermEnvUTF8() && IsSetUtf8Title(xw)) { | |
| PtyData data; | |
| if (decodeUtf8(fakePtyData(&data, cp, last))) { | |
| if (data.utf_data != UCS_REPL | |
| && (data.utf_data >= 128 || | |
| ansi_table[data.utf_data] == CASE_PRINT)) { | |
| next += (data.utf_size - 1); | |
| result = True; | |
| } else { | |
| result = False; | |
| } | |
| } else { | |
| result = False; | |
| } | |
| } else | |
| #endif | |
| #if OPT_C1_PRINT | |
| if (screen->c1_printable | |
| && (*cp >= 128 && *cp < 160)) { | |
| result = True; | |
| } else | |
| #endif | |
| if (ansi_table[*cp] == CASE_PRINT) { | |
| result = True; | |
| } | |
| *bufp = next; | |
| return result; | |
| } | |
| /***====================================================================***/ | |
| /* | |
| * Enum corresponding to the actual OSC codes rather than the internal | |
| * array indices. Compare with TermColors. | |
| */ | |
| typedef enum { | |
| OSC_TEXT_FG = 10 | |
| ,OSC_TEXT_BG | |
| ,OSC_TEXT_CURSOR | |
| ,OSC_MOUSE_FG | |
| ,OSC_MOUSE_BG | |
| #if OPT_TEK4014 | |
| ,OSC_TEK_FG = 15 | |
| ,OSC_TEK_BG | |
| #endif | |
| #if OPT_HIGHLIGHT_COLOR | |
| ,OSC_HIGHLIGHT_BG = 17 | |
| #endif | |
| #if OPT_TEK4014 | |
| ,OSC_TEK_CURSOR = 18 | |
| #endif | |
| #if OPT_HIGHLIGHT_COLOR | |
| ,OSC_HIGHLIGHT_FG = 19 | |
| #endif | |
| ,OSC_NCOLORS | |
| } OscTextColors; | |
| /* | |
| * Map codes to OSC controls that can reset colors. | |
| */ | |
| #define OSC_RESET 100 | |
| #define OSC_Reset(code) (code) + OSC_RESET | |
| static ScrnColors *pOldColors = NULL; | |
| static Bool | |
| GetOldColors(XtermWidget xw) | |
| { | |
| int i; | |
| if (pOldColors == NULL) { | |
| pOldColors = TypeXtMalloc(ScrnColors); | |
| if (pOldColors == NULL) { | |
| xtermWarning("allocation failure in GetOldColors\n"); | |
| return (False); | |
| } | |
| pOldColors->which = 0; | |
| for (i = 0; i < NCOLORS; i++) { | |
| pOldColors->colors[i] = 0; | |
| pOldColors->names[i] = NULL; | |
| } | |
| GetColors(xw, pOldColors); | |
| } | |
| return (True); | |
| } | |
| static int | |
| oppositeColor(int n) | |
| { | |
| switch (n) { | |
| case TEXT_FG: | |
| n = TEXT_BG; | |
| break; | |
| case TEXT_BG: | |
| n = TEXT_FG; | |
| break; | |
| case MOUSE_FG: | |
| n = MOUSE_BG; | |
| break; | |
| case MOUSE_BG: | |
| n = MOUSE_FG; | |
| break; | |
| #if OPT_TEK4014 | |
| case TEK_FG: | |
| n = TEK_BG; | |
| break; | |
| case TEK_BG: | |
| n = TEK_FG; | |
| break; | |
| #endif | |
| #if OPT_HIGHLIGHT_COLOR | |
| case HIGHLIGHT_FG: | |
| n = HIGHLIGHT_BG; | |
| break; | |
| case HIGHLIGHT_BG: | |
| n = HIGHLIGHT_FG; | |
| break; | |
| #endif | |
| default: | |
| break; | |
| } | |
| return n; | |
| } | |
| static void | |
| ReportColorRequest(XtermWidget xw, int ndx, int final) | |
| { | |
| if (AllowColorOps(xw, ecGetColor)) { | |
| XColor color; | |
| Colormap cmap = xw->core.colormap; | |
| char buffer[80]; | |
| /* | |
| * ChangeColorsRequest() has "always" chosen the opposite color when | |
| * reverse-video is set. Report this as the original color index, but | |
| * reporting the opposite color which would be used. | |
| */ | |
| int i = (xw->misc.re_verse) ? oppositeColor(ndx) : ndx; | |
| GetOldColors(xw); | |
| color.pixel = pOldColors->colors[ndx]; | |
| XQueryColor(TScreenOf(xw)->display, cmap, &color); | |
| sprintf(buffer, "%d;rgb:%04x/%04x/%04x", i + 10, | |
| color.red, | |
| color.green, | |
| color.blue); | |
| TRACE(("ReportColorRequest #%d: 0x%06lx as %s\n", | |
| ndx, pOldColors->colors[ndx], buffer)); | |
| unparseputc1(xw, ANSI_OSC); | |
| unparseputs(xw, buffer); | |
| unparseputc1(xw, final); | |
| unparse_end(xw); | |
| } | |
| } | |
| static Bool | |
| UpdateOldColors(XtermWidget xw GCC_UNUSED, ScrnColors * pNew) | |
| { | |
| int i; | |
| /* if we were going to free old colors, this would be the place to | |
| * do it. I've decided not to (for now), because it seems likely | |
| * that we'd have a small set of colors we use over and over, and that | |
| * we could save some overhead this way. The only case in which this | |
| * (clearly) fails is if someone is trying a boatload of colors, in | |
| * which case they can restart xterm | |
| */ | |
| for (i = 0; i < NCOLORS; i++) { | |
| if (COLOR_DEFINED(pNew, i)) { | |
| if (pOldColors->names[i] != NULL) { | |
| XtFree(pOldColors->names[i]); | |
| pOldColors->names[i] = NULL; | |
| } | |
| if (pNew->names[i]) { | |
| pOldColors->names[i] = pNew->names[i]; | |
| } | |
| pOldColors->colors[i] = pNew->colors[i]; | |
| } | |
| } | |
| return (True); | |
| } | |
| /* | |
| * OSC codes are constant, but the indices for the color arrays depend on how | |
| * xterm is compiled. | |
| */ | |
| static int | |
| OscToColorIndex(OscTextColors mode) | |
| { | |
| int result = 0; | |
| #define CASE(name) case OSC_##name: result = name; break | |
| switch (mode) { | |
| CASE(TEXT_FG); | |
| CASE(TEXT_BG); | |
| CASE(TEXT_CURSOR); | |
| CASE(MOUSE_FG); | |
| CASE(MOUSE_BG); | |
| #if OPT_TEK4014 | |
| CASE(TEK_FG); | |
| CASE(TEK_BG); | |
| #endif | |
| #if OPT_HIGHLIGHT_COLOR | |
| CASE(HIGHLIGHT_BG); | |
| CASE(HIGHLIGHT_FG); | |
| #endif | |
| #if OPT_TEK4014 | |
| CASE(TEK_CURSOR); | |
| #endif | |
| case OSC_NCOLORS: | |
| break; | |
| } | |
| return result; | |
| } | |
| static Bool | |
| ChangeColorsRequest(XtermWidget xw, | |
| int start, | |
| char *names, | |
| int final) | |
| { | |
| Bool result = False; | |
| char *thisName; | |
| ScrnColors newColors; | |
| int i, ndx; | |
| TRACE(("ChangeColorsRequest start=%d, names='%s'\n", start, names)); | |
| if (GetOldColors(xw)) { | |
| newColors.which = 0; | |
| for (i = 0; i < NCOLORS; i++) { | |
| newColors.names[i] = NULL; | |
| } | |
| for (i = start; i < OSC_NCOLORS; i++) { | |
| ndx = OscToColorIndex((OscTextColors) i); | |
| if (xw->misc.re_verse) | |
| ndx = oppositeColor(ndx); | |
| if (IsEmpty(names)) { | |
| newColors.names[ndx] = NULL; | |
| } else { | |
| if (names[0] == ';') | |
| thisName = NULL; | |
| else | |
| thisName = names; | |
| names = strchr(names, ';'); | |
| if (names != NULL) { | |
| *names++ = '\0'; | |
| } | |
| if (thisName != 0 && !strcmp(thisName, "?")) { | |
| ReportColorRequest(xw, ndx, final); | |
| } else if (!pOldColors->names[ndx] | |
| || (thisName | |
| && strcmp(thisName, pOldColors->names[ndx]))) { | |
| AllocateTermColor(xw, &newColors, ndx, thisName, False); | |
| } | |
| } | |
| } | |
| if (newColors.which != 0) { | |
| ChangeColors(xw, &newColors); | |
| UpdateOldColors(xw, &newColors); | |
| } | |
| result = True; | |
| } | |
| return result; | |
| } | |
| static Bool | |
| ResetColorsRequest(XtermWidget xw, | |
| int code) | |
| { | |
| Bool result = False; | |
| const char *thisName; | |
| ScrnColors newColors; | |
| int ndx; | |
| TRACE(("ResetColorsRequest code=%d\n", code)); | |
| #if OPT_COLOR_RES | |
| if (GetOldColors(xw)) { | |
| ndx = OscToColorIndex((OscTextColors) (code - OSC_RESET)); | |
| if (xw->misc.re_verse) | |
| ndx = oppositeColor(ndx); | |
| thisName = xw->screen.Tcolors[ndx].resource; | |
| newColors.which = 0; | |
| newColors.names[ndx] = NULL; | |
| if (thisName != 0 | |
| && pOldColors->names[ndx] != 0 | |
| && strcmp(thisName, pOldColors->names[ndx])) { | |
| AllocateTermColor(xw, &newColors, ndx, thisName, False); | |
| if (newColors.which != 0) { | |
| ChangeColors(xw, &newColors); | |
| UpdateOldColors(xw, &newColors); | |
| } | |
| } | |
| result = True; | |
| } | |
| #endif | |
| return result; | |
| } | |
| #if OPT_SHIFT_FONTS | |
| /* | |
| * Initially, 'source' points to '#' or '?'. | |
| * | |
| * Look for an optional sign and optional number. If those are found, lookup | |
| * the corresponding menu font entry. | |
| */ | |
| static int | |
| ParseShiftedFont(XtermWidget xw, String source, String * target) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| int num = screen->menu_font_number; | |
| int rel = 0; | |
| if (*++source == '+') { | |
| rel = 1; | |
| source++; | |
| } else if (*source == '-') { | |
| rel = -1; | |
| source++; | |
| } | |
| if (isdigit(CharOf(*source))) { | |
| int val = atoi(source); | |
| if (rel > 0) | |
| rel = val; | |
| else if (rel < 0) | |
| rel = -val; | |
| else | |
| num = val; | |
| } | |
| if (rel != 0) { | |
| num = lookupRelativeFontSize(xw, | |
| screen->menu_font_number, rel); | |
| } | |
| TRACE(("ParseShiftedFont(%s) ->%d (%s)\n", *target, num, source)); | |
| *target = source; | |
| return num; | |
| } | |
| static void | |
| QueryFontRequest(XtermWidget xw, String buf, int final) | |
| { | |
| if (AllowFontOps(xw, efGetFont)) { | |
| TScreen *screen = TScreenOf(xw); | |
| Bool success = True; | |
| int num; | |
| String base = buf + 1; | |
| const char *name = 0; | |
| char temp[10]; | |
| num = ParseShiftedFont(xw, buf, &buf); | |
| if (num < 0 | |
| || num > fontMenu_lastBuiltin) { | |
| Bell(xw, XkbBI_MinorError, 0); | |
| success = False; | |
| } else { | |
| #if OPT_RENDERFONT | |
| if (UsingRenderFont(xw)) { | |
| name = getFaceName(xw, False); | |
| } else | |
| #endif | |
| if ((name = screen->MenuFontName(num)) == 0) { | |
| success = False; | |
| } | |
| } | |
| unparseputc1(xw, ANSI_OSC); | |
| unparseputs(xw, "50"); | |
| if (success) { | |
| unparseputc(xw, ';'); | |
| if (buf >= base) { | |
| /* identify the font-entry, unless it is the current one */ | |
| if (*buf != '\0') { | |
| unparseputc(xw, '#'); | |
| sprintf(temp, "%d", num); | |
| unparseputs(xw, temp); | |
| if (*name != '\0') | |
| unparseputc(xw, ' '); | |
| } | |
| } | |
| unparseputs(xw, name); | |
| } | |
| unparseputc1(xw, final); | |
| unparse_end(xw); | |
| } | |
| } | |
| static void | |
| ChangeFontRequest(XtermWidget xw, String buf) | |
| { | |
| if (AllowFontOps(xw, efSetFont)) { | |
| TScreen *screen = TScreenOf(xw); | |
| Bool success = True; | |
| int num; | |
| VTFontNames fonts; | |
| char *name; | |
| /* | |
| * If the font specification is a "#", followed by an optional sign and | |
| * optional number, lookup the corresponding menu font entry. | |
| * | |
| * Further, if the "#", etc., is followed by a font name, use that | |
| * to load the font entry. | |
| */ | |
| if (*buf == '#') { | |
| num = ParseShiftedFont(xw, buf, &buf); | |
| if (num < 0 | |
| || num > fontMenu_lastBuiltin) { | |
| Bell(xw, XkbBI_MinorError, 0); | |
| success = False; | |
| } else { | |
| /* | |
| * Skip past the optional number, and any whitespace to look | |
| * for a font specification within the control. | |
| */ | |
| while (isdigit(CharOf(*buf))) { | |
| ++buf; | |
| } | |
| while (isspace(CharOf(*buf))) { | |
| ++buf; | |
| } | |
| #if OPT_RENDERFONT | |
| if (UsingRenderFont(xw)) { | |
| /* EMPTY */ | |
| /* there is only one font entry to load */ | |
| ; | |
| } else | |
| #endif | |
| { | |
| /* | |
| * Normally there is no font specified in the control. | |
| * But if there is, simply overwrite the font entry. | |
| */ | |
| if (*buf == '\0') { | |
| if ((buf = screen->MenuFontName(num)) == 0) { | |
| success = False; | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| num = screen->menu_font_number; | |
| } | |
| name = x_strtrim(buf); | |
| if (success && !IsEmpty(name)) { | |
| #if OPT_RENDERFONT | |
| if (UsingRenderFont(xw)) { | |
| setFaceName(xw, name); | |
| xtermUpdateFontInfo(xw, True); | |
| } else | |
| #endif | |
| { | |
| memset(&fonts, 0, sizeof(fonts)); | |
| fonts.f_n = name; | |
| SetVTFont(xw, num, True, &fonts); | |
| } | |
| } else { | |
| Bell(xw, XkbBI_MinorError, 0); | |
| } | |
| free(name); | |
| } | |
| } | |
| #endif /* OPT_SHIFT_FONTS */ | |
| /***====================================================================***/ | |
| void | |
| do_osc(XtermWidget xw, Char * oscbuf, size_t len, int final) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| int mode; | |
| Char *cp; | |
| int state = 0; | |
| char *buf = 0; | |
| char temp[2]; | |
| #if OPT_ISO_COLORS | |
| int ansi_colors = 0; | |
| #endif | |
| Bool need_data = True; | |
| TRACE(("do_osc %s\n", oscbuf)); | |
| (void) screen; | |
| /* | |
| * Lines should be of the form <OSC> number ; string <ST>, however | |
| * older xterms can accept <BEL> as a final character. We will respond | |
| * with the same final character as the application sends to make this | |
| * work better with shell scripts, which may have trouble reading an | |
| * <ESC><backslash>, which is the 7-bit equivalent to <ST>. | |
| */ | |
| mode = 0; | |
| for (cp = oscbuf; *cp != '\0'; cp++) { | |
| switch (state) { | |
| case 0: | |
| if (isdigit(*cp)) { | |
| mode = 10 * mode + (*cp - '0'); | |
| if (mode > 65535) { | |
| TRACE(("do_osc found unknown mode %d\n", mode)); | |
| return; | |
| } | |
| break; | |
| } | |
| /* FALLTHRU */ | |
| case 1: | |
| if (*cp != ';') { | |
| TRACE(("do_osc did not find semicolon offset %d\n", | |
| (int) (cp - oscbuf))); | |
| return; | |
| } | |
| state = 2; | |
| break; | |
| case 2: | |
| buf = (char *) cp; | |
| state = 3; | |
| /* FALLTHRU */ | |
| default: | |
| if (!xtermIsPrintable(xw, &cp, oscbuf + len)) { | |
| switch (mode) { | |
| case 0: | |
| case 1: | |
| case 2: | |
| break; | |
| default: | |
| TRACE(("do_osc found nonprinting char %02X offset %d\n", | |
| CharOf(*cp), | |
| (int) (cp - oscbuf))); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * Check if the palette changed and there are no more immediate changes | |
| * that could be deferred to the next repaint. | |
| */ | |
| if (xw->misc.palette_changed) { | |
| switch (mode) { | |
| case 3: /* change X property */ | |
| case 30: /* Konsole (unused) */ | |
| case 31: /* Konsole (unused) */ | |
| case 50: /* font operations */ | |
| case 51: /* Emacs (unused) */ | |
| #if OPT_PASTE64 | |
| case 52: /* selection data */ | |
| #endif | |
| TRACE(("forced repaint after palette changed\n")); | |
| xw->misc.palette_changed = False; | |
| xtermRepaint(xw); | |
| break; | |
| } | |
| } | |
| /* | |
| * Most OSC controls other than resets require data. Handle the others as | |
| * a special case. | |
| */ | |
| switch (mode) { | |
| #if OPT_ISO_COLORS | |
| case OSC_Reset(4): | |
| case OSC_Reset(5): | |
| case OSC_Reset(OSC_TEXT_FG): | |
| case OSC_Reset(OSC_TEXT_BG): | |
| case OSC_Reset(OSC_TEXT_CURSOR): | |
| case OSC_Reset(OSC_MOUSE_FG): | |
| case OSC_Reset(OSC_MOUSE_BG): | |
| #if OPT_HIGHLIGHT_COLOR | |
| case OSC_Reset(OSC_HIGHLIGHT_BG): | |
| case OSC_Reset(OSC_HIGHLIGHT_FG): | |
| #endif | |
| #if OPT_TEK4014 | |
| case OSC_Reset(OSC_TEK_FG): | |
| case OSC_Reset(OSC_TEK_BG): | |
| case OSC_Reset(OSC_TEK_CURSOR): | |
| #endif | |
| need_data = False; | |
| break; | |
| #endif | |
| default: | |
| break; | |
| } | |
| /* | |
| * Check if we have data when we want, and not when we do not want it. | |
| * Either way, that is a malformed control sequence, and will be ignored. | |
| */ | |
| if (IsEmpty(buf)) { | |
| if (need_data) { | |
| TRACE(("do_osc found no data\n")); | |
| return; | |
| } | |
| temp[0] = '\0'; | |
| buf = temp; | |
| } else if (!need_data) { | |
| TRACE(("do_osc found found unwanted data\n")); | |
| return; | |
| } | |
| switch (mode) { | |
| case 0: /* new icon name and title */ | |
| ChangeIconName(xw, buf); | |
| ChangeTitle(xw, buf); | |
| break; | |
| case 1: /* new icon name only */ | |
| ChangeIconName(xw, buf); | |
| break; | |
| case 2: /* new title only */ | |
| ChangeTitle(xw, buf); | |
| break; | |
| case 3: /* change X property */ | |
| if (AllowWindowOps(xw, ewSetXprop)) | |
| ChangeXprop(buf); | |
| break; | |
| #if OPT_ISO_COLORS | |
| case 5: | |
| ansi_colors = NUM_ANSI_COLORS; | |
| /* FALLTHRU */ | |
| case 4: | |
| if (ChangeAnsiColorRequest(xw, buf, ansi_colors, final)) | |
| xw->misc.palette_changed = True; | |
| break; | |
| case OSC_Reset(5): | |
| ansi_colors = NUM_ANSI_COLORS; | |
| /* FALLTHRU */ | |
| case OSC_Reset(4): | |
| if (ResetAnsiColorRequest(xw, buf, ansi_colors)) | |
| xw->misc.palette_changed = True; | |
| break; | |
| #endif | |
| case OSC_TEXT_FG: | |
| case OSC_TEXT_BG: | |
| case OSC_TEXT_CURSOR: | |
| case OSC_MOUSE_FG: | |
| case OSC_MOUSE_BG: | |
| #if OPT_HIGHLIGHT_COLOR | |
| case OSC_HIGHLIGHT_BG: | |
| case OSC_HIGHLIGHT_FG: | |
| #endif | |
| #if OPT_TEK4014 | |
| case OSC_TEK_FG: | |
| case OSC_TEK_BG: | |
| case OSC_TEK_CURSOR: | |
| #endif | |
| if (xw->misc.dynamicColors) { | |
| ChangeColorsRequest(xw, mode, buf, final); | |
| } | |
| break; | |
| case OSC_Reset(OSC_TEXT_FG): | |
| case OSC_Reset(OSC_TEXT_BG): | |
| case OSC_Reset(OSC_TEXT_CURSOR): | |
| case OSC_Reset(OSC_MOUSE_FG): | |
| case OSC_Reset(OSC_MOUSE_BG): | |
| #if OPT_HIGHLIGHT_COLOR | |
| case OSC_Reset(OSC_HIGHLIGHT_BG): | |
| case OSC_Reset(OSC_HIGHLIGHT_FG): | |
| #endif | |
| #if OPT_TEK4014 | |
| case OSC_Reset(OSC_TEK_FG): | |
| case OSC_Reset(OSC_TEK_BG): | |
| case OSC_Reset(OSC_TEK_CURSOR): | |
| #endif | |
| if (xw->misc.dynamicColors) { | |
| ResetColorsRequest(xw, mode); | |
| } | |
| break; | |
| case 30: | |
| case 31: | |
| /* reserved for Konsole (Stephan Binner <Stephan.Binner@gmx.de>) */ | |
| break; | |
| #ifdef ALLOWLOGGING | |
| case 46: /* new log file */ | |
| #ifdef ALLOWLOGFILECHANGES | |
| /* | |
| * Warning, enabling this feature allows people to overwrite | |
| * arbitrary files accessible to the person running xterm. | |
| */ | |
| if (strcmp(buf, "?") | |
| && (cp = CastMallocN(char, strlen(buf)) != NULL)) { | |
| strcpy(cp, buf); | |
| if (screen->logfile) | |
| free(screen->logfile); | |
| screen->logfile = cp; | |
| break; | |
| } | |
| #endif | |
| Bell(xw, XkbBI_Info, 0); | |
| Bell(xw, XkbBI_Info, 0); | |
| break; | |
| #endif /* ALLOWLOGGING */ | |
| case 50: | |
| #if OPT_SHIFT_FONTS | |
| if (*buf == '?') { | |
| QueryFontRequest(xw, buf, final); | |
| } else if (xw->misc.shift_fonts) { | |
| ChangeFontRequest(xw, buf); | |
| } | |
| #endif /* OPT_SHIFT_FONTS */ | |
| break; | |
| case 51: | |
| /* reserved for Emacs shell (Rob Mayoff <mayoff@dqd.com>) */ | |
| break; | |
| #if OPT_PASTE64 | |
| case 52: | |
| ManipulateSelectionData(xw, screen, buf, final); | |
| break; | |
| #endif | |
| /* | |
| * One could write code to send back the display and host names, | |
| * but that could potentially open a fairly nasty security hole. | |
| */ | |
| default: | |
| TRACE(("do_osc - unrecognized code\n")); | |
| break; | |
| } | |
| unparse_end(xw); | |
| } | |
| #ifdef SunXK_F36 | |
| #define MAX_UDK 37 | |
| #else | |
| #define MAX_UDK 35 | |
| #endif | |
| static struct { | |
| char *str; | |
| int len; | |
| } user_keys[MAX_UDK]; | |
| /* | |
| * Parse one nibble of a hex byte from the OSC string. We have removed the | |
| * string-terminator (replacing it with a null), so the only other delimiter | |
| * that is expected is semicolon. Ignore other characters (Ray Neuman says | |
| * "real" terminals accept commas in the string definitions). | |
| */ | |
| static int | |
| udk_value(const char **cp) | |
| { | |
| int result = -1; | |
| int c; | |
| for (;;) { | |
| if ((c = **cp) != '\0') | |
| *cp = *cp + 1; | |
| if (c == ';' || c == '\0') | |
| break; | |
| if ((result = x_hex2int(c)) >= 0) | |
| break; | |
| } | |
| return result; | |
| } | |
| void | |
| reset_decudk(void) | |
| { | |
| int n; | |
| for (n = 0; n < MAX_UDK; n++) { | |
| if (user_keys[n].str != 0) { | |
| free(user_keys[n].str); | |
| user_keys[n].str = 0; | |
| user_keys[n].len = 0; | |
| } | |
| } | |
| } | |
| /* | |
| * Parse the data for DECUDK (user-defined keys). | |
| */ | |
| static void | |
| parse_decudk(const char *cp) | |
| { | |
| while (*cp) { | |
| const char *base = cp; | |
| char *str = CastMallocN(char, strlen(cp) + 2); | |
| unsigned key = 0; | |
| int lo, hi; | |
| int len = 0; | |
| while (isdigit(CharOf(*cp))) | |
| key = (key * 10) + (unsigned) (*cp++ - '0'); | |
| if (*cp == '/') { | |
| cp++; | |
| while ((hi = udk_value(&cp)) >= 0 | |
| && (lo = udk_value(&cp)) >= 0) { | |
| str[len++] = (char) ((hi << 4) | lo); | |
| } | |
| } | |
| if (len > 0 && key < MAX_UDK) { | |
| str[len] = '\0'; | |
| if (user_keys[key].str != 0) | |
| free(user_keys[key].str); | |
| user_keys[key].str = str; | |
| user_keys[key].len = len; | |
| } else { | |
| free(str); | |
| } | |
| if (*cp == ';') | |
| cp++; | |
| if (cp == base) /* badly-formed sequence - bail out */ | |
| break; | |
| } | |
| } | |
| #if OPT_TRACE | |
| #define SOFT_WIDE 10 | |
| #define SOFT_HIGH 20 | |
| static void | |
| parse_decdld(ANSI * params, const char *string) | |
| { | |
| char DscsName[8]; | |
| int len; | |
| int Pfn = params->a_param[0]; | |
| int Pcn = params->a_param[1]; | |
| int Pe = params->a_param[2]; | |
| int Pcmw = params->a_param[3]; | |
| int Pw = params->a_param[4]; | |
| int Pt = params->a_param[5]; | |
| int Pcmh = params->a_param[6]; | |
| int Pcss = params->a_param[7]; | |
| int start_char = Pcn + 0x20; | |
| int char_wide = ((Pcmw == 0) | |
| ? (Pcss ? 6 : 10) | |
| : (Pcmw > 4 | |
| ? Pcmw | |
| : (Pcmw + 3))); | |
| int char_high = ((Pcmh == 0) | |
| ? ((Pcmw >= 2 && Pcmw <= 4) | |
| ? 10 | |
| : 20) | |
| : Pcmh); | |
| Char ch; | |
| Char bits[SOFT_HIGH][SOFT_WIDE]; | |
| Bool first = True; | |
| Bool prior = False; | |
| int row = 0, col = 0; | |
| TRACE(("Parsing DECDLD\n")); | |
| TRACE((" font number %d\n", Pfn)); | |
| TRACE((" starting char %d\n", Pcn)); | |
| TRACE((" erase control %d\n", Pe)); | |
| TRACE((" char-width %d\n", Pcmw)); | |
| TRACE((" font-width %d\n", Pw)); | |
| TRACE((" text/full %d\n", Pt)); | |
| TRACE((" char-height %d\n", Pcmh)); | |
| TRACE((" charset-size %d\n", Pcss)); | |
| if (Pfn > 1 | |
| || Pcn > 95 | |
| || Pe > 2 | |
| || Pcmw > 10 | |
| || Pcmw == 1 | |
| || Pt > 2 | |
| || Pcmh > 20 | |
| || Pcss > 1 | |
| || char_wide > SOFT_WIDE | |
| || char_high > SOFT_HIGH) { | |
| TRACE(("DECDLD illegal parameter\n")); | |
| return; | |
| } | |
| len = 0; | |
| while (*string != '\0') { | |
| ch = CharOf(*string++); | |
| if (ch >= ANSI_SPA && ch <= 0x2f) { | |
| if (len < 2) | |
| DscsName[len++] = (char) ch; | |
| } else if (ch >= 0x30 && ch <= 0x7e) { | |
| DscsName[len++] = (char) ch; | |
| break; | |
| } | |
| } | |
| DscsName[len] = 0; | |
| TRACE((" Dscs name '%s'\n", DscsName)); | |
| TRACE((" character matrix %dx%d\n", char_high, char_wide)); | |
| while (*string != '\0') { | |
| if (first) { | |
| TRACE(("Char %d:\n", start_char)); | |
| if (prior) { | |
| for (row = 0; row < char_high; ++row) { | |
| TRACE(("%.*s\n", char_wide, bits[row])); | |
| } | |
| } | |
| prior = False; | |
| first = False; | |
| for (row = 0; row < char_high; ++row) { | |
| for (col = 0; col < char_wide; ++col) { | |
| bits[row][col] = '.'; | |
| } | |
| } | |
| row = col = 0; | |
| } | |
| ch = CharOf(*string++); | |
| if (ch >= 0x3f && ch <= 0x7e) { | |
| int n; | |
| ch = CharOf(ch - 0x3f); | |
| for (n = 0; n < 6; ++n) { | |
| bits[row + n][col] = CharOf((ch & (1 << n)) ? '*' : '.'); | |
| } | |
| col += 1; | |
| prior = True; | |
| } else if (ch == '/') { | |
| row += 6; | |
| col = 0; | |
| } else if (ch == ';') { | |
| first = True; | |
| ++start_char; | |
| } | |
| } | |
| } | |
| #else | |
| #define parse_decdld(p,q) /* nothing */ | |
| #endif | |
| /* | |
| * Parse numeric parameters. Normally we use a state machine to simplify | |
| * interspersing with control characters, but have the string already. | |
| */ | |
| static void | |
| parse_ansi_params(ANSI * params, const char **string) | |
| { | |
| const char *cp = *string; | |
| ParmType nparam = 0; | |
| memset(params, 0, sizeof(*params)); | |
| while (*cp != '\0') { | |
| Char ch = CharOf(*cp++); | |
| if (isdigit(ch)) { | |
| if (nparam < NPARAM) { | |
| params->a_param[nparam] = | |
| (ParmType) ((params->a_param[nparam] * 10) | |
| + (ch - '0')); | |
| } | |
| } else if (ch == ';') { | |
| if (++nparam < NPARAM) | |
| params->a_nparam = nparam; | |
| } else if (ch < 32) { | |
| /* EMPTY */ ; | |
| } else { | |
| /* should be 0x30 to 0x7e */ | |
| params->a_final = ch; | |
| break; | |
| } | |
| } | |
| *string = cp; | |
| } | |
| void | |
| do_dcs(XtermWidget xw, Char * dcsbuf, size_t dcslen) | |
| { | |
| TScreen *screen = TScreenOf(xw); | |
| char reply[BUFSIZ]; | |
| const char *cp = (const char *) dcsbuf; | |
| Bool okay; | |
| ANSI params; | |
| TRACE(("do_dcs(%s:%lu)\n", (char *) dcsbuf, (unsigned long) dcslen)); | |
| if (dcslen != strlen(cp)) | |
| /* shouldn't have nulls in the string */ | |
| return; | |
| switch (*cp) { /* intermediate character, or parameter */ | |
| case '$': /* DECRQSS */ | |
| okay = True; | |
| cp++; | |
| if (*cp++ == 'q') { | |
| if (!strcmp(cp, "\"q")) { /* DECSCA */ | |
| sprintf(reply, "%d%s", | |
| (screen->protected_mode == DEC_PROTECT) | |
| && (xw->flags & PROTECTED) ? 1 : 0, | |
| cp); | |
| } else if (!strcmp(cp, "\"p")) { /* DECSCL */ | |
| if (screen->vtXX_level < 2) { | |
| /* actually none of DECRQSS is valid for vt100's */ | |
| break; | |
| } | |
| sprintf(reply, "%d%s%s", | |
| (screen->vtXX_level ? | |
| screen->vtXX_level : 1) + 60, | |
| (screen->vtXX_level >= 2) | |
| ? (screen->control_eight_bits | |
| ? ";0" : ";1") | |
| : "", | |
| cp); | |
| } else if (!strcmp(cp, "r")) { /* DECSTBM */ | |
| sprintf(reply, "%d;%dr", | |
| screen->top_marg + 1, | |
| screen->bot_marg + 1); | |
| } else if (!strcmp(cp, "s")) { /* DECSLRM */ | |
| if (screen->vtXX_level >= 4) { /* VT420 */ | |
| sprintf(reply, "%d;%ds", | |
| screen->lft_marg + 1, | |
| screen->rgt_marg + 1); | |
| } | |
| } else if (!strcmp(cp, "m")) { /* SGR */ | |
| strcpy(reply, "0"); | |
| if (xw->flags & BOLD) | |
| strcat(reply, ";1"); | |
| if (xw->flags & UNDERLINE) | |
| strcat(reply, ";4"); | |
| if (xw->flags & BLINK) | |
| strcat(reply, ";5"); | |
| if (xw->flags & INVERSE) | |
| strcat(reply, ";7"); | |
| if (xw->flags & INVISIBLE) | |
| strcat(reply, ";8"); | |
| #if OPT_256_COLORS || OPT_88_COLORS | |
| if_OPT_ISO_COLORS(screen, { | |
| if (xw->flags & FG_COLOR) { | |
| if (xw->cur_foreground >= 16) | |
| sprintf(reply + strlen(reply), | |
| ";38;5;%d", xw->cur_foreground); | |
| else | |
| sprintf(reply + strlen(reply), | |
| ";%d%d", | |
| xw->cur_foreground >= 8 ? 9 : 3, | |
| xw->cur_foreground >= 8 ? | |
| xw->cur_foreground - 8 : | |
| xw->cur_foreground); | |
| } | |
| if (xw->flags & BG_COLOR) { | |
| if (xw->cur_background >= 16) | |
| sprintf(reply + strlen(reply), | |
| ";48;5;%d", xw->cur_foreground); | |
| else | |
| sprintf(reply + strlen(reply), | |
| ";%d%d", | |
| xw->cur_background >= 8 ? 10 : 4, | |
| xw->cur_background >= 8 ? | |
| xw->cur_background - 8 : | |
| xw->cur_background); | |
| } | |
| }); | |
| #elif OPT_ISO_COLORS | |
| if_OPT_ISO_COLORS(screen, { | |
| if (xw->flags & FG_COLOR) | |
| sprintf(reply + strlen(reply), | |
| ";%d%d", | |
| xw->cur_foreground >= 8 ? 9 : 3, | |
| xw->cur_foreground >= 8 ? | |
| xw->cur_foreground - 8 : | |
| xw->cur_foreground); | |
| if (xw->flags & BG_COLOR) | |
| sprintf(reply + strlen(reply), | |
| ";%d%d", | |
| xw->cur_background >= 8 ? 10 : 4, | |
| xw->cur_background >= 8 ? | |
| xw->cur_background - 8 : | |
| xw->cur_background); | |
| }); | |
| #endif | |
| strcat(reply, "m"); | |
| } else if (!strcmp(cp, " q")) { /* DECSCUSR */ | |
| int code = STEADY_BLOCK; | |
| if (isCursorUnderline(screen)) | |
| code = STEADY_UNDERLINE; | |
| else if (isCursorBar(screen)) | |
| code = STEADY_BAR; | |
| #if OPT_BLINK_CURS | |
| if (screen->cursor_blink_esc == 0) | |
| code -= 1; | |
| #endif | |
| sprintf(reply, "%d%s", code, cp); | |
| } else | |
| okay = False; | |
| if (okay) { | |
| unparseputc1(xw, ANSI_DCS); | |
| unparseputc(xw, '1'); | |
| unparseputc(xw, '$'); | |
| unparseputc(xw, 'r'); | |
| cp = reply; | |
| unparseputs(xw, cp); | |
| unparseputc1(xw, ANSI_ST); | |
| } else { | |
| unparseputc(xw, ANSI_CAN); | |
| } | |
| } else { | |
| unparseputc(xw, ANSI_CAN); | |
| } | |
| break; | |
| #if OPT_TCAP_QUERY | |
| case '+': | |
| cp++; | |
| switch (*cp) { | |
| case 'p': | |
| if (AllowTcapOps(xw, etSetTcap)) { | |
| set_termcap(xw, cp + 1); | |
| } | |
| break; | |
| case 'q': | |
| if (AllowTcapOps(xw, etGetTcap)) { | |
| Bool fkey; | |
| unsigned state; | |
| int code; | |
| const char *tmp; | |
| const char *parsed = ++cp; | |
| code = xtermcapKeycode(xw, &parsed, &state, &fkey); | |
| unparseputc1(xw, ANSI_DCS); | |
| unparseputc(xw, code >= 0 ? '1' : '0'); | |
| unparseputc(xw, '+'); | |
| unparseputc(xw, 'r'); | |
| while (*cp != 0 && (code >= -1)) { | |
| if (cp == parsed) | |
| break; /* no data found, error */ | |
| for (tmp = cp; tmp != parsed; ++tmp) | |
| unparseputc(xw, *tmp); | |
| if (code >= 0) { | |
| unparseputc(xw, '='); | |
| screen->tc_query_code = code; | |
| screen->tc_query_fkey = fkey; | |
| #if OPT_ISO_COLORS | |
| /* XK_COLORS is a fake code for the "Co" entry (maximum | |
| * number of colors) */ | |
| if (code == XK_COLORS) { | |
| unparseputn(xw, NUM_ANSI_COLORS); | |
| } else | |
| #endif | |
| if (code == XK_TCAPNAME) { | |
| unparseputs(xw, resource.term_name); | |
| } else { | |
| XKeyEvent event; | |
| event.state = state; | |
| Input(xw, &event, False); | |
| } | |
| screen->tc_query_code = -1; | |
| } else { | |
| break; /* no match found, error */ | |
| } | |
| cp = parsed; | |
| if (*parsed == ';') { | |
| unparseputc(xw, *parsed++); | |
| cp = parsed; | |
| code = xtermcapKeycode(xw, &parsed, &state, &fkey); | |
| } | |
| } | |
| unparseputc1(xw, ANSI_ST); | |
| } | |
| break; | |
| } | |
| break; | |
| #endif | |
| default: | |
| if (screen->vtXX_level >= 2) { /* VT220 */ | |
| parse_ansi_params(¶ms, &cp); | |
| switch (params.a_final) { | |
| case '|': /* DECUDK */ | |
| if (params.a_param[0] == 0) | |
| reset_decudk(); | |
| parse_decudk(cp); | |
| break; | |
| case '{': /* DECDLD (no '}' case though) */ | |
| parse_decdld(¶ms, cp); | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| unparse_end(xw); | |
| } | |
| #if OPT_DEC_RECTOPS | |
| enum { | |
| mdUnknown = 0, | |
| mdMaybeSet = 1, | |
| mdMaybeReset = 2, | |
| mdAlwaysSet = 3, | |
| mdAlwaysReset = 4 | |
| }; | |
| #define MdBool(bool) ((bool) ? mdMaybeSet : mdMaybeReset) | |
| #define MdFlag(mode,flag) MdBool((mode) & (flag)) | |
| /* | |
| * Reply is the same format as the query, with pair of mode/value: | |
| * 0 - not recognized | |
| * 1 - set | |
| * 2 - reset | |
| * 3 - permanently set | |
| * 4 - permanently reset | |
| * Only one mode can be reported at a time. | |
| */ | |
| void | |
| do_rpm(XtermWidget xw, int nparams, int *params) | |
| { | |
| ANSI reply; | |
| int result = 0; | |
| int count = 0; | |
| TRACE(("do_rpm %d:%d\n", nparams, params[0])); | |
| memset(&reply, 0, sizeof(reply)); | |
| if (nparams >= 1) { | |
| switch (params[0]) { | |
| case 1: /* GATM */ | |
| result = mdAlwaysReset; | |
| break; | |
| case 2: | |
| result = MdFlag(xw->keyboard.flags, MODE_KAM); | |
| break; | |
| case 3: /* CRM */ | |
| result = mdMaybeReset; | |
| break; | |
| case 4: | |
| result = MdFlag(xw->flags, INSERT); | |
| break; | |
| case 5: /* SRTM */ | |
| case 7: /* VEM */ | |
| case 10: /* HEM */ | |
| case 11: /* PUM */ | |
| result = mdAlwaysReset; | |
| break; | |
| case 12: | |
| result = MdFlag(xw->keyboard.flags, MODE_SRM); | |
| break; | |
| case 13: /* FEAM */ | |
| case 14: /* FETM */ | |
| case 15: /* MATM */ | |
| case 16: /* TTM */ | |
| case 17: /* SATM */ | |
| case 18: /* TSM */ | |
| case 19: /* EBM */ | |
| result = mdAlwaysReset; | |
| break; | |
| case 20: | |
| result = MdFlag(xw->flags, LINEFEED); | |
| break; | |
| } | |
| reply.a_param[count++] = (ParmType) params[0]; | |
| reply.a_param[count++] = (ParmType) result; | |
| } | |
| reply.a_type = ANSI_CSI; | |
| reply.a_nparam = (ParmType) count; | |
| reply.a_inters = '$'; | |
| reply.a_final = 'y'; | |
| unparseseq(xw, &reply); | |
| } | |
| void | |
| do_decrpm(XtermWidget xw, int nparams, int *params) | |
| { | |
| ANSI reply; | |
| int result = 0; | |
| int count = 0; | |
| TRACE(("do_decrpm %d:%d\n", nparams, params[0])); | |
| memset(&reply, 0, sizeof(reply)); | |
| if (nparams >= 1) { | |
| TScreen *screen = TScreenOf(xw); | |
| switch (params[0]) { | |
| case 1: /* DECCKM */ | |
| result = MdFlag(xw->keyboard.flags, MODE_DECCKM); | |
| break; | |
| case 2: /* DECANM - ANSI/VT52 mode */ | |
| #if OPT_VT52_MODE | |
| result = MdBool(screen->vtXX_level >= 1); | |
| #else | |
| result = mdMaybeSet; | |
| #endif | |
| break; | |
| case 3: /* DECCOLM */ | |
| result = MdFlag(xw->flags, IN132COLUMNS); | |
| break; | |
| case 4: /* DECSCLM (slow scroll) */ | |
| result = MdFlag(xw->flags, SMOOTHSCROLL); | |
| break; | |
| case 5: /* DECSCNM */ | |
| result = MdFlag(xw->flags, REVERSE_VIDEO); | |
| break; | |
| case 6: /* DECOM */ | |
| result = MdFlag(xw->flags, ORIGIN); | |
| break; | |
| case 7: /* DECAWM */ | |
| result = MdFlag(xw->flags, WRAPAROUND); | |
| break; | |
| case 8: /* DECARM */ | |
| result = mdAlwaysReset; | |
| break; | |
| case SET_X10_MOUSE: /* X10 mouse */ | |
| result = MdBool(screen->send_mouse_pos == X10_MOUSE); | |
| break; | |
| #if OPT_TOOLBAR | |
| case 10: /* rxvt */ | |
| result = MdBool(resource.toolBar); | |
| break; | |
| #endif | |
| #if OPT_BLINK_CURS | |
| case 12: /* att610: Start/stop blinking cursor */ | |
| result = MdBool(screen->cursor_blink_res); | |
| break; | |
| #endif | |
| case 18: /* DECPFF: print form feed */ | |
| result = MdBool(PrinterOf(screen).printer_formfeed); | |
| break; | |
| case 19: /* DECPEX: print extent */ | |
| result = MdBool(PrinterOf(screen).printer_extent); | |
| break; | |
| case 25: /* DECTCEM: Show/hide cursor (VT200) */ | |
| result = MdBool(screen->cursor_set); | |
| break; | |
| case 30: /* rxvt */ | |
| result = MdBool(screen->fullVwin.sb_info.width != OFF); | |
| break; | |
| #if OPT_SHIFT_FONTS | |
| case 35: /* rxvt */ | |
| result = MdBool(xw->misc.shift_fonts); | |
| break; | |
| #endif | |
| #if OPT_TEK4014 | |
| case 38: /* DECTEK */ | |
| result = MdBool(TEK4014_ACTIVE(xw)); | |
| break; | |
| #endif | |
| case 40: /* 132 column mode */ | |
| result = MdBool(screen->c132); | |
| break; | |
| case 41: /* curses hack */ | |
| result = MdBool(screen->curses); | |
| break; | |
| case 42: /* DECNRCM national charset (VT220) */ | |
| result = MdFlag(xw->flags, NATIONAL); | |
| break; | |
| case 44: /* margin bell */ | |
| result = MdBool(screen->marginbell); | |
| break; | |
| case 45: /* reverse wraparound */ | |
| result = MdFlag(xw->flags, REVERSEWRAP); | |
| break; | |
| #ifdef ALLOWLOGGING | |
| case 46: /* logging */ | |
| #ifdef ALLOWLOGFILEONOFF | |
| result = MdBool(screen->logging); | |
| #endif /* ALLOWLOGFILEONOFF */ | |
| break; | |
| #endif | |
| case 1049: /* alternate buffer & cursor */ | |
| /* FALLTHRU */ | |
| case 1047: | |
| /* FALLTHRU */ | |
| case 47: /* alternate buffer */ | |
| result = MdBool(screen->whichBuf); | |
| break; | |
| case 66: /* DECNKM */ | |
| result = MdFlag(xw->keyboard.flags, MODE_DECKPAM); | |
| break; | |
| case 67: /* DECBKM */ | |
| result = MdFlag(xw->keyboard.flags, MODE_DECBKM); | |
| break; | |
| case 69: /* DECLRMM */ | |
| result = MdFlag(xw->flags, LEFT_RIGHT); | |
| break; | |
| case 95: /* DECNCSM */ | |
| result = MdFlag(xw->flags, NOCLEAR_COLM); | |
| break; | |
| case SET_VT200_MOUSE: /* xterm bogus sequence */ | |
| result = MdBool(screen->send_mouse_pos == VT200_MOUSE); | |
| break; | |
| case SET_VT200_HIGHLIGHT_MOUSE: /* xterm sequence w/hilite tracking */ | |
| result = MdBool(screen->send_mouse_pos == VT200_HIGHLIGHT_MOUSE); | |
| break; | |
| case SET_BTN_EVENT_MOUSE: | |
| result = MdBool(screen->send_mouse_pos == BTN_EVENT_MOUSE); | |
| break; | |
| case SET_ANY_EVENT_MOUSE: | |
| result = MdBool(screen->send_mouse_pos == ANY_EVENT_MOUSE); | |
| break; | |
| #if OPT_FOCUS_EVENT | |
| case SET_FOCUS_EVENT_MOUSE: | |
| result = MdBool(screen->send_focus_pos); | |
| break; | |
| #endif | |
| case SET_EXT_MODE_MOUSE: | |
| /* FALLTHRU */ | |
| case SET_SGR_EXT_MODE_MOUSE: | |
| /* FALLTHRU */ | |
| case SET_URXVT_EXT_MODE_MOUSE: | |
| result = MdBool(screen->extend_coords == params[0]); | |
| break; | |
| case SET_ALTERNATE_SCROLL: | |
| result = MdBool(screen->alternateScroll); | |
| break; | |
| case 1010: /* rxvt */ | |
| result = MdBool(screen->scrollttyoutput); | |
| break; | |
| case 1011: /* rxvt */ | |
| result = MdBool(screen->scrollkey); | |
| break; | |
| case 1034: | |
| result = MdBool(screen->eight_bit_meta); | |
| break; | |
| #if OPT_NUM_LOCK | |
| case 1035: | |
| result = MdBool(xw->misc.real_NumLock); | |
| break; | |
| case 1036: | |
| result = MdBool(screen->meta_sends_esc); | |
| break; | |
| #endif | |
| case 1037: | |
| result = MdBool(screen->delete_is_del); | |
| break; | |
| #if OPT_NUM_LOCK | |
| case 1039: | |
| result = MdBool(screen->alt_sends_esc); | |
| break; | |
| #endif | |
| case 1040: | |
| result = MdBool(screen->keepSelection); | |
| break; | |
| case 1041: | |
| result = MdBool(screen->selectToClipboard); | |
| break; | |
| case 1042: | |
| result = MdBool(screen->bellIsUrgent); | |
| break; | |
| case 1043: | |
| result = MdBool(screen->poponbell); | |
| break; | |
| case 1048: | |
| result = MdBool(screen->sc[screen->whichBuf].saved); | |
| break; | |
| #if OPT_TCAP_FKEYS | |
| case 1050: | |
| result = MdBool(xw->keyboard.type == keyboardIsTermcap); | |
| break; | |
| #endif | |
| #if OPT_SUN_FUNC_KEYS | |
| case 1051: | |
| result = MdBool(xw->keyboard.type == keyboardIsSun); | |
| break; | |
| #endif | |
| #if OPT_HP_FUNC_KEYS | |
| case 1052: | |
| result = MdBool(xw->keyboard.type == keyboardIsHP); | |
| break; | |
| #endif | |
| #if OPT_SCO_FUNC_KEYS | |
| case 1053: | |
| result = MdBool(xw->keyboard.type == keyboardIsSCO); | |
| break; | |
| #endif | |
| case 1060: | |
| result = MdBool(xw->keyboard.type == keyboardIsLegacy); | |
| break; | |
| #if OPT_SUNPC_KBD | |
| case 1061: | |
| result = MdBool(xw->keyboard.type == keyboardIsVT220); | |
| break; | |
| #endif | |
| #if OPT_READLINE | |
| case SET_BUTTON1_MOVE_POINT: | |
| result = MdBool(screen->click1_moves); | |
| break; | |
| case SET_BUTTON2_MOVE_POINT: | |
| result = MdBool(screen->paste_moves); | |
| break; | |
| case SET_DBUTTON3_DELETE: | |
| result = MdBool(screen->dclick3_deletes); | |
| break; | |
| case SET_PASTE_IN_BRACKET: | |
| result = MdBool(screen->paste_brackets); | |
| break; | |
| case SET_PASTE_QUOTE: | |
| result = MdBool(screen->paste_quotes); | |
| break; | |
| case SET_PASTE_LITERAL_NL: | |
| result = MdBool(screen->paste_literal_nl); | |
| break; | |
| #endif /* OPT_READLINE */ | |
| } | |
| reply.a_param[count++] = (ParmType) params[0]; | |
| reply.a_param[count++] = (ParmType) result; | |
| } | |
| reply.a_type = ANSI_CSI; | |
| reply.a_pintro = '?'; | |
| reply.a_nparam = (ParmType) count; | |
| reply.a_inters = '$'; | |
| reply.a_final = 'y'; | |
| unparseseq(xw, &reply); | |
| } | |
| #endif /* OPT_DEC_RECTOPS */ | |
| char * | |
| udk_lookup(int keycode, int *len) | |
| { | |
| if (keycode >= 0 && keycode < MAX_UDK) { | |
| *len = user_keys[keycode].len; | |
| return user_keys[keycode].str; | |
| } | |
| return 0; | |
| } | |
| #ifdef HAVE_LIBXPM | |
| #ifndef PIXMAP_ROOTDIR | |
| #define PIXMAP_ROOTDIR "/usr/share/pixmaps/" | |
| #endif | |
| typedef struct { | |
| const char *name; | |
| const char *const *data; | |
| } XPM_DATA; | |
| static char * | |
| x_find_icon(char **work, int *state, const char *suffix) | |
| { | |
| const char *filename = resource.icon_hint; | |
| const char *prefix = PIXMAP_ROOTDIR; | |
| const char *larger = "_48x48"; | |
| char *result = 0; | |
| size_t length; | |
| if (*state >= 0) { | |
| if ((*state & 1) == 0) | |
| suffix = ""; | |
| if ((*state & 2) == 0) | |
| larger = ""; | |
| if ((*state & 4) == 0) { | |
| prefix = ""; | |
| } else if (!strncmp(filename, "/", (size_t) 1) || | |
| !strncmp(filename, "./", (size_t) 2) || | |
| !strncmp(filename, "../", (size_t) 3)) { | |
| *state = -1; | |
| } else if (*state >= 8) { | |
| *state = -1; | |
| } | |
| } | |
| if (*state >= 0) { | |
| if (*work) { | |
| free(*work); | |
| *work = 0; | |
| } | |
| length = 3 + strlen(prefix) + strlen(filename) + strlen(larger) + | |
| strlen(suffix); | |
| if ((result = malloc(length)) != 0) { | |
| sprintf(result, "%s%s%s%s", prefix, filename, larger, suffix); | |
| *work = result; | |
| } | |
| *state += 1; | |
| TRACE(("x_find_icon %d:%s\n", *state, result)); | |
| } | |
| return result; | |
| } | |
| #if OPT_BUILTIN_XPMS | |
| static const XPM_DATA * | |
| BuiltInXPM(const XPM_DATA * table, Cardinal length) | |
| { | |
| const char *find = resource.icon_hint; | |
| const XPM_DATA *result = 0; | |
| if (!IsEmpty(find)) { | |
| Cardinal n; | |
| for (n = 0; n < length; ++n) { | |
| if (!x_strcasecmp(find, table[n].name)) { | |
| result = table + n; | |
| break; | |
| } | |
| } | |
| /* | |
| * As a fallback, check if the icon name matches without the lengths, | |
| * which are all _HHxWW format. | |
| */ | |
| if (result == 0) { | |
| const char *base = table[0].name; | |
| const char *last = strchr(base, '_'); | |
| if (last != 0 | |
| && !x_strncasecmp(find, base, (unsigned) (last - base))) { | |
| result = table + length - 1; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| #endif /* OPT_BUILTIN_XPMS */ | |
| typedef enum { | |
| eHintDefault = 0 /* use the largest builtin-icon */ | |
| ,eHintNone | |
| ,eHintSearch | |
| } ICON_HINT; | |
| static ICON_HINT | |
| which_icon_hint(void) | |
| { | |
| ICON_HINT result = eHintDefault; | |
| if (!IsEmpty(resource.icon_hint)) { | |
| if (!x_strcasecmp(resource.icon_hint, "none")) { | |
| result = eHintNone; | |
| } else { | |
| result = eHintSearch; | |
| } | |
| } | |
| return result; | |
| } | |
| #endif /* HAVE_LIBXPM */ | |
| int | |
| getVisualDepth(XtermWidget xw) | |
| { | |
| Display *display = TScreenOf(xw)->display; | |
| XVisualInfo myTemplate, *visInfoPtr; | |
| int numFound; | |
| int result = 0; | |
| myTemplate.visualid = XVisualIDFromVisual(DefaultVisual(display, | |
| XDefaultScreen(display))); | |
| visInfoPtr = XGetVisualInfo(display, (long) VisualIDMask, | |
| &myTemplate, &numFound); | |
| if (visInfoPtr != 0) { | |
| if (numFound != 0) { | |
| result = visInfoPtr->depth; | |
| } | |
| XFree(visInfoPtr); | |
| } | |
| return result; | |
| } | |
| /* | |
| * WM_ICON_SIZE should be honored if possible. | |
| */ | |
| void | |
| xtermLoadIcon(XtermWidget xw) | |
| { | |
| #ifdef HAVE_LIBXPM | |
| Display *dpy = XtDisplay(xw); | |
| Pixmap myIcon = 0; | |
| Pixmap myMask = 0; | |
| char *workname = 0; | |
| ICON_HINT hint = which_icon_hint(); | |
| #if OPT_BUILTIN_XPMS | |
| #include <icons/mini.xterm.xpms> | |
| #include <icons/filled-xterm.xpms> | |
| #include <icons/xterm.xpms> | |
| #include <icons/xterm-color.xpms> | |
| #else | |
| #include <icons/mini.xterm_48x48.xpm> | |
| #endif | |
| TRACE(("xtermLoadIcon %p:%s\n", (void *) xw, NonNull(resource.icon_hint))); | |
| if (hint == eHintSearch) { | |
| int state = 0; | |
| while (x_find_icon(&workname, &state, ".xpm") != 0) { | |
| Pixmap resIcon = 0; | |
| Pixmap shapemask = 0; | |
| XpmAttributes attributes; | |
| attributes.depth = (unsigned) getVisualDepth(xw); | |
| attributes.valuemask = XpmDepth; | |
| if (XpmReadFileToPixmap(dpy, | |
| DefaultRootWindow(dpy), | |
| workname, | |
| &resIcon, | |
| &shapemask, | |
| &attributes) == XpmSuccess) { | |
| myIcon = resIcon; | |
| myMask = shapemask; | |
| TRACE(("...success\n")); | |
| break; | |
| } | |
| } | |
| } | |
| /* | |
| * If no external file was found, look for the name in the built-in table. | |
| * If that fails, just use the biggest mini-icon. | |
| */ | |
| if (myIcon == 0 && hint != eHintNone) { | |
| char **data; | |
| #if OPT_BUILTIN_XPMS | |
| const XPM_DATA *myData = 0; | |
| myData = BuiltInXPM(mini_xterm_xpms, XtNumber(mini_xterm_xpms)); | |
| if (myData == 0) | |
| myData = BuiltInXPM(filled_xterm_xpms, XtNumber(filled_xterm_xpms)); | |
| if (myData == 0) | |
| myData = BuiltInXPM(xterm_color_xpms, XtNumber(xterm_color_xpms)); | |
| if (myData == 0) | |
| myData = BuiltInXPM(xterm_xpms, XtNumber(xterm_xpms)); | |
| if (myData == 0) | |
| myData = &mini_xterm_xpms[XtNumber(mini_xterm_xpms) - 1]; | |
| data = (char **) myData->data, | |
| #else | |
| data = (char **) &mini_xterm_48x48_xpm; | |
| #endif | |
| if (XpmCreatePixmapFromData(dpy, | |
| DefaultRootWindow(dpy), | |
| data, | |
| &myIcon, &myMask, 0) != 0) { | |
| myIcon = 0; | |
| myMask = 0; | |
| } | |
| } | |
| if (myIcon != 0) { | |
| XWMHints *hints = XGetWMHints(dpy, VShellWindow(xw)); | |
| if (!hints) | |
| hints = XAllocWMHints(); | |
| if (hints) { | |
| hints->flags |= IconPixmapHint; | |
| hints->icon_pixmap = myIcon; | |
| if (myMask) { | |
| hints->flags |= IconMaskHint; | |
| hints->icon_mask = myMask; | |
| } | |
| XSetWMHints(dpy, VShellWindow(xw), hints); | |
| XFree(hints); | |
| TRACE(("...loaded icon\n")); | |
| } | |
| } | |
| if (workname != 0) | |
| free(workname); | |
| #else | |
| (void) xw; | |
| #endif | |
| } | |
| void | |
| ChangeGroup(XtermWidget xw, const char *attribute, char *value) | |
| { | |
| #if OPT_WIDE_CHARS | |
| static Char *converted; /* NO_LEAKS */ | |
| #endif | |
| Arg args[1]; | |
| Boolean changed = True; | |
| Widget w = CURRENT_EMU(); | |
| Widget top = SHELL_OF(w); | |
| char *my_attr; | |
| char *name; | |
| size_t limit; | |
| Char *c1; | |
| Char *cp; | |
| if (!AllowTitleOps(xw)) | |
| return; | |
| if (value == 0) | |
| value = emptyString; | |
| if (IsTitleMode(xw, tmSetBase16)) { | |
| const char *temp; | |
| char *test; | |
| value = x_decode_hex(value, &temp); | |
| if (*temp != '\0') { | |
| free(value); | |
| return; | |
| } | |
| for (test = value; *test != '\0'; ++test) { | |
| if (CharOf(*test) < 32) { | |
| *test = '\0'; | |
| break; | |
| } | |
| } | |
| } | |
| c1 = (Char *) value; | |
| name = value; | |
| limit = strlen(name); | |
| my_attr = x_strdup(attribute); | |
| TRACE(("ChangeGroup(attribute=%s, value=%s)\n", my_attr, name)); | |
| /* | |
| * Ignore titles that are too long to be plausible requests. | |
| */ | |
| if (limit > 0 && limit < 1024) { | |
| /* | |
| * After all decoding, overwrite nonprintable characters with '?'. | |
| */ | |
| for (cp = c1; *cp != 0; ++cp) { | |
| Char *c2 = cp; | |
| if (!xtermIsPrintable(xw, &cp, c1 + limit)) { | |
| memset(c2, '?', (size_t) (cp + 1 - c2)); | |
| } | |
| } | |
| #if OPT_WIDE_CHARS | |
| /* | |
| * If we're running in UTF-8 mode, and have not been told that the | |
| * title string is in UTF-8, it is likely that non-ASCII text in the | |
| * string will be rejected because it is not printable in the current | |
| * locale. So we convert it to UTF-8, allowing the X library to | |
| * convert it back. | |
| */ | |
| if (xtermEnvUTF8() && !IsSetUtf8Title(xw)) { | |
| int n; | |
| for (n = 0; name[n] != '\0'; ++n) { | |
| if (CharOf(name[n]) > 127) { | |
| if (converted != 0) | |
| free(converted); | |
| if ((converted = TypeMallocN(Char, 1 + (6 * limit))) != 0) { | |
| Char *temp = converted; | |
| while (*name != 0) { | |
| temp = convertToUTF8(temp, CharOf(*name)); | |
| ++name; | |
| } | |
| *temp = 0; | |
| name = (char *) converted; | |
| TRACE(("...converted{%s}\n", name)); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| #endif | |
| #if OPT_SAME_NAME | |
| /* If the attribute isn't going to change, then don't bother... */ | |
| if (resource.sameName) { | |
| char *buf = 0; | |
| XtSetArg(args[0], my_attr, &buf); | |
| XtGetValues(top, args, 1); | |
| TRACE(("...comparing{%s}\n", buf)); | |
| if (buf != 0 && strcmp(name, buf) == 0) | |
| changed = False; | |
| } | |
| #endif /* OPT_SAME_NAME */ | |
| if (changed) { | |
| TRACE(("...updating %s\n", my_attr)); | |
| TRACE(("...value is %s\n", name)); | |
| XtSetArg(args[0], my_attr, name); | |
| XtSetValues(top, args, 1); | |
| #if OPT_WIDE_CHARS | |
| if (xtermEnvUTF8()) { | |
| Display *dpy = XtDisplay(xw); | |
| Atom my_atom; | |
| const char *propname = (!strcmp(my_attr, XtNtitle) | |
| ? "_NET_WM_NAME" | |
| : "_NET_WM_ICON_NAME"); | |
| if ((my_atom = XInternAtom(dpy, propname, False)) != None) { | |
| if (IsSetUtf8Title(xw)) { | |
| TRACE(("...updating %s\n", propname)); | |
| TRACE(("...value is %s\n", value)); | |
| XChangeProperty(dpy, VShellWindow(xw), my_atom, | |
| XA_UTF8_STRING(dpy), 8, | |
| PropModeReplace, | |
| (Char *) value, | |
| (int) strlen(value)); | |
| } else { | |
| TRACE(("...deleting %s\n", propname)); | |
| XDeleteProperty(dpy, VShellWindow(xw), my_atom); | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| } | |
| if (IsTitleMode(xw, tmSetBase16)) { | |
| free(value); | |
| } | |
| free(my_attr); | |
| return; | |
| } | |
| void | |
| ChangeIconName(XtermWidget xw, char *name) | |
| { | |
| if (name == 0) { | |
| name = emptyString; | |
| } | |
| if (!showZIconBeep(xw, name)) | |
| ChangeGroup(xw, XtNiconName, name); | |
| } | |
| void | |
| ChangeTitle(XtermWidget xw, char *name) | |
| { | |
| ChangeGroup(xw, XtNtitle, name); | |
| } | |
| #define Strlen(s) strlen((const char *)(s)) | |
| void | |
| ChangeXprop(char *buf) | |
| { | |
| Display *dpy = XtDisplay(toplevel); | |
| Window w = XtWindow(toplevel); | |
| XTextProperty text_prop; | |
| Atom aprop; | |
| Char *pchEndPropName = (Char *) strchr(buf, '='); | |
| if (pchEndPropName) | |
| *pchEndPropName = '\0'; | |
| aprop = XInternAtom(dpy, buf, False); | |
| if (pchEndPropName == NULL) { | |
| /* no "=value" given, so delete the property */ | |
| XDeleteProperty(dpy, w, aprop); | |
| } else { | |
| text_prop.value = pchEndPropName + 1; | |
| text_prop.encoding = XA_STRING; | |
| text_prop.format = 8; | |
| text_prop.nitems = Strlen(text_prop.value); | |
| XSetTextProperty(dpy, w, &text_prop, aprop); | |
| } | |
| } | |
| /***====================================================================***/ | |
| /* | |
| * This is part of ReverseVideo(). It reverses the data stored for the old | |
| * "dynamic" colors that might have been retrieved using OSC 10-18. | |
| */ | |
| void | |
| ReverseOldColors(void) | |
| { | |
| ScrnColors *pOld = pOldColors; | |
| Pixel tmpPix; | |
| char *tmpName; | |
| if (pOld) { | |
| /* change text cursor, if necesary */ | |
| if (pOld->colors[TEXT_CURSOR] == pOld->colors[TEXT_FG]) { | |
| pOld->colors[TEXT_CURSOR] = pOld->colors[TEXT_BG]; | |
| if (pOld->names[TEXT_CURSOR]) { | |
| XtFree(pOldColors->names[TEXT_CURSOR]); | |
| pOld->names[TEXT_CURSOR] = NULL; | |
| } | |
| if (pOld->names[TEXT_BG]) { | |
| if ((tmpName = x_strdup(pOld->names[TEXT_BG])) != 0) { | |
| pOld->names[TEXT_CURSOR] = tmpName; | |
| } | |
| } | |
| } | |
| EXCHANGE(pOld->colors[TEXT_FG], pOld->colors[TEXT_BG], tmpPix); | |
| EXCHANGE(pOld->names[TEXT_FG], pOld->names[TEXT_BG], tmpName); | |
| EXCHANGE(pOld->colors[MOUSE_FG], pOld->colors[MOUSE_BG], tmpPix); | |
| EXCHANGE(pOld->names[MOUSE_FG], pOld->names[MOUSE_BG], tmpName); | |
| #if OPT_TEK4014 | |
| EXCHANGE(pOld->colors[TEK_FG], pOld->colors[TEK_BG], tmpPix); | |
| EXCHANGE(pOld->names[TEK_FG], pOld->names[TEK_BG], tmpName); | |
| #endif | |
| } | |
| return; | |
| } | |
| Bool | |
| AllocateTermColor(XtermWidget xw, | |
| ScrnColors * pNew, | |
| int ndx, | |
| const char *name, | |
| Bool always) | |
| { | |
| Bool result = False; | |
| if (always || AllowColorOps(xw, ecSetColor)) { | |
| XColor def; | |
| char *newName; | |
| result = True; | |
| if (!x_strcasecmp(name, XtDefaultForeground)) { | |
| def.pixel = xw->old_foreground; | |
| } else if (!x_strcasecmp(name, XtDefaultBackground)) { | |
| def.pixel = xw->old_background; | |
| } else if (!xtermAllocColor(xw, &def, name)) { | |
| result = False; | |
| } | |
| if (result | |
| && (newName = x_strdup(name)) != 0) { | |
| if (COLOR_DEFINED(pNew, ndx)) { | |
| free(pNew->names[ndx]); | |
| } | |
| SET_COLOR_VALUE(pNew, ndx, def.pixel); | |
| SET_COLOR_NAME(pNew, ndx, newName); | |
| TRACE(("AllocateTermColor #%d: %s (pixel 0x%06lx)\n", | |
| ndx, newName, def.pixel)); | |
| } else { | |
| TRACE(("AllocateTermColor #%d: %s (failed)\n", ndx, name)); | |
| result = False; | |
| } | |
| } | |
| return result; | |
| } | |
| /***====================================================================***/ | |
| /* ARGSUSED */ | |
| void | |
| Panic(const char *s GCC_UNUSED, int a GCC_UNUSED) | |
| { | |
| #ifdef DEBUG | |
| if (debug) { | |
| xtermWarning(s, a); | |
| } | |
| #endif /* DEBUG */ | |
| } | |
| const char * | |
| SysErrorMsg(int code) | |
| { | |
| static char unknown[] = "unknown error"; | |
| char *s = strerror(code); | |
| return s ? s : unknown; | |
| } | |
| const char * | |
| SysReasonMsg(int code) | |
| { | |
| /* *INDENT-OFF* */ | |
| static const struct { | |
| int code; | |
| const char *name; | |
| } table[] = { | |
| { ERROR_FIONBIO, "main: ioctl() failed on FIONBIO" }, | |
| { ERROR_F_GETFL, "main: ioctl() failed on F_GETFL" }, | |
| { ERROR_F_SETFL, "main: ioctl() failed on F_SETFL", }, | |
| { ERROR_OPDEVTTY, "spawn: open() failed on /dev/tty", }, | |
| { ERROR_TIOCGETP, "spawn: ioctl() failed on TIOCGETP", }, | |
| { ERROR_PTSNAME, "spawn: ptsname() failed", }, | |
| { ERROR_OPPTSNAME, "spawn: open() failed on ptsname", }, | |
| { ERROR_PTEM, "spawn: ioctl() failed on I_PUSH/\"ptem\"" }, | |
| { ERROR_CONSEM, "spawn: ioctl() failed on I_PUSH/\"consem\"" }, | |
| { ERROR_LDTERM, "spawn: ioctl() failed on I_PUSH/\"ldterm\"" }, | |
| { ERROR_TTCOMPAT, "spawn: ioctl() failed on I_PUSH/\"ttcompat\"" }, | |
| { ERROR_TIOCSETP, "spawn: ioctl() failed on TIOCSETP" }, | |
| { ERROR_TIOCSETC, "spawn: ioctl() failed on TIOCSETC" }, | |
| { ERROR_TIOCSETD, "spawn: ioctl() failed on TIOCSETD" }, | |
| { ERROR_TIOCSLTC, "spawn: ioctl() failed on TIOCSLTC" }, | |
| { ERROR_TIOCLSET, "spawn: ioctl() failed on TIOCLSET" }, | |
| { ERROR_INIGROUPS, "spawn: initgroups() failed" }, | |
| { ERROR_FORK, "spawn: fork() failed" }, | |
| { ERROR_EXEC, "spawn: exec() failed" }, | |
| { ERROR_PTYS, "get_pty: not enough ptys" }, | |
| { ERROR_PTY_EXEC, "waiting for initial map" }, | |
| { ERROR_SETUID, "spawn: setuid() failed" }, | |
| { ERROR_INIT, "spawn: can't initialize window" }, | |
| { ERROR_TIOCKSET, "spawn: ioctl() failed on TIOCKSET" }, | |
| { ERROR_TIOCKSETC, "spawn: ioctl() failed on TIOCKSETC" }, | |
| { ERROR_LUMALLOC, "luit: command-line malloc failed" }, | |
| { ERROR_SELECT, "in_put: select() failed" }, | |
| { ERROR_VINIT, "VTInit: can't initialize window" }, | |
| { ERROR_KMMALLOC1, "HandleKeymapChange: malloc failed" }, | |
| { ERROR_TSELECT, "Tinput: select() failed" }, | |
| { ERROR_TINIT, "TekInit: can't initialize window" }, | |
| { ERROR_BMALLOC2, "SaltTextAway: malloc() failed" }, | |
| { ERROR_LOGEXEC, "StartLog: exec() failed" }, | |
| { ERROR_XERROR, "xerror: XError event" }, | |
| { ERROR_XIOERROR, "xioerror: X I/O error" }, | |
| { ERROR_SCALLOC, "Alloc: calloc() failed on base" }, | |
| { ERROR_SCALLOC2, "Alloc: calloc() failed on rows" }, | |
| { ERROR_SAVE_PTR, "ScrnPointers: malloc/realloc() failed" }, | |
| }; | |
| /* *INDENT-ON* */ | |
| Cardinal n; | |
| const char *result = "?"; | |
| for (n = 0; n < XtNumber(table); ++n) { | |
| if (code == table[n].code) { | |
| result = table[n].name; | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| void | |
| SysError(int code) | |
| { | |
| int oerrno = errno; | |
| fprintf(stderr, "%s: Error %d, errno %d: ", ProgramName, code, oerrno); | |
| fprintf(stderr, "%s\n", SysErrorMsg(oerrno)); | |
| fprintf(stderr, "Reason: %s\n", SysReasonMsg(code)); | |
| Cleanup(code); | |
| } | |
| void | |
| NormalExit(void) | |
| { | |
| static Bool cleaning; | |
| /* | |
| * Process "-hold" and session cleanup only for a normal exit. | |
| */ | |
| if (cleaning) { | |
| hold_screen = 0; | |
| return; | |
| } | |
| cleaning = True; | |
| need_cleanup = False; | |
| if (hold_screen) { | |
| hold_screen = 2; | |
| while (hold_screen) { | |
| xevents(); | |
| Sleep(10); | |
| } | |
| } | |
| #if OPT_SESSION_MGT | |
| if (resource.sessionMgt) { | |
| XtVaSetValues(toplevel, | |
| XtNjoinSession, False, | |
| (void *) 0); | |
| } | |
| #endif | |
| Cleanup(0); | |
| } | |
| /* | |
| * cleanup by sending SIGHUP to client processes | |
| */ | |
| void | |
| Cleanup(int code) | |
| { | |
| TScreen *screen = TScreenOf(term); | |
| TRACE(("Cleanup %d\n", code)); | |
| if (screen->pid > 1) { | |
| (void) kill_process_group(screen->pid, SIGHUP); | |
| } | |
| Exit(code); | |
| } | |
| #ifndef VMS | |
| #ifndef PATH_MAX | |
| #define PATH_MAX 512 /* ... is not defined consistently in Xos.h */ | |
| #endif | |
| char * | |
| xtermFindShell(char *leaf, Bool warning) | |
| { | |
| char *s0; | |
| char *s; | |
| char *d; | |
| char *tmp; | |
| char *result = leaf; | |
| Bool allocated = False; | |
| TRACE(("xtermFindShell(%s)\n", leaf)); | |
| if (!strncmp("./", result, (size_t) 2) | |
| || !strncmp("../", result, (size_t) 3)) { | |
| size_t need = PATH_MAX; | |
| size_t used = strlen(result) + 2; | |
| char *buffer = malloc(used + need); | |
| if (buffer != 0) { | |
| if (getcwd(buffer, need) != 0) { | |
| sprintf(buffer + strlen(buffer), "/%s", result); | |
| result = buffer; | |
| allocated = True; | |
| } else { | |
| free(buffer); | |
| } | |
| } | |
| } else if (*result != '\0' && strchr("+/-", *result) == 0) { | |
| /* find it in $PATH */ | |
| if ((s = s0 = x_getenv("PATH")) != 0) { | |
| if ((tmp = TypeMallocN(char, strlen(leaf) + strlen(s) + 2)) != 0) { | |
| Bool found = False; | |
| while (*s != '\0') { | |
| strcpy(tmp, s); | |
| for (d = tmp;; ++d) { | |
| if (*d == ':' || *d == '\0') { | |
| int skip = (*d != '\0'); | |
| *d = '/'; | |
| strcpy(d + 1, leaf); | |
| if (skip) | |
| ++d; | |
| s += (d - tmp); | |
| if (*tmp == '/' | |
| && strstr(tmp, "..") == 0 | |
| && access(tmp, X_OK) == 0) { | |
| result = x_strdup(tmp); | |
| found = True; | |
| allocated = True; | |
| } | |
| break; | |
| } | |
| } | |
| if (found) | |
| break; | |
| } | |
| free(tmp); | |
| } | |
| free(s0); | |
| } | |
| } | |
| TRACE(("...xtermFindShell(%s)\n", result)); | |
| if (*result != '/' | |
| || strstr(result, "..") != 0 | |
| || access(result, X_OK) != 0) { | |
| if (warning) | |
| xtermWarning("No absolute path found for shell: %s\n", result); | |
| if (allocated) | |
| free(result); | |
| result = 0; | |
| } | |
| /* be consistent, so that caller can always free the result */ | |
| if (result != 0 && !allocated) | |
| result = x_strdup(result); | |
| return result; | |
| } | |
| #endif /* VMS */ | |
| #define ENV_HUNK(n) (unsigned) ((((n) + 1) | 31) + 1) | |
| /* | |
| * If we do not have unsetenv(), make consistent updates for environ[]. | |
| * This could happen on some older machines due to the uneven standardization | |
| * process for the two functions. | |
| * | |
| * That is, putenv() makes a copy of environ, and some implementations do not | |
| * update the environ pointer, so the fallback when unsetenv() is missing would | |
| * not work as intended. Likewise, the reverse could be true, i.e., unsetenv | |
| * could copy environ. | |
| */ | |
| #if defined(HAVE_PUTENV) && !defined(HAVE_UNSETENV) | |
| #undef HAVE_PUTENV | |
| #elif !defined(HAVE_PUTENV) && defined(HAVE_UNSETENV) | |
| #undef HAVE_UNSETENV | |
| #endif | |
| /* | |
| * copy the environment before Setenv'ing. | |
| */ | |
| void | |
| xtermCopyEnv(char **oldenv) | |
| { | |
| #ifdef HAVE_PUTENV | |
| (void) oldenv; | |
| #else | |
| unsigned size; | |
| char **newenv; | |
| for (size = 0; oldenv[size] != NULL; size++) { | |
| ; | |
| } | |
| newenv = TypeCallocN(char *, ENV_HUNK(size)); | |
| memmove(newenv, oldenv, size * sizeof(char *)); | |
| environ = newenv; | |
| #endif | |
| } | |
| #if !defined(HAVE_PUTENV) || !defined(HAVE_UNSETENV) | |
| static int | |
| findEnv(const char *var, int *lengthp) | |
| { | |
| char *test; | |
| int envindex = 0; | |
| size_t len = strlen(var); | |
| int found = -1; | |
| TRACE(("findEnv(%s=..)\n", var)); | |
| while ((test = environ[envindex]) != NULL) { | |
| if (strncmp(test, var, len) == 0 && test[len] == '=') { | |
| found = envindex; | |
| break; | |
| } | |
| envindex++; | |
| } | |
| *lengthp = envindex; | |
| return found; | |
| } | |
| #endif | |
| /* | |
| * sets the value of var to be arg in the Unix 4.2 BSD environment env. | |
| * Var should end with '=' (bindings are of the form "var=value"). | |
| * This procedure assumes the memory for the first level of environ | |
| * was allocated using calloc, with enough extra room at the end so not | |
| * to have to do a realloc(). | |
| */ | |
| void | |
| xtermSetenv(const char *var, const char *value) | |
| { | |
| if (value != 0) { | |
| #ifdef HAVE_PUTENV | |
| char *both = malloc(2 + strlen(var) + strlen(value)); | |
| TRACE(("xtermSetenv(%s=%s)\n", var, value)); | |
| if (both) { | |
| sprintf(both, "%s=%s", var, value); | |
| putenv(both); | |
| } | |
| #else | |
| size_t len = strlen(var); | |
| int envindex; | |
| int found = findEnv(var, &envindex); | |
| TRACE(("xtermSetenv(%s=%s)\n", var, value)); | |
| if (found < 0) { | |
| unsigned need = ENV_HUNK(envindex + 1); | |
| unsigned have = ENV_HUNK(envindex); | |
| if (need > have) { | |
| char **newenv; | |
| newenv = TypeMallocN(char *, need); | |
| if (newenv == 0) { | |
| xtermWarning("Cannot increase environment\n"); | |
| return; | |
| } | |
| memmove(newenv, environ, have * sizeof(*newenv)); | |
| free(environ); | |
| environ = newenv; | |
| } | |
| found = envindex; | |
| environ[found + 1] = NULL; | |
| environ = environ; | |
| } | |
| environ[found] = CastMallocN(char, 1 + len + strlen(value)); | |
| if (environ[found] == 0) { | |
| xtermWarning("Cannot allocate environment %s\n", var); | |
| return; | |
| } | |
| sprintf(environ[found], "%s=%s", var, value); | |
| #endif | |
| } | |
| } | |
| void | |
| xtermUnsetenv(const char *var) | |
| { | |
| TRACE(("xtermUnsetenv(%s)\n", var)); | |
| #ifdef HAVE_UNSETENV | |
| unsetenv(var); | |
| #else | |
| { | |
| int ignore; | |
| int item = findEnv(var, &ignore); | |
| if (item >= 0) { | |
| while ((environ[item] = environ[item + 1]) != 0) { | |
| ++item; | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| /*ARGSUSED*/ | |
| int | |
| xerror(Display * d, XErrorEvent * ev) | |
| { | |
| xtermWarning("warning, error event received:\n"); | |
| (void) XmuPrintDefaultErrorMessage(d, ev, stderr); | |
| Exit(ERROR_XERROR); | |
| return 0; /* appease the compiler */ | |
| } | |
| void | |
| ice_error(IceConn iceConn) | |
| { | |
| (void) iceConn; | |
| xtermWarning("ICE IO error handler doing an exit(), pid = %ld, errno = %d\n", | |
| (long) getpid(), errno); | |
| Exit(ERROR_ICEERROR); | |
| } | |
| /*ARGSUSED*/ | |
| int | |
| xioerror(Display * dpy) | |
| { | |
| int the_error = errno; | |
| xtermWarning("fatal IO error %d (%s) or KillClient on X server \"%s\"\r\n", | |
| the_error, SysErrorMsg(the_error), | |
| DisplayString(dpy)); | |
| Exit(ERROR_XIOERROR); | |
| return 0; /* appease the compiler */ | |
| } | |
| void | |
| xt_error(String message) | |
| { | |
| xtermWarning("Xt error: %s\n", message); | |
| /* | |
| * Check for the obvious - Xt does a poor job of reporting this. | |
| */ | |
| if (x_getenv("DISPLAY") == 0) { | |
| xtermWarning("DISPLAY is not set\n"); | |
| } | |
| exit(1); | |
| } | |
| int | |
| XStrCmp(char *s1, char *s2) | |
| { | |
| if (s1 && s2) | |
| return (strcmp(s1, s2)); | |
| if (s1 && *s1) | |
| return (1); | |
| if (s2 && *s2) | |
| return (-1); | |
| return (0); | |
| } | |
| #if OPT_TEK4014 | |
| static void | |
| withdraw_window(Display * dpy, Window w, int scr) | |
| { | |
| TRACE(("withdraw_window %#lx\n", (long) w)); | |
| (void) XmuUpdateMapHints(dpy, w, NULL); | |
| XWithdrawWindow(dpy, w, scr); | |
| return; | |
| } | |
| #endif | |
| void | |
| set_vt_visibility(Bool on) | |
| { | |
| XtermWidget xw = term; | |
| TScreen *screen = TScreenOf(xw); | |
| TRACE(("set_vt_visibility(%d)\n", on)); | |
| if (on) { | |
| if (!screen->Vshow && xw) { | |
| VTInit(xw); | |
| XtMapWidget(XtParent(xw)); | |
| #if OPT_TOOLBAR | |
| /* we need both of these during initialization */ | |
| XtMapWidget(SHELL_OF(xw)); | |
| ShowToolbar(resource.toolBar); | |
| #endif | |
| screen->Vshow = True; | |
| } | |
| } | |
| #if OPT_TEK4014 | |
| else { | |
| if (screen->Vshow && xw) { | |
| withdraw_window(XtDisplay(xw), | |
| VShellWindow(xw), | |
| XScreenNumberOfScreen(XtScreen(xw))); | |
| screen->Vshow = False; | |
| } | |
| } | |
| set_vthide_sensitivity(); | |
| set_tekhide_sensitivity(); | |
| update_vttekmode(); | |
| update_tekshow(); | |
| update_vtshow(); | |
| #endif | |
| return; | |
| } | |
| #if OPT_TEK4014 | |
| void | |
| set_tek_visibility(Bool on) | |
| { | |
| TRACE(("set_tek_visibility(%d)\n", on)); | |
| if (on) { | |
| if (!TEK4014_SHOWN(term)) { | |
| if (tekWidget == 0) { | |
| TekInit(); /* will exit on failure */ | |
| } | |
| if (tekWidget != 0) { | |
| Widget tekParent = SHELL_OF(tekWidget); | |
| XtRealizeWidget(tekParent); | |
| XtMapWidget(XtParent(tekWidget)); | |
| #if OPT_TOOLBAR | |
| /* we need both of these during initialization */ | |
| XtMapWidget(tekParent); | |
| XtMapWidget(tekWidget); | |
| #endif | |
| XtOverrideTranslations(tekParent, | |
| XtParseTranslationTable | |
| ("<Message>WM_PROTOCOLS: DeleteWindow()")); | |
| (void) XSetWMProtocols(XtDisplay(tekParent), | |
| XtWindow(tekParent), | |
| &wm_delete_window, 1); | |
| TEK4014_SHOWN(term) = True; | |
| } | |
| } | |
| } else { | |
| if (TEK4014_SHOWN(term) && tekWidget) { | |
| withdraw_window(XtDisplay(tekWidget), | |
| TShellWindow, | |
| XScreenNumberOfScreen(XtScreen(tekWidget))); | |
| TEK4014_SHOWN(term) = False; | |
| } | |
| } | |
| set_tekhide_sensitivity(); | |
| set_vthide_sensitivity(); | |
| update_vtshow(); | |
| update_tekshow(); | |
| update_vttekmode(); | |
| return; | |
| } | |
| void | |
| end_tek_mode(void) | |
| { | |
| XtermWidget xw = term; | |
| if (TEK4014_ACTIVE(xw)) { | |
| FlushLog(xw); | |
| longjmp(Tekend, 1); | |
| } | |
| return; | |
| } | |
| void | |
| end_vt_mode(void) | |
| { | |
| XtermWidget xw = term; | |
| if (!TEK4014_ACTIVE(xw)) { | |
| FlushLog(xw); | |
| TEK4014_ACTIVE(xw) = True; | |
| longjmp(VTend, 1); | |
| } | |
| return; | |
| } | |
| void | |
| switch_modes(Bool tovt) /* if true, then become vt mode */ | |
| { | |
| if (tovt) { | |
| if (tekRefreshList) | |
| TekRefresh(tekWidget); | |
| end_tek_mode(); /* WARNING: this does a longjmp... */ | |
| } else { | |
| end_vt_mode(); /* WARNING: this does a longjmp... */ | |
| } | |
| } | |
| void | |
| hide_vt_window(void) | |
| { | |
| set_vt_visibility(False); | |
| if (!TEK4014_ACTIVE(term)) | |
| switch_modes(False); /* switch to tek mode */ | |
| } | |
| void | |
| hide_tek_window(void) | |
| { | |
| set_tek_visibility(False); | |
| tekRefreshList = (TekLink *) 0; | |
| if (TEK4014_ACTIVE(term)) | |
| switch_modes(True); /* does longjmp to vt mode */ | |
| } | |
| #endif /* OPT_TEK4014 */ | |
| static const char * | |
| skip_punct(const char *s) | |
| { | |
| while (*s == '-' || *s == '/' || *s == '+' || *s == '#' || *s == '%') { | |
| ++s; | |
| } | |
| return s; | |
| } | |
| static int | |
| cmp_options(const void *a, const void *b) | |
| { | |
| const char *s1 = skip_punct(((const OptionHelp *) a)->opt); | |
| const char *s2 = skip_punct(((const OptionHelp *) b)->opt); | |
| return strcmp(s1, s2); | |
| } | |
| static int | |
| cmp_resources(const void *a, const void *b) | |
| { | |
| return strcmp(((const XrmOptionDescRec *) a)->option, | |
| ((const XrmOptionDescRec *) b)->option); | |
| } | |
| XrmOptionDescRec * | |
| sortedOptDescs(XrmOptionDescRec * descs, Cardinal res_count) | |
| { | |
| static XrmOptionDescRec *res_array = 0; | |
| #ifdef NO_LEAKS | |
| if (descs == 0) { | |
| if (res_array != 0) { | |
| free(res_array); | |
| res_array = 0; | |
| } | |
| } else | |
| #endif | |
| if (res_array == 0) { | |
| Cardinal j; | |
| /* make a sorted index to 'resources' */ | |
| res_array = TypeCallocN(XrmOptionDescRec, res_count); | |
| if (res_array != 0) { | |
| for (j = 0; j < res_count; j++) | |
| res_array[j] = descs[j]; | |
| qsort(res_array, (size_t) res_count, sizeof(*res_array), cmp_resources); | |
| } | |
| } | |
| return res_array; | |
| } | |
| /* | |
| * The first time this is called, construct sorted index to the main program's | |
| * list of options, taking into account the on/off options which will be | |
| * compressed into one token. It's a lot simpler to do it this way than | |
| * maintain the list in sorted form with lots of ifdef's. | |
| */ | |
| OptionHelp * | |
| sortedOpts(OptionHelp * options, XrmOptionDescRec * descs, Cardinal numDescs) | |
| { | |
| static OptionHelp *opt_array = 0; | |
| #ifdef NO_LEAKS | |
| if (descs == 0 && opt_array != 0) { | |
| sortedOptDescs(descs, numDescs); | |
| free(opt_array); | |
| opt_array = 0; | |
| return 0; | |
| } else if (options == 0 || descs == 0) { | |
| return 0; | |
| } | |
| #endif | |
| if (opt_array == 0) { | |
| size_t opt_count, j; | |
| #if OPT_TRACE | |
| Cardinal k; | |
| XrmOptionDescRec *res_array = sortedOptDescs(descs, numDescs); | |
| int code; | |
| const char *mesg; | |
| #else | |
| (void) descs; | |
| (void) numDescs; | |
| #endif | |
| /* count 'options' and make a sorted index to it */ | |
| for (opt_count = 0; options[opt_count].opt != 0; ++opt_count) { | |
| ; | |
| } | |
| opt_array = TypeCallocN(OptionHelp, opt_count + 1); | |
| for (j = 0; j < opt_count; j++) | |
| opt_array[j] = options[j]; | |
| qsort(opt_array, opt_count, sizeof(OptionHelp), cmp_options); | |
| /* supply the "turn on/off" strings if needed */ | |
| #if OPT_TRACE | |
| for (j = 0; j < opt_count; j++) { | |
| if (!strncmp(opt_array[j].opt, "-/+", (size_t) 3)) { | |
| char temp[80]; | |
| const char *name = opt_array[j].opt + 3; | |
| for (k = 0; k < numDescs; ++k) { | |
| const char *value = res_array[k].value; | |
| if (res_array[k].option[0] == '-') { | |
| code = -1; | |
| } else if (res_array[k].option[0] == '+') { | |
| code = 1; | |
| } else { | |
| code = 0; | |
| } | |
| sprintf(temp, "%.*s", | |
| (int) sizeof(temp) - 2, | |
| opt_array[j].desc); | |
| if (x_strindex(temp, "inhibit") != 0) | |
| code = -code; | |
| if (code != 0 | |
| && res_array[k].value != 0 | |
| && !strcmp(name, res_array[k].option + 1)) { | |
| if (((code < 0) && !strcmp(value, "on")) | |
| || ((code > 0) && !strcmp(value, "off")) | |
| || ((code > 0) && !strcmp(value, "0"))) { | |
| mesg = "turn on/off"; | |
| } else { | |
| mesg = "turn off/on"; | |
| } | |
| if (strncmp(mesg, opt_array[j].desc, strlen(mesg))) { | |
| if (strncmp(opt_array[j].desc, "turn ", (size_t) 5)) { | |
| char *s = CastMallocN(char, | |
| strlen(mesg) | |
| + 1 | |
| + strlen(opt_array[j].desc)); | |
| if (s != 0) { | |
| sprintf(s, "%s %s", mesg, opt_array[j].desc); | |
| opt_array[j].desc = s; | |
| } | |
| } else { | |
| TRACE(("OOPS ")); | |
| } | |
| } | |
| TRACE(("%s: %s %s: %s (%s)\n", | |
| mesg, | |
| res_array[k].option, | |
| res_array[k].value, | |
| opt_array[j].opt, | |
| opt_array[j].desc)); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| return opt_array; | |
| } | |
| /* | |
| * Report the character-type locale that xterm was started in. | |
| */ | |
| String | |
| xtermEnvLocale(void) | |
| { | |
| static String result; | |
| if (result == 0) { | |
| if ((result = x_nonempty(setlocale(LC_CTYPE, 0))) == 0) { | |
| result = x_strdup("C"); | |
| } else { | |
| result = x_strdup(result); | |
| } | |
| TRACE(("xtermEnvLocale ->%s\n", result)); | |
| } | |
| return result; | |
| } | |
| char * | |
| xtermEnvEncoding(void) | |
| { | |
| static char *result; | |
| if (result == 0) { | |
| #ifdef HAVE_LANGINFO_CODESET | |
| result = nl_langinfo(CODESET); | |
| #else | |
| char *locale = xtermEnvLocale(); | |
| if (!strcmp(locale, "C") || !strcmp(locale, "POSIX")) { | |
| result = "ASCII"; | |
| } else { | |
| result = "ISO-8859-1"; | |
| } | |
| #endif | |
| TRACE(("xtermEnvEncoding ->%s\n", result)); | |
| } | |
| return result; | |
| } | |
| #if OPT_WIDE_CHARS | |
| /* | |
| * Tell whether xterm was started in a locale that uses UTF-8 encoding for | |
| * characters. That environment is inherited by subprocesses and used in | |
| * various library calls. | |
| */ | |
| Bool | |
| xtermEnvUTF8(void) | |
| { | |
| static Bool init = False; | |
| static Bool result = False; | |
| if (!init) { | |
| init = True; | |
| #ifdef HAVE_LANGINFO_CODESET | |
| result = (strcmp(xtermEnvEncoding(), "UTF-8") == 0); | |
| #else | |
| result = (strstr(xtermEnvLocale(), "UTF-8") != NULL); | |
| #endif | |
| TRACE(("xtermEnvUTF8 ->%s\n", BtoS(result))); | |
| } | |
| return result; | |
| } | |
| #endif /* OPT_WIDE_CHARS */ | |
| /* | |
| * Check if the current widget, or any parent, is the VT100 "xterm" widget. | |
| */ | |
| XtermWidget | |
| getXtermWidget(Widget w) | |
| { | |
| XtermWidget xw; | |
| if (w == 0) { | |
| xw = (XtermWidget) CURRENT_EMU(); | |
| if (!IsXtermWidget(xw)) { | |
| xw = 0; | |
| } | |
| } else if (IsXtermWidget(w)) { | |
| xw = (XtermWidget) w; | |
| } else { | |
| xw = getXtermWidget(XtParent(w)); | |
| } | |
| TRACE2(("getXtermWidget %p -> %p\n", w, xw)); | |
| return xw; | |
| } | |
| #if OPT_SESSION_MGT | |
| static void | |
| die_callback(Widget w GCC_UNUSED, | |
| XtPointer client_data GCC_UNUSED, | |
| XtPointer call_data GCC_UNUSED) | |
| { | |
| NormalExit(); | |
| } | |
| static void | |
| save_callback(Widget w GCC_UNUSED, | |
| XtPointer client_data GCC_UNUSED, | |
| XtPointer call_data) | |
| { | |
| XtCheckpointToken token = (XtCheckpointToken) call_data; | |
| /* we have nothing to save */ | |
| token->save_success = True; | |
| } | |
| static void | |
| icewatch(IceConn iceConn, | |
| IcePointer clientData GCC_UNUSED, | |
| Bool opening, | |
| IcePointer * watchData GCC_UNUSED) | |
| { | |
| if (opening) { | |
| ice_fd = IceConnectionNumber(iceConn); | |
| TRACE(("got IceConnectionNumber %d\n", ice_fd)); | |
| } else { | |
| ice_fd = -1; | |
| TRACE(("reset IceConnectionNumber\n")); | |
| } | |
| } | |
| void | |
| xtermOpenSession(void) | |
| { | |
| if (resource.sessionMgt) { | |
| TRACE(("Enabling session-management callbacks\n")); | |
| XtAddCallback(toplevel, XtNdieCallback, die_callback, NULL); | |
| XtAddCallback(toplevel, XtNsaveCallback, save_callback, NULL); | |
| } | |
| } | |
| void | |
| xtermCloseSession(void) | |
| { | |
| IceRemoveConnectionWatch(icewatch, NULL); | |
| } | |
| #endif /* OPT_SESSION_MGT */ | |
| Widget | |
| xtermOpenApplication(XtAppContext * app_context_return, | |
| String my_class, | |
| XrmOptionDescRec * options, | |
| Cardinal num_options, | |
| int *argc_in_out, | |
| String * argv_in_out, | |
| String * fallback_resources, | |
| WidgetClass widget_class, | |
| ArgList args, | |
| Cardinal num_args) | |
| { | |
| Widget result; | |
| XtSetErrorHandler(xt_error); | |
| #if OPT_SESSION_MGT | |
| result = XtOpenApplication(app_context_return, | |
| my_class, | |
| options, | |
| num_options, | |
| argc_in_out, | |
| argv_in_out, | |
| fallback_resources, | |
| widget_class, | |
| args, | |
| num_args); | |
| IceAddConnectionWatch(icewatch, NULL); | |
| #else | |
| result = XtAppInitialize(app_context_return, | |
| my_class, | |
| options, | |
| num_options, | |
| argc_in_out, | |
| argv_in_out, | |
| fallback_resources, | |
| NULL, 0); | |
| #endif /* OPT_SESSION_MGT */ | |
| XtSetErrorHandler((XtErrorHandler) 0); | |
| return result; | |
| } | |
| static int x11_errors; | |
| static int | |
| catch_x11_error(Display * display, XErrorEvent * error_event) | |
| { | |
| (void) display; | |
| (void) error_event; | |
| ++x11_errors; | |
| return 0; | |
| } | |
| Boolean | |
| xtermGetWinAttrs(Display * dpy, Window win, XWindowAttributes * attrs) | |
| { | |
| Boolean result = False; | |
| Status code; | |
| memset(attrs, 0, sizeof(*attrs)); | |
| if (win != None) { | |
| XErrorHandler save = XSetErrorHandler(catch_x11_error); | |
| x11_errors = 0; | |
| code = XGetWindowAttributes(dpy, win, attrs); | |
| XSetErrorHandler(save); | |
| result = (Boolean) ((code != 0) && !x11_errors); | |
| if (result) { | |
| TRACE_WIN_ATTRS(attrs); | |
| } else { | |
| xtermWarning("invalid window-id %ld\n", (long) win); | |
| } | |
| } | |
| return result; | |
| } | |
| Boolean | |
| xtermGetWinProp(Display * display, | |
| Window win, | |
| Atom property, | |
| long long_offset, | |
| long long_length, | |
| Atom req_type, | |
| Atom * actual_type_return, | |
| int *actual_format_return, | |
| unsigned long *nitems_return, | |
| unsigned long *bytes_after_return, | |
| unsigned char **prop_return) | |
| { | |
| Boolean result = True; | |
| if (win != None) { | |
| XErrorHandler save = XSetErrorHandler(catch_x11_error); | |
| x11_errors = 0; | |
| if (XGetWindowProperty(display, | |
| win, | |
| property, | |
| long_offset, | |
| long_length, | |
| False, | |
| req_type, | |
| actual_type_return, | |
| actual_format_return, | |
| nitems_return, | |
| bytes_after_return, | |
| prop_return) == Success | |
| && x11_errors == 0) { | |
| result = True; | |
| } | |
| XSetErrorHandler(save); | |
| } | |
| return result; | |
| } | |
| void | |
| xtermEmbedWindow(Window winToEmbedInto) | |
| { | |
| Display *dpy = XtDisplay(toplevel); | |
| XWindowAttributes attrs; | |
| TRACE(("checking winToEmbedInto %#lx\n", winToEmbedInto)); | |
| if (xtermGetWinAttrs(dpy, winToEmbedInto, &attrs)) { | |
| XtermWidget xw = term; | |
| TScreen *screen = TScreenOf(xw); | |
| XtRealizeWidget(toplevel); | |
| TRACE(("...reparenting toplevel %#lx into %#lx\n", | |
| XtWindow(toplevel), | |
| winToEmbedInto)); | |
| XReparentWindow(dpy, | |
| XtWindow(toplevel), | |
| winToEmbedInto, 0, 0); | |
| screen->embed_high = (Dimension) attrs.height; | |
| screen->embed_wide = (Dimension) attrs.width; | |
| } | |
| } |