Skip to content
Snippets Groups Projects
  • rswindell's avatar
    5fe7a7fb
    Added range checks when initializing textinfo.screenwidth and screeheight · 5fe7a7fb
    rswindell authored
    (don't wrap around if value is > 255) - presumably these textinfo struct
    members were left as unsigned chars (8-bits) to preserve compatiblity with
    legacy conio apps, but I'm not sure. Perhaps we could increase these to
    at least 16-bit integers or bigger in the future, in which case these range
    checks would need to be removed or replaced.
    5fe7a7fb
    History
    Added range checks when initializing textinfo.screenwidth and screeheight
    rswindell authored
    (don't wrap around if value is > 255) - presumably these textinfo struct
    members were left as unsigned chars (8-bits) to preserve compatiblity with
    legacy conio apps, but I'm not sure. Perhaps we could increase these to
    at least 16-bit integers or bigger in the future, in which case these range
    checks would need to be removed or replaced.
bitmap_con.c 23.22 KiB
/* $Id$ */

#include <stdarg.h>
#include <stdio.h>		/* NULL */
#include <stdlib.h>
#include <string.h>

#include "threadwrap.h"
#include "semwrap.h"
#include "gen_defs.h"
#include "genwrap.h"
#include "dirwrap.h"
#include "xpbeep.h"

#if (defined CIOLIB_IMPORTS)
 #undef CIOLIB_IMPORTS
#endif
#if (defined CIOLIB_EXPORTS)
 #undef CIOLIB_EXPORTS
#endif

#include "ciolib.h"
#include "vidmodes.h"
#include "bitmap_con.h"

static char *screen=NULL;
int screenwidth=0;
int screenheight=0;
#define PIXEL_OFFSET(x,y)	( (y)*screenwidth+(x) )

static int default_font=-99;
static int current_font[4]={-99, -99, -99, -99};
static int bitmap_initialized=0;
pthread_mutex_t vmem_lock;
struct video_stats vstat;

struct bitmap_callbacks {
	void	(*drawrect)		(int xpos, int ypos, int width, int height, unsigned char *data);
	void	(*flush)		(void);
};

pthread_mutex_t		vstatlock;
pthread_mutex_t		screenlock;
static struct bitmap_callbacks callbacks;
static unsigned char *font[4];
static unsigned char space=' ';
int force_redraws=0;

struct rectangle {
	int x;
	int y;
	int width;
	int height;
};

static int update_rect(int sx, int sy, int width, int height, int force);

static __inline void *locked_screen_check(void)
{
	void *ret;
	pthread_mutex_lock(&screenlock);
	ret=screen;
	pthread_mutex_unlock(&screenlock);
	return(ret);
}

static struct vstat_vmem *lock_vmem(struct video_stats *vs)
{
	struct vstat_vmem *ret;
	pthread_mutex_lock(&vstatlock);
	ret = get_vmem(vs);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);
	return ret;
}

static void unlock_vmem(struct vstat_vmem *vm)
{
	pthread_mutex_unlock(&vmem_lock);
	release_vmem(vm);
}

/* Blinker Thread */
static void blinker_thread(void *data)
{
	int count=0;

	SetThreadName("Blinker");
	while(1) {
		do {
			SLEEP(10);
		} while(locked_screen_check()==NULL);
		count++;
		if(count==50) {
			pthread_mutex_lock(&vstatlock);
			if(vstat.blink)
				vstat.blink=FALSE;
			else
				vstat.blink=TRUE;
			count=0;
			pthread_mutex_unlock(&vstatlock);
		}
		if(force_redraws)
			update_rect(0,0,0,0,force_redraws--);
		else
			update_rect(0,0,0,0,FALSE);
		callbacks.flush();
	}
}

/*
 * MUST be called only once and before any other bitmap functions
 */
int bitmap_init(void (*drawrect_cb) (int xpos, int ypos, int width, int height, unsigned char *data)
				,void (*flush_cb) (void))
{
	if(bitmap_initialized)
		return(-1);
	pthread_mutex_init(&vmem_lock, NULL);
	pthread_mutex_init(&vstatlock, NULL);
	pthread_mutex_init(&screenlock, NULL);
	pthread_mutex_lock(&vmem_lock);
	vstat.vmem=NULL;
	pthread_mutex_unlock(&vmem_lock);

	callbacks.drawrect=drawrect_cb;
	callbacks.flush=flush_cb;
	bitmap_initialized=1;
	_beginthread(blinker_thread,0,NULL);

	return(0);
}

/*
 * This function is intended to be called from the driver.
 * as a result, it cannot block waiting for driver status
 *
 * Care MUST be taken to avoid deadlocks...
 */
int bitmap_init_mode(int mode, int *width, int *height)
{
    int i;
	char *newscreen;

	if(!bitmap_initialized)
		return(-1);

	pthread_mutex_lock(&screenlock);
	pthread_mutex_lock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);

	if(load_vmode(&vstat, mode)) {
		pthread_mutex_unlock(&vmem_lock);
		pthread_mutex_unlock(&vstatlock);
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}

	/* Initialize video memory with black background, white foreground */
	for (i = 0; i < vstat.cols*vstat.rows; ++i)
	    vstat.vmem->vmem[i] = 0x0700;
	pthread_mutex_unlock(&vmem_lock);

	screenwidth=vstat.charwidth*vstat.cols;
	if(width)
		*width=screenwidth;
	screenheight=vstat.charheight*vstat.rows;
	if(height)
		*height=screenheight;
	newscreen=realloc(screen, screenwidth*screenheight);
	if(!newscreen) {
		pthread_mutex_unlock(&vstatlock);
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}
	screen=newscreen;
	memset(screen,vstat.palette[0],screenwidth*screenheight);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_unlock(&screenlock);
	for (i=0; i<sizeof(current_font)/sizeof(current_font[0]); i++)
		current_font[i]=default_font;
	bitmap_loadfont(NULL);

	cio_textinfo.attribute=7;
	cio_textinfo.normattr=7;
	cio_textinfo.currmode=mode;

	if (vstat.rows > 0xff)
		cio_textinfo.screenheight = 0xff;
	else
		cio_textinfo.screenheight = vstat.rows;

	if (vstat.cols > 0xff)
		cio_textinfo.screenwidth = 0xff;
	else
		cio_textinfo.screenwidth = vstat.cols;

	cio_textinfo.curx=1;
	cio_textinfo.cury=1;
	cio_textinfo.winleft=1;
	cio_textinfo.wintop=1;
	cio_textinfo.winright=cio_textinfo.screenwidth;
	cio_textinfo.winbottom=cio_textinfo.screenheight;

    return(0);
}

/*
 * Send by ciolib side, should not block in driver
 * Generally, if the driver may block on a rectangle draw, the updates
 * should be cached until flush is called.
 */
void send_rectangle(struct video_stats *vs, int xoffset, int yoffset, int width, int height, int force)
{
	unsigned char *rect;
	int pixel=0;
	int inpixel;
	int x,y;

	if(!bitmap_initialized)
		return;
	pthread_mutex_lock(&screenlock);
	if(callbacks.drawrect) {
		if(xoffset < 0 || xoffset >= screenwidth || yoffset < 0 || yoffset >= screenheight || width <= 0 || width > screenwidth || height <=0 || height >screenheight)
			goto end;

		rect=(unsigned char *)malloc(width*height*sizeof(unsigned char));
		if(!rect)
			goto end;

		for(y=0; y<height; y++) {
			inpixel=PIXEL_OFFSET(xoffset, yoffset+y);
			for(x=0; x<width; x++)
				rect[pixel++]=vs->palette[screen[inpixel++]];
		}
		pthread_mutex_unlock(&screenlock);
		callbacks.drawrect(xoffset,yoffset,width,height,rect);
		return;
	}
end:
	pthread_mutex_unlock(&screenlock);
}

/********************************************************/
/* High Level Stuff										*/
/********************************************************/

/* Called from main thread only (Passes Event) */

void bitmap_getcustomcursor(int *s, int *e, int *r, int *b, int *v)
{
	pthread_mutex_lock(&vstatlock);
	if(s)
		*s=vstat.curs_start;
	if(e)
		*e=vstat.curs_end;
	if(r)
		*r=vstat.charheight;
	if(b)
		*b=vstat.curs_blink;
	if(v)
		*v=vstat.curs_visible;
	pthread_mutex_unlock(&vstatlock);
}

void bitmap_setcustomcursor(int s, int e, int r, int b, int v)
{
	double ratio;

	pthread_mutex_lock(&vstatlock);
	if(r==0)
		ratio=0;
	else
		ratio=vstat.charheight/r;
	if(s>=0)
		vstat.curs_start=s*ratio;
	if(e>=0)
		vstat.curs_end=e*ratio;
	if(b>=0)
		vstat.curs_blink=b;
	if(v>=0)
		vstat.curs_visible=v;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_getvideoflags(void)
{
	int flags=0;

	pthread_mutex_lock(&vstatlock);
	if(vstat.bright_background)
		flags |= CIOLIB_VIDEO_BGBRIGHT;
	if(vstat.no_bright)
		flags |= CIOLIB_VIDEO_NOBRIGHT;
	if(vstat.bright_altcharset)
		flags |= CIOLIB_VIDEO_ALTCHARS;
	if(vstat.no_blink)
		flags |= CIOLIB_VIDEO_NOBLINK;
	if(vstat.blink_altcharset)
		flags |= CIOLIB_VIDEO_BLINKALTCHARS;
	pthread_mutex_unlock(&vstatlock);
	return(flags);
}

void bitmap_setvideoflags(int flags)
{
	pthread_mutex_lock(&vstatlock);
	if(flags & CIOLIB_VIDEO_BGBRIGHT)
		vstat.bright_background=1;
	else
		vstat.bright_background=0;

	if(flags & CIOLIB_VIDEO_NOBRIGHT)
		vstat.no_bright=1;
	else
		vstat.no_bright=0;

	if(flags & CIOLIB_VIDEO_ALTCHARS)
		vstat.bright_altcharset=1;
	else
		vstat.bright_altcharset=0;

	if(flags & CIOLIB_VIDEO_NOBLINK)
		vstat.no_blink=1;
	else
		vstat.no_blink=0;

	if(flags & CIOLIB_VIDEO_BLINKALTCHARS)
		vstat.blink_altcharset=1;
	else
		vstat.blink_altcharset=0;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_movetext(int x, int y, int ex, int ey, int tox, int toy)
{
	int	direction=1;
	int	cy;
	int	destoffset;
	int	sourcepos;
	int width=ex-x+1;
	int height=ey-y+1;
	struct vstat_vmem *vmem_ptr;

	if(		   x<1
			|| y<1
			|| ex<1
			|| ey<1
			|| tox<1
			|| toy<1
			|| x>cio_textinfo.screenwidth
			|| ex>cio_textinfo.screenwidth
			|| tox>cio_textinfo.screenwidth
			|| y>cio_textinfo.screenheight
			|| ey>cio_textinfo.screenheight
			|| toy>cio_textinfo.screenheight)
		return(0);

	if(toy > y)
		direction=-1;

	sourcepos=(y-1)*cio_textinfo.screenwidth+(x-1);
	destoffset=(((toy-1)*cio_textinfo.screenwidth+(tox-1))-sourcepos);

	vmem_ptr = lock_vmem(&vstat);
	for(cy=(direction==-1?(height-1):0); cy<height && cy>=0; cy+=direction) {
		sourcepos=((y-1)+cy)*cio_textinfo.screenwidth+(x-1);
		memmove(&(vmem_ptr->vmem[sourcepos+destoffset]), &(vmem_ptr->vmem[sourcepos]), sizeof(vmem_ptr->vmem[0])*width);
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

void bitmap_clreol(void)
{
	int pos,x;
	WORD fill=(cio_textinfo.attribute<<8)|space;
	struct vstat_vmem *vmem_ptr;

	pos=(cio_textinfo.cury+cio_textinfo.wintop-2)*cio_textinfo.screenwidth;
	vmem_ptr = lock_vmem(&vstat);
	for(x=cio_textinfo.curx+cio_textinfo.winleft-2; x<cio_textinfo.winright; x++)
		vmem_ptr->vmem[pos+x]=fill;
	unlock_vmem(vmem_ptr);
}

void bitmap_clrscr(void)
{
	int x,y;
	WORD fill=(cio_textinfo.attribute<<8)|space;
	struct vstat_vmem *vmem_ptr;

	vmem_ptr = lock_vmem(&vstat);
	for(y=cio_textinfo.wintop-1; y<cio_textinfo.winbottom; y++) {
		for(x=cio_textinfo.winleft-1; x<cio_textinfo.winright; x++)
			vmem_ptr->vmem[y*cio_textinfo.screenwidth+x]=fill;
	}
	unlock_vmem(vmem_ptr);
}

int bitmap_puttext(int sx, int sy, int ex, int ey, void *fill)
{
	int x,y;
	unsigned char *out;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(0);
	if(		   sx < 1
			|| sy < 1
			|| ex < 1
			|| ey < 1
			|| sx > cio_textinfo.screenwidth
			|| sy > cio_textinfo.screenheight
			|| sx > ex
			|| sy > ey
			|| ex > cio_textinfo.screenwidth
			|| ey > cio_textinfo.screenheight
			|| fill==NULL)
		return(0);

	vmem_ptr = lock_vmem(&vstat);
	out=fill;
	for(y=sy-1;y<ey;y++) {
		for(x=sx-1;x<ex;x++) {
			sch=*(out++);
			sch |= (*(out++))<<8;
			vmem_ptr->vmem[y*cio_textinfo.screenwidth+x]=sch;
		}
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

/* Called from main thread only */
int bitmap_gettext(int sx, int sy, int ex, int ey, void *fill)
{
	int x,y;
	unsigned char *out;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(0);

	if(		   sx < 1
			|| sy < 1
			|| ex < 1
			|| ey < 1
			|| sx > ex
			|| sy > ey
			|| ex > cio_textinfo.screenwidth
			|| ey > cio_textinfo.screenheight
			|| fill==NULL)
		return(0);

	vmem_ptr = lock_vmem(&vstat);
	out=fill;
	for(y=sy-1;y<ey;y++) {
		for(x=sx-1;x<ex;x++) {
			sch=vmem_ptr->vmem[y*cio_textinfo.screenwidth+x];
			*(out++)=sch & 0xff;
			*(out++)=sch >> 8;
		}
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

/* Called from ciolib thread only */
void bitmap_setcursortype(int type)
{
	if(!bitmap_initialized)
		return;
	pthread_mutex_lock(&vstatlock);
	switch(type) {
		case _NOCURSOR:
			vstat.curs_start=0xff;
			vstat.curs_end=0;
			break;
		case _SOLIDCURSOR:
			vstat.curs_start=0;
			vstat.curs_end=vstat.charheight-1;
			break;
		default:
		    vstat.curs_start = vstat.default_curs_start;
		    vstat.curs_end = vstat.default_curs_end;
			break;
	}
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_setfont(int font, int force, int font_num)
{
	int changemode=0;
	int	newmode=-1;
	struct text_info ti;
	char	*old;
	int		ow,oh;
	int		row,col;
	char	*new;
	int		attr;
	char	*pold;
	char	*pnew;

	if(!bitmap_initialized)
		return(-1);
	if(font < 0 || font>(sizeof(conio_fontdata)/sizeof(struct conio_font_data_struct)-2))
		return(-1);

	if(conio_fontdata[font].eight_by_sixteen!=NULL)
		newmode=C80;
	else if(conio_fontdata[font].eight_by_fourteen!=NULL)
		newmode=C80X28;
	else if(conio_fontdata[font].eight_by_eight!=NULL)
		newmode=C80X50;

	pthread_mutex_lock(&vstatlock);
	switch(vstat.charheight) {
		case 8:
			if(conio_fontdata[font].eight_by_eight==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
		case 14:
			if(conio_fontdata[font].eight_by_fourteen==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
		case 16:
			if(conio_fontdata[font].eight_by_sixteen==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
	}
	if(changemode && (newmode==-1 || font_num > 1))
		goto error_return;
	switch(font_num) {
		case 0:
			default_font=font;
			/* Fall-through */
		case 1:
			current_font[0]=font;
			if(font==36 /* ATARI */)
				space=0;
			else
				space=' ';
			break;
		case 2:
		case 3:
		case 4:
			current_font[font_num-1]=font;
			break;
	}
	pthread_mutex_unlock(&vstatlock);

	if(changemode) {
		gettextinfo(&ti);

		attr=ti.attribute;
		ow=ti.screenwidth;
		oh=ti.screenheight;

		old=malloc(ow*oh*2);
		if(old) {
			gettext(1,1,ow,oh,old);
			textmode(newmode);
			new=malloc(ti.screenwidth*ti.screenheight*2);
			if(!new) {
				free(old);
				return -1;
			}
			pold=old;
			pnew=new;
			for(row=0; row<ti.screenheight; row++) {
				for(col=0; col<ti.screenwidth; col++) {
					if(row < oh) {
						if(col < ow) {
							*(new++)=*(old++);
							*(new++)=*(old++);
						}
						else {
							*(new++)=space;
							*(new++)=attr;
						}
					}
					else {
						*(new++)=space;
						*(new++)=attr;
					}
				}
				if(row < oh) {
					for(;col<ow;col++) {
						old++;
						old++;
					}
				}
			}
			puttext(1,1,ti.screenwidth,ti.screenheight,new);
			free(pnew);
			free(pold);
		}
	}
	bitmap_loadfont(NULL);
	return(0);

error_return:
	pthread_mutex_unlock(&vstatlock);
	return(-1);
}

int bitmap_getfont(void)
{
	return(current_font[0]);
}

void bitmap_setscaling(int new_value)
{
	pthread_mutex_lock(&vstatlock);
	if(new_value > 0)
		vstat.scaling = new_value;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_getscaling(void)
{
	int ret;

	pthread_mutex_lock(&vstatlock);
	ret = vstat.scaling;
	pthread_mutex_unlock(&vstatlock);
	return ret;
}

/* Called from event thread only */
int bitmap_loadfont(char *filename)
{
	static char current_filename[MAX_PATH];
	unsigned int fontsize;
	int fw;
	int fh;
	int i;
	FILE	*fontfile=NULL;

	if(!bitmap_initialized)
		return(-1);
	if(current_font[0]==-99 || current_font[0]>(sizeof(conio_fontdata)/sizeof(struct conio_font_data_struct)-2)) {
		for(i=0; conio_fontdata[i].desc != NULL; i++) {
			if(!strcmp(conio_fontdata[i].desc, "Codepage 437 English")) {
				current_font[0]=i;
				break;
			}
		}
		if(conio_fontdata[i].desc==NULL)
			current_font[0]=0;
	}
	if(current_font[0]==-1)
		filename=current_filename;
	else if(conio_fontdata[current_font[0]].desc==NULL)
		return(-1);

	for (i=1; i<sizeof(current_font)/sizeof(current_font[0]); i++) {
		if(current_font[i] == -1)
			;
		else if (current_font[i] < 0)
			current_font[i]=current_font[0];
		else if(conio_fontdata[current_font[i]].desc==NULL)
			current_font[i]=current_font[0];
	}

	pthread_mutex_lock(&vstatlock);
	fh=vstat.charheight;
	fw=vstat.charwidth/8+(vstat.charwidth%8?1:0);

	fontsize=fw*fh*256*sizeof(unsigned char);

	for (i=0; i<sizeof(font)/sizeof(font[0]); i++) {
		if(font[i])
			FREE_AND_NULL(font[i]);
		if((font[i]=(unsigned char *)malloc(fontsize))==NULL)
			goto error_return;
	}

	if(filename != NULL) {
		if(flength(filename)!=fontsize)
			goto error_return;
		if((fontfile=fopen(filename,"rb"))==NULL)
			goto error_return;
		if(fread(font[0], 1, fontsize, fontfile)!=fontsize)
			goto error_return;
		fclose(fontfile);
		fontfile=NULL;
		current_font[0]=-1;
		if(filename != current_filename)
			SAFECOPY(current_filename,filename);
		for (i=1; i<sizeof(font)/sizeof(font[0]); i++) {
			if (current_font[i]==-1)
				memcpy(font[i], font[0], fontsize);
		}
	}
	for (i=0; i<sizeof(font)/sizeof(font[0]); i++) {
		if (current_font[i] < 0)
			continue;
		switch(vstat.charwidth) {
			case 8:
				switch(vstat.charheight) {
					case 8:
						if(conio_fontdata[current_font[i]].eight_by_eight==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_eight, fontsize);
						break;
					case 14:
						if(conio_fontdata[current_font[i]].eight_by_fourteen==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_fourteen, fontsize);
						break;
					case 16:
						if(conio_fontdata[current_font[i]].eight_by_sixteen==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_sixteen, fontsize);
						break;
					default:
						goto error_return;
				}
				break;
			default:
				goto error_return;
		}
	}

	force_redraws++;
	pthread_mutex_unlock(&vstatlock);
    return(0);

error_return:
	for (i=0; i<sizeof(font)/sizeof(font[0]); i++)
		FREE_AND_NULL(font[i]);
	if(fontfile)
		fclose(fontfile);
	pthread_mutex_unlock(&vstatlock);
	return(-1);
}

static void bitmap_draw_cursor(struct video_stats *vs)
{
	int x;
	int y;
	char attr;
	int pixel;
	int xoffset,yoffset;
	int yo, xw, yw;

	if(!bitmap_initialized)
		return;
	if(vs->curs_visible) {
		if(vs->blink || (!vs->curs_blink)) {
			if(vs->curs_start<=vs->curs_end) {
				xoffset=(vs->curs_col-1)*vs->charwidth;
				yoffset=(vs->curs_row-1)*vs->charheight;
				if(xoffset < 0 || yoffset < 0)
					return;
				attr=cio_textinfo.attribute&0x0f;
	
				pthread_mutex_lock(&screenlock);
				for(y=vs->curs_start; y<=vs->curs_end; y++) {
					if(xoffset < screenwidth && (yoffset+y) < screenheight) {
						pixel=PIXEL_OFFSET(xoffset, yoffset+y);
						for(x=0;x<vs->charwidth;x++)
							screen[pixel++]=attr;
						//memset(screen+pixel,attr,vs->charwidth);
					}
				}
				pthread_mutex_unlock(&screenlock);
				yo = yoffset+vs->curs_start;
				xw = vs->charwidth;
				yw = vs->curs_end-vs->curs_start+1;
				send_rectangle(vs, xoffset, yo, xw, yw,FALSE);
				return;
			}
		}
	}
}

/* Called from main thread only */
void bitmap_gotoxy(int x, int y)
{
	if(!bitmap_initialized)
		return;
	/* Move cursor location */
	cio_textinfo.curx=x;
	cio_textinfo.cury=y;
	if(!hold_update) {
		/* Move visible cursor */
		pthread_mutex_lock(&vstatlock);
		vstat.curs_col=x+cio_textinfo.winleft-1;
		vstat.curs_row=y+cio_textinfo.wintop-1;
		pthread_mutex_unlock(&vstatlock);
	}
}

static int bitmap_draw_one_char(struct video_stats *vs, unsigned int xpos, unsigned int ypos)
{
	int		fg;
	int		bg;
	int		xoffset=(xpos-1)*vs->charwidth;
	int		yoffset=(ypos-1)*vs->charheight;
	int		x;
	int		y;
	int		fontoffset;
	int		altfont;
	unsigned char *this_font;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(-1);

	if(!vs->vmem) {
		return(-1);
	}

	pthread_mutex_lock(&screenlock);

	if(!screen) {
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}

	vmem_ptr = lock_vmem(vs);

	sch=vmem_ptr->vmem[(ypos-1)*cio_textinfo.screenwidth+(xpos-1)];

	unlock_vmem(vmem_ptr);

	altfont = (sch>>11 & 0x01) | ((sch>>14) & 0x02);
	if(vs->bright_background) {
		bg=(sch&0xf000)>>12;
		fg=(sch&0x0f00)>>8;
	}
	else {
		bg=(sch&0x7000)>>12;
		if(sch&0x8000 && vs->blink && (!vs->no_blink))
			fg=bg;
		else
			fg=(sch&0x0f00)>>8;
	}
	if (!vs->bright_altcharset)
		altfont &= ~0x01;
	if (!vs->blink_altcharset)
		altfont &= ~0x02;
	this_font=font[altfont];
	if (this_font == NULL)
		this_font = font[0];
	if(vs->no_bright)
		fg &= 0x07;
	fontoffset=(sch&0xff)*vs->charheight;

	for(y=0; y<vs->charheight; y++) {
		memset(&screen[PIXEL_OFFSET(xoffset, yoffset+y)],bg,vs->charwidth);
		for(x=0; x<vs->charwidth; x++) {
			if(this_font[fontoffset] & (0x80 >> x))
				screen[PIXEL_OFFSET(xoffset+x, yoffset+y)]=fg;
		}
		fontoffset++;
	}
	pthread_mutex_unlock(&screenlock);

	return(0);
}

static int update_rect(int sx, int sy, int width, int height, int force)
{
	int x,y;
	unsigned int pos;
	int	redraw_cursor=0;
	int	lastcharupdated=0;
	static unsigned short *last_vmem=NULL;
	static unsigned short *this_vmem=NULL;
	static struct video_stats vs;
	struct video_stats cvstat;
	struct vstat_vmem cvmem;
	struct rectangle this_rect;
	int this_rect_used=0;
	struct rectangle last_rect;
	int last_rect_used=0;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(-1);

	if(sx<=0)
		sx=1;
	if(sy<=0)
		sy=1;
	if(width<=0 || width>cio_textinfo.screenwidth)
		width=cio_textinfo.screenwidth;
	if(height<=0 || height>cio_textinfo.screenheight)
		height=cio_textinfo.screenheight;

	pthread_mutex_lock(&vstatlock);
	if(vs.cols!=vstat.cols || vs.rows != vstat.rows || last_vmem==NULL || this_vmem == NULL) {
		unsigned short *p;

		p=(unsigned short *)realloc(last_vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
		if(p==NULL)
			return(-1);
		last_vmem=p;
		memset(last_vmem, 255, vstat.cols*vstat.rows*sizeof(unsigned short));
		sx=1;
		sy=1;
		width=vstat.cols;
		height=vstat.rows;
		force=1;
		vs.cols=vstat.cols;
		vs.rows=vstat.rows;
		p=(unsigned short *)realloc(this_vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
		if(p==NULL)
			return(-1);
		this_vmem = p;
	}

	/* Redraw cursor */
	if(vstat.blink != vs.blink
			|| vstat.curs_col!=vs.curs_col
			|| vstat.curs_row!=vs.curs_row
			|| vstat.curs_start!=vs.curs_start
			|| vstat.curs_end!=vs.curs_end)
		redraw_cursor=1;
	cvstat = vstat;
	vmem_ptr = get_vmem(&vstat);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);
	cvstat.vmem = &cvmem;
	cvstat.vmem->refcount = 1;
	cvstat.vmem->vmem = this_vmem;
	memcpy(cvstat.vmem->vmem, vmem_ptr->vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
	pthread_mutex_unlock(&vmem_lock);

	for(y=0;y<height;y++) {
		pos=(sy+y-1)*cvstat.cols+(sx-1);
		for(x=0;x<width;x++) {
			if(force
					|| (last_vmem[pos] != cvstat.vmem->vmem[pos]) 					/* Different char */
					|| ((cvstat.blink != vs.blink) && (cvstat.vmem->vmem[pos]>>15) && (!cvstat.no_blink)) 	/* Blinking char */
					|| (redraw_cursor && ((vs.curs_col==sx+x && vs.curs_row==sy+y) || (cvstat.curs_col==sx+x && cvstat.curs_row==sy+y)))	/* Cursor */
					) {
				last_vmem[pos] = cvstat.vmem->vmem[pos];
				bitmap_draw_one_char(&cvstat, sx+x,sy+y);

				if(!redraw_cursor && sx+x==cvstat.curs_col && sy+y==cvstat.curs_row)
					redraw_cursor=1;

				if(lastcharupdated) {
					this_rect.width+=cvstat.charwidth;
					lastcharupdated++;
				}
				else {
					if(this_rect_used) {
						send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height,FALSE);
					}
					this_rect.x=(sx+x-1)*cvstat.charwidth;
					this_rect.y=(sy+y-1)*cvstat.charheight;
					this_rect.width=cvstat.charwidth;
					this_rect.height=cvstat.charheight;
					this_rect_used=1;
					lastcharupdated++;
				}
			}
			else {
				if(last_rect_used) {
					send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
					last_rect_used=0;
				}
				if(this_rect_used) {
					send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height,FALSE);
					this_rect_used=0;
				}
				lastcharupdated=0;
			}
			pos++;
		}
		/* If ALL chars in the line were used, add to last_rect */
		if(lastcharupdated==width) {
			if(last_rect_used) {
				last_rect.height += cvstat.charheight;
				this_rect_used=0;
			}
			else {
				last_rect=this_rect;
				last_rect_used=1;
				this_rect_used=0;
			}
		}
		/* Otherwise send any stale line buffers */
		else
		{
			if(last_rect_used) {
				send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
				last_rect_used=0;
			}
			if(this_rect_used) {
				send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height, FALSE);
				this_rect_used=0;
			}
		}
		lastcharupdated=0;
	}

	if(last_rect_used)
		send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
	if(this_rect_used)
		send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height, FALSE);

	vs = cvstat;

	/* Did we redraw the cursor?  If so, update cursor info */
	if(redraw_cursor)
		bitmap_draw_cursor(&cvstat);

	return(0);
}