/*--------------------------------*-C-*---------------------------------*
 * File:	main.c
 *----------------------------------------------------------------------*
 * $Id: main.c,v 1.66.2.11 2000/06/04 06:47:32 gcw Exp $
 *
 * All portions of code are copyright by their respective author/s.
 * Copyright (C) 1992      John Bovey, University of Canterbury
 *				- original version
 * Copyright (C) 1994      Robert Nation <nation@rocket.sanders.lockheed.com>
 *				- extensive modifications
 * Copyright (C) 1995      Garrett D'Amore <garrett@netcom.com>
 * Copyright (C) 1997      mj olesen <olesen@me.QueensU.CA>
 *				- extensive modifications
 * Copyright (C) 1997,1998 Oezguer Kesim <kesim@math.fu-berlin.de>
 * Copyright (C) 1998      Geoff Wing <gcw@pobox.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *---------------------------------------------------------------------*/

#define INTERN			/* assign all global vars to me */
#include "rxvt.h"		/* NECESSARY */
#include "main.intpro"		/* PROTOS for internal routines */

#include <X11/Xatom.h>

static Cursor   TermWin_cursor;	/* cursor for vt window */

static const char * const def_colorName[] =
{
    COLOR_FOREGROUND,
    COLOR_BACKGROUND,
/* low-intensity colors */
    "Black",			/* 0: black             (#000000) */
#ifndef NO_BRIGHTCOLOR
    "Red3",			/* 1: red               (#CD0000) */
    "Green3",			/* 2: green             (#00CD00) */
    "Yellow3",			/* 3: yellow            (#CDCD00) */
    "Blue3",			/* 4: blue              (#0000CD) */
    "Magenta3",			/* 5: magenta           (#CD00CD) */
    "Cyan3",			/* 6: cyan              (#00CDCD) */
    "AntiqueWhite",		/* 7: white             (#FAEBD7) */
/* high-intensity colors */
    "Grey25",			/* 8: bright black      (#404040) */
#endif				/* NO_BRIGHTCOLOR */
    "Red",			/* 1/9: bright red      (#FF0000) */
    "Green",			/* 2/10: bright green   (#00FF00) */
    "Yellow",			/* 3/11: bright yellow  (#FFFF00) */
    "Blue",			/* 4/12: bright blue    (#0000FF) */
    "Magenta",			/* 5/13: bright magenta (#FF00FF) */
    "Cyan",			/* 6/14: bright cyan    (#00FFFF) */
    "White",			/* 7/15: bright white   (#FFFFFF) */
#ifndef NO_CURSORCOLOR
    COLOR_CURSOR_BACKGROUND,
    COLOR_CURSOR_FOREGROUND,
#endif				/* ! NO_CURSORCOLOR */
    NULL,			/* Color_pointer                  */
    NULL			/* Color_border                   */
#ifndef NO_BOLDUNDERLINE
  , NULL,			/* Color_BD                       */
    NULL			/* Color_UL                       */
#endif				/* ! NO_BOLDUNDERLINE */
#ifdef KEEP_SCROLLCOLOR
  , COLOR_SCROLLBAR,
    COLOR_SCROLLTROUGH
#endif				/* KEEP_SCROLLCOLOR */
};

#ifdef MULTICHAR_SET
/* Multicharacter font names, roman fonts sized to match */
static const char * const def_mfontName[] =
{
    MFONT_LIST
};
#endif				/* MULTICHAR_SET */

static const char * const def_fontName[] =
{
    NFONT_LIST
};

/*----------------------------------------------------------------------*/
/* ARGSUSED */
/* INTPROTO */
XErrorHandler
xerror_handler(const Display *display, const XErrorEvent *event)
{
    print_error("XError: Request: %d . %d, Error: %d", event->request_code,
		event->minor_code, event->error_code);
    exit(EXIT_FAILURE);
    /* NOTREACHED */
}

/* color aliases, fg/bg bright-bold */
/* INTPROTO */
void
color_aliases(int idx)
{
    if (rs[Rs_color + idx] && isdigit(*(rs[Rs_color + idx]))) {
	int             i = atoi(rs[Rs_color + idx]);

	if (i >= 8 && i <= 15) {	/* bright colors */
	    i -= 8;
#ifndef NO_BRIGHTCOLOR
	    rs[Rs_color + idx] = rs[Rs_color + minBrightCOLOR + i];
	    return;
#endif
	}
	if (i >= 0 && i <= 7)	/* normal colors */
	    rs[Rs_color + idx] = rs[Rs_color + minCOLOR + i];
    }
}

/*
 * find if fg/bg matches any of the normal (low-intensity) colors
 */
/* INTPROTO */
void
set_colorfgbg(void)
{
    unsigned int    i;
    char           *p;
    int             fg = -1, bg = -1;
    static char     env_colorfgbg[] = "COLORFGBG=default;default;bg";

    for (i = Color_Black; i <= Color_White; i++) {
	if (PixColors[Color_fg] == PixColors[i]) {
	    fg = (i - Color_Black);
	    break;
	}
    }
    for (i = Color_Black; i <= Color_White; i++) {
	if (PixColors[Color_bg] == PixColors[i]) {
	    bg = (i - Color_Black);
	    break;
	}
    }

    p = strchr(env_colorfgbg, '=');
    p++;
    if (fg >= 0)
	sprintf(p, "%d;", fg);
    else
	STRCPY(p, "default;");
    p = strchr(p, '\0');
    if (bg >= 0)
	sprintf(p,
#ifdef XPM_BACKGROUND
		"default;"
#endif
		"%d", bg);
    else
	STRCPY(p, "default");
    putenv(env_colorfgbg);

#ifndef NO_BRIGHTCOLOR
    colorfgbg = DEFAULT_RSTYLE;
    for (i = minCOLOR; i <= maxCOLOR; i++) {
	if (PixColors[Color_fg] == PixColors[i]
# ifndef NO_BOLDUNDERLINE
	    && PixColors[Color_fg] == PixColors[Color_BD]
# endif				/* NO_BOLDUNDERLINE */
    /* if we wanted boldFont to have precedence */
# if 0				/* ifndef NO_BOLDFONT */
	    && TermWin.boldFont == NULL
# endif				/* NO_BOLDFONT */
	    )
	    colorfgbg = SET_FGCOLOR(colorfgbg, i);
	if (PixColors[Color_bg] == PixColors[i])
	    colorfgbg = SET_BGCOLOR(colorfgbg, i);
    }
#endif
}

/* INTPROTO */
void
Get_Colours(void)
{
    int             i;

    for (i = 0; i < (Xdepth <= 2 ? 2 : NRS_COLORS); i++) {
	const char     *msg = "can't load color \"%s\"";
	XColor          xcol;

	if (!rs[Rs_color + i])
	    continue;

	if (!XParseColor(Xdisplay, Xcmap, rs[Rs_color + i], &xcol)
	    || !XAllocColor(Xdisplay, Xcmap, &xcol)) {
	    print_error(msg, rs[Rs_color + i]);
#ifndef XTERM_REVERSE_VIDEO
	    if (i < 2 && (Options & Opt_reverseVideo)) {
		rs[Rs_color + i] = def_colorName[!i];
	    } else
#endif
	        rs[Rs_color + i] = def_colorName[i];
	    if (!rs[Rs_color + i])
		continue;
	    if (!XParseColor(Xdisplay, Xcmap, rs[Rs_color + i], &xcol)
		|| !XAllocColor(Xdisplay, Xcmap, &xcol)) {
		print_error(msg, rs[Rs_color + i]);
		switch (i) {
		case Color_fg:
		case Color_bg:
		/* fatal: need bg/fg color */
		    print_error("aborting");
		    exit(EXIT_FAILURE);
		    /* NOTREACHED */
		    break;
#ifndef NO_CURSORCOLOR
		case Color_cursor2:
		    xcol.pixel = PixColors[Color_fg];
		    break;
#endif				/* ! NO_CURSORCOLOR */
		case Color_pointer:
		    xcol.pixel = PixColors[Color_fg];
		    break;
		default:
		    xcol.pixel = PixColors[Color_bg];	/* None */
		    break;
		}
	    }
	}
	PixColors[i] = xcol.pixel;
    }

    if (Xdepth <= 2 || !rs[Rs_color + Color_pointer])
	PixColors[Color_pointer] = PixColors[Color_fg];
    if (Xdepth <= 2 || !rs[Rs_color + Color_border])
	PixColors[Color_border] = PixColors[Color_fg];

/*
 * get scrollBar/menuBar shadow colors
 *
 * The calculations of topShadow/bottomShadow values are adapted
 * from the fvwm window manager.
 */
#ifdef KEEP_SCROLLCOLOR
    if (Xdepth <= 2) {		/* Monochrome */
	PixColors[Color_scroll] = PixColors[Color_fg];
	PixColors[Color_topShadow] = PixColors[Color_bg];
	PixColors[Color_bottomShadow] = PixColors[Color_bg];
    } else {
	XColor          xcol, white;

    /* bottomShadowColor */
	xcol.pixel = PixColors[Color_scroll];
	XQueryColor(Xdisplay, Xcmap, &xcol);

	xcol.red = ((xcol.red) / 2);
	xcol.green = ((xcol.green) / 2);
	xcol.blue = ((xcol.blue) / 2);

	if (!XAllocColor(Xdisplay, Xcmap, &xcol)) {
	    print_error("can't allocate %s", "Color_bottomShadow");
	    xcol.pixel = PixColors[minCOLOR];
	}
	PixColors[Color_bottomShadow] = xcol.pixel;

    /* topShadowColor */
# ifdef PREFER_24BIT
	white.red = white.green = white.blue = (unsigned short) ~0;
	XAllocColor(Xdisplay, Xcmap, &white);
/*        XFreeColors(Xdisplay, Xcmap, &white.pixel, 1, ~0); */
# else
	white.pixel = WhitePixel(Xdisplay, Xscreen);
	XQueryColor(Xdisplay, Xcmap, &white);
# endif

	xcol.pixel = PixColors[Color_scroll];
	XQueryColor(Xdisplay, Xcmap, &xcol);

	xcol.red = max((white.red / 5), xcol.red);
	xcol.green = max((white.green / 5), xcol.green);
	xcol.blue = max((white.blue / 5), xcol.blue);

	xcol.red = min(white.red, (xcol.red * 7) / 5);
	xcol.green = min(white.green, (xcol.green * 7) / 5);
	xcol.blue = min(white.blue, (xcol.blue * 7) / 5);

	if (!XAllocColor(Xdisplay, Xcmap, &xcol)) {
	    print_error("can't allocate %s", "Color_topShadow");
	    xcol.pixel = PixColors[Color_White];
	}
	PixColors[Color_topShadow] = xcol.pixel;
    }
#endif				/* KEEP_SCROLLCOLOR */


}

/* Create_Windows() - Open and map the window */
/* INTPROTO */
void
Create_Windows(int argc, const char * const *argv)
{
    Cursor          cursor;
    XClassHint      classHint;
    XWMHints        wmHint;
#ifdef PREFER_24BIT
    XSetWindowAttributes attributes;

    Xdepth = DefaultDepth(Xdisplay, Xscreen);
    Xcmap = DefaultColormap(Xdisplay, Xscreen);
    Xvisual = DefaultVisual(Xdisplay, Xscreen);
/*
 * If depth is not 24, look for a 24bit visual.
 */
    if (Xdepth != 24) {
	XVisualInfo     vinfo;

	if (XMatchVisualInfo(Xdisplay, Xscreen, 24, TrueColor, &vinfo)) {
	    Xdepth = 24;
	    Xvisual = vinfo.visual;
	    Xcmap = XCreateColormap(Xdisplay, RootWindow(Xdisplay, Xscreen),
				    Xvisual, AllocNone);
	}
    }
#endif

/* grab colors before netscape does */
    Get_Colours();

    change_font(1, NULL);
    szhints_set();

/* parent window - reverse video so we can see placement errors
 * sub-window placement & size in resize_subwindows()
 */

#ifdef PREFER_24BIT
    attributes.background_pixel = PixColors[Color_fg];
    attributes.border_pixel = PixColors[Color_border];
    attributes.colormap = Xcmap;
    TermWin.parent[0] = XCreateWindow(Xdisplay, Xroot,
				   szHint.x, szHint.y,
				   szHint.width, szHint.height,
				   BORDERWIDTH,
				   Xdepth, InputOutput,
				   Xvisual,
				   CWBackPixel | CWBorderPixel | CWColormap,
				   &attributes);
#else
    TermWin.parent[0] = XCreateSimpleWindow(Xdisplay, Xroot,
					 szHint.x, szHint.y,
					 szHint.width, szHint.height,
					 BORDERWIDTH,
					 PixColors[Color_border],
					 PixColors[Color_fg]);
#endif
    xterm_seq(XTerm_title, rs[Rs_title]);
    xterm_seq(XTerm_iconName, rs[Rs_iconName]);
/* ignore warning about discarded `const' */
    classHint.res_name = (char *) rs[Rs_name];
    classHint.res_class = (char *) APL_CLASS;
    wmHint.input = True;
    wmHint.initial_state = (Options & Opt_iconic ? IconicState : NormalState);
    wmHint.window_group = TermWin.parent[0];
    wmHint.flags = (InputHint | StateHint | WindowGroupHint);

    XSetWMProperties(Xdisplay, TermWin.parent[0], NULL, NULL, (char **) argv,
		     argc, &szHint, &wmHint, &classHint);

    XSelectInput(Xdisplay, TermWin.parent[0],
		 (KeyPressMask | FocusChangeMask
		  | VisibilityChangeMask
		  | StructureNotifyMask));

/* vt cursor: Black-on-White is standard, but this is more popular */
    TermWin_cursor = XCreateFontCursor(Xdisplay, XC_xterm);
    {
	XColor          fg, bg;

	fg.pixel = PixColors[Color_pointer];
	XQueryColor(Xdisplay, Xcmap, &fg);
	bg.pixel = PixColors[Color_bg];
	XQueryColor(Xdisplay, Xcmap, &bg);
	XRecolorCursor(Xdisplay, TermWin_cursor, &fg, &bg);
    }

/* cursor (menuBar/scrollBar): Black-on-White */
    cursor = XCreateFontCursor(Xdisplay, XC_left_ptr);

/* the vt window */
    TermWin.vt = XCreateSimpleWindow(Xdisplay, TermWin.parent[0],
				     0, 0,
				     szHint.width, szHint.height,
				     0,
				     PixColors[Color_fg],
				     PixColors[Color_bg]);

    XDefineCursor(Xdisplay, TermWin.vt, TermWin_cursor);
    XSelectInput(Xdisplay, TermWin.vt,
		 (ExposureMask | ButtonPressMask | ButtonReleaseMask |
		  Button1MotionMask | Button3MotionMask));

/* scrollBar: size doesn't matter */
    scrollBar.win = XCreateSimpleWindow(Xdisplay, TermWin.parent[0],
					0, 0,
					1, 1,
					0,
					PixColors[Color_fg],
					PixColors[Color_bg]);

    XDefineCursor(Xdisplay, scrollBar.win, cursor);
    XSelectInput(Xdisplay, scrollBar.win,
		 (ExposureMask | ButtonPressMask | ButtonReleaseMask |
		  Button1MotionMask | Button2MotionMask | Button3MotionMask));

    { /* ONLYIF MENUBAR */
	create_menuBar(cursor);
    }

#ifdef XPM_BACKGROUND
    if (rs[Rs_backgroundPixmap] != NULL && !(Options & Opt_transparent)) {
	const char     *p = rs[Rs_backgroundPixmap];

	if ((p = strchr(p, ';')) != NULL) {
	    p++;
	    scale_pixmap(p);
	}
	set_bgPixmap(rs[Rs_backgroundPixmap]);
	scr_touch();
    }
# ifdef XPM_BUFFERING
    else {
	set_bgPixmap("");
	scr_touch();
    }
# endif
#endif

/* graphics context for the vt window */
    {
	XGCValues       gcvalue;

	gcvalue.font = TermWin.font->fid;
	gcvalue.foreground = PixColors[Color_fg];
	gcvalue.background = PixColors[Color_bg];
	gcvalue.graphics_exposures = 0;
	TermWin.gc = XCreateGC(Xdisplay, TermWin.vt,
			       GCForeground | GCBackground |
			       GCFont | GCGraphicsExposures,
			       &gcvalue);
    }
}
/* window resizing - assuming the parent window is the correct size */
/* INTPROTO */
void
resize_subwindows(int width, int height)
{
    int             x = 0, y = 0;
    int             old_width = TermWin.width, old_height = TermWin.height;

    TermWin.width = TermWin.ncol * TermWin.fwidth;
    TermWin.height = TermWin.nrow * TermWin.fheight;

    szHint.width = width;
    szHint.height = height;

/* size and placement */
    if (scrollbar_visible()) {
	scrollBar.beg = 0;
	scrollBar.end = height;
#ifndef XTERM_SCROLLBAR
    /* arrows are as high as wide - leave 1 pixel gap */
# ifdef NEXT_SCROLLBAR
	scrollBar.end -= SB_BUTTON_TOTAL_HEIGHT + SB_PADDING;
# else
	scrollBar.beg += (SB_WIDTH + 1) + sb_shadow;
	scrollBar.end -= (SB_WIDTH + 1) + sb_shadow;
# endif
#endif
	x = (SB_WIDTH + 2 * sb_shadow);	/* placement of vt window */
	width -= x;
	if ((Options & Opt_scrollBar_right) == 0)
	    XMoveResizeWindow(Xdisplay, scrollBar.win, 0, 0,
			      x, height);
	else {
	    XMoveResizeWindow(Xdisplay, scrollBar.win, width, 0,
			      x, height);
	    x = 0;		/* scrollbar on right so vt window at left */
	}
    }
    { /* ONLYIF MENUBAR */
	if (menubar_visible()) {
	    y = menuBar_TotalHeight();	/* for placement of vt window */
	    Resize_menuBar(x, 0, width, y);
	}
    }
    XMoveResizeWindow(Xdisplay, TermWin.vt, x, y, width, height + 1);

    if (old_width)
	Gr_Resize(old_width, old_height);

    scr_clear();
    resize_pixmap();
    XSync(Xdisplay, False);
}

/* EXTPROTO */
void
resize_all_windows(void)
{
    szhints_recalc();
    XSetWMNormalHints(Xdisplay, TermWin.parent[0], &szHint);
    AddToCNQueue(szHint.width, szHint.height);
    XResizeWindow(Xdisplay, TermWin.parent[0], szHint.width, szHint.height);
    resize_window(szHint.width, szHint.height);
}

/*
 * Redraw window after exposure or size change
 * width/height are those of the parent
 */
/* EXTPROTO */
void
resize_window(unsigned int width, unsigned int height)
{
    int             new_ncol, new_nrow;
    static int      old_width, old_height = -1;

    new_ncol = (width - szHint.base_width) / TermWin.fwidth;
    new_nrow = (height - szHint.base_height) / TermWin.fheight;
    if (new_ncol > MAX_COLS) {
	new_ncol = MAX_COLS;
	width = (MAX_COLS * TermWin.fwidth) + szHint.base_width;
    }
    if (new_nrow > MAX_ROWS) {
	new_nrow = MAX_ROWS;
	height = (MAX_ROWS * TermWin.fheight) + szHint.base_height;
    }
    if (old_height == -1
	|| (new_ncol != TermWin.ncol)
	|| (new_nrow != TermWin.nrow)) {
	int             curr_screen = -1;

    /* scr_reset only works on the primary screen */
	if (old_height != -1) {	/* this is not the first time thru */
	    selection_clear();
	    curr_screen = scr_change_screen(PRIMARY);
	}
	TermWin.ncol = new_ncol;
	TermWin.nrow = new_nrow;

	resize_subwindows(width, height);
	scr_reset();

	if (curr_screen >= 0)	/* this is not the first time thru */
	    scr_change_screen(curr_screen);
    } else if (width != old_width || height != old_height)
	resize_subwindows(width, height);
    old_width = width;
    old_height = height;
}

/*
 * Set the width/height of the window in characters.  Units are pixels.
 * good for toggling 80/132 columns
 */
/* EXTPROTO */
void
set_widthheight(unsigned int width, unsigned int height)
{
    XWindowAttributes wattr;

    MIN_IT(width, (MAX_COLS * TermWin.fwidth));
    MIN_IT(height, (MAX_ROWS * TermWin.fheight));
    if (width == 0 || height == 0) {
	XGetWindowAttributes(Xdisplay, Xroot, &wattr);
	if (width == 0)
	    width = wattr.width	- szHint.base_width;
	if (height == 0)
	    height = wattr.height - szHint.base_height;
    }

    if (width != TermWin.width || height != TermWin.height) {
	width = szHint.base_width + width;
	height = szHint.base_height + height;

	AddToCNQueue(width, height);
	XResizeWindow(Xdisplay, TermWin.parent[0], width, height);
	resize_window(width, height);
#ifdef USE_XIM
	IMSetStatusPosition();
#endif
    }
}

/* INTPROTO */
void
szhints_set(void)
{
    int             x, y, flags;
    unsigned int    width, height;

    szHint.flags = PMinSize | PResizeInc | PBaseSize | PWinGravity;
    szHint.win_gravity = NorthWestGravity;
    szHint.min_aspect.x = szHint.min_aspect.y = 1;

    flags = (rs[Rs_geometry] ?
	     XParseGeometry(rs[Rs_geometry], &x, &y, &width, &height) : 0);

    if (flags & WidthValue) {
	TermWin.ncol = min(width, MAX_COLS);
	szHint.flags |= USSize;
    }
    if (flags & HeightValue) {
	TermWin.nrow = min(height, MAX_ROWS);
	szHint.flags |= USSize;
    }
    TermWin.width = TermWin.ncol * TermWin.fwidth;
    TermWin.height = TermWin.nrow * TermWin.fheight;
    szhints_recalc();

    if (flags & XValue) {
	if (flags & XNegative) {
	    x += (DisplayWidth(Xdisplay, Xscreen)
		  - (szHint.width + TermWin_internalBorder));
	    szHint.win_gravity = NorthEastGravity;
	}
	szHint.x = x;
	szHint.flags |= USPosition;
    }
    if (flags & YValue) {
	if (flags & YNegative) {
	    y += (DisplayHeight(Xdisplay, Xscreen)
		  - (szHint.height + TermWin_internalBorder));
	    szHint.win_gravity = (szHint.win_gravity == NorthEastGravity ?
				  SouthEastGravity : SouthWestGravity);
	}
	szHint.y = y;
	szHint.flags |= USPosition;
    }
}

/* INTPROTO */
void
szhints_recalc(void)
{
    szHint.base_width = (2 * TermWin_internalBorder);
    szHint.base_height = (2 * TermWin_internalBorder);
    szHint.base_width += (scrollbar_visible() ? (SB_WIDTH + 2 * sb_shadow) : 0);
    szHint.base_height += (menubar_visible() ? menuBar_TotalHeight() : 0);
    szHint.width_inc = TermWin.fwidth;
    szHint.height_inc = TermWin.fheight;
    szHint.min_width = szHint.base_width + szHint.width_inc;
    szHint.min_height = szHint.base_height + szHint.height_inc;
    szHint.width = szHint.base_width + TermWin.width;
    szHint.height = szHint.base_height + TermWin.height;
}

/* xterm sequences - title, iconName, color (exptl) */
/* INTPROTO */
void
set_title(const char *str)
{
#ifndef SMART_WINDOW_TITLE
    XStoreName(Xdisplay, TermWin.parent[0], str);
#else
    char           *name;

    if (XFetchName(Xdisplay, TermWin.parent[0], &name))
	name = NULL;
    if (name == NULL || strcmp(name, str))
	XStoreName(Xdisplay, TermWin.parent[0], str);
    if (name)
	XFree(name);
#endif
}

/* INTPROTO */
void
set_iconName(const char *str)
{
#ifndef SMART_WINDOW_TITLE
    XSetIconName(Xdisplay, TermWin.parent[0], str);
#else
    char           *name;

    if (XGetIconName(Xdisplay, TermWin.parent[0], &name))
	name = NULL;
    if (name == NULL || strcmp(name, str))
	XSetIconName(Xdisplay, TermWin.parent[0], str);
    if (name)
	XFree(name);
#endif
}

#ifdef XTERM_COLOR_CHANGE
/* INTPROTO */
void
set_window_color(int idx, const char *color)
{
    const char     *msg = "can't load color \"%s\"";
    XColor          xcol;
    int             i;

    if (color == NULL || *color == '\0')
	return;

/* handle color aliases */
    if (isdigit(*color)) {
	i = atoi(color);
	if (i >= 8 && i <= 15) {	/* bright colors */
	    i -= 8;
# ifndef NO_BRIGHTCOLOR
	    PixColors[idx] = PixColors[minBrightCOLOR + i];
	    goto Done;
# endif
	}
	if (i >= 0 && i <= 7) {	/* normal colors */
	    PixColors[idx] = PixColors[minCOLOR + i];
	    goto Done;
	}
    }
    if (!XParseColor(Xdisplay, Xcmap, color, &xcol)
	|| !XAllocColor(Xdisplay, Xcmap, &xcol)) {
	print_error(msg, color);
	return;
    }
/* XStoreColor (Xdisplay, Xcmap, XColor*); */

/*
 * FIXME: should free colors here, but no idea how to do it so instead,
 * so just keep gobbling up the colormap
 */
# if 0
    for (i = Color_Black; i <= Color_White; i++)
	if (PixColors[idx] == PixColors[i])
	    break;
    if (i > Color_White) {
    /* fprintf (stderr, "XFreeColors: PixColors [%d] = %lu\n", idx, PixColors [idx]); */
	XFreeColors(Xdisplay, Xcmap, (PixColors + idx), 1,
		    DisplayPlanes(Xdisplay, Xscreen));
    }
# endif

    PixColors[idx] = xcol.pixel;

/* XSetWindowAttributes attr; */
/* Cursor cursor; */
  Done:
    if (idx == Color_bg && !(Options & Opt_transparent))
	XSetWindowBackground(Xdisplay, TermWin.vt, PixColors[Color_bg]);

/* handle Color_BD, scrollbar background, etc. */

    set_colorfgbg();
    {
	XColor          fg, bg;

	fg.pixel = PixColors[Color_pointer];
	XQueryColor(Xdisplay, Xcmap, &fg);
	bg.pixel = PixColors[Color_bg];
	XQueryColor(Xdisplay, Xcmap, &bg);
	XRecolorCursor(Xdisplay, TermWin_cursor, &fg, &bg);
    }
/* the only reasonable way to enforce a clean update */
    scr_poweron();
}
#else
# define set_window_color(idx,color)	((void)0)
#endif				/* XTERM_COLOR_CHANGE */

/*
 * XTerm escape sequences: ESC ] Ps;Pt BEL
 *       0 = change iconName/title
 *       1 = change iconName
 *       2 = change title
 *      46 = change logfile (not implemented)
 *      50 = change font
 *
 * rxvt extensions:
 *      10 = menu
 *      20 = bg pixmap
 *      39 = change default fg color
 *      49 = change default bg color
 */
/* EXTPROTO */
void
xterm_seq(int op, const char *str)
{
    int             changed = 0;

    assert(str != NULL);
    switch (op) {
    case XTerm_name:
	set_title(str);
    /* FALLTHROUGH */
    case XTerm_iconName:
	set_iconName(str);
	break;
    case XTerm_title:
	set_title(str);
	break;
    case XTerm_Menu:
    /*
     * menubar_dispatch() violates the constness of the string,
     * so DON'T do it here
     */
	break;
    case XTerm_Pixmap:
	if (*str != ';') {
	    scale_pixmap("");	/* reset to default scaling */
	    set_bgPixmap(str);	/* change pixmap */
	    scr_touch();
	}
	while ((str = strchr(str, ';')) != NULL) {
	    str++;
	    changed += scale_pixmap(str);
	}
	if (changed) {
	    resize_pixmap();
	    scr_touch();
	}
	break;

    case XTerm_restoreFG:
	set_window_color(Color_fg, str);
	break;
    case XTerm_restoreBG:
	set_window_color(Color_bg, str);
	break;
    case XTerm_logfile:
	break;
    case XTerm_font:
	change_font(0, str);
	break;
    }
}

/* change_font() - Switch to a new font */
/*
 * init = 1   - initialize
 *
 * fontname == FONT_UP  - switch to bigger font
 * fontname == FONT_DN  - switch to smaller font
 */
/* EXTPROTO */
void
change_font(int init, const char *fontname)
{
    const char     *msg = "can't load font \"%s\"";
    int             idx = 0;	/* index into rs[Rs_font] */
    int             recheckfonts;
    XFontStruct    *xfont;
    static char    *newfont[NFONTS];
    static int      fnum;		/* logical font number */
#ifndef NO_BOLDFONT
    static XFontStruct *boldFont;
#endif

#if (FONT0_IDX == 0)
# define IDX2FNUM(i)	(i)
# define FNUM2IDX(f)	(f)
#else
# define IDX2FNUM(i)	(i == 0 ? FONT0_IDX : (i <= FONT0_IDX ? (i-1) : i))
# define FNUM2IDX(f)	(f == FONT0_IDX ? 0 : (f < FONT0_IDX  ? (f+1) : f))
#endif
#define FNUM_RANGE(i)	(i <= 0 ? 0 : (i >= NFONTS ? (NFONTS-1) : i))

    if (init) {
#ifndef NO_BOLDFONT
	boldFont = NULL;
#endif
	fnum = FONT0_IDX;	/* logical font number */
    } else {
	switch (fontname[0]) {
	case '\0':
	    fnum = FONT0_IDX;
	    fontname = NULL;
	    break;

	/* special (internal) prefix for font commands */
	case FONT_CMD:
	    idx = atoi(fontname + 1);
	    switch (fontname[1]) {
	    case '+':		/* corresponds to FONT_UP */
		fnum += (idx ? idx : 1);
		fnum = FNUM_RANGE(fnum);
		break;

	    case '-':		/* corresponds to FONT_DN */
		fnum += (idx ? idx : -1);
		fnum = FNUM_RANGE(fnum);
		break;

	    default:
		if (fontname[1] != '\0' && !isdigit(fontname[1]))
		    return;
		if (idx < 0 || idx >= (NFONTS))
		    return;
		fnum = IDX2FNUM(idx);
		break;
	    }
	    fontname = NULL;
	    break;

	default:
	    if (fontname != NULL) {
	    /* search for existing fontname */
		for (idx = 0; idx < NFONTS; idx++) {
		    if (!strcmp(rs[Rs_font + idx], fontname)) {
			fnum = IDX2FNUM(idx);
			fontname = NULL;
			break;
		    }
		}
	    } else
		return;
	    break;
	}
    /* re-position around the normal font */
	idx = FNUM2IDX(fnum);

	if (fontname != NULL) {
	    char           *name;

	    xfont = XLoadQueryFont(Xdisplay, fontname);
	    if (!xfont)
		return;

	    name = MALLOC(strlen(fontname + 1) * sizeof(char));

	    if (name == NULL) {
		XFreeFont(Xdisplay, xfont);
		return;
	    }
	    STRCPY(name, fontname);
	    if (newfont[idx] != NULL)
		FREE(newfont[idx]);
	    newfont[idx] = name;
	    rs[Rs_font + idx] = newfont[idx];
	}
    }
    if (TermWin.font)
	XFreeFont(Xdisplay, TermWin.font);

/* load font or substitute */
    xfont = XLoadQueryFont(Xdisplay, rs[Rs_font + idx]);
    if (!xfont) {
	print_error(msg, rs[Rs_font + idx]);
	rs[Rs_font + idx] = "fixed";
	xfont = XLoadQueryFont(Xdisplay, rs[Rs_font + idx]);
	if (!xfont) {
	    print_error(msg, rs[Rs_font + idx]);
	    goto Abort;
	}
    }
    TermWin.font = xfont;

#ifndef NO_BOLDFONT
/* fail silently */
    if (init && rs[Rs_boldFont] != NULL)
	boldFont = XLoadQueryFont(Xdisplay, rs[Rs_boldFont]);
#endif

#ifdef MULTICHAR_SET
    if (TermWin.mfont)
	XFreeFont(Xdisplay, TermWin.mfont);

/* load font or substitute */
    xfont = XLoadQueryFont(Xdisplay, rs[Rs_mfont + idx]);
    if (!xfont) {
	print_error(msg, rs[Rs_mfont + idx]);
#ifdef ZHCN
	rs[Rs_mfont + idx] = "-*-16-*-gb2312*-*";
#else
	rs[Rs_mfont + idx] = "k14";
#endif
	xfont = XLoadQueryFont(Xdisplay, rs[Rs_mfont + idx]);
	if (!xfont) {
	    print_error(msg, rs[Rs_mfont + idx]);
	    goto Abort;
	}
    }
    TermWin.mfont = xfont;
#endif				/* MULTICHAR_SET */

/* alter existing GC */
    if (!init) {
	XSetFont(Xdisplay, TermWin.gc, TermWin.font->fid);
	menubar_expose();
    }
/* set the sizes */
    {
	int             i, cw, fh, fw = 0;

	fw = get_fontwidest(TermWin.font);
	fh = TermWin.font->ascent + TermWin.font->descent;

	if (fw == TermWin.font->min_bounds.width)
	    TermWin.fprop = 0;	/* Mono-spaced (fixed width) font */
	else
	    TermWin.fprop = 1;	/* Proportional font */

	recheckfonts = !(fw == TermWin.fwidth && fh == TermWin.fheight);

	TermWin.fwidth = fw;
	TermWin.fheight = fh;
    }

/* check that size of boldFont is okay */
#ifndef NO_BOLDFONT
    if (recheckfonts) {
	TermWin.boldFont = NULL;
	if (boldFont != NULL) {
	    int             fh, fw;

	    fw = get_fontwidest(boldFont);
	    fh = boldFont->ascent + boldFont->descent;
	    if (fw <= TermWin.fwidth && fh <= TermWin.fheight)
		TermWin.boldFont = boldFont;
	    TermWin.bprop = !(fw == TermWin.fwidth /* && fh == TermWin.fheight */ );
	}
    }
#endif				/* NO_BOLDFONT */

#ifdef MULTICHAR_SET
    if (recheckfonts)
    /* TODO: XXX: This could be much better? */
	if (TermWin.mfont != NULL) {
	    int             fh, fw;

	    fw = get_fontwidest(TermWin.mfont);
	    fh = TermWin.mfont->ascent + TermWin.mfont->descent;
	    if (fw <= TermWin.fwidth && fh <= TermWin.fheight)
		/* WHAT TO DO!! */ ;
	    TermWin.mprop = !(fw == TermWin.fwidth /* && fh == TermWin.fheight */ );
	}
#endif

    set_colorfgbg();

    TermWin.width = TermWin.ncol * TermWin.fwidth;
    TermWin.height = TermWin.nrow * TermWin.fheight;

    if (!init) {
	resize_all_windows();
	scr_touch();
    }
    return;
  Abort:
    print_error("aborting");	/* fatal problem */
    exit(EXIT_FAILURE);
#undef IDX2FNUM
#undef FNUM2IDX
#undef FNUM_RANGE
    /* NOTREACHED */
}
/* INTPROTO */
int
get_fontwidest(XFontStruct *f)
{
    int             i, cw, fw = 0;

    if (f->min_bounds.width == f->max_bounds.width)
	return f->min_bounds.width;
    if (f->per_char == NULL)
	return 0;
    for (i = f->max_char_or_byte2 - f->min_char_or_byte2; --i >= 0; ) {
	cw = f->per_char[i].width;
	MAX_IT(fw, cw);
    }
    return fw;
}
/* ------------------------------------------------------------------------- */
/* INTPROTO */
void
init_vars(void)
{
    Options = Opt_scrollBar | Opt_scrollTtyOutput;
    sb_shadow = 0;
    TermWin.ncol = 80;
    TermWin.nrow = 24;
    TermWin.mapped = 0;
    want_refresh = 1;
    scrollBar.win = 0;
#if (MENUBAR_MAX)
    menuBar.win = 0;
#endif

#if defined (HOTKEY_CTRL) || defined (HOTKEY_META)
/* recognized when combined with HOTKEY */
    ks_bigfont = XK_greater;
    ks_smallfont = XK_less;
#endif
#ifndef NO_BRIGHTCOLOR
    colorfgbg = DEFAULT_RSTYLE;
#endif
#ifndef NO_NEW_SELECTION
    selection_style = NEW_SELECT;
#else
    selection_style = OLD_SELECT;
#endif
}

/* ------------------------------------------------------------------------- */
/* INTPROTO */
const char    **
init_resources(int argc, const char * const *argv)
{
    int             i, r_argc;
    char           *val;
    const char     *tmp;
    const char    **cmd_argv, **r_argv;

/*
 * Look for -exec option.  Find => split and make cmd_argv[] of command args
 */
    for (r_argc = 0; r_argc < argc; r_argc++)
	if (!strcmp(argv[r_argc], "-e") || !strcmp(argv[r_argc], "-exec"))
	    break;
    r_argv = (const char **) MALLOC(sizeof(char *) * (r_argc + 1));
    for (i = 0; i < r_argc; i++)
	r_argv[i] = (const char *) argv[i];
    r_argv[i] = NULL;
    if (r_argc == argc)
	cmd_argv = NULL;
    else {
	cmd_argv = (const char **) MALLOC(sizeof(char *) * (argc - r_argc));
	for (i = 0; i < argc - r_argc - 1; i++)
	    cmd_argv[i] = (const char *) argv[i + r_argc + 1];
	cmd_argv[i] = NULL;
    }

/* clear all resources */
    for (i = 0; i < TOTAL_RS; i++)
	rs[i] = NULL;

    rs[Rs_name] = my_basename(argv[0]);
    if (cmd_argv != NULL && cmd_argv[0] != NULL)
	rs[Rs_iconName] = rs[Rs_title] = my_basename(cmd_argv[0]);
/*
 * Open display, get options/resources and create the window
 */
    if ((rs[Rs_display_name] = getenv("DISPLAY")) == NULL)
	rs[Rs_display_name] = ":0";
#ifdef LOCAL_X_IS_UNIX
    if (strncmp(rs[Rs_display_name], ":0", 2) == 0)
	rs[Rs_display_name] = "unix:0";
#endif

    get_options(r_argc, r_argv);

    FREE(r_argv);

    if ((Xdisplay = XOpenDisplay(rs[Rs_display_name])) == NULL) {
	print_error("can't open display %s", rs[Rs_display_name]);
	exit(EXIT_FAILURE);
    }
#ifdef INEXPENSIVE_LOCAL_X_CALLS
    /* it's hard to determine further if we're on a local display or not */
    display_is_local = rs[Rs_display_name][0] == ':' ? 1 : 0;
#endif

    extract_resources(Xdisplay, rs[Rs_name]);

#if ! defined(XTERM_SCROLLBAR) && ! defined(NEXT_SCROLLBAR)
    if (!(Options & Opt_scrollBar_floating))
	sb_shadow = SHADOW;
#endif
    
/*
 * set any defaults not already set
 */
    if (!rs[Rs_title])
	rs[Rs_title] = rs[Rs_name];
    if (!rs[Rs_iconName])
	rs[Rs_iconName] = rs[Rs_title];
    if (!rs[Rs_saveLines] || (TermWin.saveLines = atoi(rs[Rs_saveLines])) < 0)
	TermWin.saveLines = SAVELINES;

/* no point having a scrollbar without having any scrollback! */
    if (!TermWin.saveLines)
	Options &= ~Opt_scrollBar;

#ifdef PRINTPIPE
    if (!rs[Rs_print_pipe])
	rs[Rs_print_pipe] = PRINTPIPE;
#endif
    if (!rs[Rs_cutchars])
	rs[Rs_cutchars] = CUTCHARS;
#ifndef NO_BACKSPACE_KEY
    if (!rs[Rs_backspace_key])
# ifdef DEFAULT_BACKSPACE
	key_backspace = DEFAULT_BACKSPACE;
# else
	key_backspace = "DEC";	/* can toggle between \033 or \177 */
# endif
    else {
	val = strdup(rs[Rs_backspace_key]);
	(void) Str_escaped(val);
	key_backspace = val;
    }
#endif
#ifndef NO_DELETE_KEY
    if (!rs[Rs_delete_key])
# ifdef DEFAULT_DELETE
	key_delete = DEFAULT_DELETE;
# else
	key_delete = "\033[3~";
# endif
    else {
	val = strdup(rs[Rs_delete_key]);
	(void) Str_escaped(val);
	key_delete = val;
    }
#endif

    if (rs[Rs_selectstyle]) {
	if (strncasecmp(rs[Rs_selectstyle], "oldword", 7) == 0)
	    selection_style = OLD_WORD_SELECT;
#ifndef NO_OLD_SELECTION
	else if (strncasecmp(rs[Rs_selectstyle], "old", 3) == 0)
	    selection_style = OLD_SELECT;
#endif
    }

#ifndef NO_BOLDFONT
    if (rs[Rs_font] == NULL && rs[Rs_boldFont] != NULL) {
	rs[Rs_font] = rs[Rs_boldFont];
	rs[Rs_boldFont] = NULL;
    }
#endif
    for (i = 0; i < NFONTS; i++) {
	if (!rs[Rs_font + i])
	    rs[Rs_font + i] = def_fontName[i];
#ifdef MULTICHAR_SET
	if (!rs[Rs_mfont + i])
	    rs[Rs_mfont + i] = def_mfontName[i];
#endif
    }
    TermWin.fontset = NULL;

#ifdef XTERM_REVERSE_VIDEO
/* this is how xterm implements reverseVideo */
    if (Options & Opt_reverseVideo) {
	if (!rs[Rs_color + Color_fg])
	    rs[Rs_color + Color_fg] = def_colorName[Color_bg];
	if (!rs[Rs_color + Color_bg])
	    rs[Rs_color + Color_bg] = def_colorName[Color_fg];
    }
#endif

    for (i = 0; i < NRS_COLORS; i++)
	if (!rs[Rs_color + i])
	    rs[Rs_color + i] = def_colorName[i];

#ifndef XTERM_REVERSE_VIDEO
/* this is how we implement reverseVideo */
    if (Options & Opt_reverseVideo)
	SWAP_IT(rs[Rs_color + Color_fg], rs[Rs_color + Color_bg], tmp);
#endif

/* convenient aliases for setting fg/bg to colors */
    color_aliases(Color_fg);
    color_aliases(Color_bg);
#ifndef NO_CURSORCOLOR
    color_aliases(Color_cursor);
    color_aliases(Color_cursor2);
#endif				/* NO_CURSORCOLOR */
    color_aliases(Color_pointer);
    color_aliases(Color_border);
#ifndef NO_BOLDUNDERLINE
    color_aliases(Color_BD);
    color_aliases(Color_UL);
#endif				/* NO_BOLDUNDERLINE */

    return cmd_argv;
}

/* ------------------------------------------------------------------------- */
/* INTPROTO */
void
init_env(void)
{
    int             i;
    unsigned int    u;
    char           *val;
/* these don't need to be static but do so to placate some mem checkers */
    static char    *env_windowid, *env_display, *env_term;

#ifdef DISPLAY_IS_IP
/* Fixup display_name for export over pty to any interested terminal
 * clients via "ESC[7n" (e.g. shells).  Note we use the pure IP number
 * (for the first non-loopback interface) that we get from
 * network_display().  This is more "name-resolution-portable", if you
 * will, and probably allows for faster x-client startup if your name
 * server is beyond a slow link or overloaded at client startup.  Of
 * course that only helps the shell's child processes, not us.
 *
 * Giving out the display_name also affords a potential security hole
 */
    rs[Rs_display_name] = (const char *) val = network_display(rs[Rs_display_name]);
    if (val == NULL)
#endif				/* DISPLAY_IS_IP */
	val = XDisplayString(Xdisplay);
    if (rs[Rs_display_name] == NULL)
	rs[Rs_display_name] = val;	/* use broken `:0' value */

    i = strlen(val);
    env_display = MALLOC((i + 9) * sizeof(char));
    sprintf(env_display, "DISPLAY=%s", val);

    /* avoiding the math library:
     * i = (int)(ceil(log10((unsigned int)TermWin.parent[0]))) */
    for (i = 0, u = (unsigned int)TermWin.parent[0]; u; u /= 10, i++);
    MAX_IT(i, 1);
    env_windowid = MALLOC((i + 10) * sizeof(char));
    sprintf(env_windowid, "WINDOWID=%u", (unsigned int)TermWin.parent[0]);

/* add entries to the environment:
 * @ DISPLAY:   in case we started with -display
 * @ WINDOWID:  X window id number of the window
 * @ COLORTERM: terminal sub-name and also indicates its color
 * @ TERM:      terminal name
 * @ TERMINFO:	path to terminfo directory
 */
    putenv(env_display);
    putenv(env_windowid);
#ifdef RXVT_TERMINFO
    putenv("TERMINFO=" RXVT_TERMINFO);
#endif
    if (Xdepth <= 2)
	putenv("COLORTERM=" COLORTERMENV "-mono");
    else
	putenv("COLORTERM=" COLORTERMENVFULL);
    if (rs[Rs_term_name] != NULL) {
	env_term = MALLOC((strlen(rs[Rs_term_name]) + 6) * sizeof(char));
	sprintf(env_term, "TERM=%s", rs[Rs_term_name]);
	putenv(env_term);
    } else {
	putenv("TERM=" TERMENV);
    }
}

/* ------------------------------------------------------------------------- */
/* main() */
/* INTPROTO */
int
main(int argc, const char * const *argv)
{
    const char    **cmd_argv;

/*
 * Save and then give up any super-user privileges
 * If we need privileges in any area then we must specifically request it.
 * We should only need to be root in these cases:
 *  1.  write utmp entries on some systems
 *  2.  chown tty on some systems
 */
    privileges(SAVE);
    privileges(IGNORE);

    init_vars();
    cmd_argv = init_resources(argc, argv);

#if (MENUBAR_MAX)
    menubar_read(rs[Rs_menu]);
#endif
    scrollbar_mapping(Options & Opt_scrollBar);

    Create_Windows(argc, argv);

    init_xlocale();

    scr_reset();		/* initialize screen */
    Gr_reset();			/* reset graphics */

#ifdef DEBUG_X
    XSynchronize(Xdisplay, True);
    XSetErrorHandler((XErrorHandler) abort);
#else
    XSetErrorHandler((XErrorHandler) xerror_handler);
#endif

    if (scrollbar_visible())
	XMapWindow(Xdisplay, scrollBar.win);
#if (MENUBAR_MAX)
    if (menubar_visible())
	XMapWindow(Xdisplay, menuBar.win);
#endif
#ifdef TRANSPARENT
    if (Options & Opt_transparent) {
	XSetWindowBackgroundPixmap(Xdisplay, TermWin.parent[0], ParentRelative);
	XSetWindowBackgroundPixmap(Xdisplay, TermWin.vt, ParentRelative);
	XSelectInput(Xdisplay, Xroot, PropertyChangeMask);
    }
#endif
    XMapWindow(Xdisplay, TermWin.vt);
    XMapWindow(Xdisplay, TermWin.parent[0]);

    init_env();
    init_command(cmd_argv);

    main_loop();		/* main processing loop */
    return EXIT_SUCCESS;
}
/*----------------------- end-of-file (C source) -----------------------*/
