/*
 * screen.c
 *
 * Written By Matthew Green, based on portions of window.c
 * by Michael Sandrof.
 *
 * Copyright (c) 1990 Michael Sandrof.
 * Copyright (c) 1993-2006 Matthew R. Green.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "irc.h"
IRCII_RCSID("@(#)$eterna: screen.c,v 1.151 2006/07/22 03:50:10 mrg Exp $");

#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#endif /* HAVE_SYS_UN_H */

#ifdef HAVE_ICONV_H
# include <iconv.h>
#endif /* HAVE ICONV_H */

#include "screen.h"
#include "menu.h"
#include "window.h"
#include "output.h"
#include "vars.h"
#include "server.h"
#include "list.h"
#include "ircterm.h"
#include "names.h"
#include "ircaux.h"
#include "input.h"
#include "log.h"
#include "hook.h"
#include "dcc.h"
#include "translat.h"
#include "exec.h"
#include "newio.h"
#include "parse.h"
#include "edit.h"
#include "strsep.h"

#ifdef lines
#undef lines
#undef columns
#endif /* lines */

	Window	*to_window;
	Screen	*current_screen;
	Screen	*main_screen;
	Screen	*last_input_screen;

extern	int	in_help;
extern	u_char	*redirect_nick;

/* Our list of screens */
Screen	*screen_list = NULL;

static	void	display_lastlog_lines(int, int, Window *);
static	Screen	* create_new_screen(void);
static	u_char	** split_up_line(u_char *);
static	void	scrollback_backwards_lines(int);
static	void	scrollback_forwards_lines(int);
static	u_char	display_highlight(int);
static	u_char	display_bold(int);
static	void	display_colours(int, int);
static	void	display_text(u_char *, size_t);
static	void	decode_colour(u_char **, int *, int *);
static	void	add_to_window(Window *, u_char *);
static	u_int	create_refnum(void);
static	void	redraw_window(Window *, int, int);
static	u_char	*next_line_back(Window *);
static	int	lastlog_lines(Window *);

/*
 * create_new_screen creates a new screen structure. with the help of
 * this structure we maintain ircII windows that cross screen window
 * boundaries.
 */
static	Screen	*
create_new_screen()
{
	Screen	*new = NULL,
		**list;
	static	int	refnumber = 0;

	for (list = &screen_list; *list; list = &((*list)->next))
	{
		if (!(*list)->alive)
		{
			new = *list;
			break;
		}
	}
	if (!new)
	{
		new = (Screen *) malloc(sizeof(Screen));
		new->screennum = ++refnumber;
		new->next = screen_list;
		if (screen_list)
			screen_list->prev = new;
		screen_list = new;
	}
	new->last_window_refnum = 1;
	new->window_list = NULL;
	new->window_list_end = NULL;
	new->cursor_window = NULL;
	new->current_window = NULL;
	new->visible_windows = 0;
	new->window_stack = NULL;
	new->meta1_hit = new->meta2_hit = new->meta3_hit = new->meta4_hit = 0;
	new->meta5_hit = new->meta6_hit = new->meta7_hit = new->meta8_hit = 0;
	new->quote_hit = new->digraph_hit = new->inside_menu = 0;
	new->fdout = 1;
	new->fpout = stdout;
	new->fdin = 0;
	new->fpin = stdin;
	new->alive = 1;
	new->promptlist = NULL;
	new->redirect_name = NULL;
	new->redirect_token = NULL;
	new->tty_name = (u_char *) 0;
	new->li = main_screen ? main_screen->li : 24;
	new->co = main_screen ? main_screen->co : 79;
	new->old_term_li = -1;
	new->old_term_co = -1;

	input_reset_screen(new);
	
	new->redirect_server = -1;
	last_input_screen = new;
	return new;
}

/* 
 * add_wait_prompt:  Given a prompt string, a function to call when
 * the prompt is entered.. some other data to pass to the function,
 * and the type of prompt..  either for a line, or a key, we add 
 * this to the prompt_list for the current screen..  and set the
 * input prompt accordingly.
 */
void
add_wait_prompt(prompt, func, data, type)
	u_char	*prompt;
	void	(*func)(u_char *, u_char *);
	u_char	*data;
	int	type;
{
	WaitPrompt **AddLoc,
		   *New;

	New = (WaitPrompt *) new_malloc(sizeof(WaitPrompt));
	New->prompt = NULL;
	malloc_strcpy(&New->prompt, prompt);
	New->data = NULL;
	malloc_strcpy(&New->data, data);
	New->type = type;
	New->func = func;
	New->next = NULL;
	for (AddLoc = &current_screen->promptlist; *AddLoc;
			AddLoc = &(*AddLoc)->next);
	*AddLoc = New;
	if (AddLoc == &current_screen->promptlist)
		change_input_prompt(1);
}

void
set_current_screen(screen)
	Screen	*screen;
{
	if (screen->alive)
		current_screen = screen;
	else
		current_screen = screen_list;
	term_set_fp(current_screen->fpout);
}

/*
 * window_redirect: Setting who to non null will cause IRCII to echo all
 * commands and output from that server (including output generated by IRCII)
 * to who.  Setting who to null disables this 
 */
void
window_redirect(who, server)
	u_char	*who;
	int	server;
{
	u_char	buf[BIG_BUFFER_SIZE];

	if (who)
		snprintf(CP(buf), sizeof buf, "%04d%s", server, who);
	else
		snprintf(CP(buf), sizeof buf, "%04d#LAME", server);
	malloc_strcpy(&current_screen->redirect_token, buf);
	malloc_strcpy(&current_screen->redirect_name, who);
	current_screen->redirect_server = server;
}

int
check_screen_redirect(nick)
	u_char	*nick;
{
	Screen	*screen,
		*tmp_screen;
	
	for (screen = screen_list; screen; screen = screen->next)
	{
		if (!screen->redirect_token)
			continue;
		if (!my_strcmp(nick, screen->redirect_token))
		{
			tmp_screen = current_screen;
			set_current_screen(screen);
			window_redirect(NULL, from_server);
			set_current_screen(tmp_screen);
			return 1;
		}
	}
	return 0;
}

/* Old screens never die. They just fade away. */

#ifdef WINDOW_CREATE
void
kill_screen(screen)
	Screen	*screen;
{
	Window	*window;

	if (main_screen == screen)
	{
		say("You may not kill the main screen");
		return;
	}
	if (screen->fdin)
	{
		new_close(screen->fdout);
		new_close(screen->fdin);
	}
	while ((window = screen->window_list))
	{
		screen->window_list = window->next;
		add_to_invisible_list(window);
	}

	if (last_input_screen == screen)
		last_input_screen = screen_list;
	screen->alive = 0;
}

int
is_main_screen(screen)
	Screen	*screen;
{
	return (screen == main_screen);
}

#else

static	int
is_main_screen(screen)
	Screen	*screen;
{
	return 1;
}

#endif /* WINDOW_CREATE */


/*
 * scroll_window: Given a pointer to a window, this determines if that window
 * should be scrolled, or the cursor moved to the top of the screen again, or
 * if it should be left alone. 
 */
void
scroll_window(window)
	Window	*window;
{
	if (dumb)
		return;
	if (window->cursor == window->display_size)
	{
		if (window->scroll)
		{
			int	do_scroll,
				i;

			if ((do_scroll = get_int_var(SCROLL_LINES_VAR)) <= 0)
				do_scroll = 1;

			for (i = 0; i < do_scroll; i++)
			{
				new_free(&window->top_of_display->line);
				window->top_of_display =
					window->top_of_display->next;
			}
			if (window->visible)
			{
				if (term_scroll(window->top + window->menu.lines, window->top + window->menu.lines + window->cursor - 1, do_scroll))
				{
					if (current_screen->visible_windows == 1)
					{
						Window	*tmp;

			/*
			 * this method of sim-u-scroll seems to work fairly
			 * well. The penalty is that you can only have one
			 * window, and of course you can't use the scrollback
			 * buffer either. Or menus. Actually, this method
			 * really doesn't work very well at all anymore.
			 */
						tmp = current_screen->cursor_window;
						term_move_cursor(0, current_screen->li - 2);
						if (term_clear_to_eol())
							term_space_erase(0);
						term_cr();
						term_newline();
						for (i = 0; i < do_scroll; i++)
						{
							term_cr();
							term_newline();
						}
						update_window_status(window, 1);
						update_input(UPDATE_ALL);
						current_screen->cursor_window = tmp;
						Debug((3, "scroll_window: screen %d cursor_window set to window %d (%d)", current_screen->screennum, window->refnum, window->screen->screennum));
					}
					else
						redraw_window(window, 1, 0);
				}
				window->cursor -= do_scroll;
				term_move_cursor(0, window->cursor + window->top + window->menu.lines);
			}
			else
				window->cursor -= do_scroll;
		}
		else
		{
			window->cursor = 0;
			if (window->visible)
				term_move_cursor(0, window->top + window->menu.lines);
		}
	}
	else if (window->visible && current_screen->cursor_window == window)
	{
		term_cr();
		term_newline();
	}
	if (window->visible && current_screen->cursor_window)
	{
		if (term_clear_to_eol()) /* && !window->hold_mode && !window->hold_on_next_rite) */
		{
			term_space_erase(0);
			term_cr();
		}
		term_flush();
	}
}

/* display_highlight: turns off and on the display highlight.  */
static	u_char
display_highlight(flag)
	int	flag;
{
	static	int	highlight = OFF;

	term_flush();
	if (flag == highlight)
		return (flag);
	switch (flag)
	{
	case ON:
		highlight = ON;
		if (get_int_var(INVERSE_VIDEO_VAR))
			term_standout_on();
		return (OFF);
	case OFF:
		highlight = OFF;
		if (get_int_var(INVERSE_VIDEO_VAR))
			term_standout_off();
		return (ON);
	case TOGGLE:
		if (highlight == ON)
		{
			highlight = OFF;
			if (get_int_var(INVERSE_VIDEO_VAR))
				term_standout_off();
			return (ON);
		}
		else
		{
			highlight = ON;
			if (get_int_var(INVERSE_VIDEO_VAR))
				term_standout_on();
			return (OFF);
		}
	}
	return flag;
}

/* display_bold: turns off and on the display bolding.  */
static	u_char
display_bold(flag)
	int	flag;
{
	static	int	bold = OFF;

	term_flush();
	if (flag == bold)
		return (flag);
	switch (flag)
	{
	case ON:
		bold = ON;
		if (get_int_var(BOLD_VIDEO_VAR))
			term_bold_on();
		return (OFF);
	case OFF:
		bold = OFF;
		if (get_int_var(BOLD_VIDEO_VAR))
			term_bold_off();
		return (ON);
	case TOGGLE:
		if (bold == ON)
		{
			bold = OFF;
			if (get_int_var(BOLD_VIDEO_VAR))
				term_bold_off();
			return (ON);
		}
		else
		{
			bold = ON;
			if (get_int_var(BOLD_VIDEO_VAR))
				term_bold_on();
			return (OFF);
		}
	}
	return OFF;
}

/* display_colours sets the foreground and background colours of the display
 */
static void
display_colours(fgcolour, bgcolour)
	int fgcolour;
	int bgcolour;
{
	if (get_int_var(COLOUR_VAR))
	{
		/* Some people will say that I should use termcap values but
		 * since:
		 * 1- iso 6429 is the only used way for colour in the unix
		 *    realm for now
		 * 2- colour information in termcap entries is still rare
		 * 3- information about colour information in termcap entries
		 *    is still rare too
		 * ... I'll stick with this way for now. But having only 8-9
		 * colour is a pity.
		 *    -- Sarayan
		 */
		 
		/* Written by Bisqwit (bisqwit@iki.fi) */
		 
		/* mirc colours -> iso 6469 colours translation tables */
		static const u_char trans[] = "7042115332664507";
		static const u_char bolds[] = "1000100011011110";
		                            /* 0123456789ABCDEF */
		
		u_char iso[15]; /* long enough for "e[0;1;5;37;40m" */
		
		snprintf(CP(iso), sizeof iso, "\33[0;");
		if (bolds[fgcolour] == '1')
			my_strcat(iso, "1;");
		if (bolds[bgcolour] == '1')
			my_strcat(iso, "5;");
		snprintf(CP(my_index(iso, 0)), 7, "3%c;4%cm", trans[fgcolour&15], trans[bgcolour&15]);
		
		fwrite(CP(iso), my_strlen(iso), 1, current_screen->fpout);
	}
}

static void
display_text(ustr, length)
	u_char   *ustr;
	size_t length;
{
	iconv_const char *str = (iconv_const char *)ustr;

	if (length > 0)
	{
#ifdef HAVE_ICONV_OPEN
		static iconv_t converter = NULL;

		if (display_encoding)
		{
			if (!str)
			{
				/* str = NULL means reinitialize iconv. */
				if (converter)
				{
					iconv_close(converter);
					converter = NULL;
				}
				return;
			}
			if (!converter)
			{
				converter = iconv_open(CP(display_encoding), "UTF-8");
				if (converter == (iconv_t)(-1))
				{
					iconv_close(converter);
					converter = NULL;
				}
			}
		}
		if (converter)
		{
			char final = 0;
			while (!final)
			{
				char OutBuf[512], *outptr = OutBuf;
				size_t outsize = sizeof OutBuf;
				size_t retval = 0;
				
				if (length <= 0)
				{
					/* Reset the converter, create a reset-sequence */
					retval = iconv(converter,
					               NULL,    &length,
					               &outptr, &outsize);
					final = 1;
				}
				else
				{
					retval = iconv(converter,
					               &str, &length,
					               &outptr, &outsize);
				}
			
				/* Write out as much as we got */
				fwrite(OutBuf, sizeof(OutBuf)-outsize, 1, current_screen->fpout);
				
				if (retval == (size_t)-1)
				{
					if (errno == E2BIG)
					{
						/* Outbuf could not contain everything. */
						/* Try again with a new buffer. */
						continue;
					}
					if (errno == EILSEQ)
					{
						/* Ignore 1 illegal byte silently. */
						if (length > 0)
						{
							++str;
							--length;
						}
					}
					if (errno == EINVAL)
					{
						/* Input was terminated with a partial byte. */
						/* Ignore the error silently. */
						length = 0;
					}
				}
			}
			return;
		}
#endif /* HAVE_ICONV_OPEN */
		/* No usable iconv, assume output must be ISO-8859-1 */
		if (str != NULL)
		{
			char OutBuf[1024], *outptr=OutBuf;
			/* Convert the input to ISO-8859-1 character
			 * by character, flush to fpout in blocks.
			 * Ignore undisplayable characters silently.
			 */
			while (*str != '\0' && length > 0)
			{
				unsigned len    = calc_unival_length(UP(str));
				unsigned unival;

				if (!len)
				{
				    /* ignore illegal byte (shouldn't happen) */
					++str;
					continue;
				}
				if (len > length)
					break;
				length -= len;
				
				unival = calc_unival(UP(str));
				if (displayable_unival(unival, NULL))
				{
					if (outptr >= OutBuf+sizeof(OutBuf))
					{
						/* flush a block */
						fwrite(OutBuf, outptr-OutBuf, 1,
						       current_screen->fpout);
						outptr = OutBuf;
					}
					*outptr++ = unival;
				}
				str += len;
			}
			if (outptr > OutBuf)
			{
				/* Flush the last block */
				fwrite(OutBuf, outptr-OutBuf, 1,
				       current_screen->fpout);
			}
		}
	}
}

static void
display_nonshift(void)
{
	display_text(NULL, 1);
}


/*
 * output_line prints the given string at the current screen position,
 * performing adjustments for ^_, ^B, ^V, and ^O
 * If the input is longer than line may be, it cuts it.
 * Return value: Number of columns printed
 */
int
output_line(str, startpos)
	u_char	*str;
	int	startpos;
{
	int     fgcolour_user = get_int_var(FOREGROUND_COLOUR_VAR),
		bgcolour_user = get_int_var(BACKGROUND_COLOUR_VAR);
	static	int	high = OFF,
			bold = OFF,
			fgcolour = -1,
			bgcolour = -1;
	int	rev_tog, und_tog, bld_tog, all_off;
	int     dobeep = 0;
	int     written = 0;

	display_highlight(high);
	display_bold(bold);
	display_colours(fgcolour, bgcolour);
	/* do processing on the string, handle inverse and bells */
	display_nonshift();
	while (*str)
	{
		switch (*str)
		{
		case REV_TOG:
		case UND_TOG:
		case BOLD_TOG:
		case ALL_OFF:
			display_nonshift();
			rev_tog = und_tog = bld_tog = all_off = 0;
			switch (*str++)
			{
			case REV_TOG:
				rev_tog = 1 - rev_tog;
				break;
			case UND_TOG:
				und_tog = 1 - und_tog;
				break;
			case BOLD_TOG:
				bld_tog = 1 - bld_tog;
				break;
			case ALL_OFF:
				all_off = 1;
				und_tog = rev_tog = bld_tog = 0;
				break;
			}
			if (all_off)
			{
				if (!underline)
				{
					term_underline_off();
					underline = 1;
				}
				display_highlight(OFF);
				display_bold(OFF);
				display_colours(
					fgcolour = fgcolour_user,
				    bgcolour = bgcolour_user);
				high = 0;
				bold = 0;
			}
			if (und_tog && get_int_var(UNDERLINE_VIDEO_VAR))
			{
				/*
				 * Fix up after termcap may have turned
				 * everything off.
				 */
				if (bold)
					display_bold(ON);
				if (high)
					display_highlight(ON);

				if ((underline = 1 - underline) != 0)
					term_underline_off();
				else
					term_underline_on();
			}
			if (rev_tog)
			{
				/*
				 * Fix up after termcap may have turned
				 * everything off.
				 */
				if (!underline)
					term_underline_on();
				if (bold)
					display_bold(ON);

				high = display_highlight(TOGGLE);
				high = 1 - high;
			}
			if (bld_tog)
			{
				/*
				 * Fix up after termcap may have turned
				 * everything off.
				 */
				if (!underline)
					term_underline_on();
				if (high)
					display_highlight(ON);

				bold = display_bold(TOGGLE);
				bold = 1 - bold;
			}
			break;
		case COLOUR_TAG:
			display_nonshift();
			while (*str == COLOUR_TAG)
			{
				/* parse all consequent colour settings */
				int fg, bg;

				fg = *++str;
				if (!fg--)
					break;
				
				bg = *++str;
				if (!bg--)
					break;
				
				if (fg < 16)
					fgcolour = fg;
				if (bg < 16)
					bgcolour = bg;
				
				++str;
			}
			display_colours(fgcolour, bgcolour);
			break;
		case FULL_OFF:
			++str;
			display_nonshift();
			if (!underline)
			{
				term_underline_off();
				underline = 1;
			}
			display_highlight(OFF);
			display_bold(OFF);
			display_colours(
				fgcolour = fgcolour_user,
			    bgcolour = bgcolour_user);
			high = 0;
			bold = 0;
			/* fgcolour = bgcolour = 16; */
			break;
		case '\007':
			/* After we display everything, we beep the terminal */
			++dobeep;
			++str;
			break;
		default:
			{
				unsigned n = calc_unival_length(str);
				/* n should never be 0 (that would mean a broken
				 * character), but here we just ensure we
				 * don't get an infinite loop.
				 */
				if (n == 0)
					n = 1;
				
				/* Input is supposedly an UTF-8 character */
				if (written < current_screen->co)
				{
					unsigned unival = calc_unival(str);
					written += calc_unival_width(unival);
					
					display_text(str, n);
				}
				
				str += n;
				break;
			}
		}
	}
	display_nonshift();
	if (dobeep)
		term_beep();
	return written;
}

/*
 * rite: this routine displays a line to the screen adding bold facing when
 * specified by ^Bs, etc.  It also does handles scrolling and paging, if
 * SCROLL is on, and HOLD_MODE is on, etc.  This routine assumes that str
 * already fits on one screen line.  If show is true, str is displayed
 * regardless of the hold mode state.  If redraw is true, it is assumed we a
 * redrawing the screen from the display_ip list, and we should not add what
 * we are displaying back to the display_ip list again. 
 *
 * Note that rite sets display_highlight() to what it was at then end of the
 * last rite().  Also, before returning, it sets display_highlight() to OFF.
 * This way, between susequent rites(), you can be assured that the state of
 * bold face will remain the same and the it won't interfere with anything
 * else (i.e. status line, input line). 
 */
int
rite(window, str, show, redraw, backscroll, logged)
	Window	*window;
	u_char	*str;
	int	show,
		redraw,
		backscroll,
		logged;
{
	static	int	high = OFF;
	Screen	*old_current_screen = current_screen;

	if (!redraw && !backscroll && window->scrolled_lines)
		window->new_scrolled_lines++;

	if (window->hold_mode && window->hold_on_next_rite && !redraw && !backscroll)
	{
		/* stops output from going to the window */
		window->hold_on_next_rite = 0;
		hold_mode(window, ON, 1);
		if (show)
			return (1);
	}
	/*
	 * Don't need to worry about the current_screen if the window isn't
	 * visible, as hidden windows aren't attached to a screen anyway
	 */
	if (window->visible)
	{
		old_current_screen = current_screen;
		set_current_screen(window->screen);
	}
	if (!show && (hold_output(window) || hold_queue(window)) && !in_help && !redraw && !backscroll)
		/* sends window output to the hold list for that window */
		add_to_hold_list(window, str, logged);
	else
	{
		if (!redraw && !backscroll)
		{
		/*
		 * This isn't a screen refresh, so add the line to the display
		 * list for the window 
		 */
			if (window->scroll)
				scroll_window(window);
			malloc_strcpy(&(window->display_ip->line), str);
			window->display_ip->linetype = logged;
			window->display_ip = window->display_ip->next;
			if (!window->scroll)
				new_free(&window->display_ip->line);
		}
		if (window->visible)
		{
			int written;
			
			/* make sure the cursor is in the appropriate window */
			if (current_screen->cursor_window != window &&
					!redraw && !backscroll)
			{
				current_screen->cursor_window = window;
				Debug((3, "rite: screen %d cursor_window set to window %d (%d)", current_screen->screennum, window->refnum, window->screen->screennum));
				term_move_cursor(0, window->cursor +
					window->top + window->menu.lines);
			}
			written = output_line(str, 0);
			if (term_clear_to_eol() && written < current_screen->co)
			{
				/* EOL wasn't implemented, so do it with spaces */
				term_space_erase(current_screen->co - written);
			}
		}
		else if (!(window->miscflags & WINDOW_NOTIFIED))
		{
			if ((who_level & window->notify_level)
			    || ((window->notify_level & LOG_BEEP)
				&& my_index(str, '\007')))
			{
				window->miscflags |= WINDOW_NOTIFIED;
				if (window->miscflags & WINDOW_NOTIFY)
				{
					Window	*old_to_window;
					int	lastlog_level;

					lastlog_level =
						set_lastlog_msg_level(LOG_CRAP);
					old_to_window = to_window;
					to_window = curr_scr_win;
					say("Activity in window %d",
						window->refnum);
					to_window = old_to_window;
					set_lastlog_msg_level(lastlog_level);
				}
				update_all_status();
			}
		}
		if (!redraw && !backscroll)
		{
			window->cursor++;
			window->line_cnt++;
			if (window->scroll)
			{
				if (window->line_cnt >= window->display_size)
				{
					window->hold_on_next_rite = 1;
					window->line_cnt = 0;
				}
			}
			else
			{
				scroll_window(window);
				if (window->cursor ==
				    (window->display_size - 1))
					window->hold_on_next_rite = 1;
			}
		}
		else if (window->visible)
		{
			term_cr();
			term_newline();
		}
		if (window->visible)
		{
			high = display_highlight(OFF);
			term_flush();
		}
	}
	if (window->visible)
		set_current_screen(old_current_screen);
	return (0);
}

/*
 * cursor_not_in_display: This forces the cursor out of the display by
 * setting the cursor window to null.  This doesn't actually change the
 * physical position of the cursor, but it will force rite() to reset the
 * cursor upon its next call 
 */
void
cursor_not_in_display()
{
	Debug((3, "cursor_not_in_display: screen %d cursor_window set to NULL", current_screen->screennum));
	current_screen->cursor_window = NULL;
}

/*
 * cursor_in_display: this forces the cursor_window to be the
 * current_screen->current_window. 
 * It is actually only used in hold.c to trick the system into thinking the
 * cursor is in a window, thus letting the input updating routines move the
 * cursor down to the input line.  Dumb dumb dumb 
 */
void
cursor_in_display()
{
	Debug((3, "cursor_in_display: screen %d cursor_window set to window %d (%d)", current_screen->screennum, curr_scr_win->refnum, curr_scr_win->screen->screennum));
	current_screen->cursor_window = curr_scr_win;
}

/*
 * is_cursor_in_display: returns true if the cursor is in one of the windows
 * (cursor_window is not null), false otherwise 
 */
int
is_cursor_in_display()
{
	if  (current_screen->cursor_window)
		return (1);
	else
		return (0);
}

void
redraw_resized(window, Info, AnchorTop)
	Window	*window;
	ShrinkInfo Info;
	int	AnchorTop;
{
	if (!AnchorTop)
	{
		if (Info.bottom < 0)
			term_scroll(window->top+window->menu.lines+Info.bottom,
				window->top + window->menu.lines +
				window->display_size - 1,
				Info.bottom);
		else if (Info.bottom)
			term_scroll(window->top+window->menu.lines,
				window->top + window->menu.lines +
				window->display_size -1, Info.bottom);
	}
}

/*
 * resize_display: After determining that the screen has changed sizes, this
 * resizes all the internal stuff.  If the screen grew, this will add extra
 * empty display entries to the end of the display list.  If the screen
 * shrank, this will remove entries from the end of the display list.  By
 * doing this, we try to maintain as much of the display as possible. 
 *
 * This has now been improved so that it returns enough information for
 * redraw_resized to redisplay the contents of the window without having
 * to redraw too much.
 */
ShrinkInfo
resize_display(window)
	Window	*window;
{
	int	cnt,
		i;
	Display *tmp, *pre_ip;
	int	Wrapped = 0;
	ShrinkInfo Result;

	Result.top = Result.bottom = 0;
	Result.position = window->cursor;
	if (dumb)
	{
		return Result;
	}
	if (!window->top_of_display)
	{
		window->top_of_display = (Display *)new_malloc(sizeof(Display));
		window->top_of_display->line = NULL;
		window->top_of_display->linetype = LT_UNLOGGED;
		window->top_of_display->next = window->top_of_display;
		window->display_ip = window->top_of_display;
		window->old_size = 1;
	}
	/* cnt = size - window->display_size; */
	cnt = window->display_size - window->old_size;
	if (cnt > 0)
	{
		Display *new = NULL;

	/*
	 * screen got bigger: got to last display entry and link in new
	 * entries 
	 */
		for (tmp = window->top_of_display, i = 0;
		    i < (window->old_size - 1);
		    i++, tmp = tmp->next);
		for (i = 0; i < cnt; i++)
		{
			new = (Display *) new_malloc(sizeof(Display));
			new->line = NULL;
			new->linetype = LT_UNLOGGED;
			new->next = tmp->next;
			tmp->next = new;
		}
		if (window->display_ip == window->top_of_display &&
		    window->top_of_display->line)
			window->display_ip = new;
		Result.top = 0;
		Result.bottom = cnt;
		Result.position = 0;
	}
	else if (cnt < 0)

	{
		Display *ptr;

	/*
	 * screen shrank: find last display entry we want to keep, and remove
	 * all after that point 
	 */
		cnt = -cnt;
		for (pre_ip = window->top_of_display;
		    pre_ip->next != window->display_ip;
		    pre_ip = pre_ip->next);
		for (tmp = pre_ip->next, i =0; i < cnt; i++, tmp = ptr)
		{
			ptr = tmp->next;
			if (tmp == window->top_of_display)

			{
				if (tmp->line)
					Wrapped = 1;
				window->top_of_display = ptr;
			}
			if (Wrapped)
				Result.top--;
			else
				Result.bottom--;
			new_free(&(tmp->line));
			new_free(&tmp);
		}
		window->display_ip = pre_ip->next = tmp;
		window->cursor += Result.top;
		if (!window->scroll)
		{
			if (window->cursor == window->display_size)
				window->cursor = 0;
			new_free(&window->display_ip->line);
		}
	}
	window->update |= REDRAW_DISPLAY_FULL | REDRAW_STATUS;
	window->old_size = window->display_size;
	return Result;
}

/*
 * recalculate_windows: this is called when the terminal size changes (as
 * when an xterm window size is changed).  It recalculates the sized and
 * positions of all the windows.  Currently, all windows are rebalanced and
 * window size proportionality is lost 
 */
void
recalculate_windows()
{
	int	base_size,
	size,
	top,
	extra;
	Window	*tmp;
	
	if (dumb || current_screen->visible_windows == 0)
		return;

	base_size = ((current_screen->li - 1) / current_screen->visible_windows) - 1;
	extra = (current_screen->li - 1) - ((base_size + 1)*current_screen->visible_windows);
	top = 0;
	for (tmp = current_screen->window_list; tmp; tmp = tmp->next)
	{
		tmp->update |= REDRAW_DISPLAY_FULL | REDRAW_STATUS;
		if (extra)
		{
			extra--;
			size = base_size + 1;
		}
		else
			size = base_size;
#ifdef SCROLL_AFTER_DISPLAY
		tmp->display_size = size - tmp->menu.lines
			- tmp->double_status - 1;
#else
		tmp->display_size = size - tmp->menu.lines
			- tmp->double_status;
#endif /* SCROLL_AFTER_DISPLAY */
		if (tmp->display_size<=0)
			tmp->display_size = 1;
		tmp->top = top;
		tmp->bottom = top + size - tmp->double_status;
		top += size + 1;
	}
}

/*
 * clear_window: This clears the display list for the given window, or
 * current window if null is given.  
 */
void
clear_window(window)
	Window	*window;
{
	int	i,
		cnt;

	if (dumb)
		return;
	if (window == NULL)
		window = curr_scr_win;
	erase_display(window);
	term_move_cursor(0, window->top + window->menu.lines);
	cnt = window->bottom - window->top - window->menu.lines;
	for (i = 0; i < cnt; i++)
	{
		if (term_clear_to_eol())
			term_space_erase(0);
		term_newline();
	}
	term_flush();
}

/* clear_all_windows: This clears all *visible* windows */
void
clear_all_windows(unhold)
	int	unhold;
{
	Window	*tmp;

	for (tmp = current_screen->window_list; tmp; tmp = tmp->next)
	{
		if (unhold)
			hold_mode(tmp, OFF, 1);
		clear_window(tmp);
	}
}

/*
 * redraw_window: This redraws the display list for the given window. Some
 * special considerations are made if you are just redrawing one window as
 * opposed to using this routine to redraw the whole screen one window at a
 * time 
 *
 * A negative value in just_one indicates not all of the window needs to
 * be redrawn.
 */
static void
redraw_window(window, just_one, backscroll)
	Window	*window;
	int	just_one;
	int	backscroll;
{
	Display *tmp;
	int	i;
	int	StartPoint;
	int	yScr;

	if (dumb || !window->visible)
		return;
	window = window ? window : curr_scr_win;
	if (just_one < 0)
	{
		/* This part of the window is scrolling into view */
		StartPoint = -just_one;
		just_one = 0;
	}
	else
	{
		StartPoint = 0;
		if (window->scrolled_lines)
			display_lastlog_lines(window->scrolled_lines - window->display_size,
			    window->scrolled_lines, window);
	}
	if (window->menu.menu)
		ShowMenuByWindow(window, just_one ? SMF_ERASE : 0);
	if (window->scrolled_lines + StartPoint < window->display_size)
		yScr = window->scrolled_lines + StartPoint;
	else
		yScr = 0;
	term_move_cursor(0, window->top+window->menu.lines+yScr);
	/*
	 * if (term_clear_to_eol())
	 *	{ term_space_erase(0); term_cr(); } 
	 */
	for (tmp = window->top_of_display, i = 0; i < window->display_size-window->scrolled_lines; i++, tmp = tmp->next)
	{
		if (i < StartPoint)
			continue;
		if (tmp->line)
			rite(window, tmp->line, 1, 1, backscroll, 0);
		else
		{
			if (just_one)
			{
				if (term_clear_to_eol())
					term_space_erase(0);
			}
			term_newline();
		}
	}
	term_flush();
}

/*
 * recalculate_window_positions: This runs through the window list and
 * re-adjusts the top and bottom fields of the windows according to their
 * current positions in the window list.  This doesn't change any sizes of
 * the windows 
 */
void
recalculate_window_positions()
{
	Window	*tmp;
	int	top;

	top = 0;
	for (tmp = current_screen->window_list; tmp; tmp = tmp->next)
	{
		tmp->update |= REDRAW_DISPLAY_FULL | REDRAW_STATUS;
		tmp->top = top;
		tmp->bottom = top + tmp->display_size + tmp->menu.lines;
		top += tmp->display_size + tmp->menu.lines + 1 +
			tmp->double_status;
	}
}

/*
 * redraw_all_windows: This basically clears and redraws the entire display
 * portion of the screen.  All windows and status lines are draws.  This does
 * nothing for the input line of the screen.  Only visible windows are drawn 
 */
void
redraw_all_windows()
{
	Window	*tmp;

	if (dumb)
		return;
	for (tmp = current_screen->window_list; tmp; tmp = tmp->next)
		tmp->update |= REDRAW_STATUS | REDRAW_DISPLAY_FAST;
}

static void
decode_colour(ptr, fg, bg)
	u_char **ptr;
	int *fg;
	int *bg;
{
	char c;
	/* At input, *ptr points to the first char after ^C */
	
	*fg = *bg = 16; /* both unset */
	
	/*
	 * mIRC doesn't accept codes beginning with comma (,),
	 * VIRC accepts. mIRC is out authority in this case.
	 */
	c = **ptr;
	if (c < '0' || c > '9')
	{
		--*ptr;
		return;
	}

	*fg = c - '0';
	
	c = (*ptr)[1];
	if (c >= '0' && c <= '9')
	{
		++*ptr;
		*fg = *fg * 10 + c - '0';
		c = (*ptr)[1];
	}
	if (c == ',')
	{
		/*
		 * mIRC doesn't eat the comma if it is not followed by
		 * a number, VIRC does. mIRC is again the authority.
		 */
		c = (*ptr)[2];
		if (c >= '0' && c <= '9')
		{
			*bg = c - '0';
			*ptr += 2;
			c = (*ptr)[1];
			if (c >= '0' && c <= '9')
			{
				++*ptr;
				*bg = *bg * 10 + c - '0';
			}
		}
	}
	/* At this point, *ptr points to last char WITHIN the colour code */
}

/* strlen() with internal format codes stripped */
/* Return value: number of columns this string takes */
int
my_strlen_i(c)
	u_char *c;
{
	int result = 0;
	
	struct mb_data mbdata;
#ifdef HAVE_ICONV_OPEN
	mbdata_init(&mbdata, CP(irc_encoding));
#else
	mbdata_init(&mbdata, NULL);
#endif /* HAVE_ICONV_OPEN */
	
	while (*c)
	{
		if (*c == REV_TOG || *c == UND_TOG || *c == BOLD_TOG || *c == ALL_OFF)
		{
			/* These don't add visual length */
			++c;
			continue;
		}
		if (*c == COLOUR_TAG)
		{
			c += 3;
			/* Colour code didn't add visual length */
			continue;
		}
		
		/* Anything else is character data. */
		decode_mb(c, NULL, &mbdata);
		result += mbdata.num_columns;
		c      += mbdata.input_bytes;
	}
	
	mbdata_done(&mbdata);
	return result;
}

/* strlen() with colour codes stripped */
/* Return value: Number of columns this string takes */
int
my_strlen_c(c)
	u_char *c;
{
	int result = 0;

	struct mb_data mbdata;
#ifdef HAVE_ICONV_OPEN
	mbdata_init(&mbdata, CP(irc_encoding));
#else
	mbdata_init(&mbdata, NULL);
#endif /* HAVE_ICONV_OPEN */

	while (*c)
	{
		if (*c == REV_TOG || *c == UND_TOG || *c == BOLD_TOG || *c == ALL_OFF)
		{
			/* These don't add visual length */
			++c;
			continue;
		}
		if (*c == COLOUR_TAG)
		{
			int fg, bg;

			++c;
			decode_colour(&c, &fg, &bg);
			++c;
			/* Colour code didn't add visual length */
			continue;
		}

		/* Anything else is character data. */
		decode_mb(c, NULL, &mbdata);
		result += mbdata.num_columns;
		c      += mbdata.input_bytes;
	}
	mbdata_done(&mbdata);
	return result;
}

/* strlen() with colour codes converted to internal codes */
/* Doesn't do actual conversion */
/* Return value: Number of bytes this string takes when converted */
int
my_strlen_ci(c)
	u_char *c;
{
	int result = 0;

	struct mb_data mbdata;
#ifdef HAVE_ICONV_OPEN
	mbdata_init(&mbdata, CP(irc_encoding));
#else
	mbdata_init(&mbdata, NULL);
#endif /* HAVE_ICONV_OPEN */

	while (*c)
	{
		if (*c == REV_TOG || *c == UND_TOG || *c == BOLD_TOG || *c == ALL_OFF)
		{
			/* These are preserved */
			++c;
			++result;
			continue;
		}
		if (*c == COLOUR_TAG)
		{
			int fg, bg;

			++c;
			decode_colour(&c, &fg, &bg);
			++c;
			/* Modifies into three characters */
			result += 3;
			continue;
		}
		
		/* Anything else is character data. */
		decode_mb(c, NULL, &mbdata);
		result += mbdata.output_bytes;
		c      += mbdata.input_bytes;
	}
	mbdata_done(&mbdata);
	return result;
}

/* strcpy() with colour codes converted to internal codes */
/* Converts the codes */
void
my_strcpy_ci(dest, c)
	u_char *dest;
	u_char *c;
{
	struct mb_data mbdata;
#ifdef HAVE_ICONV_OPEN
	mbdata_init(&mbdata, CP(irc_encoding));
#else
	mbdata_init(&mbdata, NULL);
#endif /* HAVE_ICONV_OPEN */

	/* FIXME: No array bound checking - very insecure */
	
	while (*c)
	{
		if (*c == REV_TOG || *c == UND_TOG || *c == BOLD_TOG || *c == ALL_OFF)
		{
			*dest++ = *c++;
			continue;
		}
		if (*c == COLOUR_TAG)
		{
			int fg, bg;

			*dest++ = *c++;
			decode_colour(&c, &fg, &bg);
			*dest++ = fg+1;
			*dest++ = bg+1;
			++c;
			continue;
		}
		
		/* Anything else is character data. */
		decode_mb(c, dest, &mbdata);
		dest   += mbdata.output_bytes;
		c      += mbdata.input_bytes;
	}
	mbdata_done(&mbdata);
	*dest++ = '\0';
}

#define	MAXIMUM_SPLITS	40
static	u_char	**
split_up_line(str)
	u_char	*str;
{
	static	u_char	*output[MAXIMUM_SPLITS] =
	{ 
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL
	};
	u_char	lbuf[BIG_BUFFER_SIZE];
	u_char	*ptr;
	u_char	*cont_ptr,
		*cont = NULL,
		*temp = NULL,
		*mystr = NULL,
		c;
	int	pos = 0,
		col = 0,
		nd_cnt = 0,
		word_break = 0,
		start = 0,
		i,
		indent = 0,
		beep_cnt = 0,
		beep_max,
		tab_cnt = 0,
		tab_max,
		line = 0;
	int	bold_state = 0,
		invert_state = 0,
		underline_state = 0,
		fg_colour_state = 16,
		bg_colour_state = 16;
		
	size_t	len;

	struct mb_data mbdata;
#ifdef HAVE_ICONV_OPEN
	mbdata_init(&mbdata, CP(irc_encoding));
#else
	mbdata_init(&mbdata, NULL);
#endif /* HAVE_ICONV_OPEN */
	
	bzero(lbuf, sizeof(lbuf));
	
	for (i = 0; i < MAXIMUM_SPLITS; i++)
		new_free(&output[i]);
	if (!str || !*str)
	{
		/* special case to make blank lines show up */
		malloc_strcpy(&mystr, UP(" "));
		str = mystr;
	}

	beep_max = get_int_var(BEEP_VAR) ? get_int_var(BEEP_MAX_VAR) : -1;
	tab_max = get_int_var(TAB_VAR) ? get_int_var(TAB_MAX_VAR) : -1;
	for (ptr = (u_char *) str; *ptr && (pos < sizeof(lbuf) - 8); ptr++)
	{
		switch (*ptr)
		{
		case '\007':	/* bell */
			if (beep_max == -1)
			{
				lbuf[pos++] = REV_TOG;
				lbuf[pos++] = (*ptr & 127) | 64;
				lbuf[pos++] = REV_TOG;
				nd_cnt += 2;
				col++;
			}
			else if (!beep_max || (++beep_cnt <= beep_max))
			{
				lbuf[pos++] = *ptr;
				nd_cnt++;
				col++;
			}
			break;
		case '\011':	/* tab */
			if (tab_max && (++tab_cnt > tab_max))
			{
				lbuf[pos++] = REV_TOG;
				lbuf[pos++] = (*ptr & 127) | 64;
				lbuf[pos++] = REV_TOG;
				nd_cnt += 2;
				col++;
			}
			else
			{
				if (indent == 0)
					indent = -1;
				len = 8 - (col % 8);
				word_break = pos;
				for (i = 0; i < len; i++)
					lbuf[pos++] = ' ';
				col += len;
			}
			break;
		case UND_TOG:
		case ALL_OFF:
		case REV_TOG:
		case BOLD_TOG:
			switch(*ptr)
			{
			case UND_TOG:
				underline_state = !underline_state;
				break;
			case REV_TOG:
				invert_state = !invert_state;
				break;
			case BOLD_TOG:
				bold_state = !bold_state;
				break;
			case ALL_OFF:
				underline_state = invert_state = bold_state = 0;
				fg_colour_state = bg_colour_state = 16;
				break;
			}
			lbuf[pos++] = *ptr;
			nd_cnt++;
			break;
		case COLOUR_TAG:
			{
				int fg, bg;

				lbuf[pos++] = *ptr++;
				decode_colour(&ptr, &fg, &bg);

				lbuf[pos++] = (u_char)(fg_colour_state = fg) + 1;
				lbuf[pos++] = (u_char)(bg_colour_state = bg) + 1;
				nd_cnt += 3;
				break;
			}
		case ' ':	/* word break */
			if (indent == 0)
				indent = -1;
			word_break = pos;
			lbuf[pos++] = *ptr;
			col++;
			break;
		default:	/* Anything else, make it displayable */
			{
   				if (indent == -1)
					indent = pos - nd_cnt;
	   		   	
	   		   	/* FIXME: No array bound checking - very insecure */
				decode_mb(ptr, lbuf+pos, &mbdata);
				
				/* If the sequence takes multiple columns,
				 * be wrappable now when we can
				 */
				if (mbdata.num_columns > 1)
					word_break = pos;
				
				pos	   += mbdata.output_bytes;
				nd_cnt += mbdata.output_bytes - mbdata.num_columns;
				ptr	   += mbdata.input_bytes - 1;
				col	   += mbdata.num_columns;
				break;
			}
		}
		if (pos >= sizeof(lbuf)-1)
			*ptr = '\0';
		
		if (col >= current_screen->co)
		{
			char c1;
			int oldpos;

			/* one big long line, no word breaks */
			if (word_break == 0)
				word_break = pos - (col - current_screen->co);
			c = lbuf[word_break];
			c1 = lbuf[word_break+1];
			lbuf[word_break] = FULL_OFF;
			lbuf[word_break+1] = '\0';
			if (cont)
			{
				malloc_strcpy(&temp, cont);
				malloc_strcat(&temp, &(lbuf[start]));
			}
			else
				malloc_strcpy(&temp, &(lbuf[start]));
			malloc_strcpy(&output[line++], temp);
			lbuf[word_break] = c;
			lbuf[word_break+1] = c1;
			start = word_break;
			word_break = 0;
			while (lbuf[start] == ' ')
				start++;
			if (start > pos)
				start = pos;

			if (!(cont_ptr = get_string_var(CONTINUED_LINE_VAR)))
				cont_ptr = empty_string;

			if (get_int_var(INDENT_VAR) && (indent < current_screen->co / 3))
			{
				if (!cont) /* Build it only the first time */
				{
					/* Visual length */
					int vlen = my_strlen_c(cont_ptr);
					/* Converted length */
					int clen = my_strlen_ci(cont_ptr);
					int padlen = indent - vlen;

					if (padlen < 0)
						padlen = 0;
					
					cont = (u_char *) new_malloc(clen + padlen + 1);
					my_strcpy_ci(cont, cont_ptr);
					
					for (; padlen > 0; --padlen)
					{
						cont[clen++] = ' ';
						/* Add space */
					}
					cont[clen] = '\0';
				}
			}
			else
			{
				/* Converted length */
				int clen = my_strlen_ci(cont_ptr);
				
				/* No indent, just prefix only */				
				cont = (u_char *) new_malloc(clen + 1);
				my_strcpy_ci(cont, cont_ptr);
			}
			
			/* cont contains internal codes, so use my_strlen_i */
			col = my_strlen_i(cont) + (pos - start);
			
			/* rebuild previous state */
			oldpos = pos;
			if (underline_state)
			{
				lbuf[pos++] = UND_TOG;
				nd_cnt++;
			}
			if (invert_state)
			{
				lbuf[pos++] = REV_TOG;
				nd_cnt++;
			}
			if (bold_state)
			{
				lbuf[pos++] = BOLD_TOG;
				nd_cnt++;
			}
			if (fg_colour_state != 16 || bg_colour_state != 16)
			{
				lbuf[pos++] = COLOUR_TAG;
				lbuf[pos++] = fg_colour_state + 1;
				lbuf[pos++] = bg_colour_state + 1;
				nd_cnt++;
			}
			if (pos != oldpos)
				while(start < oldpos)
					lbuf[pos++] = lbuf[start++];
		}
	}
	mbdata_done(&mbdata);
	
 	lbuf[pos++] = FULL_OFF;
	lbuf[pos] = '\0';
	if (lbuf[start])
	{
		if (cont)
		{
			malloc_strcpy(&temp, cont);
			malloc_strcat(&temp, &(lbuf[start]));
		}
		else
			malloc_strcpy(&temp, &(lbuf[start]));
		malloc_strcpy(&output[line++], temp);
	}
	if (mystr)
		new_free(&mystr);
	new_free(&cont);
	new_free(&temp);
	return output;
}

/*
 * add_to_window: adds the given string to the display.  No kidding. This
 * routine handles the whole ball of wax.  It keeps track of what's on the
 * screen, does the scrolling, everything... well, not quite everything...
 * The CONTINUED_LINE idea thanks to Jan L. Peterson (jlp@hamblin.byu.edu)  
 *
 * At least it used to. Now most of this is done by split_up_line, and this
 * function just dumps it onto the screen. This is because the scrollback
 * functions need to be able to figure out how to split things up too.
 */
static	void
add_to_window(window, str)
	Window	*window;
	u_char	*str;
{
	int flag;

	flag = do_hook(WINDOW_LIST, "%u %s", window->refnum, str);

	if (flag)
	{
		size_t	len = my_strlen(str);
		u_char	*my_str = new_malloc(len + 2);
		u_char	**lines;
		int	logged;

		add_to_log(window->log_fp, str);
		add_to_lastlog(window, str);
		display_highlight(OFF);
		display_bold(OFF);
		display_colours(get_int_var(FOREGROUND_COLOUR_VAR),
		    get_int_var(BACKGROUND_COLOUR_VAR));
		bcopy(str, my_str, len);
		my_str[len] = ALL_OFF;
		my_str[len + 1] = '\0';
		logged = islogged(window);
		
		/* For each of the lines created by split_up_line(),
		 * display the line.
		 * Rite() will assume each input line fits on 1 line.
		 */
		for (lines = split_up_line(my_str); *lines; lines++)
		{
			rite(window, *lines, 0, 0, 0, logged);
			if (logged == 1)
				logged = 2;
		}
		new_free(&my_str);
		term_flush();
	}
}

/*
 * XXX  this is a temporary internal interface that will go
 * away in the near future.
 */
int in_redirect;

/*
 * add_to_screen: This adds the given null terminated buffer to the screen.
 * That is, it determines which window the information should go to, which
 * lastlog the information should be added to, which log the information
 * should be sent to, etc 
 */
void
add_to_screen(incoming)
	u_char	*incoming;
{
	int	flag;
	Window	*tmp;
	u_char	buffer[BIG_BUFFER_SIZE];

	/* eek! */
	if (!current_screen)
	{
		puts(CP(incoming));
		fflush(stdout);
		return;
	}
	/* Handles output redirection first */
	if (!in_redirect && current_screen->redirect_name &&
	    from_server == current_screen->redirect_server)
	{
		int	i;

		/*
		 * More general stuff by Bisqwit, allow
		 * multiple target redirection (with different targets)
		 */
		in_redirect = 1;
		i = in_on_who;
		in_on_who = 0; /* To allow redirecting /who, /whois and /join */
		redirect_msg(current_screen->redirect_name, incoming);
		in_on_who = i;
		in_redirect = 0;
	}
	if (dumb)
	{
		/* FIXME: Do iconv for "incoming" in dumb mode too */
		add_to_lastlog(curr_scr_win, incoming);
		if (do_hook(WINDOW_LIST, "%u %s", curr_scr_win->refnum, incoming))
			puts(CP(incoming));
		term_flush();
		return;
	}
	if (in_window_command)
		update_all_windows();
	if ((who_level == LOG_CURRENT) && (curr_scr_win->server == from_server))
	{
		add_to_window(curr_scr_win, incoming);
		return;
	}
	if (to_window)
	{
		add_to_window(to_window, incoming);
		return;
	}
	/*
	 * XXX - hack alert
	 * If /echo or /xecho set the message_from, use it here before we
	 * look at who_from.  If not, look at it afterwards.
	 */
	if (who_level && my_echo_set_message_from)
	{
		flag = 1;
		while ((tmp = traverse_all_windows(&flag)) != NULL)
		{
			if (((from_server == tmp->server) || (from_server == -1)) &&
			    (who_level & tmp->window_level))
			{
				add_to_window(tmp, incoming);
				return;
			}
		}
	}
	if (who_from)
	{
		if (is_channel(who_from))
		{
			ChannelList	*chan;

			if ((chan = lookup_channel(who_from, from_server, CHAN_NOUNLINK)))
			{
				add_to_window(chan->window ? chan->window : curr_scr_win, incoming);
				return;
			}
			if (who_level == LOG_DCC)
			{
				my_strcpy(buffer, "=");
				my_strmcat(buffer, who_from, sizeof buffer);
				if ((chan = lookup_channel(buffer, from_server, CHAN_NOUNLINK)))
				{
					add_to_window(chan->window ? chan->window : curr_scr_win, incoming);
					return;
				}
			}
		}
		else
		{
			flag = 1;
			while ((tmp = traverse_all_windows(&flag)) != NULL)
			{
				if (tmp->query_nick &&
					(((who_level == LOG_MSG || who_level == LOG_NOTICE)
					&& !my_stricmp(who_from, tmp->query_nick) &&
					from_server == tmp->server) ||
					(who_level == LOG_DCC &&
					(*tmp->query_nick == '=' || *tmp->query_nick == '@') &&
					my_stricmp(who_from, tmp->query_nick + 1) == 0)))
				{
					add_to_window(tmp, incoming);
					return;
				}
			}
			flag = 1;
			while ((tmp = traverse_all_windows(&flag)) != NULL)
			{
				if (from_server == tmp->server)
				{
					if (find_in_list((List **)(void *)&(tmp->nicks),
					    who_from, !USE_WILDCARDS))
					{
						add_to_window(tmp, incoming);
						return;
					}
				}
			}
		}
	}
	if (who_level && !my_echo_set_message_from)
	{
		flag = 1;
		while ((tmp = traverse_all_windows(&flag)) != NULL)
		{
			if (((from_server == tmp->server) || (from_server == -1)) &&
			    (who_level & tmp->window_level))
			{
				add_to_window(tmp, incoming);
				return;
			}
		}
	}
	if (from_server == curr_scr_win->server)
		tmp = curr_scr_win;
	else
	{
		flag = 1;
		while ((tmp = traverse_all_windows(&flag)) != NULL)
		{
			if (tmp->server == from_server)
				break;
		}
		if (!tmp)
			tmp = curr_scr_win;
	}
	add_to_window(tmp, incoming);
}

/*
 * update_all_windows: This goes through each visible window and draws the
 * necessary portions according the the update field of the window. 
 */
void
update_all_windows()
{
	Window	*tmp;
	int	fast_window,
		full_window,
		r_status,
		u_status;

	for (tmp = current_screen->window_list; tmp; tmp = tmp->next)
	{
		if (tmp->display_size != tmp->old_size)
			resize_display(tmp);
		if (tmp->update)
		{
			fast_window = tmp->update & REDRAW_DISPLAY_FAST;
			full_window = tmp->update & REDRAW_DISPLAY_FULL;
			r_status = tmp->update & REDRAW_STATUS;
			u_status = tmp->update & UPDATE_STATUS;
			if (full_window)
				redraw_window(tmp, 1, 0);
			else if (fast_window)
				redraw_window(tmp, 0, 0);
			if (r_status)
				update_window_status(tmp, 1);
			else if (u_status)
				update_window_status(tmp, 0);
		}
		tmp->update = 0;
	}
	for (tmp = invisible_list; tmp; tmp = tmp->next)
	{
		if (tmp->display_size != tmp->old_size)
			resize_display(tmp);
		tmp->update = 0;
	}
	update_input(UPDATE_JUST_CURSOR);
	term_flush();
}

/*
 * create_refnum: this generates a reference number for a new window that is
 * not currently is use by another window.  A refnum of 0 is reserved (and
 * never returned by this routine).  Using a refnum of 0 in the message_to()
 * routine means no particular window (stuff goes to CRAP) 
 */
static	u_int
create_refnum()
{
	unsigned int	new_refnum = 1;
	Window	*tmp;
	int	done = 0,
		flag;

	while (!done)
	{
		done = 1;
		if (new_refnum == 0)
			new_refnum++;

		flag = 1;
		while ((tmp = traverse_all_windows(&flag)) != NULL)
		{
			if (tmp->refnum == new_refnum)
			{
				done = 0;
				new_refnum++;
				break;
			}
		}
	}
	return (new_refnum);
}

/*
 * new_window: This creates a new window on the screen.  It does so by either
 * splitting the current window, or if it can't do that, it splits the
 * largest window.  The new window is added to the window list and made the
 * current window 
 */
Window	*
new_window()
{
	Window	*new;
	static	int	no_screens = 1;

	if (no_screens)
	{
		set_current_screen(create_new_screen());
		main_screen = current_screen;
		no_screens = 0;
	}
	if (dumb && (current_screen->visible_windows == 1))
		return NULL;
	new = (Window *) new_malloc(sizeof(Window));
	new->refnum = create_refnum();
	if (curr_scr_win)
		new->server = curr_scr_win->server;
	else
		new->server = primary_server;
	new->prev_server = -1;
	new->line_cnt = 0;
	if (current_screen->visible_windows == 0)
		new->window_level = LOG_DEFAULT;
	else
		new->window_level = LOG_NONE;
	new->hold_mode = get_int_var(HOLD_MODE_VAR);
	new->scroll = get_int_var(SCROLL_VAR);
	new->lastlog_head = 0;
	new->lastlog_tail = 0;
	new->nicks = 0;
	new->lastlog_level = real_lastlog_level();
	new->name = 0;
	new->prompt = 0;
	new->lastlog_size = 0;
	new->held = OFF;
	new->last_held = OFF;
	new->current_channel = 0;
	new->bound_channel = 0;
	new->query_nick = 0;
	new->hold_on_next_rite = 0;
	new->status_line[0] = NULL;
	new->status_line[1] = NULL;
	new->double_status = 0;
	new->top_of_display = 0;
	new->display_ip = 0;
	new->display_size = 1;
	new->old_size = 1;
	new->hold_head = 0;
	new->hold_tail = 0;
	new->held_lines = 0;
	new->scrolled_lines = 0;
	new->new_scrolled_lines = 0;
	new->next = 0;
	new->prev = 0;
	new->cursor = 0;
	new->visible = 1;
	new->screen = current_screen;
	new->logfile = 0;
	new->log = 0;
	new->log_fp = 0;
	new->miscflags = 0;
	new->update = 0;
	new->menu.lines = 0;
	new->menu.menu = 0;
	new->notify_level = real_notify_level();
	new->server_group = 0;
	new->sticky = 1;
	resize_display(new);
	if (add_to_window_list(new))
		set_current_window(new);
	term_flush();
	return (new);
}

void
close_all_screen()
{
	Screen *screen;

	for (screen = screen_list; screen && screen != current_screen;
			screen = screen->next)
		if (screen->alive && screen->fdin != 0)
			new_close(screen->fdin);
}

#ifdef WINDOW_CREATE
Window	*
create_additional_screen(wanted_type)
	int	wanted_type;
{
	Window	*win;
	Screen	*oldscreen;
	u_char	*displayvar,
		*termvar;
	int	screen_type = ST_NOTHING;
	struct	sockaddr_un sock, error_sock,
			*sockaddr = &sock,
			*error_sockaddr = &error_sock,
			NewSock;
	socklen_t	NsZ;
	int	s, es;
	fd_set	fd_read;
	struct	timeval	time_out;
	pid_t	child;
	int	old_timeout;
#define IRCXTERM_MAX 10
	u_char	*ircxterm[IRCXTERM_MAX];
	u_char	*ircxterm_env;
	u_char	*xterm = (u_char *) 0;
	u_char	*def_xterm = get_string_var(XTERM_PATH_VAR);
	u_char	*p, *q;
	u_char	buffer[BIG_BUFFER_SIZE];
	pid_t	pid = getpid();
	int	ircxterm_num;
	int	i;
	static int cycle = 0;
	int	mycycle = cycle++;

#ifdef DAEMON_UID
	if (DAEMON_UID == getuid())
	{
		say("you are not permitted to use WINDOW CREATE");
		return (Window *) 0;
	}
#endif /* DAEMON_UID */

	if (!def_xterm)
		def_xterm = UP("xterm");

	ircxterm_num = 0;
	p = ircxterm_env = my_getenv("IRCXTERM");
	if (p)
	{
		q = ircxterm_env + my_strlen(ircxterm_env);
		while (p < q && ircxterm_num < IRCXTERM_MAX)
		{
			while (':' == *p)
				p++;
			if ('\0' == *p)
				break;
			ircxterm[ircxterm_num++] = p;
			while (':' != *p && '\0' != *p)
				p++;
			if (':' == *p)
			{
				*p = '\0';
				p++;
			}
		}
	}
	else
	{
		ircxterm[ircxterm_num] = def_xterm;
		ircxterm_num++;
	}

	/*
	 * Environment variable STY has to be set for screen to work..  so it is
	 * the best way to check screen..  regardless of what TERM is, the 
	 * execpl() for screen won't just open a new window if STY isn't set,
	 * it will open a new screen process, and run the wserv in its first
	 * window, not what we want...  -phone
	 */
	if ((wanted_type == ST_NOTHING || wanted_type == ST_SCREEN) &&
	    0 != my_getenv("STY"))
		screen_type = ST_SCREEN;
	else if ((wanted_type == ST_NOTHING || wanted_type == ST_XTERM) &&
	    (u_char *) 0 != (displayvar = my_getenv("DISPLAY")) &&
	    (u_char *) 0 != (termvar = my_getenv("TERM")))
	{
		screen_type = ST_XTERM;
		if (0 == my_strncmp(termvar, "sun", 3))
		{
			xterm = def_xterm;
		}
		else
		{
			for (; *termvar; termvar++)
			{
				for (i = 0; i < ircxterm_num; i++)
				{
					if (!my_strncmp(termvar, ircxterm[i], my_strlen(ircxterm[i])))
					{
						xterm = ircxterm[i];
						termvar = empty_string;
						break;
					}
				}
			}
		}
		if (!xterm)
			xterm = def_xterm;
	}

	if (screen_type == ST_NOTHING)
	{
		say("I don't know how to create new windows for this terminal");
		return (Window *) 0;
	}
	say("Opening new %s...",
		screen_type == ST_XTERM ?  "window" :
		screen_type == ST_SCREEN ? "screen" :
					   "wound" );
	snprintf(sock.sun_path, sizeof sock.sun_path, "/tmp/irc_%08d_%x", (int) pid, mycycle);
	sock.sun_family = AF_UNIX;
	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
	{
		say("Can't create UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}
	if (bind(s, (struct sockaddr *) &sock, (int)(2 + my_strlen(sock.sun_path))) < 0)
	{
		say("Can't bind UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}
	if (listen(s, 1) < 0)
	{
		say("Can't bind UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}
	snprintf(error_sock.sun_path, sizeof error_sock.sun_path, "/tmp/irc_error_%08d_%x", (int) pid, mycycle);
	error_sock.sun_family = AF_UNIX;
	if ((es = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
	{
		say("Can't create UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}
	if (bind(es, (struct sockaddr *) &error_sock, (int)(2 + my_strlen(error_sock.sun_path))) < 0)
	{
		say("Can't bind UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}
	if (listen(es, 1) < 0)
	{
		say("Can't bind UNIX socket, punting /WINDOW CREATE");
		return (Window *) 0;
	}

	oldscreen = current_screen;
	set_current_screen(create_new_screen());
	if (0 == (child = fork()))
	{
		setuid(getuid());
		setgid(getgid());
	/*
	 * Unlike most other cases, it is important here to close down
	 * *ALL* unneeded file descriptors. Failure to do so can cause
	 * Things like server and DCC connections to fail to close on
	 * request. This isn't a problem with "screen", but is with X.
	 */
		new_close(s);
		new_close(es);
		close_all_screen();
		close_all_dcc();
		close_all_exec();
		close_all_server();
		i = 0;
		if (screen_type == ST_SCREEN)
		{
			char	*args[64];
			u_char	*ss,
				*t,
				*opts = NULL;

			Debug((3, "going to execvp screen wserv..."));
			args[i++] = "screen";
			if ((ss = get_string_var(SCREEN_OPTIONS_VAR)) != NULL)
			{
				malloc_strcpy(&opts, ss);
				while ((t = next_arg(opts, &opts)) != NULL)
				{
					Debug((3, "added option `%s'", t));
					args[i++] = CP(t);
				}
			}
			args[i++] = WSERV_PATH;
			args[i++] = sockaddr->sun_path;
			args[i++] = error_sockaddr->sun_path;
			Debug((3, "added: %s %s %s", args[i-3], args[i-2], args[i-1]));
			args[i++] = NULL;
			execvp("screen", args);
		}
		else if (screen_type == ST_XTERM)
		{
			int	lines,
				columns;
			char	*args[64];
			u_char	geom[20],
				*ss,
				*t,
				*opts = NULL;

			Debug((3, "going to execvp xterm wserv..."));
			copy_window_size(&lines, &columns);
			snprintf(CP(geom), sizeof geom, "%dx%d", columns, lines);
			args[i++] = CP(xterm);
			Debug((3, "xterm name is %s", args[0]));
			if ((ss = get_string_var(XTERM_GEOMOPTSTR_VAR)) != NULL)
				args[i++] = CP(ss);
			else
				args[i++] = "-geom";
			args[i++] = CP(geom);
			Debug((3, "added geom: %s %s", args[i-2], args[i-1]));
			if ((ss = get_string_var(XTERM_OPTIONS_VAR)) != NULL)
			{
				malloc_strcpy(&opts, ss);
				Debug((3, "got xterm options: %s", opts));
				while ((t = next_arg(opts, &opts)) != NULL)
				{
					Debug((3, "added option `%s'", t));
					args[i++] = CP(t);
				}
			}
			args[i++] = "-e";
			args[i++] = WSERV_PATH;
			args[i++] = sockaddr->sun_path;
			args[i++] = error_sockaddr->sun_path;
			Debug((3, "added: %s %s %s %s", args[i-4], args[i-3], args[i-2], args[i-1]));
			args[i] = NULL;
			execvp(CP(xterm), args);
		}
		perror("execve");
		unlink(sockaddr->sun_path);
		unlink(error_sockaddr->sun_path);
		exit(errno ? errno : -1);
	}
	NsZ = sizeof(NewSock);
	FD_ZERO(&fd_read);
	FD_SET(s, &fd_read);
	FD_SET(es, &fd_read);
	time_out.tv_sec = (time_t) 5;
	time_out.tv_usec = 0;
	sleep(1);

	/* using say(), yell() can be bad in this next section of code. */

	switch (select(NFDBITS , &fd_read, NULL, NULL, &time_out))
	{
	case -1:
	case 0:
		errno = get_child_exit(child);
		new_close(s);
		new_close(es);
		kill_screen(current_screen);
		kill(child, SIGKILL);
		last_input_screen = oldscreen;
		set_current_screen(oldscreen);
		yell("child %s with %d", (errno < 1) ? "signaled" : "exited",
					 (errno < 1) ? -errno : errno);
		win = (Window *) 0;
		break;
	default:
		current_screen->fdin = current_screen->fdout =
			accept(s, (struct sockaddr *) &NewSock, &NsZ);
		if (current_screen->fdin < 0)
		{
			win = (Window *) 0;
			break;
		}
		current_screen->wservin = accept(es, (struct sockaddr *) &NewSock,
					       &NsZ);
		if (current_screen->wservin < 0)
		{
			win = (Window *) 0;
			break;
		}
		current_screen->fpin = current_screen->fpout =
			fdopen(current_screen->fdin, "r+");
		term_set_fp(current_screen->fpout);
		new_close(s);
		new_close(es);
		old_timeout = dgets_timeout(5);
		/*
		 * dgets returns 0 on EOF and -1 on timeout.  both of these are
		 * error conditions in this case, so we bail out here.
		 */
		if (dgets(buffer, sizeof buffer, current_screen->fdin, (u_char *) 0) < 1)
		{
			new_close(current_screen->fdin);
			kill_screen(current_screen);
			kill(child, SIGKILL);
			last_input_screen = oldscreen;
			set_current_screen(oldscreen);
			(void) dgets_timeout(old_timeout);
			win = (Window *) 0;
			break;
		}
		else
			malloc_strcpy(&current_screen->tty_name, buffer);
		win = new_window();
		(void) refresh_screen(0, NULL);
		set_current_screen(oldscreen);
		(void) dgets_timeout(old_timeout);
	}
	unlink(sockaddr->sun_path);
	unlink(error_sockaddr->sun_path);
	return win;
}
#endif /* WINDOW_CREATE */

/*
 * below here is magic.  this is where the real work of scrolling
 * backwards and forwards through the history is done.  basically,
 * what happens is some functions are called from other code to
 * make scroll requests:  forward, back, start, end.  these functions
 * do some minor house keeping, and then call the function
 * display_lastlog_lines() to do the real work.  note that these
 * functions call display_lastlog_lines() with lastlog line numbers,
 * which is possibly (probably) not the same as the number of lines
 * "Lastlog" lines in the window's history.  display_lastlog_lines()
 * uses repeated calls to the function next_line_back() which does
 * the work for splitting each "Lastlog" entry up into lines to be
 * displayed on the screen.  when the desired position is found,
 * these lines are written to the screen.
 */

/*
 * call initially with a non-NULL "window" argument to initialise
 * the count.  then, each further call to next_line_back() will
 * return the next line of the screen output, going backwards
 * through the lastlog history.  use use split_up_line() from earlier
 * in this file to do the actual splitting of each individiual line.
 */
static	u_char	*
next_line_back(window)
	Window	*window;
{
	static	int	row;
	static	Lastlog	*LogLine;
	u_char	**TheirLines;
	static	u_char	*ScreenLines[MAXIMUM_SPLITS] =
	{ 
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL
	};

	if (window)
	{
		LogLine = window->lastlog_head;
		row = -1;
	}
	if (row <= 0)
	{
		for (row = 0; ScreenLines[row]; row++)
			new_free(&ScreenLines[row]);
		if (!window && LogLine)
			LogLine = LogLine->next;
		if (!LogLine)
			return NULL;
		TheirLines = split_up_line(LogLine->msg);
		for (row = 0; TheirLines[row]; row++)
		{
			ScreenLines[row] = TheirLines[row];
			TheirLines[row] = NULL;
		}
		if (window)
			return NULL;
	}
	return ScreenLines[--row];
}

/*
 * how many lastlog lines in this window?
 */
static int
lastlog_lines(window)
	Window *window;
{
	Display	*Disp;
	int num_lines = 0, i;

	(void)next_line_back(window);

	for (i = window->new_scrolled_lines; i--; num_lines++)
		(void)next_line_back(NULL);

	for (i = 0, Disp = window->top_of_display; i < window->display_size;
			Disp = Disp->next, i++)
		if (Disp->linetype)
			(void)next_line_back(NULL);

	while (next_line_back(NULL))
		num_lines++;

	Debug((3, "  num_lines ends up as %d", num_lines));
	return (num_lines);
}

/*
 * in window "window", display the lastlog lines from "start" to "end".
 */
static	void
display_lastlog_lines(start, end, window)
	int	start,
		end;
	Window	*window;
{
	Display	*Disp;
	u_char	*Line;
	int	i;

	(void)next_line_back(window);

	for (i = window->new_scrolled_lines; i--;)
		(void)next_line_back(NULL);

	for (i = 0, Disp = window->top_of_display; i < window->display_size;
			Disp = Disp->next, i++)
		if (Disp->linetype)
			(void)next_line_back(NULL);

	for (i = 0; i < start; i++)
		(void)next_line_back(NULL);

	for (; i < end; i++)
	{
		if (!(Line = next_line_back(NULL)))
			break;
		term_move_cursor(0, window->top + window->menu.lines +
			window->scrolled_lines - i - 1);
		rite(window, Line, 0, 0, 1, 0);
	}
}

/*
 * scrollback_{back,forw}wards_lines: scroll the named distance, called by
 * internal functions here.
 */
static	void
scrollback_backwards_lines(ScrollDist)
	int	ScrollDist;
{
	Window	*window;
	int totallines;
	
	Debug((3, "scrollback_backwards_lines(%d)", ScrollDist));
	window = curr_scr_win;
	if (!window->scrolled_lines && !window->scroll)
	{
		term_beep();
		return;
	}
	totallines = lastlog_lines(window);
	Debug((3, "totallines = %d, scrolled_lines = %d", totallines, window->scrolled_lines));
	if (ScrollDist + window->scrolled_lines > totallines)
	{
		ScrollDist = totallines - window->scrolled_lines;
		Debug((3, "  adjusting ScrollDist to %d", ScrollDist));
	}
	if (ScrollDist == 0)
	{
		term_beep();
		return;
	}
	window->scrolled_lines += ScrollDist;

	Debug((3, "going to term_scroll(%d, %d, %d)",
	    window->top + window->menu.lines,
	    window->top + window->menu.lines + window->display_size - 1,
	    -ScrollDist));
	term_scroll(window->top + window->menu.lines,
	    window->top + window->menu.lines + window->display_size - 1,
	    -ScrollDist);

	Debug((3, "scrolled_lines=%d, new_scrolled_lines=%d, display_size=%d",
	    window->scrolled_lines,
	    window->new_scrolled_lines,
	    window->display_size));

	Debug((3, "going to display_lastlog_lines(%d, %d, %s)",
	    window->scrolled_lines - ScrollDist,
	    window->scrolled_lines,
	    window->name));
	display_lastlog_lines(window->scrolled_lines - ScrollDist,
	    window->scrolled_lines,
	    window);
	cursor_not_in_display();
	update_input(UPDATE_JUST_CURSOR);
	window->update |= UPDATE_STATUS;
	update_window_status(window, 0);
}

static	void
scrollback_forwards_lines(ScrollDist)
	int	ScrollDist;
{
	Window	*window;

	Debug((3, "scrollback_forward_lines(%d)", ScrollDist));
	window = curr_scr_win;
	if (!window->scrolled_lines)
	{
		term_beep();
		return;
	}
	if (ScrollDist > window->scrolled_lines)
		ScrollDist = window->scrolled_lines;

	Debug((3, "scrolled_lines = %d", window->scrolled_lines));
	window->scrolled_lines -= ScrollDist;
	Debug((3, "going to term_scroll(%d, %d, %d)",
	    window->top + window->menu.lines,
	    window->top + window->menu.lines + window->display_size - 1,
	    ScrollDist));
	term_scroll(window->top + window->menu.lines,
	    window->top + window->menu.lines + window->display_size - 1,
	    ScrollDist);

	Debug((3, "scrolled_lines=%d, new_scrolled_lines=%d, display_size=%d",
	    window->scrolled_lines,
	    window->new_scrolled_lines,
	    window->display_size));

	if (window->scrolled_lines < window->display_size)
		redraw_window(window,
		    ScrollDist + window->scrolled_lines - window->display_size, 1);

	Debug((3, "going to display_lastlog_lines(%d, %d, %s)",
	    window->scrolled_lines - window->display_size,
	    window->scrolled_lines - window->display_size + ScrollDist,
	    window->name));
	display_lastlog_lines(window->scrolled_lines - window->display_size,
	    window->scrolled_lines - window->display_size + ScrollDist,
	    window);
	cursor_not_in_display();
	update_input(UPDATE_JUST_CURSOR);

	if (!window->scrolled_lines)
	{
		window->new_scrolled_lines = 0;
		if (window->hold_mode)
			hold_mode(window, ON, 1);
		else
			hold_mode(window, OFF, 0);
	}
	window->update |= UPDATE_STATUS;
	update_window_status(window, 0);
}

/*
 * scrollback_{forw,back}wards: scrolls the window up or down half the screen.
 * these are called when the respective key are pressed.
 */
void
scrollback_forwards(key, ptr)
	u_int	key;
	u_char *	ptr;
{
	scrollback_forwards_lines(curr_scr_win->display_size/2);
}

void
scrollback_backwards(key, ptr)
	u_int	key;
	u_char	*ptr;
{
	scrollback_backwards_lines(curr_scr_win->display_size/2);
}

/*
 * scrollback_end: exits scrollback mode and moves the display back
 * to the end of the scrollback buffer.
 */
void
scrollback_end(key, ptr)
	u_int	key;
	u_char	*ptr;
{
	Window	*window;

	window = curr_scr_win;
	window->new_scrolled_lines = 0;

	if (!window->scrolled_lines)
	{
		term_beep();
		return;
	}
	if (window->scrolled_lines < window->display_size)
		scrollback_forwards_lines(window->scrolled_lines);
	else
	{
		window->scrolled_lines = window->new_scrolled_lines = 0;
		redraw_window(window, 1, 1);
		cursor_not_in_display();
		update_input(UPDATE_JUST_CURSOR);
		if (window->hold_mode)
			hold_mode(window, ON, 1);
		else
			hold_mode(window, OFF, 0);
		window->update |= UPDATE_STATUS;
		update_window_status(window, 0);
	}
}

/*
 * scrollback_start: moves the current screen back to the start of
 * the scrollback buffer.
 */
void
scrollback_start(key, ptr)
	u_int	key;
	u_char	*ptr;
{
	Window	*window;
	int	num_lines;

	window = curr_scr_win;
	if (!window->lastlog_size)
	{
		term_beep();
		return;
	}

	Debug((3, "scrollback_start: new_scrolled_lines=%d display_size=%d", window->new_scrolled_lines, window->display_size));
	num_lines = lastlog_lines(window);

	if (num_lines < window->display_size)
		scrollback_backwards_lines(num_lines);
	else
	{
		window->scrolled_lines = num_lines;
		Debug((3, "going to display_lastlog_lines(%d, %d, %s)", num_lines - window->display_size, window->scrolled_lines, window->name));
		display_lastlog_lines(num_lines - window->display_size,
		    window->scrolled_lines, window);
		cursor_not_in_display();
		update_input(UPDATE_JUST_CURSOR);
		window->new_scrolled_lines = 0;
		if (window->hold_mode)
			hold_mode(window, ON, 1);
		else
			hold_mode(window, OFF, 0);
		window->update |= UPDATE_STATUS;
		update_window_status(window, 0);
	}
}

void
screen_wserv_message(screen)
	Screen *screen;
{
	u_char buf[128], *rs, *cs, *comma;
	int old_timeout, li, co;

	old_timeout = dgets_timeout(0);
	if (dgets(buf, 128, current_screen->wservin, (u_char *) 0) < 1)
	{
		/* this should be impossible. */
		if (!is_main_screen(screen))
			kill_screen(screen);
	}
	(void) dgets_timeout(old_timeout);
	Debug((3, "screen_wserv_message: %s", buf));
	/* should have "rows,cols\n" */
	rs = buf;
	comma = my_index(buf, ',');
	if (comma == NULL)
	{
		yell("--- got wserv message: %s, no comma?", buf);
		return;
	}
	*comma = 0;
	cs = comma+1;
	comma = my_index(buf, '\n');
	if (comma != NULL)
		*comma = 0;

	li = atoi(CP(rs)); 
	co = atoi(CP(cs)); 

	Debug((3, "screen_wserv_message: li %d[%d] co %d[%d]", li, screen->li, co, screen->co));

	if (screen->li != li || screen->co != co)
	{
		Screen *old_screen;

		screen->li = li;
		screen->co = co;
		old_screen = current_screen;
		current_screen = screen;
		refresh_screen(0, 0);
		current_screen = old_screen;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1