/* Copyright (c) 2008, 2009
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
 *      Micah Cowan (micah@cowan.name)
 *      Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
 * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
 * Copyright (c) 1987 Oliver Laumann
 *
 * 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 3, 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 (see the file COPYING); if not, see
 * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 *
 ****************************************************************
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#ifndef sun
# include <sys/ioctl.h>
#endif

#include "config.h"
#include "screen.h"
#include "extern.h"
#include "braille.h"
#include "canvas.h"

/* CSI parsing status */
enum
  {
    CSI_PB=0, CSI_PX=1, CSI_PY=2, CSI_DONE=3,
    CSI_ESC_SEEN, CSI_BEGIN, CSI_INACTIVE, CSI_INVALID
  };

static int  CountChars __P((int));
static int  DoAddChar __P((int));
static int  BlankResize __P((int, int));
static int  CallRewrite __P((int, int, int, int));
static void disp_readev_fn __P((struct event *, char *));
static void disp_processinput __P((struct display *, unsigned char *, int));
static void disp_writeev_fn __P((struct event *, char *));
#ifdef linux
static void disp_writeev_eagain __P((struct event *, char *));
#endif
static void disp_status_fn __P((struct event *, char *));
static void disp_hstatus_fn __P((struct event *, char *));
static void disp_blocked_fn __P((struct event *, char *));
#ifdef MAPKEYS
static void disp_map_fn __P((struct event *, char *));
#endif
static void disp_idle_fn __P((struct event *, char *));
#ifdef BLANKER_PRG
static void disp_blanker_fn __P((struct event *, char *));
#endif
static void WriteLP __P((int, int));
static void INSERTCHAR __P((int));
static void RAW_PUTCHAR __P((int));
#ifdef COLOR
static void SetBackColor __P((int));
#endif
static void RemoveStatusMinWait __P((void));


extern struct layer *flayer;
extern struct win *windows, *fore;
extern struct LayFuncs WinLf;

extern int  use_hardstatus;
extern int  MsgWait, MsgMinWait;
extern const int  Z0width, Z1width;
extern unsigned char *blank, *null;
extern struct mline mline_blank, mline_null, mline_old;
extern struct mchar mchar_null, mchar_blank, mchar_so;
extern struct NewWindow nwin_default;
extern struct action idleaction;

/* XXX shouldn't be here */
extern char *hstatusstring;
extern char *captionstring;

extern int pastefont;
extern int idletimo;

#ifdef BLANKER_PRG
extern int pty_preopen;
#if defined(TIOCSWINSZ) || defined(TIOCGWINSZ)
extern struct winsize glwz;
#endif
extern char **NewEnv;
extern int real_uid, real_gid;
extern int ServerSocket, eff_uid, eff_gid;
#endif

/*
 * tputs needs this to calculate the padding
 */
#ifndef NEED_OSPEED
extern
#endif /* NEED_OSPEED */
short ospeed;


struct display *display, *displays;
#ifdef COLOR
int  attr2color[8][4];
int  nattr2color;
#endif

#ifndef MULTI
struct display TheDisplay;
#endif

/*
 *  The default values
 */
int defobuflimit = OBUF_MAX;
int defnonblock = -1;
int defmousetrack = 0;
#ifdef AUTO_NUKE
int defautonuke = 0;
#endif
int captionalways;
int hardstatusemu = HSTATUS_IGNORE;

int focusminwidth, focusminheight;

/*
 *  Default layer management
 */

void
DefProcess(bufp, lenp)
char **bufp;
int *lenp;
{
  *bufp += *lenp;
  *lenp = 0;
}

void
DefRedisplayLine(y, xs, xe, isblank)
int y, xs, xe, isblank;
{
  if (isblank == 0 && y >= 0)
    DefClearLine(y, xs, xe, 0);
}

void
DefClearLine(y, xs, xe, bce)
int y, xs, xe, bce;
{
  LClearLine(flayer, y, xs, xe, bce, (struct mline *)0);
}

/*ARGSUSED*/
int
DefRewrite(y, xs, xe, rend, doit)
int y, xs, xe, doit;
struct mchar *rend;
{
  return EXPENSIVE;
}

/*ARGSUSED*/
int
DefResize(wi, he)
int wi, he;
{
  return -1;
}

void
DefRestore()
{
  LAY_DISPLAYS(flayer, InsertMode(0));
  /* ChangeScrollRegion(0, D_height - 1); */
  LKeypadMode(flayer, 0);
  LCursorkeysMode(flayer, 0);
  LCursorVisibility(flayer, 0);
  LMouseMode(flayer, 0);
  LSetRendition(flayer, &mchar_null);
  LSetFlow(flayer, nwin_default.flowflag & FLOW_NOW);
}

/*
 *  Blank layer management
 */

struct LayFuncs BlankLf =
{
  DefProcess,
  0,
  DefRedisplayLine,
  DefClearLine,
  DefRewrite,
  BlankResize,
  DefRestore,
  0
};

/*ARGSUSED*/
static int
BlankResize(wi, he)
int wi, he;
{
  flayer->l_width = wi;
  flayer->l_height = he;
  return 0;
}


/*
 *  Generate new display, start with a blank layer.
 *  The termcap arrays are not initialised here.
 *  The new display is placed in the displays list.
 */

struct display *
MakeDisplay(uname, utty, term, fd, pid, Mode)
char *uname, *utty, *term;
int fd, pid;
struct mode *Mode;
{
  struct acluser **u;
  struct baud_values *b;

  if (!*(u = FindUserPtr(uname)) && UserAdd(uname, (char *)0, u))
    return 0;	/* could not find or add user */

#ifdef MULTI
  if ((display = (struct display *)calloc(1, sizeof(*display))) == 0)
    return 0;
#else
  if (displays)
    return 0;
  bzero((char *)&TheDisplay, sizeof(TheDisplay));
  display = &TheDisplay;
#endif
  display->d_next = displays;
  displays = display;
  D_flow = 1;
  D_nonblock = defnonblock;
  D_userfd = fd;
  D_readev.fd = D_writeev.fd = fd;
  D_readev.type  = EV_READ;
  D_writeev.type = EV_WRITE;
  D_readev.data = D_writeev.data = (char *)display;
  D_readev.handler  = disp_readev_fn;
  D_writeev.handler = disp_writeev_fn;
  evenq(&D_readev);
  D_writeev.condpos = &D_obuflen;
  D_writeev.condneg = &D_obuffree;
  evenq(&D_writeev);
  D_statusev.type = EV_TIMEOUT;
  D_statusev.data = (char *)display;
  D_statusev.handler = disp_status_fn;
  D_hstatusev.type = EV_TIMEOUT;
  D_hstatusev.data = (char *)display;
  D_hstatusev.handler = disp_hstatus_fn;
  D_blockedev.type = EV_TIMEOUT;
  D_blockedev.data = (char *)display;
  D_blockedev.handler = disp_blocked_fn;
  D_blockedev.condpos = &D_obuffree;
  D_blockedev.condneg = &D_obuflenmax;
  D_hstatusev.handler = disp_hstatus_fn;
#ifdef MAPKEYS
  D_mapev.type = EV_TIMEOUT;
  D_mapev.data = (char *)display;
  D_mapev.handler = disp_map_fn;
#endif
  D_idleev.type = EV_TIMEOUT;
  D_idleev.data = (char *)display;
  D_idleev.handler = disp_idle_fn;
#ifdef BLANKER_PRG
  D_blankerev.type = EV_READ;
  D_blankerev.data = (char *)display;
  D_blankerev.handler = disp_blanker_fn;
  D_blankerev.fd = -1;
#endif
  D_OldMode = *Mode;
  D_status_obuffree = -1;
  Resize_obuf();  /* Allocate memory for buffer */
  D_obufmax = defobuflimit;
  D_obuflenmax = D_obuflen - D_obufmax;
#ifdef AUTO_NUKE
  D_auto_nuke = defautonuke;
#endif
  D_obufp = D_obuf;
  D_printfd = -1;
  D_userpid = pid;

#ifdef POSIX
  if ((b = lookup_baud((int)cfgetospeed(&D_OldMode.tio))))
    D_dospeed = b->idx;
#else
# ifdef TERMIO
  if ((b = lookup_baud(D_OldMode.tio.c_cflag & CBAUD)))
    D_dospeed = b->idx;
# else
  D_dospeed = (short)D_OldMode.m_ttyb.sg_ospeed;
# endif
#endif
  debug1("New displays ospeed = %d\n", D_dospeed);

  strncpy(D_usertty, utty, sizeof(D_usertty) - 1);
  D_usertty[sizeof(D_usertty) - 1] = 0;
  strncpy(D_termname, term, MAXTERMLEN);
  D_termname[MAXTERMLEN] = 0;
  D_user = *u;
  D_processinput = ProcessInput;
  D_mousetrack = defmousetrack;
  return display;
}


void
FreeDisplay()
{
  struct win *p;
#ifdef MULTI
  struct display *d, **dp;
#endif

#ifdef FONT
  FreeTransTable();
#endif
#ifdef BLANKER_PRG
  KillBlanker();
#endif
  if (D_userfd >= 0)
    {
      Flush(3);
      if (!display)
	return;
      SetTTY(D_userfd, &D_OldMode);
      fcntl(D_userfd, F_SETFL, 0);
    }
  freetty();
  if (D_tentry)
    free(D_tentry);
  D_tentry = 0;
  if (D_processinputdata)
    free(D_processinputdata);
  D_processinputdata = 0;
  D_tcinited = 0;
  evdeq(&D_hstatusev);
  evdeq(&D_statusev);
  evdeq(&D_readev);
  evdeq(&D_writeev);
  evdeq(&D_blockedev);
#ifdef MAPKEYS
  evdeq(&D_mapev);
  if (D_kmaps)
    {
      free(D_kmaps);
      D_kmaps = 0;
      D_aseqs = 0;
      D_nseqs = 0;
      D_seqp = 0;
      D_seql = 0;
      D_seqh = 0;
    }
#endif
  evdeq(&D_idleev);
#ifdef BLANKER_PRG
  evdeq(&D_blankerev);
#endif
#ifdef HAVE_BRAILLE
  if (bd.bd_dpy == display)
    {
      bd.bd_start_braille = 0;
      StartBraille();
    }
#endif

#ifdef MULTI
  for (dp = &displays; (d = *dp) ; dp = &d->d_next)
    if (d == display)
      break;
  ASSERT(d);
  if (D_status_lastmsg)
    free(D_status_lastmsg);
  if (D_obuf)
    free(D_obuf);
  *dp = display->d_next;
#else /* MULTI */
  ASSERT(display == displays);
  ASSERT(display == &TheDisplay);
  displays = 0;
#endif /* MULTI */

  while (D_canvas.c_slperp)
    FreeCanvas(D_canvas.c_slperp);
  D_cvlist = 0;

  for (p = windows; p; p = p->w_next)
    {
      if (p->w_pdisplay == display)
	p->w_pdisplay = 0;
      if (p->w_lastdisp == display)
	p->w_lastdisp = 0;
      if (p->w_readev.condneg == &D_status || p->w_readev.condneg == &D_obuflenmax)
	p->w_readev.condpos = p->w_readev.condneg = 0;
    }
#ifdef ZMODEM
  for (p = windows; p; p = p->w_next)
    if (p->w_zdisplay == display)
      zmodem_abort(p, 0);
#endif
  if (D_mousetrack)
    {
      D_mousetrack = 0;
      MouseMode(0);
    }
#ifdef MULTI
  free((char *)display);
#endif
  display = 0;
}

/*
 * if the adaptflag is on, we keep the size of this display, else
 * we may try to restore our old window sizes.
 */
void
InitTerm(adapt)
int adapt;
{
  ASSERT(display);
  ASSERT(D_tcinited);
  D_top = D_bot = -1;
  AddCStr(D_IS);
  AddCStr(D_TI);
  /* Check for toggle */
  if (D_IM && strcmp(D_IM, D_EI))
    AddCStr(D_EI);
  D_insert = 0;
#ifdef MAPKEYS
  AddCStr(D_KS);
  AddCStr(D_CCS);
#else
  /* Check for toggle */
  if (D_KS && strcmp(D_KS, D_KE))
    AddCStr(D_KE);
  if (D_CCS && strcmp(D_CCS, D_CCE))
    AddCStr(D_CCE);
#endif
  D_keypad = 0;
  D_cursorkeys = 0;
  AddCStr(D_ME);
  AddCStr(D_EA);
  AddCStr(D_CE0);
  D_rend = mchar_null;
  D_atyp = 0;
  if (adapt == 0)
    ResizeDisplay(D_defwidth, D_defheight);
  ChangeScrollRegion(0, D_height - 1);
  D_x = D_y = 0;
  Flush(3);
  ClearAll();
  debug1("we %swant to adapt all our windows to the display\n", 
	 (adapt) ? "" : "don't ");
  /* In case the size was changed by a init sequence */
  CheckScreenSize((adapt) ? 2 : 0);
}

void
FinitTerm()
{
  ASSERT(display);
#ifdef BLANKER_PRG
  KillBlanker();
#endif
  if (D_tcinited)
    {
      ResizeDisplay(D_defwidth, D_defheight);
      InsertMode(0);
      ChangeScrollRegion(0, D_height - 1);
      KeypadMode(0);
      CursorkeysMode(0);
      CursorVisibility(0);
      if (D_mousetrack)
	D_mousetrack = 0;
      MouseMode(0);
      ExtMouseMode(0);
      SetRendition(&mchar_null);
      SetFlow(FLOW_NOW);
#ifdef MAPKEYS
      AddCStr(D_KE);
      AddCStr(D_CCE);
#endif
      if (D_hstatus)
	ShowHStatus((char *)0);
#ifdef RXVT_OSC
      ClearAllXtermOSC();
#endif
      D_x = D_y = -1;
      GotoPos(0, D_height - 1);
      AddChar('\r');
      AddChar('\n');
      AddCStr(D_TE);
    }
  Flush(3);
}


static void
INSERTCHAR(c)
int c;
{
  ASSERT(display);
  if (!D_insert && D_x < D_width - 1)
    {
      if (D_IC || D_CIC)
	{
	  if (D_IC)
	    AddCStr(D_IC);
	  else
	    AddCStr2(D_CIC, 1);
	  RAW_PUTCHAR(c);
	  return;
	}
      InsertMode(1);
      if (!D_insert)
	{
          RefreshLine(D_y, D_x, D_width-1, 0);
	  return;
	}
    }
  RAW_PUTCHAR(c);
}

void
PUTCHAR(c)
int c;
{
  ASSERT(display);
  if (D_insert && D_x < D_width - 1)
    InsertMode(0);
  RAW_PUTCHAR(c);
}

void
PUTCHARLP(c)
int c;
{
  if (D_x < D_width - 1)
    {
      if (D_insert)
	InsertMode(0);
      RAW_PUTCHAR(c);
      return;
    }
  if (D_CLP || D_y != D_bot)
    {
      int y = D_y;
      RAW_PUTCHAR(c);
      if (D_AM && !D_CLP)
	GotoPos(D_width - 1, y);
      return;
    }
  debug("PUTCHARLP: lp_missing!\n");
  D_lp_missing = 1;
  D_rend.image = c;
  D_lpchar = D_rend;
#ifdef DW_CHARS
  /* XXX -> PutChar ? */
  if (D_mbcs)
    {
      D_lpchar.mbcs = c;
      D_lpchar.image = D_mbcs;
      D_mbcs = 0;
      D_x--;
    }
#endif
}

/*
 * RAW_PUTCHAR() is for all text that will be displayed.
 * NOTE: charset Nr. 0 has a conversion table, but c1, c2, ... don't.
 */

STATIC void
RAW_PUTCHAR(c)
int c;
{
  ASSERT(display);

#ifdef FONT
# ifdef UTF8
  if (D_encoding == UTF8)
    {
      c = (c & 255) | (unsigned char)D_rend.font << 8 | (unsigned char)D_rend.fontx << 16;
#  ifdef DW_CHARS
      if (D_mbcs)
	{
	  c = D_mbcs;
	  if (D_x == D_width)
	    D_x += D_AM ? 1 : -1;
	  D_mbcs = 0;
	}
      else if (utf8_isdouble(c))
	{
	  D_mbcs = c;
	  D_x++;
	  return;
	}
#  endif
      if (c < 32)
	{
	  AddCStr2(D_CS0, '0');
	  AddChar(c + 0x5f);
	  AddCStr(D_CE0);
	  goto addedutf8;
	}
      if (c < 0x80)
	{
	  if (D_xtable && D_xtable[(int)(unsigned char)D_rend.font] && D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c])
	    AddStr(D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c]);
	  else
	    AddChar(c);
	}
      else
	AddUtf8(c);
      goto addedutf8;
    }
# endif
# ifdef DW_CHARS
  if (is_dw_font(D_rend.font))
    {
      int t = c;
      if (D_mbcs == 0)
	{
	  D_mbcs = c;
	  D_x++;
	  return;
	}
      D_x--;
      if (D_x == D_width - 1)
	D_x += D_AM ? 1 : -1;
      c = D_mbcs;
      D_mbcs = t;
    }
# endif
# if defined(ENCODINGS) && defined(DW_CHARS)
  if (D_encoding)
    c = PrepareEncodedChar(c);
# endif
# ifdef DW_CHARS
  kanjiloop:
# endif
  if (D_xtable && D_xtable[(int)(unsigned char)D_rend.font] && D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c])
    AddStr(D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c]);
  else
    AddChar(D_rend.font != '0' ? c : D_c0_tab[(int)(unsigned char)c]);
#else /* FONT */
    AddChar(c);
#endif /* FONT */

#ifdef UTF8
addedutf8:
#endif
  if (++D_x >= D_width)
    {
      if (D_AM == 0)
        D_x = D_width - 1;
      else if (!D_CLP || D_x > D_width)
	{
	  D_x -= D_width;
	  if (D_y < D_height-1 && D_y != D_bot)
	    D_y++;
	}
    }
#ifdef DW_CHARS
  if (D_mbcs)
    {
      c = D_mbcs;
      D_mbcs = 0;
      goto kanjiloop;
    }
#endif
}

static int
DoAddChar(c)
int c;
{
  /* this is for ESC-sequences only (AddChar is a macro) */
  AddChar(c);
  return c;
}

void
AddCStr(s)
char *s;
{
  if (display && s && *s)
    {
      ospeed = D_dospeed;
      tputs(s, 1, DoAddChar);
    }
}

void
AddCStr2(s, c)
char *s;
int c;
{
  if (display && s && *s)
    {
      ospeed = D_dospeed;
      tputs(tgoto(s, 0, c), 1, DoAddChar);
    }
}


/* Insert mode is a toggle on some terminals, so we need this hack:
 */
void
InsertMode(on)
int on;
{
  if (display && on != D_insert && D_IM)
    {
      D_insert = on;
      if (on)
	AddCStr(D_IM);
      else
	AddCStr(D_EI);
    }
}

/* ...and maybe keypad application mode is a toggle, too:
 */
void
KeypadMode(on)
int on;
{
#ifdef MAPKEYS
  if (display)
    D_keypad = on;
#else
  if (display && D_keypad != on && D_KS)
    {
      D_keypad = on;
      if (on)
	AddCStr(D_KS);
      else
	AddCStr(D_KE);
    }
#endif
}

void
CursorkeysMode(on)
int on;
{
#ifdef MAPKEYS
  if (display)
    D_cursorkeys = on;
#else
  if (display && D_cursorkeys != on && D_CCS)
    {
      D_cursorkeys = on;
      if (on)
	AddCStr(D_CCS);
      else
	AddCStr(D_CCE);
    }
#endif
}

void
ReverseVideo(on)
int on;
{
  if (display && D_revvid != on && D_CVR)
    {
      D_revvid = on;
      if (D_revvid)
	AddCStr(D_CVR);
      else
	AddCStr(D_CVN);
    }
}

void
CursorVisibility(v)
int v;
{
  if (display && D_curvis != v)
    {
      if (D_curvis)
	AddCStr(D_VE);		/* do this always, just to be safe */
      D_curvis = 0;
      if (v == -1 && D_VI)
	AddCStr(D_VI);
      else if (v == 1 && D_VS)
	AddCStr(D_VS);
      else
	return;
      D_curvis = v;
    }
}

void
MouseMode(mode)
int mode;
{
  if (!display)
    return;

  if (mode < D_mousetrack)
    mode = D_mousetrack;

  if (D_mouse != mode)
    {
      char mousebuf[20];
      if (!D_CXT)
	return;
      if (D_mouse)
        {
          sprintf(mousebuf, "\033[?%dl", D_mouse);
          AddStr(mousebuf);
	}
      if (mode)
        {
          sprintf(mousebuf, "\033[?%dh", mode);
          AddStr(mousebuf);
	}
      D_mouse = mode;
      D_mouse_parse.state = CSI_INACTIVE;
    }
}

void
ExtMouseMode(mode)
     int mode;
{
  if (display && D_extmouse != mode)
    {
      char mousebuf[20];
      if (!D_CXT)
        return;
      if (D_extmouse)
        {
          sprintf(mousebuf, "\033[?%dl", D_extmouse);
          AddStr(mousebuf);
        }
      if (mode)
        {
          sprintf(mousebuf, "\033[?%dh", mode);
          AddStr(mousebuf);
        }
      D_extmouse = mode;
      D_mouse_parse.state = CSI_INACTIVE;
    }
}

static int StrCost;

/* ARGSUSED */
static int
CountChars(c)
int c;
{
  StrCost++;
  return c;
}

int
CalcCost(s)
register char *s;
{
  ASSERT(display);
  if (s)
    {
      StrCost = 0;
      ospeed = D_dospeed;
      tputs(s, 1, CountChars);
      return StrCost;
    }
  else
    return EXPENSIVE;
}

static int
CallRewrite(y, xs, xe, doit)
int y, xs, xe, doit;
{
  struct canvas *cv, *cvlist, *cvlnext;
  struct viewport *vp;
  struct layer *oldflayer;
  int cost;

  debug3("CallRewrite %d %d %d\n", y, xs, xe);
  ASSERT(display);
  ASSERT(xe >= xs);

  vp = 0;
  for (cv = D_cvlist; cv; cv = cv->c_next)
    {
      if (y < cv->c_ys || y > cv->c_ye || xe < cv->c_xs || xs > cv->c_xe)
	continue;
      for (vp = cv->c_vplist; vp; vp = vp->v_next)
	if (y >= vp->v_ys && y <= vp->v_ye && xe >= vp->v_xs && xs <= vp->v_xe)
	  break;
      if (vp)
	break;
    }
  if (doit)
    {
      oldflayer = flayer;
      flayer = cv->c_layer;
      cvlist = flayer->l_cvlist;
      cvlnext = cv->c_lnext;
      flayer->l_cvlist = cv;
      cv->c_lnext = 0;
      LayRewrite(y - vp->v_yoff, xs - vp->v_xoff, xe - vp->v_xoff, &D_rend, 1);
      flayer->l_cvlist = cvlist;
      cv->c_lnext = cvlnext;
      flayer = oldflayer;
      return 0;
    }
  if (cv == 0 || cv->c_layer == 0)
    return EXPENSIVE;	/* not found or nothing on it */
  if (xs < vp->v_xs || xe > vp->v_xe)
    return EXPENSIVE;	/* crosses viewport boundaries */
  if (y - vp->v_yoff < 0 || y - vp->v_yoff >= cv->c_layer->l_height)
    return EXPENSIVE;	/* line not on layer */
  if (xs - vp->v_xoff < 0 || xe - vp->v_xoff >= cv->c_layer->l_width)
    return EXPENSIVE;	/* line not on layer */
#ifdef UTF8
  if (D_encoding == UTF8)
    D_rend.font = 0;
#endif
  oldflayer = flayer;
  flayer = cv->c_layer;
  debug3("Calling Rewrite %d %d %d\n", y - vp->v_yoff, xs - vp->v_xoff, xe - vp->v_xoff);
  cost = LayRewrite(y - vp->v_yoff, xs - vp->v_xoff, xe - vp->v_xoff, &D_rend, 0);
  flayer = oldflayer;
  if (D_insert)
    cost += D_EIcost + D_IMcost;
  return cost;
}


void
GotoPos(x2, y2)
int x2, y2;
{
  register int dy, dx, x1, y1;
  register int costx, costy;
  register int m;
  register char *s;
  int CMcost;
  enum move_t xm = M_NONE, ym = M_NONE;

  if (!display)
    return;

  x1 = D_x;
  y1 = D_y;

  if (x1 == D_width)
    {
      if (D_CLP && D_AM)
        x1 = -1;	/* don't know how the terminal treats this */
      else
        x1--;
    }
  if (x2 == D_width)
    x2--;
  dx = x2 - x1;
  dy = y2 - y1;
  if (dy == 0 && dx == 0)
    return;
  debug2("GotoPos (%d,%d)", x1, y1);
  debug2(" -> (%d,%d)\n", x2, y2);
  if (!D_MS)		/* Safe to move ? */
    SetRendition(&mchar_null);
  if (y1 < 0			/* don't know the y position */
      || (y2 > D_bot && y1 <= D_bot)	/* have to cross border */
      || (y2 < D_top && y1 >= D_top))	/* of scrollregion ?    */
    {
    DoCM:
      if (D_HO && !x2 && !y2)
        AddCStr(D_HO);
      else
        AddCStr(tgoto(D_CM, x2, y2));
      D_x = x2;
      D_y = y2;
      return;
    }

  /* some scrollregion implementations don't allow movements
   * away from the region. sigh.
   */
  if ((y1 > D_bot && y2 > y1) || (y1 < D_top && y2 < y1))
    goto DoCM;

  /* Calculate CMcost */
  if (D_HO && !x2 && !y2)
    s = D_HO;
  else
    s = tgoto(D_CM, x2, y2);
  CMcost = CalcCost(s);

  /* Calculate the cost to move the cursor to the right x position */
  costx = EXPENSIVE;
  if (x1 >= 0)	/* relative x positioning only if we know where we are */
    {
      if (dx > 0)
	{
	  if (D_CRI && (dx > 1 || !D_ND))
	    {
	      costx = CalcCost(tgoto(D_CRI, 0, dx));
	      xm = M_CRI;
	    }
	  if ((m = D_NDcost * dx) < costx)
	    {
	      costx = m;
	      xm = M_RI;
	    }
	  /* Speedup: dx <= LayRewrite() */
	  if (dx < costx && (m = CallRewrite(y1, x1, x2 - 1, 0)) < costx)
	    {
	      costx = m;
	      xm = M_RW;
	    }
	}
      else if (dx < 0)
	{
	  if (D_CLE && (dx < -1 || !D_BC))
	    {
	      costx = CalcCost(tgoto(D_CLE, 0, -dx));
	      xm = M_CLE;
	    }
	  if ((m = -dx * D_LEcost) < costx)
	    {
	      costx = m;
	      xm = M_LE;
	    }
	}
      else
	costx = 0;
    }
  /* Speedup: LayRewrite() >= x2 */
  if (x2 + D_CRcost < costx && (m = (x2 ? CallRewrite(y1, 0, x2 - 1, 0) : 0) + D_CRcost) < costx)
    {
      costx = m;
      xm = M_CR;
    }

  /* Check if it is already cheaper to do CM */
  if (costx >= CMcost)
    goto DoCM;

  /* Calculate the cost to move the cursor to the right y position */
  costy = EXPENSIVE;
  if (dy > 0)
    {
      if (D_CDO && dy > 1)	/* DO & NL are always != 0 */
	{
	  costy = CalcCost(tgoto(D_CDO, 0, dy));
	  ym = M_CDO;
	}
      if ((m = dy * ((x2 == 0) ? D_NLcost : D_DOcost)) < costy)
	{
	  costy = m;
	  ym = M_DO;
	}
    }
  else if (dy < 0)
    {
      if (D_CUP && (dy < -1 || !D_UP))
	{
	  costy = CalcCost(tgoto(D_CUP, 0, -dy));
	  ym = M_CUP;
	}
      if ((m = -dy * D_UPcost) < costy)
	{
	  costy = m;
	  ym = M_UP;
	}
    }
  else
    costy = 0;

  /* Finally check if it is cheaper to do CM */
  if (costx + costy >= CMcost)
    goto DoCM;

  switch (xm)
    {
    case M_LE:
      while (dx++ < 0)
	AddCStr(D_BC);
      break;
    case M_CLE:
      AddCStr2(D_CLE, -dx);
      break;
    case M_RI:
      while (dx-- > 0)
	AddCStr(D_ND);
      break;
    case M_CRI:
      AddCStr2(D_CRI, dx);
      break;
    case M_CR:
      AddCStr(D_CR);
      D_x = 0;
      x1 = 0;
      /* FALLTHROUGH */
    case M_RW:
      if (x1 < x2)
	(void) CallRewrite(y1, x1, x2 - 1, 1);
      break;
    default:
      break;
    }

  switch (ym)
    {
    case M_UP:
      while (dy++ < 0)
	AddCStr(D_UP);
      break;
    case M_CUP:
      AddCStr2(D_CUP, -dy);
      break;
    case M_DO:
      s =  (x2 == 0) ? D_NL : D_DO;
      while (dy-- > 0)
	AddCStr(s);
      break;
    case M_CDO:
      AddCStr2(D_CDO, dy);
      break;
    default:
      break;
    }
  D_x = x2;
  D_y = y2;
}

void
ClearAll()
{
  ASSERT(display);
  ClearArea(0, 0, 0, D_width - 1, D_width - 1, D_height - 1, 0, 0);
}

void
ClearArea(x1, y1, xs, xe, x2, y2, bce, uselayfn)
int x1, y1, xs, xe, x2, y2, bce, uselayfn;
{
  int y, xxe;
  struct canvas *cv;
  struct viewport *vp;

  debug2("Clear %d,%d", x1, y1);
  debug2(" %d-%d", xs, xe);
  debug2(" %d,%d", x2, y2);
  debug2(" uselayfn=%d bce=%d\n", uselayfn, bce);
  ASSERT(display);
  if (x1 == D_width)
    x1--;
  if (x2 == D_width)
    x2--;
  if (xs == -1)
    xs = x1;
  if (xe == -1)
    xe = x2;
  if (D_UT)	/* Safe to erase ? */
    SetRendition(&mchar_null);
#ifdef COLOR
  if (D_BE)
    SetBackColor(bce);
#endif
  if (D_lp_missing && y1 <= D_bot && xe >= D_width - 1)
    {
      if (y2 > D_bot || (y2 == D_bot && x2 >= D_width - 1))
	D_lp_missing = 0;
    }
  if (x2 == D_width - 1 && (xs == 0 || y1 == y2) && xe == D_width - 1 && y2 == D_height - 1 && (!bce || D_BE))
    {
#ifdef AUTO_NUKE
      if (x1 == 0 && y1 == 0 && D_auto_nuke)
	NukePending();
#endif
      if (x1 == 0 && y1 == 0 && D_CL)
	{
	  AddCStr(D_CL);
	  D_y = D_x = 0;
	  return;
	}
      /* 
       * Workaround a hp700/22 terminal bug. Do not use CD where CE
       * is also appropriate.
       */
      if (D_CD && (y1 < y2 || !D_CE))
	{
	  GotoPos(x1, y1);
	  AddCStr(D_CD);
	  return;
	}
    }
  if (x1 == 0 && xs == 0 && (xe == D_width - 1 || y1 == y2) && y1 == 0 && D_CCD && (!bce || D_BE))
    {
      GotoPos(x1, y1);
      AddCStr(D_CCD);
      return;
    }
  xxe = xe;
  for (y = y1; y <= y2; y++, x1 = xs)
    {
      if (y == y2)
	xxe = x2;
      if (x1 == 0 && D_CB && (xxe != D_width - 1 || (D_x == xxe && D_y == y)) && (!bce || D_BE))
	{
	  GotoPos(xxe, y);
	  AddCStr(D_CB);
	  continue;
	}
      if (xxe == D_width - 1 && D_CE && (!bce || D_BE))
	{
	  GotoPos(x1, y);
	  AddCStr(D_CE);
	  continue;
	}
      if (uselayfn)
	{
	  vp = 0;
	  for (cv = D_cvlist; cv; cv = cv->c_next)
	    {
	      if (y < cv->c_ys || y > cv->c_ye || xxe < cv->c_xs || x1 > cv->c_xe)
		continue;
	      for (vp = cv->c_vplist; vp; vp = vp->v_next)
	        if (y >= vp->v_ys && y <= vp->v_ye && xxe >= vp->v_xs && x1 <= vp->v_xe)
		  break;
	      if (vp)
		break;
	    }
	  if (cv && cv->c_layer && x1 >= vp->v_xs && xxe <= vp->v_xe &&
              y - vp->v_yoff >= 0 && y - vp->v_yoff < cv->c_layer->l_height &&
              xxe - vp->v_xoff >= 0 && x1 - vp->v_xoff < cv->c_layer->l_width)
	    {
	      struct layer *oldflayer = flayer;
	      struct canvas *cvlist, *cvlnext;
	      flayer = cv->c_layer;
	      cvlist = flayer->l_cvlist;
	      cvlnext = cv->c_lnext;
	      flayer->l_cvlist = cv;
	      cv->c_lnext = 0;
              LayClearLine(y - vp->v_yoff, x1 - vp->v_xoff, xxe - vp->v_xoff, bce);
	      flayer->l_cvlist = cvlist;
	      cv->c_lnext = cvlnext;
	      flayer = oldflayer;
	      continue;
	    }
	}
      ClearLine((struct mline *)0, y, x1, xxe, bce);
    }
}


/*
 * if cur_only > 0, we only redisplay current line, as a full refresh is
 * too expensive over a low baud line.
 */
void
Redisplay(cur_only)
int cur_only;
{
  ASSERT(display);

  /* XXX do em all? */
  InsertMode(0);
  ChangeScrollRegion(0, D_height - 1);
  KeypadMode(0);
  CursorkeysMode(0);
  CursorVisibility(0);
  MouseMode(0);
  ExtMouseMode(0);
  SetRendition(&mchar_null);
  SetFlow(FLOW_NOW);

  ClearAll();
#ifdef RXVT_OSC
  RefreshXtermOSC();
#endif
  if (cur_only > 0 && D_fore)
    RefreshArea(0, D_fore->w_y, D_width - 1, D_fore->w_y, 1);
  else
    RefreshAll(1);
  RefreshHStatus();
  CV_CALL(D_forecv, LayRestore();LaySetCursor());
}

void
RedisplayDisplays(cur_only)
int cur_only;
{
  struct display *olddisplay = display;
  for (display = displays; display; display = display->d_next)
    Redisplay(cur_only);
  display = olddisplay;
}


/* XXX: use oml! */
void
ScrollH(y, xs, xe, n, bce, oml)
int y, xs, xe, n, bce;
struct mline *oml;
{
  int i;

  if (n == 0)
    return;
  if (xe != D_width - 1)
    {
      RefreshLine(y, xs, xe, 0);
      /* UpdateLine(oml, y, xs, xe); */
      return;
    }
  GotoPos(xs, y);
  if (D_UT)
    SetRendition(&mchar_null);
#ifdef COLOR
  if (D_BE)
    SetBackColor(bce);
#endif
  if (n > 0)
    {
      if (n >= xe - xs + 1)
	n = xe - xs + 1;
      if (D_CDC && !(n == 1 && D_DC))
	AddCStr2(D_CDC, n);
      else if (D_DC)
	{
	  for (i = n; i--; )
	    AddCStr(D_DC);
	}
      else
	{
	  RefreshLine(y, xs, xe, 0);
	  /* UpdateLine(oml, y, xs, xe); */
	  return;
	}
    }
  else
    {
      if (-n >= xe - xs + 1)
	n = -(xe - xs + 1);
      if (!D_insert)
	{
	  if (D_CIC && !(n == -1 && D_IC))
	    AddCStr2(D_CIC, -n);
	  else if (D_IC)
	    {
	      for (i = -n; i--; )
		AddCStr(D_IC);
	    }
	  else if (D_IM)
	    {
	      InsertMode(1);
	      SetRendition(&mchar_null);
#ifdef COLOR
	      SetBackColor(bce);
#endif
	      for (i = -n; i--; )
		INSERTCHAR(' ');
	      bce = 0;	/* all done */
	    }
	  else
	    {
	      /* UpdateLine(oml, y, xs, xe); */
	      RefreshLine(y, xs, xe, 0);
	      return;
	    }
	}
      else
	{
	  SetRendition(&mchar_null);
#ifdef COLOR
	  SetBackColor(bce);
#endif
	  for (i = -n; i--; )
	    INSERTCHAR(' ');
	  bce = 0;	/* all done */
	}
    }
  if (bce && !D_BE)
    {
      if (n > 0)
        ClearLine((struct mline *)0, y, xe - n + 1, xe, bce);
      else
        ClearLine((struct mline *)0, y, xs, xs - n - 1, bce);
    }
  if (D_lp_missing && y == D_bot)
    {
      if (n > 0)
        WriteLP(D_width - 1 - n, y);
      D_lp_missing = 0;
    }
}

void
ScrollV(xs, ys, xe, ye, n, bce)
int xs, ys, xe, ye, n, bce;
{
  int i;
  int up;
  int oldbot;
  int alok, dlok, aldlfaster;
  int missy = 0;

  ASSERT(display);
  if (n == 0)
    return;
  if (n >= ye - ys + 1 || -n >= ye - ys + 1)
    {
      ClearArea(xs, ys, xs, xe, xe, ye, bce, 0);
      return;
    }
  if (xs > D_vpxmin || xe < D_vpxmax)
    {
      RefreshArea(xs, ys, xe, ye, 0);
      return;
    }

  if (D_lp_missing)
    {
      if (D_bot > ye || D_bot < ys)
	missy = D_bot;
      else
	{
	  missy = D_bot - n;
          if (missy > ye || missy < ys)
	    D_lp_missing = 0;
	}
    }

  up = 1;
  if (n < 0)
    {
      up = 0;
      n = -n;
    }
  if (n >= ye - ys + 1)
    n = ye - ys + 1;

  oldbot = D_bot;
  if (ys < D_top || D_bot != ye)
    ChangeScrollRegion(ys, ye);
  alok = (D_AL || D_CAL || (ys >= D_top && ye == D_bot &&  up));
  dlok = (D_DL || D_CDL || (ys >= D_top && ye == D_bot && !up));
  if (D_top != ys && !(alok && dlok))
    ChangeScrollRegion(ys, ye);

  if (D_lp_missing && 
      (oldbot != D_bot ||
       (oldbot == D_bot && up && D_top == ys && D_bot == ye)))
    {
      WriteLP(D_width - 1, oldbot);
      if (oldbot == D_bot)		/* have scrolled */
	{
	  if (--n == 0)
	    {
/* XXX
	      ChangeScrollRegion(oldtop, oldbot);
*/
	      if (bce && !D_BE)
		ClearLine((struct mline *)0, ye, xs, xe, bce);
	      return;
	    }
	}
    }

  if (D_UT)
    SetRendition(&mchar_null);
#ifdef COLOR
  if (D_BE)
    SetBackColor(bce);
#endif

  aldlfaster = (n > 1 && ys >= D_top && ye == D_bot && ((up && D_CDL) || (!up && D_CAL)));

  if ((up || D_SR) && D_top == ys && D_bot == ye && !aldlfaster)
    {
      if (up)
	{
	  GotoPos(0, ye);
	  for(i = n; i-- > 0; )
	    AddCStr(D_NL);		/* was SF, I think NL is faster */
	}
      else
	{
	  GotoPos(0, ys);
	  for(i = n; i-- > 0; )
	    AddCStr(D_SR);
	}
    }
  else if (alok && dlok)
    {
      if (up || ye != D_bot)
	{
          GotoPos(0, up ? ys : ye+1-n);
          if (D_CDL && !(n == 1 && D_DL))
	    AddCStr2(D_CDL, n);
	  else
	    for(i = n; i--; )
	      AddCStr(D_DL);
	}
      if (!up || ye != D_bot)
	{
          GotoPos(0, up ? ye+1-n : ys);
          if (D_CAL && !(n == 1 && D_AL))
	    AddCStr2(D_CAL, n);
	  else
	    for(i = n; i--; )
	      AddCStr(D_AL);
	}
    }
  else
    {
      RefreshArea(xs, ys, xe, ye, 0);
      return;
    }
  if (bce && !D_BE)
    {
      if (up)
        ClearArea(xs, ye - n + 1, xs, xe, xe, ye, bce, 0);
      else
        ClearArea(xs, ys, xs, xe, xe, ys + n - 1, bce, 0);
    }
  if (D_lp_missing && missy != D_bot)
    WriteLP(D_width - 1, missy);
/* XXX
  ChangeScrollRegion(oldtop, oldbot);
  if (D_lp_missing && missy != D_bot)
    WriteLP(D_width - 1, missy);
*/
}

void
SetAttr(new)
register int new;
{
  register int i, j, old, typ;

  if (!display || (old = D_rend.attr) == new)
    return;
#ifdef COLORS16
  D_col16change = (old ^ new) & (A_BFG | A_BBG);
  new ^= D_col16change;
  if (old == new)
    return;
#endif
#if defined(TERMINFO) && defined(USE_SGR)
  if (D_SA)
    {
      char *tparm();
      SetFont(ASCII);
      ospeed = D_dospeed;
      tputs(tparm(D_SA, new & A_SO, new & A_US, new & A_RV, new & A_BL,
			new & A_DI, new & A_BD, 0         , 0         ,
			0), 1, DoAddChar);
      D_rend.attr = new;
      D_atyp = 0;
# ifdef COLOR
      if (D_hascolor)
	rend_setdefault(&D_rend);
# endif
      return;
    }
#endif
  D_rend.attr = new;
  typ = D_atyp;
  if ((new & old) != old)
    {
      if ((typ & ATYP_U))
        AddCStr(D_UE);
      if ((typ & ATYP_S))
        AddCStr(D_SE);
      if ((typ & ATYP_M))
	{
          AddCStr(D_ME);
#ifdef COLOR
	  /* ansi attrib handling: \E[m resets color, too */
	  if (D_hascolor)
	    rend_setdefault(&D_rend);
#endif
#ifdef FONT
	  if (!D_CG0)
	    {
	      /* D_ME may also reset the alternate charset */
	      D_rend.font = 0;
# ifdef ENCODINGS
	      D_realfont = 0;
# endif
	    }
#endif
	}
      old = 0;
      typ = 0;
    }
  old ^= new;
  for (i = 0, j = 1; old && i < NATTR; i++, j <<= 1)
    {
      if ((old & j) == 0)
	continue;
      old ^= j;
      if (D_attrtab[i])
	{
	  AddCStr(D_attrtab[i]);
	  typ |= D_attrtyp[i];
	}
    }
  D_atyp = typ;
}

#ifdef FONT
void
SetFont(new)
int new;
{
  int old = D_rend.font;
  if (!display || old == new)
    return;
  D_rend.font = new;
#ifdef ENCODINGS
  if (D_encoding && CanEncodeFont(D_encoding, new))
    return;
  if (new == D_realfont)
    return;
  D_realfont = new;
#endif
  if (D_xtable && D_xtable[(int)(unsigned char)new] &&
      D_xtable[(int)(unsigned char)new][256])
    {
      AddCStr(D_xtable[(int)(unsigned char)new][256]);
      return;
    }

  if (!D_CG0 && new != '0')
    {
      new = ASCII;
      if (old == new)
	return;
    }

  if (new == ASCII)
    AddCStr(D_CE0);
#ifdef DW_CHARS
  else if (new < ' ')
    {
      AddStr("\033$");
      if (new > 2)
        AddChar('(');
      AddChar(new + '@');
    }
#endif
  else
    AddCStr2(D_CS0, new);
}
#endif

#ifdef COLOR

int
color256to16(jj)
int jj;
{
  int min, max;
  int r, g, b;

  if (jj >= 232)
    {
      jj = (jj - 232) / 6;
      jj = (jj & 1) << 3 | (jj & 2 ? 7 : 0);
    }
  else if (jj >= 16)
    {
      jj -= 16;
      r = jj / 36;
      g = (jj / 6) % 6;
      b = jj % 6;
      min = r < g ? (r < b ? r : b) : (g < b ? g : b);
      max = r > g ? (r > b ? r : b) : (g > b ? g : b);
      if (min == max)
        jj = ((max + 1) & 2) << 2 | ((max + 1) & 4 ? 7 : 0);
      else
        jj = (b - min) / (max - min) << 2 | (g - min) / (max - min) << 1 | (r - 
min) / (max - min) | (max > 3 ? 8 : 0);
    }
  return jj;
}

#ifdef COLORS256
int
color256to88(jj)
int jj;
{
  int r, g, b;

  if (jj >= 232)
    return (jj - 232) / 3 + 80;
  if (jj >= 16)
    {
      jj -= 16;
      r = jj / 36;
      g = (jj / 6) % 6;
      b = jj % 6;
      return ((r + 1) / 2) * 16 + ((g + 1) / 2) * 4 + ((b + 1) / 2) + 16;
    }
  return jj;
}
#endif

void
SetColor(f, b)
int f, b;
{
  int of, ob;
  static unsigned char sftrans[8] = {0,4,2,6,1,5,3,7};

  if (!display)
    return;

  of = rend_getfg(&D_rend);
  ob = rend_getbg(&D_rend);

#ifdef COLORS16
  /* intense default not invented yet */
  if (f == 0x100)
    f = 0;
  if (b == 0x100)
    b = 0;
#endif
  debug2("SetColor %d %d", coli2e(of), coli2e(ob));
  debug2(" -> %d %d\n", coli2e(f), coli2e(b));
  debug2("(%d %d", of, ob);
  debug2(" -> %d %d)\n", f, b);

  if (!D_CAX && D_hascolor && ((f == 0 && f != of) || (b == 0 && b != ob)))
    {
      if (D_OP)
        AddCStr(D_OP);
      else
	{
	  int oattr;
	  oattr = D_rend.attr;
	  AddCStr(D_ME ? D_ME : "\033[m");
#ifdef FONT
	  if (D_ME && !D_CG0)
	    {
	      /* D_ME may also reset the alternate charset */
	      D_rend.font = 0;
# ifdef ENCODINGS
	      D_realfont = 0;
# endif
	    }
#endif
	  D_atyp = 0;
	  D_rend.attr = 0;
	  SetAttr(oattr);
	}
      of = ob = 0;
    }
  rend_setfg(&D_rend, f);
  rend_setbg(&D_rend, b);
#ifdef COLORS16
  D_col16change = 0;
#endif
  if (!D_hascolor)
    return;
  f = f ? coli2e(f) : -1;
  b = b ? coli2e(b) : -1;
  of = of ? coli2e(of) : -1;
  ob = ob ? coli2e(ob) : -1;
#ifdef COLORS256
  if (f != of && f > 15 && D_CCO != 256)
    f = D_CCO == 88 && D_CAF ? color256to88(f) : color256to16(f);
  if (f != of && f > 15 && D_CAF)
    {
      AddCStr2(D_CAF, f);
      of = f;
    }
  if (b != ob && b > 15 && D_CCO != 256)
    b = D_CCO == 88 && D_CAB ? color256to88(b) : color256to16(b);
  if (b != ob && b > 15 && D_CAB)
    {
      AddCStr2(D_CAB, b);
      ob = b;
    }
#endif
  if (f != of && f != (of | 8))
    {
      if (f == -1)
	AddCStr("\033[39m");	/* works because AX is set */
      else if (D_CAF)
	AddCStr2(D_CAF, f & 7);
      else if (D_CSF)
	AddCStr2(D_CSF, sftrans[f & 7]);
    }
  if (b != ob && b != (ob | 8))
    {
      if (b == -1)
	AddCStr("\033[49m");	/* works because AX is set */
      else if (D_CAB)
	AddCStr2(D_CAB, b & 7);
      else if (D_CSB)
	AddCStr2(D_CSB, sftrans[b & 7]);
    }
#ifdef COLORS16
  if (f != of && D_CXT && (f & 8) != 0 && f != -1)
    {
# ifdef TERMINFO
      AddCStr2("\033[9%p1%dm", f & 7);
# else
      AddCStr2("\033[9%dm", f & 7);
# endif
    }
  if (b != ob && D_CXT && (b & 8) != 0 && b != -1)
    {
# ifdef TERMINFO
      AddCStr2("\033[10%p1%dm", b & 7);
# else
      AddCStr2("\033[10%dm", b & 7);
# endif
    }
#endif
}

static void
SetBackColor(new)
int new;
{
  if (!display)
    return;
  SetColor(rend_getfg(&D_rend), new);
}
#endif /* COLOR */

void
SetRendition(mc)
struct mchar *mc;
{
  if (!display)
    return;
#ifdef COLOR
  if (nattr2color && D_hascolor && (mc->attr & nattr2color) != 0)
    {
      static struct mchar mmc;
      int i;
      mmc = *mc;
      for (i = 0; i < 8; i++)
	if (attr2color[i] && (mc->attr & (1 << i)) != 0)
	  {
	    if (mc->color == 0 && attr2color[i][3])
              ApplyAttrColor(attr2color[i][3], &mmc);
	    else if ((mc->color & 0x0f) == 0 && attr2color[i][2])
              ApplyAttrColor(attr2color[i][2], &mmc);
	    else if ((mc->color & 0xf0) == 0 && attr2color[i][1])
              ApplyAttrColor(attr2color[i][1], &mmc);
	    else
              ApplyAttrColor(attr2color[i][0], &mmc);
	  }
      mc = &mmc;
      debug2("SetRendition: mapped to %02x %02x\n", (unsigned char)mc->attr, 0x99 - (unsigned char)mc->color);
    }
# ifdef COLORS16
  if (D_hascolor && D_CC8 && (mc->attr & (A_BFG|A_BBG)))
    {
      int a = mc->attr;
      if ((mc->attr & A_BFG) && D_MD)
	a |= A_BD;
      if ((mc->attr & A_BBG) && D_MB)
	a |= A_BL;
      if (D_rend.attr != a)
        SetAttr(a);
    }
  else
# endif /* COLORS16 */
#endif /* COLOR */
    if (D_rend.attr != mc->attr)
    SetAttr(mc->attr);

#ifdef COLOR
  if (D_rend.color != mc->color
# ifdef COLORS256
      || D_rend.colorx != mc->colorx
# endif
# ifdef COLORS16
      || D_col16change
# endif
    )
    SetColor(rend_getfg(mc), rend_getbg(mc));
#endif
#ifdef FONT
  if (D_rend.font != mc->font)
    SetFont(mc->font);
#ifdef UTF8
  if (D_encoding == UTF8)
    D_rend.fontx = mc->fontx;
#endif
#endif
}

void
SetRenditionMline(ml, x)
struct mline *ml;
int x;
{
  if (!display)
    return;
#ifdef COLOR
  if (nattr2color && D_hascolor && (ml->attr[x] & nattr2color) != 0)
    {
      struct mchar mc;
      copy_mline2mchar(&mc, ml, x);
      SetRendition(&mc);
      return;
    }
# ifdef COLORS16
  if (D_hascolor && D_CC8 && (ml->attr[x] & (A_BFG|A_BBG)))
    {
      int a = ml->attr[x];
      if ((ml->attr[x] & A_BFG) && D_MD)
	a |= A_BD;
      if ((ml->attr[x] & A_BBG) && D_MB)
	a |= A_BL;
      if (D_rend.attr != a)
        SetAttr(a);
    }
  else
# endif /* COLORS16 */
#endif /* COLOR */
    if (D_rend.attr != ml->attr[x])
    SetAttr(ml->attr[x]);
#ifdef COLOR
  if (D_rend.color != ml->color[x]
# ifdef COLORS256
      || D_rend.colorx != ml->colorx[x]
# endif
# ifdef COLORS16
      || D_col16change
# endif
    )
    {
      struct mchar mc;
      copy_mline2mchar(&mc, ml, x);
      SetColor(rend_getfg(&mc), rend_getbg(&mc));
    }
#endif
#ifdef FONT
  if (D_rend.font != ml->font[x])
    SetFont(ml->font[x]);
#ifdef UTF8
  if (D_encoding == UTF8)
    D_rend.fontx = ml->fontx[x];
#endif
#endif
}

void
MakeStatus(msg)
char *msg;
{
  register char *s, *t;
  register int max;

  if (!display)
    return;
  
  if (D_blocked)
    return;
  if (!D_tcinited)
    {
      debug("tc not inited, just writing msg\n");
      if (D_processinputdata)
	return;		/* XXX: better */
      AddStr(msg);
      AddStr("\r\n");
      Flush(0);
      return;
    }
  if (!use_hardstatus || !D_HS)
    {
      max = D_width;
      if (D_CLP == 0)
	max--;
    }
  else
    max = D_WS > 0 ? D_WS : (D_width - !D_CLP);
  if (D_status)
    {
      /* same message? */
      if (strcmp(msg, D_status_lastmsg) == 0)
	{
	  debug("same message - increase timeout");
	  if (!D_status_obufpos)
	    SetTimeout(&D_statusev, MsgWait);
	  return;
	}
      RemoveStatusMinWait();
    }
  for (s = t = msg; *s && t - msg < max; ++s)
    if (*s == BELL)
      AddCStr(D_BL);
    else if ((unsigned char)*s >= ' ' && *s != 0177)
      *t++ = *s;
  *t = '\0';
  if (t == msg)
    return;
  if (t - msg >= D_status_buflen)
    {
      char *buf;
      if (D_status_lastmsg)
	buf = realloc(D_status_lastmsg, t - msg + 1);
      else
	buf = malloc(t - msg + 1);
      if (buf)
	{
	  D_status_lastmsg = buf;
	  D_status_buflen = t - msg + 1;
	}
    }
  if (t - msg < D_status_buflen)
    strcpy(D_status_lastmsg, msg);
  D_status_len = t - msg;
  D_status_lastx = D_x;
  D_status_lasty = D_y;
  if (!use_hardstatus || D_has_hstatus == HSTATUS_IGNORE || D_has_hstatus == HSTATUS_MESSAGE)
    {
      D_status = STATUS_ON_WIN;
      debug1("using STATLINE %d\n", STATLINE);
      GotoPos(0, STATLINE);
      SetRendition(&mchar_so);
      InsertMode(0);
      AddStr(msg);
      if (D_status_len < max)
	{
	  /* Wayne Davison: add extra space for readability */
	  D_status_len++;
	  SetRendition(&mchar_null);
	  AddChar(' ');
	  if (D_status_len < max)
	    {
	      D_status_len++;
	      AddChar(' ');
	      AddChar('\b');
	    }
	  AddChar('\b');
	}
      D_x = -1;
    }
  else
    {
      D_status = STATUS_ON_HS;
      ShowHStatus(msg);
    }

  D_status_obufpos = D_obufp - D_obuf;
  ASSERT(D_status_obufpos > 0);

  if (D_status == STATUS_ON_WIN)
    {
      struct display *olddisplay = display;
      struct layer *oldflayer = flayer;

      /* this is copied over from RemoveStatus() */
      D_status = 0;
      GotoPos(0, STATLINE);
      RefreshLine(STATLINE, 0, D_status_len - 1, 0);
      GotoPos(D_status_lastx, D_status_lasty);
      flayer = D_forecv ? D_forecv->c_layer : 0;
      if (flayer)
	LaySetCursor();
      display = olddisplay;
      flayer = oldflayer;
      D_status = STATUS_ON_WIN;
    }
}

void
RemoveStatus()
{
  struct display *olddisplay;
  struct layer *oldflayer;
  int where;

  if (!display)
    return;
  if (!(where = D_status))
    return;

  debug("RemoveStatus\n");
  if (D_status_obuffree >= 0)
    {
      D_obuflen = D_status_obuflen;
      D_obuffree = D_status_obuffree;
      D_status_obuffree = -1;
    }
  D_status = 0;
  D_status_obufpos = 0;
  D_status_bell = 0;
  evdeq(&D_statusev);
  olddisplay = display;
  oldflayer = flayer;
  if (where == STATUS_ON_WIN)
    {
      if (captionalways || (D_canvas.c_slperp && D_canvas.c_slperp->c_slnext))
	{
	  GotoPos(0, STATLINE);
	  RefreshLine(STATLINE, 0, D_status_len - 1, 0);
	  GotoPos(D_status_lastx, D_status_lasty);
	}
    }
  else
    RefreshHStatus();
  flayer = D_forecv ? D_forecv->c_layer : 0;
  if (flayer)
    LaySetCursor();
  display = olddisplay;
  flayer = oldflayer;
}

/* Remove the status but make sure that it is seen for MsgMinWait ms */
static void
RemoveStatusMinWait()
{
  /* XXX: should flush output first if D_status_obufpos is set */
  if (!D_status_bell && !D_status_obufpos)
    {
      struct timeval now;
      int ti;
      gettimeofday(&now, NULL);
      ti = (now.tv_sec - D_status_time.tv_sec) * 1000 + (now.tv_usec - D_status_time.tv_usec) / 1000;
      if (ti < MsgMinWait)
	DisplaySleep1000(MsgMinWait - ti, 0);
    }
  RemoveStatus();
}

#ifdef UTF8
static int
strlen_onscreen(unsigned char *c, unsigned char *end)
{
  int len = 0;
  while (*c && (!end || c < end))
    {
      int v, dec = 0;
      do
	{
	  v = FromUtf8(*c++, &dec);
	  if (v == -2)
	    c--;
	}
      while (v < 0 && (!end || c < end));
      if (!utf8_iscomb(v))
        {
          if (utf8_isdouble(v))
            len++;
          len++;
        }
    }

  return len;
}

static int
PrePutWinMsg(s, start, max)
char *s;
int start, max;
{
  /* Avoid double-encoding problem for a UTF-8 message on a UTF-8 locale.
     Ideally, this would not be necessary. But fixing it the Right Way will
     probably take way more time. So this will have to do for now. */
  if (D_encoding == UTF8)
    {
      int chars = strlen_onscreen((unsigned char *)(s + start), (unsigned char *)(s + max));
      D_encoding = 0;
      PutWinMsg(s, start, max + ((max - start) - chars)); /* Multibyte count */
      D_encoding = UTF8;
      D_x -= (max - chars);	/* Yak! But this is necessary to count for
				   the fact that not every byte represents a
				   character. */
      return start + chars;
    }
  else
    {
      PutWinMsg(s, start, max);
      return max;
    }
}
#else
static int
PrePutWinMsg(s, start, max)
char *s;
int start, max;
{
  PutWinMsg(s, start, max);
  return max;
}
#endif

/* refresh the display's hstatus line */
void
ShowHStatus(str)
char *str;
{
  int l, ox, oy, max;

  if (D_status == STATUS_ON_WIN && D_has_hstatus == HSTATUS_LASTLINE && STATLINE == D_height-1)
    return;	/* sorry, in use */
  if (D_blocked)
    return;

  if (D_HS && D_has_hstatus == HSTATUS_HS)
    {
      if (!D_hstatus && (str == 0 || *str == 0))
	return;
      debug("ShowHStatus: using HS\n");
      SetRendition(&mchar_null);
      InsertMode(0);
      if (D_hstatus)
	AddCStr(D_DS);
      D_hstatus = 0;
      if (str == 0 || *str == 0)
	return;
      AddCStr2(D_TS, 0);
      max = D_WS > 0 ? D_WS : (D_width - !D_CLP);
      if ((int)strlen(str) > max)
	AddStrn(str, max);
      else
	AddStr(str);
      AddCStr(D_FS);
      D_hstatus = 1;
    }
  else if (D_has_hstatus == HSTATUS_LASTLINE)
    {
      debug("ShowHStatus: using last line\n");
      ox = D_x;
      oy = D_y;
      str = str ? str : "";
      l = strlen(str);
      if (l > D_width)
	l = D_width;
      GotoPos(0, D_height - 1);
      SetRendition(captionalways || D_cvlist == 0 || D_cvlist->c_next ? &mchar_null: &mchar_so);
      l = PrePutWinMsg(str, 0, l);
      if (!captionalways && D_cvlist && !D_cvlist->c_next)
        while (l++ < D_width)
	  PUTCHARLP(' ');
      if (l < D_width)
        ClearArea(l, D_height - 1, l, D_width - 1, D_width - 1, D_height - 1, 0, 0);
      if (ox != -1 && oy != -1)
	GotoPos(ox, oy);
      D_hstatus = *str ? 1 : 0;
      SetRendition(&mchar_null);
    }
  else if (D_has_hstatus == HSTATUS_FIRSTLINE)
    {
      debug("ShowHStatus: using first line\n");
      ox = D_x;
      oy = D_y;
      str = str ? str : "";
      l = strlen(str);
      if (l > D_width)
        l = D_width;
      GotoPos(0, 0);
      SetRendition(captionalways || D_cvlist == 0 || D_cvlist->c_next ? &mchar_null: &mchar_so);
      l = PrePutWinMsg(str, 0, l);
      if (!captionalways || (D_cvlist && !D_cvlist->c_next))
        while (l++ < D_width)
          PUTCHARLP(' ');
      if (l < D_width)
        ClearArea(l, 0, l, D_width - 1, D_width - 1, 0, 0, 0);
      if (ox != -1 && oy != -1)
        GotoPos(ox, oy);
      D_hstatus = *str ? 1 : 0;
      SetRendition(&mchar_null);
    }
  else if (str && *str && D_has_hstatus == HSTATUS_MESSAGE)
    {
      debug("ShowHStatus: using message\n");
      Msg(0, "%s", str);
    }
}


/*
 *  Refreshes the hardstatus of the fore window. Shouldn't be here...
 */
void
RefreshHStatus()
{
  char *buf;
#ifdef UTF8
  int extrabytes = strlen(hstatusstring) - strlen_onscreen((unsigned char *)hstatusstring, NULL);
#else
  int extrabytes = 0;
#endif
  evdeq(&D_hstatusev);
  if (D_status == STATUS_ON_HS)
    return;
  buf = MakeWinMsgEv(hstatusstring, D_fore, '%', (D_HS && D_has_hstatus == HSTATUS_HS && D_WS > 0) ? D_WS : D_width - !D_CLP + extrabytes, &D_hstatusev, 0);
  if (buf && *buf)
    {
      ShowHStatus(buf);
      if (D_has_hstatus != HSTATUS_IGNORE && D_hstatusev.timeout.tv_sec)
        evenq(&D_hstatusev);
    }
  else
    ShowHStatus((char *)0);
}

/*********************************************************************/
/*
 *  Here come the routines that refresh an arbitrary part of the screen.
 */

void
RefreshAll(isblank)
int isblank;
{
  struct canvas *cv;

  ASSERT(display);
  debug("Signalling full refresh!\n");
  for (cv = D_cvlist; cv; cv = cv->c_next)
    {
      CV_CALL(cv, LayRedisplayLine(-1, -1, -1, isblank));
      display = cv->c_display;	/* just in case! */
    }
  RefreshArea(0, 0, D_width - 1, D_height - 1, isblank);
}

void
RefreshArea(xs, ys, xe, ye, isblank)
int xs, ys, xe, ye, isblank;
{
  int y;
  ASSERT(display);
  debug2("Refresh Area: %d,%d", xs, ys);
  debug3(" - %d,%d (isblank=%d)\n", xe, ye, isblank);
  if (!isblank && xs == 0 && xe == D_width - 1 && ye == D_height - 1 && (ys == 0 || D_CD))
    {
      ClearArea(xs, ys, xs, xe, xe, ye, 0, 0);
      isblank = 1;
    }
  for (y = ys; y <= ye; y++)
    RefreshLine(y, xs, xe, isblank);
}

void
RefreshLine(y, from, to, isblank)
int y, from, to, isblank;
{
  struct viewport *vp, *lvp;
  struct canvas *cv, *lcv, *cvlist, *cvlnext;
  struct layer *oldflayer;
  int xx, yy, l;
  char *buf;
  struct win *p;

  ASSERT(display);

  debug2("RefreshLine %d %d", y, from);
  debug2(" %d %d\n", to, isblank);

  if (D_status == STATUS_ON_WIN && y == STATLINE)
    {
      if (to >= D_status_len)
	D_status_len = to + 1;
      return;	/* can't refresh status */
    }

  if (isblank == 0 && D_CE && to == D_width - 1 && from < to && D_status != STATUS_ON_HS)
    {
      GotoPos(from, y);
      if (D_UT || D_BE)
	SetRendition(&mchar_null);
      AddCStr(D_CE);
      isblank = 1;
    }

  if ((y == D_height - 1 && D_has_hstatus == HSTATUS_LASTLINE) || (y == 0 && D_has_hstatus == HSTATUS_FIRSTLINE) )
    {
      RefreshHStatus();
      return;
    }

  while (from <= to)
    {
      lcv = 0;
      lvp = 0;
      for (cv = display->d_cvlist; cv; cv = cv->c_next)
	{
	  if (y == cv->c_ye + 1 && from >= cv->c_xs && from <= cv->c_xe)
	    {
#ifdef UTF8
	      int extrabytes = strlen(captionstring) - strlen_onscreen((unsigned char *)captionstring, NULL);
#else
	      int extrabytes = 0;
#endif
	      p = Layer2Window(cv->c_layer);
	      buf = MakeWinMsgEv(captionstring, p, '%', cv->c_xe - cv->c_xs + (cv->c_xe + 1 < D_width || D_CLP) + extrabytes, &cv->c_captev, 0);
	      if (cv->c_captev.timeout.tv_sec)
		evenq(&cv->c_captev);
	      xx = to > cv->c_xe ? cv->c_xe : to;
	      l = strlen(buf);
	      GotoPos(from, y);
	      SetRendition(&mchar_so);
	      if (l > xx - cv->c_xs + 1)
		l = xx - cv->c_xs + 1;
	      l = PrePutWinMsg(buf, from - cv->c_xs, l + extrabytes);
	      from = cv->c_xs + l;
	      for (; from <= xx; from++)
		PUTCHARLP(' ');
	      break;
	    }
	  if (from == cv->c_xe + 1 && y >= cv->c_ys && y <= cv->c_ye + 1)
	    {
	      GotoPos(from, y);
	      SetRendition(&mchar_so);
	      PUTCHARLP(' ');
	      from++;
	      break;
	    }
	  if (y < cv->c_ys || y > cv->c_ye || to < cv->c_xs || from > cv->c_xe)
	    continue;
	  debug2("- canvas hit: %d %d", cv->c_xs, cv->c_ys);
	  debug2("  %d %d\n", cv->c_xe, cv->c_ye);
	  for (vp = cv->c_vplist; vp; vp = vp->v_next)
	    {
	      debug2("  - vp: %d %d", vp->v_xs, vp->v_ys);
	      debug2("  %d %d\n", vp->v_xe, vp->v_ye);
	      /* find leftmost overlapping vp */
	      if (y >= vp->v_ys && y <= vp->v_ye && from <= vp->v_xe && to >= vp->v_xs && (lvp == 0 || lvp->v_xs > vp->v_xs))
		{
		  lcv = cv;
		  lvp = vp;
		}
	    }
	}
      if (cv)
	continue;	/* we advanced from */
      if (lvp == 0)
	break;
      if (from < lvp->v_xs)
	{
	  if (!isblank)
	    DisplayLine(&mline_null, &mline_blank, y, from, lvp->v_xs - 1);
	  from = lvp->v_xs;
	}

      /* call LayRedisplayLine on canvas lcv viewport lvp */
      yy = y - lvp->v_yoff;
      xx = to < lvp->v_xe ? to : lvp->v_xe;

      if (lcv->c_layer && lcv->c_xoff + lcv->c_layer->l_width == from)
	{
	  GotoPos(from, y);
	  SetRendition(&mchar_blank);
	  PUTCHARLP('|');
	  from++;
	}
      if (lcv->c_layer && yy == lcv->c_layer->l_height)
	{
	  GotoPos(from, y);
	  SetRendition(&mchar_blank);
	  while (from <= lvp->v_xe && from - lvp->v_xoff < lcv->c_layer->l_width)
	    {
	      PUTCHARLP('-');
	      from++;
	    }
	  if (from >= lvp->v_xe + 1)
	    continue;
	}
      if (lcv->c_layer == 0 || yy >= lcv->c_layer->l_height || from - lvp->v_xoff >= lcv->c_layer->l_width)
	{
	  if (!isblank)
	    DisplayLine(&mline_null, &mline_blank, y, from, lvp->v_xe);
	  from = lvp->v_xe + 1;
	  continue;
	}

      if (xx - lvp->v_xoff >= lcv->c_layer->l_width)
	xx = lcv->c_layer->l_width + lvp->v_xoff - 1;
      oldflayer = flayer;
      flayer = lcv->c_layer;
      cvlist = flayer->l_cvlist;
      cvlnext = lcv->c_lnext;
      flayer->l_cvlist = lcv;
      lcv->c_lnext = 0;
      LayRedisplayLine(yy, from - lvp->v_xoff, xx - lvp->v_xoff, isblank);
      flayer->l_cvlist = cvlist;
      lcv->c_lnext = cvlnext;
      flayer = oldflayer;

      from = xx + 1;
    }
  if (!isblank && from <= to)
    DisplayLine(&mline_null, &mline_blank, y, from, to);
}

/*********************************************************************/

/* clear lp_missing by writing the char on the screen. The
 * position must be safe.
 */
static void
WriteLP(x2, y2)
int x2, y2;
{
  struct mchar oldrend;

  ASSERT(display);
  ASSERT(D_lp_missing);
  oldrend = D_rend;
  debug2("WriteLP(%d,%d)\n", x2, y2);
#ifdef DW_CHARS
  if (D_lpchar.mbcs)
    {
      if (x2 > 0)
	x2--;
      else
	D_lpchar = mchar_blank;
    }
#endif
  /* Can't use PutChar */
  GotoPos(x2, y2);
  SetRendition(&D_lpchar);
  PUTCHAR(D_lpchar.image);
#ifdef DW_CHARS
  if (D_lpchar.mbcs)
    PUTCHAR(D_lpchar.mbcs);
#endif
  D_lp_missing = 0;
  SetRendition(&oldrend);
}

void
ClearLine(oml, y, from, to, bce)
struct mline *oml;
int from, to, y, bce;
{
  int x;
#ifdef COLOR
  struct mchar bcechar;
#endif

  debug3("ClearLine %d,%d-%d\n", y, from, to);
  if (D_UT)	/* Safe to erase ? */
    SetRendition(&mchar_null);
#ifdef COLOR
  if (D_BE)
    SetBackColor(bce);
#endif
  if (from == 0 && D_CB && (to != D_width - 1 || (D_x == to && D_y == y)) && (!bce || D_BE))
    {
      GotoPos(to, y);
      AddCStr(D_CB);
      return;
    }
  if (to == D_width - 1 && D_CE && (!bce || D_BE))
    {
      GotoPos(from, y);
      AddCStr(D_CE);
      return;
    }
  if (oml == 0)
    oml = &mline_null;
#ifdef COLOR
  if (!bce)
    {
      DisplayLine(oml, &mline_blank, y, from, to);
      return;
    }
  bcechar = mchar_null;
  rend_setbg(&bcechar, bce);
  for (x = from; x <= to; x++)
    copy_mchar2mline(&bcechar, &mline_old, x);
  DisplayLine(oml, &mline_old, y, from, to);
#else
  DisplayLine(oml, &mline_blank, y, from, to);
#endif
}

void
DisplayLine(oml, ml, y, from, to)
struct mline *oml, *ml;
int from, to, y;
{
  register int x;
  int last2flag = 0, delete_lp = 0;

  ASSERT(display);
  ASSERT(y >= 0 && y < D_height);
  ASSERT(from >= 0 && from < D_width);
  ASSERT(to >= 0 && to < D_width);
  if (!D_CLP && y == D_bot && to == D_width - 1)
    {
      if (D_lp_missing || !cmp_mline(oml, ml, to))
	{
#ifdef DW_CHARS
	  if ((D_IC || D_IM) && from < to && !dw_left(ml, to, D_encoding))
#else
	  if ((D_IC || D_IM) && from < to)
#endif
	    {
	      last2flag = 1;
	      D_lp_missing = 0;
	      to--;
	    }
	  else
	    {
	      delete_lp = !cmp_mchar_mline(&mchar_blank, oml, to) && (D_CE || D_DC || D_CDC);
	      D_lp_missing = !cmp_mchar_mline(&mchar_blank, ml, to);
	      copy_mline2mchar(&D_lpchar, ml, to);
	    }
	}
      to--;
    }
#ifdef DW_CHARS
  if (D_mbcs)
    {
      /* finish dw-char (can happen after a wrap) */
      debug("DisplayLine finishing kanji\n");
      SetRenditionMline(ml, from);
      PUTCHAR(ml->image[from]);
      from++;
    }
#endif
  for (x = from; x <= to; x++)
    {
#if 0	/* no longer needed */
      if (x || D_x != D_width || D_y != y - 1)
#endif
        {
	  if (ml != NULL && (x < to || x != D_width - 1 || ml->image[x + 1]))
	    if (cmp_mline(oml, ml, x))
	      continue;
	  GotoPos(x, y);
        }
#ifdef DW_CHARS
      if (dw_right(ml, x, D_encoding))
	{
	  x--;
	  debug1("DisplayLine on right side of dw char- x now %d\n", x);
	  GotoPos(x, y);
	}
      if (x == to && dw_left(ml, x, D_encoding))
	break;	/* don't start new kanji */
#endif
      SetRenditionMline(ml, x);
      PUTCHAR(ml->image[x]);
#ifdef DW_CHARS
      if (dw_left(ml, x, D_encoding))
        PUTCHAR(ml->image[++x]);
#endif
    }
#if 0	/* not needed any longer */
  /* compare != 0 because ' ' can happen when clipping occures */
  if (to == D_width - 1 && y < D_height - 1 && D_x == D_width && ml->image[to + 1])
    GotoPos(0, y + 1);
#endif
  if (last2flag)
    {
      GotoPos(x, y);
      SetRenditionMline(ml, x + 1);
      PUTCHAR(ml->image[x + 1]);
      GotoPos(x, y);
      SetRenditionMline(ml, x);
      INSERTCHAR(ml->image[x]);
    }
  else if (delete_lp)
    {
      if (D_UT)
	SetRendition(&mchar_null);
      if (D_DC)
	AddCStr(D_DC);
      else if (D_CDC)
	AddCStr2(D_CDC, 1);
      else if (D_CE)
	AddCStr(D_CE);
    }
}

void
PutChar(c, x, y)
struct mchar *c;
int x, y;
{
  GotoPos(x, y);
  SetRendition(c);
  PUTCHARLP(c->image);
#ifdef DW_CHARS
  if (c->mbcs)
    {
# ifdef UTF8
      if (D_encoding == UTF8)
        D_rend.font = 0;
# endif
      PUTCHARLP(c->mbcs);
    }
#endif
}

void
InsChar(c, x, xe, y, oml)
struct mchar *c;
int x, xe, y;
struct mline *oml;
{
  GotoPos(x, y);
  if (y == D_bot && !D_CLP)
    {
      if (x == D_width - 1)
        {
          D_lp_missing = 1;
          D_lpchar = *c;
          return;
        }
      if (xe == D_width - 1)
        D_lp_missing = 0;
    }
  if (x == xe)
    {
      SetRendition(c);
      PUTCHARLP(c->image);
      return;
    }
  if (!(D_IC || D_CIC || D_IM) || xe != D_width - 1)
    {
      RefreshLine(y, x, xe, 0);
      GotoPos(x + 1, y);
      /* UpdateLine(oml, y, x, xe); */
      return;
    }
  InsertMode(1);
  if (!D_insert)
    {
#ifdef DW_CHARS
      if (c->mbcs && D_IC)
        AddCStr(D_IC);
      if (D_IC)
        AddCStr(D_IC);
      else
        AddCStr2(D_CIC, c->mbcs ? 2 : 1);
#else
      if (D_IC)
        AddCStr(D_IC);
      else
        AddCStr2(D_CIC, 1);
#endif
    }
  SetRendition(c);
  RAW_PUTCHAR(c->image);
#ifdef DW_CHARS
  if (c->mbcs)
    {
# ifdef UTF8
      if (D_encoding == UTF8)
	D_rend.font = 0;
# endif
      if (D_x == D_width - 1)
	PUTCHARLP(c->mbcs);
      else
	RAW_PUTCHAR(c->mbcs);
    }
#endif
}

void
WrapChar(c, x, y, xs, ys, xe, ye, ins)
struct mchar *c;
int x, y;
int xs, ys, xe, ye;
int ins;
{
  int bce;

#ifdef COLOR
  bce = rend_getbg(c);
#else
  bce = 0;
#endif
  debug("WrapChar:");
  debug2("  x %d  y %d", x, y);
  debug2("  Dx %d  Dy %d", D_x, D_y);
  debug2("  xs %d  ys %d", xs, ys);
  debug3("  xe %d  ye %d ins %d\n", xe, ye, ins);
  if (xs != 0 || x != D_width || !D_AM)
    {
      if (y == ye)
	ScrollV(xs, ys, xe, ye, 1, bce);
      else if (y < D_height - 1)
	y++;
      if (ins)
	InsChar(c, xs, xe, y, 0);
      else
        PutChar(c, xs, y);
      return;
    }
  if (y == ye)		/* we have to scroll */
    {
      debug("- scrolling\n");
      ChangeScrollRegion(ys, ye);
      if (D_bot != y || D_x != D_width || (!bce && !D_BE))
	{
	  debug("- have to call ScrollV\n");
	  ScrollV(xs, ys, xe, ye, 1, bce);
	  y--;
	}
    }
  else if (y == D_bot)	/* remove unusable region? */
    ChangeScrollRegion(0, D_height - 1);
  if (D_x != D_width || D_y != y)
    {
      if (D_CLP && y >= 0)		/* don't even try if !LP */
        RefreshLine(y, D_width - 1, D_width - 1, 0);
      debug2("- refresh last char -> x,y now %d,%d\n", D_x, D_y);
      if (D_x != D_width || D_y != y)	/* sorry, no bonus */
	{
	  if (y == ye)
	    ScrollV(xs, ys, xe, ye, 1, bce);
	  GotoPos(xs, y == ye || y == D_height - 1 ? y : y + 1);
	}
    }
  debug("- writeing new char");
  if (y != ye && y < D_height - 1)
    y++;
  if (ins != D_insert)
    InsertMode(ins);
  if (ins && !D_insert)
    {
      InsChar(c, 0, xe, y, 0);
      debug2(" -> done with insert (%d,%d)\n", D_x, D_y);
      return;
    }
  D_y = y;
  D_x = 0;
  SetRendition(c);
  RAW_PUTCHAR(c->image);
#ifdef DW_CHARS
  if (c->mbcs)
    {
# ifdef UTF8
      if (D_encoding == UTF8)
	D_rend.font = 0;
# endif
      RAW_PUTCHAR(c->mbcs);
    }
#endif
  debug2(" -> done (%d,%d)\n", D_x, D_y);
}

int
ResizeDisplay(wi, he)
int wi, he;
{
  ASSERT(display);
  debug2("ResizeDisplay: to (%d,%d).\n", wi, he);
  if (D_width == wi && D_height == he)
    {
      debug("ResizeDisplay: No change\n");
      return 0;
    }
  if (D_width != wi && (D_height == he || !D_CWS) && D_CZ0 && (wi == Z0width || wi == Z1width))
    {
      debug("ResizeDisplay: using Z0/Z1\n");
      AddCStr(wi == Z0width ? D_CZ0 : D_CZ1);
      ChangeScreenSize(wi, D_height, 0);
      return (he == D_height) ? 0 : -1;
    }
  if (D_CWS)
    {
      debug("ResizeDisplay: using WS\n");
      AddCStr(tgoto(D_CWS, wi, he));
      ChangeScreenSize(wi, he, 0);
      return 0;
    }
  return -1;
}

void
ChangeScrollRegion(newtop, newbot)
int newtop, newbot;
{
  if (display == 0)
    return;
  if (newtop == newbot)
    return;			/* xterm etc can't do it */
  if (newtop == -1)
    newtop = 0;
  if (newbot == -1)
    newbot = D_height - 1;
  if (D_CS == 0)
    {
      D_top = 0;
      D_bot = D_height - 1;
      return;
    }
  if (D_top == newtop && D_bot == newbot)
    return;
  debug2("ChangeScrollRegion: (%d - %d)\n", newtop, newbot);
  AddCStr(tgoto(D_CS, newbot, newtop));
  D_top = newtop;
  D_bot = newbot;
  D_y = D_x = -1;		/* Just in case... */
}

#ifdef RXVT_OSC
#define WT_FLAG "2"	/* change to "0" to set both title and icon */

void
SetXtermOSC(i, s, t)
int i;
char *s;
char *t;
{
  static char *oscs[][2] = {
    { WT_FLAG ";", "screen" }, /* set window title */
    { "11;", ""},       /* background RGB */
    { "20;", "" },      /* background */
    { "39;", "black" }, /* default foreground (black?) */
    { "49;", "white" }  /* default background (white?) */
  };

  ASSERT(display);
  if (!D_CXT)
    return;
  if (!s)
    s = "";
  if (!D_xtermosc[i] && !*s)
    return;
  if (i == 0 && !D_xtermosc[0])
    AddStr("\033[22;" WT_FLAG "t");	/* stack titles (xterm patch #251) */
  if (!*s)
    s = oscs[i][1];
  D_xtermosc[i] = 1;
  AddStr("\033]");
  AddStr(oscs[i][0]);
  AddStr(s);
  AddStr(t);
}

void
ClearAllXtermOSC()
{
  int i;
  for (i = 4; i >= 0; i--)
    SetXtermOSC(i, 0, "\a");
  if (D_xtermosc[0])
    AddStr("\033[23;" WT_FLAG "t");	/* unstack titles (xterm patch #251) */
}
#undef WT_FLAG
#endif

/*
 *  Output buffering routines
 */

void
AddStr(str)
char *str;
{
  register char c;

  ASSERT(display);

#ifdef UTF8
  if (D_encoding == UTF8)
    {
      while ((c = *str++))
        AddUtf8((unsigned char)c);
      return;
    }
#endif
  while ((c = *str++))
    AddChar(c);
}

void
AddStrn(str, n)
char *str;
int n;
{
  register char c;

  ASSERT(display);
#ifdef UTF8
  if (D_encoding == UTF8)
    {
      while ((c = *str++) && n-- > 0)
        AddUtf8((unsigned char)c);
    }
  else
#endif
  while ((c = *str++) && n-- > 0)
    AddChar(c);
  while (n-- > 0)
    AddChar(' ');
}

void
Flush(progress)
int progress;
{
  register int l;
  int wr;
  register char *p;

  ASSERT(display);
  l = D_obufp - D_obuf;
  debug1("Flush(): %d\n", l);
  if (l == 0)
    return;
  ASSERT(l + D_obuffree == D_obuflen);
  if (D_userfd < 0)
    {
      D_obuffree += l;
      D_obufp = D_obuf;
      return;
    }
  p = D_obuf;
  if (!progress)
    {
      if (fcntl(D_userfd, F_SETFL, 0))
	debug1("Warning: BLOCK fcntl failed: %d\n", errno);
    }
  while (l)
    {
      if (progress)
	{
	  fd_set w;
	  FD_ZERO(&w);
	  FD_SET(D_userfd, &w);
	  struct timeval t;
	  t.tv_sec = progress;
	  t.tv_usec = 0;
	  wr = select(FD_SETSIZE, (fd_set *)0, &w, (fd_set *)0, &t);
	  if (wr == -1)
	    {
	      if (errno == EINTR)
		continue;
	      debug1("Warning: select failed: %d\n", errno);
	      break;
	    }
	  if (wr == 0)
	    {
	      /* no progress after 3 seconds. sorry. */
	      debug1("Warning: no progress after %d seconds\n", progress);
	      break;
	    }
	}
      wr = write(D_userfd, p, l);
      if (wr <= 0)
	{
	  if (errno == EINTR)
	    continue;
	  debug1("Writing to display: %d\n", errno);
	  break;
	}
      D_obuffree += wr;
      p += wr;
      l -= wr;
    }
  if (l)
    debug1("Warning: Flush could not write %d bytes\n", l);
  D_obuffree += l;
  D_obufp = D_obuf;
  if (!progress)
    {
      if (fcntl(D_userfd, F_SETFL, FNBLOCK))
	debug1("Warning: NBLOCK fcntl failed: %d\n", errno);
    }
  if (D_blocked == 1)
    D_blocked = 0;
  D_blocked_fuzz = 0;
}

void
freetty()
{
  if (D_userfd >= 0)
    close(D_userfd);
  debug1("did freetty %d\n", D_userfd);
  D_userfd = -1;
  D_obufp = 0;
  D_obuffree = 0;
  if (D_obuf)
    free(D_obuf);
  D_obuf = 0;
  D_obuflen = 0;
  D_obuflenmax = -D_obufmax;
  D_blocked = 0;
  D_blocked_fuzz = 0;
}

/*
 *  Asynchronous output routines by
 *  Tim MacKenzie (tym@dibbler.cs.monash.edu.au)
 */

void
Resize_obuf()
{
  register int ind;

  ASSERT(display);
  if (D_status_obuffree >= 0)
    {
      ASSERT(D_obuffree == -1);
      RemoveStatusMinWait();
      if (--D_obuffree > 0)	/* redo AddChar decrement */
	return;
    }
  if (D_obuflen && D_obuf)
    {
      ind  = D_obufp - D_obuf;
      D_obuflen += GRAIN;
      D_obuffree += GRAIN;
      D_obuf = realloc(D_obuf, D_obuflen);
    }
  else
    {
      ind  = 0;
      D_obuflen = GRAIN;
      D_obuffree = GRAIN;
      D_obuf = malloc(D_obuflen);
    }
  if (!D_obuf)
    Panic(0, "Out of memory");
  D_obufp = D_obuf + ind;
  D_obuflenmax = D_obuflen - D_obufmax;
  debug1("ResizeObuf: resized to %d\n", D_obuflen);
}

void
DisplaySleep1000(n, eat)
int n;
int eat;
{
  char buf;
  fd_set r;
  struct timeval t;

  if (n <= 0)
    return;
  if (!display)
    {
      debug("DisplaySleep has no display sigh\n");
      sleep1000(n);
      return;
    }
  t.tv_usec = (n % 1000) * 1000;
  t.tv_sec = n / 1000;
  FD_ZERO(&r);
  FD_SET(D_userfd, &r);
  if (select(FD_SETSIZE, &r, (fd_set *)0, (fd_set *)0, &t) > 0)
    {
      debug("display activity stopped sleep\n");
      if (eat)
        read(D_userfd, &buf, 1);
    }
  debug2("DisplaySleep(%d) ending, eat was %d\n", n, eat);
}

#ifdef AUTO_NUKE
void
NukePending()
{/* Nuke pending output in current display, clear screen */
  register int len;
  int oldtop = D_top, oldbot = D_bot;
  struct mchar oldrend;
  int oldkeypad = D_keypad, oldcursorkeys = D_cursorkeys;
  int oldcurvis = D_curvis;
  int oldmouse = D_mouse;
  int oldextmouse = D_extmouse;

  oldrend = D_rend;
  len = D_obufp - D_obuf;
  debug1("NukePending: nuking %d chars\n", len);
  
  /* Throw away any output that we can... */
# ifdef POSIX
  tcflush(D_userfd, TCOFLUSH);
# else
#  ifdef TCFLSH
  (void) ioctl(D_userfd, TCFLSH, (char *) 1);
#  endif
# endif

  D_obufp = D_obuf;
  D_obuffree += len;
  D_top = D_bot = -1;
  AddCStr(D_IS);
  AddCStr(D_TI);
  /* Turn off all attributes. (Tim MacKenzie) */
  if (D_ME)
    AddCStr(D_ME);
  else
    {
#ifdef COLOR
      if (D_hascolor)
	AddStr("\033[m");	/* why is D_ME not set? */
#endif
      AddCStr(D_SE);
      AddCStr(D_UE);
    }
  /* Check for toggle */
  if (D_IM && strcmp(D_IM, D_EI))
    AddCStr(D_EI);
  D_insert = 0;
  /* Check for toggle */
#ifdef MAPKEYS
  if (D_KS && strcmp(D_KS, D_KE))
    AddCStr(D_KS);
  if (D_CCS && strcmp(D_CCS, D_CCE))
    AddCStr(D_CCS);
#else
  if (D_KS && strcmp(D_KS, D_KE))
    AddCStr(D_KE);
  D_keypad = 0;
  if (D_CCS && strcmp(D_CCS, D_CCE))
    AddCStr(D_CCE);
  D_cursorkeys = 0;
#endif
  AddCStr(D_CE0);
  D_rend = mchar_null;
  D_atyp = 0;
  AddCStr(D_DS);
  D_hstatus = 0;
  AddCStr(D_VE);
  D_curvis = 0;
  ChangeScrollRegion(oldtop, oldbot);
  SetRendition(&oldrend);
  KeypadMode(oldkeypad);
  CursorkeysMode(oldcursorkeys);
  CursorVisibility(oldcurvis);
  MouseMode(oldmouse);
  ExtMouseMode(oldextmouse);
  if (D_CWS)
    {
      debug("ResizeDisplay: using WS\n");
      AddCStr(tgoto(D_CWS, D_width, D_height));
    }
  else if (D_CZ0 && (D_width == Z0width || D_width == Z1width))
    {
      debug("ResizeDisplay: using Z0/Z1\n");
      AddCStr(D_width == Z0width ? D_CZ0 : D_CZ1);
    }
}
#endif /* AUTO_NUKE */

#ifdef linux
/* linux' select can't handle flow control, so wait 100ms if
 * we get EAGAIN
 */
static void
disp_writeev_eagain(ev, data)
struct event *ev;
char *data;
{
  display = (struct display *)data;
  evdeq(&D_writeev);
  D_writeev.type = EV_WRITE;
  D_writeev.handler = disp_writeev_fn;
  evenq(&D_writeev);
}
#endif

static void
disp_writeev_fn(ev, data)
struct event *ev;
char *data;
{
  int len, size = OUTPUT_BLOCK_SIZE;

  display = (struct display *)data;
  len = D_obufp - D_obuf;
  if (len < size)
    size = len;
  if (D_status_obufpos && size > D_status_obufpos)
    size = D_status_obufpos;
  ASSERT(len >= 0);
  size = write(D_userfd, D_obuf, size);
  if (size >= 0) 
    {
      len -= size;
      if (len)
	{
	  bcopy(D_obuf + size, D_obuf, len);
	  debug2("ASYNC: wrote %d - remaining %d\n", size, len);
	}
      D_obufp -= size;
      D_obuffree += size;
      if (D_status_obufpos)
	{
	  D_status_obufpos -= size;
	  if (!D_status_obufpos)
	    {
	      debug("finished writing the status message\n");
	      /* we're finished displaying the message! */
	      if (D_status == STATUS_ON_WIN)
		{
		  /* setup continue trigger */
		  D_status_obuflen = D_obuflen;
		  D_status_obuffree = D_obuffree;
		  /* setting obbuffree to 0 will make AddChar call
                   * ResizeObuf */
		  D_obuffree = D_obuflen = 0;
		}
	      gettimeofday(&D_status_time, NULL);
	      SetTimeout(&D_statusev, MsgWait);
	      evenq(&D_statusev);
#ifdef HAVE_BRAILLE
	      RefreshBraille();     /* let user see multiple Msg()s */
#endif
	    }
	}
      if (D_blocked_fuzz)
	{
	  D_blocked_fuzz -= size;
	  if (D_blocked_fuzz < 0)
	    D_blocked_fuzz = 0;
	}
      if (D_blockedev.queued)
	{
	  if (D_obufp - D_obuf > D_obufmax / 2)
	    {
	      debug2("%s: resetting timeout to %g secs\n", D_usertty, D_nonblock/1000.);
	      SetTimeout(&D_blockedev, D_nonblock);
	    }
	  else
	    {
	      debug1("%s: deleting blocked timeout\n", D_usertty);
	      evdeq(&D_blockedev);
	    }
	}
      if (D_blocked == 1 && D_obuf == D_obufp)
	{
	  /* empty again, restart output */
          debug1("%s: buffer empty, unblocking\n", D_usertty);
	  D_blocked = 0;
	  Activate(D_fore ? D_fore->w_norefresh : 0);
	  D_blocked_fuzz = D_obufp - D_obuf;
	}
    }
  else
    {
#ifdef linux
      /* linux flow control is badly broken */
      if (errno == EAGAIN)
	{
	  evdeq(&D_writeev);
	  D_writeev.type = EV_TIMEOUT;
	  D_writeev.handler = disp_writeev_eagain;
	  SetTimeout(&D_writeev, 100);
	  evenq(&D_writeev);
	}
#endif
      if (errno != EINTR && errno != EAGAIN)
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
	if (errno != EWOULDBLOCK)
#endif
	  Msg(errno, "Error writing output to display");
    }
}

/* maximum mouse sequence length is SGR: ESC [ < b ; x ; y M */
#define MAX_MOUSE_SEQUENCE (3+10+1+10+1+10+1)

static void
disp_readev_fn(ev, data)
struct event *ev;
char *data;
{
  /* We have to intercept mouse sequences in order to translate them
   * properly, and an incoming sequence could be spread over multiple
   * I/O reads. When this occurs, we buffer intermediate state in
   * D_mouse_params, and then use the reservation at the front of
   * bufspace to prepend the completed and translated mouse sequence.
   */
  int size;
  char bufspace[MAX_MOUSE_SEQUENCE + IOSIZE];
  unsigned char *buf = (unsigned char*)bufspace + MAX_MOUSE_SEQUENCE;

  struct canvas *cv;

  display = (struct display *)data;

  /* Hmmmm... a bit ugly... */
  if (D_forecv)
    for (cv = D_forecv->c_layer->l_cvlist; cv; cv = cv->c_lnext)
      {
        display = cv->c_display;
        if (D_status == STATUS_ON_WIN)
          RemoveStatus();
      }

  display = (struct display *)data;
  if (D_fore == 0)
    size = IOSIZE;
  else
    {
#ifdef PSEUDOS
      if (W_UWP(D_fore))
	size = sizeof(D_fore->w_pwin->p_inbuf) - D_fore->w_pwin->p_inlen;
      else
#endif
	size = sizeof(D_fore->w_inbuf) - D_fore->w_inlen;
    }

  if (size > IOSIZE)
    size = IOSIZE;
  if (size <= 0)
    size = 1;     /* Always allow one char for command keys */

  size = read(D_userfd, buf, size);
  if (size < 0)
    {
      if (errno == EINTR || errno == EAGAIN)
	return;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
      if (errno == EWOULDBLOCK)
	return;
#endif
      debug1("Read error: %d - hangup!\n", errno);
      Hangup();
      sleep(1);
      return;
    }
  else if (size == 0)
    {
      debug("Found EOF - hangup!\n");
      Hangup();
      sleep(1);
      return;
    }
  if (D_blocked == 4)
    {
      D_blocked = 0;
#ifdef BLANKER_PRG
      KillBlanker();
#endif
      Activate(D_fore ? D_fore->w_norefresh : 0);
      ResetIdle();
      return;
    }
#ifdef ZMODEM
  if (D_blocked > 1)	/* 2, 3 */
    {
      char *bufp;
      struct win *p;

      flayer = 0;
      for (p = windows; p ; p = p->w_next)
	if (p->w_zdisplay == display)
	  {
	    flayer = &p->w_layer;
	    bufp = (char *)buf;
	    while (size > 0)
	      LayProcess(&bufp, &size);
	    return;
	  }
      debug("zmodem window gone, deblocking display");
      zmodem_abort(0, display);
    }
#endif
  if (idletimo > 0)
    ResetIdle();
  if (D_fore)
    D_fore->w_lastdisp = display;

  if (D_mouse && D_forecv)
    {
      unsigned char *bp = (unsigned char *)buf;
      unsigned char *end = bp + size;
      unsigned char *mark = NULL;

      /* When mouse mode is enabled, buffer up incoming CSI until we
       * know whether it is a mouse sequence. If not a mouse event,
       * emit the CSI unchanged; if an invalid mouse event, swallow
       * it; otherwise, translate the sequence and emit it.
       *
       * Supported mouse events take two flavors.
       *
       * VT200: CSI M Cb Cx Cy
       * SGR:   CSI < Ps ; Ps ; Ps M|m
       *
       * UTF-8 and UXRVT modes are explicitly rejected because they
       * introduce incompatibilities with other control sequences.
       *
       * NOTE: applications wishing to use SGR mode will normally
       * enable VT200 mode first as a fallback in case the terminal
       * does not support SGR mode. Thus, we must expect to receive
       * either mode's sequences when mouse mode is enabled. We will
       * dutifully translate whatever arrives, without attempting to
       * convert between modes on behalf of the application.
       */
      switch (D_mouse_parse.state)
        {
        case CSI_PY:
        case CSI_PX:
        case CSI_PB:
          /* Partial mouse sequence; do not restore suppressed
           * characters. We will emit a translated version (if valid)
           * or swallow them (otherwise).
           */
          break;

        case CSI_BEGIN:
          /* Partial CSI; restore suppressed characters in case it
           * turns out not to be a mouse sequence after all.
           */
          *(--buf) = '[';
          ++size;
          /* fall through */

        case CSI_ESC_SEEN:
          /* Escape character; restore it in case this turns out not
           * to be the start of a mouse sequence after all.
           */
          *(--buf) = '\033';
          ++size;
          break;

        default:
          break;
        };

      while (bp != end)
	{
	  unsigned char c = *(bp++);

	  switch (D_mouse_parse.state)
	    {
	    case CSI_INACTIVE:
              if (c == '\033')
                {
                  /* potential escape sequence */
                  mark = bp-1;
                  D_mouse_parse.state = CSI_ESC_SEEN;
                }
	      break;

	    case CSI_ESC_SEEN:
              if (c == '[')
                {
                  /* continue buffering an escape sequence */
                  D_mouse_parse.state = CSI_BEGIN;
                }
              else
                  D_mouse_parse.state = CSI_INACTIVE;
	      break;

	    case CSI_BEGIN:
              if (c == 'M')
                {
                  /* VT200 mouse sequence */
                  D_mouse_parse.state = CSI_PB;
                  D_mouse_parse.sgrmode = 0;
                }
              else if (c == '<')
                {
                  /* SGR mouse sequence */
                  D_mouse_parse.state = CSI_PB;
                  D_mouse_parse.params[D_mouse_parse.state] = 0;
                  D_mouse_parse.sgrmode = 1;
                }
              else
                  D_mouse_parse.state = CSI_INACTIVE;
	      break;

	    case CSI_PB:
	    case CSI_PX:
	    case CSI_PY:
              if (D_mouse_parse.sgrmode)
                {
                  /* SGR mode: parse decimal numbers */
		  if ('0' <= c && c <= '9')
		    {
                      D_mouse_parse.params[D_mouse_parse.state] *= 10;
                      D_mouse_parse.params[D_mouse_parse.state] += c - '0';
                    }
                  else if (D_mouse_parse.state == CSI_PY)
                    {
                      if (c == 'M' || c == 'm')
                        D_mouse_parse.state = CSI_DONE;
                      else
                        D_mouse_parse.state = CSI_INVALID;
                    }
                  else if (c == ';')
                    {
                      D_mouse_parse.state++;
                      D_mouse_parse.params[D_mouse_parse.state] = 0;
                    }
                  else
                    D_mouse_parse.state = CSI_INVALID;
                }
              else
                {
                  /* VT200 mode: read raw binary values */
                  D_mouse_parse.params[D_mouse_parse.state++] = c;
                }
              break;

	    default:
	      break;
	    }

          if (D_mouse_parse.state == CSI_INVALID)
            {
              /* swallow invalid sequence, but emit whatever came before */
              if (buf < mark)
                disp_processinput(display, buf, mark-buf);

              buf = bp;
              size = end - bp;
              D_mouse_parse.state = CSI_INACTIVE;
            }
	  else if (D_mouse_parse.state == CSI_DONE)
	    {
              /* emit whatever came before this sequence */
              if (buf < mark)
                disp_processinput(display, buf, mark-buf);

              buf = bp;
              size = end - bp;

	      int x = D_mouse_parse.params[CSI_PX];
              int y = D_mouse_parse.params[CSI_PY];
              int bias = D_mouse_parse.sgrmode? 1 : 33;

              x -= bias;
              y -= bias;

	      if (x >= D_forecv->c_xs && x <= D_forecv->c_xe && y >= D_forecv->c_ys && y <= D_forecv->c_ye)
	        {
	          if ((D_fore && D_fore->w_mouse) || (D_mousetrack && D_forecv->c_layer->l_mode == 1))
	            {
	              /* Send clicks only if the window is expecting clicks */
	              x -= D_forecv->c_xoff;
	              y -= D_forecv->c_yoff;

	              if (x >= 0 && x < D_forecv->c_layer->l_width && y >= 0 && y < D_forecv->c_layer->l_height)
	                {
                          char tmp[MAX_MOUSE_SEQUENCE+1];
                          int n;

                          x += bias;
                          y += bias;

                          if (D_mouse_parse.sgrmode)
                            {
                              n = snprintf(
                                      tmp, MAX_MOUSE_SEQUENCE, "\033[<%d;%d;%d%c",
                                      D_mouse_parse.params[CSI_PB], x, y, c);
                            }
                          else
                            {
                              n = snprintf(
                                      tmp, MAX_MOUSE_SEQUENCE, "\033[M%c%c%c",
                                      D_mouse_parse.params[CSI_PB], x, y);
                            }

                          if (n > MAX_MOUSE_SEQUENCE)
                                n = MAX_MOUSE_SEQUENCE;

                          /* emit sequence */
                          buf -= n;
                          size += n;
                          memcpy(buf, tmp, n);
                        }
                    }
                }
	      else if (D_mousetrack)
                {
	          /* 'focus' to the clicked region, only on mouse up */
                  int focus = 0;
                  if (D_mouse_parse.sgrmode)
                    focus = (c == 'm');
                  else
                    focus = (D_mouse_parse.params[CSI_PB] == '#');

	          struct canvas *cv = FindCanvas(x, y);
	          if (focus && cv)
	            {
	              SetForeCanvas(display, cv);
	              /* XXX: Do we want to reset the input buffer? */
	            }
	        }

	      D_mouse_parse.state = CSI_INACTIVE;
            }
	}

      if (D_mouse_parse.state != CSI_INACTIVE)
        {
          /* suppress partial sequence at end */
          size = mark? mark - buf : 0;
        }
    }

  if (size > 0)
    disp_processinput(display, buf, size);
}

static void
disp_processinput(display, buf, size)
     struct display* display;
     unsigned char* buf;
     int size;
{
#ifdef ENCODINGS
  if (D_encoding != (D_forecv ? D_forecv->c_layer->l_encoding : 0))
    {
      int i, j, c, enc;
      char buf2[IOSIZE * 2 + 10];
      enc = D_forecv ? D_forecv->c_layer->l_encoding : 0;
      for (i = j = 0; i < size; i++)
	{
	  c = ((unsigned char *)buf)[i];
	  c = DecodeChar(c, D_encoding, &D_decodestate);
	  if (c == -2)
	    i--;	/* try char again */
	  if (c < 0)
	    continue;
	  if (pastefont)
	    {
	      int font = 0;
	      j += EncodeChar(buf2 + j, c, enc, &font);
	      j += EncodeChar(buf2 + j, -1, enc, &font);
	    }
	  else
	    j += EncodeChar(buf2 + j, c, enc, 0);
	  if (j > (int)sizeof(buf2) - 10)	/* just in case... */
	    break;
	}
      (*D_processinput)(buf2, j);
      return;
    }
#endif
  (*D_processinput)((char *)buf, size);
}

static void
disp_status_fn(ev, data)
struct event *ev;
char *data;
{
  display = (struct display *)data;
  debug1("disp_status_fn for display %lx\n", (long)display);
  if (D_status)
    RemoveStatus();
}

static void
disp_hstatus_fn(ev, data)
struct event *ev;
char *data;
{
  display = (struct display *)data;
  if (D_status == STATUS_ON_HS)
    {
      SetTimeout(ev, 1);
      evenq(ev);
      return;
    }
  RefreshHStatus();
}

static void
disp_blocked_fn(ev, data)
struct event *ev;
char *data;
{
  struct win *p;

  display = (struct display *)data;
  debug1("blocked timeout %s\n", D_usertty);
  if (D_obufp - D_obuf > D_obufmax + D_blocked_fuzz)
    {
      debug("stopping output to display\n");
      D_blocked = 1;
      /* re-enable all windows */
      for (p = windows; p; p = p->w_next)
	if (p->w_readev.condneg == &D_obuflenmax)
	  {
	    debug1("freeing window #%d\n", p->w_number);
	    p->w_readev.condpos = p->w_readev.condneg = 0;
	  }
    }
}

#ifdef MAPKEYS
static void
disp_map_fn(ev, data)
struct event *ev;
char *data;
{
  char *p;
  int l, i;
  unsigned char *q;
  display = (struct display *)data;
  debug("Flushing map sequence\n");
  if (!(l = D_seql))
    return;
  p = (char *)D_seqp - l;
  D_seqp = D_kmaps + 3;
  D_seql = 0;
  if ((q = D_seqh) != 0)
    {
      D_seqh = 0;
      i = q[0] << 8 | q[1]; 
      i &= ~KMAP_NOTIMEOUT;
      debug1("Mapping former hit #%d - ", i);
      debug2("%d(%s) - ", q[2], q + 3); 
      if (StuffKey(i))
	ProcessInput2((char *)q + 3, q[2]);
      if (display == 0)
	return;
      l -= q[2];
      p += q[2];
    }
  else
    D_dontmap = 1;
  ProcessInput(p, l);
}
#endif

static void
disp_idle_fn(ev, data)
struct event *ev;
char *data;
{
  struct display *olddisplay;
  display = (struct display *)data;
  debug("idle timeout\n");
  if (idletimo <= 0 || idleaction.nr == RC_ILLEGAL)
    return;
  olddisplay = display;
  flayer = D_forecv->c_layer;
  fore = D_fore;
  DoAction(&idleaction, -1);
  if (idleaction.nr == RC_BLANKER)
    return;
  for (display = displays; display; display = display->d_next)
    if (olddisplay == display)
      break;
  if (display)
    ResetIdle();
}

void
ResetIdle()
{
  if (idletimo > 0)
    {
      SetTimeout(&D_idleev, idletimo);
      if (!D_idleev.queued)
	evenq(&D_idleev);
    }
  else
    evdeq(&D_idleev);
}


#ifdef BLANKER_PRG

static void
disp_blanker_fn(ev, data)
struct event *ev;
char *data;
{
  char buf[IOSIZE], *b;
  int size;

  display = (struct display *)data;
  size = read(D_blankerev.fd, buf, IOSIZE);
  if (size <= 0)
    {
      evdeq(&D_blankerev);
      close(D_blankerev.fd);
      D_blankerev.fd = -1;
      return;
    }
  for (b = buf; size; size--)
    AddChar(*b++);
}

void
KillBlanker()
{
  int oldtop = D_top, oldbot = D_bot;
  struct mchar oldrend;

  if (D_blankerev.fd == -1)
    return;
  if (D_blocked == 4)
    D_blocked = 0;
  evdeq(&D_blankerev);
  close(D_blankerev.fd);
  D_blankerev.fd = -1;
  Kill(D_blankerpid, SIGHUP);
  D_top = D_bot = -1;
  oldrend = D_rend;
  if (D_ME)
    {
      AddCStr(D_ME);
      AddCStr(D_ME);
    }
  else
    {
#ifdef COLOR
      if (D_hascolor)
	AddStr("\033[m\033[m");	/* why is D_ME not set? */
#endif
      AddCStr(D_SE);
      AddCStr(D_UE);
    }
  AddCStr(D_VE);
  AddCStr(D_CE0);
  D_rend = mchar_null;
  D_atyp = 0;
  D_curvis = 0;
  D_x = D_y = -1;
  ChangeScrollRegion(oldtop, oldbot);
  SetRendition(&oldrend);
  ClearAll();
}

void
RunBlanker(cmdv)
char **cmdv;
{
  char *m;
  int pid;
  int slave = -1;
  int ptype = 0;
  char termname[MAXTERMLEN + 6];
#ifndef TIOCSWINSZ
  char libuf[20], cobuf[20];
#endif
  char **np;

  strcpy(termname, "TERM=");
  strncpy(termname + 5, D_termname, MAXTERMLEN - 6);
  termname[sizeof(termname) - 1] = 0;
  KillBlanker();
  D_blankerpid = -1;
  if ((D_blankerev.fd = OpenDevice(cmdv, 0, &ptype, &m)) == -1)
    {
      Msg(0, "OpenDevice failed");
      return;
    }
#ifdef O_NOCTTY
  if (pty_preopen)
    {
      if ((slave = open(m, O_RDWR|O_NOCTTY)) == -1)
	{
	  Msg(errno, "%s", m);
	  close(D_blankerev.fd);
	  D_blankerev.fd = -1;
	  return;
	}
    }
#endif
  switch (pid = (int)fork())
    {
    case -1:
      Msg(errno, "fork");
      close(D_blankerev.fd);
      D_blankerev.fd = -1;
      close(slave);
      return;
    case 0:
      displays = 0;
      ServerSocket = -1;
#ifdef SIGPIPE
      signal(SIGPIPE, SIG_DFL);
#endif
      if (setgid(real_gid) || setuid(real_uid))
        Panic(errno, "setuid/setgid");
      eff_uid = real_uid;
      eff_gid = real_gid;
      brktty(D_userfd);
      freetty();
#ifdef DEBUG
      if (dfp && dfp != stderr)
	  fclose(dfp);
#endif
      if (slave != -1)
	{
	  close(0);
	  dup(slave);
	  close(slave);
	  closeallfiles(D_blankerev.fd);
	  slave = dup(0);
	}
      else
        closeallfiles(D_blankerev.fd);
#ifdef DEBUG
      if (dfp)
        {
          char buf[256];

	  sprintf(buf, "%s/screen.blanker", DEBUGDIR);
	  if ((dfp = fopen(buf, "a")) == 0)
	    dfp = stderr;
	  else
	    (void) chmod(buf, 0666);
	}
      debug1("=== RunBlanker: pid %d\n", (int)getpid());
#endif
      close(0);
      close(1);
      close(2);
      if (open(m, O_RDWR))
	Panic(errno, "Cannot open %s", m);
      dup(0);
      dup(0);
      close(D_blankerev.fd);
      if (slave != -1)
	close(slave);
      InitPTY(0);
      fgtty(0);
      SetTTY(0, &D_OldMode);
      np = NewEnv + 3;
      *np++ = NewEnv[0];
      *np++ = termname;
#ifdef TIOCSWINSZ
      glwz.ws_col = D_width;
      glwz.ws_row = D_height;
      (void)ioctl(0, TIOCSWINSZ, (char *)&glwz);
#else
      /* Always turn off nonblocking mode */
      (void)fcntl(0, F_SETFL, 0);
      sprintf(libuf, "LINES=%d", D_height);
      sprintf(cobuf, "COLUMNS=%d", D_width);
      *np++ = libuf;
      *np++ = cobuf;
#endif
      debug1("calling execvpe %s\n", *cmdv);
      execvpe(*cmdv, cmdv, NewEnv + 3);
      debug1("exec error: %d\n", errno);
      Panic(errno, "Cannot exec '%s'", *cmdv);
    default:
      break;
    }
  D_blankerpid = pid;
  evenq(&D_blankerev);
  D_blocked = 4;
  ClearAll();
  close(slave);
}

#endif /* BLANKER_PRG */


