/*
 * screen.c
 *
 * Copyright (c) 1993-1996 Matthew Green.
 * Copyright 1998 J. Kean Johnston, used with permission
 * Copyright 1997, 2015 EPIC Software Labs.
 * 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
 *    notices, the above paragraph (the one permitting redistribution),
 *    this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The names of the author(s) 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.
 */
/*
 * This file includes major work contributed by FireClown (J. Kean Johnston), 
 * and I am indebted to him for the work he has graciously donated to the 
 * project.  Without his contributions, EPIC's robust handling of colors and 
 * escape sequences would never have been possible.
 */

#define __need_term_h__
#define __need_putchar_x__
#define __need_ScreenStru__

#include "irc.h"
#include "alias.h"
#include "clock.h"
#include "exec.h"
#include "screen.h"
#include "window.h"
#include "output.h"
#include "vars.h"
#include "server.h"
#include "lastlog.h"
#include "list.h"
#include "termx.h"
#include "names.h"
#include "ircaux.h"
#include "input.h"
#include "log.h"
#include "hook.h"
#include "dcc.h"
#include "status.h"
#include "commands.h"
#include "parse.h"
#include "newio.h"
#include <sys/ioctl.h>

#define CURRENT_WSERV_VERSION	4

/*
 * When all else fails, this is the screen that is attached to the controlling
 * terminal, and we know *that* screen will always be there.
 */
	int	main_screen = -1;

/*
 * This is the screen in which we last handled an input event.  This takes
 * care of the input duties that "current_screen" used to handle.
 */
	int	last_input_screen = -1;

/*
 * This is used to set the default output device for tputs_x().  This takes
 * care of the output duties that "current_screen" used to handle.  Since
 * the input screen and output screen are independant, it isnt impossible 
 * for a command in one screen to cause output in another.
 */
	int	output_screen = -1;

/*
 * How things output to the display get mangled (set via /set mangle_display)
 */
	int	display_line_mangler = 0;

/*******************/
typedef struct PromptStru
{
struct  PromptStru *    next;

        char *          data;
        int             type;
        void            (*func) (char *, const char *);

        void *          my_input_line;
        void *          saved_input_line;
}       WaitPrompt;

typedef struct _window_attachment
{
        int             window;
} WindowAttachment;

typedef	struct	ScreenStru
{
struct	ScreenStru *	prev;			/* Next screen in list */
struct	ScreenStru *	next;			/* Previous screen in list */
	int		alive;

	/* List stuff and overhead */
	int		screennum;		/* Refnum for this screen */
	int		input_window;		/* Window that has the input focus */
	int	 	last_window_refnum;	/* The previous input window (for /window back) */
	int		_window_list;		/* The top window on me */
	int		visible_windows;	/* Number of windows on me */
	WindowStack *	window_stack;		/* Number of windows on my stack */

	/* Output stuff */
	FILE *		fpin;			/* The input FILE (eg, stdin) */
	int		fdin;			/* The input FD (eg, 0) */
	FILE *		fpout;			/* The output FILE (eg, stdout) */
	int		fdout;			/* The output FD (eg, 1) */
	int		control;		/* The control FD (to wserv) */
	int		wserv_version;		/* The version of wserv talking to */

	void *		il;			/* InputLine (opaque - see input.c) */
	WaitPrompt *	promptlist;

	/* Key qualifier stuff */
	int		quote_hit;		/* True after QUOTE_CHARACTER hit */
	Timeval 	last_press;		/* The last time a key was pressed. */
	void *		last_key;		/* The last Key pressed. */

	int		co;
	int		li;
	int		old_co;
	int		old_li;

#define MAX_WINDOWS_ON_SCREEN	1000
	WindowAttachment	_windows[MAX_WINDOWS_ON_SCREEN + 1];	/* This is experimental, for now */
}	Screen;



/* * * * * * * * * * * * * OUTPUT CHAIN * * * * * * * * * * * * * * * * * * *
 * The front-end api to output stuff to windows is:
 *
 * 1) Set the window, either directly or indirectly:
 *     a) Directly with		l = message_setall(window, target, level);
 *     b) Indirectly with	l = message_from(target, level);
 * 2) Call an output routine:
 *	say(), output(), yell(), put_it(), put_echo(), etc.
 * 3) Reset the window:
 *     b) Indirectly with	pop_message_from(l);
 *
 * This file implements the middle part of the "ircII window", everything
 * that sits behind the say/output/yell/put_it/put_echo functions, and in
 * front of the low-level terminal stuff.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static	void		do_screens		(int);
static	int		rite			(int, const char *);
static	void		scroll_window		(int);
static	void		add_to_window		(int, const char *);
static	int		ok_to_output		(int);
static	void		edit_codepoint		(uint32_t);
static	ssize_t		read_esc_seq		(const char *, void *, int *);
static	ssize_t		read_color_seq_new	(const char *, void *);
static	ssize_t		read_color256_seq	(const char *, void *);
static	ssize_t		read_rgb_seq		(const char *, void *);

static 	WaitPrompt *	get_screen_prompt_list	(int);
static  void		set_screen_prompt_list	(int, WaitPrompt *);
static	void		destroy_prompt		(int, WaitPrompt **);

/*
 * The list of all the screens we're handling.  Under most cases, there's 
 * only one screen on the list, "main_screen".
 * XXX This should become an array at some point.
 */
static	Screen *	screen_list = NULL;
static	Screen *	get_screen_by_refnum	(int);


/********************/
/* A list of the supported color spaces */
#define COLORSPACE_NONE	0x00U
#define COLORSPACE_ANSI	0x01U
#define COLORSPACE_C	0x02U
#define COLORSPACE_X	0x03U
#define COLORSPACE_RGB	0x04U

/* Extract the color space from a unified color value */
#define GET_COLORSPACE(x)	(((uint32_t)( x ) & 0xFF000000U) >> 24)
#define GET_VALUE(x)		((uint32_t)( x ) & 0x00FFFFFF)

/* Convert a color space into a unified color value (just add value!) */
#define SET_COLORSPACE(x)	((( x ) & 0xFFU) << 24)

/* Convert a color number to a unified color value */
#define COLOR_NONE		(SET_COLORSPACE(COLORSPACE_NONE) | 0x000000)
#define COLOR_ANSI(x)		(SET_COLORSPACE(COLORSPACE_ANSI) | (( x ) & 0xFF))
#define COLOR_C(x)		(SET_COLORSPACE(COLORSPACE_C) | (( x ) & 0x7F))
#define COLOR_X(x)		(SET_COLORSPACE(COLORSPACE_X) | (( x ) & 0xFF))
#define COLOR_RGB(r,g,b)	(SET_COLORSPACE(COLORSPACE_RGB) | ((( r ) & 0xFF) << 16)  	\
								| ((( g ) & 0xFF) << 8) 	\
							        | ((( b ) & 0xFF)) )

/* Extract the color number from a unified color value */
#define GET_NONE_COLOR			0x00U
#define GET_ANSI_COLOR(x)		(( x ) & 0x0000000FU)
#define GET_C_COLOR(x)			(( x ) & 0x0000007FU)
#define GET_X_COLOR(x)			(( x ) & 0x000000FFU)
#define GET_RGB_COLOR(x, r, g, b)	( (r = ((( x ) & 0x00FF0000U) >> 16)),	\
					  (g = ((( x ) & 0x0000FF00U) >> 8)),	\
					  (b = ((( x ) & 0x000000FFU) )) )

/* Determine if a unified color value is of a certain type or another */
#define IS_COLOR_NONE(x)	(GET_COLORSPACE(x) == COLORSPACE_NONE)
#define IS_COLOR_ANSI(x)	(GET_COLORSPACE(x) == COLORSPACE_ANSI)
#define IS_COLOR_C(x)		(GET_COLORSPACE(x) == COLORSPACE_C)
#define IS_COLOR_X(x)		(GET_COLORSPACE(x) == COLORSPACE_X)
#define IS_COLOR_RGB(x)		(GET_COLORSPACE(x) == COLORSPACE_RGB)

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

/*
 * "Attributes" were an invention for epic5, and the general idea was
 * to handle all character markups (bold/color/reverse/etc) not as toggle
 * switches, but as absolute settings, handled inline.
 *
 * The function normalize_string() converts all of the character markup
 * toggle characters (^B, ^C, ^V, etc) into Attributes.  Nothing further
 * in the output chain needs to know about highlight toggle chars.
 *
 * Attributes are expressed in two formats, an umarshalled form and a 
 * marshalled form.  The unmarshalled form is (struct attributes) and is
 * a struct of 9 eight-bit ints which hold toggle switches for which 
 * attributes are currently active.  The marshalled form is 5 bytes which
 * can be stored in a C string.
 *
 * An unmarshalled attribute can be injected into a C string using the 
 * write_internal_attribute() function.  A marshalled attribute can be extracted
 * from a C string using read_internal_attribute().
 *
 * The logical_attributes() function will marshall an attribute into the
 * logical (un-normalized) equivalents.   The ignore_attributes() function
 * is a function that essentially ignores/strips attribute changes.
 */
struct 	attributes {
	unsigned char	reverse;
	unsigned char	bold;
	unsigned char	blink;
	unsigned char	underline;
	unsigned char	altchar;
	unsigned char	italic;
	uint32_t	fg;	/* Unified color value */
	uint32_t	bg;	/* Unified color value */
};
typedef struct attributes Attribute;

static size_t	write_internal_attribute (char *output_, size_t, Attribute *old_a, Attribute *a);
static int	read_internal_attribute (const char *input_, Attribute *a, size_t *numbytes);
static size_t	write_ircii_attributes (char *output, size_t output_size, Attribute *old_a, Attribute *a);


void	zero_attribute (Attribute *a)
{
        /* Reset all attributes to zero */
	a->reverse = a->bold = a->blink = a->underline = a->altchar = a->italic = 0;
	a->fg = a->bg = COLOR_NONE;
}

const char *all_off (void)
{
	Attribute 	old_a, a;
static	unsigned char	retval[16];

	zero_attribute(&a);
	zero_attribute(&old_a);
	write_internal_attribute((char *)retval, sizeof(retval), &old_a, &a);
	return (char *)retval;
}

/* * * * * * * * * * * */
/*
 * These are "attribute changer" functions.  They work like this:
 *	output		An output (string) buffer to write the change to
 *	old_a		The previous (Attribute *)
 *	new_a		The new (Attribute *)
 * Each function should write any changes between "old_a" and "new_a" to 
 * output in whatever way is appropriate, and should copy new_a to old_a 
 * before returning.  The special case is when "old_a" is NULL, which 
 * should be treated as an explicit "all off" before handling "a".
 */


/*
 * write_internal_attribute - serialize an attribute to the internal format (that read_internal_attribute() wants)
 *
 * Arguments: 
 *	output_		- OUTPUT - Where to write the attribute to 
 *			  - This is expected to be a casted (unsigned char *).
 *	output_size	- INPUT - How many bytes i can write to output_
 *			  - This had better be at least 13.
 *	old_a		- INPUT/OUTPUT - The state of the Attribute most recently written to output_
 *			  - The value of 'a' is written to 'old_a"
 *	a		- INPUT - The state of the Attribute you want written to output_
 *
 * Return value:
 *	The number of bytes that was written to output_. 
 *	This function always returns 13.
 *
 * Side effects:
 *	The attribute 'a' is written to output_ as a C-string friendly binary blob.
 *	The value 'a' is written to 'old_a'
 *
 * Errors:
 *	This function always succeeds
 *
 * Bugs:
 *	It should check 'output_', 'old_a', and 'a' for NULL-ness before dereffing them
 *	It should check not to overrun (output_ + output_size).
 */
static size_t	write_internal_attribute (char *output_, size_t output_size, Attribute *old_a, Attribute *a)
{
	unsigned char *output = (unsigned char *)output_;

	/* XXX Should do something more meaningful here */
	if (output_size < 13)
		return 0;

	/*
	 * A "Display attribute" is a \006 followed by 11 bytes (plus a nul).
	 * Each of the 11 bytes have the high bit set (to avoid nuls)
	 * Each of the 11 bytes thus contain 7 bits of information
	 */
        *output++ = '\006';

	/* 
	 * Byte 0 uses its 7 bits for 7 attribute toggles
	 *	1	Reverse
	 *	2	Bold
	 *	3	Blink
	 *	4	Underline
	 *	5	AltCharSet 
	 *	6	Italics
	 *	7 	[Reserved for future expansion]
	 */
	output[0] = 0x80U;
	if (a->reverse)		output[0] |= 0x01U;
	if (a->bold)		output[0] |= 0x02U;
	if (a->blink)		output[0] |= 0x04U;
	if (a->underline)	output[0] |= 0x08U;
	if (a->altchar)		output[0] |= 0x10U;
	if (a->italic)		output[0] |= 0x20U;

	/*
	 * Byte 1-5 holds fg color information
	 *	We pack a 32 bit int to 5 7-bit ints
	 *
	 * Byte 6-10 holds bg color information
	 *	We pack a 32 bit int to 5 7-bit ints
	 *
	 * Byte 11 is a nul.
	 *
	 * (I do confess I asked Bard for hints here.)
	 */
	output[1] = 0x80 | ((a->fg >> 28) & 0x7f);
	output[2] = 0x80 | ((a->fg >> 21) & 0x7f);
	output[3] = 0x80 | ((a->fg >> 14) & 0x7f);
	output[4] = 0x80 | ((a->fg >>  7) & 0x7f);
	output[5] = 0x80 | ((a->fg)       & 0x7f);

	output[6] = 0x80 | ((a->bg >> 28) & 0x7f);
	output[7] = 0x80 | ((a->bg >> 21) & 0x7f);
	output[8] = 0x80 | ((a->bg >> 14) & 0x7f);
	output[9] = 0x80 | ((a->bg >>  7) & 0x7f);
	output[10] = 0x80 | ((a->bg)       & 0x7f);

	output[11] = 0;

	*old_a = *a;
	return 12;
}

/*
 * read_internal_attribute - Recover an attribute written by write_internal_attribute() into a C string
 *
 * Arguments: 
 *	input_		- INPUT - A string containing an attribute written by write_internal_attribute()
 *				- input_ may point at the leading \006 or the byte after the \006.
 *	a		- OUTPUT - The recovered attribute stored at 'input_'
 *	numbytes	- OUTPUT - The number of bytes consumed from 'input_'
 *			   - The number of bytes consumed may be > 0 even in case of error!
 *
 * Return value:
 *	 0	- The attribute was successfully recovered
 *		  - The number of bytes consumed is stored in '*numbytes'
 *	-1	- Something went wrong
 *		    - 'input_' was NULL
 *		    - 'input_' does not point at something written by write_internal_attribute()
 *		  - '*numbytes' is always written to
 *
 * Side effects:
 *	'*numbytes' is always written to,  It will be >= 0, even in case of error!
 *
 * Errors:
 *	If 'input_' is NULL, return -1
 *	If 'input_' does ont point at something written by write_internal_attributes, return -1.
 *	This function always succeeds
 *
 * Bugs:
 *	It should check 'a' and 'numbytes' for NULL-ness before derefing them
 *	The number of bytes should be the return value, and an error should be
 *		an output parameter
 */
static int	read_internal_attribute (const char *input_, Attribute *a, size_t *numbytes)
{
	const unsigned char *input = (const unsigned char *)input_;
	int	i;

	*numbytes = 0;

	if (!input)
		return -1;

	/* 
	 * This used to check for *input != '\006' but it was inconvenient
	 * to enforce that, so now it's just an optional check.
	 */
	if (*input == '\006')
		(*numbytes)++, input++;

	/* Bytes 0 through 10 after the indicator contain the data */
	for (i = 0; i <= 10; i++)
	{
		if (!input[i] || ((input[i] & 0x80U) != 0x80U))
			return -1;
	}
	(*numbytes) += 11;

	zero_attribute(a);

	if ((unsigned char)input[0] & 0x01U)	a->reverse = 1;
	if ((unsigned char)input[0] & 0x02U)	a->bold = 1;
	if ((unsigned char)input[0] & 0x04U)	a->blink = 1;
	if ((unsigned char)input[0] & 0x08U)	a->underline = 1;
	if ((unsigned char)input[0] & 0x10U)	a->altchar = 1;
	if ((unsigned char)input[0] & 0x20U)	a->italic = 1;

	/* I confess bard gave me suggestions here */
	a->fg = ((input[1] & 0x7F) << 28) |
		((input[2] & 0x7F) << 21) |
		((input[3] & 0x7F) << 14) |
		((input[4] & 0x7F) << 7)  |
		((input[5] & 0x7F));
	a->bg = ((input[6] & 0x7F) << 28) |
		((input[7] & 0x7F) << 21) |
		((input[8] & 0x7F) << 14) |
		((input[9] & 0x7F) << 7)  |
		((input[10] & 0x7F));

	return 0;
}

int	copy_internal_attribute (const char *input_, char *output_, size_t output_size, size_t *numbytes)
{
	Attribute 	a;
	size_t		i;

	/* First, read the attribute to get the number of bytes */
	*numbytes = 0;
	if (read_internal_attribute(input_, &a, numbytes) < 0)
		return -1;

	/* Then, copy that number of bytes */
	/* XXX Do i care if it needs a nul at the end? */
	for (i = 0; (i < *numbytes && i < output_size); i++)
		output_[i] = input_[i];
	return 0;
}




 
/* * * * * * * * * */
/*
 * write_ircii_attributes - serialize an attribute to the ircii format (that new_normalize_string() wants)
 *
 * Arguments: 
 *	output_		- OUTPUT - Where to write the attribute to 
 *	output_size	- INPUT - How many bytes i can write to output_
 *	old_a		- INPUT/OUTPUT - The state of the Attribute most recently written to output_
 *			  - The value of 'a' is written to 'old_a"
 *	a		- INPUT - The state of the Attribute you want written to output_
 *
 * Return value:
 *	The number of bytes that was written to output_, which will be >= 0
 *
 * Side effects:
 *	The differences between 'old_a' and 'a' are written to 'output_' as a valid C string.
 *	If 'old_a' and 'a' are identical, nothing will be written.
 *	The differences are rendered as ircII highlights (^V, ^B, ^C, ^X, etc)
 *	The value 'a' is written to 'old_a'
 *
 * Errors:
 *	This function always succeeds
 */
static size_t	write_ircii_attributes (char *output, size_t output_size, Attribute *old_a, Attribute *a)
{
	char	*str = output;
	size_t	count = 0;
	Attribute dummy;

	if (old_a == NULL)
	{
		zero_attribute(&dummy);
		old_a = &dummy;
		*str++ = ALL_OFF, count++;
	}

	/* If the new attribute is ALL_OFF... */
	if (a->reverse == 0 && a->bold == 0 && a->blink == 0 
	    && a->underline == 0 && a->altchar == 0 && a->italic == 0 
	    && a->fg == COLOR_NONE && a->bg == COLOR_NONE
		)
	{
	    /* ... and the old attribute was not ... */
	    if (old_a->reverse != 0 || old_a->bold != 0 || old_a->blink != 0 
		|| old_a->underline != 0 || old_a->altchar != 0 || old_a->italic 
		|| old_a->fg != COLOR_NONE || old_a->bg != COLOR_NONE
		)
	    {
		/* ... Collapse it to an ALL_OFF, and we're done here */
		*str++ = ALL_OFF;
		*old_a = *a;
		return 1;
	    }
	}

	if (a->fg != old_a->fg || a->bg != old_a->bg)
	{
		if (IS_COLOR_NONE(a->fg)) {
			*str++ = '\030', count++;
			*str++ = '-', count++;
			*str++ = '1', count++;
		} else if (IS_COLOR_ANSI(a->fg)) {
			*str++ = '\030', count++;
			count += hex256(GET_ANSI_COLOR(a->fg), &str);
		} else if (IS_COLOR_C(a->fg)) {
			*str++ = '\003', count++;
			*str++ = GET_C_COLOR(a->fg) / 10 + '0', count++;
			*str++ = GET_C_COLOR(a->fg) % 10 + '0', count++;
		} else if (IS_COLOR_X(a->fg)) {
			*str++ = '\030', count++;
			count += hex256(GET_X_COLOR(a->fg), &str);
		} else if (IS_COLOR_RGB(a->fg)) {
			int	r, g, b;

			*str++ = '\030', count++;
			*str++ = '{', count++;
			*str++ = '#', count++;
			GET_RGB_COLOR(a->fg, r, g, b);
			count += hex256(r & 0xFFU, &str);
			count += hex256(g & 0xFFU, &str);
			count += hex256(b & 0xFFU, &str);
			*str++ = '}', count++;
		} 

		if (IS_COLOR_NONE(a->bg)) {
			/* */ (void) 0;
		} else if (IS_COLOR_ANSI(a->bg)) {
			*str++ = '\030', count++;
			*str++ = ',', count++;
			count += hex256(GET_ANSI_COLOR(a->bg), &str);
		} else if (IS_COLOR_C(a->bg)) {
			*str++ = '\003', count++;
			*str++ = ',', count++;
			*str++ = GET_C_COLOR(a->bg) / 10 + '0', count++;
			*str++ = GET_C_COLOR(a->bg) % 10 + '0', count++;
		} else if (IS_COLOR_X(a->bg)) {
			*str++ = '\030', count++;
			*str++ = ',', count++;
			count += hex256(GET_X_COLOR(a->bg), &str);
		} else if (IS_COLOR_RGB(a->bg)) {
			int	r, g, b;

			*str++ = '\030', count++;
			*str++ = '{', count++;
			*str++ = '#', count++;
			*str++ = ',', count++;
			GET_RGB_COLOR(a->bg, r, g, b);
			count += hex256(r & 0xFFU, &str);
			count += hex256(g & 0xFFU, &str);
			count += hex256(b & 0xFFU, &str);
			*str++ = '}', count++;
		} 
	}

	if (old_a->bold != a->bold)
		*str++ = BOLD_TOG, count++;
	if (old_a->blink != a->blink)
		*str++ = BLINK_TOG, count++;
	if (old_a->reverse != a->reverse)
		*str++ = REV_TOG, count++;
	if (old_a->underline != a->underline)
		*str++ = UND_TOG, count++;
	if (old_a->altchar != a->altchar)
		*str++ = ALT_TOG, count++;
	if (old_a->italic != a->italic)
		*str++ = ITALIC_TOG, count++;

	*old_a = *a;
	return count;
}

/*
 * ignore_attributes - Don't output attribute changes (so they are stripped out)
 *
 * Arguments: 
 *	output_		- IGNORED - Where to write the attribute to 
 *	output_size	- IGNORED - How many bytes i can write to output_
 *	old_a		- IGNORED - The state of the Attribute most recently written to output_
 *			  - The value of 'a' is written to 'old_a"
 *	a		- IGNORED - The state of the Attribute you want written to output_
 *
 * Return value:
 *	Always returns 0
 *
 * Side effects:
 *	This function does not write 'a' to 'old_a', but maybe it should!
 *
 * Errors:
 *	This function always succeeds
 */
static size_t	ignore_attributes (char *output, size_t output_size, Attribute *old_a, Attribute *a)
{
	/* XXX Should 'a' be written to 'old_a' ? */
	return 0;
}



/* Invoke all of the neccesary functions so output attributes reflect 'a'. */
static void	term_attribute (Attribute *a)
{
	term_all_off();
	if (a->reverse)		term_standout_on();
	if (a->bold)		term_bold_on();
	if (a->blink)		term_blink_on();
	if (a->underline)	term_underline_on();
	if (a->altchar)		term_altcharset_on();
	if (a->italic)		term_italics_on();

	/* Globally defeat colors */
	if (x_debug & DEBUG_NO_COLOR)
		return;

	if (IS_COLOR_NONE(a->fg)) {
		/* */ (void) 0;
	} else if (IS_COLOR_ANSI(a->fg)) {
		if (a->bold && get_int_var(BROKEN_AIXTERM_VAR))
			term_set_bold_foreground(GET_ANSI_COLOR(a->fg));
		else
			term_set_foreground(GET_ANSI_COLOR(a->fg));
	} else if (IS_COLOR_C(a->fg)) {
		panic(1, "I don't support ^C colors internally just yet");
	} else if (IS_COLOR_X(a->fg)) {
		char 	buffer[BIG_BUFFER_SIZE];
		int 	value;
		value = GET_X_COLOR(a->fg);

		snprintf(buffer, sizeof(buffer), "\e[38;5;%dm", value);
		tputs_x(buffer);
	} else if (IS_COLOR_RGB(a->fg)) {
		char buffer[BIG_BUFFER_SIZE];
		int	r, g, b;
		GET_RGB_COLOR(a->fg, r, g, b);
		snprintf(buffer, sizeof(buffer), "\e[38;2;%d;%d;%dm", r, g, b);
		tputs_x(buffer);
	} 

	if (IS_COLOR_NONE(a->bg)) {
		/* */ (void) 0;
	} else if (IS_COLOR_ANSI(a->bg)) {
		if (a->blink && get_int_var(BROKEN_AIXTERM_VAR))
			term_set_bold_background(GET_ANSI_COLOR(a->bg));
		else
			term_set_background(GET_ANSI_COLOR(a->bg));
	} else if (IS_COLOR_C(a->bg)) {
		panic(1, "I don't support ^C colors internally just yet");
	} else if (IS_COLOR_X(a->bg)) {
		char 	buffer[BIG_BUFFER_SIZE];
		int 	value;
		value = GET_X_COLOR(a->bg);

		snprintf(buffer, sizeof(buffer), "\e[48;5;%dm", value);
		tputs_x(buffer);
	} else if (IS_COLOR_RGB(a->bg)) {
		char buffer[BIG_BUFFER_SIZE];
		int	r, g, b;
		GET_RGB_COLOR(a->bg, r, g, b);
		snprintf(buffer, sizeof(buffer), "\e[48;2;%d;%d;%dm", r, g, b);
		tputs_x(buffer);
	} 

}

/* * * * * * * * * * * * * COLOR SUPPORT * * * * * * * * * * * * * * * * */
/*
 * read_c_color_new -- Parse out and count the length of a ^C color sequence
 * Arguments:
 *	start     - A string beginning with ^C that represents a color sequence
 *	d         - An (Attribute *) [or NULL] that shall be modified by the
 *		    color sequence.  It will be converted to an X-color.
 * Return Value:
 *	The length of the ^C color sequence, such that (start + retval) is
 *	the first character that is not part of the ^C color sequence.
 *	In no case does the return value pass the string's terminating null.
 *
 * Note:
 *	Unlike some other clients, EPIC does not simply slurp up all digits 
 *	after a ^C sequence (either by calling strtol() or while (isdigit()),
 *	because some people put ^C sequences before legitimate output with 
 * 	numbers (like the time on your status bar).  This function is very
 *	careful only to consume the characters that represent a bona fide 
 *	^C code.  This means things like "^C49" resolve to "^C4" + "9"
 *
 * DO NOT USE ANY OTHER FUNCTION TO PARSE ^C CODES.  YOU HAVE BEEN WARNED!
 */
static ssize_t	read_color_seq_new (const char *start, void *d)
{
	/* 
	 * We map C-colors to X-colors here
	 * If the value is -1, then that is an illegal ^C lvalue.
	 */
	static	uint32_t	fg_x_color_conv[] = {
		 COLOR_X(231),  COLOR_X(16),  COLOR_X(18),  COLOR_X(28), 
		 COLOR_X(196),  COLOR_X(88),  COLOR_X(90),  COLOR_X(208),	/*  0-7  */
		 COLOR_X(226),  COLOR_X(46),  COLOR_X(30),  COLOR_X(51),  
		 COLOR_X(21),   COLOR_X(201), COLOR_X(244), COLOR_X(252),	/*  8-15 */
		 COLOR_X(252),  COLOR_NONE,   COLOR_NONE,   COLOR_NONE,  
		 COLOR_NONE,    COLOR_NONE,   COLOR_NONE,   COLOR_NONE, 	/* 16-23 */
		 COLOR_NONE,    COLOR_NONE,   COLOR_NONE,   COLOR_NONE,  
		 COLOR_NONE,    COLOR_NONE,   COLOR_X(0),   COLOR_X(1), 	/* 24-31 */
		 COLOR_X(2), COLOR_X(3), COLOR_X(4), COLOR_X(5), 
		 COLOR_X(6), COLOR_X(7), COLOR_NONE,  COLOR_NONE,		/* 32-39 */
		 COLOR_NONE,    COLOR_NONE,   COLOR_NONE,  COLOR_NONE,  
		 COLOR_NONE,    COLOR_NONE,   COLOR_NONE,  COLOR_NONE, 		/* 40-47 */
		 COLOR_NONE,    COLOR_NONE,   COLOR_X(8), COLOR_X(9), 
		 COLOR_X(10), COLOR_X(11), COLOR_X(12), COLOR_X(13), 	/* 48-55 */
		 COLOR_X(14), COLOR_X(15), COLOR_NONE, COLOR_NONE, COLOR_NONE	/* 56-60 */
	};
	static	uint32_t	bg_x_color_conv[] = {
		 COLOR_X(231),  COLOR_X(16),  COLOR_X(18),  COLOR_X(28), 
		 COLOR_X(196),  COLOR_X(88),  COLOR_X(90), COLOR_X(208),	/*  0-7  */
		 COLOR_X(226),  COLOR_X(46),  COLOR_X(30),  COLOR_X(51),  
		 COLOR_X(21), COLOR_X(201), COLOR_X(244), COLOR_X(252),		/*  8-15 */
		 COLOR_X(252),  COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  
		 COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  COLOR_NONE, 		/* 16-23 */
		 COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  
		 COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  COLOR_NONE, 		/* 24-31 */
		 COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  
		 COLOR_NONE,  COLOR_NONE,  COLOR_NONE,  COLOR_NONE, 		/* 32-39 */
		 COLOR_X(0), COLOR_X(1),  COLOR_X(2), COLOR_X(3),
		 COLOR_X(4), COLOR_X(5), COLOR_X(6), COLOR_X(7),	/* 40-47 */
		 COLOR_NONE,  COLOR_NONE,   COLOR_X(8), COLOR_X(9),
		 COLOR_X(10), COLOR_X(11), COLOR_X(12), COLOR_X(13), 	/* 48-55 */
		 COLOR_X(14), COLOR_X(15), COLOR_NONE, COLOR_NONE, COLOR_NONE	/* 56-60 */
	};


	/* Local variables, of course */
	const 	char *		ptr = start;
		int		c1, c2;
		Attribute *	a;
		Attribute	ad;
		int		fg;
		int		val;
		int		noval;

        /* Reset all attributes to zero */
	zero_attribute(&ad);

	/* Copy the inward attributes, if provided */
	a = (d) ? (Attribute *)d : &ad;

	/*
	 * Originally this was a check for *ptr == '\003' but it was
	 * inconvenient to pass in the ^C here for prepare_display, so
	 * i'm making this check optional.
	 */
	if (*ptr == COLOR_TAG)
		ptr++;

	/*
	 * This is a one-or-two-time-through loop.  We find the maximum
	 * span that can compose a legit ^C sequence, then if the first
	 * nonvalid character is a comma, we grab the rhs of the code.
	 */
	for (fg = 1; ; fg = 0)
	{
		/*
		 * If its just a lonely old ^C, then its probably a terminator.
		 * Just skip over it and go on.
		 */
		if (*ptr == 0)
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			a->bold = a->blink = 0;
			return ptr - start;
		}

		/*
		 * Check for the very special case of a definite terminator.
		 * If the argument to ^C is -1, then we absolutely know that
		 * this ends the code without starting a new one
		 */
		/* XXX *cough* is 'ptr[1]' valid here? */
		else if (ptr[0] == '-' && ptr[1] == '1')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			a->bold = a->blink = 0;
			return (ptr + 2) - start;
		}

		/*
		 * Further checks against a lonely old naked ^C.
		 */
		else if (!isdigit(ptr[0]) && ptr[0] != ',')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			a->bold = a->blink = 0;
			return ptr - start;
		}


		/*
		 * Code certainly cant have more than two chars in it
		 */
		c1 = ptr[0];
		c2 = ptr[1];
		val = 0;
		noval = 0;

#define mkdigit(x) ((x) - '0')

		/* Our action depends on the char immediately after the ^C. */
		switch (c1)
		{
			/* These might take one or two characters */
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			{
			    if (c2 >= '0' && c2 <= '9')
			    {
				int	val1;
				int	val2;

			        ptr++;
				val1 = mkdigit(c1);
				val2 = mkdigit(c1) * 10 + mkdigit(c2);

				if (fg)
				{
					if (fg_x_color_conv[val2] == COLOR_NONE)
						val = val1;
					else
						val = val2, ptr++;
				}
				else
				{
					if (bg_x_color_conv[val2] == COLOR_NONE)
						val = val1;
					else
						val = val2, ptr++;
				}
				break;
			    }

			    FALLTHROUGH
			    /* FALLTHROUGH */
			    /* 
			     * Fallthrough if 1st digit is 0-5
			     * and the 2nd digit is not a number.
			     * This is a 1 digit color code.
			     */
			}

			/* This might take one or two characters (sigh) */
			case '9':
			{
				/* Ignore color 99. sigh. */
				if (c1 == '9' && c2 == '9')
				{
					ptr += 2;
					noval = 1;
					break;
				}

				FALLTHROUGH
				/* FALLTHROUGH */
				/*
				 * Fallthrough if 1st digit is 0-5
				 * and the 2nd digit is not a number,
				 * or if 1st digit is 9, the 2nd digit 
				 * is not 9.
				 */
			}

			/* These can only take one character */
			case '6':
			case '7':
			case '8':
			{
				ptr++;

				val = mkdigit(c1);
				break;
			}

					

			/*
			 * Y -> <stop> Y for any other nonnumeric Y
			 */
			default:
			{
				noval = 1;
				break;
			}
		}

		if (noval == 0)
		{
			if (fg)
				a->fg = fg_x_color_conv[val];
			else
				a->bg = bg_x_color_conv[val];
		}

		if (fg && *ptr == ',')
		{
			ptr++;
			continue;
		}
		else
			break;
	}

	return ptr - start;
}

/*
 * read_rgb_seq -- Parse out and count the length of a ^X color sequence
 * Arguments:
 *	start     - A string beginning with ^C that represents a color sequence
 *	d         - An (Attribute *) [or NULL] that shall be modified by the
 *		    color sequence.
 * Return Value:
 *	The length of the ^X color sequence, such that (start + retval) is
 *	the first character that is not part of the ^X color sequence.
 *	In no case does the return value pass the string's terminating null.
 *
 * DO NOT USE ANY OTHER FUNCTION TO PARSE ^X CODES.  YOU HAVE BEEN WARNED!
 */
static ssize_t	read_rgb_seq (const char *start, void *d)
{
	/* Local variables, of course */
	const 	char *		ptr = start;
		Attribute *	a;
		Attribute	ad;
		int		fg;
		int		set;
		int		brace = 0;

	/* Copy the inward attributes, if provided */
	if (d)
		a = (Attribute *)d;
	else
	{
		zero_attribute(&ad);
		a = &ad;
	}

	/*
	 * Originally this was a check for *ptr == '\030' but it was
	 * inconvenient to pass in the ^C here for prepare_display, so
	 * i'm making this check optional.
	 */
	if (*ptr == COLOR_EXTENDED_TAG)
		ptr++;
	if (*ptr == '{') {
		brace = 1;
		ptr++;
	}

	/*
	 * This is a one-or-two-time-through loop.  We find the maximum
	 * span that can compose a legit ^C sequence, then if the first
	 * nonvalid character is a comma, we grab the rhs of the code.
	 */
	for (fg = 1; ; fg = 0)
	{
		/*
		 * The first color (fg) is always prefixed with a #.
		 * As a UX accomoodation, if you prefix the bg color
		 * with a #, we will ignore it.  The # is not necessary
		 * before the bg color.
		 */
		if (*ptr == '#')
			ptr++;

		/*
		 * If its just a lonely old ^X, then its probably a terminator.
		 * Just skip over it and go on.
		 * XXX Note -- this is invalid; but we're tolerant because
		 * it's unambiguous.
		 */
		if (*ptr == 0)
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			break;
		}

		/*
		 * Check for the very special case of a definite terminator.
		 * If the argument to ^X is -1, then we absolutely know that
		 * this ends the code without starting a new one
		 */
		else if (ptr[0] == '-' && ptr[1] == '1')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			ptr += 2;
			break;
		}

		/*
		 * Further checks against a lonely old naked ^X#.
		 * XXX Note -- this is invalid; but we're tolerant 
		 * (even though it's ambiguous)
		 */
		else if (!isxdigit(ptr[0]) && ptr[0] != ',')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			break;
		}

		/*
		 * If there is a leading comma, then no color here.
		 */
		if (fg && *ptr == ',')
		{
			ptr++;
			continue;
		}


		char d1, d2, d3, d4, d5, d6;
		int	r, g, b;

		/* If the code is too short, stop where we stop. */
		/* clang complains if i initialize d1. bah */
		d2 = d3 = d4 = d5 = d6 = 0;
		if ((d1 = ptr[0]))
		    if ((d2 = ptr[1]))
		        if ((d3 = ptr[2]))
			    if ((d4 = ptr[3]))
				if ((d5 = ptr[4]))
				    d6 = ptr[5];

		/*
		 * Highest priority -- check for six hex digits
		 */
		if (isxdigit(d1) && isxdigit(d2) && 
		    isxdigit(d3) && isxdigit(d4) &&
		    isxdigit(d5) && isxdigit(d5) )
		{
			set = 1;
			r = check_xdigit(d1) * 16 + check_xdigit(d2);
			g = check_xdigit(d3) * 16 + check_xdigit(d4);
			b = check_xdigit(d5) * 16 + check_xdigit(d6);
			ptr += 6;
		}
		/*
		 * next, check for an explicit "-1"
		 */
		else if (d1 == '-' && d2 == '1')
		{
			set = 0;
			ptr += 2;
		}
		else
			set = 0;


		if (fg)
		{
			if (set)
				a->fg = COLOR_RGB(r, g, b);
		}
		else 
		{
			if (set)
				a->bg = COLOR_RGB(r, g, b);
		}

		if (fg && *ptr == ',')
		{
			ptr++;
			continue;
		}
		else
			break;
	}
	if (brace && *ptr == '}')
		ptr++;

	return ptr - start;
}

/*
 * read_color256_seq -- Parse out and count the length of an ^X color sequence
 *
 * Arguments:
 *	start     - A string beginning with '^X' that represents an RGB color sequence
 *	d         - An (Attribute *) [or NULL] that shall be modified by the
 *		    color sequence.
 *
 * Return Value:
 *	The length of the ^X# color sequence, such that (start + retval) is
 *	the first character that is not part of the ^X# color sequence.
 *	In no case does the return value pass the string's terminating null.
 *
 * DO NOT USE ANY OTHER FUNCTION TO PARSE ^X# CODES.  YOU HAVE BEEN WARNED!
 */
static ssize_t	read_color256_seq (const char *start, void *d)
{
	/* Local variables, of course */
	const 	char *		ptr = start;
		int		c1, c2;
		Attribute *	a;
		Attribute	ad;
		int		fg;
		int		val;
		int		set;

	/* Copy the inward attributes, if provided */
	if (d)
		a = (Attribute *)d;
	else
	{
		zero_attribute(&ad);
		a = &ad;
	}

	/*
	 * Originally this was a check for *ptr == '\030' but it was
	 * inconvenient to pass in the ^X here for prepare_display, so
	 * i'm making this check optional.
	 */
	if (*ptr == COLOR_EXTENDED_TAG)
		ptr++;

	/*
	 * This is a one-or-two-time-through loop.  We find the maximum
	 * span that can compose a legit ^C sequence, then if the first
	 * nonvalid character is a comma, we grab the rhs of the code.
	 */
	for (fg = 1; ; fg = 0)
	{
		/*
		 * If its just a lonely old ^X#, then its probably a terminator.
		 * Just skip over it and go on.
		 * XXX Note -- this is invalid; but we're tolerant because
		 * it's unambiguous.
		 */
		if (*ptr == 0)
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			return ptr - start;
		}

		/*
		 * Check for the very special case of a definite terminator.
		 * If the argument to ^X# is -1, then we absolutely know that
		 * this ends the code without starting a new one
		 */
		else if (ptr[0] == '-' && ptr[1] == '1')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			return (ptr + 2) - start;
		}

		/*
		 * Further checks against a lonely old naked ^X.
		 * XXX Note -- this is invalid; but we're tolerant 
		 * (even though it's ambiguous)
		 */
		else if (!isxdigit(ptr[0]) && ptr[0] != ',')
		{
			if (fg)
				a->fg = COLOR_NONE;
			a->bg = COLOR_NONE;
			return ptr - start;
		}


		/*
		 * Code certainly cant have more than three chars in it
		 */
		c2 = 0;
		if ((c1 = ptr[0]))
			c2 = ptr[1];

		/*
		 * If there is a leading comma, then no color here.
		 */
		if (fg && c1 == ',')
		{
			ptr++;
			continue;
		}

		/*
		 * Highest priority -- check for two hex digits
		 */
		else if (isxdigit(c1) && isxdigit(c2))
		{
			set = 1;
			val = check_xdigit(c1) * 16 + check_xdigit(c2);
			ptr += 2;
		}
		/*
		 * Third, check for one hex digit
		 */
		else if (isxdigit(c1))
		{
			set = 1;
			val = check_xdigit(c1);
			ptr++;
		}
		/*
		 * Fourth, check for an explicit "-1"
		 */
		else if (c1 == '-' && c2 == '1')
		{
			set = 0;
			val = 0;
			ptr += 2;
		}
		else
		{
			set = 0;
			val = 0;
		}


		if (fg)
		{
			if (set)
				a->fg = COLOR_X(val);
		}
		else 
		{
			if (set)
				a->bg = COLOR_X(val);
		}

		if (fg && *ptr == ',')
		{
			ptr++;
			continue;
		}
		else
			break;
	}

	return ptr - start;
}

/**************************************************************************/
/*
 * read_esc_seq -- Parse out and count the length of an escape (ansi) sequence
 * Arguments:
 *	start     - A string beginning with ^[ that represents an ansi sequence
 *	ptr_a     - An (Attribute *) [or NULL] that shall be modified by the
 *		    ansi sequence.
 *	nd_spaces - [OUTPUT] The number of ND_SPACES the sequence generates.
 * Return Value:
 *	The length of the escape (ansi) sequence, such that (start + retval) is
 *	the first character that is not part of the escape (ansi) sequence.
 *	In no case does the return value pass the string's terminating null.
 *
 * Note:
 *	EPIC supports the "Non-destructive space" escape sequence (^[[10C)
 *	which is used by ascii artists.  If this is used, then "nd_spaces" is
 *	set to the value, otherwise it is set to 0.
 *
 *	All escape sequences are parsed, but not all escape sequences are 
 *	honored.  If a dishonored escape sequence is encountered, then its
 *	length is counted, but 'ptr_a' will be unchanged.
 *
 * DO NOT USE ANY OTHER FUNCTION TO PARSE ESCAPES.  YOU HAVE BEEN WARNED!
 */
static ssize_t	read_esc_seq (const char *start, void *ptr_a, int *nd_spaces)
{
	Attribute *	a = NULL;
	Attribute 	safe_a;
	int 		args[10];
	int		nargs;
	unsigned char 	chr;
	ssize_t		len;

	if (ptr_a == NULL)
	{
		/* Always start from zero */
		zero_attribute(&safe_a);
		a = &safe_a;
	}
	else
		a = (Attribute *)ptr_a;

	*nd_spaces = 0;
	len = 0;

	switch (start[len])
	{
	    /*
	     * These are two-character commands.  The second
	     * char is the argument.
	     */
	    case ('#') : case ('(') : case (')') :
	    case ('*') : case ('+') : case ('$') :
	    case ('@') :
	    {
		if (start[len+1] != 0)
			len++;
		break;
	    }

	    /*
	     * These are just single-character commands.
	     */
	    case ('7') : case ('8') : case ('=') :
	    case ('>') : case ('D') : case ('E') :
	    case ('F') : case ('H') : case ('M') :
	    case ('N') : case ('O') : case ('Z') :
	    case ('l') : case ('m') : case ('n') :
	    case ('o') : case ('|') : case ('}') :
	    case ('~') : case ('c') :
	    default:
	    {
		break;		/* Don't do anything */
	    }

	    /*
	     * Swallow up graphics sequences...
	     */
	    case ('G'):
	    {
		while ((chr = start[++len]) && chr != ':')
			;
		if (chr == 0)
			len--;
		break;
	    }

	    /*
	     * Not sure what this is, it's not supported by
	     * rxvt, but its supposed to end with an ESCape.
	     */
	    case ('P') :
	    {
		while ((chr = start[++len]) && chr != 033)
			;
		if (chr == 0)
			len--;
		break;
	    }

	    /*
	     * Strip out Xterm sequences
	     */
	    case (']') :
	    {
		while ((chr = start[++len]) && chr != 7)
			;
		if (chr == 0)
			len--;
		break;
	    }

	    case ('[') :
	    {
start_over:

	    /*
	     * Set up the arguments list
	     */
	    args[0] = args[1] = args[2] = args[3] = 0;
	    args[4] = args[5] = args[6] = args[7] = 0;
	    args[8] = args[9] = 0;
        
	   /*
	    * This stuff was taken/modified/inspired by rxvt.  We do it this 
	    * way in order to trap an esc sequence that is embedded in another
	    * (blah).  We're being really really really paranoid doing this, 
	    * but it is for the best.
	    */

	   /*
	    * Check to see if the stuff after the command is a "private" 
	    * modifier.  If it is, then we definitely arent interested.
	    *   '<' , '=' , '>' , '?'
	    */
	   chr = start[len];
	   if (chr >= '<' && chr <= '?')
		(void)0;		/* Skip it */

	   /*
	    * Now pull the arguments off one at a time.  Keep pulling them 
	    * off until we find a character that is not a number or a semicolon.
	    * Skip everything else.
	    */
	   for (nargs = 0; nargs < 10; )
	   {
		int n = 0;

		len++;
		for (n = 0; isdigit(start[len]); len++)
			n = n * 10 + (start[len] - '0');

		args[nargs++] = n;

		/*
		 * If we run out of code here, then we're totaly confused.
		 * just back out with whatever we have...
		 */
		if (!start[len])
			return len;

		if (start[len] != ';')
			break;
	    }

	    /*
	     * If we find a new ansi char, start all over from the top 
	     * and strip it out too
	     */
	    if (start[len] == 033)
		goto start_over;

	    /*
	     * Support "spaces" (cursor right) code
	     */
	    else if (start[len] == 'a' || start[len] == 'C')
	    {
		len++;
		if (nargs >= 1)
		{
		    /* Keep this within reality.  */
		    if (args[0] > 256)
			args[0] = 256;
		    *nd_spaces = args[0];
		}
	    }

	    /*
	     * Walk all of the numeric arguments, plonking the appropriate 
	     * attribute changes as needed.
	     */
	    else if (start[len] == 'm')
	    {
		int	i;

		len++;
		for (i = 0; i < nargs; i++)
		{
		    switch (args[i])
		    {
			case 0:		/* Reset to default */
			{
				zero_attribute(a);
				break;
			}
			case 1:		/* bold on */
				a->bold = 1;
				break;
			case 2:		/* dim on -- not supported */
				break;
			case 3:		/* Italics on -- not supported */
				break;
			case 4:		/* Underline on */
				a->underline = 1;
				break;
			case 5:		/* Blink on */
			case 26:	/* Blink on */
				a->blink = 1;
				break;
			case 6:		/* Blink off */
			case 25:	/* Blink off */
				a->blink = 0;
				break;
			case 7:		/* Reverse on */
				a->reverse = 1;
				break;
			case 21:	/* Bold off */
			case 22:	/* Bold off */
				a->bold = 0;
				break;
			case 23:	/* Italics off -- not supported */
				break;
			case 24:	/* Underline off */
				a->underline = 0;
				break;
			case 27:	/* Reverse off */
				a->reverse = 0;
				break;

			case 30: case 31: case 32: case 33: case 34: case 35:
			case 36: case 37:	/* Set foreground color */
			{
				if (a->bold)
					a->fg = COLOR_ANSI(args[i] - 30 + 8);
				else
					a->fg = COLOR_ANSI(args[i] - 30);
				break;
			}
			case 38:	/* Set 256 fg color */
			{
				/* 38 takes at least one argument */
				if (i == nargs)
					break;		/* Invalid */
				i++;

				/* 38-5 takes 1 argument */
				if (args[i] == 5)
				{
					if (i == nargs)
					    break;	/* Invalid */

					a->fg = COLOR_X(args[++i]);
					break;		
				}
				else if (args[i] == 2)
				{
					int	r, g, b;

					if (i + 3 >= nargs)
					    break;	/* Invalid */

					r = args[++i];
					g = args[++i];
					b = args[++i];
					a->fg = COLOR_RGB(r, g, b);
					break;
				}

				break;
			}
			case 39:	/* Reset foreground color to default */
			{
				a->fg = COLOR_NONE;
				break;
			}

			case 40: case 41: case 42: case 43: case 44: case 45:
			case 46: case 47:	/* Set background color */
			{
				if (a->blink)
					a->bg = COLOR_ANSI(args[i] - 40 + 8);
				else
					a->bg = COLOR_ANSI(args[i] - 40);
				break;
			}
			case 48:	/* Set 256 bg color */
			{
				/* 48 takes at least one argument */
				if (i == nargs)
					break;		/* Invalid */
				i++;

				/* 48-5 takes 1 argument */
				if (args[i] == 5)
				{
					if (i == nargs)
					    break;	/* Invalid */

					a->bg = COLOR_X(args[++i]);
					break;	
				}
				else if (args[i] == 2)
				{
					int	r, g, b;

					if (i + 3 >= nargs)
					    break;	/* Invalid */

					r = args[++i];
					g = args[++i];
					b = args[++i];
					a->bg = COLOR_RGB(r, g, b);
					break;
				}

				break;
			}
			case 49:	/* Reset background color to default */
			{
				a->bg = COLOR_NONE;
				break;
			}

			/* 
			 * To accommodate irregular emulators, we store
			 * "bold" or "bright" colors in spots 8-15.
			 * So we subtract 82 to convert it to a 8-15 number
			 */
			case 90: case 91: case 92: case 93: case 94: case 95:
			case 96: case 97:	/* Set bright foreground color */
			{
				a->fg = COLOR_ANSI(args[i] - 82);
				break;
			}
			case 100: case 101: case 102: case 103: case 104: case 105:
			case 106: case 107:	/* Set bright (blink) background color */
			{
				a->bg = COLOR_ANSI(args[i] - 92);
				break;
			}


			default:	/* Everything else is not supported */
				break;
		    }
		} /* End of for loop */
	    } /* End of handling esc-[...m */
	    } /* End of case esc-[ */
	} /* End of case esc */

	/* All other escape sequences are ignored! */
	return len;
}

/**************************** STRIP ANSI ***********************************/
/*
 * The THREE FUNCTIONS OF DOOM
 *
 * 1) normalize_string -- given any arbitrary string, make it "safe" to use.
 * 2) prepare_display -- given a "safe" string, break it into lines
 * 3) output_with_count -- given a broken "safe" string, actually output it.
 */

/*
 * State 0 is a "normal, printable character" (8 bits included)
 * State 1 is a "C1 character", aka a control character with high bit set.
 * State 2 is an "escape character" (\033)
 * State 3 is a "color code character" (\003 aka COLOR_TAG)
 * State 4 is an "attribute change character"
 * State 5 is a "suppressed character" (always stripped)
 * State 6 is a "character that is never printable."
 * State 7 is a "beep"
 * State 8 is a "tab"
 * State 9 is a "non-destructive space"
 * State 10 is a "extended color code character" (\030 aka COLOR_EXTENDED_TAG)
 */
static	unsigned char	ansi_state[128] = {
/*	^@	^A	^B	^C	^D	^E	^F	^G(\a) */
	6,	6,	4,	3,	6,	4,	4,	7,  /* 000 */
/*	^H	^I	^J(\n)	^K	^L(\f)	^M(\r)	^N	^O */
	6,	8,	0,	6,	6,	5,	6,	4,  /* 010 */
/*	^P	^Q	^R	^S	^T	^U	^V	^W */
	4,	6,	6,	9,	6,	6,	4,	6,  /* 020 */
/*	^X	^Y	^Z	^[	^\	^]	^^	^_ */
	10,	6,	6,	2,	6,	6,	6,	4,  /* 030 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 040 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 050 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 060 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 070 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 100 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 110 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 120 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 130 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 140 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 150 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 160 */
	0,	0,	0,	0,	0,	0,	0,	0,  /* 170 */
};

/*
	A = Character or sequence converted into an attribute
	M = Character mangled
	S = Character stripped, sequence (if any) NOT stripped
	X = Character stripped, sequence (if any) also stripped
	T = Transformed into other (safe) chars
	- = No transformation

					Type
    			0    1    2    3    4    5    6    7    8    9  10
(Default)		-    -    -    -    A    -    -    T    T    T   -
NORMALIZE		-    -    A    A    -    X    M    -    -    -   A
MANGLE_ESCAPES		-    -    S    -    -    -    -    -    -    -   -
STRIP_COLOR		-    -    -    X    -    -    -    -    -    -   X
STRIP_*			-    -    -    -    X    -    -    -    -    -   -
STRIP_UNPRINTABLE	-    X    S    S    X    X    X    X    -    -   S
STRIP_OTHER		X    -    -    -    -    -    -    -    X    X   -
(/SET ALLOW_C1)		-    X    -    -    -    -    -    -    -    -   -
*/

/*
 * new_normalize_string -- Transform an untrusted input string into something
 *				we can trust.
 * Arguments:
 *   str	An untrusted input string
 *   logical	How attribute changes should look in the output:
 *		0	internal attributes (suitable for output_with_count())
 *		1	ircii attributes (^B/^V/^C, etc)
 *		2	Stripped out entirely
 *		3	internal attributes, used especially by make_status()
 *   mangler	How we want the string to be transformed
 *		The above chart shows how the different types of characters
 *		are transformed by the different mangler types.  There are
 *		three ambiguous cases, which are resolved as such:
 *		Type 2:
 *			MANGLE_ESCAPES has the first priority, then
 *			NORMALIZE is next, finally STRIP_UNPRINTABLE.
 *		Type 3:
 *			STRIP_UNPRINTABLE has the first priority, then
 *			NORMALIZE and STRIP_COLOR.  You need to use both 
 *			NORMALIZE and STRIP_COLOR to strip color changes 
 *			in color sequences
 *		Type 6:
 *			STRIP_UNPRINTABLE has higher priority than NORMALIZE.
 *
 * Furthermore, the following two sets affect behavior:
 *	  /SET ALLOW_C1_CHARS
 *		ON  == Type 1 chars are treated as Type 0 chars (safe)
 *		OFF == Type 1 chars are treated as Type 5 chars (unsafe)
 *	  /SET TERM_DOES_BRIGHT_BLINK
 *		???
 *
 * Return Value:
 *	A new trusted string, that has been subjected to the transformations
 *	in "mangler", with attribute changes represented in the "logical"
 *	format.
 */
char *	new_normalize_string (const char *str, int logical, int mangle)
{
	char *	output;
	Attribute	a, olda;
	int 		pos;
	int		maxpos;
	int		i;
	int		pc = 0;
	int		mangle_escapes, normalize;
	int		strip_reverse, strip_bold, strip_blink, 
			strip_underline, strip_altchar, strip_color, 
			strip_all_off, strip_nd_space;
	int		strip_unprintable, strip_other, strip_c1, strip_italic;
	int		codepoint, state, cols;
	char 		utf8str[16], *x;
	size_t		(*attrout) (char *, size_t, Attribute *, Attribute *) = NULL;
	ptrdiff_t	offset;

	mangle_escapes 	= ((mangle & MANGLE_ESCAPES) != 0);
	normalize	= ((mangle & NORMALIZE) != 0);

	strip_color 	= ((mangle & STRIP_COLOR) != 0);
	strip_reverse 	= ((mangle & STRIP_REVERSE) != 0);
	strip_underline	= ((mangle & STRIP_UNDERLINE) != 0);
	strip_bold 	= ((mangle & STRIP_BOLD) != 0);
	strip_blink 	= ((mangle & STRIP_BLINK) != 0);
	strip_nd_space	= ((mangle & STRIP_ND_SPACE) != 0);
	strip_altchar 	= ((mangle & STRIP_ALT_CHAR) != 0);
	strip_all_off 	= ((mangle & STRIP_ALL_OFF) != 0);
	strip_unprintable = ((mangle & STRIP_UNPRINTABLE) != 0);
	strip_other	= ((mangle & STRIP_OTHER) != 0);
	strip_italic	= ((mangle & STRIP_ITALIC) != 0);

	strip_c1	= !get_int_var(ALLOW_C1_CHARS_VAR);

	if (logical == 0)
		attrout = write_internal_attribute;	/* prep for screen output */
	else if (logical == 1)
		attrout = write_ircii_attributes;	/* non-screen handlers */
	else if (logical == 2)
		attrout = ignore_attributes;	/* $stripansi() function */
	else if (logical == 3)
		attrout = write_internal_attribute;	/* The status line */
	else
		panic(1, "'logical == %d' is not valid.", logical);

	zero_attribute(&a);
	zero_attribute(&olda);

	/* 
	 * The output string has a few extra chars on the end just 
	 * in case you need to tack something else onto it.
	 */
	maxpos = strlen(str);
	output = new_malloc(maxpos + 192);
	pos = 0;

	while ((codepoint = next_code_point2(str, &offset, 1)) > 0)
	{
	    str += offset;

	    if (pos > maxpos - 16)
	    {
		maxpos += 192; /* Extend 192 chars at a time */
		RESIZE(output, char, maxpos + 192);
	    }

	    /* Normal unicode code points are always permitted */
	    if (codepoint >= 0xA0)		/* Valid unicode points */
		state = 0;
	    else if (codepoint >= 0x80)		/* C1 chars */
		state = 1;
	    else
		state = ansi_state[codepoint];

	    switch (state)
	    {
		/*
		 * Unicode does not define any code points in the
		 * U+0080 - U+009F zone ("C1 chars") and many terminal
		 * emulators do weird things with them.   So we should
		 * probably just strip them out.
		 */
		case 1:	     	/* C1 Chars */
		case 5:		/* Unprintable chars */
		{
			if (strip_unprintable)
				break;
			if (state == 5 && normalize)
				break;
			if (state == 1 && strip_c1)
			{
				codepoint = (codepoint | 0x60U) & 0x7FU;
abnormal_char:
				a.reverse = !a.reverse;
				pos += attrout(output + pos, maxpos - pos, &olda, &a);
				output[pos++] = (char)(codepoint);
				a.reverse = !a.reverse;
				pos += attrout(output + pos, maxpos - pos, &olda, &a);
				pc += 1;
				break;
			}
			FALLTHROUGH
			/* FALLTHROUGH */
		}

		/*
		 * State 0 are characters that are permitted under all
		 * circumstances.
		 */
		case 0:
		{
			if (strip_other)
				break;

normal_char:
			cols = codepoint_numcolumns(codepoint);
			if (cols == -1)
				cols = 0;
			pc += cols;

			ucs_to_utf8(codepoint, utf8str, sizeof(utf8str));
			for (x = utf8str; *x; x++)
				output[pos++] = *x;

			break;
		}

		/*
		 * State 6 is a control character (without high bit set)
		 * that doesn't have a special meaning to ircII.
		 */
		case 6:
		{
			/*
			 * \f is a special case, state 0, for status bar.  
			 * Either I special case it here, or in 
			 * output_to_count.  I prefer here.
			 */
			if (logical == 3 && codepoint == '\f')
				goto normal_char;

			if (strip_unprintable)
				break;

			/* XXX Sigh -- I bet anything this is wrong */
			if (termfeatures & TERM_CAN_GCHAR)
				goto normal_char;

			if (normalize)
			{
				codepoint = (codepoint | 0x40U) & 0x7FU;
				goto abnormal_char;
			}

			goto normal_char;
		}

		/*
		 * State 2 is the escape character
		 */
		case 2:
		{
		    int	nd_spaces = 0;
		    ssize_t	esclen;

		    if (mangle_escapes == 1)
		    {
			codepoint = '[';
			goto abnormal_char;
		    }

		    if (normalize == 1)
		    {
			esclen = read_esc_seq(str, (void *)&a, &nd_spaces);

			if (nd_spaces != 0 && !strip_nd_space)
			{
			    /* This is just sanity */
			    if (pos + nd_spaces > maxpos)
			    {
				maxpos += nd_spaces; 
				RESIZE(output, char, maxpos + 192);
			    }
			    while (nd_spaces-- > 0)
			    {
				output[pos++] = ND_SPACE;
				pc++;
			    }
			    str += esclen;
			    break;		/* attributes can't change */
			}

			if (a.reverse && strip_reverse)		a.reverse = 0;
			if (a.bold && strip_bold)		a.bold = 0;
			if (a.blink && strip_blink)		a.blink = 0;
			if (a.underline && strip_underline)	a.underline = 0;
			if (a.altchar && strip_altchar)		a.altchar = 0;
			if (a.italic && strip_italic)		a.italic = 0;
			if (strip_color)			a.fg = a.bg = COLOR_NONE;

			pos += attrout(output + pos, maxpos - pos, &olda, &a);
			str += esclen;
			break;
		    }

		    if (strip_unprintable)
			break;

		    goto normal_char;
		}

	        /*
		 * Normalize ^C codes...  And ^X codes...
	         */
		case 3:
		case 10:
		{
		   if (strip_unprintable)
			break;

		   if (strip_color || normalize)
		   {
			ssize_t	len = 0;

			if (state == 3)
				len = read_color_seq_new(str, (void *)&a);
			else if (*str == '{' || *str == '#')
				len = read_rgb_seq(str, (void *)&a);
			else
				len = read_color256_seq(str, (void *)&a);

			str += len;

			/* Suppress the color if no color is permitted */
			if (a.bold && strip_bold)		a.bold = 0;
			if (a.blink && strip_blink)		a.blink = 0;
			if (strip_color)			a.fg = a.bg = COLOR_NONE;

			/* Output the new attributes */
			pos += attrout(output + pos, maxpos - pos, &olda, &a);
			break;
		    }

		    goto normal_char;
		}

		/*
		 * State 4 is for the special highlight characters
		 */
		case 4:
		{
		    if (strip_unprintable)
			break;

		    switch (codepoint)
		    {
			case REV_TOG:
				if (!strip_reverse)
					a.reverse = !a.reverse;
				break;
			case BOLD_TOG:
				if (!strip_bold)
					a.bold = !a.bold;
				break;
			case BLINK_TOG:
				if (!strip_blink)
					a.blink = !a.blink;
				break;
			case UND_TOG:
				if (!strip_underline)
					a.underline = !a.underline;
				break;
			case ALT_TOG:
				if (!strip_altchar)
					a.altchar = !a.altchar;
				break;
			case ITALIC_TOG:
				if (!strip_italic)
					a.italic = !a.italic;
				break;
			case ALL_OFF:
				if (!strip_all_off)
				{
				    zero_attribute(&a);
				    pos += attrout(output + pos, maxpos - pos, &olda, &a);
				    olda = a;
				}
				break;
			default:
				break;
		    }

		    /* After ALL_OFF, this is a harmless no-op */
		    pos += attrout(output + pos, maxpos - pos, &olda, &a);
		    break;
		}

		case 7:      /* bell */
		{
			if (strip_unprintable)
				break;

			output[pos++] = '\007';
			break;
		}

		case 8:		/* Tab */
		{
			int	len = 8 - (pc % 8);

			if (strip_other)
				break;

			for (i = 0; i < len; i++)
			{
				output[pos++] = ' ';
				pc++;
			}
			break;
		}

		case 9:		/* Non-destruct space */
		{
			if (strip_other)
				break;
			if (strip_nd_space)
				break;

			output[pos++] = ND_SPACE;
			break;
		}

		default:
		{
			panic(1, "Unknown normalize_string mode");
			break;
		}
	    } /* End of huge ansi-state switch */
	} /* End of while, iterating over input string */

	/* Terminate the output and return it. */
	if (logical == 0)
	{
		zero_attribute(&a);
		pos += attrout(output + pos, maxpos - pos, &olda, &a);
	}
	output[pos] = output[pos + 1] = 0;
	return output;
}


/* 
 * XXX I'm not sure where this belongs, but for now it goes here.
 * This function converts a type-0 normalized string (with the attribute
 * markers) into a type-1 normalized string (with the logical characters 
 * the user understands).  
 *
 * denormalize_string - Convert a Type 0 Normalized String into Type 1
 *
 * Arguments 
 *	str 	- A Type 0 normalized string (ie, returned by 
 *			new_normalize_string() with logical == 0 or 3)
 * Return Value:
 *	A Type 1 normalized string (with ^B, ^V, ^C, ^_s, etc)
 */
char *	denormalize_string (const char *str_)
{
	const char *str = (const char *)str_;
	char *	output = NULL;
	size_t		maxpos;
	Attribute 	olda, a;
	size_t		span;
	size_t		pos;

	zero_attribute(&a);
	zero_attribute(&olda);

	/* 
	 * The output string has a few extra chars on the end just 
	 * in case you need to tack something else onto it.
	 */
	if (!str)
		str = "<denormalize_string was called with NULL>";

	maxpos = strlen(str);
	output = (char *)new_malloc(maxpos + 192);
	pos = 0;

	while (*str)
	{
		if (pos > maxpos)
		{
			maxpos += 192; /* Extend 192 chars at a time */
			RESIZE(output, char, maxpos + 192);
		}
		switch (*str)
		{
		    case '\006':
		    {
			size_t	numbytes = 0;
			if (read_internal_attribute(str, &a, &numbytes))
				continue;		/* Mangled */
			str += numbytes;

			span = write_ircii_attributes(output + pos, maxpos - pos, &olda, &a);
			pos += span;
			break;
		    }
		    default:
		    {
			output[pos++] = *str++;
			break;
		    }
		}
	}
	output[pos] = 0;
	return (char *)output;
}

/* 
 * XXX I'm not sure where this belongs, but for now it goes here.
 * This function converts a type-0 normalized string (with the attribute
 * markers) into plain text (with no attributes)
 *
 * normalized_string_to_plain_text - Convert a Type 0 Normalized String into Type 1
 *
 * Arguments 
 *	str 	- A Type 0 normalized string (ie, returned by 
 *			new_normalize_string() with logical == 0 or 3)
 * Return Value:
 *	Just the plain text from the string.
 */
char *	normalized_string_to_plain_text (const char *str_)
{
	const char *str = (const char *)str_;
	char *		output = NULL;
	size_t		maxpos;
	Attribute 	a;
	size_t		pos;

	zero_attribute(&a);

	/* 
	 * The output string has a few extra chars on the end just 
	 * in case you need to tack something else onto it.
	 */
	if (!str)
		str = "<normalized_string_to_plain_text was called with NULL>";

	maxpos = strlen(str);
	output = (char *)new_malloc(maxpos + 192);
	pos = 0;

	while (*str)
	{
		if (pos > maxpos)
		{
			maxpos += 192; /* Extend 192 chars at a time */
			RESIZE(output, char, maxpos + 192);
		}
		switch (*str)
		{
		    /* Attribute changes -- read them but ignore them */
		    case '\006':
		    {
			size_t	numbytes = 0;
			if (read_internal_attribute(str, &a, &numbytes))
				continue;		/* Mangled */
			str += numbytes;
			break;
		    }
		    default:
		    {
			output[pos++] = *str++;
			break;
		    }
		}
	}
	output[pos] = 0;
	return output;
}



/*
 * Prepare_display -- this is a new twist on FireClown's original function.
 * We dont do things quite the way they were explained in the previous 
 * comment that used to be here, so here's the rewrite. ;-)
 *
 * This function is used to break a logical line of display into some
 * number of physical lines of display, while accounting for various kinds
 * of display codes.  The logical line is passed in the 'orig_str' variable,
 * and the width of the physical display is passed in 'max_cols'.   If 
 * 'lused' is not NULL, then it points at an integer that specifies the 
 * maximum number of lines that should be prepared.  The actual number of 
 * lines that are prepared is stored into 'lused'.  The 'flags' variable
 * specifies some extra options, the only one of which that is supported
 * right now is "PREPARE_NOWRAP" which indicates that you want the function
 * to break off the text at 'max_cols' and not to "wrap" the last partial
 * word to the next line. ($leftpc() depends on this)
 */
#define SPLIT_EXTENT 40
char **prepare_display (int window,
			 const char *str,
			 int max_cols,
			 int *lused,
			 int flags)
{
static 	int 	recursion = 0;
static	char 	**output = (char **) 0;
static  size_t	output_size = 0;
	size_t	line = 0;           /* Current pos in "output"      */

	int     col = 0;            /* Current column in display    */
	int	cols;
	int	indent = 0;         /* Start of second word         */
	size_t	word_break = 0;     /* Last end of word             */
	size_t	firstwb = 0;	    /* Buffer position of second word */
	size_t	my_newline = 0;        /* Number of newlines           */
	int	do_indent;          /* Use indent or continued line? */
const 	char	*ptr;
	char	c,
		*pos_copy;
const	char	*first_ptr;
const	char	*cont_ptr;
const 	char 	*words;
	char *	str_free = NULL;
	char *	cont = NULL;
	char *	cont_free = NULL;
	char *	x;
	Attribute	a, olda;
	int	codepoint;
	char	utf8str[16];
	char *	buffer = NULL;
	size_t	bufsiz;
	size_t	pos = 0;            /* Current position in "buffer" */

	if (recursion)
		panic(1, "prepare_display() called recursively");
	recursion++;

	zero_attribute(&a);
	zero_attribute(&olda);

	if (window < 0)
		do_indent = get_int_var(INDENT_VAR);
	else
		do_indent = get_window_indent(window);
	if (!(words = get_string_var(WORD_BREAK_VAR)))
		words = " \t";
	if (!(cont_ptr = get_string_var(CONTINUED_LINE_VAR)))
		cont_ptr = empty_string;

	bufsiz = BIG_BUFFER_SIZE * 10 + 1;
	buffer = new_malloc(bufsiz);
	*buffer = 0;

	if (!output_size)
	{
		size_t 	new_i = SPLIT_EXTENT;
		RESIZE(output, char *, new_i);
		while (output_size < new_i)
			output[output_size++] = 0;
	}

	if (!(first_ptr = get_string_var(FIRST_LINE_VAR)))
		first_ptr = empty_string;
	malloc_strcpy((char **)&str_free, first_ptr);
	malloc_strcat((char **)&str_free, str);
	str = str_free;

	/*
	 * Start walking through the entire string.
	 */
	for (pos = 0, ptr = str; *ptr; )
	{
		if (pos >= bufsiz - 64)
		{
			char *	newbuf;
			bufsiz += BIG_BUFFER_SIZE;
			newbuf = new_malloc(bufsiz);
			memmove(newbuf, buffer, pos);
			new_free(&buffer);
			buffer = newbuf;
		}

		ptrdiff_t	offset;

		codepoint = next_code_point2(ptr, &offset, 1);
		ptr += offset;

		switch (codepoint)
		{
		   case '\007':      /* bell */
			buffer[pos++] = 7;
			break;

		    case '\n':      /* Forced newline */
		    {
			my_newline = 1;
			if (indent == 0)
				indent = -1;
			word_break = pos;
			break; /* case '\n' */
		    }

		    /* Attribute changes -- copy them unmodified. */
		    case '\006':
		    {
			size_t	numbytes = 0;

			/* We need to both READ and COPY the attribute here. */
			read_internal_attribute(ptr, &a, &numbytes);

			buffer[pos++] = '\006';
			copy_internal_attribute(ptr, buffer + pos, bufsiz - pos, &numbytes);
			pos += numbytes;
			ptr += numbytes;
			continue;          /* Skip the column check */
		    }

		    default:
		    {
			const char *	xptr;
			int	cp;
			int	spacechar = 0;

			xptr = words;
			while ((cp = next_code_point2(xptr, &offset, 1)))
			{
				xptr += offset;
				if (codepoint == cp)
				{
					spacechar = 1;
					word_break = pos;
					break;
				}
			}

			if (spacechar == 0)
			{
				if (indent == -1)
					indent = col;

				cols = codepoint_numcolumns(codepoint);
				if (cols == -1)
					cols = 0;
				col += cols;

				ucs_to_utf8(codepoint, utf8str, sizeof(utf8str));
				for (x = utf8str; *x; x++)
					buffer[pos++] = *x;
				break;
			}
			/* 
			 * The above is only for non-whitespace.
			 * whitespace is...
			 */
			FALLTHROUGH
			/* FALLTHROUGH */
		    }

		    case ' ':
		    case ND_SPACE:
		    {
			const char *x2;
			int	ncp;
			int	old_pos = pos;

			/* 
			 * 'ptr' points at the character after the
			 * one we are evaluating: so 'x2' is used to
			 * avoid changing ptr.  Therefore 'ncp' is the
			 * "next code point" after the space.
			 */
			x2 = ptr;
			ncp = next_code_point2(x2, &offset, 1);

			if (indent == 0)
			{
				indent = -1;
				firstwb = pos;
			}

#if 0
			saved_a = a;
#endif

			/* XXX Sigh -- exactly the same as above. */
			cols = codepoint_numcolumns(codepoint);
			if (cols == -1)
				cols = 0;
			col += cols;

			ucs_to_utf8(codepoint, utf8str, sizeof(utf8str));
			for (x = utf8str; *x; x++)
				buffer[pos++] = *x;

			/*
			 * A space always breaks here.
			 *
			 * A non-space breaks at the character AFTER the
			 * break character (ie, the breaking character stays
			 * on the first line) unless the non-space character
			 * would be the rightmost column, in which case it
			 * breaks here (and slips to the 2nd line)
			 */
			if (codepoint == ' ')
				word_break = old_pos;
			else if (ncp && (col + 1 < max_cols))
				word_break = pos;
			else
				word_break = old_pos;

			break;
		    }
		} /* End of switch (codepoint) */

		/*
		 * Must check for cols >= maxcols+1 becuase we can have a 
		 * character on the extreme screen edge, and we would still 
		 * want to treat this exactly as 1 line, and col has already 
		 * been incremented.
		 */
		if ((col > max_cols) || my_newline)
		{
			/*
			 * We just incremented col, but we need to decrement
			 * it in order to keep the count correct!
			 *		--zinx
			 */
			if (col > max_cols)
				col--;

			/*
			 * XXX Hackwork and trickery here.  In the very rare
			 * case where we end the output string *exactly* at
			 * the end of the line, then do not do any more of
			 * the following handling.  Just punt right here.
			 */
			if (ptr[1] == 0)
				break;		/* stop all processing */

			/*
			 * Default the end of line wrap to the last character
			 * we parsed if there were no spaces in the line, or
			 * if we're preparing output that is not to be
			 * wrapped (such as for counting output length.
			 */
			if (word_break == 0 || (flags & PREPARE_NOWRAP))
				word_break = pos - 1;


			/*
			 * XXXX Massive hackwork here.
			 *
			 * Due to some ... interesting design considerations,
			 * if you have /set indent on and your first line has
			 * exactly one word seperation in it, then obviously
			 * there is a really long "word" to the right of the 
			 * first word.  Normally, we would just break the 
			 * line after the first word and then plop the really 
			 * big word down to the second line.  Only problem is 
			 * that the (now) second line needs to be broken right 
			 * there, and we chew up (and lose) a character going 
			 * through the parsing loop before we notice this.
			 * Not good.  It seems that in this very rare case, 
			 * people would rather not have the really long word 
			 * be sent to the second line, but rather included on 
			 * the first line (it does look better this way), 
			 * and so we can detect this condition here, without 
			 * losing a char but this really is just a hack when 
			 * it all comes down to it.  Good thing its cheap. ;-)
			 */
			if (!cont && (firstwb == word_break) && do_indent) 
				word_break = pos - 1;

			/*
			 * If we are approaching the number of lines that
			 * we have space for, then resize the line
			 * buffer so we dont run out.
			 */
			if (line >= output_size - 3)
			{
				size_t new_i = output_size + SPLIT_EXTENT;
				RESIZE(output, char *, new_i);
				while (output_size < new_i)
					output[output_size++] = 0;
			}

			/* XXXX HACK! XXXX HACK! XXXX HACK! XXXX */
			/*
			 * Unfortunately, due to the "line wrapping bug", if
			 * you have a really long line at the end of the first
			 * line of output, and it needs to be wrapped to the
			 * second line of input, we were blindly assuming that
			 * it would fit on the second line, but that may not
			 * be true!  If the /set continued_line jazz ends up
			 * being longer than whatever was before the wrapped
			 * word on the first line, then the resulting second
			 * line would be > max_cols, causing corruption of the
			 * display (eg, the status bar gets written over)!
			 *
			 * To counteract this bug, at the end of the first
			 * line, we calcluate the continued line marker
			 * *before* we commit the first line.  That way, we
			 * can know if the word to be wrapped will overflow
			 * the second line, and in such case, we break that
			 * word precisely at the current point, rather than
			 * at the word_break point!  This prevents the
			 * "line wrap bug", albeit in a confusing way.
			 */

			/*
			 * Calculate the continued line marker.  This is
			 * a little bit tricky because we cant figure it out
			 * until after the first line is done.  The first
			 * time through, cont == empty_string, so if !*cont,
			 * we know it has not been initialized.
			 *
			 * So if it has not been initialized and /set indent
			 * is on, and the place to indent is less than a third
			 * of the screen width and /set continued_line is
			 * less than the indented width, then we pad the 
			 * /set continued line value out to the appropriate
			 * width.
			 */
			if (!cont)
			{
				int	lhs_count = 0;
				int	continued_count = 0;

				/* Because Blackjac asked me to */
				/* The test against 0 is for lines w/o spaces */
				if (indent > max_cols / 3 || indent <= 0)
					indent = max_cols / 3;

				if (do_indent)
				{
				    char *fixedstr;

				    fixedstr = prepare_display_fixed_size(cont_ptr, indent, 0, ' ', 1);
				    cont = LOCAL_COPY(fixedstr);
				    new_free(&fixedstr);
				}

				/*
				 * Otherwise, we just use /set continued_line, 
				 * whatever it is.
				 */
				else /* if ((!cont || !*cont) && *cont_ptr) */
					cont = LOCAL_COPY(cont_ptr);

				cont_free = cont = new_normalize_string(cont, 0, display_line_mangler);

				/*
				 * XXXX "line wrap bug" fix.  If we are here,
				 * then we are between the first and second
				 * lines, and we might have a word that does
				 * not fit on the first line that also does
				 * not fit on the second line!  We check for
				 * that right here, and if it won't fit on
				 * the next line, we revert "word_break" to
				 * the current position.
				 *
				 * THIS IS UNFORTUNATELY VERY EXPENSIVE! :(
				 */
				c = buffer[word_break];
				buffer[word_break] = 0;
				lhs_count = output_with_count(buffer, 0, 0);
				buffer[word_break] = c;
				continued_count = output_with_count(cont, 0, 0);

				/* 
				 * Chop the line right here if it will
				 * overflow the next line.
				 *
				 * Save the attributes, too! (05/29/02)
				 *
				 * XXX Saving the attributes may be 
				 * spurious but i'm erring on the side
				 * of caution for the moment.
				 */
				if (lhs_count <= continued_count) {
					word_break = pos;
#if 0
					saved_a = a;
#endif
				}

				/*
				 * XXXX End of nasty nasty hack.
				 */
			}

			/*
			 * Now we break off the line at the last space or
			 * last char and copy it off to the result buffer.
			 */
			c = buffer[word_break];
			buffer[word_break] = 0;
			malloc_strcpy((char **)&(output[line++]), buffer);
			buffer[word_break] = c;


			/*
			 * Skip over all spaces that occur after the break
			 * point, up to the right part of the screen (where
			 * we are currently parsing).  This is what allows
			 * lots and lots of spaces to take up their room.
			 * We let spaces fill in lines as much as neccesary
			 * and if they overflow the line we let them bleed
			 * to the next line.
			 */
			while (word_break < pos && buffer[word_break] == ' ')
				word_break++;

			/*
			 * At this point, we still have some junk left in
			 * 'buffer' that needs to be moved to the next line.
			 * But of course, all new lines must be prefixed by
			 * the /set continued_line and /set indent stuff, so
			 * we copy off the stuff we have to a temporary
			 * buffer, copy the continued-line stuff into buffer
			 * and then re-append the junk into buffer.  Then we
			 * fix col and pos appropriately and continue parsing 
			 * str...
			 */
			/* 'pos' has already been incremented... */
			buffer[pos] = 0;
			pos_copy = LOCAL_COPY(buffer + word_break);
			strlcpy(buffer, cont, bufsiz / 2);
#if 0
			/* -- I'm not sure if this is necessary */
			/* write_internal_attribute(buffer + strlen(buffer), &olda, &saved_a); */
#endif
			/* -- and I think this should be before we copy pos_copy back */
			write_internal_attribute(buffer + strlen(buffer), bufsiz - strlen(buffer), &olda, &a);
			strlcat(buffer, pos_copy, bufsiz / 2);

			pos = strlen(buffer);
			/* Watch this -- ugh. how expensive! :( */
			col = output_with_count(buffer, 0, 0);
			word_break = 0;
			my_newline = 0;

			/*
			 * The 'lused' argument allows us to truncate the
			 * parsing at '*lused' lines.  This is most helpful
			 * for the $leftpc() function, which sets a logical
			 * screen width and asks us to "output" one line.
			 */
			if (*lused && *lused > 0 && line >= (size_t) *lused)
			{
				*buffer = 0;
				break;
			}
		} /* End of new line handling */
	} /* End of (ptr = str; *ptr && (pos < sizeof(buffer) - 8); ptr++) */

        /* Reset all attributes to zero */
	zero_attribute(&a);
	pos += write_internal_attribute(buffer + pos, bufsiz - pos, &olda, &a);
	buffer[pos] = '\0';
	if (*buffer)
		malloc_strcpy((char **)&(output[line++]),buffer);

	recursion--;
	new_free(&output[line]);
	new_free(&cont_free);
	new_free(&str_free);
	new_free(&buffer);
	*lused = line - 1;
	return output;
}

/*
 * An evil bastard child hack that pulls together the important parts of
 * prepare_display(), fix_string_width(), and output_with_count().
 *
 * This function is evil because it was spawned by copying the above 
 * functions and they were not refactored to make use of this function,
 * which they would do if I were not doing this in a rush.
 *
 * If you change the above three functions, you would do well to make sure to 
 * adjust this function, for if you do not, then HIGGLEDY PIGGLEDY WILL ENSUE.
 *
 * XXX - This is a bletcherous inelegant hack and i hate it.
 *
 * This function is used for:
 *	1. Toplines
 *	2. Continuation line markers
 */
char *prepare_display_fixed_size (const char *orig_str, int max_cols, int allow_truncate, char fillchar, int denormalize)
{
	int 	pos = 0,            /* Current position in "buffer" */
		col = 0,            /* Current column in display    */
		my_newline = 0;        /* Number of newlines           */
	char 	*str = NULL;
	char	*retval = NULL;
const 	char	*ptr;
	char 	buffer[BIG_BUFFER_SIZE + 1];
	char *	real_retval;
	int	codepoint;
	char 	utf8str[16];
	char *	x;
	int	cols;
	ptrdiff_t	offset;

        str = new_normalize_string((const char *)orig_str, 3, NORMALIZE);
	buffer[0] = 0;

	/*
	 * Start walking through the entire string.
	 */
	for (ptr = (const char *)str; *ptr && (pos < BIG_BUFFER_SIZE - 8); )
	{
	    codepoint = next_code_point2(ptr, &offset, 1);
	    ptr += offset;

	    switch (codepoint)
	    {
		case 7:		/* Bell */
			buffer[pos++] = '\007';
			break;

		case 10:	/* Forced newline */
			my_newline = 1;
			break;

		/* Attribute changes -- copy them unmodified. */
		case 6:
		{
			size_t 	numbytes = 0;
			buffer[pos++] = '\006';
			copy_internal_attribute(ptr, buffer + pos, BIG_BUFFER_SIZE - pos, &numbytes);
			pos += numbytes;
			ptr += numbytes;
			continue;          /* Skip the column check */
		}

		/* Any other printable character */
		default:
		{
			/* How many columns does this codepoint take? */
			cols = codepoint_numcolumns(codepoint);
			if (cols == -1)
				cols = 0;
			col += cols;

			ucs_to_utf8(codepoint, utf8str, sizeof(utf8str));
			for (x = utf8str; *x; x++)
				buffer[pos++] = *x;

			break;
		}
	    } /* End of switch (*ptr) */

	    /*
	     * Must check for cols >= maxcols+1 becuase we can have a 
	     * character on the extreme screen edge, and we would still 
	     * want to treat this exactly as 1 line, and col has already 
	     * been incremented.
	     */
	    /* I'm not sure about this >= here... it's always been > */
	    if ((allow_truncate && col >= max_cols) || my_newline)
			break;
	}
	buffer[pos] = 0;
	malloc_strcpy((char **)&retval, buffer);

	/*
	 * If we get here, either we have slurped up 'max_cols' cols, or
	 * we hit a newline, or the string was too short.
	 */
	if (col < max_cols && fillchar != '\0')
	{
		char fillstr[2];
		fillstr[0] = fillchar;
		fillstr[1] = 0;

		/* XXX One col per byte assumption! */
		while (col++ < max_cols)
			malloc_strcat((char **)&retval, fillstr);
	}

	if (denormalize)
		real_retval = denormalize_string(retval);
	else
		real_retval = retval, retval = NULL;

	new_free(&retval);
	new_free(&str);
	return real_retval;
}

/*
 * rite: This is the primary display wrapper to the 'output_with_count'
 * function.  This function is called whenever a line of text is to be
 * displayed to an irc window.  It is assumed that the cursor has been
 * placed in the appropriate position before this function is called.
 *
 * This function will "scroll" the target window.  Note that "scrolling"
 * is both a logical and physical act.  The window needs to be told that
 * a line is going to be output, and so it needs to be able to adjust its
 * top_of_display pointer; the hardware terminal also needs to be scrolled
 * so that there is room to put the new text.  scroll_window() handles both
 * of these tasks for us.
 *
 * output_with_count() actually calls putchar_x() for each character in
 * the string, doing the physical output.  It also emits any attribute
 * markers that are in the string.  It does do a clear-to-line, but it does
 * NOT move the cursor away from the end of the line.  We do that after it
 * has returned.
 *
 * This function is used by both irciiwin_display, and irciiwin_repaint.
 * Dont ever 'fold' it in anywhere.
 *
 * The arguments:
 *	window		- The target window for the output
 *	str		- What is to be outputted
 */
static int 	rite (int window_, const char *str)
{
	output_screen = get_window_screennum(window_);
	scroll_window(window_);

	if (get_window_screennum(window_) >= 0 && get_window_display_lines(window_))
		output_with_count(str, 1, foreground);

	set_window_cursor_incr(window_);
	return 0;
}

/*
 * output_with_count - Actually output to the terminal! (or do a dry run)
 *
 * Arguments:
 *	str1	 - A string in "internal attribute format".
 *		     You probably got it from prepare_display(),
 *		     prepare_display_fixed_size() or new_normalize_string()
 *	cleareol - Clear the line before outputting 
 *	output	 - 0 = this is a dry run  1 = actually output it!
 *
 * Return value:
 *	The number of columns that 'str1' would take up on the screen
 *
 * Errors:
 *	This function always succeeds
 *
 * Side effects:
 *	- When not a dry run (when output == 0)
 *	  - If 'str1' contains a beep, then it will beep
 *	  - If 'clreol' == 1, then it will clear the line on the screen
 *	    - This means you need to have already positioned the cursor,
 *	      eg, by having called scroll_window() or term_move_cursor().
 *
 * Unhappy things:
 *	"outputting" and "counting" are usually mutually exclusive.
 *	If you're outputting, you don't care about the count,
 *	and if you're counting, you're never wanting to output.
 *
 *	And besides, prepare_display() and prepare_display_fixed_size() ALSO 
 * 	do column counting...
 *	Is this really the best way to handle that?
 *
 *	OUTPUTTERS:
 *	 - rite() for normal window display (see above)
 *	 - repaint_window_body() for screen redraws (^L)
 *	 - the input line
 *	 - the status bar
 *	COUNTERS:
 *	 - TBD XXX
 *
 */
size_t 	output_with_count (const char *str1, int clreol, int output)
{
	int 		beep = 0;
	size_t		out = 0;
	Attribute	a;
	const char *	str;
	int		codepoint;
	char		utf8str[16];
	char *		x;
	int		cols;
	ptrdiff_t	offset;

	zero_attribute(&a);

	/* This flag is used by the input line and status bars. */
	if (output && clreol)
		term_clear_to_eol();

	for (str = (const char *)str1; str && *str; )
	{
	    /* On any error, just stop. */
	    if ((codepoint = next_code_point2(str, &offset, 1)) == -1)
		break;

	    str += offset;
	    switch (codepoint)
	    {
		/* Attribute marker */
		case 6:
		{
			size_t	numbytes = 0;
			if (read_internal_attribute(str, &a, &numbytes))
				break;
			if (output)
				term_attribute(&a);
			str += numbytes;
			break;
		}

		/* Terminal beep */
		case 7:
		{
			beep++;
			break;
		}

		/* Non-destructive space */
		case ND_SPACE:
		{
			if (output)
				term_cursor_right();
			out++;		/* Ooops */
			break;
		}

		/* Any other printable character */
		default:
		{
			/* C1 chars will be ignored unless you want them */
			if (codepoint >= 0x80 && codepoint < 0xA0)
			{
				if (!get_int_var(ALLOW_C1_CHARS_VAR))
					break;
			}

			/* How many columns does this codepoint take? */
			cols = codepoint_numcolumns(codepoint);
			if (cols < 0)
				cols = 0;
			out += cols;

			/*
			 * Note that 'putchar_x()' is safe here because 
			 * normalize_string() has already removed all of the 
			 * nasty stuff that could end up getting here.  And
			 * for those things that are nasty that get here, its 
			 * probably because the user specifically asked for it.
			 */
			if (output)
			{
				ucs_to_console(codepoint, utf8str, sizeof(utf8str));
				for (x = utf8str; *x; x++)
					putchar_x(*x);
			}

			break;
		}
	    }
	}

	if (output)
	{
		if (beep)
			term_beep();
		term_all_off();		/* Clean up after ourselves! */
	}

	return out;
}


/*
 * add_to_screen: This adds the given null terminated buffer to the screen.
 * That is, it routes the line to the appropriate window.  It also handles
 * /redirect handling.
 */
void 	add_to_screen (const char *buffer)
{
	int	window;
	int	w = 0;

	/*
	 * Just paranoia.
	 */
	if (!window_is_valid(0))
	{
		puts(buffer);
		return;
	}

	if (dumb_mode)
	{
		add_to_lastlog(get_window_refnum(0), buffer);
		if (privileged_output || 
		    do_hook(WINDOW_LIST, "%u %s", get_window_user_refnum(0), buffer))
			puts(buffer);
		fflush(stdout);
		return;
	}

	/* All windows MUST be "current" before output can occur */
	update_all_windows();

	/*
	 * The highest priority is if we have explicitly stated what
	 * window we want this output to go to.
	 */
	if ((window = get_to_window()) > 0)
	{
		if (window_is_valid(window))
		{
			add_to_window(window, buffer);
			return;
		}
	}

	/*
	 * The next priority is "LEVEL_NONE" which is only ever
	 * used by the /WINDOW command, but I'm not even sure it's very
	 * useful.  Maybe I'll think about this again later.
	 */
	else if ((get_who_level() == LEVEL_NONE) && 
	        ((window = get_server_current_window(from_server)) > -1) && 
                (window_is_valid(window)))
	{
		add_to_window(window, buffer);
		return;
	}

	/*
	 * Next priority is if the output is targeted at a certain
	 * user or channel (used for /window channel or /window add targets)
	 */
	else if (get_who_from())
	{
	    if (is_channel(get_who_from()))
	    {
		int	w;

		if (from_server == NOSERV)
		    panic(0, "Output to channel [%s:NOSERV]: %s", get_who_from(), buffer);

		w = get_channel_window(get_who_from(), from_server);
	        if (window_is_valid(w))
		{
		    add_to_window(w, buffer);
		    return;
		}
	    }
	    else
	    {
		w = 0;
		while (traverse_all_windows2(&w))
		{
		    /* Must be for our server */
		    if (get_who_level() != LEVEL_DCC && (get_window_server(w) != from_server))
			continue;

		    /* Must be on the nick list */
		    if (!find_in_list(get_window_nicks(w), get_who_from()))
			continue;

		    add_to_window(w, buffer);
		    return;
		}
	    }
	}

	/*
	 * Check to see if this level should go to current window
	 */
	if ((mask_isset(&current_window_mask, get_who_level())) &&
	    ((window = get_server_current_window(from_server)) > -1) && 
            (window_is_valid(window)))
	{
		add_to_window(window, buffer);
		return;
	}

	/*
	 * Check to see if any window can claim this level
	 */
	w = 0;
	while (traverse_all_windows2(&w))
	{
		Mask	window_mask;
		get_window_mask(w, &window_mask);

		/*
		 * Check for /WINDOW LEVELs that apply
		 */
		if (get_who_level() == LEVEL_DCC && 
			mask_isset(&window_mask, get_who_level()))
		{
			add_to_window(w, buffer);
			return;
		}
		if ((from_server == get_window_server(w) || from_server == NOSERV)
			&& mask_isset(&window_mask, get_who_level()))
		{
			add_to_window(w, buffer);
			return;
		}
	}

	/*
	 * If all else fails, if the current window is connected to the
	 * given server, use the current window.
	 */
	if (get_window_server(0) == from_server)
	{
		add_to_window(0, buffer);
		return;
	}

	/*
	 * And if that fails, look for ANY window that is bound to the
	 * given server (this never fails if we're connected.)
	 */
	w = 0;
	while (traverse_all_windows2(&w))
	{
		if (get_window_server(w) != from_server)
			continue;

		add_to_window(w, buffer);
		return;
	}

	/*
	 * No window found for a server is usually because we're
	 * disconnected or not yet connected.
	 */
	add_to_window(0, buffer);
	return;
}

/*
 * add_to_window: Given a window and a line to display, this handles all
 * of the window-level stuff like the logfile, the lastlog, splitting
 * the line up into rows, adding it to the display (scrollback) buffer, and
 * if we're invisible and the user wants notification, we handle that too.
 *
 * add_to_display_list() handles the *composure* of the buffer that backs the
 * screen, handling HOLD_MODE, trimming the scrollback buffer if it gets too
 * big, scrolling the window and moving the top_of_window pointer as neccesary.
 * It also tells us if we should display to the screen or not.
 *
 * rite() handles the *appearance* of the display, writing to the screen as
 * neccesary.
 */
static void 	add_to_window (int window_, const char *str)
{
	const char *	pend;
	char *		strval;
	char *		free_me = NULL;
        char **       	my_lines;
        int             cols;
	int		numl = 0;
	intmax_t	refnum;
	const char *	rewriter = NULL;
	int		mangler = 0;

	if (get_server_redirect(get_window_server(window_)))
		if (redirect_text(get_window_server(window_),
			          get_server_redirect(get_window_server(window_)),
				  str, NULL, 1))
			return;

	if (!privileged_output)
	{
	   static int recursion = 0;

	   if (!do_hook(WINDOW_LIST, "%u %s", get_window_user_refnum(window_), str))
		return;

	   /* 
	    * If output rewriting causes more output, (such as a stray error
	    * message) allow a few levels of nesting [just to be kind], but 
	    * cut the recursion off at its knees at 5 levels.  This is an 
	    * entirely arbitrary value.  Change it if you wish.
	    * (Recursion detection by larne in epic4-2.1.3)
	    */
	    recursion++;
	    if (recursion < 5 && (pend = get_string_var(OUTPUT_REWRITE_VAR)))
	    {
#if 0
		char	argstuff[102400];

		/* Create $* and then expand with it */
		snprintf(argstuff, 102400, "%u %s", get_window_user_refnum(window_), str);
#else
		char *argstuff = NULL;
		malloc_sprintf(&argstuff, "%u %s", get_window_user_refnum(window_), str);
#endif

		str = free_me = expand_alias(pend, argstuff);
#if 1
		new_free(&argstuff);
#endif
	    }
	    recursion--;
	}

	/* Add to logs + lastlog... */
	if (get_window_log_rewrite(window_))
		rewriter = get_window_log_rewrite(window_);
	if (get_window_log_mangle(window_))
		mangler = get_window_log_mangle(window_);
	add_to_log(0, get_window_log_fp(window_), window_, str, mangler, rewriter);
	add_to_logs(window_, from_server, get_who_from(), get_who_level(), str);
	refnum = add_to_lastlog(window_, str);

	/* Add to scrollback + display... */
	cols = get_window_my_columns(window_);	/* Don't -1 this! already -1'd */
	strval = new_normalize_string((const char *)str, 0, display_line_mangler);
        for (my_lines = prepare_display(window_, strval, cols, &numl, 0); *my_lines; my_lines++)
	{
		if (add_to_scrollback(window_, *my_lines, refnum))
		    if (ok_to_output(window_))
			rite(window_, *my_lines);
	}
	new_free(&strval);

	/* Check the status of the window and scrollback */
	trim_scrollback(window_);

	cursor_to_input();

	/*
	 * Handle special cases for output to hidden windows -- A beep to
	 * a hidden window with /window beep_always on results in a real beep 
	 * and a message to the current window.  Output to a hidden window 
	 * with /window notify on results in a message to the current window 
	 * and a status bar redraw.
	 *
	 * /XECHO -F sets "do_window_notifies" which overrules this.
	 */
	if ((get_window_screennum(window_) < 0) && do_window_notifies)
	{
	    const char *type = NULL;

            /* /WINDOW BEEP_ALWAYS added for archon.  */
	    if (get_window_beep_always(window_) && strchr(str, '\007'))
	    {
		type = "Beep";
		term_beep();
	    }
	    if (!(get_window_notified(window_)) &&
			mask_isset(get_window_notify_mask(window_), get_who_level()))
	    {
		set_window_notified(window_, 1);
	    	do_hook(WINDOW_NOTIFIED_LIST, "%u %s", get_window_user_refnum(window_), level_to_str(get_who_level()));
		if (get_window_notify_when_hidden(window_))
			type = "Activity";
		update_all_status();
	    }

	    if (type)
	    {
		int l = message_setall(get_window_refnum(0), get_who_from(), get_who_level());
		say("%s in window %d", type, get_window_user_refnum(window_));
		pop_message_from(l);
	    }
	}
	if (free_me)
		new_free(&free_me);
}

/*
 * add_to_window_scrollback: XXX -- doesn't belong here. oh well.
 * This unifies the important parts of add_to_window and window_disp
 * for the purpose of reconstituting the scrollback of a window after
 * a resize event.
 */
void 	add_to_window_scrollback (int window, const char *str, intmax_t refnum)
{
	char *	strval;
        char **       my_lines;
        int             cols;
	int		numl = 0;

	/* Normalize the line of output */
	cols = get_window_my_columns(window);			/* Don't -1 this! Already -1'd! */
	strval = new_normalize_string((const char *)str, 0, display_line_mangler);
        for (my_lines = prepare_display(window, strval, cols, &numl, 0); *my_lines; my_lines++)
		add_to_scrollback(window, *my_lines, refnum);
	new_free(&strval);
}

/*
 * This returns 1 if the window does not need to scroll for new output.
 * This returns 0 if the window does need to scroll for new output.
 *
 * This call should be used to guard calls to rite(), because rite() will
 * call scroll_window() if the window is full.  Scroll_window() will panic 
 * if the window is not using the "scrolling" view.  Therefore, this function
 * differentiates between a window that is full because it is in hold mode or
 * scrollback, and a window that is full and can be scrolled.
 */
static int	ok_to_output (int window_)
{
	/*
	 * Output is ok as long as the three top of displays all are 
	 * within a screenful of the insertion point!
	 */
	if (get_window_scrollback_top_of_display_exists(window_))
	{
	    if (get_window_scrollback_distance_from_display_ip(window_) > get_window_display_lines(window_))
		return 0;	/* Definitely no output here */
	}

	if (get_window_hold_mode(window_))
	{
	    if (get_window_holding_distance_from_display_ip(window_) > get_window_display_lines(window_))
		return 0;	/* Definitely no output here */
	}

	return 1;		/* Output is authorized */
}

/*
 * scroll_window: Given a window, this is responsible for making sure that
 * the cursor is placed onto the "next" line.  If the window is full, then
 * it will scroll the window as neccesary.  The cursor is always set to the
 * correct place when this returns.
 *
 * This is only ever (to be) called by rite(), and you must always call
 * ok_to_output() before you call rite().  If you do not call ok_to_output(),
 * this function will panic if the window needs to be scrolled.
 */
static void 	scroll_window (int window_)
{
	if (dumb_mode)
		return;

	if (get_window_cursor(window_) > get_window_display_lines(window_))
		panic(1, "Window [%d]'s cursor [%d] is off the display [%d]",
			get_window_user_refnum(window_), 
			get_window_cursor(window_),
			get_window_display_lines(window_));

	/*
	 * If the cursor is beyond the window then we should probably
	 * look into scrolling.
	 */
	if (get_window_cursor(window_) == get_window_display_lines(window_))
	{
		int scroll;

		/*
		 * If we ever need to scroll a window that is in scrollback
		 * or in hold_mode, then that means either display_window isnt
		 * doing its job or something else is completely broken.
		 * Probably shouldnt be fatal, but i want to trap these.
		 */
		if (get_window_holding_distance_from_display_ip(window_) > 
						get_window_display_lines(window_))
			panic(1, "Can't output to window [%d] "
				"because it is holding stuff: [%d] [%d]", 
				get_window_user_refnum(window_), 
				get_window_holding_distance_from_display_ip(window_), 
				get_window_display_lines(window_));

		if (get_window_scrollback_distance_from_display_ip(window_) > 
						get_window_display_lines(window_))
			panic(1, "Can't output to window [%d] "
				"because it is scrolling back: [%d] [%d]", 
				get_window_user_refnum(window_), 
				get_window_scrollback_distance_from_display_ip(window_), 
				get_window_display_lines(window_));

		/* Scroll by no less than 1 line */
		if ((scroll = get_window_scroll_lines(window_)) <= 0)
		    if ((scroll = get_int_var(SCROLL_LINES_VAR)) <= 0)
			scroll = 1;
		if (scroll > get_window_display_lines(window_))
			scroll = get_window_display_lines(window_);

		/* Adjust the top of the physical display */
		if (get_window_screennum(window_) >= 0 && foreground && get_window_display_lines(window_))
		{
			term_scroll(get_window_top(window_),
				get_window_top(window_) + get_window_cursor(window_) - 1, 
				scroll);
		}

		/* Adjust the cursor */
		set_window_cursor(window_, get_window_cursor(window_) - scroll);
	}

	/*
	 * Move to the new line and wipe it
	 */
	if (get_window_screennum(window_) >= 0 && get_window_display_lines(window_))
	{
		term_move_cursor(0, get_window_top(window_) + get_window_cursor(window_));
		term_clear_to_eol();
	}
}

/* * * * * * * SCREEN UDPATING AND RESIZING * * * * * * * * */
/*
 * repaint_window_body: redraw the entire window's scrollable region
 * The old logic for doing a partial repaint has been removed with prejudice.
 */
void 	repaint_window_body (int window_)
{
	Display *curr_line;
	int 	count;

	if (!window_is_valid(window_))
		return;

	if (dumb_mode || get_window_screennum(window_) < 0)
		return;

	global_beep_ok = 0;		/* Suppress beeps */

	if (get_window_scrollback_distance_from_display_ip(window_) > get_window_holding_distance_from_display_ip(window_))
	{
	    if (get_window_scrolling_distance_from_display_ip(window_) >= get_window_scrollback_distance_from_display_ip(window_))
		curr_line = get_window_scrolling_top_of_display(window_);
	    else
		curr_line = get_window_scrollback_top_of_display(window_);
	}
	else
	{
	    if (get_window_scrolling_distance_from_display_ip(window_) >= get_window_holding_distance_from_display_ip(window_))
		curr_line = get_window_scrolling_top_of_display(window_);
	    else
		curr_line = get_window_holding_top_of_display(window_);
	}

	if (get_window_screennum(window_) >= 0 && get_window_toplines_showing(window_))
	{
	    for (count = 0; count < get_window_toplines_showing(window_); count++)
	    {
		char 	*widthstr;
		const char *str;

		if (!(str = get_window_topline(window_, count)))
			str = empty_string;

		term_move_cursor(0, get_window_top(window_) - get_window_toplines_showing(window_) + count);
		term_clear_to_eol();

		/* Don't -1 get_window_by_columns()! */
		widthstr = prepare_display_fixed_size(str, get_window_my_columns(window_), 1, ' ', 0);
		output_with_count(widthstr, 1, foreground);
		new_free(&widthstr);
	   }
	}

	set_window_cursor(window_, 0);
	for (count = 0; count < get_window_display_lines(window_); count++)
	{
		rite(get_window_refnum(window_), curr_line->line);

		/*
		 * Clean off the rest of this window.
		 */
		if (curr_line == get_window_display_ip(window_))
		{
			const char *x;

			x = get_string_var(BLANK_LINE_INDICATOR_VAR);

			set_window_cursor_decr(window_);		/* Bumped by rite */
			for (; count < get_window_display_lines(window_); count++)
			{
				term_clear_to_eol();
				if (x)
					putchar_x(x[0]);
				term_newline();
			}
			break;
		}

		curr_line = curr_line->next;
	}

	global_beep_ok = 1;		/* Suppress beeps */
}


/* * * * * * * * * * * * * * SCREEN MANAGEMENT * * * * * * * * * * * * */
/*
 * create_new_screen creates a new screen structure. with the help of
 * this structure we maintain ircII windows that cross screen window
 * boundaries.
 *
 * The new screen is stored in "last_input_screen"!
 */
void	create_new_screen (void)
{
	Screen	*new_s = NULL, *list;
static	int	refnumber = 0;
	int	i;

	for (list = screen_list; list; list = list->next)
	{
		if (!list->alive)
		{
			new_s = list;
			break;
		}

		if (!list->next)
			break;		/* XXXX */
	}

	if (!new_s)
	{
		new_s = (Screen *)new_malloc(sizeof(Screen));
		new_s->screennum = ++refnumber;
		new_s->next = NULL;
		if (list)
			list->next = new_s;
		else
			screen_list = new_s;
	}

	new_s->last_window_refnum = -1;
	new_s->_window_list = -1;
	new_s->input_window = -1;
	new_s->visible_windows = 0;
	new_s->window_stack = NULL;
	new_s->last_press.tv_sec = new_s->last_press.tv_usec  = 0;
	new_s->last_key = NULL;
	new_s->quote_hit = 0;
	new_s->fdout = 1;
	new_s->fpout = stdout;
	new_s->fdin = 0;
	if (use_input)
		new_open(0, do_screens, NEWIO_READ, 0, -1);
	new_s->fpin = stdin;
	new_s->control = -1;
	new_s->wserv_version = 0;
	new_s->alive = 1;
	new_s->promptlist = NULL;
	new_s->li = current_term->TI_lines;
	new_s->co = current_term->TI_cols;
	new_s->old_li = 0; 
	new_s->old_co = 0;

	new_s->il = new_input_line(NULL, 1);

	for (i = 0 ; i <= MAX_WINDOWS_ON_SCREEN; i++)
		new_s->_windows[i].window = -1;

	last_input_screen = new_s->screennum;

	if (main_screen < 0)
		main_screen = new_s->screennum;

	init_input();
}


#define ST_NOTHING      -1
#define ST_SCREEN       0
#define ST_XTERM        1
#define ST_TMUX		2
int	create_additional_screen (void)
{
	int		oldscreen, new_s;
        int     	screen_type = ST_NOTHING;
	SSu		local_sockaddr;
        SSu		new_socket;
	int		new_cmd;
	pid_t		child;
	unsigned short 	port;
	socklen_t	new_sock_size;
	const char *	wserv_path;

	char 		subcmd[128];
	char *		opts;
	const char *	xterm;
	char *		args[64];
	char **		args_ptr = args;
	char 		geom[32];
	int 		i;


	/* Don't "move" this down! It belongs here. */
	oldscreen = get_window_screennum(0);

	if (!use_input)
		return -1;

	if (!(wserv_path = get_string_var(WSERV_PATH_VAR)))
	{
		say("You need to /SET WSERV_PATH before using /WINDOW CREATE");
		return -1;
	}

	/*
	 * 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 (getenv("STY") && getenv("DISPLAY"))
	{
		const char *p = get_string_var(WSERV_TYPE_VAR);
		if (p && !my_stricmp(p, "SCREEN"))
			screen_type = ST_SCREEN;
		else if (p && !my_stricmp(p, "TMUX"))
			screen_type = ST_SCREEN;
		else if (p && !my_stricmp(p, "XTERM"))
			screen_type = ST_XTERM;
		else
			screen_type = ST_SCREEN;	/* Sucks to be you */
	}
	else if (getenv("TMUX") && getenv("DISPLAY"))
	{
		const char *p = get_string_var(WSERV_TYPE_VAR);
		if (p && !my_stricmp(p, "SCREEN"))
			screen_type = ST_TMUX;
		else if (p && !my_stricmp(p, "TMUX"))
			screen_type = ST_TMUX;
		else if (p && !my_stricmp(p, "XTERM"))
			screen_type = ST_XTERM;
		else
			screen_type = ST_TMUX;	/* Sucks to be you */
	}
	else if (getenv("TMUX"))
		screen_type = ST_TMUX;
	else if (getenv("STY"))
		screen_type = ST_SCREEN;
	else if (getenv("DISPLAY") && getenv("TERM"))
		screen_type = ST_XTERM;
	else
	{
		say("I don't know how to create new windows for this terminal");
		return -1;
	}

	if (screen_type == ST_SCREEN)
		say("Opening new screen...");
	else if (screen_type == ST_TMUX)
		say("Opening new tmux...");
	else if (screen_type == ST_XTERM)
		say("Opening new window...");
	else
		panic(1, "Opening new wound");

	local_sockaddr.si.sin_family = AF_INET;
#ifndef INADDR_LOOPBACK
#define INADDR_LOOPBACK 0x7f000001
#endif
	local_sockaddr.si.sin_addr.s_addr = htonl((INADDR_ANY));
	local_sockaddr.si.sin_port = 0;

	if ((new_cmd = client_bind(&local_sockaddr, sizeof(local_sockaddr.si))) < 0)
	{
		yell("Couldn't establish server side of new screen");
		return -1;
	}
	port = ntohs(local_sockaddr.si.sin_port);

	/* Create the command line arguments... */
	if (screen_type == ST_SCREEN)
	{
	    opts = malloc_strdup(get_string_var(SCREEN_OPTIONS_VAR));
	    *args_ptr++ = malloc_strdup("screen");
	    while (opts && *opts)
		*args_ptr++ = malloc_strdup(new_next_arg(opts, &opts));
	    *args_ptr++ = malloc_strdup(wserv_path);
	    *args_ptr++ = malloc_strdup("localhost");
	    *args_ptr++ = malloc_strdup(ltoa((long)port));
	    *args_ptr++ = NULL;
	}
	else if (screen_type == ST_XTERM)
	{
	    snprintf(geom, 31, "%dx%d", 
		get_screen_columns(oldscreen) + 1, 
		get_screen_lines(oldscreen));

	    opts = malloc_strdup(get_string_var(XTERM_OPTIONS_VAR));
	    if (!(xterm = getenv("XTERM")))
		if (!(xterm = get_string_var(XTERM_VAR)))
		    xterm = "xterm";

	    *args_ptr++ = malloc_strdup(xterm);
	    *args_ptr++ = malloc_strdup("-geometry");
	    *args_ptr++ = malloc_strdup(geom);
	    while (opts && *opts)
		*args_ptr++ = malloc_strdup(new_next_arg(opts, &opts));
	    *args_ptr++ = malloc_strdup("-e");
	    *args_ptr++ = malloc_strdup(wserv_path);
	    *args_ptr++ = malloc_strdup("localhost");
	    *args_ptr++ = malloc_strdup(ltoa((long)port));
	    *args_ptr++ = NULL;
	}
	else if (screen_type == ST_TMUX)
	{
	    snprintf(subcmd, 127, "%s %s %hu", 
			wserv_path, "localhost", port);

	    *args_ptr++ = malloc_strdup("tmux");

	    opts = malloc_strdup(get_string_var(TMUX_OPTIONS_VAR));
	    while (opts && *opts)
		*args_ptr++ = malloc_strdup(new_next_arg(opts, &opts));

	    *args_ptr++ = malloc_strdup("new-window");
	    *args_ptr++ = malloc_strdup(subcmd);
	    *args_ptr++ = NULL;
	}

	/* Now create a new screen */
	create_new_screen();
	new_s = last_input_screen;

	/*
	 * At this point, doing a say() or yell() or anything else that would
	 * output to the screen will cause a refresh of the status bar and
	 * input line.  new_s->input_window is -1 after the above line,
	 * so any attempt to reference $C or $T will be to NULL pointers,
	 * which will cause a crash.  For various reasons, we can't fire up
	 * a new window this early, so its just easier to make sure we don't
	 * output anything until kill_screen() or new_window() is called first.
	 * You have been warned!
	 */
	switch ((child = fork()))
	{
		case -1:
		{
			kill_screen(new_s);
			say("I couldnt fire up a new wserv process");
			break;
		}

		case 0:
		{
			if (setgid(getgid()))
				_exit(0);
			if (setuid(getuid()))
				_exit(0);
			setsid();

			/*
			 * Make sure that no inhereted file descriptors
			 * are left over past the exec.  xterm will reopen
			 * any fd's that it is interested in.
			 * (Start at three sb kanan).
			 */
			for (i = 3; i < 256; i++)
				close(i);

			/*
			 * Try to restore some sanity to the signal
			 * handlers, since theyre not really appropriate here
			 */
			my_signal(SIGINT,  SIG_IGN);
			my_signal(SIGSEGV, SIG_DFL);
			my_signal(SIGBUS,  SIG_DFL);
			my_signal(SIGABRT, SIG_DFL);

			execvp(args[0], args);
			_exit(0);
		}
	}

	/* All the rest of this is the parent.... */
	new_sock_size = sizeof(new_socket.ss);

	/* 
	 * This infinite loop sb kanan to allow us to trap transitory
	 * error signals
	 */
	for (;;)

	/* 
	 * You need to kill_screen(new_s) before you do say() or yell()
	 * if you know what is good for you...
	 */
	switch (my_isreadable(new_cmd, 10))
	{
	    case -1:
	    {
		if ((errno == EINTR) || (errno == EAGAIN))
			continue;
		FALLTHROUGH
		/* FALLTHROUGH */
	    }
	    case 0:
	    {
		int 	old_errno = errno;
		int 	errnod = get_child_exit(child);

		close(new_cmd);
		kill_screen(new_s);
		kill(child, SIGKILL);
		if (get_screen_fdin(new_s) != 0)
		{
			say("The wserv only connected once -- it's probably "
			    "an old, incompatable version.");
		}

                yell("child %s with %d", (errnod < 1) ? "signaled" : "exited",
                                         (errnod < 1) ? -errnod : errnod);
		yell("Errno is %d", old_errno);
		return -1;
	    }
	    default:
	    {
		if (get_screen_fdin(new_s) == 0) 
		{
			int	fd;	/* Hurt me harder, clang... */
			FILE *	fin, *fout;

			if ((fd = accept(new_cmd, &new_socket.sa, &new_sock_size)) < 0)
			{
				close(new_cmd);
				kill_screen(new_s);
				yell("Couldn't establish data connection to new screen");
				return -1;
			}

			set_screen_fdin(new_s, fd);
			set_screen_fdout(new_s, fd);
			new_open(fd, do_screens, NEWIO_RECV, 1, -1);

			if (!(fin = fdopen(fd, "r+")))
			{
				close(new_cmd);
				kill_screen(new_s);
				yell("Couldn't establish data connection to new screen");
				return -1;
			}
			if (!(fout = fdopen(fd, "r+")))
			{
				fclose(fin);
				close(new_cmd);
				kill_screen(new_s);
				yell("Couldn't establish data connection to new screen");
				return -1;
			}
			set_screen_fpin(new_s, fin);
			set_screen_fpout(new_s, fout);
			continue;
		}
		else
		{
			int	refnum;

			set_screen_control(new_s, accept(new_cmd, &new_socket.sa, &new_sock_size));
			close(new_cmd);

			if (get_screen_control(new_s) < 0)
			{
                                kill_screen(new_s);
                                yell("Couldn't establish control connection to new screen");
                                return -1;
                        }

			new_open(get_screen_control(new_s), do_screens, NEWIO_RECV, 1, -1);

                        if ((refnum = new_window(new_s)) < 1)
                                panic(1, "WINDOW is NULL and it shouldnt be!");

                        return refnum;
		}
	    }
	}
	close(new_cmd);		/* Oh, shut up clang */
	return -1;
}

/* Old screens never die. They just fade away. */
void 	kill_screen (int screen_)
{
	int	window;
	Screen *screen = get_screen_by_refnum(screen_);

	if (!screen)
	{
		say("You may not kill the hidden screen.");
		return;
	}
	if (main_screen == screen_)
	{
		say("You may not kill the main screen");
		return;
	}
	if (screen->fdin)
	{
		if (use_input)
			screen->fdin = new_close(screen->fdin);
		close(screen->fdout);
		close(screen->fdin);
	}
	if (screen->control)
		screen->control = new_close(screen->control);
	while ((window = screen->_window_list) != -1)
	{
		screen->_window_list = get_window_next(window);
		add_to_invisible_list(window);
	}

	/* Take out some of the garbage left around */
	screen->input_window = -1;
	screen->_window_list = -1;
	screen->last_window_refnum = -1;
	screen->visible_windows = 0;
	screen->window_stack = NULL;
	if (screen->fpin)
	{
		fclose(screen->fpin);
		screen->fpin = NULL;
	}
	if (screen->fpout)
	{
		fclose(screen->fpout);
		screen->fpout = NULL;
	}
	screen->fdin = -1;
	screen->fdout = -1;

	destroy_input_line(screen->il);
	screen->il = NULL;

	/* Dont fool around. */
	if (last_input_screen == screen->screennum)
		last_input_screen = main_screen;

	screen->alive = 0;
	make_window_current_by_refnum(0);
	say("The screen is now dead.");
}

int	get_screen_bottom_window (int screen_)
{
	Screen *screen;
	int	refnum;

	if (!(screen = get_screen_by_refnum(screen_)))
		panic(1, "get_screen_bottom_window: screen %d is hidden", screen_);
	if (screen->_window_list == -1)
		panic(1, "get_screen_bottom_window: screen %d has no windows?", screen->screennum);

	for (refnum = screen->_window_list; get_window_next(refnum) != -1; refnum = get_window_next(refnum))
		;
	return refnum;
}


/* * * * * * * * * * * * * USER INPUT HANDLER * * * * * * * * * * * */
/*
 * do_screens - A NewIO callback to handle input from "a screen" 
 *		(ie, stdin, or a wserv)
 *	fd - the file descriptor that is Ready
 *
 * Process:
 *  1. Identify the screen that 'fd' belongs to.
 *  2. If this is the control fd for a screen, process the control message
 *  3. If this is the data fd for a screen,
 *	a. Establish the context for input (screen/window/server)
 *	b. Read all the bytes
 *	c. Publish the bytes one at a time through edit_char() which will 
 *	   assemble them into codepoints (using iconv) and inject them.
 */
static void 	do_screens (int fd)
{
	Screen *screen;
	char 	buffer[IO_BUFFER_SIZE + 1];
	int	saved_from_server;
	int	n, i;
	int	proto;

	saved_from_server = from_server;

	if (use_input)
	for (screen = screen_list; screen; screen = screen->next)
	{
		if (!screen->alive)
			continue;

		/*
		 * Handle input from a WSERV screen's control channel
		 */
		if (screen->control != -1 && screen->control == fd)
		{
		    if (dgets(screen->control, buffer, IO_BUFFER_SIZE, 1) < 0)
		    {
			kill_screen(screen->screennum);
			yell("Error from remote screen.");
			continue;
		    }

		    if (!strncmp(buffer, "tty=", 4))
			(void) 0;	/* We no longer use this info */
		    else if (!strncmp(buffer, "geom=", 5))
		    {
			char *ptr;
			if ((ptr = strchr(buffer, ' ')))
				*ptr++ = 0;
			screen->li = atoi(buffer + 5);
			screen->co = atoi(ptr);
			recalculate_windows(screen->screennum);
		    }
		    else if (!strncmp(buffer, "version=", 8))
		    {
			int     version;
			version = atoi(buffer + 8);
			if (version != CURRENT_WSERV_VERSION)
			{
			    yell("WSERV version %d is incompatable "
					"with this binary", version);
			    kill_screen(screen->screennum);
			}
			screen->wserv_version = version;
		    }
		}


		/*
		 * Handle user input from any screen's fdin channel
		 */
		if (screen->fdin != fd)
			continue;

		/*
		 * If normal data comes in, and the wserv didn't tell me
		 * what its version is, then throw it out of the system.
		 */
		if (screen->screennum != main_screen && screen->wserv_version == 0)
		{
			kill_screen(screen->screennum);
			yell("The WSERV used to create this new screen "
				"is too old.");
			return;		/* Bail out entirely */
		}

		/* Reset the idleness data for the user */
		get_time(&idle_time);
		if (cpu_saver)
			reset_system_timers();

		/* 
		 * Set the global processing context for the client to the 
		 * processing context of the screen's current input window.
		 */
		last_input_screen = screen->screennum;
		output_screen = screen->screennum;
		make_window_current_by_refnum(screen->input_window);
		from_server = get_window_server(0);

		/*
		 * PRIVMSG/NOTICE restrictions are suspended
		 * when you're typing something.
		 */
		if ((proto = get_server_protocol_state(from_server)) >= 0)
			set_server_protocol_state(from_server, 0);

		/*
		 * We create a 'stack' of current windows so whenever a 
		 * window is killed, we know which window was the current
		 * window immediately prior to it.
		 */
		set_window_priority(0, current_window_priority++);

		/* Dumb mode only accepts complete lines from the user */
		if (dumb_mode)
		{
			if (dgets(screen->fdin, buffer, IO_BUFFER_SIZE, 1) < 0)
			{
				say("IRCII exiting on EOF from stdin");
				irc_exit(1, "EPIC - EOF from stdin");
			}

			if (strlen(buffer))
				buffer[strlen(buffer) - 1] = 0;
			parse_statement(buffer, 1, NULL);
		}

		/* Ordinary full screen input is handled one byte at a time */
		else if ((n = dgets(screen->fdin, buffer, BIG_BUFFER_SIZE, -1)) > 0)
		{
			for (i = 0; i < n; i++)
				translate_user_input((unsigned char)buffer[i]);
		}

		/* An EOF/error error on a wserv screen kills that screen */
		else if (screen->screennum != main_screen)
			kill_screen(screen->screennum);

		/* An EOF/error on main screen kills the whole client. */
		else
			irc_exit(1, "Hey!  Where'd my controlling terminal go?");

		if (proto >= 0)
			set_server_protocol_state(from_server, proto);
	}

	from_server = saved_from_server;
} 

/*
 * Each byte received by user input goes through this function.
 * This function needs to decide what to do -- do we accumulate
 * a UTF8 string, convert it to UCS32, and send it to edit_char()?
 * or do we translate it based on /set translation and send that
 * to edit_char()?  Perhaps we have to apply /set'ings, etc.
 */
void	translate_user_input (unsigned char byte)
{
static	unsigned char	workbuf[32];
static	size_t		workbuf_idx = 0;
const	char *		s;
	char		dest_ptr[32] = { 0 };
	int		codepoint;
	char *		in;
	size_t		inlen;
	char *		out;
	size_t		outlen;
	iconv_t		xlat;
const 	char *		enc;
static	int		prev_char = -1;
static	int		suspicious = 0;
static	int		never_warn_again = 0;
	ptrdiff_t	offset;

	/*
	 * This is "impossible", but I'm just being safe.
	 */
	if (workbuf_idx > 30)
	{
		/* Whatever we have isn't useful. flush it. */
		workbuf_idx = 0;
		*workbuf = 0;
		prev_char = -1;
		suspicious = 0;
		return;
	}

	workbuf[workbuf_idx++] = byte;
	workbuf[workbuf_idx] = 0;

	in = (char *)workbuf;
	inlen = workbuf_idx;
	/* Must leave at least one \0 at the end of the buffer, as
	 * next_code_point() expects a nul-terminated string. */
	out = dest_ptr;
	outlen = sizeof dest_ptr - 1;

	enc = find_recoding("console", &xlat, NULL);

	/* Very crude, ad-hoc check for UTF8 type things */
	if (strcmp(enc, "UTF-8") &&  (prev_char != -1 && ((prev_char & 0x80U) == 0x80U)))
	{
	    if ((prev_char & 0xE0U) == 0xC0U)
	    {
		if ((byte & 0xC0U) == 0x80U)
		  suspicious++;
		else
		  suspicious = 0;
	    }
	    if ((prev_char & 0xF0U) == 0xE0U)
	    {
		if ((byte & 0xC0U) == 0x80U)
		  suspicious++;
		else
		  suspicious = 0;
	    }
	    if ((prev_char & 0xF8U) == 0xF0U)
	    {
		if ((byte & 0xC0U) == 0x80U)
		   suspicious++;
		else
		   suspicious = 0;
	    }
	    if (suspicious == 3 && never_warn_again == 0)
	    {
		yell("Your /ENCODING CONSOLE is %s but you seem to be typing UTF-8.", enc);
		yell("   Use %s/ENCODING CONSOLE UTF-8.%s if you really are using UTF-8", BOLD_TOG_STR, BOLD_TOG_STR);
		never_warn_again = 1;
	    }
	}
	prev_char = byte;

	/* Convert whatever the user is typing into UTF8 */
	if (iconv(xlat, &in, &inlen, &out, &outlen) != 0)
	{
		if (errno == EILSEQ)
		{
			yell("Your /ENCODING CONSOLE is %s which is wrong.", enc);
			yell("   Use %s/ENCODING CONSOLE ISO-8859-1.%s or whatever is correct for you", BOLD_TOG_STR, BOLD_TOG_STR);
			workbuf_idx = 0;
			workbuf[0] = 0;
			return;
		}
		else if  (errno == EINVAL)
		{
			return;		/* Not enough to convert yet */
		}
	}

	/* Now inject the converted (utf8) bytes into the input stream. */
	s = (const char *)dest_ptr;
	codepoint = next_code_point2(s, &offset, 1);
	if (codepoint > -1)
	{
		/* Clear the buffer BEFORE dispatching the results */
		workbuf_idx = 0;
		workbuf[0] = 0;
		edit_codepoint(codepoint);
	}
}

/*
 * This should be called once for each codepoint we receive.
 * This means a utf8 string converted into UCS32.  We don't support
 * that quite yet, so this is just a placeholder.
 *
 * edit_char: handles each character for an input stream.  Not too difficult
 * to work out.
 */
static	void	edit_codepoint (uint32_t key)
{
	int	old_quote_hit;

        if (dumb_mode)
                return;

        /*
         * This is only used by /pause to see when a keypress event occurs,
         * but not to impact how that keypress is handled at all.
         */
        if (get_screen_prompt_list(last_input_screen) &&
            get_screen_prompt_list(last_input_screen)->type == WAIT_PROMPT_DUMMY)
		fire_wait_prompt(key);

        /* were we waiting for a keypress? */
        if (get_screen_prompt_list(last_input_screen) && 
            get_screen_prompt_list(last_input_screen)->type == WAIT_PROMPT_KEY)
        {
		fire_wait_prompt(key);
		return;
        }

	/*
	 * New: handle_keypress now takes "quote" argument, where it will
	 * treat this key as though it were bound to self_insert, rather
	 * than whatever it is.  This will allow me to (eventually) implement
	 * a self_insert toggle mode.   In any case, we always clear the
	 * quote_hit flag when we're done. (Is it cheaper to unconditionally
	 * set it to 0, or to do a test-and-set?  Does it matter?)
	 */
	old_quote_hit = get_screen_quote_hit(last_input_screen);

	set_screen_last_key(last_input_screen, handle_keypress(
		get_screen_last_key(last_input_screen),
		get_screen_last_press(last_input_screen), key,
		get_screen_quote_hit(last_input_screen)));
	set_screen_last_press(last_input_screen, get_time(NULL));

	if (old_quote_hit)
		set_screen_quote_hit(last_input_screen, 0);
}


void	fire_wait_prompt (uint32_t key)
{
	WaitPrompt *	oldprompt;
        char   utf8str[8];

	if (!(oldprompt = get_screen_prompt_list(last_input_screen)))
		return;		/* Oh well.... */
	set_screen_prompt_list(last_input_screen, oldprompt->next);
	set_screen_input_line(last_input_screen, oldprompt->saved_input_line);
	update_input(last_input_screen, UPDATE_ALL);

	ucs_to_utf8(key, utf8str, sizeof(utf8str));
	(*oldprompt->func)(oldprompt->data, utf8str);
	destroy_prompt(last_input_screen, &oldprompt);

}

void	fire_normal_prompt (const char *utf8str)
{
	WaitPrompt *	oldprompt;

	if (!(oldprompt = get_screen_prompt_list(last_input_screen)))
		return;		/* Oh well... */
	set_screen_prompt_list(last_input_screen, oldprompt->next);
	set_screen_input_line(last_input_screen, oldprompt->saved_input_line);
	update_input(last_input_screen, UPDATE_ALL);

	(*oldprompt->func)(oldprompt->data, utf8str);
	destroy_prompt(last_input_screen, &oldprompt);
}


/* * * * * * * * * INPUT PROMPTS * * * * * * * * * * */
/* 
 * add_wait_prompt: Start a modal input prompt, which interrupts the user
 *  and requires them to answer a prompt before continuing.
 *
 * This function is asynchronous (it returns immediately, and your function
 * will be called some time later), so make sure to structure your code
 * appropriately.
 *
 * Users don't like being interrupted by modal inputs, so you shouldn't use
 * this function unless it's for something so critical that the user 
 * shouldn't continue without answering.
 *
 * As of the time of this writing (10/13) your callback function does not
 * need to be utf8 aware, but that is going to change very soon.
 *
 * Arguments:
 *	prompt	The prompt to display to the user.  This will interrupt
 *		the normal input processing, so it is a "modal input"
 *	func	A function to call back when the user has answered the 
 *		prompt.  It will be passed back two arguments:
 *		1. A pointer to "data" (see next)
 *		2. A C string (possibly encoded in UTF8 in the future)
 *	   	containing the user's response.
 *  	data	A payload of data to be passed to your callback.
 *		**NOTE -- It is important that this be new_malloc()ed memory.
 *		YOU are responsible for new_free()ing it in the callback.
 *		This may be NULL.
 *  	type	One of:
 *		WAIT_PROMPT_LINE - An entire line of input (up through SENDLINE)
 *		WAIT_PROMPT_KEY - The next keypress (possibly UTF8 code point)
 *		WAIT_PROMPT_DUMMY - Internal use only (for /PAUSE)
 *	echo	Whether to display the characters as they are typed (1) or to
 *	   	not display the characters (0).  Useful for passwords.
 *	   	NOTE - Even if echo == 0, the cursor may move each keypress,
 *	   	so this isn't a serious security protection.
 *
 * XXX - Prompts are basically global -- they don't know about windows, and
 *       therefore they don't know about servers.  That is probably wrong.  
 *       You don't want your callback firing off in the wrong context, do you?
 *	 Suggestion: "data" should be a struct that contains window and server 
 * 	 context information.
 */
void 	add_wait_prompt (const char *prompt, void (*func)(char *data, const char *utf8str), const char *data, int type, int echo)
{
	WaitPrompt *New;
	int	s;
	int	old_last_input_screen;;

	old_last_input_screen = last_input_screen;

	if (get_window_screennum(0) >= 0)
		s = get_window_screennum(0);
	else
		s = main_screen;

	last_input_screen = s;
	New = (WaitPrompt *) new_malloc(sizeof(WaitPrompt));
	New->data = malloc_strdup(data);
	New->type = type;
	New->func = func;
	New->my_input_line = new_input_line(prompt, echo);
	New->saved_input_line = get_screen_input_line(s);
	set_screen_input_line(s, New->my_input_line);

	New->next = get_screen_prompt_list(s);
	set_screen_prompt_list(s, New);

	init_input();
	update_input(last_input_screen, UPDATE_ALL);
	last_input_screen = old_last_input_screen;
}

static void	destroy_prompt (int s_, WaitPrompt **oldprompt)
{
	destroy_input_line((*oldprompt)->my_input_line);
	(*oldprompt)->my_input_line = NULL;
	new_free(&(*oldprompt)->data);
	new_free((char **)oldprompt);

	update_input(last_input_screen, UPDATE_ALL);
}

/* 
 * chop_columns - Remove the first 'num' columns from 'str'.
 * Arguments:
 *	str	- A pointer to a Type 0 normalized string
 *		  Passing in a non-normalized string will probably crash
 * Return Value:
 *	'str' is changed to point to the start of the 'num'th column
 *	in the original value of 'str'.
 *
 * Example:
 *	*str = "one two three"
 *	num = 2
 * results in
 *	*str = "e two three"
 *
 * This is modeled after output_with_count, and denormalize_string().
 * All these functions should be refactored somehow.  Too much copypasta!
 */
void	chop_columns (char **str, size_t num)
{
	char 	*s, *x;
	int	codepoint, cols;
	ptrdiff_t	offset;

	if (!str || !*str)
		return;

	for (s = (char *)*str; s && *s; s = x)
	{
		/* 
		 * This resyncs to UTF-8; 
		 * In the worst case, eventually codepoint == 0 at EOS.
		 */
		x = s;
		while ((codepoint = next_code_point2(x, &offset, 0)) == -1)
			x++;
		x += offset;

		/* 
		 * \006 is the "attribute marker" which is followed by
		 * four bytes which we don't care about.  the whole
	 	 * thing takes up 0 columns.
		 */
		if (codepoint == 6)
		{
			size_t		numbytes = 0;
			Attribute	a;

			zero_attribute(&a);
			if (read_internal_attribute(x, &a, &numbytes))
				break;
			x += numbytes;
			continue;
		}

		/* \007 is the beep -- and we don't care. */
		else if (codepoint == 7)
			continue;
		/* The ND_SPACE does take up a column */
		else if (codepoint == ND_SPACE)
			cols = 1;
		/* Everything else is evaluated by codepoint_numcolumns */
		else 
		{
			cols = codepoint_numcolumns(codepoint);
			if (cols == -1)
				cols = 0;
		}

		/*
		 * NOW -- here is the tricky part....
		 *
		 * Why did we do the above on 'x' ?
		 * Because we need to keep slurping up code points
		 * after 'num == 0' because of things like continuation
		 * points and highlight chars.
		 * So once num == 0 *AND* we have a cp that takes up a column,
		 * that's where we stop.
		 */
		if (num <= 0 && cols > 0)
		{
			break;	/* Remember, DON'T include the char we 
				 * just evaluated! */
		}

		num -= cols;
	}

	*str = s;
}


/*
 * I suppose this is cheating -- but since it is only used by 
 * $fix_width(), do i really care?
 */
void	chop_final_columns (char **str, size_t num)
{
	char 	*s, *x;
	int	cols, codepoint;
	size_t	numcols;
	ptrdiff_t	offset;

	if (!str || !*str)
		return;

	/* 
	 * Why do i go forward rather than backward?
	 * The attribute marker includes 4 printable chars,
	 * so we cannot just evalute the length of a string
	 * from the end->start without accounting for that.
	 * so it just makes more sense to go start->end
	 * even though that's not optimally efficient.
	 */
	for (s = (char *)*str; s && *s; s = x)
	{
		/* 
		 * This resyncs to UTF-8; 
		 * In the worst case, eventually codepoint == 0 at EOS.
		 */
		x = s;
		while ((codepoint = next_code_point2(x, &offset, 0)) == -1)
			x++;
		x += offset;

		/* 
		 * \006 is the "attribute marker" which is followed by
		 * four bytes which we don't care about.  the whole
	 	 * thing takes up 0 columns.
		 */
		if (codepoint == 6)
		{
			size_t		numbytes = 0;
			Attribute	a;

			if (read_internal_attribute(x, &a, &numbytes))
				break;
			x += numbytes;
			continue;
		}
		/* \007 is the beep -- and we don't care. */
		else if (codepoint == 7)
			continue;
		else
		{
			/* Skip unprintable chars */
			cols = codepoint_numcolumns(codepoint);
			if (cols == -1)
				continue;

			/* 
			 * Now we're looking at a printable char. 
			 * Is string, starting at this char, <= our target? 
			 * If so, then we're done. 
			 */
			numcols = output_with_count(s, 0, 0);
			if (numcols <= num)
			{
				/* Truncate and stop */
				*s = 0;
				break;
			}
		}
	}
}

int	parse_mangle (const char *value, int nvalue, char **rv)
{
	char	*str1, *str2;
	char	*copy;
	char	*nv = NULL;

	if (rv)
		*rv = NULL;

	if (!value)
		return 0;

	copy = LOCAL_COPY(value);

	while ((str1 = new_next_arg(copy, &copy)))
	{
		while (*str1 && (str2 = next_in_comma_list(str1, &str1)))
		{
			     if (!my_strnicmp(str2, "ALL_OFF", 4))
				nvalue |= STRIP_ALL_OFF;
			else if (!my_strnicmp(str2, "-ALL_OFF", 5))
				nvalue &= ~(STRIP_ALL_OFF);
			else if (!my_strnicmp(str2, "ALL", 3))
				nvalue = (0x7FFFFFFFU ^ (MANGLE_ESCAPES) ^ (STRIP_OTHER) ^ (STRIP_UNPRINTABLE));
			else if (!my_strnicmp(str2, "-ALL", 4))
				nvalue = 0;
			else if (!my_strnicmp(str2, "ALT_CHAR", 3))
				nvalue |= STRIP_ALT_CHAR;
			else if (!my_strnicmp(str2, "-ALT_CHAR", 4))
				nvalue &= ~(STRIP_ALT_CHAR);
			else if (!my_strnicmp(str2, "ANSI", 2))
				nvalue |= NORMALIZE;
			else if (!my_strnicmp(str2, "-ANSI", 3))
				nvalue &= ~(NORMALIZE);
			else if (!my_strnicmp(str2, "BLINK", 2))
				nvalue |= STRIP_BLINK;
			else if (!my_strnicmp(str2, "-BLINK", 3))
				nvalue &= ~(STRIP_BLINK);
			else if (!my_strnicmp(str2, "BOLD", 2))
				nvalue |= STRIP_BOLD;
			else if (!my_strnicmp(str2, "-BOLD", 3))
				nvalue &= ~(STRIP_BOLD);
			else if (!my_strnicmp(str2, "COLOR", 1))
				nvalue |= STRIP_COLOR;
			else if (!my_strnicmp(str2, "-COLOR", 2))
				nvalue &= ~(STRIP_COLOR);
			else if (!my_strnicmp(str2, "ESCAPE", 1))
				nvalue |= MANGLE_ESCAPES;
			else if (!my_strnicmp(str2, "-ESCAPE", 2))
				nvalue &= ~(MANGLE_ESCAPES);
			else if (!my_strnicmp(str2, "ND_SPACE", 2))
				nvalue |= STRIP_ND_SPACE;
			else if (!my_strnicmp(str2, "-ND_SPACE", 3))
				nvalue &= ~(STRIP_ND_SPACE);
			else if (!my_strnicmp(str2, "NORMALIZE", 3))
				nvalue |= NORMALIZE;
			else if (!my_strnicmp(str2, "-NORMALIZE", 4))
				nvalue &= ~(NORMALIZE);
			else if (!my_strnicmp(str2, "NONE", 2))
				nvalue = 0;
			else if (!my_strnicmp(str2, "OTHER", 2))
				nvalue |= STRIP_OTHER;
			else if (!my_strnicmp(str2, "-OTHER", 3))
				nvalue &= ~(STRIP_OTHER);
			else if (!my_strnicmp(str2, "REVERSE", 2))
				nvalue |= STRIP_REVERSE;
			else if (!my_strnicmp(str2, "-REVERSE", 3))
				nvalue &= ~(STRIP_REVERSE);
			else if (!my_strnicmp(str2, "UNDERLINE", 3))
				nvalue |= STRIP_UNDERLINE;
			else if (!my_strnicmp(str2, "-UNDERLINE", 4))
				nvalue &= ~(STRIP_UNDERLINE);
			else if (!my_strnicmp(str2, "UNPRINTABLE", 3))
				nvalue |= STRIP_UNPRINTABLE;
			else if (!my_strnicmp(str2, "-UNPRINTABLE", 4))
				nvalue &= ~(STRIP_UNPRINTABLE);
		}
	}

	if (rv)
	{
		if (nvalue & MANGLE_ESCAPES)
			malloc_strcat_wordlist(&nv, space, "ESCAPE");
		if (nvalue & NORMALIZE)
			malloc_strcat_wordlist(&nv, space, "NORMALIZE");
		if (nvalue & STRIP_COLOR)
			malloc_strcat_wordlist(&nv, space, "COLOR");
		if (nvalue & STRIP_REVERSE)
			malloc_strcat_wordlist(&nv, space, "REVERSE");
		if (nvalue & STRIP_UNDERLINE)
			malloc_strcat_wordlist(&nv, space, "UNDERLINE");
		if (nvalue & STRIP_BOLD)
			malloc_strcat_wordlist(&nv, space, "BOLD");
		if (nvalue & STRIP_BLINK)
			malloc_strcat_wordlist(&nv, space, "BLINK");
		if (nvalue & STRIP_ALT_CHAR)
			malloc_strcat_wordlist(&nv, space, "ALT_CHAR");
		if (nvalue & STRIP_ND_SPACE)
			malloc_strcat_wordlist(&nv, space, "ND_SPACE");
		if (nvalue & STRIP_ALL_OFF)
			malloc_strcat_wordlist(&nv, space, "ALL_OFF");
		if (nvalue & STRIP_UNPRINTABLE)
			malloc_strcat_wordlist(&nv, space, "UNPRINTABLE");
		if (nvalue & STRIP_OTHER)
			malloc_strcat_wordlist(&nv, space, "OTHER");

		*rv = nv;
	}

	return nvalue;
}

int	screen_is_valid (int screen_)
{
	Screen *s;

	if (screen_ < 0)
		return 0;

	for (s = screen_list; s; s = s->next)
	{
		if (s->screennum == screen_)
		{
			if (s->alive)
				return 1;
			else
				return 0;
		}
	}

	return 0;
}

int	traverse_all_screens (int *screen_)
{
	Screen *s;

	if (!*screen_)
		s = screen_list;
	else
	{
		for (s = screen_list; s; s = s->next)
		{
			if (s->screennum == *screen_)
			{
				s = s->next;
				break;
			}
		}
	}

	if (!s)
		return 0;

	*screen_ = s->screennum;
	return 1;
}

static Screen *get_screen_by_refnum (int screen_)
{
	Screen *s;

	if (screen_ == -1)
		return NULL;

	for (s = screen_list; s; s = s->next)
		if (s->screennum == screen_)
			return s;

	return NULL;
}


int             get_screen_prev                 (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	if (s->prev)
		return s->prev->screennum;
	return -1;
}

int             get_screen_next                 (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	if (s->next)
		return s->next->screennum;
	return -1;
}

int             get_screen_alive                (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->alive;
}

int             get_screen_screennum            (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->screennum;
}

int             get_screen_input_window         (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->input_window;
}

int             get_screen_last_window_refnum   (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return (int)s->last_window_refnum;
}

int             get_screen_window_list          (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->_window_list;
}

int             get_screen_visible_windows      (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->visible_windows;
}

WindowStack *   get_screen_window_stack         (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->window_stack;
}

FILE *          get_screen_fpin                 (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->fpin;
}

int             get_screen_fdin                 (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->fdin;
}

FILE *          get_screen_fpout                (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->fpout;
}

int             get_screen_fdout                (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->fdout;
}

int             get_screen_control              (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->control;
}

int             get_screen_wserv_version        (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->wserv_version;
}

void *     get_screen_input_line           (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->il;
}

static WaitPrompt *    get_screen_prompt_list          (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->promptlist;
}

int	get_screen_prompt_list_type		(int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return WAIT_PROMPT_NONE;
	if (!s->promptlist)
		return WAIT_PROMPT_NONE;
	return s->promptlist->type;
}

int             get_screen_quote_hit            (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->quote_hit;
}

Timeval         get_screen_last_press           (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return (Timeval){0, 0};
	return s->last_press;
}

void *          get_screen_last_key             (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return NULL;
	return s->last_key;
}

int             get_screen_columns              (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->co;
}

int             get_screen_lines                (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->li;
}

int             get_screen_old_columns          (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->old_co;
}

int             get_screen_old_lines            (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return -1;
	return s->old_li;
}


void            set_screen_alive                (int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->alive = value;
}

void            set_screen_input_window         (int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->input_window = value;
}

void            set_screen_last_window_refnum   (int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->last_window_refnum = value;
}

void		set_screen_window_list		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->_window_list = value;
}

void		set_screen_visible_windows	(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->visible_windows = value;
}

void		set_screen_visible_windows_incr	(int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->visible_windows++;
}

void		set_screen_visible_windows_dec	(int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->visible_windows--;
}

void		set_screen_window_stack		(int screen_, WindowStack *ws)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->window_stack = ws;
}

void		set_screen_lines		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->li = value;
}

void		set_screen_columns		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->co = value;
}

void		set_screen_old_lines		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->old_li = value;
}

void		set_screen_old_columns		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->old_co = value;
}

void		set_screen_quote_hit		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->quote_hit = value;
}

void		set_screen_fdin			(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->fdin = value;
}

void		set_screen_fdout		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->fdout = value;
}

void		set_screen_fpin			(int screen_, FILE *value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->fpin = value;
}

void		set_screen_fpout		(int screen_, FILE *value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->fpout = value;
}

void		set_screen_control		(int screen_, int value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->control = value;
}

void		set_screen_last_key		(int screen_, void *value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->last_key = value;
}

void		set_screen_last_press		(int screen_, Timeval value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->last_press = value;
}

static void		set_screen_prompt_list		(int screen_, WaitPrompt *value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->promptlist = value;
}

void		set_screen_input_line		(int screen_, void *value)
{
	Screen *s = get_screen_by_refnum(screen_);
	if (!s)
		return;
	s->il = value;
}

/****************************************************/
int	screen_add_window_before (int screen_, int existing_window_, int new_window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	ewp;

	if (!s)
		return 0;

	if ((ewp = screen_window_find(screen_, existing_window_)) >= 0)
	{
		if (screen_windows_make_room_at(screen_, ewp))
		{
			s->_windows[ewp].window = new_window_;
			return 1;
		}
	}
	return 0;
}

int	screen_add_window_after (int screen_, int existing_window_, int new_window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	ewp;

	if (!s)
		return 0;

	if ((ewp = screen_window_find(screen_, existing_window_)) >= 0)
	{
		if (screen_windows_make_room_at(screen_, ewp + 1))
		{
			s->_windows[ewp + 1].window = new_window_;
			return 1;
		}
	}
	return 0;
}

int	screen_add_window_first (int screen_, int new_window_)
{
	Screen *s = get_screen_by_refnum(screen_);

	if (!s)
		return 0;

	if (screen_windows_make_room_at(screen_, 0))
	{
		s->_windows[0].window = new_window_;
		return 1;
	}
	return 0;
}

int	screen_add_window_last (int screen_, int new_window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	lp;

	if (!s)
		return 0;

	if ((lp = screen_window_find(screen_, -1)) >= 0)
	{
		s->_windows[lp].window = new_window_;
		return 1;
	}
	return 0;
}

int	screen_remove_window (int screen_, int old_window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	location;

	if (!s)
		return 0;

	for (location = 0; location <= MAX_WINDOWS_ON_SCREEN; location++)
	{
		if (s->_windows[location].window == old_window_)
			s->_windows[location].window = -1;
	}
	if (screen_windows_squeeze(screen_))
		return 1;
	return 0;
}

int	screen_windows_squeeze (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	location;

	if (!s)
		return 0;

	for (location = 0; location <= MAX_WINDOWS_ON_SCREEN; location++)
	{
		/* Squeeze both 0 and -1 */
		if (s->_windows[location].window <= 0)
		{
			int	squeezer;
			for (squeezer = location; squeezer < MAX_WINDOWS_ON_SCREEN; squeezer++)
				s->_windows[squeezer] = s->_windows[squeezer + 1];
		}
	}
	return 1;
}

int	screen_windows_make_room_at (int screen_, int location)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	end = location;

	if (!s)
		return 0;

	if (location < 0 || location > MAX_WINDOWS_ON_SCREEN )
		return 0;		/* No. */

	while (end <= MAX_WINDOWS_ON_SCREEN && s->_windows[end].window != -1)
		end++;

	if (end > MAX_WINDOWS_ON_SCREEN)
		return 0;		/* No more room */

	while (end >= location)
	{
		s->_windows[end + 1] = s->_windows[end];
		end--;
	}

	s->_windows[location].window = 0;	/* Window 0 is "placeholder" */
	return 1;
}

int	screen_window_find (int screen_, int window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	location;

	if (!s)
		return -1;

	for (location = 0; location <= MAX_WINDOWS_ON_SCREEN; location++)
		if (s->_windows[location].window == window_)
			return location;

	return -1;
}

int	screen_window_swap (int screen_, int v_window_, int window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	location;

	if (!s)
		return -1;

	if ((location = screen_window_find(screen_, v_window_)) >= 0)
	{
		s->_windows[location].window = window_;
		return 1;
	}
	return 0;
}

int	screen_window_dump (int screen_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	location;

	if (!s)
		return -1;

	yell("Screen %d (%#lx)", screen_, (intptr_t)s);
	for (location = 0; location <= MAX_WINDOWS_ON_SCREEN; location++)
	{
		if (s->_windows[location].window != -1)
			yell("Screen %d, location %d, window %d", screen_, location, s->_windows[location].window);
	}
	return 0;

}

int	screen_window_place (int screen_, int location, int window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	x;

	if (!s)
	{
#if 0
		yell("Screen_window_place: screen %d is invalid", screen_);
#endif
		return -1;
	}
	if (location < 1 || location > MAX_WINDOWS_ON_SCREEN)
	{
#if 0
		yell("Screen_window_place: location %d is invalid", location);
#endif
		return -1;
	}

	/* If we're moving UP the window, put it above the window that is already there */
	if ((x = screen_window_find(screen_, window_)) >= 0)
		if (x >= location)
			location--;

#if 0
	yell("screen_window_place: %d, %d (-1), %d", screen_, location, window_);
#endif

	if ((x = screen_window_find(screen_, window_)) >= 0)
	{
		WindowAttachment wa = s->_windows[x];

#if 0
		yell("Moving existing window %d from %d to %d (-1)", window_, x, location);
#endif
		screen_windows_make_room_at(screen_, location);
		if ((x = screen_window_find(screen_, window_)) >= 0)
			s->_windows[x].window = 0;
		s->_windows[location] = wa;
		screen_windows_squeeze(screen_);
#if 0
		yell("Done");
#endif
	}
	else
	{
#if 0
		yell("Making room for new window %d on %d/%d (-1)", window_, screen_, location);
#endif
		screen_windows_make_room_at(screen_, location);
		s->_windows[location].window = window_;
		screen_windows_squeeze(screen_);
#if 0
		yell("done");
#endif
	}

	return 0;
}

int	screen_get_window_prev (int screen_, int window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	x;

	if (!s)
		return -1;

	x = screen_window_find(screen_, window_);
	if (x < 0)
		return -1;	/* Not on screen */
	else if (x == 0)
		return -1;	/* First window on screen */
	else
		return s->_windows[x - 1].window;
}

int	screen_get_window_next (int screen_, int window_)
{
	Screen *s = get_screen_by_refnum(screen_);
	int	x;

	if (!s)
		return -1;

	x = screen_window_find(screen_, window_);
	if (x < 0)
		return -1;	/* Not on screen */
	else if (x >= MAX_WINDOWS_ON_SCREEN)
		return -1;	/* No windows past this point */
	else
		return s->_windows[x + 1].window;
}

