/* 
 * An original curses library for EUC-kanji by Akinori ITO,     December 1989
 * revised by Akinori ITO, January 1995
 */
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "config.h"
#include <string.h>
#include <math.h>
#ifdef HAVE_WAITPID
#include <sys/wait.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef USE_MOUSE
#ifdef USE_GPM
#include <gpm.h>
static int gpm_process_mouse(Gpm_Event *event, CookedEvent *cev);
#endif				/* USE_GPM */
#ifdef USE_SYSMOUSE
#include <osreldate.h>
#if (__FreeBSD_version >= 400017)
#include <sys/consio.h>
#include <sys/fbio.h>
#else
#include <machine/console.h>
#endif
static int process_sysmouse_event(RawEvent *rev);
static int sysm_process_mouse(int x, int y, int nbs, int obs, CookedEvent *cev);
static int sysmouse_active;
static int sysmouse_nevents;
static int cwidth = 8, cheight = 16;
static int xpix, ypix, nbs, obs = 0;
#endif				/* USE_SYSMOUSE */

#ifndef __CYGWIN__
static
#endif
int is_xterm = 0;

void mouse_init(), mouse_end();
#endif				/* USE_MOUSE */

static char *title_str = NULL;

static int tty;

#include "terms.h"
#include "fm.h"
#include "myctype.h"

#ifdef __EMX__
#define INCL_DOSNLS
#include <os2.h>

#ifndef JP_CHARSET
extern int	CodePage;
#endif				/* !JP_CHARSET */
#endif				/* __EMX__ */

static int
cmp_tv(const struct timeval *tv_a, const struct timeval *tv_b)
{
    return ((tv_a->tv_sec < tv_b->tv_sec) ? -1 :
	    (tv_a->tv_sec > tv_b->tv_sec) ? 1 :
	    (tv_a->tv_usec < tv_b->tv_usec) ? -1 :
	    (tv_a->tv_usec > tv_b->tv_usec) ? 1 :
	    0);
}

static int
subtract_tv(struct timeval *dst, const struct timeval *src)
{
    if ((dst->tv_usec -= src->tv_usec) < 0) {
	--(dst->tv_sec);
	dst->tv_usec += 1000000;
    }

    return ((dst->tv_sec -= src->tv_sec) < 0 ? -1 :
	    !dst->tv_sec ? (dst->tv_usec < 0 ? -1 : !dst->tv_usec ? 0 : 1) : 1);
}

static void
add_tv(struct timeval *dst, const struct timeval *src)
{
    if ((dst->tv_usec += src->tv_usec) >= 1000000) {
	++(dst->tv_sec);
	dst->tv_usec -= 1000000;
    }

    dst->tv_sec += src->tv_sec;
}

#if defined(__CYGWIN__)
#include <windows.h>
static HANDLE hConIn;
static int isWin95;
static int isWinConsole;
static char *ConInV;
static int iConIn, nConIn, nConInMax;
#ifdef USE_MOUSE
static MOUSE_EVENT_RECORD lastConMouse;
#endif

void
check_win32_console(void)
{
    char *tty;

    tty = ttyname(1);
    if (!strncmp(tty, "/dev/con", 8)) {
	isWinConsole = TRUE;
    }
    else {
	isWinConsole = FALSE;
    }
}

void
init_win32_console_handle(void)
{
    OSVERSIONINFO winVersionInfo;

    check_win32_console();
    winVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (GetVersionEx(&winVersionInfo) == 0) {
	fprintf(stderr, "can't get Windows version information.\n");
	exit(1);
    }
    if (winVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
	isWin95 = 1;
    }
    hConIn = INVALID_HANDLE_VALUE;
    if (isWin95) {
	if (isWinConsole) {
	    if (isatty(0)) {
		hConIn = GetStdHandle(STD_INPUT_HANDLE);
	    }
	    else {
		hConIn = CreateFile("CONIN$", GENERIC_READ,
				    FILE_SHARE_READ,
				    NULL, OPEN_EXISTING, 0, NULL);
	    }
	}
    }
}

static void
expand_win32_console_input_buffer(int n)
{
    if (nConIn + n >= nConInMax) {
	char *oldv;

	nConInMax = ((nConIn + n) / 2 + 1) * 3;
	oldv = ConInV;
	ConInV = GC_MALLOC_ATOMIC(nConInMax);
	memcpy(ConInV, oldv, nConIn);
    }
}

static int
read_win32_console_input(void)
{
    INPUT_RECORD rec;
    DWORD nevents;

    if (PeekConsoleInput(hConIn, &rec, 1, &nevents) && nevents) {
	switch (rec.EventType) {
	case KEY_EVENT:
	    expand_win32_console_input_buffer(6);

	    if (ReadConsole(hConIn, &ConInV[nConIn], 1, &nevents, NULL)) {
		nConIn += nevents;
		return nevents;
	    }

	    break;
#ifdef USE_MOUSE
	case MOUSE_EVENT:
	    if ((lastConMouse.dwButtonState ^ rec.Event.MouseEvent.dwButtonState) & ~(~0 << 5)) {
		int down;
		MOUSE_EVENT_RECORD *mer;

		expand_win32_console_input_buffer(6);
		mer = &rec.Event.MouseEvent;
		ConInV[nConIn] = '\033';
		ConInV[nConIn + 1] = '[';
		ConInV[nConIn + 2] = 'M';

		if (~(mer->dwButtonState) & lastConMouse.dwButtonState & ~(~0 << 5))
		    ConInV[nConIn + 3] = MOUSE_BTN_UP_TERM + ' ';
		else {
		    down = mer->dwButtonState & ~lastConMouse.dwButtonState & ~(~0 << 5);
		    ConInV[nConIn + 3] = (down & (1 << 0) ? MOUSE_BTN1_DOWN :
					  down & (1 << 1) ? MOUSE_BTN3_DOWN :
					  down & (1 << 2) ? MOUSE_BTN2_DOWN :
					  down & (1 << 3) ? MOUSE_BTN4_DOWN_XTERM :
					  MOUSE_BTN5_DOWN_XTERM) + ' ';
		}

		ConInV[nConIn + 4] = mer->dwMousePosition.X + '!';
		ConInV[nConIn + 5] = mer->dwMousePosition.Y + '!';
		nConIn += 6;
		lastConMouse = *mer;
		ReadConsoleInput(hConIn, &rec, 1, &nevents);
		return 6;
	    }
#endif
	default:
	    break;
	}

	ReadConsoleInput(hConIn, &rec, 1, &nevents);
    }

    return 0;
}

int
read_win32_console(char *s, int n)
{
    KEY_EVENT_RECORD *ker;
    DWORD nevents;

    if (hConIn == INVALID_HANDLE_VALUE)
	return read(tty, s, n);

    if (n > 0) {
	int doinput;

	for (doinput = !(iConIn < nConIn) ;;) {
	    if (iConIn < nConIn) {
		if (n > nConIn - iConIn)
		    n = nConIn - iConIn;

		memcpy(s, ConInV, n);

		if ((iConIn += n) >= nConIn)
		    iConIn = nConIn = 0;

		break;
	    }

	    if (doinput) {
		iConIn = nConIn = 0;

		while (!read_win32_console_input())
		    ;

		while (GetNumberOfConsoleInputEvents(hConIn, &nevents) && nevents)
		    read_win32_console_input();

		doinput = 0;
	    }
	    else {
		n = 0;
		break;
	    }
	}
    }

    return n;
}

int
select_or_poll_win32_console(int n, fd_set *rfds, fd_set *wfds,
			     fd_set *efds, struct timeval *tout)
{
    int m;
    DWORD nevents;

    if ((m = select(n, rfds, wfds, efds, tout)) < 0)
	return m;

    if (iConIn < nConIn) {
	FD_SET(tty, rfds);
	++m;
    }
    else {
	iConIn = nConIn = 0;

	while (GetNumberOfConsoleInputEvents(hConIn, &nevents) && nevents)
	    read_win32_console_input();

	if (nConIn) {
	    FD_SET(tty, rfds);
	    ++m;
	}
    }

    return m;
}

int
select_win32_console(int n, fd_set *rfds, fd_set *wfds, fd_set *efds,
		     struct timeval *tout)
{
    static struct timeval polltv = {
#if CLOCKS_PER_SEC > 1
	0, 1000000 / CLOCKS_PER_SEC
#else
	0, 10000
#endif
    };
    int m;
    struct timeval tv;

    if (hConIn == INVALID_HANDLE_VALUE || tty < 0 || tty >= n || !rfds
	|| !FD_ISSET(tty, rfds))
	return select(n, rfds, wfds, efds, tout);

    FD_CLR(tty, rfds);

    if (tout) {
	while (cmp_tv(tout, &polltv) > 0) {
	    tv = polltv;

	    if ((m = select_or_poll_win32_console(n, rfds, wfds, efds, &tv)))
		return m;

	    subtract_tv(tout, &polltv);
	}

	return select_or_poll_win32_console(n, rfds, wfds, efds, tout);
    }
    else
	for (;;) {
	    tv = polltv;

	    if ((m = select_or_poll_win32_console(n, rfds, wfds, efds, &tv)))
		return m;
	}
}

#endif

char *getenv(const char *);
MySignalHandler reset_exit(SIGNAL_ARG), error_dump(SIGNAL_ARG);
void setlinescols(void);
void flush_tty();
static RawEvent tty_event;
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
static RawEvent tty_mouse_event;
#else
#define tty_mouse_event tty_event
#endif

#ifndef SIGIOT
#define SIGIOT SIGABRT
#endif				/* not SIGIOT */

#ifdef HAVE_TERMIO_H
#include        <sys/ioctl.h>
#include        <termio.h>
typedef struct termio TerminalMode;
#define TerminalSet(fd,x)       ioctl(fd,TCSETA,x)
#define TerminalGet(fd,x)       ioctl(fd,TCGETA,x)
#define MODEFLAG(d)     ((d).c_lflag)
#define IMODEFLAG(d)    ((d).c_iflag)
#endif				/* HAVE_TERMIO_H */

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#include <unistd.h>
typedef struct termios TerminalMode;
#define TerminalSet(fd,x)       tcsetattr(fd,TCSANOW,x)
#define TerminalGet(fd,x)       tcgetattr(fd,x)
#define MODEFLAG(d)     ((d).c_lflag)
#define IMODEFLAG(d)    ((d).c_iflag)
#endif				/* HAVE_TERMIOS_H */

#ifdef HAVE_SGTTY_H
#include        <sys/ioctl.h>
#include        <sgtty.h>
typedef struct sgttyb TerminalMode;
#define TerminalSet(fd,x)       ioctl(fd,TIOCSETP,x)
#define TerminalGet(fd,x)       ioctl(fd,TIOCGETP,x)
#define MODEFLAG(d)     ((d).sg_flags)
#endif				/* HAVE_SGTTY_H */

#define MAX_LINE        200
#define MAX_COLUMN      400

/* Screen properties */
#define S_SCREENPROP    0x0f
#define S_NORMAL        0x00
#define S_STANDOUT      0x01
#define S_UNDERLINE     0x02
#define S_BOLD          0x04
#define S_EOL           0x08

/* Sort of Character */
#define C_WHICHCHAR     0xc0
#define C_ASCII         0x00
#if defined(JP_CHARSET) || defined(MANY_CHARSET)
#define C_WCHAR1        0x40
#define C_WCHAR2        0x80
#endif				/* JP_CHARSET */
#define C_GRAPHICS      0xc0

#define CHMODE(c)       ((c)&C_WHICHCHAR)
#define SETCHMODE(var,mode)     var = (((var)&~C_WHICHCHAR) | mode)

/* Charactor Color */
#define COL_FCOLOR      0xf00
#define COL_FBLACK      0x800
#define COL_FRED        0x900
#define COL_FGREEN      0xa00
#define COL_FYELLOW     0xb00
#define COL_FBLUE       0xc00
#define COL_FMAGENTA    0xd00
#define COL_FCYAN       0xe00
#define COL_FWHITE      0xf00
#define COL_FTERM       0x000

#define S_COLORED       0xf00

#ifdef USE_BG_COLOR
/* Background Color */
#define COL_BCOLOR      0xf000
#define COL_BBLACK      0x8000
#define COL_BRED        0x9000
#define COL_BGREEN      0xa000
#define COL_BYELLOW     0xb000
#define COL_BBLUE       0xc000
#define COL_BMAGENTA    0xd000
#define COL_BCYAN       0xe000
#define COL_BWHITE      0xf000
#define COL_BTERM       0x0000

#define S_BCOLORED      0xf000
#endif				/* USE_BG_COLOR */


#define S_COMBINED      0x10

#define S_DIRTY         0x20

#define SETPROP(var,prop) (var = (((var)&S_DIRTY) | prop))

/* Line status */
#define L_DIRTY         0x01
#define L_UNUSED        0x02
#define L_NEED_CE       0x04
#define L_CLRTOEOL      0x08

#define ISDIRTY(d)      ((d) & L_DIRTY)
#define ISUNUSED(d)     ((d) & L_UNUSED)
#define NEED_CE(d)      ((d) & L_NEED_CE)

typedef unsigned short l_prop;

#ifdef MANY_CHARSET
struct sccv_st;

typedef union scchar_st {
    mb_wchar_t wc;
    struct sccv_st *sccv;
} scchar_t;

typedef struct sccv_st {
    size_t size;
    size_t len;
    scchar_t v[1];
} sccv_t;

#endif

typedef struct scline {
#ifdef MANY_CHARSET
    scchar_t *lineimage;
#else
    char *lineimage;
#endif
    l_prop *lineprop;
    short isdirty;
    short eol;
} Screen;

static TerminalMode d_ioval;
static FILE *ttyf;

static
char bp[1024], funcstr[256];

char *T_cd, *T_ce, *T_kr, *T_kl, *T_cr, *T_bt, *T_ta, *T_sc, *T_rc,
*T_so, *T_se, *T_us, *T_ue, *T_cl, *T_cm, *T_al, *T_sr, *T_md, *T_me,
*T_ti, *T_te, *T_nd, *T_as, *T_ae, *T_eA, *T_ac, *T_op;

int LINES, COLS, LASTLINE, INPUTLINE;
static int max_LINES = 0, max_COLS = 0;
static int tab_step = 8;
static int CurLine, CurColumn;
static Screen *ScreenElem = NULL, **ScreenImage = NULL, **ScreenScroll;
static l_prop CurrentMode = 0;
static int graph_enabled = 0;

static fd_set read_fds_in;
static fd_set read_fds_out;
int read_nfd;
void (**read_hookv)(int, void *);
void (**close_hookv)(int, void *);
void **read_hook_argv;
static int read_hookv_size;

void
record_read_fd(int rfd, void (*read_hook)(int, void *), void (*close_hook)(int, void *), void *hook_arg)
{
    if (rfd >= 0) {
	FD_SET(rfd, &read_fds_in);

	if (read_nfd <= rfd)
	    read_nfd = rfd + 1;

	new_objv(&read_hookv_size, rfd,
		 &read_hookv, sizeof(void (*)(int, void *)), 0,
		 &close_hookv, sizeof(void (*)(int, void *)), 0,
		 &read_hook_argv, sizeof(void *), 0,
		 NULL);

	if (read_hook || close_hook || hook_arg) {
	    read_hookv[rfd] = read_hook;
	    close_hookv[rfd] = close_hook;
	    read_hook_argv[rfd] = hook_arg;
	}
    }
}

void
change_read_fd(int rfd, void (*hook)(int, void *), void *hook_arg)
{
    if (rfd >= 0 && rfd < read_nfd && FD_ISSET(rfd, &read_fds_in)) {
	read_hookv[rfd] = hook;
	read_hook_argv[rfd] = hook_arg;
    }
}

void
clear_read_fd(int *p_rfd, int istemp)
{
    int rfd;

    rfd = *p_rfd;

    if (rfd >= 0 && rfd < read_nfd && FD_ISSET(rfd, &read_fds_in)) {
	FD_CLR(rfd, &read_fds_in);
	FD_CLR(rfd, &read_fds_out);

	if (!(istemp & CLEAR_READ_FD_KEEPHOOK)) {
	    read_hookv[rfd] = NULL;
	    read_hook_argv[rfd] = NULL;
	}

	if (rfd + 1 >= read_nfd) {
	    while (rfd > 0 && !FD_ISSET(rfd - 1, &read_fds_in))
		--rfd;

	    read_nfd = rfd;
	}
    }

    if (!(istemp & CLEAR_READ_FD_KEEPFD)) {
	if (*p_rfd >= 0)
	    close(*p_rfd);

	*p_rfd = -1;
    }
}

void
clear_all_read_fds(void)
{
    while (read_nfd > 0) {
	--read_nfd;

	if (close_hookv[read_nfd])
	    close_hookv[read_nfd](read_nfd, read_hook_argv[read_nfd]);

	if (read_hookv[read_nfd])
	    close(read_nfd);

	FD_CLR(read_nfd, &read_fds_in);
	FD_CLR(read_nfd, &read_fds_out);
	read_hookv[read_nfd] = NULL;
	read_hook_argv[read_nfd] = NULL;
    }
}

int
is_recorded_read_fd(int rfd)
{
    return rfd >= 0 && rfd < read_nfd && FD_ISSET(rfd, &read_fds_in);
}

int
is_hook_recorded_read_fd(int rfd)
{
    return rfd >= 0 && rfd < read_nfd && read_hookv[rfd];
}

#ifdef SIGWINCH
static int sigwinchp;

MySignalHandler
resize_hook(SIGNAL_ARG)
{
    ++sigwinchp;
    signal(SIGWINCH, resize_hook);
    SIGNAL_RETURN;
}

static void
do_resize(int fmini)
{
    for (; fmini && sigwinchp > 0 ; --sigwinchp) {
	setlinescols();

#ifdef USE_IMAGE
	if (activeImage) {
	    termImage(-1);
	    initImage();
	}
#endif

	setupscreen();

	if (Currentbuf) {
	    displayCurrentView(NULL);

	    if (next_dcompl_top >= 0)
		next_dcompl(next_dcompl_next, next_dcompl_env);
#ifdef USE_MENU
	    else if (CurrentMenu && CurrentMenuPopup) {
		draw_all_menu(CurrentMenu);
		select_menu(CurrentMenu, CurrentMenu->select);
	    }
#endif
	}

	signal(SIGWINCH, resize_hook);
    }
}
#endif				/* SIGWINCH */

#ifdef USE_IMAGE
static int
displayCurrentBuffers(BufferView *v, int display_p)
{
    if (v) {
	if (v->frameset) {
	    int i, n;

	    for (i = 0, n = v->nrows * v->ncols ; i < n ; ++i)
		display_p = displayCurrentBuffers(v->subv[i].top, display_p);
	}
	else if (v->top && !v->top->async_buf && !v->top->image_unloaded &&
		 (v->top->need_reshape || v->top->redraw_mode)) {
	    displayBuffer(v->top, B_NORMAL);
	    display_p = TRUE;
	}
    }

    return display_p;
}
#endif

static int sigintp;

static MySignalHandler
ign_sigint(SIGNAL_ARG)
{
    sigintp = 1;
    signal(SIGINT, ign_sigint);
    SIGNAL_RETURN;
}

static void
raise_sigint(MySignalHandler (**trap)(SIGNAL_ARG), int last_p)
{
    signal(SIGINT, *trap);
    sigintp = 0;
    raise(SIGINT);

    if (!last_p)
	*trap = signal(SIGINT, ign_sigint);
}

struct cmd_crontab_arg {
    int cmd;
    void *cmd_arg;
    Buffer *argbuf;
};

static struct crontab_entry {
    void (*func)(void *);
    void *arg;
    struct timeval at;
} *crontab;
static int ncrontab;
static int ncrontab_max;

static int
cmp_crontab(const void *a, const void *b)
{
    return cmp_tv(&((struct crontab_entry *)a)->at,
		  &((struct crontab_entry *)b)->at);
}

void
record_raw_crontab(void (*func)(void *), void *arg, int relative, long seconds)
{
    NEW_OBJV1(&ncrontab_max, ncrontab, &crontab, sizeof(struct crontab_entry), 0);
    crontab[ncrontab].func = func;
    crontab[ncrontab].arg = arg;

    if (relative) {
	gettimeofday(&crontab[ncrontab].at, NULL);
	crontab[ncrontab].at.tv_sec += seconds;
    }
    else {
	crontab[ncrontab].at.tv_sec = seconds;
	crontab[ncrontab].at.tv_usec = 0;
    }

    ++ncrontab;
    qsort(crontab, ncrontab, sizeof(crontab[0]), cmp_crontab);
}

static void
exec_crontab_cmd(void *arg)
{
    struct cmd_crontab_arg *cca;

    cca = arg;
    CurrentKeyData = cca->cmd_arg;
    ForcedKeyData = NULL;
    TargetX = TargetY = PrevTargetX = PrevTargetY = -1;
    Argumentbuf = cca->argbuf;
    CurrentCmdData = NULL;
    w3mFuncList[cca->cmd].func.main_func();
    processBufferEvents(Currentbuf);
    ForcedKeyData = CurrentKeyData = NULL;
    TargetX = TargetY = PrevTargetX = PrevTargetY = -1;
    Argumentbuf = NULL;
}

void
record_crontab(int cmd, void *cmd_arg, int relative, long seconds, Buffer *argbuf)
{
    struct cmd_crontab_arg *cca;

    cca = New(struct cmd_crontab_arg);
    cca->cmd = cmd;
    cca->cmd_arg = cmd_arg;
    cca->argbuf = argbuf;
    record_raw_crontab(exec_crontab_cmd, cca, relative, seconds);
}

#ifdef MANY_CHARSET
static char tty_mb_r_buf[PIPE_BUF];
static mb_info_t tty_mb_r_info = {};
#define read_tty_chars (tty_mb_r_buf)
#define read_tty_nchars (tty_mb_r_info.e)
#define read_tty_cur (tty_mb_r_info.i)
#define read_tty_keep (tty_mb_r_info.b)
#else
static char read_tty_chars[PIPE_BUF];
static int read_tty_nchars;
static int read_tty_cur;
#endif

static struct timeval display_delta = {
#if CLOCKS_PER_SEC > 1
    0, 1000000 / CLOCKS_PER_SEC,
#else
    0, 10000
#endif
};

static int
setup_timeout(const struct timeval *timeout, const struct timeval *beg,
	      struct timeval *cur, struct timeval *tgoal, struct timeval **p_dest)
{
    int may_break;

    *p_dest = NULL;
    gettimeofday(cur, NULL);

    if (timeout) {
	*tgoal = *beg;
	add_tv(tgoal, timeout);
	*p_dest = tgoal;
    }

    if (ncrontab && (!*p_dest || cmp_tv(*p_dest, &crontab[0].at) > 0)) {
	*tgoal = crontab[0].at;
	*p_dest = tgoal;
    }

    if (*p_dest && cmp_tv(*p_dest, cur) < 0)
	**p_dest = *cur;

    may_break = *p_dest && subtract_tv(*p_dest, cur) <= 0;

    if (!*p_dest || cmp_tv(*p_dest, &display_delta) > 0) {
	*tgoal = display_delta;
	*p_dest = tgoal;
	may_break = FALSE;
    }

    return may_break;
}

static void do_refresh(void);
static int need_refresh;
static char *need_term_title;
static char *prev_term_title;
static void do_term_title(void);
#ifdef USE_IMAGE
static int needDrawImage;
static void doDrawImage(void);
static void untouchImageRegion(void);
#endif

int
check_tty_input(RawEvent *special)
{
    if (tty >= 0 && FD_ISSET(tty, &special->rfds)) {
	if (need_refresh)
	    do_refresh();
#ifdef USE_IMAGE
	else if (needDrawImage)
	    doDrawImage();
#endif

	if (read_tty_cur < read_tty_nchars) {
	    FD_CLR(tty, &special->rfds);
	    return 1;
	}

#ifdef MANY_CHARSET
	if (read_tty_keep > 0) {
	    if ((read_tty_nchars -= read_tty_keep) > 0)
		memmove(read_tty_chars, read_tty_chars + read_tty_keep, read_tty_nchars);

	    read_tty_cur -= read_tty_keep;
	    read_tty_keep = 0;
	}
#else
	read_tty_cur = read_tty_nchars = 0;
#endif
    }

    return 0;
}

int
wait_until(RawEvent *special, struct timeval *timeout)
{
    int i, nfound, n, restarted = 0, fmini;
    int tty_found = 0;
    struct timeval beg, cur, tm, *ptm;
    MySignalHandler (*prev)(SIGNAL_ARG) = NULL;
    MySignalHandler (*temp)(SIGNAL_ARG) = NULL;

    if ((n = check_tty_input(special)))
	return n;

#if defined(USE_MOUSE) && defined(USE_SYSMOUSE)
    if ((n = process_sysmouse_event(special)))
	return n;
#endif

    gettimeofday(&beg, NULL);
    cur = beg;
restart:
    sigintp = 0;

    if (!restarted)
	prev = signal(SIGINT, ign_sigint);

    setup_timeout(timeout, &beg, &cur, &tm, &ptm);

    for (n = 0, fmini = fmInitialized ;;) {
	for (i = 0 ; i < read_nfd ; ++i)
	    if (FD_ISSET(i, &read_fds_in))
		FD_SET(i, &read_fds_out);

	if (fmini && sigintp)
	    raise_sigint(&prev, 0);

	while ((nfound =
#ifdef __CYGWIN__
		select_win32_console(read_nfd, &read_fds_out, NULL, NULL, ptm)
#else
		select(read_nfd, &read_fds_out, NULL, NULL, ptm)
#endif
		) == -1) {
#ifdef SIGWINCH
	    do_resize(fmini);
#endif				/* SIGWINCH */

	    if (fmini && sigintp)
		raise_sigint(&prev, 0);

#if defined(USE_MOUSE) && defined(USE_SYSMOUSE)
	    if (process_sysmouse_event(special)) {
		++n;
		goto finish;
	    }
#endif

	    if (setup_timeout(timeout, &beg, &cur, &tm, &ptm)) {
		nfound = 0;
		break;
	    }

	    if (fmini && sigintp)
		raise_sigint(&prev, 0);
	}

#ifdef SIGWINCH
	do_resize(fmini);
#endif				/* SIGWINCH */

	if (kept_sock < 0 && !nfound) {
#ifdef USE_IMAGE
	    Buffer *cur;
#endif

	    expireKeptAsyncRWBuf();
#ifdef USE_IMAGE
	    cur = Currentbuf;

#if defined(USE_MENU) && defined(USE_MENU_BUFFER)
	    if (cur && cur->menu)
		cur = cur->menu->current;
#endif

	    if (cur) {
		checkUnloadedImage(NULL);

		if (displayCurrentBuffers(cur->view->root, FALSE)) {
		    if (next_dcompl_top >= 0)
			next_dcompl(next_dcompl_next, next_dcompl_env);
#ifdef USE_MENU
		    else if (CurrentMenu && CurrentMenuPopup) {
			draw_all_menu(CurrentMenu);
			select_menu(CurrentMenu, CurrentMenu->select);
		    }
#endif
		}
	    }
#endif

	    if (need_refresh)
		do_refresh();
#ifdef USE_IMAGE
	    else if (needDrawImage)
		doDrawImage();
#endif
	}

	for (i = 0 ; i < read_nfd && nfound > 0 ; ++i) {
	    if (fmini && sigintp)
		raise_sigint(&prev, 0);

	    if (FD_ISSET(i, &read_fds_out)) {
		if (FD_ISSET(i, &special->rfds)) {
		    if (i == tty)
			++tty_found;

		    FD_CLR(i, &special->rfds);
		    ++n;
		}

		if (FD_ISSET(i, &read_fds_in) && read_hookv[i]) {
		    read_hookv[i](i, read_hook_argv[i]);

		    if ((temp = signal(SIGINT, ign_sigint)) != ign_sigint)
			prev = temp;
		}

		FD_CLR(i, &read_fds_out);
		--nfound;
	    }
	}

	if (n || setup_timeout(timeout, &beg, &cur, &tm, &ptm))
	    break;

	if ((n = check_tty_input(special)))
	    return n;
    }

    if (fmini && sigintp)
	raise_sigint(&prev, 0);
#if defined(USE_MOUSE) && defined(USE_SYSMOUSE)
finish:
#endif
    if (ncrontab) {
	do {
	    void (*cfunc)(void *arg);
	    void *carg;

	    if (cmp_tv(&crontab[0].at, &cur) > 0)
		break;

	    cfunc = crontab[0].func;
	    carg = crontab[0].arg;

	    for (i = 1 ; i < ncrontab ; ++i)
		crontab[i - 1] = crontab[i];

	    memset(&crontab[--ncrontab], 0, sizeof(crontab[0]));
	    cfunc(carg);

	    if ((temp = signal(SIGINT, ign_sigint)) != ign_sigint)
		prev = temp;
	} while (ncrontab);

	if (!n && !setup_timeout(timeout, &beg, &cur, &tm, &ptm)) {
	    restarted = 1;
	    goto restart;
	}
    }

    if (tty_found &&
	read_tty_nchars < sizeof(read_tty_chars)) {
	int n;

	while ((n =
#ifdef __CYGWIN__
		read_win32_console(read_tty_chars + read_tty_nchars, sizeof(read_tty_chars) - read_tty_nchars)
#else
		read(tty, read_tty_chars + read_tty_nchars, sizeof(read_tty_chars) - read_tty_nchars)
#endif
		) <= 0)
	    ;

	read_tty_nchars += n;
    }

    if (fmini && sigintp)
	raise_sigint(&prev, 1);
    else
	signal(SIGINT, prev);

    return n;
}

int
wait_user_event_and(int fd, struct timeval *timeout)
{
    RawEvent rev;

    rev = tty_mouse_event;

    if (fd >= 0 && fd < read_nfd && fd != tty)
	FD_SET(fd, &rev.rfds);

    return wait_until(&rev, timeout);
}

#ifdef MANY_CHARSET

mb_setup_t tty_mb_r_setup = {};
mb_setup_t tty_mb_w_setup = {};
static char tty_mb_w_buf[PIPE_BUF];
static mb_info_t *tty_mb_w_p_info = NULL, dummy_info = {};
const char *tty_initial_input_charset = NULL;
const char *tty_initial_output_charset = NULL;
mb_ws_conv_t *tty_input_converters = NULL;
mb_ws_conv_t *tty_output_converters = NULL;
mb_ws_conv_t *tty_fallback_converters = NULL;

static size_t
tty_mb_read(char *s, size_t n, void *arg)
{
    return 0;
}

mb_wchar_t
tty_getwc(void)
{
    mb_wchar_t wc;
loop:
    switch (wc = mb_encode_to_wchar(&tty_mb_r_info)) {
    case mb_notchar_eof:
    case mb_notchar_enc_short:
	read_tty_cur = read_tty_nchars;
	getch_internal(GETCH_MODE_RST);
	goto loop;
    default:
	mb_apply_convv(&wc, &wc + 1, tty_input_converters, &tty_mb_r_info);
    }

    return wc;
}

void
setup_tty_mb_r(const char *frmt, ...)
{
    va_list ap;

    va_start(ap, frmt);
    mb_vsetsetup(&tty_mb_r_setup, frmt, ap);

    if (tty_mb_r_info.io_arg == &tty_mb_r_setup)
	mb_setup(&tty_mb_r_info, &tty_mb_r_setup, "|", MB_FLAG_NOSSL);

    va_end(ap);
}

static size_t
dummy_write(const char *buf, size_t n, void *ap)
{
    return 0;
}

void
setup_tty_mb_w(const char *frmt, ...)
{
    va_list ap;

    va_start(ap, frmt);
    mb_vsetsetup(&tty_mb_w_setup, frmt, ap);

    if (ttyf) {
	mb_fflush(ttyf);
	mb_fsetup(ttyf, "w!|", &tty_mb_w_setup,
		  MB_FLAG_ASCIIATCTL | MB_FLAG_NOSSL | MB_FLAG_DISCARD_NOTPREFERED_CHAR);
    }
    else {
	dummy_info.buf = tty_mb_w_buf;
	dummy_info.size = sizeof(tty_mb_w_buf);
	mb_init_w(&dummy_info, NULL, dummy_write, &tty_mb_w_setup, "|",
		  MB_FLAG_ASCIIATCTL | MB_FLAG_NOSSL | MB_FLAG_DISCARD_NOTPREFERED_CHAR);
	tty_mb_w_p_info = &dummy_info;
    }

    va_end(ap);
}

tty_char_conv_t tty_char_conv_tab = {NULL};
char *term_char_repl = NULL;

static size_t
tty_char_conv_default_alt(mb_wchar_t *wbuf)
{
    mb_wchar_t *q;

    q = tty_char_conv_mbs2wcs((term_char_repl && *term_char_repl) ? term_char_repl : "?", -1, wbuf);
    return q - wbuf;
}

static mb_info_t *
tty_check_write_info(mb_info_t *info)
{
    if (!info &&
	!(info = tty_mb_w_p_info)) {
	static mb_info_t tinfo, *p_tinfo;

	if (!p_tinfo) {
	    p_tinfo = &tinfo;
	    mb_ces_by_name("us-ascii", p_tinfo);
	}

	info = p_tinfo;
    }

    return info;
}

mb_wchar_t *
tty_char_conv_mbs2wcs(char *s, int n, mb_wchar_t *wbuf)
{
    mb_wchar_t wtemp[TTY_CHAR_CONV_LEN_MAX], *wp, *ewp;
    int cw;

    ewp = wtemp;
    mb_mem_to_wstr(s, s + (n >= 0 ? n : strlen(s)), &ewp, wtemp + sizeof(wtemp) / sizeof(wtemp[0]));
    tty_apply_convv(wtemp, ewp, NULL);

    for (cw = 0, wp = wtemp ; wp < ewp ; ++wp)
	if ((cw += MB_WCHAR_WIDTH(*wp)) > TTY_WCWIDTH_MAX)
	    break;

    for (ewp = wtemp ; ewp < wp ;)
	*wbuf++ = *ewp++;

    return wbuf;
}

int
tty_char_conv(mb_wchar_t **p_from, mb_wchar_t *from_end,
	      mb_wchar_t **p_to, mb_wchar_t *to_end)
{
    mb_wchar_t *from, *to, clen, *alt, wc;
    size_t nconv;
    mb_wchar_t wbuf[TTY_CHAR_CONV_LEN_MAX];
    unsigned int off;

    if (!tty_char_conv_tab.tab)
	return tty_char_conv_none;

    for (from = *p_from, to = *p_to, nconv = 0 ; from < from_end ; ++from)
	if (bt_search(*from, tty_char_conv_tab.tab, &off) == bt_failure || !off) {
	    if (nconv && to_end) {
		if (to_end - to < 1)
		    goto to_short;

		*to++ = *from;
	    }
	    else
		++to;
	}
	else {
	    wc = *from;

	    if (!nconv) {
		mb_wchar_t *to_begin, *from_begin;
		size_t i;

		to_begin = *p_to;
		from_begin = *p_from;

		for (i = to - to_begin ; i > 0 ;) {
		    --i;
		    to_begin[i] = from_begin[i];
		}
	    }
	retry:
	    if ((clen = tty_char_conv_tab.pool[off - 1]) &&
		tty_char_conv_tab.pool[off] == mb_notchar_enc_invalid) {
		if (tty_fallback_converters) {
		    mb_info_t *info;
		    unsigned int alt_off;

		    info = tty_check_write_info(NULL);

		    if (mb_apply_convv(&wc, &wc + 1, tty_fallback_converters, info)) {
			if (bt_search(wc, tty_char_conv_tab.tab, &alt_off) != bt_failure && alt_off) {
			    off = alt_off;
			    goto retry;
			}
			else {
			    clen = 1;
			    alt = &wc;
			    goto copy;
			}
		    }
		}

		clen = tty_char_conv_default_alt(wbuf);
		alt = wbuf;
	    }
	    else
		alt = &tty_char_conv_tab.pool[off];
	copy:
	    if (to_end) {
		if (to_end - to < clen)
		    goto to_short;

		for (; clen > 0 ; --clen)
		    *to++ = *alt++;
	    }
	    else
		to += clen;

	    ++nconv;
	}

    if (nconv) {
	*p_from = from;
	*p_to = to;
	return tty_char_conv_done;
    }
    else
	return tty_char_conv_none;
to_short:
    *p_from = from;
    *p_to = to;
    return tty_char_conv_short;
}

size_t
tty_apply_convv(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
    size_t n = 0;

    info = tty_check_write_info(info);

    if (tty_output_converters)
	mb_apply_convv(ws, ws_end, tty_output_converters, info);

    if (info)
	n = mb_conv_for_ces(ws, ws_end, info);

    return n;
}

char *
tty_mbs_apply_convv(char *s, int *p_len)
{
    mb_ws_conv_t cv[2] = {tty_apply_convv, NULL};

    return conv_apply_convv(s, p_len, cv, tty_mb_w_p_info);
}

static unsigned long **ttyfix_wcwidth_vv;

void
ttyfix_wcwidth_init(void)
{
    ttyfix_wcwidth_vv = NULL;
}

size_t
ttyfix_wchar_width(mb_wchar_t wc)
{
    int i, j, k;
    mb_wchar_t wbuf[TTY_CHAR_CONV_LEN_MAX], *from, *to;
    size_t cw = 0;

    i = (wc & TTY_WCWIDTH_LO_MASK) / (sizeof(unsigned long) * CHAR_BIT / TTY_WCWIDTH_BIT);
    j = (wc & TTY_WCWIDTH_LO_MASK) % (sizeof(unsigned long) * CHAR_BIT / TTY_WCWIDTH_BIT) * TTY_WCWIDTH_BIT;
    k = (wc >> TTY_WCWIDTH_LO_BIT) & TTY_WCWIDTH_HI_MASK;

    if (ttyfix_wcwidth_vv && ttyfix_wcwidth_vv[k])
	switch (cw = (ttyfix_wcwidth_vv[k][i] >> j) & TTY_WCWIDTH_MASK) {
	case tty_wcwidth_none:
	    break;
	default:
	    return cw - tty_wcwidth_0;
	}

    tty_apply_convv(&wc, &wc + 1, NULL);
    from = &wc;
    to = wbuf;

    switch (tty_char_conv(&from, from + 1, &to, to + TTY_CHAR_CONV_LEN_MAX)) {
    case tty_char_conv_none:
	cw = MB_WCHAR_WIDTH(wc);
	break;
    default:
	for (cw = 0, from = wbuf ; from < to ; ++from)
	    cw += MB_WCHAR_WIDTH(*from);

	break;
    }

    if (!ttyfix_wcwidth_vv) {
	ttyfix_wcwidth_vv = New_N(unsigned long *, 1U << TTY_WCWIDTH_HI_BIT);
	memset(ttyfix_wcwidth_vv, 0, sizeof(unsigned long *) * (1U << TTY_WCWIDTH_HI_BIT));
    }

    if (ttyfix_wcwidth_vv[k])
	ttyfix_wcwidth_vv[k][i] &= ~(TTY_WCWIDTH_MASK << j);
    else {
	ttyfix_wcwidth_vv[k] = NewAtom_N(unsigned long,
					 (1U << TTY_WCWIDTH_LO_BIT) / (sizeof(unsigned long) * CHAR_BIT / TTY_WCWIDTH_BIT));
	memset(ttyfix_wcwidth_vv[k], 0,
	       sizeof(unsigned long) * ((1U << TTY_WCWIDTH_LO_BIT) / (sizeof(unsigned long) * CHAR_BIT / TTY_WCWIDTH_BIT)));
    }

    ttyfix_wcwidth_vv[k][i] |= (cw + tty_wcwidth_0) << j;
    return cw;
}

size_t
ttyfix_ws_width(mb_wchar_t *ws, mb_wchar_t *ws_end)
{
    size_t w;
    mb_wchar_t tv[BUFSIZ], *t, *te, *p;

    tty_apply_convv(ws, ws_end, NULL);

    for (w = 0 ; ws < ws_end ;) {
	te = tv;
	p = ws;

	switch (tty_char_conv(&p, ws_end, &te, tv + sizeof(tv) / sizeof(tv[0]))) {
	case tty_char_conv_none:
	    for (; ws < ws_end ; ++ws)
		w += MB_WCHAR_WIDTH(*ws);

	    break;
	default:
	    for (t = tv ; t < te ; ++t)
		w += MB_WCHAR_WIDTH(*t);

	    ws = p;
	    break;
	}
    }

    return w;
}

size_t
ttyfix_width(const char *s)
{
    size_t w;
    mb_wchar_t tv[BUFSIZ], *te;

    for (w = 0 ; *s ;) {
	te = tv;
	s = mb_str_to_wstr(s, &te, tv + sizeof(tv) / sizeof(tv[0]));
	w += ttyfix_ws_width(tv, te);
    }

    return w;
}

size_t
ttyfix_width_n(const char *s, size_t n)
{
    size_t w;
    int cn;
    mb_wchar_t tv[BUFSIZ], *te;

    for (w = 0 ; *s && n > 0 ;) {
	for (te = tv ; *s && n > 0 && te < tv + sizeof(tv) / sizeof(tv[0]) ; ++te)
	    if ((cn = mb_mem_to_wchar_internal(s, n, *te)) > 0) {
		s += cn;
		n -= cn;
	    }
	    else {
		++s;
		--n;
	    }

	w += ttyfix_ws_width(tv, te);
    }

    return w;
}

#endif

static char gcmap[96];

extern int tgetent(char *, char *);
extern int tgetnum(char *);
extern int tgetflag(char *);
extern char *tgetstr(char *, char **);
extern char *tgoto(char *, int, int);
extern int tputs(char *, int, int (*)(char));
void putchars(unsigned char, unsigned char, FILE *);
#ifdef JP_CHARSET
void switch_wchar(FILE *);
void switch_ascii(FILE *);
#endif
void clrtoeol(void);

int write1(char);

/* #define writestr(s)  tputs(s,1,write1) */

static void
writestr(char *s)
{
    tputs(s, 1, write1);
}

#define MOVE(line,column)       writestr(tgoto(T_cm,column,line));

#ifdef USE_MOUSE
#define W3M_TERM_INFO(name, title, mouse)	name, title, mouse
#else
#define W3M_TERM_INFO(name, title, mouse)	name, title
#endif

static char XTERM_TITLE[] = "\033]0;w3m: %s\007";
static char SCREEN_TITLE[] = "\033k%s\033\134";
#ifdef __CYGWIN__
static char CYGWIN_TITLE[] = "w3m: %s";
#endif

/* *INDENT-OFF* */
static struct w3m_term_info {
    char *term;
    char *title_str;
#ifdef USE_MOUSE
    int mouse_flag;
#endif
} w3m_term_info_list[] = {
    {W3M_TERM_INFO("xterm", XTERM_TITLE, (NEED_XTERM_ON|NEED_XTERM_OFF))},
    {W3M_TERM_INFO("kterm", XTERM_TITLE, (NEED_XTERM_ON|NEED_XTERM_OFF))},
    {W3M_TERM_INFO("rxvt", XTERM_TITLE, (NEED_XTERM_ON|NEED_XTERM_OFF))},
    {W3M_TERM_INFO("Eterm", XTERM_TITLE, (NEED_XTERM_ON|NEED_XTERM_OFF))},
    {W3M_TERM_INFO("mlterm", XTERM_TITLE, (NEED_XTERM_ON|NEED_XTERM_OFF))},
    {W3M_TERM_INFO("screen", SCREEN_TITLE, 0)},
#ifdef __CYGWIN__
    {W3M_TERM_INFO("cygwin", CYGWIN_TITLE, (NEED_CYGWIN_ON|NEED_CYGWIN_OFF))},
#endif
    {W3M_TERM_INFO(NULL, NULL, 0)}
};
#undef W3M_TERM_INFO
/* *INDENT-ON * */

int
set_tty(void)
{
    char *ttyn;
#ifdef USE_MOUSE
    char *term;
#endif

    if (isatty(0))		/* stdin */
	ttyn = ttyname(0);
    else
	ttyn = DEV_TTY_PATH;
    tty = open(ttyn, O_RDWR);
    if (tty < 0) {
	fprintf(stderr, "open(ttyn==\"%s\", O_RDWR): %s\nTry stdout instead\n",
		ttyn, strerror(errno));
	if ((tty = dup(1)) < 0) {
	    fprintf(stderr, "dup(1): %s\n", strerror(errno));
	    w3m_exit(1);
	}
	fflush(stderr);
    }
#ifdef MANY_CHARSET
    tty_mb_r_info.buf = tty_mb_r_buf;
    tty_mb_r_info.size = sizeof(tty_mb_r_buf);
    mb_init_r(&tty_mb_r_info, &tty_mb_r_setup, tty_mb_read, &tty_mb_r_setup, "|", MB_FLAG_NOSSL);
    ttyf = mb_fdopen(tty, "w!|", &tty_mb_w_setup, MB_FLAG_ASCIIATCTL | MB_FLAG_DISCARD_NOTPREFERED_CHAR);
    mb_finfo(ttyf, NULL, &tty_mb_w_p_info);

    if (tty_initial_input_charset || tty_initial_output_charset) {
	mb_info_t init;

	if (tty_initial_input_charset) {
	    mb_ces_by_name(tty_initial_input_charset, &init);
	    tty_mb_r_info.G = tty_mb_r_info.Gsave = init.G;
	}

	if (tty_initial_output_charset && tty_mb_w_p_info) {
	    mb_ces_by_name(tty_initial_output_charset, &init);
	    tty_mb_w_p_info->G = tty_mb_w_p_info->Gsave = init.G;
	}
    }
#else
    ttyf = fdopen(tty, "w");
#endif
#ifdef __CYGWIN__
    init_win32_console_handle();
#endif
    record_read_fd(tty, NULL, NULL, ttyf);
    FD_ZERO(&tty_event.rfds);
    FD_SET(tty, &tty_event.rfds);
    TerminalGet(tty, &d_ioval);
    if (displayTitleTerm != NULL) {
	struct w3m_term_info *p;
	for (p = w3m_term_info_list; p->term != NULL; p++) {
	    if (!strncmp(displayTitleTerm, p->term, strlen(p->term))) {
		title_str = p->title_str;
		break;
	    }
	}
    }
#ifdef USE_MOUSE
#if defined(USE_GPM) || defined(USE_SYSMOUSE)
    FD_ZERO(&tty_mouse_event.rfds);
    FD_SET(tty, &tty_mouse_event.rfds);
#ifdef USE_SYSMOUSE
    tty_mouse_event.sigmouse = 0;
#endif
#endif
    term = getenv("TERM");
    {
	struct w3m_term_info *p;
	for (p = w3m_term_info_list; p->term != NULL; p++) {
	    if (!strncmp(term, p->term, strlen(p->term))) {
		is_xterm = p->mouse_flag;
		break;
	    }
	}
    }
#endif
    return 0;
}

void
ttymode_set(int mode, int imode)
{
    TerminalMode ioval;

    TerminalGet(tty, &ioval);
    MODEFLAG(ioval) |= mode;
#ifndef HAVE_SGTTY_H
    IMODEFLAG(ioval) |= imode;
#endif				/* not HAVE_SGTTY_H */

    while (TerminalSet(tty, &ioval) == -1) {
	if (errno == EINTR || errno == EAGAIN) continue;
	printf("Error occured while set %x %x: errno=%d\n", mode, imode, errno);
	reset_exit(SIGNAL_ARGLIST);
    }
}

void
ttymode_reset(int mode, int imode)
{
    TerminalMode ioval;

    TerminalGet(tty, &ioval);
    MODEFLAG(ioval) &= ~mode;
#ifndef HAVE_SGTTY_H
    IMODEFLAG(ioval) &= ~imode;
#endif				/* not HAVE_SGTTY_H */

    while (TerminalSet(tty, &ioval) == -1) {
	if (errno == EINTR || errno == EAGAIN) continue;
	printf("Error occured while reset %x %x: errno=%d\n", mode, imode, errno);
	reset_exit(SIGNAL_ARGLIST);
    }
}

#ifndef HAVE_SGTTY_H
void
set_cc(int spec, int val)
{
    TerminalMode ioval;

    TerminalGet(tty, &ioval);
    ioval.c_cc[spec] = val;
    while (TerminalSet(tty, &ioval) == -1) {
	if (errno == EINTR || errno == EAGAIN) continue;
	printf("Error occured: errno=%d\n", errno);
	reset_exit(SIGNAL_ARGLIST);
    }
}
#endif				/* not HAVE_SGTTY_H */

void
close_tty(void)
{
    if (tty >= 0) {
	FD_CLR(tty, &tty_event.rfds);
	clear_read_fd(&tty, 0);
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
	FD_CLR(tty, &tty_mouse_event.rfds);
#endif
    }

    if (ttyf) {
#ifdef MANY_CHARSET
	mb_fclose(ttyf);
	tty_mb_w_p_info = NULL;
#else
	fclose(ttyf);
#endif
	ttyf = NULL;
    }

#ifdef MANY_CHARSET
    read_tty_keep =
#endif
	read_tty_cur = read_tty_nchars = 0;
}

char *
ttyname_tty(void)
{
    return ttyname(tty);
}

void
reset_tty(void)
{
    if (!ttyf)
	return;
    if (need_refresh)
	do_refresh();
#ifdef USE_IMAGE
    else if (needDrawImage)
	doDrawImage();
#endif
#if defined(JP_CHARSET) || defined(__EMX__)
    if (
#ifdef JP_CHARSET
	DisplayCode != CODE_EUC && DisplayCode != CODE_SJIS
#else
	!CodePage
#endif
	)
	writestr("\033(B");	/* designate US_ASCII */
#endif
    writestr(T_op);		/* turn off */
    writestr(T_me);
    if (!Do_not_use_ti_te) {
	if (T_te && *T_te)
	    writestr(T_te);
	else
	    writestr(T_cl);
    }
    writestr(T_se);		/* reset terminal */
#ifdef MANY_CHARSET
    mb_fflush(ttyf);
#else
    fflush(ttyf);
#endif
    TerminalSet(tty, &d_ioval);
    close_tty();
}

MySignalHandler
reset_exit(SIGNAL_ARG)
{
    reset_tty();
#ifdef USE_MOUSE
    if (mouseActive)
	mouse_end();
#endif				/* USE_MOUSE */
    w3m_exit(0);
    SIGNAL_RETURN;
}

MySignalHandler
error_dump(SIGNAL_ARG)
{
    signal(SIGIOT, SIG_DFL);
    reset_tty();
    abort();
    SIGNAL_RETURN;
}

void
set_int(void)
{
    signal(SIGHUP, reset_exit);
    signal(SIGINT, reset_exit);
    signal(SIGQUIT, reset_exit);
    signal(SIGTERM, reset_exit);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGILL, error_dump);
    signal(SIGIOT, error_dump);
    signal(SIGFPE, error_dump);
#ifdef	SIGBUS
    signal(SIGBUS, error_dump);
#endif				/* SIGBUS */
    /* signal(SIGSEGV, error_dump); */
}


static void
setgraphchar(void)
{
    int c, i, n;

    for (c = 0; c < 96; c++)
	gcmap[c] = (char) (c + ' ');

    if (!T_ac)
	return;

    n = strlen(T_ac);
    for (i = 0; i < n - 1; i += 2) {
	c = (unsigned) T_ac[i] - ' ';
	if (c >= 0 && c < 96)
	    gcmap[c] = T_ac[i + 1];
    }
}

#define graphchar(c) (((unsigned)(c)>=' ' && (unsigned)(c)<128)? gcmap[(c)-' '] : (c))

#define GETSTR(v,s) {v = pt; suc = tgetstr(s,&pt); if (!suc) v = ""; else v = allocStr(suc, -1); }

void
getTCstr(void)
{
    char *ent;
    char *suc;
    char *pt = funcstr;
    int r;

    ent = getenv("TERM") ? getenv("TERM") : DEFAULT_TERM;
    if (ent == NULL) {
	fprintf(stderr, "TERM is not set\n");
	reset_exit(SIGNAL_ARGLIST);
    }

    r = tgetent(bp, ent);
    if (r != 1) {
	/* Can't find termcap entry */
	fprintf(stderr, "Can't find termcap entry %s\n", ent);
	reset_exit(SIGNAL_ARGLIST);
    }

    GETSTR(T_ce, "ce");		/* clear to the end of line */
    GETSTR(T_cd, "cd");		/* clear to the end of display */
    GETSTR(T_kr, "nd");		/* cursor right */
    if (suc == NULL)
	GETSTR(T_kr, "kr");
    if (tgetflag("bs"))
	T_kl = "\b";		/* cursor left */
    else {
	GETSTR(T_kl, "le");
	if (suc == NULL)
	    GETSTR(T_kl, "kb");
	if (suc == NULL)
	    GETSTR(T_kl, "kl");
    }
    GETSTR(T_cr, "cr");		/* carriage return */
    GETSTR(T_ta, "ta");		/* tab */
    GETSTR(T_sc, "sc");		/* save cursor */
    GETSTR(T_rc, "rc");		/* restore cursor */
    GETSTR(T_so, "so");		/* standout mode */
    GETSTR(T_se, "se");		/* standout mode end */
    GETSTR(T_us, "us");		/* underline mode */
    GETSTR(T_ue, "ue");		/* underline mode end */
    GETSTR(T_md, "md");		/* bold mode */
    GETSTR(T_me, "me");		/* bold mode end */
    GETSTR(T_cl, "cl");		/* clear screen */
    GETSTR(T_cm, "cm");		/* cursor move */
    GETSTR(T_al, "al");		/* append line */
    GETSTR(T_sr, "sr");		/* scroll reverse */
    GETSTR(T_ti, "ti");		/* terminal init */
    GETSTR(T_te, "te");		/* terminal end */
    GETSTR(T_nd, "nd");		/* move right one space */
    GETSTR(T_eA, "eA");		/* enable alternative charset */
    GETSTR(T_as, "as");		/* alternative (graphic) charset start */
    GETSTR(T_ae, "ae");		/* alternative (graphic) charset end */
    GETSTR(T_ac, "ac");		/* graphics charset pairs */
    GETSTR(T_op, "op");		/* set default color pair to its original value */
#if defined( CYGWIN ) && CYGWIN < 1
    /* for TERM=pcansi on MS-DOS prompt.
       T_eA = "";
       T_as = "\033[12m";
       T_ae = "\033[10m";
       T_ac = "l\001k\002m\003j\004x\005q\006n\020a\024v\025w\026u\027t\031";
       */
    T_eA = "";
    T_as = "";
    T_ae = "";
    T_ac = "";
#endif				/* CYGWIN */

    LINES = COLS = 0;
    setlinescols();
    setgraphchar();
}

#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif				/* not TIOCGWINSZ */

void
setlinescols(void)
{
    char *p;
    int i;
#ifdef __EMX__
    {
	int s[2];
	_scrsize(s);
	COLS = s[0];
	LINES = s[1];

	if (getenv("WINDOWID")) {
	    FILE *fd = popen("scrsize", "rt");
	    if (fd) {
		fscanf(fd, "%i %i", &COLS, &LINES);
		pclose(fd);
	    }
	}
#ifndef JP_CHARSET
	else {
	    ULONG CpList[8], CpSize;
	    CodePage = -1;
	    if (!DosQueryCp(sizeof(CpList), CpList, &CpSize))
		CodePage = *CpList;
	}
#endif
    }
#elif defined(HAVE_TERMIOS_H) && defined(TIOCGWINSZ)
    struct winsize wins;

    i = ioctl(tty, TIOCGWINSZ, &wins);
    if (i >= 0 && wins.ws_row != 0 && wins.ws_col != 0) {
	LINES = wins.ws_row;
	COLS = wins.ws_col;
    }
#endif /* defined(HAVE_TERMIOS_H) && defined(TIOCGWINSZ) */
    if (LINES <= 0 &&
	(p = getenv("LINES")) != NULL &&
	(i = atoi(p)) >= 0)
	LINES = i;
    if (COLS <= 0 &&
	(p = getenv("COLUMNS")) != NULL &&
	(i = atoi(p)) >= 0)
	COLS = i;
    if (LINES <= 0)
	LINES = tgetnum("li");	/* number of line */
    if (COLS <= 0)
	COLS = tgetnum("co");	/* number of column */
    if (COLS > MAX_COLUMN)
	COLS = MAX_COLUMN;
    table_natural_width = COLS;
    if (LINES > MAX_LINE)
	LINES = MAX_LINE;
#if defined(__CYGWIN__) && LANG == JA
    INPUTLINE = LINES - (isWinConsole ? 2 : 1);
#else				/* not defined(__CYGWIN__) && LANG == JA */
    INPUTLINE = LINES - 1;
#endif				/* not defined(__CYGWIN__) && LANG == JA */
    LASTLINE = INPUTLINE - 1;
}

void
setupscreen(void)
{
    int i;

    if (LINES + 1 > max_LINES) {
	max_LINES = LINES + 1;
	max_COLS = 0;
	ScreenElem = New_N(Screen, max_LINES);
	ScreenImage = New_N(Screen *, max_LINES);
	ScreenScroll = New_N(Screen *, max_LINES);
    }
    if (COLS + 1 > max_COLS) {
	max_COLS = COLS + 1;
	for (i = 0; i < max_LINES; i++) {
#ifdef MANY_CHARSET
	    ScreenElem[i].lineimage = New_N(scchar_t, max_COLS);
#else
	    ScreenElem[i].lineimage = NewAtom_N(char, max_COLS);
#endif
	    ScreenElem[i].lineprop = NewAtom_N(l_prop, max_COLS);
	}
    }
    for (i = 0; i < LINES; i++) {
	ScreenImage[i] = &ScreenElem[i];
	ScreenImage[i]->lineprop[0] = S_EOL;
	ScreenImage[i]->isdirty = 0;
    }
    for (; i < max_LINES; i++) {
	ScreenElem[i].isdirty = L_UNUSED;
    }

    clear();
}

/* 
 * Screen initialize
 */
int
initscr(void)
{
    if (set_tty() < 0)
	return -1;
    set_int();
    getTCstr();
    if (T_ti && !Do_not_use_ti_te)
	writestr(T_ti);
    setupscreen();
    return 0;
}

#ifdef JP_CHARSET
static int wmode = C_ASCII;
static char wbuf;
#endif

int
write1(char c)
{
#ifdef SCREEN_DEBUG
    usleep(50);
#endif				/* SCREEN_DEBUG */
#ifdef MANY_CHARSET
    if (tty_mb_w_p_info) {
	mb_flush_auxbuf(tty_mb_w_p_info);
	mb_store_char_noconv(c, tty_mb_w_p_info);
#ifdef SCREEN_DEBUG
	mb_fflush(tty_mb_w_p_info);
#endif				/* SCREEN_DEBUG */
	return 0;
    }
#endif
#ifdef JP_CHARSET
    if (IS_KANJI(c)) {
	switch (wmode) {
	case C_ASCII:
	    switch_wchar(ttyf);
	case C_WCHAR2:
	    wmode = C_WCHAR1;
	    wbuf = c;
	    break;
	case C_WCHAR1:
	    wmode = C_WCHAR2;
	    putchars((unsigned char) wbuf, (unsigned char) c, ttyf);
	    break;
	}
    }
    else {
	switch (wmode) {
	case C_ASCII:
	    break;
	case C_WCHAR1:
	    /* ignore byte */
	    wmode = C_ASCII;
	    switch_ascii(ttyf);
	    break;
	case C_WCHAR2:
	    wmode = C_ASCII;
	    switch_ascii(ttyf);
	    break;
	}
	putc(c, ttyf);
    }
#else				/* not JP_CHARSET */
    putc(c, ttyf);
#endif				/* not JP_CHARSET */
#ifdef SCREEN_DEBUG
    fflush(ttyf);
#endif				/* SCREEN_DEBUG */
    return 0;
}

#ifdef MANY_CHARSET
void
endline(void)
{
    if (tty_mb_w_p_info)
	mb_store_esc(&tty_mb_w_p_info->Gsave, tty_mb_w_p_info);
}

void
tty_store_wchar(mb_wchar_t wc, mb_info_t *info)
{
    mb_wchar_t ws[TTY_CHAR_CONV_LEN_MAX], *from, *to;

    tty_apply_convv(&wc, &wc + 1, info);
    from = &wc;
    to = ws;

    if (tty_char_conv(&from, &wc + 1, &to, ws + sizeof(ws) / sizeof(ws[0]))
	== tty_char_conv_none)
	mb_decode(&wc, &wc + 1, info);
    else
	mb_decode(ws, to, info);
}

void
write1mbc(scchar_t *pch, l_prop prop)
{
    if (prop & S_COMBINED) {
	sccv_t *psccv = pch->sccv;
	size_t i;

	prop &= ~S_COMBINED;

	for (i = 0 ; i < psccv->len ; ++i)
	    write1mbc(&psccv->v[i], prop);
    }
    else {
#ifdef SCREEN_DEBUG
	usleep(50);
#endif				/* SCREEN_DEBUG */

	if (tty_mb_w_p_info)
	    tty_store_wchar(pch->wc, tty_mb_w_p_info);
	else
	    putc(pch->wc, ttyf);

#ifdef SCREEN_DEBUG
	mb_fflush(ttyf);
#endif				/* SCREEN_DEBUG */
    }
}
#endif

#ifdef JP_CHARSET
void
endline(void)
{				/* End of line */
    if (wmode != C_ASCII) {
	switch_ascii(ttyf);
	wmode = C_ASCII;
    }
}

void
switch_ascii(FILE * f)
{
    if (CODE_JIS(DisplayCode)) {
	fputs(GetSOCode(DisplayCode), f);
    }
}

void
switch_wchar(FILE * f)
{
    if (CODE_JIS(DisplayCode)) {
	fputs(GetSICode(DisplayCode), f);
    }
}

void
putchars(unsigned char c1, unsigned char c2, FILE * f)
{
    char buf[2];

    switch (DisplayCode) {
    case CODE_EUC:
	putc(c1, f);
	putc(c2, f);
	return;
    case CODE_JIS_n:
    case CODE_JIS_m:
    case CODE_JIS_N:
    case CODE_JIS_j:
    case CODE_JIS_J:
	putc(c1 & 0x7f, f);
	putc(c2 & 0x7f, f);
	return;
    case CODE_SJIS:
    default:
	put_sjis(c1 & 0x7f, c2 & 0x7f, buf);
	putc((unsigned char)buf[0], f);
	putc((unsigned char)buf[1], f);
	return;
    }
}
#endif				/* JP_CHARSET */

void
move(int line, int column)
{
    if (line >= 0 && line < LINES)
	CurLine = line;
    if (column >= 0 && column < COLS)
	CurColumn = column;
}

void
my_getyx(int *y, int *x)
{
    *y = CurLine;
    *x = CurColumn;
}

#ifdef USE_BG_COLOR
#define M_SPACE (S_SCREENPROP|S_COLORED|S_BCOLORED)
#else				/* not USE_BG_COLOR */
#define M_SPACE (S_SCREENPROP|S_COLORED)
#endif				/* not USE_BG_COLOR */

#ifdef MANY_CHARSET
static int
need_redraw(scchar_t *c1, l_prop pr1, scchar_t *c2, l_prop pr2)
{
    if (pr1 & S_COMBINED) {
	if (pr2 & S_COMBINED) {
	    sccv_t *psccv1 = c1->sccv;
	    sccv_t *psccv2 = c2->sccv;

	    if (psccv1->len == psccv2->len) {
		size_t i;

		SETCHMODE(pr1, C_ASCII);
		SETCHMODE(pr2, C_ASCII);

		for (i = 0 ; i < psccv1->len ; ++i)
		    if (need_redraw(&psccv1->v[i], pr1, &psccv2->v[i], pr2))
			return 1;

		return 0;
	    }
	    else
		return 1;
	}
	else
	    return 1;
    }

    if (c1->wc != c2->wc)
	return 1;

    if (c1->wc == ' ')
	return ((pr1 ^ pr2) & M_SPACE & ~S_DIRTY ||
		(CHMODE(pr1) == C_GRAPHICS) ^ (CHMODE(pr1) == C_GRAPHICS));

    return (pr1 ^ pr2) & ~S_DIRTY;
}
#else
static int
need_redraw(char c1, l_prop pr1, char c2, l_prop pr2)
{
    if (c1 != c2)
	return 1;

    if (c1 == ' ')
	return ((pr1 ^ pr2) & M_SPACE & ~S_DIRTY ||
		(CHMODE(pr1) == C_GRAPHICS) ^ (CHMODE(pr1) == C_GRAPHICS));

    return (pr1 ^ pr2) & ~S_DIRTY;
}
#endif

#define M_CEOL (~(M_SPACE|C_WHICHCHAR))

void
addch(char c)
#ifdef MANY_CHARSET
{
    add_wch((unsigned char)c & ~0x7F ? MB_WORD_SBC_ENC(MB_CTL_FC, c & 0x7F) : c, 1);
}

void
add_wch(mb_wchar_t c, size_t cn)
#endif
{
#ifdef MANY_CHARSET
    scchar_t *p, scc;
    size_t j;
#else
    char *p;
#endif
    l_prop *pr;
    int dest, i, is_ctl = FALSE;
    short *dirty;

#ifdef MANY_CHARSET
    scc.wc = c;
#endif
    if (CurColumn == COLS)
	wrap();
    if (CurColumn >= COLS)
	return;
    p = ScreenImage[CurLine]->lineimage;
    pr = ScreenImage[CurLine]->lineprop;
    dirty = &ScreenImage[CurLine]->isdirty;

#ifdef MANY_CHARSET
    if (!cn &&
	(!CurColumn || CHMODE(CurrentMode) == C_GRAPHICS))
	return; /* must not happen */

    for (j = 0 ; j < cn && CurColumn < COLS - j ; ++j)
	if (pr[CurColumn + j] & S_EOL)
	    break;

    if (j < cn && CurColumn < COLS - j) {
	if (!j && c == ' ' && !(CurrentMode & M_SPACE) && CHMODE(CurrentMode) != C_GRAPHICS) {
	    CurColumn++;
	    return;
	}

	touch_line();

	if (CurColumn < COLS - cn) {
	    pr[CurColumn + cn] |= S_EOL;
	    touch_column(CurColumn + cn);
	}

	for (i = CurColumn + j; i >= 0 && (pr[i] & S_EOL); --i) {
	    p[i].wc = ' ';
	    SETPROP(pr[i], (pr[i] & M_CEOL) | C_ASCII);
	    touch_column(i);
	}
    }
#else

    /* Eliminate unprintables according to * iso-8859-*.
     * Particularly 0x96 messes up T.Dickey's * (xfree-)xterm */
    if (IS_INTERNAL(c))
	c = ' ';

    if (pr[CurColumn] & S_EOL) {
	if (c == ' ' &&
#ifdef JP_CHARSET
	    CHMODE(CurrentMode) != C_WCHAR1 &&
#endif				/* JP_CHARSET */
	    !(CurrentMode & M_SPACE)) {
	    CurColumn++;
	    return;
	}
	for (i = CurColumn; i >= 0 && (pr[i] & S_EOL); i--) {
	    p[i] = ' ';
	    SETPROP(pr[i], (pr[i] & M_CEOL) | C_ASCII);
	}
    }
#endif

    if (c == '\t' || c == '\n' || c == '\r' || c == '\b')
	is_ctl = TRUE;
#ifdef JP_CHARSET
    else if (CHMODE(CurrentMode) == C_WCHAR1)
	SETCHMODE(CurrentMode, C_WCHAR2);
    else if (IS_KANJI1(c))
	SETCHMODE(CurrentMode, C_WCHAR1);
#endif				/* JP_CHARSET */
#ifdef MANY_CHARSET
    else if (cn > 1)
	SETCHMODE(CurrentMode, C_WCHAR1);
#endif
    else if (
#ifdef MANY_CHARSET
	     c & ~0xFF ||
#endif
	     !IS_CNTRL(c)) {
	if (CHMODE(CurrentMode) != C_GRAPHICS)
	    SETCHMODE(CurrentMode, C_ASCII);
    }
    else
	return;

    /* Required to erase bold or underlined character for some
     * terminal emulators. */
    if (((pr[CurColumn] & S_BOLD) &&
	 (is_ctl ||
#ifdef MANY_CHARSET
	  need_redraw(&p[CurColumn], pr[CurColumn], &scc, CurrentMode)
#else
	  need_redraw(p[CurColumn], pr[CurColumn], c, CurrentMode)
#endif
	  )) ||
	((pr[CurColumn] & S_UNDERLINE) && !(CurrentMode & S_UNDERLINE))) {
	touch_line();
#ifdef MANY_CHARSET
	if (CurColumn < COLS - cn) {
	    touch_column(CurColumn + cn);

	    if (pr[CurColumn + cn] & S_EOL) {
		p[CurColumn + cn].wc = ' ';
		SETPROP(pr[CurColumn + cn], (pr[CurColumn + cn] & M_CEOL) | C_ASCII);

		if (CurColumn < COLS - cn - 1) {
		    pr[CurColumn + cn + 1] |= S_EOL;
		    touch_column(CurColumn + cn + 1);
		}
	    }
	    else if (CHMODE(pr[CurColumn + cn]) == C_WCHAR1 && CurColumn < COLS - cn - 1)
		touch_column(CurColumn + cn + 1);
	}
#else
	if (CurColumn < COLS - 1) {
	    touch_column(CurColumn + 1);
	    if (pr[CurColumn + 1] & S_EOL) {
		p[CurColumn + 1] = ' ';
		SETPROP(pr[CurColumn + 1], (pr[CurColumn + 1] & M_CEOL) | C_ASCII);
	    }
#ifdef JP_CHARSET
	    else if (CHMODE(pr[CurColumn + 1]) == C_WCHAR1 && CurColumn < COLS - 2)
		touch_column(CurColumn + 2);
#endif				/* JP_CHARSET */
	}
#endif
    }

#ifdef MANY_CHARSET
    if (cn > 0) {
	if (CHMODE(pr[CurColumn]) == C_WCHAR2 && CurColumn > 0) {
	    p[CurColumn - 1].wc = ' ';
	    SETCHMODE(pr[CurColumn - 1], C_ASCII);
	    touch_line();
	    touch_column(CurColumn - 1);
	}

	if (CurColumn < COLS - cn && !(pr[CurColumn + cn] & S_EOL)
	    && CHMODE(pr[CurColumn + cn]) == C_WCHAR2) {
	    p[CurColumn + cn].wc = ' ';
	    SETCHMODE(pr[CurColumn + cn], C_ASCII);
	    touch_line();
	    touch_column(CurColumn + cn);
	}

	if (CurColumn > COLS - cn) {
	    wrap();
	    p = ScreenImage[CurLine]->lineimage;
	    pr = ScreenImage[CurLine]->lineprop;
	}
    }
#endif
#ifdef JP_CHARSET
    if (CurColumn >= 1 && CHMODE(pr[CurColumn - 1]) == C_WCHAR1 &&
	CHMODE(CurrentMode) != C_WCHAR2) {
	p[CurColumn - 1] = ' ';
	SETCHMODE(pr[CurColumn - 1], C_ASCII);
	touch_line();
	touch_column(CurColumn - 1);
    }

    if (CurColumn < COLS - 1 && CHMODE(pr[CurColumn + 1]) == C_WCHAR2 &&
	CHMODE(CurrentMode) != C_WCHAR1) {
	p[CurColumn + 1] = ' ';
	SETCHMODE(pr[CurColumn + 1], C_ASCII);
	touch_line();
	touch_column(CurColumn + 1);
    }

    if (CurColumn == COLS - 1 && CHMODE(CurrentMode) == C_WCHAR1) {
	wrap();
	p = ScreenImage[CurLine]->lineimage;
	pr = ScreenImage[CurLine]->lineprop;
    }
#endif				/* JP_CHARSET */
    if (!is_ctl) {
#ifdef MANY_CHARSET
	if (!cn) {
	    int col, dwc;
	    sccv_t *psccv;

	    if ((col = CurColumn - 1) > 0 && CHMODE(pr[col]) == C_WCHAR2)
		--col;

	    dwc = CHMODE(pr[col]) == C_WCHAR1 && col + 1 < COLS;


	    if (pr[col] & S_COMBINED) {
		psccv = p[col].sccv;

		if (!psccv->len || psccv->v[psccv->len - 1].wc != c) {
		    touch_line();

		    if (psccv->len < psccv->size) {
			psccv->v[(psccv->len)++].wc = c;
			touch_column(col);

			if (dwc)
			    touch_column(col + 1);
		    }
		    else {
			size_t nsize;

			nsize = (psccv->len + 1) * 2;
			p[col].sccv = psccv = GC_REALLOC(psccv, sizeof(sccv_t) + sizeof(scchar_t) * (nsize - 1));
			psccv->size = nsize;
			psccv->v[(psccv->len)++].wc = c;
			p[col].sccv = psccv;
			touch_column(col);

			if (dwc) {
			    p[col + 1].sccv = psccv;
			    touch_column(col + 1);
			}
		    }
		}
	    }
	    else {
		psccv = GC_MALLOC(sizeof(sccv_t) + sizeof(scchar_t));
		psccv->v[0] = p[col];
		psccv->v[1] = scc;
		psccv->len = psccv->size = 2;
		p[col].sccv = psccv;
		pr[col] |= S_COMBINED;
		touch_line();
		touch_column(col);

		if (dwc) {
		    p[col + 1].sccv = psccv;
		    pr[col + 1] |= S_COMBINED;
		    touch_column(col + 1);
		}
	    }
	}
	else if (need_redraw(&p[CurColumn], pr[CurColumn], &scc, CurrentMode)) {
	    p[CurColumn].wc = c;
	    SETPROP(pr[CurColumn], CurrentMode);
	    touch_line();
	    touch_column(CurColumn);

	    if (CHMODE(CurrentMode) == C_WCHAR1) {
		p[CurColumn + 1].wc = c;
		SETPROP(pr[CurColumn + 1], (CurrentMode & ~C_WHICHCHAR) | C_WCHAR2);
		touch_column(CurColumn + 1);
	    }
	}
	CurColumn += cn;
#else
	if (need_redraw(p[CurColumn], pr[CurColumn], c, CurrentMode)) {
	    p[CurColumn] = c;
	    SETPROP(pr[CurColumn], CurrentMode);
	    touch_line();
	    touch_column(CurColumn);
#ifdef JP_CHARSET
	    if (CHMODE(CurrentMode) == C_WCHAR1)
		touch_column(CurColumn + 1);
	    else if (CHMODE(CurrentMode) == C_WCHAR2)
		touch_column(CurColumn - 1);
#endif				/* JP_CHARSET */
	}
	CurColumn++;
#endif
    }
    else if (c == '\t') {
#ifdef MANY_CHARSET
	scchar_t scsp;

	scsp.wc = ' ';
#endif
	dest = (CurColumn + tab_step) / tab_step * tab_step;
	if (dest >= COLS) {
	    wrap();
	    touch_line();
	    dest = tab_step;
	    p = ScreenImage[CurLine]->lineimage;
	    pr = ScreenImage[CurLine]->lineprop;
	}
	SETCHMODE(CurrentMode, C_ASCII);
	for (i = CurColumn; i < dest; i++) {
	    if (need_redraw(
#ifdef MANY_CHARSET
			    &p[i]
#else
			    p[i]
#endif
			    , pr[i],
#ifdef MANY_CHARSET
			    &scsp
#else
			    ' '
#endif
			    , CurrentMode)) {
#ifdef MANY_CHARSET
		p[i].wc = ' ';
#else
		p[i] = ' ';
#endif
		SETPROP(pr[i], CurrentMode);
		touch_line();
		touch_column(i);
	    }
	}
	CurColumn = i;
    }
    else if (c == '\n') {
	wrap();
    }
    else if (c == '\r') {	/* Carriage return */
	CurColumn = 0;
    }
    else if (c == '\b' && CurColumn > 0) {	/* Backspace */
	CurColumn--;
#if defined(MANY_CHARSET) || defined(JP_CHARSET)
	if (CurColumn > 0 && CHMODE(pr[CurColumn]) == C_WCHAR2)
	    CurColumn--;
#endif				/* JP_CHARSET */
    }
}

void
wrap(void)
{
    if (CurLine == INPUTLINE)
	return;
    CurLine++;
    CurColumn = 0;
}

void
touch_column(int col)
{
    if (col >= 0 && col < COLS)
	ScreenImage[CurLine]->lineprop[col] |= S_DIRTY;
}

void
touch_line(void)
{
    if (!(ScreenImage[CurLine]->isdirty & L_DIRTY)) {
	int i;
	for (i = 0; i < COLS; i++)
	    ScreenImage[CurLine]->lineprop[i] &= ~S_DIRTY;
	ScreenImage[CurLine]->isdirty |= L_DIRTY;
    }

}

void
standout(void)
{
    CurrentMode |= S_STANDOUT;
}

void
standend(void)
{
    CurrentMode &= ~S_STANDOUT;
}

void
toggle_stand(void)
{
    l_prop *pr = ScreenImage[CurLine]->lineprop;
    pr[CurColumn] ^= S_STANDOUT;
#if defined(MANY_CHARSET) || defined(JP_CHARSET)
    if (CHMODE(pr[CurColumn]) == C_WCHAR1)
	pr[CurColumn + 1] ^= S_STANDOUT;
#endif				/* JP_CHARSET */
}

void
bold(void)
{
    CurrentMode |= S_BOLD;
}

void
boldend(void)
{
    CurrentMode &= ~S_BOLD;
}

void
underline(void)
{
    CurrentMode |= S_UNDERLINE;
}

void
underlineend(void)
{
    CurrentMode &= ~S_UNDERLINE;
}

void
graphstart(void)
{
    SETCHMODE(CurrentMode, C_GRAPHICS);
}

void
graphend(void)
{
    SETCHMODE(CurrentMode, C_ASCII);
}

int
graph_ok(void)
{
#ifndef KANJI_SYMBOLS
    if (no_graphic_char)
	return 0;
#endif				/* not KANJI_SYMBOLS */
    return T_as[0] != 0 && T_ae[0] != 0 && T_ac[0] != 0;
}

void
setfcolor(int color)
{
    CurrentMode &= ~COL_FCOLOR;
    if ((color & 0xf) <= 7)
	CurrentMode |= (((color & 7) | 8) << 8);
}

static char *
color_seq(int colmode)
{
    static char seqbuf[32];
    sprintf(seqbuf, "\033[%dm", ((colmode >> 8) & 7) + 30);
    return seqbuf;
}

#ifdef USE_BG_COLOR
void
setbcolor(int color)
{
    CurrentMode &= ~COL_BCOLOR;
    if ((color & 0xf) <= 7)
	CurrentMode |= (((color & 7) | 8) << 12);
}

static char *
bcolor_seq(int colmode)
{
    static char seqbuf[32];
    sprintf(seqbuf, "\033[%dm", ((colmode >> 12) & 7) + 40);
    return seqbuf;
}
#endif				/* USE_BG_COLOR */

#define RF_NEED_TO_MOVE    0
#define RF_CR_OK           1
#define RF_NONEED_TO_MOVE  2
#ifdef USE_BG_COLOR
#define M_MEND (S_STANDOUT|S_UNDERLINE|S_BOLD|S_COLORED|S_BCOLORED)
#else				/* not USE_BG_COLOR */
#define M_MEND (S_STANDOUT|S_UNDERLINE|S_BOLD|S_COLORED)
#endif				/* not USE_BG_COLOR */
static void
do_refresh(void)
{
    int line, col, pcol;
    int pline = CurLine;
    int moved = RF_NEED_TO_MOVE;
#ifdef MANY_CHARSET
    scchar_t *pc, scsp;
#else
    char *pc;
#endif
    l_prop *pr, mode = 0;
    l_prop color = COL_FTERM;
#ifdef USE_BG_COLOR
    l_prop bcolor = COL_BTERM;
#endif				/* USE_BG_COLOR */
    short *dirty;

#ifdef USE_IMAGE
    if (activeImage)
	untouchImageRegion();
#endif
    if (need_term_title)
	do_term_title();
#ifdef MANY_CHARSET
    scsp.wc = ' ';
#endif
    for (line = 0; line <= INPUTLINE; line++) {
	dirty = &ScreenImage[line]->isdirty;
	if (*dirty & L_DIRTY) {
	    *dirty &= ~L_DIRTY;
	    pc = ScreenImage[line]->lineimage;
	    pr = ScreenImage[line]->lineprop;
	    for (col = 0; col < COLS && !(pr[col] & S_EOL); col++) {
		if (*dirty & L_NEED_CE && col >= ScreenImage[line]->eol) {
		    if (need_redraw(
#ifdef MANY_CHARSET
				    &pc[col],
#else
				    pc[col],
#endif
				    pr[col],
#ifdef MANY_CHARSET
				    &scsp,
#else
				    ' ',
#endif
				    0))
			break;
		}
		else {
		    if (pr[col] & S_DIRTY)
			break;
		}
#ifdef MANY_CHARSET
		if (CHMODE(pr[col]) == C_WCHAR1)
		    ++col;
#endif
	    }
	    if (*dirty & (L_NEED_CE | L_CLRTOEOL)) {
		pcol = ScreenImage[line]->eol;
		if (pcol >= COLS) {
		    *dirty &= ~(L_NEED_CE | L_CLRTOEOL);
		    pcol = col;
		}
	    }
	    else {
		pcol = col;
	    }
	    if (line < LINES - 2 && pline == line - 1 && pcol == 0) {
		switch (moved) {
		case RF_NEED_TO_MOVE:
		    MOVE(line, 0);
		    moved = RF_CR_OK;
		    break;
		case RF_CR_OK:
		    write1('\n');
		    write1('\r');
		    break;
		case RF_NONEED_TO_MOVE:
		    moved = RF_CR_OK;
		    break;
		}
	    }
	    else {
		MOVE(line, pcol);
		moved = RF_CR_OK;
	    }
	    if (*dirty & (L_NEED_CE | L_CLRTOEOL)) {
		writestr(T_ce);
		if (col != pcol)
		    MOVE(line, col);
	    }
	    pline = line;
	    pcol = col;
	    for (; col < COLS; col++) {
		if (pr[col] & S_EOL)
		    break;

		/* 
		 * some terminal emulators do linefeed when a
		 * character is put on COLS-th column. this behavior
		 * is different from one of vt100, but such terminal
		 * emulators are used as vt100-compatible
		 * emulators. This behaviour causes scroll when a
		 * character is drawn on (COLS-1,LINES-1) point.  To
		 * avoid the scroll, I prohibit to draw character on
		 * (COLS-1,LINES-1).
		 */
#if !defined(USE_BG_COLOR) || defined(__CYGWIN__)
		if (
#if defined(__CYGWIN__) && LANG == JA
		    isWinConsole &&
#endif
		    line == LINES - 1 && col == COLS - 1)
		    break;
#endif				/* not USE_BG_COLOR */
		if ((!(pr[col] & S_STANDOUT) && (mode & S_STANDOUT)) ||
		    (!(pr[col] & S_UNDERLINE) && (mode & S_UNDERLINE)) ||
		    (!(pr[col] & S_BOLD) && (mode & S_BOLD)) ||
		    (!(pr[col] & S_COLORED) && (mode & S_COLORED))
#ifdef USE_BG_COLOR
		    || (!(pr[col] & S_BCOLORED) && (mode & S_BCOLORED))
#endif				/* USE_BG_COLOR */
		    || (CHMODE(pr[col]) != C_GRAPHICS && CHMODE(mode) == C_GRAPHICS)) {
		    if ((mode & S_COLORED)
#ifdef USE_BG_COLOR
			|| (mode & S_BCOLORED)
#endif				/* USE_BG_COLOR */
			)
			writestr(T_op);
		    if (CHMODE(mode) == C_GRAPHICS) {
			writestr(T_ae);
			SETCHMODE(mode, C_ASCII);
		    }
		    writestr(T_me);
		    mode &= ~M_MEND;
		}
		if ((*dirty & L_NEED_CE && col >= ScreenImage[line]->eol) ?
#ifdef MANY_CHARSET
		    need_redraw(&pc[col], pr[col], &scsp, 0) : pr[col] & S_DIRTY
#else
		    need_redraw(pc[col], pr[col], ' ', 0) : pr[col] & S_DIRTY
#endif
		    ) {
		    if (pcol == col - 1)
			writestr(T_nd);
		    else if (pcol != col)
			MOVE(line, col);

		    if ((pr[col] & S_STANDOUT) && !(mode & S_STANDOUT)) {
			writestr(T_so);
			mode |= S_STANDOUT;
		    }
		    if ((pr[col] & S_UNDERLINE) && !(mode & S_UNDERLINE)) {
			writestr(T_us);
			mode |= S_UNDERLINE;
		    }
		    if ((pr[col] & S_BOLD) && !(mode & S_BOLD)) {
			writestr(T_md);
			mode |= S_BOLD;
		    }
		    if ((pr[col] & S_COLORED) && (pr[col] ^ mode) & COL_FCOLOR) {
			color = (pr[col] & COL_FCOLOR);
			mode = ((mode & ~COL_FCOLOR) | color);
			writestr(color_seq(color));
		    }
#ifdef USE_BG_COLOR
		    if ((pr[col] & S_BCOLORED) && (pr[col] ^ mode) & COL_BCOLOR) {
			bcolor = (pr[col] & COL_BCOLOR);
			mode = ((mode & ~COL_BCOLOR) | bcolor);
			writestr(bcolor_seq(bcolor));
		    }
#endif				/* USE_BG_COLOR */
		    if (CHMODE(pr[col]) == C_GRAPHICS && CHMODE(mode) != C_GRAPHICS) {
			if (!graph_enabled) {
			    graph_enabled = 1;
			    writestr(T_eA);
			}
			writestr(T_as);
			SETCHMODE(mode, C_GRAPHICS);
		    }
#ifdef MANY_CHARSET
		    if (CHMODE(pr[col]) == C_GRAPHICS) {
			scchar_t gr;

			gr.wc = graphchar(pc[col].wc);
			write1mbc(&gr, pr[col] & ~S_COMBINED);
		    }
		    else
			write1mbc(&pc[col], pr[col]);

		    pcol = col + 1;

		    if (CHMODE(pr[pcol]) == C_WCHAR2)
			++pcol;
#else
		    write1(CHMODE(pr[col]) == C_GRAPHICS ? graphchar(pc[col]) : pc[col]);
		    pcol = col + 1;
#endif
		}
#ifdef MANY_CHARSET
		if (CHMODE(pr[col]) == C_WCHAR1)
		    ++col;
#endif
	    }
	    if (col == COLS)
		moved = RF_NEED_TO_MOVE;
	    else
		for (; col < COLS && !(pr[col] & S_EOL); col++)
		    pr[col] |= S_EOL;
	}
	*dirty &= ~(L_NEED_CE | L_CLRTOEOL);
	if (mode & M_MEND || CHMODE(mode) == C_GRAPHICS) {
	    if (mode & (S_COLORED
#ifdef USE_BG_COLOR
			| S_BCOLORED
#endif				/* USE_BG_COLOR */
		))
		writestr(T_op);
	    if (CHMODE(mode) == C_GRAPHICS) {
		writestr(T_ae);
		SETCHMODE(mode, C_ASCII);
	    }
	    writestr(T_me);
	    mode &= ~M_MEND;
	}
#if defined(JP_CHARSET) || defined(MANY_CHARSET)
	endline();
#endif				/* defined(JP_CHARSET) || defined(MANY_CHARSET) */
    }
    MOVE(CurLine, CurColumn);
#ifdef MANY_CHARSET
    mb_fflush(ttyf);
#else
    fflush(ttyf);
#endif
#ifdef USE_IMAGE
    if (needDrawImage)
	doDrawImage();
#endif
    need_refresh = FALSE;
}

void
refresh(void)
{
    if (concurrent)
	need_refresh = TRUE;
    else
	do_refresh();
}

void
clear(void)
{
    int i, j;
    l_prop *p;
    if (need_refresh)
	do_refresh();
#ifdef USE_IMAGE
    else if (needDrawImage)
	doDrawImage();
#endif
    writestr(T_cl);
    move(0, 0);
    for (i = 0; i < LINES; i++) {
	ScreenImage[i]->isdirty = 0;
	p = ScreenImage[i]->lineprop;
	for (j = 0; j < COLS; j++) {
	    p[j] = S_EOL;
	}
    }
    CurrentMode = C_ASCII;
}

void
write_clear(int x, int y, int w, int h)
{
    if (need_refresh)
	do_refresh();
#ifdef USE_IMAGE
    else if (needDrawImage)
	doDrawImage();
#endif
    if (!x && !y && x + w == COLS && y + h == LINES)
	writestr(T_cl);
    else if (x + w == COLS)
	while (h > 0) {
	    --h;
	    MOVE(y + h, x);
	    writestr(T_ce);
	}
    else {    
	Str s;

	s = Strnew_size(w);

	for (; w > 0 ; --w)
	    Strcat_char(s, ' ');

	while (h > 0) {
	    --h;
	    MOVE(y + h, x);
	    writestr(s->ptr);
	}
    }
}

void
clear_partial(int x, int y, int w, int h)
{
    int i, j;
    Screen *l;

    write_clear(x, y, w, h);

    if (x + w < COLS) {
	l_prop pr;

	pr = CurrentMode;
	CurrentMode = (CurrentMode & (M_CEOL
#ifdef USE_BG_COLOR
				      | S_BCOLORED
#endif				/* USE_BG_COLOR */
				      )) | C_ASCII;

	for (i = y ; i < y + h ; ++i) {
	    l = ScreenImage[i];

	    if (l->lineprop[x + w] & S_EOL) {
		for (j = x ; j < x + w ;)
		    l->lineprop[j++] = S_EOL;

		if (l->eol > x)
		    l->eol = x;
	    }
	    else {
		move(i, x);

		for (j = x ; j < x + w ;) {
		    addch(' ');
		    l->lineprop[j++] &= ~S_DIRTY;
		}
	    }
	}

	CurrentMode = pr;
    }
    else if (x)
	for (i = y ; i < y + h ;) {
	    l = ScreenImage[i++];

	    for (j = x ; j < COLS ;)
		l->lineprop[j++] = S_EOL;

	    if (l->eol > x)
		l->eol = x;
	}
    else
	for (i = y ; i < y + h ;) {
	    l = ScreenImage[i++];
	    l->isdirty = 0;

	    for (j = 0 ; j < COLS ;)
		l->lineprop[j++] = S_EOL;

	    l->eol = 0;
	}

    move(y, x);
}

static void
scroll_raw(void)
{				/* raw scroll */
    MOVE(LINES - 1, 0);
    write1('\n');
}

void
scroll(int n, int beg, int m)
{				/* scroll up */
    int cli = CurLine, cco = CurColumn;
    Screen **top, *t;
    int i, j;

    if (beg < 0 || beg >= LINES || m <= 0 || m > LINES - beg || !(n %= m))
	return;

    if (need_refresh)
	do_refresh();
#ifdef USE_IMAGE
    else if (needDrawImage)
	doDrawImage();
#endif

    top = &ScreenImage[beg];
    memcpy(ScreenScroll, top, sizeof(top[0]) * n);
    memmove(top, top + n, sizeof(top[0]) * (m - n));
    memcpy(top + m - n, ScreenScroll, sizeof(top[0]) * n);

    if (beg > 0 || m < LINES) {
	for (i = 0 ; i < m - n ; ++i) {
	    t = top[i];
	    t->isdirty |= L_DIRTY | L_NEED_CE;
	    for (j = 0; j < COLS; j++)
		t->lineprop[j] |= S_DIRTY;
	}
	write_clear(0, beg + i, COLS, n);
	for (; i < m ; ++i) {
	    t = top[i];
	    t->isdirty = 0;
	    t->eol = 0;
	    for (j = 0; j < COLS; j++)
		t->lineprop[j] = S_EOL;
	}
    }
    else {
	for (i = 1; i <= n; i++) {
	    t = top[m - i];
	    t->isdirty = 0;
	    t->eol = 0;
	    for (j = 0; j < COLS; j++)
		t->lineprop[j] = S_EOL;
	    scroll_raw();
	}
    }
    move(cli, cco);
}

void
rscroll(int n, int beg, int m)
{				/* scroll down */
    int cli = CurLine, cco = CurColumn;
    Screen **top, *t;
    int i, j;

    if (beg < 0 || beg >= LINES || m <= 0 || m > LINES - beg || !(n %= m))
	return;

    if (need_refresh)
	do_refresh();
#ifdef USE_IMAGE
    else if (needDrawImage)
	doDrawImage();
#endif

    top = &ScreenImage[beg];
    memcpy(ScreenScroll, top + m - n, sizeof(top[0]) * n);
    memmove(top + n, top, sizeof(top[0]) * (m - n));
    memcpy(top, ScreenScroll, sizeof(top[0]) * n);

    if (beg > 0 || m < LINES || !T_sr || !*T_sr) {
	write_clear(0, beg, COLS, n);
	for (i = 0 ; i < n ; ++i) {
	    t = top[i];
	    t->isdirty = 0;
	    t->eol = 0;
	    for (j = 0; j < COLS; j++)
		t->lineprop[j] = S_EOL;
	}
	for (; i < m ; ++i) {
	    t = top[i];
	    t->isdirty |= L_DIRTY | L_NEED_CE;
	    for (j = 0; j < COLS; j++) {
		t->lineprop[j] |= S_DIRTY;
	    }
	}
    }
    else {
	MOVE(beg, 0);
	for (i = 0 ; i < n ; ++i) {
	    t = top[i];
	    t->isdirty = 0;
	    t->eol = 0;
	    for (j = 0; j < COLS; j++)
		t->lineprop[j] = S_EOL;
	    writestr(T_sr);
	}
    }
    move(cli, cco);
}

void
clrtoeol(void)
{				/* Clear to the end of line */
    int i;
    l_prop *lprop = ScreenImage[CurLine]->lineprop;

    if (lprop[CurColumn] & S_EOL)
	return;

    if (!(ScreenImage[CurLine]->isdirty & (L_NEED_CE | L_CLRTOEOL)) ||
	ScreenImage[CurLine]->eol > CurColumn)
	ScreenImage[CurLine]->eol = CurColumn;

    ScreenImage[CurLine]->isdirty |= L_CLRTOEOL;
    touch_line();
    for (i = CurColumn; i < COLS && !(lprop[i] & S_EOL); i++) {
	lprop[i] = S_EOL | S_DIRTY;
    }
}

void
clrtoeol_partial(int cols)
{
    int i, cli, cco;
    l_prop pr;

    cli = CurLine;
    cco = CurColumn;
    pr = CurrentMode;
    CurrentMode = (CurrentMode & (M_CEOL
#ifdef USE_BG_COLOR
				  | S_BCOLORED
#endif				/* USE_BG_COLOR */
				  )) | C_ASCII;
    for (i = CurColumn; i < cols; i++)
	addch(' ');
    move(cli, cco);
    CurrentMode = pr;
}

#ifdef USE_BG_COLOR
void
clrtoeol_with_bcolor(void)
{
    if ((CurrentMode & S_BCOLORED))
	clrtoeol_partial(COLS);
    else
	clrtoeol();
}

void
clrtoeolx(void)
{
    clrtoeol_with_bcolor();
}
#else				/* not USE_BG_COLOR */

void
clrtoeolx(void)
{
    clrtoeol();
}
#endif				/* not USE_BG_COLOR */

void
clrtobot_eol(void (*clrtoeol) ())
{
    int l, c;

    l = CurLine;
    c = CurColumn;
    (*clrtoeol) ();
    CurColumn = 0;
    CurLine++;
    for (; CurLine <= LASTLINE; CurLine++)
	(*clrtoeol) ();
    CurLine = l;
    CurColumn = c;
}

void
clrtobot(void)
{
    clrtobot_eol(clrtoeol);
}

void
clrtobotx(void)
{
    clrtobot_eol(clrtoeolx);
}

void
clrtobot_eol_partial(int x, int y, int w, int h, void (*clrtoeol_func)())
{
    int l, c, cols, lines;

    l = CurLine;
    c = CurColumn;

    if ((cols = x + w) > COLS)
	cols = COLS;

    if ((lines = y + h) > LINES)
	lines = LINES;

    if (l < y) {
	CurColumn = x;

	while (++l < y)
	    ;
    }

    if (l < lines) {
	if (cols < COLS) {
	    l_prop pr;

	    pr = CurrentMode;

#ifdef USE_BG_COLOR
	    if (clrtoeol_func != clrtoeolx)
		CurrentMode &= ~S_BCOLORED; 
#endif

	    clrtoeol_partial(cols);
	    CurColumn = x;
	    CurLine++;
	    for (; CurLine < lines; CurLine++)
		clrtoeol_partial(cols);
	}
	else {
	    (*clrtoeol_func)();
	    CurColumn = x;
	    CurLine++;
	    for (; CurLine < lines; CurLine++)
		(*clrtoeol_func)();
	}
    }

    CurLine = l;
    CurColumn = c;
}

void
clrtobot_partial(int x, int y, int w, int h)
{
    clrtobot_eol_partial(x, y, w, h, clrtoeol);
}

void
clrtobotx_partial(int x, int y, int w, int h)
{
    clrtobot_eol_partial(x, y, w, h, clrtoeolx);
}

void
addstr(char *s)
{
#ifdef MANY_CHARSET
    int e;
    mb_wchar_t wc;

    while (*s) {
	if ((e = mb_str_to_wchar_internal(s, wc)) < 0)
	    e = 1;

	add_wch(wc, ttyfix_wchar_width(wc));
	s += e;
    }
#else
    while (*s != '\0')
	addch(*(s++));
#endif
}

void
addnstr(char *s, int n)
{
#ifdef MANY_CHARSET
    int e, cn;
    mb_wchar_t wc;

    if (n > 0)
	while (*s) {
	    if ((e = mb_str_to_wchar_internal(s, wc)) < 0)
		e = 1;

	    cn = ttyfix_wchar_width(wc);

	    if (cn > n)
		break;

	    add_wch(wc, cn);
	    n -= cn;
	    s += e;
	}
#else
    int i;

#ifdef JP_CHARSET
    for (i = 0; i < n - 1 && *s != '\0'; i++)
	addch(*(s++));
    if (*s != '\0') {
	if (IS_KANJI2(*s)) {
	    /* WCHAR */
	    if (CHMODE(CurrentMode) == C_WCHAR1) {
		/* WCHAR second byte */
		addch(*s);
	    }
	}
	else {
	    /* Ascii or WCHAR2 */
	    addch(*s);
	}
    }
#else				/* not JP_CHARSET */
    for (i = 0; i < n && *s != '\0'; i++)
	addch(*(s++));
#endif				/* not JP_CHARSET */
#endif
}

void
addnstr_sup(char *s, int n)
{
#ifdef MANY_CHARSET
    int e, cn;
    mb_wchar_t wc;

    if (n > 0) {
	while (*s) {
	    if ((e = mb_str_to_wchar_internal(s, wc)) < 0)
		e = 1;

	    cn = ttyfix_wchar_width(wc);

	    if (cn > n)
		break;

	    add_wch(wc, cn);
	    n -= cn;
	    s += e;
	}

	for (; n > 0 ; --n)
	    add_wch(' ', 1);
    }
#else
    int i;

#ifdef JP_CHARSET
    for (i = 0; i < n - 1 && *s != '\0'; i++)
	addch(*(s++));
    if (*s != '\0') {
	if (IS_KANJI2(*s)) {
	    /* WCHAR */
	    if (CHMODE(CurrentMode) == C_WCHAR1) {
		/* WCHAR second byte */
		addch(*s);
		i++;
	    }
	}
	else {
	    /* Ascii or WCHAR2 */
	    addch(*s);
	    i++;
	}
    }
#else				/* not JP_CHARSET */
    for (i = 0; i < n && *s != '\0'; i++)
	addch(*(s++));
#endif				/* not JP_CHARSET */
    for (; i < n; i++)
	addch(' ');
#endif
}

void
crmode(void)
#ifndef HAVE_SGTTY_H
{
    ttymode_reset(ICANON, IXON);
    ttymode_set(ISIG, 0);
#ifdef HAVE_TERMIOS_H
    set_cc(VMIN, 1);
#else				/* not HAVE_TERMIOS_H */
    set_cc(VEOF, 1);
#endif				/* not HAVE_TERMIOS_H */
}
#else				/* HAVE_SGTTY_H */
{
    ttymode_set(CBREAK, 0);
}
#endif				/* HAVE_SGTTY_H */

void
nocrmode(void)
#ifndef HAVE_SGTTY_H
{
    ttymode_set(ICANON, 0);
#ifdef HAVE_TERMIOS_H
    set_cc(VMIN, 4);
#else				/* not HAVE_TERMIOS_H */
    set_cc(VEOF, 4);
#endif				/* not HAVE_TERMIOS_H */
}
#else				/* HAVE_SGTTY_H */
{
    ttymode_reset(CBREAK, 0);
}
#endif				/* HAVE_SGTTY_H */

void
term_echo(void)
{
    ttymode_set(ECHO, 0);
}

void
term_noecho(void)
{
    ttymode_reset(ECHO, 0);
}

void
term_raw(void)
#ifndef HAVE_SGTTY_H
#ifdef IEXTEN
#define TTY_MODE ISIG|ICANON|ECHO|IEXTEN
#else				/* not IEXTEN */
#define TTY_MODE ISIG|ICANON|ECHO
#endif				/* not IEXTEN */
{
    ttymode_reset(TTY_MODE, IXON | IXOFF);
#ifdef HAVE_TERMIOS_H
    set_cc(VMIN, 1);
#else				/* not HAVE_TERMIOS_H */
    set_cc(VEOF, 1);
#endif				/* not HAVE_TERMIOS_H */
}
#else				/* HAVE_SGTTY_H */
{
    ttymode_set(RAW, 0);
}
#endif				/* HAVE_SGTTY_H */

void
term_cooked(void)
#ifndef HAVE_SGTTY_H
{
#ifdef __EMX__
    /* On XFree86/OS2, some scrambled characters
     * will appear when asserting IEXTEN flag.
     */
    ttymode_set((TTY_MODE)&~IEXTEN,0);
#else
    ttymode_set(TTY_MODE, 0);
#endif
#ifdef HAVE_TERMIOS_H
    set_cc(VMIN, 4);
#else				/* not HAVE_TERMIOS_H */
    set_cc(VEOF, 4);
#endif				/* not HAVE_TERMIOS_H */
}
#else				/* HAVE_SGTTY_H */
{
    ttymode_reset(RAW, 0);
}
#endif				/* HAVE_SGTTY_H */

void
term_cbreak(void)
{
    term_cooked();
    term_noecho();
}

static void
do_term_title(void)
{
    if (title_str != NULL && ttyf &&
	(!prev_term_title || strcmp(prev_term_title, need_term_title))) {
#if defined(MANY_CHARSET) || defined(JP_CHARSET)
	Str tmp;

	tmp = Sprintf(title_str, mybasename((char *)myname), need_term_title);
	prev_term_title = need_term_title;
	need_term_title = NULL;

#ifdef MANY_CHARSET
	if (tty_mb_w_p_info) {
	    char *p, *ep;
	    mb_wchar_t wbuf[BUFSIZ], *wp, *ewp;

	    endline();

	    for (p = tmp->ptr, ep = p + tmp->length ; p < ep ;) {
		ewp = wbuf;
		p = (char *)mb_mem_to_wstr(p, ep, &ewp, wbuf + sizeof(wbuf) / sizeof(wbuf[0]));

		for (wp = wbuf ; wp < ewp ; ++wp)
		    tty_store_wchar(*wp & ~0x7F ? '?' : *wp, tty_mb_w_p_info);
	    }

	    endline();
	    return;
	}
#else
	tmp = conv(tmp->ptr, InnerCode, DisplayCode);
#endif
        writestr(tmp->ptr);
#else
	fprintf(ttyf, title_str, myname, term_title);
#endif
    }
}

void
term_title(char *s)
{
    need_term_title = s;
}

#ifdef MANY_CHARSET
void
process_getch_mode(int mode)
{
    switch (mode) {
    case GETCH_MODE_CLR:
	tty_mb_r_info.b = tty_mb_r_info.i;
	break;
    case GETCH_MODE_RST:
	tty_mb_r_info.i = tty_mb_r_info.b;
	break;
    case GETCH_MODE_SKIP:
	if (tty_mb_r_info.b < tty_mb_r_info.e)
	    tty_mb_r_info.i = ++(tty_mb_r_info.b);
	else
	    tty_mb_r_info.i = tty_mb_r_info.b = tty_mb_r_info.e;

	break;
    }
}

char
getch_internal(int mode)
{
    char c;

    while (read_tty_cur >= read_tty_nchars) {
	while (wait_until(&tty_event, NULL) <= 0)
	    ;

	if (tty >= 0 && !FD_ISSET(tty, &tty_event.rfds)) {
	    FD_SET(tty, &tty_event.rfds);
	    break;
	}
    }

    c = mb_call_getc(&tty_mb_r_info);

    if (mode)
	process_getch_mode(mode);

    return c;
}
#endif

int
getevent(CookedEvent *cev)
{
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
    int mouse_p = 0;
#endif
    int tty_p = 0;

    while (wait_until(&tty_mouse_event, NULL) <= 0)
	;

#ifdef USE_MOUSE
#ifdef USE_GPM
    if (gpm_fd >= 0 && !FD_ISSET(gpm_fd, &tty_mouse_event.rfds)) {
	Gpm_Event mev;

	switch (Gpm_GetEvent(&mev)) {
	default:
	    mouse_p = gpm_process_mouse(&mev, cev);
	case -1:
	    FD_SET(gpm_fd, &tty_mouse_event.rfds);
	    break;
	case 0:
	    clear_read_fd(&save_gpm_fd, 0);
	    break;
	}
    }
#endif
#ifdef USE_SYSMOUSE
    if (sysmouse_active && !tty_mouse_event.sigmouse) {
	mouse_p = sysm_process_mouse(xpix / cwidth, y / cheight, nbs, obs, cev);
	tty_mouse_event.sigmouse = 1;
    }
#endif
#endif

    if (tty >= 0 && !FD_ISSET(tty, &tty_mouse_event.rfds)) {
	FD_SET(tty, &tty_mouse_event.rfds);
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
	if (!mouse_p)
#endif
	    {
		cev->type = cooked_event_char;
		while (read_tty_cur >= read_tty_nchars) {
		    wait_until(&tty_event, NULL);

		    if (tty >= 0 && !FD_ISSET(tty, &tty_event.rfds)) {
			FD_SET(tty, &tty_event.rfds);
			break;
		    }
		}

		cev->what = read_tty_chars[read_tty_cur++];
#ifdef MANY_CHARSET
		process_getch_mode(GETCH_MODE_CLR);
#endif
		cev->x = CurColumn;
		cev->y = CurLine;
		tty_p = 1;
	    }
    }

    return (
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
	    mouse_p ||
#endif
	    tty_p);
}

#ifndef MANY_CHARSET
char
getch(void)
{
    char c;

    while (read_tty_cur >= read_tty_nchars) {
	wait_until(&tty_event, NULL);

	if (tty >= 0 && !FD_ISSET(tty, &tty_event.rfds)) {
	    FD_SET(tty, &tty_event.rfds);
	    break;
	}
    }

    c = read_tty_chars[read_tty_cur++];
    return c;
}
#endif

void
bell(void)
{
    write1(7);
}

void
skip_escseq(void)
{
    int c;

    c = getch();
    if (c == '[' || c == 'O') {
	c = getch();
#ifdef USE_MOUSE
	if (is_xterm && c == 'M') {
	    getch();
	    getch();
	    getch();
	}
	else
#endif	
	    while (IS_DIGIT(c))
		c = getch();
    }
}

void
sleep_till_anykey(int sec, int purge)
{
    TerminalMode ioval;
    int err;
    struct timeval tim;

    TerminalGet(tty, &ioval);
    term_raw();
    tim.tv_sec = sec;
    tim.tv_usec = 0;

    while (wait_until(&tty_event, &tim) <= 0 ||
	   tty < 0 || FD_ISSET(tty, &tty_event.rfds))
	;

    FD_SET(tty, &tty_event.rfds);

    if (purge) {
	if (getch() == ESC_CODE)
	    skip_escseq();
    }

    if ((err = TerminalSet(tty, &ioval)) == -1) {
	printf("Error occured: errno=%d\n", errno);
	reset_exit(SIGNAL_ARGLIST);
    }
}

#ifdef USE_MOUSE

#if defined(MANY_CHARSET) || defined(JP_CHARSET)
int
here_is_mousecursor_p(int x, int y, int cursor_x)
{
    Screen *l;

    if (y < 0 || y >= LINES || x < 0 || x >= COLS)
	return FALSE;

    l = ScreenImage[y];

    switch (CHMODE(l->lineprop[x])) {
    case C_WCHAR1:
	return cursor_x == x + 1 || cursor_x == x;
    case C_WCHAR2:
	return cursor_x == x - 1 || cursor_x == x;
    default:
	return cursor_x == x;
    }
}
#endif

#if 0
#ifdef MANY_CHARSET
#define XTERM_ON   {mb_fputs("\033[?1001s\033[?1000h",ttyf); flush_tty();}
#define XTERM_OFF  {mb_fputs("\033[?1000l\033[?1001r",ttyf); flush_tty();}
#define CYGWIN_ON  {mb_fputs("\033[?1000h",ttyf); flush_tty();}
#define CYGWIN_OFF {mb_fputs("\033[?1000l",ttyf); flush_tty();}
#else
#define XTERM_ON   {fputs("\033[?1001s\033[?1000h",ttyf); flush_tty();}
#define XTERM_OFF  {fputs("\033[?1000l\033[?1001r",ttyf); flush_tty();}
#define CYGWIN_ON  {fputs("\033[?1000h",ttyf); flush_tty();}
#define CYGWIN_OFF {fputs("\033[?1000l",ttyf); flush_tty();}
#endif
#else
#ifdef MANY_CHARSET
/* tell XTerm to report mouse dragging as well as clicks. RXVT doesn't
 * understand CSI?1002h, so it only sees CSI?1000h. -- LH */
#define XTERM_ON   {mb_fputs("\033[?1001s\033[?1000h\033[?1002h",ttyf); flush_tty();}
#define XTERM_OFF  {mb_fputs("\033[?1002l\033[?1000l\033[?1001r",ttyf); flush_tty();}
#define CYGWIN_ON  {mb_fputs("\033[?1000h",ttyf); flush_tty();}
#define CYGWIN_OFF {mb_fputs("\033[?1000l",ttyf); flush_tty();}
#else
#define XTERM_ON   {fputs("\033[?1001s\033[?1000h\033[?1002h",ttyf); flush_tty();}
#define XTERM_OFF  {fputs("\033[?1002l\033[?1000l\033[?1001r",ttyf); flush_tty();}
#define CYGWIN_ON  {fputs("\033[?1000h",ttyf); flush_tty();}
#define CYGWIN_OFF {fputs("\033[?1000l",ttyf); flush_tty();}
#endif
#endif

#ifdef USE_GPM
/* Linux console with GPM support */
static int save_gpm_fd = -1;

int
gpm_process_mouse(Gpm_Event *event, CookedEvent *cev)
{
    if (event->type & GPM_UP)
	cev->what = MOUSE_BTN_UP_TERM;
    else if (event->type & GPM_DOWN) {
	switch (event->buttons) {
	case GPM_B_LEFT:
	    cev->what = MOUSE_BTN1_DOWN;
	    break;
	case GPM_B_MIDDLE:
	    cev->what = MOUSE_BTN2_DOWN;
	    break;
	case GPM_B_RIGHT:
	    cev->what = MOUSE_BTN3_DOWN;
	    break;
	}
    }
    else {
	GPM_DRAWPOINTER(event);
	return 0;
    }
    cev->x = event->x;
    cev->y = event->y;
    cev->type = cooked_event_mouse;
    return 1;
}

void
mouse_init()
{
    Gpm_Connect conn;

    if (mouseActive)
	return;
    conn.eventMask = ~0;
    conn.defaultMask = 0;
    conn.maxMod = 0;
    conn.minMod = 0;

    switch (save_gpm_fd = Gpm_Open(&conn, 0)) {
    case -2:
	/*
	 * If Gpm_Open() success, returns >= 0
	 * Gpm_Open() returns -2 in case of xterm.
	 * Gpm_Close() is necessary here. Otherwise,
	 * xterm is being left in the mode where the mouse clicks are
	 * passed through to the application.
	 */
	Gpm_Close();
	if (is_xterm == 0)
	    is_xterm = NEED_XTERM_ON|NEED_XTERM_OFF;
    case -1:
	break;
    default:
	record_read_fd(save_gpm_fd, NULL, NULL, NULL);
	FD_SET(save_gpm_fd, &tty_mouse_event.rfds);
	break;
    }

    if ((is_xterm & NEED_XTERM_ON)) {
	XTERM_ON;
    }
#ifdef SIGWINCH
    signal(SIGWINCH, resize_hook);
#endif				/* SIGWINCH */
    mouseActive = 1;
}

void
mouse_end()
{
    if (mouseActive == 0)
	return;
    if (ttyf && (is_xterm & NEED_XTERM_OFF)) {
	XTERM_OFF;
    }
    else {
	if (save_gpm_fd >= 0)
	    FD_CLR(save_gpm_fd, &tty_mouse_event.rfds);
	Gpm_Close();
    }
    mouseActive = 0;
}

#elif	defined(USE_SYSMOUSE)
/* *BSD console with sysmouse support */
MySignalHandler
sysmouse(SIGNAL_ARG)
{
    struct mouse_info mi;

    mi.operation = MOUSE_GETINFO;
    if (ioctl(tty, CONS_MOUSECTL, &mi) == -1)
	return;
    xpix = mi.u.data.x;
    ypix = mi.u.data.y;
    obs = nbs;
    nbs = mi.u.data.buttons & 0x7;
    /* for cosmetic bug in syscons.c on FreeBSD 3.[34] */
    mi.operation = MOUSE_HIDE;
    ioctl(tty, CONS_MOUSECTL, &mi);
    mi.operation = MOUSE_SHOW;
    ioctl(tty, CONS_MOUSECTL, &mi);
    ++sysmouse_nevents;
    SIGNAL_RETURN;
}

int
process_sysmouse_event(RawEvent *rev)
{
    if (sysmouse_nevents > 0 && rev->sigmouse) {
	rev->sigmouse = 0;
	return 1;
    }

    return 0;
}

int
sysm_process_mouse(int x, int y, int nbs, int obs, CookedCev *cev)
{
    int btn;
    int bits;

    if (obs & ~nbs)
	cev->what = MOUSE_BTN_UP_TERM;
    else if (nbs & ~obs) {
	bits = nbs & ~obs;
	cev->what = (bits & 0x1 ? MOUSE_BTN1_DOWN :
		     bits & 0x2 ? MOUSE_BTN2_DOWN :
		     bits & 0x4 ? MOUSE_BTN3_DOWN : 0);
    }
    else			/* nbs == obs */
	return 0;

    cev->x = x;
    cev->y = y;
    cev->type = cooked_event_mouse;
    return 1;
}

void
mouse_init()
{
    mouse_info_t mi;

    if (mouseActive)
	return;
    if ((is_xterm & NEED_XTERM_ON)) {
	XTERM_ON;
    }
    else {
#if defined(FBIO_MODEINFO) || defined(CONS_MODEINFO)	/* FreeBSD > 2.x */
#ifndef FBIO_GETMODE		/* FreeBSD 3.x */
#define FBIO_GETMODE    CONS_GET
#define FBIO_MODEINFO   CONS_MODEINFO
#endif				/* FBIO_GETMODE */
	video_info_t vi;

	if (ioctl(tty, FBIO_GETMODE, &vi.vi_mode) != -1 &&
	    ioctl(tty, FBIO_MODEINFO, &vi) != -1) {
	    cwidth = vi.vi_cwidth;
	    cheight = vi.vi_cheight;
	}
#endif				/* defined(FBIO_MODEINFO) ||
				 * defined(CONS_MODEINFO) */
	signal(SIGUSR2, SIG_IGN);
	mi.operation = MOUSE_MODE;
	mi.u.mode.mode = 0;
	mi.u.mode.signal = SIGUSR2;
	sysmouse_active = 0;
	if (ioctl(tty, CONS_MOUSECTL, &mi) != -1) {
	    signal(SIGUSR2, sysmouse);
	    mi.operation = MOUSE_SHOW;
	    ioctl(tty, CONS_MOUSECTL, &mi);
	    sysmouse_active = 1;
	    tty_mouse_event.sigmouse = 1;
	}
    }
    mouseActive = 1;
}

void
mouse_end()
{
    if (mouseActive == 0)
	return;
    if (ttyf && (is_xterm & NEED_XTERM_OFF)) {
	XTERM_OFF;
    }
    else {
	mouse_info_t mi;
	mi.operation = MOUSE_MODE;
	mi.u.mode.mode = 0;
	mi.u.mode.signal = 0;
	ioctl(tty, CONS_MOUSECTL, &mi);
	sysmouse_active = 0;
	tty_mouse_event.sigmouse = 0;
    }
    mouseActive = 0;
}

#else
/* neither GPM nor SYSMOUSE, but use mouse with xterm */

void
mouse_init()
{
    if (mouseActive)
	return;
    if (is_xterm & NEED_XTERM_ON) {
	XTERM_ON;
    }
#ifdef __CYGWIN__
    else if (is_xterm & NEED_CYGWIN_ON) {
	CYGWIN_ON;
    }
#endif
    mouseActive = 1;
}

void
mouse_end()
{
    if (mouseActive == 0)
	return;
    if (ttyf && is_xterm & NEED_XTERM_OFF) {
	XTERM_OFF;
    }
#ifdef __CYGWIN__
    else if (is_xterm & NEED_CYGWIN_OFF) {
	CYGWIN_OFF;
    }
#endif
    mouseActive = 0;
}

#endif				/* not USE_GPM nor USE_SYSMOUSE */


void
mouse_active()
{
    if (!mouseActive)
	mouse_init();
}

void
mouse_inactive()
{
    if (mouseActive && is_xterm)
	mouse_end();
}

#endif				/* USE_MOUSE */

void
flush_tty()
{
    fflush(stdout);

    if (urgent_out)
	fflush(urgent_out);

    if (ttyf) {
#ifdef MANY_CHARSET
	mb_fflush(ttyf);
#else
	fflush(ttyf);
#endif
    }
}

#ifdef USE_IMAGE
static unsigned short *ImageScreen;
static int ImageScreenSize;

typedef struct _termialImage {
    ImageCache *cache;
    char clear_p, dirty_p;
    int serial;
    short x, y, sx, sy, width, height;
} TerminalImage;

static TerminalImage *terminal_image;
static int n_terminal_image;
static int max_terminal_image;
static int terminal_image_serial;
static FILE *Imgdisplay_wf;
static FILE *Imgdisplay_rf;
static pid_t Imgdisplay_pid = -1;
static int activeImgdisplay;
extern int image_index;

void
initImage()
{
    char *cmd;
    FILE *wh;
    Str l;
    int w, h;

    if (activeImage)
	return;

    cmd = Imgdisplay[0] == '/' ? Imgdisplay : Sprintf("%s/%s", w3m_auxbin_dir(), Imgdisplay)->ptr;

    if (!(wh = popen(Sprintf("%s -test", cmd)->ptr, "r"))) {
	activeImage = FALSE;
	return;
    }

    l = Strfgets(wh);
    pclose(wh);

    if ((activeImage = l->length > 0 && sscanf(l->ptr, "%d;%d;", &w, &h) == 2)) {
	if (auto_pixel_per_char)
	    pixel_per_char = w / COLS;

	if (auto_pixel_per_line)
	    pixel_per_line = h / LINES;
    }

    NEW_OBJV1(&ImageScreenSize, COLS * LASTLINE,
	      &ImageScreen, sizeof(ImageScreen[0]), TRUE);
    memset(ImageScreen, 0, sizeof(ImageScreen[0]) * ImageScreenSize);
    n_terminal_image = 0;
}

static void
closeImgdisplay(void)
{
    if (Imgdisplay_wf) {
	fclose(Imgdisplay_wf);
	Imgdisplay_wf = NULL;
    }

    if (Imgdisplay_rf) {
	fclose(Imgdisplay_rf);
	Imgdisplay_rf = NULL;
    }
}

void
initImgdisplay()
{
    int wfd[2], rfd[2];
    char *cmd;

    if (activeImgdisplay)
	return;

    closeImgdisplay();
    activeImage = FALSE;
    flush_tty();

    if (pipe(wfd) < 0)
	return;

    if (pipe(rfd) < 0) {
	close(wfd[0]);
	close(wfd[1]);
	return;
    }

    if ((Imgdisplay_pid = fork()) == 0) {
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	close_tty();
	close(wfd[1]);
	close(rfd[0]);
	dup2(wfd[0], 0);
	dup2(rfd[1], 1);

	if (wfd[0] > 1)
	    close(wfd[0]);

	if (rfd[1] > 1)
	    close(rfd[1]);

	cmd = Imgdisplay[0] == '/' ? Imgdisplay : Sprintf("%s/%s", w3m_auxbin_dir(), Imgdisplay)->ptr;
	execl("/bin/sh", "sh", "-c", cmd, NULL);
	exit(1);
    }

    close(wfd[0]);
    close(rfd[1]);

    if (Imgdisplay_pid < 0 ||
	!(Imgdisplay_wf = fdopen(wfd[1], "w")) ||
	!(Imgdisplay_rf = fdopen(rfd[0], "r"))) {
	if (Imgdisplay_wf) {
	    fclose(Imgdisplay_wf);
	    Imgdisplay_wf = NULL;
	}
	else
	    close(wfd[1]);

	close(rfd[0]);
	return;
    }

    activeImage = activeImgdisplay = TRUE;
}

void
termImage(pid_t pid)
{
    if (pid < 0 || pid == Imgdisplay_pid) {
	if (pid < 0)
	    closeImgdisplay();

	Imgdisplay_pid = -1;
	activeImage = activeImgdisplay = FALSE;
    }
}

void
addImage(ImageCache *cache, int x, int y, int sx, int sy, int w, int h)
{
    TerminalImage *i;
    int cxb, cyb, cxe, cye, j, row, col, old;

    if (!activeImage)
	return;

    if (!activeImgdisplay) {
	initImgdisplay();

	if (!activeImgdisplay) {
	    termImage(-1);
	    return;
	}
    }

    cxb = x / pixel_per_char;
    cyb = y / pixel_per_line;
    cxe = (cxb * pixel_per_char + w + pixel_per_char - 1.0) / pixel_per_char;
    cye = (cyb * pixel_per_line + h + pixel_per_line - 1.0) / pixel_per_line;

    for (old = 0, row = cyb ; row < cye ; ++row)
	for (j = row * COLS + cxb, col = cxb ; col < cxe ; ++col, ++j) {
	    if (ImageScreen[j]) {
		TerminalImage *oi;

		oi = &terminal_image[ImageScreen[j] - 1];

		if ((!old || old == ImageScreen[j]) &&
		    oi->cache == cache &&
		    oi->x == x && oi->y == y &&
		    oi->sx == sx && oi->sy == sy &&
		    oi->width == w && oi->height == h) {
		    if (!old) {
			if (!oi->clear_p) {
			    oi->cache->displayed = TRUE;
			    return;
			}

			old = ImageScreen[j];
		    }

		    continue;
		}

		if (!oi->clear_p)
		    oi->dirty_p = TRUE;

		oi->clear_p = TRUE;
	    }

	    old = -1;
	}

    if (old > 0) {
	i = &terminal_image[old - 1];
	i->cache->displayed = TRUE;

	if (i->clear_p) {
	    i->dirty_p = TRUE;
	    i->clear_p = FALSE;
	}

	return;
    }

    NEW_OBJV1(&max_terminal_image, n_terminal_image,
	      &terminal_image, sizeof(TerminalImage), FALSE);
    i = &terminal_image[n_terminal_image++];

    while (cyb < cye)
	for (j = cyb++ * COLS + cxb, col = cxb ; col < cxe ; ++col)
	    ImageScreen[j++] = n_terminal_image;

    i->cache = cache;
    i->dirty_p = TRUE;
    i->clear_p = FALSE;
    i->serial = terminal_image_serial++;
    i->x = x;
    i->y = y;
    i->sx = sx;
    i->sy = sy;
    i->width = w;
    i->height = h;
    cache->displayed = TRUE;
}

int
getImageSize(ImageCache *cache, int subproc_p)
{
    Str tmp;
    int w = 0, h = 0;

    if (!cache)
	return -1;

    if (cache->loaded != IMG_FLAG_LOADED)
	return -1;

    if (cache->width > 0 && cache->height > 0)
	goto end;

    if (subproc_p) {
	fprintf(urgent_out, "%X %s\n", urgent_image_size, quoteImageCache(cache)->ptr);
	fflush(urgent_out);
	tmp = Strfgets(stdin);
	Strchop(tmp);

	if (tmp->length) {
	    unquoteImageCache(cache, urgent_unquote(tmp)->ptr);

	    if (cache->width > 0 && cache->height > 0)
		goto end;
	}

	return -1;
    }

    initImgdisplay();

    if (!activeImgdisplay)
	return -1;

    fprintf(Imgdisplay_wf, "%c;%s\n", '0' + IMGDISPLAY_SIZE, file_quote(cache->file));
    fflush(Imgdisplay_wf);

    if (!(tmp = Strfgets(Imgdisplay_rf))->length)
	return -1;

    sscanf(tmp->ptr, "%d %d", &w, &h);

    if (!(w > 0 && h > 0))
	return -1;

    if (!(w = (int)(w * image_scale / 100 + 0.5)))
	w = 1;

    if (!(h = (int)(h * image_scale / 100 + 0.5)))
	h = 1;

    if (cache->width < 0 && cache->height < 0) {
	cache->width = (w > MAX_IMAGE_SIZE) ? MAX_IMAGE_SIZE : w;
	cache->height = (h > MAX_IMAGE_SIZE) ? MAX_IMAGE_SIZE : h;
    }
    else if (cache->width < 0) {
	if ((cache->width = (int)((double)cache->height * w / h + 0.5)) > MAX_IMAGE_SIZE)
	    cache->width = MAX_IMAGE_SIZE;
    }
    else if (cache->height < 0) {
	if ((cache->height = (int)((double)cache->width * h / w + 0.5)) > MAX_IMAGE_SIZE)
	    cache->height = MAX_IMAGE_SIZE;
    }

    if (cache->width == 0)
	cache->width = 1;

    if (cache->height == 0)
	cache->height = 1;
end:
    putImageCache(cache);
    return 0;
}

static void
doDrawImage(void)
{
    TerminalImage *i;
    int n, j, k, l, cxb, cyb, cxe, cye, col;

    needDrawImage = FALSE;

    if (!activeImage)
	return;

    for (n = j = k = 0 ; j < n_terminal_image ;) {
	i = &terminal_image[j++];

	if (i->cache->loaded == IMG_FLAG_LOADED &&
	    i->width > 0 && i->height > 0) {
	    if (i->dirty_p) {
		if (i->clear_p)
		    fprintf(Imgdisplay_wf, "%c;%d\n",
			    '0' + IMGDISPLAY_CLRIMG, i->serial);
		else
		    fprintf(Imgdisplay_wf, "%c;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%s\n",
			    '0' + IMGDISPLAY_DRAW,
			    i->cache->index, i->serial, i->x, i->y,
			    i->cache->width > 0 ? i->cache->width : 0,
			    i->cache->height > 0 ? i->cache->height : 0,
			    i->sx, i->sy, i->width, i->height,
			    i->cache->file);

		i->dirty_p = FALSE;
	    }

	    ++n;
	}

	if (i->clear_p) {
	    cxb = i->x / pixel_per_char;
	    cyb = i->y / pixel_per_line;
	    cxe = (cxb * pixel_per_char + i->width + pixel_per_char - 1.0) / pixel_per_char;
	    cye = (cyb * pixel_per_line + i->height + pixel_per_line - 1.0) / pixel_per_line;

	    for (; cyb < cye ; ++cyb)
		for (l = cyb * COLS + cxb, col = cxb ; col < cxe ; ++col, ++l)
		    if (ImageScreen[l] == j)
			ImageScreen[l] = 0;
	}
	else {
	    terminal_image[k++] = *i;

	    if (k != j) {
		cxb = i->x / pixel_per_char;
		cyb = i->y / pixel_per_line;
		cxe = (cxb * pixel_per_char + i->width + pixel_per_char - 1.0) / pixel_per_char;
		cye = (cyb * pixel_per_line + i->height + pixel_per_line - 1.0) / pixel_per_line;

		for (; cyb < cye ; ++cyb)
		    for (l = cyb * COLS + cxb, col = cxb ; col < cxe ; ++col)
			ImageScreen[l++] = k;
	    }
	}
    }

    n_terminal_image = k;

    if (n || k) {
	fprintf(Imgdisplay_wf, "%c;\n", '0' + IMGDISPLAY_XSYNC);
	fflush(Imgdisplay_wf);
    }
}

void
drawImage(void)
{
    needDrawImage = TRUE;
}

void
clearImage(BufferView *v)
{
    int j, cxb, cxe, cyb, cye;
    TerminalImage *i;

    if (!activeImage)
	return;

    for (j = 0 ; j < n_terminal_image ;) {
	i = &terminal_image[j++];

	if (!i->clear_p) {
	    if (v) {
		cxb = i->x / pixel_per_char;
		cyb = i->y / pixel_per_line;
		cxe = (i->x + i->width + pixel_per_char - 1) / pixel_per_char;
		cye = (i->y + i->height + pixel_per_line - 1) / pixel_per_line;

		if (cxe <= v->rootX || cxb >= v->rootX + v->width ||
		    cye <= v->rootY || cyb >= v->rootY + v->height)
		    continue;
	    }

	    i->dirty_p = i->clear_p = TRUE;
	}
    }

    drawImage();
}

void
resetImage()
{
    if (!activeImgdisplay)
	return;

    fprintf(Imgdisplay_wf, "%c;\n", '0' + IMGDISPLAY_CLEAR);	/* ClearImage() */
    fflush(Imgdisplay_wf);
    clearImage(NULL);
}

void
untouchImageRegion(void)
{
    if (n_terminal_image) {
	TerminalImage *i;
	Screen *l;
	int j = 0, cxb, cxe, cyb, cye, col;
	Str buf;

	do {
	    i = &terminal_image[j++];
	    i->cache->displayed = TRUE;

	    if (i->cache->loaded == IMG_FLAG_LOADED &&
		i->width > 0 && i->height > 0) {
		cxb = i->x / pixel_per_char;
		cyb = i->y / pixel_per_line;
		cxe = (cxb * pixel_per_char + i->width + pixel_per_char - 1.0) / pixel_per_char;
		cye = (cyb * pixel_per_line + i->height + pixel_per_line - 1.0) / pixel_per_line;

		while (cyb < cye)
		    for (l = ScreenImage[cyb++], col = cxb ; col < cxe ;)
			if (i->clear_p)
			    l->lineprop[col++] |= S_DIRTY;
			else
			    l->lineprop[col++] &= ~S_DIRTY;
	    }
	} while (j < n_terminal_image);

	if (next_dcompl_top >= 0) {
	    int x, y;

	    for (y = next_dcompl_top ; y < INPUTLINE ;)
		for (j = y * COLS, l = ScreenImage[y++], x = 0 ; x < COLS ; ++j) {
		    l->lineprop[x++] |= S_DIRTY;

		    if (ImageScreen[j]) {
			i = &terminal_image[ImageScreen[j] - 1];

			if (!i->clear_p)
			    i->dirty_p = TRUE;
		    }
		}

	    buf = Sprintf(";%d;%d;%d;%d",
			  0, (int)(next_dcompl_top * pixel_per_line),
			  (int)(COLS * pixel_per_char),
			  (int)((INPUTLINE - next_dcompl_top) * pixel_per_line));
	}
#ifdef USE_MENU
	else if (CurrentMenu && CurrentMenuPopup) {
	    Menu *menu;
	    int x, y, xe, ye;
#ifdef USE_MENU_BUFFER
	    Buffer *b;
	    BufferView *v;
#endif

	    buf = Strnew();

	    for (menu = CurrentMenu ; menu ; menu = menu->parent) {
#ifdef USE_MENU_BUFFER
		if (!(b = menu->owner) || !(v = b->view))
		    continue;

		x = v->rootX - FRAME_WIDTH;
		xe = v->rootX + v->width + FRAME_WIDTH;
		y = v->rootY - 1;
		ye = v->rootY + v->height + 1;
#else
		x = menu->x - FRAME_WIDTH;
		xe = menu->x + menu->width + FRAME_WIDTH;
		y = menu->y - 1;
		ye = menu->y + menu->height + 1;
#endif
		Strcat(buf,
		       Sprintf(";%d;%d;%d;%d",
			       (int)(x * pixel_per_char),
			       (int)(y * pixel_per_line),
			       (int)((xe - x) * pixel_per_char),
			       (int)((ye - y) * pixel_per_line)));

		while (y < ye) {
		    l = ScreenImage[y++];

		    for (j = y * COLS + x, col = x ; col < xe ; ++j) {
			l->lineprop[col++] |= S_DIRTY;

			if (ImageScreen[j]) {
			    i = &terminal_image[ImageScreen[j] - 1];

			    if (!i->clear_p)
				i->dirty_p = TRUE;
			}
		    }
		}
	    }
	}
#endif
	else if (CurLine < LASTLINE) {
	    int xb, xe;

	    xb = CurColumn;
	    xe = xb + 1;
	    l = ScreenImage[CurLine];

#if defined(MANY_CHARSET) || defined(JP_CHARSET)
	    if (xb > 0 && CHMODE(l->lineprop[xb]) == C_WCHAR2)
		--xb;

	    if (xe < COLS && CHMODE(l->lineprop[xe]) == C_WCHAR2)
		++xe;
#endif

	    for (col = xb ; col < xe ; ++col)
		if (l->lineprop[col] & S_EOL)
		    break;

	    if (xb < col) {
		buf = Sprintf(";%d;%d;%d;%d",
			      (int)(xb * pixel_per_char), (int)(CurLine * pixel_per_line),
			      (int)((col - xb) * pixel_per_char), (int)pixel_per_line);

		for (j = CurLine * COLS + xb ; xb < col ; ++j) {
		    l->lineprop[xb++] |= S_DIRTY;

		    if (ImageScreen[j]) {
			i = &terminal_image[ImageScreen[j] - 1];

			if (!i->clear_p)
			    i->dirty_p = TRUE;
		    }
		}
	    }
	    else
		return;
	}
	else
	    return;

	fprintf(Imgdisplay_wf, "%c%s\n", '0' + IMGDISPLAY_CLIP, buf->ptr);
	fflush(Imgdisplay_wf);
    }
}

static void
makeImageLoaded(BufferView *v)
{
    if (!v)
	return;

    if (v->frameset) {
	int i;

	for (i = v->nrows * v->ncols ; i > 0 ;)
	    makeImageLoaded(v->subv[--i].top);
    }
    else {
	Buffer *buf;

	for (buf = v->top ; buf ; buf = buf->down)
	    if (buf->bufferprop & BP_VISIBLE) {
		buf->image_unloaded = FALSE;
		break;
	    }
    }
}

void
checkUnloadedImage(BufferView *v)
{
    int j;
    TerminalImage *i;
    Buffer *buf;

    if (!v) {
	Buffer *cur;

	cur = Currentbuf;

#if defined(USE_MENU) && defined(USE_MENU_BUFFER)
	if (cur && cur->menu)
	    cur = cur->menu->current;
#endif

	if (!cur || !cur->view)
	    return;

	v = cur->view->root;
    }

    makeImageLoaded(v);

    for (j = n_terminal_image ; j > 0 ;) {
	i = &terminal_image[--j];

	if (i->cache->loaded == IMG_FLAG_UNLOADED &&
	    (buf = bufferAtXY(i->x / pixel_per_char, i->y / pixel_per_line))
#if defined(USE_MENU) && defined(USE_MENU_BUFFER)
	    && !buf->menu
#endif
	    ) {
	    buf->image_unloaded = TRUE;
	    buf->redraw_mode = B_REDRAW_IMAGE;
	}
    }
}
#endif

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
